Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Formatting-only commits, ignored by GitHub blame and `git blame` (git config blame.ignoreRevsFile .git-blame-ignore-revs)

# Format MyApp.Client with Oxfmt
f031f77c7dda50e1d54ddd7c6fa7316f6e21b66a
13 changes: 5 additions & 8 deletions .github/workflows/build-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,13 @@ jobs:
echo "client_exists=false" >> $GITHUB_OUTPUT
fi

- name: Setup Node.js
- name: Setup Vite+ and install npm dependencies
if: steps.check_client.outputs.client_exists == 'true'
uses: actions/setup-node@v7
uses: voidzero-dev/setup-vp@v1.22.0
with:
node-version: 24

- name: Install npm dependencies
if: steps.check_client.outputs.client_exists == 'true'
working-directory: ./MyApp.Client
run: npm install
node-version: '24'
working-directory: ./MyApp.Client
cache: true

- name: Build client
if: steps.check_client.outputs.client_exists == 'true'
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,27 @@ jobs:
fi
working-directory: ./MyApp.Tests

client:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v7

- name: Setup Vite+ and install npm dependencies
uses: voidzero-dev/setup-vp@v1.22.0
with:
node-version: '24'
working-directory: ./MyApp.Client
cache: true

- name: check (format, lint, type check)
run: vp check
working-directory: ./MyApp.Client

- name: test
run: vp test run
working-directory: ./MyApp.Client

- name: build
run: npm run build
working-directory: ./MyApp.Client
45 changes: 32 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ A full-stack .NET 10 + React 19 + Next.js template combining ServiceStack backen
### Development

```bash
# Start both .NET and Vite dev servers (from project root)
# Start both .NET and Next.js dev servers (from project root)
dotnet watch

# After making changes to C# DTOs restart .NET before regenerating TypeScript DTOs by running:
# After changing C# DTOs, restart .NET: TypeScript DTOs (dtos.ts) are regenerated on startup.
# Manual regeneration is rarely needed (requires the .NET app to be running):
cd MyApp.Client && npm run dtos
```

### Building

```bash
# Build frontend (TypeScript + Vite)
# Build frontend (Next.js static export)
cd MyApp.Client && npm run build

# Build backend (.NET)
Expand All @@ -31,10 +32,25 @@ dotnet build
cd MyApp && npm run ui:build
```

### Checking (format, lint, type check)

The frontend uses the [Vite+](https://viteplus.dev) toolchain (`vp` CLI) for everything except building: Vitest, Oxlint and Oxfmt.
Next.js still runs the dev server and production build (`next dev` / `next build`).
All tool config (`lint`, `fmt`, `test`) lives in [vite.config.ts](MyApp.Client/vite.config.ts). There is no ESLint, Prettier or `tsc` step.

```bash
cd MyApp.Client && npm run check # Format + lint + type check (run before committing)
cd MyApp.Client && npm run lint # Lint + type check only (Oxlint)
cd MyApp.Client && npm run typecheck # Type check only
cd MyApp.Client && npm run fmt # Format all files (Oxfmt)
```

`lib/dtos.ts` and `next-env.d.ts` are generated, so they are excluded from lint and format.

### Testing

```bash
# Frontend tests (Vitest)
# Frontend tests (Vitest via Vite+), import from 'vite-plus/test'
cd MyApp.Client && npm run test # Watch mode
cd MyApp.Client && npm run test:ui # UI mode
cd MyApp.Client && npm run test:run # Single run
Expand Down Expand Up @@ -112,13 +128,14 @@ MyApp.Client/ # React Frontend
├── app/ # Next.js App Router pages
├── components/ # React components
├── lib/ # Utilities and helpers
│ ├── dtos.ts # Auto-generated from C# (via `npm run dtos`)
│ ├── dtos.ts # Auto-generated from C# on .NET App startup
│ ├── gateway.ts # ServiceStack JsonServiceClient
│ └── utils.ts # Utility functions
├── public/ # Static assets
├── dist/ # Build output (production)
├── styles/ # Tailwind CSS styles
└── next.config.mjs # Next.js config for dev mode
├── next.config.mjs # Next.js config for dev mode
└── vite.config.ts # Vite+ config: lint, fmt, test (not used to build)

MyApp.ServiceModel/ # DTOs & API contracts
├── *.cs # C# Request/Response DTOs
Expand Down Expand Up @@ -262,13 +279,15 @@ ServiceStack's AutoQuery generates full CRUD APIs from declarative request DTOs.

### TypeScript DTO Generation

After changing C# DTOs in `MyApp.ServiceModel/`, restart the .NET Server then run:
`MyApp.Client/lib/dtos.ts` is regenerated automatically every time the .NET App starts (e.g. `dotnet run` or a `dotnet watch` restart).
This is done by the `dtos` StartupTask registered in [Configure.StartupTasks.GenerateDtos.cs](MyApp/Configure.StartupTasks.GenerateDtos.cs).
So after changing C# DTOs in `MyApp.ServiceModel/`, restarting the .NET Server is all that's needed, and the Next.js dev server auto-reloads.

Running `npm run dtos` manually is unnecessary, but it's available if needed. It calls the running .NET App's `/types/typescript` endpoint to update `dtos.ts`:
```bash
cd MyApp.Client && npm run dtos
```

This calls ServiceStack's `/types/typescript` endpoint and updates `dtos.ts` with type-safe client DTOs. The Vite dev server auto-reloads.

### okai AutoQuery Code Generation

The `npx okai` tool generates C# AutoQuery APIs and migrations from TypeScript data models (`.d.ts` files):
Expand Down Expand Up @@ -577,11 +596,11 @@ Configured in [Configure.BackgroundJobs.cs](MyApp/Configure.BackgroundJobs.cs) u

## Development Workflow

1. **Start dev servers:** `dotnet watch` (starts both .NET and Vite)
1. **Start dev servers:** `dotnet watch` (starts both .NET and Next.js)
2. **Make backend changes:** Edit C# files in `MyApp.ServiceModel` or `MyApp.ServiceInterface`
3. **Restart .NET Server**
4. **Regenerate DTOs:** `cd MyApp.Client && npm run dtos`
5. **Make frontend changes:** Edit React files in `MyApp.Client/src`
3. **Restart .NET Server:** this also regenerates `dtos.ts` (`npm run dtos` isn't needed)
4. **Use updated DTOs** from `@/lib/dtos` in the frontend
5. **Make frontend changes:** Edit React files in `MyApp.Client`, then `npm run check`
6. **Add new CRUD feature:**
- `npx okai init Feature`
- Edit `MyApp.ServiceModel/Feature.d.ts`
Expand Down
1 change: 0 additions & 1 deletion CLAUDE.md

This file was deleted.

1 change: 1 addition & 0 deletions MyApp.Client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ yarn-error.log*

# vercel
.vercel
.vitest/
41 changes: 41 additions & 0 deletions MyApp.Client/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!--VITE PLUS START-->

# Using Vite+, the Unified Toolchain for the Web

This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. Vite+ is distinct from Vite, and it invokes Vite through `vp dev` and `vp build`. Run `vp help` to print a list of commands and `vp <command> --help` for information about a specific command.

Docs are local at `node_modules/vite-plus/docs` or online at https://viteplus.dev/guide/.

## Built-in Commands vs Scripts

`vp <name>` runs a built-in command. `vp run <name>` runs a `package.json` script or a `vite.config.ts` task. Scripts cannot overwrite built-ins, so `vp dev` and `vp run dev` may do different things. Check `package.json` and `vite.config.ts` first, and run `vp run <name>` when the project defines a script or task with that name.

## Running Scripts

Run TypeScript and JavaScript files with `vpx <file>`, for example `vpx ./scripts/seed.ts --dry-run`. It handles enums, decorators, JSX, and tsconfig `paths`, so do not add `tsx` or `ts-node` to run scripts. `vpx --watch <file>` reruns the file when a file it uses changes, in place of `tsx watch`. `vp node <file>` runs plain Node.js.

## Tool Versions

Run `vp toolchain` to show versions and relationships in the active Vite+
release. Add a tool name to select part of the graph. For example, run
`vp toolchain vite`. Use `--global` to ignore the local `vite-plus` package. Use
`vp why <package>` to show the package-manager dependency graph.

## Review Checklist

- [ ] Run `vp install` after pulling remote changes and before getting started.
- [ ] Run `vp check` and `vp test` to format, lint, type check and test changes.
- [ ] Check if there are `vite.config.ts` tasks or `package.json` scripts necessary for validation, run via `vp run <script>`.
- [ ] If setup, runtime, or package-manager behavior looks wrong, run `vp env doctor` and include its output when asking for help.

<!--VITE PLUS END-->

<!-- BEGIN:nextjs-agent-rules -->

## This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.

This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.

<!-- END:nextjs-agent-rules -->
58 changes: 34 additions & 24 deletions MyApp.Client/app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
'use client'

import { PrimaryButton, SecondaryButton, TextLink } from "@servicestack/react"
import Page from "@/components/layout-page"
import { ValidateAuth, appAuth } from "@/lib/auth"
import { PrimaryButton, SecondaryButton, TextLink } from '@servicestack/react'
import Page from '@/components/layout-page'
import { ValidateAuth, appAuth } from '@/lib/auth'

function Admin () {
function Admin() {
const { user, signOut } = appAuth()
return (<Page title="Admin Page">
<div className="max-w-3xs flex flex-col justify-center items-center">
<svg className="w-36 h-36 text-gray-700" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/>
</svg>
<div>{user!.displayName}</div>
<div>{user!.userName}</div>
<div className="mt-2">
{(user!.roles || []).map((role:string) =>
<span key={role} className="ml-3 inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-indigo-100 text-indigo-800">{role}</span>)}
return (
<Page title="Admin Page">
<div className="max-w-3xs flex flex-col justify-center items-center">
<svg className="w-36 h-36 text-gray-700" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z" />
</svg>
<div>{user!.displayName}</div>
<div>{user!.userName}</div>
<div className="mt-2">
{(user!.roles || []).map((role: string) => (
<span
key={role}
className="ml-3 inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-indigo-100 text-indigo-800"
>
{role}
</span>
))}
</div>
<SecondaryButton className="mt-8" onClick={() => signOut()}>
Sign Out
</SecondaryButton>
<PrimaryButton className="mt-8" href="/Identity/Account/Manage">
Identity Auth Account
</PrimaryButton>
<TextLink className="mt-8" href="/">
🏠 Home
</TextLink>
</div>
<SecondaryButton className="mt-8" onClick={() => signOut()}>Sign Out</SecondaryButton>
<PrimaryButton className="mt-8" href="/Identity/Account/Manage">
Identity Auth Account
</PrimaryButton>
<TextLink className="mt-8" href="/">
🏠 Home
</TextLink>
</div>
</Page>)
</Page>
)
}
export default ValidateAuth(Admin, { role: "Admin" })
export default ValidateAuth(Admin, { role: 'Admin' })
40 changes: 20 additions & 20 deletions MyApp.Client/app/bookings-auto/page.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
'use client'

import Link from "next/link"
import { AutoQueryGrid } from "@servicestack/react"
import Page from "@/components/layout-page"
import { ValidateAuth } from "@/lib/auth"
import SrcPage from "@/components/src-page"
import Link from 'next/link'
import { AutoQueryGrid } from '@servicestack/react'
import Page from '@/components/layout-page'
import { ValidateAuth } from '@/lib/auth'
import SrcPage from '@/components/src-page'

function BookingsAuto() {
return (<Page title="Bookings CRUD (Auto Columns)">

<div className="mb-4 flex justify-end">
<Link href="/bookings-custom" className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300">
View Custom Bookings →
</Link>
</div>
return (
<Page title="Bookings CRUD (Auto Columns)">
<div className="mb-4 flex justify-end">
<Link href="/bookings-custom" className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300">
View Custom Bookings →
</Link>
</div>

<div className="mt-4 flex flex-col ">
<AutoQueryGrid type="Booking"/>
<div className="mt-4 flex flex-col ">
<AutoQueryGrid type="Booking" />

<div className="mt-4 text-center text-gray-400 flex justify-center -ml-6">
<SrcPage path="bookings-auto/page.tsx" />
<div className="mt-4 text-center text-gray-400 flex justify-center -ml-6">
<SrcPage path="bookings-auto/page.tsx" />
</div>
</div>
</div>

</Page>)
</Page>
)
}

export default ValidateAuth(BookingsAuto, {role: 'Employee'})
export default ValidateAuth(BookingsAuto, { role: 'Employee' })
Loading
Loading