diff --git a/client/src/components/onboarding-brand-header.tsx b/client/src/components/onboarding-brand-header.tsx new file mode 100644 index 0000000..ba290bd --- /dev/null +++ b/client/src/components/onboarding-brand-header.tsx @@ -0,0 +1,56 @@ +import { cn } from "@/lib/utils"; + +import { VirensLogo } from "./virens-logo"; + +export interface OnboardingBrandHeaderProps { + /** "plain": centered mark for a plain white screen (e.g. About). + * "hero": mark sits over a decorative gradient banner (e.g. Welcome, Sign in). */ + variant?: "plain" | "hero"; + className?: string; +} + +/** + * Shared brand mark for the pre-sign-in onboarding screens (About, Welcome, + * Sign in). Reuses the same Leaf-in-badge treatment as the authenticated + * AppShell header (see app-shell.tsx) instead of a separate logo, since no + * dedicated Virens logo asset exists in the repo. + */ +export function OnboardingBrandHeader({ + variant = "plain", + className, +}: OnboardingBrandHeaderProps) { + const content = ( +
+ +

+ virens +

+

+ let the good greens flourish +

+
+ ); + + if (variant === "plain") { + return
{content}
; + } + + return ( +
+
+ ); +} diff --git a/client/src/components/onboarding-layout.tsx b/client/src/components/onboarding-layout.tsx new file mode 100644 index 0000000..131770e --- /dev/null +++ b/client/src/components/onboarding-layout.tsx @@ -0,0 +1,33 @@ +import type { ReactNode } from "react"; + +import { fontSans } from "@/lib/fonts"; +import { cn } from "@/lib/utils"; + +export interface OnboardingLayoutProps { + children: ReactNode; + className?: string; +} + +/** + * Responsive public-site shell. It deliberately omits the signed-in + * Identify/Explore/Map navigation while sharing the same brand tokens. + */ +export function OnboardingLayout({ + children, + className, +}: OnboardingLayoutProps) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/client/src/components/public-header.tsx b/client/src/components/public-header.tsx new file mode 100644 index 0000000..94b27fb --- /dev/null +++ b/client/src/components/public-header.tsx @@ -0,0 +1,48 @@ +import Link from "next/link"; + +import { VirensLogo } from "@/components/virens-logo"; + +export function PublicHeader() { + return ( +
+
+ + + + virens + + + + +
+
+ ); +} diff --git a/client/src/pages/about.tsx b/client/src/pages/about.tsx new file mode 100644 index 0000000..4f5f4c8 --- /dev/null +++ b/client/src/pages/about.tsx @@ -0,0 +1,105 @@ +import { BookOpen, Camera, Heart, User } from "lucide-react"; +import Head from "next/head"; +import { useRouter } from "next/router"; + +import { OnboardingLayout } from "@/components/onboarding-layout"; +import { PublicHeader } from "@/components/public-header"; +import { Button } from "@/components/ui/button"; + +const FEATURES = [ + { + icon: Camera, + title: "Identify invasive weeds", + description: + "Photograph a plant and get clear identification cues in seconds.", + }, + { + icon: BookOpen, + title: "Explore local ecology", + description: + "Learn about nearby bushland, native habitats and priority species.", + }, + { + icon: User, + title: "Take action in the field", + description: + "Report sightings, discover events and support restoration teams.", + }, +]; + +export default function AboutPage() { + const router = useRouter(); + + return ( + <> + + About Virens + + + +
+
+
+

+ About Virens +

+

+ Better bushland, together +

+

+ Virens helps local communities recognise invasive weeds and care + for the places that matter. +

+
+ +
    + {FEATURES.map(({ icon: Icon, title, description }) => ( +
  • + + +
    +

    + {title} +

    +

    + {description} +

    +
    +
  • + ))} +
+ +
+ + +

+ Built for volunteers, rangers and everyone who cares for + Country. +

+
+
+ + +
+
+ + ); +} diff --git a/client/src/pages/index.tsx b/client/src/pages/index.tsx index c051281..5c2deb8 100644 --- a/client/src/pages/index.tsx +++ b/client/src/pages/index.tsx @@ -1,36 +1 @@ -import { Inter as FontSans } from "next/font/google"; -import { useState } from "react"; - -import { usePings } from "@/hooks/pings"; -import { cn } from "@/lib/utils"; - -import { Button } from "../components/ui/button"; - -const fontSans = FontSans({ - subsets: ["latin"], - variable: "--font-sans", -}); - -export default function Home() { - const [clicked, setClicked] = useState(false); - const { data, isLoading } = usePings({ - enabled: clicked, - }); - - return ( -
-

Test title

- -

- Response from server: {data as string} -

-
- ); -} +export { default } from "./about"; diff --git a/client/src/pages/signin.tsx b/client/src/pages/signin.tsx new file mode 100644 index 0000000..994c32d --- /dev/null +++ b/client/src/pages/signin.tsx @@ -0,0 +1,160 @@ +import { Eye, EyeOff, Lock, Mail } from "lucide-react"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import { type FormEvent, useState } from "react"; + +import { OnboardingBrandHeader } from "@/components/onboarding-brand-header"; +import { OnboardingLayout } from "@/components/onboarding-layout"; +import { PublicHeader } from "@/components/public-header"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +const INPUT_CLASSES = + "h-12 w-full rounded-xl border border-input bg-background py-2 pl-10 pr-3 text-sm text-foreground shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"; + +/** + * Frontend-only, clickable sign-in prototype. Performs basic required-field + * validation only — no credentials, tokens, cookies or sessions are created + * or stored anywhere. + */ +export default function SignInPage() { + const router = useRouter(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [error, setError] = useState(null); + + function handleSubmit(event: FormEvent) { + event.preventDefault(); + + if (!email.trim() || !password.trim()) { + setError("Email and password are required."); + return; + } + + setError(null); + router.push("/identify"); + } + + return ( + <> + + Sign in to Virens + + + + +
+ + +
+
+

+ Member access +

+

+ Welcome back +

+

+ Sign in to continue identifying weeds and joining local field + events. +

+
+ +
+
+ +
+
+
+ +
+ +
+
+
+ + {error && ( +

+ {error} +

+ )} + +

+ Forgot password? +

+ + +
+ +

+ New to Virens?{" "} + + Create an account + +

+
+
+
+ + ); +} diff --git a/client/src/pages/welcome.tsx b/client/src/pages/welcome.tsx new file mode 100644 index 0000000..f9bf09f --- /dev/null +++ b/client/src/pages/welcome.tsx @@ -0,0 +1,54 @@ +import Head from "next/head"; +import { useRouter } from "next/router"; + +import { OnboardingBrandHeader } from "@/components/onboarding-brand-header"; +import { OnboardingLayout } from "@/components/onboarding-layout"; +import { PublicHeader } from "@/components/public-header"; +import { Button } from "@/components/ui/button"; + +export default function WelcomePage() { + const router = useRouter(); + + return ( + <> + + Welcome to Virens + + + + +
+
+

+ Western Australia +

+

+ Protect bushland. +
+ Grow community. +

+

+ Virens helps you identify invasive weeds, explore local bushland + and join people taking action across Western Australia. +

+ +
+ +

+ By continuing, you agree to our Terms and Privacy Policy. +

+
+
+ + +
+
+ + ); +}