create-aws-project 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/__tests__/generator/replace-tokens.spec.d.ts +1 -0
- package/dist/__tests__/generator/replace-tokens.spec.js +281 -0
- package/dist/__tests__/generator.spec.d.ts +1 -0
- package/dist/__tests__/generator.spec.js +162 -0
- package/dist/__tests__/validation/project-name.spec.d.ts +1 -0
- package/dist/__tests__/validation/project-name.spec.js +57 -0
- package/dist/__tests__/wizard.spec.d.ts +1 -0
- package/dist/__tests__/wizard.spec.js +232 -0
- package/dist/aws/iam.d.ts +75 -0
- package/dist/aws/iam.js +264 -0
- package/dist/aws/organizations.d.ts +79 -0
- package/dist/aws/organizations.js +168 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +206 -0
- package/dist/commands/setup-github.d.ts +4 -0
- package/dist/commands/setup-github.js +185 -0
- package/dist/generator/copy-file.d.ts +15 -0
- package/dist/generator/copy-file.js +56 -0
- package/dist/generator/generate-project.d.ts +14 -0
- package/dist/generator/generate-project.js +81 -0
- package/dist/generator/index.d.ts +4 -0
- package/dist/generator/index.js +3 -0
- package/dist/generator/replace-tokens.d.ts +29 -0
- package/dist/generator/replace-tokens.js +68 -0
- package/dist/github/secrets.d.ts +109 -0
- package/dist/github/secrets.js +275 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/prompts/auth.d.ts +3 -0
- package/dist/prompts/auth.js +23 -0
- package/dist/prompts/aws-config.d.ts +2 -0
- package/dist/prompts/aws-config.js +14 -0
- package/dist/prompts/features.d.ts +2 -0
- package/dist/prompts/features.js +10 -0
- package/dist/prompts/github-setup.d.ts +53 -0
- package/dist/prompts/github-setup.js +208 -0
- package/dist/prompts/org-structure.d.ts +9 -0
- package/dist/prompts/org-structure.js +93 -0
- package/dist/prompts/platforms.d.ts +2 -0
- package/dist/prompts/platforms.js +12 -0
- package/dist/prompts/project-name.d.ts +2 -0
- package/dist/prompts/project-name.js +8 -0
- package/dist/prompts/theme.d.ts +2 -0
- package/dist/prompts/theme.js +14 -0
- package/dist/templates/index.d.ts +4 -0
- package/dist/templates/index.js +2 -0
- package/dist/templates/manifest.d.ts +11 -0
- package/dist/templates/manifest.js +99 -0
- package/dist/templates/tokens.d.ts +39 -0
- package/dist/templates/tokens.js +37 -0
- package/dist/templates/types.d.ts +52 -0
- package/dist/templates/types.js +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +1 -0
- package/dist/validation/project-name.d.ts +1 -0
- package/dist/validation/project-name.js +12 -0
- package/dist/wizard.d.ts +2 -0
- package/dist/wizard.js +81 -0
- package/package.json +68 -0
- package/templates/.github/actions/build-and-test/action.yml +24 -0
- package/templates/.github/actions/deploy-cdk/action.yml +46 -0
- package/templates/.github/actions/deploy-web/action.yml +72 -0
- package/templates/.github/actions/setup/action.yml +29 -0
- package/templates/.github/pull_request_template.md +15 -0
- package/templates/.github/workflows/deploy-dev.yml +80 -0
- package/templates/.github/workflows/deploy-prod.yml +67 -0
- package/templates/.github/workflows/deploy-stage.yml +77 -0
- package/templates/.github/workflows/pull-request.yml +72 -0
- package/templates/.vscode/extensions.json +7 -0
- package/templates/.vscode/settings.json +67 -0
- package/templates/apps/api/.eslintrc.json +18 -0
- package/templates/apps/api/cdk/app.ts +93 -0
- package/templates/apps/api/cdk/auth/cognito-stack.ts +164 -0
- package/templates/apps/api/cdk/cdk.json +73 -0
- package/templates/apps/api/cdk/deployment-user-stack.ts +187 -0
- package/templates/apps/api/cdk/org-stack.ts +67 -0
- package/templates/apps/api/cdk/static-stack.ts +361 -0
- package/templates/apps/api/cdk/tsconfig.json +39 -0
- package/templates/apps/api/cdk/user-stack.ts +255 -0
- package/templates/apps/api/jest.config.ts +38 -0
- package/templates/apps/api/lambdas.yml +84 -0
- package/templates/apps/api/project.json.template +58 -0
- package/templates/apps/api/src/__tests__/setup.ts +10 -0
- package/templates/apps/api/src/handlers/users/create-user.ts +52 -0
- package/templates/apps/api/src/handlers/users/delete-user.ts +45 -0
- package/templates/apps/api/src/handlers/users/get-me.ts +72 -0
- package/templates/apps/api/src/handlers/users/get-user.ts +45 -0
- package/templates/apps/api/src/handlers/users/get-users.ts +23 -0
- package/templates/apps/api/src/handlers/users/index.ts +17 -0
- package/templates/apps/api/src/handlers/users/update-user.ts +72 -0
- package/templates/apps/api/src/lib/dynamo/dynamo-model.ts +504 -0
- package/templates/apps/api/src/lib/dynamo/index.ts +12 -0
- package/templates/apps/api/src/lib/dynamo/utils.ts +39 -0
- package/templates/apps/api/src/middleware/auth0-auth.ts +97 -0
- package/templates/apps/api/src/middleware/cognito-auth.ts +90 -0
- package/templates/apps/api/src/models/UserModel.ts +109 -0
- package/templates/apps/api/src/schemas/user.schema.ts +44 -0
- package/templates/apps/api/src/services/user-service.ts +108 -0
- package/templates/apps/api/src/utils/auth-context.ts +60 -0
- package/templates/apps/api/src/utils/common/helpers.ts +26 -0
- package/templates/apps/api/src/utils/lambda-handler.ts +148 -0
- package/templates/apps/api/src/utils/response.ts +52 -0
- package/templates/apps/api/src/utils/validator.ts +75 -0
- package/templates/apps/api/tsconfig.app.json +15 -0
- package/templates/apps/api/tsconfig.json +19 -0
- package/templates/apps/api/tsconfig.spec.json +17 -0
- package/templates/apps/mobile/.env.example +5 -0
- package/templates/apps/mobile/.eslintrc.json +33 -0
- package/templates/apps/mobile/app.json +33 -0
- package/templates/apps/mobile/assets/.gitkeep +0 -0
- package/templates/apps/mobile/babel.config.js +19 -0
- package/templates/apps/mobile/index.js +7 -0
- package/templates/apps/mobile/jest.config.ts +22 -0
- package/templates/apps/mobile/metro.config.js +35 -0
- package/templates/apps/mobile/package.json +22 -0
- package/templates/apps/mobile/project.json.template +64 -0
- package/templates/apps/mobile/src/App.tsx +367 -0
- package/templates/apps/mobile/src/__tests__/App.spec.tsx +46 -0
- package/templates/apps/mobile/src/__tests__/store/user-store.spec.ts +156 -0
- package/templates/apps/mobile/src/config/api.ts +16 -0
- package/templates/apps/mobile/src/store/user-store.ts +56 -0
- package/templates/apps/mobile/src/test-setup.ts +10 -0
- package/templates/apps/mobile/tsconfig.json +22 -0
- package/templates/apps/web/.env.example +13 -0
- package/templates/apps/web/.eslintrc.json +26 -0
- package/templates/apps/web/index.html +13 -0
- package/templates/apps/web/jest.config.ts +24 -0
- package/templates/apps/web/package.json +15 -0
- package/templates/apps/web/project.json.template +66 -0
- package/templates/apps/web/src/App.tsx +352 -0
- package/templates/apps/web/src/__mocks__/config/api.ts +41 -0
- package/templates/apps/web/src/__tests__/App.spec.tsx +240 -0
- package/templates/apps/web/src/__tests__/store/user-store.spec.ts +185 -0
- package/templates/apps/web/src/auth/auth0-provider.tsx +103 -0
- package/templates/apps/web/src/auth/cognito-provider.tsx +143 -0
- package/templates/apps/web/src/auth/index.ts +7 -0
- package/templates/apps/web/src/auth/use-auth.ts +16 -0
- package/templates/apps/web/src/config/amplify-config.ts +31 -0
- package/templates/apps/web/src/config/api.ts +38 -0
- package/templates/apps/web/src/config/auth0-config.ts +17 -0
- package/templates/apps/web/src/main.tsx +41 -0
- package/templates/apps/web/src/store/user-store.ts +56 -0
- package/templates/apps/web/src/styles.css +165 -0
- package/templates/apps/web/src/test-setup.ts +1 -0
- package/templates/apps/web/src/theme/index.ts +30 -0
- package/templates/apps/web/src/vite-env.d.ts +19 -0
- package/templates/apps/web/tsconfig.app.json +24 -0
- package/templates/apps/web/tsconfig.json +22 -0
- package/templates/apps/web/tsconfig.spec.json +28 -0
- package/templates/apps/web/vite.config.ts +87 -0
- package/templates/manifest.json +28 -0
- package/templates/packages/api-client/.eslintrc.json +18 -0
- package/templates/packages/api-client/jest.config.ts +13 -0
- package/templates/packages/api-client/package.json +8 -0
- package/templates/packages/api-client/project.json.template +34 -0
- package/templates/packages/api-client/src/__tests__/api-client.spec.ts +408 -0
- package/templates/packages/api-client/src/api-client.ts +201 -0
- package/templates/packages/api-client/src/config.ts +193 -0
- package/templates/packages/api-client/src/index.ts +9 -0
- package/templates/packages/api-client/tsconfig.json +22 -0
- package/templates/packages/api-client/tsconfig.lib.json +11 -0
- package/templates/packages/api-client/tsconfig.spec.json +14 -0
- package/templates/packages/common-types/.eslintrc.json +18 -0
- package/templates/packages/common-types/package.json +6 -0
- package/templates/packages/common-types/project.json.template +26 -0
- package/templates/packages/common-types/src/api.types.ts +24 -0
- package/templates/packages/common-types/src/auth.types.ts +36 -0
- package/templates/packages/common-types/src/common.types.ts +46 -0
- package/templates/packages/common-types/src/index.ts +19 -0
- package/templates/packages/common-types/src/lambda.types.ts +39 -0
- package/templates/packages/common-types/src/user.types.ts +31 -0
- package/templates/packages/common-types/tsconfig.json +19 -0
- package/templates/packages/common-types/tsconfig.lib.json +11 -0
- package/templates/root/.editorconfig +23 -0
- package/templates/root/.nvmrc +1 -0
- package/templates/root/eslint.config.js +61 -0
- package/templates/root/jest.preset.js +16 -0
- package/templates/root/nx.json +29 -0
- package/templates/root/package.json +131 -0
- package/templates/root/tsconfig.base.json +29 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { StatusBar } from "expo-status-bar";
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
Text,
|
|
5
|
+
View,
|
|
6
|
+
ScrollView,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
ActivityIndicator,
|
|
9
|
+
Alert,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import { useEffect } from "react";
|
|
12
|
+
import { useUserStore } from "./store/user-store";
|
|
13
|
+
import type { User } from "{{PACKAGE_SCOPE}}/common-types";
|
|
14
|
+
import { apiClient } from "./config/api";
|
|
15
|
+
import { ApiError } from "{{PACKAGE_SCOPE}}/api-client";
|
|
16
|
+
|
|
17
|
+
export default function App() {
|
|
18
|
+
const {
|
|
19
|
+
user,
|
|
20
|
+
users,
|
|
21
|
+
setUser,
|
|
22
|
+
addUser,
|
|
23
|
+
setUsers,
|
|
24
|
+
isLoading,
|
|
25
|
+
error,
|
|
26
|
+
setLoading,
|
|
27
|
+
setError,
|
|
28
|
+
clearError,
|
|
29
|
+
} = useUserStore();
|
|
30
|
+
|
|
31
|
+
// Fetch users on mount
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
handleFetchUsers();
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const handleLoadDemoUser = () => {
|
|
37
|
+
const demoUser: User = {
|
|
38
|
+
id: crypto.randomUUID(),
|
|
39
|
+
email: "demo@example.com",
|
|
40
|
+
name: "Demo User",
|
|
41
|
+
createdAt: new Date().toISOString(),
|
|
42
|
+
};
|
|
43
|
+
setUser(demoUser);
|
|
44
|
+
addUser(demoUser);
|
|
45
|
+
Alert.alert("User loaded", "Demo user has been loaded successfully");
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleClearUser = () => {
|
|
49
|
+
setUser(null);
|
|
50
|
+
Alert.alert("User cleared", "Current user has been cleared");
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleFetchUsers = async () => {
|
|
54
|
+
setLoading(true);
|
|
55
|
+
clearError();
|
|
56
|
+
try {
|
|
57
|
+
const fetchedUsers = await apiClient.getUsers();
|
|
58
|
+
setUsers(fetchedUsers);
|
|
59
|
+
Alert.alert(
|
|
60
|
+
"Users fetched",
|
|
61
|
+
`Loaded ${fetchedUsers.length} users from API`
|
|
62
|
+
);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
const message =
|
|
65
|
+
err instanceof ApiError ? err.message : "Failed to fetch users";
|
|
66
|
+
setError(message);
|
|
67
|
+
Alert.alert("Error", message);
|
|
68
|
+
} finally {
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleCreateUser = async () => {
|
|
74
|
+
setLoading(true);
|
|
75
|
+
clearError();
|
|
76
|
+
try {
|
|
77
|
+
const newUser = await apiClient.createUser({
|
|
78
|
+
email: `user${Date.now()}@example.com`,
|
|
79
|
+
name: `User ${Date.now()}`,
|
|
80
|
+
});
|
|
81
|
+
addUser(newUser);
|
|
82
|
+
Alert.alert("User created", `Created ${newUser.name}`);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
const message =
|
|
85
|
+
err instanceof ApiError ? err.message : "Failed to create user";
|
|
86
|
+
setError(message);
|
|
87
|
+
Alert.alert("Error", message);
|
|
88
|
+
} finally {
|
|
89
|
+
setLoading(false);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<View style={styles.container}>
|
|
95
|
+
<StatusBar style="light" />
|
|
96
|
+
|
|
97
|
+
{/* Header */}
|
|
98
|
+
<View style={styles.header}>
|
|
99
|
+
<Text style={styles.title}>{{PROJECT_NAME_TITLE}}</Text>
|
|
100
|
+
<Text style={styles.subtitle}>
|
|
101
|
+
Nx Monorepo with React, AWS Lambda, and Shared Types
|
|
102
|
+
</Text>
|
|
103
|
+
</View>
|
|
104
|
+
|
|
105
|
+
{/* Main Content */}
|
|
106
|
+
<ScrollView
|
|
107
|
+
style={styles.content}
|
|
108
|
+
contentContainerStyle={styles.contentContainer}
|
|
109
|
+
>
|
|
110
|
+
{/* Welcome Card */}
|
|
111
|
+
<View style={styles.card}>
|
|
112
|
+
<Text style={styles.cardTitle}>Welcome to Mobile Client</Text>
|
|
113
|
+
<Text style={styles.cardText}>
|
|
114
|
+
This is a React Native application built with Expo and TypeScript,
|
|
115
|
+
managed by Nx in a monorepo structure.
|
|
116
|
+
</Text>
|
|
117
|
+
</View>
|
|
118
|
+
|
|
119
|
+
{/* Features Card */}
|
|
120
|
+
<View style={styles.card}>
|
|
121
|
+
<Text style={styles.cardTitle}>Features:</Text>
|
|
122
|
+
<View style={styles.featureList}>
|
|
123
|
+
<Text style={styles.featureItem}>- React Native with Expo</Text>
|
|
124
|
+
<Text style={styles.featureItem}>- TypeScript Support</Text>
|
|
125
|
+
<Text style={styles.featureItem}>
|
|
126
|
+
- Cross-platform (iOS & Android)
|
|
127
|
+
</Text>
|
|
128
|
+
<Text style={styles.featureItem}>
|
|
129
|
+
- Zustand for state management
|
|
130
|
+
</Text>
|
|
131
|
+
<Text style={styles.featureItem}>- Jest for testing</Text>
|
|
132
|
+
<Text style={styles.featureItem}>
|
|
133
|
+
- Shared types from common-types library
|
|
134
|
+
</Text>
|
|
135
|
+
<Text style={styles.featureItem}>- Nx for monorepo management</Text>
|
|
136
|
+
<Text style={styles.featureItem}>
|
|
137
|
+
- Type-safe API client with Axios
|
|
138
|
+
</Text>
|
|
139
|
+
<Text style={styles.featureItem}>
|
|
140
|
+
- AWS CDK infrastructure deployment
|
|
141
|
+
</Text>
|
|
142
|
+
</View>
|
|
143
|
+
</View>
|
|
144
|
+
|
|
145
|
+
{/* API Actions Card */}
|
|
146
|
+
<View style={styles.card}>
|
|
147
|
+
<Text style={styles.cardTitle}>API Actions:</Text>
|
|
148
|
+
<View style={styles.buttonGroup}>
|
|
149
|
+
<TouchableOpacity
|
|
150
|
+
style={[styles.button, styles.buttonPrimary]}
|
|
151
|
+
onPress={handleFetchUsers}
|
|
152
|
+
disabled={isLoading}
|
|
153
|
+
>
|
|
154
|
+
{isLoading ? (
|
|
155
|
+
<ActivityIndicator color="#fff" />
|
|
156
|
+
) : (
|
|
157
|
+
<Text style={styles.buttonText}>Fetch Users</Text>
|
|
158
|
+
)}
|
|
159
|
+
</TouchableOpacity>
|
|
160
|
+
|
|
161
|
+
<TouchableOpacity
|
|
162
|
+
style={[styles.button, styles.buttonSuccess]}
|
|
163
|
+
onPress={handleCreateUser}
|
|
164
|
+
disabled={isLoading}
|
|
165
|
+
>
|
|
166
|
+
{isLoading ? (
|
|
167
|
+
<ActivityIndicator color="#fff" />
|
|
168
|
+
) : (
|
|
169
|
+
<Text style={styles.buttonText}>Create Test User</Text>
|
|
170
|
+
)}
|
|
171
|
+
</TouchableOpacity>
|
|
172
|
+
</View>
|
|
173
|
+
{error && <Text style={styles.errorText}>Error: {error}</Text>}
|
|
174
|
+
</View>
|
|
175
|
+
|
|
176
|
+
{/* Current User Card */}
|
|
177
|
+
{user ? (
|
|
178
|
+
<View style={styles.card}>
|
|
179
|
+
<Text style={styles.cardTitle}>Current User:</Text>
|
|
180
|
+
<View style={styles.userInfo}>
|
|
181
|
+
<View style={styles.userInfoRow}>
|
|
182
|
+
<Text style={styles.userInfoLabel}>ID:</Text>
|
|
183
|
+
<Text style={styles.userInfoValue}>{user.id}</Text>
|
|
184
|
+
</View>
|
|
185
|
+
<View style={styles.userInfoRow}>
|
|
186
|
+
<Text style={styles.userInfoLabel}>Email:</Text>
|
|
187
|
+
<Text style={styles.userInfoValue}>{user.email}</Text>
|
|
188
|
+
</View>
|
|
189
|
+
<View style={styles.userInfoRow}>
|
|
190
|
+
<Text style={styles.userInfoLabel}>Name:</Text>
|
|
191
|
+
<Text style={styles.userInfoValue}>{user.name}</Text>
|
|
192
|
+
</View>
|
|
193
|
+
<View style={styles.userInfoRow}>
|
|
194
|
+
<Text style={styles.userInfoLabel}>Created:</Text>
|
|
195
|
+
<Text style={styles.userInfoValue}>
|
|
196
|
+
{new Date(user.createdAt).toLocaleString()}
|
|
197
|
+
</Text>
|
|
198
|
+
</View>
|
|
199
|
+
</View>
|
|
200
|
+
<TouchableOpacity
|
|
201
|
+
style={[styles.button, styles.buttonDanger]}
|
|
202
|
+
onPress={handleClearUser}
|
|
203
|
+
>
|
|
204
|
+
<Text style={styles.buttonText}>Clear User</Text>
|
|
205
|
+
</TouchableOpacity>
|
|
206
|
+
</View>
|
|
207
|
+
) : (
|
|
208
|
+
<TouchableOpacity
|
|
209
|
+
style={[styles.button, styles.buttonPrimary]}
|
|
210
|
+
onPress={handleLoadDemoUser}
|
|
211
|
+
>
|
|
212
|
+
<Text style={styles.buttonText}>Load Demo User</Text>
|
|
213
|
+
</TouchableOpacity>
|
|
214
|
+
)}
|
|
215
|
+
|
|
216
|
+
{/* Users Count */}
|
|
217
|
+
{users.length > 0 && (
|
|
218
|
+
<View style={styles.statsCard}>
|
|
219
|
+
<Text style={styles.statsText}>
|
|
220
|
+
Total users in store: {users.length}
|
|
221
|
+
</Text>
|
|
222
|
+
</View>
|
|
223
|
+
)}
|
|
224
|
+
|
|
225
|
+
{/* Footer Spacer */}
|
|
226
|
+
<View style={{ height: 20 }} />
|
|
227
|
+
</ScrollView>
|
|
228
|
+
|
|
229
|
+
{/* Footer */}
|
|
230
|
+
<View style={styles.footer}>
|
|
231
|
+
<Text style={styles.footerText}>
|
|
232
|
+
Built with React Native, TypeScript, Expo, and Zustand
|
|
233
|
+
</Text>
|
|
234
|
+
</View>
|
|
235
|
+
</View>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const styles = StyleSheet.create({
|
|
240
|
+
container: {
|
|
241
|
+
flex: 1,
|
|
242
|
+
backgroundColor: "#0f172a",
|
|
243
|
+
},
|
|
244
|
+
header: {
|
|
245
|
+
backgroundColor: "#3B82F6",
|
|
246
|
+
paddingTop: 60,
|
|
247
|
+
paddingBottom: 30,
|
|
248
|
+
paddingHorizontal: 20,
|
|
249
|
+
alignItems: "center",
|
|
250
|
+
},
|
|
251
|
+
title: {
|
|
252
|
+
fontSize: 32,
|
|
253
|
+
fontWeight: "bold",
|
|
254
|
+
color: "#fff",
|
|
255
|
+
marginBottom: 8,
|
|
256
|
+
},
|
|
257
|
+
subtitle: {
|
|
258
|
+
fontSize: 16,
|
|
259
|
+
color: "#e0e7ff",
|
|
260
|
+
},
|
|
261
|
+
content: {
|
|
262
|
+
flex: 1,
|
|
263
|
+
},
|
|
264
|
+
contentContainer: {
|
|
265
|
+
padding: 20,
|
|
266
|
+
},
|
|
267
|
+
card: {
|
|
268
|
+
backgroundColor: "#1e293b",
|
|
269
|
+
borderRadius: 12,
|
|
270
|
+
padding: 20,
|
|
271
|
+
marginBottom: 16,
|
|
272
|
+
borderWidth: 1,
|
|
273
|
+
borderColor: "#334155",
|
|
274
|
+
},
|
|
275
|
+
cardTitle: {
|
|
276
|
+
fontSize: 20,
|
|
277
|
+
fontWeight: "bold",
|
|
278
|
+
color: "#fff",
|
|
279
|
+
marginBottom: 12,
|
|
280
|
+
},
|
|
281
|
+
cardText: {
|
|
282
|
+
fontSize: 14,
|
|
283
|
+
color: "#94a3b8",
|
|
284
|
+
lineHeight: 20,
|
|
285
|
+
},
|
|
286
|
+
featureList: {
|
|
287
|
+
gap: 8,
|
|
288
|
+
},
|
|
289
|
+
featureItem: {
|
|
290
|
+
fontSize: 14,
|
|
291
|
+
color: "#94a3b8",
|
|
292
|
+
lineHeight: 20,
|
|
293
|
+
},
|
|
294
|
+
buttonGroup: {
|
|
295
|
+
gap: 12,
|
|
296
|
+
},
|
|
297
|
+
button: {
|
|
298
|
+
paddingVertical: 14,
|
|
299
|
+
paddingHorizontal: 20,
|
|
300
|
+
borderRadius: 8,
|
|
301
|
+
alignItems: "center",
|
|
302
|
+
justifyContent: "center",
|
|
303
|
+
minHeight: 50,
|
|
304
|
+
},
|
|
305
|
+
buttonPrimary: {
|
|
306
|
+
backgroundColor: "#8b5cf6",
|
|
307
|
+
},
|
|
308
|
+
buttonSuccess: {
|
|
309
|
+
backgroundColor: "#10b981",
|
|
310
|
+
},
|
|
311
|
+
buttonDanger: {
|
|
312
|
+
backgroundColor: "#ef4444",
|
|
313
|
+
marginTop: 12,
|
|
314
|
+
},
|
|
315
|
+
buttonText: {
|
|
316
|
+
color: "#fff",
|
|
317
|
+
fontSize: 16,
|
|
318
|
+
fontWeight: "600",
|
|
319
|
+
},
|
|
320
|
+
errorText: {
|
|
321
|
+
color: "#f87171",
|
|
322
|
+
fontSize: 14,
|
|
323
|
+
marginTop: 8,
|
|
324
|
+
},
|
|
325
|
+
userInfo: {
|
|
326
|
+
gap: 12,
|
|
327
|
+
marginBottom: 12,
|
|
328
|
+
},
|
|
329
|
+
userInfoRow: {
|
|
330
|
+
flexDirection: "row",
|
|
331
|
+
alignItems: "center",
|
|
332
|
+
},
|
|
333
|
+
userInfoLabel: {
|
|
334
|
+
fontSize: 14,
|
|
335
|
+
fontWeight: "600",
|
|
336
|
+
color: "#94a3b8",
|
|
337
|
+
width: 80,
|
|
338
|
+
},
|
|
339
|
+
userInfoValue: {
|
|
340
|
+
fontSize: 14,
|
|
341
|
+
color: "#e2e8f0",
|
|
342
|
+
flex: 1,
|
|
343
|
+
},
|
|
344
|
+
statsCard: {
|
|
345
|
+
backgroundColor: "#1e293b",
|
|
346
|
+
borderRadius: 8,
|
|
347
|
+
padding: 16,
|
|
348
|
+
borderWidth: 1,
|
|
349
|
+
borderColor: "#334155",
|
|
350
|
+
},
|
|
351
|
+
statsText: {
|
|
352
|
+
color: "#94a3b8",
|
|
353
|
+
fontSize: 14,
|
|
354
|
+
},
|
|
355
|
+
footer: {
|
|
356
|
+
backgroundColor: "#1e293b",
|
|
357
|
+
paddingVertical: 20,
|
|
358
|
+
paddingHorizontal: 20,
|
|
359
|
+
borderTopWidth: 1,
|
|
360
|
+
borderTopColor: "#374151",
|
|
361
|
+
},
|
|
362
|
+
footerText: {
|
|
363
|
+
textAlign: "center",
|
|
364
|
+
color: "#94a3b8",
|
|
365
|
+
fontSize: 12,
|
|
366
|
+
},
|
|
367
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Note: @testing-library/react-native uses react-test-renderer which
|
|
2
|
+
// is not yet compatible with React 19. These tests are skipped until
|
|
3
|
+
// the library is updated. See: https://github.com/callstack/react-native-testing-library/issues/1531
|
|
4
|
+
|
|
5
|
+
// Skipping tests due to React 19 incompatibility with react-test-renderer
|
|
6
|
+
describe.skip("App", () => {
|
|
7
|
+
it("should render the app title", () => {
|
|
8
|
+
// Test skipped - waiting for react-test-renderer React 19 support
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should render the subtitle", () => {
|
|
12
|
+
// Test skipped
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should render welcome message", () => {
|
|
16
|
+
// Test skipped
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should render feature list", () => {
|
|
20
|
+
// Test skipped
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should render Load Demo User button when no user", () => {
|
|
24
|
+
// Test skipped
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should load demo user when button is pressed", () => {
|
|
28
|
+
// Test skipped
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should clear user when Clear User button is pressed", () => {
|
|
32
|
+
// Test skipped
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should render API action buttons", () => {
|
|
36
|
+
// Test skipped
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should display users count when users exist", () => {
|
|
40
|
+
// Test skipped
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should render footer", () => {
|
|
44
|
+
// Test skipped
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { useUserStore } from '../../store/user-store';
|
|
2
|
+
import type { User } from '{{PACKAGE_SCOPE}}/common-types';
|
|
3
|
+
|
|
4
|
+
describe('useUserStore', () => {
|
|
5
|
+
const mockUser: User = {
|
|
6
|
+
id: '1',
|
|
7
|
+
email: 'test@example.com',
|
|
8
|
+
name: 'Test User',
|
|
9
|
+
createdAt: '2024-01-01T00:00:00.000Z',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Reset store before each test
|
|
14
|
+
useUserStore.setState({
|
|
15
|
+
user: null,
|
|
16
|
+
users: [],
|
|
17
|
+
isLoading: false,
|
|
18
|
+
error: null,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('initial state', () => {
|
|
23
|
+
it('should have null user', () => {
|
|
24
|
+
expect(useUserStore.getState().user).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should have empty users array', () => {
|
|
28
|
+
expect(useUserStore.getState().users).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should not be loading', () => {
|
|
32
|
+
expect(useUserStore.getState().isLoading).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should have no error', () => {
|
|
36
|
+
expect(useUserStore.getState().error).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('setUser', () => {
|
|
41
|
+
it('should set the current user', () => {
|
|
42
|
+
useUserStore.getState().setUser(mockUser);
|
|
43
|
+
expect(useUserStore.getState().user).toEqual(mockUser);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should clear the current user when set to null', () => {
|
|
47
|
+
useUserStore.getState().setUser(mockUser);
|
|
48
|
+
useUserStore.getState().setUser(null);
|
|
49
|
+
expect(useUserStore.getState().user).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('setUsers', () => {
|
|
54
|
+
it('should set the users array', () => {
|
|
55
|
+
const users = [mockUser, { ...mockUser, id: '2', email: 'test2@example.com' }];
|
|
56
|
+
useUserStore.getState().setUsers(users);
|
|
57
|
+
expect(useUserStore.getState().users).toEqual(users);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('addUser', () => {
|
|
62
|
+
it('should add a user to the array', () => {
|
|
63
|
+
useUserStore.getState().addUser(mockUser);
|
|
64
|
+
expect(useUserStore.getState().users).toContainEqual(mockUser);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should append to existing users', () => {
|
|
68
|
+
const existingUser = { ...mockUser, id: '0' };
|
|
69
|
+
useUserStore.setState({ users: [existingUser] });
|
|
70
|
+
|
|
71
|
+
useUserStore.getState().addUser(mockUser);
|
|
72
|
+
|
|
73
|
+
expect(useUserStore.getState().users).toHaveLength(2);
|
|
74
|
+
expect(useUserStore.getState().users[0]).toEqual(existingUser);
|
|
75
|
+
expect(useUserStore.getState().users[1]).toEqual(mockUser);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('updateUser', () => {
|
|
80
|
+
it('should update a user in the array', () => {
|
|
81
|
+
useUserStore.setState({ users: [mockUser] });
|
|
82
|
+
|
|
83
|
+
useUserStore.getState().updateUser('1', { name: 'Updated Name' });
|
|
84
|
+
|
|
85
|
+
expect(useUserStore.getState().users[0].name).toBe('Updated Name');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should update the current user if it matches', () => {
|
|
89
|
+
useUserStore.setState({ user: mockUser, users: [mockUser] });
|
|
90
|
+
|
|
91
|
+
useUserStore.getState().updateUser('1', { name: 'Updated Name' });
|
|
92
|
+
|
|
93
|
+
expect(useUserStore.getState().user?.name).toBe('Updated Name');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should not update current user if id does not match', () => {
|
|
97
|
+
useUserStore.setState({ user: mockUser, users: [mockUser] });
|
|
98
|
+
|
|
99
|
+
useUserStore.getState().updateUser('different-id', { name: 'Updated Name' });
|
|
100
|
+
|
|
101
|
+
expect(useUserStore.getState().user?.name).toBe('Test User');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('removeUser', () => {
|
|
106
|
+
it('should remove a user from the array', () => {
|
|
107
|
+
useUserStore.setState({ users: [mockUser] });
|
|
108
|
+
|
|
109
|
+
useUserStore.getState().removeUser('1');
|
|
110
|
+
|
|
111
|
+
expect(useUserStore.getState().users).toHaveLength(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should clear current user if it matches removed user', () => {
|
|
115
|
+
useUserStore.setState({ user: mockUser, users: [mockUser] });
|
|
116
|
+
|
|
117
|
+
useUserStore.getState().removeUser('1');
|
|
118
|
+
|
|
119
|
+
expect(useUserStore.getState().user).toBeNull();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should not clear current user if id does not match', () => {
|
|
123
|
+
useUserStore.setState({ user: mockUser, users: [mockUser] });
|
|
124
|
+
|
|
125
|
+
useUserStore.getState().removeUser('different-id');
|
|
126
|
+
|
|
127
|
+
expect(useUserStore.getState().user).toEqual(mockUser);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('loading state', () => {
|
|
132
|
+
it('should set loading to true', () => {
|
|
133
|
+
useUserStore.getState().setLoading(true);
|
|
134
|
+
expect(useUserStore.getState().isLoading).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should set loading to false', () => {
|
|
138
|
+
useUserStore.setState({ isLoading: true });
|
|
139
|
+
useUserStore.getState().setLoading(false);
|
|
140
|
+
expect(useUserStore.getState().isLoading).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('error handling', () => {
|
|
145
|
+
it('should set error message', () => {
|
|
146
|
+
useUserStore.getState().setError('Something went wrong');
|
|
147
|
+
expect(useUserStore.getState().error).toBe('Something went wrong');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should clear error', () => {
|
|
151
|
+
useUserStore.setState({ error: 'Some error' });
|
|
152
|
+
useUserStore.getState().clearError();
|
|
153
|
+
expect(useUserStore.getState().error).toBeNull();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createApiClient, createConfigFromEnv } from '{{PACKAGE_SCOPE}}/api-client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configured API client instance
|
|
5
|
+
*
|
|
6
|
+
* Configuration is loaded from environment variables:
|
|
7
|
+
* - EXPO_PUBLIC_API_BASE_URL: API base URL (default: http://localhost:3000)
|
|
8
|
+
* - EXPO_PUBLIC_API_TIMEOUT: Request timeout in milliseconds
|
|
9
|
+
* - EXPO_PUBLIC_API_KEY: API key for authentication
|
|
10
|
+
* - EXPO_PUBLIC_API_WITH_CREDENTIALS: Whether to send credentials
|
|
11
|
+
* - EXPO_PUBLIC_API_DEBUG: Enable debug logging
|
|
12
|
+
*
|
|
13
|
+
* Create a .env file in the mobile app directory with these variables.
|
|
14
|
+
* See ENV_EXAMPLE.md in the api-client package for more details.
|
|
15
|
+
*/
|
|
16
|
+
export const apiClient = createApiClient(createConfigFromEnv());
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import type { User } from '{{PACKAGE_SCOPE}}/common-types';
|
|
3
|
+
|
|
4
|
+
interface UserState {
|
|
5
|
+
user: User | null;
|
|
6
|
+
users: User[];
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
error: string | null;
|
|
9
|
+
setUser: (user: User | null) => void;
|
|
10
|
+
setUsers: (users: User[]) => void;
|
|
11
|
+
addUser: (user: User) => void;
|
|
12
|
+
updateUser: (id: string, updates: Partial<User>) => void;
|
|
13
|
+
removeUser: (id: string) => void;
|
|
14
|
+
setLoading: (loading: boolean) => void;
|
|
15
|
+
setError: (error: string | null) => void;
|
|
16
|
+
clearError: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const useUserStore = create<UserState>((set) => ({
|
|
20
|
+
user: null,
|
|
21
|
+
users: [],
|
|
22
|
+
isLoading: false,
|
|
23
|
+
error: null,
|
|
24
|
+
|
|
25
|
+
setUser: (user) => set({ user }),
|
|
26
|
+
|
|
27
|
+
setUsers: (users) => set({ users }),
|
|
28
|
+
|
|
29
|
+
addUser: (user) =>
|
|
30
|
+
set((state) => ({
|
|
31
|
+
users: [...state.users, user],
|
|
32
|
+
})),
|
|
33
|
+
|
|
34
|
+
updateUser: (id, updates) =>
|
|
35
|
+
set((state) => ({
|
|
36
|
+
users: state.users.map((u) =>
|
|
37
|
+
u.id === id ? { ...u, ...updates } : u
|
|
38
|
+
),
|
|
39
|
+
user:
|
|
40
|
+
state.user?.id === id
|
|
41
|
+
? { ...state.user, ...updates }
|
|
42
|
+
: state.user,
|
|
43
|
+
})),
|
|
44
|
+
|
|
45
|
+
removeUser: (id) =>
|
|
46
|
+
set((state) => ({
|
|
47
|
+
users: state.users.filter((u) => u.id !== id),
|
|
48
|
+
user: state.user?.id === id ? null : state.user,
|
|
49
|
+
})),
|
|
50
|
+
|
|
51
|
+
setLoading: (loading) => set({ isLoading: loading }),
|
|
52
|
+
|
|
53
|
+
setError: (error) => set({ error }),
|
|
54
|
+
|
|
55
|
+
clearError: () => set({ error: null }),
|
|
56
|
+
}));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import '@testing-library/jest-native/extend-expect';
|
|
2
|
+
|
|
3
|
+
// Mock expo-status-bar
|
|
4
|
+
jest.mock('expo-status-bar', () => ({
|
|
5
|
+
StatusBar: 'StatusBar',
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
// Silence console warnings in tests
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
10
|
+
jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react-native",
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"allowSyntheticDefaultImports": true,
|
|
8
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"noEmit": true
|
|
14
|
+
},
|
|
15
|
+
"include": [
|
|
16
|
+
"**/*.ts",
|
|
17
|
+
"**/*.tsx"
|
|
18
|
+
],
|
|
19
|
+
"exclude": [
|
|
20
|
+
"node_modules"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# API Configuration
|
|
2
|
+
VITE_API_BASE_URL=https://your-api-domain.cloudfront.net
|
|
3
|
+
|
|
4
|
+
# Cognito Configuration (when using Cognito auth provider)
|
|
5
|
+
VITE_COGNITO_USER_POOL_ID=
|
|
6
|
+
VITE_COGNITO_CLIENT_ID=
|
|
7
|
+
# Optional: Identity Pool for AWS credentials (when social-login enabled)
|
|
8
|
+
# VITE_COGNITO_IDENTITY_POOL_ID=
|
|
9
|
+
|
|
10
|
+
# Auth0 Configuration (when using Auth0 auth provider)
|
|
11
|
+
VITE_AUTH0_DOMAIN=your-tenant.auth0.com
|
|
12
|
+
VITE_AUTH0_CLIENT_ID=your-client-id
|
|
13
|
+
VITE_AUTH0_AUDIENCE=https://your-api-identifier
|