diff --git a/.ciagent/ARCHITECTURE.md b/.ciagent/ARCHITECTURE.md new file mode 100644 index 0000000..17194b2 --- /dev/null +++ b/.ciagent/ARCHITECTURE.md @@ -0,0 +1,136 @@ +# Nextcraft — ARCHITECTURE.md + +## Overview + +Nextcraft v0.1 is a high-fidelity UI/UX prototype built as a TypeScript monorepo using pnpm workspaces and turborepo. The prototype consists of a single Next.js application with route groups for each surface (Learner, Marketplace, Employer Dashboard, Admin), backed by a shared component library, typed mock data layer, and shared types package. + +**No backend, no database, no authentication logic.** All data is static/mock. The architecture is designed to be replaced piece-by-piece with real backend services in future milestones. + +### Confirmed Technology Stack (Research Findings) + +| Technology | Version | Purpose | +|------------|---------|---------| +| Node.js | v24.15.0 | Runtime | +| pnpm | 12.3.4 | Package manager + workspaces | +| turborepo | latest | Build orchestration | +| Next.js | latest (App Router) | Web application framework | +| React | 19+ | UI library | +| TypeScript | 5.x | Type system | +| Tailwind CSS | v4 | Utility-first CSS framework | +| lucide-react | latest | Icon system | +| recharts | latest | Charts for employer/admin dashboards | +| @xyflow/react (react-flow) | latest | Competency graph viewer in admin surface | +| Inter font | via next/font | Typography | +| ESLint | via Next.js | Linting | +| Prettier | latest | Code formatting | + +### Architecture Decisions from Research + +1. **Next.js App Router with route groups** — `(learner)`, `(marketplace)`, `(employer)`, `(admin)` provide clean URL separation without affecting paths +2. **Tailwind CSS v4** — Configured via `@theme` in CSS, no `tailwind.config.js` needed (v4 paradigm shift). Dark mode via `class` strategy. +3. **Server components by default** — All pages are server components. Client components only for interactive elements (filters, search, chat, graph viewer, dark mode toggle) +4. **Mock data as ES modules** — Typed TS files exported from `packages/mock-data`. No JSON files — all mock data is programmatically generated for richer structure. +5. **react-flow (@xyflow/react)** — Confirmed for competency graph viewer. Provides interactive node/edge rendering with built-in controls. +6. **recharts** — Confirmed for analytics dashboards. Responsive, composable, integrates well with React server components. +7. **pnpm workspaces** — `apps/web` + `packages/ui` + `packages/mock-data` + `packages/types`. Shared deps hoisted. + +--- + +## Components + +### apps/web — Next.js Application + +| Component | Description | Boundaries | Depends On | +|-----------|-------------|------------|------------| +| `app/(learner)/` | Learner surface route group: landing, catalog, competency stack, dashboard, byte viewer, sandbox mockup, assessment mockup | Learner-only routes and layouts | packages/ui, packages/mock-data, packages/types | +| `app/(marketplace)/` | Marketplace surface route group: job board, job detail, employer profile, search/filter, pricing | Marketplace-only routes and layouts | packages/ui, packages/mock-data, packages/types | +| `app/(employer)/` | Employer dashboard route group: overview, talent search, candidate profile, posting management | Employer-only routes and layouts | packages/ui, packages/mock-data, packages/types | +| `app/(admin)/` | Admin surface route group: overview, learner management, competency graph viewer, moderation | Admin-only routes and layouts | packages/ui, packages/mock-data, packages/types | +| `app/layout.tsx` | Root layout: theme provider, navigation shell, responsive container | All routes | packages/ui | +| `components/` | Surface-specific components (not shared across surfaces) | Per-surface only | packages/ui | + +### packages/ui — Shared Component Library + +| Component | Description | Boundaries | Depends On | +|-----------|-------------|------------|------------| +| `design-tokens/` | CSS custom properties: color palette, typography scale, spacing system, breakpoints, shadows, radii | Foundation layer — no dependencies | None | +| `primitives/` | Button, Input, Card, Badge, Avatar, Dialog, Tabs, Progress, Tooltip, Skeleton, Toast | Atomic UI components | design-tokens | +| `composites/` | Navigation, Table, SearchBar, FilterPanel, ChatInterface, GraphViewer, ArtifactCard, CompetencyBadge, JobCard, CandidateCard, MetricCard | Composite components built from primitives | primitives, packages/types | +| `layouts/` | Container, Grid, Sidebar, SplitPanel, DashboardLayout | Layout components | primitives, design-tokens | +| `theme/` | Theme provider, CSS variable overrides per surface (learner, marketplace, employer, admin) | Theme context | design-tokens | + +### packages/mock-data — Mock Data Layer + +| Component | Description | Boundaries | Depends On | +|-----------|-------------|------------|------------| +| `competency-stacks.ts` | 5 competency stacks (AI Orchestration Engineer, AI Safety & Governance, Human-AI Product Designer, AI-Augmented Field Operator, Computational Sciences), each with 12-18 competencies | Typed mock data | packages/types | +| `jobs.ts` | 20+ mock AI-era job listings with skills, seniority, salary, match scores | Typed mock data | packages/types | +| `candidates.ts` | 15+ mock candidate profiles with artifacts, process traces, defense scores, microcredentials | Typed mock data | packages/types | +| `employers.ts` | 10+ mock employer profiles with logos, descriptions, open positions | Typed mock data | packages/types | +| `learner-progress.ts` | Mock learner progress data: active competencies, completion percentages, recent artifacts | Typed mock data | packages/types | +| `ai-tutor-responses.ts` | Pre-scripted AI tutor chat responses for Coach and Tutor agent mockups | Typed mock data | packages/types | + +### packages/types — Shared Types + +| Component | Description | Boundaries | Depends On | +|-----------|-------------|------------|------------| +| `domain.ts` | Competency, CompetencyStack, Microcredential, Artifact, ProcessTrace, OralDefense, AssessmentRubric | Domain types | None | +| `marketplace.ts` | Job, Employer, Candidate, JobPosting, TalentMatch, SearchFilter | Marketplace types | None | +| `user.ts` | Learner, Admin, EmployerUser, AgeGroup, Role | User types | None | +| `ui.ts` | Component props, theme config, breakpoint definitions | UI types | None | + +--- + +## Data Flow + +``` +[Mock Data Layer] ──typed──> [Shared Types] <──typed──> [UI Components] + │ │ + │ │ + ▼ ▼ +[Next.js Route Groups] [Surface Components] + (learner)/ (marketplace)/ (employer)/ (admin)/ + │ │ │ │ + └──────────────────┴─────────────────┴───────────────┘ + │ + ▼ + [Root Layout + Theme Provider] + │ + ▼ + [Responsive Navigation Shell] +``` + +All data flows from the mock data layer through typed imports into Next.js route handlers / server components, which pass data as props to UI components. No client-side data fetching, no API routes, no server actions in v0.1. + +--- + +## Build Order + +1. **Monorepo scaffolding** — pnpm-workspace.yaml, turbo.json, tsconfig.json, package.json, Next.js app initialization +2. **Shared types** — packages/types with all domain, marketplace, user, and UI type definitions +3. **Mock data layer** — packages/mock-data with typed mock data for all surfaces +4. **Design tokens** — packages/ui/design-tokens with CSS custom properties +5. **UI primitives** — Button, Input, Card, Badge, Avatar, Dialog, Tabs, Progress, Tooltip, Skeleton, Toast +6. **Root layout + navigation shell** — Root layout with theme provider, responsive navigation, role switcher +7. **UI composites** — Navigation, Table, SearchBar, FilterPanel, ChatInterface, GraphViewer, ArtifactCard, CompetencyBadge, JobCard, CandidateCard, MetricCard +8. **Learner surface routes** — Landing, catalog, competency stack, dashboard, byte viewer, sandbox mockup, assessment mockup +9. **Marketplace surface routes** — Job board, job detail, employer profile, search/filter, pricing +10. **Employer dashboard routes** — Overview, talent search, candidate profile, posting management +11. **Admin surface routes** — Overview, learner management, competency graph viewer, moderation +12. **Polish + integration** — Cross-surface navigation, responsive QA, visual consistency, Storybook + +--- + +## Future Architecture (Post-v0.1, for reference) + +The v0.1 prototype is designed to be replaced piece-by-piece with real backend services: + +- **Mock data → PostgreSQL + Drizzle ORM** — mock data files replaced with database queries via repository layer +- **Static routes → Fastify API + Next.js SSR** — API routes replaced with Fastify backend services +- **Mock AI tutor → Python FastAPI AI services** — Chat interface mockup replaced with real AI agent microservices +- **Mock assessment → Assessment engine** — Assessment mockup replaced with process-trace grading + oral defense engine +- **No auth → Identity verification + age-gating** — Registration flow mockup replaced with real KYC and age verification +- **No search → Semantic vector search (pgvector)** — Filter UI replaced with vector similarity search +- **No payments → Payment processing** — Pricing page replaced with real subscription/payment flows + +The monorepo structure (apps/web + packages/*) is designed to accommodate additional apps (e.g., apps/api, apps/ai-service) in future milestones without restructuring. \ No newline at end of file diff --git a/.ciagent/CHECKPOINT.json b/.ciagent/CHECKPOINT.json new file mode 100644 index 0000000..78d5de2 --- /dev/null +++ b/.ciagent/CHECKPOINT.json @@ -0,0 +1,8 @@ +{ + "phase": 6, + "stage": "execute", + "milestone": "v0.1", + "phase_role": "execution", + "attempts": 0, + "updated_at": "2026-09-10T22:30:00Z" +} \ No newline at end of file diff --git a/.ciagent/PERSONAS.md b/.ciagent/PERSONAS.md new file mode 100644 index 0000000..62ac61d --- /dev/null +++ b/.ciagent/PERSONAS.md @@ -0,0 +1,124 @@ +# Nextcraft — PERSONAS.md + +## Persona Roster + +### lead-developer +```yaml +active: true +phase_specific: false +reason: Coordinates task decomposition across surfaces, resolves conflicts between frontend and data personas +domain: coordination +frameworks: + - next.js + - turborepo + - pnpm +constraints: + - pragmatic + - battle-tested defaults + - monorepo-architecture +territory: + - "**/package.json" + - "**/turbo.json" + - "**/pnpm-workspace.yaml" + - "**/tsconfig.json" +``` + +### frontend-engineer +```yaml +active: true +phase_specific: false +reason: Primary persona for v0.1 — all 28 REQ-IDs are UI/frontend work. Owns all page components, layouts, and surface-specific UI. +domain: frontend +frameworks: + - react + - next.js + - tailwindcss + - lucide-react + - recharts + - react-flow +constraints: + - component-first + - server-components-default + - minimal-client-js + - mock-data-only + - responsive-all-breakpoints + - dark-mode-support +territory: + - "apps/web/**" + - "packages/ui/**" + - "packages/mock-data/**" + - "packages/types/**" +``` + +### data-engineer +```yaml +active: true +phase_specific: false +reason: Owns mock data layer schema and typed definitions. No real database in v0.1, but data structures must be well-typed for future migration. +domain: data +frameworks: + - typescript +constraints: + - schema-first + - type-safe + - migration-ready + - mock-data-only +territory: + - "packages/types/**" + - "packages/mock-data/**" +``` + +### backend-engineer +```yaml +active: false +phase_specific: false +reason: No backend in v0.1. All data is mock/static. Backend persona deactivated until v0.2+ when API services are needed. +domain: backend +frameworks: [] +constraints: [] +territory: [] +``` + +### security-auditor +```yaml +active: false +phase_specific: false +reason: No auth, no API, no real data in v0.1. Security review handled by verifier's STRIDE analysis layer. No dedicated security persona needed for UI prototype. +domain: security +frameworks: [] +constraints: [] +territory: [] +``` + +## Custom Personas + +### design-system-engineer +```yaml +active: true +phase_specific: false +reason: Custom persona for v0.1 — owns the shared component library, design tokens, and visual consistency. Combines frontend expertise with design system ownership. +domain: frontend +frameworks: + - tailwindcss + - storybook + - lucide-react +constraints: + - design-token-driven + - wcag-aa-contrast + - dark-mode-required + - consistent-across-surfaces +territory: + - "packages/ui/**" +``` + +## Phase-Specific Personas + +None for v0.1. All active personas span the entire milestone. + +## Territory Conflict Resolution + +| Conflict | Resolution | +|----------|-----------| +| frontend-engineer vs data-engineer (packages/types, packages/mock-data) | data-engineer owns type definitions and mock data schema; frontend-engineer consumes them. If changes needed, data-engineer updates types first. | +| frontend-engineer vs design-system-engineer (packages/ui) | design-system-engineer owns design tokens and primitive components; frontend-engineer owns composite components and page-level UI. | +| lead-developer vs any | lead-developer coordinates only, does not directly modify code files. | \ No newline at end of file diff --git a/.ciagent/PLAN.md b/.ciagent/PLAN.md new file mode 100644 index 0000000..bba0daa --- /dev/null +++ b/.ciagent/PLAN.md @@ -0,0 +1,398 @@ +# Nextcraft v0.1 — PLAN.md + +## Overview + +This plan covers execution phases 1-6 of milestone v0.1 (UI/UX Prototype). Each phase is a vertical slice that produces a demoable milestone. Phases are ordered by dependency — later phases build on earlier ones. + +--- + +## Phase 1: Project Scaffolding + +**Requirements:** REQ-001, REQ-002, REQ-003, REQ-004, REQ-005 +**Persona:** design-system-engineer (Wave 1), data-engineer (Wave 1), frontend-engineer (Wave 2) +**Goal:** Monorepo builds, dev server starts, component library has all primitives, mock data typed, routing works + +### Wave 1: Foundation (parallel — no shared file conflicts) + +#### Task 1-1-01: Monorepo scaffolding +- **Persona:** design-system-engineer +- **Files:** `package.json`, `pnpm-workspace.yaml`, `turbo.json`, `tsconfig.json`, `.npmrc` +- **Action:** Create root monorepo config. pnpm workspaces pointing to `apps/*` and `packages/*`. Turborepo with build/dev/lint/typecheck pipelines. Root tsconfig with path aliases. +- **Verify:** `pnpm install` succeeds; workspace packages detected +- **Done:** `pnpm list -r` shows all workspace packages + +#### Task 1-1-02: Shared types package +- **Persona:** data-engineer +- **Files:** `packages/types/package.json`, `packages/types/tsconfig.json`, `packages/types/domain.ts`, `packages/types/marketplace.ts`, `packages/types/user.ts`, `packages/types/ui.ts`, `packages/types/index.ts` +- **Action:** Create all shared TypeScript type definitions: Competency, CompetencyStack, Microcredential, Artifact, ProcessTrace, OralDefense, AssessmentRubric, Job, Employer, Candidate, JobPosting, TalentMatch, SearchFilter, Learner, Admin, EmployerUser, AgeGroup, Role, plus UI types (ComponentProps, ThemeConfig, Breakpoint). +- **Verify:** `pnpm typecheck` passes in packages/types +- **Done:** All types exported from index.ts; no type errors + +#### Task 1-1-03: Mock data package +- **Persona:** data-engineer +- **Files:** `packages/mock-data/package.json`, `packages/mock-data/tsconfig.json`, `packages/mock-data/competency-stacks.ts`, `packages/mock-data/jobs.ts`, `packages/mock-data/candidates.ts`, `packages/mock-data/employers.ts`, `packages/mock-data/learner-progress.ts`, `packages/mock-data/ai-tutor-responses.ts`, `packages/mock-data/index.ts` +- **Action:** Create typed mock data: 5 competency stacks (AI Orchestration Engineer with 15 competencies, AI Safety & Governance with 14, Human-AI Product Designer with 13, AI-Augmented Field Operator with 12, Computational Sciences with 16). 20+ mock jobs. 15+ mock candidates. 10+ mock employers. Mock learner progress. Pre-scripted AI tutor responses. +- **Verify:** All mock data matches types from packages/types; `pnpm typecheck` passes +- **Done:** All mock data exported from index.ts; types match + +### Wave 2: UI Foundation (depends on Wave 1) + +#### Task 1-2-01: Next.js app initialization +- **Persona:** frontend-engineer +- **Files:** `apps/web/package.json`, `apps/web/tsconfig.json`, `apps/web/next.config.ts`, `apps/web/app/layout.tsx`, `apps/web/app/page.tsx`, `apps/web/app/globals.css` +- **Action:** Initialize Next.js app with App Router, TypeScript, Tailwind CSS v4. Configure Inter font via next/font. Set up path aliases to packages. Create root layout with theme provider. Create placeholder home page. +- **Verify:** `pnpm dev` starts; `pnpm build` succeeds; page renders at localhost:3000 +- **Done:** Next.js app runs with Tailwind CSS and Inter font + +#### Task 1-2-02: Design tokens and Tailwind config +- **Persona:** design-system-engineer +- **Files:** `packages/ui/package.json`, `packages/ui/tsconfig.json`, `packages/ui/src/index.ts`, `packages/ui/src/tokens/index.ts`, `apps/web/app/globals.css` (update) +- **Action:** Create packages/ui package. Define design tokens as TypeScript constants and CSS custom properties: color palette (indigo/violet primary, emerald accent, slate neutral, dark mode variants), typography scale (12px-48px), spacing system (4px base), breakpoints (375px, 768px, 1280px), shadows, radii. Configure Tailwind v4 `@theme` with custom colors. +- **Verify:** Design tokens importable from packages/ui; Tailwind classes work with custom colors +- **Done:** `import { tokens } from '@nextcraft/ui'` works; `bg-primary-500` class works + +#### Task 1-2-03: UI primitives +- **Persona:** design-system-engineer +- **Files:** `packages/ui/src/primitives/button.tsx`, `packages/ui/src/primitives/input.tsx`, `packages/ui/src/primitives/card.tsx`, `packages/ui/src/primitives/badge.tsx`, `packages/ui/src/primitives/avatar.tsx`, `packages/ui/src/primitives/dialog.tsx`, `packages/ui/src/primitives/tabs.tsx`, `packages/ui/src/primitives/progress.tsx`, `packages/ui/src/primitives/tooltip.tsx`, `packages/ui/src/primitives/skeleton.tsx`, `packages/ui/src/primitives/toast.tsx`, `packages/ui/src/primitives/index.ts` +- **Action:** Create all 12 primitive components. Each is a React component with TypeScript props, Tailwind styling, forwardRef, and size variants. Button (primary, secondary, ghost, destructive). Input (text, search, with label). Card (with header, body, footer slots). Badge (default, success, warning, error). Avatar (with image, fallback, sizes). Dialog (modal, with overlay). Tabs (horizontal, with active indicator). Progress (linear, circular). Tooltip (on hover). Skeleton (shimmer). Toast (with variants). +- **Verify:** All primitives importable; each renders without error; variants work +- **Done:** 12 primitives exported from packages/ui + +#### Task 1-2-04: Layout components +- **Persona:** design-system-engineer +- **Files:** `packages/ui/src/layouts/container.tsx`, `packages/ui/src/layouts/grid.tsx`, `packages/ui/src/layouts/sidebar.tsx`, `packages/ui/src/layouts/split-panel.tsx`, `packages/ui/src/layouts/dashboard-layout.tsx`, `packages/ui/src/layouts/index.ts` +- **Action:** Create layout components. Container (max-width variants: sm, md, lg, xl, full). Grid (responsive cols prop). Sidebar (collapsible, with nav items). SplitPanel (resizable divider). DashboardLayout (sidebar + main content area). +- **Verify:** Layout components render; responsive breakpoints work +- **Done:** 5 layout components exported + +#### Task 1-2-05: Root layout + navigation shell +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/layout.tsx` (update), `apps/web/components/navigation-shell.tsx`, `apps/web/components/role-switcher.tsx`, `apps/web/components/header.tsx`, `apps/web/components/footer.tsx` +- **Action:** Update root layout with theme provider (dark mode toggle), Inter font, responsive navigation shell. Create role switcher (learner/employer/admin toggle in header). Create header with logo, nav links, role switcher, dark mode toggle. Create footer with links. +- **Verify:** Dark mode toggle works; role switcher navigates between surfaces; responsive at 375px/768px/1280px +- **Done:** Navigation shell renders on all pages; role switcher functional + +#### Task 1-2-06: Route groups with placeholder pages +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(learner)/page.tsx`, `apps/web/app/(learner)/layout.tsx`, `apps/web/app/(marketplace)/page.tsx`, `apps/web/app/(marketplace)/layout.tsx`, `apps/web/app/(employer)/page.tsx`, `apps/web/app/(employer)/layout.tsx`, `apps/web/app/(admin)/page.tsx`, `apps/web/app/(admin)/layout.tsx` +- **Action:** Create route groups with layouts for each surface. Each layout has surface-specific navigation. Placeholder pages with surface name and brief description. +- **Verify:** Navigation to / (learner), /marketplace, /employer, /admin works +- **Done:** 4 route groups with layouts and placeholder pages + +### Must-Haves (Phase 1) +- [ ] `pnpm install` succeeds +- [ ] `pnpm dev` starts Next.js dev server +- [ ] `pnpm build` succeeds +- [ ] `pnpm typecheck` passes +- [ ] 12 UI primitives importable from `@nextcraft/ui` +- [ ] Mock data typed and importable from `@nextcraft/mock-data` +- [ ] Types importable from `@nextcraft/types` +- [ ] 4 route groups with layouts +- [ ] Dark mode toggle works +- [ ] Role switcher navigates between surfaces +- [ ] Responsive at 375px, 768px, 1280px + +--- + +## Phase 2: Learner Surface UI + +**Requirements:** REQ-006, REQ-007, REQ-008, REQ-009, REQ-010, REQ-011, REQ-012 +**Persona:** frontend-engineer +**Goal:** All 7 learner pages render with mock data; navigation works; responsive + +### Wave 1: Core learner pages + +#### Task 2-1-01: Landing page +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(learner)/page.tsx`, `apps/web/components/learner/hero.tsx`, `apps/web/components/learner/how-it-works.tsx`, `apps/web/components/learner/program-highlights.tsx`, `apps/web/components/learner/testimonials.tsx` +- **Action:** Build landing page with hero section (headline, subheadline, CTA buttons), how-it-works section (Byte→Build→Demonstrate→Defend visual flow), program highlights (3-4 featured competency stacks), testimonials mockup (3 cards with avatar, quote, name, role), CTA to program catalog. +- **Verify:** Landing page renders with all sections; CTA links to /catalog +- **Done:** Landing page complete + +#### Task 2-1-02: Program catalog +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(learner)/catalog/page.tsx`, `apps/web/components/learner/stack-card.tsx` +- **Action:** Build program catalog page. Grid of 5 competency stack cards. Each card: stack name, role description, competency count, estimated duration, microcredential count, "Explore" button linking to stack detail. +- **Verify:** 5 stack cards render with mock data; clicking "Explore" navigates to stack detail +- **Done:** Catalog page complete + +#### Task 2-1-03: Competency stack view +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(learner)/catalog/[stackId]/page.tsx`, `apps/web/components/learner/competency-list.tsx`, `apps/web/components/learner/competency-item.tsx`, `apps/web/components/learner/microcredential-badge.tsx` +- **Action:** Build competency stack detail page. Header with stack name, role, description. List of 12-18 competencies. Each competency: name, description, status (locked/available/in-progress/mastered), microcredential badge, progress bar. Clicking a competency navigates to byte tutorial viewer. +- **Verify:** Competencies render with correct status indicators; microcredential badges display +- **Done:** Stack detail page complete + +#### Task 2-1-04: Learner dashboard +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(learner)/dashboard/page.tsx`, `apps/web/components/learner/dashboard/active-competencies.tsx`, `apps/web/components/learner/dashboard/progress-graph.tsx`, `apps/web/components/learner/dashboard/recent-artifacts.tsx`, `apps/web/components/learner/dashboard/upcoming-defenses.tsx`, `apps/web/components/learner/dashboard/ai-tutor-chat.tsx`, `apps/web/components/learner/dashboard/milestone-tracker.tsx` +- **Action:** Build learner dashboard. Active competencies panel (current stack, progress). Progress graph (recharts area chart showing mastery over time). Recent artifacts (cards with artifact name, type, date). Upcoming defenses (list with competency name, date, status). AI tutor chat mockup (chat interface with pre-scripted responses, message input). Milestone tracker (progress through stack). +- **Verify:** Dashboard renders all panels; AI tutor chat displays scripted responses on "send" +- **Done:** Dashboard complete + +### Wave 2: Detail views + +#### Task 2-2-01: Byte tutorial viewer +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(learner)/learn/[competencyId]/page.tsx`, `apps/web/components/learner/byte-viewer/concept-panel.tsx`, `apps/web/components/learner/byte-viewer/worked-example.tsx`, `apps/web/components/learner/byte-viewer/viewer-tabs.tsx` +- **Action:** Build byte tutorial viewer. Layout: left panel (concept text, 3-7 minute read), right panel (worked example with code/design/simulation viewer mockup). Tabbed viewer (code, design, simulation). Navigation to build sandbox. +- **Verify:** Tutorial viewer renders with concept and example panels; tabs switch +- **Done:** Byte tutorial viewer complete + +#### Task 2-2-02: Build sandbox mockup +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(learner)/build/[competencyId]/page.tsx`, `apps/web/components/learner/sandbox/toolbar.tsx`, `apps/web/components/learner/sandbox/file-explorer.tsx`, `apps/web/components/learner/sandbox/editor-area.tsx`, `apps/web/components/learner/sandbox/telemetry-sidebar.tsx` +- **Action:** Build sandbox mockup IDE UI. Layout: top toolbar (run, save, submit buttons), left sidebar (file explorer tree), center (code editor area with syntax-highlighted mock code), right sidebar (telemetry — process capture indicators showing commits, keystrokes, time spent). No real code execution. +- **Verify:** Sandbox UI renders with all panels; toolbar buttons are interactive (non-functional) +- **Done:** Build sandbox mockup complete + +#### Task 2-2-03: Assessment/defense mockup +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(learner)/defend/[competencyId]/page.tsx`, `apps/web/components/learner/assessment/rubric-display.tsx`, `apps/web/components/learner/assessment/ai-reviewer-panel.tsx`, `apps/web/components/learner/assessment/oral-defense.tsx`, `apps/web/components/learner/assessment/process-trace.tsx`, `apps/web/components/learner/assessment/artifact-viewer.tsx` +- **Action:** Build assessment/defense mockup. Layout: top (artifact viewer — submitted work preview), left (rubric display — competency criteria with checkmarks), right (AI reviewer panel — assessment results), bottom (oral defense interface — mic button mockup, waveform animation, transcript area). Process trace timeline (showing build steps with timestamps). +- **Verify:** Assessment mockup renders all panels; mic button has hover state; timeline displays mock events +- **Done:** Assessment/defense mockup complete + +### Must-Haves (Phase 2) +- [ ] Landing page renders with hero, how-it-works, highlights, testimonials, CTA +- [ ] Program catalog shows 5 competency stack cards +- [ ] Competency stack view shows 12-18 competencies with status and badges +- [ ] Learner dashboard shows all 6 panels including AI tutor chat mockup +- [ ] Byte tutorial viewer renders with concept and worked example panels +- [ ] Build sandbox mockup renders with toolbar, file explorer, editor, telemetry +- [ ] Assessment mockup renders with rubric, AI reviewer, oral defense, process trace +- [ ] All pages responsive at 375px, 768px, 1280px +- [ ] Navigation between all learner pages works + +--- + +## Phase 3: Marketplace Surface UI + +**Requirements:** REQ-013, REQ-014, REQ-015, REQ-016, REQ-017 +**Persona:** frontend-engineer +**Goal:** All 5 marketplace pages render with mock data; search/filter interactive + +### Wave 1: Job board + search + +#### Task 3-1-01: Job board listing +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(marketplace)/page.tsx`, `apps/web/components/marketplace/job-card.tsx`, `apps/web/components/marketplace/job-listing-grid.tsx` +- **Action:** Build job board listing page. Layout: left sidebar (filters), main area (grid of job cards). Each job card: job title, company name, logo, location, remote badge, salary range, match score (percentage badge), required skills (tag chips), posted date. 20+ mock job listings. +- **Verify:** 20+ job cards render; match scores display; skill tags show +- **Done:** Job board listing complete + +#### Task 3-1-02: Search/filter UI +- **Persona:** frontend-engineer +- **Files:** `apps/web/components/marketplace/filter-sidebar.tsx`, `apps/web/components/marketplace/search-bar.tsx`, `apps/web/components/marketplace/saved-searches.tsx` +- **Action:** Build filter sidebar. Semantic search bar at top (with icon, placeholder text). Filters: skills (multi-select chips), seniority (dropdown), location (text input), remote toggle, salary range (min/max inputs). Saved searches mockup section. Client-side filtering of mock jobs using useState. +- **Verify:** Typing in search filters jobs; selecting skill filters jobs; toggling remote filters jobs +- **Done:** Search/filter UI functional with client-side filtering + +### Wave 2: Detail pages + +#### Task 3-2-01: Job detail page +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(marketplace)/jobs/[jobId]/page.tsx`, `apps/web/components/marketplace/job-detail.tsx`, `apps/web/components/marketplace/skills-breakdown.tsx`, `apps/web/components/marketplace/related-jobs.tsx` +- **Action:** Build job detail page. Layout: header (job title, company, location, salary, apply button), body (full description, required competencies list, AI-matched skills breakdown with match percentages), sidebar (employer info card, related jobs list). +- **Verify:** Job detail renders with all sections; apply button is interactive (non-functional) +- **Done:** Job detail page complete + +#### Task 3-2-02: Employer profile +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(marketplace)/employers/[employerId]/page.tsx`, `apps/web/components/marketplace/employer-header.tsx`, `apps/web/components/marketplace/employer-openings.tsx`, `apps/web/components/marketplace/employer-culture.tsx` +- **Action:** Build employer profile page. Header: logo, company name, tagline, industry, size, location, social links. Body: about section, culture section (mock photos grid), open positions list (job cards). +- **Verify:** Employer profile renders with all sections; social links are clickable (mock URLs) +- **Done:** Employer profile complete + +#### Task 3-2-03: Pricing page +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(marketplace)/pricing/page.tsx`, `apps/web/components/marketplace/pricing-card.tsx`, `apps/web/components/marketplace/feature-comparison.tsx` +- **Action:** Build pricing page. Three pricing cards: Job Posting (single — $49, bundle — $399 for 10, enterprise — custom). Talent Access plans (starter, pro, enterprise). Feature comparison table. CTA buttons on each plan. +- **Verify:** Pricing page renders with 3 tiers and comparison table; responsive +- **Done:** Pricing page complete + +### Must-Haves (Phase 3) +- [ ] Job board shows 20+ mock job listings with match scores +- [ ] Search bar filters jobs client-side +- [ ] Filter sidebar filters by skills, seniority, remote, salary +- [ ] Job detail page shows full description, competencies, skills breakdown +- [ ] Employer profile shows company info, culture, open positions +- [ ] Pricing page shows 3 tiers with feature comparison +- [ ] All pages responsive at 375px, 768px, 1280px + +--- + +## Phase 4: Employer Dashboard UI + +**Requirements:** REQ-018, REQ-019, REQ-020, REQ-021 +**Persona:** frontend-engineer +**Goal:** All 4 employer dashboard pages render with mock data; charts display + +### Wave 1: Dashboard + talent search + +#### Task 4-1-01: Employer dashboard overview +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(employer)/page.tsx`, `apps/web/components/employer/overview/metric-cards.tsx`, `apps/web/components/employer/overview/applicant-pipeline.tsx`, `apps/web/components/employer/overview/talent-matches.tsx`, `apps/web/components/employer/overview/analytics-charts.tsx` +- **Action:** Build employer dashboard overview. Top row: 4 metric cards (active postings, total applicants, talent matches, placement rate). Middle: applicant pipeline (kanban-style columns: applied, screening, interview, offer, hired). Right: talent matches (list of matched candidates with match score). Bottom: analytics charts (recharts — bar chart for postings over time, donut chart for applicant sources, line chart for placement trends). +- **Verify:** Dashboard renders with metric cards, pipeline, matches, and 3 chart types +- **Done:** Employer dashboard overview complete + +#### Task 4-1-02: Talent search +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(employer)/talent/page.tsx`, `apps/web/components/employer/talent/candidate-card.tsx`, `apps/web/components/employer/talent/talent-filters.tsx` +- **Action:** Build talent search page. Layout: top (search bar + AI-matched filters), main (grid of candidate cards). Each candidate card: avatar, name, headline (competency stack + level), microcredential badges (top 3), artifact count, defense score, match percentage, "View Profile" button. 15+ mock candidates. Client-side filtering. +- **Verify:** 15+ candidate cards render; filters work client-side; clicking "View Profile" navigates to candidate profile +- **Done:** Talent search complete + +### Wave 2: Detail + management + +#### Task 4-2-01: Candidate profile view +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(employer)/talent/[candidateId]/page.tsx`, `apps/web/components/employer/talent-profile/artifact-gallery.tsx`, `apps/web/components/employer/talent-profile/process-trace-summary.tsx`, `apps/web/components/employer/talent-profile/defense-transcripts.tsx`, `apps/web/components/employer/talent-profile/competency-mini-graph.tsx`, `apps/web/components/employer/talent-profile/microcredential-verification.tsx` +- **Action:** Build candidate profile view. Header: avatar, name, headline, match score, contact button. Body sections: artifact gallery (grid of project artifacts with thumbnails), process trace summary (timeline of build steps), oral defense transcripts (accordion of defense sessions with Q&A), competency mini-graph (react-flow visualization of candidate's competencies), microcredential verification (list of earned credentials with verification badges). +- **Verify:** All profile sections render; defense transcripts accordion expands; mini-graph renders +- **Done:** Candidate profile complete + +#### Task 4-2-02: Posting management +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(employer)/postings/page.tsx`, `apps/web/components/employer/postings/posting-list.tsx`, `apps/web/components/employer/postings/posting-form.tsx`, `apps/web/components/employer/postings/applicant-list.tsx` +- **Action:** Build posting management page. Layout: left (list of job postings with status badges: active, draft, expired), right (selected posting detail or create/edit form). Form fields: title, description, required competencies (multi-select), seniority, salary range, location, remote toggle. Applicant list per posting (table with name, applied date, status, match score). "Create New Posting" button opens form. +- **Verify:** Posting list renders; form is interactive (inputs work, non-functional submit); applicant list displays +- **Done:** Posting management complete + +### Must-Haves (Phase 4) +- [ ] Dashboard overview shows 4 metric cards, applicant pipeline, 3 charts +- [ ] Talent search shows 15+ candidate cards with filters +- [ ] Candidate profile shows artifact gallery, process trace, defense transcripts, mini-graph, credentials +- [ ] Posting management shows posting list, create/edit form, applicant list +- [ ] All pages responsive at 375px, 768px, 1280px + +--- + +## Phase 5: Admin Surface UI + +**Requirements:** REQ-022, REQ-023, REQ-024, REQ-025 +**Persona:** frontend-engineer +**Goal:** All 4 admin pages render; competency graph viewer interactive + +### Wave 1: Overview + learner management + +#### Task 5-1-01: Admin overview +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(admin)/page.tsx`, `apps/web/components/admin/overview/platform-metrics.tsx`, `apps/web/components/admin/overview/activity-feed.tsx`, `apps/web/components/admin/overview/system-health.tsx` +- **Action:** Build admin overview. Top: platform metric cards (total learners, total employers, placements, completion rate, NPS). Middle: activity feed (timeline of recent platform events — new registrations, completions, job postings, placements). Right: system health mockup (status indicators for services, uptime bars, error rates). +- **Verify:** Admin overview renders with metrics, activity feed, system health +- **Done:** Admin overview complete + +#### Task 5-1-02: Learner management +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(admin)/learners/page.tsx`, `apps/web/components/admin/learners/learner-table.tsx`, `apps/web/components/admin/learners/learner-detail.tsx` +- **Action:** Build learner management page. Layout: searchable learner table (columns: name, email, stack, progress %, status, joined date). Search and filter (by stack, status). Clicking a learner opens detail panel (progress tracking, competency completion, credential issuance log, recent activity). +- **Verify:** Learner table renders with mock data; search filters; detail panel opens on click +- **Done:** Learner management complete + +### Wave 2: Graph viewer + moderation + +#### Task 5-2-01: Competency graph viewer +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(admin)/graph/page.tsx`, `apps/web/components/admin/graph/competency-graph.tsx`, `apps/web/components/admin/graph/node-detail.tsx` +- **Action:** Build competency graph viewer using @xyflow/react. Interactive node/edge graph showing competency stacks and their relationships. Nodes: competency stacks (colored by category) and individual competencies. Edges: prerequisite relationships. Node click opens detail panel (competency name, description, stack, prerequisites, learners mastering it). Controls: zoom, pan, fit-to-screen. Mock graph data. +- **Verify:** Graph renders with nodes and edges; nodes are clickable; detail panel opens; zoom/pan works +- **Done:** Competency graph viewer complete + +#### Task 5-2-02: Marketplace moderation +- **Persona:** frontend-engineer +- **Files:** `apps/web/app/(admin)/moderation/page.tsx`, `apps/web/components/admin/moderation/review-queue.tsx`, `apps/web/components/admin/moderation/verification-queue.tsx`, `apps/web/components/admin/moderation/flagged-content.tsx` +- **Action:** Build marketplace moderation page. Three tabs: Job Posting Review (queue of pending job postings with approve/reject buttons), Employer Verification (queue of employers awaiting identity verification), Flagged Content (list of reported content with reason, reporter, actions). All buttons non-functional but interactive (hover states, click feedback). +- **Verify:** Moderation page renders with 3 tabs; queues display mock items; tab switching works +- **Done:** Marketplace moderation complete + +### Must-Haves (Phase 5) +- [ ] Admin overview shows platform metrics, activity feed, system health +- [ ] Learner management shows searchable table with detail panel +- [ ] Competency graph viewer renders interactive graph with clickable nodes +- [ ] Marketplace moderation shows 3 tabbed queues +- [ ] All pages responsive at 375px, 768px, 1280px + +--- + +## Phase 6: Polish + Integration + +**Requirements:** REQ-026, REQ-027, REQ-028 +**Persona:** frontend-engineer, design-system-engineer +**Goal:** Cross-surface consistency, dark mode everywhere, Storybook + +### Wave 1: Navigation + consistency + +#### Task 6-1-01: Cross-surface navigation polish +- **Persona:** frontend-engineer +- **Files:** `apps/web/components/navigation-shell.tsx` (update), `apps/web/components/role-switcher.tsx` (update), `apps/web/components/breadcrumbs.tsx` +- **Action:** Polish cross-surface navigation. Role switcher dropdown with surface-specific sub-navigation. Breadcrumbs on all pages showing current location. Consistent header/footer across all 4 surfaces. Active nav link highlighting. +- **Verify:** Breadcrumbs show on all pages; role switcher dropdown works; active links highlighted +- **Done:** Cross-surface navigation polished + +#### Task 6-1-02: Visual consistency audit +- **Persona:** design-system-engineer +- **Files:** `apps/web/app/globals.css` (update), `packages/ui/src/tokens/index.ts` (update if needed) +- **Action:** Audit all surfaces for visual consistency. Check: typography scale (all text uses design tokens), color palette (no hardcoded colors), spacing system (all margins/padding use design tokens), dark mode (all pages support dark mode toggle), WCAG AA contrast (all text meets 4.5:1 ratio). Fix any inconsistencies found. +- **Verify:** No hardcoded colors; dark mode works on all pages; contrast check passes +- **Done:** Visual consistency audit complete + +### Wave 2: Storybook + +#### Task 6-2-01: Storybook setup +- **Persona:** design-system-engineer +- **Files:** `apps/web/.storybook/main.ts`, `apps/web/.storybook/preview.ts`, `packages/ui/src/primitives/*.stories.tsx` (one per primitive) +- **Action:** Set up Storybook for the component library. Configure for Next.js + Tailwind. Create stories for all 12 primitives and key composites (JobCard, CandidateCard, CompetencyBadge, MetricCard). Each story: default, variants, sizes, dark mode. +- **Verify:** `pnpm storybook` starts; all primitive stories render; dark mode toggle in Storybook +- **Done:** Storybook documents all primitives + key composites + +### Must-Haves (Phase 6) +- [ ] Breadcrumbs on all pages +- [ ] Role switcher dropdown with surface-specific nav +- [ ] Active nav link highlighting +- [ ] No hardcoded colors — all from design tokens +- [ ] Dark mode works on all 4 surfaces +- [ ] WCAG AA contrast passes +- [ ] Storybook runs with all primitive stories + +--- + +## MVP/UX Sections + +### User-Facing Surface +The primary user-facing surface is the **Nextcraft web application** at `localhost:3000` (dev) with 4 route groups: +- `/` — Learner surface (landing, catalog, dashboard, learn, build, defend) +- `/marketplace` — Marketplace surface (jobs, employers, pricing) +- `/employer` — Employer dashboard (overview, talent, postings) +- `/admin` — Admin surface (overview, learners, graph, moderation) + +### Happy Path +1. User visits landing page → sees hero, how-it-works (Byte→Build→Demonstrate→Defend), program highlights +2. User clicks "Explore Programs" → program catalog shows 5 competency stacks +3. User clicks "AI Orchestration Engineer" → competency stack view shows 15 competencies +4. User clicks a competency → byte tutorial viewer shows concept + worked example +5. User clicks "Start Building" → build sandbox mockup shows IDE UI with telemetry +6. User clicks "Submit for Assessment" → assessment/defense mockup shows rubric, AI reviewer, oral defense interface +7. User navigates to marketplace → job board shows AI-era job listings with match scores +8. User searches/filters jobs → results update client-side +9. User clicks a job → job detail page shows full description, required competencies, employer info +10. User switches to employer dashboard → overview shows metrics, pipeline, charts +11. User searches talent → candidate cards with microcredentials, defense scores +12. User clicks a candidate → profile shows artifact gallery, process trace, defense transcripts +13. User switches to admin → overview shows platform metrics, activity feed +14. User opens competency graph viewer → interactive graph with clickable nodes + +### UX Acceptance Criteria +1. All 4 surfaces are accessible via the role switcher in the header +2. Navigation is consistent across all surfaces (same header, footer, breadcrumb pattern) +3. All pages are responsive at 375px (mobile), 768px (tablet), 1280px (desktop) +4. Dark mode toggle works on all surfaces +5. All interactive elements have hover states and click feedback +6. Form inputs are non-functional but visually complete (placeholders, labels, validation states) +7. Mock data is realistic and typed — no "lorem ipsum" or placeholder text +8. All charts render correctly (recharts) with mock data +9. Competency graph viewer is interactive (zoom, pan, click nodes) +10. AI tutor chat displays pre-scripted responses on message "send" +11. No console errors on any page +12. `pnpm build` succeeds without warnings \ No newline at end of file diff --git a/.ciagent/PROJECT.md b/.ciagent/PROJECT.md new file mode 100644 index 0000000..f90950e --- /dev/null +++ b/.ciagent/PROJECT.md @@ -0,0 +1,102 @@ +# Nextcraft — PROJECT.md + +## What This Is + +Nextcraft is an AI-native outcome school where graduates prove what they can build — not what they can write. It credentials verifiable skill for a post-AI labor market, rejects legacy degree structures, and trains the workforce of tomorrow through hands-on competency stacks assessed entirely by AI tutors. The marketplace is the symbiotic second surface: employers meet AI-credentialed talent through an automated, algorithmically-matched job board that runs without human headcount. + +**Single guiding outcome:** ≥1,000 learners placed in AI-orchestration roles via the Nextcraft marketplace within 36 months of launch, verified by CIRR-style third-party audit. + +--- + +## Current Milestone: v0.1 — UI/UX Prototype + +**Scope:** High-fidelity interactive prototype of all four Nextcraft surfaces with realistic mock data, navigation flows, responsive layouts, and a shared component library. No backend, no business logic, no database — everything static/mocked. + +**Constraint:** MAJOR 0 until MVP is released. No business logic until the first milestone prototype is agreed upon by the founder. + +**Tech stack:** TypeScript monorepo (pnpm/turborepo) with Next.js for all web surfaces. Python FastAPI AI services planned for later milestones (not in v0.1). + +--- + +## Requirements (Validated) + +The following requirements have been validated during specification and are locked for milestone v0.1: + +1. Learner surface UI — landing page, program catalog, competency stack view, learner dashboard, byte tutorial viewer, build sandbox mockup, assessment/defense mockup +2. Marketplace surface UI — job board listing, job detail page, employer profile, search/filter UI, job posting packages pricing page +3. Employer dashboard UI — overview, talent search, candidate profile view, posting management +4. Admin surface UI — overview, learner management, competency graph viewer, marketplace moderation +5. Shared component library — design system, reusable UI primitives, surface-specific theming +6. Responsive layout system — mobile, tablet, desktop breakpoints +7. Mock data layer — typed, realistic data reflecting the vision (competency stacks, job listings, candidate profiles) +8. Navigation/routing — cross-surface navigation, role-based route groups + +## Requirements (Active — Future Milestones) + +The following are deferred beyond v0.1 and will be activated in subsequent milestones: + +- AI tutor agent architecture (Coach, Tutor, Lab, Assessor, Proctor, Mentor) +- Competency graph engine and adaptive pathways +- Assessment engine (process-trace grading, oral defense, per-learner variant tasks) +- Sandbox fabric (sandboxed IDE, design tool, simulation) +- Identity verification and age-gating logic (16+/18+) +- Marketplace job aggregation pipeline (3M+ jobs from 120K companies) +- AI-powered tagging, semantic vector search, company enrichment +- AI resume parsing and job matching +- SEO-optimized programmatic pages +- Payment processing and subscription management +- Human tutor marketplace (third-party course creation) +- B2B employer network functionality +- CIRR-style placement tracking and audit + +## Requirements (Out of Scope — Per Vision Doctrine) + +- Traditional accreditation — not sought, not pursued, not revisited +- Under-16 learners — excluded; AI school floor is 16+ +- Marketplace for under-18 — excluded; marketplace is 18+ with verified identity +- Human tutors in the core school — excluded; humans exist only in the open marketplace +- Graded written exams without process trace — excluded +- Legacy job titles in curriculum — excluded +- Junk advertising — excluded + +--- + +## Constraints + +1. **MAJOR 0 until MVP** — all versions remain v0.x until the MVP is released and agreed upon by the founder +2. **No business logic in v0.1** — pure UI/UX prototype with mock data only +3. **High-fidelity interactive** — clickable navigation, realistic content, hover states, form inputs (non-functional), responsive breakpoints, loading states (mock) +4. **All data mocked** — no real API calls, no database, no authentication logic +5. **Shared design system** — all 4 surfaces use a unified component library with surface-specific theming via CSS variables +6. **TypeScript monorepo** — pnpm workspaces + turborepo for build orchestration +7. **Next.js App Router** — route groups for each surface: (learner), (marketplace), (employer), (admin) +8. **Solo founder constraint** — Phase 1 is build-only with no headcount; the prototype must be producible by a solo developer with AI assistance +9. **No traditional accreditation** — proprietary credential replaces degrees +10. **AI-first architecture** — AI tutors are the primary human-facing layer (in future milestones) + +--- + +## Key Decisions + +| ID | Decision | Rationale | Outcome | +|----|----------|-----------|---------| +| D-001 | Milestone v0.1 = UI/UX prototype only, no business logic | Founder directive: validate UX before building backend. Prototype must be agreed upon before proceeding to business logic. | Scope locked to frontend surfaces with mock data | +| D-002 | MAJOR 0 until MVP released | Founder directive: remain on v0.x until MVP is validated. Signals pre-release status. | All tags are v0.x.y until MVP agreement | +| D-003 | TypeScript monorepo (pnpm/turborepo) + Next.js | Unified codebase for all 4 surfaces. Shared component library, types, mock data. Next.js App Router for route-based surface separation. Python AI services deferred to later milestones. | Monorepo structure with apps/web + packages/* | +| D-004 | All 4 surfaces in v0.1 (Learner, Marketplace, Employer, Admin) | Founder selected all 4 surfaces for the prototype. Complete product visualization before any backend work. | 24 REQ-IDs covering all surfaces + shared infrastructure | +| D-005 | High-fidelity interactive prototype | Founder selected high-fidelity over wireframes. Realistic mock data, navigation flows, responsive layouts, component library. No backend calls. | Clickable prototype with realistic content | +| D-006 | Release forge = Gitea @ git.cloudinit.dev, owner=coreci, repo=nextcraft | Founder-provided Gitea instance for release management. Token stored in .ciagent/.env.secrets. | Ship workflow creates tags + releases on Gitea | +| D-007 | Full autonomy for CIAgent pipeline | Founder selected full autonomy. No HITL after clarify. Auto-decide above confidence 0.60. Escalation hooks: deploy, delete_data, merge_to_main. | Rapid autonomous building with kill criteria | +| D-008 | Shared component library in packages/ui/ | All surfaces share a unified design system with surface-specific theming via CSS variables. Promotes consistency and reduces duplication. | packages/ui, packages/mock-data, packages/types | +| D-009 | AI tutor UI as chat interface mockup with pre-scripted responses | The learner surface includes an AI tutor chat UI mockup. No real AI backend — pre-scripted responses simulate the Coach and Tutor agents. | Mockup only in v0.1, real agents in future milestone | +| D-010 | Age-gating represented as visual registration flow mockup | 16+/18+ age-gating shown as a UI flow with age verification step. No actual verification logic. | Visual mockup only | +| D-011 | Competency graph viewer as interactive static visualization | Admin surface includes a competency graph viewer using react-flow or similar. Mock competency nodes and edges. No real graph data. | Static graph with mock data | +| D-012 | Tech stack: TS monorepo + Python AI services (future) | v0.1 uses TS only. Python FastAPI microservices planned for AI tutor agents and assessment engine in later milestones. | v0.1: TS only. Future: TS + Python | + +--- + +## Context + +Nextcraft is being built by a solo founder using the CIAgent v0.7.0 autonomous pipeline. The vision document defines a 36-month, 3-phase roadmap to ≥1,000 placements. The first CIAgent milestone (v0.1) is intentionally scoped to UI/UX only — validating the product vision through interactive prototypes before any backend or business logic investment. + +The four surfaces (Learner, Marketplace, Employer Dashboard, Admin) map directly to the four audiences in the vision: learners, employers, marketplace operators, and platform administrators. The prototype will demonstrate the complete user journey across all surfaces with realistic mock data reflecting the AI-era competency stacks, AI-orchestration job listings, and artifact+process trace+oral defense credential model. \ No newline at end of file diff --git a/.ciagent/REQUIREMENTS.md b/.ciagent/REQUIREMENTS.md new file mode 100644 index 0000000..f1df24d --- /dev/null +++ b/.ciagent/REQUIREMENTS.md @@ -0,0 +1,154 @@ +# Nextcraft — REQUIREMENTS.md + +## v0.1 Requirements (UI/UX Prototype) + +### Infrastructure + +| ID | Description | Priority | Phase | Status | +|----|-------------|----------|-------|--------| +| REQ-001 | Monorepo scaffolding: pnpm workspaces, turborepo, Next.js app, TypeScript config, ESLint, Prettier | critical | 1 | pending | +| REQ-002 | Shared component library: design tokens, Button, Input, Card, Badge, Avatar, Dialog, Navigation, Table, Tabs, Progress, Tooltip, Skeleton, Toast | critical | 1 | pending | +| REQ-003 | Mock data layer: typed mock data for competency stacks, job listings, candidate profiles, employers, learner progress | critical | 1 | pending | +| REQ-004 | Routing and navigation: App Router route groups for (learner), (marketplace), (employer), (admin); shared layout components; cross-surface navigation | critical | 1 | pending | +| REQ-005 | Responsive layout system: mobile, tablet, desktop breakpoints; container components; grid system | high | 1 | pending | + +### Learner Surface + +| ID | Description | Priority | Phase | Status | +|----|-------------|----------|-------|--------| +| REQ-006 | Landing page: hero, value proposition, program highlights, how-it-works (Byte→Build→Demonstrate→Defend), testimonials mockup, CTA to program catalog | critical | 2 | pending | +| REQ-007 | Program catalog: grid of competency stacks (AI Orchestration Engineer, AI Safety & Governance Lead, Human-AI Product Designer, AI-Augmented Field Operator, Computational Sciences Practitioner); stack cards with role descriptions | critical | 2 | pending | +| REQ-008 | Competency stack view: selected stack detail with 12-18 competencies listed, progress indicators, microcredential badges, mastery status | critical | 2 | pending | +| REQ-009 | Learner dashboard: active competencies, progress graph, recent artifacts, upcoming defenses, AI tutor chat mockup, milestone tracker | critical | 2 | pending | +| REQ-010 | Byte tutorial viewer: 3-7 minute micro-tutorial layout with concept panel, worked example panel, code/design/simulation viewer mockup | high | 2 | pending | +| REQ-011 | Build sandbox mockup: sandboxed IDE/design tool/simulation UI mockup with toolbar, file explorer, editor area, telemetry sidebar (process capture indicators) | high | 2 | pending | +| REQ-012 | Assessment/defense mockup: rubric display, AI reviewer panel, oral defense interface (voice/mic mockup), process trace timeline, artifact viewer | high | 2 | pending | + +### Marketplace Surface + +| ID | Description | Priority | Phase | Status | +|----|-------------|----------|-------|--------| +| REQ-013 | Job board listing: searchable grid of AI-era job listings, filter sidebar (skills, seniority, location, salary), result cards with match score | critical | 3 | pending | +| REQ-014 | Job detail page: full job description, required competencies, employer info, application CTA, related jobs, AI-matched skills breakdown | critical | 3 | pending | +| REQ-015 | Employer profile: company overview, logo, description, social links, open positions, company culture mockup | high | 3 | pending | +| REQ-016 | Search/filter UI: semantic search bar, skill tags, category filters, seniority filter, remote/on-site toggle, saved searches mockup | critical | 3 | pending | +| REQ-017 | Pricing page: job posting packages (single, bundle, enterprise), talent access plans, subscription tiers, feature comparison table | high | 3 | pending | + +### Employer Dashboard + +| ID | Description | Priority | Phase | Status | +|----|-------------|----------|-------|--------| +| REQ-018 | Employer dashboard overview: active postings, applicant pipeline, talent matches, analytics mockup (charts, placement stats) | critical | 4 | pending | +| REQ-019 | Talent search: searchable candidate database with AI-matched filters, candidate cards showing competency stack, microcredentials, artifact count, defense score | critical | 4 | pending | +| REQ-020 | Candidate profile view: full candidate profile with artifact gallery, process trace summary, oral defense transcripts, competency graph, microcredential verification | high | 4 | pending | +| REQ-021 | Posting management: create/edit/delete job postings, posting status tracking, applicant list per posting, interview pipeline mockup | high | 4 | pending | + +### Admin Surface + +| ID | Description | Priority | Phase | Status | +|----|-------------|----------|-------|--------| +| REQ-022 | Admin overview: platform metrics (learners, employers, placements, completion rate, NPS), recent activity feed, system health mockup | critical | 5 | pending | +| REQ-023 | Learner management: searchable learner table, learner detail view, progress tracking, competency completion status, credential issuance log | high | 5 | pending | +| REQ-024 | Competency graph viewer: interactive visualization of competency stacks and their relationships, node/edge graph using react-flow, stack details on node click | high | 5 | pending | +| REQ-025 | Marketplace moderation: job posting review queue, employer verification queue, flagged content, content moderation tools mockup | high | 5 | pending | + +### Polish & Integration + +| ID | Description | Priority | Phase | Status | +|----|-------------|----------|-------|--------| +| REQ-026 | Cross-surface navigation: role switcher (learner/employer/admin), breadcrumbs, consistent header/footer across all surfaces | critical | 6 | pending | +| REQ-027 | Visual consistency audit: typography scale, color palette, spacing system, dark mode toggle, accessibility baseline (WCAG AA contrast) | high | 6 | pending | +| REQ-028 | Component library finalization: Storybook setup, component documentation, prop tables, usage examples | medium | 6 | pending | + +--- + +## v2 Requirements (Future Milestones — Deferred) + +### AI Tutor Architecture + +| ID | Description | Priority | Milestone | Status | +|----|-------------|----------|-----------|--------| +| REQ-F-001 | Coach agent: pacing, motivation, retrieval practice | high | v0.2+ | deferred | +| REQ-F-002 | Tutor agent: concept delivery, Socratic questioning | high | v0.2+ | deferred | +| REQ-F-003 | Lab agent: sandbox execution, in-flow feedback | high | v0.2+ | deferred | +| REQ-F-004 | Assessor agent: rubric application to artifacts and defenses | high | v0.2+ | deferred | +| REQ-F-005 | Proctor agent: identity, attention, integrity signals | high | v0.2+ | deferred | +| REQ-F-006 | Mentor agent: long-horizon career narrative | medium | v0.2+ | deferred | + +### Assessment Engine + +| ID | Description | Priority | Milestone | Status | +|----|-------------|----------|-----------|--------| +| REQ-F-007 | Process-trace grading engine | high | v0.3+ | deferred | +| REQ-F-008 | Per-learner variant task generation | high | v0.3+ | deferred | +| REQ-F-009 | Oral/voice defense with AI examiner | high | v0.3+ | deferred | +| REQ-F-010 | Live in-environment build with telemetry | high | v0.3+ | deferred | + +### Marketplace Engine + +| ID | Description | Priority | Milestone | Status | +|----|-------------|----------|-----------|--------| +| REQ-F-011 | AI job aggregation pipeline (3M+ jobs from 120K companies) | high | v0.4+ | deferred | +| REQ-F-012 | AI-powered tagging (skills, categories, seniority) | high | v0.4+ | deferred | +| REQ-F-013 | Semantic vector search for role matching | high | v0.4+ | deferred | +| REQ-F-014 | AI company enrichment (logos, descriptions, social links) | medium | v0.4+ | deferred | +| REQ-F-015 | AI resume parsing (profile auto-fill) | medium | v0.4+ | deferred | +| REQ-F-016 | SEO-optimized programmatic pages | medium | v0.4+ | deferred | + +### Platform Infrastructure + +| ID | Description | Priority | Milestone | Status | +|----|-------------|----------|-----------|--------| +| REQ-F-017 | Identity verification and age-gating (16+/18+) | high | v0.2+ | deferred | +| REQ-F-018 | Payment processing and subscription management | high | v0.3+ | deferred | +| REQ-F-019 | Human tutor marketplace (third-party courses) | medium | v0.4+ | deferred | +| REQ-F-020 | CIRR-style placement tracking and audit | medium | v0.5+ | deferred | + +--- + +## Out of Scope (Per Vision Doctrine) + +| Feature | Reason | +|---------|--------| +| Traditional accreditation | Not sought, not pursued, not revisited (Principle 4) | +| Under-16 learners | COPPA avoidance; AI school floor is 16+ | +| Marketplace for under-18 | Marketplace restricted to 18+ with verified identity | +| Human tutors in core school | Humans exist only in open marketplace as third-party sellers | +| Graded written exams without process trace | Trivially solvable by frontier AI; process-trace + oral defense more valid | +| Legacy job titles in curriculum | Programs train for AI-era roles, not obsolete ones | +| Junk advertising | Ad quality enforced algorithmically; relevant ads only | + +--- + +## Traceability Matrix + +| Requirement | Phase | Status | +|-------------|-------|--------| +| REQ-001 | 1 | complete | +| REQ-002 | 1 | complete | +| REQ-003 | 1 | complete | +| REQ-004 | 1 | complete | +| REQ-005 | 1 | complete | +| REQ-006 | 2 | complete | +| REQ-007 | 2 | complete | +| REQ-008 | 2 | complete | +| REQ-009 | 2 | complete | +| REQ-010 | 2 | complete | +| REQ-011 | 2 | complete | +| REQ-012 | 2 | complete | +| REQ-013 | 3 | complete | +| REQ-014 | 3 | complete | +| REQ-015 | 3 | complete | +| REQ-016 | 3 | complete | +| REQ-017 | 3 | complete | +| REQ-018 | 4 | complete | +| REQ-019 | 4 | complete | +| REQ-020 | 4 | complete | +| REQ-021 | 4 | complete | +| REQ-022 | 5 | complete | +| REQ-023 | 5 | complete | +| REQ-024 | 5 | complete | +| REQ-025 | 5 | complete | +| REQ-026 | 6 | complete | +| REQ-027 | 6 | complete | +| REQ-028 | 6 | complete | \ No newline at end of file diff --git a/.ciagent/ROADMAP.md b/.ciagent/ROADMAP.md new file mode 100644 index 0000000..185e32f --- /dev/null +++ b/.ciagent/ROADMAP.md @@ -0,0 +1,192 @@ +# Nextcraft — ROADMAP.md + +## Overview + +**Milestone v0.1** — UI/UX Prototype: High-fidelity interactive prototype of all four Nextcraft surfaces (Learner, Marketplace, Employer Dashboard, Admin) with realistic mock data, shared component library, responsive layouts, and navigation flows. No backend, no business logic. + +**Milestone type:** Feature (includes new UI features) +**Tag line:** v0.0.x (patches on the v0.0 line; milestone release as v0.1.0) +**Branch:** milestone/v0.1-nextcraft-ui-prototype + +--- + +## Phase List + +| # | Name | Status | Depends On | Requirements | Success Criteria | +|---|------|--------|------------|--------------|------------------| +| 0 | Pre-execution | complete | — | — | Specification, clarify, research, plan complete; .ciagent/ files created | +| 1 | Project scaffolding | complete | 0 | REQ-001, REQ-002, REQ-003, REQ-004, REQ-005 | Monorepo builds; dev server starts; component library has all primitives; mock data typed; routing works between route groups | +| 2 | Learner surface UI | complete | 1 | REQ-006, REQ-007, REQ-008, REQ-009, REQ-010, REQ-011, REQ-012 | All 7 learner pages render with mock data; navigation between pages works; responsive at mobile/tablet/desktop | +| 3 | Marketplace surface UI | complete | 1 | REQ-013, REQ-014, REQ-015, REQ-016, REQ-017 | All 5 marketplace pages render with mock data; search/filter UI interactive (client-side); job board listing displays mock jobs | +| 4 | Employer dashboard UI | complete | 1 | REQ-018, REQ-019, REQ-020, REQ-021 | All 4 employer pages render with mock data; talent search displays mock candidates; candidate profile shows artifact gallery + process trace | +| 5 | Admin surface UI | complete | 1 | REQ-022, REQ-023, REQ-024, REQ-025 | All 4 admin pages render with mock data; competency graph viewer renders interactive graph; moderation queue displays mock flagged content | +| 6 | Polish + integration | complete | 2, 3, 4, 5 | REQ-026, REQ-027, REQ-028 | Cross-surface navigation works; visual consistency audit passes; dark mode toggle functional; Storybook documents all components | +| 7 | Final review + ship | in_progress | 6 | — | Code review clean; audit passes; milestone tagged v0.1.0; release created on Gitea | + +--- + +## Phase Details + +### Phase 0: Pre-execution + +**Goal:** Establish project specification, clarify ambiguities, research tech stack, create detailed plans. + +**Stages:** SPECIFY → CLARIFY → RESEARCH → PLAN → (GRILL optional) → SHIP + +**Deliverables:** +- .ciagent/config.json +- .ciagent/PROJECT.md +- .ciagent/REQUIREMENTS.md +- .ciagent/ARCHITECTURE.md +- .ciagent/ROADMAP.md +- .ciagent/CHECKPOINT.json + +**Success criteria:** All .ciagent/ files created; initial commit with ---ci--- block; phase 0 shipped as v0.0.1. + +--- + +### Phase 1: Project Scaffolding + +**Goal:** Set up the monorepo, component library, mock data layer, routing, and responsive system. + +**Requirements:** REQ-001, REQ-002, REQ-003, REQ-004, REQ-005 + +**Key deliverables:** +- pnpm-workspace.yaml + turbo.json + root package.json +- apps/web Next.js app with App Router +- packages/ui with design tokens + all primitives (Button, Input, Card, Badge, Avatar, Dialog, Tabs, Progress, Tooltip, Skeleton, Toast) +- packages/mock-data with typed mock data for all domains +- packages/types with shared TypeScript type definitions +- Route groups: (learner), (marketplace), (employer), (admin) +- Responsive layout system with breakpoints +- Root layout with theme provider + +**Success criteria:** +- `pnpm dev` starts the Next.js dev server +- `pnpm build` succeeds without errors +- `pnpm typecheck` passes +- All primitive components exist and are importable +- Mock data is typed and importable +- Navigation between route groups works (even if pages are placeholders) + +--- + +### Phase 2: Learner Surface UI + +**Goal:** Build all 7 learner surface pages with realistic mock data and interactive elements. + +**Requirements:** REQ-006, REQ-007, REQ-008, REQ-009, REQ-010, REQ-011, REQ-012 + +**Key deliverables:** +- Landing page: hero, value proposition, Byte→Build→Demonstrate→Defend flow, testimonials, CTA +- Program catalog: 5 competency stack cards with role descriptions +- Competency stack view: selected stack with 12-18 competencies, progress indicators, microcredential badges +- Learner dashboard: active competencies, progress graph, recent artifacts, AI tutor chat mockup, milestones +- Byte tutorial viewer: concept panel, worked example, code/design viewer mockup +- Build sandbox mockup: IDE UI with toolbar, file explorer, editor area, telemetry sidebar +- Assessment/defense mockup: rubric display, AI reviewer panel, oral defense interface, process trace timeline + +**Success criteria:** +- All 7 pages render with mock data +- Navigation between pages works +- AI tutor chat mockup displays pre-scripted responses +- Responsive at mobile (375px), tablet (768px), desktop (1280px) +- Hover states and interactive elements functional + +--- + +### Phase 3: Marketplace Surface UI + +**Goal:** Build all 5 marketplace surface pages with job listings, search, and pricing. + +**Requirements:** REQ-013, REQ-014, REQ-015, REQ-016, REQ-017 + +**Key deliverables:** +- Job board listing: searchable grid of mock AI-era jobs, filter sidebar, match score cards +- Job detail page: full description, required competencies, employer info, AI-matched skills +- Employer profile: company overview, logo, open positions, culture mockup +- Search/filter UI: semantic search bar, skill tags, filters (seniority, remote, salary), saved searches +- Pricing page: job posting packages, talent access plans, feature comparison table + +**Success criteria:** +- All 5 pages render with mock data +- Filter sidebar interactive (client-side filtering of mock jobs) +- Job cards display match scores and skill tags +- Pricing table is responsive and readable + +--- + +### Phase 4: Employer Dashboard UI + +**Goal:** Build all 4 employer dashboard pages for talent search and posting management. + +**Requirements:** REQ-018, REQ-019, REQ-020, REQ-021 + +**Key deliverables:** +- Dashboard overview: active postings, applicant pipeline, talent matches, analytics charts +- Talent search: searchable candidate database with AI-matched filters, candidate cards +- Candidate profile: artifact gallery, process trace summary, defense transcripts, competency graph, microcredentials +- Posting management: create/edit/delete job postings, status tracking, applicant list, interview pipeline + +**Success criteria:** +- All 4 pages render with mock data +- Analytics charts display mock metrics (bar/line/donut charts) +- Candidate cards show competency stacks, microcredentials, defense scores +- Posting management form inputs are interactive (non-functional submit) + +--- + +### Phase 5: Admin Surface UI + +**Goal:** Build all 4 admin surface pages including the interactive competency graph viewer. + +**Requirements:** REQ-022, REQ-023, REQ-024, REQ-025 + +**Key deliverables:** +- Admin overview: platform metrics (learners, employers, placements, completion rate, NPS), activity feed, system health +- Learner management: searchable learner table, detail view, progress tracking, credential log +- Competency graph viewer: interactive node/edge graph using react-flow, competency stack nodes, dependency edges +- Marketplace moderation: job posting review queue, employer verification queue, flagged content, moderation tools + +**Success criteria:** +- All 4 pages render with mock data +- Competency graph viewer renders an interactive graph with clickable nodes +- Admin table supports sorting and filtering (client-side, mock data) +- Moderation queue displays mock flagged items with approve/reject buttons (non-functional) + +--- + +### Phase 6: Polish + Integration + +**Goal:** Ensure cross-surface consistency, responsive quality, and component library documentation. + +**Requirements:** REQ-026, REQ-027, REQ-028 + +**Key deliverables:** +- Cross-surface navigation: role switcher (learner/employer/admin), breadcrumbs, consistent header/footer +- Visual consistency: typography scale, color palette, spacing system, dark mode toggle, WCAG AA contrast +- Storybook: component documentation, prop tables, usage examples for all primitives and composites + +**Success criteria:** +- Role switcher navigates between surfaces +- Dark mode toggle works across all surfaces +- All pages pass WCAG AA contrast checks +- Storybook runs and documents all components +- No visual inconsistencies between surfaces + +--- + +### Phase 7: Final Review + Ship + +**Goal:** Code review, audit, milestone release. + +**Key deliverables:** +- Multi-persona code review (correctness, testing, security, performance, maintainability) +- Project health audit (reconstruction test, .ciagent/ file discipline, branch hygiene, commit discipline) +- Milestone ship: merge milestone → main, tag v0.1.0, create Gitea release + +**Success criteria:** +- Code review: P0 fixes applied, P1+ documented +- Audit: all checks pass, project state reconstructable from git log +- Ship: v0.1.0 tagged, milestone branch merged to main, Gitea release created +- All 28 requirements marked complete \ No newline at end of file diff --git a/.ciagent/config.json b/.ciagent/config.json new file mode 100644 index 0000000..0757607 --- /dev/null +++ b/.ciagent/config.json @@ -0,0 +1,54 @@ +{ + "version": "0.7.0", + "autonomy": { + "level": "full", + "decision_confidence_threshold": 0.6, + "escalation_hooks": ["deploy", "delete_data", "merge_to_main"], + "max_revision_iterations": 3, + "clarify_budget": 10 + }, + "release": { + "forge": "gitea", + "base_url": "https://git.cloudinit.dev", + "owner": "coreci", + "repo": "nextcraft" + }, + "secrets": { + "scopes": ["gitea"], + "env_files": [".env", ".env.secrets", ".env.*"] + }, + "ship": { + "per_phase": true, + "allow_skip": false, + "release_blocking": false, + "max_release_retries": 3 + }, + "verification": { + "bdd_default": false + }, + "personas": { + "enabled": true, + "territory_enforcement": "warn" + }, + "parallelization": { + "enabled": false, + "max_concurrent_agents": 1 + }, + "ideation": { + "max_ideas": 20, + "categories": ["security", "quality", "architecture", "coverage", "improvement", "spec", "chaos", "bdd"] + }, + "security": { + "bash_allowlist": { + "blocked_env_vars": ["GITEA_TOKEN", "GITHUB_TOKEN", "GITLAB_TOKEN"] + } + }, + "projects": [], + "active_project": null, + "milestone": { + "version": "v0.1", + "name": "nextcraft-ui-prototype", + "type": "feature", + "branch": "milestone/v0.1-nextcraft-ui-prototype" + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a81500a --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Environment files — never commit credentials +.env +.env.secrets +.env.* +!.env.example + +# CIAgent secrets +.ciagent/.env.secrets + +# Dependencies +node_modules/ +__pycache__/ +*.pyc +.venv/ +venv/ + +# Build artifacts +dist/ +/build/ +.next/ +.turbo/ +*.tsbuildinfo + +# Storybook +storybook-static/ + +# OS files +.DS_Store +Thumbs.db + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Test coverage +coverage/ +.nyc_output/ \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..1cfbd6f --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +shamefully-hoist=true +strict-peer-dependencies=false \ No newline at end of file diff --git a/apps/web/.storybook/main.ts b/apps/web/.storybook/main.ts new file mode 100644 index 0000000..8d99057 --- /dev/null +++ b/apps/web/.storybook/main.ts @@ -0,0 +1,28 @@ +import type { StorybookConfig } from '@storybook/nextjs'; + +/** + * Storybook config for the Nextcraft monorepo. + * + * Stories live alongside the UI primitives in `packages/ui` (referenced + * through the `@nextcraft/ui` workspace symlink so Storybook's webpack + * resolver finds them inside the app's module graph) and next to composite + * components in `apps/web/components`. The Next.js framework preset bundles + * the essential addons (controls, docs, actions, viewport, backgrounds, + * toolbars) so we don't add `@storybook/addon-essentials` separately. + */ +const config: StorybookConfig = { + stories: [ + '../node_modules/@nextcraft/ui/src/**/*.stories.@(ts|tsx|mdx)', + '../components/**/*.stories.@(ts|tsx|mdx)', + ], + addons: [], + framework: { + name: '@storybook/nextjs', + options: {}, + }, + docs: { + autodocs: true, + }, +}; + +export default config; \ No newline at end of file diff --git a/apps/web/.storybook/preview.tsx b/apps/web/.storybook/preview.tsx new file mode 100644 index 0000000..0f365c2 --- /dev/null +++ b/apps/web/.storybook/preview.tsx @@ -0,0 +1,38 @@ +import type { Preview } from '@storybook/react'; +import '../app/globals.css'; + +/** + * Storybook preview — imports the app's Tailwind stylesheet so stories + * render with the full design-token system (colors, fonts, dark mode). + */ +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + backgrounds: { + default: 'light', + values: [ + { name: 'light', value: '#ffffff' }, + { name: 'dark', value: '#020617' }, + ], + }, + layout: 'padded', + }, + // Apply the `dark` class to the story root when the dark background is + // active so class-based dark: variants resolve. + decorators: [ + (Story, context) => { + const isDark = context.globals?.backgrounds?.value === '#020617'; + if (typeof document !== 'undefined') { + document.documentElement.classList.toggle('dark', isDark); + } + return ; + }, + ], +}; + +export default preview; \ No newline at end of file diff --git a/apps/web/app/(admin)/admin/graph/page.tsx b/apps/web/app/(admin)/admin/graph/page.tsx new file mode 100644 index 0000000..52c782c --- /dev/null +++ b/apps/web/app/(admin)/admin/graph/page.tsx @@ -0,0 +1,17 @@ +import { CompetencyGraph } from '../../../../components/admin/competency-graph'; + +export default function CompetencyGraphPage() { + return ( +
+
+

+ Competency Graph +

+

+ Visualize stack relationships and dependencies +

+
+ +
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(admin)/admin/learners/page.tsx b/apps/web/app/(admin)/admin/learners/page.tsx new file mode 100644 index 0000000..eafd09b --- /dev/null +++ b/apps/web/app/(admin)/admin/learners/page.tsx @@ -0,0 +1,18 @@ +import { LearnerTable } from '../../../../components/admin/learner-table'; +import { adminLearners } from '@nextcraft/mock-data'; + +export default function LearnerManagementPage() { + return ( +
+
+

+ Learner Management +

+

+ Track and manage learner progress +

+
+ +
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(admin)/admin/moderation/page.tsx b/apps/web/app/(admin)/admin/moderation/page.tsx new file mode 100644 index 0000000..31f37d4 --- /dev/null +++ b/apps/web/app/(admin)/admin/moderation/page.tsx @@ -0,0 +1,17 @@ +import { ModerationTabs } from '../../../../components/admin/moderation-tabs'; + +export default function MarketplaceModerationPage() { + return ( +
+
+

+ Marketplace Moderation +

+

+ Review and manage marketplace content +

+
+ +
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(admin)/admin/page.tsx b/apps/web/app/(admin)/admin/page.tsx new file mode 100644 index 0000000..f462c46 --- /dev/null +++ b/apps/web/app/(admin)/admin/page.tsx @@ -0,0 +1,244 @@ +import { + Activity, + AlertTriangle, + Briefcase, + Building2, + CheckCircle, + Clock, + Cpu, + Database, + GraduationCap, + Server, + ThumbsUp, + Users, +} from 'lucide-react'; +import type { LucideIcon } from 'lucide-react'; +import { Card, CardBody, CardHeader } from '@nextcraft/ui'; +import { + activityFeed, + platformMetrics, + serviceHealth, + systemStats, + uptimeBars, +} from '@nextcraft/mock-data'; + +const ICONS: Record = { + Users, + Building2, + Briefcase, + GraduationCap, + ThumbsUp, + Activity, + Server, + Database, + Cpu, +}; + +const TONE_CLASSES: Record = { + indigo: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300', + emerald: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300', + amber: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300', + rose: 'bg-rose-100 text-rose-700 dark:bg-rose-900/40 dark:text-rose-300', + cyan: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/40 dark:text-cyan-300', + violet: 'bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300', +}; + +function statusDot(status: string): string { + if (status === 'operational') return 'bg-emerald-500'; + if (status === 'degraded') return 'bg-amber-500'; + return 'bg-rose-500'; +} + +function statusLabel(status: string): string { + if (status === 'operational') return 'Operational'; + if (status === 'degraded') return 'Degraded'; + return 'Down'; +} + +export default function AdminOverviewPage() { + return ( +
+ {/* Page header */} +
+

+ Admin Dashboard +

+

+ Platform overview +

+
+ + {/* Metric cards */} +
+ {platformMetrics.map((m) => { + const Icon = ICONS[m.icon] ?? Activity; + const positive = m.trendPct >= 0; + return ( + + +
+ + + + + {positive ? '+' : ''} + {m.trendPct}% + +
+
+

+ {m.label} +

+

+ {m.value.toLocaleString()} + {m.suffix} +

+
+
+
+ ); + })} +
+ + {/* Activity + System health */} +
+ {/* Activity feed — 2/3 width */} +
+ + +
+ +

+ Activity Feed +

+
+
+ +
    + {activityFeed.map((evt, i) => { + const Icon = ICONS[evt.icon] ?? Activity; + const last = i === activityFeed.length - 1; + return ( +
  1. + + + +
    +

    + {evt.message} +

    + + + {evt.relativeTime} + +
    +
  2. + ); + })} +
+
+
+
+ + {/* System health — 1/3 width */} +
+ + +
+ +

+ System Health +

+
+
+ +
    + {serviceHealth.map((svc) => ( +
  • + + {svc.name} + + + + + {statusLabel(svc.status)} + + +
  • + ))} +
+ +
+
+

+ Uptime (30d) +

+ + 99.96% + +
+
+ {uptimeBars.map((u, idx) => { + const h = Math.max(20, Math.round((u - 99.8) * 100 * 4)); + const degraded = u < 99.95; + return ( + + ); + })} +
+
+ +
+
+ + Error Rate + + + + {systemStats.errorRate} + +
+
+ + Avg Response Time + + + + {systemStats.avgResponseMs}ms + +
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(admin)/layout.tsx b/apps/web/app/(admin)/layout.tsx new file mode 100644 index 0000000..ad9a22c --- /dev/null +++ b/apps/web/app/(admin)/layout.tsx @@ -0,0 +1,48 @@ +'use client'; + +import type { ReactNode } from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { Shield } from 'lucide-react'; + +const LINKS = [ + { label: 'Overview', href: '/admin' }, + { label: 'Learners', href: '/admin/learners' }, + { label: 'Competency Graph', href: '/admin/graph' }, + { label: 'Moderation', href: '/admin/moderation' }, +]; + +export default function AdminLayout({ children }: { children: ReactNode }) { + const pathname = usePathname() ?? ''; + + return ( +
+ +
{children}
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(employer)/employer/page.tsx b/apps/web/app/(employer)/employer/page.tsx new file mode 100644 index 0000000..fd4bd4e --- /dev/null +++ b/apps/web/app/(employer)/employer/page.tsx @@ -0,0 +1,234 @@ +import Link from 'next/link'; +import { + Briefcase, + Users, + TrendingUp, + Target, + ArrowRight, +} from 'lucide-react'; +import { Badge, Card, CardBody, Avatar } from '@nextcraft/ui'; +import { candidates, employers } from '@nextcraft/mock-data'; +import { AnalyticsCharts } from '../../../components/employer/analytics-charts'; +import { matchBadgeClasses, initials } from '../../../lib/format'; + +/* -------------------------------------------------------------------------- */ +/* Mock pipeline + metric data */ +/* -------------------------------------------------------------------------- */ + +const METRICS = [ + { label: 'Active Postings', value: 7, trend: '+12%', icon: Briefcase, tone: 'primary' as const }, + { label: 'Total Applicants', value: 142, trend: '+24%', icon: Users, tone: 'accent' as const }, + { label: 'Talent Matches', value: 28, trend: '+8%', icon: Target, tone: 'violet' as const }, + { label: 'Placement Rate', value: '18%', trend: '+3%', icon: TrendingUp, tone: 'amber' as const }, +]; + +const TONE_CLASSES: Record = { + primary: 'bg-primary-100 text-primary-700 dark:bg-primary-900/40 dark:text-primary-300', + accent: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300', + violet: 'bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300', + amber: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300', +}; + +interface PipelineStage { + name: string; + count: number; + cards: Array<{ name: string; position: string; score: number }>; +} + +const PIPELINE: PipelineStage[] = [ + { + name: 'Applied', + count: 8, + cards: [ + { name: 'Maya Okonkwo', position: 'AI Orchestration Engineer', score: 96 }, + { name: 'Devon Park', position: 'AI Safety Researcher', score: 91 }, + { name: 'Priya Iyer', position: 'Human-AI Designer', score: 89 }, + ], + }, + { + name: 'Screening', + count: 5, + cards: [ + { name: 'Tomás Vega', position: 'LLM App Developer', score: 93 }, + { name: 'Sofia Marchetti', position: 'AI Governance Lead', score: 85 }, + ], + }, + { + name: 'Interview', + count: 3, + cards: [ + { name: 'Liam Chen', position: 'Agent Reliability Eng', score: 90 }, + { name: 'Yuki Tanaka', position: 'Evaluation Engineer', score: 86 }, + ], + }, + { + name: 'Offer', + count: 2, + cards: [{ name: 'Ingrid Solberg', position: 'AI Red Team Lead', score: 88 }], + }, + { + name: 'Hired', + count: 1, + cards: [{ name: 'Hana Lindqvist', position: 'Comp Biologist', score: 88 }], + }, +]; + +/* -------------------------------------------------------------------------- */ +/* Page */ +/* -------------------------------------------------------------------------- */ + +export default function EmployerDashboardPage() { + const employer = employers[0]; + const topMatches = [...candidates].sort((a, b) => b.matchScore - a.matchScore).slice(0, 5); + + return ( +
+ {/* Header */} +
+ Employer Dashboard +

+ Employer Dashboard +

+

+ Welcome back, {employer.name}. + Here is your talent pipeline at a glance. +

+
+ + {/* Metric cards */} +
+ {METRICS.map((m) => { + const Icon = m.icon; + return ( + + +
+ + + + + {m.trend} + +
+
+ + {m.value} + + {m.label} +
+
+
+ ); + })} +
+ + {/* Middle row — pipeline + talent matches */} +
+ {/* Applicant pipeline — 2/3 width */} +
+
+

+ Applicant Pipeline +

+ + {PIPELINE.reduce((s, c) => s + c.count, 0)} candidates in flight + +
+
+ {PIPELINE.map((stage) => ( +
+
+

+ {stage.name} +

+ + {stage.count} + +
+
+ {stage.cards.map((c) => ( +
+ +
+ + {c.name} + + + {c.position} + +
+ + {c.score} + +
+ ))} +
+
+ ))} +
+
+ + {/* Talent matches — 1/3 width */} +
+
+

+ Talent Matches +

+ + See all + +
+ + + {topMatches.map((cand) => ( + + +
+ + {cand.name} + + + {cand.headline} + +
+ + {cand.matchScore}% + + + ))} +
+
+
+
+ + {/* Analytics charts */} +
+

Analytics

+ +
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(employer)/employer/postings/page.tsx b/apps/web/app/(employer)/employer/postings/page.tsx new file mode 100644 index 0000000..dab870a --- /dev/null +++ b/apps/web/app/(employer)/employer/postings/page.tsx @@ -0,0 +1,22 @@ +import { Badge } from '@nextcraft/ui'; +import { jobs } from '@nextcraft/mock-data'; +import { PostingManager } from '../../../../components/employer/posting-manager'; + +export default function PostingsPage() { + return ( +
+
+ Posting Management +

+ Posting Management +

+

+ Create, edit, and track your job postings. View the applicant pipeline for each posting + and manage interview stages. +

+
+ + +
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(employer)/employer/talent/[candidateId]/page.tsx b/apps/web/app/(employer)/employer/talent/[candidateId]/page.tsx new file mode 100644 index 0000000..76261f2 --- /dev/null +++ b/apps/web/app/(employer)/employer/talent/[candidateId]/page.tsx @@ -0,0 +1,443 @@ +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { + ArrowLeft, + CheckCircle, + Clock, + FileCode, + Palette, + Cpu, + Mail, + ShieldCheck, +} from 'lucide-react'; +import { Avatar, Badge, Button, Card, CardBody } from '@nextcraft/ui'; +import { candidates, competencyStacks } from '@nextcraft/mock-data'; +import type { Artifact, ArtifactType } from '@nextcraft/types'; +import { matchBadgeClasses, initials } from '../../../../../lib/format'; +import { DefenseAccordion } from '../../../../../components/employer/defense-accordion'; + +interface PageProps { + params: Promise<{ candidateId: string }>; +} + +/* -------------------------------------------------------------------------- */ +/* Deterministic mock-data generators (per-candidate) */ +/* -------------------------------------------------------------------------- */ + +function hashId(id: string): number { + let h = 0; + for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) >>> 0; + return h; +} + +function generateArtifacts(cand: { id: string; artifactCount: number; competencyStackId: string }): Artifact[] { + const namesByStack: Record> = { + 'stack-orchestration': [ + { name: 'Multi-agent research assistant', type: 'code', desc: 'LangGraph assistant with eval harness for cited literature reviews.' }, + { name: 'RAG retrieval quality dashboard', type: 'code', desc: 'Streamlit dashboard comparing chunking strategies across 800 eval queries.' }, + { name: 'Agent topology diagram', type: 'design', desc: 'Plan-and-execute agent with reflection and tool-retrieval sub-graphs.' }, + { name: 'Prompt regression suite', type: 'code', desc: 'Pytest suite of 320 prompt assertions with LLM-as-judge scoring.' }, + { name: 'Token-cost alerting system', type: 'code', desc: 'Real-time cost monitoring across agent calls with Slack escalation.' }, + { name: 'Function calling sandbox', type: 'simulation', desc: 'Sandboxed tool execution environment with retry + validation.' }, + ], + 'stack-safety': [ + { name: 'Red-team probe suite', type: 'code', desc: '1,200+ adversarial probes across three model families with scoring.' }, + { name: 'Model card — internal Q&A agent', type: 'document', desc: 'Capabilities, limitations, intended use, and red-team findings.' }, + { name: 'Bias audit dashboard', type: 'code', desc: 'Disparate-impact testing across demographics with visual report.' }, + { name: 'Risk register template', type: 'document', desc: 'Risk taxonomy with severity and likelihood for 30+ deployed systems.' }, + { name: 'Jailbreak defense prototype', type: 'code', desc: 'Instruction-hierarchy enforcement with indirect injection mitigation.' }, + ], + 'stack-designer': [ + { name: 'Transparency pattern library', type: 'design', desc: 'Confidence indicators, source attribution, and model limitation disclosure.' }, + { name: 'Agentic interaction prototype', type: 'design', desc: 'Figma prototype for delegating, interrupting, and reviewing autonomous agents.' }, + { name: 'Repair flow spec', type: 'document', desc: 'Multi-turn dialogue repair flows reducing escalations by 35%.' }, + { name: 'Persona consistency framework', type: 'design', desc: 'Character design for AI assistants with contextual tone adaptation.' }, + { name: 'Trust calibration study', type: 'document', desc: 'User mental model alignment research with over-trust mitigations.' }, + ], + 'stack-operator': [ + { name: 'Anomaly triage dashboard', type: 'code', desc: 'ML-based anomaly score visualization cutting false positives by 40%.' }, + { name: 'Sensor calibration log', type: 'document', desc: 'Calibration schedule and drift-correction workflow for fleet sensors.' }, + { name: 'Vision inspection tuning', type: 'simulation', desc: 'Threshold tuning for vision-based quality control station.' }, + { name: 'Field data collection template', type: 'document', desc: 'Structured annotation workflow for high-quality dataset capture.' }, + ], + 'stack-science': [ + { name: 'Active-learning DFT toolkit', type: 'code', desc: 'Open-source toolkit for active-learning loops over DFT calculations.' }, + { name: 'Phenotype prediction pipeline', type: 'code', desc: 'ML pipeline for drug discovery with reproducible Snakemake workflows.' }, + { name: 'Wind forecast downscaling', type: 'simulation', desc: 'Physics-informed model improving 72-hour wind forecasts by 18%.' }, + { name: 'Materials screening notebook', type: 'code', desc: 'Jupyter notebook for ML property prediction with active learning.' }, + { name: 'Reproducible research container', type: 'document', desc: 'Containerized workflow with Nextflow and DOI assignment.' }, + ], + }; + + const pool = namesByStack[cand.competencyStackId] ?? namesByStack['stack-orchestration']; + const offset = hashId(cand.id) % pool.length; + const count = Math.min(cand.artifactCount, pool.length); + const artifacts: Artifact[] = []; + for (let i = 0; i < count; i++) { + const item = pool[(offset + i) % pool.length]; + artifacts.push({ + id: `art-${cand.id}-${i}`, + name: item.name, + type: item.type, + url: `https://example.com/artifacts/${cand.id}/${i}`, + description: item.desc, + createdAt: new Date(Date.UTC(2026, 7 - i, 20 - i * 3)).toISOString(), + }); + } + return artifacts; +} + +function generateProcessTrace(cand: { id: string }) { + const offset = hashId(cand.id); + const baseEvents = [ + { action: 'Repository initialized', detail: 'Created project scaffold with README and license.' }, + { action: 'First commit pushed', detail: 'Initial proof-of-concept with placeholder data.' }, + { action: 'Evaluation harness added', detail: 'Wired up 50-question regression suite with LLM-as-judge.' }, + { action: 'Peer review feedback', detail: 'Two reviewers flagged edge cases in retrieval fallback path.' }, + { action: 'Iteration — fallback hardened', detail: 'Added retry + validation; eval score improved 12 points.' }, + { action: 'Final submission', detail: 'Artifact submitted for oral defense scheduling.' }, + ]; + return baseEvents.map((e, i) => ({ + timestamp: new Date(Date.UTC(2026, 6 + Math.floor(i / 2), (offset % 20) + i * 2)).toISOString(), + ...e, + })); +} + +function generateDefenseSessions(cand: { id: string; competencyStackId: string; microcredentials: number }) { + const stack = competencyStacks.find((s) => s.id === cand.competencyStackId); + const mastered = (stack?.competencies ?? []).filter((c) => c.status === 'mastered' || c.status === 'in_progress'); + const offset = hashId(cand.id); + const count = Math.min(cand.microcredentials, mastered.length, 3); + const sessions = []; + for (let i = 0; i < count; i++) { + const comp = mastered[(offset + i) % mastered.length]; + const score = 82 + ((offset + i) % 12); + sessions.push({ + id: `def-${cand.id}-${i}`, + competencyName: comp.name, + score, + date: new Date(Date.UTC(2026, 5 + i, (offset % 24) + 1)).toISOString(), + transcript: [ + { + question: 'Walk us through the architecture of your artifact. Why did you choose this approach?', + answer: + 'I chose a plan-and-execute topology because the task required multi-step retrieval with reflection. The plan node decomposes the query, sub-agents retrieve and draft in parallel, and a reflection node scores and routes for a second pass when below threshold.', + }, + { + question: 'What evaluation did you run, and what were the headline numbers?', + answer: + 'I ran a 50-query regression suite scored by LLM-as-judge calibrated against a human panel (0.86 agreement). Baseline scored 71%; the reflection pass lifted it to 88% with a 14% latency cost, which stayed within budget.', + }, + { + question: 'Describe a failure mode you found and how you mitigated it.', + answer: + 'Retrieval fallback returned stale context on schema changes. I added a freshness check + retry with a smaller context window, which reduced stale-grounded answers from 9% to under 2%.', + }, + ], + }); + } + return sessions; +} + +function generateMicrocredentials(cand: { id: string; competencyStackId: string; microcredentials: number }) { + const stack = competencyStacks.find((s) => s.id === cand.competencyStackId); + const mastered = (stack?.competencies ?? []).filter((c) => c.status === 'mastered' || c.status === 'in_progress'); + const offset = hashId(cand.id); + const count = Math.min(cand.microcredentials, mastered.length, 5); + const items = []; + for (let i = 0; i < count; i++) { + const comp = mastered[(offset + i) % mastered.length]; + const score = 85 + ((offset + i) % 11); + items.push({ + id: `mc-${cand.id}-${i}`, + competencyName: comp.name, + issuedAt: new Date(Date.UTC(2026, 5 + i, (offset % 24) + 1)).toISOString(), + score, + }); + } + return items; +} + +function generateCompetencies(cand: { id: string; competencyStackId: string }) { + const stack = competencyStacks.find((s) => s.id === cand.competencyStackId); + const comps = stack?.competencies ?? []; + const offset = hashId(cand.id); + return comps.slice(0, 8).map((c, i) => { + // Deterministically assign status biased toward mastered/in_progress for high-score candidates. + const r = (offset + i) % 10; + const status = r < 5 ? 'mastered' : r < 8 ? 'in_progress' : 'available'; + return { id: c.id, name: c.name, status: status as 'mastered' | 'in_progress' | 'available' }; + }); +} + +/* -------------------------------------------------------------------------- */ +/* Icon helpers */ +/* -------------------------------------------------------------------------- */ + +function ArtifactTypeIcon({ type }: { type: ArtifactType }) { + const cls = 'h-5 w-5'; + if (type === 'code') return ; + if (type === 'design') return ; + if (type === 'simulation') return ; + return ; +} + +const ARTIFACT_ICON_BG: Record = { + code: 'bg-primary-100 text-primary-700 dark:bg-primary-900/40 dark:text-primary-300', + design: 'bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300', + simulation: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300', + document: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300', +}; + +/* -------------------------------------------------------------------------- */ +/* Page */ +/* -------------------------------------------------------------------------- */ + +export default async function CandidateProfilePage({ params }: PageProps) { + const { candidateId } = await params; + const cand = candidates.find((c) => c.id === candidateId); + if (!cand) notFound(); + + const stack = competencyStacks.find((s) => s.id === cand.competencyStackId); + const artifacts = generateArtifacts(cand); + const processTrace = generateProcessTrace(cand); + const defenseSessions = generateDefenseSessions(cand); + const microcredentials = generateMicrocredentials(cand); + const competencies = generateCompetencies(cand); + + const masteredCount = competencies.filter((c) => c.status === 'mastered').length; + + return ( +
+ {/* Back link */} +
+ + + Back to talent search + +
+ + {/* Header */} + + +
+
+ +
+

+ {cand.name} +

+

{cand.headline}

+
+ {stack?.name ?? 'AI'} + Verified +
+
+
+
+
+ + Match Score + + + {cand.matchScore}% + +
+ +
+
+

{cand.bio}

+
+
+ + {/* Summary stats bar */} +
+ } /> + } /> + } /> + } /> +
+ +
+ {/* Artifact gallery */} + + +

+ Artifact Gallery +

+ +
+
+ + {/* Process trace summary */} + + +

+ Process Trace Summary +

+
    + {processTrace.map((step, i) => ( +
  1. + + + +
    +
    + + {step.action} + + + + {new Date(step.timestamp).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + })} + +
    +

    {step.detail}

    +
    +
  2. + ))} +
+
+
+ + {/* Oral defense transcripts */} + + +

+ Oral Defense Transcripts +

+

+ Recorded Q&A from each verified oral defense session. Expand a session to read the + transcript. +

+ +
+
+ + {/* Competency mini-graph */} + + +

+ Competency Progress +

+
+ {competencies.map((c) => ( +
+ {c.name} + {c.status === 'mastered' ? ( + + Mastered + + ) : c.status === 'in_progress' ? ( + + In Progress + + ) : ( + Available + )} +
+ ))} +
+
+
+ + {/* Microcredential verification */} + + +

+ Microcredential Verification +

+
+ {microcredentials.map((mc) => ( +
+
+ +
+ + {mc.competencyName} + + + Issued{' '} + {new Date(mc.issuedAt).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + })} + +
+
+
+ + {mc.score} + + Verified +
+
+ ))} +
+
+
+
+
+ ); +} + +function StatCard({ label, value, icon }: { label: string; value: number | string; icon: React.ReactNode }) { + return ( +
+ + {icon} + +
+ {value} + {label} +
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(employer)/employer/talent/page.tsx b/apps/web/app/(employer)/employer/talent/page.tsx new file mode 100644 index 0000000..3686728 --- /dev/null +++ b/apps/web/app/(employer)/employer/talent/page.tsx @@ -0,0 +1,22 @@ +import { Badge } from '@nextcraft/ui'; +import { candidates } from '@nextcraft/mock-data'; +import { TalentSearch } from '../../../../components/employer/talent-search'; + +export default function TalentSearchPage() { + return ( +
+
+ Talent Search +

+ Talent Search +

+

+ AI-credentialed candidates with verified microcredentials, evidence portfolios, and oral + defense scores. Filter by competency stack, defense score, and artifact count. +

+
+ + +
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(employer)/layout.tsx b/apps/web/app/(employer)/layout.tsx new file mode 100644 index 0000000..3605e81 --- /dev/null +++ b/apps/web/app/(employer)/layout.tsx @@ -0,0 +1,47 @@ +'use client'; + +import type { ReactNode } from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { Building2 } from 'lucide-react'; + +const LINKS = [ + { label: 'Overview', href: '/employer' }, + { label: 'Talent search', href: '/employer/talent' }, + { label: 'Postings', href: '/employer/postings' }, +]; + +export default function EmployerLayout({ children }: { children: ReactNode }) { + const pathname = usePathname() ?? ''; + + return ( +
+ +
{children}
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(learner)/build/[competencyId]/page.tsx b/apps/web/app/(learner)/build/[competencyId]/page.tsx new file mode 100644 index 0000000..139b180 --- /dev/null +++ b/apps/web/app/(learner)/build/[competencyId]/page.tsx @@ -0,0 +1,295 @@ +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { + ArrowLeft, + ArrowRight, + Play, + Save, + Upload, + Folder, + FileText, + Activity, +} from 'lucide-react'; +import { Button } from '@nextcraft/ui'; +import { allCompetencies, competencyStacks } from '@nextcraft/mock-data'; + +interface FileEntry { + label: string; + children?: { label: string }[]; +} + +const FILE_TREE: FileEntry[] = [ + { + label: 'src/', + children: [ + { label: 'main.ts' }, + { label: 'agent.ts' }, + { label: 'tools.ts' }, + ], + }, + { + label: 'tests/', + children: [{ label: 'agent.test.ts' }], + }, + { label: 'README.md' }, +]; + +const EDITOR_LINES = [ + { n: 1, content: "import { ChatOpenAI } from '@langchain/openai';" }, + { n: 2, content: "import { tool } from '@langchain/core/tools';" }, + { n: 3, content: "import { z } from 'zod';" }, + { n: 4, content: '' }, + { n: 5, content: '// Define a typed tool for fetching the current weather' }, + { n: 6, content: "const getWeather = tool(" }, + { n: 7, content: ' async ({ city, units }) => {' }, + { n: 8, content: ' const res = await fetch(`/api/weather?city=${city}&units=${units}`);' }, + { n: 9, content: ' return res.json();' }, + { n: 10, content: ' },' }, + { n: 11, content: ' {' }, + { n: 12, content: " name: 'get_weather'," }, + { n: 13, content: " description: 'Fetch the current weather for a city'," }, + { n: 14, content: ' schema: z.object({' }, + { n: 15, content: " city: z.string().describe('City to fetch weather for')," }, + { n: 16, content: " units: z.enum(['celsius', 'fahrenheit']).default('celsius')," }, + { n: 17, content: ' }),' }, + { n: 18, content: ' },' }, + { n: 19, content: ');' }, + { n: 20, content: '' }, + { n: 21, content: 'export async function main(query: string) {' }, + { n: 22, content: ' const model = new ChatOpenAI({ model: "gpt-4o-mini" });' }, + { n: 23, content: ' const modelWithTools = model.bindTools([getWeather]);' }, + { n: 24, content: ' const response = await modelWithTools.invoke(query);' }, + { n: 25, content: ' return response.tool_calls;' }, + { n: 26, content: '}' }, +]; + +const TELEMETRY_METRICS = [ + { label: 'Commits', value: '7' }, + { label: 'Keystrokes', value: '1,247' }, + { label: 'Time spent', value: '23 min' }, + { label: 'Build attempts', value: '3' }, +]; + +const TELEMETRY_EVENTS = [ + { time: '14:02:11', action: 'File created: src/main.ts' }, + { time: '14:09:48', action: 'First build attempt (failed)' }, + { time: '14:12:30', action: 'Test suite passed (2/2)' }, + { time: '14:18:05', action: 'Commit: scaffold agent entrypoint' }, + { time: '14:21:42', action: 'Tool schema validated' }, + { time: '14:25:17', action: 'Build attempt 2 (success)' }, + { time: '14:28:03', action: 'Commit: implement tool calling' }, +]; + +function highlight(line: string): { text: string; cls: string }[] { + // Very small token highlighter for the mock editor + const tokens: { text: string; cls: string }[] = []; + let i = 0; + while (i < line.length) { + // comment + if (line.slice(i).startsWith('//')) { + tokens.push({ text: line.slice(i), cls: 'text-slate-500' }); + break; + } + // string with backtick or single/double quote + const ch = line[i]; + if (ch === '`' || ch === "'" || ch === '"') { + const end = line.indexOf(ch, i + 1); + if (end !== -1) { + tokens.push({ text: line.slice(i, end + 1), cls: 'text-emerald-300' }); + i = end + 1; + continue; + } + } + // keyword + const rest = line.slice(i); + const kwMatch = rest.match(/^(import|from|export|async|function|const|return|await|new)/); + if (kwMatch) { + tokens.push({ text: kwMatch[0], cls: 'text-primary-300' }); + i += kwMatch[0].length; + continue; + } + // default: consume one char + tokens.push({ text: ch, cls: 'text-slate-200' }); + i += 1; + } + return tokens; +} + +export default async function BuildSandboxPage({ + params, +}: { + params: Promise<{ competencyId: string }>; +}) { + const { competencyId } = await params; + const competency = allCompetencies.find((c) => c.id === competencyId); + if (!competency) notFound(); + const stack = competencyStacks.find((s) => s.id === competency.stackId); + + return ( +
+ + + Back to Byte + + + {/* Toolbar */} +
+
+ + Sandbox + + + {competency.name} + + + {stack?.name} + +
+
+ + + +
+
+ + {/* IDE layout */} +
+ {/* File explorer */} + + + {/* Editor */} +
+
+ + src/main.ts +
+
+            
+              {EDITOR_LINES.map((line) => (
+                
+ + {line.n} + + + {line.content === '' ? ( +   + ) : ( + highlight(line.content).map((t, idx) => ( + + {t.text} + + )) + )} + +
+ ))} +
+
+
+ + {/* Telemetry */} + +
+ + {/* Submit */} +
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(learner)/catalog/[stackId]/page.tsx b/apps/web/app/(learner)/catalog/[stackId]/page.tsx new file mode 100644 index 0000000..63cd013 --- /dev/null +++ b/apps/web/app/(learner)/catalog/[stackId]/page.tsx @@ -0,0 +1,164 @@ +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { + ArrowLeft, + ArrowRight, + Lock, + Circle, + Loader, + CheckCircle, + Award, + Layers, +} from 'lucide-react'; +import { Button, Card, CardBody, Badge } from '@nextcraft/ui'; +import { competencyStacks, learnerMicrocredentials } from '@nextcraft/mock-data'; +import type { Competency, CompetencyStatus } from '@nextcraft/types'; + +const STATUS_META: Record< + CompetencyStatus, + { label: string; icon: typeof Lock; badgeClass: string } +> = { + locked: { label: 'Locked', icon: Lock, badgeClass: 'bg-slate-100 text-slate-500 dark:bg-slate-800 dark:text-slate-400' }, + available: { label: 'Available', icon: Circle, badgeClass: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300' }, + in_progress: { label: 'In progress', icon: Loader, badgeClass: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300' }, + mastered: { label: 'Mastered', icon: CheckCircle, badgeClass: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300' }, +}; + +const PROGRESS_BY_ID: Record = { + 'stack-orchestration-c003': 45, + 'stack-orchestration-c008': 38, + 'stack-safety-c019': 52, + 'stack-safety-c023': 30, + 'stack-designer-c034': 60, + 'stack-designer-c037': 41, + 'stack-operator-c048': 28, + 'stack-operator-c053': 35, + 'stack-science-c063': 47, + 'stack-science-c066': 33, +}; + +function isUnlocked(c: Competency): boolean { + return c.status !== 'locked'; +} + +export default async function StackViewPage({ + params, +}: { + params: Promise<{ stackId: string }>; +}) { + const { stackId } = await params; + const stack = competencyStacks.find((s) => s.id === stackId); + if (!stack) notFound(); + + const masteredCount = stack.competencies.filter((c) => c.status === 'mastered').length; + const inProgressCount = stack.competencies.filter((c) => c.status === 'in_progress').length; + const mcByComp = new Map(learnerMicrocredentials.map((m) => [m.competencyId, m])); + + return ( +
+ {/* Back link */} + + + Back to catalog + + + {/* Header */} +
+
+ +

+ {stack.name} +

+
+

{stack.description}

+
+ {stack.competencies.length} competencies + {masteredCount} mastered + {inProgressCount} in progress + + Target roles: {stack.targetRoles} + +
+
+ + {/* Competency list */} +
+ {stack.competencies.map((c) => { + const meta = STATUS_META[c.status]; + const StatusIcon = meta.icon; + const mc = mcByComp.get(c.id); + const progress = PROGRESS_BY_ID[c.id]; + + return ( + + +
+
+
+

+ {c.name} +

+ + + {meta.label} + + {mc && mc.verified && ( + + + Microcredential · {mc.score} + + )} +
+

+ {c.description} +

+
+ +
+ {isUnlocked(c) ? ( + + + + ) : ( + + )} +
+
+ + {/* Progress / mastery bar */} + {c.status === 'in_progress' && typeof progress === 'number' && ( +
+
+
+
+ + {progress}% + +
+ )} + {c.status === 'mastered' && ( +
+ + Microcredential earned +
+ )} + + + ); + })} +
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(learner)/catalog/page.tsx b/apps/web/app/(learner)/catalog/page.tsx new file mode 100644 index 0000000..3539391 --- /dev/null +++ b/apps/web/app/(learner)/catalog/page.tsx @@ -0,0 +1,58 @@ +import Link from 'next/link'; +import { ArrowRight, Layers } from 'lucide-react'; +import { Button, Card, CardBody, Badge } from '@nextcraft/ui'; +import { competencyStacks } from '@nextcraft/mock-data'; + +export default function CatalogPage() { + return ( +
+ {/* Header */} +
+ Learner · Catalog +

+ Program Catalog +

+

+ Five competency stacks covering orchestration, safety, design, field operations, and + computational sciences. Each stack is a sequence of micro-tutorials, sandbox builds, + artifacts, and oral defenses. +

+
+ + {/* Grid */} +
+ {competencyStacks.map((stack) => ( + + +
+ + + + {stack.competencies.length} competencies +
+

+ {stack.name} +

+

+ {stack.description} +

+

+ + Target roles: + {' '} + {stack.targetRoles} +

+
+ + + +
+
+
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(learner)/dashboard/page.tsx b/apps/web/app/(learner)/dashboard/page.tsx new file mode 100644 index 0000000..2b78331 --- /dev/null +++ b/apps/web/app/(learner)/dashboard/page.tsx @@ -0,0 +1,279 @@ +import Link from 'next/link'; +import { + ArrowRight, + Award, + Calendar, + Code, + FileText, + PenTool, + Sparkles, + Bot, +} from 'lucide-react'; +import { Card, CardBody, CardHeader, Badge, Avatar, Button } from '@nextcraft/ui'; +import { + primaryLearner, + competencyStacks, + allCompetencies, + learnerArtifacts, + upcomingDefenses, + learnerMicrocredentials, +} from '@nextcraft/mock-data'; +import { AiTutorChat } from '../../../components/learner/ai-tutor-chat'; +import { ProgressGraph } from '../../../components/learner/progress-graph'; + +const ACTIVE_COMPETENCY_IDS = [ + 'stack-orchestration-c003', + 'stack-orchestration-c008', + 'stack-safety-c019', + 'stack-safety-c023', +]; + +const ACTIVE_PROGRESS: Record = { + 'stack-orchestration-c003': 45, + 'stack-orchestration-c008': 38, + 'stack-safety-c019': 52, + 'stack-safety-c023': 30, +}; + +const ARTIFACT_ICON = { + code: Code, + design: PenTool, + simulation: Sparkles, + document: FileText, +} as const; + +function formatDate(iso: string): string { + const d = new Date(iso); + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); +} + +export default function DashboardPage() { + const learner = primaryLearner; + const activeStack = competencyStacks.find((s) => s.id === 'stack-orchestration')!; + const activeCompetencies = ACTIVE_COMPETENCY_IDS + .map((id) => allCompetencies.find((c) => c.id === id)) + .filter((c): c is NonNullable => Boolean(c)); + + const stackProgress = learner.progress[activeStack.id] ?? 0; + const masteredInStack = activeStack.competencies.filter((c) => c.status === 'mastered').length; + const totalInStack = activeStack.competencies.length; + + const milestones = Array.from({ length: totalInStack }, (_, i) => i < masteredInStack); + + return ( +
+ {/* Header */} +
+ +
+

+ Welcome back, {learner.name.split(' ')[0]} +

+

+ You're {stackProgress}% through the {activeStack.name} stack. +

+
+
+ + {/* Top row: active competencies + milestone tracker */} +
+ {/* Active competencies (spans 2) */} + + +

+ Active competencies +

+

+ {activeStack.name} +

+
+ + {activeCompetencies.map((c) => { + const pct = ACTIVE_PROGRESS[c.id] ?? 0; + return ( +
+
+ + {c.name} + + {pct}% +
+
+
+
+
+ ); + })} + + + + {/* Milestone tracker */} + + +

+ Milestone tracker +

+

+ {masteredInStack}/{totalInStack} competencies mastered +

+
+ +
    + {milestones.map((done, i) => ( +
  1. + {done ? : i + 1} +
  2. + ))} +
+
+
+
+
+

+ {stackProgress}% overall stack progress +

+
+ + +
+ + {/* Progress graph (full width) */} + + +

+ Mastery progress +

+

+ Last 8 weeks · AI Orchestration stack +

+
+ + + +
+ + {/* Bottom row: recent artifacts + upcoming defenses */} +
+ {/* Recent artifacts */} + + +

+ Recent artifacts +

+

+ {learnerArtifacts.length} submitted +

+
+ + {learnerArtifacts.slice(0, 4).map((a) => { + const Icon = ARTIFACT_ICON[a.type] ?? FileText; + return ( +
+ + + +
+
+

+ {a.name} +

+ {formatDate(a.createdAt)} +
+

+ {a.type} +

+
+
+ ); + })} +
+
+ + {/* Upcoming defenses */} + + +

+ Upcoming defenses +

+

+ Oral exams on the calendar +

+
+ + {upcomingDefenses.map((d) => { + const comp = allCompetencies.find((c) => c.id === d.competencyId); + if (!comp) return null; + return ( +
+
+ + + +
+

+ {comp.name} +

+

+ {d.status === 'scheduled' ? 'Scheduled' : 'Pending scheduling'} +

+
+
+ + + +
+ ); + })} +
+
+
+ + {/* AI Tutor chat */} + + +
+ + + +
+

+ AI Tutor +

+

+ Coach and Socratic tutor · mock responses +

+
+
+
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(learner)/defend/[competencyId]/page.tsx b/apps/web/app/(learner)/defend/[competencyId]/page.tsx new file mode 100644 index 0000000..2b37662 --- /dev/null +++ b/apps/web/app/(learner)/defend/[competencyId]/page.tsx @@ -0,0 +1,317 @@ +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { + ArrowLeft, + CheckCircle, + XCircle, + Bot, + Award, + Code, + GitCommit, + FileText, + PlayCircle, + TestTube, + ShieldCheck, +} from 'lucide-react'; +import { Card, CardBody, CardHeader, Badge, Button } from '@nextcraft/ui'; +import { allCompetencies, competencyStacks } from '@nextcraft/mock-data'; +import { OralDefenseInterface } from '../../../../components/learner/oral-defense-interface'; + +const RUBRIC = [ + { name: 'Correctness of agent architecture', passed: true, weight: 25 }, + { name: 'Tool schema design & validation', passed: true, weight: 20 }, + { name: 'Error handling & fallbacks', passed: false, weight: 20 }, + { name: 'Test coverage', passed: true, weight: 15 }, + { name: 'Code clarity & documentation', passed: true, weight: 20 }, +]; + +const CRITERION_SCORES: Record = { + 'Correctness of agent architecture': 92, + 'Tool schema design & validation': 88, + 'Error handling & fallbacks': 61, + 'Test coverage': 84, + 'Code clarity & documentation': 90, +}; + +const OVERALL_SCORE = 84; + +const TRACE_EVENTS = [ + { icon: FileText, label: 'File created: src/main.ts', time: '14:02:11' }, + { icon: PlayCircle, label: 'First build attempt (failed)', time: '14:09:48' }, + { icon: TestTube, label: 'Test suite passed (2/2)', time: '14:12:30' }, + { icon: GitCommit, label: 'Commit: scaffold agent entrypoint', time: '14:18:05' }, + { icon: ShieldCheck, label: 'Tool schema validated', time: '14:21:42' }, + { icon: PlayCircle, label: 'Build attempt 2 (success)', time: '14:25:17' }, + { icon: GitCommit, label: 'Commit: implement tool calling', time: '14:28:03' }, + { icon: Award, label: 'Artifact submitted for assessment', time: '14:31:50' }, +]; + +const SUBMITTED_CODE = `import { ChatOpenAI } from '@langchain/openai'; +import { tool } from '@langchain/core/tools'; +import { z } from 'zod'; + +const getWeather = tool( + async ({ city, units }) => { + const res = await fetch(\`/api/weather?city=\${city}&units=\${units}\`); + return res.json(); + }, + { + name: 'get_weather', + description: 'Fetch the current weather for a city', + schema: z.object({ + city: z.string(), + units: z.enum(['celsius', 'fahrenheit']).default('celsius'), + }), + }, +); + +export async function main(query: string) { + const model = new ChatOpenAI({ model: 'gpt-4o-mini' }); + const modelWithTools = model.bindTools([getWeather]); + const response = await modelWithTools.invoke(query); + return response.tool_calls; +}`; + +export default async function DefensePage({ + params, +}: { + params: Promise<{ competencyId: string }>; +}) { + const { competencyId } = await params; + const competency = allCompetencies.find((c) => c.id === competencyId); + if (!competency) notFound(); + const stack = competencyStacks.find((s) => s.id === competency.stackId); + + const radius = 36; + const circumference = 2 * Math.PI * radius; + const offset = circumference - (OVERALL_SCORE / 100) * circumference; + + return ( +
+ + + Back to build + + +
+ Assessment · Defense +

+ {competency.name} +

+

+ {stack?.name} · oral defense and rubric review +

+
+ + {/* Artifact viewer */} + + +
+ +

+ Submitted Artifact +

+
+
+ +
+            {SUBMITTED_CODE}
+          
+
+
+ + {/* Rubric + AI reviewer */} +
+ {/* Rubric */} + + +

+ Assessment rubric +

+

+ Criteria and weights +

+
+ + {RUBRIC.map((c) => { + const Icon = c.passed ? CheckCircle : XCircle; + return ( +
+
+ + + {c.name} + +
+ {c.weight}% +
+ ); + })} +
+
+ + {/* AI reviewer panel */} + + +
+ + + +
+

+ AI Assessor +

+

+ Automated review results +

+
+
+
+ + {/* Overall score */} +
+
+ + + + +
+ {OVERALL_SCORE} +
+
+
+

+ Overall score +

+

+ Pass threshold: 75 ·{' '} + Passing +

+
+
+ + {/* Per-criterion scores */} +
    + {RUBRIC.map((c) => { + const score = CRITERION_SCORES[c.name] ?? 0; + return ( +
  • + + {c.name} + +
    +
    = 75 ? 'bg-emerald-500' : 'bg-amber-500' + }`} + style={{ width: `${score}%` }} + /> +
    + + {score} + +
  • + ); + })} +
+ +
+

Feedback

+

+ Strong tool-schema design and clear architecture. Error handling loses points: + malformed model output is not guarded with a fallback parser. Add a retry with + a structured-output schema and re-run the eval harness before your oral defense. +

+
+
+
+
+ + {/* Process trace timeline */} + + +

+ Process trace +

+

+ Build history captured during the sandbox session +

+
+ +
    + {TRACE_EVENTS.map((e, i) => { + const Icon = e.icon; + return ( +
  1. + + + +
    + + {e.label} + + {e.time} +
    +
  2. + ); + })} +
+
+
+ + {/* Oral defense */} + + +

+ Oral defense +

+

+ Live oral exam with an AI examiner +

+
+ + + +
+ +
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(learner)/layout.tsx b/apps/web/app/(learner)/layout.tsx new file mode 100644 index 0000000..73326b0 --- /dev/null +++ b/apps/web/app/(learner)/layout.tsx @@ -0,0 +1,48 @@ +'use client'; + +import type { ReactNode } from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { GraduationCap } from 'lucide-react'; + +const LINKS = [ + { label: 'Dashboard', href: '/dashboard' }, + { label: 'Catalog', href: '/catalog' }, + { label: 'AI Tutor', href: '/tutor' }, + { label: 'Defenses', href: '/defenses' }, +]; + +export default function LearnerLayout({ children }: { children: ReactNode }) { + const pathname = usePathname() ?? ''; + + return ( +
+ +
{children}
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(learner)/learn/[competencyId]/page.tsx b/apps/web/app/(learner)/learn/[competencyId]/page.tsx new file mode 100644 index 0000000..15701bb --- /dev/null +++ b/apps/web/app/(learner)/learn/[competencyId]/page.tsx @@ -0,0 +1,98 @@ +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { ArrowLeft, ArrowRight, Clock } from 'lucide-react'; +import { Button, Card, CardBody, Badge } from '@nextcraft/ui'; +import { allCompetencies, competencyStacks } from '@nextcraft/mock-data'; +import { WorkedExampleTabs } from '../../../../components/learner/worked-example-tabs'; + +export default async function ByteTutorialPage({ + params, +}: { + params: Promise<{ competencyId: string }>; +}) { + const { competencyId } = await params; + const competency = allCompetencies.find((c) => c.id === competencyId); + if (!competency) notFound(); + const stack = competencyStacks.find((s) => s.id === competency.stackId); + + return ( +
+ + + Back to {stack?.name ?? 'stack'} + + +
+
+ Byte · 3–7 min read + + ~5 min + +
+

+ {competency.name} +

+

{competency.description}

+
+ +
+ {/* Concept panel */} + + +

+ Concept +

+
+

+ {competency.name} sits at the core of the {stack?.name} stack. + It is the bridge between theory and a buildable artifact: you learn just enough + of the concept here (a Byte), then immediately apply it in the sandbox. The goal + is not exhaustive coverage — it is enough to build confidently and defend what + you built. +

+

+ In production systems this competency shows up as a trade-off between + reliability and velocity. A naive implementation works in the happy path, but the + real test is how it behaves when tools fail, contexts overflow, or the model + returns malformed output. The worked example on the right shows a small but + realistic implementation you can adapt in the build sandbox. +

+

+ As you read, keep this question in mind: what would I say in an oral defense + if the examiner asked me to justify one design choice in this Byte? Capture + that one sentence before you move on — it becomes part of your process trace. +

+
+
+ + + +
+
+
+ + {/* Worked example panel */} + + +
+

+ Worked example +

+

+ Reference implementation you can adapt in the sandbox. +

+
+
+ +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(learner)/page.tsx b/apps/web/app/(learner)/page.tsx new file mode 100644 index 0000000..edf4d37 --- /dev/null +++ b/apps/web/app/(learner)/page.tsx @@ -0,0 +1,236 @@ +import Link from 'next/link'; +import { + ArrowRight, + BookOpen, + Hammer, + Trophy, + ShieldCheck, + Quote, +} from 'lucide-react'; +import { Button, Card, CardBody, Badge, Avatar } from '@nextcraft/ui'; +import { competencyStacks } from '@nextcraft/mock-data'; + +const STEPS = [ + { + name: 'Byte', + detail: '3–7 min micro-tutorials', + icon: BookOpen, + }, + { + name: 'Build', + detail: 'Hands-on in a sandbox', + icon: Hammer, + }, + { + name: 'Demonstrate', + detail: 'Artifact + process trace', + icon: Trophy, + }, + { + name: 'Defend', + detail: 'Oral defense with AI', + icon: ShieldCheck, + }, +]; + +const FEATURED = ['stack-orchestration', 'stack-safety', 'stack-designer']; + +const TESTIMONIALS = [ + { + quote: + 'I went from dabbling with APIs to defending my multi-agent system in front of an AI examiner. The artifact I built is now in my portfolio — that landed me the interview.', + name: 'Priya Shah', + role: 'ML Engineer, Cohort 3', + }, + { + quote: + 'The Byte → Build → Demonstrate → Defend loop forced me to actually understand. You can’t bluff an oral defense with a clever-looking repo.', + name: 'Marcus Okafor', + role: 'Agent Reliability Engineer, Cohort 2', + }, + { + quote: + 'Employers stopped asking me where I studied. They asked what I had built and defended. That conversation is completely different.', + name: 'Lena Hoffmann', + role: 'AI Product Designer, Cohort 4', + }, +]; + +export default function LearnerHome() { + const featured = competencyStacks.filter((s) => FEATURED.includes(s.id)); + + return ( +
+ {/* Hero */} +
+ v0.1 · Learner Surface · UI Prototype +

+ Prove what you can build, not what you can write. +

+

+ Nextcraft is an AI-native outcome school. Learn in micro-tutorials, build in a + sandbox, demonstrate with real artifacts, and defend your work in an oral exam — + then carry that evidence into the talent marketplace. +

+
+ + + + + + +
+
+ + {/* How it works */} +
+
+

+ How it works +

+

+ Four stages, repeated for every competency. +

+
+
    + {STEPS.map((step, i) => { + const Icon = step.icon; + return ( + + +
    + + + + + Step {i + 1} + +
    +

    + {step.name} +

    +

    + {step.detail} +

    +
    +
    + ); + })} +
+
+ + {/* Program highlights */} +
+
+
+

+ Program highlights +

+

+ Three of five competency stacks. +

+
+ + + +
+
+ {featured.map((stack) => ( + + +
+

+ {stack.name} +

+ {stack.competencies.length} competencies +
+

+ {stack.description} +

+

+ Target roles: {stack.targetRoles} +

+
+ + + +
+
+
+ ))} +
+
+ + {/* Testimonials */} +
+
+

+ Learners who proved it +

+

+ Mock testimonials for the prototype. +

+
+
+ {TESTIMONIALS.map((t) => ( + + + +

+ “{t.quote}” +

+
+ +
+

+ {t.name} +

+

{t.role}

+
+
+
+
+ ))} +
+
+ + {/* Final CTA */} +
+

+ Build something defensible. +

+

+ Start with one Byte. Earn a microcredential. Walk into an interview with evidence. +

+
+ + + + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(marketplace)/layout.tsx b/apps/web/app/(marketplace)/layout.tsx new file mode 100644 index 0000000..a750f4a --- /dev/null +++ b/apps/web/app/(marketplace)/layout.tsx @@ -0,0 +1,46 @@ +'use client'; + +import type { ReactNode } from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { ShoppingBag } from 'lucide-react'; + +const LINKS = [ + { label: 'Job board', href: '/marketplace' }, + { label: 'Pricing', href: '/marketplace/pricing' }, +]; + +export default function MarketplaceLayout({ children }: { children: ReactNode }) { + const pathname = usePathname() ?? ''; + + return ( +
+ +
{children}
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(marketplace)/marketplace/employers/[employerId]/page.tsx b/apps/web/app/(marketplace)/marketplace/employers/[employerId]/page.tsx new file mode 100644 index 0000000..b3a79b8 --- /dev/null +++ b/apps/web/app/(marketplace)/marketplace/employers/[employerId]/page.tsx @@ -0,0 +1,188 @@ +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { ArrowLeft, Briefcase, Github, Globe, Linkedin, MapPin, Twitter, Users } from 'lucide-react'; +import { employers, jobs } from '@nextcraft/mock-data'; +import { Badge, Button, Card, CardBody } from '@nextcraft/ui'; +import { formatSalaryRange, initials as initialsOf, relativeTime } from '../../../../../lib/format'; + +interface PageProps { + params: Promise<{ employerId: string }>; +} + +const CULTURE_CARDS = [ + { label: 'Open Workspace', tone: 'bg-primary-100 text-primary-700 dark:bg-primary-900/40 dark:text-primary-300' }, + { label: 'Team Collaboration', tone: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300' }, + { label: 'Innovation Lab', tone: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300' }, + { label: 'Learning Culture', tone: 'bg-rose-100 text-rose-700 dark:bg-rose-900/40 dark:text-rose-300' }, + { label: 'Global Team', tone: 'bg-teal-100 text-teal-700 dark:bg-teal-900/40 dark:text-teal-300' }, + { label: 'Mission First', tone: 'bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300' }, +] as const; + +export default async function EmployerProfilePage({ params }: PageProps) { + const { employerId } = await params; + const employer = employers.find((e) => e.id === employerId); + if (!employer) notFound(); + + const openPositions = jobs.filter((j) => j.employerId === employer.id); + + const aboutParagraphs = [ + employer.description, + `Headquartered in ${employer.location}, ${employer.name} operates in the ${employer.industry} space with a team of ${employer.size} people. Our culture is best described as: ${employer.culture}`, + `We are actively hiring across roles that pair AI engineering rigor with real customer outcomes. Browse our open positions below and apply through the Nextcraft marketplace to connect directly with our hiring team.`, + ]; + + return ( +
+
+ + + Back to job board + +
+ + {/* Header */} + + +
+
+ + {initialsOf(employer.name)} + +
+

+ {employer.name} +

+

+ {employer.industry} +

+
+ + + {employer.size} + + + + {employer.location} + +
+
+
+
+ + + + + + + + + + + + +
+
+
+
+ + {/* About */} + + +

About

+
+ {aboutParagraphs.map((p, i) => ( +

{p}

+ ))} +
+
+
+ + {/* Culture */} + + +

Culture

+
+ {CULTURE_CARDS.map((c, i) => ( +
+ #{i + 1} + {c.label} +
+ ))} +
+
+
+ + {/* Open positions */} + + +
+

+ Open positions +

+ + + {openPositions.length} + +
+ {openPositions.length === 0 ? ( +

No current openings.

+ ) : ( +
+ {openPositions.map((job) => ( + +
+ + {job.title} + + + {job.location} · {formatSalaryRange(job.salaryMin, job.salaryMax)} · Posted{' '} + {relativeTime(job.postedAt)} + +
+ + View + + + ))} +
+ )} +
+
+
+ ); +} + +function SocialIcon({ + href, + label, + children, +}: { + href: string; + label: string; + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/apps/web/app/(marketplace)/marketplace/jobs/[jobId]/page.tsx b/apps/web/app/(marketplace)/marketplace/jobs/[jobId]/page.tsx new file mode 100644 index 0000000..4173179 --- /dev/null +++ b/apps/web/app/(marketplace)/marketplace/jobs/[jobId]/page.tsx @@ -0,0 +1,311 @@ +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { ArrowLeft, Building2, Check, MapPin, Wallet } from 'lucide-react'; +import { employers, jobs } from '@nextcraft/mock-data'; +import { Badge, Button, Card, CardBody } from '@nextcraft/ui'; +import type { Job } from '@nextcraft/types'; +import { + formatSalaryRange, + matchBarClasses, + matchBadgeClasses, + matchTone, + relativeTime, + seniorityLabel, + initials as initialsOf, +} from '../../../../../lib/format'; + +interface PageProps { + params: Promise<{ jobId: string }>; +} + +/** + * Derive a stable mock "AI-matched skills" breakdown from a job's skill list. + * The first skill is given the highest score (close to the job's matchScore) + * and subsequent skills taper slightly so the visualization reads naturally. + */ +function aiMatchedSkills(job: Job): Array<{ skill: string; score: number }> { + const top = job.skills.slice(0, 5); + return top.map((skill, idx) => { + const taper = Math.round(idx * 4); + const score = Math.max(55, Math.min(99, job.matchScore - taper)); + return { skill, score }; + }); +} + +export default async function JobDetailPage({ params }: PageProps) { + const { jobId } = await params; + const job = jobs.find((j) => j.id === jobId); + if (!job) notFound(); + + const employer = employers.find((e) => e.id === job.employerId); + const employerName = employer?.name ?? 'Unknown employer'; + + const related = jobs + .filter((j) => j.id !== job.id && j.employerId === job.employerId) + .slice(0, 3); + const relatedFallback = + related.length > 0 + ? related + : jobs + .filter((j) => j.id !== job.id && j.seniority === job.seniority) + .slice(0, 3); + const relatedJobs = (related.length > 0 ? related : relatedFallback).slice(0, 3); + + const matchedSkills = aiMatchedSkills(job); + + // Build out 3–4 paragraph rich description. + const descriptionParagraphs = [ + job.description, + `${employerName} is looking for a ${seniorityLabel(job.seniority)}-level ${ + job.title.toLowerCase().includes('engineer') || + job.title.toLowerCase().includes('designer') || + job.title.toLowerCase().includes('researcher') + ? 'practitioner' + : 'professional' + } who can own outcomes end-to-end. You will partner with product, research, and platform teams to ship reliable AI-powered features, instrumented with the evaluation harnesses this role is responsible for.`, + `Day-to-day you will design systems around ${job.skills + .slice(0, 3) + .join(', ')}, write production code, review pull requests, run evaluations, and contribute to a blameless postmortem culture. We expect strong written communication and comfort working in a fast-moving, evidence-driven environment.`, + `This role is ${job.remote ? 'remote-friendly' : `on-site in ${job.location}`} and reports into the ${employer?.industry ?? 'AI'} practice. Compensation ranges from ${formatSalaryRange( + job.salaryMin, + job.salaryMax, + )} plus equity and benefits. We are an equal-opportunity employer and actively seek candidates from non-traditional backgrounds who have built real AI systems.`, + ]; + + return ( +
+ {/* Back link */} +
+ + + Back to job board + +
+ + {/* Header */} + + +
+
+ + {initialsOf(employerName)} + +
+

+ {job.title} +

+ + {employerName} + +
+ + + {job.location} + + {job.remote && Remote} + + + {formatSalaryRange(job.salaryMin, job.salaryMax)} + + {seniorityLabel(job.seniority)} + Posted {relativeTime(job.postedAt)} +
+
+
+
+ + {job.matchScore}% + + +
+
+
+
+ +
+ {/* Main content */} +
+ + +

+ Job description +

+
+ {descriptionParagraphs.map((p, i) => ( +

{p}

+ ))} +
+
+
+ + + +

+ Required competencies +

+
    + {job.requiredCompetencies.map((c) => ( +
  • + + + + {humanizeCompetency(c)} +
  • + ))} +
+
+
+ + + +

+ AI-Matched Skills +

+

+ How your competency profile maps to this role, computed by Nextcraft's match + engine. +

+
+ {matchedSkills.map(({ skill, score }) => ( +
+
+ {skill} + {score}% +
+
+
+
+
+ ))} +
+
+ + Overall match score {job.matchScore}% +
+ + +
+ + {/* Sidebar */} +
+ + +

+ Employer +

+
+ + {initialsOf(employerName)} + +
+ + {employerName} + + + {employer?.industry ?? 'AI'} + +
+
+
+ {employer && ( + <> +
+
Size
+
{employer.size}
+
+
+
Location
+
{employer.location}
+
+ + )} +
+ {employer && ( + + + + )} +
+
+ + + +

+ Related jobs +

+
+ {relatedJobs.map((r) => ( + + + {r.title} + + {r.location} + + {formatSalaryRange(r.salaryMin, r.salaryMax)} · {r.matchScore}% match + + + ))} +
+
+
+
+
+
+ ); +} + +/** Turn a competency code like "stack-orchestration-c001" into readable prose. */ +function humanizeCompetency(code: string): string { + const parts = code.split('-'); + if (parts.length >= 3) { + const stack = parts[1]; + const capLabel = `Capability ${parts[parts.length - 1].toUpperCase()}`; + const stackPretty = stack.charAt(0).toUpperCase() + stack.slice(1); + return `${stackPretty} · ${capLabel}`; + } + return code; +} \ No newline at end of file diff --git a/apps/web/app/(marketplace)/marketplace/page.tsx b/apps/web/app/(marketplace)/marketplace/page.tsx new file mode 100644 index 0000000..6ececef --- /dev/null +++ b/apps/web/app/(marketplace)/marketplace/page.tsx @@ -0,0 +1,26 @@ +import { Badge } from '@nextcraft/ui'; +import { employers, jobs } from '@nextcraft/mock-data'; +import { JobFilters } from '../../../components/marketplace/job-filters'; + +export default function MarketplaceHomePage() { + const employerNames: Record = Object.fromEntries( + employers.map((e) => [e.id, e.name]), + ); + + return ( +
+
+ Marketplace · Job Board +

+ AI-Native Job Board +

+

+ AI-credentialed talent meets AI-era employers. Every listing shows a live AI match + score based on the skills recruiters need most right now. +

+
+ + +
+ ); +} \ No newline at end of file diff --git a/apps/web/app/(marketplace)/marketplace/pricing/page.tsx b/apps/web/app/(marketplace)/marketplace/pricing/page.tsx new file mode 100644 index 0000000..83081fb --- /dev/null +++ b/apps/web/app/(marketplace)/marketplace/pricing/page.tsx @@ -0,0 +1,287 @@ +import { Check, Crown, Star, X, Zap } from 'lucide-react'; +import { Badge, Button, Card, CardBody } from '@nextcraft/ui'; + +interface JobPricingPlan { + id: string; + name: string; + price: string; + cadence: string; + features: string[]; + cta: string; + popular?: boolean; + icon: React.ReactNode; +} + +interface TalentPricingPlan { + id: string; + name: string; + price: string; + cadence: string; + features: string[]; + cta: string; + popular?: boolean; + icon: React.ReactNode; +} + +const JOB_PLANS: JobPricingPlan[] = [ + { + id: 'single', + name: 'Single Post', + price: '$49', + cadence: 'one-time', + icon: , + features: ['1 job posting', '30 days live', 'Basic analytics', 'Standard placement'], + cta: 'Post a Job', + }, + { + id: 'bundle', + name: 'Bundle', + price: '$399', + cadence: 'one-time', + icon: , + features: ['10 job postings', '90 days each', 'Advanced analytics', 'Priority placement'], + cta: 'Buy Bundle', + popular: true, + }, + { + id: 'enterprise-jobs', + name: 'Enterprise', + price: 'Custom', + cadence: 'annual', + icon: , + features: [ + 'Unlimited postings', + 'Dedicated account manager', + 'API access', + 'Custom branding', + ], + cta: 'Contact Sales', + }, +]; + +const TALENT_PLANS: TalentPricingPlan[] = [ + { + id: 'starter', + name: 'Starter', + price: '$99', + cadence: '/mo', + icon: , + features: ['Browse talent database', '10 candidate views/mo', 'Basic filters'], + cta: 'Get Starter', + }, + { + id: 'pro', + name: 'Pro', + price: '$299', + cadence: '/mo', + icon: , + features: [ + 'Unlimited views', + 'AI matching', + 'Advanced filters', + 'Saved searches', + ], + cta: 'Get Pro', + popular: true, + }, + { + id: 'enterprise-talent', + name: 'Enterprise', + price: 'Custom', + cadence: 'annual', + icon: , + features: ['Everything in Pro', 'API access', 'Bulk export', 'Dedicated CSM'], + cta: 'Contact Sales', + }, +]; + +interface FeatureRow { + feature: string; + values: Array; +} + +const COMPARISON: FeatureRow[] = [ + { feature: 'Job postings', values: ['1', '10', 'Unlimited'] }, + { feature: 'Posting duration', values: ['30 days', '90 days', 'Custom'] }, + { feature: 'Analytics', values: ['Basic', 'Advanced', 'Advanced + Custom'] }, + { feature: 'Priority placement', values: [false, true, true] }, + { feature: 'Account manager', values: [false, false, true] }, + { feature: 'API access', values: [false, false, true] }, + { feature: 'Custom branding', values: [false, false, true] }, + { feature: 'Talent views', values: ['10/mo', 'Unlimited', 'Unlimited'] }, + { feature: 'AI matching', values: [false, true, true] }, + { feature: 'Saved searches', values: [false, true, true] }, + { feature: 'Bulk export', values: [false, false, true] }, +]; + +export default function PricingPage() { + return ( +
+
+ Marketplace · Pricing +

+ Pricing +

+

+ Hire AI-credentialed talent. Post roles, unlock the talent database, and let AI matching + find the right practitioner for your stack. +

+
+ + {/* Job posting packages */} +
+
+

+ Job Posting Packages +

+
+
+ {JOB_PLANS.map((plan) => ( + + ))} +
+
+ + {/* Talent access plans */} +
+
+

+ Talent Access Plans +

+
+
+ {TALENT_PLANS.map((plan) => ( + + ))} +
+
+ + {/* Feature comparison */} +
+

+ Compare features +

+ +
+ + + + + {COMPARISON_COLUMNS.map((c) => ( + + ))} + + + + {COMPARISON.map((row, i) => ( + + + {row.values.map((v, idx) => ( + + ))} + + ))} + +
+ Feature + + {c.label} +
{row.feature} + {typeof v === 'boolean' ? ( + v ? ( + + + + ) : ( + + + + ) + ) : ( + {v} + )} +
+
+
+
+
+ ); +} + +const COMPARISON_COLUMNS = [ + { key: 'single', label: 'Single Post' }, + { key: 'bundle', label: 'Bundle' }, + { key: 'enterprise', label: 'Enterprise' }, +] as const; + +function PricingCard({ + plan, +}: { + plan: JobPricingPlan | TalentPricingPlan; +}) { + const isPopular = plan.popular; + return ( + + +
+ + {plan.icon} + + {isPopular && ( + + + Most Popular + + )} +
+
+

+ {plan.name} +

+
+ + {plan.price} + + {plan.cadence} +
+
+
    + {plan.features.map((f) => ( +
  • + + + + {f} +
  • + ))} +
+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css new file mode 100644 index 0000000..22955c2 --- /dev/null +++ b/apps/web/app/globals.css @@ -0,0 +1,58 @@ +@import 'tailwindcss'; + +/* Use class-based dark mode: the `dark:` variant responds to a `.dark` + class on (toggled by ThemeProvider) instead of the OS + prefers-color-scheme media query. */ +@custom-variant dark (&:where(.dark, .dark *)); + +@theme { + --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif; + + --color-primary-50: #eef2ff; + --color-primary-100: #e0e7ff; + --color-primary-200: #c7d2fe; + --color-primary-300: #a5b4fc; + --color-primary-400: #818cf8; + --color-primary-500: #6366f1; + --color-primary-600: #4f46e5; + --color-primary-700: #4338ca; + --color-primary-800: #3730a3; + --color-primary-900: #312e81; + --color-primary-950: #1e1b4b; + + --color-accent-50: #ecfdf5; + --color-accent-100: #d1fae5; + --color-accent-200: #a7f3d0; + --color-accent-300: #6ee7b7; + --color-accent-400: #34d399; + --color-accent-500: #10b981; + --color-accent-600: #059669; + --color-accent-700: #047857; + --color-accent-800: #065f46; + --color-accent-900: #064e3b; + --color-accent-950: #022c22; + + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; +} + +@layer base { + html { + font-family: var(--font-sans); + color-scheme: light; + } + + html.dark { + color-scheme: dark; + } + + body { + @apply bg-white text-slate-900 antialiased; + } + + html.dark body { + @apply bg-slate-950 text-slate-100; + } +} \ No newline at end of file diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx new file mode 100644 index 0000000..3e3e40a --- /dev/null +++ b/apps/web/app/layout.tsx @@ -0,0 +1,25 @@ +import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; +import './globals.css'; +import { ThemeProvider } from '../components/theme-provider'; +import { NavigationShell } from '../components/navigation-shell'; + +const inter = Inter({ subsets: ['latin'], variable: '--font-sans', display: 'swap' }); + +export const metadata: Metadata = { + title: 'Nextcraft — AI-native outcome school', + description: + 'Prove what you can build, not what you can write. Nextcraft is an AI-native outcome school and talent marketplace prototype.', +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ); +} \ No newline at end of file diff --git a/apps/web/components/admin/competency-graph.tsx b/apps/web/components/admin/competency-graph.tsx new file mode 100644 index 0000000..811824c --- /dev/null +++ b/apps/web/components/admin/competency-graph.tsx @@ -0,0 +1,345 @@ +'use client'; + +import { useCallback, useMemo, useState } from 'react'; +import { ReactFlow, Background, Controls, MiniMap, ReactFlowProvider, useNodesState, useEdgesState, type Node, type Edge, type NodeMouseHandler } from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import { competencyStacks, allCompetencies } from '@nextcraft/mock-data'; +import type { CompetencyStack, Competency } from '@nextcraft/types'; +import { colors } from '@nextcraft/ui'; + +// Stack color → hex used for node fills + minimap. Sourced from the design +// tokens (colors.*) rather than inline literals. +const STACK_HEX: Record = { + indigo: colors.primary[500], + rose: colors.rose[500], + violet: colors.violet[500], + emerald: colors.accent[500], + cyan: colors.cyan[500], +}; + +const STACK_BG: Record = { + indigo: colors.primary[50], + rose: '#fff1f2', + violet: '#f5f3ff', + emerald: colors.accent[50], + cyan: '#ecfeff', +}; + +interface StackNodeData { + label: string; + kind: 'stack'; + stackId: string; + description: string; + competencyCount: number; + targetRoles: string; + stackColor: string; + [key: string]: unknown; +} + +interface CompetencyNodeData { + label: string; + kind: 'competency'; + stackId: string; + description: string; + stackColor: string; + prerequisites: string[]; + learnersMastering: number; + [key: string]: unknown; +} + +type GraphData = StackNodeData | CompetencyNodeData; +type GraphNode = Node; + +function buildGraph(): { nodes: GraphNode[]; edges: Edge[] } { + const nodes: GraphNode[] = []; + const edges: Edge[] = []; + + // Place stacks in a horizontal row at the top. + const stackCount = competencyStacks.length; + const stackGap = 280; + const stackY = 0; + const startX = -((stackCount - 1) * stackGap) / 2; + + competencyStacks.forEach((stack: CompetencyStack, sIdx) => { + const x = startX + sIdx * stackGap; + const color = STACK_HEX[stack.color] ?? colors.primary[500]; + nodes.push({ + id: stack.id, + type: 'default', + position: { x, y: stackY }, + data: { + label: stack.name, + kind: 'stack', + stackId: stack.id, + description: stack.description, + competencyCount: stack.competencies.length, + targetRoles: stack.targetRoles, + stackColor: color, + }, + style: { + background: STACK_BG[stack.color] ?? colors.primary[50], + border: `2px solid ${color}`, + color: colors.neutral[900], + borderRadius: 12, + padding: 10, + fontWeight: 600, + fontSize: 13, + width: 220, + textAlign: 'center', + }, + }); + }); + + // Place competencies below their stack in a vertical column. + competencyStacks.forEach((stack, sIdx) => { + const stackX = startX + sIdx * stackGap; + const color = STACK_HEX[stack.color] ?? colors.primary[500]; + stack.competencies.forEach((comp: Competency, cIdx) => { + // Show up to 3 competencies per stack in the graph (10-15 total). + if (cIdx >= 3) return; + const compId = comp.id; + const y = 160 + cIdx * 90; + nodes.push({ + id: compId, + type: 'default', + position: { x: stackX + 30, y }, + data: { + label: comp.name, + kind: 'competency', + stackId: stack.id, + description: comp.description, + stackColor: color, + prerequisites: comp.prerequisites, + learnersMastering: 12 + ((cIdx + sIdx) % 40), + }, + style: { + background: '#ffffff', + border: `1.5px solid ${color}`, + color: colors.neutral[700], + borderRadius: 8, + padding: 8, + fontSize: 11, + width: 200, + }, + }); + + // Edge: competency → parent stack. + edges.push({ + id: `e-${compId}-${stack.id}`, + source: compId, + target: stack.id, + style: { stroke: color, strokeWidth: 1.5 }, + type: 'smoothstep', + }); + }); + }); + + // A few cross-stack dependency edges between competencies. + const crossDeps: Array<{ from: string; to: string }> = [ + { from: 'stack-orchestration-c001', to: 'stack-safety-c001' }, + { from: 'stack-orchestration-c005', to: 'stack-science-c001' }, + { from: 'stack-designer-c001', to: 'stack-orchestration-c001' }, + ]; + crossDeps.forEach((d, i) => { + const fromComp = allCompetencies.find((c) => c.id === d.from); + const toComp = allCompetencies.find((c) => c.id === d.to); + if (!fromComp || !toComp) return; + if (!nodes.find((n) => n.id === d.from) || !nodes.find((n) => n.id === d.to)) return; + edges.push({ + id: `cross-${i}`, + source: d.from, + target: d.to, + style: { stroke: colors.neutral[400], strokeWidth: 1.5, strokeDasharray: '4 3' }, + type: 'smoothstep', + animated: true, + }); + }); + + return { nodes, edges }; +} + +interface SelectedStackInfo { + kind: 'stack'; + label: string; + description: string; + competencyCount: number; + targetRoles: string; + stackColor: string; +} + +interface SelectedCompetencyInfo { + kind: 'competency'; + label: string; + description: string; + stackId: string; + stackName: string; + stackColor: string; + prerequisites: string[]; + learnersMastering: number; +} + +type SelectedInfo = SelectedStackInfo | SelectedCompetencyInfo | null; + +function GraphInner() { + const initial = useMemo(() => buildGraph(), []); + const [nodes, , onNodesChange] = useNodesState(initial.nodes); + const [edges, , onEdgesChange] = useEdgesState(initial.edges); + const [selected, setSelected] = useState(null); + + const handleNodeClick: NodeMouseHandler = useCallback((_evt, node) => { + const data = node.data as StackNodeData | CompetencyNodeData; + if (data.kind === 'stack') { + setSelected({ + kind: 'stack', + label: data.label, + description: data.description, + competencyCount: data.competencyCount, + targetRoles: data.targetRoles, + stackColor: data.stackColor, + }); + } else { + const stackName = + competencyStacks.find((s) => s.id === data.stackId)?.name ?? data.stackId; + setSelected({ + kind: 'competency', + label: data.label, + description: data.description, + stackId: data.stackId, + stackName, + stackColor: data.stackColor, + prerequisites: data.prerequisites, + learnersMastering: data.learnersMastering, + }); + } + }, []); + + return ( +
+
+ + + + { + const d = n.data as StackNodeData | CompetencyNodeData; + return d.stackColor ?? colors.neutral[400]; + }} + maskColor="rgba(241, 245, 249, 0.7)" + /> + +
+ + {/* Detail panel */} + +
+ ); +} + +export function CompetencyGraph() { + return ( + + + + ); +} \ No newline at end of file diff --git a/apps/web/components/admin/learner-detail-panel.tsx b/apps/web/components/admin/learner-detail-panel.tsx new file mode 100644 index 0000000..b2aa5f3 --- /dev/null +++ b/apps/web/components/admin/learner-detail-panel.tsx @@ -0,0 +1,197 @@ +'use client'; + +import { X, Mail, CalendarDays, GraduationCap, Award, CheckCircle, Circle, Lock } from 'lucide-react'; +import { Badge } from '@nextcraft/ui'; +import type { AdminLearner } from '@nextcraft/mock-data'; + +export interface LearnerDetailPanelProps { + learner: AdminLearner | null; + onClose: () => void; +} + +const STATUS_CLASSES: Record = { + mastered: 'text-emerald-600 dark:text-emerald-400', + in_progress: 'text-amber-600 dark:text-amber-400', + available: 'text-slate-500 dark:text-slate-400', + locked: 'text-slate-300 dark:text-slate-600', +}; + +const STATUS_LABEL: Record = { + mastered: 'Mastered', + in_progress: 'In Progress', + available: 'Available', + locked: 'Locked', +}; + +function formatDate(iso: string): string { + const d = new Date(iso); + return d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); +} + +export function LearnerDetailPanel({ learner, onClose }: LearnerDetailPanelProps) { + if (!learner) return null; + + const masteredCount = learner.masteredCount; + const total = learner.totalCompetencies; + const pct = learner.progressPct; + + return ( +
+
+ {/* Header */} +
+
+ + {initials(learner.name)} + +
+

+ {learner.name} +

+

+ {learner.stackName} +

+
+
+ +
+ + {/* Body */} +
+ {/* Profile */} +
+

+ Profile +

+
+
+ + {learner.email} +
+
+ + Joined {formatDate(learner.joinedAt)} +
+
+
+ + {/* Progress */} +
+

+ Progress Tracking +

+
+
+ + {masteredCount} of {total} competencies mastered + + + {pct}% + +
+
+
+
+
+
+ + {/* Competencies */} +
+

+ Competency Completion +

+
    + {learner.competencies.map((c) => { + const Icon = + c.status === 'mastered' + ? CheckCircle + : c.status === 'locked' + ? Lock + : Circle; + return ( +
  • + + + {c.name} + + + {STATUS_LABEL[c.status]} + +
  • + ); + })} +
+
+ + {/* Credentials */} +
+

+ Credential Issuance Log +

+ {learner.credentials.length === 0 ? ( +

+ No microcredentials issued yet. +

+ ) : ( +
    + {learner.credentials.map((cr) => ( +
  • + + + +
    +

    + {cr.name} +

    +
    + + + Issued {formatDate(cr.issuedAt)} + + {cr.score}/100 +
    +
    +
  • + ))} +
+ )} +
+
+
+
+ ); +} + +function initials(name: string): string { + return name + .split(' ') + .filter(Boolean) + .slice(0, 2) + .map((p) => p[0]?.toUpperCase() ?? '') + .join(''); +} \ No newline at end of file diff --git a/apps/web/components/admin/learner-table.tsx b/apps/web/components/admin/learner-table.tsx new file mode 100644 index 0000000..74d1cab --- /dev/null +++ b/apps/web/components/admin/learner-table.tsx @@ -0,0 +1,229 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { Search } from 'lucide-react'; +import { Badge, Input } from '@nextcraft/ui'; +import type { AdminLearner, LearnerStatus } from '@nextcraft/mock-data'; +import { competencyStacks } from '@nextcraft/mock-data'; +import { LearnerDetailPanel } from './learner-detail-panel'; + +export interface LearnerTableProps { + learners: AdminLearner[]; +} + +const STATUS_VARIANT: Record = { + active: 'success', + completed: 'default', + paused: 'warning', +}; + +const STATUS_LABEL: Record = { + active: 'Active', + completed: 'Completed', + paused: 'Paused', +}; + +function formatDate(iso: string): string { + return new Date(iso).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + }); +} + +function initials(name: string): string { + return name + .split(' ') + .filter(Boolean) + .slice(0, 2) + .map((p) => p[0]?.toUpperCase() ?? '') + .join(''); +} + +export function LearnerTable({ learners }: LearnerTableProps) { + const [query, setQuery] = useState(''); + const [stackFilter, setStackFilter] = useState('all'); + const [selectedId, setSelectedId] = useState(null); + + const filtered = useMemo(() => { + const q = query.trim().toLowerCase(); + return learners.filter((l) => { + if (q && !`${l.name} ${l.email}`.toLowerCase().includes(q)) return false; + if (stackFilter !== 'all' && l.stackId !== stackFilter) return false; + return true; + }); + }, [learners, query, stackFilter]); + + const selected = learners.find((l) => l.id === selectedId) ?? null; + + return ( +
+ {/* Top bar */} +
+
+ } + value={query} + onChange={(e) => setQuery(e.target.value)} + aria-label="Search learners" + /> +
+
+ + +
+
+ +

+ Showing{' '} + + {filtered.length} + {' '} + of {learners.length} learners +

+ + {/* Desktop table */} +
+ + + + + + + + + + + + + {filtered.length === 0 ? ( + + + + ) : ( + filtered.map((l) => ( + setSelectedId(l.id)} + className="cursor-pointer transition-colors hover:bg-slate-50 dark:hover:bg-slate-800/60" + > + + + + + + + + )) + )} + +
NameEmailStackProgressStatusJoined
+ No learners match your filters. +
+
+ + {initials(l.name)} + + + {l.name} + +
+
+ {l.email} + + {l.stackName} + +
+
+
+
+ + {l.progressPct}% + +
+
+ + {STATUS_LABEL[l.status]} + + + {formatDate(l.joinedAt)} +
+
+ + {/* Mobile card list */} +
+ {filtered.length === 0 ? ( +
+ No learners match your filters. +
+ ) : ( + filtered.map((l) => ( + + )) + )} +
+ + setSelectedId(null)} + /> +
+ ); +} \ No newline at end of file diff --git a/apps/web/components/admin/moderation-tabs.tsx b/apps/web/components/admin/moderation-tabs.tsx new file mode 100644 index 0000000..3249c35 --- /dev/null +++ b/apps/web/components/admin/moderation-tabs.tsx @@ -0,0 +1,288 @@ +'use client'; + +import { useState } from 'react'; +import { + CheckCircle, + FileText, + Flag, + Shield, + XCircle, + Building2, +} from 'lucide-react'; +import { Badge } from '@nextcraft/ui'; +import { + employerVerificationQueue, + flaggedContentQueue, + jobReviewQueue, +} from '@nextcraft/mock-data'; +import type { + EmployerVerificationItem, + FlaggedContentItem, + JobReviewItem, +} from '@nextcraft/mock-data'; + +type TabId = 'jobs' | 'employers' | 'flagged'; + +const TABS: Array<{ id: TabId; label: string }> = [ + { id: 'jobs', label: 'Job Posting Review' }, + { id: 'employers', label: 'Employer Verification' }, + { id: 'flagged', label: 'Flagged Content' }, +]; + +const STATUS_VARIANT = { ok: 'success', warn: 'warning', err: 'error' } as const; + +export function ModerationTabs() { + const [tab, setTab] = useState('jobs'); + + const counts: Record = { + jobs: jobReviewQueue.length, + employers: employerVerificationQueue.length, + flagged: flaggedContentQueue.length, + }; + + return ( +
+ {/* Tab bar */} +
+ {TABS.map((t) => { + const active = t.id === tab; + return ( + + ); + })} +
+ + {tab === 'jobs' && } + {tab === 'employers' && } + {tab === 'flagged' && } +
+ ); +} + +// --------------------------------------------------------------------------- +// Tab 1: Job Posting Review +// --------------------------------------------------------------------------- + +function JobReviewTab() { + return ( +
+ {jobReviewQueue.map((item) => ( + + ))} +
+ ); +} + +function JobReviewCard({ item }: { item: JobReviewItem }) { + return ( +
+
+
+ + + +
+

+ {item.title} +

+

+ {item.employerName} · {item.stackName} +

+

+ {item.location} · Submitted {formatDate(item.submittedAt)} +

+
+
+ +
+
+ }> + Approve + + }> + Reject + + }> + Flag + +
+
+ ); +} + +// --------------------------------------------------------------------------- +// Tab 2: Employer Verification +// --------------------------------------------------------------------------- + +function EmployerVerificationTab() { + return ( +
+ {employerVerificationQueue.map((item) => ( + + ))} +
+ ); +} + +function EmployerVerificationCard({ item }: { item: EmployerVerificationItem }) { + return ( +
+
+
+ + {item.logoInitials} + +
+

+ {item.name} +

+

+ {item.industry} · {item.location} +

+

+ Submitted {formatDate(item.submittedAt)} +

+
+
+ +
+
+ }> + Verify + + }> + Request More Info + + }> + Reject + +
+
+ ); +} + +// --------------------------------------------------------------------------- +// Tab 3: Flagged Content +// --------------------------------------------------------------------------- + +function FlaggedContentTab() { + return ( +
+ {flaggedContentQueue.map((item) => ( + + ))} +
+ ); +} + +function FlaggedContentCard({ item }: { item: FlaggedContentItem }) { + return ( +
+
+
+ + + +
+
+

+ {item.title} +

+ {item.contentType} +
+

+ Flagged by {item.flaggedBy} · {item.reason} +

+

+ {formatDate(item.flaggedAt)} +

+
+
+
+
+ }> + Dismiss Flag + + }> + Remove Content + + }> + Warn User + +
+
+ ); +} + +// --------------------------------------------------------------------------- +// Shared action button +// --------------------------------------------------------------------------- + +type ActionTone = 'success' | 'error' | 'warning' | 'info' | 'neutral'; + +const ACTION_CLASSES: Record = { + success: 'border-emerald-300 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 dark:border-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-300 dark:hover:bg-emerald-900/50', + error: 'border-rose-300 bg-rose-50 text-rose-700 hover:bg-rose-100 dark:border-rose-800 dark:bg-rose-900/30 dark:text-rose-300 dark:hover:bg-rose-900/50', + warning: 'border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-100 dark:border-amber-800 dark:bg-amber-900/30 dark:text-amber-300 dark:hover:bg-amber-900/50', + info: 'border-primary-300 bg-primary-50 text-primary-700 hover:bg-primary-100 dark:border-primary-800 dark:bg-primary-900/30 dark:text-primary-300 dark:hover:bg-primary-900/50', + neutral: 'border-slate-300 bg-slate-50 text-slate-700 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 dark:hover:bg-slate-700', +}; + +function ActionButton({ + tone, + icon, + children, +}: { + tone: ActionTone; + icon: React.ReactNode; + children: React.ReactNode; +}) { + return ( + + ); +} + +function formatDate(iso: string): string { + return new Date(iso).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + }); +} \ No newline at end of file diff --git a/apps/web/components/breadcrumbs.stories.tsx b/apps/web/components/breadcrumbs.stories.tsx new file mode 100644 index 0000000..f55e88a --- /dev/null +++ b/apps/web/components/breadcrumbs.stories.tsx @@ -0,0 +1,101 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Breadcrumbs } from './breadcrumbs'; + +const meta: Meta = { + title: 'Composites/Breadcrumbs', + component: Breadcrumbs, + tags: ['autodocs'], + parameters: { + layout: 'padded', + nextjs: { + navigation: { + // Simulate different pathnames for each story. + pathname: '/', + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +/** + * Breadcrumbs reads `usePathname()` from next/navigation. In Storybook the + * pathname is provided via the Next.js framework mock — each story below + * documents a representative route. + */ +export const Home: Story = { + parameters: { nextjs: { navigation: { pathname: '/' } } }, +}; + +export const Catalog: Story = { + parameters: { nextjs: { navigation: { pathname: '/catalog' } } }, +}; + +export const StackDetail: Story = { + parameters: { nextjs: { navigation: { pathname: '/catalog/stack-orchestration' } } }, +}; + +export const Dashboard: Story = { + parameters: { nextjs: { navigation: { pathname: '/dashboard' } } }, +}; + +export const LearnCompetency: Story = { + parameters: { nextjs: { navigation: { pathname: '/learn/stack-orchestration-c001' } } }, +}; + +export const BuildCompetency: Story = { + parameters: { nextjs: { navigation: { pathname: '/build/stack-safety-c002' } } }, +}; + +export const DefendCompetency: Story = { + parameters: { nextjs: { navigation: { pathname: '/defend/stack-designer-c001' } } }, +}; + +export const Marketplace: Story = { + parameters: { nextjs: { navigation: { pathname: '/marketplace' } } }, +}; + +export const MarketplacePricing: Story = { + parameters: { nextjs: { navigation: { pathname: '/marketplace/pricing' } } }, +}; + +export const JobDetail: Story = { + parameters: { nextjs: { navigation: { pathname: '/marketplace/jobs/job-001' } } }, +}; + +export const EmployerProfile: Story = { + parameters: { nextjs: { navigation: { pathname: '/marketplace/employers/emp-openai' } } }, +}; + +export const EmployerDashboard: Story = { + parameters: { nextjs: { navigation: { pathname: '/employer' } } }, +}; + +export const EmployerTalent: Story = { + parameters: { nextjs: { navigation: { pathname: '/employer/talent' } } }, +}; + +export const CandidateProfile: Story = { + parameters: { nextjs: { navigation: { pathname: '/employer/talent/cand-001' } } }, +}; + +export const EmployerPostings: Story = { + parameters: { nextjs: { navigation: { pathname: '/employer/postings' } } }, +}; + +export const Admin: Story = { + parameters: { nextjs: { navigation: { pathname: '/admin' } } }, +}; + +export const AdminLearners: Story = { + parameters: { nextjs: { navigation: { pathname: '/admin/learners' } } }, +}; + +export const AdminGraph: Story = { + parameters: { nextjs: { navigation: { pathname: '/admin/graph' } } }, +}; + +export const AdminModeration: Story = { + parameters: { nextjs: { navigation: { pathname: '/admin/moderation' } } }, +}; \ No newline at end of file diff --git a/apps/web/components/breadcrumbs.tsx b/apps/web/components/breadcrumbs.tsx new file mode 100644 index 0000000..79962c2 --- /dev/null +++ b/apps/web/components/breadcrumbs.tsx @@ -0,0 +1,62 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { ChevronRight, Home } from 'lucide-react'; +import { resolveBreadcrumbs } from '../lib/breadcrumbs'; + +/** + * Breadcrumbs — reads the current pathname and renders a breadcrumb trail. + * The final crumb (current page) is rendered as plain text; all earlier + * crumbs are links. Segments are separated by a chevron-right icon. + */ +export function Breadcrumbs() { + const pathname = usePathname() ?? '/'; + const crumbs = resolveBreadcrumbs(pathname); + + if (crumbs.length <= 1 && crumbs[0]?.label === 'Home') { + // On the home page, show a minimal "Home" marker for consistency. + return ( + + ); + } + + return ( + + ); +} \ No newline at end of file diff --git a/apps/web/components/dark-mode-toggle.tsx b/apps/web/components/dark-mode-toggle.tsx new file mode 100644 index 0000000..146cd5b --- /dev/null +++ b/apps/web/components/dark-mode-toggle.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { Moon, Sun } from 'lucide-react'; +import { Button } from '@nextcraft/ui'; +import { useTheme } from './theme-provider'; + +export function DarkModeToggle() { + const { mode, toggle } = useTheme(); + return ( + + ); +} \ No newline at end of file diff --git a/apps/web/components/employer/analytics-charts.tsx b/apps/web/components/employer/analytics-charts.tsx new file mode 100644 index 0000000..3f56042 --- /dev/null +++ b/apps/web/components/employer/analytics-charts.tsx @@ -0,0 +1,157 @@ +'use client'; + +import { + Bar, + BarChart, + CartesianGrid, + Cell, + Legend, + Line, + LineChart, + Pie, + PieChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { colors } from '@nextcraft/ui'; + +/* -------------------------------------------------------------------------- */ +/* Mock chart data */ +/* -------------------------------------------------------------------------- */ + +const postingsData = [ + { month: 'Apr', postings: 4, applications: 38 }, + { month: 'May', postings: 6, applications: 62 }, + { month: 'Jun', postings: 5, applications: 51 }, + { month: 'Jul', postings: 8, applications: 84 }, + { month: 'Aug', postings: 9, applications: 112 }, + { month: 'Sep', postings: 7, applications: 142 }, +]; + +const sourceData = [ + { name: 'Direct', value: 40, color: colors.primary[500] }, + { name: 'AI Match', value: 30, color: colors.accent[500] }, + { name: 'Referral', value: 20, color: colors.violet[400] }, + { name: 'Job Board', value: 10, color: colors.amber[400] }, +]; + +const placementData = [ + { month: 'Apr', placements: 2 }, + { month: 'May', placements: 5 }, + { month: 'Jun', placements: 9 }, + { month: 'Jul', placements: 14 }, + { month: 'Aug', placements: 21 }, + { month: 'Sep', placements: 28 }, +]; + +/* -------------------------------------------------------------------------- */ +/* Shared styling */ +/* -------------------------------------------------------------------------- */ + +const tooltipStyle = { + borderRadius: 8, + border: `1px solid ${colors.neutral[200]}`, + fontSize: 12, + background: '#ffffff', +} as const; + +const axisTick = { fontSize: 12, fill: colors.neutral[500] } as const; + +/* -------------------------------------------------------------------------- */ +/* Component */ +/* -------------------------------------------------------------------------- */ + +export function AnalyticsCharts() { + return ( +
+ {/* Bar chart — Postings vs Applications */} +
+

+ Postings vs Applications +

+

Last 6 months

+
+ + + + + + + + + + + +
+
+ + {/* Donut chart — Applicant Sources */} +
+

+ Applicant Sources +

+

Where candidates come from

+
+ + + + {sourceData.map((entry) => ( + + ))} + + `${v}%`} /> + + + +
+
+ + {/* Line chart — Placement Trends */} +
+

+ Placement Trends +

+

Cumulative placements

+
+ + + + + + + + + + + + + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/web/components/employer/defense-accordion.tsx b/apps/web/components/employer/defense-accordion.tsx new file mode 100644 index 0000000..30a7646 --- /dev/null +++ b/apps/web/components/employer/defense-accordion.tsx @@ -0,0 +1,102 @@ +'use client'; + +import { useState } from 'react'; +import { ChevronDown, ChevronRight, ShieldCheck } from 'lucide-react'; +import { Badge } from '@nextcraft/ui'; +import { matchBadgeClasses } from '../../lib/format'; + +/* -------------------------------------------------------------------------- */ +/* Types + mock transcript data */ +/* -------------------------------------------------------------------------- */ + +export interface DefenseSession { + id: string; + competencyName: string; + score: number; + date: string; + transcript: Array<{ question: string; answer: string }>; +} + +interface DefenseAccordionProps { + sessions: DefenseSession[]; +} + +/* -------------------------------------------------------------------------- */ +/* Component */ +/* -------------------------------------------------------------------------- */ + +export function DefenseAccordion({ sessions }: DefenseAccordionProps) { + const [openId, setOpenId] = useState(sessions[0]?.id ?? null); + + return ( +
+ {sessions.map((session) => { + const open = openId === session.id; + return ( +
+ + {open && ( +
+
+ {session.transcript.map((qa, idx) => ( +
+
+ Q{idx + 1} +

+ {qa.question} +

+
+
+ + A + +

+ {qa.answer} +

+
+
+ ))} +
+
+ )} +
+ ); + })} +
+ ); +} \ No newline at end of file diff --git a/apps/web/components/employer/posting-manager.tsx b/apps/web/components/employer/posting-manager.tsx new file mode 100644 index 0000000..1fd2512 --- /dev/null +++ b/apps/web/components/employer/posting-manager.tsx @@ -0,0 +1,343 @@ +'use client'; + +import { useState } from 'react'; +import { Plus, Trash2, Save } from 'lucide-react'; +import { Badge, Button } from '@nextcraft/ui'; +import type { Job, Seniority } from '@nextcraft/types'; +import { formatSalaryK, relativeTime, seniorityLabel } from '../../lib/format'; + +/* -------------------------------------------------------------------------- */ +/* Mock posting list (status + applicant count derived from the Job) */ +/* -------------------------------------------------------------------------- */ + +type PostingStatus = 'active' | 'draft' | 'expired'; + +interface PostingRow { + job: Job; + status: PostingStatus; + applicants: number; +} + +interface ApplicantRow { + name: string; + appliedAt: string; + stage: 'Screening' | 'Interview' | 'Offer'; + matchScore: number; +} + +/* Deterministically derive a posting status per job id. */ +function deriveStatus(job: Job, idx: number): PostingStatus { + if (idx < 8) return 'active'; + if (idx < 11) return 'draft'; + return 'expired'; +} + +function deriveApplicants(job: Job, idx: number): number { + return 4 + ((job.matchScore + idx * 7) % 18); +} + +function deriveApplicantList(job: Job): ApplicantRow[] { + const names = [ + 'Maya Okonkwo', + 'Devon Park', + 'Priya Iyer', + 'Tomás Vega', + 'Liam Chen', + 'Yuki Tanaka', + 'Sofia Marchetti', + ]; + const stages: ApplicantRow['stage'][] = ['Screening', 'Interview', 'Offer']; + const count = 5 + (job.matchScore % 2); + const rows: ApplicantRow[] = []; + for (let i = 0; i < count; i++) { + const stage = stages[i % stages.length]; + rows.push({ + name: names[(job.matchScore + i) % names.length], + appliedAt: new Date(Date.UTC(2026, 7, 28 - i * 2)).toISOString(), + stage, + matchScore: 78 + ((job.matchScore + i * 3) % 18), + }); + } + return rows; +} + +/* -------------------------------------------------------------------------- */ +/* Status badge */ +/* -------------------------------------------------------------------------- */ + +function StatusBadge({ status }: { status: PostingStatus }) { + if (status === 'active') return Active; + if (status === 'draft') return Draft; + return Expired; +} + +function StageBadge({ stage }: { stage: ApplicantRow['stage'] }) { + if (stage === 'Offer') return Offer; + if (stage === 'Interview') return Interview; + return Screening; +} + +/* -------------------------------------------------------------------------- */ +/* Component */ +/* -------------------------------------------------------------------------- */ + +export interface PostingManagerProps { + jobs: Job[]; +} + +export function PostingManager({ jobs }: PostingManagerProps) { + const postings: PostingRow[] = jobs.map((job, idx) => ({ + job, + status: deriveStatus(job, idx), + applicants: deriveApplicants(job, idx), + })); + + const [selectedId, setSelectedId] = useState(postings[0]?.job.id ?? ''); + const selected = postings.find((p) => p.job.id === selectedId) ?? null; + + return ( +
+ {/* Left panel — posting list */} +
+
+

+ Postings +

+ +
+
+ {postings.map((p) => { + const active = p.job.id === selectedId; + return ( + + ); + })} +
+
+ + {/* Right panel — detail */} +
+ {selected ? ( + + ) : ( +
+ Select a posting to view details +
+ )} +
+
+ ); +} + +function PostingDetail({ posting }: { posting: PostingRow }) { + const { job, applicants } = posting; + const applicantList = deriveApplicantList(job); + const seniorityOptions: Seniority[] = ['entry', 'mid', 'senior', 'staff', 'principal']; + + const inputClass = + 'h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm text-slate-900 focus:border-primary-500 focus:ring-2 focus:ring-primary-500/30 focus:outline-none dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100'; + + return ( +
+ {/* Posting form */} +
+
+

+ Edit Posting +

+ +
+ +
+
+ + +
+ +
+ +