-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ee691c0
commit 71f970e
Showing
10 changed files
with
389 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
client/src/containers/profile/account-details/form/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
"use client"; | ||
|
||
import { FC, KeyboardEvent, useCallback, useRef, useState } from "react"; | ||
|
||
import { useForm } from "react-hook-form"; | ||
|
||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import { useQueryClient } from "@tanstack/react-query"; | ||
import { SquarePenIcon, XIcon } from "lucide-react"; | ||
import { useSession } from "next-auth/react"; | ||
import { z } from "zod"; | ||
|
||
import { client } from "@/lib/query-client"; | ||
import { queryKeys } from "@/lib/query-keys"; | ||
|
||
import { | ||
Form, | ||
FormControl, | ||
FormField, | ||
FormItem, | ||
FormLabel, | ||
FormMessage, | ||
} from "@/components/ui/form"; | ||
import { Input } from "@/components/ui/input"; | ||
import { useToast } from "@/components/ui/toast/use-toast"; | ||
|
||
import { accountDetailsSchema } from "./schema"; | ||
|
||
const AccountDetailsForm: FC = () => { | ||
const queryClient = useQueryClient(); | ||
const { data: session, update: updateSession } = useSession(); | ||
const [isEditing, setEditing] = useState(false); | ||
const formRef = useRef<HTMLFormElement>(null); | ||
const { toast } = useToast(); | ||
|
||
const { data: user } = client.user.findMe.useQuery( | ||
queryKeys.users.me(session?.user?.id as string).queryKey, | ||
{ | ||
extraHeaders: { | ||
authorization: `Bearer ${session?.accessToken}`, | ||
}, | ||
query: {}, | ||
}, | ||
{ | ||
select: (res) => res.body.data, | ||
}, | ||
); | ||
|
||
const form = useForm<z.infer<typeof accountDetailsSchema>>({ | ||
resolver: zodResolver(accountDetailsSchema), | ||
defaultValues: { | ||
name: user?.name, | ||
email: user?.email, | ||
}, | ||
mode: "onSubmit", | ||
}); | ||
|
||
const onSubmit = useCallback( | ||
async (data: FormData) => { | ||
const formData = Object.fromEntries(data); | ||
const parsed = accountDetailsSchema.safeParse(formData); | ||
|
||
if (parsed.success) { | ||
const response = await client.user.updateUser.mutation({ | ||
params: { | ||
id: session?.user?.id as string, | ||
}, | ||
body: { | ||
email: parsed.data.email, | ||
}, | ||
extraHeaders: { | ||
authorization: `Bearer ${session?.accessToken}`, | ||
}, | ||
}); | ||
|
||
if (response.status === 200) { | ||
await updateSession(response.body); | ||
|
||
await queryClient.invalidateQueries({ | ||
queryKey: queryKeys.users.me(session?.user?.id as string).queryKey, | ||
}); | ||
|
||
toast({ | ||
description: "Your email has been updated successfully.", | ||
}); | ||
} | ||
} | ||
}, | ||
[queryClient, session, updateSession, toast], | ||
); | ||
|
||
const handleEnterKey = useCallback( | ||
(evt: KeyboardEvent) => { | ||
if (evt.code === "Enter" && isEditing && form.formState.isValid) { | ||
form.handleSubmit(async () => { | ||
await onSubmit(new FormData(formRef.current!)); | ||
})(); | ||
} | ||
}, | ||
[isEditing, form, onSubmit], | ||
); | ||
|
||
return ( | ||
<Form {...form}> | ||
<form | ||
ref={formRef} | ||
className="w-full space-y-4" | ||
onSubmit={(evt) => { | ||
form.handleSubmit(async () => { | ||
await onSubmit(new FormData(formRef.current!)); | ||
})(evt); | ||
}} | ||
> | ||
<FormField | ||
control={form.control} | ||
name="email" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Email</FormLabel> | ||
<FormControl> | ||
<div className="relative flex items-center"> | ||
<Input | ||
type="email" | ||
autoComplete={field.name} | ||
readOnly={!isEditing} | ||
onKeyDown={handleEnterKey} | ||
{...field} | ||
onBlur={() => { | ||
field.onBlur(); | ||
setEditing(false); | ||
}} | ||
/> | ||
<button | ||
type="button" | ||
onClick={() => { | ||
setEditing((prev) => !prev); | ||
form.setFocus("email"); | ||
}} | ||
className="absolute right-20 text-muted-foreground" | ||
> | ||
{isEditing ? ( | ||
<XIcon className="h-4 w-4" /> | ||
) : ( | ||
<SquarePenIcon className="h-4 w-4" /> | ||
)} | ||
</button> | ||
</div> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
</form> | ||
</Form> | ||
); | ||
}; | ||
|
||
export default AccountDetailsForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { z } from "zod"; | ||
|
||
export const accountDetailsSchema = z.object({ | ||
email: z | ||
.string({ message: "Email is required" }) | ||
.min(1, "Email is required") | ||
.email("Invalid email"), | ||
name: z.string({ message: "Name is required" }).min(1, "Name is required"), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { FC } from "react"; | ||
|
||
import AccountDetailsForm from "./form"; | ||
|
||
const AccountDetails: FC = () => { | ||
return <AccountDetailsForm />; | ||
}; | ||
|
||
export default AccountDetails; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
155 changes: 155 additions & 0 deletions
155
client/src/containers/profile/update-password/form/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
"use client"; | ||
|
||
import { FC, useCallback, useRef } from "react"; | ||
|
||
import { useForm } from "react-hook-form"; | ||
|
||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import { useSession } from "next-auth/react"; | ||
import { z } from "zod"; | ||
|
||
import { client } from "@/lib/query-client"; | ||
|
||
import { Button } from "@/components/ui/button"; | ||
import { | ||
Form, | ||
FormControl, | ||
FormField, | ||
FormItem, | ||
FormLabel, | ||
FormMessage, | ||
FormDescription, | ||
} from "@/components/ui/form"; | ||
import { Input } from "@/components/ui/input"; | ||
import { useToast } from "@/components/ui/toast/use-toast"; | ||
|
||
import { changePasswordSchema } from "./schema"; | ||
|
||
const UpdatePassword: FC = () => { | ||
const formRef = useRef<HTMLFormElement>(null); | ||
const { data: session } = useSession(); | ||
const { toast } = useToast(); | ||
|
||
const form = useForm<z.infer<typeof changePasswordSchema>>({ | ||
resolver: zodResolver(changePasswordSchema), | ||
defaultValues: { | ||
password: "", | ||
newPassword: "", | ||
}, | ||
mode: "onSubmit", | ||
}); | ||
|
||
const onSubmit = useCallback( | ||
async (data: FormData) => { | ||
const formData = Object.fromEntries(data); | ||
const parsed = changePasswordSchema.safeParse(formData); | ||
if (parsed.success) { | ||
try { | ||
const response = await client.user.updatePassword.mutation({ | ||
body: { | ||
currentPassword: parsed.data.password, | ||
newPassword: parsed.data.newPassword, | ||
}, | ||
extraHeaders: { | ||
authorization: `Bearer ${session?.accessToken}`, | ||
}, | ||
}); | ||
|
||
if (response.status === 200) { | ||
toast({ | ||
description: "Your password has been updated successfully.", | ||
}); | ||
} | ||
|
||
if (response.status === 400 || response.status === 401) { | ||
toast({ | ||
variant: "destructive", | ||
description: response.body.errors?.[0].title, | ||
}); | ||
} | ||
} catch (e) { | ||
toast({ | ||
variant: "destructive", | ||
description: "Something went wrong updating the password", | ||
}); | ||
} | ||
} | ||
}, | ||
[session, toast], | ||
); | ||
|
||
return ( | ||
<Form {...form}> | ||
<form | ||
ref={formRef} | ||
className="w-full space-y-4" | ||
onSubmit={(evt) => { | ||
evt.preventDefault(); | ||
form.handleSubmit(async () => { | ||
await onSubmit(new FormData(formRef.current!)); | ||
})(evt); | ||
}} | ||
> | ||
<FormField | ||
control={form.control} | ||
name="password" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Password</FormLabel> | ||
<FormControl> | ||
<div className="relative flex items-center"> | ||
<Input | ||
placeholder="Type your current password" | ||
type="password" | ||
autoComplete="current-password" | ||
{...field} | ||
/> | ||
</div> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
|
||
<FormField | ||
control={form.control} | ||
name="newPassword" | ||
render={({ field, fieldState }) => ( | ||
<FormItem> | ||
<FormLabel>New password</FormLabel> | ||
<FormControl> | ||
<div className="relative flex items-center"> | ||
<Input | ||
placeholder="Create new password" | ||
type="password" | ||
autoComplete="new-password" | ||
{...field} | ||
/> | ||
</div> | ||
</FormControl> | ||
{!fieldState.invalid && ( | ||
<FormDescription> | ||
Password must contain at least 8 characters. | ||
</FormDescription> | ||
)} | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
|
||
<div className="!mt-10 px-8"> | ||
<Button | ||
variant="secondary" | ||
type="submit" | ||
className="w-full" | ||
disabled={!form.formState.isValid} | ||
> | ||
Apply | ||
</Button> | ||
</div> | ||
</form> | ||
</Form> | ||
); | ||
}; | ||
|
||
export default UpdatePassword; |
Oops, something went wrong.