@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,134 @@
|
|
|
1
|
+
# Constants files — templates
|
|
2
|
+
|
|
3
|
+
## `src/utils/constants/apiConstants.ts`
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
export const API_VERSION = '/v1';
|
|
7
|
+
export const API_URL = import.meta.env.VITE_API_BASE_URL;
|
|
8
|
+
|
|
9
|
+
// HTTP methods as constants — used by axiosBaseQuery method comparisons
|
|
10
|
+
export const GET = 'GET' as const;
|
|
11
|
+
export const POST = 'POST' as const;
|
|
12
|
+
export const PUT = 'PUT' as const;
|
|
13
|
+
export const PATCH = 'PATCH' as const;
|
|
14
|
+
export const DELETE = 'DELETE' as const;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## `src/utils/constants/appConstants.ts`
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// Storage keys
|
|
21
|
+
export const LOGGED_IN_USER = 'loggedInUser';
|
|
22
|
+
export const SELECTED_LOCALE = 'selectedLocale';
|
|
23
|
+
export const THEME_PREFERENCE = 'themePreference';
|
|
24
|
+
|
|
25
|
+
// Notification types
|
|
26
|
+
export const SUCCESS = 'success' as const;
|
|
27
|
+
export const ERROR = 'error' as const;
|
|
28
|
+
export const INFO = 'info' as const;
|
|
29
|
+
export const WARNING = 'warning' as const;
|
|
30
|
+
|
|
31
|
+
// Status enums
|
|
32
|
+
export const KYC_STATUS = {
|
|
33
|
+
PENDING: 'pending',
|
|
34
|
+
APPROVED: 'approved',
|
|
35
|
+
REJECTED: 'rejected',
|
|
36
|
+
REVIEW: 'review',
|
|
37
|
+
} as const;
|
|
38
|
+
export type KycStatus = (typeof KYC_STATUS)[keyof typeof KYC_STATUS];
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## `src/utils/constants/urlConstants.ts`
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// Group endpoints by feature. Use functions for dynamic paths.
|
|
45
|
+
|
|
46
|
+
export const AUTH_URLS = {
|
|
47
|
+
LOGIN: '/auth/login',
|
|
48
|
+
LOGOUT: '/auth/logout',
|
|
49
|
+
REFRESH: '/auth/refresh',
|
|
50
|
+
PROFILE: '/auth/profile',
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
export const KYC_URLS = {
|
|
54
|
+
LIST: '/kyc',
|
|
55
|
+
DETAIL: (id: string) => `/kyc/${id}`,
|
|
56
|
+
SUBMIT: '/kyc/submit',
|
|
57
|
+
APPROVE: (id: string) => `/kyc/${id}/approve`,
|
|
58
|
+
REJECT: (id: string) => `/kyc/${id}/reject`,
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
export const TRANSACTION_URLS = {
|
|
62
|
+
LIST: '/transactions',
|
|
63
|
+
DETAIL: (id: string) => `/transactions/${id}`,
|
|
64
|
+
CREATE: '/transactions',
|
|
65
|
+
} as const;
|
|
66
|
+
|
|
67
|
+
// Used by request interceptor side-paths (if any)
|
|
68
|
+
export const VALIDATE_INVITATION = '/auth/validate-invitation';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## `src/utils/constants/routeConstants.ts`
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
export const ROUTES = {
|
|
75
|
+
home: '/',
|
|
76
|
+
login: '/login',
|
|
77
|
+
dashboard: '/dashboard',
|
|
78
|
+
kyc: {
|
|
79
|
+
list: '/kyc',
|
|
80
|
+
detail: '/kyc/:id',
|
|
81
|
+
submit: '/kyc/submit',
|
|
82
|
+
},
|
|
83
|
+
transactions: {
|
|
84
|
+
list: '/transactions',
|
|
85
|
+
detail: '/transactions/:id',
|
|
86
|
+
create: '/transactions/new',
|
|
87
|
+
},
|
|
88
|
+
notFound: '*',
|
|
89
|
+
} as const;
|
|
90
|
+
|
|
91
|
+
// For useNavigate() with dynamic params, write small helpers:
|
|
92
|
+
export const kycDetailPath = (id: string): string => `/kyc/${id}`;
|
|
93
|
+
export const transactionDetailPath = (id: string): string => `/transactions/${id}`;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## `src/utils/constants/tagTypes.ts`
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// Every RTK Query API's tagTypes array unions from here.
|
|
100
|
+
// One literal per feature/entity. Keep this list as the source of truth.
|
|
101
|
+
|
|
102
|
+
export const TAG_TYPES = [
|
|
103
|
+
'User',
|
|
104
|
+
'Kyc',
|
|
105
|
+
'Transaction',
|
|
106
|
+
'Loan',
|
|
107
|
+
'Document',
|
|
108
|
+
'AccessRights',
|
|
109
|
+
] as const;
|
|
110
|
+
|
|
111
|
+
export type TagType = (typeof TAG_TYPES)[number];
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## `src/utils/constants/regexConstants.ts`
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// Re-export the BFSI patterns from core so there's one source of truth.
|
|
118
|
+
// Add app-specific regexes below.
|
|
119
|
+
|
|
120
|
+
export { PII_PATTERNS } from '@<scope>/core/pii';
|
|
121
|
+
|
|
122
|
+
// Pull out common ones with friendlier names:
|
|
123
|
+
import { PII_PATTERNS } from '@<scope>/core/pii';
|
|
124
|
+
export const PAN_REGEX = PII_PATTERNS.pan;
|
|
125
|
+
export const AADHAAR_REGEX = PII_PATTERNS.aadhaar;
|
|
126
|
+
export const MOBILE_REGEX = PII_PATTERNS.mobileIndia;
|
|
127
|
+
export const IFSC_REGEX = PII_PATTERNS.ifsc;
|
|
128
|
+
export const PINCODE_REGEX = PII_PATTERNS.pincodeIndia;
|
|
129
|
+
export const EMAIL_REGEX = PII_PATTERNS.email;
|
|
130
|
+
|
|
131
|
+
// App-specific patterns:
|
|
132
|
+
export const REFERENCE_CODE_REGEX = /^ERR-[A-Z0-9]{4}$/;
|
|
133
|
+
export const TRANSACTION_ID_REGEX = /^TXN[0-9]{12}$/;
|
|
134
|
+
```
|
package/templates/rtk-query/.claude/skills/constants-organization/references/tag-types-catalog.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Tag types catalog
|
|
2
|
+
|
|
3
|
+
RTK Query uses tag types for cache invalidation. Each feature picks ONE tag type per entity it manages.
|
|
4
|
+
|
|
5
|
+
## Naming
|
|
6
|
+
|
|
7
|
+
- Singular, PascalCase: `User`, `Kyc`, `Transaction`, `Loan`
|
|
8
|
+
- One tag per logical entity, not per endpoint
|
|
9
|
+
- A single tag covers list + detail of the same entity
|
|
10
|
+
|
|
11
|
+
## Provides vs invalidates
|
|
12
|
+
|
|
13
|
+
| Endpoint | Use | Why |
|
|
14
|
+
| -------------- | ------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
15
|
+
| `getKycList` | `providesTags: ['Kyc']` | Reads → marks the cache as having Kyc data |
|
|
16
|
+
| `getKycDetail` | `providesTags: (_, _, id) => [{ type: 'Kyc', id }]` | Read for one — tagged with the id so detail invalidates separately |
|
|
17
|
+
| `submitKyc` | `invalidatesTags: ['Kyc']` | Mutation → invalidates ALL Kyc-tagged caches (list refetches) |
|
|
18
|
+
| `approveKyc` | `invalidatesTags: (_, _, id) => [{ type: 'Kyc', id }, 'Kyc']` | Per-record + list |
|
|
19
|
+
|
|
20
|
+
## Cross-feature invalidation
|
|
21
|
+
|
|
22
|
+
When a mutation in feature A should invalidate caches in feature B, two patterns:
|
|
23
|
+
|
|
24
|
+
### Pattern 1 — Tag both features
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// transactionsApi.ts
|
|
28
|
+
endpoints: (builder) => ({
|
|
29
|
+
createTransaction: builder.mutation({
|
|
30
|
+
query: ...,
|
|
31
|
+
invalidatesTags: ['Transaction', 'AccountBalance'],
|
|
32
|
+
}),
|
|
33
|
+
}),
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If both `Transaction` and `AccountBalance` are tag types provided by their respective APIs, both refetch.
|
|
37
|
+
|
|
38
|
+
### Pattern 2 — Middleware-driven
|
|
39
|
+
|
|
40
|
+
Use the `invalidateCacheMiddleware` (in `src/redux/`). When `transactionsApi.endpoints.create.matchFulfilled(action)` fires, dispatch invalidation on another API:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
store.dispatch(accountsApi.util.invalidateTags(['AccountBalance']));
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use Pattern 2 when there's a chain (one mutation affects N caches) and listing them all in `invalidatesTags` becomes noisy.
|
|
47
|
+
|
|
48
|
+
## Anti-patterns
|
|
49
|
+
|
|
50
|
+
- ❌ A tag per endpoint (`KycList`, `KycDetail`, `KycSubmit`) — proliferates without benefit
|
|
51
|
+
- ❌ Forgetting `as const` on the array — types degrade to `string[]`
|
|
52
|
+
- ❌ Same tag name across multiple APIs without sharing — invalidations don't cross
|
|
53
|
+
- ❌ String-typed tag references instead of using the exported `TagType` union
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: redux-store-integration
|
|
3
|
+
description: Wire a new RTK Query API or Redux slice into the store. Covers registering reducers in rootReducer.ts, appending middleware in store.ts, using typed hooks (useAppDispatch / useAppSelector), and cross-API cache invalidation. Use when the user just scaffolded a new feature's api.ts or slice.ts and needs to plug it into the store, or asks about cache invalidation middleware, typed hooks, or Redux wiring.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Redux Store Integration
|
|
7
|
+
|
|
8
|
+
After scaffolding a new feature with an `api.ts` (RTK Query) or `slice.ts` (regular Redux), you have to register it in TWO places. Both are tiny but easy to forget.
|
|
9
|
+
|
|
10
|
+
## File map
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
src/redux/
|
|
14
|
+
├── store.ts configureStore + middleware concat
|
|
15
|
+
├── rootReducer.ts combineReducers (slices + API reducers)
|
|
16
|
+
├── reduxHooks.ts typed useAppDispatch / useAppSelector
|
|
17
|
+
└── invalidateCacheMiddleware.ts cross-API cache invalidation rules
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Workflow — adding an RTK Query API
|
|
21
|
+
|
|
22
|
+
### Step 1 — Register the reducer
|
|
23
|
+
|
|
24
|
+
Open `src/redux/rootReducer.ts`:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { combineReducers } from '@reduxjs/toolkit';
|
|
28
|
+
import kycApi from '@/features/Kyc/api';
|
|
29
|
+
|
|
30
|
+
const rootReducer = combineReducers({
|
|
31
|
+
[kycApi.reducerPath]: kycApi.reducer,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export default rootReducer;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use `[api.reducerPath]` as the key — never a hardcoded string. The `reducerPath` is set inside `createApi({ reducerPath: 'kycApi', ... })`.
|
|
38
|
+
|
|
39
|
+
### Step 2 — Append the middleware
|
|
40
|
+
|
|
41
|
+
Open `src/redux/store.ts`:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import kycApi from '@/features/Kyc/api';
|
|
45
|
+
|
|
46
|
+
const store = configureStore({
|
|
47
|
+
reducer: rootReducer,
|
|
48
|
+
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat([kycApi.middleware]),
|
|
49
|
+
devTools: true,
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Without this, RTK Query won't intercept the API's actions. Symptoms: queries fire but cache never updates, subscriptions don't trigger refetch.
|
|
54
|
+
|
|
55
|
+
### Step 3 — Use the API in components
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { useGetKycListQuery } from '@/features/Kyc/api';
|
|
59
|
+
import { useAppDispatch, useAppSelector } from '@/redux/reduxHooks';
|
|
60
|
+
|
|
61
|
+
function KycList() {
|
|
62
|
+
const { data, isLoading, error } = useGetKycListQuery();
|
|
63
|
+
// ...
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
NEVER use plain `useDispatch` / `useSelector` directly — use the typed hooks. They give you `RootState` autocomplete + `AppDispatch` action types.
|
|
68
|
+
|
|
69
|
+
## Workflow — adding a regular Redux slice
|
|
70
|
+
|
|
71
|
+
### Step 1 — Create the slice
|
|
72
|
+
|
|
73
|
+
In `src/features/<Feature>/slice.ts`:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
|
77
|
+
|
|
78
|
+
interface FeatureState {
|
|
79
|
+
selectedId: string | null;
|
|
80
|
+
filters: Record<string, unknown>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const initialState: FeatureState = { selectedId: null, filters: {} };
|
|
84
|
+
|
|
85
|
+
const featureSlice = createSlice({
|
|
86
|
+
name: 'feature',
|
|
87
|
+
initialState,
|
|
88
|
+
reducers: {
|
|
89
|
+
setSelected: (state, action: PayloadAction<string | null>) => {
|
|
90
|
+
state.selectedId = action.payload;
|
|
91
|
+
},
|
|
92
|
+
setFilters: (state, action: PayloadAction<Record<string, unknown>>) => {
|
|
93
|
+
state.filters = action.payload;
|
|
94
|
+
},
|
|
95
|
+
reset: () => initialState,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export const { setSelected, setFilters, reset } = featureSlice.actions;
|
|
100
|
+
export const featureReducer = featureSlice.reducer;
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Step 2 — Register
|
|
104
|
+
|
|
105
|
+
In `src/redux/rootReducer.ts`:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { featureReducer } from '@/features/Feature/slice';
|
|
109
|
+
|
|
110
|
+
const rootReducer = combineReducers({
|
|
111
|
+
feature: featureReducer,
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
No middleware step for plain slices (they go through `getDefaultMiddleware` automatically).
|
|
116
|
+
|
|
117
|
+
## Cross-API cache invalidation
|
|
118
|
+
|
|
119
|
+
When mutation in one API should invalidate another API's cache, use `src/redux/invalidateCacheMiddleware.ts`:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import type { Middleware } from '@reduxjs/toolkit';
|
|
123
|
+
import kycApi from '@/features/Kyc/api';
|
|
124
|
+
import userApi from '@/features/User/api';
|
|
125
|
+
|
|
126
|
+
const invalidateCacheMiddleware: Middleware = (storeApi) => (next) => (action) => {
|
|
127
|
+
if (kycApi.endpoints.submitKyc.matchFulfilled(action)) {
|
|
128
|
+
storeApi.dispatch(userApi.util.invalidateTags(['User']));
|
|
129
|
+
}
|
|
130
|
+
return next(action);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export default invalidateCacheMiddleware;
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Make sure `invalidateCacheMiddleware` is included in `store.ts`'s middleware concat array.
|
|
137
|
+
|
|
138
|
+
See [`references/middleware-patterns.md`](references/middleware-patterns.md) for common invalidation patterns.
|
|
139
|
+
|
|
140
|
+
## Conventions enforced
|
|
141
|
+
|
|
142
|
+
- ❌ NEVER import `useDispatch` / `useSelector` from `react-redux` — use `useAppDispatch` / `useAppSelector` from `reduxHooks`.
|
|
143
|
+
- ❌ NEVER hardcode an API's reducerPath as a string in `rootReducer.ts` — use `[api.reducerPath]`.
|
|
144
|
+
- ❌ NEVER skip the middleware registration — leads to silent cache-bust failures.
|
|
145
|
+
- ✅ Register middleware in the order: `invalidateCacheMiddleware` first, then feature APIs.
|
|
146
|
+
- ✅ Slices live next to their feature (`src/features/<Feature>/slice.ts`).
|
|
147
|
+
- ✅ Each API has a unique `reducerPath`; never share.
|
|
148
|
+
|
|
149
|
+
## Checklist when wiring a new feature
|
|
150
|
+
|
|
151
|
+
- [ ] Reducer registered in `rootReducer.ts`
|
|
152
|
+
- [ ] Middleware appended in `store.ts` (if it's an API)
|
|
153
|
+
- [ ] Containers use `useAppDispatch` / `useAppSelector` (typed)
|
|
154
|
+
- [ ] Cross-API invalidations added to `invalidateCacheMiddleware.ts` (if any)
|
|
155
|
+
|
|
156
|
+
## References
|
|
157
|
+
|
|
158
|
+
- [`references/middleware-patterns.md`](references/middleware-patterns.md) — common cross-API invalidation patterns
|
|
159
|
+
- [`references/localStorage-persistence.md`](references/localStorage-persistence.md) — pattern for persisting a slice to localStorage
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Persisting a slice to localStorage
|
|
2
|
+
|
|
3
|
+
For state that must survive a page reload (locale, theme, in-progress form drafts) but is NOT sensitive (no PII, no tokens).
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
> Tokens NEVER go in localStorage. Use `setAuthToken()` from `@<scope>/core/http` instead, which keeps them in memory.
|
|
8
|
+
|
|
9
|
+
For non-sensitive state, this pattern is fine.
|
|
10
|
+
|
|
11
|
+
## Pattern — `store.subscribe`
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// src/redux/store.ts
|
|
15
|
+
import { LOCALE_KEY, THEME_KEY } from '@/utils/constants/appConstants';
|
|
16
|
+
|
|
17
|
+
store.subscribe(() => {
|
|
18
|
+
const state = store.getState();
|
|
19
|
+
// Only persist allow-listed slices, not the entire store:
|
|
20
|
+
localStorage.setItem(LOCALE_KEY, JSON.stringify(state.locale));
|
|
21
|
+
localStorage.setItem(THEME_KEY, JSON.stringify(state.theme));
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Pattern — `preloadedState`
|
|
26
|
+
|
|
27
|
+
Rehydrate on store init:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const preloadedState = {
|
|
31
|
+
locale: tryParse(localStorage.getItem(LOCALE_KEY)) ?? initialLocaleState,
|
|
32
|
+
theme: tryParse(localStorage.getItem(THEME_KEY)) ?? initialThemeState,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const store = configureStore({
|
|
36
|
+
reducer: rootReducer,
|
|
37
|
+
preloadedState,
|
|
38
|
+
middleware: ...,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function tryParse<T>(raw: string | null): T | null {
|
|
42
|
+
if (!raw) return null;
|
|
43
|
+
try { return JSON.parse(raw) as T; } catch { return null; }
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Don't persist these slices
|
|
48
|
+
|
|
49
|
+
- Anything containing auth tokens
|
|
50
|
+
- Anything containing PII (PAN, Aadhaar, account#)
|
|
51
|
+
- RTK Query API cache (`[api.reducerPath]`) — let RTK Query handle its own cache lifecycle
|
|
52
|
+
- Anything with timestamps that go stale (notifications, idle markers)
|
|
53
|
+
|
|
54
|
+
## Preferred alternative for non-trivial cases
|
|
55
|
+
|
|
56
|
+
Use `@<scope>/core/storage` (`secureStorage.put/get`) with sensitivity tier `'session'`. It does the same job but:
|
|
57
|
+
|
|
58
|
+
- One memory + sessionStorage codepath (no leak risk)
|
|
59
|
+
- Optional TTL eviction
|
|
60
|
+
- Easier to swap to encrypted IndexedDB later
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { put, get } from '@<scope>/core/storage';
|
|
64
|
+
|
|
65
|
+
// On store.subscribe:
|
|
66
|
+
put('locale', state.locale, { sensitivity: 'session' });
|
|
67
|
+
|
|
68
|
+
// On init:
|
|
69
|
+
const persisted = get<LocaleState>('locale', { sensitivity: 'session' });
|
|
70
|
+
```
|
package/templates/rtk-query/.claude/skills/redux-store-integration/references/middleware-patterns.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Cross-API cache invalidation patterns
|
|
2
|
+
|
|
3
|
+
When `invalidatesTags` on a single endpoint isn't enough — typically because the mutation in API A should invalidate caches in API B that's not coupled to A's tagTypes.
|
|
4
|
+
|
|
5
|
+
## Pattern: One mutation invalidates many APIs
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import type { Middleware } from '@reduxjs/toolkit';
|
|
9
|
+
import authApi from '@/features/Auth/api';
|
|
10
|
+
import userApi from '@/features/User/api';
|
|
11
|
+
import permissionsApi from '@/features/Permissions/api';
|
|
12
|
+
|
|
13
|
+
const invalidateCacheMiddleware: Middleware = (storeApi) => (next) => (action) => {
|
|
14
|
+
// Login → re-fetch user profile + permissions
|
|
15
|
+
if (authApi.endpoints.login.matchFulfilled(action)) {
|
|
16
|
+
storeApi.dispatch(userApi.util.invalidateTags(['User']));
|
|
17
|
+
storeApi.dispatch(permissionsApi.util.invalidateTags(['Permission']));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return next(action);
|
|
21
|
+
};
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Pattern: Conditional invalidation
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
if (kycApi.endpoints.submitKyc.matchFulfilled(action)) {
|
|
28
|
+
const newStatus = action.payload?.status;
|
|
29
|
+
if (newStatus === 'approved') {
|
|
30
|
+
storeApi.dispatch(accountsApi.util.invalidateTags(['Account']));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Pattern: Reset on logout
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
if (authApi.endpoints.logout.matchFulfilled(action)) {
|
|
39
|
+
storeApi.dispatch(kycApi.util.resetApiState());
|
|
40
|
+
storeApi.dispatch(transactionsApi.util.resetApiState());
|
|
41
|
+
storeApi.dispatch(userApi.util.resetApiState());
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`resetApiState` wipes all cached data for that API — safer than tag invalidation on logout because it also clears in-flight queries.
|
|
46
|
+
|
|
47
|
+
## Pattern: matchAny for grouped events
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { isAnyOf } from '@reduxjs/toolkit';
|
|
51
|
+
|
|
52
|
+
const matchAnyMutation = isAnyOf(
|
|
53
|
+
kycApi.endpoints.submitKyc.matchFulfilled,
|
|
54
|
+
kycApi.endpoints.approveKyc.matchFulfilled,
|
|
55
|
+
kycApi.endpoints.rejectKyc.matchFulfilled,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (matchAnyMutation(action)) {
|
|
59
|
+
storeApi.dispatch(auditApi.util.invalidateTags(['AuditEvent']));
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Anti-pattern: invalidate inside endpoints' `onQueryStarted`
|
|
64
|
+
|
|
65
|
+
Tempting but bad: it couples the source API to the target. Prefer middleware so the source API stays unaware. Easier to grep for invalidations (one file) and easier to test.
|
|
66
|
+
|
|
67
|
+
## Order matters
|
|
68
|
+
|
|
69
|
+
In `store.ts`, register `invalidateCacheMiddleware` BEFORE the feature APIs' middleware:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
middleware: (getDefaultMiddleware) =>
|
|
73
|
+
getDefaultMiddleware().concat([
|
|
74
|
+
invalidateCacheMiddleware, // first
|
|
75
|
+
authApi.middleware,
|
|
76
|
+
userApi.middleware,
|
|
77
|
+
kycApi.middleware,
|
|
78
|
+
// ...
|
|
79
|
+
]),
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This way the invalidation middleware sees the fulfilled action BEFORE the API's own subscription dispatches a refetch, avoiding a brief stale window.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rtk-query-api
|
|
3
|
+
description: Create an RTK Query API file for a feature. Covers createApi with axiosBaseQuery, query/mutation endpoints, tagTypes for cache invalidation, opt-in success/error notifications, typed request/response interfaces with Zod parse, and exported React hooks. Use when adding a new feature's api.ts, adding endpoints to an existing API, or wiring tag types.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# RTK Query API
|
|
7
|
+
|
|
8
|
+
Every feature has an `api.ts` at `src/features/<Feature>/api.ts`. It uses `createApi` with the project's `axiosBaseQuery` and exposes auto-generated React hooks.
|
|
9
|
+
|
|
10
|
+
## File map (typical feature)
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
src/features/<Feature>/
|
|
14
|
+
├── api.ts createApi + endpoints
|
|
15
|
+
├── schema.ts Zod schemas (request + response)
|
|
16
|
+
├── types.ts inferred types from Zod
|
|
17
|
+
├── constants.ts cache tag references + audit event names
|
|
18
|
+
└── containers/...
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Workflow — new feature API
|
|
22
|
+
|
|
23
|
+
### Step 1 — Define schemas
|
|
24
|
+
|
|
25
|
+
`src/features/Kyc/schema.ts`:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { z } from 'zod';
|
|
29
|
+
import { PAN_REGEX, AADHAAR_REGEX } from '@/utils/constants/regexConstants';
|
|
30
|
+
|
|
31
|
+
export const kycRecordSchema = z.object({
|
|
32
|
+
id: z.string(),
|
|
33
|
+
pan: z.string().regex(PAN_REGEX),
|
|
34
|
+
aadhaar: z.string().regex(AADHAAR_REGEX),
|
|
35
|
+
status: z.enum(['pending', 'approved', 'rejected']),
|
|
36
|
+
createdAt: z.coerce.date(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const kycListResponseSchema = z.object({
|
|
40
|
+
items: z.array(kycRecordSchema),
|
|
41
|
+
total: z.number().int().nonnegative(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const kycSubmitRequestSchema = z.object({
|
|
45
|
+
pan: z.string().regex(PAN_REGEX),
|
|
46
|
+
aadhaar: z.string().regex(AADHAAR_REGEX),
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Types are inferred — never hand-write them:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// types.ts
|
|
54
|
+
import { z } from 'zod';
|
|
55
|
+
import type { kycRecordSchema, kycListResponseSchema, kycSubmitRequestSchema } from './schema';
|
|
56
|
+
|
|
57
|
+
export type KycRecord = z.infer<typeof kycRecordSchema>;
|
|
58
|
+
export type KycListResponse = z.infer<typeof kycListResponseSchema>;
|
|
59
|
+
export type KycSubmitRequest = z.infer<typeof kycSubmitRequestSchema>;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Step 2 — Create the API
|
|
63
|
+
|
|
64
|
+
`src/features/Kyc/api.ts`:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { createApi } from '@reduxjs/toolkit/query/react';
|
|
68
|
+
import axiosBaseQuery from '@/axiosconfig/baseQuery';
|
|
69
|
+
import { KYC_URLS } from '@/utils/constants/urlConstants';
|
|
70
|
+
import { GET, POST } from '@/utils/constants/apiConstants';
|
|
71
|
+
import { kycRecordSchema, kycListResponseSchema } from './schema';
|
|
72
|
+
import type { KycListResponse, KycSubmitRequest, KycRecord } from './types';
|
|
73
|
+
|
|
74
|
+
const kycApi = createApi({
|
|
75
|
+
reducerPath: 'kycApi',
|
|
76
|
+
baseQuery: axiosBaseQuery(),
|
|
77
|
+
tagTypes: ['Kyc'],
|
|
78
|
+
endpoints: (builder) => ({
|
|
79
|
+
getKycList: builder.query<KycListResponse, void>({
|
|
80
|
+
query: () => ({ url: KYC_URLS.LIST, method: GET }),
|
|
81
|
+
transformResponse: (raw: unknown) => kycListResponseSchema.parse(raw),
|
|
82
|
+
providesTags: ['Kyc'],
|
|
83
|
+
}),
|
|
84
|
+
getKycDetail: builder.query<KycRecord, string>({
|
|
85
|
+
query: (id) => ({ url: KYC_URLS.DETAIL(id), method: GET }),
|
|
86
|
+
transformResponse: (raw: unknown) => kycRecordSchema.parse(raw),
|
|
87
|
+
providesTags: (_, __, id) => [{ type: 'Kyc', id }],
|
|
88
|
+
}),
|
|
89
|
+
submitKyc: builder.mutation<KycRecord, KycSubmitRequest>({
|
|
90
|
+
query: (body) => ({
|
|
91
|
+
url: KYC_URLS.SUBMIT,
|
|
92
|
+
method: POST,
|
|
93
|
+
data: body,
|
|
94
|
+
showSuccessNotification: true,
|
|
95
|
+
showFailureNotification: true,
|
|
96
|
+
}),
|
|
97
|
+
transformResponse: (raw: unknown) => kycRecordSchema.parse(raw),
|
|
98
|
+
invalidatesTags: ['Kyc'],
|
|
99
|
+
}),
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export const { useGetKycListQuery, useGetKycDetailQuery, useSubmitKycMutation } = kycApi;
|
|
104
|
+
export default kycApi;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Step 3 — Register in the store
|
|
108
|
+
|
|
109
|
+
See the `redux-store-integration` skill: register `kycApi.reducer` in `rootReducer.ts` and `kycApi.middleware` in `store.ts`.
|
|
110
|
+
|
|
111
|
+
### Step 4 — Use the hooks
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { useGetKycListQuery, useSubmitKycMutation } from '@/features/Kyc/api';
|
|
115
|
+
|
|
116
|
+
function KycList() {
|
|
117
|
+
const { data, isLoading, error } = useGetKycListQuery();
|
|
118
|
+
const [submit] = useSubmitKycMutation();
|
|
119
|
+
// ...
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Conventions enforced
|
|
124
|
+
|
|
125
|
+
- ❌ NEVER use `fetchBaseQuery` — always `axiosBaseQuery()` (so notifications/auth/interceptors apply).
|
|
126
|
+
- ❌ NEVER skip `transformResponse: schema.parse` — every response must validate at runtime.
|
|
127
|
+
- ❌ NEVER hand-write request/response types — infer from Zod.
|
|
128
|
+
- ❌ NEVER inline a URL string — use `urlConstants.ts`.
|
|
129
|
+
- ❌ NEVER inline a method string — use `apiConstants.ts` (`GET`, `POST`, …).
|
|
130
|
+
- ✅ One `reducerPath` per feature API (kebab → camelCase: `kycApi`, `loanApi`).
|
|
131
|
+
- ✅ Mutations always set `showFailureNotification: true`. Queries usually don't.
|
|
132
|
+
- ✅ `tagTypes` from `tagTypes.ts` catalog (see constants-organization skill).
|
|
133
|
+
- ✅ Audit-sensitive mutations also dispatch audit events (see the toolkit's `bfsi-audit-action` skill).
|
|
134
|
+
|
|
135
|
+
## Notification flags
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
showSuccessNotification: true; // success path dispatches toast
|
|
139
|
+
showFailureNotification: true; // error path dispatches toast
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Both default to `false` — opt-in per endpoint. Read by `axiosBaseQuery` and routed through your Notification slice (see the `axios-auth` skill's notification-wiring reference).
|
|
143
|
+
|
|
144
|
+
## References
|
|
145
|
+
|
|
146
|
+
- [`references/endpoint-cookbook.md`](references/endpoint-cookbook.md) — list, detail, create, update, delete, paginated, polling, file-upload examples
|
|
147
|
+
- [`references/optimistic-update.md`](references/optimistic-update.md) — when and how to do optimistic UI updates with `onQueryStarted`
|
|
148
|
+
- [`references/cache-strategies.md`](references/cache-strategies.md) — `providesTags` / `invalidatesTags` patterns by feature shape
|