@mediacubeco/expo-template-fsd 1.0.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/.claude/mcp.json +0 -0
- package/.claude/rules/abstract-skills-agents.mdc +25 -0
- package/.claude/rules/app-routing.mdc +47 -0
- package/.claude/rules/exports-at-end.mdc +55 -0
- package/.claude/rules/kebab-case-naming.mdc +29 -0
- package/.claude/rules/prettier-eslint-typescript.mdc +24 -0
- package/.claude/settings.json +0 -0
- package/.claude/skills/generate-api-model/SKILL.md +98 -0
- package/.claude/skills/generate-api-model-mock/SKILL.md +56 -0
- package/.env +1 -0
- package/.env.example +1 -0
- package/.husky/pre-commit +6 -0
- package/.secret/README.md +26 -0
- package/README.md +36 -0
- package/app.config.js +54 -0
- package/env.d.ts +7 -0
- package/eslint.config.js +14 -0
- package/i18next.d.ts +12 -0
- package/package.json +76 -0
- package/prettier.config.js +9 -0
- package/src/app/_app.tsx +18 -0
- package/src/app/_layout.tsx +20 -0
- package/src/app/_splash.tsx +22 -0
- package/src/entities/.gitkeep +0 -0
- package/src/features/.gitkeep +0 -0
- package/src/screens/.gitkeep +0 -0
- package/src/shared/assets/fonts/index.ts +7 -0
- package/src/shared/assets/icons/index.ts +3 -0
- package/src/shared/assets/icons/like.tsx +17 -0
- package/src/shared/assets/icons/type.ts +6 -0
- package/src/shared/assets/images/default.png +0 -0
- package/src/shared/assets/images/index.ts +11 -0
- package/src/shared/assets/images/logo-android-background.png +0 -0
- package/src/shared/assets/images/logo-android-foreground.png +0 -0
- package/src/shared/assets/images/logo-android-monochrome.png +0 -0
- package/src/shared/assets/images/logo-ios.png +0 -0
- package/src/shared/assets/images/logo-web.png +0 -0
- package/src/shared/assets/images/splash-dark.png +0 -0
- package/src/shared/assets/images/splash.png +0 -0
- package/src/shared/assets/index.ts +4 -0
- package/src/shared/assets/translations/en.ts +3 -0
- package/src/shared/assets/translations/index.ts +9 -0
- package/src/shared/assets/translations/ru.ts +3 -0
- package/src/shared/constants/environment.ts +13 -0
- package/src/shared/constants/index.ts +1 -0
- package/src/shared/hooks/index.ts +4 -0
- package/src/shared/hooks/use-animated.ts +194 -0
- package/src/shared/hooks/use-breakpoints.ts +25 -0
- package/src/shared/hooks/use-mixins.ts +36 -0
- package/src/shared/hooks/use-safe-area.ts +56 -0
- package/src/shared/lib/api/api.provider.tsx +47 -0
- package/src/shared/lib/api/api.ts +7 -0
- package/src/shared/lib/api/api.types.ts +9 -0
- package/src/shared/lib/api/constants/index.ts +6 -0
- package/src/shared/lib/api/entities/common.ts +28 -0
- package/src/shared/lib/api/entities/index.ts +1 -0
- package/src/shared/lib/api/hooks/common.ts +31 -0
- package/src/shared/lib/api/hooks/index.ts +0 -0
- package/src/shared/lib/api/index.ts +4 -0
- package/src/shared/lib/api/models/api-instance/api-instance.constants.ts +9 -0
- package/src/shared/lib/api/models/api-instance/api-instance.ts +118 -0
- package/src/shared/lib/api/models/api-instance/api-instance.types.ts +31 -0
- package/src/shared/lib/api/models/api-instance/api-instance.utils.ts +22 -0
- package/src/shared/lib/api/models/api-instance/index.ts +1 -0
- package/src/shared/lib/api/models/index.ts +1 -0
- package/src/shared/lib/api/repositories/auth/auth.ts +18 -0
- package/src/shared/lib/api/repositories/auth/auth.types.ts +22 -0
- package/src/shared/lib/api/repositories/auth/index.ts +1 -0
- package/src/shared/lib/api/repositories/index.ts +1 -0
- package/src/shared/lib/api/resources/common.ts +8 -0
- package/src/shared/lib/api/resources/index.ts +1 -0
- package/src/shared/lib/api/utils/index.ts +1 -0
- package/src/shared/lib/api/utils/storage.ts +9 -0
- package/src/shared/lib/index.ts +4 -0
- package/src/shared/lib/localization/components/index.ts +1 -0
- package/src/shared/lib/localization/components/localization-translate/index.ts +1 -0
- package/src/shared/lib/localization/components/localization-translate/localization-translate.tsx +5 -0
- package/src/shared/lib/localization/components/localization-translate/localization-translate.types.ts +0 -0
- package/src/shared/lib/localization/constants/index.ts +54 -0
- package/src/shared/lib/localization/hooks/index.ts +2 -0
- package/src/shared/lib/localization/hooks/use-localization-setup.ts +34 -0
- package/src/shared/lib/localization/hooks/use-localization-text.ts +9 -0
- package/src/shared/lib/localization/hooks/use-localization.ts +23 -0
- package/src/shared/lib/localization/index.ts +4 -0
- package/src/shared/lib/localization/localization.provider.tsx +30 -0
- package/src/shared/lib/localization/localization.types.ts +5 -0
- package/src/shared/lib/localization/utils/index.ts +1 -0
- package/src/shared/lib/localization/utils/storage.ts +11 -0
- package/src/shared/lib/navigation/components/index.ts +1 -0
- package/src/shared/lib/navigation/components/navigation-logger/index.ts +1 -0
- package/src/shared/lib/navigation/components/navigation-logger/navigation-logger.ts +17 -0
- package/src/shared/lib/navigation/components/navigation-logger/navigation-logger.types.ts +0 -0
- package/src/shared/lib/navigation/hooks/index.ts +3 -0
- package/src/shared/lib/navigation/hooks/use-navigation-options.ts +94 -0
- package/src/shared/lib/navigation/hooks/use-navigation-params.ts +9 -0
- package/src/shared/lib/navigation/hooks/use-navigation.ts +9 -0
- package/src/shared/lib/navigation/index.ts +7 -0
- package/src/shared/lib/navigation/navigation.provider.tsx +26 -0
- package/src/shared/lib/navigation/navigation.types.ts +5 -0
- package/src/shared/lib/theme/constants/borders.ts +8 -0
- package/src/shared/lib/theme/constants/colors.ts +57 -0
- package/src/shared/lib/theme/constants/delays.ts +8 -0
- package/src/shared/lib/theme/constants/durations.ts +8 -0
- package/src/shared/lib/theme/constants/fonts.ts +7 -0
- package/src/shared/lib/theme/constants/index.ts +54 -0
- package/src/shared/lib/theme/constants/shadows.ts +40 -0
- package/src/shared/lib/theme/constants/sizes.ts +32 -0
- package/src/shared/lib/theme/constants/transparencies.ts +10 -0
- package/src/shared/lib/theme/constants/weights.ts +13 -0
- package/src/shared/lib/theme/hooks/index.ts +2 -0
- package/src/shared/lib/theme/hooks/use-theme-mode.ts +9 -0
- package/src/shared/lib/theme/hooks/use-theme-setup.ts +49 -0
- package/src/shared/lib/theme/hooks/use-theme.ts +15 -0
- package/src/shared/lib/theme/index.ts +4 -0
- package/src/shared/lib/theme/theme.context.ts +6 -0
- package/src/shared/lib/theme/theme.provider.tsx +60 -0
- package/src/shared/lib/theme/theme.types.ts +15 -0
- package/src/shared/lib/theme/utils/index.ts +1 -0
- package/src/shared/lib/theme/utils/storage.ts +11 -0
- package/src/shared/ui/atoms/index.ts +1 -0
- package/src/shared/ui/index.ts +4 -0
- package/src/shared/ui/molecules/index.ts +2 -0
- package/src/shared/ui/molecules/keyboard-area/index.ts +2 -0
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.styles.ts +20 -0
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.tsx +34 -0
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.types.ts +3 -0
- package/src/shared/ui/molecules/refresh-control/index.ts +2 -0
- package/src/shared/ui/molecules/refresh-control/refresh-control.tsx +32 -0
- package/src/shared/ui/molecules/refresh-control/refresh-control.types.ts +6 -0
- package/src/shared/ui/organisms/index.ts +1 -0
- package/src/shared/ui/templates/index.ts +1 -0
- package/src/shared/ui/templates/screen-container/index.ts +12 -0
- package/src/shared/ui/templates/screen-container/screen-container-list.tsx +23 -0
- package/src/shared/ui/templates/screen-container/screen-container-scroll.tsx +23 -0
- package/src/shared/ui/templates/screen-container/screen-container-section-list.tsx +24 -0
- package/src/shared/ui/templates/screen-container/screen-container.hooks.tsx +72 -0
- package/src/shared/ui/templates/screen-container/screen-container.styles.ts +45 -0
- package/src/shared/ui/templates/screen-container/screen-container.types.tsx +75 -0
- package/src/shared/utils/generators.ts +11 -0
- package/src/shared/utils/index.ts +1 -0
- package/src/widgets/.gitkeep +0 -0
- package/svg.d.ts +7 -0
- package/tsconfig.json +8 -0
package/.claude/mcp.json
ADDED
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Skills and agent instructions must stay abstract; no project-specific code examples
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Abstract skills and agent definitions
|
|
7
|
+
|
|
8
|
+
When creating or editing **Cursor skills** (e.g. under `.cursor/skills/`), **agent prompts**, or other reusable AI guidance for this repo:
|
|
9
|
+
|
|
10
|
+
## Principles
|
|
11
|
+
|
|
12
|
+
- **Stay abstract**: describe *what* to do, *constraints*, and *workflow* — not *this* app’s concrete modules, paths, or identifiers.
|
|
13
|
+
- **No examples lifted from this codebase**: do not paste snippets, file paths, component names, store keys, or API shapes from the project as the canonical “example.” That ties the skill to one implementation and goes stale quickly.
|
|
14
|
+
- **Use placeholders** when structure must be shown: generic names (`<entity>`, `SomeFeature`, `path/to/feature`), pseudo-paths, and minimal schematic snippets that could apply in any similar project.
|
|
15
|
+
- **Prefer references over copies**: if the agent must follow local conventions, point to an existing project rule or high-level convention (“follow the same layout as other features in the repo”) without embedding full real-file excerpts unless the user explicitly asked for documentation of existing code.
|
|
16
|
+
|
|
17
|
+
## Why
|
|
18
|
+
|
|
19
|
+
Skills and agents are meant to **transfer and stay maintainable**. Project-specific examples read as documentation of one snapshot of the repo, not as durable instructions.
|
|
20
|
+
|
|
21
|
+
## Anti-patterns
|
|
22
|
+
|
|
23
|
+
- Long blocks copied from `src/...` files into a `SKILL.md`.
|
|
24
|
+
- Hard-coded real route names, translation keys, or mock data from this app in a skill.
|
|
25
|
+
- Teaching “how we did X” with the exact current implementation instead of the invariant rules (naming, layering, file layout conventions).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Thin Expo Router layer in src/app — layout/screen and delegation to ~screens
|
|
3
|
+
globs: src/app/**/*.tsx
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# `src/app` layer (Expo Router)
|
|
8
|
+
|
|
9
|
+
Files under `src/app` only wire routes to implementation. Screen UI and business logic live in `~screens/*` (or other features), not in `app`.
|
|
10
|
+
|
|
11
|
+
## `_layout.tsx`
|
|
12
|
+
|
|
13
|
+
- The route’s main component is a function named **`layout`** (`Stack` / `Tabs` / `NativeTabs` config, `Stack.Screen`, options).
|
|
14
|
+
- **Default**-export that function (Expo Router requirement). Follow the “exports at end” policy in `.cursor/rules/exports-at-end.mdc`: declare `function layout()` first, then `export default layout` at the bottom of the file.
|
|
15
|
+
- Do not turn `_layout` into a “screen”: no content markup, only navigation and options.
|
|
16
|
+
|
|
17
|
+
## Route screen files (`index.tsx`, `[param].tsx`, named routes, etc.)
|
|
18
|
+
|
|
19
|
+
- The main component is a function named **`screen`**; it only renders the root screen from `~screens/...` (import a single root screen component).
|
|
20
|
+
- **Default**-export it (`export default screen` at the end of the file, same export rules).
|
|
21
|
+
- Do not duplicate screen markup in the route file — only a thin wrapper around the `~screens` import.
|
|
22
|
+
|
|
23
|
+
## Exceptions
|
|
24
|
+
|
|
25
|
+
- Special Expo Router files (`+not-found.tsx`, `+html.tsx`, etc.), if you add them, may follow a different contract — use Expo’s docs for that file.
|
|
26
|
+
|
|
27
|
+
## Templates
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
// _layout.tsx — navigation only
|
|
31
|
+
function Layout() {
|
|
32
|
+
return <Stack>{/* Stack.Screen … */}</Stack>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default Layout
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
// route screen
|
|
40
|
+
import { SomeFeatureScreen } from '~screens/some-feature'
|
|
41
|
+
|
|
42
|
+
function Screen() {
|
|
43
|
+
return <SomeFeatureScreen />
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default Screen
|
|
47
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Place all export statements at the end of TS/TSX modules
|
|
3
|
+
globs: **/*.{ts,tsx}
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Exports at end of file
|
|
8
|
+
|
|
9
|
+
All public exports from a module must appear **at the end of the file**, after imports, types, and implementation.
|
|
10
|
+
|
|
11
|
+
## Rules
|
|
12
|
+
|
|
13
|
+
- Do not scatter `export` on declarations through the file (e.g. `export const`, `export function`, `export class`, `export interface`). Declare first, then export in one place at the bottom.
|
|
14
|
+
- Use explicit export statements: `export { … }`, `export type { … }`, and `export default …`.
|
|
15
|
+
- Barrel files (`export … from '…'`) should also keep exports in the final section of the file when combined with other code (prefer dedicated barrel files with only re-exports).
|
|
16
|
+
|
|
17
|
+
## Suggested order (bottom of file)
|
|
18
|
+
|
|
19
|
+
1. `export { … }` — runtime named exports
|
|
20
|
+
2. `export type { … }` — type-only exports
|
|
21
|
+
3. `export default …` — default export last
|
|
22
|
+
|
|
23
|
+
Adjust if the team prefers default first; stay consistent within the repo.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// ❌ BAD — exports mixed with implementation
|
|
29
|
+
export const someVar = 1
|
|
30
|
+
export type { SomeType }
|
|
31
|
+
export function someFunction() {}
|
|
32
|
+
|
|
33
|
+
// ✅ GOOD — definitions first, exports last
|
|
34
|
+
const someVar = 1
|
|
35
|
+
type SomeType = { x: number }
|
|
36
|
+
function someFunction() {}
|
|
37
|
+
|
|
38
|
+
export { someVar, someFunction }
|
|
39
|
+
export type { SomeType }
|
|
40
|
+
export default someFunction
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// ❌ BAD
|
|
45
|
+
export default function SomeComponent() {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ✅ GOOD
|
|
50
|
+
function SomeComponent() {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default SomeComponent
|
|
55
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enforce kebab-case naming for all files and folders
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Kebab-case naming convention
|
|
7
|
+
|
|
8
|
+
All files and folders must use kebab-case naming.
|
|
9
|
+
|
|
10
|
+
## Rules
|
|
11
|
+
|
|
12
|
+
- Use only lowercase letters, digits, and hyphens: `some-name.ts`
|
|
13
|
+
- No camelCase, PascalCase, or snake_case in file/folder names
|
|
14
|
+
- Dots are allowed only for file extensions and config modifiers: `eslint.config.js`, `app.json`
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
# ✅ GOOD
|
|
20
|
+
src/shared/ui/
|
|
21
|
+
some-entity
|
|
22
|
+
some-feature
|
|
23
|
+
some-widget
|
|
24
|
+
|
|
25
|
+
# ❌ BAD
|
|
26
|
+
SomeEntity
|
|
27
|
+
some_feature
|
|
28
|
+
someWidget
|
|
29
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: All code must satisfy Prettier, ESLint, and TypeScript for this project
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Prettier, ESLint, and TypeScript
|
|
7
|
+
|
|
8
|
+
Every change you produce must align with the project toolchain: **Prettier** (`prettier.config.js`), **ESLint** (`eslint.config.js`), and **TypeScript** (`tsconfig.json` and extended configs).
|
|
9
|
+
|
|
10
|
+
## Expectations
|
|
11
|
+
|
|
12
|
+
- **Prettier**: formatting, line breaks, and import/style choices must match what Prettier would produce for this repo (run `npm format` or format-on-save with the same config).
|
|
13
|
+
- **ESLint**: no new violations of project rules; fix issues instead of widening ignores. Avoid blanket `eslint-disable` unless there is a documented, narrow reason.
|
|
14
|
+
- **TypeScript**: valid types, no errors under `npm type` (`tsc`). Do not paper over problems with `any`, unchecked casts, or `@ts-expect-error` / `@ts-ignore` unless unavoidable and explained in place.
|
|
15
|
+
|
|
16
|
+
## Verification
|
|
17
|
+
|
|
18
|
+
After substantive edits, prefer that the tree would pass:
|
|
19
|
+
|
|
20
|
+
- `npm format`
|
|
21
|
+
- `npm lint`
|
|
22
|
+
- `npm type`
|
|
23
|
+
|
|
24
|
+
If a constraint from the configs conflicts with another instruction, follow the stricter requirement or resolve the conflict explicitly in code without disabling tools by default.
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-api-model
|
|
3
|
+
description: >-
|
|
4
|
+
Generates API resource types and entity classes following project conventions.
|
|
5
|
+
Use when creating new API models, entities, resources, or data types, or when
|
|
6
|
+
the user asks to add a new API model, entity, or resource type.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Generate API Model
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
For each **domain** object from the API, add:
|
|
14
|
+
|
|
15
|
+
- **Resource** — a TypeScript type describing the DTO as returned on the wire (JSON shape).
|
|
16
|
+
- **Entity** — a class that wraps that resource and exposes **camelCase** getters for the UI and domain logic.
|
|
17
|
+
|
|
18
|
+
Do **not** generate resource/entity pairs for generic HTTP envelopes by default (paginated wrappers, transport-only error shapes, etc.). Add those only if the task explicitly asks for response-layer types.
|
|
19
|
+
|
|
20
|
+
## Where things live
|
|
21
|
+
|
|
22
|
+
Use the project’s shared API library: sibling trees **`resources`** (DTO types) and **`entities`** (classes). In this repo they sit under the same parent folder as `models`, `repositories`, etc.; domain folders are **kebab-case** (see workspace naming rules).
|
|
23
|
+
|
|
24
|
+
| Layer | Typical layout |
|
|
25
|
+
| -------- | ------------------------------------------------------------------------------------------ |
|
|
26
|
+
| Resource | `<api-lib>/resources/<domain>/<domain>.ts` plus `resources/common.ts` for shared fragments |
|
|
27
|
+
| Entity | `<api-lib>/entities/<domain>/<domain>.ts` plus `entities/common.ts` for the base class |
|
|
28
|
+
|
|
29
|
+
Each `<domain>/` folder has an `index.ts` that re-exports its modules. After adding a domain, export it from the **barrel** `resources/index.ts` and `entities/index.ts` (same pattern as existing domains).
|
|
30
|
+
|
|
31
|
+
## Naming
|
|
32
|
+
|
|
33
|
+
| Item | Convention |
|
|
34
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| Folder / file | kebab-case; primary domain module is `<domain>.ts`, shared cross-domain fragments use a name like `common.ts` |
|
|
36
|
+
| Resource type | `{Domain}Resource` (and `{Domain}{Aspect}Resource` for small unions or nested DTOs owned by that file) |
|
|
37
|
+
| Entity class | `{Domain}Entity` |
|
|
38
|
+
| Resource fields | **Match the wire format** — same spelling and casing as the API for each property |
|
|
39
|
+
| Entity getters | camelCase |
|
|
40
|
+
|
|
41
|
+
Nested domain objects get their own `<nested-domain>/` with `*Resource` + `*Entity`, then referenced from the parent resource/entity.
|
|
42
|
+
|
|
43
|
+
## Wire format (resources)
|
|
44
|
+
|
|
45
|
+
1. Take field names from the real JSON, OpenAPI schema, or agreed contract.
|
|
46
|
+
2. Property names on `*Resource` must **match the API** for each field (whether snake_case, camelCase, or mixed).
|
|
47
|
+
3. Do **not** normalize keys to a different convention “by habit” if the contract says otherwise.
|
|
48
|
+
4. `BaseResource` (or equivalent shared type) should include only fields that are **actually shared** across many DTOs, usually as **optional** members. If some responses omit `id` or timestamps, keep those optional or omit them from the base and declare per-resource.
|
|
49
|
+
5. A concrete `*Resource` may **intersect** with the base and add or refine fields (including refining `id` if the backend uses different scalar types per resource). Avoid types that claim keys the response never sends.
|
|
50
|
+
|
|
51
|
+
## Base types
|
|
52
|
+
|
|
53
|
+
### Shared resource fragment (`resources/common.ts`)
|
|
54
|
+
|
|
55
|
+
Export a minimal **type** (e.g. `BaseResource`) for identifiers and audit columns **in the naming the backend uses** for those shared fields.
|
|
56
|
+
|
|
57
|
+
### Base entity (`entities/common.ts`)
|
|
58
|
+
|
|
59
|
+
- Define a **generic** base class, e.g. `BaseEntity<Resource extends BaseResource>`, with a `protected` optional reference to the payload (commonly `resource`).
|
|
60
|
+
- Constructor accepts that optional `Resource`.
|
|
61
|
+
- Shared getters map from **the keys present on the shared resource fragment** to camelCase (e.g. wire `created_at` → getter `createdAt`), using optional chaining and sensible defaults only where the product expects them (e.g. dates, counts).
|
|
62
|
+
|
|
63
|
+
Subclasses: `export class {Domain}Entity extends BaseEntity<{Domain}Resource>` — rely on the generic parameter for typing `this.resource`; **do not** duplicate the pattern from older guides that reassign a separate typed field after `super` unless this codebase already does that elsewhere.
|
|
64
|
+
|
|
65
|
+
## Entity subclasses
|
|
66
|
+
|
|
67
|
+
1. Extend `BaseEntity<{Domain}Resource>`.
|
|
68
|
+
2. Add getters only for fields that exist on that resource type (plus inherited shared getters).
|
|
69
|
+
3. **Nested entities:** return `new ChildEntity(this.resource?.someKey)` (or map arrays with the same idea).
|
|
70
|
+
4. **Primitives:** optional chaining; use `??` with defaults where the UI needs a non-optional value (counts → `0`, flags → `false`, strings → `''` when appropriate).
|
|
71
|
+
|
|
72
|
+
Getter names are always camelCase; the **property access** inside uses the actual keys declared on `*Resource`.
|
|
73
|
+
|
|
74
|
+
## Resource module rules
|
|
75
|
+
|
|
76
|
+
1. Import the shared fragment from the resources `common` module (relative path depends on depth).
|
|
77
|
+
2. Export `export type {Domain}Resource = BaseResource & { ... }`.
|
|
78
|
+
3. Nested object → separate `*Resource` in its own module; arrays → `ChildResource[]`.
|
|
79
|
+
4. String unions / enums → `export type` aliases in the same file as the resource that owns the field.
|
|
80
|
+
|
|
81
|
+
## Imports between layers
|
|
82
|
+
|
|
83
|
+
Entities depend on resource types (import from the resources tree or its barrel). Keep dependency direction **resources → nothing in entities**; **entities → resources** only.
|
|
84
|
+
|
|
85
|
+
## Workflow
|
|
86
|
+
|
|
87
|
+
1. **Scope** — list domain DTOs only; exclude wrappers unless requested.
|
|
88
|
+
2. **Wire names** — copy property names from the contract into `*Resource`.
|
|
89
|
+
3. **Align the shared resource fragment** — smallest common optional shape; refine per domain where needed.
|
|
90
|
+
4. **Resources first, bottom-up** — leaf nested DTOs, then parents.
|
|
91
|
+
5. **Entities** — one class per resource; generic `BaseEntity<{Domain}Resource>`; nest `new …Entity(…)`.
|
|
92
|
+
6. **Barrels** — `index.ts` per domain folder and parent `resources/index.ts` / `entities/index.ts`.
|
|
93
|
+
|
|
94
|
+
## Export style
|
|
95
|
+
|
|
96
|
+
- Resource types: `export type { … }`
|
|
97
|
+
- Entity classes: `export class …`
|
|
98
|
+
- Index files: `export * from './<module>'`
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-api-model-mock
|
|
3
|
+
description: >-
|
|
4
|
+
Generate mock data (JSON fixtures and optional helpers) aligned with API
|
|
5
|
+
resource and entity types. Use when the user asks for mocks, mock data, test
|
|
6
|
+
fixtures, or fake API responses.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Generate API model mocks
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
Produce static data that **conforms** to the project’s `*Resource` types and, when useful, round-trips through `*Entity` classes the same way production code would.
|
|
14
|
+
|
|
15
|
+
## Before generating
|
|
16
|
+
|
|
17
|
+
If the user has not specified **where** fixtures live or **which response shape** (single object vs array vs envelope), ask once for:
|
|
18
|
+
|
|
19
|
+
- Target folder (or confirm the repo’s usual fixtures root), and
|
|
20
|
+
- Whether the mock is raw JSON, a TS module that exports objects, or both.
|
|
21
|
+
|
|
22
|
+
Do not invent a new root if the project already has a convention—search for existing `mock`, `fixtures`, or `__fixtures__` directories first.
|
|
23
|
+
|
|
24
|
+
## Alignment with the API layer
|
|
25
|
+
|
|
26
|
+
Follow the same **resources / entities** split as in `generate-api-model`:
|
|
27
|
+
|
|
28
|
+
1. Read the **`{Domain}Resource`** type from `<api-lib>/resources/<domain>/<domain>.ts` (and `resources/common.ts` if the DTO extends a shared fragment).
|
|
29
|
+
2. Read the **`{Domain}Entity`** class from `<api-lib>/entities/<domain>/<domain>.ts` and `entities/common.ts` if you need defaults or nesting behavior.
|
|
30
|
+
3. Field names in JSON must match the **resource** type (wire names), not entity getter names—unless the user explicitly wants a camelCase transport sample that still type-checks via mapping.
|
|
31
|
+
|
|
32
|
+
Generate **2–3** sample items unless the user asks otherwise. Values should be realistic and respect optional vs required fields implied by the types.
|
|
33
|
+
|
|
34
|
+
## Typical artifacts
|
|
35
|
+
|
|
36
|
+
### Fixture file (e.g. `resource.json` or `*.mock.json`)
|
|
37
|
+
|
|
38
|
+
Valid JSON array or object matching the agreed response shape; property names match `*Resource`.
|
|
39
|
+
|
|
40
|
+
### Optional helper module (e.g. `entity.ts` or `*.ts`)
|
|
41
|
+
|
|
42
|
+
- Import the resource type from the API barrel or the domain module.
|
|
43
|
+
- Import the fixture (JSON module or inline object) according to project bundler/tsconfig rules.
|
|
44
|
+
- Export functions such as `get{Domain}MockResource()` that return a **deep clone** (e.g. `JSON.parse(JSON.stringify(data))`) when callers must not mutate shared state.
|
|
45
|
+
- If tests should use entities, export helpers that return `new {Domain}Entity(resource)` from the same payload.
|
|
46
|
+
|
|
47
|
+
Annotate return types with the resource type or a dedicated mock alias (e.g. `{Domain}MockResource`) if the response is a subset or wrapper around the flat resource.
|
|
48
|
+
|
|
49
|
+
### Barrel
|
|
50
|
+
|
|
51
|
+
If the folder has `index.ts`, re-export the helper module. Update the nearest parent barrel only if that matches existing project practice.
|
|
52
|
+
|
|
53
|
+
## Constraints
|
|
54
|
+
|
|
55
|
+
- Do not drift from the **current** `*Resource` definition; if the contract changed, update the types first (see `generate-api-model`), then regenerate mocks.
|
|
56
|
+
- Keep skills **abstract**: no hard-coded app routes, copy-pasted production payloads, or long excerpts from `src/` in this file—describe steps and placeholders, not a snapshot of one endpoint.
|
package/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
EXPO_PUBLIC_API_URL=-
|
package/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
EXPO_PUBLIC_API_URL=-
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Папка .secret
|
|
2
|
+
|
|
3
|
+
Здесь хранятся **секретные и чувствительные** файлы, которые не должны попадать в репозиторий. Папка добавлена в `.gitignore`.
|
|
4
|
+
|
|
5
|
+
## Что должно храниться
|
|
6
|
+
|
|
7
|
+
### Android
|
|
8
|
+
|
|
9
|
+
| Файл | Назначение |
|
|
10
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| **android-debug.keystore** | Keystore для подписи debug-сборок. Используется в `android/app/build.gradle` (signingConfigs.debug). Передаётся как `ANDROID_DEBUG_KEYSTORE_PATH`. |
|
|
12
|
+
| **android-release.keystore** | Keystore для подписи release-сборок. Используется в `android/app/build.gradle` (signingConfigs.release). Передаётся как `ANDROID_RELEASE_KEYSTORE_PATH`. |
|
|
13
|
+
|
|
14
|
+
### iOS
|
|
15
|
+
|
|
16
|
+
| Файл | Назначение |
|
|
17
|
+
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| **ios-auth-key-centroflex.p8** | API-ключ App Store Connect для white-label Centroflex. Передаётся в Fastlane как `IOS_APP_STORE_CONNECT_KEY_FILEPATH`. |
|
|
19
|
+
| **ios-auth-key-garna.p8** | API-ключ App Store Connect для white-label Garna. Передаётся в Fastlane как `IOS_APP_STORE_CONNECT_KEY_FILEPATH`. |
|
|
20
|
+
| **ios-auth-key-mcpay.p8** | API-ключ App Store Connect для white-label McPay. Передаётся в Fastlane как `IOS_APP_STORE_CONNECT_KEY_FILEPATH`. |
|
|
21
|
+
|
|
22
|
+
### Google / Firebase
|
|
23
|
+
|
|
24
|
+
| Файл | Назначение |
|
|
25
|
+
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| **google-application.credentials.json** | Учётные данные сервисного аккаунта Google / Firebase. Путь задаётся через переменную окружения `GOOGLE_APPLICATION_CREDENTIALS`. |
|
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
## App structure
|
|
2
|
+
|
|
3
|
+
```mermaid
|
|
4
|
+
flowchart TD
|
|
5
|
+
Root[Stack] --> Auth{DID_AUTH};
|
|
6
|
+
|
|
7
|
+
Auth[asdf] -->|Нет| AuthStack[Auth Stack];
|
|
8
|
+
AuthStack --> Login[Login];
|
|
9
|
+
AuthStack --> Register[Register];
|
|
10
|
+
AuthStack --> ForgotPass[Восстановление пароля];
|
|
11
|
+
|
|
12
|
+
Auth -->|Да| MainApp[Главное приложение];
|
|
13
|
+
|
|
14
|
+
MainApp --> Drawer[DRAWER NAVIGATOR];
|
|
15
|
+
|
|
16
|
+
Drawer --> HomeStack[HOME STACK];
|
|
17
|
+
Drawer --> ProfileStack[PROFILE STACK];
|
|
18
|
+
Drawer --> Settings[Настройки];
|
|
19
|
+
Drawer --> About[О приложении];
|
|
20
|
+
|
|
21
|
+
HomeStack --> Tabs[BOTTOM TAB NAVIGATOR];
|
|
22
|
+
|
|
23
|
+
Tabs --> Feed[Лента новостей];
|
|
24
|
+
Tabs --> Explore[Обзор];
|
|
25
|
+
Tabs --> Cart[Корзина];
|
|
26
|
+
|
|
27
|
+
Feed -->|Выбрать пост| PostDetail[Детали поста];
|
|
28
|
+
PostDetail -->|Комментарии| Comments[Комментарии];
|
|
29
|
+
|
|
30
|
+
Cart --> Checkout[Оформление заказа];
|
|
31
|
+
Checkout -->|Успех| OrderSuccess[Заказ оформлен];
|
|
32
|
+
|
|
33
|
+
ProfileStack --> Profile[Профиль пользователя];
|
|
34
|
+
Profile --> EditProfile[Редактировать профиль];
|
|
35
|
+
Profile --> OrderHistory[История заказов];
|
|
36
|
+
```
|
package/app.config.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const config = {
|
|
2
|
+
expo: {
|
|
3
|
+
name: 'expo-template-fsd',
|
|
4
|
+
slug: 'expo-template-fsd',
|
|
5
|
+
scheme: 'expotemplatefsd',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
orientation: 'portrait',
|
|
8
|
+
userInterfaceStyle: 'automatic',
|
|
9
|
+
web: {
|
|
10
|
+
output: 'static',
|
|
11
|
+
favicon: './src/shared/assets/images/logo-web.png',
|
|
12
|
+
},
|
|
13
|
+
ios: {
|
|
14
|
+
supportsTablet: true,
|
|
15
|
+
bundleIdentifier: 'com.mediacube.expotemplatefsd',
|
|
16
|
+
icon: './src/shared/assets/images/logo-ios.png',
|
|
17
|
+
},
|
|
18
|
+
android: {
|
|
19
|
+
predictiveBackGestureEnabled: false,
|
|
20
|
+
package: 'com.mediacube.expotemplatefsd',
|
|
21
|
+
adaptiveIcon: {
|
|
22
|
+
backgroundColor: '#E6F4FE',
|
|
23
|
+
foregroundImage: './src/shared/assets/images/logo-android-foreground.png',
|
|
24
|
+
backgroundImage: './src/shared/assets/images/logo-android-background.png',
|
|
25
|
+
monochromeImage: './src/shared/assets/images/logo-android-monochrome.png',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
plugins: [
|
|
29
|
+
['expo-font', {}],
|
|
30
|
+
['expo-image', {}],
|
|
31
|
+
['expo-router', {}],
|
|
32
|
+
['expo-system-ui', {}],
|
|
33
|
+
['expo-web-browser', {}],
|
|
34
|
+
['expo-localization', {}],
|
|
35
|
+
['expo-secure-store', {}],
|
|
36
|
+
[
|
|
37
|
+
'expo-splash-screen',
|
|
38
|
+
{
|
|
39
|
+
backgroundColor: '#208AEF',
|
|
40
|
+
android: {
|
|
41
|
+
image: './assets/images/splash-icon.png',
|
|
42
|
+
imageWidth: 76,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
],
|
|
47
|
+
experiments: {
|
|
48
|
+
typedRoutes: true,
|
|
49
|
+
reactCompiler: true,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default config
|
package/env.d.ts
ADDED
package/eslint.config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import eslintConfig from '@mediacubeco/eslint-config'
|
|
2
|
+
import eslintConfigFSD from '@mediacubeco/eslint-config-fsd'
|
|
3
|
+
import eslintConfigReact from '@mediacubeco/eslint-config-react'
|
|
4
|
+
import eslintConfigReactNative from '@mediacubeco/eslint-config-react-native'
|
|
5
|
+
import { defineConfig } from 'eslint/config'
|
|
6
|
+
|
|
7
|
+
const config = defineConfig([
|
|
8
|
+
eslintConfigReactNative,
|
|
9
|
+
eslintConfigReact,
|
|
10
|
+
eslintConfigFSD,
|
|
11
|
+
eslintConfig,
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
export default config
|
package/i18next.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"private": false,
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"name": "@mediacubeco/expo-template-fsd",
|
|
5
|
+
"description": "Application by Mediacube developers",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "expo-router/entry",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@tanstack/query-async-storage-persister": "^5.99.0",
|
|
11
|
+
"@tanstack/react-query": "^5.99.0",
|
|
12
|
+
"@tanstack/react-query-persist-client": "^5.99.0",
|
|
13
|
+
"expo": "~55.0.15",
|
|
14
|
+
"expo-blur": "~55.0.14",
|
|
15
|
+
"expo-constants": "~55.0.14",
|
|
16
|
+
"expo-device": "~55.0.15",
|
|
17
|
+
"expo-font": "~55.0.6",
|
|
18
|
+
"expo-glass-effect": "~55.0.10",
|
|
19
|
+
"expo-haptics": "~55.0.14",
|
|
20
|
+
"expo-image": "~55.0.8",
|
|
21
|
+
"expo-linking": "~55.0.13",
|
|
22
|
+
"expo-localization": "~55.0.13",
|
|
23
|
+
"expo-router": "~55.0.12",
|
|
24
|
+
"expo-secure-store": "~55.0.13",
|
|
25
|
+
"expo-splash-screen": "~55.0.18",
|
|
26
|
+
"expo-status-bar": "~55.0.5",
|
|
27
|
+
"expo-symbols": "~55.0.7",
|
|
28
|
+
"expo-system-ui": "~55.0.15",
|
|
29
|
+
"expo-web-browser": "~55.0.14",
|
|
30
|
+
"i18next": "^26.0.5",
|
|
31
|
+
"react": "19.2.0",
|
|
32
|
+
"react-dom": "19.2.0",
|
|
33
|
+
"react-i18next": "^17.0.3",
|
|
34
|
+
"react-native": "0.83.4",
|
|
35
|
+
"react-native-gesture-handler": "~2.30.0",
|
|
36
|
+
"react-native-keyboard-controller": "1.20.7",
|
|
37
|
+
"react-native-reanimated": "4.2.1",
|
|
38
|
+
"react-native-safe-area-context": "~5.6.2",
|
|
39
|
+
"react-native-screens": "~4.23.0",
|
|
40
|
+
"react-native-svg": "15.15.3",
|
|
41
|
+
"react-native-web": "^0.21.0",
|
|
42
|
+
"react-native-worklets": "0.7.2",
|
|
43
|
+
"@mediacubeco/base": "1.0.0",
|
|
44
|
+
"@mediacubeco/base-react": "1.0.0",
|
|
45
|
+
"@mediacubeco/base-react-native": "1.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/react": "~19.2.2",
|
|
49
|
+
"eslint": "^9.39.1",
|
|
50
|
+
"husky": "^9.1.7",
|
|
51
|
+
"prettier": "^3.8.1",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"@mediacubeco/eslint-config-react-native": "1.0.0",
|
|
54
|
+
"@mediacubeco/eslint-config": "1.0.0",
|
|
55
|
+
"@mediacubeco/eslint-config-fsd": "1.0.0",
|
|
56
|
+
"@mediacubeco/eslint-config-react": "1.0.0",
|
|
57
|
+
"@mediacubeco/typescript-config": "1.0.0",
|
|
58
|
+
"@mediacubeco/prettier-config": "1.0.0",
|
|
59
|
+
"@mediacubeco/typescript-config-fsd": "1.0.0",
|
|
60
|
+
"@mediacubeco/prettier-config-fsd": "1.0.0",
|
|
61
|
+
"@mediacubeco/typescript-config-react": "1.0.0",
|
|
62
|
+
"@mediacubeco/typescript-config-react-native": "1.0.0"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"prebuild": "expo prebuild",
|
|
66
|
+
"start": "expo start",
|
|
67
|
+
"android": "expo start --android",
|
|
68
|
+
"android:device": "expo run:android -d",
|
|
69
|
+
"ios": "expo start --ios",
|
|
70
|
+
"ios:device": "expo run:ios -d",
|
|
71
|
+
"web": "expo start --web",
|
|
72
|
+
"format": "prettier --write .",
|
|
73
|
+
"lint": "eslint .",
|
|
74
|
+
"type": "tsc"
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/app/_app.tsx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Stack } from 'expo-router'
|
|
2
|
+
import { StatusBar } from 'expo-status-bar'
|
|
3
|
+
import { useNavigationStackOptions } from '~shared/lib'
|
|
4
|
+
|
|
5
|
+
const App = () => {
|
|
6
|
+
const { createStackOptions } = useNavigationStackOptions()
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
<StatusBar style={'auto'} />
|
|
11
|
+
<Stack screenOptions={createStackOptions()}>
|
|
12
|
+
<Stack.Screen name={'(root)'} />
|
|
13
|
+
</Stack>
|
|
14
|
+
</>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default App
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ApiProvider, LocalizationProvider, NavigationProvider, ThemeProvider } from '~shared/lib'
|
|
2
|
+
import App from './_app'
|
|
3
|
+
import Splash from './_splash'
|
|
4
|
+
|
|
5
|
+
const Layout = () => {
|
|
6
|
+
return (
|
|
7
|
+
<ApiProvider>
|
|
8
|
+
<ThemeProvider>
|
|
9
|
+
<LocalizationProvider>
|
|
10
|
+
<NavigationProvider>
|
|
11
|
+
<App />
|
|
12
|
+
<Splash />
|
|
13
|
+
</NavigationProvider>
|
|
14
|
+
</LocalizationProvider>
|
|
15
|
+
</ThemeProvider>
|
|
16
|
+
</ApiProvider>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default Layout
|