ginskill-init 2.7.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/.wrangler/cache/pages.json +4 -0
- package/.wrangler/cache/wrangler-account.json +6 -0
- package/DEVELOPMENT.md +510 -0
- package/README.md +104 -0
- package/agents/developer.md +56 -0
- package/agents/frontend-design.md +69 -0
- package/agents/mobile-reviewer.md +36 -0
- package/agents/review-code.md +49 -0
- package/agents/security-scanner.md +50 -0
- package/agents/tester.md +72 -0
- package/bin/cli.js +461 -0
- package/landing/ai-build-ai.png +0 -0
- package/landing/index.html +1495 -0
- package/landing/logo.png +0 -0
- package/package.json +37 -0
- package/skills/active-life-dev/SKILL.md +157 -0
- package/skills/active-life-dev/docs/auth.md +187 -0
- package/skills/active-life-dev/docs/customers.md +216 -0
- package/skills/active-life-dev/docs/integrations.md +209 -0
- package/skills/active-life-dev/docs/inventory.md +192 -0
- package/skills/active-life-dev/docs/modules.md +181 -0
- package/skills/active-life-dev/docs/orders.md +180 -0
- package/skills/active-life-dev/docs/patterns.md +319 -0
- package/skills/active-life-dev/docs/products.md +216 -0
- package/skills/active-life-dev/docs/schema.md +502 -0
- package/skills/active-life-dev/docs/setup.md +169 -0
- package/skills/active-life-dev/docs/vouchers.md +144 -0
- package/skills/ai-asset-generator/SKILL.md +247 -0
- package/skills/ai-asset-generator/docs/gen-image.md +274 -0
- package/skills/ai-asset-generator/docs/genvideo.md +341 -0
- package/skills/ai-asset-generator/docs/remove-background.md +19 -0
- package/skills/ai-asset-generator/lib/bg-remove.mjs +34 -0
- package/skills/ai-asset-generator/lib/env.mjs +48 -0
- package/skills/ai-asset-generator/lib/kie-client.mjs +100 -0
- package/skills/ai-build-ai/SKILL.md +127 -0
- package/skills/ai-build-ai/docs/agent-teams.md +293 -0
- package/skills/ai-build-ai/docs/checkpointing.md +161 -0
- package/skills/ai-build-ai/docs/create-agent.md +399 -0
- package/skills/ai-build-ai/docs/create-mcp.md +395 -0
- package/skills/ai-build-ai/docs/create-skill.md +299 -0
- package/skills/ai-build-ai/docs/headless-mode.md +614 -0
- package/skills/ai-build-ai/docs/hooks.md +578 -0
- package/skills/ai-build-ai/docs/memory-claude-md.md +375 -0
- package/skills/ai-build-ai/docs/output-styles.md +208 -0
- package/skills/ai-build-ai/docs/overview.md +162 -0
- package/skills/ai-build-ai/docs/permissions.md +391 -0
- package/skills/ai-build-ai/docs/plugins.md +396 -0
- package/skills/ai-build-ai/docs/sandbox.md +262 -0
- package/skills/ai-build-ai/docs/team-lead-workflow.md +648 -0
- package/skills/ant-design/SKILL.md +323 -0
- package/skills/ant-design/docs/components.md +160 -0
- package/skills/ant-design/docs/data-entry.md +406 -0
- package/skills/ant-design/docs/display.md +594 -0
- package/skills/ant-design/docs/feedback.md +451 -0
- package/skills/ant-design/docs/key-components.md +414 -0
- package/skills/ant-design/docs/navigation.md +310 -0
- package/skills/ant-design/docs/pro-components.md +543 -0
- package/skills/ant-design/docs/setup.md +213 -0
- package/skills/ant-design/docs/theme.md +265 -0
- package/skills/flutter-performance/SKILL.md +803 -0
- package/skills/flutter-performance/references/flutter-patterns.md +595 -0
- package/skills/icon-generator/SKILL.md +270 -0
- package/skills/mobile-app-review/SKILL.md +321 -0
- package/skills/mobile-app-review/references/apple-review.md +132 -0
- package/skills/mobile-app-review/references/google-play-review.md +203 -0
- package/skills/mongodb/SKILL.md +667 -0
- package/skills/mongodb/references/mongoose-patterns.md +368 -0
- package/skills/nestjs-architecture/SKILL.md +1086 -0
- package/skills/nestjs-architecture/references/advanced-patterns.md +590 -0
- package/skills/performance/SKILL.md +509 -0
- package/skills/react-fsd-architecture/SKILL.md +693 -0
- package/skills/react-fsd-architecture/references/fsd-patterns.md +747 -0
- package/skills/react-native-expo/SKILL.md +128 -0
- package/skills/react-native-expo/references/data-layer.md +252 -0
- package/skills/react-native-expo/references/design-system.md +252 -0
- package/skills/react-native-expo/references/navigation.md +199 -0
- package/skills/react-native-expo/references/performance.md +229 -0
- package/skills/react-native-expo/references/platform-services.md +179 -0
- package/skills/react-native-expo/references/state-management.md +209 -0
- package/skills/react-native-expo/references/ui-patterns.md +301 -0
- package/skills/react-query/SKILL.md +685 -0
- package/skills/react-query/references/query-patterns.md +365 -0
- package/skills/review-code/SKILL.md +374 -0
- package/skills/review-code/references/clean-code-principles.md +395 -0
- package/skills/review-code/references/frontend-patterns.md +136 -0
- package/skills/review-code/references/nestjs-patterns.md +184 -0
- package/skills/security-scanner/SKILL.md +366 -0
- package/skills/security-scanner/references/nestjs-security.md +260 -0
- package/skills/security-scanner/references/nextjs-security.md +201 -0
- package/skills/security-scanner/references/react-native-security.md +199 -0
- package/skills/traefik/SKILL.md +105 -0
- package/skills/traefik/docs/advanced-routing.md +186 -0
- package/skills/traefik/docs/auth-providers.md +137 -0
- package/skills/traefik/docs/cicd-devops.md +396 -0
- package/skills/traefik/docs/core-config.md +171 -0
- package/skills/traefik/docs/distributed-config.md +96 -0
- package/skills/traefik/docs/docker-compose.md +182 -0
- package/skills/traefik/docs/ha-performance.md +177 -0
- package/skills/traefik/docs/kubernetes.md +278 -0
- package/skills/traefik/docs/middleware.md +205 -0
- package/skills/traefik/docs/monitoring.md +357 -0
- package/skills/traefik/docs/security.md +391 -0
- package/skills/traefik/docs/tls-acme.md +155 -0
- package/skills/ui-ux-pro-max/SKILL.md +377 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-native-expo
|
|
3
|
+
description: |
|
|
4
|
+
**React Native Expo (Sty AI Mobile)**: Production patterns for the Sty AI React Native app — Expo SDK 54, Expo Router v5, React Query v5, Zustand v5, Reanimated v4, FlashList v2, MMKV, BottomSheet v5, react-hook-form + zod, and the full Sty AI design system.
|
|
5
|
+
- MANDATORY TRIGGERS: react native, expo, mobile app, screen, component, navigation, route, tab, stack, modal, animation, reanimated, gesture, flash list, flatlist, list performance, bottom sheet, form, validation, zod, state management, zustand, store, query, mutation, api call, fetch data, image, expo-image, styling, theme, color, typography, spacing, font, button, card, skeleton, shimmer, toast, dialog, upload, camera, photo, push notification, deep link, branch, auth, login, onboarding, keyboard, MMKV, storage, cache, i18n, localization, live activity, adapty, subscription, in-app purchase, analytics, airbridge, facebook sdk
|
|
6
|
+
- Use this skill whenever working on ANY file in the styai-mobile directory, creating new screens/components, debugging mobile issues, optimizing performance, or reviewing mobile code. Also trigger when discussing React Native architecture, Expo configuration, or mobile-specific patterns — even casual mentions like 'fix this screen' or 'add a button'.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# React Native Expo — Sty AI Mobile
|
|
10
|
+
|
|
11
|
+
Production-grade skill for the Sty AI React Native mobile app (Expo SDK 54, New Architecture, Hermes).
|
|
12
|
+
|
|
13
|
+
## Quick Start — Load Only What You Need
|
|
14
|
+
|
|
15
|
+
Before writing code, read the correct reference file based on the task:
|
|
16
|
+
|
|
17
|
+
| Task | Reference File |
|
|
18
|
+
|------|---------------|
|
|
19
|
+
| Screens, routes, navigation, tabs, modals | `references/navigation.md` |
|
|
20
|
+
| React Query hooks, API calls, mutations | `references/data-layer.md` |
|
|
21
|
+
| Zustand stores, MMKV, global state | `references/state-management.md` |
|
|
22
|
+
| Animations, gestures, Reanimated, lists | `references/performance.md` |
|
|
23
|
+
| Theme, colors, typography, spacing, scaling | `references/design-system.md` |
|
|
24
|
+
| Forms, validation, zod, bottom sheets | `references/ui-patterns.md` |
|
|
25
|
+
| Auth, notifications, deep links, i18n, analytics | `references/platform-services.md` |
|
|
26
|
+
|
|
27
|
+
Read ONLY the relevant reference file(s) for the current task. This keeps token usage minimal.
|
|
28
|
+
|
|
29
|
+
## Project Identity
|
|
30
|
+
|
|
31
|
+
- **App**: Sty AI — AI-powered wardrobe management
|
|
32
|
+
- **Bundle**: `app.styai.android` (Android) / `dev.jerrypham.easycloset` (iOS)
|
|
33
|
+
- **Stack**: Expo SDK 54, React Native 0.81, React 19, TypeScript 5.9
|
|
34
|
+
- **Package Manager**: pnpm
|
|
35
|
+
- **Font**: Poppins (300-900 weights, platform-specific families)
|
|
36
|
+
- **Design**: Sophisticated minimal with rose/blush brand accent (#EC4899)
|
|
37
|
+
|
|
38
|
+
## Architecture at a Glance
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
styai-mobile/src/
|
|
42
|
+
app/ # Route files (thin delegators to screens)
|
|
43
|
+
screens/ # Actual screen logic (mirrors app/ structure)
|
|
44
|
+
models/ # Domain models: _services/, _types/, _ui/, _store/
|
|
45
|
+
features/ # Cross-cutting features (auth, credit, stylist, etc.)
|
|
46
|
+
shared/ # Shared infrastructure
|
|
47
|
+
components/ # Typography, Button, BottomSheet, shimmer, toast
|
|
48
|
+
hooks/ # use-auth-init, use-push-notifications, use-infinite-scroll
|
|
49
|
+
libs/ # api-client (Axios + auto-refresh), token-storage (Keychain)
|
|
50
|
+
stores/ # Zustand stores with slices pattern
|
|
51
|
+
theme/ # Design tokens: color, typography, spacing, scaling
|
|
52
|
+
services/ # Singletons: logger, airbridge, facebook, push
|
|
53
|
+
types/ # PaginatedResponse<T>, IdRes, TimeStampRes, ApiError
|
|
54
|
+
constant/ # ROUTES object, enums
|
|
55
|
+
utils/ # Helpers: compress-image, date, logger, share-app
|
|
56
|
+
components/ # (deprecated — use shared/components/)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Critical Rules
|
|
60
|
+
|
|
61
|
+
1. **Route files are thin delegators** — actual logic lives in `src/screens/`
|
|
62
|
+
2. **Always use `Typography`** instead of raw `<Text>`
|
|
63
|
+
3. **Always use `Theme.color.*`** semantic tokens, never raw hex values
|
|
64
|
+
4. **Always use `useShallow`** when selecting multiple fields from Zustand
|
|
65
|
+
5. **Query keys = API URL path strings** (e.g., `"/api/v1/item"`)
|
|
66
|
+
6. **All API calls through `client`** from `@/shared/libs/api-client`
|
|
67
|
+
7. **Use `expo-image`** (`Image` from `expo-image`), not React Native's `Image`
|
|
68
|
+
8. **Use `FlashList`** for lists, never `FlatList`
|
|
69
|
+
9. **Use `Spacing.*`** semantic tokens for all spacing values
|
|
70
|
+
10. **Platform fonts differ**: iOS uses `Poppins-Medium`, Android uses `Poppins_500Medium` — always use `typefaceBase` or `withFontWeight()`, never hardcode font names
|
|
71
|
+
|
|
72
|
+
## Path Aliases
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
@/* → ./src/*
|
|
76
|
+
@auth/* → ./src/features/auth/*
|
|
77
|
+
@testimonials/* → ./src/assets/images/testimonials/*
|
|
78
|
+
assets/* → ./assets/*
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Commands
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pnpm start # Dev server (Metro)
|
|
85
|
+
pnpm ios # iOS device
|
|
86
|
+
pnpm android # Android emulator
|
|
87
|
+
pnpm type-check # TypeScript check
|
|
88
|
+
pnpm lint:fix # ESLint + auto-fix
|
|
89
|
+
pnpm test # Jest
|
|
90
|
+
pnpm prebuild # Regenerate native code
|
|
91
|
+
pnpm cng:development # Full dev env setup
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Key Dependencies
|
|
95
|
+
|
|
96
|
+
| Package | Version | Use |
|
|
97
|
+
|---------|---------|-----|
|
|
98
|
+
| `expo` | 54 | Framework (New Arch, Hermes) |
|
|
99
|
+
| `expo-router` | 6 | File-based routing (v5 API) |
|
|
100
|
+
| `@tanstack/react-query` | 5 | Server state |
|
|
101
|
+
| `zustand` | 5 | Client state |
|
|
102
|
+
| `react-native-mmkv` | 3.1 | Fast sync storage |
|
|
103
|
+
| `react-native-reanimated` | 4.1 | UI-thread animations |
|
|
104
|
+
| `@shopify/flash-list` | 2.0 | High-perf lists (New Arch only) |
|
|
105
|
+
| `@gorhom/bottom-sheet` | 5 | Bottom sheets |
|
|
106
|
+
| `react-hook-form` + `zod` | 7 + 3 | Forms + validation |
|
|
107
|
+
| `expo-image` | 3.0 | Cached images |
|
|
108
|
+
| `react-native-gesture-handler` | 2.28 | Native gestures |
|
|
109
|
+
| `@shopify/react-native-skia` | 2.2 | Canvas drawing |
|
|
110
|
+
| `react-native-keychain` | 10 | Secure JWT storage |
|
|
111
|
+
| `i18next` + `react-i18next` | 25 + 16 | i18n (9 languages) |
|
|
112
|
+
| `react-native-adapty` | 3.15 | Subscriptions |
|
|
113
|
+
| `airbridge-expo-sdk` | 4.8 | Analytics |
|
|
114
|
+
| `lottie-react-native` | 7.3 | Lottie animations |
|
|
115
|
+
| `date-fns` / `dayjs` | 4 / 1.11 | Date utils |
|
|
116
|
+
| `axios` | 1.11 | HTTP client (wrapped) |
|
|
117
|
+
| `react-native-svg` | 15.12 | SVG rendering |
|
|
118
|
+
| `ai` + `@ai-sdk/react` | 4 + 1 | AI SDK streaming |
|
|
119
|
+
|
|
120
|
+
## New Code Placement
|
|
121
|
+
|
|
122
|
+
- **New model** → `src/models/<model-name>/` with `_services/`, `_types/`
|
|
123
|
+
- **New screen** → `src/screens/` + thin delegator in `src/app/`
|
|
124
|
+
- **New shared component** → `src/shared/components/`
|
|
125
|
+
- **New hook** → `src/shared/hooks/` or model's `_hooks/`
|
|
126
|
+
- **New store** → `src/shared/stores/` (Zustand) or feature's `_stores/`
|
|
127
|
+
- **New feature** → `src/features/<feature-name>/`
|
|
128
|
+
- **DO NOT** add to `src/components/` (deprecated)
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Data Layer — React Query v5 + API Client
|
|
2
|
+
|
|
3
|
+
## API Client (`@/shared/libs/api-client`)
|
|
4
|
+
|
|
5
|
+
Custom Axios wrapper with:
|
|
6
|
+
- **Auto token injection** via request interceptor
|
|
7
|
+
- **401 auto-refresh** with single-flight pattern (max 2 retries, prevents race conditions)
|
|
8
|
+
- **FormData fallback** to native `fetch` (Axios has RN FormData issues)
|
|
9
|
+
- **Credit header parsing**: Response interceptor reads `x-credits-balance` and `x-credits-used` headers
|
|
10
|
+
- **Custom headers**: `User-Agent`, `X-Client-Type: mobile`, `Accept-Language`
|
|
11
|
+
- Base URL from `EXPO_PUBLIC_API_URL` env var
|
|
12
|
+
- Android emulator: `localhost` auto-replaced with `10.0.2.2`
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { client } from "@/shared/libs/api-client"
|
|
16
|
+
|
|
17
|
+
// Standard requests
|
|
18
|
+
const { data } = await client.get<ItemRes[]>("/api/v1/item")
|
|
19
|
+
const { data } = await client.post<ItemRes>("/api/v1/item", body)
|
|
20
|
+
const { data } = await client.patch<ItemRes>(`/api/v1/item/${id}`, body)
|
|
21
|
+
await client.delete(`/api/v1/item/${id}`)
|
|
22
|
+
|
|
23
|
+
// FormData (auto falls back to native fetch)
|
|
24
|
+
const formData = new FormData()
|
|
25
|
+
formData.append("file", { uri, type, name } as any)
|
|
26
|
+
const { data } = await client.post("/api/v1/upload", formData, {
|
|
27
|
+
headers: { "X-FormData-Upload": "true" },
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Service Pattern — One Hook Per File
|
|
32
|
+
|
|
33
|
+
Each query/mutation lives in its own file under `_services/`:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
src/models/item/_services/
|
|
37
|
+
use-get-items.ts # useInfiniteQuery
|
|
38
|
+
use-get-item-detail.ts # useQuery
|
|
39
|
+
use-create-item.ts # useMutation
|
|
40
|
+
use-update-item.ts # useMutation
|
|
41
|
+
use-delete-item.ts # useMutation
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Query Key Convention
|
|
45
|
+
|
|
46
|
+
Query keys are **always the API URL path string**:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
export const ITEM_INFINITE_LIST_QUERY_KEY = "/api/v1/item"
|
|
50
|
+
export const ITEM_DETAIL_QUERY_KEY = "/api/v1/item/detail"
|
|
51
|
+
export const CATEGORIES_QUERY_KEY = "/api/v1/category"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### useQuery Pattern
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { useQuery } from "@tanstack/react-query"
|
|
58
|
+
import { client } from "@/shared/libs/api-client"
|
|
59
|
+
|
|
60
|
+
export const ITEM_DETAIL_QUERY_KEY = "/api/v1/item/detail"
|
|
61
|
+
|
|
62
|
+
export const useGetItemDetail = (id: string) => {
|
|
63
|
+
return useQuery({
|
|
64
|
+
queryKey: [ITEM_DETAIL_QUERY_KEY, id],
|
|
65
|
+
queryFn: async () => {
|
|
66
|
+
const { data } = await client.get<ItemDetailPopulated>(`/api/v1/item/${id}`)
|
|
67
|
+
return data
|
|
68
|
+
},
|
|
69
|
+
enabled: !!id,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### useInfiniteQuery Pattern
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { useInfiniteQuery } from "@tanstack/react-query"
|
|
78
|
+
|
|
79
|
+
export const ITEM_INFINITE_LIST_QUERY_KEY = "/api/v1/item"
|
|
80
|
+
|
|
81
|
+
export const useGetItemInfiniteList = (params?: ItemListParams, opts?: { enabled?: boolean }) => {
|
|
82
|
+
const limit = 20
|
|
83
|
+
const query = useInfiniteQuery({
|
|
84
|
+
queryKey: [ITEM_INFINITE_LIST_QUERY_KEY, limit, params],
|
|
85
|
+
initialPageParam: 0,
|
|
86
|
+
queryFn: async ({ pageParam }) => {
|
|
87
|
+
const { data } = await client.get<PaginatedResponse<ItemRes>>(ITEM_INFINITE_LIST_QUERY_KEY, {
|
|
88
|
+
params: { limit, skip: pageParam, paging_count: pageParam === 0, ...params },
|
|
89
|
+
})
|
|
90
|
+
return data
|
|
91
|
+
},
|
|
92
|
+
getNextPageParam: (lastPage, allPages) => {
|
|
93
|
+
if ((lastPage?.data?.length ?? 0) < limit) return undefined
|
|
94
|
+
return allPages.reduce((sum, p) => sum + (p?.data?.length ?? 0), 0)
|
|
95
|
+
},
|
|
96
|
+
enabled: opts?.enabled,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// Flatten pages
|
|
100
|
+
const items = query.data?.pages.flatMap((p) => p.data) ?? []
|
|
101
|
+
const firstMeta = query.data?.pages?.[0]?.meta
|
|
102
|
+
|
|
103
|
+
return { items, meta: firstMeta, ...query }
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Usage with FlashList + infinite scroll:
|
|
108
|
+
```typescript
|
|
109
|
+
const { items, fetchNextPage, hasNextPage, isFetchingNextPage } = useGetItemInfiniteList()
|
|
110
|
+
const { onEndReached } = useInfiniteScroll({ fetchNextPage, hasNextPage, isFetchingNextPage })
|
|
111
|
+
|
|
112
|
+
<FlashList
|
|
113
|
+
data={items}
|
|
114
|
+
renderItem={renderItem}
|
|
115
|
+
estimatedItemSize={120}
|
|
116
|
+
onEndReached={onEndReached}
|
|
117
|
+
onEndReachedThreshold={0.5}
|
|
118
|
+
ListFooterComponent={isFetchingNextPage ? <ActivityIndicator /> : null}
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### useMutation Pattern
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
|
126
|
+
import { showErrorToast } from "@/shared/components/toast"
|
|
127
|
+
import { logEvent } from "@/shared/utils/log-event"
|
|
128
|
+
|
|
129
|
+
export const useUpdateItem = () => {
|
|
130
|
+
const queryClient = useQueryClient()
|
|
131
|
+
|
|
132
|
+
const { mutate, isPending, error } = useMutation({
|
|
133
|
+
mutationFn: async ({ _id, body }: { _id: string; body: UpdateItemBody }) => {
|
|
134
|
+
return (await client.patch<ItemRes>(`/api/v1/item/${_id}`, body)).data
|
|
135
|
+
},
|
|
136
|
+
onSuccess: () => {
|
|
137
|
+
queryClient.invalidateQueries({ queryKey: [ITEM_INFINITE_LIST_QUERY_KEY] })
|
|
138
|
+
queryClient.invalidateQueries({ queryKey: [ITEM_DETAIL_QUERY_KEY] })
|
|
139
|
+
logEvent("item_updated")
|
|
140
|
+
},
|
|
141
|
+
onError: () => {
|
|
142
|
+
showErrorToast("Update Failed", "Could not update the item. Please try again.")
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
return { updateItem: mutate, isUpdatingItem: isPending, updateItemError: error }
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Optimistic Updates
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
export const useToggleFavorite = () => {
|
|
154
|
+
const queryClient = useQueryClient()
|
|
155
|
+
|
|
156
|
+
return useMutation({
|
|
157
|
+
mutationFn: async (id: string) => (await client.patch(`/api/v1/item/${id}/favorite`)).data,
|
|
158
|
+
onMutate: async (id) => {
|
|
159
|
+
await queryClient.cancelQueries({ queryKey: [ITEM_DETAIL_QUERY_KEY, id] })
|
|
160
|
+
const previous = queryClient.getQueryData([ITEM_DETAIL_QUERY_KEY, id])
|
|
161
|
+
queryClient.setQueryData([ITEM_DETAIL_QUERY_KEY, id], (old: any) => ({
|
|
162
|
+
...old,
|
|
163
|
+
isFavorite: !old.isFavorite,
|
|
164
|
+
}))
|
|
165
|
+
return { previous }
|
|
166
|
+
},
|
|
167
|
+
onError: (_err, id, context) => {
|
|
168
|
+
queryClient.setQueryData([ITEM_DETAIL_QUERY_KEY, id], context?.previous)
|
|
169
|
+
},
|
|
170
|
+
onSettled: (_data, _err, id) => {
|
|
171
|
+
queryClient.invalidateQueries({ queryKey: [ITEM_DETAIL_QUERY_KEY, id] })
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Async Service Pattern (for Zustand stores)
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// Non-hook function for use inside Zustand slices
|
|
181
|
+
export const getStyBalance = async (): Promise<StyBalance> => {
|
|
182
|
+
const response = await client.get<StyBalance>(GET_STY_BALANCE_QUERY_KEY)
|
|
183
|
+
return response.data
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Type Patterns
|
|
188
|
+
|
|
189
|
+
Types extend shared bases from `@/shared/types/`:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { IdRes, TimeStampRes } from "@/shared/types"
|
|
193
|
+
|
|
194
|
+
// Response type (composition pattern)
|
|
195
|
+
type ItemRes = {
|
|
196
|
+
name: string
|
|
197
|
+
image_url: string
|
|
198
|
+
category_id: string
|
|
199
|
+
blurhash?: string
|
|
200
|
+
} & IdRes & TimeStampRes
|
|
201
|
+
// IdRes = { _id: string }
|
|
202
|
+
// TimeStampRes = { created_at: string; updated_at: string }
|
|
203
|
+
|
|
204
|
+
// Populated type (nested objects expanded)
|
|
205
|
+
type ItemDetailPopulated = Omit<ItemRes, "styles" | "occasions"> & {
|
|
206
|
+
styles: FashionStyleRes[]
|
|
207
|
+
occasions: FashionOccasionRes[]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Paginated response
|
|
211
|
+
type PaginatedResponse<T> = {
|
|
212
|
+
data: T[]
|
|
213
|
+
meta: PaginationMeta // page, take, item_count, page_count, has_next_page
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## React Query Provider Setup
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// src/shared/providers/react-query-provider.tsx
|
|
221
|
+
// - Sets online manager from expo-network (device connectivity)
|
|
222
|
+
// - Sets focus manager from AppState (refetch on app focus)
|
|
223
|
+
// - Exports queryClient singleton
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Token Storage (`@/shared/libs/token-storage`)
|
|
227
|
+
|
|
228
|
+
Secure JWT storage via `react-native-keychain`:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { storeTokens, getAccessToken, clearTokens, hasValidTokens, validateTokenIntegrity } from "@/shared/libs/token-storage"
|
|
232
|
+
|
|
233
|
+
await storeTokens({ accessToken, refreshToken })
|
|
234
|
+
const token = await getAccessToken()
|
|
235
|
+
await clearTokens() // On sign-out
|
|
236
|
+
const isValid = await hasValidTokens()
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Best Practices
|
|
240
|
+
|
|
241
|
+
- **One query/mutation hook per file** — named `use-get-*.ts` or `use-create-*.ts`
|
|
242
|
+
- **Export `QUERY_KEY` constant** from every query file
|
|
243
|
+
- **Query key = API URL path** — always a string constant
|
|
244
|
+
- **Always `invalidateQueries`** in mutation `onSuccess`
|
|
245
|
+
- **Always `showErrorToast`** in mutation `onError`
|
|
246
|
+
- **Always `logEvent`** for analytics in mutation `onSuccess`
|
|
247
|
+
- **Use `enabled` option** to prevent queries without required params
|
|
248
|
+
- **Use `useInfiniteQuery`** for paginated lists, never manual page tracking
|
|
249
|
+
- **Flatten pages** in the hook: `data?.pages.flatMap((p) => p.data) ?? []`
|
|
250
|
+
- **Distinguish loading states**: `isLoading` (initial), `isFetching` (refetch), `isPending` (mutation)
|
|
251
|
+
- **Never import axios directly** — always use `client` from `api-client`
|
|
252
|
+
- **FormData uploads** need `"X-FormData-Upload": "true"` header
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Design System — Theme, Colors, Typography, Spacing
|
|
2
|
+
|
|
3
|
+
## Theme Usage
|
|
4
|
+
|
|
5
|
+
Always import from `@/shared/theme`:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Theme, Spacing, scale, moderateScale, withFontWeight } from "@/shared/theme"
|
|
9
|
+
|
|
10
|
+
Theme.color.brand // #EC4899
|
|
11
|
+
Theme.color.text // #262626
|
|
12
|
+
Theme.typography.b1 // { fontSize: 16, lineHeight: 24, fontFamily: "Poppins-Regular" }
|
|
13
|
+
Spacing.lg // 16
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Color System
|
|
17
|
+
|
|
18
|
+
Sophisticated minimal with rose/blush brand accent. **Always use semantic tokens, never raw hex.**
|
|
19
|
+
|
|
20
|
+
### Brand Colors (use sparingly)
|
|
21
|
+
|
|
22
|
+
| Token | Value | Use |
|
|
23
|
+
|-------|-------|-----|
|
|
24
|
+
| `brand` | `#EC4899` | Primary brand (buttons, links) |
|
|
25
|
+
| `brandLight` | `#FFE4E9` | Light accent backgrounds |
|
|
26
|
+
| `brandSubtle` | `#FFF1F3` | Super light tint |
|
|
27
|
+
| `brandDark` | `#BE185D` | Dark brand emphasis |
|
|
28
|
+
| `brandText` | `#DB2777` | Brand-colored text |
|
|
29
|
+
|
|
30
|
+
### Backgrounds
|
|
31
|
+
|
|
32
|
+
| Token | Value | Use |
|
|
33
|
+
|-------|-------|-----|
|
|
34
|
+
| `background` | `#F6F6F9` | Screen background |
|
|
35
|
+
| `backgroundSubtle` | `#EDEDF0` | Secondary background |
|
|
36
|
+
| `backgroundElevated` | `#FFFFFF` | Cards, elevated surfaces |
|
|
37
|
+
| `backgroundInverse` | `#171717` | Dark backgrounds |
|
|
38
|
+
|
|
39
|
+
### Text
|
|
40
|
+
|
|
41
|
+
| Token | Value | Use |
|
|
42
|
+
|-------|-------|-----|
|
|
43
|
+
| `text` | `#262626` | Primary text |
|
|
44
|
+
| `textSecondary` | `#737373` | Secondary/muted |
|
|
45
|
+
| `textTertiary` | `#A3A3A3` | Placeholder, disabled |
|
|
46
|
+
| `textInverse` | `#FFFFFF` | On dark backgrounds |
|
|
47
|
+
| `textBrand` | `#DB2777` | Brand-colored text |
|
|
48
|
+
|
|
49
|
+
### Borders
|
|
50
|
+
|
|
51
|
+
| Token | Value | Use |
|
|
52
|
+
|-------|-------|-----|
|
|
53
|
+
| `border` | `#E5E5E5` | Default |
|
|
54
|
+
| `borderSubtle` | `#EDEDED` | Subtle |
|
|
55
|
+
| `borderStrong` | `#D4D4D4` | Emphasized |
|
|
56
|
+
| `borderBrand` | `#FDA4B8` | Brand accent |
|
|
57
|
+
|
|
58
|
+
### Interactive States
|
|
59
|
+
|
|
60
|
+
`interactive` (brand), `interactiveHover`, `interactiveActive`, `interactiveSubtle` (bg), `interactiveMuted` (selected bg)
|
|
61
|
+
|
|
62
|
+
### Status Colors
|
|
63
|
+
|
|
64
|
+
| Status | Main | Subtle | Muted | Text |
|
|
65
|
+
|--------|------|--------|-------|------|
|
|
66
|
+
| Success | `#22C55E` | `#F0FDF4` | `#DCFCE7` | `#15803D` |
|
|
67
|
+
| Warning | `#F59E0B` | `#FFFBEB` | `#FEF3C7` | `#B45309` |
|
|
68
|
+
| Error | `#EF4444` | `#FEF2F2` | `#FEE2E2` | `#B91C1C` |
|
|
69
|
+
|
|
70
|
+
### Special Tokens
|
|
71
|
+
|
|
72
|
+
`overlay` (rgba 0.5), `scrim` (rgba 0.6 for modals), `skeleton` (#E5E5E5), `disabled` (#D4D4D4), `focus` (#F87198), `ripple` (rgba 0.08 for Android)
|
|
73
|
+
|
|
74
|
+
## Typography
|
|
75
|
+
|
|
76
|
+
Font: **Poppins** (Google Fonts, weights 300-900)
|
|
77
|
+
|
|
78
|
+
### Variants
|
|
79
|
+
|
|
80
|
+
| Variant | Size | Height | Weight | Use |
|
|
81
|
+
|---------|------|--------|--------|-----|
|
|
82
|
+
| `xxl` | 72 | 80 | ExtraBold | Hero numbers |
|
|
83
|
+
| `h1` | 22 | 33 | Bold | Page titles |
|
|
84
|
+
| `h2` | 20 | 30 | Medium | Section headings |
|
|
85
|
+
| `h3` | 18 | 27 | Medium | Sub-headings |
|
|
86
|
+
| `b1` | 16 | 24 | Regular | Body text |
|
|
87
|
+
| `b2` | 14 | 21 | Regular | Small body |
|
|
88
|
+
| `b3` | 12 | 18 | Regular | Captions, labels |
|
|
89
|
+
|
|
90
|
+
### Usage
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { Typography } from "@/shared/components"
|
|
94
|
+
|
|
95
|
+
<Typography variant="h1">Page Title</Typography>
|
|
96
|
+
<Typography variant="b2" color={Theme.color.textSecondary}>Subtitle</Typography>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Font Weight Override
|
|
100
|
+
|
|
101
|
+
**Always use `withFontWeight()`** — never set fontWeight directly (breaks Android):
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { withFontWeight } from "@/shared/theme"
|
|
105
|
+
|
|
106
|
+
// CORRECT: Maps to correct platform font family
|
|
107
|
+
const boldBody = withFontWeight(Theme.typography.b1, "700")
|
|
108
|
+
|
|
109
|
+
// WRONG: Breaks on Android (fontWeight without matching fontFamily)
|
|
110
|
+
const bad = { ...Theme.typography.b1, fontWeight: "700" }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Platform Font Families
|
|
114
|
+
|
|
115
|
+
| Weight | iOS | Android |
|
|
116
|
+
|--------|-----|---------|
|
|
117
|
+
| 300 | `Poppins-Light` | `Poppins_300Light` |
|
|
118
|
+
| 400 | `Poppins-Regular` | `Poppins_400Regular` |
|
|
119
|
+
| 500 | `Poppins-Medium` | `Poppins_500Medium` |
|
|
120
|
+
| 600 | `Poppins-SemiBold` | `Poppins_600SemiBold` |
|
|
121
|
+
| 700 | `Poppins-Bold` | `Poppins_700Bold` |
|
|
122
|
+
| 800 | `Poppins-ExtraBold` | `Poppins_800ExtraBold` |
|
|
123
|
+
|
|
124
|
+
Typography component: `maxFontSizeMultiplier = 1.15` (prevents OS font scaling issues).
|
|
125
|
+
|
|
126
|
+
## Spacing System
|
|
127
|
+
|
|
128
|
+
4-point grid. **Always use `Spacing.*` tokens.**
|
|
129
|
+
|
|
130
|
+
### Component Spacing
|
|
131
|
+
|
|
132
|
+
| Token | Value | Use |
|
|
133
|
+
|-------|-------|-----|
|
|
134
|
+
| `xs` | 4 | Tight elements |
|
|
135
|
+
| `sm` | 8 | Compact layouts |
|
|
136
|
+
| `md` | 12 | Standard gaps |
|
|
137
|
+
| `lg` | 16 | Comfortable gaps |
|
|
138
|
+
| `xl` | 20 | Emphasis |
|
|
139
|
+
| `xxl` | 24 | Section spacing |
|
|
140
|
+
| `xxxl` | 32 | Large sections |
|
|
141
|
+
|
|
142
|
+
### Layout Spacing
|
|
143
|
+
|
|
144
|
+
| Token | Value | Use |
|
|
145
|
+
|-------|-------|-----|
|
|
146
|
+
| `screenHorizontal` | 16 | Screen padding L/R |
|
|
147
|
+
| `screenVertical` | 24 | Screen padding T/B |
|
|
148
|
+
| `sectionGap` | 24 | Between sections |
|
|
149
|
+
| `cardPadding` | 16 | Card internal padding |
|
|
150
|
+
| `cardInnerGap` | 12 | Gap inside cards |
|
|
151
|
+
| `cardRadius` | 16 | Standard card radius |
|
|
152
|
+
| `cardRadiusLarge` | 20 | Hero card radius |
|
|
153
|
+
| `touchTarget` | 44 | Min touch size (iOS HIG) |
|
|
154
|
+
| `listItemGap` | 12 | Between list items |
|
|
155
|
+
|
|
156
|
+
## Responsive Scaling
|
|
157
|
+
|
|
158
|
+
Baseline: 375x812 (iPhone 13/14)
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { scale, verticalScale, moderateScale, DeviceSize } from "@/shared/theme"
|
|
162
|
+
|
|
163
|
+
scale(16) // Linear horizontal scaling
|
|
164
|
+
verticalScale(24) // Linear vertical scaling
|
|
165
|
+
moderateScale(16) // Dampened (factor=0.5, recommended default)
|
|
166
|
+
moderateScale(16, 0.3) // Less aggressive
|
|
167
|
+
|
|
168
|
+
// Device breakpoints
|
|
169
|
+
DeviceSize.isSmall // < 360px (older devices)
|
|
170
|
+
DeviceSize.isMedium // 360-399px (mid-range)
|
|
171
|
+
DeviceSize.isLarge // >= 400px (standard+)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Use `moderateScale` for text/padding, `scale` for widths/icons, raw values for tokens/borders.
|
|
175
|
+
|
|
176
|
+
## Shadow Pattern
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const styles = StyleSheet.create({
|
|
180
|
+
card: {
|
|
181
|
+
backgroundColor: Theme.color.backgroundElevated,
|
|
182
|
+
borderRadius: Spacing.cardRadius,
|
|
183
|
+
padding: Spacing.cardPadding,
|
|
184
|
+
shadowColor: Theme.color.shadowColor,
|
|
185
|
+
shadowOffset: { width: 0, height: 2 },
|
|
186
|
+
shadowOpacity: 0.08,
|
|
187
|
+
shadowRadius: 8,
|
|
188
|
+
elevation: 3, // Android
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Standard Card Pattern
|
|
194
|
+
|
|
195
|
+
Consistent card style used across all sections (overview, lists, settings):
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
const styles = StyleSheet.create({
|
|
199
|
+
card: {
|
|
200
|
+
backgroundColor: Theme.color.backgroundElevated,
|
|
201
|
+
borderRadius: Spacing.cardRadius, // 16
|
|
202
|
+
padding: Spacing.cardPadding, // 16
|
|
203
|
+
borderWidth: 1,
|
|
204
|
+
borderColor: Theme.color.border,
|
|
205
|
+
},
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
For hero/featured cards, use `brandSubtle` background with `brandLight` border:
|
|
210
|
+
```typescript
|
|
211
|
+
heroCard: {
|
|
212
|
+
backgroundColor: Theme.color.brandSubtle,
|
|
213
|
+
borderRadius: Spacing.cardRadius,
|
|
214
|
+
padding: Spacing.cardPadding,
|
|
215
|
+
borderWidth: 1,
|
|
216
|
+
borderColor: Theme.color.brandLight,
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Progress Bar Pattern
|
|
221
|
+
|
|
222
|
+
Standard progress bar: 4px height, 2px radius, `backgroundMuted` track, `brand` fill.
|
|
223
|
+
Use status colors (error/warning) only for actual error/warning states.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
progressTrack: {
|
|
227
|
+
height: 4,
|
|
228
|
+
borderRadius: 2,
|
|
229
|
+
backgroundColor: Theme.color.backgroundMuted,
|
|
230
|
+
overflow: "hidden",
|
|
231
|
+
},
|
|
232
|
+
progressFill: {
|
|
233
|
+
height: "100%",
|
|
234
|
+
borderRadius: 2,
|
|
235
|
+
backgroundColor: Theme.color.brand, // Default — use consistently
|
|
236
|
+
},
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Safe Area in Modals
|
|
240
|
+
|
|
241
|
+
For absolute-positioned elements inside modals (e.g., close button in fullscreen image viewer),
|
|
242
|
+
`SafeAreaView` doesn't reliably apply insets. Use `useSafeAreaInsets()` with explicit padding:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const insets = useSafeAreaInsets()
|
|
246
|
+
const topPadding = insets.top + (Platform.OS === "ios" ? 8 : 12)
|
|
247
|
+
|
|
248
|
+
<View style={{ paddingTop: topPadding }}>
|
|
249
|
+
<TouchableOpacity onPress={onClose}>...</TouchableOpacity>
|
|
250
|
+
</View>
|
|
251
|
+
```
|
|
252
|
+
```
|