Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ Make user session optional #1515

Merged
merged 14 commits into from
Jan 27, 2025
3 changes: 2 additions & 1 deletion apps/web/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ SECRET_PASSWORD=abcdef1234567890abcdef1234567890
DATABASE_URL=postgres://postgres:postgres@localhost:5450/rallly
[email protected]
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_PORT=1025
QUICK_CREATE_ENABLED=true
2 changes: 1 addition & 1 deletion apps/web/declarations/next-auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ declare module "next-auth" {
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user: {
user?: {
id: string;
timeZone?: string | null;
timeFormat?: TimeFormat | null;
Expand Down
171 changes: 63 additions & 108 deletions apps/web/src/app/[locale]/(admin)/settings/profile/profile-page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
"use client";
import { Alert, AlertDescription, AlertTitle } from "@rallly/ui/alert";
import { Button } from "@rallly/ui/button";
import { DialogTrigger } from "@rallly/ui/dialog";
import { Input } from "@rallly/ui/input";
import { Label } from "@rallly/ui/label";
import { InfoIcon, LogOutIcon, TrashIcon, UserXIcon } from "lucide-react";
import { LogOutIcon, TrashIcon } from "lucide-react";
import Head from "next/head";
import Link from "next/link";
import { useTranslation } from "next-i18next";

import { DeleteAccountDialog } from "@/app/[locale]/(admin)/settings/profile/delete-account-dialog";
Expand All @@ -31,112 +27,71 @@ export const ProfilePage = () => {
<Head>
<title>{t("profile")}</title>
</Head>
{user.isGuest ? (
<SettingsContent>
<SettingsSection
title={<Trans i18nKey="profile" />}
description={<Trans i18nKey="profileDescription" />}
>
<Label className="mb-2.5">
<Trans i18nKey="userId" defaults="User ID" />
</Label>
<Input
className="w-full"
value={user.id.substring(0, 10)}
readOnly
disabled
<SettingsContent>
<SettingsSection
title={<Trans i18nKey="profile" defaults="Profile" />}
description={
<Trans
i18nKey="profileDescription"
defaults="Set your public profile information"
/>
<Alert className="mt-4" icon={InfoIcon}>
<AlertTitle>
<Trans i18nKey="aboutGuest" defaults="Guest User" />
</AlertTitle>
<AlertDescription>
}
>
<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 />

<SettingsSection
title={<Trans i18nKey="logout" />}
description={
<Trans
i18nKey="logoutDescription"
defaults="Sign out of your existing session"
/>
}
>
<LogoutButton>
<LogOutIcon className="size-4" />
<Trans i18nKey="logout" defaults="Logout" />
</LogoutButton>
</SettingsSection>
{user.email ? (
<>
<hr />
<SettingsSection
title={<Trans i18nKey="dangerZone" defaults="Danger Zone" />}
description={
<Trans
i18nKey="aboutGuestDescription"
defaults="Profile settings are not available for guest users. <0>Sign in</0> to your existing account or <1>create a new account</1> to customize your profile."
components={[
<Link className="text-link" key={0} href="/login" />,
<Link className="text-link" key={1} href="/register" />,
]}
i18nKey="dangerZoneAccount"
defaults="Delete your account permanently. This action cannot be undone."
/>
</AlertDescription>
</Alert>
<LogoutButton className="mt-6" variant="destructive">
<UserXIcon className="size-4" />
<Trans i18nKey="forgetMe" />
</LogoutButton>
</SettingsSection>
</SettingsContent>
) : (
<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 />

<SettingsSection
title={<Trans i18nKey="logout" />}
description={
<Trans
i18nKey="logoutDescription"
defaults="Sign out of your existing session"
/>
}
>
<LogoutButton>
<LogOutIcon className="size-4" />
<Trans i18nKey="logout" defaults="Logout" />
</LogoutButton>
</SettingsSection>
{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>
)}
}
>
<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}
lukevella marked this conversation as resolved.
Show resolved Hide resolved
</SettingsContent>
</Settings>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const LoginPage = ({ magicLink, email }: PageProps) => {
if (!data.url.includes("auth/error")) {
// if login was successful, update the session
const updatedSession = await session.update();
if (updatedSession) {
if (updatedSession?.user) {
// identify the user in posthog
posthog?.identify(updatedSession.user.id, {
email: updatedSession.user.email,
Expand Down
10 changes: 0 additions & 10 deletions apps/web/src/app/api/logout/route.ts

This file was deleted.

2 changes: 1 addition & 1 deletion apps/web/src/app/api/notifications/unsubscribe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const GET = async (req: NextRequest) => {

const session = await getServerSession();

if (!session || !session.user.email) {
if (!session || !session.user?.email) {
return NextResponse.redirect(new URL("/login", req.url));
}

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/stripe/checkout/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function POST(request: NextRequest) {
Object.fromEntries(formData.entries()),
);

if (!userSession || userSession.user.email === null) {
if (!userSession?.user || userSession.user?.email === null) {
// You need to be logged in to subscribe
return NextResponse.redirect(
new URL(
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/stripe/portal/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function GET(request: NextRequest) {
}
} else {
const userSession = await getServerSession();
if (!userSession || userSession.user.email === null) {
if (!userSession?.user || userSession.user.email === null) {
Sentry.captureException(new Error("User not logged in"));
return NextResponse.json(
{ error: "User not logged in" },
Expand Down
37 changes: 18 additions & 19 deletions apps/web/src/app/api/trpc/[trpc]/route.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
import * as Sentry from "@sentry/nextjs";
import { TRPCError } from "@trpc/server";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { ipAddress } from "@vercel/functions";
import type { NextRequest } from "next/server";

import { getLocaleFromHeader } from "@/app/guest";
import { getServerSession } from "@/auth";
import type { TRPCContext } from "@/trpc/context";
import { appRouter } from "@/trpc/routers";
import { getEmailClient } from "@/utils/emails";

const handler = (request: Request) => {
const handler = (req: NextRequest) => {
return fetchRequestHandler({
endpoint: "/api/trpc",
req: request,
req,
router: appRouter,
createContext: async () => {
const session = await getServerSession();

if (!session?.user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "Unauthorized",
});
}
const locale = await getLocaleFromHeader(req);
const user = session?.user
? {
id: session.user.id,
isGuest: !session.user.email,
locale: session.user.locale ?? undefined,
image: session.user.image ?? undefined,
getEmailClient: () =>
getEmailClient(session.user?.locale ?? undefined),
}
: undefined;

return {
user: {
id: session.user.id,
isGuest: session.user.email === null,
locale: session.user.locale ?? undefined,
image: session.user.image ?? undefined,
getEmailClient: () =>
getEmailClient(session.user?.locale ?? undefined),
},
ip: ipAddress(request) ?? undefined,
user,
locale,
ip: ipAddress(req) ?? undefined,
} satisfies TRPCContext;
},
onError({ error }) {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/user/verify-email-change/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const GET = async (request: NextRequest) => {

const session = await getServerSession();

if (!session || !session.user.email) {
if (!session?.user || !session.user.email) {
return NextResponse.redirect(
new URL(`/login?callbackUrl=${request.url}`, request.url),
);
Expand Down
Loading
Loading