-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
test(solid-start): basic-auth e2e suite and example #5558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6e5f0c7
8591fb5
06e6406
ef60ad8
6f32364
4252b83
2262f8b
757b46c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Environment variables declared in this file are automatically made available to Prisma. | ||
| # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema | ||
|
|
||
| # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. | ||
| # See the documentation for all the connection string options: https://pris.ly/d/connection-strings | ||
|
|
||
| DATABASE_URL="file:./dev.db" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| node_modules | ||
| package-lock.json | ||
| yarn.lock | ||
|
|
||
| !.env | ||
| .DS_Store | ||
| .cache | ||
| .vercel | ||
| .output | ||
|
|
||
| /build/ | ||
| /api/ | ||
| /server/build | ||
| /public/build | ||
| # Sentry Config File | ||
| .env.sentry-build-plugin | ||
| /test-results/ | ||
| /playwright-report/ | ||
| /blob-report/ | ||
| /playwright/.cache/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| **/build | ||
| **/public | ||
| pnpm-lock.yaml | ||
| routeTree.gen.ts |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||||
| { | ||||||
| "name": "tanstack-solid-start-e2e-basic-auth", | ||||||
| "private": true, | ||||||
| "sideEffects": false, | ||||||
| "type": "module", | ||||||
| "scripts": { | ||||||
| "dev": "vite dev --port 3000", | ||||||
| "dev:e2e": "vite dev", | ||||||
| "build": "vite build", | ||||||
| "start": "node .output/server/index.mjs", | ||||||
| "prisma-generate": "prisma generate", | ||||||
| "test:e2e": "exit 0; rm -rf port*.txt; pnpm run prisma-generate && playwright test --project=chromium" | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests never run: Remove it so Playwright actually executes. - "test:e2e": "exit 0; rm -rf port*.txt; pnpm run prisma-generate && playwright test --project=chromium"
+ "test:e2e": "rm -rf port*.txt && pnpm run prisma-generate && playwright test --project=chromium"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| }, | ||||||
| "dependencies": { | ||||||
| "@prisma/client": "5.22.0", | ||||||
| "@tanstack/solid-router": "workspace:^", | ||||||
| "@tanstack/solid-router-devtools": "workspace:^", | ||||||
| "@tanstack/solid-start": "workspace:^", | ||||||
| "prisma": "^5.22.0", | ||||||
|
Comment on lines
+16
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainUse Align all internal TanStack packages to As per coding guidelines. "dependencies": {
"@prisma/client": "5.22.0",
- "@tanstack/solid-router": "workspace:^",
- "@tanstack/solid-router-devtools": "workspace:^",
- "@tanstack/solid-start": "workspace:^",
+ "@tanstack/solid-router": "workspace:*",
+ "@tanstack/solid-router-devtools": "workspace:*",
+ "@tanstack/solid-start": "workspace:*",
@@
"devDependencies": {
@@
- "@tanstack/router-e2e-utils": "workspace:^",
+ "@tanstack/router-e2e-utils": "workspace:*",Also applies to: 27-27 I'll verify the review comment by checking the current state of the file and examining patterns across the repository. 🏁 Script executed: #!/bin/bash
# Check the actual content of the target package.json file
echo "=== Target file content (lines 16-27) ==="
sed -n '16,27p' e2e/solid-start/basic-auth/package.json
echo -e "\n=== All package.json files in e2e/ directory ==="
find e2e -name "package.json" -type f
echo -e "\n=== Checking workspace protocol usage in all e2e package.json files ==="
for file in $(find e2e -name "package.json" -type f); do
echo "File: $file"
grep -E '@tanstack/.*workspace:\^|@tanstack/.*workspace:\*' "$file" || echo " No TanStack workspace deps found"
done
echo -e "\n=== Checking workspace protocol usage in root packages ==="
find . -maxdepth 2 -name "package.json" -type f | xargs grep -l '@tanstack' | head -5 | while read file; do
echo "File: $file"
grep -E '@tanstack/.*workspace:\^|@tanstack/.*workspace:\*' "$file" | head -3 || echo " No matches"
doneLength of output: 18194 Change all internal TanStack dependencies from This pattern violation is systematic across the entire e2e/ directory. All internal TanStack packages must use "dependencies": {
"@prisma/client": "5.22.0",
- "@tanstack/solid-router": "workspace:^",
- "@tanstack/solid-router-devtools": "workspace:^",
- "@tanstack/solid-start": "workspace:^",
+ "@tanstack/solid-router": "workspace:*",
+ "@tanstack/solid-router-devtools": "workspace:*",
+ "@tanstack/solid-start": "workspace:*",
},
"devDependencies": {
- "@tanstack/router-e2e-utils": "workspace:^",
+ "@tanstack/router-e2e-utils": "workspace:*",
🤖 Prompt for AI Agents |
||||||
| "solid-js": "^1.9.5", | ||||||
| "redaxios": "^0.5.1", | ||||||
| "tailwind-merge": "^2.6.0", | ||||||
| "vite": "^7.1.7" | ||||||
| }, | ||||||
| "devDependencies": { | ||||||
| "@playwright/test": "^1.50.1", | ||||||
| "@tanstack/router-e2e-utils": "workspace:^", | ||||||
| "@types/node": "^22.10.2", | ||||||
| "vite-plugin-solid": "^2.11.9", | ||||||
| "autoprefixer": "^10.4.20", | ||||||
| "postcss": "^8.5.1", | ||||||
| "tailwindcss": "^3.4.17", | ||||||
| "typescript": "^5.7.2", | ||||||
| "vite-tsconfig-paths": "^5.1.4" | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { defineConfig, devices } from '@playwright/test' | ||
| import { getTestServerPort } from '@tanstack/router-e2e-utils' | ||
| import packageJson from './package.json' with { type: 'json' } | ||
|
|
||
| const PORT = await getTestServerPort(packageJson.name) | ||
| const baseURL = `http://localhost:${PORT}` | ||
| /** | ||
| * See https://playwright.dev/docs/test-configuration. | ||
| */ | ||
| export default defineConfig({ | ||
| testDir: './tests', | ||
| workers: 1, | ||
|
|
||
| reporter: [['line']], | ||
|
|
||
| use: { | ||
| /* Base URL to use in actions like `await page.goto('/')`. */ | ||
| baseURL, | ||
| }, | ||
|
|
||
| webServer: { | ||
| command: `VITE_SERVER_PORT=${PORT} pnpm build && PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`, | ||
| url: baseURL, | ||
| reuseExistingServer: !process.env.CI, | ||
| stdout: 'pipe', | ||
| }, | ||
|
|
||
| projects: [ | ||
| { | ||
| name: 'chromium', | ||
| use: { ...devices['Desktop Chrome'] }, | ||
| }, | ||
| ], | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export default { | ||
| plugins: { | ||
| tailwindcss: {}, | ||
| autoprefixer: {}, | ||
| }, | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,8 @@ | ||||||
| -- CreateTable | ||||||
| CREATE TABLE "User" ( | ||||||
| "email" TEXT NOT NULL PRIMARY KEY, | ||||||
| "password" TEXT NOT NULL | ||||||
| ); | ||||||
|
|
||||||
| -- CreateIndex | ||||||
| CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); | ||||||
|
Comment on lines
+7
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant unique index on primary key column. The Apply this diff to remove the redundant index: -
--- CreateIndex
-CREATE UNIQUE INDEX "User_email_key" ON "User"("email");📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # Please do not edit this file manually | ||
| # It should be added in your version-control system (i.e. Git) | ||
| provider = "sqlite" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| // This is your Prisma schema file, | ||
| // learn more about it in the docs: https://pris.ly/d/prisma-schema | ||
|
|
||
| generator client { | ||
| provider = "prisma-client-js" | ||
| } | ||
|
|
||
| datasource db { | ||
| provider = "sqlite" | ||
| url = env("DATABASE_URL") | ||
| } | ||
|
|
||
| model User { | ||
| email String @id @unique | ||
| password String | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "name": "", | ||
| "short_name": "", | ||
| "icons": [ | ||
| { | ||
| "src": "/android-chrome-192x192.png", | ||
| "sizes": "192x192", | ||
| "type": "image/png" | ||
| }, | ||
| { | ||
| "src": "/android-chrome-512x512.png", | ||
| "sizes": "512x512", | ||
| "type": "image/png" | ||
| } | ||
| ], | ||
| "theme_color": "#ffffff", | ||
| "background_color": "#ffffff", | ||
| "display": "standalone" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import type { JSX } from 'solid-js' | ||
|
|
||
| export function Auth({ | ||
| actionText, | ||
| onSubmit, | ||
| status, | ||
| afterSubmit, | ||
| }: { | ||
| actionText: string | ||
| onSubmit: (e: HTMLFormElement) => void | ||
| status: 'pending' | 'idle' | 'success' | 'error' | ||
| afterSubmit?: JSX.Element | ||
|
Comment on lines
+10
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Fix Prop type promises an }: {
actionText: string
- onSubmit: (e: HTMLFormElement) => void
+ onSubmit: (form: HTMLFormElement) => void
status: 'pending' | 'idle' | 'success' | 'error'
afterSubmit?: JSX.Element
}) {
@@
- <form
- onSubmit={(e: any) => {
+ <form
+ onSubmit={(e) => {
e.preventDefault()
- onSubmit(e)
+ onSubmit(e.currentTarget as HTMLFormElement)
}}
class="space-y-4"
>Also applies to: 19-22 🤖 Prompt for AI Agents |
||
| }) { | ||
| return ( | ||
| <div class="fixed inset-0 bg-white dark:bg-black flex items-start justify-center p-8"> | ||
| <div class="bg-white dark:bg-gray-900 p-8 rounded-lg shadow-lg"> | ||
| <h1 class="text-2xl font-bold mb-4">{actionText}</h1> | ||
| <form | ||
| onSubmit={(e: any) => { | ||
| e.preventDefault() | ||
| onSubmit(e) | ||
| }} | ||
| class="space-y-4" | ||
| > | ||
| <div> | ||
| <label for="email" class="block text-xs"> | ||
| </label> | ||
| <input | ||
| type="email" | ||
| name="email" | ||
| id="email" | ||
| class="px-2 py-1 w-full rounded border border-gray-500/20 bg-white dark:bg-gray-800" | ||
| /> | ||
| </div> | ||
| <div> | ||
| <label for="password" class="block text-xs"> | ||
| Password | ||
| </label> | ||
| <input | ||
| type="password" | ||
| name="password" | ||
| id="password" | ||
| class="px-2 py-1 w-full rounded border border-gray-500/20 bg-white dark:bg-gray-800" | ||
| /> | ||
| </div> | ||
| <button | ||
| type="submit" | ||
| class="w-full bg-cyan-600 text-white rounded py-2 font-black uppercase" | ||
| disabled={status === 'pending'} | ||
| > | ||
| {status === 'pending' ? '...' : actionText} | ||
| </button> | ||
| {afterSubmit ? afterSubmit : null} | ||
| </form> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { | ||
| ErrorComponent, | ||
| Link, | ||
| rootRouteId, | ||
| useMatch, | ||
| useRouter, | ||
| } from '@tanstack/solid-router' | ||
| import type { ErrorComponentProps } from '@tanstack/solid-router' | ||
|
|
||
| export function DefaultCatchBoundary({ error }: ErrorComponentProps) { | ||
| const router = useRouter() | ||
| const isRoot = useMatch({ | ||
| strict: false, | ||
| select: (state) => state.id === rootRouteId, | ||
| }) | ||
|
|
||
| console.error(error) | ||
|
|
||
| return ( | ||
| <div class="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6"> | ||
| <ErrorComponent error={error} /> | ||
| <div class="flex gap-2 items-center flex-wrap"> | ||
| <button | ||
| onClick={() => { | ||
| router.invalidate() | ||
| }} | ||
| class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`} | ||
| > | ||
| Try Again | ||
| </button> | ||
| {isRoot() ? ( | ||
| <Link | ||
| to="/" | ||
| class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`} | ||
| > | ||
| Home | ||
| </Link> | ||
| ) : ( | ||
| <Link | ||
| to="/" | ||
| class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`} | ||
| onClick={(e) => { | ||
| e.preventDefault() | ||
| window.history.back() | ||
| }} | ||
| > | ||
| Go Back | ||
| </Link> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,71 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import { useRouter } from '@tanstack/solid-router' | ||||||||||||||||||||||||||||||||||||||||||
| import { useServerFn } from '@tanstack/solid-start' | ||||||||||||||||||||||||||||||||||||||||||
| import { useMutation } from '../hooks/useMutation' | ||||||||||||||||||||||||||||||||||||||||||
| import { loginFn } from '../routes/_authed' | ||||||||||||||||||||||||||||||||||||||||||
| import { Auth } from './Auth' | ||||||||||||||||||||||||||||||||||||||||||
| import { signupFn } from '~/routes/signup' | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export function Login() { | ||||||||||||||||||||||||||||||||||||||||||
| const router = useRouter() | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const loginMutation = useMutation({ | ||||||||||||||||||||||||||||||||||||||||||
| fn: loginFn, | ||||||||||||||||||||||||||||||||||||||||||
| onSuccess: async (ctx) => { | ||||||||||||||||||||||||||||||||||||||||||
| if (!ctx.data?.error) { | ||||||||||||||||||||||||||||||||||||||||||
| await router.invalidate() | ||||||||||||||||||||||||||||||||||||||||||
| router.navigate({ to: '/' }) | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Call the server function via Directly invoking const loginMutation = useMutation({
- fn: loginFn,
+ fn: useServerFn(loginFn),
onSuccess: async (ctx) => {
if (!ctx.data?.error) {
await router.invalidate()
router.navigate({ to: '/' })
return
}
},
})📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const signupMutation = useMutation({ | ||||||||||||||||||||||||||||||||||||||||||
| fn: useServerFn(signupFn), | ||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||
| <Auth | ||||||||||||||||||||||||||||||||||||||||||
| actionText="Login" | ||||||||||||||||||||||||||||||||||||||||||
| status={loginMutation.status()} | ||||||||||||||||||||||||||||||||||||||||||
| onSubmit={(e) => { | ||||||||||||||||||||||||||||||||||||||||||
| const formData = new FormData(e.target as unknown as HTMLFormElement) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| loginMutation.mutate({ | ||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||
| email: formData.get('email') as string, | ||||||||||||||||||||||||||||||||||||||||||
| password: formData.get('password') as string, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+33
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix login payload: remove Matches - loginMutation.mutate({
- data: {
- email: formData.get('email') as string,
- password: formData.get('password') as string,
- },
- })
+ loginMutation.mutate({
+ email: formData.get('email') as string,
+ password: formData.get('password') as string,
+ })📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Align After fixing Auth, receive the form and build FormData from it. - onSubmit={(e) => {
- const formData = new FormData(e.target as unknown as HTMLFormElement)
+ onSubmit={(form) => {
+ const formData = new FormData(form)
- loginMutation.mutate({
- data: {
- email: formData.get('email') as string,
- password: formData.get('password') as string,
- },
- })
+ loginMutation.mutate({
+ email: formData.get('email') as string,
+ password: formData.get('password') as string,
+ })
}}
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| afterSubmit={ | ||||||||||||||||||||||||||||||||||||||||||
| loginMutation.data() ? ( | ||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||
| <div class="text-red-400">{loginMutation.data()?.message}</div> | ||||||||||||||||||||||||||||||||||||||||||
| {loginMutation.data()?.userNotFound ? ( | ||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||
| class="text-blue-500" | ||||||||||||||||||||||||||||||||||||||||||
| onClick={(e) => { | ||||||||||||||||||||||||||||||||||||||||||
| const formData = new FormData( | ||||||||||||||||||||||||||||||||||||||||||
| (e.target as HTMLButtonElement).form!, | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| signupMutation.mutate({ | ||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||
| email: formData.get('email') as string, | ||||||||||||||||||||||||||||||||||||||||||
| password: formData.get('password') as string, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix signup fallback payload: remove Keep the shape consistent with - signupMutation.mutate({
- data: {
- email: formData.get('email') as string,
- password: formData.get('password') as string,
- },
- })
+ signupMutation.mutate({
+ email: formData.get('email') as string,
+ password: formData.get('password') as string,
+ })📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||
| Sign up instead? | ||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| ) : null} | ||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||
| ) : null | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { Link } from '@tanstack/solid-router' | ||
|
|
||
| export function NotFound({ children }: { children?: any }) { | ||
| return ( | ||
| <div class="space-y-2 p-2"> | ||
| <div class="text-gray-600 dark:text-gray-400"> | ||
| {children || <p>The page you are looking for does not exist.</p>} | ||
| </div> | ||
| <p class="flex items-center gap-2 flex-wrap"> | ||
| <button | ||
| onClick={() => window.history.back()} | ||
| class="bg-emerald-500 text-white px-2 py-1 rounded uppercase font-black text-sm" | ||
| > | ||
| Go back | ||
| </button> | ||
| <Link | ||
| to="/" | ||
| class="bg-cyan-600 text-white px-2 py-1 rounded uppercase font-black text-sm" | ||
| > | ||
| Start Over | ||
| </Link> | ||
| </p> | ||
| </div> | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not un-ignore .env — risk of committing secrets.
Replace the negate rule with ignores and keep only an example file tracked.
Optional: also ignore common debug logs
📝 Committable suggestion
🤖 Prompt for AI Agents