@idealyst/cli 1.0.41 → 1.0.43
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/dist/generators/api.js +1 -2
- package/dist/generators/api.js.map +1 -1
- package/dist/generators/database.js +1 -2
- package/dist/generators/database.js.map +1 -1
- package/dist/generators/fullstack.js +371 -0
- package/dist/generators/fullstack.js.map +1 -0
- package/dist/generators/index.js +5 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/native.js +2 -1
- package/dist/generators/native.js.map +1 -1
- package/dist/generators/shared.js +1 -2
- package/dist/generators/shared.js.map +1 -1
- package/dist/generators/utils.js +71 -7
- package/dist/generators/utils.js.map +1 -1
- package/dist/generators/web.js +1 -2
- package/dist/generators/web.js.map +1 -1
- package/dist/generators/workspace.js +56 -2
- package/dist/generators/workspace.js.map +1 -1
- package/dist/index.js +50 -2
- package/dist/index.js.map +1 -1
- package/dist/templates/database/.env.example +1 -8
- package/dist/templates/database/README.md +29 -74
- package/dist/templates/database/package.json +20 -34
- package/dist/templates/database/prisma/seed.ts +11 -11
- package/dist/templates/database/schema.prisma +97 -0
- package/dist/templates/database/src/index.ts +12 -8
- package/dist/templates/database/tsconfig.json +9 -23
- package/dist/templates/native/src/App-with-trpc-and-shared.tsx +266 -0
- package/dist/templates/shared/package.json +28 -3
- package/dist/templates/shared/src/components/index.ts +392 -0
- package/dist/templates/shared/src/index.ts +59 -1
- package/dist/templates/shared/src/types/index.ts +148 -0
- package/dist/templates/shared/src/utils/index.ts +278 -0
- package/dist/templates/web/package.json +2 -2
- package/dist/templates/web/src/App-with-trpc-and-shared.tsx +304 -0
- package/dist/templates/workspace/.devcontainer/Dockerfile +1 -1
- package/dist/templates/workspace/.devcontainer/devcontainer.json +7 -2
- package/dist/templates/workspace/.devcontainer/docker-compose.yml +14 -0
- package/dist/templates/workspace/.devcontainer/figma-mcp.sh +32 -0
- package/dist/templates/workspace/.devcontainer/setup.sh +3 -0
- package/dist/templates/workspace/setup.sh +22 -197
- package/dist/templates/workspace/tsconfig.json +32 -0
- package/dist/types/generators/fullstack.d.ts +2 -0
- package/dist/types/generators/index.d.ts +1 -0
- package/dist/types/generators/utils.d.ts +4 -1
- package/dist/types/types.d.ts +3 -1
- package/package.json +1 -1
- package/templates/database/.env.example +1 -0
- package/templates/database/README.md +48 -0
- package/templates/database/package.json +21 -2
- package/templates/database/prisma/seed.ts +28 -0
- package/templates/database/schema.prisma +85 -9
- package/templates/database/src/index.ts +7 -7
- package/templates/database/src/validators.ts +10 -0
- package/templates/native/src/App-with-trpc-and-shared.tsx +266 -0
- package/templates/shared/package.json +28 -3
- package/templates/shared/src/components/index.ts +392 -0
- package/templates/shared/src/index.ts +59 -1
- package/templates/shared/src/types/index.ts +148 -0
- package/templates/shared/src/utils/index.ts +278 -0
- package/templates/web/package.json +1 -1
- package/templates/web/src/App-with-trpc-and-shared.tsx +304 -0
- package/templates/workspace/.devcontainer/devcontainer.json +7 -2
- package/templates/workspace/.devcontainer/docker-compose.yml +14 -0
- package/templates/workspace/.devcontainer/figma-mcp.sh +32 -0
- package/templates/workspace/.devcontainer/setup.sh +3 -0
- package/templates/workspace/setup.sh +30 -0
- package/templates/workspace/tsconfig.json +32 -0
- package/dist/templates/database/__tests__/database.test.ts +0 -14
- package/dist/templates/database/jest.config.js +0 -19
- package/dist/templates/database/jest.setup.js +0 -11
- package/dist/templates/database/prisma/schema.prisma +0 -21
- package/dist/templates/database/src/client.ts +0 -18
- package/dist/templates/database/src/schemas.ts +0 -26
- package/dist/templates/workspace/scripts/docker/db-backup.sh +0 -230
- package/dist/templates/workspace/scripts/docker/deploy.sh +0 -212
- package/dist/templates/workspace/scripts/test-runner.js +0 -120
- /package/{templates/database/src/validatgors.ts → dist/templates/database/src/validators.ts} +0 -0
|
@@ -2,9 +2,31 @@
|
|
|
2
2
|
"name": "{{packageName}}",
|
|
3
3
|
"version": "{{version}}",
|
|
4
4
|
"description": "{{description}}",
|
|
5
|
-
"main": "
|
|
6
|
-
"module": "
|
|
7
|
-
"types": "
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./types": {
|
|
15
|
+
"import": "./dist/types/index.js",
|
|
16
|
+
"require": "./dist/types/index.js",
|
|
17
|
+
"types": "./dist/types/index.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./components": {
|
|
20
|
+
"import": "./dist/components/index.js",
|
|
21
|
+
"require": "./dist/components/index.js",
|
|
22
|
+
"types": "./dist/components/index.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./utils": {
|
|
25
|
+
"import": "./dist/utils/index.js",
|
|
26
|
+
"require": "./dist/utils/index.js",
|
|
27
|
+
"types": "./dist/utils/index.d.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
8
30
|
"scripts": {
|
|
9
31
|
"build": "tsc",
|
|
10
32
|
"test": "jest",
|
|
@@ -12,6 +34,9 @@
|
|
|
12
34
|
"test:coverage": "jest --coverage",
|
|
13
35
|
"type-check": "tsc --noEmit"
|
|
14
36
|
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"zod": "^3.22.4"
|
|
39
|
+
},
|
|
15
40
|
"peerDependencies": {
|
|
16
41
|
"@idealyst/components": "^1.0.21",
|
|
17
42
|
"@idealyst/navigation": "^1.0.21",
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, Button, Image } from '@idealyst/components';
|
|
3
|
+
import type { User, Post, Comment } from '../types';
|
|
4
|
+
|
|
5
|
+
interface UserCardProps {
|
|
6
|
+
user: User;
|
|
7
|
+
onPress?: () => void;
|
|
8
|
+
showBio?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const UserCard: React.FC<UserCardProps> = ({
|
|
12
|
+
user,
|
|
13
|
+
onPress,
|
|
14
|
+
showBio = false
|
|
15
|
+
}) => {
|
|
16
|
+
return (
|
|
17
|
+
<View
|
|
18
|
+
style={{
|
|
19
|
+
padding: 16,
|
|
20
|
+
borderRadius: 8,
|
|
21
|
+
backgroundColor: 'white',
|
|
22
|
+
shadowColor: '#000',
|
|
23
|
+
shadowOffset: { width: 0, height: 2 },
|
|
24
|
+
shadowOpacity: 0.1,
|
|
25
|
+
shadowRadius: 4,
|
|
26
|
+
elevation: 2,
|
|
27
|
+
marginBottom: 8
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
31
|
+
{user.avatar && (
|
|
32
|
+
<Image
|
|
33
|
+
source={{ uri: user.avatar }}
|
|
34
|
+
style={{
|
|
35
|
+
width: 50,
|
|
36
|
+
height: 50,
|
|
37
|
+
borderRadius: 25,
|
|
38
|
+
marginRight: 12
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
)}
|
|
42
|
+
<View style={{ flex: 1 }}>
|
|
43
|
+
<Text variant="h3" style={{ marginBottom: 4 }}>
|
|
44
|
+
{user.name || 'Anonymous User'}
|
|
45
|
+
</Text>
|
|
46
|
+
<Text variant="caption" style={{ color: 'gray' }}>
|
|
47
|
+
{user.email}
|
|
48
|
+
</Text>
|
|
49
|
+
{user.location && (
|
|
50
|
+
<Text variant="caption" style={{ color: 'gray', marginTop: 2 }}>
|
|
51
|
+
📍 {user.location}
|
|
52
|
+
</Text>
|
|
53
|
+
)}
|
|
54
|
+
</View>
|
|
55
|
+
{onPress && (
|
|
56
|
+
<Button
|
|
57
|
+
title="View Profile"
|
|
58
|
+
onPress={onPress}
|
|
59
|
+
size="small"
|
|
60
|
+
/>
|
|
61
|
+
)}
|
|
62
|
+
</View>
|
|
63
|
+
|
|
64
|
+
{showBio && user.bio && (
|
|
65
|
+
<Text variant="body" style={{ marginTop: 12, fontStyle: 'italic' }}>
|
|
66
|
+
{user.bio}
|
|
67
|
+
</Text>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{user.website && (
|
|
71
|
+
<Text variant="caption" style={{ marginTop: 8, color: 'blue' }}>
|
|
72
|
+
🌐 {user.website}
|
|
73
|
+
</Text>
|
|
74
|
+
)}
|
|
75
|
+
</View>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
interface PostCardProps {
|
|
80
|
+
post: Post;
|
|
81
|
+
author?: User;
|
|
82
|
+
onPress?: () => void;
|
|
83
|
+
onLike?: () => void;
|
|
84
|
+
showFullContent?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const PostCard: React.FC<PostCardProps> = ({
|
|
88
|
+
post,
|
|
89
|
+
author,
|
|
90
|
+
onPress,
|
|
91
|
+
onLike,
|
|
92
|
+
showFullContent = false
|
|
93
|
+
}) => {
|
|
94
|
+
const content = showFullContent ? post.content : (post.excerpt || post.content.substring(0, 150) + '...');
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<View
|
|
98
|
+
style={{
|
|
99
|
+
padding: 16,
|
|
100
|
+
borderRadius: 8,
|
|
101
|
+
backgroundColor: 'white',
|
|
102
|
+
shadowColor: '#000',
|
|
103
|
+
shadowOffset: { width: 0, height: 2 },
|
|
104
|
+
shadowOpacity: 0.1,
|
|
105
|
+
shadowRadius: 4,
|
|
106
|
+
elevation: 2,
|
|
107
|
+
marginBottom: 12
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
{/* Post Header */}
|
|
111
|
+
{author && (
|
|
112
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 12 }}>
|
|
113
|
+
{author.avatar && (
|
|
114
|
+
<Image
|
|
115
|
+
source={{ uri: author.avatar }}
|
|
116
|
+
style={{
|
|
117
|
+
width: 32,
|
|
118
|
+
height: 32,
|
|
119
|
+
borderRadius: 16,
|
|
120
|
+
marginRight: 8
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
<View>
|
|
125
|
+
<Text variant="body" style={{ fontWeight: 'bold' }}>
|
|
126
|
+
{author.name || 'Anonymous'}
|
|
127
|
+
</Text>
|
|
128
|
+
<Text variant="caption" style={{ color: 'gray' }}>
|
|
129
|
+
{new Date(post.createdAt).toLocaleDateString()}
|
|
130
|
+
</Text>
|
|
131
|
+
</View>
|
|
132
|
+
</View>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{/* Post Content */}
|
|
136
|
+
<Text variant="h2" style={{ marginBottom: 8 }}>
|
|
137
|
+
{post.title}
|
|
138
|
+
</Text>
|
|
139
|
+
|
|
140
|
+
<Text variant="body" style={{ marginBottom: 12 }}>
|
|
141
|
+
{content}
|
|
142
|
+
</Text>
|
|
143
|
+
|
|
144
|
+
{/* Tags */}
|
|
145
|
+
{post.tags.length > 0 && (
|
|
146
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', marginBottom: 12 }}>
|
|
147
|
+
{post.tags.map((tag, index) => (
|
|
148
|
+
<View
|
|
149
|
+
key={index}
|
|
150
|
+
style={{
|
|
151
|
+
backgroundColor: '#e3f2fd',
|
|
152
|
+
paddingHorizontal: 8,
|
|
153
|
+
paddingVertical: 4,
|
|
154
|
+
borderRadius: 12,
|
|
155
|
+
marginRight: 6,
|
|
156
|
+
marginBottom: 4
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<Text variant="caption" style={{ color: '#1976d2' }}>
|
|
160
|
+
#{tag}
|
|
161
|
+
</Text>
|
|
162
|
+
</View>
|
|
163
|
+
))}
|
|
164
|
+
</View>
|
|
165
|
+
)}
|
|
166
|
+
|
|
167
|
+
{/* Post Stats and Actions */}
|
|
168
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
169
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
170
|
+
<Text variant="caption" style={{ color: 'gray', marginRight: 12 }}>
|
|
171
|
+
👁️ {post.views} views
|
|
172
|
+
</Text>
|
|
173
|
+
<Text variant="caption" style={{ color: 'gray' }}>
|
|
174
|
+
❤️ {post.likes} likes
|
|
175
|
+
</Text>
|
|
176
|
+
</View>
|
|
177
|
+
|
|
178
|
+
<View style={{ flexDirection: 'row' }}>
|
|
179
|
+
{onLike && (
|
|
180
|
+
<Button
|
|
181
|
+
title="Like"
|
|
182
|
+
onPress={onLike}
|
|
183
|
+
size="small"
|
|
184
|
+
variant="outline"
|
|
185
|
+
style={{ marginRight: 8 }}
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
188
|
+
{onPress && (
|
|
189
|
+
<Button
|
|
190
|
+
title="Read More"
|
|
191
|
+
onPress={onPress}
|
|
192
|
+
size="small"
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
195
|
+
</View>
|
|
196
|
+
</View>
|
|
197
|
+
</View>
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
interface CommentCardProps {
|
|
202
|
+
comment: Comment;
|
|
203
|
+
author?: User;
|
|
204
|
+
onReply?: () => void;
|
|
205
|
+
level?: number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const CommentCard: React.FC<CommentCardProps> = ({
|
|
209
|
+
comment,
|
|
210
|
+
author,
|
|
211
|
+
onReply,
|
|
212
|
+
level = 0
|
|
213
|
+
}) => {
|
|
214
|
+
const indentStyle = {
|
|
215
|
+
marginLeft: level * 20,
|
|
216
|
+
maxWidth: level > 2 ? '90%' : '100%'
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<View
|
|
221
|
+
style={[
|
|
222
|
+
{
|
|
223
|
+
padding: 12,
|
|
224
|
+
borderRadius: 6,
|
|
225
|
+
backgroundColor: level === 0 ? 'white' : '#f8f9fa',
|
|
226
|
+
borderLeftWidth: level > 0 ? 3 : 0,
|
|
227
|
+
borderLeftColor: '#e3f2fd',
|
|
228
|
+
marginBottom: 8
|
|
229
|
+
},
|
|
230
|
+
indentStyle
|
|
231
|
+
]}
|
|
232
|
+
>
|
|
233
|
+
{/* Comment Header */}
|
|
234
|
+
{author && (
|
|
235
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 8 }}>
|
|
236
|
+
{author.avatar && (
|
|
237
|
+
<Image
|
|
238
|
+
source={{ uri: author.avatar }}
|
|
239
|
+
style={{
|
|
240
|
+
width: 24,
|
|
241
|
+
height: 24,
|
|
242
|
+
borderRadius: 12,
|
|
243
|
+
marginRight: 6
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
)}
|
|
247
|
+
<Text variant="body" style={{ fontWeight: 'bold', fontSize: 14 }}>
|
|
248
|
+
{author.name || 'Anonymous'}
|
|
249
|
+
</Text>
|
|
250
|
+
<Text variant="caption" style={{ color: 'gray', marginLeft: 8 }}>
|
|
251
|
+
{new Date(comment.createdAt).toLocaleDateString()}
|
|
252
|
+
</Text>
|
|
253
|
+
</View>
|
|
254
|
+
)}
|
|
255
|
+
|
|
256
|
+
{/* Comment Content */}
|
|
257
|
+
<Text variant="body" style={{ marginBottom: 8 }}>
|
|
258
|
+
{comment.content}
|
|
259
|
+
</Text>
|
|
260
|
+
|
|
261
|
+
{/* Comment Actions */}
|
|
262
|
+
{onReply && level < 3 && (
|
|
263
|
+
<Button
|
|
264
|
+
title="Reply"
|
|
265
|
+
onPress={onReply}
|
|
266
|
+
size="small"
|
|
267
|
+
variant="outline"
|
|
268
|
+
/>
|
|
269
|
+
)}
|
|
270
|
+
</View>
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
interface LoadingSpinnerProps {
|
|
275
|
+
size?: 'small' | 'medium' | 'large';
|
|
276
|
+
message?: string;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
|
|
280
|
+
size = 'medium',
|
|
281
|
+
message
|
|
282
|
+
}) => {
|
|
283
|
+
const sizeMap = {
|
|
284
|
+
small: 20,
|
|
285
|
+
medium: 40,
|
|
286
|
+
large: 60
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<View style={{ alignItems: 'center', justifyContent: 'center', padding: 20 }}>
|
|
291
|
+
<View
|
|
292
|
+
style={{
|
|
293
|
+
width: sizeMap[size],
|
|
294
|
+
height: sizeMap[size],
|
|
295
|
+
borderRadius: sizeMap[size] / 2,
|
|
296
|
+
borderWidth: 3,
|
|
297
|
+
borderColor: '#e3f2fd',
|
|
298
|
+
borderTopColor: '#1976d2',
|
|
299
|
+
// Animation would be handled by the platform-specific implementation
|
|
300
|
+
}}
|
|
301
|
+
/>
|
|
302
|
+
{message && (
|
|
303
|
+
<Text variant="body" style={{ marginTop: 12, textAlign: 'center' }}>
|
|
304
|
+
{message}
|
|
305
|
+
</Text>
|
|
306
|
+
)}
|
|
307
|
+
</View>
|
|
308
|
+
);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
interface ErrorMessageProps {
|
|
312
|
+
message: string;
|
|
313
|
+
onRetry?: () => void;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export const ErrorMessage: React.FC<ErrorMessageProps> = ({ message, onRetry }) => {
|
|
317
|
+
return (
|
|
318
|
+
<View
|
|
319
|
+
style={{
|
|
320
|
+
padding: 16,
|
|
321
|
+
backgroundColor: '#ffebee',
|
|
322
|
+
borderRadius: 8,
|
|
323
|
+
borderLeftWidth: 4,
|
|
324
|
+
borderLeftColor: '#f44336',
|
|
325
|
+
margin: 16
|
|
326
|
+
}}
|
|
327
|
+
>
|
|
328
|
+
<Text variant="body" style={{ color: '#c62828', marginBottom: onRetry ? 12 : 0 }}>
|
|
329
|
+
⚠️ {message}
|
|
330
|
+
</Text>
|
|
331
|
+
{onRetry && (
|
|
332
|
+
<Button
|
|
333
|
+
title="Try Again"
|
|
334
|
+
onPress={onRetry}
|
|
335
|
+
size="small"
|
|
336
|
+
style={{ backgroundColor: '#f44336' }}
|
|
337
|
+
/>
|
|
338
|
+
)}
|
|
339
|
+
</View>
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// Feature Card Component - reusable for both web and native
|
|
344
|
+
interface FeatureCardProps {
|
|
345
|
+
icon: string;
|
|
346
|
+
title: string;
|
|
347
|
+
description: string;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export const FeatureCard: React.FC<FeatureCardProps> = ({ icon, title, description }) => {
|
|
351
|
+
return (
|
|
352
|
+
<View style={{
|
|
353
|
+
padding: 16,
|
|
354
|
+
backgroundColor: 'white',
|
|
355
|
+
borderRadius: 8,
|
|
356
|
+
shadowColor: '#000',
|
|
357
|
+
shadowOffset: { width: 0, height: 2 },
|
|
358
|
+
shadowOpacity: 0.1,
|
|
359
|
+
shadowRadius: 4,
|
|
360
|
+
elevation: 2,
|
|
361
|
+
marginBottom: 12
|
|
362
|
+
}}>
|
|
363
|
+
<Text style={{ fontSize: 24, marginBottom: 8, textAlign: 'center' }}>{icon}</Text>
|
|
364
|
+
<Text variant="h4" style={{ marginBottom: 8, textAlign: 'center' }}>{title}</Text>
|
|
365
|
+
<Text variant="body" style={{ color: '#666', textAlign: 'center' }}>{description}</Text>
|
|
366
|
+
</View>
|
|
367
|
+
);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Tab Button Component - for navigation tabs in mobile/web apps
|
|
371
|
+
interface TabButtonProps {
|
|
372
|
+
title: string;
|
|
373
|
+
icon: string;
|
|
374
|
+
active: boolean;
|
|
375
|
+
onPress: () => void;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export const TabButton: React.FC<TabButtonProps> = ({ title, icon, active, onPress }) => {
|
|
379
|
+
return (
|
|
380
|
+
<Button
|
|
381
|
+
title={`${icon} ${title}`}
|
|
382
|
+
onPress={onPress}
|
|
383
|
+
variant={active ? 'primary' : 'outline'}
|
|
384
|
+
style={{
|
|
385
|
+
flex: 1,
|
|
386
|
+
marginHorizontal: 4,
|
|
387
|
+
backgroundColor: active ? '#007bff' : 'transparent',
|
|
388
|
+
borderColor: active ? '#007bff' : '#ccc'
|
|
389
|
+
}}
|
|
390
|
+
/>
|
|
391
|
+
);
|
|
392
|
+
};
|
|
@@ -1 +1,59 @@
|
|
|
1
|
-
|
|
1
|
+
// Export all types
|
|
2
|
+
export * from './types';
|
|
3
|
+
|
|
4
|
+
// Export all components
|
|
5
|
+
export * from './components';
|
|
6
|
+
|
|
7
|
+
// Export all utilities
|
|
8
|
+
export * from './utils';
|
|
9
|
+
|
|
10
|
+
// Re-export commonly used items for convenience
|
|
11
|
+
export type {
|
|
12
|
+
User,
|
|
13
|
+
Post,
|
|
14
|
+
Comment,
|
|
15
|
+
UserSettings,
|
|
16
|
+
CreateUser,
|
|
17
|
+
CreatePost,
|
|
18
|
+
CreateComment,
|
|
19
|
+
PostWithAuthor,
|
|
20
|
+
CommentWithAuthor,
|
|
21
|
+
ApiResponse,
|
|
22
|
+
PaginatedResponse,
|
|
23
|
+
Theme,
|
|
24
|
+
LoadingState,
|
|
25
|
+
PaginationParams
|
|
26
|
+
} from './types';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
UserCard,
|
|
30
|
+
PostCard,
|
|
31
|
+
CommentCard,
|
|
32
|
+
LoadingSpinner,
|
|
33
|
+
ErrorMessage,
|
|
34
|
+
FeatureCard,
|
|
35
|
+
TabButton
|
|
36
|
+
} from './components';
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
formatDate,
|
|
40
|
+
formatDateTime,
|
|
41
|
+
formatRelativeTime,
|
|
42
|
+
truncateText,
|
|
43
|
+
capitalizeFirst,
|
|
44
|
+
slugify,
|
|
45
|
+
paginate,
|
|
46
|
+
sortBy,
|
|
47
|
+
pick,
|
|
48
|
+
omit,
|
|
49
|
+
isValidEmail,
|
|
50
|
+
isValidUrl,
|
|
51
|
+
storage,
|
|
52
|
+
debounce,
|
|
53
|
+
getSystemTheme,
|
|
54
|
+
getErrorMessage,
|
|
55
|
+
isWeb,
|
|
56
|
+
isMobile,
|
|
57
|
+
DEMO_USERS,
|
|
58
|
+
DEMO_POSTS
|
|
59
|
+
} from './utils';
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
// User related schemas and types
|
|
4
|
+
export const UserSchema = z.object({
|
|
5
|
+
id: z.string(),
|
|
6
|
+
email: z.string().email(),
|
|
7
|
+
name: z.string().nullable(),
|
|
8
|
+
avatar: z.string().url().nullable(),
|
|
9
|
+
bio: z.string().nullable(),
|
|
10
|
+
location: z.string().nullable(),
|
|
11
|
+
website: z.string().url().nullable(),
|
|
12
|
+
createdAt: z.date(),
|
|
13
|
+
updatedAt: z.date(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const CreateUserSchema = z.object({
|
|
17
|
+
email: z.string().email(),
|
|
18
|
+
name: z.string().min(1, 'Name is required'),
|
|
19
|
+
avatar: z.string().url().optional(),
|
|
20
|
+
bio: z.string().optional(),
|
|
21
|
+
location: z.string().optional(),
|
|
22
|
+
website: z.string().url().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const UpdateUserSchema = CreateUserSchema.partial();
|
|
26
|
+
|
|
27
|
+
// Post related schemas and types
|
|
28
|
+
export const PostSchema = z.object({
|
|
29
|
+
id: z.string(),
|
|
30
|
+
title: z.string(),
|
|
31
|
+
content: z.string(),
|
|
32
|
+
excerpt: z.string().nullable(),
|
|
33
|
+
published: z.boolean(),
|
|
34
|
+
tags: z.array(z.string()),
|
|
35
|
+
authorId: z.string(),
|
|
36
|
+
views: z.number(),
|
|
37
|
+
likes: z.number(),
|
|
38
|
+
createdAt: z.date(),
|
|
39
|
+
updatedAt: z.date(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const CreatePostSchema = z.object({
|
|
43
|
+
title: z.string().min(1, 'Title is required'),
|
|
44
|
+
content: z.string().min(1, 'Content is required'),
|
|
45
|
+
excerpt: z.string().optional(),
|
|
46
|
+
published: z.boolean().default(false),
|
|
47
|
+
tags: z.array(z.string()).default([]),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const UpdatePostSchema = CreatePostSchema.partial();
|
|
51
|
+
|
|
52
|
+
// Comment related schemas and types
|
|
53
|
+
export const CommentSchema = z.object({
|
|
54
|
+
id: z.string(),
|
|
55
|
+
content: z.string(),
|
|
56
|
+
authorId: z.string(),
|
|
57
|
+
postId: z.string(),
|
|
58
|
+
parentId: z.string().nullable(),
|
|
59
|
+
createdAt: z.date(),
|
|
60
|
+
updatedAt: z.date(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const CreateCommentSchema = z.object({
|
|
64
|
+
content: z.string().min(1, 'Comment cannot be empty'),
|
|
65
|
+
postId: z.string(),
|
|
66
|
+
parentId: z.string().optional(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// User settings schemas and types
|
|
70
|
+
export const UserSettingsSchema = z.object({
|
|
71
|
+
id: z.string(),
|
|
72
|
+
theme: z.enum(['light', 'dark', 'auto']),
|
|
73
|
+
notifications: z.boolean(),
|
|
74
|
+
emailUpdates: z.boolean(),
|
|
75
|
+
publicProfile: z.boolean(),
|
|
76
|
+
userId: z.string(),
|
|
77
|
+
createdAt: z.date(),
|
|
78
|
+
updatedAt: z.date(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export const UpdateUserSettingsSchema = z.object({
|
|
82
|
+
theme: z.enum(['light', 'dark', 'auto']).optional(),
|
|
83
|
+
notifications: z.boolean().optional(),
|
|
84
|
+
emailUpdates: z.boolean().optional(),
|
|
85
|
+
publicProfile: z.boolean().optional(),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Inferred TypeScript types
|
|
89
|
+
export type User = z.infer<typeof UserSchema>;
|
|
90
|
+
export type CreateUser = z.infer<typeof CreateUserSchema>;
|
|
91
|
+
export type UpdateUser = z.infer<typeof UpdateUserSchema>;
|
|
92
|
+
|
|
93
|
+
export type Post = z.infer<typeof PostSchema>;
|
|
94
|
+
export type CreatePost = z.infer<typeof CreatePostSchema>;
|
|
95
|
+
export type UpdatePost = z.infer<typeof UpdatePostSchema>;
|
|
96
|
+
|
|
97
|
+
export type Comment = z.infer<typeof CommentSchema>;
|
|
98
|
+
export type CreateComment = z.infer<typeof CreateCommentSchema>;
|
|
99
|
+
|
|
100
|
+
export type UserSettings = z.infer<typeof UserSettingsSchema>;
|
|
101
|
+
export type UpdateUserSettings = z.infer<typeof UpdateUserSettingsSchema>;
|
|
102
|
+
|
|
103
|
+
// Extended types with relations for UI components
|
|
104
|
+
export type UserWithPosts = User & {
|
|
105
|
+
posts: Post[];
|
|
106
|
+
settings?: UserSettings;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type PostWithAuthor = Post & {
|
|
110
|
+
author: User;
|
|
111
|
+
comments: CommentWithAuthor[];
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export type CommentWithAuthor = Comment & {
|
|
115
|
+
author: User;
|
|
116
|
+
children?: CommentWithAuthor[];
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// API Response types
|
|
120
|
+
export type ApiResponse<T> = {
|
|
121
|
+
data: T;
|
|
122
|
+
success: boolean;
|
|
123
|
+
message?: string;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export type PaginatedResponse<T> = ApiResponse<{
|
|
127
|
+
items: T[];
|
|
128
|
+
total: number;
|
|
129
|
+
page: number;
|
|
130
|
+
pageSize: number;
|
|
131
|
+
hasMore: boolean;
|
|
132
|
+
}>;
|
|
133
|
+
|
|
134
|
+
// Common utility types
|
|
135
|
+
export type Theme = 'light' | 'dark' | 'auto';
|
|
136
|
+
|
|
137
|
+
export interface LoadingState {
|
|
138
|
+
isLoading: boolean;
|
|
139
|
+
error?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface PaginationParams {
|
|
143
|
+
page?: number;
|
|
144
|
+
pageSize?: number;
|
|
145
|
+
sortBy?: string;
|
|
146
|
+
sortOrder?: 'asc' | 'desc';
|
|
147
|
+
search?: string;
|
|
148
|
+
}
|