another step for constructing base project
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
'use client';
|
||||
import type { ReactNode } from 'react';
|
||||
import { PrivateLayout } from '@/layout';
|
||||
|
||||
/*
|
||||
* Wraps all private (authenticated) routes with the application shell:
|
||||
* TopBar, SideBar, and main content area.
|
||||
*
|
||||
* Authentication enforcement belongs in middleware — this layout only
|
||||
* applies the visual structure. Add a middleware matcher for this group
|
||||
* when real session-based auth is introduced.
|
||||
*/
|
||||
export default function PrivateRouteLayout({ children }: { children: ReactNode }) {
|
||||
return <PrivateLayout>{children}</PrivateLayout>;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function HomePage() {
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
'use client';
|
||||
import type { ReactNode } from 'react';
|
||||
import { PublicLayout } from '@/layout';
|
||||
|
||||
/*
|
||||
* Wraps all public (unauthenticated) routes — login, registration, landing pages.
|
||||
* Renders a minimal shell without the authenticated sidebar/topbar.
|
||||
*/
|
||||
export default function PublicRouteLayout({ children }: { children: ReactNode }) {
|
||||
return <PublicLayout>{children}</PublicLayout>;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { setRequestLocale, getMessages } from 'next-intl/server';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { getThemeMode } from '@/lib/cookies/server';
|
||||
import { AppStoreProvider } from '@/store';
|
||||
import { ThemeProvider, getDirection } from '@/theme';
|
||||
import { routing } from '@/i18n/routing';
|
||||
|
||||
/*
|
||||
* This layout is the correct place for NextIntlClientProvider because it
|
||||
* receives the locale directly from URL params — no header reads, no caching
|
||||
* surprises. setRequestLocale(locale) is called first so that any server
|
||||
* component deeper in the tree that calls getLocale() / getTranslations()
|
||||
* gets the right locale from React.cache instead of falling through to the
|
||||
* header fallback.
|
||||
*
|
||||
* getMessages({ locale }) passes the locale explicitly so the config
|
||||
* callback in src/i18n/request.ts receives it via requestLocale directly
|
||||
* (Promise.resolve(locale)) rather than reading it from the cache — an
|
||||
* extra layer of defense against cache-ordering races.
|
||||
*/
|
||||
export default async function LocaleLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
|
||||
const safeLocale = routing.locales.includes(locale as (typeof routing.locales)[number])
|
||||
? locale
|
||||
: routing.defaultLocale;
|
||||
|
||||
setRequestLocale(safeLocale);
|
||||
|
||||
const messages = await getMessages({ locale: safeLocale });
|
||||
const { defaultMode } = await getThemeMode();
|
||||
const dir = getDirection(safeLocale);
|
||||
|
||||
return (
|
||||
<NextIntlClientProvider locale={safeLocale} messages={messages}>
|
||||
<AppStoreProvider>
|
||||
<ThemeProvider dir={dir} defaultMode={defaultMode}>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</AppStoreProvider>
|
||||
</NextIntlClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return routing.locales.map((locale) => ({ locale }));
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import { NextPage } from 'next';
|
||||
|
||||
/**
|
||||
* Renders About Application page
|
||||
* @page About
|
||||
*/
|
||||
const AboutPage: NextPage = () => {
|
||||
return (
|
||||
<Stack spacing={2} padding={2}>
|
||||
<Stack>
|
||||
<Typography variant="h3">About application</Typography>
|
||||
<Typography variant="body1">Balinyaar is a Next.js (App Router) application built with Material UI.</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutPage;
|
||||
@@ -1,42 +0,0 @@
|
||||
'use client';
|
||||
import { Stack } from '@mui/material';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { AppButton } from '@/components';
|
||||
import { useAppStore } from '@/store';
|
||||
import { useEventLogout } from '@/hooks';
|
||||
import { sessionStorageSet } from '@/utils';
|
||||
|
||||
/**
|
||||
* Renders login form for user to authenticate
|
||||
* @component LoginForm
|
||||
*/
|
||||
const LoginForm = () => {
|
||||
const router = useRouter();
|
||||
const [, dispatch] = useAppStore();
|
||||
const onLogout = useEventLogout();
|
||||
|
||||
const onLogin = () => {
|
||||
// TODO: AUTH: Sample of access token store, replace next line in real application
|
||||
sessionStorageSet('access_token', 'TODO:_save-real-access-token-here');
|
||||
|
||||
dispatch({ type: 'LOG_IN' });
|
||||
router.replace('/'); // Redirect to home page without ability to go back
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack alignItems="center" spacing={2} padding={2}>
|
||||
<Stack>Put form controls or add social login buttons here...</Stack>
|
||||
|
||||
<Stack direction="row">
|
||||
<AppButton color="success" onClick={onLogin}>
|
||||
Emulate User Login
|
||||
</AppButton>
|
||||
<AppButton color="warning" onClick={onLogout}>
|
||||
Logout User
|
||||
</AppButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Metadata, NextPage } from 'next';
|
||||
import LoginForm from './LoginForm';
|
||||
|
||||
/**
|
||||
* User Login page
|
||||
* @page Login
|
||||
*/
|
||||
const LoginPage: NextPage = () => {
|
||||
return (
|
||||
<>
|
||||
<LoginForm />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Login - Balinyaar',
|
||||
description: 'Balinyaar web application',
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
@@ -1,13 +0,0 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
/**
|
||||
* Redirects to default Auth page
|
||||
* @page Auth
|
||||
* @redirect /auth
|
||||
*/
|
||||
const AuthPage = () => {
|
||||
redirect('/auth/login');
|
||||
// return <div>Auth Page</div>;
|
||||
};
|
||||
|
||||
export default AuthPage;
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Metadata } from 'next';
|
||||
import LoginPage from '../login/page';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Signup - Balinyaar',
|
||||
description: 'Balinyaar web application',
|
||||
};
|
||||
|
||||
export default LoginPage; // Reuses the Login page for now
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,24 +0,0 @@
|
||||
import { Metadata, NextPage } from 'next';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Balinyaar',
|
||||
description: 'Balinyaar web application',
|
||||
};
|
||||
|
||||
/**
|
||||
* Main page of the Application
|
||||
* @page Home
|
||||
*/
|
||||
const Home: NextPage = () => {
|
||||
return (
|
||||
<Stack spacing={2} padding={2}>
|
||||
<Stack>
|
||||
<Typography variant="h3">Welcome to Balinyaar</Typography>
|
||||
<Typography variant="body1">This is the home page of the application.</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
+69
-20
@@ -1,35 +1,84 @@
|
||||
import { FunctionComponent, PropsWithChildren } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Metadata, Viewport } from 'next';
|
||||
import { SimplePaletteColorOptions } from '@mui/material';
|
||||
import { AppStoreProvider } from '@/store';
|
||||
import defaultTheme, { ThemeProvider } from '@/theme';
|
||||
import CurrentLayout from '@/layout';
|
||||
import { Space_Grotesk } from 'next/font/google';
|
||||
import localFont from 'next/font/local';
|
||||
import { headers } from 'next/headers';
|
||||
import { getThemeMode } from '@/lib/cookies/server';
|
||||
import { ColorSchemeScript, getDirection } from '@/theme';
|
||||
import { BRAND } from '@/theme/colors';
|
||||
import { routing } from '@/i18n/routing';
|
||||
import './globals.css';
|
||||
import '@/theme/tokens.css';
|
||||
|
||||
const THEME_COLOR = (defaultTheme.palette?.primary as SimplePaletteColorOptions)?.main || '#FFFFFF';
|
||||
// EN brand font — loaded for all locales so the CSS variable is always defined
|
||||
const spaceGrotesk = Space_Grotesk({
|
||||
subsets: ['latin'],
|
||||
weight: ['400', '500', '600', '700'],
|
||||
display: 'swap',
|
||||
variable: '--font-space-grotesk',
|
||||
});
|
||||
|
||||
// FA brand font — Mikhak, a free Persian typeface
|
||||
const mikhak = localFont({
|
||||
src: [
|
||||
{ path: './fonts/Mikhak-Regular.woff2', weight: '400', style: 'normal' },
|
||||
{ path: './fonts/Mikhak-Medium.woff2', weight: '500', style: 'normal' },
|
||||
{ path: './fonts/Mikhak-Bold.woff2', weight: '700', style: 'normal' },
|
||||
],
|
||||
display: 'swap',
|
||||
variable: '--font-mikhak',
|
||||
});
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: THEME_COLOR,
|
||||
themeColor: BRAND.teal,
|
||||
};
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Balinyaar',
|
||||
title: 'Balinyaar | بالینیار',
|
||||
description: 'Balinyaar web application',
|
||||
manifest: '/site.webmanifest',
|
||||
};
|
||||
|
||||
const RootLayout: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||
export default async function RootLayout({ children }: { children: ReactNode }) {
|
||||
/*
|
||||
* The root layout is intentionally kept as a lean HTML shell.
|
||||
* NextIntlClientProvider, ThemeProvider, and AppStoreProvider live in
|
||||
* [locale]/layout.tsx so they always receive the correct locale from URL
|
||||
* params — independent of whether this root layout is rendered statically
|
||||
* or dynamically.
|
||||
*
|
||||
* We still read x-next-intl-locale here for lang/dir on <html> so that
|
||||
* screen readers and CSS direction are correct on real requests.
|
||||
* During build-time pre-rendering (no request context) we fall back to
|
||||
* defaultLocale — this only affects the static HTML shell; the live
|
||||
* request always re-renders with the correct locale header.
|
||||
*/
|
||||
let locale: string = routing.defaultLocale;
|
||||
try {
|
||||
const hdrs = await headers();
|
||||
const headerLocale = hdrs.get('x-next-intl-locale');
|
||||
if (headerLocale && routing.locales.includes(headerLocale as (typeof routing.locales)[number])) {
|
||||
locale = headerLocale;
|
||||
}
|
||||
} catch {
|
||||
// No request context (build-time prerendering) — use defaultLocale
|
||||
}
|
||||
|
||||
const dir = getDirection(locale);
|
||||
const { colorScheme } = await getThemeMode();
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<AppStoreProvider>
|
||||
<ThemeProvider>
|
||||
<CurrentLayout>{children}</CurrentLayout>
|
||||
</ThemeProvider>
|
||||
</AppStoreProvider>
|
||||
</body>
|
||||
<html
|
||||
lang={locale}
|
||||
dir={dir}
|
||||
className={`${spaceGrotesk.variable} ${mikhak.variable}`}
|
||||
data-mui-color-scheme={colorScheme}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<head>
|
||||
<ColorSchemeScript />
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootLayout;
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Stack } from '@mui/material';
|
||||
import { NextPage } from 'next';
|
||||
import { AppAlert, UserInfo } from '../../components';
|
||||
|
||||
/**
|
||||
* Renders User Profile Page
|
||||
* @page Me
|
||||
*/
|
||||
const MeAkaProfilePage: NextPage = () => {
|
||||
return (
|
||||
<Stack spacing={2} padding={2}>
|
||||
<AppAlert severity="warning">This page is under construction</AppAlert>
|
||||
<UserInfo showAvatar />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default MeAkaProfilePage;
|
||||
@@ -1,3 +0,0 @@
|
||||
import HomePage from './home/page';
|
||||
|
||||
export default HomePage;
|
||||
Reference in New Issue
Block a user