Basic MUI implemented
This commit is contained in:
parent
5e10df93b1
commit
65b3d4baa4
16 changed files with 922 additions and 263 deletions
14
errors.txt
Normal file
14
errors.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
> 124_webcalibre2@0.1.0 dev
|
||||
> next dev
|
||||
|
||||
▲ Next.js 16.1.1 (Turbopack)
|
||||
- Local: http://localhost:3000
|
||||
- Network: http://192.168.1.100:3000
|
||||
- Environments: .env.local, .env
|
||||
|
||||
✓ Starting...
|
||||
✓ Ready in 810ms
|
||||
GET / 500 in 7.5s (compile: 127ms, proxy.ts: 128ms, render: 7.3s)
|
||||
GET / 500 in 6.9s (compile: 1654µs, proxy.ts: 2ms, render: 6.9s)
|
||||
[?25h
|
||||
672
package-lock.json
generated
672
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,6 +9,12 @@
|
|||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/cache": "^11.14.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@mui/icons-material": "^7.3.6",
|
||||
"@mui/material": "^7.3.6",
|
||||
"@mui/material-nextjs": "^7.3.6",
|
||||
"next": "16.1.1",
|
||||
"next-auth": "^5.0.0-beta.30",
|
||||
"react": "19.2.3",
|
||||
|
|
|
|||
|
|
@ -1,41 +1,32 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Navbar from "@/components/navbar";
|
||||
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
|
||||
import ThemeRegistry from '@/components/ThemeRegistry';
|
||||
import Navbar from '@/components/layout/Navbar';
|
||||
import { auth } from "@/auth";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
export const metadata = {
|
||||
title: "WebCalibre 2",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
// Fetch the session server-side to prevent UI flickering
|
||||
const session = await auth();
|
||||
|
||||
return (
|
||||
<SessionProvider session={session}>
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<Navbar />
|
||||
<main className="p-5">{children}</main>
|
||||
</body>
|
||||
</html>
|
||||
</SessionProvider>
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body>
|
||||
<InitColorSchemeScript attribute="class" />
|
||||
|
||||
{/* ThemeRegistry handles the MUI Theme and Cache Provider */}
|
||||
<ThemeRegistry>
|
||||
{/* We pass the user object to the Navbar so it can show the profile pic */}
|
||||
<Navbar user={session?.user} />
|
||||
|
||||
<main style={{ minHeight: '100vh' }}>
|
||||
{children}
|
||||
</main>
|
||||
</ThemeRegistry>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import LoginButton from "@/components/LoginButton";
|
||||
|
||||
function Login(){
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[90vh]">
|
||||
<div className="p-5 border rounded-md max-w-[50%] flex gap-5 flex-col">
|
||||
<h1 className="text-center font-semibold text-xl">Login</h1>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusamus
|
||||
reprehenderit aut vitae consequuntur deleniti itaque totam eaque
|
||||
perspiciatis nulla magnam officiis blanditiis enim fuga perferendis
|
||||
deserunt, praesentium vel. Omnis, dolorum?
|
||||
</p>
|
||||
<LoginButton />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login
|
||||
|
|
@ -1,66 +1,15 @@
|
|||
import Image from "next/image";
|
||||
import styles from "./page.module.css";
|
||||
// src/app/page.tsx
|
||||
import { Container, Typography, Button, Box } from '@mui/material';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<main className={styles.main}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className={styles.intro}>
|
||||
<h1>To get started, edit the page.tsx file.</h1>
|
||||
<p>
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.ctas}>
|
||||
<a
|
||||
className={styles.primary}
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className={styles.secondary}
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<Container maxWidth="md">
|
||||
<Box sx={{ my: 4, textAlign: 'center' }}>
|
||||
<Typography variant="h2" component="h1" gutterBottom>
|
||||
WebCalibre 2
|
||||
</Typography>
|
||||
<Button variant="contained">Explore Library</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
"use client";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useState } from "react";
|
||||
|
||||
function LoginButton() {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handleLogin = async () => {
|
||||
setIsLoading(true);
|
||||
await signIn("microsoft-entra-id");
|
||||
setIsLoading(false);
|
||||
};
|
||||
return (
|
||||
<button
|
||||
onClick={() => handleLogin()}
|
||||
className="w-full bg-blue-800 text-white my-5 p-3 rounded-md hover:opacity-80 disabled:opacity-75"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? "Signing in..." : "Login With Microsoft"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default LoginButton;
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
"use client";
|
||||
import { signOut } from "next-auth/react";
|
||||
|
||||
function LogoutButton() {
|
||||
return (
|
||||
<button
|
||||
onClick={() => signOut()}
|
||||
className="w-full bg-blue-800 text-white p-3 rounded-md hover:opacity-80"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogoutButton;
|
||||
17
src/components/ThemeRegistry.tsx
Normal file
17
src/components/ThemeRegistry.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
'use client';
|
||||
|
||||
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import theme from '@/lib/theme';
|
||||
|
||||
export default function ThemeRegistry({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</AppRouterCacheProvider>
|
||||
);
|
||||
}
|
||||
22
src/components/layout/AppShell.tsx
Normal file
22
src/components/layout/AppShell.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// src/components/layout/AppShell.tsx
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { AppBar, Box, Toolbar } from '@mui/material';
|
||||
import Navbar from './Navbar';
|
||||
|
||||
const AppShell = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
||||
<AppBar position="fixed">
|
||||
<Toolbar>
|
||||
<Navbar />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
<Box component="main" sx={{ flexGrow: 1, p: 3, mt: 8 }}>
|
||||
{children} {/* This replaces <Outlet /> */}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
87
src/components/layout/Navbar.tsx
Normal file
87
src/components/layout/Navbar.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { AppBar, Toolbar, Typography, Button, Box, Container, Avatar, IconButton } from '@mui/material';
|
||||
import LibraryBooksIcon from '@mui/icons-material/LibraryBooks';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import Link from 'next/link';
|
||||
import { signIn, signOut } from "next-auth/react";
|
||||
|
||||
interface NavbarProps {
|
||||
user?: {
|
||||
name?: string | null;
|
||||
email?: string | null;
|
||||
image?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export default function Navbar({ user }: NavbarProps) {
|
||||
const isLoggedIn = !!user;
|
||||
|
||||
return (
|
||||
<AppBar position="sticky" elevation={0} sx={{ backgroundColor: 'white', color: 'text.primary', borderBottom: '1px solid #e0e0e0' }}>
|
||||
<Container maxWidth="lg">
|
||||
<Toolbar disableGutters>
|
||||
{/* Menu Icon for the future AppDrawer */}
|
||||
<IconButton edge="start" color="inherit" aria-label="menu" sx={{ mr: 2 }}>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
|
||||
<Typography
|
||||
variant="h6"
|
||||
component={Link}
|
||||
href="/"
|
||||
sx={{ fontWeight: 700, color: 'primary.main', textDecoration: 'none', flexGrow: 1 }}
|
||||
>
|
||||
WebCalibre 2
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
{isLoggedIn ? (
|
||||
<>
|
||||
{/* User Info: Name and Small Email */}
|
||||
<Box sx={{ textAlign: 'right', display: { xs: 'none', sm: 'block' } }}>
|
||||
<Typography variant="body2" fontWeight={600} sx={{ lineHeight: 1.2 }}>
|
||||
{user.name}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'text.secondary', display: 'block' }}>
|
||||
{user.email}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Profile Picture */}
|
||||
<Avatar
|
||||
src={user.image || ""}
|
||||
sx={{ width: 38, height: 38, border: '1px solid #eee' }}
|
||||
>
|
||||
{user.name?.charAt(0)}
|
||||
</Avatar>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
size="small"
|
||||
// We are still calling the function, just giving it a destination!
|
||||
onClick={() => signOut({ callbackUrl: '/' })}
|
||||
sx={{ borderRadius: '8px', textTransform: 'none' }}
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
// Login button triggers the Microsoft flow directly
|
||||
<Button
|
||||
variant="contained"
|
||||
disableElevation
|
||||
onClick={() => signIn("microsoft-entra-id", { callbackUrl: "/dashboard" })}
|
||||
sx={{ borderRadius: '8px', textTransform: 'none', px: 3 }}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import { auth } from "@/auth";
|
||||
import Link from "next/link";
|
||||
|
||||
async function Navbar() {
|
||||
const session = await auth();
|
||||
return (
|
||||
<nav className="flex items-center justify-between gap-4 px-5 py-2">
|
||||
<div>
|
||||
<Link href={"/"} className="font-bold">
|
||||
AppLogo
|
||||
</Link>
|
||||
</div>
|
||||
{session?.user?.email ? (
|
||||
<ul className="flex items-center gap-4 flex-row">
|
||||
<li>
|
||||
<Link href={"/dashboard"}>Dashboard</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href={"/profile"}>Profile</Link>
|
||||
</li>
|
||||
</ul>
|
||||
) : (
|
||||
<ul className="flex items-center gap-4 flex-row">
|
||||
<li>
|
||||
<Link href={"/login"}>Login</Link>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
export default Navbar;
|
||||
15
src/components/providers.tsx
Normal file
15
src/components/providers.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
'use client';
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
// ... other imports like ThemeProvider
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<SessionProvider>
|
||||
<AppRouterCacheProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</AppRouterCacheProvider>
|
||||
</SessionProvider>
|
||||
);
|
||||
}
|
||||
58
src/lib/theme.ts
Normal file
58
src/lib/theme.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { createTheme } from '@mui/material/styles';
|
||||
import { Roboto } from 'next/font/google';
|
||||
|
||||
// Load the font optimized for Next.js
|
||||
const roboto = Roboto({
|
||||
weight: ['300', '400', '500', '700'],
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
const theme = createTheme({
|
||||
// 1. Color Palette (Clean & Professional for a Library App)
|
||||
palette: {
|
||||
mode: 'light',
|
||||
primary: {
|
||||
main: '#1976d2', // Professional Blue
|
||||
},
|
||||
secondary: {
|
||||
main: '#9c27b0', // Purple for accents
|
||||
},
|
||||
background: {
|
||||
default: '#f4f6f8', // Light grey for the app background
|
||||
paper: '#ffffff',
|
||||
},
|
||||
},
|
||||
|
||||
// 2. Typography
|
||||
typography: {
|
||||
fontFamily: roboto.style.fontFamily,
|
||||
h6: {
|
||||
fontWeight: 600,
|
||||
},
|
||||
},
|
||||
|
||||
// 3. Component Defaults
|
||||
components: {
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
textTransform: 'none', // Prevents all-caps buttons
|
||||
borderRadius: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiPaper: {
|
||||
defaultProps: {
|
||||
elevation: 2,
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderRadius: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default theme;
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import {auth } from "@/auth";
|
||||
//export { auth as middleware } from "@/auth";
|
||||
|
||||
|
||||
|
||||
const protectedRoutes=["/dashboard","/profile"]
|
||||
const authPageRoutes=["/login"]
|
||||
const apiAuthPrefix ="/api/auth"
|
||||
|
||||
export default auth( (req) =>{
|
||||
const { nextUrl } = req;
|
||||
//const authData = await req.auth;
|
||||
const isLoggedIn = !!req.auth;
|
||||
|
||||
const path = nextUrl.pathname
|
||||
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix);
|
||||
const isProtectedRoute = protectedRoutes.includes(path);
|
||||
const isAuthPageRoute = authPageRoutes.includes(path);
|
||||
|
||||
// console.log({ authData });
|
||||
|
||||
if(isApiAuthRoute){
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
if(isProtectedRoute && !isLoggedIn){
|
||||
return NextResponse.redirect(new URL("/login", req.nextUrl));
|
||||
}
|
||||
|
||||
if(isLoggedIn && isAuthPageRoute){
|
||||
return NextResponse.redirect(new URL("/dashboard",req.nextUrl))
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
})
|
||||
|
||||
export const config = {
|
||||
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
||||
}
|
||||
30
src/proxy.ts
Normal file
30
src/proxy.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
const protectedRoutes = ["/dashboard", "/profile"];
|
||||
const apiAuthPrefix = "/api/auth";
|
||||
|
||||
export const proxy = auth((req) => {
|
||||
const { nextUrl } = req;
|
||||
const isLoggedIn = !!req.auth;
|
||||
|
||||
const path = nextUrl.pathname;
|
||||
const isApiAuthRoute = path.startsWith(apiAuthPrefix);
|
||||
const isProtectedRoute = protectedRoutes.includes(path);
|
||||
|
||||
// 1. Allow API Auth calls (Login/Logout/Callback)
|
||||
if (isApiAuthRoute) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// 2. CHANGED: Redirect to HOME (/) instead of /login if logged out
|
||||
if (isProtectedRoute && !isLoggedIn) {
|
||||
return NextResponse.redirect(new URL("/", nextUrl));
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
});
|
||||
|
||||
export const config = {
|
||||
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
||||
};
|
||||
Loading…
Reference in a new issue