🐛 Fix layout not updated when changing profile (#1674)

This commit is contained in:
Luke Vella 2025-04-18 18:03:42 +01:00 committed by GitHub
parent 7faa698b18
commit 5c2bb835e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 92 additions and 107 deletions

View file

@ -1,10 +1,67 @@
import { Button } from "@rallly/ui/button";
import { DialogTrigger } from "@rallly/ui/dialog";
import { TrashIcon } from "lucide-react";
import type { Params } from "@/app/[locale]/types";
import { Trans } from "@/components/trans";
import { getUser } from "@/data/get-user";
import { getTranslation } from "@/i18n/server";
import { ProfilePage } from "./profile-page";
import {
SettingsContent,
SettingsSection,
} from "../components/settings-layout";
import { DeleteAccountDialog } from "./delete-account-dialog";
import { ProfileEmailAddress } from "./profile-email-address";
import { ProfileSettings } from "./profile-settings";
export default async function Page() {
return <ProfilePage />;
const user = await getUser();
return (
<SettingsContent>
<SettingsSection
title={<Trans i18nKey="profile" defaults="Profile" />}
description={
<Trans
i18nKey="profileDescription"
defaults="Set your public profile information"
/>
}
>
<ProfileSettings name={user.name} image={user.image} />
</SettingsSection>
<SettingsSection
title={<Trans i18nKey="profileEmailAddress" defaults="Email Address" />}
description={
<Trans
i18nKey="profileEmailAddressDescription"
defaults="Your email address is used to log in to your account"
/>
}
>
<ProfileEmailAddress />
</SettingsSection>
<hr />
<SettingsSection
title={<Trans i18nKey="dangerZone" defaults="Danger Zone" />}
description={
<Trans
i18nKey="dangerZoneAccount"
defaults="Delete your account permanently. This action cannot be undone."
/>
}
>
<DeleteAccountDialog email={user.email}>
<DialogTrigger asChild>
<Button className="text-destructive">
<TrashIcon className="size-4" />
<Trans i18nKey="deleteAccount" defaults="Delete Account" />
</Button>
</DialogTrigger>
</DeleteAccountDialog>
</SettingsSection>
</SettingsContent>
);
}
export async function generateMetadata({ params }: { params: Params }) {

View file

@ -1,3 +1,5 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { usePostHog } from "@rallly/posthog/client";
import { Alert, AlertDescription, AlertTitle } from "@rallly/ui/alert";
@ -29,7 +31,7 @@ const emailChangeFormData = z.object({
type EmailChangeFormData = z.infer<typeof emailChangeFormData>;
export const ProfileEmailAddress = () => {
const { user, refresh } = useUser();
const { user } = useUser();
const requestEmailChange = trpc.user.requestEmailChange.useMutation();
const posthog = usePostHog();
const form = useForm<EmailChangeFormData>({
@ -78,7 +80,7 @@ export const ProfileEmailAddress = () => {
}),
});
}
}, [posthog, refresh, t, toast]);
}, [posthog, t, toast]);
const { handleSubmit, formState, reset } = form;
return (

View file

@ -1,71 +0,0 @@
"use client";
import { Button } from "@rallly/ui/button";
import { DialogTrigger } from "@rallly/ui/dialog";
import { TrashIcon } from "lucide-react";
import { Trans } from "@/components/trans";
import { useUser } from "@/components/user-provider";
import {
SettingsContent,
SettingsSection,
} from "../components/settings-layout";
import { DeleteAccountDialog } from "./delete-account-dialog";
import { ProfileEmailAddress } from "./profile-email-address";
import { ProfileSettings } from "./profile-settings";
export const ProfilePage = () => {
const { user } = useUser();
return (
<SettingsContent>
<SettingsSection
title={<Trans i18nKey="profile" defaults="Profile" />}
description={
<Trans
i18nKey="profileDescription"
defaults="Set your public profile information"
/>
}
>
<ProfileSettings />
</SettingsSection>
<SettingsSection
title={<Trans i18nKey="profileEmailAddress" defaults="Email Address" />}
description={
<Trans
i18nKey="profileEmailAddressDescription"
defaults="Your email address is used to log in to your account"
/>
}
>
<ProfileEmailAddress />
</SettingsSection>
<hr />
{user.email ? (
<>
<hr />
<SettingsSection
title={<Trans i18nKey="dangerZone" defaults="Danger Zone" />}
description={
<Trans
i18nKey="dangerZoneAccount"
defaults="Delete your account permanently. This action cannot be undone."
/>
}
>
<DeleteAccountDialog email={user.email}>
<DialogTrigger asChild>
<Button className="text-destructive">
<TrashIcon className="size-4" />
<Trans i18nKey="deleteAccount" defaults="Delete Account" />
</Button>
</DialogTrigger>
</DeleteAccountDialog>
</SettingsSection>
</>
) : null}
</SettingsContent>
);
};

View file

@ -1,7 +1,6 @@
import { usePostHog } from "@rallly/posthog/client";
import { Button } from "@rallly/ui/button";
import { useToast } from "@rallly/ui/hooks/use-toast";
import * as Sentry from "@sentry/nextjs";
import React, { useState } from "react";
import { z } from "zod";
@ -39,12 +38,6 @@ function ChangeAvatarButton({ onSuccess }: { onSuccess: () => void }) {
defaultValue: "Please upload a JPG or PNG file.",
}),
});
Sentry.captureMessage("Invalid file type", {
level: "info",
extra: {
fileType: file.type,
},
});
return;
}
@ -59,12 +52,6 @@ function ChangeAvatarButton({ onSuccess }: { onSuccess: () => void }) {
defaultValue: "Please upload a file smaller than 2MB.",
}),
});
Sentry.captureMessage("File too large", {
level: "info",
extra: {
fileSize: file.size,
},
});
return;
}
setIsUploading(true);
@ -91,7 +78,7 @@ function ChangeAvatarButton({ onSuccess }: { onSuccess: () => void }) {
});
onSuccess();
} catch (error) {
} catch {
toast({
title: t("errorUploadPicture", {
defaultValue: "Failed to upload",
@ -101,7 +88,6 @@ function ChangeAvatarButton({ onSuccess }: { onSuccess: () => void }) {
"There was an issue uploading your picture. Please try again later.",
}),
});
Sentry.captureException(error);
} finally {
setIsUploading(false);
}
@ -183,15 +169,16 @@ function Upload() {
);
}
export function ProfilePicture() {
const { user } = useUser();
export function ProfilePicture({
name,
image,
}: {
name: string;
image?: string;
}) {
return (
<div className="flex items-center gap-x-4">
<OptimizedAvatarImage
src={user.image ?? undefined}
name={user.name}
size="lg"
/>
<OptimizedAvatarImage src={image} name={name} size="lg" />
<IfCloudHosted>
<Upload />
</IfCloudHosted>

View file

@ -1,3 +1,4 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@rallly/ui/button";
import {
@ -9,11 +10,11 @@ import {
FormMessage,
} from "@rallly/ui/form";
import { Input } from "@rallly/ui/input";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Trans } from "@/components/trans";
import { useUser } from "@/components/user-provider";
import { trpc } from "@/trpc/client";
import { ProfilePicture } from "./profile-picture";
@ -24,13 +25,18 @@ const profileSettingsFormData = z.object({
type ProfileSettingsFormData = z.infer<typeof profileSettingsFormData>;
export const ProfileSettings = () => {
const { user, refresh } = useUser();
export const ProfileSettings = ({
name,
image,
}: {
name: string;
image?: string;
}) => {
const changeName = trpc.user.changeName.useMutation();
const router = useRouter();
const form = useForm<ProfileSettingsFormData>({
defaultValues: {
name: user.isGuest ? "" : user.name,
name,
},
resolver: zodResolver(profileSettingsFormData),
});
@ -41,15 +47,15 @@ export const ProfileSettings = () => {
<Form {...form}>
<form
onSubmit={handleSubmit(async (data) => {
if (data.name !== user.name) {
if (data.name !== name) {
await changeName.mutateAsync({ name: data.name });
}
reset(data);
await refresh();
router.refresh();
})}
>
<div className="flex flex-col gap-y-4">
<ProfilePicture />
<ProfilePicture name={name} image={image} />
<FormField
control={control}
name="name"

View file

@ -1,5 +1,6 @@
"use client";
import { usePostHog } from "@rallly/posthog/client";
import { useRouter } from "next/navigation";
import type { Session } from "next-auth";
import { signOut, useSession } from "next-auth/react";
import React from "react";
@ -63,7 +64,7 @@ export const UserProvider = (props: { children?: React.ReactNode }) => {
const subscription = useSubscription();
const updatePreferences = trpc.user.updatePreferences.useMutation();
const { t, i18n } = useTranslation();
const router = useRouter();
const posthog = usePostHog();
const isGuest = !user?.email;
@ -96,7 +97,10 @@ export const UserProvider = (props: { children?: React.ReactNode }) => {
image: user?.image ?? null,
locale: user?.locale ?? i18n.language,
},
refresh: session.update,
refresh: async (data) => {
router.refresh();
return await session.update(data);
},
logout: async () => {
await signOut();
posthog?.capture("logout");