diff --git a/.gitignore b/.gitignore
index 3f7cb9d5..519457e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,7 @@ docs
.DS_Store
.vscode
.env
-.claude/settings.local.json
+.claude/
.pnp.*
.yarn/*
!.yarn/patches
diff --git a/next/.env.example b/next/.env.example
index 337ec3ce..0523d9df 100644
--- a/next/.env.example
+++ b/next/.env.example
@@ -3,4 +3,7 @@ ENVIRONMENT=development
PORT=3000
NEXT_PUBLIC_API_URL=http://localhost:1337
-PREVIEW_SECRET=preview_secret
\ No newline at end of file
+PREVIEW_SECRET=preview_secret
+
+# Must match BETTER_AUTH_SECRET in strapi/.env
+BETTER_AUTH_SECRET=tobemodified
\ No newline at end of file
diff --git a/next/app/[locale]/sign-in/page.tsx b/next/app/[locale]/sign-in/page.tsx
new file mode 100644
index 00000000..00460c04
--- /dev/null
+++ b/next/app/[locale]/sign-in/page.tsx
@@ -0,0 +1,11 @@
+import { AmbientColor } from '@/components/decorations/ambient-color';
+import { SignInForm } from '@/components/sign-in-form';
+
+export default function SignInPage() {
+ return (
+
+ );
+}
diff --git a/next/components/navbar/desktop-navbar.tsx b/next/components/navbar/desktop-navbar.tsx
index fe905a04..31efeab8 100644
--- a/next/components/navbar/desktop-navbar.tsx
+++ b/next/components/navbar/desktop-navbar.tsx
@@ -11,8 +11,10 @@ import { useState } from 'react';
import { LocaleSwitcher } from '../locale-switcher';
import { NavbarItem } from './navbar-item';
+import { UserMenu } from './user-menu';
import { Button } from '@/components/elements/button';
import { Logo } from '@/components/logo';
+import { useSession } from '@/lib/auth-client';
import { cn } from '@/lib/utils';
type Props = {
@@ -37,6 +39,7 @@ export const DesktopNavbar = ({
locale,
}: Props) => {
const { scrollY } = useScroll();
+ const { data: session } = useSession();
const [showBackground, setShowBackground] = useState(false);
@@ -90,18 +93,22 @@ export const DesktopNavbar = ({
- {rightNavbarItems.map((item, index) => (
-
- ))}
+ {session?.user ? (
+
+ ) : (
+ rightNavbarItems.map((item, index) => (
+
+ ))
+ )}
);
diff --git a/next/components/navbar/mobile-navbar.tsx b/next/components/navbar/mobile-navbar.tsx
index 27531592..70732718 100644
--- a/next/components/navbar/mobile-navbar.tsx
+++ b/next/components/navbar/mobile-navbar.tsx
@@ -3,12 +3,13 @@
import { useMotionValueEvent, useScroll } from 'framer-motion';
import { Link } from 'next-view-transitions';
import { useState } from 'react';
-import { IoIosMenu } from 'react-icons/io';
-import { IoIosClose } from 'react-icons/io';
+import { IoIosMenu, IoIosClose } from 'react-icons/io';
import { LocaleSwitcher } from '../locale-switcher';
+import { UserMenu } from './user-menu';
import { Button } from '@/components/elements/button';
import { Logo } from '@/components/logo';
+import { useSession } from '@/lib/auth-client';
import { cn } from '@/lib/utils';
type Props = {
@@ -33,6 +34,7 @@ export const MobileNavbar = ({
locale,
}: Props) => {
const [open, setOpen] = useState(false);
+ const { data: session } = useSession();
const { scrollY } = useScroll();
@@ -109,18 +111,22 @@ export const MobileNavbar = ({
))}
- {rightNavbarItems.map((item, index) => (
-
- ))}
+ {session?.user ? (
+
+ ) : (
+ rightNavbarItems.map((item, index) => (
+
+ ))
+ )}
)}
diff --git a/next/components/navbar/user-menu.tsx b/next/components/navbar/user-menu.tsx
new file mode 100644
index 00000000..5b608023
--- /dev/null
+++ b/next/components/navbar/user-menu.tsx
@@ -0,0 +1,42 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+import { useState } from 'react';
+
+import { Button } from '@/components/elements/button';
+import { signOut, useSession } from '@/lib/auth-client';
+
+export const UserMenu = ({ locale }: { locale: string }) => {
+ const router = useRouter();
+ const { data: session, isPending } = useSession();
+ const [isSigningOut, setIsSigningOut] = useState(false);
+
+ if (isPending) return null;
+
+ if (!session?.user) return null;
+
+ const displayName = session.user.name || session.user.email;
+
+ async function handleSignOut() {
+ setIsSigningOut(true);
+ await signOut();
+ setIsSigningOut(false);
+ router.push(`/${locale}`);
+ router.refresh();
+ }
+
+ return (
+
+
+ Hi {displayName}
+
+
+
+ );
+};
diff --git a/next/components/register.tsx b/next/components/register.tsx
index ec1d3585..b55935f5 100644
--- a/next/components/register.tsx
+++ b/next/components/register.tsx
@@ -4,13 +4,58 @@ import {
IconBrandGithubFilled,
IconBrandGoogleFilled,
} from '@tabler/icons-react';
-import React from 'react';
+import { Link } from 'next-view-transitions';
+import { useParams, useRouter } from 'next/navigation';
+import React, { useState } from 'react';
import { Container } from './container';
import { Button } from './elements/button';
import { Logo } from './logo';
+import { signIn, signUp } from '@/lib/auth-client';
export const Register = () => {
+ const router = useRouter();
+ const params = useParams<{ locale: string }>();
+ const locale = params?.locale ?? 'en';
+ const [name, setName] = useState('');
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState(null);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ async function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+ setError(null);
+ setIsSubmitting(true);
+
+ const { error: signUpError } = await signUp.email({
+ email,
+ password,
+ name: name || email,
+ });
+
+ setIsSubmitting(false);
+
+ if (signUpError) {
+ setError(signUpError.message ?? 'Sign up failed');
+ return;
+ }
+
+ router.push('/');
+ router.refresh();
+ }
+
+ async function handleSocial(provider: 'github' | 'google') {
+ setError(null);
+ const { error: socialError } = await signIn.social({
+ provider,
+ callbackURL: `/${locale}`,
+ });
+ if (socialError) {
+ setError(socialError.message ?? `${provider} sign-in failed`);
+ }
+ }
+
return (
@@ -18,30 +63,70 @@ export const Register = () => {
Sign up for LaunchPad
-
+
+ Already have an account?{' '}
+
+ Sign in
+
+
+
-