@idealyst/cli 1.0.41 → 1.0.44
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
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// Date and time utilities
|
|
2
|
+
export const formatDate = (date: Date | string): string => {
|
|
3
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
4
|
+
return d.toLocaleDateString('en-US', {
|
|
5
|
+
year: 'numeric',
|
|
6
|
+
month: 'long',
|
|
7
|
+
day: 'numeric'
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const formatDateTime = (date: Date | string): string => {
|
|
12
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
13
|
+
return d.toLocaleString('en-US', {
|
|
14
|
+
year: 'numeric',
|
|
15
|
+
month: 'short',
|
|
16
|
+
day: 'numeric',
|
|
17
|
+
hour: '2-digit',
|
|
18
|
+
minute: '2-digit'
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const formatRelativeTime = (date: Date | string): string => {
|
|
23
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
24
|
+
const now = new Date();
|
|
25
|
+
const diffInSeconds = Math.floor((now.getTime() - d.getTime()) / 1000);
|
|
26
|
+
|
|
27
|
+
if (diffInSeconds < 60) return 'just now';
|
|
28
|
+
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`;
|
|
29
|
+
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`;
|
|
30
|
+
if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 86400)} days ago`;
|
|
31
|
+
return formatDate(d);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// String utilities
|
|
35
|
+
export const truncateText = (text: string, maxLength: number): string => {
|
|
36
|
+
if (text.length <= maxLength) return text;
|
|
37
|
+
return text.substring(0, maxLength).trim() + '...';
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const capitalizeFirst = (text: string): string => {
|
|
41
|
+
if (!text) return text;
|
|
42
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const slugify = (text: string): string => {
|
|
46
|
+
return text
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/[^\w\s-]/g, '') // Remove special chars
|
|
49
|
+
.replace(/[\s_-]+/g, '-') // Replace spaces and underscores with hyphens
|
|
50
|
+
.replace(/^-+|-+$/g, ''); // Remove leading/trailing hyphens
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Array utilities
|
|
54
|
+
export const paginate = <T>(array: T[], page: number, pageSize: number) => {
|
|
55
|
+
const startIndex = (page - 1) * pageSize;
|
|
56
|
+
const endIndex = startIndex + pageSize;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
items: array.slice(startIndex, endIndex),
|
|
60
|
+
total: array.length,
|
|
61
|
+
page,
|
|
62
|
+
pageSize,
|
|
63
|
+
hasMore: endIndex < array.length,
|
|
64
|
+
totalPages: Math.ceil(array.length / pageSize)
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const sortBy = <T>(array: T[], key: keyof T, order: 'asc' | 'desc' = 'asc'): T[] => {
|
|
69
|
+
return [...array].sort((a, b) => {
|
|
70
|
+
const aVal = a[key];
|
|
71
|
+
const bVal = b[key];
|
|
72
|
+
|
|
73
|
+
if (aVal < bVal) return order === 'asc' ? -1 : 1;
|
|
74
|
+
if (aVal > bVal) return order === 'asc' ? 1 : -1;
|
|
75
|
+
return 0;
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Object utilities
|
|
80
|
+
export const pick = <T extends object, K extends keyof T>(
|
|
81
|
+
obj: T,
|
|
82
|
+
keys: K[]
|
|
83
|
+
): Pick<T, K> => {
|
|
84
|
+
const result = {} as Pick<T, K>;
|
|
85
|
+
keys.forEach(key => {
|
|
86
|
+
if (key in obj) {
|
|
87
|
+
result[key] = obj[key];
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return result;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const omit = <T extends object, K extends keyof T>(
|
|
94
|
+
obj: T,
|
|
95
|
+
keys: K[]
|
|
96
|
+
): Omit<T, K> => {
|
|
97
|
+
const result = { ...obj };
|
|
98
|
+
keys.forEach(key => {
|
|
99
|
+
delete result[key];
|
|
100
|
+
});
|
|
101
|
+
return result as Omit<T, K>;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Validation utilities
|
|
105
|
+
export const isValidEmail = (email: string): boolean => {
|
|
106
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
107
|
+
return emailRegex.test(email);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const isValidUrl = (url: string): boolean => {
|
|
111
|
+
try {
|
|
112
|
+
new URL(url);
|
|
113
|
+
return true;
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Local storage utilities (with fallbacks for environments without localStorage)
|
|
120
|
+
export const storage = {
|
|
121
|
+
get: (key: string): string | null => {
|
|
122
|
+
try {
|
|
123
|
+
if (typeof localStorage !== 'undefined') {
|
|
124
|
+
return localStorage.getItem(key);
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.warn('localStorage not available:', error);
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
set: (key: string, value: string): void => {
|
|
133
|
+
try {
|
|
134
|
+
if (typeof localStorage !== 'undefined') {
|
|
135
|
+
localStorage.setItem(key, value);
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.warn('localStorage not available:', error);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
remove: (key: string): void => {
|
|
143
|
+
try {
|
|
144
|
+
if (typeof localStorage !== 'undefined') {
|
|
145
|
+
localStorage.removeItem(key);
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.warn('localStorage not available:', error);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
getObject: <T>(key: string): T | null => {
|
|
153
|
+
const value = storage.get(key);
|
|
154
|
+
if (value) {
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(value);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.warn('Failed to parse stored object:', error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
setObject: <T>(key: string, value: T): void => {
|
|
165
|
+
try {
|
|
166
|
+
storage.set(key, JSON.stringify(value));
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.warn('Failed to stringify object for storage:', error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Debounce utility for search and input handling
|
|
174
|
+
export const debounce = <T extends (...args: any[]) => any>(
|
|
175
|
+
func: T,
|
|
176
|
+
delay: number
|
|
177
|
+
): ((...args: Parameters<T>) => void) => {
|
|
178
|
+
let timeoutId: NodeJS.Timeout;
|
|
179
|
+
|
|
180
|
+
return (...args: Parameters<T>) => {
|
|
181
|
+
clearTimeout(timeoutId);
|
|
182
|
+
timeoutId = setTimeout(() => func(...args), delay);
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Theme utilities
|
|
187
|
+
export const getSystemTheme = (): 'light' | 'dark' => {
|
|
188
|
+
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
189
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
190
|
+
}
|
|
191
|
+
return 'light';
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Error handling utilities
|
|
195
|
+
export const getErrorMessage = (error: unknown): string => {
|
|
196
|
+
if (error instanceof Error) {
|
|
197
|
+
return error.message;
|
|
198
|
+
}
|
|
199
|
+
if (typeof error === 'string') {
|
|
200
|
+
return error;
|
|
201
|
+
}
|
|
202
|
+
return 'An unexpected error occurred';
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Platform detection utilities
|
|
206
|
+
export const isWeb = (): boolean => {
|
|
207
|
+
return typeof window !== 'undefined';
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const isMobile = (): boolean => {
|
|
211
|
+
return typeof navigator !== 'undefined' && /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Constants for demo data
|
|
215
|
+
export const DEMO_USERS = [
|
|
216
|
+
{
|
|
217
|
+
id: '1',
|
|
218
|
+
name: 'Alice Johnson',
|
|
219
|
+
email: 'alice@example.com',
|
|
220
|
+
avatar: 'https://via.placeholder.com/150/4CAF50/white?text=AJ',
|
|
221
|
+
bio: 'Full-stack developer passionate about React and TypeScript',
|
|
222
|
+
location: 'San Francisco, CA',
|
|
223
|
+
website: 'https://alice-dev.com'
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: '2',
|
|
227
|
+
name: 'Bob Smith',
|
|
228
|
+
email: 'bob@example.com',
|
|
229
|
+
avatar: 'https://via.placeholder.com/150/2196F3/white?text=BS',
|
|
230
|
+
bio: 'UI/UX designer and frontend enthusiast',
|
|
231
|
+
location: 'New York, NY',
|
|
232
|
+
website: 'https://bobdesigns.co'
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: '3',
|
|
236
|
+
name: 'Carol Williams',
|
|
237
|
+
email: 'carol@example.com',
|
|
238
|
+
avatar: 'https://via.placeholder.com/150/FF9800/white?text=CW',
|
|
239
|
+
bio: 'Product manager with a love for user research',
|
|
240
|
+
location: 'Austin, TX'
|
|
241
|
+
}
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
export const DEMO_POSTS = [
|
|
245
|
+
{
|
|
246
|
+
id: '1',
|
|
247
|
+
title: 'Getting Started with React Native',
|
|
248
|
+
content: 'React Native is a powerful framework for building cross-platform mobile applications...',
|
|
249
|
+
excerpt: 'Learn the basics of React Native development',
|
|
250
|
+
authorId: '1',
|
|
251
|
+
tags: ['react-native', 'mobile', 'javascript'],
|
|
252
|
+
published: true,
|
|
253
|
+
views: 234,
|
|
254
|
+
likes: 15
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: '2',
|
|
258
|
+
title: 'Design Systems in Modern Web Development',
|
|
259
|
+
content: 'A design system is a complete set of standards intended to manage design at scale...',
|
|
260
|
+
excerpt: 'Building consistent user interfaces across applications',
|
|
261
|
+
authorId: '2',
|
|
262
|
+
tags: ['design-systems', 'ui-ux', 'frontend'],
|
|
263
|
+
published: true,
|
|
264
|
+
views: 189,
|
|
265
|
+
likes: 23
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: '3',
|
|
269
|
+
title: 'User Research Best Practices',
|
|
270
|
+
content: 'Understanding your users is crucial for building successful products...',
|
|
271
|
+
excerpt: 'How to conduct effective user research',
|
|
272
|
+
authorId: '3',
|
|
273
|
+
tags: ['user-research', 'product-management', 'ux'],
|
|
274
|
+
published: true,
|
|
275
|
+
views: 156,
|
|
276
|
+
likes: 31
|
|
277
|
+
}
|
|
278
|
+
];
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"react-dom": "^19.1.0",
|
|
31
31
|
"react-native": "^0.80.1",
|
|
32
32
|
"react-native-edge-to-edge": "^1.6.2",
|
|
33
|
-
"react-native-nitro-modules": "0.
|
|
34
|
-
"react-native-unistyles": "^3.0.
|
|
33
|
+
"react-native-nitro-modules": "0.28.0",
|
|
34
|
+
"react-native-unistyles": "^3.0.10",
|
|
35
35
|
"react-native-web": "^0.20.0",
|
|
36
36
|
"react-router": "^7.6.3",
|
|
37
37
|
"react-router-dom": "^7.6.3",
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
+
import { httpBatchLink } from '@trpc/client';
|
|
4
|
+
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
|
|
5
|
+
import { trpc } from './utils/trpc';
|
|
6
|
+
import { Screen, Text, View, Button, ScrollView } from '@idealyst/components';
|
|
7
|
+
|
|
8
|
+
// Import shared components and utilities
|
|
9
|
+
import {
|
|
10
|
+
UserCard,
|
|
11
|
+
PostCard,
|
|
12
|
+
LoadingSpinner,
|
|
13
|
+
ErrorMessage,
|
|
14
|
+
FeatureCard,
|
|
15
|
+
DEMO_USERS,
|
|
16
|
+
DEMO_POSTS,
|
|
17
|
+
formatRelativeTime,
|
|
18
|
+
type User,
|
|
19
|
+
type Post,
|
|
20
|
+
type PostWithAuthor
|
|
21
|
+
} from '{{workspaceScope}}/shared';
|
|
22
|
+
|
|
23
|
+
// Create tRPC client
|
|
24
|
+
const queryClient = new QueryClient();
|
|
25
|
+
|
|
26
|
+
const trpcClient = trpc.createClient({
|
|
27
|
+
links: [
|
|
28
|
+
httpBatchLink({
|
|
29
|
+
url: 'http://localhost:3001/trpc', // Updated to match API port
|
|
30
|
+
// Optional: Add headers for authentication
|
|
31
|
+
// headers() {
|
|
32
|
+
// return {
|
|
33
|
+
// authorization: getAuthToken(),
|
|
34
|
+
// };
|
|
35
|
+
// },
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Navigation Component
|
|
41
|
+
function Navigation() {
|
|
42
|
+
return (
|
|
43
|
+
<View style={{
|
|
44
|
+
flexDirection: 'row',
|
|
45
|
+
padding: 16,
|
|
46
|
+
backgroundColor: '#f8f9fa',
|
|
47
|
+
borderBottomWidth: 1,
|
|
48
|
+
borderBottomColor: '#e9ecef'
|
|
49
|
+
}}>
|
|
50
|
+
<Text variant="h2" style={{ marginRight: 24 }}>{{projectName}}</Text>
|
|
51
|
+
<View style={{ flexDirection: 'row', gap: 16 }}>
|
|
52
|
+
<Link to="/" style={{ textDecoration: 'none' }}>
|
|
53
|
+
<Text style={{ color: '#007bff' }}>Home</Text>
|
|
54
|
+
</Link>
|
|
55
|
+
<Link to="/users" style={{ textDecoration: 'none' }}>
|
|
56
|
+
<Text style={{ color: '#007bff' }}>Users</Text>
|
|
57
|
+
</Link>
|
|
58
|
+
<Link to="/posts" style={{ textDecoration: 'none' }}>
|
|
59
|
+
<Text style={{ color: '#007bff' }}>Posts</Text>
|
|
60
|
+
</Link>
|
|
61
|
+
</View>
|
|
62
|
+
</View>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Home Page Component
|
|
67
|
+
function HomePage() {
|
|
68
|
+
// Example tRPC usage
|
|
69
|
+
const { data: helloData, isLoading: helloLoading, error: helloError } = trpc.hello.useQuery({ name: 'Web User' });
|
|
70
|
+
const { data: usersData, isLoading: usersLoading } = trpc.users.getAll.useQuery();
|
|
71
|
+
const { data: postsData, isLoading: postsLoading } = trpc.posts.getAll.useQuery();
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<ScrollView style={{ flex: 1 }}>
|
|
75
|
+
<View style={{ padding: 20 }}>
|
|
76
|
+
{/* Welcome Section */}
|
|
77
|
+
<View style={{ marginBottom: 32, textAlign: 'center' }}>
|
|
78
|
+
<Text variant="h1" style={{ marginBottom: 16 }}>
|
|
79
|
+
Welcome to {{projectName}}! 🚀
|
|
80
|
+
</Text>
|
|
81
|
+
<Text variant="body" style={{ marginBottom: 16, fontSize: 18 }}>
|
|
82
|
+
A full-stack application built with the Idealyst Framework
|
|
83
|
+
</Text>
|
|
84
|
+
|
|
85
|
+
{/* tRPC Connection Test */}
|
|
86
|
+
<View style={{
|
|
87
|
+
padding: 16,
|
|
88
|
+
backgroundColor: '#e3f2fd',
|
|
89
|
+
borderRadius: 8,
|
|
90
|
+
marginBottom: 24
|
|
91
|
+
}}>
|
|
92
|
+
<Text variant="h3" style={{ marginBottom: 8 }}>🔗 API Connection:</Text>
|
|
93
|
+
{helloLoading && <Text>Testing connection...</Text>}
|
|
94
|
+
{helloError && <Text style={{ color: 'red' }}>Error: {helloError.message}</Text>}
|
|
95
|
+
{helloData && <Text style={{ color: 'green' }}>✅ {helloData.greeting}</Text>}
|
|
96
|
+
</View>
|
|
97
|
+
</View>
|
|
98
|
+
|
|
99
|
+
{/* Features Overview */}
|
|
100
|
+
<View style={{ marginBottom: 32 }}>
|
|
101
|
+
<Text variant="h2" style={{ marginBottom: 16 }}>🏗️ Architecture Overview</Text>
|
|
102
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 16 }}>
|
|
103
|
+
<FeatureCard
|
|
104
|
+
icon="🗄️"
|
|
105
|
+
title="Database Layer"
|
|
106
|
+
description="Prisma ORM with SQLite, user management, posts, and comments"
|
|
107
|
+
/>
|
|
108
|
+
<FeatureCard
|
|
109
|
+
icon="🚀"
|
|
110
|
+
title="API Server"
|
|
111
|
+
description="tRPC API with type-safe endpoints and real-time capabilities"
|
|
112
|
+
/>
|
|
113
|
+
<FeatureCard
|
|
114
|
+
icon="🌐"
|
|
115
|
+
title="Web Application"
|
|
116
|
+
description="React web app with Idealyst components and responsive design"
|
|
117
|
+
/>
|
|
118
|
+
<FeatureCard
|
|
119
|
+
icon="📱"
|
|
120
|
+
title="Mobile App"
|
|
121
|
+
description="React Native app with shared components and unified styling"
|
|
122
|
+
/>
|
|
123
|
+
<FeatureCard
|
|
124
|
+
icon="📦"
|
|
125
|
+
title="Shared Library"
|
|
126
|
+
description="Cross-platform components, utilities, and type definitions"
|
|
127
|
+
/>
|
|
128
|
+
<FeatureCard
|
|
129
|
+
icon="🔗"
|
|
130
|
+
title="Full Integration"
|
|
131
|
+
description="End-to-end type safety and unified development workflow"
|
|
132
|
+
/>
|
|
133
|
+
</View>
|
|
134
|
+
</View>
|
|
135
|
+
|
|
136
|
+
{/* Live Data Preview */}
|
|
137
|
+
<View style={{ marginBottom: 32 }}>
|
|
138
|
+
<Text variant="h2" style={{ marginBottom: 16 }}>📊 Live Data Preview</Text>
|
|
139
|
+
|
|
140
|
+
{/* Users Section */}
|
|
141
|
+
<View style={{ marginBottom: 24 }}>
|
|
142
|
+
<Text variant="h3" style={{ marginBottom: 12 }}>👥 Users ({usersLoading ? '...' : usersData?.length || DEMO_USERS.length})</Text>
|
|
143
|
+
{usersLoading ? (
|
|
144
|
+
<LoadingSpinner message="Loading users..." />
|
|
145
|
+
) : (
|
|
146
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 12 }}>
|
|
147
|
+
{(usersData || DEMO_USERS.slice(0, 3)).map((user: User) => (
|
|
148
|
+
<View key={user.id} style={{ width: '300px' }}>
|
|
149
|
+
<UserCard
|
|
150
|
+
user={user}
|
|
151
|
+
showBio={false}
|
|
152
|
+
onPress={() => console.log('View profile:', user.name)}
|
|
153
|
+
/>
|
|
154
|
+
</View>
|
|
155
|
+
))}
|
|
156
|
+
</View>
|
|
157
|
+
)}
|
|
158
|
+
</View>
|
|
159
|
+
|
|
160
|
+
{/* Posts Section */}
|
|
161
|
+
<View style={{ marginBottom: 24 }}>
|
|
162
|
+
<Text variant="h3" style={{ marginBottom: 12 }}>📝 Recent Posts ({postsLoading ? '...' : postsData?.length || DEMO_POSTS.length})</Text>
|
|
163
|
+
{postsLoading ? (
|
|
164
|
+
<LoadingSpinner message="Loading posts..." />
|
|
165
|
+
) : (
|
|
166
|
+
<View>
|
|
167
|
+
{(postsData || DEMO_POSTS.slice(0, 2)).map((post: Post) => {
|
|
168
|
+
const author = DEMO_USERS.find(u => u.id === post.authorId);
|
|
169
|
+
return (
|
|
170
|
+
<PostCard
|
|
171
|
+
key={post.id}
|
|
172
|
+
post={post}
|
|
173
|
+
author={author}
|
|
174
|
+
onPress={() => console.log('Read post:', post.title)}
|
|
175
|
+
onLike={() => console.log('Like post:', post.title)}
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
})}
|
|
179
|
+
</View>
|
|
180
|
+
)}
|
|
181
|
+
</View>
|
|
182
|
+
</View>
|
|
183
|
+
|
|
184
|
+
{/* Quick Start Section */}
|
|
185
|
+
<View style={{
|
|
186
|
+
padding: 20,
|
|
187
|
+
backgroundColor: '#f8f9fa',
|
|
188
|
+
borderRadius: 8,
|
|
189
|
+
marginBottom: 24
|
|
190
|
+
}}>
|
|
191
|
+
<Text variant="h2" style={{ marginBottom: 16 }}>🚀 Quick Start</Text>
|
|
192
|
+
<Text variant="body" style={{ marginBottom: 12 }}>
|
|
193
|
+
Your full-stack workspace is ready! Here's what you can do:
|
|
194
|
+
</Text>
|
|
195
|
+
<View style={{ marginLeft: 16 }}>
|
|
196
|
+
<Text style={{ marginBottom: 4 }}>• 🗄️ Add your models in <code>packages/database/schema.prisma</code></Text>
|
|
197
|
+
<Text style={{ marginBottom: 4 }}>• 🚀 Create API endpoints in <code>packages/api/src/routers/</code></Text>
|
|
198
|
+
<Text style={{ marginBottom: 4 }}>• 📦 Build shared components in <code>packages/shared/src/</code></Text>
|
|
199
|
+
<Text style={{ marginBottom: 4 }}>• 🌐 Customize this web app in <code>packages/web/src/</code></Text>
|
|
200
|
+
<Text style={{ marginBottom: 4 }}>• 📱 Update the mobile app in <code>packages/mobile/src/</code></Text>
|
|
201
|
+
</View>
|
|
202
|
+
</View>
|
|
203
|
+
|
|
204
|
+
{/* Development Commands */}
|
|
205
|
+
<View style={{
|
|
206
|
+
padding: 20,
|
|
207
|
+
backgroundColor: '#e8f5e8',
|
|
208
|
+
borderRadius: 8,
|
|
209
|
+
marginBottom: 24
|
|
210
|
+
}}>
|
|
211
|
+
<Text variant="h3" style={{ marginBottom: 12 }}>💻 Development Commands</Text>
|
|
212
|
+
<View style={{ fontFamily: 'monospace', fontSize: 14 }}>
|
|
213
|
+
<Text style={{ marginBottom: 4 }}>yarn dev # Start all servers</Text>
|
|
214
|
+
<Text style={{ marginBottom: 4 }}>yarn web:dev # Start web app only</Text>
|
|
215
|
+
<Text style={{ marginBottom: 4 }}>yarn mobile:start # Start mobile bundler</Text>
|
|
216
|
+
<Text style={{ marginBottom: 4 }}>yarn api:dev # Start API server only</Text>
|
|
217
|
+
<Text style={{ marginBottom: 4 }}>yarn db:push # Update database schema</Text>
|
|
218
|
+
<Text style={{ marginBottom: 4 }}>yarn db:studio # Open database admin</Text>
|
|
219
|
+
</View>
|
|
220
|
+
</View>
|
|
221
|
+
</View>
|
|
222
|
+
</ScrollView>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Users Page Component
|
|
227
|
+
function UsersPage() {
|
|
228
|
+
const { data: users, isLoading, error } = trpc.users.getAll.useQuery();
|
|
229
|
+
|
|
230
|
+
if (isLoading) return <LoadingSpinner message="Loading users..." />;
|
|
231
|
+
if (error) return <ErrorMessage message={error.message} />;
|
|
232
|
+
|
|
233
|
+
const allUsers = users || DEMO_USERS;
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<ScrollView style={{ flex: 1, padding: 20 }}>
|
|
237
|
+
<Text variant="h1" style={{ marginBottom: 20 }}>👥 Users ({allUsers.length})</Text>
|
|
238
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 16 }}>
|
|
239
|
+
{allUsers.map((user: User) => (
|
|
240
|
+
<View key={user.id} style={{ width: '400px' }}>
|
|
241
|
+
<UserCard
|
|
242
|
+
user={user}
|
|
243
|
+
showBio={true}
|
|
244
|
+
onPress={() => console.log('View profile:', user.name)}
|
|
245
|
+
/>
|
|
246
|
+
</View>
|
|
247
|
+
))}
|
|
248
|
+
</View>
|
|
249
|
+
</ScrollView>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Posts Page Component
|
|
254
|
+
function PostsPage() {
|
|
255
|
+
const { data: posts, isLoading, error } = trpc.posts.getAll.useQuery();
|
|
256
|
+
|
|
257
|
+
if (isLoading) return <LoadingSpinner message="Loading posts..." />;
|
|
258
|
+
if (error) return <ErrorMessage message={error.message} />;
|
|
259
|
+
|
|
260
|
+
const allPosts = posts || DEMO_POSTS;
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<ScrollView style={{ flex: 1, padding: 20 }}>
|
|
264
|
+
<Text variant="h1" style={{ marginBottom: 20 }}>📝 Posts ({allPosts.length})</Text>
|
|
265
|
+
<View>
|
|
266
|
+
{allPosts.map((post: Post) => {
|
|
267
|
+
const author = DEMO_USERS.find(u => u.id === post.authorId);
|
|
268
|
+
return (
|
|
269
|
+
<PostCard
|
|
270
|
+
key={post.id}
|
|
271
|
+
post={post}
|
|
272
|
+
author={author}
|
|
273
|
+
showFullContent={false}
|
|
274
|
+
onPress={() => console.log('Read post:', post.title)}
|
|
275
|
+
onLike={() => console.log('Like post:', post.title)}
|
|
276
|
+
/>
|
|
277
|
+
);
|
|
278
|
+
})}
|
|
279
|
+
</View>
|
|
280
|
+
</ScrollView>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Main App Component
|
|
285
|
+
function App() {
|
|
286
|
+
return (
|
|
287
|
+
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
|
288
|
+
<QueryClientProvider client={queryClient}>
|
|
289
|
+
<BrowserRouter>
|
|
290
|
+
<Screen>
|
|
291
|
+
<Navigation />
|
|
292
|
+
<Routes>
|
|
293
|
+
<Route path="/" element={<HomePage />} />
|
|
294
|
+
<Route path="/users" element={<UsersPage />} />
|
|
295
|
+
<Route path="/posts" element={<PostsPage />} />
|
|
296
|
+
</Routes>
|
|
297
|
+
</Screen>
|
|
298
|
+
</BrowserRouter>
|
|
299
|
+
</QueryClientProvider>
|
|
300
|
+
</trpc.Provider>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export default App;
|
|
@@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y \
|
|
|
10
10
|
RUN corepack enable
|
|
11
11
|
|
|
12
12
|
# Install global tools that might be useful
|
|
13
|
-
RUN npm install -g @expo/cli
|
|
13
|
+
RUN npm install -g @expo/cli @anthropic-ai/claude-code
|
|
14
14
|
|
|
15
15
|
# Set up git (will be configured in setup script)
|
|
16
16
|
RUN git config --global init.defaultBranch main
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "{{projectName}} Development",
|
|
3
3
|
"dockerComposeFile": "docker-compose.yml",
|
|
4
4
|
"service": "app",
|
|
5
5
|
"workspaceFolder": "/workspace",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
8080, // Additional dev server
|
|
13
13
|
19006, // Expo dev tools
|
|
14
14
|
5432, // PostgreSQL
|
|
15
|
-
6379 // Redis
|
|
15
|
+
6379, // Redis
|
|
16
|
+
3333 // Figma MCP server
|
|
16
17
|
],
|
|
17
18
|
|
|
18
19
|
// Port attributes
|
|
@@ -44,6 +45,10 @@
|
|
|
44
45
|
"6379": {
|
|
45
46
|
"label": "Redis Cache",
|
|
46
47
|
"onAutoForward": "silent"
|
|
48
|
+
},
|
|
49
|
+
"3333": {
|
|
50
|
+
"label": "Figma MCP Server",
|
|
51
|
+
"onAutoForward": "openBrowser"
|
|
47
52
|
}
|
|
48
53
|
},
|
|
49
54
|
|
|
@@ -42,6 +42,20 @@ services:
|
|
|
42
42
|
- redis
|
|
43
43
|
command: sleep infinity
|
|
44
44
|
|
|
45
|
+
# Figma MCP Server
|
|
46
|
+
figma-mcp:
|
|
47
|
+
image: node:18-alpine
|
|
48
|
+
working_dir: /app
|
|
49
|
+
environment:
|
|
50
|
+
- FIGMA_ACCESS_TOKEN=${FIGMA_ACCESS_TOKEN:-}
|
|
51
|
+
ports:
|
|
52
|
+
- "3333:3333"
|
|
53
|
+
volumes:
|
|
54
|
+
- ./figma-mcp.sh:/app/figma-mcp.sh:ro
|
|
55
|
+
- figma_mcp_data:/app/node_modules
|
|
56
|
+
command: ["sh", "/app/figma-mcp.sh"]
|
|
57
|
+
|
|
45
58
|
volumes:
|
|
46
59
|
postgres_data:
|
|
47
60
|
redis_data:
|
|
61
|
+
figma_mcp_data:
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo "Starting Figma MCP Server..."
|
|
5
|
+
|
|
6
|
+
# Check if FIGMA_ACCESS_TOKEN is provided
|
|
7
|
+
if [ -z "$FIGMA_ACCESS_TOKEN" ]; then
|
|
8
|
+
echo "❌ No FIGMA_ACCESS_TOKEN provided"
|
|
9
|
+
echo "💡 Add your token to .devcontainer/.env to enable Figma integration"
|
|
10
|
+
echo " Example: echo 'FIGMA_ACCESS_TOKEN=fig_your_token_here' > .devcontainer/.env"
|
|
11
|
+
echo ""
|
|
12
|
+
echo "🔄 Keeping container alive (waiting for token)..."
|
|
13
|
+
sleep infinity
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
echo "✅ Figma token found, installing figma-developer-mcp..."
|
|
18
|
+
|
|
19
|
+
# Install figma-developer-mcp if not already installed
|
|
20
|
+
if ! command -v figma-developer-mcp &> /dev/null; then
|
|
21
|
+
npm install -g figma-developer-mcp
|
|
22
|
+
echo "📦 figma-developer-mcp installed successfully"
|
|
23
|
+
else
|
|
24
|
+
echo "📦 figma-developer-mcp already installed"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo "🚀 Starting Figma MCP server on port 3333..."
|
|
28
|
+
echo "🎨 Figma designs will be available to AI tools like Claude"
|
|
29
|
+
|
|
30
|
+
# Start the MCP server with the correct environment variable
|
|
31
|
+
export FIGMA_API_KEY="$FIGMA_ACCESS_TOKEN"
|
|
32
|
+
exec npx figma-developer-mcp --port 3333
|