create-next-imagicma 0.0.5 → 0.0.7

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.
Files changed (104) hide show
  1. package/README.md +30 -14
  2. package/bin/create-next-imagicma.mjs +121 -23
  3. package/package.json +2 -1
  4. package/template/app/globals.css +331 -0
  5. package/template/app/layout.tsx +4 -6
  6. package/template/app/page.tsx +18 -40
  7. package/template/package.json +1 -1
  8. package/template/public/imagicma-picker-bridge.js +374 -0
  9. package/template-hono/.env.example +8 -0
  10. package/template-hono/AGENTS.md +39 -0
  11. package/template-hono/README.md +58 -0
  12. package/template-hono/client/index.html +13 -0
  13. package/template-hono/client/public/favicon.ico +0 -0
  14. package/template-hono/client/public/file.svg +1 -0
  15. package/template-hono/client/public/globe.svg +1 -0
  16. package/template-hono/client/public/imagicma-picker-bridge.js +374 -0
  17. package/template-hono/client/public/next.svg +1 -0
  18. package/template-hono/client/public/vercel.svg +1 -0
  19. package/template-hono/client/public/window.svg +1 -0
  20. package/template-hono/client/src/App.tsx +13 -0
  21. package/template-hono/client/src/components/ErrorBoundary.tsx +74 -0
  22. package/template-hono/client/src/components/HelloClient.tsx +69 -0
  23. package/template-hono/client/src/components/ui/accordion.tsx +58 -0
  24. package/template-hono/client/src/components/ui/alert-dialog.tsx +141 -0
  25. package/template-hono/client/src/components/ui/alert.tsx +61 -0
  26. package/template-hono/client/src/components/ui/aspect-ratio.tsx +7 -0
  27. package/template-hono/client/src/components/ui/avatar.tsx +51 -0
  28. package/template-hono/client/src/components/ui/badge.tsx +40 -0
  29. package/template-hono/client/src/components/ui/breadcrumb.tsx +117 -0
  30. package/template-hono/client/src/components/ui/button.tsx +64 -0
  31. package/template-hono/client/src/components/ui/calendar.tsx +72 -0
  32. package/template-hono/client/src/components/ui/card.tsx +87 -0
  33. package/template-hono/client/src/components/ui/carousel.tsx +262 -0
  34. package/template-hono/client/src/components/ui/chart.tsx +365 -0
  35. package/template-hono/client/src/components/ui/checkbox.tsx +30 -0
  36. package/template-hono/client/src/components/ui/collapsible.tsx +11 -0
  37. package/template-hono/client/src/components/ui/command.tsx +153 -0
  38. package/template-hono/client/src/components/ui/context-menu.tsx +200 -0
  39. package/template-hono/client/src/components/ui/dialog.tsx +122 -0
  40. package/template-hono/client/src/components/ui/drawer.tsx +118 -0
  41. package/template-hono/client/src/components/ui/dropdown-menu.tsx +200 -0
  42. package/template-hono/client/src/components/ui/form.tsx +178 -0
  43. package/template-hono/client/src/components/ui/hover-card.tsx +29 -0
  44. package/template-hono/client/src/components/ui/input-otp.tsx +71 -0
  45. package/template-hono/client/src/components/ui/input.tsx +25 -0
  46. package/template-hono/client/src/components/ui/label.tsx +26 -0
  47. package/template-hono/client/src/components/ui/menubar.tsx +256 -0
  48. package/template-hono/client/src/components/ui/navigation-menu.tsx +130 -0
  49. package/template-hono/client/src/components/ui/pagination.tsx +119 -0
  50. package/template-hono/client/src/components/ui/popover.tsx +31 -0
  51. package/template-hono/client/src/components/ui/progress.tsx +28 -0
  52. package/template-hono/client/src/components/ui/radio-group.tsx +44 -0
  53. package/template-hono/client/src/components/ui/resizable.tsx +45 -0
  54. package/template-hono/client/src/components/ui/scroll-area.tsx +48 -0
  55. package/template-hono/client/src/components/ui/select.tsx +160 -0
  56. package/template-hono/client/src/components/ui/separator.tsx +31 -0
  57. package/template-hono/client/src/components/ui/sheet.tsx +140 -0
  58. package/template-hono/client/src/components/ui/sidebar.tsx +732 -0
  59. package/template-hono/client/src/components/ui/skeleton.tsx +17 -0
  60. package/template-hono/client/src/components/ui/slider.tsx +28 -0
  61. package/template-hono/client/src/components/ui/switch.tsx +29 -0
  62. package/template-hono/client/src/components/ui/table.tsx +119 -0
  63. package/template-hono/client/src/components/ui/tabs.tsx +55 -0
  64. package/template-hono/client/src/components/ui/textarea.tsx +24 -0
  65. package/template-hono/client/src/components/ui/toast.tsx +129 -0
  66. package/template-hono/client/src/components/ui/toaster.tsx +35 -0
  67. package/template-hono/client/src/components/ui/toggle-group.tsx +61 -0
  68. package/template-hono/client/src/components/ui/toggle.tsx +45 -0
  69. package/template-hono/client/src/components/ui/tooltip.tsx +30 -0
  70. package/template-hono/client/src/globals.css +767 -0
  71. package/template-hono/client/src/hooks/use-greeting.ts +15 -0
  72. package/template-hono/client/src/hooks/use-mobile.ts +21 -0
  73. package/template-hono/client/src/hooks/use-toast.ts +194 -0
  74. package/template-hono/client/src/lib/queryClient.ts +59 -0
  75. package/template-hono/client/src/lib/theme/default-theme.ts +11 -0
  76. package/template-hono/client/src/lib/utils.ts +6 -0
  77. package/template-hono/client/src/main.tsx +24 -0
  78. package/template-hono/client/src/pages/HelloPage.tsx +22 -0
  79. package/template-hono/client/src/pages/HomePage.tsx +30 -0
  80. package/template-hono/client/src/providers.tsx +21 -0
  81. package/template-hono/drizzle.config.ts +50 -0
  82. package/template-hono/eslint.config.mjs +13 -0
  83. package/template-hono/gitignore +40 -0
  84. package/template-hono/package.json +83 -0
  85. package/template-hono/pnpm-lock.yaml +5176 -0
  86. package/template-hono/postcss.config.mjs +7 -0
  87. package/template-hono/process-compose.yaml +13 -0
  88. package/template-hono/scripts/imagicma-common.mjs +118 -0
  89. package/template-hono/scripts/imagicma-dev.mjs +29 -0
  90. package/template-hono/scripts/imagicma-guard.mjs +17 -0
  91. package/template-hono/scripts/imagicma-start.mjs +24 -0
  92. package/template-hono/server/app.ts +40 -0
  93. package/template-hono/server/db.ts +22 -0
  94. package/template-hono/server/dev-app.ts +5 -0
  95. package/template-hono/server/index.ts +94 -0
  96. package/template-hono/server/routes/greeting.ts +25 -0
  97. package/template-hono/server/storage.ts +39 -0
  98. package/template-hono/shared/routes.ts +13 -0
  99. package/template-hono/shared/schema.ts +17 -0
  100. package/template-hono/tailwind.config.mjs +94 -0
  101. package/template-hono/tsconfig.json +33 -0
  102. package/template-hono/tsconfig.server.json +15 -0
  103. package/template-hono/types/pg.d.ts +19 -0
  104. package/template-hono/vite.config.ts +126 -0
@@ -0,0 +1,374 @@
1
+ (function () {
2
+ var CHANNEL = 'imagicma.preview-picker';
3
+ var VERSION = 1;
4
+ var MAX_TEXT_LENGTH = 240;
5
+ var MAX_SELECTOR_LENGTH = 512;
6
+ var PROD_PARENT_ORIGIN = 'https://www.imagicma.cn';
7
+ var LOCAL_PARENT_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i;
8
+ var LOCAL_IMAGICMA_PARENT_RE = /^https?:\/\/([a-z0-9-]+\.)?local\.imagicma\.cn(:\d+)?$/i;
9
+
10
+ var state = {
11
+ active: false,
12
+ sessionId: '',
13
+ nonce: '',
14
+ parentOrigin: '',
15
+ hoverEl: null,
16
+ overlayEl: null,
17
+ rafId: 0,
18
+ };
19
+
20
+ var listenersBound = false;
21
+
22
+ function isAllowedParentOrigin(origin) {
23
+ if (!origin) return false;
24
+ return origin === PROD_PARENT_ORIGIN || LOCAL_PARENT_RE.test(origin) || LOCAL_IMAGICMA_PARENT_RE.test(origin);
25
+ }
26
+
27
+ function isRecord(value) {
28
+ return typeof value === 'object' && value !== null;
29
+ }
30
+
31
+ function trimText(value) {
32
+ return typeof value === 'string' ? value.trim() : '';
33
+ }
34
+
35
+ function truncateText(value, max) {
36
+ if (!value) return '';
37
+ return value.length > max ? value.slice(0, max) : value;
38
+ }
39
+
40
+ function safePostMessage(type, payload) {
41
+ if (!state.parentOrigin) return;
42
+ if (window.parent === window) return;
43
+
44
+ try {
45
+ window.parent.postMessage(
46
+ {
47
+ channel: CHANNEL,
48
+ version: VERSION,
49
+ type: type,
50
+ sessionId: state.sessionId,
51
+ nonce: state.nonce,
52
+ payload: payload,
53
+ },
54
+ state.parentOrigin,
55
+ );
56
+ } catch (_error) {
57
+ // Ignore postMessage failures.
58
+ }
59
+ }
60
+
61
+ function ensureOverlay() {
62
+ if (state.overlayEl && document.body.contains(state.overlayEl)) return state.overlayEl;
63
+ var overlay = document.createElement('div');
64
+ overlay.setAttribute('data-imagicma-picker-overlay', 'true');
65
+ overlay.style.position = 'fixed';
66
+ overlay.style.left = '0';
67
+ overlay.style.top = '0';
68
+ overlay.style.width = '0';
69
+ overlay.style.height = '0';
70
+ overlay.style.zIndex = '2147483647';
71
+ overlay.style.pointerEvents = 'none';
72
+ overlay.style.border = '2px solid #1d4ed8';
73
+ overlay.style.background = 'rgba(29, 78, 216, 0.10)';
74
+ overlay.style.boxSizing = 'border-box';
75
+ overlay.style.display = 'none';
76
+ document.body.appendChild(overlay);
77
+ state.overlayEl = overlay;
78
+ return overlay;
79
+ }
80
+
81
+ function hideOverlay() {
82
+ if (!state.overlayEl) return;
83
+ state.overlayEl.style.display = 'none';
84
+ }
85
+
86
+ function updateOverlay() {
87
+ if (!state.active || !state.hoverEl) {
88
+ hideOverlay();
89
+ return;
90
+ }
91
+
92
+ var rect = state.hoverEl.getBoundingClientRect();
93
+ var overlay = ensureOverlay();
94
+ if (rect.width <= 0 || rect.height <= 0) {
95
+ overlay.style.display = 'none';
96
+ return;
97
+ }
98
+
99
+ overlay.style.display = 'block';
100
+ overlay.style.left = rect.left + 'px';
101
+ overlay.style.top = rect.top + 'px';
102
+ overlay.style.width = rect.width + 'px';
103
+ overlay.style.height = rect.height + 'px';
104
+ }
105
+
106
+ function queueOverlayUpdate() {
107
+ if (state.rafId) return;
108
+ state.rafId = window.requestAnimationFrame(function () {
109
+ state.rafId = 0;
110
+ updateOverlay();
111
+ });
112
+ }
113
+
114
+ function isValidElementTarget(target) {
115
+ if (!(target instanceof Element)) return false;
116
+ if (state.overlayEl && state.overlayEl.contains(target)) return false;
117
+ return true;
118
+ }
119
+
120
+ function cssEscapeIdent(value) {
121
+ if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape === 'function') {
122
+ return window.CSS.escape(value);
123
+ }
124
+ return value.replace(/[^a-zA-Z0-9_-]/g, '\\$&');
125
+ }
126
+
127
+ function selectorByClassChain(element) {
128
+ var tokens = [];
129
+ if (element.classList && element.classList.length > 0) {
130
+ for (var i = 0; i < element.classList.length && i < 3; i += 1) {
131
+ var cls = trimText(element.classList[i]);
132
+ if (cls) tokens.push('.' + cssEscapeIdent(cls));
133
+ }
134
+ }
135
+ return tokens.length > 0 ? element.tagName.toLowerCase() + tokens.join('') : '';
136
+ }
137
+
138
+ function selectorByPath(element) {
139
+ var parts = [];
140
+ var current = element;
141
+ var depth = 0;
142
+ while (current && current.nodeType === 1 && depth < 6) {
143
+ var part = current.tagName.toLowerCase();
144
+ if (current.id) {
145
+ part += '#' + cssEscapeIdent(current.id);
146
+ parts.unshift(part);
147
+ break;
148
+ }
149
+
150
+ var classSelector = selectorByClassChain(current);
151
+ if (classSelector) {
152
+ part = classSelector;
153
+ } else if (current.parentElement) {
154
+ var siblings = current.parentElement.children;
155
+ var sameTagIndex = 0;
156
+ var sameTagCount = 0;
157
+ for (var i = 0; i < siblings.length; i += 1) {
158
+ var sibling = siblings[i];
159
+ if (sibling.tagName === current.tagName) {
160
+ sameTagCount += 1;
161
+ if (sibling === current) {
162
+ sameTagIndex = sameTagCount;
163
+ }
164
+ }
165
+ }
166
+ if (sameTagCount > 1 && sameTagIndex > 0) {
167
+ part += ':nth-of-type(' + sameTagIndex + ')';
168
+ }
169
+ }
170
+
171
+ parts.unshift(part);
172
+ current = current.parentElement;
173
+ depth += 1;
174
+ }
175
+
176
+ return parts.join(' > ');
177
+ }
178
+
179
+ function getElementSelector(element) {
180
+ if (element.id) {
181
+ return '#' + cssEscapeIdent(element.id);
182
+ }
183
+
184
+ var testId = element.getAttribute('data-testid');
185
+ if (testId) {
186
+ return '[data-testid="' + testId.replace(/"/g, '\\"') + '"]';
187
+ }
188
+
189
+ var ariaLabel = element.getAttribute('aria-label');
190
+ if (ariaLabel) {
191
+ return element.tagName.toLowerCase() + '[aria-label="' + ariaLabel.replace(/"/g, '\\"') + '"]';
192
+ }
193
+
194
+ var role = element.getAttribute('role');
195
+ if (role) {
196
+ return element.tagName.toLowerCase() + '[role="' + role.replace(/"/g, '\\"') + '"]';
197
+ }
198
+
199
+ var byClass = selectorByClassChain(element);
200
+ if (byClass) return byClass;
201
+
202
+ return selectorByPath(element) || element.tagName.toLowerCase();
203
+ }
204
+
205
+ function buildSelectionPayload(element) {
206
+ var rect = element.getBoundingClientRect();
207
+ var selector = truncateText(getElementSelector(element), MAX_SELECTOR_LENGTH);
208
+ var textSource = trimText(element.innerText || element.textContent || '');
209
+ var attributes = {};
210
+
211
+ function putAttr(name, value) {
212
+ if (!name || typeof value !== 'string') return;
213
+ var key = trimText(name).toLowerCase();
214
+ var val = trimText(value);
215
+ if (!key || !val) return;
216
+ attributes[key] = truncateText(val, 240);
217
+ }
218
+
219
+ putAttr('id', element.id || '');
220
+ putAttr('class', element.className || '');
221
+ putAttr('role', element.getAttribute('role') || '');
222
+ putAttr('name', element.getAttribute('name') || '');
223
+ putAttr('type', element.getAttribute('type') || '');
224
+ putAttr('title', element.getAttribute('title') || '');
225
+ putAttr('aria-label', element.getAttribute('aria-label') || '');
226
+ putAttr('aria-labelledby', element.getAttribute('aria-labelledby') || '');
227
+ putAttr('aria-describedby', element.getAttribute('aria-describedby') || '');
228
+ putAttr('placeholder', element.getAttribute('placeholder') || '');
229
+ putAttr('href', element.getAttribute('href') || '');
230
+ putAttr('src', element.getAttribute('src') || '');
231
+ putAttr('alt', element.getAttribute('alt') || '');
232
+
233
+ if (element.attributes && element.attributes.length > 0) {
234
+ for (var i = 0; i < element.attributes.length; i += 1) {
235
+ var attr = element.attributes[i];
236
+ if (!attr) continue;
237
+ var attrName = trimText(attr.name || '').toLowerCase();
238
+ if (!attrName) continue;
239
+ if (attrName.indexOf('data-') === 0 || attrName.indexOf('aria-') === 0) {
240
+ putAttr(attrName, attr.value || '');
241
+ }
242
+ }
243
+ }
244
+
245
+ return {
246
+ pageUrl: window.location.href,
247
+ selector: selector,
248
+ tagName: element.tagName.toLowerCase(),
249
+ textSnippet: truncateText(textSource, MAX_TEXT_LENGTH),
250
+ attributes: attributes,
251
+ rect: {
252
+ x: Number(rect.left.toFixed(2)),
253
+ y: Number(rect.top.toFixed(2)),
254
+ width: Number(rect.width.toFixed(2)),
255
+ height: Number(rect.height.toFixed(2)),
256
+ },
257
+ };
258
+ }
259
+
260
+ function handleMouseMove(event) {
261
+ if (!state.active) return;
262
+ var target = event.target;
263
+ if (!isValidElementTarget(target)) return;
264
+ state.hoverEl = target;
265
+ queueOverlayUpdate();
266
+ }
267
+
268
+ function handleScrollOrResize() {
269
+ if (!state.active) return;
270
+ queueOverlayUpdate();
271
+ }
272
+
273
+ function handleClickCapture(event) {
274
+ if (!state.active) return;
275
+ var target = event.target;
276
+ if (!isValidElementTarget(target)) return;
277
+
278
+ event.preventDefault();
279
+ event.stopPropagation();
280
+ if (typeof event.stopImmediatePropagation === 'function') {
281
+ event.stopImmediatePropagation();
282
+ }
283
+
284
+ try {
285
+ var payload = buildSelectionPayload(target);
286
+ safePostMessage('IMAGICMA_PICKER_SELECTED', payload);
287
+ } catch (error) {
288
+ safePostMessage('IMAGICMA_PICKER_ERROR', {
289
+ message: error instanceof Error ? error.message : 'Failed to build selected element payload',
290
+ });
291
+ }
292
+ }
293
+
294
+ function bindInteractionListeners() {
295
+ if (listenersBound) return;
296
+ listenersBound = true;
297
+ document.addEventListener('mousemove', handleMouseMove, true);
298
+ document.addEventListener('click', handleClickCapture, true);
299
+ window.addEventListener('scroll', handleScrollOrResize, true);
300
+ window.addEventListener('resize', handleScrollOrResize, true);
301
+ }
302
+
303
+ function unbindInteractionListeners() {
304
+ if (!listenersBound) return;
305
+ listenersBound = false;
306
+ document.removeEventListener('mousemove', handleMouseMove, true);
307
+ document.removeEventListener('click', handleClickCapture, true);
308
+ window.removeEventListener('scroll', handleScrollOrResize, true);
309
+ window.removeEventListener('resize', handleScrollOrResize, true);
310
+ }
311
+
312
+ function activatePicking() {
313
+ state.active = true;
314
+ state.hoverEl = null;
315
+ bindInteractionListeners();
316
+ document.documentElement.style.cursor = 'crosshair';
317
+ document.body.style.cursor = 'crosshair';
318
+ safePostMessage('IMAGICMA_PICKER_READY', {
319
+ pageUrl: window.location.href,
320
+ });
321
+ }
322
+
323
+ function deactivatePicking() {
324
+ state.active = false;
325
+ state.hoverEl = null;
326
+ if (state.rafId) {
327
+ window.cancelAnimationFrame(state.rafId);
328
+ state.rafId = 0;
329
+ }
330
+ hideOverlay();
331
+ unbindInteractionListeners();
332
+ document.documentElement.style.cursor = '';
333
+ document.body.style.cursor = '';
334
+ }
335
+
336
+ function isValidMessage(data) {
337
+ if (!isRecord(data)) return false;
338
+ if (data.channel !== CHANNEL) return false;
339
+ if (data.version !== VERSION) return false;
340
+ if (typeof data.type !== 'string') return false;
341
+ if (typeof data.sessionId !== 'string' || data.sessionId.length === 0) return false;
342
+ if (typeof data.nonce !== 'string' || data.nonce.length === 0) return false;
343
+ return true;
344
+ }
345
+
346
+ function handleParentMessage(event) {
347
+ if (!isAllowedParentOrigin(event.origin)) return;
348
+ if (!isValidMessage(event.data)) return;
349
+
350
+ var data = event.data;
351
+ state.parentOrigin = event.origin;
352
+ state.sessionId = data.sessionId;
353
+ state.nonce = data.nonce;
354
+
355
+ if (data.type === 'IMAGICMA_PICKER_PING') {
356
+ safePostMessage('IMAGICMA_PICKER_READY', {
357
+ pageUrl: window.location.href,
358
+ });
359
+ return;
360
+ }
361
+
362
+ if (data.type === 'IMAGICMA_PICKER_START') {
363
+ activatePicking();
364
+ return;
365
+ }
366
+
367
+ if (data.type === 'IMAGICMA_PICKER_STOP') {
368
+ deactivatePicking();
369
+ }
370
+ }
371
+
372
+ window.addEventListener('message', handleParentMessage);
373
+ window.addEventListener('beforeunload', deactivatePicking);
374
+ })();
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
@@ -0,0 +1,13 @@
1
+ import { Navigate, Route, Routes } from "react-router-dom";
2
+ import { HelloPage } from "./pages/HelloPage";
3
+ import { HomePage } from "./pages/HomePage";
4
+
5
+ export function App() {
6
+ return (
7
+ <Routes>
8
+ <Route path="/" element={<HomePage />} />
9
+ <Route path="/hello" element={<HelloPage />} />
10
+ <Route path="*" element={<Navigate to="/" replace />} />
11
+ </Routes>
12
+ );
13
+ }
@@ -0,0 +1,74 @@
1
+ import React from "react";
2
+
3
+ type AppErrorBoundaryState = {
4
+ error: Error | null;
5
+ };
6
+
7
+ export class AppErrorBoundary extends React.Component<
8
+ { children: React.ReactNode },
9
+ AppErrorBoundaryState
10
+ > {
11
+ state: AppErrorBoundaryState = { error: null };
12
+
13
+ static getDerivedStateFromError(error: Error): AppErrorBoundaryState {
14
+ return { error };
15
+ }
16
+
17
+ componentDidCatch(error: Error) {
18
+ console.error(error);
19
+ }
20
+
21
+ private reset = () => {
22
+ this.setState({ error: null });
23
+ };
24
+
25
+ render() {
26
+ const { error } = this.state;
27
+ if (!error) {
28
+ return this.props.children;
29
+ }
30
+
31
+ const isDev = import.meta.env.DEV;
32
+ const message = isDev ? (error.message || "发生未知错误").trim() : "";
33
+
34
+ return (
35
+ <div
36
+ className="relative flex min-h-screen items-center justify-center overflow-hidden bg-gradient-to-br from-indigo-600 via-purple-600 to-sky-600 px-6 py-16 text-white"
37
+ role="alert"
38
+ >
39
+ <div className="absolute inset-0 bg-[radial-gradient(60%_60%_at_50%_40%,rgba(255,255,255,0.20),transparent_60%)]" />
40
+ <main className="relative w-full max-w-xl rounded-3xl border border-white/15 bg-white/10 p-10 shadow-2xl backdrop-blur-xl">
41
+ <div className="flex flex-col items-center text-center">
42
+ <h1 className="text-3xl font-semibold tracking-tight">预览暂时不可用</h1>
43
+ <p className="mt-4 max-w-md text-base leading-7 text-white/80">
44
+ 检测到错误。修复后可点击“重试”恢复,或直接刷新页面。
45
+ </p>
46
+
47
+ {isDev && message ? (
48
+ <pre className="mt-6 w-full max-h-40 overflow-auto rounded-2xl border border-white/10 bg-black/25 p-4 text-left text-sm leading-6 text-white/85 shadow-inner">
49
+ <code>{message}</code>
50
+ </pre>
51
+ ) : null}
52
+
53
+ <div className="mt-8 flex w-full flex-col gap-3 sm:flex-row sm:justify-center">
54
+ <button
55
+ type="button"
56
+ className="inline-flex h-11 w-full items-center justify-center rounded-full bg-white px-5 text-sm font-medium text-black transition-colors hover:bg-white/90 sm:w-auto"
57
+ onClick={this.reset}
58
+ >
59
+ 重试
60
+ </button>
61
+ <button
62
+ type="button"
63
+ className="inline-flex h-11 w-full items-center justify-center rounded-full border border-white/20 bg-white/10 px-5 text-sm font-medium text-white transition-colors hover:bg-white/15 sm:w-auto"
64
+ onClick={() => window.location.reload()}
65
+ >
66
+ 刷新页面
67
+ </button>
68
+ </div>
69
+ </div>
70
+ </main>
71
+ </div>
72
+ );
73
+ }
74
+ }
@@ -0,0 +1,69 @@
1
+ import * as React from "react";
2
+ import { Badge } from "@/components/ui/badge";
3
+ import { Button } from "@/components/ui/button";
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
5
+ import { useGreeting } from "@/hooks/use-greeting";
6
+ import { toast } from "@/hooks/use-toast";
7
+
8
+ export function HelloClient() {
9
+ const { data, isLoading, error, refetch, isFetching } = useGreeting();
10
+
11
+ const status = isLoading ? "loading" : error ? "error" : "ready";
12
+
13
+ return (
14
+ <Card>
15
+ <CardHeader>
16
+ <div className="flex items-center justify-between gap-4">
17
+ <div className="space-y-1">
18
+ <CardTitle>GET /api/greeting</CardTitle>
19
+ <CardDescription>响应会被 Zod 校验,并展示到 UI 中。</CardDescription>
20
+ </div>
21
+ <Badge variant={status === "error" ? "destructive" : "secondary"}>{status}</Badge>
22
+ </div>
23
+ </CardHeader>
24
+ <CardContent className="space-y-4">
25
+ <div className="rounded-lg border bg-card p-4">
26
+ {isLoading ? (
27
+ <p className="text-sm text-muted-foreground">加载中…</p>
28
+ ) : error ? (
29
+ <p className="text-sm text-destructive">
30
+ {error instanceof Error ? error.message : "请求失败"}
31
+ </p>
32
+ ) : (
33
+ <p className="text-lg leading-7">“{data?.message}”</p>
34
+ )}
35
+ </div>
36
+
37
+ <div className="flex flex-wrap gap-2">
38
+ <Button type="button" onClick={() => refetch()} disabled={isFetching}>
39
+ {isFetching ? "刷新中…" : "刷新"}
40
+ </Button>
41
+ <Button
42
+ type="button"
43
+ variant="outline"
44
+ onClick={() =>
45
+ toast({
46
+ title: "Toast 正常工作",
47
+ description: "如果你能看到我,说明 shadcn/toast 已接入。",
48
+ })
49
+ }
50
+ >
51
+ 触发 Toast
52
+ </Button>
53
+ </div>
54
+
55
+ <EnvHint />
56
+ </CardContent>
57
+ </Card>
58
+ );
59
+ }
60
+
61
+ function EnvHint() {
62
+ return (
63
+ <div className="text-sm text-muted-foreground">
64
+ <p>
65
+ 若 API 报错请检查:是否已在 <code>.env.local</code> 设置 <code>DATABASE_URL</code>,并执行 <code>npm run db:push</code>。
66
+ </p>
67
+ </div>
68
+ );
69
+ }
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ import * as React from "react"
4
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
5
+ import { ChevronDown } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Accordion = AccordionPrimitive.Root
10
+
11
+ const AccordionItem = React.forwardRef<
12
+ React.ElementRef<typeof AccordionPrimitive.Item>,
13
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
14
+ >(({ className, ...props }, ref) => (
15
+ <AccordionPrimitive.Item
16
+ ref={ref}
17
+ className={cn("border-b", className)}
18
+ {...props}
19
+ />
20
+ ))
21
+ AccordionItem.displayName = "AccordionItem"
22
+
23
+ const AccordionTrigger = React.forwardRef<
24
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
25
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
26
+ >(({ className, children, ...props }, ref) => (
27
+ <AccordionPrimitive.Header className="flex">
28
+ <AccordionPrimitive.Trigger
29
+ ref={ref}
30
+ className={cn(
31
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
38
+ </AccordionPrimitive.Trigger>
39
+ </AccordionPrimitive.Header>
40
+ ))
41
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42
+
43
+ const AccordionContent = React.forwardRef<
44
+ React.ElementRef<typeof AccordionPrimitive.Content>,
45
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
46
+ >(({ className, children, ...props }, ref) => (
47
+ <AccordionPrimitive.Content
48
+ ref={ref}
49
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
50
+ {...props}
51
+ >
52
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
53
+ </AccordionPrimitive.Content>
54
+ ))
55
+
56
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
57
+
58
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }