This commit is contained in:
Luke Vella 2025-04-23 15:13:23 +01:00
parent f838c5401a
commit 22c616e4a0
No known key found for this signature in database
GPG key ID: 469CAD687F0D784C
6 changed files with 78 additions and 100 deletions

View file

@ -1,7 +1,6 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { usePostHog } from "@rallly/posthog/client";
import { Button } from "@rallly/ui/button"; import { Button } from "@rallly/ui/button";
import { import {
Form, Form,
@ -42,7 +41,6 @@ export function OTPForm({ token }: { token: string }) {
const locale = i18n.language; const locale = i18n.language;
const queryClient = trpc.useUtils(); const queryClient = trpc.useUtils();
const posthog = usePostHog();
const authenticateRegistration = const authenticateRegistration =
trpc.auth.authenticateRegistration.useMutation(); trpc.auth.authenticateRegistration.useMutation();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@ -64,11 +62,6 @@ export function OTPForm({ token }: { token: string }) {
queryClient.invalidate(); queryClient.invalidate();
posthog?.identify(res.user.id, {
email: res.user.email,
name: res.user.name,
});
signIn("registration-token", { signIn("registration-token", {
token, token,
redirectTo: searchParams?.get("redirectTo") ?? "/", redirectTo: searchParams?.get("redirectTo") ?? "/",

View file

@ -27,8 +27,8 @@ const formSchema = z.object({
type FormData = z.infer<typeof formSchema>; type FormData = z.infer<typeof formSchema>;
const DateTimePreferencesForm = () => { const DateTimePreferencesForm = () => {
const { timeFormat, weekStart, timeZone, locale } = useDayjs(); const { timeFormat, weekStart, timeZone } = useDayjs();
const { preferences, updatePreferences } = usePreferences(); const { updatePreferences } = usePreferences();
const form = useForm<FormData>({ const form = useForm<FormData>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
@ -126,25 +126,6 @@ const DateTimePreferencesForm = () => {
> >
<Trans i18nKey="save" /> <Trans i18nKey="save" />
</Button> </Button>
{preferences.timeFormat || preferences.weekStart ? (
<Button
onClick={async () => {
updatePreferences({
weekStart: null,
timeFormat: null,
});
form.reset({
weekStart: locale.weekStart,
timeFormat: locale.timeFormat,
});
}}
>
<Trans
defaults="Use locale defaults"
i18nKey="useLocaleDefaults"
/>
</Button>
) : null}
</div> </div>
</form> </form>
</Form> </Form>

View file

@ -12,6 +12,7 @@ import React from "react";
import { TimeZoneChangeDetector } from "@/app/[locale]/timezone-change-detector"; import { TimeZoneChangeDetector } from "@/app/[locale]/timezone-change-detector";
import { UserProvider } from "@/components/user-provider"; import { UserProvider } from "@/components/user-provider";
import { PreferencesProvider } from "@/contexts/preferences";
import { getUser } from "@/data/get-user"; import { getUser } from "@/data/get-user";
import { TimezoneProvider } from "@/features/timezone/client/context"; import { TimezoneProvider } from "@/features/timezone/client/context";
import { I18nProvider } from "@/i18n/client"; import { I18nProvider } from "@/i18n/client";
@ -64,27 +65,42 @@ export default async function Root({
<PostHogPageView /> <PostHogPageView />
<TooltipProvider> <TooltipProvider>
<UserProvider <UserProvider
user={{ user={
id: user?.id ?? session?.user?.id ?? "", user
name: user?.name ?? session?.user?.name ?? "", ? {
email: user?.email ?? session?.user?.email ?? undefined, id: user.id,
tier: user ? (user.isPro ? "pro" : "hobby") : "guest", name: user.name,
timeZone: email: user.email,
user?.timeZone ?? session?.user?.timeZone ?? undefined, tier: user
timeFormat: session?.user?.timeFormat ?? undefined, ? user.isPro
weekStart: session?.user?.weekStart ?? undefined, ? "pro"
image: session?.user?.image ?? undefined, : "hobby"
locale: session?.user?.locale ?? undefined, : "guest",
}} image: user.image,
}
: session?.user
? {
id: session.user.id,
tier: "guest",
}
: undefined
}
> >
<TimezoneProvider <PreferencesProvider
initialTimezone={session?.user?.timeZone ?? undefined} initialValue={{
timeFormat: user?.timeFormat,
timeZone: user?.timeZone,
locale: user?.locale,
weekStart: user?.weekStart,
}}
> >
<ConnectedDayjsProvider> <TimezoneProvider initialTimezone={user?.timeZone}>
{children} <ConnectedDayjsProvider>
<TimeZoneChangeDetector /> {children}
</ConnectedDayjsProvider> <TimeZoneChangeDetector />
</TimezoneProvider> </ConnectedDayjsProvider>
</TimezoneProvider>
</PreferencesProvider>
</UserProvider> </UserProvider>
</TooltipProvider> </TooltipProvider>
</PostHogProvider> </PostHogProvider>

View file

@ -5,9 +5,7 @@ import { signOut } from "next-auth/react";
import React from "react"; import React from "react";
import { useSubscription } from "@/contexts/plan"; import { useSubscription } from "@/contexts/plan";
import { PreferencesProvider } from "@/contexts/preferences";
import { useTranslation } from "@/i18n/client"; import { useTranslation } from "@/i18n/client";
import { trpc } from "@/trpc/client";
import { isOwner } from "@/utils/permissions"; import { isOwner } from "@/utils/permissions";
import { useRequiredContext } from "./use-required-context"; import { useRequiredContext } from "./use-required-context";
@ -15,14 +13,10 @@ import { useRequiredContext } from "./use-required-context";
type UserData = { type UserData = {
id?: string; id?: string;
name: string; name: string;
email?: string | null; email?: string;
isGuest: boolean; isGuest: boolean;
tier: "guest" | "hobby" | "pro"; tier: "guest" | "hobby" | "pro";
timeZone?: string | null; image?: string;
timeFormat?: "hours12" | "hours24" | null;
weekStart?: number | null;
image?: string | null;
locale?: string | null;
}; };
export const UserContext = React.createContext<{ export const UserContext = React.createContext<{
@ -58,30 +52,37 @@ export const IfGuest = (props: { children?: React.ReactNode }) => {
return <>{props.children}</>; return <>{props.children}</>;
}; };
type BaseUser = {
id: string;
tier: "guest" | "hobby" | "pro";
image?: string;
name?: string;
email?: string;
};
type RegisteredUser = BaseUser & {
email: string;
name: string;
tier: "hobby" | "pro";
};
type GuestUser = BaseUser & {
tier: "guest";
};
export const UserProvider = ({ export const UserProvider = ({
children, children,
user, user,
}: { }: {
children?: React.ReactNode; children?: React.ReactNode;
user?: { user?: RegisteredUser | GuestUser;
id: string;
name: string;
email?: string;
tier: "guest" | "hobby" | "pro";
timeZone?: string;
timeFormat?: "hours12" | "hours24";
weekStart?: number;
image?: string;
locale?: string;
};
}) => { }) => {
const subscription = useSubscription(); const subscription = useSubscription();
const updatePreferences = trpc.user.updatePreferences.useMutation(); const { t } = useTranslation();
const { t, i18n } = useTranslation();
const router = useRouter(); const router = useRouter();
const posthog = usePostHog(); const posthog = usePostHog();
const isGuest = !user?.email; const isGuest = !user || user.tier === "guest";
const tier = isGuest ? "guest" : subscription?.active ? "pro" : "hobby"; const tier = isGuest ? "guest" : subscription?.active ? "pro" : "hobby";
React.useEffect(() => { React.useEffect(() => {
@ -90,9 +91,7 @@ export const UserProvider = ({
email: user.email, email: user.email,
name: user.name, name: user.name,
tier, tier,
timeZone: user.timeZone ?? null, image: user.image,
image: user.image ?? null,
locale: user.locale ?? i18n.language,
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -104,12 +103,10 @@ export const UserProvider = ({
user: { user: {
id: user?.id, id: user?.id,
name: user?.name ?? t("guest"), name: user?.name ?? t("guest"),
email: user?.email || null, email: user?.email,
isGuest, isGuest,
tier, tier,
timeZone: user?.timeZone ?? null, image: user?.image,
image: user?.image ?? null,
locale: user?.locale ?? i18n.language,
}, },
isAuthenticated: !!user, isAuthenticated: !!user,
refresh: router.refresh, refresh: router.refresh,
@ -123,27 +120,7 @@ export const UserProvider = ({
}, },
}} }}
> >
<PreferencesProvider {children}
initialValue={{
locale: user?.locale ?? undefined,
timeZone: user?.timeZone ?? undefined,
timeFormat: user?.timeFormat ?? undefined,
weekStart: user?.weekStart ?? undefined,
}}
onUpdate={async (newPreferences) => {
if (!isGuest) {
await updatePreferences.mutateAsync({
locale: newPreferences.locale ?? undefined,
timeZone: newPreferences.timeZone ?? undefined,
timeFormat: newPreferences.timeFormat ?? undefined,
weekStart: newPreferences.weekStart ?? undefined,
});
}
router.refresh();
}}
>
{children}
</PreferencesProvider>
</UserContext.Provider> </UserContext.Provider>
); );
}; };

View file

@ -1,8 +1,11 @@
"use client";
import type { TimeFormat } from "@rallly/database"; import type { TimeFormat } from "@rallly/database";
import React from "react"; import React from "react";
import { useSetState } from "react-use"; import { useSetState } from "react-use";
import { useRequiredContext } from "@/components/use-required-context"; import { useRequiredContext } from "@/components/use-required-context";
import { trpc } from "@/trpc/client";
type Preferences = { type Preferences = {
timeZone?: string | null; timeZone?: string | null;
@ -23,13 +26,12 @@ const PreferencesContext = React.createContext<PreferencesContextValue | null>(
export const PreferencesProvider = ({ export const PreferencesProvider = ({
children, children,
initialValue, initialValue,
onUpdate,
}: { }: {
children?: React.ReactNode; children?: React.ReactNode;
initialValue: Partial<Preferences>; initialValue: Partial<Preferences>;
onUpdate?: (preferences: Partial<Preferences>) => Promise<void>;
}) => { }) => {
const [preferences, setPreferences] = useSetState<Preferences>(initialValue); const [preferences, setPreferences] = useSetState<Preferences>(initialValue);
const updatePreferences = trpc.user.updatePreferences.useMutation();
return ( return (
<PreferencesContext.Provider <PreferencesContext.Provider
@ -37,7 +39,12 @@ export const PreferencesProvider = ({
preferences, preferences,
updatePreferences: async (newPreferences) => { updatePreferences: async (newPreferences) => {
setPreferences(newPreferences); setPreferences(newPreferences);
await onUpdate?.(newPreferences); await updatePreferences.mutateAsync({
locale: newPreferences.locale ?? undefined,
timeZone: newPreferences.timeZone ?? undefined,
timeFormat: newPreferences.timeFormat ?? undefined,
weekStart: newPreferences.weekStart ?? undefined,
});
}, },
}} }}
> >

View file

@ -18,6 +18,8 @@ export const getUser = cache(async () => {
image: true, image: true,
locale: true, locale: true,
timeZone: true, timeZone: true,
timeFormat: true,
weekStart: true,
subscription: { subscription: {
select: { select: {
active: true, active: true,
@ -37,6 +39,8 @@ export const getUser = cache(async () => {
image: user.image ?? undefined, image: user.image ?? undefined,
locale: user.locale ?? undefined, locale: user.locale ?? undefined,
timeZone: user.timeZone ?? undefined, timeZone: user.timeZone ?? undefined,
timeFormat: user.timeFormat ?? undefined,
weekStart: user.weekStart ?? undefined,
isPro: user.subscription?.active ?? false, isPro: user.subscription?.active ?? false,
}; };
}); });