Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions app/(app)/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,21 @@ export default function Home() {
};

return (
<div className="min-h-screen bg-bg-primary transition-colors shell-desktop:flex">
<div
className={cn(
"bg-bg-primary transition-colors",
"h-dvh max-h-dvh overflow-hidden flex flex-col",
"shell-desktop:h-auto shell-desktop:max-h-none shell-desktop:min-h-screen shell-desktop:overflow-visible shell-desktop:flex-row"
)}
>
<Sidebar />

<div className={cn("flex min-h-screen flex-1 flex-col main-with-bottom-nav min-w-0", shellSidebarInset)}>
<div
className={cn(
"flex flex-1 flex-col min-h-0 min-w-0 main-with-bottom-nav",
shellSidebarInset
)}
>
{/* Mobile header — switcher inside the green band */}
<header className={cn(shellMobileOnly, "bg-line-green text-white safe-top shrink-0")}>
<div className="px-5 pt-5 pb-4 flex flex-col gap-4">
Expand Down Expand Up @@ -291,7 +302,7 @@ export default function Home() {
</div>
</header>

<main className="relative z-[1] flex-1 min-w-0 overflow-x-clip bg-bg-secondary px-5 pt-5 pb-6 rounded-t-2xl -mt-3 shell-desktop:mt-0 shell-desktop:rounded-none shell-desktop:px-8 shell-desktop:pt-8 shell-desktop:pb-8 xl:px-12 xl:pb-12">
<main className="relative z-[1] flex-1 min-h-0 overflow-y-auto overscroll-contain min-w-0 bg-bg-secondary px-5 pt-5 pb-6 rounded-t-2xl -mt-3 shell-desktop:mt-0 shell-desktop:rounded-none shell-desktop:px-8 shell-desktop:pt-8 shell-desktop:pb-8 xl:px-12 xl:pb-12">
<HomeQuickMenu className={cn("mb-6", shellMobileOnly)} />

<HomeDashboardSection
Expand Down
4 changes: 1 addition & 3 deletions app/(app)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ export default function AppRouteLayout({
children: React.ReactNode;
}) {
return (
<MotionProvider>
<div className="min-h-screen">{children}</div>
</MotionProvider>
<MotionProvider>{children}</MotionProvider>
);
}
2 changes: 1 addition & 1 deletion app/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ function LoginPageContent() {
value={studentId}
onChange={(e) => setStudentId(e.target.value.replace(/\D/g, "").slice(0, 5))}
className={authInputClassName("studentId")}
placeholder="12345"
placeholder="เช่น 12345"
autoComplete="username"
required
/>
Expand Down
2 changes: 1 addition & 1 deletion app/auth/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export default function RegisterPage() {
aria-invalid={errors.studentId ? true : undefined}
aria-describedby={errors.studentId ? fieldErrorId("studentId") : undefined}
className={authInputClassName("studentId", errors.studentId)}
placeholder="12345"
placeholder="เช่น 12345"
autoFocus
/>
<FieldValidationMessage
Expand Down
6 changes: 6 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,12 @@ body {
padding-bottom: calc(var(--bottom-nav-height) + env(safe-area-inset-bottom, 0px));
}

@media (min-width: 768px) and (orientation: landscape) and (min-height: 600px) {
.main-with-bottom-nav {
padding-bottom: 0;
}
}

/* Extra scroll room when a sticky form footer sits above bottom nav (mobile) */
.form-sticky-footer-padding {
padding-bottom: calc(var(--bottom-nav-height) + 4.5rem + env(safe-area-inset-bottom, 0px));
Expand Down
4 changes: 3 additions & 1 deletion components/agent/agent-chat-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ function AgentChatInner() {
export function AgentChatShell() {
return (
<ChatProvider>
<AgentChatInner />
<div className="flex flex-1 flex-col min-h-0 h-full overflow-hidden">
<AgentChatInner />
</div>
</ChatProvider>
);
}
11 changes: 8 additions & 3 deletions components/layout/assistant-app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ export function AssistantAppShell({
mainClassName,
}: AssistantAppShellProps) {
return (
<div className={cn("min-h-screen bg-bg-secondary transition-colors", className)}>
<div className="assistant-desktop:hidden h-[100dvh] flex flex-col w-full min-w-0 overflow-hidden">
<div
className={cn(
"h-dvh max-h-dvh overflow-hidden bg-bg-secondary flex flex-col transition-colors",
className
)}
>
<div className="assistant-desktop:hidden flex flex-1 flex-col min-h-0 min-w-0 overflow-hidden">
{children}
</div>

<div
className={cn(
"hidden assistant-desktop:flex h-screen w-full overflow-hidden min-h-0 min-w-0",
"hidden assistant-desktop:flex flex-1 flex-col min-h-0 min-w-0 overflow-hidden",
shellAssistantDesktopPadding,
mainClassName
)}
Expand Down
8 changes: 1 addition & 7 deletions components/not-found/not-found-home-button.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
"use client";

import { Home } from "lucide-react";
import { useAuth } from "@/contexts/auth-context";

export function NotFoundHomeButton() {
const { user, loading } = useAuth();
const href = !loading && user ? "/home" : "/";

return (
<a
href={href}
href="/"
className="inline-flex w-full items-center justify-center gap-2 rounded-xl bg-line-green px-6 py-3.5 font-medium text-white transition-colors hover:bg-line-green-hover"
>
<Home className="w-5 h-5" />
Expand Down
30 changes: 30 additions & 0 deletions components/providers/full-app-providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client";

import { AuthProvider } from "@/contexts/auth-context";
import { AppModeProvider } from "@/contexts/app-mode-context";
import { DataProvider } from "@/contexts/DataContext";
import AuthGuard from "@/components/auth/auth-guard";
import { BfcacheRestoreHandler } from "@/components/bfcache-restore-handler";

function AppShell({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-bg-secondary transition-colors">
<div className="w-full bg-bg-primary transition-colors">{children}</div>
</div>
);
}

export function FullAppProviders({ children }: { children: React.ReactNode }) {
return (
<AuthProvider>
<AppModeProvider>
<BfcacheRestoreHandler />
<DataProvider>
<AuthGuard>
<AppShell>{children}</AppShell>
</AuthGuard>
</DataProvider>
</AppModeProvider>
</AuthProvider>
);
}
25 changes: 5 additions & 20 deletions components/providers/setup-aware-providers.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,23 @@
"use client";

import { usePathname } from "next/navigation";
import { AuthProvider } from "@/contexts/auth-context";
import { AppModeProvider } from "@/contexts/app-mode-context";
import { DataProvider } from "@/contexts/DataContext";
import AuthGuard from "@/components/auth/auth-guard";
import { BfcacheRestoreHandler } from "@/components/bfcache-restore-handler";
import { isSetupPublicPath } from "@/lib/auth-routes";
import { isLightweightShellPath } from "@/lib/auth-routes";
import { FullAppProviders } from "@/components/providers/full-app-providers";

function AppShell({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-bg-secondary transition-colors">
<div className="w-full min-h-screen bg-bg-primary transition-colors">{children}</div>
<div className="w-full bg-bg-primary transition-colors">{children}</div>
</div>
);
}

export function SetupAwareProviders({ children }: { children: React.ReactNode }) {
const pathname = usePathname() ?? "";

if (isSetupPublicPath(pathname)) {
if (isLightweightShellPath(pathname)) {
return <AppShell>{children}</AppShell>;
}

return (
<AuthProvider>
<AppModeProvider>
<BfcacheRestoreHandler />
<DataProvider>
<AuthGuard>
<AppShell>{children}</AppShell>
</AuthGuard>
</DataProvider>
</AppModeProvider>
</AuthProvider>
);
return <FullAppProviders>{children}</FullAppProviders>;
}
10 changes: 5 additions & 5 deletions lib/auth-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export const AUTH_COPY = {
schoolPasswordField: "รหัสผ่านจากโรงเรียน",

/** Hints */
passwordRules: "อย่างน้อย 8 ตัว มีทั้งตัวอักษรและตัวเลข",
pinQuickUnlock: "ใช้แทนรหัสผ่านเมื่อเปิดแอปบนเครื่องนี้",
passwordRules: "อย่างน้อย 8 ตัวอักษร โดยต้องมีทั้งตัวอักษร (A-Z/a-z) และตัวเลข (0-9)",
pinQuickUnlock: "ใช้เพื่อเข้าสู่ระบบอย่างรวดเร็ว หากเคยล็อกอินด้วยรหัสผ่านแล้ว",
registerStudentIdHint:
"กรอกเลขที่โรงเรียนแจ้ง ระบบจะแสดงชื่อและห้องให้ตรวจสอบก่อนดำเนินการต่อ",
"กรอกเลขประจำตัวนักเรียนของคุณ",
schoolPasswordHint:
"รหัสชั่วคราวที่โรงเรียนแจก — สำหรับบัญชีที่ยังไม่เคยตั้งรหัสผ่านเอง",
profileMismatchHint: "ข้อมูลไม่ตรง? ลอง \"แก้ไขเลขประจำตัว\" ด้านล่าง หรือติดต่อผู้ดูแลระบบ",
profileMismatchHint: "ข้อมูลไม่ตรง? ลองกด \"แก้ไขเลขประจำตัว\" ด้านล่าง หรือติดต่อผู้ดูแลระบบ",

/** Primary actions */
signIn: "เข้าสู่ระบบ",
Expand Down Expand Up @@ -92,7 +92,7 @@ export const AUTH_COPY = {
/** Register flow */
registerTitle: "เริ่มใช้งาน",
registerSubtitle: "สมัครสมาชิก",
registerDescription: "สมัครสมาชิกเพื่อใช้งานระบบของโรงเรียน",
registerDescription: "สมัครสมาชิกเพื่อเริ่มต้น",
registerStepStudentId: "เลขประจำตัว",
registerStepConfirm: "ยืนยันตัวตน",
registerStepPassword: "รหัสผ่าน",
Expand Down
16 changes: 16 additions & 0 deletions lib/auth-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ export function isAuthPublicPath(pathname: string): boolean {
);
}

/** Routes that skip AuthProvider / DataProvider / AuthGuard (no useAuth on page) */
export const LIGHTWEIGHT_SHELL_EXACT_PATHS = [
"/",
AUTH_ROUTES.hub,
AUTH_ROUTES.register,
AUTH_ROUTES.forgotPin,
AUTH_ROUTES.resetPassword,
] as const;

export function isLightweightShellPath(pathname: string): boolean {
return (
isSetupPublicPath(pathname) ||
(LIGHTWEIGHT_SHELL_EXACT_PATHS as readonly string[]).includes(pathname)
);
}

export function resolvePostLoginPath(payload: {
mustChangePassword?: boolean;
mustSetupPin?: boolean;
Expand Down