this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(settings): dialog

+115 -94
+3 -2
src/components/avatar.tsx
··· 6 6 export default function CustomAvatar({ 7 7 name, 8 8 image, 9 + ...props 9 10 }: { 10 11 name: string; 11 12 image: string; 12 - }) { 13 + } & React.ComponentProps<typeof Avatar>) { 13 14 return ( 14 - <Avatar size="lg"> 15 + <Avatar {...props}> 15 16 <AvatarImage src={image as string} /> 16 17 <AvatarFallback>{getInitials(name)}</AvatarFallback> 17 18 </Avatar>
+110 -90
src/components/settings.tsx
··· 9 9 import type { User } from "better-auth"; 10 10 import Link from "next/link"; 11 11 import { useRouter } from "next/navigation"; 12 + import { useState } from "react"; 12 13 import { Button } from "@/components/ui/button"; 13 14 import { ButtonGroup } from "@/components/ui/button-group"; 14 15 import { ··· 19 20 CardHeader, 20 21 CardTitle, 21 22 } from "@/components/ui/card"; 23 + import { Dialog, DialogContent } from "@/components/ui/dialog"; 22 24 import { 23 25 Field, 24 26 FieldContent, ··· 36 38 type Setting = typeof setting.$inferSelect; 37 39 38 40 export function Settings({ user, setting }: { user: User; setting: Setting }) { 41 + const [dialog, setDialog] = useState<string | null>(null); 42 + 43 + const router = useRouter(); 44 + 39 45 const handleSave = async (e: React.SubmitEvent<HTMLFormElement>) => { 40 46 e.preventDefault(); 41 47 const formData = new FormData(e.currentTarget); 42 48 43 - const picture = formData.get("picture") as File | null; 49 + const avatar = formData.get("avatar") as File | null; 44 50 const name = formData.get("name") as string; 45 51 const email = formData.get("email") as string; 46 52 const notify = formData.get("notify") === "on"; 47 53 48 - if (picture && picture.size > 0) { 49 - const { url } = await uploadImage(picture); 50 - await authClient.updateUser({ 51 - image: url, 52 - }); 53 - } 54 + let imageUrl: string | undefined; 55 + if (avatar && avatar.size > 0) 56 + await uploadImage(avatar).then(({ url }) => (imageUrl = url)); 57 + 58 + const tasks: Promise<unknown>[] = []; 59 + const messages: string[] = []; 60 + 61 + tasks.push( 62 + authClient 63 + .updateUser({ name, ...(imageUrl && { image: imageUrl }) }) 64 + .then(() => messages.push("Profile details updated.")), 65 + ); 66 + 67 + tasks.push( 68 + updateSetting(notify).then(() => messages.push("Settings updated.")), 69 + ); 54 70 55 71 if (email && email !== user.email) 56 - await authClient.changeEmail({ 57 - newEmail: email, 58 - callbackURL: "/", 59 - }); 72 + tasks.push( 73 + authClient 74 + .changeEmail({ newEmail: email }) 75 + .then(() => messages.push("Email confirmation sent.")), 76 + ); 60 77 61 - await authClient.updateUser({ name }); 62 - await updateSetting(notify); 78 + await Promise.all(tasks); 79 + setDialog(messages.join("\n")); 63 80 }; 64 81 65 - const router = useRouter(); 66 - 67 82 return ( 68 - <Card> 69 - <CardHeader> 70 - <CardTitle>Settings</CardTitle> 71 - <CardDescription> 72 - Manage your account settings and preferences. 73 - </CardDescription> 74 - <CardAction> 75 - <ButtonGroup orientation="vertical"> 76 - <Button 77 - variant="outline" 78 - render={<Link href="/" />} 79 - nativeButton={false} 80 - > 81 - <HugeiconsIcon icon={ArrowLeft01Icon} /> 82 - Back 83 - </Button> 84 - <Button 85 - variant="destructive" 86 - onClick={() => 87 - authClient.signOut({ 88 - fetchOptions: { 89 - onSuccess: () => { 90 - router.push("/"); 83 + <> 84 + <Card> 85 + <CardHeader> 86 + <CardTitle>Settings</CardTitle> 87 + <CardDescription> 88 + Manage your account settings and preferences. 89 + </CardDescription> 90 + <CardAction> 91 + <ButtonGroup orientation="vertical"> 92 + <Button 93 + variant="outline" 94 + render={<Link href="/" />} 95 + nativeButton={false} 96 + > 97 + <HugeiconsIcon icon={ArrowLeft01Icon} /> 98 + Back 99 + </Button> 100 + <Button 101 + variant="destructive" 102 + onClick={() => 103 + authClient.signOut({ 104 + fetchOptions: { 105 + onSuccess: () => { 106 + router.push("/"); 107 + }, 91 108 }, 92 - }, 93 - }) 94 - } 95 - > 96 - <HugeiconsIcon icon={Logout01Icon} /> 97 - Logout 98 - </Button> 99 - </ButtonGroup> 100 - </CardAction> 101 - </CardHeader> 102 - <CardContent> 103 - <form onSubmit={handleSave}> 104 - <FieldGroup> 105 - <Field> 106 - <FieldLabel htmlFor="avatar">Avatar</FieldLabel> 107 - <Input id="avatar" type="file" name="avatar" accept="image/*" /> 108 - </Field> 109 - <Field> 110 - <FieldLabel>Name</FieldLabel> 111 - <Input defaultValue={user.name} name="name" required /> 112 - </Field> 113 - <Field> 114 - <FieldLabel>Email</FieldLabel> 115 - <Input 116 - defaultValue={user.email} 117 - name="email" 118 - type="email" 119 - required 120 - /> 121 - </Field> 122 - <Field orientation="horizontal"> 123 - <FieldContent> 124 - <FieldLabel htmlFor="notify">Notification</FieldLabel> 125 - <FieldDescription> 126 - Get notified when someone claims a voucher from your groups. 127 - </FieldDescription> 128 - </FieldContent> 129 - <Switch 130 - id="notify" 131 - name="notify" 132 - defaultChecked={setting.notify} 133 - /> 134 - </Field> 135 - <Field> 136 - <Button variant="outline" type="submit"> 137 - <HugeiconsIcon icon={Bookmark03Icon} /> 138 - Save Changes 109 + }) 110 + } 111 + > 112 + <HugeiconsIcon icon={Logout01Icon} /> 113 + Logout 139 114 </Button> 140 - </Field> 141 - </FieldGroup> 142 - </form> 143 - </CardContent> 144 - </Card> 115 + </ButtonGroup> 116 + </CardAction> 117 + </CardHeader> 118 + <CardContent> 119 + <form onSubmit={handleSave}> 120 + <FieldGroup> 121 + <Field> 122 + <FieldLabel htmlFor="avatar">Avatar</FieldLabel> 123 + <Input id="avatar" type="file" name="avatar" accept="image/*" /> 124 + </Field> 125 + <Field> 126 + <FieldLabel>Name</FieldLabel> 127 + <Input defaultValue={user.name} name="name" required /> 128 + </Field> 129 + <Field> 130 + <FieldLabel>Email</FieldLabel> 131 + <Input 132 + defaultValue={user.email} 133 + name="email" 134 + type="email" 135 + required 136 + /> 137 + </Field> 138 + <Field orientation="horizontal"> 139 + <FieldContent> 140 + <FieldLabel htmlFor="notify">Notification</FieldLabel> 141 + <FieldDescription> 142 + Get notified when someone claims a voucher from your groups. 143 + </FieldDescription> 144 + </FieldContent> 145 + <Switch 146 + id="notify" 147 + name="notify" 148 + defaultChecked={setting.notify} 149 + /> 150 + </Field> 151 + <Field> 152 + <Button variant="outline" type="submit"> 153 + <HugeiconsIcon icon={Bookmark03Icon} /> 154 + Save Changes 155 + </Button> 156 + </Field> 157 + </FieldGroup> 158 + </form> 159 + </CardContent> 160 + </Card> 161 + <Dialog open={!!dialog} onOpenChange={() => setDialog(null)}> 162 + <DialogContent className="whitespace-pre-line">{dialog}</DialogContent> 163 + </Dialog> 164 + </> 145 165 ); 146 166 }
+2 -2
src/lib/image.ts
··· 3 3 import { put } from "@vercel/blob"; 4 4 5 5 export async function uploadImage(file: File) { 6 - //TODO: remove the old file 7 - return put(file.name, file, { 6 + return put(Math.random().toString(36).substring(2), file, { 8 7 access: "public", 8 + allowOverwrite: true, 9 9 }); 10 10 }