This is an automated email from the ASF dual-hosted git repository. lahirujayathilake pushed a commit to branch auth-endpoints-web in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit 3486c35fd3debf6e8799c2fe3cdf307445ceeef3 Author: lahiruj <[email protected]> AuthorDate: Wed Jun 17 16:16:35 2026 -0400 Reset SignInForm submitting state on signIn failure --- web/CLAUDE.md | 122 ------------------------------ web/src/app/(auth)/sign-in/SignInForm.tsx | 19 ++++- 2 files changed, 15 insertions(+), 126 deletions(-) diff --git a/web/CLAUDE.md b/web/CLAUDE.md deleted file mode 100644 index 68ab9c745..000000000 --- a/web/CLAUDE.md +++ /dev/null @@ -1,122 +0,0 @@ -# CLAUDE.md - -Context for AI assistants working in `airavata-custos/web/`. Human contributors should start with [README.md](./README.md). - -## What this is - -Web portal for **Apache Custos** — allocation management, identity, and admin tooling for HPC sites. Pairs with the Apache Custos backend; expects either a live backend (`CUSTOS_CORE_API_BASE_URL`) or the in-repo MSW mock layer. - -This portal was ported from a working prototype. The prototype's ADRs and glossary remain the single source of truth — see References below. - -## Hard rules in force (these override defaults) - -1. **No `git commit` and no `git push` in `airavata-custos` from automation.** Only the human maintainer commits here. AI work stays in the working tree on the active branch. -2. **No prototype branding.** CSS tokens are `--custos-*`, env vars are `CUSTOS_*` / `NEXT_PUBLIC_CUSTOS_*`. Don't introduce prototype-era identifiers in code, comments, or specs. -3. **Code comments: precise, why-only, 2 lines max.** Default to no comments. No restate-what-the-code-does, no change-log comments, no client/SDK references. -4. **Keep it simple. No over-engineering.** Default to the simplest sufficient solution. Resist layers, hedges, "for safety" guards. - -## Commands - -```bash -pnpm dev # Start dev server (localhost:3000) -pnpm build # Production build -pnpm lint # Biome lint -pnpm format # Biome format (writes in place) -pnpm typecheck # tsc --noEmit -pnpm test # Vitest (unit, run once) -pnpm test:watch # Vitest watch mode -pnpm test:e2e # Playwright e2e tests -pnpm verify # lint + typecheck + test + build (full gate) -``` - -Every change must leave `pnpm verify` green. - -## Local development - -Copy `.env.example` to `.env.local`. Defaults work without a backend once auth + MSW are wired (Phase 2+): - -- `PORTAL_AUTH_MODE=dev` — credentials-based sign-in (Phase 3). -- `NEXT_PUBLIC_PORTAL_USE_MSW=true` — MSW intercepts `/api/v1/*` calls in the browser; no backend required. - -To test against a real Custos backend, set `PORTAL_AUTH_MODE=oidc` and provide `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, plus `CUSTOS_CORE_API_BASE_URL`. The env schema in `src/lib/env.ts` fails fast at boot if anything required is missing. - -## Architecture - -### Next.js App Router layout - -``` -src/app/ - (auth)/sign-in/ — sign-in (credentials / OIDC) - (portal)/ — authenticated portal shell - api/ - auth/[...nextauth]/ — NextAuth handler (Phase 3) - v1/[...path]/ — transparent proxy to Custos backend (Phase 2) -``` - -### Feature structure (lands in later phases) - -Each feature in `src/features/{core,connectors}/<name>/` follows the same internal shape: - -``` -schemas.ts — Zod schemas + inferred TypeScript types -types.ts — non-schema types (query params, discriminated unions) -api.ts — apiFetch calls; each validates response with Zod -queries.ts — TanStack Query hooks + query key factories -components/ — React components scoped to this feature -__tests__/ — Vitest unit tests -``` - -Features must not import from each other. The one documented exception is the cross-feature trace deep-link primitive that lands with the audit feature. - -### Shared layer (`src/shared/`) - -- `api/client.ts` (Phase 2) — `apiFetch`: prepends `/api/v1`, attaches headers, throws `ApiError` on non-2xx, records the response `X-Trace-Id` on a singleton. -- `auth/auth.ts` (Phase 3) — NextAuth v5 config. -- `casl/abilities.ts` (Phase 3) — CASL `defineAbility()` driven by `/user/privileges`. -- `layout/nav.ts` — `portalNav` array; sidebar items declare an optional `ability` gate. -- `providers/Providers.tsx` — Root provider tree. -- `hooks/useShallowSearchParams.ts` — Drop-in for `useSearchParams()` whose writes don't trigger an RSC roundtrip. -- `ui/` — shadcn/ui components (style: `base-nova`). Add new components with `pnpm dlx shadcn@latest add <component>`. - -### MSW (Mock Service Worker) - -With `NEXT_PUBLIC_PORTAL_USE_MSW=true`, MSW boots in the browser via `src/mocks/browser.ts`. Handlers live in `src/mocks/handlers.ts` and aggregate per-feature handlers from later phases. - -### Design tokens - -CSS custom properties in `design-tokens/` are the source of truth for color, spacing, radius, and typography. `design-tokens/tokens.json` is the machine-readable version. A Vitest smoke test (`src/shared/__tests__/tokens.test.ts`) guards against regressions. - -Light tokens are declared in `:root`; dark overrides live in `.dark`. Both selectors have equal specificity, so order in the source file matters — keep `.dark` overrides after the `:root` block they shadow. - -## Code conventions - -- **Biome** for lint + format. Config: `biome.json`. `noExplicitAny` is an error; `useImportType` is a warning (off inside `src/shared/ui/**`). -- **Zod** validation happens at the API boundary only. -- **Query keys** follow the factory pattern: each feature exports a `<feature>Keys` object with `all`, `list(params)`, `detail(id)` methods. -- **URL state** uses `useShallowSearchParams` for filter/drawer/tab state. -- **CASL** for permission gating. -- **Server-only code** (NextAuth, backend proxy, env validation) imports `"server-only"` to prevent accidental bundling into client code. - -## Pitfalls - -- **Don't gate UI with `session.role === "admin"`.** Use `<Can I="manage" a="Site">` or `useAbility()`. -- **Don't re-validate Zod schemas inside components.** Validation is at the API boundary (`api.ts`); components consume typed data and trust it. -- **Don't `router.replace` for filter/drawer/tab state.** Use `useShallowSearchParams`. -- **Don't import across `src/features/<name>/`.** The only sanctioned cross-feature import is the trace deep-link primitive. -- **Don't add `.dark` token overrides above the `:root` block they shadow.** Source order wins for equal specificity. -- **Don't write WHAT a piece of code does in a comment.** Comment only when WHY isn't obvious. Two-line max. No change-log comments. - -## References (single source of truth) - -The prototype repo holds the architecture decision records and glossary that govern this portal. Cite them from there rather than copying: - -- ADR-0001 — CASL over role strings -- ADR-0002 — Zod at the boundary -- ADR-0003 — MSW browser-only -- ADR-0004 — Feature isolation -- ADR-0005 — Shallow URL state -- `docs/glossary.md` — domain vocabulary -- `docs/features/tracing.md` — tracing feature spec - -- Prototype repo: maintainer-local sibling checkout (cite ADR paths from there) -- Portal scaffold spec: `docs/internal/portal/2026-06-10-portal-scaffold.md` diff --git a/web/src/app/(auth)/sign-in/SignInForm.tsx b/web/src/app/(auth)/sign-in/SignInForm.tsx index aa4c30188..c31c82837 100644 --- a/web/src/app/(auth)/sign-in/SignInForm.tsx +++ b/web/src/app/(auth)/sign-in/SignInForm.tsx @@ -10,14 +10,25 @@ export function SignInForm() { const callbackUrl = params.get("callbackUrl") ?? "/"; const [submitting, setSubmitting] = useState(false); - const onSubmit = async () => { + const onClick = async () => { setSubmitting(true); - await signIn("oidc", { callbackUrl }); + try { + await signIn("oidc", { callbackUrl }); + } catch { + // signIn normally redirects on success; reset on failure so the button + // doesn't stay disabled forever. + setSubmitting(false); + } }; return ( - <Button onClick={onSubmit} disabled={submitting} className="w-full"> - Sign in + <Button + onClick={onClick} + disabled={submitting} + aria-busy={submitting} + className="w-full" + > + {submitting ? "Signing in…" : "Sign in"} </Button> ); }
