Skip to content

Commit

Permalink
feat: add setting user profile avatar preview
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Sep 10, 2024
1 parent 34392ec commit 2412d59
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 148 deletions.
118 changes: 118 additions & 0 deletions src/renderer/src/modules/profile/profile-setting-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { setWhoami, useWhoami } from "@renderer/atoms/user"
import { Avatar, AvatarImage } from "@renderer/components/ui/avatar"
import { Button } from "@renderer/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@renderer/components/ui/form"
import { Input } from "@renderer/components/ui/input"
import { apiClient } from "@renderer/lib/api-fetch"
import { useMutation } from "@tanstack/react-query"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { z } from "zod"

const formSchema = z.object({
handle: z.string().max(50),
name: z.string().min(3).max(50),
image: z.string().url(),
})

export const ProfileSettingForm = () => {
const user = useWhoami()

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
handle: user?.handle || "",
name: user?.name || "",
image: user?.image || "",
},
})

const updateMutation = useMutation({
mutationFn: async (values: z.infer<typeof formSchema>) =>
apiClient["auth-app"]["update-account"].$patch({
json: values,
}),
onSuccess: (_, variables) => {
if (user && variables) {
setWhoami({ ...user, ...variables })
}
toast("Profile updated.", {
duration: 3000,
})
},
})

function onSubmit(values: z.infer<typeof formSchema>) {
updateMutation.mutate(values)
}

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="mt-4 space-y-4">
<FormField
control={form.control}
name="handle"
render={({ field }) => (
<FormItem>
<FormLabel>Handle</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Your unique identifier.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Display Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="image"
render={({ field }) => (
<div className="flex gap-4">
<FormItem className="w-full">
<FormLabel>Avatar</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>

<Avatar className="-translate-y-2 self-end">
<AvatarImage src={field.value} />
</Avatar>
</div>
)}
/>

<div className="text-right">
<Button type="submit" isLoading={updateMutation.isPending}>
Submit
</Button>
</div>
</form>
</Form>
)
}
56 changes: 12 additions & 44 deletions src/renderer/src/modules/settings/tabs/integration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,12 @@ import {
setIntegrationSetting,
useIntegrationSettingValue,
} from "@renderer/atoms/settings/integration"
import {
createDefaultSettings,
setUISetting,
useUISettingSelector,
} from "@renderer/atoms/settings/ui"
import { Divider } from "@renderer/components/ui/divider"
import {
SimpleIconsEagle,
SimpleIconsInstapaper,
SimpleIconsReadwise,
} from "@renderer/components/ui/platform-icon/icons"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@renderer/components/ui/select"
import { tipcClient } from "@renderer/lib/client"
import { useQuery } from "@tanstack/react-query"

import { SettingsTitle } from "../title"

Expand All @@ -38,7 +25,7 @@ export const SettingIntegration = () => (
{
type: "title",
value: (
<span className="flex items-center gap-2">
<span className="flex items-center gap-2 font-bold">
<SimpleIconsEagle />
Eagle
</span>
Expand All @@ -55,7 +42,7 @@ export const SettingIntegration = () => (
{
type: "title",
value: (
<span className="flex items-center gap-2">
<span className="flex items-center gap-2 font-bold">
<SimpleIconsReadwise />
Readwise
</span>
Expand Down Expand Up @@ -91,7 +78,7 @@ export const SettingIntegration = () => (
{
type: "title",
value: (
<span className="flex items-center gap-2">
<span className="flex items-center gap-2 font-bold">
<SimpleIconsInstapaper />
Instapaper
</span>
Expand All @@ -118,40 +105,21 @@ export const SettingIntegration = () => (
labelClassName: "w-[150px]",
},
}),

BottomTip,
]}
/>
</div>
</>
)

export const VoiceSelector = () => {
const { data } = useQuery({
queryFn: () => tipcClient?.getVoices(),
queryKey: ["voices"],
})
const voice = useUISettingSelector((state) => state.voice)

const BottomTip = () => {
return (
<div className="-mt-1 mb-3 flex items-center justify-between">
<span className="shrink-0 text-sm font-medium">Voices</span>
<Select
defaultValue={createDefaultSettings().voice}
value={voice}
onValueChange={(value) => {
setUISetting("voice", value)
}}
>
<SelectTrigger size="sm" className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent className="h-64">
{data?.map((item) => (
<SelectItem key={item.ShortName} value={item.ShortName}>
{item.FriendlyName}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="mt-6">
<Divider />
<p className="opacity-60">
<small>Tip: Your sensitive data is stored locally and is not uploaded to the server.</small>
</p>
</div>
)
}
106 changes: 2 additions & 104 deletions src/renderer/src/pages/settings/(settings)/profile.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { setWhoami, useWhoami } from "@renderer/atoms/user"
import { Button } from "@renderer/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@renderer/components/ui/form"
import { Input } from "@renderer/components/ui/input"
import { apiClient } from "@renderer/lib/api-fetch"
import { ProfileSettingForm } from "@renderer/modules/profile/profile-setting-form"
import { SettingsTitle } from "@renderer/modules/settings/title"
import { defineSettingPage } from "@renderer/modules/settings/utils"
import { useMutation } from "@tanstack/react-query"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { z } from "zod"

const formSchema = z.object({
handle: z.string().max(50),
name: z.string().min(3).max(50),
image: z.string().url(),
})

const iconName = "i-mgc-user-setting-cute-re"
const name = "Profile"
Expand All @@ -35,89 +12,10 @@ export const loader = defineSettingPage({
})

export function Component() {
const user = useWhoami()

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
handle: user?.handle || "",
name: user?.name || "",
image: user?.image || "",
},
})

const updateMutation = useMutation({
mutationFn: async (values: z.infer<typeof formSchema>) =>
apiClient["auth-app"]["update-account"].$patch({
json: values,
}),
onSuccess: (_, variables) => {
if (user && variables) {
setWhoami({ ...user, ...variables })
}
toast("Profile updated.", {
duration: 3000,
})
},
})

function onSubmit(values: z.infer<typeof formSchema>) {
updateMutation.mutate(values)
}

return (
<>
<SettingsTitle />
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="mt-4 space-y-4">
<FormField
control={form.control}
name="handle"
render={({ field }) => (
<FormItem>
<FormLabel>Handle</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Your unique identifier.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Display Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Your public display name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="image"
render={({ field }) => (
<FormItem>
<FormLabel>Avatar</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="text-right">
<Button type="submit" isLoading={updateMutation.isPending}>
Submit
</Button>
</div>
</form>
</Form>
<ProfileSettingForm />
</>
)
}

0 comments on commit 2412d59

Please sign in to comment.