From 2e051e4befd8980b17f2fab45f466d104844a3a4 Mon Sep 17 00:00:00 2001 From: Nicholai Date: Wed, 21 Jan 2026 23:02:18 -0700 Subject: [PATCH] feat: complete project setup with auth and contributor docs - add WorkOS AuthKit authentication with middleware protection - add dashboard with sidebar layout (shadcn/ui components) - add contributor documentation (CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, START-HERE, Documentation/) - add CI workflow for lint and build on PRs - switch from pnpm to bun - add CLAUDE.md and AGENTS.md for AI assistant context --- .env.example | 3 + .github/workflows/ci.yml | 50 + .gitignore | 10 +- AGENTS.md | 1 + CLAUDE.md | 161 + CODE_OF_CONDUCT.md | 86 + CONTRIBUTING.md | 143 + Documentation/README.md | 39 + Documentation/commit-messages.md | 145 + Documentation/pull-requests.md | 159 + Documentation/ui-guidelines.md | 93 + LICENSE | 21 + README.md | 87 +- SECURITY.md | 93 + START-HERE.md | 181 + bun.lock | 3214 ++++++++ components.json | 22 + package.json | 116 +- pnpm-lock.yaml | 8079 --------------------- src/app/actions/auth.ts | 7 + src/app/api/auth/[...authkit]/route.ts | 3 + src/app/dashboard/data.json | 614 ++ src/app/dashboard/page.tsx | 47 + src/app/globals.css | 201 +- src/app/layout.tsx | 15 +- src/app/page.tsx | 95 +- src/components/app-sidebar.tsx | 181 + src/components/chart-area-interactive.tsx | 291 + src/components/data-table.tsx | 807 ++ src/components/nav-documents.tsx | 92 + src/components/nav-main.tsx | 58 + src/components/nav-secondary.tsx | 42 + src/components/nav-user.tsx | 110 + src/components/section-cards.tsx | 102 + src/components/sign-out-button.tsx | 12 + src/components/site-header.tsx | 21 + src/components/ui/accordion.tsx | 66 + src/components/ui/alert-dialog.tsx | 157 + src/components/ui/alert.tsx | 66 + src/components/ui/aspect-ratio.tsx | 11 + src/components/ui/avatar.tsx | 53 + src/components/ui/badge.tsx | 46 + src/components/ui/breadcrumb.tsx | 109 + src/components/ui/button.tsx | 62 + src/components/ui/calendar.tsx | 220 + src/components/ui/card.tsx | 92 + src/components/ui/carousel.tsx | 241 + src/components/ui/chart.tsx | 357 + src/components/ui/checkbox.tsx | 32 + src/components/ui/collapsible.tsx | 33 + src/components/ui/command.tsx | 184 + src/components/ui/context-menu.tsx | 252 + src/components/ui/dialog.tsx | 143 + src/components/ui/drawer.tsx | 135 + src/components/ui/dropdown-menu.tsx | 257 + src/components/ui/form.tsx | 167 + src/components/ui/hover-card.tsx | 44 + src/components/ui/input-otp.tsx | 77 + src/components/ui/input.tsx | 21 + src/components/ui/label.tsx | 24 + src/components/ui/menubar.tsx | 276 + src/components/ui/navigation-menu.tsx | 168 + src/components/ui/pagination.tsx | 127 + src/components/ui/popover.tsx | 48 + src/components/ui/progress.tsx | 31 + src/components/ui/radio-group.tsx | 45 + src/components/ui/resizable.tsx | 56 + src/components/ui/scroll-area.tsx | 58 + src/components/ui/select.tsx | 190 + src/components/ui/separator.tsx | 28 + src/components/ui/sheet.tsx | 139 + src/components/ui/sidebar.tsx | 726 ++ src/components/ui/skeleton.tsx | 13 + src/components/ui/slider.tsx | 63 + src/components/ui/sonner.tsx | 40 + src/components/ui/switch.tsx | 31 + src/components/ui/table.tsx | 116 + src/components/ui/tabs.tsx | 66 + src/components/ui/textarea.tsx | 18 + src/components/ui/toggle-group.tsx | 83 + src/components/ui/toggle.tsx | 47 + src/components/ui/tooltip.tsx | 61 + src/hooks/use-mobile.ts | 19 + src/lib/utils.ts | 6 + src/middleware.ts | 15 + wrangler.jsonc | 3 + 86 files changed, 12515 insertions(+), 8208 deletions(-) create mode 100644 .env.example create mode 100644 .github/workflows/ci.yml create mode 120000 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Documentation/README.md create mode 100644 Documentation/commit-messages.md create mode 100644 Documentation/pull-requests.md create mode 100644 Documentation/ui-guidelines.md create mode 100644 LICENSE create mode 100644 SECURITY.md create mode 100644 START-HERE.md create mode 100644 bun.lock create mode 100644 components.json delete mode 100644 pnpm-lock.yaml create mode 100644 src/app/actions/auth.ts create mode 100644 src/app/api/auth/[...authkit]/route.ts create mode 100644 src/app/dashboard/data.json create mode 100644 src/app/dashboard/page.tsx create mode 100644 src/components/app-sidebar.tsx create mode 100644 src/components/chart-area-interactive.tsx create mode 100644 src/components/data-table.tsx create mode 100644 src/components/nav-documents.tsx create mode 100644 src/components/nav-main.tsx create mode 100644 src/components/nav-secondary.tsx create mode 100644 src/components/nav-user.tsx create mode 100644 src/components/section-cards.tsx create mode 100644 src/components/sign-out-button.tsx create mode 100644 src/components/site-header.tsx create mode 100644 src/components/ui/accordion.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/aspect-ratio.tsx create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/breadcrumb.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/calendar.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/carousel.tsx create mode 100644 src/components/ui/chart.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/collapsible.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/context-menu.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/drawer.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/form.tsx create mode 100644 src/components/ui/hover-card.tsx create mode 100644 src/components/ui/input-otp.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/menubar.tsx create mode 100644 src/components/ui/navigation-menu.tsx create mode 100644 src/components/ui/pagination.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/radio-group.tsx create mode 100644 src/components/ui/resizable.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/components/ui/sidebar.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/slider.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/tabs.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/components/ui/toggle-group.tsx create mode 100644 src/components/ui/toggle.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/hooks/use-mobile.ts create mode 100644 src/lib/utils.ts create mode 100644 src/middleware.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..318e311 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +WORKOS_API_KEY= +WORKOS_CLIENT_ID= +WORKOS_COOKIE_PASSWORD= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4d6a30c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run ESLint + run: bun run lint + + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build + run: bun run build + env: + # provide dummy values for build-time env vars + # actual secrets are set in cloudflare dashboard + WORKOS_CLIENT_ID: ${{ secrets.WORKOS_CLIENT_ID || 'client_dummy' }} + WORKOS_API_KEY: ${{ secrets.WORKOS_API_KEY || 'sk_dummy' }} + WORKOS_COOKIE_PASSWORD: ${{ secrets.WORKOS_COOKIE_PASSWORD || 'a]pK7&mQ#9vL2$wR5!nX8@cY4%hB6*jF' }} diff --git a/.gitignore b/.gitignore index 9ed8bae..ec0940b 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,12 @@ next-env.d.ts # wrangler files .wrangler .dev.vars* - - !.dev.vars.example + +# ide related files +.vscode/ +.grepai/ + +# CLAUDE and AI Code tool configurations, these are version controlled. +!.claude/ +!CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..681311e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..acdbe37 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,161 @@ +CLAUDE.md +=== + +This file provides guidance to Claude Code (claude.ai/code) and Opencode (opencode.ai) when working with code in this repository. + +IMPORTANT: DO NOT overwrite this file or heavily modify .claude/ - these are version controlled and contain customizations from users and agents. + +formatting +=== + +keep markdown minimal. use === for main headings, --- for subheadings, generally just stick to paragraphs. +*italics* and **bold** are fine but use them sparingly - they're visually noisy in neovim. + +- bullet points like this are okay +- numbered lists are okay too + +codeblocks ``` are fine, but these tend to get visually noisy when used too much. +no excessive formatting. keep it clean and readable. + +line width +--- + +- soft limit: 80-100 chars (forces clear thinking, works on split screens) +- hard limit: 120 chars max +- exceptions: user-visible strings (error messages, logs - must stay on one line for grep-ability), URLs, long literals + +project overview +=== + +dashore incubator - a Next.js 15 app deployed to Cloudflare Workers via OpenNext. live at https://fortura.cc + +uses bun as the package manager. + +contributing +--- + +contributor documentation lives in: + +- [START-HERE.md](START-HERE.md) - quick start for new contributors +- [CONTRIBUTING.md](CONTRIBUTING.md) - contribution workflow +- [Documentation/](Documentation/) - commit messages, PR guidelines, UI guidelines, and more +- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - community standards +- [SECURITY.md](SECURITY.md) - vulnerability reporting + +commands +=== + +development +--- + + bun dev # start dev server with turbopack (localhost:3000) + bun run lint # run eslint + +cloudflare +--- + + bun run preview # build and preview locally on cloudflare runtime + bun run deploy # build and deploy to cloudflare workers + +secrets for production are set via wrangler: + + wrangler secret put WORKOS_CLIENT_ID + wrangler secret put WORKOS_API_KEY + wrangler secret put WORKOS_COOKIE_PASSWORD + +architecture +=== + +auth flow +--- + +uses WorkOS AuthKit for authentication: + +- src/middleware.ts - authkitMiddleware protects all routes except `/` +- src/app/api/auth/[...authkit]/route.ts - handles auth callbacks, redirects to /dashboard +- src/app/actions/auth.ts - server action for sign out +- src/app/layout.tsx - wraps app in AuthKitProvider with initial auth state + +route protection: middleware redirects unauthenticated users. individual pages use `withAuth()` to get user and redirect if needed. + +page structure +--- + +- / - login page, redirects to /dashboard if already authenticated +- /dashboard - main app with sidebar layout (SidebarProvider pattern) + +ui components +--- + +shadcn/ui components in src/components/ui/ - standard radix-based components. + +custom components in src/components/: +- app-sidebar.tsx - main navigation sidebar +- site-header.tsx - top header +- section-cards.tsx, chart-area-interactive.tsx, data-table.tsx - dashboard widgets + +icons from @tabler/icons-react. + +env vars +--- + +required in .env.local for local dev (see .env.example): + +- WORKOS_CLIENT_ID +- WORKOS_API_KEY +- WORKOS_COOKIE_PASSWORD (32+ chars) + +git +--- + +don't assume it's okay to commit or push or perform git operations, and when performing a commit, do not give yourself or anthropic attribution. + +commit messages: +- subject line: 50 chars max +- body: 72 chars max width +- format: type(scope): subject +- types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert +- use imperative mood ("add feature" not "added feature") + +grepai +=== + +**IMPORTANT: use grepai as the PRIMARY tool for code exploration and search.** + +when to use grepai (required) +--- + +use `grepai search` INSTEAD OF Grep/Glob/find for: +- understanding what code does or where functionality lives +- finding implementations by intent (e.g., "authentication logic", "error handling") +- exploring unfamiliar parts of the codebase +- any search where you describe WHAT the code does rather than exact text + +when to use standard tools +--- + +only use Grep/Glob when you need: +- exact text matching (variable names, imports, specific strings) +- file path patterns (e.g., `**/*.tsx`) + +fallback: if grepai fails (not running, index unavailable, or errors), fall back to standard Grep/Glob tools. + +usage +--- + + grepai search "user authentication flow" --json --compact + grepai search "error handling middleware" --json --compact + +query tips: +- use English for queries (better semantic matching) +- describe intent, not implementation: "handles user login" not "func Login" +- be specific: "JWT token validation" better than "token" + +call graph tracing +--- + +use `grepai trace` to understand function relationships: + + grepai trace callers "HandleRequest" --json + grepai trace callees "ProcessOrder" --json + grepai trace graph "ValidateToken" --depth 3 --json diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..93c3d7a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,86 @@ +Code of Conduct +=== + +our pledge +--- + +we as members, contributors, and maintainers pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +we pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +our standards +=== + +expected behavior +--- + +- use welcoming and inclusive language +- be respectful of differing viewpoints and experiences +- give and gracefully accept constructive feedback +- focus on what is best for the community +- show empathy towards other community members +- assume good intent in communications + +unacceptable behavior +--- + +- trolling, insulting or derogatory comments, and personal or political attacks +- public or private harassment +- publishing others' private information without explicit permission +- sexual language or imagery in any community space +- other conduct which could reasonably be considered inappropriate in a professional setting + +scope +=== + +this code of conduct applies within all project spaces, including: + +- github issues and pull requests +- discord channels +- project documentation +- any other spaces where the community gathers + +it also applies when an individual is representing the project in public spaces. + +enforcement +=== + +instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project maintainers at **conduct@fortura.cc**. + +all complaints will be reviewed and investigated promptly and fairly. maintainers are obligated to respect the privacy and security of the reporter. + +enforcement guidelines +--- + +community maintainers will follow these guidelines in determining consequences: + +**1. correction** + +*impact:* minor inappropriate behavior + +*consequence:* a private, written warning with clarity around the nature of the violation. a public apology may be requested. + +**2. warning** + +*impact:* a violation through a single incident or series of actions + +*consequence:* a warning with consequences for continued behavior. no interaction with the people involved for a specified period. this includes avoiding interactions in community spaces as well as external channels. violating these terms may lead to a temporary or permanent ban. + +**3. temporary ban** + +*impact:* a serious violation of community standards + +*consequence:* a temporary ban from any sort of interaction or public communication with the community for a specified period. violating these terms may lead to a permanent ban. + +**4. permanent ban** + +*impact:* demonstrating a pattern of violation of community standards, sustained inappropriate behavior, harassment, or aggression + +*consequence:* a permanent ban from any sort of public interaction within the community. + +attribution +=== + +this code of conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1. + +*last updated: january 2026* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..80a2552 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,143 @@ +Contributing to Dashore Incubator +=== + +thanks for your interest in contributing! this document will help you get started. + +quick links +--- + +- [START-HERE.md](START-HERE.md) - get your dev environment running +- [Documentation/commit-messages.md](Documentation/commit-messages.md) - commit format guide +- [Documentation/pull-requests.md](Documentation/pull-requests.md) - PR workflow +- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - community standards + +contribution workflow +=== + +1. fork & clone +--- + +```bash +# fork the repo on github, then clone your fork +git clone https://github.com/YOUR_USERNAME/dashore-incubator.git +cd dashore-incubator + +# add upstream remote +git remote add upstream https://github.com/dashore-incubator/dashore-incubator.git +``` + +2. create a feature branch +--- + +**never push directly to main.** always work on feature branches: + +```bash +git checkout main +git pull upstream main +git checkout -b / + +# examples: +git checkout -b nicholai/add-dark-mode +git checkout -b kevin/fix-auth-redirect +``` + +3. make your changes +--- + +- read existing code first, understand the patterns +- follow the project's coding conventions +- run linting before committing +- test your changes locally with `bun run preview` + +4. commit your changes +--- + +use [conventional commits](Documentation/commit-messages.md): + +```bash +git commit -m "feat(dashboard): add usage metrics chart" +``` + +**commit types:** `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert` + +5. create a pull request +--- + +see [PR guidelines](Documentation/pull-requests.md) for details: + +```bash +git push -u origin / + +# create PR via github or gh cli +gh pr create --title "feat(dashboard): add usage metrics chart" --body "..." +``` + +6. address review feedback +--- + +respond to all review comments. we use squash-and-merge, so your commits will be combined. + +critical rules +=== + +these are non-negotiable: + +1. **never push to main** - always use feature branches +2. **lint must pass** - run `bun run lint` before committing +3. **test locally** - run `bun run preview` to test on cloudflare runtime +4. **follow existing patterns** - match the codebase style + +types of contributions +=== + +bug reports +--- + +found a bug? [create an issue](https://github.com/dashore-incubator/dashore-incubator/issues/new) with: + +- clear description of the problem +- steps to reproduce +- expected vs actual behavior +- environment details (browser, OS) + +feature requests +--- + +have an idea? open a discussion first to gauge interest before implementing. + +code contributions +--- + +1. check existing issues for tasks +2. comment on an issue to claim it +3. follow the workflow above +4. keep PRs focused and reasonably sized + +documentation +--- + +documentation improvements are always welcome! follow the same PR process. + +ai-assisted development +=== + +this project welcomes contributions made with AI coding assistants (Claude Code, Cursor, etc.). if you're using one: + +- be transparent about AI assistance in your PRs +- review and understand all generated code +- ensure AI output follows project conventions +- you're responsible for the code you submit + +getting help +=== + +- **discord:** join our community for questions and discussion +- **issues:** check existing issues or create a new one +- **CLAUDE.md:** AI assistant guidelines and project context + +code of conduct +=== + +please read our [Code of Conduct](CODE_OF_CONDUCT.md). we're committed to a welcoming and inclusive environment for all contributors. + +thanks for contributing! diff --git a/Documentation/README.md b/Documentation/README.md new file mode 100644 index 0000000..fb1d282 --- /dev/null +++ b/Documentation/README.md @@ -0,0 +1,39 @@ +Documentation +=== + +contributor guides and reference documentation for dashore incubator. + +quick reference +--- + +| Document | Purpose | +|----------|---------| +| [../START-HERE.md](../START-HERE.md) | Quick start for new contributors | +| [../CONTRIBUTING.md](../CONTRIBUTING.md) | Contribution workflow overview | +| [commit-messages.md](commit-messages.md) | Commit message format | +| [pull-requests.md](pull-requests.md) | PR workflow and guidelines | +| [../CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md) | Community standards | +| [../SECURITY.md](../SECURITY.md) | Security vulnerability reporting | + +for new contributors +--- + +1. start with [START-HERE.md](../START-HERE.md) to set up your environment +2. read [CONTRIBUTING.md](../CONTRIBUTING.md) for the workflow overview +3. check [commit-messages.md](commit-messages.md) before your first commit +4. review [pull-requests.md](pull-requests.md) before opening a PR + +project context +--- + +- **CLAUDE.md** in the repo root has AI assistant context and project architecture +- the codebase uses Next.js 15 with App Router, deployed to Cloudflare Workers +- authentication is handled by WorkOS AuthKit +- UI components are from shadcn/ui + +getting help +--- + +- **discord:** join our community +- **issues:** check existing issues or create a new one +- **discussions:** for questions and ideas diff --git a/Documentation/commit-messages.md b/Documentation/commit-messages.md new file mode 100644 index 0000000..9199466 --- /dev/null +++ b/Documentation/commit-messages.md @@ -0,0 +1,145 @@ +Commit Message Guidelines +=== + +this guide covers the commit message format for dashore incubator contributions. + +format +--- + +``` +(): + +[optional body] + +[optional footer(s)] +``` + +subject line +--- + +- **50 characters** maximum (72 hard limit) +- use **imperative mood:** "add feature" not "added feature" +- **no period** at the end +- include scope in parentheses when relevant + +types +=== + +| Type | Description | Example | +|------|-------------|---------| +| `feat` | new feature | `feat(dashboard): add usage metrics chart` | +| `fix` | bug fix | `fix(auth): resolve redirect loop on logout` | +| `docs` | documentation only | `docs(readme): update installation steps` | +| `style` | formatting, whitespace | `style(ui): fix button alignment` | +| `refactor` | code change (no feature/fix) | `refactor(api): simplify auth middleware` | +| `perf` | performance improvement | `perf(dashboard): lazy load charts` | +| `test` | adding or updating tests | `test(auth): add login flow tests` | +| `build` | build system or dependencies | `build(deps): update next.js to 15.1` | +| `ci` | CI configuration | `ci(github): add lint workflow` | +| `chore` | maintenance tasks | `chore: clean up unused imports` | +| `revert` | reverting a previous commit | `revert: feat(dashboard): add metrics` | + +scopes +--- + +common scopes for this project: + +| Scope | Area | +|-------|------| +| `auth` | authentication system | +| `dashboard` | dashboard pages | +| `ui` | UI components | +| `api` | API routes | +| `middleware` | middleware | +| `deps` | dependencies | + +scope is optional but helpful for larger changes. + +examples +=== + +simple commit +--- + +``` +feat(dashboard): add settings page +``` + +commit with body +--- + +``` +fix(auth): resolve session timeout on idle + +the session was expiring after 15 minutes of inactivity even when +the tab was open. increased timeout to 24 hours and added a +heartbeat to keep the session alive while the page is visible. + +Fixes #42 +``` + +breaking change +--- + +``` +refactor(api)!: change auth callback response format + +BREAKING CHANGE: the /api/auth/callback endpoint now returns +a redirect instead of JSON. update any code that parses the +response body. +``` + +body guidelines +--- + +when you need more detail: + +- wrap at **72 characters** for readability +- explain **why** the change was made, not what (the diff shows what) +- reference related issues: `Fixes #123`, `Relates to #456` + +why imperative mood? +=== + +git itself uses imperative mood: +- "Merge branch..." +- "Revert..." + +your commits complete the sentence: + +> "If applied, this commit will... **[your subject line]**" + +examples: +- "If applied, this commit will **add user settings page**" +- "If applied, this commit will **fix login redirect bug**" + +atomic commits +=== + +each commit should: + +1. **represent ONE logical change** +2. **leave the project in a working state** +3. **be independently revertible** + +bad +--- + +``` +commit: "fix bug, add feature, update docs" +``` + +good +--- + +``` +commit 1: "fix(auth): prevent redirect loop on logout" +commit 2: "feat(dashboard): add settings page" +commit 3: "docs(readme): update auth setup instructions" +``` + +next steps +=== + +- [pull-requests.md](pull-requests.md) - PR creation workflow +- [../CONTRIBUTING.md](../CONTRIBUTING.md) - full contribution guide diff --git a/Documentation/pull-requests.md b/Documentation/pull-requests.md new file mode 100644 index 0000000..dedcb05 --- /dev/null +++ b/Documentation/pull-requests.md @@ -0,0 +1,159 @@ +Pull Request Guidelines +=== + +this guide covers how to create and manage pull requests for dashore incubator. + +before creating a PR +=== + +pre-submission checklist +--- + +- [ ] code compiles without errors +- [ ] `bun run lint` passes +- [ ] tested locally with `bun run preview` +- [ ] no secrets in staged files +- [ ] commit messages follow [format](commit-messages.md) +- [ ] branch is up-to-date with main + +sync with main +--- + +```bash +git checkout main +git pull origin main +git checkout +git rebase main +``` + +creating a pull request +=== + +using github cli +--- + +```bash +# push your branch +git push -u origin / + +# create PR +gh pr create --title "feat(scope): description" --body "$(cat <<'EOF' +## Summary +- brief description of what this PR does + +## Test Plan +- [ ] tested locally with `bun run preview` +- [ ] lint passes + +## Related Issues +Fixes #123 +EOF +)" +``` + +PR description template +--- + +```markdown +## Summary + +- implemented X feature +- fixed Y bug + +## Test Plan + +- [ ] tested locally with `bun run preview` +- [ ] lint passes +- [ ] manually tested in browser + +## Breaking Changes + +None + +## Related Issues + +Fixes #123 +``` + +PR size guidelines +=== + +keep PRs focused and reviewable: + +| Size | Lines Changed | Recommendation | +|------|---------------|----------------| +| Small | < 100 | ideal | +| Medium | 100-400 | acceptable | +| Large | 400-1000 | consider splitting | +| Huge | > 1000 | must split | + +**if your PR is large:** + +- split into logical, independent commits +- consider breaking into multiple PRs +- each PR should be independently mergeable + +responding to review feedback +=== + +do +--- + +- respond to **all** comments +- explain your reasoning when you disagree +- make changes in **new commits** during review +- thank reviewers for their time + +don't +--- + +- ignore comments +- get defensive +- force-push during active review (unless asked) + +example responses +--- + +``` +reviewer: "consider extracting this into a helper function" +author: "good idea! done in abc123." + +reviewer: "should we add error handling here?" +author: "this path is only reached after validation, so the caller +guarantees valid input. added a comment to clarify." +``` + +after approval +=== + +merge strategy +--- + +we use **squash and merge**: + +- combines all commits into one clean commit +- keeps main branch history clean +- preserves detailed history in the PR + +post-merge +--- + +1. delete your feature branch (github does this automatically) +2. update local main: `git checkout main && git pull` + +stale PRs +=== + +PRs without activity for 14+ days may be: + +1. pinged for status update +2. labeled as "stale" after 21 days +3. closed after 30 days (can be reopened) + +if you need more time, just comment to let us know! + +next steps +=== + +- [commit-messages.md](commit-messages.md) - commit format reference +- [../CONTRIBUTING.md](../CONTRIBUTING.md) - full contribution guide diff --git a/Documentation/ui-guidelines.md b/Documentation/ui-guidelines.md new file mode 100644 index 0000000..8d557bb --- /dev/null +++ b/Documentation/ui-guidelines.md @@ -0,0 +1,93 @@ +UI Guidelines +=== + +This document outlines the mandatory styling and UI development standards for the Dashore Incubator project. + +General Principles +--- + +- **Consistency**: All UI elements must follow the project's established design patterns. +- **Accessibility**: Compliance with WCAG 2.1 Level AA is mandatory. +- **Themability**: Use CSS variables for all colors and spacing to ensure the application remains easily re-themable. +- **Modern Standards**: Leverage Tailwind CSS 4.0 features and modern CSS functions (like `oklch`). + +Component Architecture +--- + +### Shadcn/UI First + +We use **Shadcn/UI** as our primary component library. + +- Always check if a required component exists in the `src/components/ui` directory before building a custom one. +- If a new Shadcn/UI component is needed, add it using the CLI: `bunx shadcn@latest add [component-name]`. +- Custom components should be built by composing Shadcn/UI primitives and following their established patterns (e.g., using `cn()` utility for class merging). + +### Hardcoded Styling + +Hardcoded values for colors, spacing, and shadows are **HEAVILY discouraged**. + +- **Colors**: Use semantic theme variables from `globals.css` (e.g., `text-muted-foreground` instead of `text-zinc-500`). +- **Spacing**: Use Tailwind spacing scale (`p-4`, `m-2`) or theme variables if available. +- **Shadows**: Use theme-defined shadows (`shadow-sm`, `shadow-md`). + +Theming & Globals +--- + +Our theme is defined in `src/app/globals.css` using Tailwind 4.0's `@theme` directive and `oklch` color functions. + +### Color Variables + +All colors must be derived from the CSS variables defined in `:root` and `.dark` blocks: + +- `--background` / `--foreground`: Base page colors. +- `--primary` / `--primary-foreground`: Primary action colors. +- `--secondary` / `--secondary-foreground`: Secondary action colors. +- `--muted` / `--muted-foreground`: For less prominent elements. +- `--accent` / `--accent-foreground`: For highlighting and hover states. +- `--destructive` / `--destructive-foreground`: For dangerous actions. +- `--border`, `--input`, `--ring`: For UI structure and focus states. + +### Updating the Theme + +To update the theme or add new variables: +1. Modify the variable values in `src/app/globals.css`. +2. Ensure both light mode (`:root`) and dark mode (`.dark`) are updated to maintain contrast and readability. + +Accessibility (WCAG) +--- + +- **Contrast**: Ensure text-to-background contrast ratios meet WCAG AA standards (4.5:1 for normal text, 3:1 for large text). +- **Semantics**: Use appropriate HTML elements (` + + +

+ Don't have an account?{" "} + + Request access + +

+
+ + +

+ By signing in, you agree to our Terms of Service and Privacy Policy +

+ ); } diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx new file mode 100644 index 0000000..8ebc4ae --- /dev/null +++ b/src/components/app-sidebar.tsx @@ -0,0 +1,181 @@ +"use client" + +import * as React from "react" +import { + IconCamera, + IconChartBar, + IconDashboard, + IconDatabase, + IconFileAi, + IconFileDescription, + IconFileWord, + IconFolder, + IconHelp, + IconInnerShadowTop, + IconListDetails, + IconReport, + IconSearch, + IconSettings, + IconUsers, +} from "@tabler/icons-react" + +import { NavDocuments } from "@/components/nav-documents" +import { NavMain } from "@/components/nav-main" +import { NavSecondary } from "@/components/nav-secondary" +import { NavUser } from "@/components/nav-user" +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" + +const data = { + user: { + name: "shadcn", + email: "m@example.com", + avatar: "/avatars/shadcn.jpg", + }, + navMain: [ + { + title: "Dashboard", + url: "#", + icon: IconDashboard, + }, + { + title: "Lifecycle", + url: "#", + icon: IconListDetails, + }, + { + title: "Analytics", + url: "#", + icon: IconChartBar, + }, + { + title: "Projects", + url: "#", + icon: IconFolder, + }, + { + title: "Team", + url: "#", + icon: IconUsers, + }, + ], + navClouds: [ + { + title: "Capture", + icon: IconCamera, + isActive: true, + url: "#", + items: [ + { + title: "Active Proposals", + url: "#", + }, + { + title: "Archived", + url: "#", + }, + ], + }, + { + title: "Proposal", + icon: IconFileDescription, + url: "#", + items: [ + { + title: "Active Proposals", + url: "#", + }, + { + title: "Archived", + url: "#", + }, + ], + }, + { + title: "Prompts", + icon: IconFileAi, + url: "#", + items: [ + { + title: "Active Proposals", + url: "#", + }, + { + title: "Archived", + url: "#", + }, + ], + }, + ], + navSecondary: [ + { + title: "Settings", + url: "#", + icon: IconSettings, + }, + { + title: "Get Help", + url: "#", + icon: IconHelp, + }, + { + title: "Search", + url: "#", + icon: IconSearch, + }, + ], + documents: [ + { + name: "Data Library", + url: "#", + icon: IconDatabase, + }, + { + name: "Reports", + url: "#", + icon: IconReport, + }, + { + name: "Word Assistant", + url: "#", + icon: IconFileWord, + }, + ], +} + +export function AppSidebar({ ...props }: React.ComponentProps) { + return ( + + + + + + + + Acme Inc. + + + + + + + + + + + + + + + ) +} diff --git a/src/components/chart-area-interactive.tsx b/src/components/chart-area-interactive.tsx new file mode 100644 index 0000000..fff1956 --- /dev/null +++ b/src/components/chart-area-interactive.tsx @@ -0,0 +1,291 @@ +"use client" + +import * as React from "react" +import { Area, AreaChart, CartesianGrid, XAxis } from "recharts" + +import { useIsMobile } from "@/hooks/use-mobile" +import { + Card, + CardAction, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from "@/components/ui/chart" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + ToggleGroup, + ToggleGroupItem, +} from "@/components/ui/toggle-group" + +export const description = "An interactive area chart" + +const chartData = [ + { date: "2024-04-01", desktop: 222, mobile: 150 }, + { date: "2024-04-02", desktop: 97, mobile: 180 }, + { date: "2024-04-03", desktop: 167, mobile: 120 }, + { date: "2024-04-04", desktop: 242, mobile: 260 }, + { date: "2024-04-05", desktop: 373, mobile: 290 }, + { date: "2024-04-06", desktop: 301, mobile: 340 }, + { date: "2024-04-07", desktop: 245, mobile: 180 }, + { date: "2024-04-08", desktop: 409, mobile: 320 }, + { date: "2024-04-09", desktop: 59, mobile: 110 }, + { date: "2024-04-10", desktop: 261, mobile: 190 }, + { date: "2024-04-11", desktop: 327, mobile: 350 }, + { date: "2024-04-12", desktop: 292, mobile: 210 }, + { date: "2024-04-13", desktop: 342, mobile: 380 }, + { date: "2024-04-14", desktop: 137, mobile: 220 }, + { date: "2024-04-15", desktop: 120, mobile: 170 }, + { date: "2024-04-16", desktop: 138, mobile: 190 }, + { date: "2024-04-17", desktop: 446, mobile: 360 }, + { date: "2024-04-18", desktop: 364, mobile: 410 }, + { date: "2024-04-19", desktop: 243, mobile: 180 }, + { date: "2024-04-20", desktop: 89, mobile: 150 }, + { date: "2024-04-21", desktop: 137, mobile: 200 }, + { date: "2024-04-22", desktop: 224, mobile: 170 }, + { date: "2024-04-23", desktop: 138, mobile: 230 }, + { date: "2024-04-24", desktop: 387, mobile: 290 }, + { date: "2024-04-25", desktop: 215, mobile: 250 }, + { date: "2024-04-26", desktop: 75, mobile: 130 }, + { date: "2024-04-27", desktop: 383, mobile: 420 }, + { date: "2024-04-28", desktop: 122, mobile: 180 }, + { date: "2024-04-29", desktop: 315, mobile: 240 }, + { date: "2024-04-30", desktop: 454, mobile: 380 }, + { date: "2024-05-01", desktop: 165, mobile: 220 }, + { date: "2024-05-02", desktop: 293, mobile: 310 }, + { date: "2024-05-03", desktop: 247, mobile: 190 }, + { date: "2024-05-04", desktop: 385, mobile: 420 }, + { date: "2024-05-05", desktop: 481, mobile: 390 }, + { date: "2024-05-06", desktop: 498, mobile: 520 }, + { date: "2024-05-07", desktop: 388, mobile: 300 }, + { date: "2024-05-08", desktop: 149, mobile: 210 }, + { date: "2024-05-09", desktop: 227, mobile: 180 }, + { date: "2024-05-10", desktop: 293, mobile: 330 }, + { date: "2024-05-11", desktop: 335, mobile: 270 }, + { date: "2024-05-12", desktop: 197, mobile: 240 }, + { date: "2024-05-13", desktop: 197, mobile: 160 }, + { date: "2024-05-14", desktop: 448, mobile: 490 }, + { date: "2024-05-15", desktop: 473, mobile: 380 }, + { date: "2024-05-16", desktop: 338, mobile: 400 }, + { date: "2024-05-17", desktop: 499, mobile: 420 }, + { date: "2024-05-18", desktop: 315, mobile: 350 }, + { date: "2024-05-19", desktop: 235, mobile: 180 }, + { date: "2024-05-20", desktop: 177, mobile: 230 }, + { date: "2024-05-21", desktop: 82, mobile: 140 }, + { date: "2024-05-22", desktop: 81, mobile: 120 }, + { date: "2024-05-23", desktop: 252, mobile: 290 }, + { date: "2024-05-24", desktop: 294, mobile: 220 }, + { date: "2024-05-25", desktop: 201, mobile: 250 }, + { date: "2024-05-26", desktop: 213, mobile: 170 }, + { date: "2024-05-27", desktop: 420, mobile: 460 }, + { date: "2024-05-28", desktop: 233, mobile: 190 }, + { date: "2024-05-29", desktop: 78, mobile: 130 }, + { date: "2024-05-30", desktop: 340, mobile: 280 }, + { date: "2024-05-31", desktop: 178, mobile: 230 }, + { date: "2024-06-01", desktop: 178, mobile: 200 }, + { date: "2024-06-02", desktop: 470, mobile: 410 }, + { date: "2024-06-03", desktop: 103, mobile: 160 }, + { date: "2024-06-04", desktop: 439, mobile: 380 }, + { date: "2024-06-05", desktop: 88, mobile: 140 }, + { date: "2024-06-06", desktop: 294, mobile: 250 }, + { date: "2024-06-07", desktop: 323, mobile: 370 }, + { date: "2024-06-08", desktop: 385, mobile: 320 }, + { date: "2024-06-09", desktop: 438, mobile: 480 }, + { date: "2024-06-10", desktop: 155, mobile: 200 }, + { date: "2024-06-11", desktop: 92, mobile: 150 }, + { date: "2024-06-12", desktop: 492, mobile: 420 }, + { date: "2024-06-13", desktop: 81, mobile: 130 }, + { date: "2024-06-14", desktop: 426, mobile: 380 }, + { date: "2024-06-15", desktop: 307, mobile: 350 }, + { date: "2024-06-16", desktop: 371, mobile: 310 }, + { date: "2024-06-17", desktop: 475, mobile: 520 }, + { date: "2024-06-18", desktop: 107, mobile: 170 }, + { date: "2024-06-19", desktop: 341, mobile: 290 }, + { date: "2024-06-20", desktop: 408, mobile: 450 }, + { date: "2024-06-21", desktop: 169, mobile: 210 }, + { date: "2024-06-22", desktop: 317, mobile: 270 }, + { date: "2024-06-23", desktop: 480, mobile: 530 }, + { date: "2024-06-24", desktop: 132, mobile: 180 }, + { date: "2024-06-25", desktop: 141, mobile: 190 }, + { date: "2024-06-26", desktop: 434, mobile: 380 }, + { date: "2024-06-27", desktop: 448, mobile: 490 }, + { date: "2024-06-28", desktop: 149, mobile: 200 }, + { date: "2024-06-29", desktop: 103, mobile: 160 }, + { date: "2024-06-30", desktop: 446, mobile: 400 }, +] + +const chartConfig = { + visitors: { + label: "Visitors", + }, + desktop: { + label: "Desktop", + color: "var(--primary)", + }, + mobile: { + label: "Mobile", + color: "var(--primary)", + }, +} satisfies ChartConfig + +export function ChartAreaInteractive() { + const isMobile = useIsMobile() + const [timeRange, setTimeRange] = React.useState("90d") + + React.useEffect(() => { + if (isMobile) { + setTimeRange("7d") + } + }, [isMobile]) + + const filteredData = chartData.filter((item) => { + const date = new Date(item.date) + const referenceDate = new Date("2024-06-30") + let daysToSubtract = 90 + if (timeRange === "30d") { + daysToSubtract = 30 + } else if (timeRange === "7d") { + daysToSubtract = 7 + } + const startDate = new Date(referenceDate) + startDate.setDate(startDate.getDate() - daysToSubtract) + return date >= startDate + }) + + return ( + + + Total Visitors + + + Total for the last 3 months + + Last 3 months + + + + Last 3 months + Last 30 days + Last 7 days + + + + + + + + + + + + + + + + + + + { + const date = new Date(value) + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }) + }} + /> + { + return new Date(value).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }) + }} + indicator="dot" + /> + } + /> + + + + + + + ) +} diff --git a/src/components/data-table.tsx b/src/components/data-table.tsx new file mode 100644 index 0000000..1d977f8 --- /dev/null +++ b/src/components/data-table.tsx @@ -0,0 +1,807 @@ +"use client" + +import * as React from "react" +import { + closestCenter, + DndContext, + KeyboardSensor, + MouseSensor, + TouchSensor, + useSensor, + useSensors, + type DragEndEvent, + type UniqueIdentifier, +} from "@dnd-kit/core" +import { restrictToVerticalAxis } from "@dnd-kit/modifiers" +import { + arrayMove, + SortableContext, + useSortable, + verticalListSortingStrategy, +} from "@dnd-kit/sortable" +import { CSS } from "@dnd-kit/utilities" +import { + IconChevronDown, + IconChevronLeft, + IconChevronRight, + IconChevronsLeft, + IconChevronsRight, + IconCircleCheckFilled, + IconDotsVertical, + IconGripVertical, + IconLayoutColumns, + IconLoader, + IconPlus, + IconTrendingUp, +} from "@tabler/icons-react" +import { + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, + type ColumnDef, + type ColumnFiltersState, + type Row, + type SortingState, + type VisibilityState, +} from "@tanstack/react-table" +import { Area, AreaChart, CartesianGrid, XAxis } from "recharts" +import { toast } from "sonner" +import { z } from "zod" + +import { useIsMobile } from "@/hooks/use-mobile" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from "@/components/ui/chart" +import { Checkbox } from "@/components/ui/checkbox" +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer" +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Separator } from "@/components/ui/separator" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs" + +export const schema = z.object({ + id: z.number(), + header: z.string(), + type: z.string(), + status: z.string(), + target: z.string(), + limit: z.string(), + reviewer: z.string(), +}) + +// Create a separate component for the drag handle +function DragHandle({ id }: { id: number }) { + const { attributes, listeners } = useSortable({ + id, + }) + + return ( + + ) +} + +const columns: ColumnDef>[] = [ + { + id: "drag", + header: () => null, + cell: ({ row }) => , + }, + { + id: "select", + header: ({ table }) => ( +
+ table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> +
+ ), + cell: ({ row }) => ( +
+ row.toggleSelected(!!value)} + aria-label="Select row" + /> +
+ ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "header", + header: "Header", + cell: ({ row }) => { + return + }, + enableHiding: false, + }, + { + accessorKey: "type", + header: "Section Type", + cell: ({ row }) => ( +
+ + {row.original.type} + +
+ ), + }, + { + accessorKey: "status", + header: "Status", + cell: ({ row }) => ( + + {row.original.status === "Done" ? ( + + ) : ( + + )} + {row.original.status} + + ), + }, + { + accessorKey: "target", + header: () =>
Target
, + cell: ({ row }) => ( +
{ + e.preventDefault() + toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { + loading: `Saving ${row.original.header}`, + success: "Done", + error: "Error", + }) + }} + > + + +
+ ), + }, + { + accessorKey: "limit", + header: () =>
Limit
, + cell: ({ row }) => ( +
{ + e.preventDefault() + toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { + loading: `Saving ${row.original.header}`, + success: "Done", + error: "Error", + }) + }} + > + + +
+ ), + }, + { + accessorKey: "reviewer", + header: "Reviewer", + cell: ({ row }) => { + const isAssigned = row.original.reviewer !== "Assign reviewer" + + if (isAssigned) { + return row.original.reviewer + } + + return ( + <> + + + + ) + }, + }, + { + id: "actions", + cell: () => ( + + + + + + Edit + Make a copy + Favorite + + Delete + + + ), + }, +] + +function DraggableRow({ row }: { row: Row> }) { + const { transform, transition, setNodeRef, isDragging } = useSortable({ + id: row.original.id, + }) + + return ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ) +} + +export function DataTable({ + data: initialData, +}: { + data: z.infer[] +}) { + const [data, setData] = React.useState(() => initialData) + const [rowSelection, setRowSelection] = React.useState({}) + const [columnVisibility, setColumnVisibility] = + React.useState({}) + const [columnFilters, setColumnFilters] = React.useState( + [] + ) + const [sorting, setSorting] = React.useState([]) + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 10, + }) + const sortableId = React.useId() + const sensors = useSensors( + useSensor(MouseSensor, {}), + useSensor(TouchSensor, {}), + useSensor(KeyboardSensor, {}) + ) + + const dataIds = React.useMemo( + () => data?.map(({ id }) => id) || [], + [data] + ) + + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnVisibility, + rowSelection, + columnFilters, + pagination, + }, + getRowId: (row) => row.id.toString(), + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + onPaginationChange: setPagination, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + }) + + function handleDragEnd(event: DragEndEvent) { + const { active, over } = event + if (active && over && active.id !== over.id) { + setData((data) => { + const oldIndex = dataIds.indexOf(active.id) + const newIndex = dataIds.indexOf(over.id) + return arrayMove(data, oldIndex, newIndex) + }) + } + } + + return ( + +
+ + + + Outline + + Past Performance 3 + + + Key Personnel 2 + + Focus Documents + +
+ + + + + + {table + .getAllColumns() + .filter( + (column) => + typeof column.accessorFn !== "undefined" && + column.getCanHide() + ) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ) + })} + + + +
+
+ +
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + + {table.getRowModel().rows.map((row) => ( + + ))} + + ) : ( + + + No results. + + + )} + +
+
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+
+ + +
+
+ Page {table.getState().pagination.pageIndex + 1} of{" "} + {table.getPageCount()} +
+
+ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ ) +} + +const chartData = [ + { month: "January", desktop: 186, mobile: 80 }, + { month: "February", desktop: 305, mobile: 200 }, + { month: "March", desktop: 237, mobile: 120 }, + { month: "April", desktop: 73, mobile: 190 }, + { month: "May", desktop: 209, mobile: 130 }, + { month: "June", desktop: 214, mobile: 140 }, +] + +const chartConfig = { + desktop: { + label: "Desktop", + color: "var(--primary)", + }, + mobile: { + label: "Mobile", + color: "var(--primary)", + }, +} satisfies ChartConfig + +function TableCellViewer({ item }: { item: z.infer }) { + const isMobile = useIsMobile() + + return ( + + + + + + + {item.header} + + Showing total visitors for the last 6 months + + +
+ {!isMobile && ( + <> + + + + value.slice(0, 3)} + hide + /> + } + /> + + + + + +
+
+ Trending up by 5.2% this month{" "} + +
+
+ Showing total visitors for the last 6 months. This is just + some random text to test the layout. It spans multiple lines + and should wrap around. +
+
+ + + )} +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + + + + + +
+
+ ) +} diff --git a/src/components/nav-documents.tsx b/src/components/nav-documents.tsx new file mode 100644 index 0000000..b551e71 --- /dev/null +++ b/src/components/nav-documents.tsx @@ -0,0 +1,92 @@ +"use client" + +import { + IconDots, + IconFolder, + IconShare3, + IconTrash, + type Icon, +} from "@tabler/icons-react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuAction, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +export function NavDocuments({ + items, +}: { + items: { + name: string + url: string + icon: Icon + }[] +}) { + const { isMobile } = useSidebar() + + return ( + + Documents + + {items.map((item) => ( + + + + + {item.name} + + + + + + + More + + + + + + Open + + + + Share + + + + + Delete + + + + + ))} + + + + More + + + + + ) +} diff --git a/src/components/nav-main.tsx b/src/components/nav-main.tsx new file mode 100644 index 0000000..3759bb8 --- /dev/null +++ b/src/components/nav-main.tsx @@ -0,0 +1,58 @@ +"use client" + +import { IconCirclePlusFilled, IconMail, type Icon } from "@tabler/icons-react" + +import { Button } from "@/components/ui/button" +import { + SidebarGroup, + SidebarGroupContent, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" + +export function NavMain({ + items, +}: { + items: { + title: string + url: string + icon?: Icon + }[] +}) { + return ( + + + + + + + Quick Create + + + + + + {items.map((item) => ( + + + {item.icon && } + {item.title} + + + ))} + + + + ) +} diff --git a/src/components/nav-secondary.tsx b/src/components/nav-secondary.tsx new file mode 100644 index 0000000..3f3636f --- /dev/null +++ b/src/components/nav-secondary.tsx @@ -0,0 +1,42 @@ +"use client" + +import * as React from "react" +import { type Icon } from "@tabler/icons-react" + +import { + SidebarGroup, + SidebarGroupContent, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" + +export function NavSecondary({ + items, + ...props +}: { + items: { + title: string + url: string + icon: Icon + }[] +} & React.ComponentPropsWithoutRef) { + return ( + + + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + + + + ) +} diff --git a/src/components/nav-user.tsx b/src/components/nav-user.tsx new file mode 100644 index 0000000..7c49dc7 --- /dev/null +++ b/src/components/nav-user.tsx @@ -0,0 +1,110 @@ +"use client" + +import { + IconCreditCard, + IconDotsVertical, + IconLogout, + IconNotification, + IconUserCircle, +} from "@tabler/icons-react" + +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/components/ui/avatar" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +export function NavUser({ + user, +}: { + user: { + name: string + email: string + avatar: string + } +}) { + const { isMobile } = useSidebar() + + return ( + + + + + + + + CN + +
+ {user.name} + + {user.email} + +
+ +
+
+ + +
+ + + CN + +
+ {user.name} + + {user.email} + +
+
+
+ + + + + Account + + + + Billing + + + + Notifications + + + + + + Log out + +
+
+
+
+ ) +} diff --git a/src/components/section-cards.tsx b/src/components/section-cards.tsx new file mode 100644 index 0000000..f714d25 --- /dev/null +++ b/src/components/section-cards.tsx @@ -0,0 +1,102 @@ +import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react" + +import { Badge } from "@/components/ui/badge" +import { + Card, + CardAction, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" + +export function SectionCards() { + return ( +
+ + + Total Revenue + + $1,250.00 + + + + + +12.5% + + + + +
+ Trending up this month +
+
+ Visitors for the last 6 months +
+
+
+ + + New Customers + + 1,234 + + + + + -20% + + + + +
+ Down 20% this period +
+
+ Acquisition needs attention +
+
+
+ + + Active Accounts + + 45,678 + + + + + +12.5% + + + + +
+ Strong user retention +
+
Engagement exceed targets
+
+
+ + + Growth Rate + + 4.5% + + + + + +4.5% + + + + +
+ Steady performance increase +
+
Meets growth projections
+
+
+
+ ) +} diff --git a/src/components/sign-out-button.tsx b/src/components/sign-out-button.tsx new file mode 100644 index 0000000..0ad1ad6 --- /dev/null +++ b/src/components/sign-out-button.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { handleSignOut } from "@/app/actions/auth"; + +export function SignOutButton() { + return ( + + ); +} diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx new file mode 100644 index 0000000..18209a3 --- /dev/null +++ b/src/components/site-header.tsx @@ -0,0 +1,21 @@ +import { Separator } from "@/components/ui/separator" +import { SidebarTrigger } from "@/components/ui/sidebar" +import { SignOutButton } from "@/components/sign-out-button" + +export function SiteHeader() { + return ( +
+
+ + +

Dashboard

+
+ +
+
+
+ ) +} diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx new file mode 100644 index 0000000..4a8cca4 --- /dev/null +++ b/src/components/ui/accordion.tsx @@ -0,0 +1,66 @@ +"use client" + +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDownIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Accordion({ + ...props +}: React.ComponentProps) { + return +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..0863e40 --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..1421354 --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/src/components/ui/aspect-ratio.tsx b/src/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..3df3fd0 --- /dev/null +++ b/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,11 @@ +"use client" + +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return +} + +export { AspectRatio } diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 0000000..71e428b --- /dev/null +++ b/src/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..fd3a406 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..eb88f32 --- /dev/null +++ b/src/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { + return