Skip to content

Commit 91525a0

Browse files
committed
feat: Enhance SettingsPage with user forms and update input handling
1 parent 25406ce commit 91525a0

File tree

6 files changed

+248
-13
lines changed

6 files changed

+248
-13
lines changed
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
"use client";
2+
3+
import * as userActions from "@/backend/services/user.action";
4+
import { User } from "@/backend/models/domain-models";
5+
import { UserActionInput } from "@/backend/services/inputs/user.input";
6+
import { Button } from "@/components/ui/button";
7+
import {
8+
Form,
9+
FormControl,
10+
FormDescription,
11+
FormField,
12+
FormItem,
13+
FormLabel,
14+
FormMessage,
15+
} from "@/components/ui/form";
16+
import { Input } from "@/components/ui/input";
17+
import { Textarea } from "@/components/ui/textarea";
18+
import { useTranslation } from "@/i18n/use-translation";
19+
import { zodResolver } from "@hookform/resolvers/zod";
20+
import { useMutation } from "@tanstack/react-query";
21+
import React from "react";
22+
import { SubmitHandler, useForm } from "react-hook-form";
23+
import z from "zod";
24+
import { Loader2 } from "lucide-react";
25+
26+
// fields
27+
// name -> ✅
28+
// username -> ✅
29+
// email -> ✅
30+
// profile_photo
31+
// education -> ✅
32+
// designation -> ✅
33+
// bio -> ✅
34+
// websiteUrl -> ✅
35+
// location -> ✅
36+
// social_links
37+
// profile_readme
38+
39+
interface Props {
40+
user: User;
41+
}
42+
43+
const GeneralForm: React.FC<Props> = ({ user }) => {
44+
const { _t } = useTranslation();
45+
const mutation = useMutation({
46+
mutationFn: (
47+
payload: z.infer<typeof UserActionInput.updateMyProfileInput>
48+
) => userActions.updateMyProfile(payload),
49+
});
50+
const form = useForm({
51+
defaultValues: {
52+
name: user?.name,
53+
username: user?.username,
54+
bio: user?.bio,
55+
email: user?.email,
56+
websiteUrl: user.website_url,
57+
education: user.education,
58+
designation: user.designation,
59+
location: user.location,
60+
},
61+
resolver: zodResolver(UserActionInput.updateMyProfileInput),
62+
});
63+
64+
const onSubmit: SubmitHandler<
65+
z.infer<typeof UserActionInput.updateMyProfileInput>
66+
> = (payload) => {
67+
mutation.mutate(payload);
68+
};
69+
70+
return (
71+
<Form {...form}>
72+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
73+
<FormField
74+
control={form.control}
75+
name="name"
76+
render={({ field }) => (
77+
<FormItem>
78+
<FormLabel>{_t("Name")}</FormLabel>
79+
<FormControl>
80+
<Input className="py-6" {...field} />
81+
</FormControl>
82+
<FormDescription />
83+
<FormMessage />
84+
</FormItem>
85+
)}
86+
/>
87+
88+
<FormField
89+
control={form.control}
90+
name="username"
91+
render={({ field }) => (
92+
<FormItem>
93+
<FormLabel>{_t("Username")}</FormLabel>
94+
<FormControl>
95+
<Input
96+
disabled
97+
className="py-6"
98+
placeholder="shadcn"
99+
{...field}
100+
/>
101+
</FormControl>
102+
<FormDescription />
103+
<FormMessage />
104+
</FormItem>
105+
)}
106+
/>
107+
108+
<FormField
109+
control={form.control}
110+
name="websiteUrl"
111+
render={({ field }) => (
112+
<FormItem>
113+
<FormLabel>{_t("Website url")}</FormLabel>
114+
<FormControl>
115+
<Input className="py-6" {...field} />
116+
</FormControl>
117+
<FormDescription />
118+
<FormMessage />
119+
</FormItem>
120+
)}
121+
/>
122+
123+
<FormField
124+
control={form.control}
125+
name="email"
126+
render={({ field }) => (
127+
<FormItem>
128+
<FormLabel>{_t("Email")}</FormLabel>
129+
<FormControl>
130+
<Input disabled className="py-6" {...field} />
131+
</FormControl>
132+
<FormDescription />
133+
<FormMessage />
134+
</FormItem>
135+
)}
136+
/>
137+
138+
<FormField
139+
control={form.control}
140+
name="bio"
141+
render={({ field }) => (
142+
<FormItem>
143+
<FormLabel>{_t("Bio")}</FormLabel>
144+
<FormControl>
145+
<Textarea className="py-6" {...field} />
146+
</FormControl>
147+
<FormDescription />
148+
<FormMessage />
149+
</FormItem>
150+
)}
151+
/>
152+
153+
<FormField
154+
control={form.control}
155+
name="education"
156+
render={({ field }) => (
157+
<FormItem>
158+
<FormLabel>{_t("Education")}</FormLabel>
159+
<FormControl>
160+
<Input className="py-6" {...field} />
161+
</FormControl>
162+
<FormDescription />
163+
<FormMessage />
164+
</FormItem>
165+
)}
166+
/>
167+
168+
<FormField
169+
control={form.control}
170+
name="designation"
171+
render={({ field }) => (
172+
<FormItem>
173+
<FormLabel>{_t("Designation")}</FormLabel>
174+
<FormControl>
175+
<Input className="py-6" {...field} />
176+
</FormControl>
177+
<FormDescription />
178+
<FormMessage />
179+
</FormItem>
180+
)}
181+
/>
182+
183+
<FormField
184+
control={form.control}
185+
name="location"
186+
render={({ field }) => (
187+
<FormItem>
188+
<FormLabel>{_t("Location")}</FormLabel>
189+
<FormControl>
190+
<Input className="py-6" {...field} />
191+
</FormControl>
192+
<FormDescription />
193+
<FormMessage />
194+
</FormItem>
195+
)}
196+
/>
197+
198+
<Button type="submit" disabled={mutation.isPending}>
199+
{mutation.isPending && <Loader2 className="animate-spin" />}
200+
{_t("Save")}
201+
</Button>
202+
</form>
203+
</Form>
204+
);
205+
};
206+
207+
export default GeneralForm;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from "react";
2+
3+
const ReadmeForm = () => {
4+
return <div>ReadmeForm</div>;
5+
};
6+
7+
export default ReadmeForm;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from "react";
2+
3+
const SocialMediaForm = () => {
4+
return <div>SocialMediaForm</div>;
5+
};
6+
7+
export default SocialMediaForm;

src/app/dashboard/settings/page.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
1+
import * as userActions from "@/backend/services/user.action";
12
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
23
import _t from "@/i18n/_t";
4+
import GeneralForm from "./_components/GeneralForm";
5+
import SocialMediaForm from "./_components/SocialMediaForm";
6+
import ReadmeForm from "./_components/ReadmeForm";
7+
import { authID } from "@/backend/services/session.actions";
8+
9+
const SettingsPage = async () => {
10+
const auth_id = await authID();
11+
const current_user = await userActions.getUserById(auth_id!);
312

4-
const SettingsPage = () => {
513
return (
614
<div>
7-
<Tabs defaultValue="account">
15+
<Tabs defaultValue="general">
816
<TabsList>
917
<TabsTrigger value="general">{_t("General")}</TabsTrigger>
1018
<TabsTrigger value="social">{_t("Social")}</TabsTrigger>
1119
<TabsTrigger value="profile_readme">
1220
{_t("Profile Readme")}
1321
</TabsTrigger>
1422
</TabsList>
15-
<TabsContent value="general">Change your password here.</TabsContent>
16-
<TabsContent value="social">Change your password here.</TabsContent>
23+
<TabsContent value="general">
24+
{current_user && (
25+
<div className="max-w-2xl my-10">
26+
<GeneralForm user={current_user} />
27+
</div>
28+
)}
29+
</TabsContent>
30+
<TabsContent value="social">
31+
<SocialMediaForm />
32+
</TabsContent>
1733
<TabsContent value="profile_readme">
18-
Change your profile readme here.
34+
<ReadmeForm />
1935
</TabsContent>
2036
</Tabs>
2137
</div>

src/backend/services/inputs/user.input.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { z } from "zod";
22

3-
export const UserRepositoryInput = {
3+
export const UserActionInput = {
44
syncSocialUserInput: z.object({
55
service: z.enum(["github"]),
66
service_uid: z.string(),

src/backend/services/user.action.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { z } from "zod";
55
import { User } from "../models/domain-models";
66
import { persistenceRepository } from "../persistence/persistence-repositories";
77
import { ActionException, handleActionException } from "./RepositoryException";
8-
import { UserRepositoryInput } from "./inputs/user.input";
8+
import { UserActionInput } from "./inputs/user.input";
99
import { drizzleClient } from "@/backend/persistence/clients";
1010
import { usersTable } from "@/backend/persistence/schemas";
1111
import { authID } from "./session.actions";
@@ -19,11 +19,10 @@ import { authID } from "./session.actions";
1919
* @throws {RepositoryException} If user creation/sync fails or validation fails
2020
*/
2121
export async function bootSocialUser(
22-
_input: z.infer<typeof UserRepositoryInput.syncSocialUserInput>
22+
_input: z.infer<typeof UserActionInput.syncSocialUserInput>
2323
) {
2424
try {
25-
const input =
26-
await UserRepositoryInput.syncSocialUserInput.parseAsync(_input);
25+
const input = await UserActionInput.syncSocialUserInput.parseAsync(_input);
2726
let [user] = await persistenceRepository.user.find({
2827
where: eq("email", input.email),
2928
columns: ["id", "name", "username", "email"],
@@ -82,16 +81,15 @@ export async function bootSocialUser(
8281
* @throws {RepositoryException} If update fails or validation fails
8382
*/
8483
export async function updateMyProfile(
85-
_input: z.infer<typeof UserRepositoryInput.updateMyProfileInput>
84+
_input: z.infer<typeof UserActionInput.updateMyProfileInput>
8685
) {
8786
try {
8887
const sessionUser = await authID();
8988
if (!sessionUser) {
9089
throw new ActionException(`User not authenticated`);
9190
}
9291

93-
const input =
94-
await UserRepositoryInput.updateMyProfileInput.parseAsync(_input);
92+
const input = await UserActionInput.updateMyProfileInput.parseAsync(_input);
9593

9694
const updatedUser = await persistenceRepository.user.update({
9795
where: eq("id", sessionUser!),

0 commit comments

Comments
 (0)