@hiver/skills 1.0.1
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/README.md +394 -0
- package/build.js +15 -0
- package/collections/common/skills/create-prd/SKILL.md +43 -0
- package/collections/common/skills/discuss-problem/SKILL.md +55 -0
- package/collections/common/skills/explore-codebase/SKILL.md +79 -0
- package/collections/common/skills/prd-to-milestone/SKILL.md +40 -0
- package/collections/common/skills/refractor-and-clean/SKILL.md +41 -0
- package/collections/common/skills/solve-issue/SKILL.md +31 -0
- package/collections/common/skills/write-test-cases/SKILL.md +93 -0
- package/collections/web/agents/build-feature.md +42 -0
- package/collections/web/agents/build-milestone.md +61 -0
- package/collections/web/skills/build-component/SKILL.md +66 -0
- package/collections/web/skills/build-component/palette/README.md +3 -0
- package/collections/web/skills/build-component/palette/colors.md +87 -0
- package/collections/web/skills/build-component/references/best-practices.md +8 -0
- package/collections/web/skills/build-component/references/component-organization.md +21 -0
- package/collections/web/skills/build-component/references/figma-integration.md +5 -0
- package/collections/web/skills/build-component/references/icons.md +8 -0
- package/collections/web/skills/build-component/references/layout-and-structure.md +8 -0
- package/collections/web/skills/build-component/references/state-management.md +25 -0
- package/collections/web/skills/build-component/references/ui-kit-and-styling.md +13 -0
- package/collections/web/skills/build-component/typography/README.md +3 -0
- package/collections/web/skills/build-component/typography/variants.md +79 -0
- package/collections/web/skills/connect-api/SKILL.md +23 -0
- package/collections/web/skills/connect-api/references/api-call-structure.md +84 -0
- package/collections/web/skills/connect-api/references/best-practices.md +10 -0
- package/collections/web/skills/connect-api/references/data-fetchers.md +52 -0
- package/collections/web/skills/connect-api/references/error-handling.md +34 -0
- package/collections/web/skills/connect-api/references/hook-organization.md +11 -0
- package/collections/web/skills/connect-api/references/query-keys.md +21 -0
- package/collections/web/skills/connect-api/references/react-query-usage.md +83 -0
- package/collections/web/skills/connect-api/references/url-placeholders.md +17 -0
- package/collections/web/skills/scaffold-react-feature/SKILL.md +208 -0
- package/dist/cli.js +990 -0
- package/package.json +33 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# API Call Structure
|
|
2
|
+
|
|
3
|
+
## For Modern Auth Fetchers (HiverNewAuthCall based)
|
|
4
|
+
|
|
5
|
+
- **Use async/await pattern** with error handling that returns `[data, null]` or `[null, error]`:
|
|
6
|
+
- Applies to: `AiBifrostAuth`, `CustomFieldsService`, `AutomationService`, `HiverSlaNewAuth`, `CsatNewAuthCall`, `TagBifrostAuth`, `HiverIntegrationBifrostAuth`, `HiverChatBifrostAuth`, `HiverNotesAxios`, `OutOfOfficeService`, `CustomerPortalApiService`, `HiverNotifCall`
|
|
7
|
+
```javascript
|
|
8
|
+
import { AiBifrostAuth } from "../../../../utils/AiNewAuth";
|
|
9
|
+
// OR any other modern auth fetcher
|
|
10
|
+
|
|
11
|
+
const fetchData = async (param) => {
|
|
12
|
+
try {
|
|
13
|
+
const response = await AiBifrostAuth({
|
|
14
|
+
url: `/v1/endpoint?param=${param}`,
|
|
15
|
+
method: "GET", // or "POST", "PUT", "PATCH", "DELETE"
|
|
16
|
+
data: {}, // For POST/PUT/PATCH
|
|
17
|
+
});
|
|
18
|
+
return [response.data, null];
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return [null, err.message];
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## For createAxios based Fetchers
|
|
26
|
+
|
|
27
|
+
- **Use async/await pattern** with error handling:
|
|
28
|
+
- Applies to: `AiAxios`, `HiverCustomerPortalAxios`
|
|
29
|
+
```javascript
|
|
30
|
+
import { AiAxios } from "../../../../utils/AiAxios";
|
|
31
|
+
|
|
32
|
+
const fetchData = async (param) => {
|
|
33
|
+
try {
|
|
34
|
+
const response = await AiAxios({
|
|
35
|
+
url: `/v1/endpoint?param=${param}`,
|
|
36
|
+
method: "GET", // or "POST", "PUT", "PATCH", "DELETE"
|
|
37
|
+
data: {}, // For POST/PUT/PATCH
|
|
38
|
+
});
|
|
39
|
+
return [response.data, null];
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return [null, err.message];
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## For Legacy HiverAxios
|
|
47
|
+
|
|
48
|
+
- **Use async/await with try/catch**:
|
|
49
|
+
```javascript
|
|
50
|
+
import { HiverAxios } from "h-extension-common";
|
|
51
|
+
|
|
52
|
+
const fetchData = async (payload) => {
|
|
53
|
+
try {
|
|
54
|
+
const response = await HiverAxios({
|
|
55
|
+
url: `/api/endpoint`,
|
|
56
|
+
type: "GET", // or "POST", "PUT", "DELETE" - Note: uses "type" not "method"
|
|
57
|
+
params: payload, // Request body/params
|
|
58
|
+
});
|
|
59
|
+
return [response.data, null];
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return [null, err.message];
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## For HiverAnalyticsAxios
|
|
67
|
+
|
|
68
|
+
- **Use async/await with try/catch**:
|
|
69
|
+
```javascript
|
|
70
|
+
import { HiverAnalyticsAxios } from "../../../../utils/HiverAnalyticsAxios";
|
|
71
|
+
|
|
72
|
+
const fetchData = async (payload) => {
|
|
73
|
+
try {
|
|
74
|
+
const response = await HiverAnalyticsAxios({
|
|
75
|
+
url: `/api/endpoint`,
|
|
76
|
+
type: "GET", // or "POST", "PUT", "PATCH", "DELETE"
|
|
77
|
+
params: payload, // Request body/params
|
|
78
|
+
});
|
|
79
|
+
return [response.parsedBody, null]; // Note: HiverAnalyticsAxios returns parsedBody
|
|
80
|
+
} catch (err) {
|
|
81
|
+
return [null, err.message];
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Best Practices
|
|
2
|
+
|
|
3
|
+
- **Keep API functions pure** - separate API logic from React Query hooks
|
|
4
|
+
- **Use TypeScript types** if the project uses TypeScript
|
|
5
|
+
- **Add JSDoc comments** for complex hooks explaining parameters and return values
|
|
6
|
+
- **Handle loading states** using `isLoading`, `isFetching` from React Query
|
|
7
|
+
- **Handle error states** using `isError`, `error` from React Query
|
|
8
|
+
- **Use `enabled` option** for conditional queries
|
|
9
|
+
- **Use `refetchOnMount`, `refetchOnWindowFocus`** options when needed
|
|
10
|
+
- **Export query keys** from hook files for use in other components
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Data Fetcher Selection
|
|
2
|
+
|
|
3
|
+
- **All data fetcher utilities are located in `react-extn/src/utils`**
|
|
4
|
+
- **Use the appropriate fetcher** based on the service you're working with:
|
|
5
|
+
|
|
6
|
+
## Modern Auth Fetchers (HiverNewAuthCall based)
|
|
7
|
+
|
|
8
|
+
These use `method` (GET, POST, PUT, PATCH, DELETE) and return promises:
|
|
9
|
+
|
|
10
|
+
| Fetcher | Use Case | Service | Import Path |
|
|
11
|
+
|---------|----------|---------|-------------|
|
|
12
|
+
| `AiBifrostAuth` | AI service endpoints | AI Service | `../../../../utils/AiNewAuth` |
|
|
13
|
+
| `CustomFieldsService` | Custom fields operations | Custom Fields Service | `../../../../utils/CustomFieldsService` |
|
|
14
|
+
| `AutomationService` | Automation workflows | Automations Service | `../../../../utils/AutomationService` |
|
|
15
|
+
| `HiverSlaNewAuth` | SLA management | SLA Service | `../../../../utils/slaNewAuth` |
|
|
16
|
+
| `CsatNewAuthCall` | CSAT surveys | CSAT Service | `../../../../utils/csatNewAuth` |
|
|
17
|
+
| `TagBifrostAuth` | Tag operations | Tags Service | `../../../../utils/tagNewAuth` |
|
|
18
|
+
| `HiverIntegrationBifrostAuth` | Integration endpoints | Integration Service | `../../../../utils/integrationNewAuth` |
|
|
19
|
+
| `HiverChatBifrostAuth` | Chat/channels operations | Channels Service | `../../../../utils/chatNewAuth` |
|
|
20
|
+
| `HiverNotesAxios` | Notes operations | Notes Service | `../../../../utils/HiverNotesNewAuth` |
|
|
21
|
+
| `OutOfOfficeService` | Schedule management | Schedule Service | `../../../../utils/OutOfOfficeService` |
|
|
22
|
+
| `CustomerPortalApiService` | Customer portal | Customer Portal Service | `../../../../utils/CustomerPortalApiService` |
|
|
23
|
+
| `HiverNotifCall` | Notifications | Notifications Service | `../../../../utils/notificationAuthCall` |
|
|
24
|
+
|
|
25
|
+
**Usage pattern for modern auth fetchers:**
|
|
26
|
+
```javascript
|
|
27
|
+
import { AiBifrostAuth } from "../../../../utils/AiNewAuth";
|
|
28
|
+
// OR
|
|
29
|
+
import { CustomFieldsService } from "../../../../utils/CustomFieldsService";
|
|
30
|
+
|
|
31
|
+
const response = await AiBifrostAuth({
|
|
32
|
+
url: `/v1/endpoint`,
|
|
33
|
+
method: "GET", // or "POST", "PUT", "PATCH", "DELETE"
|
|
34
|
+
data: {}, // For POST/PUT/PATCH
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## createAxios based Fetchers
|
|
39
|
+
|
|
40
|
+
These use `method` and return promises:
|
|
41
|
+
|
|
42
|
+
| Fetcher | Use Case | Service | Import Path |
|
|
43
|
+
|---------|----------|---------|-------------|
|
|
44
|
+
| `AiAxios` | AI service (legacy) | AI Service | `../../../../utils/AiAxios` |
|
|
45
|
+
| `HiverCustomerPortalAxios` | Customer portal (legacy) | Customer Portal Service | `../../../../utils/CustomerPortalAxios` |
|
|
46
|
+
|
|
47
|
+
## Legacy/Custom Fetchers
|
|
48
|
+
|
|
49
|
+
| Fetcher | Use Case | Service | Import Path |
|
|
50
|
+
|---------|----------|---------|-------------|
|
|
51
|
+
| `HiverAxios` | Legacy API calls | Default Hiver API | `h-extension-common` |
|
|
52
|
+
| `HiverAnalyticsAxios` | Analytics data | Analytics Service | `../../../../utils/HiverAnalyticsAxios` |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
- **Handle errors in mutation hooks**:
|
|
4
|
+
```javascript
|
|
5
|
+
import { useSnackbar } from "../../../../common";
|
|
6
|
+
import { theme } from "@hiver/hiver-ui-kit";
|
|
7
|
+
|
|
8
|
+
const showSnackbar = useSnackbar();
|
|
9
|
+
|
|
10
|
+
// In mutation onSuccess handler
|
|
11
|
+
onSuccess: (response) => {
|
|
12
|
+
const [result, error] = response;
|
|
13
|
+
if (error) {
|
|
14
|
+
showSnackbar({
|
|
15
|
+
message: error || "Operation failed",
|
|
16
|
+
background: theme.palette.red.primary,
|
|
17
|
+
});
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// Handle success...
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// In mutation onError handler (for unexpected errors)
|
|
24
|
+
onError: (error, variables, context) => {
|
|
25
|
+
showSnackbar({
|
|
26
|
+
message: error.message || "Operation failed",
|
|
27
|
+
background: theme.palette.red.primary,
|
|
28
|
+
});
|
|
29
|
+
// Rollback optimistic updates if needed
|
|
30
|
+
if (context?.previousData) {
|
|
31
|
+
queryClient.setQueryData([API_QUERIES.resource], context.previousData);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Hook Organization
|
|
2
|
+
|
|
3
|
+
- **Always create separate custom hooks** for querying and mutating server data
|
|
4
|
+
- **Place hooks in a `hooks` folder** within the module/component directory
|
|
5
|
+
- Example structure: `react-extn/src/AiCopilot/Pages/Chat/hooks/` or `react-extn/src/AiCopilot/Pages/Tags/hooks/`
|
|
6
|
+
- **Naming convention**:
|
|
7
|
+
- Query hooks: `useGet[ResourceName].js` (e.g., `useGetChatList.js`, `useGetSuggestedTags.js`)
|
|
8
|
+
- Mutation hooks: `use[Action][ResourceName].js` (e.g., `useSendChatMessage.js`, `useApplyTags.js`)
|
|
9
|
+
- **Reference examples**:
|
|
10
|
+
- `react-extn/src/AiCopilot/Pages/Chat/hooks/useGetChatList.js`
|
|
11
|
+
- `react-extn/src/AiCopilot/Pages/Tags/hooks/useGetSuggestedTags.js`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Query Key and Mutation Key Management
|
|
2
|
+
|
|
3
|
+
- **Never hardcode query/mutation keys** directly in hook files
|
|
4
|
+
- **Always define keys in a constants file** for the module
|
|
5
|
+
- Create or use existing `constants/queries.js` file in the module directory
|
|
6
|
+
- Example: `react-extn/src/AiCopilot/Pages/Chat/constants/queries.js` or `react-extn/src/AiCopilot/Pages/Tags/constants/queries.js`
|
|
7
|
+
- **Naming convention**: Use descriptive constant names in an `API_QUERIES` object
|
|
8
|
+
```javascript
|
|
9
|
+
// In constants/queries.js file
|
|
10
|
+
export const API_QUERIES = {
|
|
11
|
+
list: 'ai_chat_list',
|
|
12
|
+
send: 'ai_chat_message',
|
|
13
|
+
tagsList: 'ai_tags_list',
|
|
14
|
+
};
|
|
15
|
+
```
|
|
16
|
+
- **Include dynamic parameters** in query keys for proper cache invalidation:
|
|
17
|
+
```javascript
|
|
18
|
+
queryKey: [API_QUERIES.list, smIdsKey, conversationId]
|
|
19
|
+
// OR for single parameter
|
|
20
|
+
queryKey: [API_QUERIES.tagsList, String(smId), String(conversationId)]
|
|
21
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# React Query Usage
|
|
2
|
+
|
|
3
|
+
## For Data Fetching (Queries)
|
|
4
|
+
|
|
5
|
+
- **Use `useQuery` from `@tanstack/react-query`** for GET requests
|
|
6
|
+
- **Structure**:
|
|
7
|
+
```javascript
|
|
8
|
+
import { useQuery } from "@tanstack/react-query";
|
|
9
|
+
import { AiBifrostAuth } from "../../../../utils/AiNewAuth"; // For AI service
|
|
10
|
+
// OR
|
|
11
|
+
import { HiverAxios } from "h-extension-common"; // For legacy API calls
|
|
12
|
+
|
|
13
|
+
// Import query keys from constants (see section 3)
|
|
14
|
+
import { API_QUERIES } from '../constants/queries';
|
|
15
|
+
|
|
16
|
+
// API function - returns [data, null] on success or [null, error] on failure
|
|
17
|
+
const fetchResource = async (param1, param2) => {
|
|
18
|
+
const response = await AiBifrostAuth({
|
|
19
|
+
url: `/v1/resource?param=${param1}`,
|
|
20
|
+
method: "GET",
|
|
21
|
+
});
|
|
22
|
+
return response.data;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Custom hook
|
|
26
|
+
export const useGetResource = ({ param1, param2, enabled = true }) => {
|
|
27
|
+
return useQuery({
|
|
28
|
+
queryKey: [API_QUERIES.resource, param1, param2],
|
|
29
|
+
queryFn: () => fetchResource(param1, param2),
|
|
30
|
+
enabled: enabled && param1 !== undefined,
|
|
31
|
+
refetchOnMount: true, // Optional
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## For Data Mutations
|
|
37
|
+
|
|
38
|
+
- **Use `useMutation` from `@tanstack/react-query`** for POST, PUT, PATCH, DELETE requests
|
|
39
|
+
- **Structure**:
|
|
40
|
+
```javascript
|
|
41
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
42
|
+
import { AiBifrostAuth } from "../../../../utils/AiNewAuth"; // For AI service
|
|
43
|
+
// OR
|
|
44
|
+
import { HiverAxios } from "h-extension-common"; // For legacy API calls
|
|
45
|
+
import { useSnackbar } from "../../../../common";
|
|
46
|
+
import { theme } from "@hiver/hiver-ui-kit";
|
|
47
|
+
import { API_QUERIES } from '../constants/queries';
|
|
48
|
+
|
|
49
|
+
// API function - returns [data, null] on success or [null, error] on failure
|
|
50
|
+
const updateResource = async (id, data) => {
|
|
51
|
+
|
|
52
|
+
const response = await AiBifrostAuth({
|
|
53
|
+
url: `/v1/resource/${id}`,
|
|
54
|
+
method: "POST",
|
|
55
|
+
data: data,
|
|
56
|
+
});
|
|
57
|
+
return response.data
|
|
58
|
+
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Custom hook
|
|
62
|
+
export const useUpdateResource = () => {
|
|
63
|
+
const queryClient = useQueryClient();
|
|
64
|
+
const showSnackbar = useSnackbar();
|
|
65
|
+
|
|
66
|
+
return useMutation({
|
|
67
|
+
mutationFn: ({ id, data }) => updateResource(id, data),
|
|
68
|
+
onSuccess: (response) => {
|
|
69
|
+
// Invalidate related queries
|
|
70
|
+
queryClient.invalidateQueries({ queryKey: [API_QUERIES.resource] });
|
|
71
|
+
showSnackbar({
|
|
72
|
+
message: "Resource updated successfully",
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
onError: () => {
|
|
76
|
+
showSnackbar({
|
|
77
|
+
message: "Failed to update resource",
|
|
78
|
+
background: theme.palette.red.primary,
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# URL and Method Placeholders
|
|
2
|
+
|
|
3
|
+
- **Keep placeholders** for URL and HTTP method when creating hooks
|
|
4
|
+
- **Format**: Use clear comments indicating where to fill in:
|
|
5
|
+
```javascript
|
|
6
|
+
const fetchData = async (id) => {
|
|
7
|
+
try {
|
|
8
|
+
const response = await AiBifrostAuth({
|
|
9
|
+
url: `/v1/resource/${id}`, // TODO: Update endpoint URL
|
|
10
|
+
method: "GET", // TODO: Update HTTP method (GET, POST, PUT, PATCH, DELETE)
|
|
11
|
+
});
|
|
12
|
+
return [response.data, null];
|
|
13
|
+
} catch (err) {
|
|
14
|
+
return [null, err.message];
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
```
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scaffold-react-feature
|
|
3
|
+
description: Generate standard feature folder structure and boilerplate for new React features/modules. Internal skill — used by build-milestone agent, not invoked directly by users.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Scaffold Feature
|
|
7
|
+
|
|
8
|
+
**Trigger:** Only when building a **new feature or module**. Do NOT use for bug fixes.
|
|
9
|
+
|
|
10
|
+
**Used by:** `build-milestone` agent internally. Not invoked directly by users.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Step 1 — Determine Structure Needs
|
|
15
|
+
|
|
16
|
+
Before creating anything, infer from the milestone/context:
|
|
17
|
+
|
|
18
|
+
1. **Does the module have multiple distinct views?**
|
|
19
|
+
- Yes → use `pages/` folder + internal routing in `index.js`
|
|
20
|
+
- No → write module logic directly in `index.js`
|
|
21
|
+
|
|
22
|
+
2. **How is the module consumed?** (this is the consumer's concern, not the scaffold's)
|
|
23
|
+
- As a route component somewhere in the app
|
|
24
|
+
- Directly imported inside another component
|
|
25
|
+
- Either way, the internal structure is the same
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Step 2 — Folder Structure
|
|
30
|
+
|
|
31
|
+
**Only create a directory if there is real content for it.** The list below is the ceiling, not the floor. A small feature may only need `index.js`, `constants.js`, and a hook file.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
<FeatureName>/
|
|
35
|
+
index.js ← entry point (routing + providers, OR direct module logic if no pages)
|
|
36
|
+
constants.js ← query keys, route paths, enums, UI strings
|
|
37
|
+
utils.js ← pure helper functions (only if needed)
|
|
38
|
+
components/ ← shared UI components reused across pages (only if needed)
|
|
39
|
+
hooks/ ← API hooks and custom hooks (only if needed)
|
|
40
|
+
context/ ← React context + useReducer (only if local state is too complex for useState)
|
|
41
|
+
pages/ ← sub-page components (only if module has multiple distinct views)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Each page inside `pages/` is an independent mini-module and can recursively follow the same structure.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Step 3 — `index.js` Patterns
|
|
49
|
+
|
|
50
|
+
### With pages (routing)
|
|
51
|
+
```js
|
|
52
|
+
import React from 'react';
|
|
53
|
+
import { Route, Switch, useRouteMatch } from 'react-router-dom';
|
|
54
|
+
import Boundary from '../../common/ErrorBoundaries/Boundary';
|
|
55
|
+
import { XxxProvider } from './context/XxxContext'; // only if context exists
|
|
56
|
+
import XxxListPage from './pages/XxxListPage';
|
|
57
|
+
import XxxDetailPage from './pages/XxxDetailPage';
|
|
58
|
+
|
|
59
|
+
const Xxx = () => {
|
|
60
|
+
const match = useRouteMatch();
|
|
61
|
+
return (
|
|
62
|
+
<Boundary fallbackType="default">
|
|
63
|
+
<XxxProvider> {/* only if context exists */}
|
|
64
|
+
<Switch>
|
|
65
|
+
<Route exact path={match.path} component={XxxListPage} />
|
|
66
|
+
<Route path={`${match.path}/:id`} component={XxxDetailPage} />
|
|
67
|
+
</Switch>
|
|
68
|
+
</XxxProvider>
|
|
69
|
+
</Boundary>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default Xxx;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Without pages (no routing)
|
|
77
|
+
```js
|
|
78
|
+
import React from 'react';
|
|
79
|
+
import Boundary from '../../common/ErrorBoundaries/Boundary';
|
|
80
|
+
|
|
81
|
+
const Xxx = () => {
|
|
82
|
+
// module logic lives here directly
|
|
83
|
+
return (
|
|
84
|
+
<Boundary fallbackType="default">
|
|
85
|
+
{/* component UI */}
|
|
86
|
+
</Boundary>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default Xxx;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Step 4 — Hook Conventions (`hooks/`)
|
|
96
|
+
|
|
97
|
+
- **API hooks** → file named `useXxxApi.js`. Contains `useQuery` and/or `useMutation`. Both can live in the same file. API call function defined in the same file.
|
|
98
|
+
- **Custom hooks** → file named `useXxx.js`. Contains business logic, derived state, orchestration.
|
|
99
|
+
- No separate `api/` folder — API calls live inside the hook file directly.
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
// hooks/useXxxApi.js
|
|
103
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
104
|
+
import { showMessage } from 'h-commons';
|
|
105
|
+
import { HiverAxiosInstance } from '../../../common';
|
|
106
|
+
import { QUERY_KEYS } from '../constants';
|
|
107
|
+
|
|
108
|
+
const fetchXxxList = (params) =>
|
|
109
|
+
HiverAxiosInstance({ url: '/api/v1/xxx', method: 'GET', params });
|
|
110
|
+
|
|
111
|
+
export const useXxxListApi = (id) =>
|
|
112
|
+
useQuery({
|
|
113
|
+
queryKey: [QUERY_KEYS.XXX_LIST, id],
|
|
114
|
+
queryFn: () => fetchXxxList({ id }),
|
|
115
|
+
enabled: !!id,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const createXxx = (payload) =>
|
|
119
|
+
HiverAxiosInstance({ url: '/api/v1/xxx', method: 'POST', data: payload });
|
|
120
|
+
|
|
121
|
+
export const useXxxCreateApi = (id, onSuccess) => {
|
|
122
|
+
const queryClient = useQueryClient();
|
|
123
|
+
return useMutation({
|
|
124
|
+
mutationFn: createXxx,
|
|
125
|
+
onSuccess: () => {
|
|
126
|
+
showMessage({ message: 'Created successfully' });
|
|
127
|
+
if (typeof onSuccess === 'function') onSuccess();
|
|
128
|
+
},
|
|
129
|
+
onSettled: () => {
|
|
130
|
+
queryClient.invalidateQueries([QUERY_KEYS.XXX_LIST, id]);
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Step 5 — Context (optional)
|
|
139
|
+
|
|
140
|
+
Only add when local state is shared across multiple components and is too complex for `useState`.
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
// context/XxxContext.js
|
|
144
|
+
import React, { useMemo, useReducer } from 'react';
|
|
145
|
+
|
|
146
|
+
const initialState = { fieldA: '', fieldB: null };
|
|
147
|
+
|
|
148
|
+
const xxxReducer = (state, action) => {
|
|
149
|
+
switch (action.type) {
|
|
150
|
+
case 'UPDATE_FIELD': return { ...state, ...action.payload };
|
|
151
|
+
case 'RESET': return initialState;
|
|
152
|
+
default: return state;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const XxxContext = React.createContext();
|
|
157
|
+
|
|
158
|
+
export const useXxxContext = () => React.useContext(XxxContext);
|
|
159
|
+
|
|
160
|
+
export const XxxProvider = ({ children }) => {
|
|
161
|
+
const [state, dispatch] = useReducer(xxxReducer, initialState);
|
|
162
|
+
const value = useMemo(() => ({
|
|
163
|
+
state,
|
|
164
|
+
updateField: (payload) => dispatch({ type: 'UPDATE_FIELD', payload }),
|
|
165
|
+
reset: () => dispatch({ type: 'RESET' }),
|
|
166
|
+
}), [state]);
|
|
167
|
+
|
|
168
|
+
return <XxxContext.Provider value={value}>{children}</XxxContext.Provider>;
|
|
169
|
+
};
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Step 6 — Feature Flag (if needed)
|
|
175
|
+
|
|
176
|
+
**1. Add key** to `modules/common/constants/featureConstants.js`:
|
|
177
|
+
```js
|
|
178
|
+
export const XXX_FEATURE_KEY = 'rls-xxx-feature-name'; // rls- for release, ftr- for feature
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**2. Add checker** to `modules/common/helpers/featureKeys.js`:
|
|
182
|
+
```js
|
|
183
|
+
export const isXxxFeatureEnabled = (availableFeatures) =>
|
|
184
|
+
isFeatureEnabled(availableFeatures, XXX_FEATURE_KEY);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**3. Gate in component:**
|
|
188
|
+
```js
|
|
189
|
+
import { useSelector } from 'react-redux';
|
|
190
|
+
import { availableFeaturesSelector } from 'h-commons';
|
|
191
|
+
import { isXxxFeatureEnabled } from '../../common/helpers/featureKeys';
|
|
192
|
+
|
|
193
|
+
const availableFeatures = useSelector(availableFeaturesSelector);
|
|
194
|
+
if (!isXxxFeatureEnabled(availableFeatures)) return null;
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Checklist
|
|
200
|
+
|
|
201
|
+
- [ ] Folder created only with directories that have real content
|
|
202
|
+
- [ ] `index.js` set up — routing if multiple views, direct logic if single view
|
|
203
|
+
- [ ] `constants.js` created with query keys, route paths, enums
|
|
204
|
+
- [ ] API hooks in `hooks/useXxxApi.js`, custom hooks in `hooks/useXxx.js`
|
|
205
|
+
- [ ] `context/` added only if local state is too complex for `useState`
|
|
206
|
+
- [ ] `pages/` added only if module has multiple distinct views
|
|
207
|
+
- [ ] Feature flag key added to `featureConstants.js` (if needed)
|
|
208
|
+
- [ ] Feature flag checker added to `featureKeys.js` (if needed)
|