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

✨ New Login Page #1504

Merged
merged 5 commits into from
Jan 21, 2025
Merged
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
35 changes: 26 additions & 9 deletions apps/web/public/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"12h": "12-hour",
"24h": "24-hour",
"addTimeOption": "Add time option",
"alreadyRegistered": "Already registered? <a>Login</a>",
"applyToAllDates": "Apply to all dates",
"areYouSure": "Are you sure?",
"cancel": "Cancel",
Expand Down Expand Up @@ -39,7 +38,6 @@
"location": "Location",
"locationPlaceholder": "Joe's Coffee Shop",
"login": "Login",
"loginWith": "Login with {provider}",
"logout": "Logout",
"manage": "Manage",
"mixedOptionsDescription": "You can't have both time and date options in the same poll. Which would you like to keep?",
Expand Down Expand Up @@ -75,11 +73,9 @@
"titlePlaceholder": "Monthly Meetup",
"today": "Today",
"userAlreadyExists": "A user with that email already exists",
"userNotFound": "A user with that email doesn't exist",
"validEmail": "Please enter a valid email",
"verificationCodeHelp": "Didn't get the email? Check your spam/junk.",
"verificationCodePlaceholder": "Enter your 6-digit code",
"verifyYourEmail": "Verify your email",
"startOfWeek": "Start of week",
"weekView": "Week view",
"wrongVerificationCode": "Your verification code is incorrect or has expired",
Expand Down Expand Up @@ -174,7 +170,6 @@
"duplicateTitleLabel": "Title",
"duplicateTitleDescription": "Hint: Give your new poll a unique title",
"upgrade": "Upgrade",
"continueAsGuest": "Continue as Guest",
"scrollLeft": "Scroll Left",
"scrollRight": "Scroll Right",
"shrink": "Shrink",
Expand All @@ -200,7 +195,6 @@
"hideScoresLabel": "Hide scores until after a participant has voted",
"continueAs": "Continue as",
"pageMovedDescription": "Redirecting to <a>{newUrl}</a>",
"notRegistered": "Don't have an account? <a>Register</a>",
"unlockFeatures": "Unlock all Pro features.",
"pollStatusFinalized": "Finalized",
"share": "Share",
Expand All @@ -213,7 +207,6 @@
"inviteParticipantsDescription": "Copy and share the invite link to start gathering responses from your participants.",
"inviteLink": "Invite Link",
"inviteParticipantLinkInfo": "Anyone with this link will be able to vote on your poll.",
"accountNotLinkedTitle": "Your account cannot be linked to an existing user",
"accountNotLinkedDescription": "A user with this email already exists. Please log in using the original method.",
"or": "Or",
"autoTimeZone": "Automatic Time Zone Conversion",
Expand All @@ -234,7 +227,6 @@
"dangerZoneAccount": "Delete your account permanently. This action cannot be undone.",
"upgradePromptTitle": "Upgrade to Pro",
"upgradeOverlaySubtitle3": "Unlock these feature by upgrading to a Pro plan.",
"verificationCodeSentTo": "We sent a verification code to <b>{email}</b>",
"home": "Home",
"groupPoll": "Group Poll",
"groupPollDescription": "Share your availability with a group of people and find the best time to meet.",
Expand Down Expand Up @@ -289,5 +281,30 @@
"emailChangeRequestSentDescription": "To complete the change, please check your email for a verification link.",
"profileEmailAddress": "Email Address",
"profileEmailAddressDescription": "Your email address is used to log in to your account",
"emailAlreadyInUse": "This email address is already associated with another account. Please use a different email address."
"emailAlreadyInUse": "This email address is already associated with another account. Please use a different email address.",
"continueWith": "Continue with {provider}",
"continueWithProvider": "Continue with {{provider}}",
"loginFooter": "Don't have an account? <a>Sign up</a>",
"back": "Back",
"verifyEmail": "Verify your email",
"alreadyHaveAccount": "Already have an account? <a>Log in</a>",
"loginDescription": "Login to your account to continue",
"userNotFound": "A user with that email doesn't exist",
"loginTitle": "Welcome",
"registerTitle": "Create Your Account",
"registerDescription": "Streamline your scheduling process and save time",
"quickActionCreate": "Quick Create",
"quickActionsDescription": "Create a group poll without signing in. Login later to link it to your account.",
"quickCreateGroupPoll": "Create Group Poll",
"quickCreate": "Quick Create",
"quickCreateRecentlyCreated": "Recently Created",
"quickCreateWhyCreateAnAccount": "Why create an account?",
"quickCreateSecurePolls": "Secure access through your account",
"quickCreateGetNotifications": "Get email notifications",
"quickCreateManagePollsFromAnyDevice": "Manage your polls from any device",
"registerVerifyTitle": "Finish Registering",
"registerVerifyDescription": "Check your email for the verification code",
"loginVerifyTitle": "Finish Logging In",
"loginVerifyDescription": "Check your email for the verification code",
"createAccount": "Create Account"
}
2 changes: 1 addition & 1 deletion apps/web/src/app/[locale]/(admin)/app-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function GroupPollIcon({
"size-6 rounded": size === "xs",
"size-8 rounded-md": size === "sm",
"size-9 rounded-md": size === "md",
"size-10 rounded-lg": size === "lg",
"size-10 rounded-md": size === "lg",
},
)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useMutation } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";

import { Logo } from "@/components/logo";
import { OptimizedAvatarImage } from "@/components/optimized-avatar-image";
import { Skeleton } from "@/components/skeleton";
import { Trans } from "@/components/trans";
Expand Down Expand Up @@ -40,42 +39,37 @@ export const LoginPage = ({ magicLink, email }: PageProps) => {
const { data } = trpc.user.getByEmail.useQuery({ email });
const router = useRouter();
return (
<div className="flex h-screen flex-col items-center justify-center gap-4 p-4">
<div className="mb-6">
<Logo />
</div>

<div className="shadow-huge rounded-md bg-white p-4">
<div className="w-48 text-center">
<div className="mb-4 font-semibold">
<Trans i18nKey="continueAs" defaults="Continue as" />
</div>
<div className="flex flex-col items-center gap-2">
<OptimizedAvatarImage
src={data?.image ?? undefined}
name={data?.name ?? ""}
size="xl"
/>
<div className="text-center">
<div className="mb-1 h-6 font-medium">
{data?.name ?? <Skeleton className="inline-block h-5 w-16" />}
</div>
<div className="text-muted-foreground h-5 truncate text-sm">
{data?.email ?? (
<Skeleton className="inline-block h-full w-20" />
)}
</div>
<div className="flex h-full w-full flex-col items-center justify-center">
<div className="w-48 space-y-8 text-center">
<h1 className="text-xl font-bold">
<Trans i18nKey="continueAs" defaults="Continue as" />
</h1>
<div className="flex flex-col items-center gap-4">
<OptimizedAvatarImage
src={data?.image ?? undefined}
name={data?.name ?? ""}
size="xl"
/>
<div>
<div className="mb-1 h-6 font-medium">
{data?.name ?? <Skeleton className="inline-block h-5 w-16" />}
</div>
<div className="text-muted-foreground h-5 truncate text-sm">
{data?.email ?? <Skeleton className="inline-block h-full w-20" />}
</div>
</div>
</div>
<div>
<Button
size="lg"
loading={magicLinkFetch.isLoading}
onClick={async () => {
await magicLinkFetch.mutateAsync();
}}
variant="primary"
className="mt-6 w-full"
className="w-full"
Comment on lines +64 to +70
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

Add error handling for failed magic link authentication.

The magic link fetch doesn't handle network errors or invalid responses.

Add error handling:

  onClick={async () => {
-   await magicLinkFetch.mutateAsync();
+   try {
+     await magicLinkFetch.mutateAsync();
+   } catch (error) {
+     console.error('Magic link authentication failed:', error);
+     // Show error message to user
+   }
  }}

Committable suggestion skipped: line range outside the PR's diff.

>
<Trans i18nKey="continue" />
<Trans i18nKey="login" defaults="Login" />
</Button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from "zod";

import { getTranslation } from "@/i18n/server";

import { LoginPage } from "./login-page";
import { LoginPage } from "./components/login-page";

export const dynamic = "force-dynamic";

Expand Down
29 changes: 29 additions & 0 deletions apps/web/src/app/[locale]/(auth)/components/auth-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export function AuthPageContainer({ children }: { children: React.ReactNode }) {
return <div className="space-y-8 lg:space-y-10">{children}</div>;
}

export function AuthPageHeader({ children }: { children: React.ReactNode }) {
return <div className="space-y-1 text-center">{children}</div>;
}

export function AuthPageTitle({ children }: { children: React.ReactNode }) {
return <h1 className="text-2xl font-bold">{children}</h1>;
}

export function AuthPageDescription({
children,
}: {
children: React.ReactNode;
}) {
return <p className="text-muted-foreground">{children}</p>;
}

export function AuthPageContent({ children }: { children: React.ReactNode }) {
return <div className="space-y-4">{children}</div>;
}

export function AuthPageExternal({ children }: { children: React.ReactNode }) {
return (
<p className="text-muted-foreground px-4 py-3 text-center">{children}</p>
);
}
54 changes: 51 additions & 3 deletions apps/web/src/app/[locale]/(auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,55 @@
export default function Layout({ children }: { children: React.ReactNode }) {
import { cn } from "@rallly/ui";
import { DotPattern } from "@rallly/ui/dot-pattern";
import type { Metadata } from "next";

import { Logo } from "@/components/logo";
import { isQuickCreateEnabled } from "@/features/quick-create";
import { QuickStartButton } from "@/features/quick-create/quick-create-button";
import { QuickStartWidget } from "@/features/quick-create/quick-create-widget";

export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
return (
Comment on lines +10 to +14
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Remove unnecessary async keyword

The layout component doesn't perform any asynchronous operations. It's a purely presentational component that renders static JSX with UI components and styling. The async keyword can be safely removed.

🔗 Analysis chain

Verify the necessity of the async keyword.

The component is marked as async but doesn't appear to perform any asynchronous operations. If there are no server-side data fetches or async operations planned, consider removing the async keyword.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any async operations or data fetches in related auth files
rg -l "fetch|await" "apps/web/src/app/\[locale\]/\(auth\)/"

Length of output: 139


Script:

#!/bin/bash
# First, find auth-related files
fd -t f . apps/web/src/app | grep -i "auth"

# Then search for async/await usage in the layout file specifically
fd "layout.tsx" apps/web/src/app | grep -i "auth" | xargs rg "async|await|fetch" -A 3

Length of output: 1535


Script:

#!/bin/bash
# Check imports and content of the auth layout
rg -A 10 "^import|'use server'" "apps/web/src/app/[locale]/(auth)/layout.tsx" || true

# Check for server actions in auth-related action files
rg -A 5 "'use server'" "apps/web/src/app/[locale]/(auth)/login/actions.ts" "apps/web/src/app/[locale]/(auth)/register/actions.ts" || true

Length of output: 1012

<div className="h-full p-3 sm:p-8">
<div className="mx-auto max-w-lg">{children}</div>
<div className="relative flex h-screen flex-col items-center justify-center bg-gray-100 p-2 lg:p-4">
<div className="z-10 flex w-full max-w-7xl flex-1 rounded-xl border bg-white shadow-sm lg:max-h-[720px] lg:p-2">
<div className="flex flex-1 flex-col gap-4 p-6 lg:p-16">
<div className="py-8">
<Logo className="mx-auto" />
</div>
<div className="flex h-full w-full flex-1 flex-col items-center justify-center">
<div className="w-full max-w-sm">{children}</div>
</div>
{isQuickCreateEnabled ? (
<div className="flex justify-center lg:hidden">
<QuickStartButton />
</div>
) : null}
</div>
{isQuickCreateEnabled ? (
<div className="relative hidden flex-1 flex-col justify-center rounded-lg border border-gray-100 bg-gray-50 lg:flex lg:p-16">
<div className="z-10 mx-auto w-full max-w-md">
<QuickStartWidget />
</div>
<DotPattern
cx={10}
cy={10}
className={cn(
"[mask-image:radial-gradient(400px_circle_at_top,white,transparent)]",
)}
/>
</div>
) : null}
</div>
</div>
);
}

export const metadata: Metadata = {
title: {
template: "%s - Rallly",
default: "Rallly",
},
};
21 changes: 21 additions & 0 deletions apps/web/src/app/[locale]/(auth)/login/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use server";

import { prisma } from "@rallly/database";
import { cookies } from "next/headers";

export async function setVerificationEmail(email: string) {
const count = await prisma.user.count({
where: {
email,
},
});

cookies().set("verification-email", email, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 15 * 60,
});

return count > 0;
}
21 changes: 21 additions & 0 deletions apps/web/src/app/[locale]/(auth)/login/components/auth-errors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client";
import { useSearchParams } from "next/navigation";
import { useTranslation } from "react-i18next";

export function AuthErrors() {
const { t } = useTranslation();
const searchParams = useSearchParams();
const error = searchParams?.get("error");
if (error === "OAuthAccountNotLinked") {
return (
<p className="text-destructive text-sm">
{t("accountNotLinkedDescription", {
defaultValue:
"A user with this email already exists. Please log in using the original method.",
})}
</p>
);
}

return null;
}
Loading
Loading