cinqcinqdev-seo 0.1.5 → 0.1.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.
- package/dist/module.d.mts +62 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +99 -14
- package/dist/runtime/assets/admin-tw.css +1 -0
- package/dist/runtime/assets/admin-tw.input.css +1 -0
- package/dist/runtime/assets/admin.css +3 -0
- package/dist/runtime/components/admin/DynamicRenderer.d.vue.ts +3 -0
- package/dist/runtime/components/admin/DynamicRenderer.vue +30 -0
- package/dist/runtime/components/admin/DynamicRenderer.vue.d.ts +3 -0
- package/dist/runtime/components/admin/Navbar.d.vue.ts +3 -0
- package/dist/runtime/components/admin/Navbar.vue +80 -0
- package/dist/runtime/components/admin/Navbar.vue.d.ts +3 -0
- package/dist/runtime/composables/useAdminSections.d.ts +11 -0
- package/dist/runtime/composables/useAdminSections.js +332 -0
- package/dist/runtime/middleware/admin-auth.d.ts +2 -0
- package/dist/runtime/middleware/admin-auth.js +11 -0
- package/dist/runtime/pages/admin/account.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/account.vue +154 -0
- package/dist/runtime/pages/admin/account.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/editor/[id].d.vue.ts +3 -0
- package/dist/runtime/pages/admin/editor/[id].vue +521 -0
- package/dist/runtime/pages/admin/editor/[id].vue.d.ts +3 -0
- package/dist/runtime/pages/admin/index.d.vue.ts +3 -0
- package/dist/runtime/pages/admin/index.vue +40 -0
- package/dist/runtime/pages/admin/index.vue.d.ts +3 -0
- package/dist/runtime/pages/admin/pages/[type].d.vue.ts +3 -0
- package/dist/runtime/pages/admin/pages/[type].vue +137 -0
- package/dist/runtime/pages/admin/pages/[type].vue.d.ts +3 -0
- package/dist/runtime/plugins/admin-font.client.d.ts +7 -0
- package/dist/runtime/plugins/admin-font.client.js +6 -0
- package/dist/runtime/stores/adminUser.d.ts +1 -0
- package/dist/runtime/stores/adminUser.js +55 -0
- package/dist/types.d.mts +2 -12
- package/package.json +2 -2
- package/dist/module.d.cts +0 -2
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
const tagOptions = [
|
|
2
|
+
{ value: "h1", label: "H1" },
|
|
3
|
+
{ value: "h2", label: "H2" },
|
|
4
|
+
{ value: "h3", label: "H3" },
|
|
5
|
+
{ value: "h4", label: "H4" },
|
|
6
|
+
{ value: "div", label: "DIV" }
|
|
7
|
+
];
|
|
8
|
+
const layoutOptions = [
|
|
9
|
+
{ value: "centered", label: "Centered", icon: "\u25C9" },
|
|
10
|
+
{ value: "split", label: "Split", icon: "\u25E7" },
|
|
11
|
+
{ value: "bento", label: "Bento", icon: "\u229E" },
|
|
12
|
+
{ value: "fullscreen", label: "Fullscreen", icon: "\u25A3" },
|
|
13
|
+
{ value: "minimal", label: "Minimal", icon: "\u2014" },
|
|
14
|
+
{ value: "editorial", label: "Editorial", icon: "\u{1D5E1}" }
|
|
15
|
+
];
|
|
16
|
+
const animationOptions = [
|
|
17
|
+
{ value: "none", label: "None", icon: "\u2715" },
|
|
18
|
+
{ value: "fadeUp", label: "Fade Up", icon: "\u2191" },
|
|
19
|
+
{ value: "blur", label: "Blur", icon: "\u25CE" },
|
|
20
|
+
{ value: "reveal", label: "Reveal", icon: "\u25B6" },
|
|
21
|
+
{ value: "slideLeft", label: "Slide", icon: "\u2190" },
|
|
22
|
+
{ value: "typewriter", label: "Typewriter", icon: "\u258C" },
|
|
23
|
+
{ value: "float", label: "Float", icon: "\u301C" }
|
|
24
|
+
];
|
|
25
|
+
const i18n0 = (fr = "") => ({ fr, ar: "", en: "" });
|
|
26
|
+
export const DEFAULT_SECTIONS = {
|
|
27
|
+
HeroSection: {
|
|
28
|
+
label: "Hero",
|
|
29
|
+
icon: "\u26A1",
|
|
30
|
+
defaultProps: {
|
|
31
|
+
layout: "centered",
|
|
32
|
+
animation: "fadeUp",
|
|
33
|
+
titleSize: "md",
|
|
34
|
+
title: i18n0("Impactful Title"),
|
|
35
|
+
titleTag: "h1",
|
|
36
|
+
subtitle: i18n0(),
|
|
37
|
+
badge: i18n0(),
|
|
38
|
+
image: "",
|
|
39
|
+
imageStyle: "cover",
|
|
40
|
+
imagePosition: "center",
|
|
41
|
+
bgColor: "#3d35ff",
|
|
42
|
+
textColor: "#ffffff",
|
|
43
|
+
ctaText: i18n0("Get Started"),
|
|
44
|
+
ctaLink: "/"
|
|
45
|
+
},
|
|
46
|
+
groups: [
|
|
47
|
+
{ key: "design", label: "Design" },
|
|
48
|
+
{ key: "animation", label: "Animation" }
|
|
49
|
+
],
|
|
50
|
+
fields: {
|
|
51
|
+
layout: { type: "cards", label: "Layout", group: "design", options: layoutOptions },
|
|
52
|
+
titleSize: { type: "cards", label: "Title Size", group: "design", options: [
|
|
53
|
+
{ value: "sm", label: "Small", icon: "S" },
|
|
54
|
+
{ value: "md", label: "Medium", icon: "M" },
|
|
55
|
+
{ value: "lg", label: "Large", icon: "L" }
|
|
56
|
+
] },
|
|
57
|
+
titleTag: { type: "select", label: "SEO Tag", group: "design", options: tagOptions },
|
|
58
|
+
badge: { type: "text", label: "Badge", group: "design", i18n: true },
|
|
59
|
+
title: { type: "text", label: "Title", group: "design", i18n: true },
|
|
60
|
+
subtitle: { type: "textarea", label: "Subtitle", group: "design", i18n: true },
|
|
61
|
+
image: { type: "image", label: "Image", group: "design" },
|
|
62
|
+
imageStyle: { type: "cards", label: "Image Style", group: "design", options: [
|
|
63
|
+
{ value: "cover", label: "Cover", icon: "\u25A3" },
|
|
64
|
+
{ value: "contain", label: "Contain", icon: "\u25FB" },
|
|
65
|
+
{ value: "portrait", label: "Portrait", icon: "\u25AF" },
|
|
66
|
+
{ value: "square", label: "Square", icon: "\u25A1" },
|
|
67
|
+
{ value: "circle", label: "Circle", icon: "\u25EF" }
|
|
68
|
+
] },
|
|
69
|
+
ctaText: { type: "text", label: "Button Text", group: "design", i18n: true },
|
|
70
|
+
ctaLink: { type: "text", label: "Button URL", group: "design" },
|
|
71
|
+
bgColor: { type: "color", label: "Background", group: "design" },
|
|
72
|
+
textColor: { type: "color", label: "Text Color", group: "design" },
|
|
73
|
+
animation: { type: "cards", label: "Animation", group: "animation", options: animationOptions }
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
TextVisual: {
|
|
77
|
+
label: "Text & Image",
|
|
78
|
+
icon: "\u{1F5BC}\uFE0F",
|
|
79
|
+
defaultProps: {
|
|
80
|
+
layout: "split",
|
|
81
|
+
title: i18n0("Our Vision"),
|
|
82
|
+
titleTag: "h2",
|
|
83
|
+
badge: i18n0(),
|
|
84
|
+
content: i18n0(),
|
|
85
|
+
image: "",
|
|
86
|
+
imageAlt: i18n0(),
|
|
87
|
+
imageStyle: "rounded",
|
|
88
|
+
imageFit: "cover",
|
|
89
|
+
imageShadow: false,
|
|
90
|
+
reverse: false,
|
|
91
|
+
bgColor: "#ffffff",
|
|
92
|
+
textColor: "#000000",
|
|
93
|
+
ctas: []
|
|
94
|
+
},
|
|
95
|
+
fields: {
|
|
96
|
+
layout: { type: "cards", label: "Layout", options: [
|
|
97
|
+
{ value: "split", label: "Split", icon: "\u25E7" },
|
|
98
|
+
{ value: "stacked", label: "Stacked", icon: "\u25A4" },
|
|
99
|
+
{ value: "wide", label: "Wide", icon: "\u25AC" }
|
|
100
|
+
] },
|
|
101
|
+
imageStyle: { type: "cards", label: "Image Style", options: [
|
|
102
|
+
{ value: "rounded", label: "Rounded", icon: "\u25A2" },
|
|
103
|
+
{ value: "square", label: "Square", icon: "\u25A1" },
|
|
104
|
+
{ value: "circle", label: "Circle", icon: "\u25EF" },
|
|
105
|
+
{ value: "portrait", label: "Portrait", icon: "\u25AF" }
|
|
106
|
+
] },
|
|
107
|
+
imageFit: { type: "cards", label: "Image Fit", options: [
|
|
108
|
+
{ value: "cover", label: "Cover", icon: "\u25A3" },
|
|
109
|
+
{ value: "contain", label: "Contain", icon: "\u25FB" }
|
|
110
|
+
] },
|
|
111
|
+
reverse: { type: "select", label: "Direction", options: [{ value: false, label: "Image right" }, { value: true, label: "Image left" }] },
|
|
112
|
+
titleTag: { type: "select", label: "SEO Tag", options: tagOptions },
|
|
113
|
+
badge: { type: "text", label: "Badge", i18n: true },
|
|
114
|
+
title: { type: "text", label: "Title", i18n: true },
|
|
115
|
+
content: { type: "textarea", label: "Text", i18n: true },
|
|
116
|
+
image: { type: "image", label: "Image" },
|
|
117
|
+
imageAlt: { type: "text", label: "Alt text (SEO)", i18n: true },
|
|
118
|
+
ctas: { type: "list", label: "CTA Buttons", itemFields: {
|
|
119
|
+
text: { type: "text", label: "Text", i18n: true },
|
|
120
|
+
link: { type: "text", label: "URL" }
|
|
121
|
+
} },
|
|
122
|
+
bgColor: { type: "color", label: "Background" },
|
|
123
|
+
textColor: { type: "color", label: "Text Color" }
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
ServiceGrid: {
|
|
127
|
+
label: "Services",
|
|
128
|
+
icon: "\u{1F6E0}\uFE0F",
|
|
129
|
+
defaultProps: {
|
|
130
|
+
layout: "grid",
|
|
131
|
+
title: i18n0("Our Services"),
|
|
132
|
+
titleTag: "h2",
|
|
133
|
+
badge: i18n0(),
|
|
134
|
+
description: i18n0(),
|
|
135
|
+
headerAlign: "left",
|
|
136
|
+
ctas: [],
|
|
137
|
+
itemTag: "h3",
|
|
138
|
+
items: [],
|
|
139
|
+
bgColor: "#ffffff",
|
|
140
|
+
textColor: "#000000"
|
|
141
|
+
},
|
|
142
|
+
fields: {
|
|
143
|
+
layout: { type: "cards", label: "Layout", options: [
|
|
144
|
+
{ value: "grid", label: "Grid", icon: "\u229E" },
|
|
145
|
+
{ value: "list", label: "List", icon: "\u2261" },
|
|
146
|
+
{ value: "cards", label: "Cards", icon: "\u25A4" },
|
|
147
|
+
{ value: "minimal", label: "Minimal", icon: "\u2014" }
|
|
148
|
+
] },
|
|
149
|
+
titleTag: { type: "select", label: "Section SEO Tag", options: tagOptions },
|
|
150
|
+
itemTag: { type: "select", label: "Item SEO Tag", options: tagOptions },
|
|
151
|
+
badge: { type: "text", label: "Badge", i18n: true },
|
|
152
|
+
title: { type: "text", label: "Title", i18n: true },
|
|
153
|
+
description: { type: "textarea", label: "Description", i18n: true },
|
|
154
|
+
items: { type: "list", label: "Services", itemFields: {
|
|
155
|
+
title: { type: "text", label: "Name", i18n: true },
|
|
156
|
+
description: { type: "textarea", label: "Description", i18n: true },
|
|
157
|
+
price: { type: "text", label: "Price" },
|
|
158
|
+
ctaText: { type: "text", label: "Button text", i18n: true },
|
|
159
|
+
link: { type: "text", label: "URL" },
|
|
160
|
+
image: { type: "image", label: "Icon/Image" }
|
|
161
|
+
} },
|
|
162
|
+
bgColor: { type: "color", label: "Background" },
|
|
163
|
+
textColor: { type: "color", label: "Text Color" }
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
Features: {
|
|
167
|
+
label: "Features",
|
|
168
|
+
icon: "\u{1F48E}",
|
|
169
|
+
defaultProps: {
|
|
170
|
+
title: i18n0("Why choose us?"),
|
|
171
|
+
items: [],
|
|
172
|
+
bgColor: "#ffffff",
|
|
173
|
+
textColor: "#000000"
|
|
174
|
+
},
|
|
175
|
+
fields: {
|
|
176
|
+
title: { type: "text", label: "Section Title", i18n: true },
|
|
177
|
+
items: { type: "list", label: "Features", itemFields: {
|
|
178
|
+
title: { type: "text", label: "Title", i18n: true },
|
|
179
|
+
description: { type: "textarea", label: "Description", i18n: true }
|
|
180
|
+
} },
|
|
181
|
+
bgColor: { type: "color", label: "Background" },
|
|
182
|
+
textColor: { type: "color", label: "Text Color" }
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
FAQ: {
|
|
186
|
+
label: "FAQ",
|
|
187
|
+
icon: "\u2753",
|
|
188
|
+
defaultProps: {
|
|
189
|
+
title: i18n0("FAQ"),
|
|
190
|
+
items: [],
|
|
191
|
+
bgColor: "#F9F9FB",
|
|
192
|
+
textColor: "#000000"
|
|
193
|
+
},
|
|
194
|
+
fields: {
|
|
195
|
+
title: { type: "text", label: "Title", i18n: true },
|
|
196
|
+
items: { type: "list", label: "Questions", itemFields: {
|
|
197
|
+
question: { type: "text", label: "Question", i18n: true },
|
|
198
|
+
answer: { type: "textarea", label: "Answer", i18n: true }
|
|
199
|
+
} }
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
Process: {
|
|
203
|
+
label: "Process",
|
|
204
|
+
icon: "\u{1F504}",
|
|
205
|
+
defaultProps: {
|
|
206
|
+
layout: "steps",
|
|
207
|
+
title: i18n0("How it works"),
|
|
208
|
+
titleTag: "h2",
|
|
209
|
+
badge: i18n0(),
|
|
210
|
+
description: i18n0(),
|
|
211
|
+
items: [],
|
|
212
|
+
bgColor: "#ffffff",
|
|
213
|
+
textColor: "#000000"
|
|
214
|
+
},
|
|
215
|
+
fields: {
|
|
216
|
+
layout: { type: "cards", label: "Layout", options: [
|
|
217
|
+
{ value: "steps", label: "Steps", icon: "\u2193" },
|
|
218
|
+
{ value: "horizontal", label: "Horizontal", icon: "\u2192" },
|
|
219
|
+
{ value: "cards", label: "Cards", icon: "\u229E" },
|
|
220
|
+
{ value: "timeline", label: "Timeline", icon: "\u25CE" },
|
|
221
|
+
{ value: "minimal", label: "Minimal", icon: "\u2014" }
|
|
222
|
+
] },
|
|
223
|
+
titleTag: { type: "select", label: "SEO Tag", options: tagOptions },
|
|
224
|
+
badge: { type: "text", label: "Badge", i18n: true },
|
|
225
|
+
title: { type: "text", label: "Title", i18n: true },
|
|
226
|
+
description: { type: "textarea", label: "Description", i18n: true },
|
|
227
|
+
items: { type: "list", label: "Steps", itemFields: {
|
|
228
|
+
icon: { type: "text", label: "Icon/Emoji" },
|
|
229
|
+
title: { type: "text", label: "Step Title", i18n: true },
|
|
230
|
+
description: { type: "textarea", label: "Description", i18n: true }
|
|
231
|
+
} },
|
|
232
|
+
bgColor: { type: "color", label: "Background" },
|
|
233
|
+
textColor: { type: "color", label: "Text Color" }
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
Testimonials: {
|
|
237
|
+
label: "Testimonials",
|
|
238
|
+
icon: "\u{1F4AC}",
|
|
239
|
+
defaultProps: {
|
|
240
|
+
layout: "grid",
|
|
241
|
+
title: i18n0("What our clients say"),
|
|
242
|
+
titleTag: "h2",
|
|
243
|
+
badge: i18n0(),
|
|
244
|
+
description: i18n0(),
|
|
245
|
+
items: [],
|
|
246
|
+
bgColor: "#ffffff",
|
|
247
|
+
textColor: "#000000"
|
|
248
|
+
},
|
|
249
|
+
fields: {
|
|
250
|
+
layout: { type: "cards", label: "Layout", options: [
|
|
251
|
+
{ value: "grid", label: "Grid", icon: "\u229E" },
|
|
252
|
+
{ value: "featured", label: "Featured", icon: "\u25E7" },
|
|
253
|
+
{ value: "wall", label: "Wall", icon: "\u25A6" },
|
|
254
|
+
{ value: "minimal", label: "Minimal", icon: "\u2014" }
|
|
255
|
+
] },
|
|
256
|
+
titleTag: { type: "select", label: "SEO Tag", options: tagOptions },
|
|
257
|
+
badge: { type: "text", label: "Badge", i18n: true },
|
|
258
|
+
title: { type: "text", label: "Title", i18n: true },
|
|
259
|
+
description: { type: "textarea", label: "Description", i18n: true },
|
|
260
|
+
items: { type: "list", label: "Testimonials", itemFields: {
|
|
261
|
+
name: { type: "text", label: "Name" },
|
|
262
|
+
role: { type: "text", label: "Role" },
|
|
263
|
+
company: { type: "text", label: "Company" },
|
|
264
|
+
rating: { type: "text", label: "Rating (1-5)" },
|
|
265
|
+
quote: { type: "textarea", label: "Quote", i18n: true },
|
|
266
|
+
avatar: { type: "image", label: "Photo" }
|
|
267
|
+
} },
|
|
268
|
+
bgColor: { type: "color", label: "Background" },
|
|
269
|
+
textColor: { type: "color", label: "Text Color" }
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
Pricing: {
|
|
273
|
+
label: "Pricing",
|
|
274
|
+
icon: "\u{1F4B0}",
|
|
275
|
+
defaultProps: {
|
|
276
|
+
layout: "cards",
|
|
277
|
+
title: i18n0("Our Pricing"),
|
|
278
|
+
titleTag: "h2",
|
|
279
|
+
badge: i18n0(),
|
|
280
|
+
description: i18n0(),
|
|
281
|
+
items: [],
|
|
282
|
+
bgColor: "#ffffff",
|
|
283
|
+
textColor: "#000000"
|
|
284
|
+
},
|
|
285
|
+
fields: {
|
|
286
|
+
layout: { type: "cards", label: "Layout", options: [
|
|
287
|
+
{ value: "cards", label: "Cards", icon: "\u25A4" },
|
|
288
|
+
{ value: "minimal", label: "Minimal", icon: "\u2014" },
|
|
289
|
+
{ value: "table", label: "Table", icon: "\u229E" }
|
|
290
|
+
] },
|
|
291
|
+
titleTag: { type: "select", label: "SEO Tag", options: tagOptions },
|
|
292
|
+
badge: { type: "text", label: "Badge", i18n: true },
|
|
293
|
+
title: { type: "text", label: "Title", i18n: true },
|
|
294
|
+
description: { type: "textarea", label: "Description", i18n: true },
|
|
295
|
+
items: { type: "list", label: "Plans", itemFields: {
|
|
296
|
+
name: { type: "text", label: "Plan name", i18n: true },
|
|
297
|
+
price: { type: "text", label: "Price" },
|
|
298
|
+
period: { type: "text", label: "Period (e.g. /month)" },
|
|
299
|
+
description: { type: "textarea", label: "Description", i18n: true },
|
|
300
|
+
ctaText: { type: "text", label: "Button text", i18n: true },
|
|
301
|
+
ctaLink: { type: "text", label: "URL" },
|
|
302
|
+
highlighted: { type: "select", label: "Highlight", options: [{ value: false, label: "No" }, { value: true, label: "Yes" }] }
|
|
303
|
+
} },
|
|
304
|
+
bgColor: { type: "color", label: "Background" },
|
|
305
|
+
textColor: { type: "color", label: "Text Color" }
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
ContactForm: {
|
|
309
|
+
label: "Contact Form",
|
|
310
|
+
icon: "\u{1F4E9}",
|
|
311
|
+
defaultProps: {
|
|
312
|
+
title: i18n0("Let's talk"),
|
|
313
|
+
subtitle: i18n0("Describe your project and we'll get back to you within 24h."),
|
|
314
|
+
badge: i18n0(),
|
|
315
|
+
button_text: i18n0("Send"),
|
|
316
|
+
bgColor: "#ffffff",
|
|
317
|
+
textColor: "#000000"
|
|
318
|
+
},
|
|
319
|
+
fields: {
|
|
320
|
+
badge: { type: "text", label: "Badge", i18n: true },
|
|
321
|
+
title: { type: "text", label: "Title", i18n: true },
|
|
322
|
+
subtitle: { type: "text", label: "Subtitle", i18n: true },
|
|
323
|
+
button_text: { type: "text", label: "Button", i18n: true },
|
|
324
|
+
bgColor: { type: "color", label: "Background" },
|
|
325
|
+
textColor: { type: "color", label: "Text Color" }
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
export const useAdminSections = () => {
|
|
330
|
+
const config = useRuntimeConfig().public.adminCms;
|
|
331
|
+
return { ...DEFAULT_SECTIONS, ...config?.extraSections || {} };
|
|
332
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware((to) => {
|
|
2
|
+
const config = useRuntimeConfig().public.adminCms;
|
|
3
|
+
const user = useSupabaseUser();
|
|
4
|
+
const loginRoute = config?.loginRoute || "/login";
|
|
5
|
+
if (to.path.startsWith("/admin") && !user.value) {
|
|
6
|
+
return navigateTo(loginRoute);
|
|
7
|
+
}
|
|
8
|
+
if (to.path === loginRoute && user.value) {
|
|
9
|
+
return navigateTo("/admin");
|
|
10
|
+
}
|
|
11
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useAdminUserStore } from "../../stores/adminUser";
|
|
3
|
+
const user = useSupabaseUser();
|
|
4
|
+
const client = useSupabaseClient();
|
|
5
|
+
const userStore = useAdminUserStore();
|
|
6
|
+
const config = useRuntimeConfig().public.adminCms;
|
|
7
|
+
const plans = config?.plans || [];
|
|
8
|
+
const table = config?.tables?.users || "users";
|
|
9
|
+
const userInitials = computed(() => user.value?.email?.substring(0, 2).toUpperCase() || "?");
|
|
10
|
+
const truncatedId = computed(() => {
|
|
11
|
+
const id = user.value?.id || "";
|
|
12
|
+
return id ? `${id.substring(0, 6)}...${id.substring(id.length - 4)}` : "";
|
|
13
|
+
});
|
|
14
|
+
const createdAt = computed(
|
|
15
|
+
() => user.value?.created_at ? new Date(user.value.created_at).toLocaleDateString(void 0, { dateStyle: "medium" }) : "\u2014"
|
|
16
|
+
);
|
|
17
|
+
const lastSignIn = computed(
|
|
18
|
+
() => user.value?.last_sign_in_at ? new Date(user.value.last_sign_in_at).toLocaleDateString(void 0, { dateStyle: "medium" }) : "\u2014"
|
|
19
|
+
);
|
|
20
|
+
const currentPlan = ref("");
|
|
21
|
+
const selectedPlan = ref("");
|
|
22
|
+
const isUpdating = ref(false);
|
|
23
|
+
const updateError = ref("");
|
|
24
|
+
const updateSuccess = ref(false);
|
|
25
|
+
const loadUserData = async () => {
|
|
26
|
+
if (!user.value?.id) return;
|
|
27
|
+
await userStore.fetchUserData(user.value.id);
|
|
28
|
+
currentPlan.value = userStore.userData?.plan || user.value.user_metadata?.plan || "";
|
|
29
|
+
selectedPlan.value = currentPlan.value;
|
|
30
|
+
};
|
|
31
|
+
onMounted(loadUserData);
|
|
32
|
+
watch(user, (u) => {
|
|
33
|
+
if (u) loadUserData();
|
|
34
|
+
}, { immediate: true });
|
|
35
|
+
const updatePlan = async () => {
|
|
36
|
+
if (!user.value?.id || !selectedPlan.value || selectedPlan.value === currentPlan.value) return;
|
|
37
|
+
isUpdating.value = true;
|
|
38
|
+
updateError.value = "";
|
|
39
|
+
updateSuccess.value = false;
|
|
40
|
+
try {
|
|
41
|
+
const { error } = await client.from(table).update({ plan: selectedPlan.value }).eq("id", user.value.id);
|
|
42
|
+
if (error) throw error;
|
|
43
|
+
await client.auth.updateUser({ data: { plan: selectedPlan.value } });
|
|
44
|
+
currentPlan.value = selectedPlan.value;
|
|
45
|
+
updateSuccess.value = true;
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
updateSuccess.value = false;
|
|
48
|
+
}, 3e3);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
updateError.value = e.message || "Error updating plan.";
|
|
51
|
+
} finally {
|
|
52
|
+
isUpdating.value = false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
useHead({ title: "Account \u2014 Admin" });
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div data-admin-cms>
|
|
60
|
+
<AdminNavbar />
|
|
61
|
+
<div class="max-w-3xl mx-auto p-6 space-y-6">
|
|
62
|
+
|
|
63
|
+
<!-- Profile card -->
|
|
64
|
+
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
|
65
|
+
<div class="bg-black px-6 py-5">
|
|
66
|
+
<h1 class="text-xl font-bold text-white">Profile</h1>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="p-6">
|
|
69
|
+
<div v-if="!user" class="text-center py-8 text-gray-400">Not logged in.</div>
|
|
70
|
+
<div v-else class="space-y-6">
|
|
71
|
+
<div class="flex items-center gap-4 pb-4 border-b border-gray-100">
|
|
72
|
+
<div class="w-14 h-14 rounded-full bg-black flex items-center justify-center text-white font-bold text-lg">
|
|
73
|
+
{{ userInitials }}
|
|
74
|
+
</div>
|
|
75
|
+
<div>
|
|
76
|
+
<p class="font-semibold text-gray-800">{{ user.email }}</p>
|
|
77
|
+
<p class="text-xs text-gray-400">ID: {{ truncatedId }}</p>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="grid sm:grid-cols-2 gap-4">
|
|
81
|
+
<div class="bg-gray-50 p-4 rounded-lg">
|
|
82
|
+
<p class="text-xs text-gray-500 mb-1">Account created</p>
|
|
83
|
+
<p class="font-medium text-gray-800">{{ createdAt }}</p>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="bg-gray-50 p-4 rounded-lg">
|
|
86
|
+
<p class="text-xs text-gray-500 mb-1">Last sign in</p>
|
|
87
|
+
<p class="font-medium text-gray-800">{{ lastSignIn }}</p>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="bg-gray-50 p-4 rounded-lg">
|
|
90
|
+
<p class="text-xs text-gray-500 mb-1">Auth method</p>
|
|
91
|
+
<p class="font-medium text-gray-800 capitalize">{{ user.app_metadata?.provider || "email" }}</p>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="bg-gray-50 p-4 rounded-lg">
|
|
94
|
+
<p class="text-xs text-gray-500 mb-1">Email verified</p>
|
|
95
|
+
<p :class="['font-medium', user.email_confirmed_at ? 'text-green-600' : 'text-amber-600']">
|
|
96
|
+
{{ user.email_confirmed_at ? "\u2713 Verified" : "\u26A0 Not verified" }}
|
|
97
|
+
</p>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- Plan management (only shown if plans are configured) -->
|
|
105
|
+
<div v-if="user && plans.length" class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
|
106
|
+
<div class="bg-gradient-to-r from-blue-600 to-violet-600 px-6 py-5">
|
|
107
|
+
<h2 class="text-xl font-bold text-white">Plan</h2>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="p-6">
|
|
110
|
+
<!-- Feedback -->
|
|
111
|
+
<div v-if="updateError" class="mb-4 p-3 bg-red-50 border border-red-100 text-red-700 rounded-lg text-sm">
|
|
112
|
+
{{ updateError }}
|
|
113
|
+
</div>
|
|
114
|
+
<div v-if="updateSuccess" class="mb-4 p-3 bg-green-50 border border-green-100 text-green-700 rounded-lg text-sm">
|
|
115
|
+
Plan updated successfully.
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- Plan cards -->
|
|
119
|
+
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
120
|
+
<div
|
|
121
|
+
v-for="plan in plans"
|
|
122
|
+
:key="plan.value"
|
|
123
|
+
@click="selectedPlan = plan.value"
|
|
124
|
+
:class="[
|
|
125
|
+
'relative border-2 rounded-xl p-5 cursor-pointer transition-all',
|
|
126
|
+
selectedPlan === plan.value ? 'border-blue-500 bg-blue-50' : currentPlan === plan.value ? 'border-green-500 bg-green-50' : 'border-gray-200 hover:border-blue-300'
|
|
127
|
+
]"
|
|
128
|
+
>
|
|
129
|
+
<div v-if="currentPlan === plan.value" class="absolute -top-2 -right-2 bg-green-500 text-white text-[9px] font-black px-2 py-0.5 rounded-full uppercase">Current</div>
|
|
130
|
+
<div v-else-if="plan.popular" class="absolute -top-2 -right-2 bg-orange-500 text-white text-[9px] font-black px-2 py-0.5 rounded-full uppercase">Popular</div>
|
|
131
|
+
<h3 class="font-bold text-gray-900 mb-1">{{ plan.title }}</h3>
|
|
132
|
+
<p v-if="plan.subtitle" class="text-xs text-gray-500 mb-3">{{ plan.subtitle }}</p>
|
|
133
|
+
<p class="text-xl font-bold text-gray-900 mb-3">{{ plan.price }}</p>
|
|
134
|
+
<ul class="space-y-1">
|
|
135
|
+
<li v-for="feature in plan.features" :key="feature" class="text-xs text-gray-600 flex items-start gap-1.5">
|
|
136
|
+
<span class="text-green-500 mt-0.5">✓</span> {{ feature }}
|
|
137
|
+
</li>
|
|
138
|
+
</ul>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<!-- Update button -->
|
|
143
|
+
<div v-if="selectedPlan && selectedPlan !== currentPlan" class="mt-6 text-center">
|
|
144
|
+
<button @click="updatePlan" :disabled="isUpdating"
|
|
145
|
+
class="bg-blue-600 text-white px-8 py-3 rounded-lg font-medium hover:bg-blue-700 transition disabled:opacity-50">
|
|
146
|
+
{{ isUpdating ? "Updating..." : `Switch to ${plans.find((p) => p.value === selectedPlan)?.title}` }}
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</template>
|