added splash page

This commit is contained in:
makearmy 2025-09-30 22:38:25 -04:00
parent 41614a96cd
commit 6abe450f1d
2 changed files with 66 additions and 141 deletions

View file

@ -1,129 +1,41 @@
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import Link from "next/link";
// app/page.tsx
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import SignIn from "@/app/auth/sign-in/sign-in";
import SignUp from "@/app/auth/sign-up/sign-up";
export default async function HomePage() {
// If already signed in, go straight to the app
const ck = await cookies();
const at = ck.get("ma_at")?.value;
if (at) redirect("/portal");
export default function Home() {
return (
<main className="min-h-screen p-8 bg-background text-foreground">
<h1 className="text-3xl font-bold mb-6">Laser Everything Community Database</h1>
<main className="mx-auto max-w-5xl px-4 py-12">
<section className="mb-10 text-center">
<h1 className="text-3xl font-bold tracking-tight">MakeArmy</h1>
<p className="mt-2 text-base text-muted-foreground">
Free to use. Manage laser rigs, settings, and projectsall in one place.
</p>
</section>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<Card>
<CardContent className="p-6">
<h2 className="text-xl font-semibold mb-2">Fiber Laser Settings</h2>
<p className="mb-4 text-sm text-muted-foreground">
Browse and submit settings for fiber laser engraving.
</p>
<Link href="/fiber-settings">
<Button>View Settings</Button>
</Link>
</CardContent>
</Card>
<section className="grid gap-6 md:grid-cols-2">
<div className="rounded-lg border p-6">
<h2 className="mb-3 text-lg font-semibold">Create an account</h2>
{/* Uses your existing sign-up component */}
<SignUp nextPath="/portal" />
</div>
<Card>
<CardContent className="p-6">
<h2 className="text-xl font-semibold mb-2">CO2 Galvo Settings</h2>
<p className="mb-4 text-sm text-muted-foreground">
Settings for CO2 Galvo laser machines.
</p>
<Link href="/co2-galvo-settings">
<Button>View Settings</Button>
</Link>
</CardContent>
</Card>
<div className="rounded-lg border p-6">
<h2 className="mb-3 text-lg font-semibold">Sign in</h2>
{/* Uses your existing sign-in component */}
<SignIn nextPath="/portal" reauth={false} />
</div>
</section>
<Card>
<CardContent className="p-6">
<h2 className="text-xl font-semibold mb-2">CO2 Gantry Settings</h2>
<p className="mb-4 text-sm text-muted-foreground">
Settings for CO2 Gantry laser systems.
</p>
<Link href="/co2-gantry-settings">
<Button>View Settings</Button>
</Link>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<h2 className="text-xl font-semibold mb-2">UV Laser Settings</h2>
<p className="mb-4 text-sm text-muted-foreground">
Settings for UV laser engraving and marking.
</p>
<Link href="/uv-settings">
<Button>View Settings</Button>
</Link>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<h2 className="text-xl font-semibold mb-2">Materials and Coatings</h2>
<p className="mb-4 text-sm text-muted-foreground">
Explore materials and surface coatings along with their laser safety classifications.
</p>
<div className="flex flex-col sm:flex-row gap-2">
<Link href="/materials">
<Button className="w-full sm:w-auto">View Materials</Button>
</Link>
<Link href="/materials-coatings">
<Button className="w-full sm:w-auto">View Coatings</Button>
</Link>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<h2 className="text-xl font-semibold mb-2">Laser Source Database</h2>
<p className="mb-4 text-sm text-muted-foreground">
Technical specs and info on various laser sources.
</p>
<Link href="/lasers">
<Button>View Sources</Button>
</Link>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<h2 className="text-xl font-semibold mb-2">Projects Database</h2>
<p className="mb-4 text-sm text-muted-foreground">
Community-submitted projects and guides.
</p>
<Link href="/projects">
<Button>View Projects</Button>
</Link>
</CardContent>
</Card>
{/* 🔽 NEW FILE DOWNLOAD SECTION 🔽 */}
<Card>
<CardContent className="p-6">
<h2 className="text-xl font-semibold mb-2">Downloadable Files</h2>
<p className="mb-4 text-sm text-muted-foreground">
Browse and download shared files from the server.
</p>
<Link href="/files">
<Button>View Files</Button>
</Link>
</CardContent>
</Card>
{/* 🔽 NEW BUYING GUIDE CARD 🔽 */}
<Card>
<CardContent className="p-6">
<h2 className="text-xl font-semibold mb-2">Buying Guide</h2>
<p className="mb-4 text-sm text-muted-foreground">
Reviews and recommendations for laser products and accessories.
</p>
<Link href="/buying-guide">
<Button>View Guide</Button>
</Link>
</CardContent>
</Card>
</div>
<section className="mt-8 text-center text-xs text-muted-foreground">
We only use cookies strictly necessary to operate the site (e.g., your sign-in session).
</section>
</main>
);
}

View file

@ -5,7 +5,11 @@ import { NextResponse, NextRequest } from "next/server";
* Public pages that should remain reachable without being signed in.
* Everything else is considered protected (including most /api/*).
*/
const PUBLIC_PAGES = new Set<string>(["/auth/sign-in", "/auth/sign-up"]);
const PUBLIC_PAGES = new Set<string>([
"/", // ← splash page is public
"/auth/sign-in",
"/auth/sign-up",
]);
/**
* API paths that are explicitly allowed without auth.
@ -17,8 +21,7 @@ import { NextResponse, NextRequest } from "next/server";
];
/** Directus base (used to remotely validate the token after restarts). */
const DIRECTUS =
(process.env.NEXT_PUBLIC_API_BASE_URL || process.env.DIRECTUS_URL || "").replace(/\/$/, "");
const DIRECTUS = (process.env.NEXT_PUBLIC_API_BASE_URL || process.env.DIRECTUS_URL || "").replace(/\/$/, "");
/** Helper: does the path start with any prefix in a list? */
function startsWithAny(pathname: string, prefixes: string[]) {
@ -47,22 +50,32 @@ import { NextResponse, NextRequest } from "next/server";
}
}
/** Build redirect to /auth/sign-in?reauth=1&next=<original>, and clear auth markers. */
function kickToSignIn(req: NextRequest) {
/**
* Build redirect to /auth/sign-in?next=<original>.
* Only set reauth=1 (and clear cookies) when opts.reauth === true.
*/
function kickToSignIn(req: NextRequest, opts?: { reauth?: boolean }) {
const wantReauth = !!opts?.reauth;
const orig = new URL(req.url);
const next = orig.pathname + (orig.search || "");
const url = new URL(req.url);
url.pathname = "/auth/sign-in";
url.search = "";
url.searchParams.set("reauth", "1");
if (wantReauth) url.searchParams.set("reauth", "1");
url.searchParams.set("next", next);
const res = NextResponse.redirect(url);
// Clear tokens so the very next /auth/* request is truly unauthenticated
res.cookies.set("ma_at", "", { maxAge: 0, path: "/" });
res.cookies.set("ma_v", "", { maxAge: 0, path: "/" }); // throttle marker
// If you also use a refresh token, clear it here too:
// res.cookies.set("ma_rt", "", { maxAge: 0, path: "/" });
// Only clear auth markers in true re-auth scenarios
if (wantReauth) {
res.cookies.set("ma_at", "", { maxAge: 0, path: "/" });
res.cookies.set("ma_v", "", { maxAge: 0, path: "/" }); // throttle marker
// If you also use a refresh token, clear it here too:
// res.cookies.set("ma_rt", "", { maxAge: 0, path: "/" });
}
return res;
}
@ -90,9 +103,9 @@ import { NextResponse, NextRequest } from "next/server";
isAuthRoute &&
(url.searchParams.get("reauth") === "1" || url.searchParams.get("force") === "1");
// If unauthenticated and the route is protected, send to sign-in (with next + reauth)
// If unauthenticated and the route is protected, send to sign-in WITHOUT reauth
if (!token && isProtected) {
return kickToSignIn(req);
return kickToSignIn(req, { reauth: false });
}
// If we have a token, perform local expiry check.
@ -100,8 +113,7 @@ import { NextResponse, NextRequest } from "next/server";
const exp = jwtExp(token);
const expired = !exp || exp * 1000 <= Date.now();
// If it's an auth route and token looks valid, keep your existing UX:
// bounce away from auth pages — unless this is a forced reauth.
// If it's an auth route and token looks valid, bounce away from auth pages — unless this is a forced reauth.
if (isAuthRoute && !expired && !forceAuth) {
url.pathname = "/portal";
url.search = "";
@ -111,7 +123,8 @@ import { NextResponse, NextRequest } from "next/server";
// If protected route: enforce validity
if (isProtected) {
if (expired) {
return kickToSignIn(req);
// True reauth
return kickToSignIn(req, { reauth: true });
}
// ── Throttled remote validation (catches server restarts / revoked tokens)
@ -131,8 +144,8 @@ import { NextResponse, NextRequest } from "next/server";
});
if (!r.ok) {
// Token no longer valid on the server → force re-auth, carry next
return kickToSignIn(req);
// Token no longer valid on the server → true reauth, carry next
return kickToSignIn(req, { reauth: true });
}
// Cache the success for ~1 minute to avoid hammering Directus
@ -146,7 +159,7 @@ import { NextResponse, NextRequest } from "next/server";
return res;
} catch {
// If Directus is unreachable, be conservative and require re-auth
return kickToSignIn(req);
return kickToSignIn(req, { reauth: true });
}
}
}
@ -213,7 +226,7 @@ import { NextResponse, NextRequest } from "next/server";
}
function isPublicPath(pathname: string): boolean {
// 1) Public pages (auth screens)
// 1) Public pages (root splash & auth screens)
if (PUBLIC_PAGES.has(pathname)) return true;
// 2) Static assets / internals