@linktr.ee/linkapp 0.0.21 → 0.0.23

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 (115) hide show
  1. package/dev-server/README.md +5 -5
  2. package/dev-server/components/SettingsPreview.tsx +330 -0
  3. package/dev-server/components/ui/dialog.tsx +6 -3
  4. package/dev-server/env.d.ts +8 -0
  5. package/dev-server/featured/main.tsx +48 -43
  6. package/dev-server/featured.html +21 -35
  7. package/dev-server/index.html +8 -1
  8. package/dev-server/lib/utils.ts +6 -0
  9. package/dev-server/package-lock.json +245 -287
  10. package/dev-server/package.json +7 -4
  11. package/dev-server/postcss/tailwind-source-fallback.js +94 -0
  12. package/dev-server/postcss.config.mjs +30 -0
  13. package/dev-server/preview/Preview.tsx +182 -381
  14. package/dev-server/preview/preview.css +22 -34
  15. package/dev-server/public/apple-touch-icon.png +0 -0
  16. package/dev-server/public/favicon-96x96.png +0 -0
  17. package/dev-server/public/favicon.ico +0 -0
  18. package/dev-server/public/favicon.svg +6 -0
  19. package/dev-server/public/site.webmanifest +21 -0
  20. package/dev-server/public/web-app-manifest-192x192.png +0 -0
  21. package/dev-server/public/web-app-manifest-512x512.png +0 -0
  22. package/dev-server/rsbuild.config.ts +45 -0
  23. package/dev-server/shared/theme-presets.ts +315 -0
  24. package/dev-server/shared/theme-utils.ts +38 -0
  25. package/dev-server/sheet/main.tsx +44 -0
  26. package/dev-server/sheet.html +56 -0
  27. package/dist/commands/add.d.ts.map +1 -1
  28. package/dist/commands/add.js +20 -12
  29. package/dist/commands/add.js.map +1 -1
  30. package/dist/commands/build.d.ts.map +1 -1
  31. package/dist/commands/build.js +55 -36
  32. package/dist/commands/build.js.map +1 -1
  33. package/dist/commands/deploy.d.ts.map +1 -1
  34. package/dist/commands/deploy.js +74 -59
  35. package/dist/commands/deploy.js.map +1 -1
  36. package/dist/commands/dev.d.ts.map +1 -1
  37. package/dist/commands/dev.js +564 -43
  38. package/dist/commands/dev.js.map +1 -1
  39. package/dist/commands/login.d.ts.map +1 -1
  40. package/dist/commands/login.js +19 -9
  41. package/dist/commands/login.js.map +1 -1
  42. package/dist/commands/logout.d.ts.map +1 -1
  43. package/dist/commands/logout.js +9 -4
  44. package/dist/commands/logout.js.map +1 -1
  45. package/dist/commands/test-url-match-rules.d.ts.map +1 -1
  46. package/dist/commands/test-url-match-rules.js +24 -13
  47. package/dist/commands/test-url-match-rules.js.map +1 -1
  48. package/dist/components/index.d.ts +0 -1
  49. package/dist/components/index.d.ts.map +1 -1
  50. package/dist/components/index.js +2 -1
  51. package/dist/components/index.js.map +1 -1
  52. package/dist/lib/auth/device-flow.d.ts +1 -6
  53. package/dist/lib/auth/device-flow.d.ts.map +1 -1
  54. package/dist/lib/auth/device-flow.js +3 -9
  55. package/dist/lib/auth/device-flow.js.map +1 -1
  56. package/dist/lib/auth/token-storage.d.ts +0 -5
  57. package/dist/lib/auth/token-storage.d.ts.map +1 -1
  58. package/dist/lib/auth/token-storage.js +16 -14
  59. package/dist/lib/auth/token-storage.js.map +1 -1
  60. package/dist/lib/build/detect-layouts.d.ts +1 -1
  61. package/dist/lib/build/detect-layouts.d.ts.map +1 -1
  62. package/dist/lib/build/detect-layouts.js +8 -7
  63. package/dist/lib/build/detect-layouts.js.map +1 -1
  64. package/dist/lib/deploy/generate-manifest-files.js +1 -1
  65. package/dist/lib/deploy/generate-manifest-files.js.map +1 -1
  66. package/dist/lib/deploy/pack-project.js +2 -2
  67. package/dist/lib/deploy/pack-project.js.map +1 -1
  68. package/dist/lib/deploy/test-url-match-rules.d.ts +2 -1
  69. package/dist/lib/deploy/test-url-match-rules.d.ts.map +1 -1
  70. package/dist/lib/deploy/test-url-match-rules.js +1 -1
  71. package/dist/lib/deploy/test-url-match-rules.js.map +1 -1
  72. package/dist/lib/deploy/upload.d.ts.map +1 -1
  73. package/dist/lib/deploy/upload.js +7 -3
  74. package/dist/lib/deploy/upload.js.map +1 -1
  75. package/dist/lib/deploy/validation.d.ts.map +1 -1
  76. package/dist/lib/deploy/validation.js +8 -5
  77. package/dist/lib/deploy/validation.js.map +1 -1
  78. package/dist/lib/rsbuild/config-factory.d.ts +24 -0
  79. package/dist/lib/rsbuild/config-factory.d.ts.map +1 -0
  80. package/dist/lib/rsbuild/config-factory.js +135 -0
  81. package/dist/lib/rsbuild/config-factory.js.map +1 -0
  82. package/dist/lib/rsbuild/plugins/asset-versioning.d.ts +11 -0
  83. package/dist/lib/rsbuild/plugins/asset-versioning.d.ts.map +1 -0
  84. package/dist/lib/rsbuild/plugins/asset-versioning.js +62 -0
  85. package/dist/lib/rsbuild/plugins/asset-versioning.js.map +1 -0
  86. package/dist/lib/rsbuild/plugins/copy-public.d.ts +11 -0
  87. package/dist/lib/rsbuild/plugins/copy-public.d.ts.map +1 -0
  88. package/dist/lib/rsbuild/plugins/copy-public.js +32 -0
  89. package/dist/lib/rsbuild/plugins/copy-public.js.map +1 -0
  90. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.d.ts +12 -0
  91. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.d.ts.map +1 -0
  92. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.js +60 -0
  93. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.js.map +1 -0
  94. package/dist/lib/utils/setup-runtime.d.ts.map +1 -1
  95. package/dist/lib/utils/setup-runtime.js +78 -17
  96. package/dist/lib/utils/setup-runtime.js.map +1 -1
  97. package/dist/lib/vite/config-factory.d.ts.map +1 -1
  98. package/dist/lib/vite/config-factory.js +8 -3
  99. package/dist/lib/vite/config-factory.js.map +1 -1
  100. package/dist/lib/vite/plugins/copy-public.d.ts +12 -0
  101. package/dist/lib/vite/plugins/copy-public.d.ts.map +1 -0
  102. package/dist/lib/vite/plugins/copy-public.js +31 -0
  103. package/dist/lib/vite/plugins/copy-public.js.map +1 -0
  104. package/dist/schema/config.schema.d.ts +121 -32
  105. package/dist/schema/config.schema.d.ts.map +1 -1
  106. package/dist/schema/config.schema.js +28 -17
  107. package/dist/schema/config.schema.js.map +1 -1
  108. package/dist/types.d.ts +6 -57
  109. package/dist/types.d.ts.map +1 -1
  110. package/dist/types.js.map +1 -1
  111. package/package.json +7 -4
  112. package/runtime/index.html +19 -36
  113. package/dev-server/classic/main.tsx +0 -62
  114. package/dev-server/classic.html +0 -70
  115. package/dev-server/vite.config.ts +0 -29
@@ -1,13 +1,13 @@
1
1
  # Development Server Setup
2
2
 
3
- This directory contains a custom Vite-based development server for previewing LinkApp components with multiple routes and iframe embeds.
3
+ This directory contains a custom Rsbuild-based development server for previewing LinkApp components with multiple routes and iframe embeds.
4
4
 
5
5
  ## Structure
6
6
 
7
7
  ```
8
8
  dev-server/
9
9
  ├── index.html # Root preview page entry
10
- ├── vite.config.ts # Vite configuration (MPA setup)
10
+ ├── rsbuild.config.ts # Rsbuild configuration
11
11
  ├── server.ts # TypeScript server launcher
12
12
  ├── preview/
13
13
  │ ├── main.tsx # Preview page React entry
@@ -44,12 +44,12 @@ dev-server/
44
44
  npm run dev:preview
45
45
  ```
46
46
 
47
- This command uses `tsx` to run the TypeScript server script, which programmatically starts Vite with the custom configuration.
47
+ This command uses `tsx` to run the TypeScript server script, which programmatically starts Rsbuild with the custom configuration.
48
48
 
49
49
  ## Technical Details
50
50
 
51
51
  ### Multi-Page Application (MPA)
52
- The setup uses Vite's MPA capability with multiple HTML entry points:
52
+ The setup uses Rsbuild's MPA capability with multiple HTML entry points:
53
53
  - Root: `index.html` → Preview page
54
54
  - `/classic/`: `classic/index.html` → Classic layout
55
55
  - `/featured/`: `featured/index.html` → Featured layout
@@ -77,6 +77,6 @@ Server runs on port 3000 with `strictPort: true` to match requirements.
77
77
  This implementation follows a clean separation of concerns:
78
78
  - Original app files (`1-demo/app/*`) remain untouched
79
79
  - New dev-server directory contains all preview infrastructure
80
- - Uses Vite's native MPA support for clean routing (no client-side router)
80
+ - Uses Rsbuild's native MPA support for clean routing (no client-side router)
81
81
  - TypeScript throughout for type safety
82
82
  - Programmatic server creation for flexibility
@@ -0,0 +1,330 @@
1
+ import { LinkAppSettings, SettingsElement } from "../../src/types";
2
+
3
+ interface SettingsPreviewProps {
4
+ settings: LinkAppSettings;
5
+ }
6
+
7
+ export function SettingsPreview({ settings }: SettingsPreviewProps) {
8
+ return (
9
+ <div className="max-w-4xl mx-auto p-6 space-y-6">
10
+ {/* Header */}
11
+ <div className="space-y-2">
12
+ <h2 className="text-2xl font-bold">{settings.title}</h2>
13
+ {settings.overview && (
14
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
15
+ {settings.overview.title && (
16
+ <h3 className="font-semibold text-blue-900 mb-1">
17
+ {settings.overview.title}
18
+ </h3>
19
+ )}
20
+ <p className="text-blue-800 text-sm">
21
+ {settings.overview.description}
22
+ </p>
23
+ </div>
24
+ )}
25
+ </div>
26
+
27
+ {/* Metadata */}
28
+ <div className="grid grid-cols-2 gap-4 bg-gray-50 border border-gray-200 rounded-lg p-4">
29
+ <div>
30
+ <span className="text-sm font-medium text-gray-600">Uses URL:</span>
31
+ <span className="ml-2 text-sm">
32
+ {settings.uses_url || settings.has_url ? "Yes" : "No"}
33
+ </span>
34
+ </div>
35
+ <div>
36
+ <span className="text-sm font-medium text-gray-600">
37
+ Supports Featured Layout:
38
+ </span>
39
+ <span className="ml-2 text-sm">
40
+ {settings.supports_featured_layout ? "Yes" : "No"}
41
+ </span>
42
+ </div>
43
+ {settings.icon && (
44
+ <div>
45
+ <span className="text-sm font-medium text-gray-600">Icon:</span>
46
+ <span className="ml-2 text-sm">{settings.icon}</span>
47
+ </div>
48
+ )}
49
+ {settings.settings_tab_title && (
50
+ <div>
51
+ <span className="text-sm font-medium text-gray-600">
52
+ Settings Tab Title:
53
+ </span>
54
+ <span className="ml-2 text-sm">{settings.settings_tab_title}</span>
55
+ </div>
56
+ )}
57
+ </div>
58
+
59
+ {/* Setup Instructions */}
60
+ {settings.setup_instructions && (
61
+ <div className="bg-green-50 border border-green-200 rounded-lg p-4">
62
+ {settings.setup_instructions.title && (
63
+ <h3 className="font-semibold text-green-900 mb-1">
64
+ {settings.setup_instructions.title}
65
+ </h3>
66
+ )}
67
+ <p className="text-green-800 text-sm">
68
+ {settings.setup_instructions.description}
69
+ </p>
70
+ </div>
71
+ )}
72
+
73
+ {/* Settings Elements */}
74
+ <div className="space-y-4">
75
+ <h3 className="text-xl font-semibold">Settings Elements</h3>
76
+ {settings.elements.length === 0 ? (
77
+ <p className="text-gray-500 italic">No settings elements defined</p>
78
+ ) : (
79
+ <div className="space-y-4">
80
+ {settings.elements.map((element, index) => (
81
+ <SettingsElementCard key={element.id} element={element} index={index} />
82
+ ))}
83
+ </div>
84
+ )}
85
+ </div>
86
+ </div>
87
+ );
88
+ }
89
+
90
+ interface SettingsElementCardProps {
91
+ element: SettingsElement;
92
+ index: number;
93
+ }
94
+
95
+ function SettingsElementCard({ element, index }: SettingsElementCardProps) {
96
+ return (
97
+ <div className="border border-gray-300 rounded-lg p-4 bg-white shadow-sm">
98
+ <div className="flex items-start justify-between mb-3">
99
+ <div className="flex-1">
100
+ <div className="flex items-center gap-2">
101
+ <span className="text-xs font-mono bg-gray-100 px-2 py-1 rounded">
102
+ {index + 1}
103
+ </span>
104
+ <h4 className="font-semibold text-lg">{element.title || element.label}</h4>
105
+ </div>
106
+ <p className="text-sm text-gray-600 mt-1">
107
+ <span className="font-mono text-xs bg-blue-100 text-blue-800 px-1.5 py-0.5 rounded">
108
+ {element.id}
109
+ </span>
110
+ </p>
111
+ </div>
112
+ <span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
113
+ {element.inputType}
114
+ </span>
115
+ </div>
116
+
117
+ {element.description && (
118
+ <p className="text-sm text-gray-700 mb-3">{element.description}</p>
119
+ )}
120
+
121
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
122
+ {element.placeholder && (
123
+ <div>
124
+ <span className="font-medium text-gray-600">Placeholder:</span>
125
+ <span className="ml-2 text-gray-800">{element.placeholder}</span>
126
+ </div>
127
+ )}
128
+
129
+ {element.defaultValue !== undefined && (
130
+ <div>
131
+ <span className="font-medium text-gray-600">Default Value:</span>
132
+ <span className="ml-2 text-gray-800 font-mono text-xs">
133
+ {typeof element.defaultValue === "boolean"
134
+ ? element.defaultValue.toString()
135
+ : Array.isArray(element.defaultValue)
136
+ ? `[${element.defaultValue.join(", ")}]`
137
+ : element.defaultValue}
138
+ </span>
139
+ </div>
140
+ )}
141
+
142
+ {element.validation && (
143
+ <div className="col-span-2">
144
+ <span className="font-medium text-gray-600">Validation:</span>
145
+ <div className="ml-2 mt-1 space-y-1">
146
+ {element.validation.required && (
147
+ <div className="text-xs">
148
+ <span className="bg-red-100 text-red-800 px-2 py-0.5 rounded">
149
+ Required
150
+ </span>
151
+ </div>
152
+ )}
153
+ {element.validation.minLength !== undefined && (
154
+ <div className="text-xs text-gray-600">
155
+ Min Length: {element.validation.minLength}
156
+ </div>
157
+ )}
158
+ {element.validation.maxLength !== undefined && (
159
+ <div className="text-xs text-gray-600">
160
+ Max Length: {element.validation.maxLength}
161
+ </div>
162
+ )}
163
+ {element.validation.min !== undefined && (
164
+ <div className="text-xs text-gray-600">
165
+ Min: {element.validation.min}
166
+ </div>
167
+ )}
168
+ {element.validation.max !== undefined && (
169
+ <div className="text-xs text-gray-600">
170
+ Max: {element.validation.max}
171
+ </div>
172
+ )}
173
+ {element.validation.maxSize !== undefined && (
174
+ <div className="text-xs text-gray-600">
175
+ Max Size: {element.validation.maxSize}
176
+ </div>
177
+ )}
178
+ {element.validation.pattern && (
179
+ <div className="text-xs text-gray-600 font-mono">
180
+ Pattern: {element.validation.pattern}
181
+ </div>
182
+ )}
183
+ </div>
184
+ </div>
185
+ )}
186
+
187
+ {element.options && element.options.length > 0 && (
188
+ <div className="col-span-2">
189
+ <span className="font-medium text-gray-600">Options:</span>
190
+ <div className="ml-2 mt-1 space-y-1">
191
+ {element.options.map((option) => (
192
+ <div key={option.value} className="text-xs bg-gray-50 px-2 py-1 rounded">
193
+ <span className="font-mono text-blue-700">{option.value}</span>
194
+ <span className="mx-1 text-gray-400">→</span>
195
+ <span>{option.label}</span>
196
+ </div>
197
+ ))}
198
+ </div>
199
+ </div>
200
+ )}
201
+
202
+ {element.linkBehaviorLabels && (
203
+ <div className="col-span-2">
204
+ <span className="font-medium text-gray-600">Link Behavior Labels:</span>
205
+ <div className="ml-2 mt-1 space-y-1">
206
+ <div className="text-xs">
207
+ <span className="font-medium">Embed:</span> {element.linkBehaviorLabels.embedLabel}
208
+ </div>
209
+ <div className="text-xs">
210
+ <span className="font-medium">Link Off:</span> {element.linkBehaviorLabels.linkOffLabel}
211
+ </div>
212
+ </div>
213
+ </div>
214
+ )}
215
+
216
+ {element.accept && element.accept.length > 0 && (
217
+ <div className="col-span-2">
218
+ <span className="font-medium text-gray-600">Accepted Files:</span>
219
+ <span className="ml-2 text-xs font-mono">
220
+ {element.accept.join(", ")}
221
+ </span>
222
+ </div>
223
+ )}
224
+
225
+ {element.multiple && (
226
+ <div>
227
+ <span className="bg-orange-100 text-orange-800 px-2 py-0.5 rounded text-xs">
228
+ Multiple values allowed
229
+ </span>
230
+ </div>
231
+ )}
232
+
233
+ {element.capability && (
234
+ <div>
235
+ <span className="font-medium text-gray-600">Integration Capability:</span>
236
+ <span className="ml-2 text-xs">{element.capability}</span>
237
+ </div>
238
+ )}
239
+
240
+ {element.vendor && (
241
+ <div>
242
+ <span className="font-medium text-gray-600">Vendor:</span>
243
+ <span className="ml-2 text-xs">{element.vendor}</span>
244
+ </div>
245
+ )}
246
+
247
+ {element.array_options && (
248
+ <div className="col-span-2">
249
+ <span className="font-medium text-gray-600">Array Options:</span>
250
+ <div className="ml-2 mt-1 space-y-1 text-xs">
251
+ {element.array_options.add_item_button_text && (
252
+ <div>Add Button: "{element.array_options.add_item_button_text}"</div>
253
+ )}
254
+ {element.array_options.add_item_title && (
255
+ <div>Add Title: "{element.array_options.add_item_title}"</div>
256
+ )}
257
+ {element.array_options.edit_item_title && (
258
+ <div>Edit Title: "{element.array_options.edit_item_title}"</div>
259
+ )}
260
+ {element.array_options.item_format && (
261
+ <div>Item Format: "{element.array_options.item_format}"</div>
262
+ )}
263
+ {element.array_options.min !== undefined && (
264
+ <div>Min Items: {element.array_options.min}</div>
265
+ )}
266
+ {element.array_options.max !== undefined && (
267
+ <div>Max Items: {element.array_options.max}</div>
268
+ )}
269
+ </div>
270
+ </div>
271
+ )}
272
+
273
+ {element.array_elements && element.array_elements.length > 0 && (
274
+ <div className="col-span-2">
275
+ <span className="font-medium text-gray-600">Array Elements:</span>
276
+ <div className="ml-2 mt-2 space-y-2">
277
+ {element.array_elements.map((arrayElement, arrayIndex) => (
278
+ <div key={arrayElement.id} className="bg-gray-50 border border-gray-200 rounded p-3">
279
+ <div className="text-xs font-mono text-blue-700 mb-1">
280
+ {arrayElement.id}
281
+ </div>
282
+ <div className="text-xs">
283
+ <span className="font-medium">Type:</span> {arrayElement.inputType}
284
+ </div>
285
+ {arrayElement.label && (
286
+ <div className="text-xs">
287
+ <span className="font-medium">Label:</span> {arrayElement.label}
288
+ </div>
289
+ )}
290
+ {arrayElement.placeholder && (
291
+ <div className="text-xs">
292
+ <span className="font-medium">Placeholder:</span> {arrayElement.placeholder}
293
+ </div>
294
+ )}
295
+ </div>
296
+ ))}
297
+ </div>
298
+ </div>
299
+ )}
300
+
301
+ {element.conditionalDisplay && (
302
+ <div className="col-span-2">
303
+ <span className="font-medium text-gray-600">Conditional Display:</span>
304
+ <div className="ml-2 mt-1 text-xs bg-yellow-50 border border-yellow-200 rounded p-2">
305
+ Depends on <span className="font-mono">{element.conditionalDisplay.dependsOn}</span>
306
+ {" "}
307
+ {element.conditionalDisplay.operator || "equals"}
308
+ {" "}
309
+ <span className="font-mono">{String(element.conditionalDisplay.value)}</span>
310
+ </div>
311
+ </div>
312
+ )}
313
+
314
+ {element.action && (
315
+ <div className="col-span-2">
316
+ <span className="font-medium text-gray-600">Action:</span>
317
+ <div className="ml-2 mt-1 text-xs">
318
+ On {element.action.on}: {element.action.type}
319
+ {element.action.data && (
320
+ <div className="font-mono text-xs mt-1 bg-gray-50 p-1 rounded">
321
+ {JSON.stringify(element.action.data, null, 2)}
322
+ </div>
323
+ )}
324
+ </div>
325
+ </div>
326
+ )}
327
+ </div>
328
+ </div>
329
+ );
330
+ }
@@ -45,12 +45,15 @@ function DialogContent({
45
45
  <DialogPrimitive.Content
46
46
  data-slot="dialog-content"
47
47
  className={cn(
48
- 'bg-background fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
48
+ 'bg-background fixed top-[50%] left-[50%] z-50 w-full translate-x-[-50%] translate-y-[-50%] rounded-3xl duration-200',
49
49
  className
50
50
  )}
51
51
  {...props}
52
52
  >
53
- {children}
53
+ <div className="flex h-full flex-col">
54
+ {children}
55
+ </div>
56
+
54
57
  {showCloseButton && (
55
58
  <DialogPrimitive.Close
56
59
  data-slot="dialog-close"
@@ -69,7 +72,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
69
72
  return (
70
73
  <div
71
74
  data-slot="dialog-header"
72
- className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
75
+ className={cn(className)}
73
76
  {...props}
74
77
  />
75
78
  )
@@ -0,0 +1,8 @@
1
+ /// <reference types="@rsbuild/core/types" />
2
+
3
+ import type { LinkAppSettings } from '../src/types'
4
+
5
+ declare global {
6
+ const __PREVIEW_PROPS__: Record<string, unknown>
7
+ const __SETTINGS_CONFIG__: LinkAppSettings
8
+ }
@@ -2,61 +2,66 @@ import Featured from "@/app/featured";
2
2
  import { StrictMode } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
4
  import "@/app/globals.css";
5
+ import { THEME_PRESETS } from "../shared/theme-presets";
6
+ import { getThemeFromUrl, mergeThemeProps } from "../shared/theme-utils";
5
7
 
6
- // Conditionally import Layout if it exists
7
- const layoutModules = import.meta.glob("@/app/layout.{ts,tsx}", {
8
- eager: true,
9
- });
10
- const Layout = Object.values(layoutModules)[0]?.default as
11
- | React.ComponentType<{ children: React.ReactNode; theme?: any }>
12
- | undefined;
8
+ // Try to import FeaturedCarousel if it exists
9
+ let FeaturedCarousel: React.ComponentType<any> | null = null;
10
+ try {
11
+ FeaturedCarousel = require("@/app/featured-carousel").default;
12
+ } catch (e) {
13
+ // featured-carousel.tsx doesn't exist, that's ok
14
+ }
15
+
16
+ // Declare global window property for theme application
17
+ declare global {
18
+ interface Window {
19
+ __linkapp_applyTheme?: (variables: Record<string, string>) => void
20
+ }
21
+ }
13
22
 
14
23
  // Preview props injected by dev server via Vite define
15
24
  declare const __PREVIEW_PROPS__: Record<string, unknown>;
16
25
 
17
- const previewProps = __PREVIEW_PROPS__ || {
18
- linkUrl: "https://example.com/demo",
19
- theme: {
20
- cssVariables: {
21
- "--button-style-text": "#000000",
22
- "--button-style-background": "#ffffff",
23
- "--button-style-inner-radius": "12px",
24
- "--button-style-border-color": "#e5e7eb",
25
- "--button-style-background-hover": "#f3f4f6",
26
- "--button-style-contrast-color": "#ffffff",
27
- "--profileBackground": "#ffffff",
28
- },
29
- // Legacy properties (deprecated)
30
- textColor: "#000000",
31
- backgroundColor: "#ffffff",
32
- borderRadius: "12px",
33
- borderColor: "#e5e7eb",
34
- backgroundHover: "#f3f4f6",
35
- contrastColor: "#ffffff",
36
- textHoverColor: "#111827",
37
- },
38
- locale: "en-US",
39
- currency: "USD",
40
- username: "demo-user",
41
- viewport: {
42
- width: 680,
43
- height: 800,
44
- },
45
- };
26
+ // Extract just the variables from THEME_PRESETS for theme lookups
27
+ const THEME_VARS = Object.fromEntries(
28
+ Object.entries(THEME_PRESETS).map(([key, { variables }]) => [key, variables])
29
+ );
30
+
31
+ // Get theme variables and groupLayoutOption from URL
32
+ const params = new URLSearchParams(window.location.search);
33
+ const groupLayoutOptionParam = params.get('groupLayoutOption');
34
+ const themeVariables = getThemeFromUrl(THEME_VARS);
35
+
36
+ // Merge with preview props and add groupLayoutOption
37
+ const previewProps = __PREVIEW_PROPS__ || {};
38
+ const mergedProps = mergeThemeProps(themeVariables, previewProps, {
39
+ groupLayoutOption: groupLayoutOptionParam || undefined,
40
+ });
41
+
42
+ // Apply theme CSS variables on mount
43
+ if (themeVariables && window.__linkapp_applyTheme) {
44
+ window.__linkapp_applyTheme(themeVariables);
45
+ }
46
46
 
47
47
  const rootElement = document.getElementById("root");
48
48
  if (!rootElement) {
49
49
  throw new Error("Root element not found");
50
50
  }
51
51
 
52
+ // Select the appropriate component based on groupLayoutOption parameter
53
+ const LayoutComponent = (groupLayoutOptionParam === 'carousel' && FeaturedCarousel)
54
+ ? FeaturedCarousel
55
+ : Featured;
56
+
57
+ console.log('[Featured Dev Server] Rendering:', {
58
+ groupLayoutOption: groupLayoutOptionParam,
59
+ component: groupLayoutOptionParam === 'carousel' && FeaturedCarousel ? 'FeaturedCarousel' : 'Featured',
60
+ hasFeaturedCarousel: !!FeaturedCarousel,
61
+ });
62
+
52
63
  createRoot(rootElement).render(
53
64
  <StrictMode>
54
- {Layout ? (
55
- <Layout theme={previewProps.theme}>
56
- <Featured {...previewProps} />
57
- </Layout>
58
- ) : (
59
- <Featured {...previewProps} />
60
- )}
65
+ <LayoutComponent {...mergedProps} />
61
66
  </StrictMode>,
62
67
  );
@@ -5,6 +5,9 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Featured - LinkApp</title>
7
7
 
8
+ <!-- iframe-resizer: Required for iframe height auto-adjustment -->
9
+ <script crossorigin src="https://unpkg.com/iframe-resizer@4.3.2/js/iframeResizer.contentWindow.js"></script>
10
+
8
11
  <!-- Linktree theme variables will be injected here -->
9
12
  <style id="linktree-theme"></style>
10
13
 
@@ -21,46 +24,29 @@
21
24
  }
22
25
 
23
26
  /**
24
- * Listen for theme updates from parent window
25
- * Applies CSS variables to the document root
27
+ * Apply theme CSS variables to :root
28
+ * Called once on mount from main.tsx
29
+ * @param {Record<string, string>} variables - CSS variables to apply
26
30
  */
27
- window.addEventListener('message', function(event) {
28
- // Only process messages with the correct structure
29
- if (
30
- event.data &&
31
- typeof event.data === 'object' &&
32
- event.data.type === 'THEME_UPDATE'
33
- ) {
34
- const themeUpdate = event.data
35
-
36
- // Get the style element where theme variables are injected
37
- const themeStyle = document.getElementById('linktree-theme')
31
+ function applyTheme(variables) {
32
+ if (!variables || typeof variables !== 'object') {
33
+ return
34
+ }
38
35
 
39
- if (!themeStyle) {
40
- console.warn('linktree-theme style element not found')
41
- return
42
- }
36
+ // Get the style element where theme variables are injected
37
+ const themeStyle = document.getElementById('linktree-theme')
43
38
 
44
- // Inject CSS variables into :root
45
- themeStyle.textContent = ':root { ' + renderCssVariables(themeUpdate.payload.variables) + ' }'
39
+ if (!themeStyle) {
40
+ console.warn('linktree-theme style element not found')
41
+ return
42
+ }
46
43
 
47
- // Remove any existing theme classes
48
- const classesToRemove = []
49
- document.documentElement.classList.forEach((className) => {
50
- if (className.startsWith('linktree-theme-')) {
51
- classesToRemove.push(className)
52
- }
53
- })
54
- classesToRemove.forEach((className) => {
55
- document.documentElement.classList.remove(className)
56
- })
44
+ // Inject CSS variables into :root
45
+ themeStyle.textContent = ':root { ' + renderCssVariables(variables) + ' }'
46
+ }
57
47
 
58
- // Add theme class for additional styling hooks
59
- if (themeUpdate.payload.name && themeUpdate.payload.name !== 'legacy') {
60
- document.documentElement.classList.add('linktree-theme-' + themeUpdate.payload.name)
61
- }
62
- }
63
- }, false)
48
+ // Export applyTheme to be used by main.tsx
49
+ window.__linkapp_applyTheme = applyTheme
64
50
  </script>
65
51
  </head>
66
52
  <body>
@@ -5,8 +5,15 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Preview - LinkApp</title>
7
7
 
8
+ <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
9
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
10
+ <link rel="shortcut icon" href="/favicon.ico" />
11
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
12
+ <meta name="apple-mobile-web-app-title" content="Linktree" />
13
+ <link rel="manifest" href="/site.webmanifest" />
14
+
8
15
  <!-- iframe-resizer: Required for LinkApps to communicate with Linktree parent frame -->
9
- <script crossorigin src="https://unpkg.com/iframe-resizer@4.3.2/js/iframeResizer.contentWindow.min.js"></script>
16
+ <script crossorigin src="https://unpkg.com/iframe-resizer@4.3.2/js/iframeResizer.contentWindow.js"></script>
10
17
  </head>
11
18
  <body>
12
19
  <div id="root"></div>
@@ -4,3 +4,9 @@ import { twMerge } from 'tailwind-merge'
4
4
  export function cn(...inputs: ClassValue[]) {
5
5
  return twMerge(clsx(inputs))
6
6
  }
7
+
8
+ export const renderCssVariables = (vars: Record<string, string>): string => {
9
+ return Object.entries(vars)
10
+ .map((entry) => entry.join(': '))
11
+ .join('; ') + ';'
12
+ }