mirror of
https://github.com/supabase/supabase.git
synced 2026-05-16 15:49:19 +08:00
## Problem The `_Shadcn_` suffix isn't needed anymore on `Select` components ## Solution Remove it. No other changes <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Updated internal component architecture to standardize and simplify the codebase. These changes improve code maintainability and consistency across the application without affecting existing functionality or user experience. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45988) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
217 lines
7.4 KiB
TypeScript
217 lines
7.4 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod'
|
|
import { groupBy } from 'lodash'
|
|
import { SubmitHandler, useForm } from 'react-hook-form'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Button,
|
|
Card,
|
|
CardContent,
|
|
CardFooter,
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
Input,
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from 'ui'
|
|
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
|
import {
|
|
PageSection,
|
|
PageSectionContent,
|
|
PageSectionMeta,
|
|
PageSectionSummary,
|
|
PageSectionTitle,
|
|
} from 'ui-patterns/PageSection'
|
|
import z from 'zod'
|
|
|
|
import { useProfileIdentitiesQuery } from '@/data/profile/profile-identities-query'
|
|
import { useProfileUpdateMutation } from '@/data/profile/profile-update-mutation'
|
|
import { useProfile } from '@/lib/profile'
|
|
import type { FormSchema } from '@/types'
|
|
|
|
const FormSchema = z.object({
|
|
first_name: z.string().optional(),
|
|
last_name: z.string().optional(),
|
|
username: z.string().optional(),
|
|
primary_email: z.string().email().optional(),
|
|
})
|
|
|
|
const formId = 'profile-information-form'
|
|
|
|
export const ProfileInformation = () => {
|
|
const { profile } = useProfile()
|
|
|
|
const {
|
|
data: identityData,
|
|
isPending: isIdentitiesLoading,
|
|
isSuccess: isIdentitiesSuccess,
|
|
} = useProfileIdentitiesQuery()
|
|
const identities = (identityData?.identities ?? []).filter((x) => x.identity_data?.email !== null)
|
|
const dedupedIdentityEmails = Object.keys(groupBy(identities, 'identity_data.email'))
|
|
|
|
const defaultValues = {
|
|
first_name: profile?.first_name ?? '',
|
|
last_name: profile?.last_name ?? '',
|
|
username: profile?.username ?? '',
|
|
primary_email: profile?.primary_email ?? '',
|
|
}
|
|
|
|
const form = useForm({
|
|
resolver: zodResolver(FormSchema),
|
|
defaultValues,
|
|
values: defaultValues,
|
|
})
|
|
|
|
const { mutate: updateProfile, isPending: isUpdatingProfile } = useProfileUpdateMutation({
|
|
onSuccess: (data) => {
|
|
toast.success('Successfully saved profile')
|
|
const { first_name, last_name, username, primary_email } = data
|
|
form.reset({
|
|
first_name: first_name ?? undefined,
|
|
last_name: last_name ?? undefined,
|
|
username,
|
|
primary_email,
|
|
})
|
|
},
|
|
onError: (error) => toast.error(`Failed to update profile: ${error.message}`),
|
|
})
|
|
|
|
const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (data) => {
|
|
updateProfile({
|
|
firstName: data.first_name || '',
|
|
lastName: data.last_name || '',
|
|
username: data.username || '',
|
|
primaryEmail: data.primary_email || '',
|
|
})
|
|
}
|
|
|
|
return (
|
|
<PageSection>
|
|
<PageSectionMeta>
|
|
<PageSectionSummary>
|
|
<PageSectionTitle>Profile information</PageSectionTitle>
|
|
</PageSectionSummary>
|
|
</PageSectionMeta>
|
|
<PageSectionContent>
|
|
<Form {...form}>
|
|
<form id={formId} className="space-y-6 w-full" onSubmit={form.handleSubmit(onSubmit)}>
|
|
<Card>
|
|
<CardContent>
|
|
<FormField
|
|
control={form.control}
|
|
name="first_name"
|
|
render={({ field }) => (
|
|
<FormItemLayout label="First name" layout="flex-row-reverse">
|
|
<FormControl className="col-span-8">
|
|
<Input {...field} placeholder="First name" className="w-full" />
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
</CardContent>
|
|
<CardContent>
|
|
<FormField
|
|
control={form.control}
|
|
name="last_name"
|
|
render={({ field }) => (
|
|
<FormItemLayout label="Last name" layout="flex-row-reverse">
|
|
<FormControl className="col-span-8">
|
|
<Input {...field} placeholder="Last name" className="w-full" />
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
</CardContent>
|
|
<CardContent>
|
|
<FormField
|
|
control={form.control}
|
|
name="primary_email"
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
label="Primary email"
|
|
description={
|
|
profile?.is_sso_user
|
|
? 'Managed by your SSO provider and cannot be changed here'
|
|
: 'Used for account notifications'
|
|
}
|
|
layout="flex-row-reverse"
|
|
>
|
|
<FormControl className="col-span-8">
|
|
<div className="flex flex-col gap-1">
|
|
<Select
|
|
value={field.value}
|
|
onValueChange={field.onChange}
|
|
disabled={profile?.is_sso_user}
|
|
>
|
|
<SelectTrigger className="col-span-8 w-full">
|
|
<SelectValue placeholder="Select primary email" />
|
|
</SelectTrigger>
|
|
<SelectContent className="col-span-8">
|
|
{isIdentitiesSuccess &&
|
|
dedupedIdentityEmails.map((email) => (
|
|
<SelectItem key={email} value={email}>
|
|
{email}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
</CardContent>
|
|
<CardContent>
|
|
<FormField
|
|
control={form.control}
|
|
name="username"
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
label="Username"
|
|
description={
|
|
profile?.is_sso_user
|
|
? 'Managed by your SSO provider and cannot be changed here'
|
|
: 'Display name used across dashboard'
|
|
}
|
|
layout="flex-row-reverse"
|
|
>
|
|
<FormControl className="col-span-8">
|
|
<div className="flex flex-col gap-1">
|
|
<Input
|
|
{...field}
|
|
className="w-full"
|
|
placeholder="Username"
|
|
disabled={profile?.is_sso_user}
|
|
/>
|
|
</div>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
</CardContent>
|
|
<CardFooter className="justify-end space-x-2">
|
|
{form.formState.isDirty && (
|
|
<Button type="default" onClick={() => form.reset()}>
|
|
Cancel
|
|
</Button>
|
|
)}
|
|
<Button
|
|
type="primary"
|
|
htmlType="submit"
|
|
loading={isUpdatingProfile || isIdentitiesLoading}
|
|
disabled={!form.formState.isDirty}
|
|
>
|
|
Save
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
</form>
|
|
</Form>
|
|
</PageSectionContent>
|
|
</PageSection>
|
|
)
|
|
}
|