Skip to content

Commit 7325939

Browse files
authored
fix validation & implement profile page (#5)
* fix validation * implement profile page * catch error
1 parent b0476bc commit 7325939

File tree

13 files changed

+258
-43
lines changed

13 files changed

+258
-43
lines changed

src/api/user.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { ModelProfile, ModelUserForm } from "@/types"
1+
import { ModelProfile, ModelUserForm, ModelProfileForm } from "@/types"
22
import { fetcher, FetcherMethod } from "./api"
33

44
export const getProfile = async (): Promise<ModelProfile> => {
55
return fetcher<ModelProfile>(FetcherMethod.GET, '/api/v1/profile', null);
66
}
77

8-
export const login = async (username: string, password: string): Promise<any> => {
9-
return fetcher<any>(FetcherMethod.POST, '/api/v1/login', { username, password });
8+
export const login = async (username: string, password: string): Promise<void> => {
9+
return fetcher<void>(FetcherMethod.POST, '/api/v1/login', { username, password });
1010
}
1111

1212
export const createUser = async (data: ModelUserForm): Promise<number> => {
@@ -16,3 +16,7 @@ export const createUser = async (data: ModelUserForm): Promise<number> => {
1616
export const deleteUser = async (id: number[]): Promise<void> => {
1717
return fetcher<void>(FetcherMethod.POST, '/api/v1/batch-delete/user', id);
1818
}
19+
20+
export const updateProfile = async (data: ModelProfileForm): Promise<void> => {
21+
return fetcher<void>(FetcherMethod.POST, '/api/v1/profile', data);
22+
}

src/components/alert-rule.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,8 @@ const alertRuleFormSchema = z.object({
7575
message: 'Invalid JSON string',
7676
}),
7777
rules: z.array(ruleSchema),
78-
fail_trigger_tasks: z.array(z.string()).transform((v => {
79-
return v.filter(Boolean).map(Number);
80-
})),
81-
recover_trigger_tasks: z.array(z.string()).transform((v => {
82-
return v.filter(Boolean).map(Number);
83-
})),
78+
fail_trigger_tasks: z.array(z.number()),
79+
recover_trigger_tasks: z.array(z.number()),
8480
notification_group_id: z.coerce.number().int(),
8581
trigger_mode: z.coerce.number().int().min(0),
8682
enable: asOptionalField(z.boolean()),
@@ -225,7 +221,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
225221
{...field}
226222
value={conv.arrToStr(field.value ?? [])}
227223
onChange={e => {
228-
const arr = conv.strToArr(e.target.value);
224+
const arr = conv.strToArr(e.target.value).map(Number);
229225
field.onChange(arr);
230226
}}
231227
/>
@@ -246,7 +242,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
246242
{...field}
247243
value={conv.arrToStr(field.value ?? [])}
248244
onChange={e => {
249-
const arr = conv.strToArr(e.target.value);
245+
const arr = conv.strToArr(e.target.value).map(Number);
250246
field.onChange(arr);
251247
}}
252248
/>

src/components/cron.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ const cronFormSchema = z.object({
5252
name: z.string().min(1),
5353
scheduler: z.string(),
5454
command: asOptionalField(z.string()),
55-
servers: z.array(z.string()).transform((v => {
56-
return v.filter(Boolean).map(Number);
57-
})),
55+
servers: z.array(z.number()),
5856
cover: z.coerce.number().int(),
5957
push_successful: asOptionalField(z.boolean()),
6058
notification_group_id: z.coerce.number().int(),
@@ -217,7 +215,10 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
217215
<FormControl>
218216
<MultiSelect
219217
options={serverList}
220-
onValueChange={field.onChange}
218+
onValueChange={e => {
219+
const arr = e.map(Number);
220+
field.onChange(arr);
221+
}}
221222
defaultValue={field.value?.map(String)}
222223
/>
223224
</FormControl>

src/components/header.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { useMainStore } from "@/hooks/useMainStore";
1111
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
1212
import { NzNavigationMenuLink } from "./xui/navigation-menu";
1313
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger } from "./ui/dropdown-menu";
14-
import { LogOut, Settings } from "lucide-react";
14+
import { LogOut, Settings, User2 } from "lucide-react";
1515
import { useAuth } from "@/hooks/useAuth";
1616
import { Link, useLocation } from "react-router-dom";
1717
import { useMediaQuery } from "@/hooks/useMediaQuery";
@@ -117,11 +117,18 @@ export default function Header() {
117117
<DropdownMenuLabel>{profile.username}</DropdownMenuLabel>
118118
<DropdownMenuSeparator />
119119
<DropdownMenuGroup>
120+
<DropdownMenuItem onClick={() => { setDropdownOpen(false) }}>
121+
<Link to="/dashboard/profile" className="flex items-center gap-2 w-full">
122+
<User2 />
123+
Profile
124+
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
125+
</Link>
126+
</DropdownMenuItem>
120127
<DropdownMenuItem onClick={() => { setDropdownOpen(false) }}>
121128
<Link to="/dashboard/settings" className="flex items-center gap-2 w-full">
122129
<Settings />
123130
Settings
124-
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
131+
<DropdownMenuShortcut>⇧⌘S</DropdownMenuShortcut>
125132
</Link>
126133
</DropdownMenuItem>
127134
</DropdownMenuGroup>
@@ -191,11 +198,18 @@ export default function Header() {
191198
<DropdownMenuLabel>{profile.username}</DropdownMenuLabel>
192199
<DropdownMenuSeparator />
193200
<DropdownMenuGroup>
201+
<DropdownMenuItem onClick={() => { setDropdownOpen(false) }}>
202+
<Link to="/dashboard/profile" className="flex items-center gap-2 w-full">
203+
<User2 />
204+
Profile
205+
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
206+
</Link>
207+
</DropdownMenuItem>
194208
<DropdownMenuItem onClick={() => { setDropdownOpen(false) }}>
195209
<Link to="/dashboard/settings" className="flex items-center gap-2 w-full">
196210
<Settings />
197211
Settings
198-
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
212+
<DropdownMenuShortcut>⇧⌘S</DropdownMenuShortcut>
199213
</Link>
200214
</DropdownMenuItem>
201215
</DropdownMenuGroup>

src/components/notification-group.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ interface NotificationGroupCardProps {
3737

3838
const notificationGroupFormSchema = z.object({
3939
name: z.string().min(1),
40-
notifications: z.array(z.string()).transform((v => {
41-
return v.filter(Boolean).map(Number);
42-
})),
40+
notifications: z.array(z.number()),
4341
});
4442

4543
export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ data, mutate }) => {
@@ -115,7 +113,10 @@ export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ da
115113
<FormLabel>Notifiers</FormLabel>
116114
<MultiSelect
117115
options={notifierList}
118-
onValueChange={field.onChange}
116+
onValueChange={e => {
117+
const arr = e.map(Number);
118+
field.onChange(arr);
119+
}}
119120
defaultValue={field.value?.map(String)}
120121
/>
121122
<FormMessage />

src/components/profile.tsx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { Button } from "@/components/ui/button"
2+
import {
3+
Dialog,
4+
DialogClose,
5+
DialogContent,
6+
DialogDescription,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogTitle,
10+
DialogTrigger,
11+
} from "@/components/ui/dialog"
12+
import { Input } from "@/components/ui/input"
13+
import {
14+
Form,
15+
FormControl,
16+
FormField,
17+
FormItem,
18+
FormLabel,
19+
FormMessage,
20+
} from "@/components/ui/form"
21+
import { ScrollArea } from "@/components/ui/scroll-area"
22+
import { useForm } from "react-hook-form"
23+
import { z } from "zod"
24+
import { zodResolver } from "@hookform/resolvers/zod"
25+
import { getProfile, updateProfile } from "@/api/user"
26+
import { useState } from "react"
27+
import { useMainStore } from "@/hooks/useMainStore"
28+
import { toast } from "sonner"
29+
30+
const profileFormSchema = z.object({
31+
original_password: z.string().min(5).max(72),
32+
new_password: z.string().min(8).max(72),
33+
});
34+
35+
export const ProfileCard = ({ className }: { className: string }) => {
36+
const form = useForm<z.infer<typeof profileFormSchema>>({
37+
resolver: zodResolver(profileFormSchema),
38+
defaultValues: {
39+
original_password: '',
40+
new_password: '',
41+
},
42+
resetOptions: {
43+
keepDefaultValues: false,
44+
}
45+
})
46+
47+
const { setProfile } = useMainStore();
48+
const [open, setOpen] = useState(false);
49+
50+
const onSubmit = async (values: z.infer<typeof profileFormSchema>) => {
51+
try {
52+
await updateProfile(values);
53+
} catch (e) {
54+
toast("Update failed", {
55+
description: `${e}`,
56+
})
57+
return;
58+
}
59+
const profile = await getProfile();
60+
setProfile(profile);
61+
setOpen(false);
62+
form.reset();
63+
}
64+
65+
return (
66+
<Dialog open={open} onOpenChange={setOpen}>
67+
<DialogTrigger asChild>
68+
<Button variant="outline" className={className}>
69+
Update Password
70+
</Button>
71+
</DialogTrigger>
72+
<DialogContent className="sm:max-w-xl">
73+
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
74+
<div className="items-center mx-1">
75+
<DialogHeader>
76+
<DialogTitle>Update Server</DialogTitle>
77+
<DialogDescription />
78+
</DialogHeader>
79+
<Form {...form}>
80+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2 my-2">
81+
<FormField
82+
control={form.control}
83+
name="original_password"
84+
render={({ field }) => (
85+
<FormItem>
86+
<FormLabel>Original Password</FormLabel>
87+
<FormControl>
88+
<Input
89+
{...field}
90+
/>
91+
</FormControl>
92+
<FormMessage />
93+
</FormItem>
94+
)}
95+
/>
96+
<FormField
97+
control={form.control}
98+
name="new_password"
99+
render={({ field }) => (
100+
<FormItem>
101+
<FormLabel>New Password</FormLabel>
102+
<FormControl>
103+
<Input
104+
{...field}
105+
/>
106+
</FormControl>
107+
<FormMessage />
108+
</FormItem>
109+
)}
110+
/>
111+
112+
<DialogFooter className="justify-end">
113+
<DialogClose asChild>
114+
<Button type="button" className="my-2" variant="secondary">
115+
Close
116+
</Button>
117+
</DialogClose>
118+
<Button type="submit" className="my-2">Submit</Button>
119+
</DialogFooter>
120+
</form>
121+
</Form>
122+
</div>
123+
</ScrollArea>
124+
</DialogContent>
125+
</Dialog>
126+
)
127+
}

src/components/server-group.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ interface ServerGroupCardProps {
3737

3838
const serverGroupFormSchema = z.object({
3939
name: z.string().min(1),
40-
servers: z.array(z.string()).transform((v => {
41-
return v.filter(Boolean).map(Number);
42-
})),
40+
servers: z.array(z.number()),
4341
});
4442

4543
export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }) => {
@@ -116,7 +114,10 @@ export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }
116114
<FormControl>
117115
<MultiSelect
118116
options={serverList}
119-
onValueChange={field.onChange}
117+
onValueChange={e => {
118+
const arr = e.map(Number);
119+
field.onChange(arr);
120+
}}
120121
defaultValue={field.value?.map(String)}
121122
/>
122123
</FormControl>

src/components/server.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ const serverFormSchema = z.object({
4545
display_index: z.number().int(),
4646
hide_for_guest: asOptionalField(z.boolean()),
4747
enable_ddns: asOptionalField(z.boolean()),
48-
ddns_profiles: z.array(z.string()).transform((v => {
49-
return v.filter(Boolean).map(Number);
50-
})),
48+
ddns_profiles: asOptionalField(z.array(z.number())),
5149
});
5250

5351
export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
@@ -77,7 +75,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
7775
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
7876
<div className="items-center mx-1">
7977
<DialogHeader>
80-
<DialogTitle>New Service</DialogTitle>
78+
<DialogTitle>Update Server</DialogTitle>
8179
<DialogDescription />
8280
</DialogHeader>
8381
<Form {...form}>
@@ -125,9 +123,10 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
125123
<Input
126124
placeholder="1,2,3"
127125
{...field}
128-
value={conv.arrToStr(field.value ?? [])}
126+
value={conv.arrToStr(field.value || [])}
129127
onChange={e => {
130-
const arr = conv.strToArr(e.target.value);
128+
console.log(field.value)
129+
const arr = conv.strToArr(e.target.value).map(Number);
131130
field.onChange(arr);
132131
}}
133132
/>

src/components/service.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,17 @@ const serviceFormSchema = z.object({
5454
duration: z.coerce.number().int().min(30),
5555
enable_show_in_service: asOptionalField(z.boolean()),
5656
enable_trigger_task: asOptionalField(z.boolean()),
57-
fail_trigger_tasks: z.array(z.string()).transform((v => {
58-
return v.filter(Boolean).map(Number);
59-
})),
57+
fail_trigger_tasks: z.array(z.number()),
6058
latency_notify: asOptionalField(z.boolean()),
6159
max_latency: z.coerce.number().int().min(0),
6260
min_latency: z.coerce.number().int().min(0),
6361
name: z.string().min(1),
6462
notification_group_id: z.coerce.number().int(),
6563
notify: asOptionalField(z.boolean()),
66-
recover_trigger_tasks: z.array(z.string()).transform((v => {
67-
return v.filter(Boolean).map(Number);
68-
})),
64+
recover_trigger_tasks: z.array(z.number()),
6965
skip_servers: z.record(z.boolean()),
7066
skip_servers_raw: z.array(z.string()),
71-
target: z.string().url(),
67+
target: z.string(),
7268
type: z.coerce.number().int().min(0),
7369
});
7470

@@ -385,7 +381,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
385381
{...field}
386382
value={conv.arrToStr(field.value ?? [])}
387383
onChange={e => {
388-
const arr = conv.strToArr(e.target.value);
384+
const arr = conv.strToArr(e.target.value).map(Number);
389385
field.onChange(arr);
390386
}}
391387
/>
@@ -406,7 +402,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
406402
{...field}
407403
value={conv.arrToStr(field.value ?? [])}
408404
onChange={e => {
409-
const arr = conv.strToArr(e.target.value);
405+
const arr = conv.strToArr(e.target.value).map(Number);
410406
field.onChange(arr);
411407
}}
412408
/>

0 commit comments

Comments
 (0)