@mohantn/gate-keeper 2.2.3 → 2.2.4

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.
@@ -1,315 +0,0 @@
1
- # React 17 Development Instructions
2
-
3
- **Scope**: All `.tsx`/`.ts` files | **Version**: 3.0 | **Tags**: `react`, `typescript`, `hooks`, `emotion`, `react-query`
4
-
5
- **Related Files**: See [react-testing-optimization.instructions.md](./react-testing-optimization.instructions.md) for testing & optimization
6
-
7
- ## 🔴 MANDATORY: Import Organization
8
- Group: **React** → **Third-party** → **dh-component-library/store-components** → **Local Components** → **Hooks** → **Utils/API** → **Types**
9
-
10
- ```typescript
11
- import { useState, useCallback } from 'react';
12
- import { useQuery } from 'react-query';
13
- import styled from '@emotion/styled';
14
- import { Button, Flex } from 'dh-component-library';
15
- import { PageCard } from 'store-components';
16
- import { FormElementText } from 'src/Components/Forms/FormElements';
17
- import { useGetActivationPrice } from 'src/Hooks/query/pricingData';
18
- import { activationPrice } from 'src/api';
19
- import { t } from 'src/I18n';
20
- import { Activation, ThemeType } from 'src/types';
21
- ```
22
-
23
- ## Core Patterns
24
-
25
- | Pattern | Usage |
26
- |---------|-------|
27
- | **Functional Components** | `export function Component({ prop }: Props)` OR `const Component: React.FC<Props>` |
28
- | **Emotion Styling** | `styled(Component)<Props>(({ theme }) => ({ ... }))` with ThemeType |
29
- | **React Query v3** | `useQuery(['key', id, adAccountId], fn, { onError: onErrorPopToast })` |
30
- | **Context Providers** | Cascade: Theme → User → Configuration → PostHog → Domain contexts |
31
- | **Custom Hooks** | Prefix `use*`, return object: `{ data, isLoading, error }` |
32
- | **Test IDs** | Hierarchical: `DATA_TEST_ID.section.field.name` |
33
- | **i18n** | All text: `t('translationKey')` from `src/I18n` |
34
-
35
- ## Functional Components
36
-
37
- ```typescript
38
- interface ProductProps {
39
- productId: string;
40
- onSelect?: (id: string) => void;
41
- }
42
-
43
- const Product: React.FC<ProductProps> = ({ productId, onSelect }) => {
44
- const [isLoading, setIsLoading] = React.useState(false);
45
-
46
- const handleClick = React.useCallback(() => {
47
- setIsLoading(true);
48
- onSelect?.(productId);
49
- }, [productId, onSelect]);
50
-
51
- return (
52
- <div onClick={handleClick} className={isLoading ? 'loading' : ''}>
53
- {productId}
54
- </div>
55
- );
56
- };
57
-
58
- export default Product;
59
- ```
60
-
61
- ## Custom Hooks
62
-
63
- ```typescript
64
- interface UseProductReturn {
65
- product: Product | null;
66
- loading: boolean;
67
- error: Error | null;
68
- refetch: () => Promise<void>;
69
- }
70
-
71
- export const useProduct = (productId: string): UseProductReturn => {
72
- const [product, setProduct] = React.useState<Product | null>(null);
73
- const [loading, setLoading] = React.useState(false);
74
- const [error, setError] = React.useState<Error | null>(null);
75
-
76
- const fetchProduct = React.useCallback(async () => {
77
- setLoading(true);
78
- try {
79
- const response = await fetch(`/api/products/${productId}`);
80
- if (!response.ok) throw new Error('Failed to fetch');
81
- const data = await response.json();
82
- setProduct(data);
83
- setError(null);
84
- } catch (err) {
85
- setError(err instanceof Error ? err : new Error('Unknown error'));
86
- } finally {
87
- setLoading(false);
88
- }
89
- }, [productId]);
90
-
91
- React.useEffect(() => {
92
- fetchProduct();
93
- }, [fetchProduct]);
94
-
95
- return { product, loading, error, refetch: fetchProduct };
96
- };
97
- ```
98
-
99
- ## Context API
100
-
101
- ```typescript
102
- interface ProductContextType {
103
- selectedProduct: Product | null;
104
- selectProduct: (product: Product) => void;
105
- }
106
-
107
- const ProductContext = React.createContext<ProductContextType | undefined>(undefined);
108
-
109
- export const ProductProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
110
- const [selectedProduct, setSelectedProduct] = React.useState<Product | null>(null);
111
-
112
- const value: ProductContextType = {
113
- selectedProduct,
114
- selectProduct: (product) => setSelectedProduct(product),
115
- };
116
-
117
- return <ProductContext.Provider value={value}>{children}</ProductContext.Provider>;
118
- };
119
-
120
- export const useProductContext = () => {
121
- const context = React.useContext(ProductContext);
122
- if (!context) throw new Error('useProductContext must be used within ProductProvider');
123
- return context;
124
- };
125
- ```
126
-
127
- ## React Query v3 (Project Standard)
128
-
129
- ```typescript
130
- import { useQuery, UseQueryOptions } from 'react-query';
131
- import { AxiosError } from 'axios';
132
- import { onErrorPopToast } from './helpers/onErrorPopToast';
133
- import { t } from 'src/I18n';
134
-
135
- // Query hook pattern
136
- export function useGetActivationPrice(options?: UseQueryOptions<Pricing, AxiosError>) {
137
- const { activationMerged } = useStoreMediaContext();
138
- const { adAccountId } = useUserContext();
139
-
140
- const { data, isLoading, error, isError } = useQuery<Pricing, AxiosError>(
141
- ['activationPrice', activationMerged.activationId, adAccountId],
142
- () => activationPrice(activationMerged.activationId),
143
- {
144
- onError: (err) => onErrorPopToast(err, t('getPricingError')),
145
- enabled: !!activationMerged.activationId,
146
- ...options,
147
- }
148
- );
149
-
150
- return { dataActivationPrice: data, isLoadingActivationPrice: isLoading, isErrorActivationPrice: isError };
151
- }
152
-
153
- // CRITICAL: Always include adAccountId in query keys for multi-tenancy
154
- // Use descriptive return names: data{EntityName}, isLoading{EntityName}
155
- ```
156
-
157
- ## Emotion Styling (Project Standard)
158
-
159
- ```typescript
160
- import styled from '@emotion/styled';
161
- import { Flex } from 'dh-component-library';
162
- import { ThemeType } from 'src/types';
163
-
164
- // Styled component with theme
165
- const FilterActions = styled(Flex)<{ theme?: ThemeType }>(({ theme }) => ({
166
- gap: '16px',
167
- justifyContent: 'flex-end',
168
- marginTop: '24px',
169
- paddingTop: '16px',
170
- borderTop: `1px solid ${theme?.palette?.greyscale?.[200] || '#e5e7eb'}`,
171
-
172
- '& button': {
173
- minWidth: '44px',
174
- minHeight: '44px',
175
- },
176
-
177
- '@media (max-width: 768px)': {
178
- flexDirection: 'column',
179
- gap: '12px',
180
- },
181
- }));
182
-
183
- // Always use optional chaining for theme properties
184
- // Include responsive breakpoints: 768px (tablet), 1024px (desktop)
185
- // Ensure touch targets ≥ 44px for accessibility
186
- ```
187
-
188
- ## Type Safety
189
-
190
- ```typescript
191
- // ✅ Good: Proper typing with interfaces and generics
192
- interface Product {
193
- id: string;
194
- name: string;
195
- price: number;
196
- retailerId: string;
197
- }
198
-
199
- interface UseAsyncState<T> {
200
- data: T | null;
201
- loading: boolean;
202
- error: Error | null;
203
- }
204
-
205
- const useAsync = <T,>(fn: () => Promise<T>): UseAsyncState<T> => {
206
- // implementation
207
- return { data: null, loading: false, error: null };
208
- };
209
-
210
- // ❌ Bad: No typing, unclear props/returns
211
- const useAsync = (fn) => {
212
- return { data: null, loading: false, error: null };
213
- };
214
- ```
215
-
216
- ## Project Dependencies
217
-
218
- ```bash
219
- # Core (React 17 - DO NOT upgrade to 18)
220
- yarn add react@^17.0.2 react-dom@^17.0.2
221
-
222
- # State & Data Fetching
223
- yarn add react-query@^3.39.3 axios@^1.12.0
224
-
225
- # Styling
226
- yarn add @emotion/react @emotion/styled
227
- yarn add dh-component-library@5.37.47 store-components@^0.0.47
228
-
229
- # Routing (Dual setup required for portal integration)
230
- yarn add react-router-dom@5.1.2
231
- yarn add react-router-dom6@npm:react-router-dom@^6.2.1
232
-
233
- # Testing
234
- yarn add -D @testing-library/react @testing-library/user-event
235
- yarn add -D msw@^1.1.0 cypress dh-cypress-support
236
-
237
- # Utilities
238
- yarn add lodash ramda react-toastify file-saver react-error-boundary
239
- ```
240
-
241
- ## Project-Specific Patterns
242
-
243
- ### Context Cascading Order
244
- ```typescript
245
- App → ThemeProvider → UserContext → ConfigurationProvider → PostHogProvider
246
- → StoreMediaProvider → RegionsEstatesProvider → SelectedStoresProvider
247
- ```
248
-
249
- ### Multi-Step Navigation
250
- ```typescript
251
- // Each step: { title, route, component, provider, isValid?, subSteps? }
252
- const step = {
253
- title: t('stepTitle'),
254
- route: 'step-route',
255
- component: StepComponent,
256
- provider: StepProvider,
257
- };
258
- ```
259
-
260
- ### API Call Pattern
261
- ```typescript
262
- import { apiCall } from 'src/api/utils';
263
-
264
- export const getActivation = (id: string): Promise<Activation> =>
265
- apiCall({ url: `/api/booking-store/${id}`, method: 'GET' });
266
- ```
267
-
268
- ### Test ID Convention
269
- ```typescript
270
- export const DATA_TEST_ID = {
271
- stepName: {
272
- section: 'section-step-name',
273
- field: { fieldName: 'field-name' },
274
- button: { submit: 'step-submit-button' },
275
- },
276
- };
277
- ```
278
-
279
- ## Quick Checklist ✅
280
-
281
- - Imports: React → Third-party → Components → Hooks/Utils → Types
282
- - Props typed with interfaces
283
- - Components are `React.FC<Props>`
284
- - All handlers wrapped in `useCallback`
285
- - `useEffect` dependencies correct
286
- - Custom hooks follow naming: `use*`
287
- - State lifted appropriately or use Context
288
- - React Query for server state
289
- - Forms use validation
290
- - Error boundary wraps app/sections
291
- - Memoization with `React.memo()`, `useMemo()`, `useCallback()`
292
- - No `any` types
293
- - No side effects in render
294
-
295
- ## Common Mistakes ❌
296
-
297
- - Import order not organized
298
- - Missing TypeScript types
299
- - useCallback missing dependencies
300
- - useEffect infinite loops (missing/wrong dependencies)
301
- - Mixing server & client state management
302
- - Inline object/array in props (creates new refs each render)
303
- - Props mutation directly
304
- - useEffect with async directly (use wrapper function)
305
- - Missing error boundary
306
- - Not memoizing expensive computations
307
- - Rendering without keys in lists
308
- - `console.log()` left in production code
309
-
310
- ## Resources
311
-
312
- - [React Query v3](https://react-query-v3.tanstack.com)
313
- - [Emotion](https://emotion.sh)
314
- - [dh-component-library](https://dh-component-library-docs)
315
- - [TypeScript React](https://react-typescript-cheatsheet.netlify.app/)
@@ -1,373 +0,0 @@
1
- # React 17 Testing & Optimization Instructions
2
-
3
- **Scope**: Testing, Performance, Error Handling | **Version**: 3.0 | **Tags**: `testing`, `msw`, `performance`, `error-handling`
4
-
5
- **Related Files**: See [react-development.instructions.md](./react-development.instructions.md) for core patterns, [uiux.instructions.md](./uiux.instructions.md) for UI patterns
6
-
7
- ## Error Handling
8
-
9
- ### Error Boundary Pattern
10
- ```typescript
11
- import { ErrorBoundary } from 'react-error-boundary';
12
- import { ErrorPage } from 'dh-component-library';
13
- import { onErrorPopToast } from 'src/Hooks/query/helpers/onErrorPopToast';
14
- import { t } from 'src/I18n';
15
-
16
- // Wrap routes with ErrorBoundary
17
- <ErrorBoundary FallbackComponent={ErrorPage}>
18
- <Routes>...</Routes>
19
- </ErrorBoundary>
20
-
21
- // API error handling pattern
22
- const { data, isLoading } = useQuery(
23
- ['key', id, adAccountId],
24
- () => apiCall(),
25
- { onError: (err) => onErrorPopToast(err, t('errorMessageKey')) }
26
- );
27
-
28
- // Always use i18n keys for error messages, never hardcoded strings
29
- ```
30
-
31
- ## Suspense & Lazy Loading
32
-
33
- ```typescript
34
- const ProductDetails = React.lazy(() => import('./ProductDetails'));
35
-
36
- const App: React.FC = () => (
37
- <ErrorBoundary>
38
- <React.Suspense fallback={<div>Loading...</div>}>
39
- <ProductDetails productId="123" />
40
- </React.Suspense>
41
- </ErrorBoundary>
42
- );
43
- ```
44
-
45
- ## Memoization & Optimization
46
-
47
- ```typescript
48
- interface ProductListItemProps {
49
- product: Product;
50
- onSelect: (id: string) => void;
51
- }
52
-
53
- const ProductListItem = React.memo<ProductListItemProps>(({ product, onSelect }) => {
54
- const handleClick = React.useCallback(() => {
55
- onSelect(product.id);
56
- }, [product.id, onSelect]);
57
-
58
- const displayPrice = React.useMemo(() => {
59
- return `$${(product.price / 100).toFixed(2)}`;
60
- }, [product.price]);
61
-
62
- return (
63
- <div onClick={handleClick}>
64
- {product.name} - {displayPrice}
65
- </div>
66
- );
67
- });
68
-
69
- export default ProductListItem;
70
- ```
71
-
72
- ### When to Memoize
73
-
74
- **Use `React.memo`**:
75
- - Component renders often with same props
76
- - Component is expensive to render
77
- - Parent re-renders frequently
78
-
79
- **Use `useMemo`**:
80
- - Expensive calculations
81
- - Creating objects/arrays passed as props
82
- - Filtering/transforming large datasets
83
-
84
- **Use `useCallback`**:
85
- - Functions passed as props to memoized components
86
- - Functions in dependency arrays
87
- - Event handlers passed to child components
88
-
89
- **DON'T memoize**:
90
- - Cheap calculations
91
- - Primitive values
92
- - Components that always render with different props
93
-
94
- ## Testing (MSW + React Testing Library)
95
-
96
- ### Test Setup with MSW
97
- ```typescript
98
- import { render, screen } from 'src/test/app-test-utils';
99
- import { ComponentTestContainer } from 'src/test/app-test-utils';
100
- import { server } from 'src/test/msw-api/server/test-server';
101
- import { rest } from 'msw';
102
- import { DATA_TEST_ID } from 'src/test/generic-ids';
103
-
104
- describe('Component', () => {
105
- it('renders with context', () => {
106
- const mockActivation = { activationId: '123', name: 'Test' };
107
-
108
- render(
109
- <ComponentTestContainer state={{ activationMerged: mockActivation }}>
110
- <Component />
111
- </ComponentTestContainer>
112
- );
113
-
114
- expect(screen.getByTestId(DATA_TEST_ID.section.field)).toBeInTheDocument();
115
- });
116
-
117
- it('overrides MSW response', () => {
118
- server.use(
119
- rest.get('/api/booking-store/:id', (req, res, ctx) =>
120
- res(ctx.json({ data: 'override' }))
121
- )
122
- );
123
-
124
- render(<Component />);
125
- // Test with overridden response
126
- });
127
- });
128
-
129
- // Use app-test-utils for pre-configured providers
130
- // MSW automatically mocked in setupTests.ts
131
- ```
132
-
133
- ### Testing Hooks
134
- ```typescript
135
- import { renderHook, waitFor } from '@testing-library/react';
136
- import { QueryClient, QueryClientProvider } from 'react-query';
137
-
138
- describe('useProduct', () => {
139
- it('fetches product data', async () => {
140
- const queryClient = new QueryClient();
141
- const wrapper = ({ children }) => (
142
- <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
143
- );
144
-
145
- const { result } = renderHook(() => useProduct('123'), { wrapper });
146
-
147
- await waitFor(() => expect(result.current.loading).toBe(false));
148
- expect(result.current.product).toBeDefined();
149
- expect(result.current.error).toBeNull();
150
- });
151
- });
152
- ```
153
-
154
- ### Testing User Interactions
155
- ```typescript
156
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
157
- import userEvent from '@testing-library/user-event';
158
-
159
- describe('ProductForm', () => {
160
- it('submits form with valid data', async () => {
161
- const mockSubmit = jest.fn();
162
- render(<ProductForm onSubmit={mockSubmit} />);
163
-
164
- // Type into inputs
165
- await userEvent.type(screen.getByLabelText(/name/i), 'Test Product');
166
- await userEvent.type(screen.getByLabelText(/price/i), '99.99');
167
-
168
- // Click submit
169
- fireEvent.click(screen.getByRole('button', { name: /submit/i }));
170
-
171
- // Assert
172
- await waitFor(() => {
173
- expect(mockSubmit).toHaveBeenCalledWith({
174
- name: 'Test Product',
175
- price: 99.99,
176
- });
177
- });
178
- });
179
-
180
- it('shows validation errors', async () => {
181
- render(<ProductForm onSubmit={jest.fn()} />);
182
-
183
- // Submit without filling
184
- fireEvent.click(screen.getByRole('button', { name: /submit/i }));
185
-
186
- // Assert errors shown
187
- expect(await screen.findByText(/name is required/i)).toBeInTheDocument();
188
- });
189
- });
190
- ```
191
-
192
- ### Testing Async Operations
193
- ```typescript
194
- describe('ProductList', () => {
195
- it('shows loading state', () => {
196
- render(<ProductList />);
197
- expect(screen.getByTestId(DATA_TEST_ID.loadingSpinner)).toBeInTheDocument();
198
- });
199
-
200
- it('displays products after loading', async () => {
201
- render(<ProductList />);
202
-
203
- await waitFor(() => {
204
- expect(screen.queryByTestId(DATA_TEST_ID.loadingSpinner)).not.toBeInTheDocument();
205
- });
206
-
207
- expect(screen.getByText(/Product 1/i)).toBeInTheDocument();
208
- expect(screen.getByText(/Product 2/i)).toBeInTheDocument();
209
- });
210
-
211
- it('shows error message on failure', async () => {
212
- server.use(
213
- rest.get('/api/products', (req, res, ctx) =>
214
- res(ctx.status(500), ctx.json({ error: 'Server error' }))
215
- )
216
- );
217
-
218
- render(<ProductList />);
219
-
220
- expect(await screen.findByText(/failed to load/i)).toBeInTheDocument();
221
- });
222
- });
223
- ```
224
-
225
- ## Performance Best Practices
226
-
227
- ### Code Splitting
228
- ```typescript
229
- // Lazy load step components
230
- const LazyStepComponent = lazy(() => import('./StepComponent'));
231
-
232
- // Use in router
233
- <Route
234
- path="/step"
235
- element={
236
- <Suspense fallback={<LoadingSpinner />}>
237
- <LazyStepComponent />
238
- </Suspense>
239
- }
240
- />
241
- ```
242
-
243
- ### Bundle Optimization
244
- ```typescript
245
- // ✅ Tree-shakeable imports
246
- import { Button } from 'dh-component-library';
247
- import { map, filter } from 'lodash';
248
-
249
- // ❌ Avoid full imports
250
- import * as DH from 'dh-component-library';
251
- import _ from 'lodash';
252
- ```
253
-
254
- ### Virtualization for Long Lists
255
- ```typescript
256
- import { FixedSizeList } from 'react-window';
257
-
258
- const VirtualizedList: React.FC<{ items: Product[] }> = ({ items }) => (
259
- <FixedSizeList
260
- height={600}
261
- itemCount={items.length}
262
- itemSize={50}
263
- width="100%"
264
- >
265
- {({ index, style }) => (
266
- <div style={style}>{items[index].name}</div>
267
- )}
268
- </FixedSizeList>
269
- );
270
- ```
271
-
272
- ### Debouncing & Throttling
273
- ```typescript
274
- import { useMemo } from 'react';
275
- import debounce from 'lodash/debounce';
276
-
277
- const SearchInput: React.FC = () => {
278
- const [searchTerm, setSearchTerm] = useState('');
279
-
280
- const debouncedSearch = useMemo(
281
- () => debounce((value: string) => {
282
- // API call here
283
- console.log('Searching for:', value);
284
- }, 300),
285
- []
286
- );
287
-
288
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
289
- setSearchTerm(e.target.value);
290
- debouncedSearch(e.target.value);
291
- };
292
-
293
- return <input value={searchTerm} onChange={handleChange} />;
294
- };
295
- ```
296
-
297
- ## Common Performance Issues
298
-
299
- ### ❌ Creating Functions in Render
300
- ```typescript
301
- // ❌ BAD: New function every render
302
- <Button onClick={() => handleClick(id)} />
303
-
304
- // ✅ GOOD: Memoized with useCallback
305
- const handleButtonClick = useCallback(() => handleClick(id), [id]);
306
- <Button onClick={handleButtonClick} />
307
- ```
308
-
309
- ### ❌ Creating Objects/Arrays in Render
310
- ```typescript
311
- // ❌ BAD: New array every render
312
- <Component items={products.filter(p => p.active)} />
313
-
314
- // ✅ GOOD: Memoized with useMemo
315
- const activeProducts = useMemo(() => products.filter(p => p.active), [products]);
316
- <Component items={activeProducts} />
317
- ```
318
-
319
- ### ❌ Not Memoizing Expensive Calculations
320
- ```typescript
321
- // ❌ BAD: Recalculates every render
322
- const total = products.reduce((sum, p) => sum + p.price, 0);
323
-
324
- // ✅ GOOD: Memoized
325
- const total = useMemo(() =>
326
- products.reduce((sum, p) => sum + p.price, 0),
327
- [products]
328
- );
329
- ```
330
-
331
- ## Testing Checklist ✅
332
-
333
- - All components have test coverage
334
- - User interactions tested with userEvent
335
- - Async operations tested with waitFor
336
- - Error states tested
337
- - Loading states tested
338
- - MSW handlers for all API calls
339
- - Test IDs used for querying elements
340
- - No hardcoded waits (use waitFor)
341
- - Tests isolated (no shared state)
342
- - Cleanup after tests
343
-
344
- ## Performance Checklist ✅
345
-
346
- - Lazy load route components
347
- - Memoize expensive calculations
348
- - Use React.memo for frequently re-rendering components
349
- - useCallback for functions in dependency arrays
350
- - Code splitting for large features
351
- - Tree-shakeable imports
352
- - Virtualization for long lists
353
- - Debounce user input
354
- - Optimize images (lazy loading, compression)
355
-
356
- ## Common Testing Mistakes ❌
357
-
358
- - Using `getBy*` instead of `findBy*` for async elements
359
- - Not waiting for async operations
360
- - Testing implementation details instead of behavior
361
- - Hardcoded waits (`setTimeout`)
362
- - Not cleaning up after tests
363
- - Missing act() warnings
364
- - Not mocking external dependencies
365
- - Snapshot tests for everything
366
-
367
- ## Resources
368
-
369
- - [React Testing Library](https://testing-library.com/react)
370
- - [MSW](https://mswjs.io/docs)
371
- - [React Query Testing](https://react-query-v3.tanstack.com/guides/testing)
372
- - [React Performance](https://reactjs.org/docs/optimizing-performance.html)
373
- - [Web.dev React Performance](https://web.dev/react/)