Skip to content

Commit

Permalink
feat: add success particles
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbrusegard committed Oct 31, 2024
1 parent 7f5fc36 commit 53d4e94
Show file tree
Hide file tree
Showing 17 changed files with 464 additions and 125 deletions.
Binary file modified bun.lockb
Binary file not shown.
14 changes: 8 additions & 6 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
"goToHomepage": "Return to homepage",
"tryAgain": "Try again"
},
"auth": {
"welcome": "Welcome!",
"description": "Is it your first time here? Use Feide",
"signInWith": "Sign in with",
"hackerspaceAccount": "Hackerspace Account",
"success": "Success!",
"home": "Home"
},
"layout": {
"hackerspaceHome": "Hackerspace homepage",
"navigationMenu": "Navigation menu",
Expand Down Expand Up @@ -63,12 +71,6 @@
"copyright": "Copyright",
"allRightsReserved": "All rights reserved"
},
"signIn": {
"welcome": "Welcome!",
"description": "Is it your first time here? Use Feide",
"signInWith": "Sign in with",
"hackerspaceAccount": "Hackerspace Account"
},
"news": {
"title": "News",
"internalArticle": "This is an internal article",
Expand Down
14 changes: 8 additions & 6 deletions messages/no.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
"goToHomepage": "Gå tilbake til hjemmesiden",
"tryAgain": "Prøv igjen"
},
"auth": {
"welcome": "Velkommen!",
"description": "Er du her for første gang? Bruk Feide",
"signInWith": "Logg inn med",
"hackerspaceAccount": "Hackerspace-konto",
"success": "Suksess!",
"home": "Hjem"
},
"layout": {
"hackerspaceHome": "Hackerspace hjemmeside",
"navigationMenu": "Navigasjonsmeny",
Expand Down Expand Up @@ -63,12 +71,6 @@
"copyright": "Opphavsrett",
"allRightsReserved": "Alle rettigheter reservert"
},
"signIn": {
"welcome": "Velkommen!",
"description": "Er du her for første gang? Bruk Feide",
"signInWith": "Logg inn med",
"hackerspaceAccount": "Hackerspace-konto"
},
"news": {
"title": "Nyheter",
"internalArticle": "Dette er en intern artikkel",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"reading-time": "^1.5.0",
"superjson": "^2.2.1",
"tailwind-merge": "^2.5.4",
"tsparticles": "^3.5.0",
"vaul": "^1.1.0",
"zod": "^3.23.8"
},
Expand Down
60 changes: 60 additions & 0 deletions src/app/[locale]/auth/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { setRequestLocale } from 'next-intl/server';
import { getTranslations } from 'next-intl/server';

import { PendingBar, PendingProvider } from '@/components/auth/PendingBar';
import { LogoLink } from '@/components/layout/LogoLink';
import { Main } from '@/components/layout/Main';
import { AnimatePresenceProvider } from '@/components/providers/AnimatePresenceProvider';
import { Card, CardHeader } from '@/components/ui/Card';

type AuthLayoutProps = {
children: React.ReactNode;
params: Promise<{ locale: string }>;
};

export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'layout' });

return {
title: t('signIn'),
};
}

export default async function AuthLayout({
children,
params,
}: AuthLayoutProps) {
const { locale } = await params;
setRequestLocale(locale);
const { auth, ui } = await getMessages();
return (
<Main className='flex h-full items-center justify-center'>
<Card className='~p-3/6 relative w-full max-w-md overflow-hidden'>
<PendingProvider>
<PendingBar />
<CardHeader className='flex items-center justify-between py-2'>
<LogoLink logoClassName='h-7 w-7' titleClassName='text-lg' />
</CardHeader>
<div className='h-96'>
<NextIntlClientProvider
messages={{ auth, ui } as Pick<Messages, 'auth' | 'ui'>}
>
<AnimatePresenceProvider className='absolute left-0 flex w-full justify-center'>
<div className='~px-3/6 h-96 w-full max-w-md overflow-hidden'>
{children}
</div>
</AnimatePresenceProvider>
</NextIntlClientProvider>
</div>
</PendingProvider>
</Card>
</Main>
);
}
34 changes: 34 additions & 0 deletions src/app/[locale]/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { FeideLogo } from '@/components/assets/logos/FeideLogo';
import { Button } from '@/components/ui/Button';
import { Separator } from '@/components/ui/Separator';
import { FingerprintIcon } from 'lucide-react';
import { getTranslations, setRequestLocale } from 'next-intl/server';

export default async function SignInPage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations('auth');
return (
<div className='flex h-full flex-col transition-opacity duration-500'>
<div className='mb-4 space-y-2 text-center'>
<h1 className='text-4xl'>{t('welcome')}</h1>
<p className='text-sm'>{t('description')}</p>
</div>
<Separator />
<div className='absolute bottom-0 space-y-4'>
<p className='text-center font-montserrat'>{t('signInWith')}</p>
<Button className='w-full bg-[#3FACC2]/90 hover:bg-[#3FACC2] dark:bg-[#222832] hover:dark:bg-[#222832]/90'>
<FeideLogo title='Feide' />
</Button>
<Button className='flex w-full gap-1 bg-primary/80 font-montserrat font-semibold text-black text-md dark:bg-primary/50 dark:text-white hover:dark:bg-primary/40'>
<FingerprintIcon className='text-accent dark:text-primary' />
{t('hackerspaceAccount')}
</Button>
</div>
</div>
);
}
21 changes: 0 additions & 21 deletions src/app/[locale]/auth/sign-in/layout.tsx

This file was deleted.

62 changes: 0 additions & 62 deletions src/app/[locale]/auth/sign-in/page.tsx

This file was deleted.

34 changes: 34 additions & 0 deletions src/app/[locale]/auth/success/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { SuccessParticles } from '@/components/auth/SuccessParticles';
import { Button } from '@/components/ui/Button';
import { Separator } from '@/components/ui/Separator';
import { Link } from '@/lib/locale/navigation';
import { getTranslations, setRequestLocale } from 'next-intl/server';

export default async function SuccessPage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations('auth');
return (
<div className='flex h-full flex-col transition-opacity duration-500'>
<div className='mb-4 space-y-2 text-center'>
<h1 className='text-4xl'>{t('success')}</h1>
<p className='text-sm'>
{
'you are now a member of Hackerspace. Now you can finally start praying to our one true leader'
}
</p>
</div>
<Separator />
<div className='absolute bottom-0 space-y-4'>
<Button asChild>
<Link href='/'>{t('home')}</Link>
</Button>
</div>
<SuccessParticles />
</div>
);
}
21 changes: 21 additions & 0 deletions src/app/[locale]/auth/template.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use client';

import { type HTMLMotionProps, m } from 'framer-motion';

export default function AuthTemplate({
children,
}: {
children: React.ReactNode;
}) {
return (
<m.div
initial={{ x: '100%', opacity: 0 }}
animate={{ x: '0%', opacity: 1 }}
exit={{ x: '-100%', opacity: 0 }}
transition={{ ease: [0.4, 0, 0.2, 1], duration: 0.5 }}
{...({ className: 'flex h-full flex-col p-6' } as HTMLMotionProps<'div'>)}
>
{children}
</m.div>
);
}
71 changes: 71 additions & 0 deletions src/components/auth/PendingBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use client';

import { type HTMLMotionProps, m } from 'framer-motion';
import { createContext, useContext, useEffect, useState } from 'react';

type PendingContextType = {
isPending: boolean;
setPending: (pending: boolean) => void;
};

const PendingContext = createContext<PendingContextType | undefined>(undefined);

type PendingProviderProps = {
children: React.ReactNode;
};

function PendingProvider({ children }: PendingProviderProps) {
const [isPending, setPending] = useState(false);

return (
<PendingContext.Provider value={{ isPending, setPending }}>
{children}
</PendingContext.Provider>
);
}

const usePending = () => {
const context = useContext(PendingContext);
if (!context) {
throw new Error('usePending must be used within a PendingProvider');
}
return context;
};

function PendingBar() {
const { isPending } = usePending();
const [visible, setVisible] = useState(false);

useEffect(() => {
if (isPending) {
setVisible(true);
} else {
const timeout = setTimeout(() => setVisible(false), 300);
return () => clearTimeout(timeout);
}
}, [isPending]);

if (!visible) return null;

return (
<div
className={`-top-1 absolute left-0 h-2 w-full transition-opacity duration-300 ${
isPending ? 'opacity-100' : 'opacity-0'
}`}
>
<m.div
animate={{ x: ['-100%', '300%'] }}
transition={{
duration: 1,
ease: 'easeInOut',
repeat: Number.POSITIVE_INFINITY,
}}
{...({
className: 'h-2 w-1/3 rounded-sm bg-primary/80',
} as HTMLMotionProps<'div'>)}
/>
</div>
);
}

export { PendingBar, PendingProvider, usePending };
Loading

0 comments on commit 53d4e94

Please sign in to comment.