@react-vault/create-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +12 -0
- package/README.md +16 -0
- package/bin/create-app.js +8 -0
- package/claude-toolkit/README.md +131 -0
- package/claude-toolkit/agents/bfsi-accessibility-auditor.md +132 -0
- package/claude-toolkit/agents/bfsi-architect.md +156 -0
- package/claude-toolkit/agents/bfsi-code-reviewer.md +137 -0
- package/claude-toolkit/agents/bfsi-compliance-auditor.md +161 -0
- package/claude-toolkit/agents/bfsi-pii-scanner.md +142 -0
- package/claude-toolkit/agents/bfsi-pr-reviewer.md +114 -0
- package/claude-toolkit/agents/bfsi-security-reviewer.md +136 -0
- package/claude-toolkit/commands/bfsi-audit.md +46 -0
- package/claude-toolkit/commands/bfsi-doctor.md +97 -0
- package/claude-toolkit/commands/bfsi-review.md +46 -0
- package/claude-toolkit/commands/bfsi-scaffold.md +47 -0
- package/claude-toolkit/hooks/hooks.json +181 -0
- package/claude-toolkit/hooks/scripts/a11y-check.sh +63 -0
- package/claude-toolkit/hooks/scripts/audit-prompt.sh +36 -0
- package/claude-toolkit/hooks/scripts/block-destructive.sh +41 -0
- package/claude-toolkit/hooks/scripts/block-force-push.sh +30 -0
- package/claude-toolkit/hooks/scripts/format.sh +42 -0
- package/claude-toolkit/hooks/scripts/inject-context.sh +44 -0
- package/claude-toolkit/hooks/scripts/lint.sh +45 -0
- package/claude-toolkit/hooks/scripts/protect-files.sh +53 -0
- package/claude-toolkit/hooks/scripts/save-compliance-context.sh +35 -0
- package/claude-toolkit/hooks/scripts/scan-pii.sh +87 -0
- package/claude-toolkit/hooks/scripts/scan-secrets.sh +67 -0
- package/claude-toolkit/hooks/scripts/verify-clean.sh +50 -0
- package/claude-toolkit/package.json +22 -0
- package/claude-toolkit/plugin.json +31 -0
- package/claude-toolkit/skills/bfsi-api-endpoint/SKILL.md +105 -0
- package/claude-toolkit/skills/bfsi-commit/SKILL.md +102 -0
- package/claude-toolkit/skills/bfsi-compliance-check/SKILL.md +107 -0
- package/claude-toolkit/skills/bfsi-encrypt-helper/SKILL.md +127 -0
- package/claude-toolkit/skills/bfsi-error-message/SKILL.md +162 -0
- package/claude-toolkit/skills/bfsi-feature/SKILL.md +120 -0
- package/claude-toolkit/skills/bfsi-feature/references/architecture.md +69 -0
- package/claude-toolkit/skills/bfsi-feature/references/audit-events.md +70 -0
- package/claude-toolkit/skills/bfsi-feature/scripts/scaffold.mjs +136 -0
- package/claude-toolkit/skills/bfsi-form/SKILL.md +73 -0
- package/claude-toolkit/skills/bfsi-form/references/validation-regex.md +50 -0
- package/claude-toolkit/skills/bfsi-onboarding/SKILL.md +110 -0
- package/claude-toolkit/skills/bfsi-pii-field/SKILL.md +90 -0
- package/claude-toolkit/skills/bfsi-test-pattern/SKILL.md +179 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +339 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/templates/_shared/.claude/settings.json +31 -0
- package/templates/_shared/.env.local.sample +25 -0
- package/templates/_shared/.github/workflows/ci.yml +49 -0
- package/templates/_shared/CLAUDE.md +89 -0
- package/templates/_shared/README.md +50 -0
- package/templates/_shared/index.html +16 -0
- package/templates/_shared/package.json +73 -0
- package/templates/_shared/postcss.config.cjs +6 -0
- package/templates/_shared/src/app/App.tsx +13 -0
- package/templates/_shared/src/app/globals.css +64 -0
- package/templates/_shared/src/env.ts +33 -0
- package/templates/_shared/src/i18n/i18n.ts +18 -0
- package/templates/_shared/src/i18n/translations/en.json +54 -0
- package/templates/_shared/src/i18n/translations/hi.json +30 -0
- package/templates/_shared/src/main.tsx +16 -0
- package/templates/_shared/src/routes/ProtectedRoute.tsx +28 -0
- package/templates/_shared/src/routes/index.tsx +67 -0
- package/templates/_shared/src/shared/ErrorBoundary.tsx +60 -0
- package/templates/_shared/tailwind.config.ts +68 -0
- package/templates/_shared/tests/setup.ts +7 -0
- package/templates/_shared/tsconfig.json +33 -0
- package/templates/_shared/tsconfig.node.json +13 -0
- package/templates/_shared/vite.config.ts +47 -0
- package/templates/rtk-query/.claude/skills/axios-auth/SKILL.md +103 -0
- package/templates/rtk-query/.claude/skills/axios-auth/references/error-shape.md +84 -0
- package/templates/rtk-query/.claude/skills/axios-auth/references/full-code-walkthrough.md +146 -0
- package/templates/rtk-query/.claude/skills/axios-auth/references/notification-wiring.md +141 -0
- package/templates/rtk-query/.claude/skills/constants-organization/SKILL.md +112 -0
- package/templates/rtk-query/.claude/skills/constants-organization/references/example-files.md +134 -0
- package/templates/rtk-query/.claude/skills/constants-organization/references/tag-types-catalog.md +53 -0
- package/templates/rtk-query/.claude/skills/redux-store-integration/SKILL.md +159 -0
- package/templates/rtk-query/.claude/skills/redux-store-integration/references/localStorage-persistence.md +70 -0
- package/templates/rtk-query/.claude/skills/redux-store-integration/references/middleware-patterns.md +82 -0
- package/templates/rtk-query/.claude/skills/rtk-query-api/SKILL.md +148 -0
- package/templates/rtk-query/.claude/skills/rtk-query-api/references/cache-strategies.md +96 -0
- package/templates/rtk-query/.claude/skills/rtk-query-api/references/endpoint-cookbook.md +145 -0
- package/templates/rtk-query/.claude/skills/rtk-query-api/references/optimistic-update.md +53 -0
- package/templates/rtk-query/README.md +84 -0
- package/templates/rtk-query/package.partial.json +7 -0
- package/templates/rtk-query/src/app/App.tsx +23 -0
- package/templates/rtk-query/src/axiosconfig/axiosInstance.ts +26 -0
- package/templates/rtk-query/src/axiosconfig/baseQuery.ts +72 -0
- package/templates/rtk-query/src/axiosconfig/interceptor.ts +42 -0
- package/templates/rtk-query/src/redux/invalidateCacheMiddleware.ts +20 -0
- package/templates/rtk-query/src/redux/reduxHooks.ts +10 -0
- package/templates/rtk-query/src/redux/rootReducer.ts +18 -0
- package/templates/rtk-query/src/redux/store.ts +36 -0
- package/templates/tanstack-query/.claude/skills/axios-auth/SKILL.md +109 -0
- package/templates/tanstack-query/.claude/skills/axios-auth/references/error-shape.md +89 -0
- package/templates/tanstack-query/.claude/skills/axios-auth/references/full-code-walkthrough.md +121 -0
- package/templates/tanstack-query/.claude/skills/axios-auth/references/notification-pattern.md +109 -0
- package/templates/tanstack-query/.claude/skills/constants-organization/SKILL.md +144 -0
- package/templates/tanstack-query/.claude/skills/constants-organization/references/example-files.md +111 -0
- package/templates/tanstack-query/.claude/skills/constants-organization/references/query-key-factories.md +129 -0
- package/templates/tanstack-query/.claude/skills/query-client-setup/SKILL.md +165 -0
- package/templates/tanstack-query/.claude/skills/query-client-setup/references/devtools.md +67 -0
- package/templates/tanstack-query/.claude/skills/query-client-setup/references/global-handlers.md +94 -0
- package/templates/tanstack-query/.claude/skills/tanstack-services/SKILL.md +142 -0
- package/templates/tanstack-query/.claude/skills/tanstack-services/references/audited-mutation.md +144 -0
- package/templates/tanstack-query/.claude/skills/tanstack-services/references/optimistic-update.md +102 -0
- package/templates/tanstack-query/.claude/skills/tanstack-services/references/service-cookbook.md +151 -0
- package/templates/tanstack-query/README.md +63 -0
- package/templates/tanstack-query/package.partial.json +8 -0
- package/templates/tanstack-query/src/api/axiosInstance.ts +20 -0
- package/templates/tanstack-query/src/api/http.ts +62 -0
- package/templates/tanstack-query/src/api/queryClient.ts +28 -0
- package/templates/tanstack-query/src/app/App.tsx +20 -0
- package/templates/tanstack-query/src/services/example.ts +32 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
BFSI React app scaffolded from `@react-vault/create-app`.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install
|
|
9
|
+
pnpm dev # http://localhost:5173
|
|
10
|
+
claude # Claude Code with BFSI toolkit enabled
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
In Claude Code, run `/bfsi-doctor` first to verify everything is wired.
|
|
14
|
+
|
|
15
|
+
## Project layout
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/
|
|
19
|
+
├── app/ App.tsx, providers, globals.css
|
|
20
|
+
├── routes/ Route config, ProtectedRoute, CanAccess
|
|
21
|
+
├── features/<Feature>/ One folder per feature (use /bfsi-feature to scaffold)
|
|
22
|
+
├── shared/ Cross-feature components (ErrorBoundary)
|
|
23
|
+
├── i18n/ react-i18next setup + en/hi translations
|
|
24
|
+
├── env.ts Zod-validated env vars
|
|
25
|
+
└── main.tsx Entry point
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Common commands
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm dev # dev server (vite)
|
|
32
|
+
pnpm build # production build
|
|
33
|
+
pnpm test # vitest
|
|
34
|
+
pnpm test:e2e # playwright
|
|
35
|
+
pnpm typecheck # tsc --noEmit
|
|
36
|
+
pnpm lint # eslint
|
|
37
|
+
pnpm format # prettier write
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Claude Code commands
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
/bfsi-doctor # health check this project
|
|
44
|
+
/bfsi-scaffold feature X # scaffold a new feature
|
|
45
|
+
/bfsi-review # full PR review (security + a11y + perf + tests)
|
|
46
|
+
/bfsi-audit # compliance audit (RBI / PCI / IRDAI / SOC2)
|
|
47
|
+
/bfsi-commit # generate audit-friendly commit message
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
See `packages/claude-toolkit/README.md` in the starter repo for the full list.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<meta name="theme-color" content="#0b1736" />
|
|
8
|
+
<meta name="referrer" content="strict-origin-when-cross-origin" />
|
|
9
|
+
<title>{{projectName}}</title>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
<noscript>JavaScript is required for this application to run.</noscript>
|
|
14
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
11
|
+
"lint:fix": "eslint . --ext ts,tsx --fix",
|
|
12
|
+
"typecheck": "tsc -b --noEmit",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:e2e": "playwright test",
|
|
16
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,json,md,css}\"",
|
|
17
|
+
"prepare": "husky"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@react-vault/core": "0.1.0",
|
|
21
|
+
"@react-vault/ui": "0.1.0",
|
|
22
|
+
"react": "18.3.1",
|
|
23
|
+
"react-dom": "18.3.1",
|
|
24
|
+
"react-router-dom": "6.23.0",
|
|
25
|
+
"react-hook-form": "7.51.4",
|
|
26
|
+
"@hookform/resolvers": "3.3.4",
|
|
27
|
+
"zod": "3.23.6",
|
|
28
|
+
"react-i18next": "14.1.1",
|
|
29
|
+
"i18next": "23.11.3",
|
|
30
|
+
"axios": "1.6.8",
|
|
31
|
+
"date-fns": "3.6.0",
|
|
32
|
+
"lucide-react": "0.378.0",
|
|
33
|
+
"clsx": "2.1.1",
|
|
34
|
+
"tailwind-merge": "2.3.0",
|
|
35
|
+
"class-variance-authority": "0.7.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/react": "18.3.0",
|
|
39
|
+
"@types/react-dom": "18.3.0",
|
|
40
|
+
"@types/node": "20.11.30",
|
|
41
|
+
"@vitejs/plugin-react-swc": "3.6.0",
|
|
42
|
+
"@testing-library/react": "15.0.6",
|
|
43
|
+
"@testing-library/user-event": "14.5.2",
|
|
44
|
+
"@testing-library/jest-dom": "6.4.5",
|
|
45
|
+
"@playwright/test": "1.43.1",
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "7.8.0",
|
|
47
|
+
"@typescript-eslint/parser": "7.8.0",
|
|
48
|
+
"autoprefixer": "10.4.19",
|
|
49
|
+
"eslint": "8.57.0",
|
|
50
|
+
"eslint-config-prettier": "9.1.0",
|
|
51
|
+
"eslint-plugin-jsx-a11y": "6.8.0",
|
|
52
|
+
"eslint-plugin-react": "7.34.1",
|
|
53
|
+
"eslint-plugin-react-hooks": "4.6.2",
|
|
54
|
+
"eslint-plugin-react-refresh": "0.4.6",
|
|
55
|
+
"husky": "9.0.11",
|
|
56
|
+
"jsdom": "24.0.0",
|
|
57
|
+
"lint-staged": "15.2.2",
|
|
58
|
+
"postcss": "8.4.38",
|
|
59
|
+
"prettier": "3.2.5",
|
|
60
|
+
"tailwindcss": "3.4.3",
|
|
61
|
+
"tailwindcss-animate": "1.0.7",
|
|
62
|
+
"typescript": "5.4.5",
|
|
63
|
+
"vite": "5.2.10",
|
|
64
|
+
"vitest": "1.6.0",
|
|
65
|
+
"@vitest/coverage-v8": "1.6.0"
|
|
66
|
+
},
|
|
67
|
+
"lint-staged": {
|
|
68
|
+
"*.{ts,tsx}": [
|
|
69
|
+
"prettier --write",
|
|
70
|
+
"eslint --fix"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
2
|
+
import { ErrorBoundary } from '../shared/ErrorBoundary.js';
|
|
3
|
+
import { AppRoutes } from '../routes/index.js';
|
|
4
|
+
|
|
5
|
+
export function App(): JSX.Element {
|
|
6
|
+
return (
|
|
7
|
+
<ErrorBoundary>
|
|
8
|
+
<BrowserRouter>
|
|
9
|
+
<AppRoutes />
|
|
10
|
+
</BrowserRouter>
|
|
11
|
+
</ErrorBoundary>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
--background: 0 0% 100%;
|
|
8
|
+
--foreground: 240 10% 3.9%;
|
|
9
|
+
--card: 0 0% 100%;
|
|
10
|
+
--card-foreground: 240 10% 3.9%;
|
|
11
|
+
--popover: 0 0% 100%;
|
|
12
|
+
--popover-foreground: 240 10% 3.9%;
|
|
13
|
+
--primary: 217 91% 35%; /* BFSI navy */
|
|
14
|
+
--primary-foreground: 0 0% 98%;
|
|
15
|
+
--secondary: 240 4.8% 95.9%;
|
|
16
|
+
--secondary-foreground: 240 5.9% 10%;
|
|
17
|
+
--muted: 240 4.8% 95.9%;
|
|
18
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
19
|
+
--accent: 240 4.8% 95.9%;
|
|
20
|
+
--accent-foreground: 240 5.9% 10%;
|
|
21
|
+
--destructive: 0 84.2% 60.2%;
|
|
22
|
+
--destructive-foreground: 0 0% 98%;
|
|
23
|
+
--border: 240 5.9% 90%;
|
|
24
|
+
--input: 240 5.9% 90%;
|
|
25
|
+
--ring: 217 91% 35%;
|
|
26
|
+
--radius: 0.5rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dark {
|
|
30
|
+
--background: 240 10% 3.9%;
|
|
31
|
+
--foreground: 0 0% 98%;
|
|
32
|
+
--card: 240 10% 3.9%;
|
|
33
|
+
--card-foreground: 0 0% 98%;
|
|
34
|
+
--popover: 240 10% 3.9%;
|
|
35
|
+
--popover-foreground: 0 0% 98%;
|
|
36
|
+
--primary: 217 91% 60%;
|
|
37
|
+
--primary-foreground: 240 5.9% 10%;
|
|
38
|
+
--secondary: 240 3.7% 15.9%;
|
|
39
|
+
--secondary-foreground: 0 0% 98%;
|
|
40
|
+
--muted: 240 3.7% 15.9%;
|
|
41
|
+
--muted-foreground: 240 5% 64.9%;
|
|
42
|
+
--accent: 240 3.7% 15.9%;
|
|
43
|
+
--accent-foreground: 0 0% 98%;
|
|
44
|
+
--destructive: 0 62.8% 30.6%;
|
|
45
|
+
--destructive-foreground: 0 0% 98%;
|
|
46
|
+
--border: 240 3.7% 15.9%;
|
|
47
|
+
--input: 240 3.7% 15.9%;
|
|
48
|
+
--ring: 217 91% 60%;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
* {
|
|
52
|
+
@apply border-border;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
body {
|
|
56
|
+
@apply bg-background text-foreground;
|
|
57
|
+
font-feature-settings: "rlig" 1, "calt" 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Disable text selection on PII reveal toggle icons */
|
|
61
|
+
[data-testid^="pii-"] button {
|
|
62
|
+
user-select: none;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Env vars — validated at startup via Zod.
|
|
3
|
+
* Throws on app boot if anything required is missing or malformed.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
const envSchema = z.object({
|
|
8
|
+
VITE_API_BASE_URL: z.string().url(),
|
|
9
|
+
VITE_API_TIMEOUT_MS: z.coerce.number().int().positive().default(30_000),
|
|
10
|
+
VITE_AUTH_HEADER_NAME: z.string().min(1).default('Authorization'),
|
|
11
|
+
VITE_AUDIT_ENDPOINT: z.string().min(1).default('/api/audit'),
|
|
12
|
+
VITE_AUDIT_BATCH_SIZE: z.coerce.number().int().positive().default(20),
|
|
13
|
+
VITE_AUDIT_FLUSH_INTERVAL_MS: z.coerce.number().int().positive().default(5_000),
|
|
14
|
+
VITE_SENTRY_DSN: z.string().optional(),
|
|
15
|
+
VITE_IDLE_TIMEOUT_MS: z.coerce.number().int().positive().default(900_000),
|
|
16
|
+
VITE_SENSITIVE_IDLE_TIMEOUT_MS: z.coerce.number().int().positive().default(300_000),
|
|
17
|
+
VITE_FEATURE_FLAGS_PROVIDER: z.enum(['local', 'growthbook', 'unleash']).default('local'),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type Env = z.infer<typeof envSchema>;
|
|
21
|
+
|
|
22
|
+
function load(): Env {
|
|
23
|
+
const parsed = envSchema.safeParse(import.meta.env);
|
|
24
|
+
if (!parsed.success) {
|
|
25
|
+
const issues = parsed.error.issues
|
|
26
|
+
.map((i) => ` ${i.path.join('.')}: ${i.message}`)
|
|
27
|
+
.join('\n');
|
|
28
|
+
throw new Error(`Invalid environment variables:\n${issues}`);
|
|
29
|
+
}
|
|
30
|
+
return parsed.data;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const env: Env = load();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import i18n from 'i18next';
|
|
2
|
+
import { initReactI18next } from 'react-i18next';
|
|
3
|
+
import en from './translations/en.json';
|
|
4
|
+
import hi from './translations/hi.json';
|
|
5
|
+
|
|
6
|
+
void i18n.use(initReactI18next).init({
|
|
7
|
+
resources: {
|
|
8
|
+
en: { translation: en },
|
|
9
|
+
hi: { translation: hi },
|
|
10
|
+
},
|
|
11
|
+
lng: 'en',
|
|
12
|
+
fallbackLng: 'en',
|
|
13
|
+
interpolation: {
|
|
14
|
+
escapeValue: false, // React already escapes
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export default i18n;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app": {
|
|
3
|
+
"name": "{{projectName}}",
|
|
4
|
+
"loading": "Loading…",
|
|
5
|
+
"empty": "Nothing to show"
|
|
6
|
+
},
|
|
7
|
+
"errors": {
|
|
8
|
+
"network": {
|
|
9
|
+
"title": "Can't reach the server",
|
|
10
|
+
"description": "Check your internet connection and try again."
|
|
11
|
+
},
|
|
12
|
+
"timeout": {
|
|
13
|
+
"title": "Request took too long",
|
|
14
|
+
"description": "Please try again."
|
|
15
|
+
},
|
|
16
|
+
"session_expired": {
|
|
17
|
+
"title": "Session expired",
|
|
18
|
+
"description": "Please sign in again."
|
|
19
|
+
},
|
|
20
|
+
"forbidden": {
|
|
21
|
+
"title": "Not allowed",
|
|
22
|
+
"description": "You don't have access to this. Contact your administrator if you think this is wrong."
|
|
23
|
+
},
|
|
24
|
+
"not_found": {
|
|
25
|
+
"title": "Not found",
|
|
26
|
+
"description": "The page or resource you were looking for doesn't exist."
|
|
27
|
+
},
|
|
28
|
+
"validation": {
|
|
29
|
+
"title": "Check your inputs",
|
|
30
|
+
"description": "Some fields need attention before we can proceed."
|
|
31
|
+
},
|
|
32
|
+
"rate_limited": {
|
|
33
|
+
"title": "Too many attempts",
|
|
34
|
+
"description": "Please wait a moment and try again."
|
|
35
|
+
},
|
|
36
|
+
"server": {
|
|
37
|
+
"title": "Something went wrong on our end",
|
|
38
|
+
"description": "Please try again in a moment. (Ref: {{ref}})"
|
|
39
|
+
},
|
|
40
|
+
"generic": {
|
|
41
|
+
"title": "Something went wrong",
|
|
42
|
+
"description": "Please try again. If it persists, contact support with reference {{ref}}."
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"pii": {
|
|
46
|
+
"reveal": {
|
|
47
|
+
"pan": "Reveal PAN",
|
|
48
|
+
"aadhaar": "Reveal Aadhaar",
|
|
49
|
+
"account": "Reveal account number",
|
|
50
|
+
"mobile": "Reveal mobile",
|
|
51
|
+
"email": "Reveal email"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app": {
|
|
3
|
+
"name": "{{projectName}}",
|
|
4
|
+
"loading": "लोड हो रहा है…",
|
|
5
|
+
"empty": "कुछ नहीं दिखाने को"
|
|
6
|
+
},
|
|
7
|
+
"errors": {
|
|
8
|
+
"network": {
|
|
9
|
+
"title": "सर्वर तक नहीं पहुंच पा रहे",
|
|
10
|
+
"description": "अपना इंटरनेट कनेक्शन जांचें और फिर से प्रयास करें।"
|
|
11
|
+
},
|
|
12
|
+
"session_expired": {
|
|
13
|
+
"title": "सत्र समाप्त हो गया",
|
|
14
|
+
"description": "कृपया फिर से साइन इन करें।"
|
|
15
|
+
},
|
|
16
|
+
"generic": {
|
|
17
|
+
"title": "कुछ गलत हुआ",
|
|
18
|
+
"description": "फिर से प्रयास करें। यदि समस्या बनी रहती है, तो रेफरेंस {{ref}} के साथ समर्थन से संपर्क करें।"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"pii": {
|
|
22
|
+
"reveal": {
|
|
23
|
+
"pan": "पैन दिखाएं",
|
|
24
|
+
"aadhaar": "आधार दिखाएं",
|
|
25
|
+
"account": "खाता संख्या दिखाएं",
|
|
26
|
+
"mobile": "मोबाइल दिखाएं",
|
|
27
|
+
"email": "ईमेल दिखाएं"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import './app/globals.css';
|
|
4
|
+
import { App } from './app/App.js';
|
|
5
|
+
import './i18n/i18n.js';
|
|
6
|
+
|
|
7
|
+
const root = document.getElementById('root');
|
|
8
|
+
if (!root) {
|
|
9
|
+
throw new Error('root element not found');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
ReactDOM.createRoot(root).render(
|
|
13
|
+
<React.StrictMode>
|
|
14
|
+
<App />
|
|
15
|
+
</React.StrictMode>,
|
|
16
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { Navigate, useLocation } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* v0.1 stub. Wire up to your auth context once you have it.
|
|
6
|
+
*
|
|
7
|
+
* Expected behaviour:
|
|
8
|
+
* - If not authenticated → redirect to /login (with `from` for return)
|
|
9
|
+
* - If authenticated but lacks `permission` → render 403 + emit audit event
|
|
10
|
+
* - Else → render children
|
|
11
|
+
*/
|
|
12
|
+
export interface ProtectedRouteProps {
|
|
13
|
+
permission?: string;
|
|
14
|
+
/** Override idle timeout for this route (in ms). */
|
|
15
|
+
idleTimeoutMs?: number;
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ProtectedRoute({ permission: _permission, children }: ProtectedRouteProps): JSX.Element {
|
|
20
|
+
const location = useLocation();
|
|
21
|
+
// TODO: wire to actual auth context
|
|
22
|
+
const isAuthenticated = true; // placeholder
|
|
23
|
+
if (!isAuthenticated) {
|
|
24
|
+
return <Navigate to="/login" state={{ from: location }} replace />;
|
|
25
|
+
}
|
|
26
|
+
// TODO: check permission against user's permission set
|
|
27
|
+
return <>{children}</>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Routes, Route } from 'react-router-dom';
|
|
2
|
+
import { ProtectedRoute } from './ProtectedRoute.js';
|
|
3
|
+
|
|
4
|
+
export function AppRoutes(): JSX.Element {
|
|
5
|
+
return (
|
|
6
|
+
<Routes>
|
|
7
|
+
<Route path="/" element={<Landing />} />
|
|
8
|
+
<Route path="/login" element={<Login />} />
|
|
9
|
+
<Route
|
|
10
|
+
path="/dashboard"
|
|
11
|
+
element={
|
|
12
|
+
<ProtectedRoute permission="dashboard.view">
|
|
13
|
+
<Dashboard />
|
|
14
|
+
</ProtectedRoute>
|
|
15
|
+
}
|
|
16
|
+
/>
|
|
17
|
+
<Route path="*" element={<NotFound />} />
|
|
18
|
+
</Routes>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function Landing(): JSX.Element {
|
|
23
|
+
return (
|
|
24
|
+
<main className="container mx-auto py-12">
|
|
25
|
+
<h1 className="text-3xl font-bold tracking-tight">{`{{projectName}}`}</h1>
|
|
26
|
+
<p className="mt-2 text-muted-foreground">
|
|
27
|
+
Scaffolded from @react-vault/create-app. Go to{' '}
|
|
28
|
+
<a className="underline" href="/dashboard">
|
|
29
|
+
/dashboard
|
|
30
|
+
</a>
|
|
31
|
+
.
|
|
32
|
+
</p>
|
|
33
|
+
<p className="mt-4 text-sm text-muted-foreground">
|
|
34
|
+
Run <code className="rounded bg-muted px-1.5 py-0.5">claude</code> in this directory to
|
|
35
|
+
start a Claude Code session with the BFSI toolkit enabled. Then run{' '}
|
|
36
|
+
<code className="rounded bg-muted px-1.5 py-0.5">/bfsi-doctor</code>.
|
|
37
|
+
</p>
|
|
38
|
+
</main>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function Login(): JSX.Element {
|
|
43
|
+
return (
|
|
44
|
+
<main className="container mx-auto py-12">
|
|
45
|
+
<h1 className="text-2xl font-semibold">Sign in</h1>
|
|
46
|
+
<p className="mt-2 text-muted-foreground">Login form goes here.</p>
|
|
47
|
+
</main>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Dashboard(): JSX.Element {
|
|
52
|
+
return (
|
|
53
|
+
<main className="container mx-auto py-12">
|
|
54
|
+
<h1 className="text-2xl font-semibold">Dashboard</h1>
|
|
55
|
+
<p className="mt-2 text-muted-foreground">Protected by <ProtectedRoute>.</p>
|
|
56
|
+
</main>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function NotFound(): JSX.Element {
|
|
61
|
+
return (
|
|
62
|
+
<main className="container mx-auto py-12">
|
|
63
|
+
<h1 className="text-2xl font-semibold">Not found</h1>
|
|
64
|
+
<p className="mt-2 text-muted-foreground">The page you were looking for doesn't exist.</p>
|
|
65
|
+
</main>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
2
|
+
import { generateErrorRef } from '@react-vault/core/audit';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface State {
|
|
9
|
+
errorRef: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* BFSI error boundary. Catches render errors; shows a generic safe message
|
|
14
|
+
* with a ref code that maps to a full log entry.
|
|
15
|
+
*
|
|
16
|
+
* NEVER expose error.message / error.stack to the UI — that leaks technical
|
|
17
|
+
* detail per the bfsi-error-message skill.
|
|
18
|
+
*/
|
|
19
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
20
|
+
state: State = { errorRef: null };
|
|
21
|
+
|
|
22
|
+
static getDerivedStateFromError(_error: Error): State {
|
|
23
|
+
return { errorRef: generateErrorRef() };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
27
|
+
// Forward to your observability (PII-scrubbed). Replace with your client.
|
|
28
|
+
// For now, log to console with structure.
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.error('[error-boundary]', {
|
|
31
|
+
ref: this.state.errorRef,
|
|
32
|
+
message: error.message,
|
|
33
|
+
stack: error.stack,
|
|
34
|
+
componentStack: info.componentStack,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
render(): ReactNode {
|
|
39
|
+
if (this.state.errorRef) {
|
|
40
|
+
return (
|
|
41
|
+
<main className="container mx-auto py-12">
|
|
42
|
+
<h1 className="text-2xl font-semibold">Something went wrong</h1>
|
|
43
|
+
<p className="mt-2 text-muted-foreground">
|
|
44
|
+
We're sorry — an unexpected error occurred. If this persists, contact support with this
|
|
45
|
+
reference:
|
|
46
|
+
</p>
|
|
47
|
+
<code className="mt-4 inline-block rounded bg-muted px-2 py-1 font-mono text-sm">
|
|
48
|
+
{this.state.errorRef}
|
|
49
|
+
</code>
|
|
50
|
+
<div className="mt-6">
|
|
51
|
+
<a className="underline" href="/">
|
|
52
|
+
Go back to home
|
|
53
|
+
</a>
|
|
54
|
+
</div>
|
|
55
|
+
</main>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return this.props.children;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Config } from 'tailwindcss';
|
|
2
|
+
import animate from 'tailwindcss-animate';
|
|
3
|
+
|
|
4
|
+
const config: Config = {
|
|
5
|
+
darkMode: ['class'],
|
|
6
|
+
content: [
|
|
7
|
+
'./index.html',
|
|
8
|
+
'./src/**/*.{ts,tsx}',
|
|
9
|
+
'./node_modules/@react-vault/ui/dist/**/*.{js,mjs}',
|
|
10
|
+
],
|
|
11
|
+
theme: {
|
|
12
|
+
container: {
|
|
13
|
+
center: true,
|
|
14
|
+
padding: '2rem',
|
|
15
|
+
screens: { '2xl': '1400px' },
|
|
16
|
+
},
|
|
17
|
+
extend: {
|
|
18
|
+
colors: {
|
|
19
|
+
// shadcn/ui semantic colors (CSS vars in app/globals.css)
|
|
20
|
+
border: 'hsl(var(--border))',
|
|
21
|
+
input: 'hsl(var(--input))',
|
|
22
|
+
ring: 'hsl(var(--ring))',
|
|
23
|
+
background: 'hsl(var(--background))',
|
|
24
|
+
foreground: 'hsl(var(--foreground))',
|
|
25
|
+
primary: {
|
|
26
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
27
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
28
|
+
},
|
|
29
|
+
secondary: {
|
|
30
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
31
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
32
|
+
},
|
|
33
|
+
destructive: {
|
|
34
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
35
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
36
|
+
},
|
|
37
|
+
muted: {
|
|
38
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
39
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
40
|
+
},
|
|
41
|
+
accent: {
|
|
42
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
43
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
44
|
+
},
|
|
45
|
+
// BFSI status colours
|
|
46
|
+
status: {
|
|
47
|
+
pending: 'hsl(38 92% 50%)',
|
|
48
|
+
approved: 'hsl(142 71% 45%)',
|
|
49
|
+
rejected: 'hsl(0 84% 60%)',
|
|
50
|
+
review: 'hsl(217 91% 60%)',
|
|
51
|
+
expired: 'hsl(240 5% 50%)',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
borderRadius: {
|
|
55
|
+
lg: 'var(--radius)',
|
|
56
|
+
md: 'calc(var(--radius) - 2px)',
|
|
57
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
58
|
+
},
|
|
59
|
+
fontFamily: {
|
|
60
|
+
sans: ['Inter', 'system-ui', 'sans-serif'],
|
|
61
|
+
mono: ['JetBrains Mono', 'ui-monospace', 'monospace'],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
plugins: [animate],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default config;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"allowImportingTsExtensions": false,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noImplicitAny": true,
|
|
16
|
+
"strictNullChecks": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"useUnknownInCatchVariables": true,
|
|
23
|
+
"skipLibCheck": true,
|
|
24
|
+
"forceConsistentCasingInFileNames": true,
|
|
25
|
+
"baseUrl": ".",
|
|
26
|
+
"paths": {
|
|
27
|
+
"@/*": ["./src/*"]
|
|
28
|
+
},
|
|
29
|
+
"types": ["vite/client", "node"]
|
|
30
|
+
},
|
|
31
|
+
"include": ["src", "tests"],
|
|
32
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"composite": true,
|
|
4
|
+
"skipLibCheck": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"allowSyntheticDefaultImports": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"types": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["vite.config.ts", "tailwind.config.ts", "postcss.config.cjs", "playwright.config.ts"]
|
|
13
|
+
}
|