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
2 changes: 1 addition & 1 deletion apps/web/app/academic/academic-record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export function AcademicRecord({ catalogue }: { catalogue: PlanCatalogue }) {
router.refresh();
} catch {
setSelected(null);
toast.error("An unexpected error occurred");
notify("We couldn't save that result. Try again.", "error");
}
});
}
Expand Down
26 changes: 13 additions & 13 deletions apps/web/app/auth/auth-loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,28 @@ export function AuthLoading({
<AuthShell>
<div aria-busy="true">
<span className="sr-only">{label}</span>
<Skeleton className="h-8 w-48 bg-zinc-100 sm:h-9" />
<Skeleton className="mt-3 h-3.5 w-full bg-zinc-100" />
<Skeleton className="mt-2 h-3.5 w-2/3 bg-zinc-100" />
<div className="mt-7 grid gap-3 sm:grid-cols-2">
<Skeleton className="h-10 rounded-lg bg-zinc-100" />
<Skeleton className="h-10 rounded-lg bg-zinc-100" />
<Skeleton className="h-8 w-48 sm:h-9" />
<Skeleton className="mt-3 h-3.5 w-full" />
<Skeleton className="mt-2 h-3.5 w-2/3" />
<div className="mt-7 space-y-2.5">
<Skeleton className="h-11 rounded-lg" />
<Skeleton className="h-11 rounded-lg" />
</div>
<div className="my-6 flex items-center gap-3">
<span className="h-px flex-1 bg-zinc-200" />
<Skeleton className="h-2.5 w-28 bg-zinc-100" />
<span className="h-px flex-1 bg-zinc-200" />
<span className="h-px flex-1 bg-border" />
<Skeleton className="h-2.5 w-28" />
<span className="h-px flex-1 bg-border" />
</div>
<div className="space-y-4">
{Array.from({ length: fields }, (_, index) => (
<div key={index}>
<Skeleton className="mb-2 h-3 w-20 bg-zinc-100" />
<Skeleton className="h-10 w-full rounded-lg bg-zinc-100" />
<Skeleton className="mb-2 h-3 w-20" />
<Skeleton className="h-11 w-full rounded-lg" />
</div>
))}
<Skeleton className="h-10 w-full rounded-lg bg-zinc-100" />
<Skeleton className="h-11 w-full rounded-lg" />
</div>
<Skeleton className="mx-auto mt-6 h-3.5 w-52 bg-zinc-100" />
<Skeleton className="mx-auto mt-6 h-3.5 w-52" />
</div>
</AuthShell>
);
Expand Down
32 changes: 16 additions & 16 deletions apps/web/app/auth/auth-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ const highlights = [
*/
export function AuthShell({ children }: { children: ReactNode }) {
return (
<main className="grid min-h-dvh bg-white lg:grid-cols-[1fr_minmax(0,44rem)]">
<main className="grid min-h-dvh bg-background lg:grid-cols-[1fr_minmax(0,44rem)]">
<section className="flex flex-col px-5 py-6 sm:px-10">
<Link
href="/"
aria-label="Coursemap home"
className="inline-flex w-fit items-center gap-2.5 text-zinc-900"
className="inline-flex w-fit items-center gap-2.5 text-foreground"
>
<BrandMark className="size-9" />
<strong className="brand-wordmark text-lg">coursemap</strong>
Expand All @@ -31,31 +31,31 @@ export function AuthShell({ children }: { children: ReactNode }) {
<div className="w-full max-w-sm">{children}</div>
</div>

<p className="text-[11px] leading-relaxed text-zinc-400">
<p className="text-[11px] leading-relaxed text-muted-foreground">
Coursemap is an independent planning tool. It is not an official ANU
system and does not replace Programs and Courses or academic advice.
</p>
</section>

<aside
aria-hidden="true"
className="landing-mesh relative hidden overflow-hidden border-l border-zinc-100 lg:block"
className="landing-mesh relative hidden overflow-hidden border-l border-border lg:block"
>
<div className="absolute inset-0 flex flex-col justify-center gap-8 px-14">
<div className="max-w-md">
<p className="text-xs font-bold tracking-wider text-brand-700 uppercase">
<p className="text-xs font-bold tracking-wider text-primary uppercase">
Your ANU degree, mapped
</p>
<h2 className="mt-3 text-3xl leading-tight font-bold tracking-tight text-zinc-950">
<h2 className="mt-3 text-3xl leading-tight font-bold tracking-tight text-foreground">
Every course, every prerequisite, one clear plan.
</h2>
<ul className="mt-6 space-y-3">
{highlights.map((highlight) => (
<li
key={highlight}
className="flex items-center gap-3 text-sm text-zinc-700"
className="flex items-center gap-3 text-sm text-foreground/80"
>
<span className="grid size-6 shrink-0 place-items-center rounded-full bg-white text-brand-700 shadow-xs ring-1 ring-zinc-200">
<span className="grid size-6 shrink-0 place-items-center rounded-full bg-card text-primary shadow-xs ring-1 ring-border">
<Check className="size-3.5" />
</span>
{highlight}
Expand All @@ -65,8 +65,8 @@ export function AuthShell({ children }: { children: ReactNode }) {
</div>

<div className="relative max-w-md">
<div className="rounded-3xl bg-white/90 p-5 shadow-lg ring-1 ring-zinc-200/80 backdrop-blur-sm">
<p className="text-[11px] font-bold tracking-wider text-zinc-400 uppercase">
<div className="rounded-3xl bg-card/90 p-5 shadow-lg ring-1 ring-border backdrop-blur-sm">
<p className="text-[11px] font-bold tracking-wider text-muted-foreground uppercase">
Semester 1 路 2026
</p>
<div className="mt-3 space-y-2">
Expand All @@ -77,7 +77,7 @@ export function AuthShell({ children }: { children: ReactNode }) {
].map(([code, name, tone]) => (
<div
key={code}
className="flex items-center gap-3 rounded-xl bg-zinc-50 px-3 py-2.5 ring-1 ring-zinc-100"
className="flex items-center gap-3 rounded-xl bg-muted/60 px-3 py-2.5 ring-1 ring-border"
>
<span
className={
Expand All @@ -88,21 +88,21 @@ export function AuthShell({ children }: { children: ReactNode }) {
: "size-2 rounded-full bg-amber-400"
}
/>
<span className="font-mono text-xs font-semibold text-zinc-900">
<span className="font-mono text-xs font-semibold text-foreground">
{code}
</span>
<span className="truncate text-xs text-zinc-500">
<span className="truncate text-xs text-muted-foreground">
{name}
</span>
</div>
))}
</div>
</div>
<div className="absolute -top-4 -right-4 rounded-2xl bg-white px-4 py-3 shadow-md ring-1 ring-zinc-200/80">
<p className="text-[11px] font-semibold text-zinc-500">
<div className="absolute -top-4 -right-4 rounded-2xl bg-card px-4 py-3 shadow-md ring-1 ring-border">
<p className="text-[11px] font-semibold text-muted-foreground">
Prerequisites met
</p>
<p className="mt-0.5 text-lg font-bold tracking-tight text-emerald-600">
<p className="mt-0.5 text-lg font-bold tracking-tight text-emerald-600 dark:text-emerald-400">
3 of 3
</p>
</div>
Expand Down
35 changes: 24 additions & 11 deletions apps/web/app/auth/sign-in/sign-in-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import { Alert, AlertDescription } from "@coursemap/ui/components/alert";
import { Button } from "@coursemap/ui/primitives/button";
import { Field } from "@coursemap/ui/primitives/field";
import { Input } from "@coursemap/ui/primitives/input";
import { cn } from "@/lib/cn";

import { CircleAlert, LockKeyhole, Mail } from "lucide-react";
import { useState, type FormEvent } from "react";
import { useId, useRef, useState, type FormEvent } from "react";

import { createClient } from "@/lib/supabase/browser";

Expand All @@ -23,6 +22,8 @@ export function SignInForm({
const [password, setPassword] = useState("");
const [submitting, setSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(initialError);
const errorId = useId();
const passwordRef = useRef<HTMLInputElement>(null);

const submit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
Expand All @@ -39,11 +40,15 @@ export function SignInForm({
});

if (error) {
const invalidCredentials = error.message
.toLowerCase()
.includes("invalid login credentials");
setErrorMessage(
error.message.toLowerCase().includes("invalid login credentials")
invalidCredentials
? "Email or password is incorrect."
: "Coursemap could not sign you in. Wait a moment and try again.",
);
if (invalidCredentials) passwordRef.current?.select();
return;
}

Expand All @@ -70,7 +75,7 @@ export function SignInForm({
<span className="text-sm font-medium">{"Email address"}</span>
<span className="relative block">
<Mail
className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-zinc-400"
className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground"
aria-hidden="true"
/>
<Input
Expand All @@ -82,7 +87,10 @@ export function SignInForm({
maxLength={254}
placeholder="name@anu.edu.au"
required
disabled={!configured || submitting}
disabled={!configured}
readOnly={submitting}
aria-invalid={errorMessage ? true : undefined}
aria-describedby={errorMessage ? errorId : undefined}
className="min-h-11 pl-10"
/>
</span>
Expand All @@ -94,37 +102,42 @@ export function SignInForm({
<span className="text-sm font-medium">{"Password"}</span>
<span className="relative block">
<LockKeyhole
className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-zinc-400"
className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground"
aria-hidden="true"
/>
<Input
type="password"
name="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
ref={passwordRef}
autoComplete="current-password"
minLength={8}
maxLength={128}
required
disabled={!configured || submitting}
disabled={!configured}
readOnly={submitting}
aria-invalid={errorMessage ? true : undefined}
aria-describedby={errorMessage ? errorId : undefined}
className="min-h-11 pl-10"
/>
</span>
</label>
</Field>

{errorMessage && (
<Alert role="alert" variant={"destructive"}>
<CircleAlert />
<Alert id={errorId} role="alert" variant="destructive">
<CircleAlert aria-hidden="true" />
<AlertDescription>{errorMessage}</AlertDescription>
</Alert>
)}

<Button
type="submit"
variant="default"
disabled={!configured || submitting}
className={cn("min-h-11", "w-full")}
disabled={!configured}
aria-disabled={submitting || undefined}
className="min-h-11 w-full"
>
{submitting ? "Signing in..." : "Sign in"}
</Button>
Expand Down
Loading
Loading