Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 64 additions & 2 deletions apps/web/src/app/[lang]/profile/[personSlug]/@aside/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '@hypha-platform/core/client';
import { z } from 'zod';
import { tryDecodeUriPart } from '@hypha-platform/ui-utils';
import { useUpdateAccount, usePrivy } from '@privy-io/react-auth';

export default function EditProfilePage() {
const { lang, personSlug: personSlugRaw } = useParams<ProfilePageParams>();
Expand All @@ -32,16 +33,76 @@ export default function EditProfilePage() {
reset,
} = useEditProfile();
const router = useRouter();
const { user: privyUser, authenticated } = usePrivy();

const hasEmailAccount = !!privyUser?.email?.address;
const currentPrivyEmail = privyUser?.email?.address;

const hasOAuthAccount = !!(privyUser?.google || privyUser?.apple);
const cannotChangeEmail =
authenticated && hasOAuthAccount && !hasEmailAccount;
Comment on lines +36 to +43
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align cannotChangeEmail gating with OAuth provider‑agnostic copy

cannotChangeEmail is computed as:

const hasOAuthAccount = !!(privyUser?.google || privyUser?.apple);
const cannotChangeEmail = authenticated && hasOAuthAccount && !hasEmailAccount;

and then passed to EditPersonSection. For Apple SSO users (where privyUser?.apple is truthy but privyUser?.google is not), this flag will also be true, but the explanatory text rendered in EditPersonSection currently talks only about “Google SSO” and “Google Account settings”.

To avoid confusing Apple‑SSO users, consider either:

  • Making the message in EditPersonSection provider‑agnostic, or
  • Passing down more specific context (e.g., which OAuth provider is in use) so the message can be tailored appropriately.

Also applies to: 128-136


const schemaEditPersonForm = schemaEditPersonWeb2.extend(
editPersonFiles.shape,
);
type EditPersonFormData = z.infer<typeof schemaEditPersonForm>;

const pendingFormDataRef = React.useRef<EditPersonFormData | null>(null);

const { updateEmail } = useUpdateAccount({
onSuccess: async () => {
if (pendingFormDataRef.current) {
try {
await editProfile(pendingFormDataRef.current);
pendingFormDataRef.current = null;
router.push('/profile');
} catch (err) {
console.log(err);
pendingFormDataRef.current = null;
}
}
},
onError: (error, details) => {
console.error('Error updating email in Privy:', error, details);
if (
error?.includes('does not have an email linked') ||
error?.includes('email account')
) {
if (pendingFormDataRef.current) {
editProfile(pendingFormDataRef.current)
.then(() => {
router.push('/profile');
})
.catch((err) => {
console.log(err);
})
.finally(() => {
pendingFormDataRef.current = null;
});
}
} else {
pendingFormDataRef.current = null;
}
},
});

const onEdit = async (data: EditPersonFormData) => {
try {
await editProfile(data);
router.push('/profile');
const newEmail = data.email?.trim();
const emailChanged =
authenticated &&
hasEmailAccount &&
currentPrivyEmail &&
newEmail &&
newEmail !== currentPrivyEmail;

if (emailChanged) {
pendingFormDataRef.current = data;
updateEmail();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't updateEmail return Promise, too?

} else {
await editProfile(data);
router.push('/profile');
}
} catch (err) {
console.log(err);
}
Expand Down Expand Up @@ -71,6 +132,7 @@ export default function EditProfilePage() {
onEdit={onEdit}
onUpdate={revalidate}
error={error}
cannotChangeEmail={cannotChangeEmail}
/>
</LoadingBackdrop>
</SidePanel>
Expand Down
16 changes: 15 additions & 1 deletion packages/epics/src/people/components/edit-person-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export type EditPersonSectionProps = {
onEdit: (values: z.infer<typeof schemaEditPersonForm>) => Promise<void>;
onUpdate: () => void;
error?: string | null;
cannotChangeEmail?: boolean;
};

type FormData = z.infer<typeof schemaEditPersonForm>;
Expand All @@ -59,6 +60,7 @@ export const EditPersonSection = ({
onEdit,
onUpdate,
error,
cannotChangeEmail = false,
}: EditPersonSectionProps) => {
const formRef = useRef<HTMLFormElement>(null);
const form = useForm<FormData>({
Expand Down Expand Up @@ -242,7 +244,7 @@ export const EditPersonSection = ({
<FormItem>
<FormControl>
<Input
disabled={isLoading}
disabled={isLoading || cannotChangeEmail}
placeholder="Email"
className="w-60"
{...field}
Expand All @@ -254,6 +256,18 @@ export const EditPersonSection = ({
/>
</span>
</div>
{cannotChangeEmail && (
<div className="bg-neutral-3 border border-neutral-6 rounded-lg p-4">
<Text className={cn('text-2', 'text-neutral-11')}>
You cannot change the email address on your Privy account
directly if you log in with Google SSO, because the email is
managed by Google. You must update the email address within
your Google Account settings first. If you log in to Privy
with a standard account (not Google SSO), you can change the
email directly in your Privy profile settings.
</Text>
</div>
)}
<div className="flex justify-between">
<Text className={cn('text-2', 'text-neutral-11')}>
Location
Expand Down