proagents 1.6.16 → 1.6.18
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/settings.local.json +169 -0
- package/COMMANDS.md +595 -0
- package/README.md +22 -64
- package/bin/proagents.js +0 -2
- package/lib/commands/init.js +4 -174
- package/package.json +2 -7
- package/.proagents/ai-models/README.md +0 -141
- package/.proagents/ai-models/cost-management.md +0 -362
- package/.proagents/ai-models/fallbacks.md +0 -342
- package/.proagents/ai-models/model-config.md +0 -318
- package/.proagents/ai-models/task-routing.md +0 -503
- package/.proagents/ai-training/README.md +0 -155
- package/.proagents/ai-training/continuous-learning.md +0 -413
- package/.proagents/ai-training/domain-knowledge.md +0 -378
- package/.proagents/ai-training/pattern-learning.md +0 -455
- package/.proagents/ai-training/training-data.md +0 -337
- package/.proagents/ai-training/user-preferences.md +0 -346
- package/.proagents/approval-workflows/README.md +0 -146
- package/.proagents/approval-workflows/approval-config.md +0 -332
- package/.proagents/approval-workflows/approval-stages.md +0 -503
- package/.proagents/approval-workflows/emergency-bypass.md +0 -351
- package/.proagents/approval-workflows/examples.md +0 -859
- package/.proagents/approval-workflows/notifications.md +0 -320
- package/.proagents/compliance/README.md +0 -206
- package/.proagents/compliance/access-control.md +0 -310
- package/.proagents/compliance/audit-logging.md +0 -444
- package/.proagents/compliance/compliance-frameworks.md +0 -429
- package/.proagents/compliance/reports.md +0 -491
- package/.proagents/compliance/retention-policies.md +0 -454
- package/.proagents/config-versioning/README.md +0 -120
- package/.proagents/config-versioning/changelog.md +0 -300
- package/.proagents/config-versioning/rollback.md +0 -283
- package/.proagents/config-versioning/versioning.md +0 -330
- package/.proagents/contract-testing/README.md +0 -223
- package/.proagents/contract-testing/contract-testing.md +0 -614
- package/.proagents/contract-testing/pact-integration.md +0 -507
- package/.proagents/contract-testing/schema-validation.md +0 -565
- package/.proagents/dependency-management/README.md +0 -140
- package/.proagents/dependency-management/automation.md +0 -363
- package/.proagents/dependency-management/compatibility.md +0 -319
- package/.proagents/dependency-management/security-scanning.md +0 -413
- package/.proagents/dependency-management/update-policies.md +0 -374
- package/.proagents/disaster-recovery/README.md +0 -247
- package/.proagents/disaster-recovery/automation.md +0 -366
- package/.proagents/disaster-recovery/backup-recovery.md +0 -571
- package/.proagents/disaster-recovery/incident-response.md +0 -565
- package/.proagents/disaster-recovery/rollback-procedures.md +0 -499
- package/.proagents/disaster-recovery/runbooks.md +0 -603
- package/.proagents/disaster-recovery/scenarios.md +0 -892
- package/.proagents/disaster-recovery/testing.md +0 -438
- package/.proagents/environments/README.md +0 -244
- package/.proagents/environments/configuration.md +0 -437
- package/.proagents/environments/promotion.md +0 -434
- package/.proagents/environments/setup.md +0 -420
- package/.proagents/examples/README.md +0 -55
- package/.proagents/examples/backend-nodejs/README.md +0 -188
- package/.proagents/examples/backend-nodejs/complete-conversation.md +0 -601
- package/.proagents/examples/backend-nodejs/proagents.config.yaml +0 -415
- package/.proagents/examples/backend-nodejs/workflow-example.md +0 -909
- package/.proagents/examples/fullstack-nextjs/README.md +0 -155
- package/.proagents/examples/fullstack-nextjs/complete-conversation.md +0 -604
- package/.proagents/examples/fullstack-nextjs/proagents.config.yaml +0 -287
- package/.proagents/examples/fullstack-nextjs/workflow-example.md +0 -553
- package/.proagents/examples/mobile-react-native/README.md +0 -171
- package/.proagents/examples/mobile-react-native/complete-conversation.md +0 -825
- package/.proagents/examples/mobile-react-native/proagents.config.yaml +0 -330
- package/.proagents/examples/mobile-react-native/workflow-example.md +0 -723
- package/.proagents/examples/web-frontend-react/README.md +0 -125
- package/.proagents/examples/web-frontend-react/complete-conversation.md +0 -556
- package/.proagents/examples/web-frontend-react/proagents.config.yaml +0 -183
- package/.proagents/examples/web-frontend-react/workflow-example.md +0 -603
- package/.proagents/existing-projects/README.md +0 -65
- package/.proagents/existing-projects/challenges.md +0 -861
- package/.proagents/existing-projects/coexistence-mode.md +0 -483
- package/.proagents/existing-projects/compatibility-assessment.md +0 -541
- package/.proagents/existing-projects/gradual-adoption.md +0 -515
- package/.proagents/existing-projects/migration-strategies.md +0 -788
- package/.proagents/existing-projects/pattern-reconciliation.md +0 -489
- package/.proagents/existing-projects/team-onboarding.md +0 -617
- package/.proagents/existing-projects/technical-debt-handling.md +0 -644
- package/.proagents/feature-flags/README.md +0 -263
- package/.proagents/feature-flags/ab-testing.md +0 -413
- package/.proagents/feature-flags/configuration.md +0 -420
- package/.proagents/feature-flags/kill-switches.md +0 -444
- package/.proagents/feature-flags/rollout-strategies.md +0 -392
- package/.proagents/history.log +0 -12
- package/.proagents/i18n/README.md +0 -133
- package/.proagents/i18n/extraction.md +0 -433
- package/.proagents/i18n/tms-integration.md +0 -332
- package/.proagents/i18n/translation-workflow.md +0 -413
- package/.proagents/i18n/validation.md +0 -355
- package/.proagents/logging/README.md +0 -276
- package/.proagents/logging/aggregation.md +0 -475
- package/.proagents/logging/log-levels.md +0 -376
- package/.proagents/logging/sensitive-data.md +0 -423
- package/.proagents/logging/structured-logging.md +0 -406
- package/.proagents/metrics/README.md +0 -69
- package/.proagents/metrics/code-quality-kpis.md +0 -461
- package/.proagents/metrics/deployment-metrics.md +0 -517
- package/.proagents/metrics/developer-productivity.md +0 -368
- package/.proagents/metrics/learning-effectiveness.md +0 -478
- package/.proagents/migrations/README.md +0 -77
- package/.proagents/migrations/from-claude-projects.md +0 -313
- package/.proagents/migrations/from-cursor-rules.md +0 -345
- package/.proagents/migrations/from-custom-workflows.md +0 -410
- package/.proagents/monitoring/README.md +0 -308
- package/.proagents/monitoring/alerting.md +0 -449
- package/.proagents/monitoring/dashboards.md +0 -454
- package/.proagents/monitoring/health-checks.md +0 -436
- package/.proagents/monitoring/metrics.md +0 -434
- package/.proagents/multi-project/README.md +0 -170
- package/.proagents/multi-project/coordinated-deploy.md +0 -510
- package/.proagents/multi-project/cross-project-deps.md +0 -395
- package/.proagents/multi-project/unified-changelog.md +0 -477
- package/.proagents/multi-project/walkthroughs/monorepo-setup.md +0 -787
- package/.proagents/multi-project/workspace-config.md +0 -408
- package/.proagents/notifications/README.md +0 -151
- package/.proagents/notifications/channels.md +0 -457
- package/.proagents/notifications/preferences.md +0 -415
- package/.proagents/notifications/routing.md +0 -449
- package/.proagents/notifications/scheduling.md +0 -425
- package/.proagents/notifications/templates.md +0 -446
- package/.proagents/offline-mode/README.md +0 -145
- package/.proagents/offline-mode/caching.md +0 -344
- package/.proagents/offline-mode/offline-operations.md +0 -312
- package/.proagents/offline-mode/queue-specifications.md +0 -679
- package/.proagents/offline-mode/sync.md +0 -475
- package/.proagents/parallel-features/README.md +0 -85
- package/.proagents/parallel-features/conflict-detection.md +0 -226
- package/.proagents/parallel-features/dependency-management.md +0 -392
- package/.proagents/parallel-features/merge-coordination.md +0 -506
- package/.proagents/parallel-features/tracking-system.md +0 -416
- package/.proagents/performance/README.md +0 -59
- package/.proagents/performance/bundle-analysis.md +0 -375
- package/.proagents/performance/load-testing.md +0 -563
- package/.proagents/performance/runtime-metrics.md +0 -489
- package/.proagents/performance/web-vitals.md +0 -425
- package/.proagents/plugins/README.md +0 -139
- package/.proagents/plugins/creating-plugins.md +0 -504
- package/.proagents/plugins/plugin-api.md +0 -467
- package/.proagents/plugins/plugin-registry.md +0 -276
- package/.proagents/reporting/README.md +0 -158
- package/.proagents/reporting/dashboards.md +0 -366
- package/.proagents/reporting/exports.md +0 -524
- package/.proagents/reporting/quality-metrics.md +0 -385
- package/.proagents/reporting/templates/README.md +0 -56
- package/.proagents/reporting/templates/dashboard-config.json +0 -187
- package/.proagents/reporting/templates/metrics-queries.md +0 -427
- package/.proagents/reporting/templates/react-dashboard.tsx +0 -544
- package/.proagents/reporting/templates/widgets.md +0 -451
- package/.proagents/reporting/velocity-metrics.md +0 -340
- package/.proagents/reverse-engineering/README.md +0 -151
- package/.proagents/reverse-engineering/architecture-extraction.md +0 -325
- package/.proagents/reverse-engineering/code-analysis.md +0 -377
- package/.proagents/reverse-engineering/dependency-mapping.md +0 -567
- package/.proagents/reverse-engineering/diagram-generation.md +0 -586
- package/.proagents/reverse-engineering/documentation-generation.md +0 -468
- package/.proagents/reverse-engineering/pattern-detection.md +0 -569
- package/.proagents/reverse-engineering/quality-assessment.md +0 -733
- package/.proagents/secrets/README.md +0 -278
- package/.proagents/secrets/access-control.md +0 -443
- package/.proagents/secrets/rotation.md +0 -403
- package/.proagents/secrets/scanning.md +0 -487
- package/.proagents/secrets/storage.md +0 -394
- package/.proagents/webhooks/README.md +0 -126
- package/.proagents/webhooks/endpoints.md +0 -298
- package/.proagents/webhooks/events.md +0 -316
- package/.proagents/webhooks/payloads.md +0 -325
- package/.proagents/webhooks/reliability.md +0 -363
- package/.proagents/webhooks/security.md +0 -380
|
@@ -1,723 +0,0 @@
|
|
|
1
|
-
# Mobile React Native Workflow Example
|
|
2
|
-
|
|
3
|
-
Complete workflow for adding a User Profile feature to a React Native application.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Feature Request
|
|
8
|
-
|
|
9
|
-
> "Add a user profile screen with editable information, profile picture, and settings access."
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Phase 0: Initialization
|
|
14
|
-
|
|
15
|
-
### Mode Detection
|
|
16
|
-
```
|
|
17
|
-
Detected: Full Workflow Mode
|
|
18
|
-
Reason: New feature with UI, navigation, and API integration
|
|
19
|
-
Project Type: Mobile (React Native)
|
|
20
|
-
Platforms: iOS, Android
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### Branch Creation
|
|
24
|
-
```bash
|
|
25
|
-
git checkout -b feature/user-profile
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Phase 1: Codebase Analysis
|
|
31
|
-
|
|
32
|
-
### Project Structure
|
|
33
|
-
```
|
|
34
|
-
Existing structure detected:
|
|
35
|
-
├── src/
|
|
36
|
-
│ ├── screens/
|
|
37
|
-
│ ├── components/
|
|
38
|
-
│ ├── navigation/
|
|
39
|
-
│ ├── services/
|
|
40
|
-
│ ├── stores/
|
|
41
|
-
│ └── theme/
|
|
42
|
-
├── ios/
|
|
43
|
-
├── android/
|
|
44
|
-
└── [configs]
|
|
45
|
-
|
|
46
|
-
Framework: React Native 0.73 + Expo
|
|
47
|
-
Navigation: React Navigation 6
|
|
48
|
-
State: Zustand
|
|
49
|
-
Styling: StyleSheet
|
|
50
|
-
API: Axios + React Query
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Existing Patterns
|
|
54
|
-
```yaml
|
|
55
|
-
patterns_detected:
|
|
56
|
-
screens: "src/screens/{Name}/{Name}Screen.tsx"
|
|
57
|
-
components: "src/components/{category}/{Name}.tsx"
|
|
58
|
-
navigation: "Stack navigator with typed routes"
|
|
59
|
-
api_calls: "React Query hooks in src/hooks/"
|
|
60
|
-
styling: "StyleSheet.create at bottom of file"
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Phase 2: Requirements
|
|
66
|
-
|
|
67
|
-
### User Stories
|
|
68
|
-
```markdown
|
|
69
|
-
1. As a user, I want to view my profile information
|
|
70
|
-
- Acceptance: Shows name, email, bio, and profile picture
|
|
71
|
-
- Acceptance: Shows member since date
|
|
72
|
-
|
|
73
|
-
2. As a user, I want to edit my profile
|
|
74
|
-
- Acceptance: Can tap to edit name, bio
|
|
75
|
-
- Acceptance: Changes save to server
|
|
76
|
-
- Acceptance: Shows loading/success feedback
|
|
77
|
-
|
|
78
|
-
3. As a user, I want to change my profile picture
|
|
79
|
-
- Acceptance: Can select from gallery
|
|
80
|
-
- Acceptance: Can take new photo
|
|
81
|
-
- Acceptance: Image uploads to server
|
|
82
|
-
|
|
83
|
-
4. As a user, I want to access settings
|
|
84
|
-
- Acceptance: Settings button navigates to settings
|
|
85
|
-
- Acceptance: Logout option available
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Technical Requirements
|
|
89
|
-
```yaml
|
|
90
|
-
requirements:
|
|
91
|
-
authentication: "Required - must be logged in"
|
|
92
|
-
permissions:
|
|
93
|
-
ios: "Camera, Photo Library"
|
|
94
|
-
android: "Camera, Storage"
|
|
95
|
-
api:
|
|
96
|
-
- "GET /api/users/me"
|
|
97
|
-
- "PUT /api/users/me"
|
|
98
|
-
- "POST /api/users/me/avatar"
|
|
99
|
-
offline: "Show cached profile when offline"
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## Phase 3: UI Design
|
|
105
|
-
|
|
106
|
-
### Screen Layout
|
|
107
|
-
```
|
|
108
|
-
ProfileScreen
|
|
109
|
-
├── SafeAreaView
|
|
110
|
-
│ ├── Header
|
|
111
|
-
│ │ ├── BackButton
|
|
112
|
-
│ │ ├── Title ("Profile")
|
|
113
|
-
│ │ └── SettingsButton
|
|
114
|
-
│ ├── ScrollView
|
|
115
|
-
│ │ ├── AvatarSection
|
|
116
|
-
│ │ │ ├── Avatar (touchable)
|
|
117
|
-
│ │ │ └── ChangePhotoButton
|
|
118
|
-
│ │ ├── InfoSection
|
|
119
|
-
│ │ │ ├── NameField (editable)
|
|
120
|
-
│ │ │ ├── EmailField (read-only)
|
|
121
|
-
│ │ │ ├── BioField (editable)
|
|
122
|
-
│ │ │ └── MemberSince
|
|
123
|
-
│ │ └── StatsSection
|
|
124
|
-
│ │ ├── PostsCount
|
|
125
|
-
│ │ ├── FollowersCount
|
|
126
|
-
│ │ └── FollowingCount
|
|
127
|
-
│ └── SaveButton (when editing)
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Design Guidelines
|
|
131
|
-
```yaml
|
|
132
|
-
design:
|
|
133
|
-
follows: ["ios-hig", "material-design"]
|
|
134
|
-
|
|
135
|
-
avatar:
|
|
136
|
-
size: 120
|
|
137
|
-
border_radius: 60
|
|
138
|
-
placeholder: "User initials"
|
|
139
|
-
|
|
140
|
-
spacing:
|
|
141
|
-
section_gap: 24
|
|
142
|
-
field_gap: 16
|
|
143
|
-
padding: 20
|
|
144
|
-
|
|
145
|
-
colors:
|
|
146
|
-
primary: "theme.colors.primary"
|
|
147
|
-
text: "theme.colors.text"
|
|
148
|
-
muted: "theme.colors.textSecondary"
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## Phase 4: Implementation Plan
|
|
154
|
-
|
|
155
|
-
### File Structure
|
|
156
|
-
```
|
|
157
|
-
Create:
|
|
158
|
-
├── src/screens/Profile/
|
|
159
|
-
│ ├── ProfileScreen.tsx
|
|
160
|
-
│ ├── ProfileScreen.test.tsx
|
|
161
|
-
│ ├── components/
|
|
162
|
-
│ │ ├── AvatarSection.tsx
|
|
163
|
-
│ │ ├── InfoSection.tsx
|
|
164
|
-
│ │ └── StatsSection.tsx
|
|
165
|
-
│ └── index.ts
|
|
166
|
-
├── src/components/common/
|
|
167
|
-
│ ├── Avatar.tsx
|
|
168
|
-
│ └── EditableField.tsx
|
|
169
|
-
├── src/hooks/
|
|
170
|
-
│ ├── useProfile.ts
|
|
171
|
-
│ └── useImagePicker.ts
|
|
172
|
-
├── src/services/
|
|
173
|
-
│ └── profileService.ts
|
|
174
|
-
└── src/types/
|
|
175
|
-
└── profile.types.ts
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Navigation Update
|
|
179
|
-
```typescript
|
|
180
|
-
// Add to navigation types
|
|
181
|
-
type RootStackParamList = {
|
|
182
|
-
// ... existing routes
|
|
183
|
-
Profile: undefined;
|
|
184
|
-
Settings: undefined;
|
|
185
|
-
};
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
## Phase 5: Implementation
|
|
191
|
-
|
|
192
|
-
### Types (src/types/profile.types.ts)
|
|
193
|
-
```typescript
|
|
194
|
-
export interface UserProfile {
|
|
195
|
-
id: string;
|
|
196
|
-
name: string;
|
|
197
|
-
email: string;
|
|
198
|
-
bio: string | null;
|
|
199
|
-
avatarUrl: string | null;
|
|
200
|
-
createdAt: string;
|
|
201
|
-
stats: {
|
|
202
|
-
posts: number;
|
|
203
|
-
followers: number;
|
|
204
|
-
following: number;
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export interface UpdateProfileInput {
|
|
209
|
-
name?: string;
|
|
210
|
-
bio?: string;
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Profile Service (src/services/profileService.ts)
|
|
215
|
-
```typescript
|
|
216
|
-
import api from './api';
|
|
217
|
-
import type { UserProfile, UpdateProfileInput } from '@/types/profile.types';
|
|
218
|
-
|
|
219
|
-
export const profileService = {
|
|
220
|
-
async getProfile(): Promise<UserProfile> {
|
|
221
|
-
const { data } = await api.get('/users/me');
|
|
222
|
-
return data.data;
|
|
223
|
-
},
|
|
224
|
-
|
|
225
|
-
async updateProfile(input: UpdateProfileInput): Promise<UserProfile> {
|
|
226
|
-
const { data } = await api.put('/users/me', input);
|
|
227
|
-
return data.data;
|
|
228
|
-
},
|
|
229
|
-
|
|
230
|
-
async uploadAvatar(imageUri: string): Promise<{ avatarUrl: string }> {
|
|
231
|
-
const formData = new FormData();
|
|
232
|
-
formData.append('avatar', {
|
|
233
|
-
uri: imageUri,
|
|
234
|
-
type: 'image/jpeg',
|
|
235
|
-
name: 'avatar.jpg',
|
|
236
|
-
} as any);
|
|
237
|
-
|
|
238
|
-
const { data } = await api.post('/users/me/avatar', formData, {
|
|
239
|
-
headers: { 'Content-Type': 'multipart/form-data' },
|
|
240
|
-
});
|
|
241
|
-
return data.data;
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Profile Hook (src/hooks/useProfile.ts)
|
|
247
|
-
```typescript
|
|
248
|
-
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
249
|
-
import { profileService } from '@/services/profileService';
|
|
250
|
-
import type { UpdateProfileInput } from '@/types/profile.types';
|
|
251
|
-
|
|
252
|
-
export function useProfile() {
|
|
253
|
-
return useQuery({
|
|
254
|
-
queryKey: ['profile'],
|
|
255
|
-
queryFn: profileService.getProfile,
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export function useUpdateProfile() {
|
|
260
|
-
const queryClient = useQueryClient();
|
|
261
|
-
|
|
262
|
-
return useMutation({
|
|
263
|
-
mutationFn: (input: UpdateProfileInput) =>
|
|
264
|
-
profileService.updateProfile(input),
|
|
265
|
-
onSuccess: () => {
|
|
266
|
-
queryClient.invalidateQueries({ queryKey: ['profile'] });
|
|
267
|
-
},
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export function useUploadAvatar() {
|
|
272
|
-
const queryClient = useQueryClient();
|
|
273
|
-
|
|
274
|
-
return useMutation({
|
|
275
|
-
mutationFn: (imageUri: string) => profileService.uploadAvatar(imageUri),
|
|
276
|
-
onSuccess: () => {
|
|
277
|
-
queryClient.invalidateQueries({ queryKey: ['profile'] });
|
|
278
|
-
},
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### Image Picker Hook (src/hooks/useImagePicker.ts)
|
|
284
|
-
```typescript
|
|
285
|
-
import { useState } from 'react';
|
|
286
|
-
import * as ImagePicker from 'expo-image-picker';
|
|
287
|
-
import { Alert, Platform } from 'react-native';
|
|
288
|
-
|
|
289
|
-
export function useImagePicker() {
|
|
290
|
-
const [loading, setLoading] = useState(false);
|
|
291
|
-
|
|
292
|
-
const requestPermission = async () => {
|
|
293
|
-
if (Platform.OS !== 'web') {
|
|
294
|
-
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
295
|
-
if (status !== 'granted') {
|
|
296
|
-
Alert.alert(
|
|
297
|
-
'Permission Required',
|
|
298
|
-
'Please allow access to your photo library.'
|
|
299
|
-
);
|
|
300
|
-
return false;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return true;
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const pickImage = async (): Promise<string | null> => {
|
|
307
|
-
const hasPermission = await requestPermission();
|
|
308
|
-
if (!hasPermission) return null;
|
|
309
|
-
|
|
310
|
-
setLoading(true);
|
|
311
|
-
try {
|
|
312
|
-
const result = await ImagePicker.launchImageLibraryAsync({
|
|
313
|
-
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
314
|
-
allowsEditing: true,
|
|
315
|
-
aspect: [1, 1],
|
|
316
|
-
quality: 0.8,
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
if (!result.canceled && result.assets[0]) {
|
|
320
|
-
return result.assets[0].uri;
|
|
321
|
-
}
|
|
322
|
-
return null;
|
|
323
|
-
} finally {
|
|
324
|
-
setLoading(false);
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
const takePhoto = async (): Promise<string | null> => {
|
|
329
|
-
const { status } = await ImagePicker.requestCameraPermissionsAsync();
|
|
330
|
-
if (status !== 'granted') {
|
|
331
|
-
Alert.alert('Permission Required', 'Please allow camera access.');
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
setLoading(true);
|
|
336
|
-
try {
|
|
337
|
-
const result = await ImagePicker.launchCameraAsync({
|
|
338
|
-
allowsEditing: true,
|
|
339
|
-
aspect: [1, 1],
|
|
340
|
-
quality: 0.8,
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
if (!result.canceled && result.assets[0]) {
|
|
344
|
-
return result.assets[0].uri;
|
|
345
|
-
}
|
|
346
|
-
return null;
|
|
347
|
-
} finally {
|
|
348
|
-
setLoading(false);
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
return { pickImage, takePhoto, loading };
|
|
353
|
-
}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### Avatar Component (src/components/common/Avatar.tsx)
|
|
357
|
-
```typescript
|
|
358
|
-
import React, { memo } from 'react';
|
|
359
|
-
import {
|
|
360
|
-
View,
|
|
361
|
-
Image,
|
|
362
|
-
Text,
|
|
363
|
-
StyleSheet,
|
|
364
|
-
TouchableOpacity,
|
|
365
|
-
ActivityIndicator,
|
|
366
|
-
} from 'react-native';
|
|
367
|
-
import { colors } from '@/theme/colors';
|
|
368
|
-
|
|
369
|
-
interface AvatarProps {
|
|
370
|
-
uri?: string | null;
|
|
371
|
-
name: string;
|
|
372
|
-
size?: number;
|
|
373
|
-
onPress?: () => void;
|
|
374
|
-
loading?: boolean;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
export const Avatar = memo(function Avatar({
|
|
378
|
-
uri,
|
|
379
|
-
name,
|
|
380
|
-
size = 80,
|
|
381
|
-
onPress,
|
|
382
|
-
loading,
|
|
383
|
-
}: AvatarProps) {
|
|
384
|
-
const initials = name
|
|
385
|
-
.split(' ')
|
|
386
|
-
.map((n) => n[0])
|
|
387
|
-
.join('')
|
|
388
|
-
.toUpperCase()
|
|
389
|
-
.slice(0, 2);
|
|
390
|
-
|
|
391
|
-
const content = (
|
|
392
|
-
<View style={[styles.container, { width: size, height: size, borderRadius: size / 2 }]}>
|
|
393
|
-
{loading ? (
|
|
394
|
-
<ActivityIndicator color={colors.primary} />
|
|
395
|
-
) : uri ? (
|
|
396
|
-
<Image
|
|
397
|
-
source={{ uri }}
|
|
398
|
-
style={[styles.image, { borderRadius: size / 2 }]}
|
|
399
|
-
/>
|
|
400
|
-
) : (
|
|
401
|
-
<Text style={[styles.initials, { fontSize: size / 3 }]}>{initials}</Text>
|
|
402
|
-
)}
|
|
403
|
-
</View>
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
if (onPress) {
|
|
407
|
-
return <TouchableOpacity onPress={onPress}>{content}</TouchableOpacity>;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return content;
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
const styles = StyleSheet.create({
|
|
414
|
-
container: {
|
|
415
|
-
backgroundColor: colors.surface,
|
|
416
|
-
justifyContent: 'center',
|
|
417
|
-
alignItems: 'center',
|
|
418
|
-
overflow: 'hidden',
|
|
419
|
-
},
|
|
420
|
-
image: {
|
|
421
|
-
width: '100%',
|
|
422
|
-
height: '100%',
|
|
423
|
-
},
|
|
424
|
-
initials: {
|
|
425
|
-
color: colors.primary,
|
|
426
|
-
fontWeight: '600',
|
|
427
|
-
},
|
|
428
|
-
});
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
### Profile Screen (src/screens/Profile/ProfileScreen.tsx)
|
|
432
|
-
```typescript
|
|
433
|
-
import React, { useState, useCallback } from 'react';
|
|
434
|
-
import {
|
|
435
|
-
View,
|
|
436
|
-
ScrollView,
|
|
437
|
-
StyleSheet,
|
|
438
|
-
Alert,
|
|
439
|
-
ActionSheetIOS,
|
|
440
|
-
Platform,
|
|
441
|
-
} from 'react-native';
|
|
442
|
-
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
443
|
-
import { useNavigation } from '@react-navigation/native';
|
|
444
|
-
import { useProfile, useUpdateProfile, useUploadAvatar } from '@/hooks/useProfile';
|
|
445
|
-
import { useImagePicker } from '@/hooks/useImagePicker';
|
|
446
|
-
import { Header } from '@/components/layout/Header';
|
|
447
|
-
import { Button } from '@/components/common/Button';
|
|
448
|
-
import { AvatarSection } from './components/AvatarSection';
|
|
449
|
-
import { InfoSection } from './components/InfoSection';
|
|
450
|
-
import { StatsSection } from './components/StatsSection';
|
|
451
|
-
import { colors } from '@/theme/colors';
|
|
452
|
-
|
|
453
|
-
export function ProfileScreen() {
|
|
454
|
-
const navigation = useNavigation();
|
|
455
|
-
const { data: profile, isLoading, error } = useProfile();
|
|
456
|
-
const updateProfile = useUpdateProfile();
|
|
457
|
-
const uploadAvatar = useUploadAvatar();
|
|
458
|
-
const { pickImage, takePhoto } = useImagePicker();
|
|
459
|
-
|
|
460
|
-
const [isEditing, setIsEditing] = useState(false);
|
|
461
|
-
const [editedName, setEditedName] = useState('');
|
|
462
|
-
const [editedBio, setEditedBio] = useState('');
|
|
463
|
-
|
|
464
|
-
const handleEditPress = useCallback(() => {
|
|
465
|
-
if (profile) {
|
|
466
|
-
setEditedName(profile.name);
|
|
467
|
-
setEditedBio(profile.bio || '');
|
|
468
|
-
setIsEditing(true);
|
|
469
|
-
}
|
|
470
|
-
}, [profile]);
|
|
471
|
-
|
|
472
|
-
const handleSave = useCallback(async () => {
|
|
473
|
-
try {
|
|
474
|
-
await updateProfile.mutateAsync({
|
|
475
|
-
name: editedName,
|
|
476
|
-
bio: editedBio,
|
|
477
|
-
});
|
|
478
|
-
setIsEditing(false);
|
|
479
|
-
} catch (error) {
|
|
480
|
-
Alert.alert('Error', 'Failed to update profile');
|
|
481
|
-
}
|
|
482
|
-
}, [editedName, editedBio, updateProfile]);
|
|
483
|
-
|
|
484
|
-
const handleChangePhoto = useCallback(() => {
|
|
485
|
-
if (Platform.OS === 'ios') {
|
|
486
|
-
ActionSheetIOS.showActionSheetWithOptions(
|
|
487
|
-
{
|
|
488
|
-
options: ['Cancel', 'Take Photo', 'Choose from Library'],
|
|
489
|
-
cancelButtonIndex: 0,
|
|
490
|
-
},
|
|
491
|
-
async (buttonIndex) => {
|
|
492
|
-
let imageUri: string | null = null;
|
|
493
|
-
if (buttonIndex === 1) imageUri = await takePhoto();
|
|
494
|
-
if (buttonIndex === 2) imageUri = await pickImage();
|
|
495
|
-
|
|
496
|
-
if (imageUri) {
|
|
497
|
-
try {
|
|
498
|
-
await uploadAvatar.mutateAsync(imageUri);
|
|
499
|
-
} catch (error) {
|
|
500
|
-
Alert.alert('Error', 'Failed to upload photo');
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
);
|
|
505
|
-
} else {
|
|
506
|
-
Alert.alert('Change Photo', 'Choose an option', [
|
|
507
|
-
{ text: 'Cancel', style: 'cancel' },
|
|
508
|
-
{
|
|
509
|
-
text: 'Take Photo',
|
|
510
|
-
onPress: async () => {
|
|
511
|
-
const uri = await takePhoto();
|
|
512
|
-
if (uri) uploadAvatar.mutateAsync(uri);
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
text: 'Choose from Library',
|
|
517
|
-
onPress: async () => {
|
|
518
|
-
const uri = await pickImage();
|
|
519
|
-
if (uri) uploadAvatar.mutateAsync(uri);
|
|
520
|
-
},
|
|
521
|
-
},
|
|
522
|
-
]);
|
|
523
|
-
}
|
|
524
|
-
}, [takePhoto, pickImage, uploadAvatar]);
|
|
525
|
-
|
|
526
|
-
if (isLoading) {
|
|
527
|
-
return <LoadingScreen />;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
if (error || !profile) {
|
|
531
|
-
return <ErrorScreen onRetry={() => {}} />;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
return (
|
|
535
|
-
<SafeAreaView style={styles.container} edges={['top']}>
|
|
536
|
-
<Header
|
|
537
|
-
title="Profile"
|
|
538
|
-
rightAction={{
|
|
539
|
-
icon: 'settings',
|
|
540
|
-
onPress: () => navigation.navigate('Settings'),
|
|
541
|
-
}}
|
|
542
|
-
/>
|
|
543
|
-
|
|
544
|
-
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
|
545
|
-
<AvatarSection
|
|
546
|
-
avatarUrl={profile.avatarUrl}
|
|
547
|
-
name={profile.name}
|
|
548
|
-
onChangePhoto={handleChangePhoto}
|
|
549
|
-
uploading={uploadAvatar.isPending}
|
|
550
|
-
/>
|
|
551
|
-
|
|
552
|
-
<InfoSection
|
|
553
|
-
profile={profile}
|
|
554
|
-
isEditing={isEditing}
|
|
555
|
-
editedName={editedName}
|
|
556
|
-
editedBio={editedBio}
|
|
557
|
-
onNameChange={setEditedName}
|
|
558
|
-
onBioChange={setEditedBio}
|
|
559
|
-
onEditPress={handleEditPress}
|
|
560
|
-
/>
|
|
561
|
-
|
|
562
|
-
<StatsSection stats={profile.stats} />
|
|
563
|
-
|
|
564
|
-
{isEditing && (
|
|
565
|
-
<View style={styles.actions}>
|
|
566
|
-
<Button
|
|
567
|
-
title="Cancel"
|
|
568
|
-
variant="secondary"
|
|
569
|
-
onPress={() => setIsEditing(false)}
|
|
570
|
-
/>
|
|
571
|
-
<Button
|
|
572
|
-
title="Save Changes"
|
|
573
|
-
onPress={handleSave}
|
|
574
|
-
loading={updateProfile.isPending}
|
|
575
|
-
/>
|
|
576
|
-
</View>
|
|
577
|
-
)}
|
|
578
|
-
</ScrollView>
|
|
579
|
-
</SafeAreaView>
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const styles = StyleSheet.create({
|
|
584
|
-
container: {
|
|
585
|
-
flex: 1,
|
|
586
|
-
backgroundColor: colors.background,
|
|
587
|
-
},
|
|
588
|
-
content: {
|
|
589
|
-
flex: 1,
|
|
590
|
-
padding: 20,
|
|
591
|
-
},
|
|
592
|
-
actions: {
|
|
593
|
-
flexDirection: 'row',
|
|
594
|
-
gap: 12,
|
|
595
|
-
marginTop: 24,
|
|
596
|
-
},
|
|
597
|
-
});
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
---
|
|
601
|
-
|
|
602
|
-
## Phase 6: Testing
|
|
603
|
-
|
|
604
|
-
### Test Files
|
|
605
|
-
```
|
|
606
|
-
src/screens/Profile/ProfileScreen.test.tsx
|
|
607
|
-
src/hooks/useProfile.test.ts
|
|
608
|
-
src/components/common/Avatar.test.tsx
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
### Sample Test
|
|
612
|
-
```typescript
|
|
613
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
|
|
614
|
-
import { ProfileScreen } from './ProfileScreen';
|
|
615
|
-
import { useProfile } from '@/hooks/useProfile';
|
|
616
|
-
|
|
617
|
-
jest.mock('@/hooks/useProfile');
|
|
618
|
-
|
|
619
|
-
describe('ProfileScreen', () => {
|
|
620
|
-
const mockProfile = {
|
|
621
|
-
id: '1',
|
|
622
|
-
name: 'John Doe',
|
|
623
|
-
email: 'john@example.com',
|
|
624
|
-
bio: 'Hello!',
|
|
625
|
-
avatarUrl: null,
|
|
626
|
-
stats: { posts: 10, followers: 100, following: 50 },
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
beforeEach(() => {
|
|
630
|
-
(useProfile as jest.Mock).mockReturnValue({
|
|
631
|
-
data: mockProfile,
|
|
632
|
-
isLoading: false,
|
|
633
|
-
});
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
it('renders profile information', () => {
|
|
637
|
-
render(<ProfileScreen />);
|
|
638
|
-
|
|
639
|
-
expect(screen.getByText('John Doe')).toBeTruthy();
|
|
640
|
-
expect(screen.getByText('john@example.com')).toBeTruthy();
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
it('enters edit mode when edit is pressed', () => {
|
|
644
|
-
render(<ProfileScreen />);
|
|
645
|
-
|
|
646
|
-
fireEvent.press(screen.getByText('Edit'));
|
|
647
|
-
|
|
648
|
-
expect(screen.getByText('Save Changes')).toBeTruthy();
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
---
|
|
654
|
-
|
|
655
|
-
## Phase 7: Platform Testing
|
|
656
|
-
|
|
657
|
-
### iOS Testing
|
|
658
|
-
```
|
|
659
|
-
Tested on:
|
|
660
|
-
- iPhone 15 Pro (iOS 17)
|
|
661
|
-
- iPhone SE (iOS 16)
|
|
662
|
-
- iPad Pro (iPadOS 17)
|
|
663
|
-
|
|
664
|
-
Results:
|
|
665
|
-
✅ All functionality working
|
|
666
|
-
✅ Safe area handling correct
|
|
667
|
-
✅ Keyboard avoiding works
|
|
668
|
-
✅ Image picker permissions work
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
### Android Testing
|
|
672
|
-
```
|
|
673
|
-
Tested on:
|
|
674
|
-
- Pixel 7 (Android 14)
|
|
675
|
-
- Samsung Galaxy S21 (Android 13)
|
|
676
|
-
- Emulator (Android 12)
|
|
677
|
-
|
|
678
|
-
Results:
|
|
679
|
-
✅ All functionality working
|
|
680
|
-
✅ Status bar handling correct
|
|
681
|
-
✅ Permissions flow works
|
|
682
|
-
✅ Action sheet alternative works
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
---
|
|
686
|
-
|
|
687
|
-
## Phase 8: Deployment
|
|
688
|
-
|
|
689
|
-
### Build Commands
|
|
690
|
-
```bash
|
|
691
|
-
# iOS
|
|
692
|
-
eas build --platform ios --profile production
|
|
693
|
-
|
|
694
|
-
# Android
|
|
695
|
-
eas build --platform android --profile production
|
|
696
|
-
```
|
|
697
|
-
|
|
698
|
-
### App Store Submission
|
|
699
|
-
- Updated app description
|
|
700
|
-
- New screenshots with profile feature
|
|
701
|
-
- Privacy policy updated (camera access)
|
|
702
|
-
|
|
703
|
-
---
|
|
704
|
-
|
|
705
|
-
## Summary
|
|
706
|
-
|
|
707
|
-
```
|
|
708
|
-
Feature: User Profile Screen
|
|
709
|
-
Duration: 5 hours
|
|
710
|
-
Files Created: 12
|
|
711
|
-
Tests Added: 8
|
|
712
|
-
Coverage: 85%
|
|
713
|
-
|
|
714
|
-
Platform Support:
|
|
715
|
-
- iOS 15+ ✅
|
|
716
|
-
- Android 12+ ✅
|
|
717
|
-
|
|
718
|
-
Decisions Made:
|
|
719
|
-
- Used Expo ImagePicker for cross-platform compatibility
|
|
720
|
-
- Used ActionSheet for iOS-native feel
|
|
721
|
-
- Cached profile data with React Query
|
|
722
|
-
- Followed existing screen/component patterns
|
|
723
|
-
```
|