Skip to content

Commit 68e0d64

Browse files
committed
✨ Add new UserSettings profile and card components
1 parent 1c6d656 commit 68e0d64

File tree

6 files changed

+551
-0
lines changed

6 files changed

+551
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { Key } from "lucide-react"
2+
import { useState } from "react"
3+
import { Badge } from "@/components/ui/badge"
4+
import { Button } from "@/components/ui/button"
5+
import {
6+
Card,
7+
CardContent,
8+
CardDescription,
9+
CardHeader,
10+
CardTitle,
11+
} from "@/components/ui/card"
12+
import {
13+
Dialog,
14+
DialogContent,
15+
DialogDescription,
16+
DialogHeader,
17+
DialogTitle,
18+
DialogTrigger,
19+
} from "@/components/ui/dialog"
20+
import { Label } from "@/components/ui/label"
21+
import { Separator } from "@/components/ui/separator"
22+
import useAuth from "@/hooks/useAuth"
23+
import ChangePassword from "./ChangePassword"
24+
25+
export function AccountSecurityCard() {
26+
const { user } = useAuth()
27+
const [changePasswordOpen, setChangePasswordOpen] = useState(false)
28+
29+
return (
30+
<Card>
31+
<CardHeader>
32+
<CardTitle>Account</CardTitle>
33+
<CardDescription>
34+
Manage your account status and password.
35+
</CardDescription>
36+
</CardHeader>
37+
<CardContent className="space-y-6">
38+
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
39+
<div className="space-y-1">
40+
<Label className="text-base font-medium">Account status</Label>
41+
<p className="text-muted-foreground text-sm">
42+
Your account is currently active.
43+
</p>
44+
</div>
45+
<Badge
46+
variant="outline"
47+
className={
48+
user?.is_active
49+
? "shrink-0 border-green-200 bg-green-50 text-green-700 dark:border-green-900 dark:bg-green-950 dark:text-green-400"
50+
: "shrink-0 border-muted text-muted-foreground"
51+
}
52+
>
53+
{user?.is_active ? "Active" : "Inactive"}
54+
</Badge>
55+
</div>
56+
57+
<Separator />
58+
59+
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
60+
<div className="space-y-1">
61+
<Label className="text-base font-medium">Password</Label>
62+
<p className="text-muted-foreground text-sm">
63+
Update your password.
64+
</p>
65+
</div>
66+
<Dialog
67+
open={changePasswordOpen}
68+
onOpenChange={setChangePasswordOpen}
69+
>
70+
<DialogTrigger asChild>
71+
<Button type="button" variant="outline" className="shrink-0">
72+
<Key className="mr-2 h-4 w-4" aria-hidden />
73+
Change password
74+
</Button>
75+
</DialogTrigger>
76+
<DialogContent>
77+
<DialogHeader>
78+
<DialogTitle>Change password</DialogTitle>
79+
<DialogDescription>
80+
Enter your current password and choose a new one.
81+
</DialogDescription>
82+
</DialogHeader>
83+
<ChangePassword
84+
onSuccess={() => setChangePasswordOpen(false)}
85+
embedded
86+
/>
87+
</DialogContent>
88+
</Dialog>
89+
</div>
90+
</CardContent>
91+
</Card>
92+
)
93+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Trash2 } from "lucide-react"
2+
import { useState } from "react"
3+
4+
import { Button } from "@/components/ui/button"
5+
import {
6+
Card,
7+
CardContent,
8+
CardDescription,
9+
CardHeader,
10+
CardTitle,
11+
} from "@/components/ui/card"
12+
import { Label } from "@/components/ui/label"
13+
import { DeleteAccountDialog } from "./DeleteAccountDialog"
14+
15+
export function DangerZoneCard() {
16+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
17+
18+
return (
19+
<>
20+
<Card className="border-destructive/50">
21+
<CardHeader>
22+
<CardTitle className="text-destructive">Danger zone</CardTitle>
23+
<CardDescription>
24+
Irreversible and destructive actions
25+
</CardDescription>
26+
</CardHeader>
27+
<CardContent>
28+
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
29+
<div className="space-y-1">
30+
<Label className="text-base font-medium">Delete account</Label>
31+
<p className="text-muted-foreground text-sm">
32+
Permanently delete your account and all data.
33+
</p>
34+
</div>
35+
<Button
36+
type="button"
37+
variant="destructive"
38+
onClick={() => setDeleteDialogOpen(true)}
39+
className="shrink-0"
40+
>
41+
<Trash2 className="mr-2 h-4 w-4" aria-hidden />
42+
Delete account
43+
</Button>
44+
</div>
45+
</CardContent>
46+
</Card>
47+
48+
<DeleteAccountDialog
49+
open={deleteDialogOpen}
50+
onOpenChange={setDeleteDialogOpen}
51+
/>
52+
</>
53+
)
54+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { useMutation, useQueryClient } from "@tanstack/react-query"
2+
import { useForm } from "react-hook-form"
3+
4+
import { UsersService } from "@/client"
5+
import { Button } from "@/components/ui/button"
6+
import {
7+
Dialog,
8+
DialogClose,
9+
DialogContent,
10+
DialogDescription,
11+
DialogFooter,
12+
DialogHeader,
13+
DialogTitle,
14+
} from "@/components/ui/dialog"
15+
import { LoadingButton } from "@/components/ui/loading-button"
16+
import useAuth from "@/hooks/useAuth"
17+
import useCustomToast from "@/hooks/useCustomToast"
18+
import { handleError } from "@/utils"
19+
20+
export type DeleteAccountDialogProps = {
21+
open: boolean
22+
onOpenChange: (open: boolean) => void
23+
}
24+
25+
export function DeleteAccountDialog({
26+
open,
27+
onOpenChange,
28+
}: DeleteAccountDialogProps) {
29+
const queryClient = useQueryClient()
30+
const { showSuccessToast, showErrorToast } = useCustomToast()
31+
const { handleSubmit } = useForm()
32+
const { logout } = useAuth()
33+
34+
const mutation = useMutation({
35+
mutationFn: () => UsersService.deleteUserMe(),
36+
onSuccess: () => {
37+
showSuccessToast("Your account has been successfully deleted")
38+
onOpenChange(false)
39+
logout()
40+
},
41+
onError: handleError.bind(showErrorToast),
42+
onSettled: () => {
43+
queryClient.invalidateQueries({ queryKey: ["currentUser"] })
44+
},
45+
})
46+
47+
const onSubmit = () => {
48+
mutation.mutate()
49+
}
50+
51+
return (
52+
<Dialog open={open} onOpenChange={onOpenChange}>
53+
<DialogContent>
54+
<form onSubmit={handleSubmit(onSubmit)} noValidate>
55+
<DialogHeader>
56+
<DialogTitle>Delete account</DialogTitle>
57+
<DialogDescription>
58+
All your account data will be{" "}
59+
<strong>permanently deleted.</strong> If you are sure, click{" "}
60+
<strong>Delete</strong> to proceed. This action cannot be undone.
61+
</DialogDescription>
62+
</DialogHeader>
63+
64+
<DialogFooter className="mt-4 gap-2 sm:gap-0">
65+
<DialogClose asChild>
66+
<Button
67+
type="button"
68+
variant="outline"
69+
disabled={mutation.isPending}
70+
>
71+
Cancel
72+
</Button>
73+
</DialogClose>
74+
<LoadingButton
75+
type="submit"
76+
variant="destructive"
77+
loading={mutation.isPending}
78+
disabled={mutation.isPending}
79+
>
80+
Delete
81+
</LoadingButton>
82+
</DialogFooter>
83+
</form>
84+
</DialogContent>
85+
</Dialog>
86+
)
87+
}

0 commit comments

Comments
 (0)