|
| 1 | +import { languageLevels } from "@/constants/student"; |
| 2 | +import { useOnError } from "@/hooks/error"; |
| 3 | +import { useUser } from "@litespace/headless/context/user"; |
| 4 | +import { useForm } from "@litespace/headless/form"; |
| 5 | +import { useUpdateStudent, useStudentMeta } from "@litespace/headless/student"; |
| 6 | +import { useInfiniteTopics } from "@litespace/headless/topic"; |
| 7 | +import { IStudent, ITopic } from "@litespace/types"; |
| 8 | +import { Button } from "@litespace/ui/Button"; |
| 9 | +import { Form } from "@litespace/ui/Form"; |
| 10 | +import { useFormatMessage } from "@litespace/ui/hooks/intl"; |
| 11 | +import { useMakeValidators } from "@litespace/ui/hooks/validation"; |
| 12 | +import { Input } from "@litespace/ui/Input"; |
| 13 | +import { validateEnglishLevel } from "@litespace/ui/lib/validate"; |
| 14 | +import { LocalId } from "@litespace/ui/locales"; |
| 15 | +import { MultiSelect } from "@litespace/ui/MultiSelect"; |
| 16 | +import { Select } from "@litespace/ui/Select"; |
| 17 | +import { Textarea } from "@litespace/ui/Textarea"; |
| 18 | +import { useToast } from "@litespace/ui/Toast"; |
| 19 | +import { Typography } from "@litespace/ui/Typography"; |
| 20 | +import { random } from "lodash"; |
| 21 | +import React, { useCallback, useMemo } from "react"; |
| 22 | + |
| 23 | +type IForm = { |
| 24 | + topics: ITopic.Self["name"]["ar"][]; |
| 25 | + career: string; |
| 26 | + level: IStudent.EnglishLevel; |
| 27 | + aim: string; |
| 28 | +}; |
| 29 | + |
| 30 | +export const StudentPublicInfo: React.FC = () => { |
| 31 | + const intl = useFormatMessage(); |
| 32 | + const toast = useToast(); |
| 33 | + const { user } = useUser(); |
| 34 | + |
| 35 | + const meta = useStudentMeta(); |
| 36 | + |
| 37 | + const { list: allTopics } = useInfiniteTopics(); |
| 38 | + |
| 39 | + const onSuccess = useCallback(() => { |
| 40 | + toast.success({ |
| 41 | + title: intl("student-settings.updated-successfully"), |
| 42 | + }); |
| 43 | + }, [intl, toast]); |
| 44 | + |
| 45 | + const onError = useOnError({ |
| 46 | + type: "mutation", |
| 47 | + handler: ({ messageId }) => { |
| 48 | + toast.error({ |
| 49 | + title: intl("complete-profile.update.error"), |
| 50 | + description: intl(messageId), |
| 51 | + }); |
| 52 | + }, |
| 53 | + }); |
| 54 | + |
| 55 | + const updateStudent = useUpdateStudent({ onSuccess, onError }); |
| 56 | + |
| 57 | + const validators = useMakeValidators<IForm>({ |
| 58 | + career: { required: false }, |
| 59 | + level: { required: false, validate: validateEnglishLevel }, |
| 60 | + aim: { required: false }, |
| 61 | + }); |
| 62 | + |
| 63 | + const levels = useMemo( |
| 64 | + () => |
| 65 | + Object.entries(languageLevels).map(([key, value]) => ({ |
| 66 | + label: intl(value), |
| 67 | + value: Number(key), |
| 68 | + })), |
| 69 | + [intl] |
| 70 | + ); |
| 71 | + |
| 72 | + const form = useForm<IForm>({ |
| 73 | + defaults: { |
| 74 | + topics: allTopics |
| 75 | + ? allTopics |
| 76 | + ?.slice(0, random(1, allTopics.length / 2)) |
| 77 | + .map((topic) => topic.name.ar) |
| 78 | + : [], |
| 79 | + career: meta.data?.career ? intl(meta.data?.career as LocalId) : "", |
| 80 | + level: meta.data?.level || IStudent.EnglishLevel.Beginner, |
| 81 | + aim: meta.data?.aim ? intl(meta.data?.aim as LocalId) : "", |
| 82 | + }, |
| 83 | + validators, |
| 84 | + onSubmit(data) { |
| 85 | + if (!user) return; |
| 86 | + updateStudent.mutate({ |
| 87 | + id: user?.id, |
| 88 | + payload: { |
| 89 | + topics: data.topics, |
| 90 | + career: data.career, |
| 91 | + level: data.level, |
| 92 | + aim: data.aim, |
| 93 | + }, |
| 94 | + }); |
| 95 | + }, |
| 96 | + }); |
| 97 | + |
| 98 | + return ( |
| 99 | + <div className="w-full"> |
| 100 | + <Form onSubmit={form.onSubmit} className="flex flex-col gap-6"> |
| 101 | + <div className="flex flex-col gap-4"> |
| 102 | + <Typography tag="h5" className="text-subtitle-2 font-bold"> |
| 103 | + {intl("student-settings.public-info.title")} |
| 104 | + </Typography> |
| 105 | + <MultiSelect |
| 106 | + label={intl("complete-profile.topics.label")} |
| 107 | + options={ |
| 108 | + allTopics?.map((topic) => ({ |
| 109 | + label: topic.name.ar, |
| 110 | + value: topic.name.ar, |
| 111 | + })) || [] |
| 112 | + } |
| 113 | + placeholder={intl("complete-profile.topics.placeholder")} |
| 114 | + values={form.state.topics} |
| 115 | + setValues={(values) => form.set("topics", values)} |
| 116 | + /> |
| 117 | + |
| 118 | + <Input |
| 119 | + id="career" |
| 120 | + name="career" |
| 121 | + idleDir="rtl" |
| 122 | + value={form.state.career} |
| 123 | + inputSize={"large"} |
| 124 | + autoComplete="off" |
| 125 | + onChange={(e) => form.set("career", e.target.value)} |
| 126 | + label={intl("labels.job")} |
| 127 | + placeholder={intl("complete-profile.job.placeholder")} |
| 128 | + state={form.errors.career ? "error" : undefined} |
| 129 | + helper={form.errors.career} |
| 130 | + /> |
| 131 | + |
| 132 | + <Select |
| 133 | + id="level" |
| 134 | + value={form.state.level} |
| 135 | + onChange={(value) => form.set("level", value)} |
| 136 | + options={levels} |
| 137 | + label={intl("complete-profile.level.label")} |
| 138 | + placeholder={intl("complete-profile.level.label")} |
| 139 | + helper={form.errors.level} |
| 140 | + /> |
| 141 | + |
| 142 | + <Textarea |
| 143 | + className="min-h-[138px]" |
| 144 | + id="aim" |
| 145 | + name="aim" |
| 146 | + idleDir="rtl" |
| 147 | + value={form.state.aim} |
| 148 | + label={intl("complete-profile.aim.label")} |
| 149 | + placeholder={intl("complete-profile.aim.placeholder")} |
| 150 | + state={form.errors.aim ? "error" : undefined} |
| 151 | + helper={form.errors.aim} |
| 152 | + onChange={({ target }) => form.set("aim", target.value)} |
| 153 | + disabled={updateStudent.isPending} |
| 154 | + autoComplete="off" |
| 155 | + /> |
| 156 | + </div> |
| 157 | + |
| 158 | + <Button |
| 159 | + size="large" |
| 160 | + htmlType="submit" |
| 161 | + loading={updateStudent.isPending} |
| 162 | + disabled={updateStudent.isPending} |
| 163 | + > |
| 164 | + {intl("shared-settings.save")} |
| 165 | + </Button> |
| 166 | + </Form> |
| 167 | + </div> |
| 168 | + ); |
| 169 | +}; |
| 170 | + |
| 171 | +export default StudentPublicInfo; |
0 commit comments