another step for constructing base project

This commit is contained in:
hamid
2026-06-17 22:53:49 +03:30
parent 5b4c0d183f
commit 5388bea320
76 changed files with 3836 additions and 1961 deletions
@@ -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>;
}
+54
View File
@@ -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 }));
}
-19
View File
@@ -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;
-42
View File
@@ -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;
-21
View File
@@ -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;
-13
View File
@@ -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;
-9
View File
@@ -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.
-24
View File
@@ -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
View File
@@ -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;
}
-18
View File
@@ -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;
-3
View File
@@ -1,3 +0,0 @@
import HomePage from './home/page';
export default HomePage;