144 lines
4.0 KiB
TypeScript
144 lines
4.0 KiB
TypeScript
import '../styles/globals.css';
|
|
import type {AppProps} from 'next/app';
|
|
import {useRouter} from 'next/router';
|
|
import React, {useEffect} from 'react';
|
|
import {Toaster} from 'sonner';
|
|
import {SWRConfig} from 'swr';
|
|
import {DefaultSeo} from 'next-seo';
|
|
import {Loader} from '@plunk/ui';
|
|
import {ActiveProjectProvider} from '../lib/contexts/ActiveProjectProvider';
|
|
import {useProjects} from '../lib/hooks/useProject';
|
|
import {useUser} from '../lib/hooks/useUser';
|
|
import {network} from '../lib/network';
|
|
import dayjs from 'dayjs';
|
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
|
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
|
|
|
// Configure dayjs plugins globally
|
|
dayjs.extend(relativeTime);
|
|
dayjs.extend(advancedFormat);
|
|
|
|
// Routes that don't require authentication
|
|
const PUBLIC_ROUTES = [
|
|
'/auth/login',
|
|
'/auth/signup',
|
|
'/auth/reset-password',
|
|
'/auth/verify-email',
|
|
'/unsubscribe',
|
|
'/subscribe',
|
|
'/manage',
|
|
];
|
|
|
|
// Routes that don't require a project
|
|
const NO_PROJECT_ROUTES = ['/projects/create'];
|
|
|
|
function App({Component, pageProps}: AppProps) {
|
|
return (
|
|
<>
|
|
<Component {...pageProps} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
function AuthGuard({children}: {children: React.ReactNode}) {
|
|
const {data: user, isLoading} = useUser();
|
|
const router = useRouter();
|
|
const isPublicRoute = PUBLIC_ROUTES.some(
|
|
route => router.pathname === route || router.pathname.startsWith(`${route}/`),
|
|
);
|
|
|
|
useEffect(() => {
|
|
// If not loading, no user, and trying to access a protected route, redirect to login
|
|
if (!isLoading && !user && !isPublicRoute) {
|
|
void router.push('/auth/login');
|
|
}
|
|
|
|
// If user is logged in and trying to access login/signup, redirect to home
|
|
if (!isLoading && user && (router.pathname === '/auth/login' || router.pathname === '/auth/signup')) {
|
|
void router.push('/');
|
|
}
|
|
}, [user, isLoading, router, isPublicRoute]);
|
|
|
|
// Show loading state while checking authentication (only for protected routes)
|
|
if (isLoading && !isPublicRoute) {
|
|
return <Loader message="Authenticating..." />;
|
|
}
|
|
|
|
// Don't render protected content if redirecting
|
|
if (!user && !isPublicRoute) {
|
|
return null;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function ProjectGuard({children}: {children: React.ReactNode}) {
|
|
const {data: projects, isLoading} = useProjects();
|
|
const router = useRouter();
|
|
const isNoProjectRoute = NO_PROJECT_ROUTES.includes(router.pathname);
|
|
|
|
useEffect(() => {
|
|
// If not loading, user has no projects, and not already on project creation page
|
|
if (!isLoading && projects && projects.length === 0 && !isNoProjectRoute) {
|
|
void router.push('/projects/create');
|
|
}
|
|
}, [projects, isLoading, router, isNoProjectRoute]);
|
|
|
|
// Show loading state while checking projects (only for routes that need a project)
|
|
if (isLoading && !isNoProjectRoute) {
|
|
return <Loader message="Loading project..." />;
|
|
}
|
|
|
|
// Don't render protected content if redirecting
|
|
if (!isLoading && projects && projects.length === 0 && !isNoProjectRoute) {
|
|
return null;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
/**
|
|
* Main app root component that houses all components
|
|
* @param props Default nextjs props
|
|
*/
|
|
export default function WithProviders(props: AppProps) {
|
|
return (
|
|
<SWRConfig
|
|
value={{
|
|
fetcher: (url: string) => network.fetch('GET', url),
|
|
shouldRetryOnError: false,
|
|
}}
|
|
>
|
|
<DefaultSeo titleTemplate="%s | Plunk" defaultTitle="Plunk | Email Platform Dashboard" />
|
|
<ActiveProjectProvider>
|
|
<Root {...props} />
|
|
</ActiveProjectProvider>
|
|
</SWRConfig>
|
|
);
|
|
}
|
|
|
|
function Root(props: AppProps) {
|
|
const router = useRouter();
|
|
const isPublicRoute = PUBLIC_ROUTES.some(
|
|
route => router.pathname === route || router.pathname.startsWith(`${route}/`),
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<Toaster position={'top-right'} />
|
|
|
|
<div>
|
|
<AuthGuard>
|
|
{isPublicRoute ? (
|
|
<App {...props} />
|
|
) : (
|
|
<ProjectGuard>
|
|
<App {...props} />
|
|
</ProjectGuard>
|
|
)}
|
|
</AuthGuard>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|