cinqcinqdev-seo 0.1.9 → 0.1.11

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 (64) hide show
  1. package/dist/module.d.mts +17 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +19 -11
  4. package/dist/runtime/assets/admin-tw.css +1 -1
  5. package/dist/runtime/components/admin/ContentCalendar.d.vue.ts +3 -0
  6. package/dist/runtime/components/admin/ContentCalendar.vue +196 -0
  7. package/dist/runtime/components/admin/ContentCalendar.vue.d.ts +3 -0
  8. package/dist/runtime/components/admin/DashboardLayout.d.vue.ts +13 -0
  9. package/dist/runtime/components/admin/DashboardLayout.vue +12 -0
  10. package/dist/runtime/components/admin/DashboardLayout.vue.d.ts +13 -0
  11. package/dist/runtime/components/admin/NavItem.d.vue.ts +3 -0
  12. package/dist/runtime/components/admin/NavItem.vue +46 -0
  13. package/dist/runtime/components/admin/NavItem.vue.d.ts +3 -0
  14. package/dist/runtime/components/admin/Navbar.vue +1 -2
  15. package/dist/runtime/components/admin/Sidebar.d.vue.ts +3 -0
  16. package/dist/runtime/components/admin/Sidebar.vue +115 -0
  17. package/dist/runtime/components/admin/Sidebar.vue.d.ts +3 -0
  18. package/dist/runtime/composables/useAdminBranding.d.ts +4 -0
  19. package/dist/runtime/composables/useAdminBranding.js +22 -0
  20. package/dist/runtime/composables/useAdminContent.d.ts +9 -0
  21. package/dist/runtime/composables/useAdminContent.js +46 -0
  22. package/dist/runtime/composables/useAdminPersonas.d.ts +8 -0
  23. package/dist/runtime/composables/useAdminPersonas.js +31 -0
  24. package/dist/runtime/composables/useAdminProducts.d.ts +6 -0
  25. package/dist/runtime/composables/useAdminProducts.js +24 -0
  26. package/dist/runtime/composables/useAdminStrategies.d.ts +10 -0
  27. package/dist/runtime/composables/useAdminStrategies.js +57 -0
  28. package/dist/runtime/composables/useAdminTodos.d.ts +10 -0
  29. package/dist/runtime/composables/useAdminTodos.js +55 -0
  30. package/dist/runtime/pages/admin/account.vue +59 -51
  31. package/dist/runtime/pages/admin/branding.d.vue.ts +3 -0
  32. package/dist/runtime/pages/admin/branding.vue +276 -0
  33. package/dist/runtime/pages/admin/branding.vue.d.ts +3 -0
  34. package/dist/runtime/pages/admin/calendar.d.vue.ts +3 -0
  35. package/dist/runtime/pages/admin/calendar.vue +169 -0
  36. package/dist/runtime/pages/admin/calendar.vue.d.ts +3 -0
  37. package/dist/runtime/pages/admin/content-library.d.vue.ts +3 -0
  38. package/dist/runtime/pages/admin/content-library.vue +359 -0
  39. package/dist/runtime/pages/admin/content-library.vue.d.ts +3 -0
  40. package/dist/runtime/pages/admin/editor/[id].vue +5 -3
  41. package/dist/runtime/pages/admin/index.vue +81 -23
  42. package/dist/runtime/pages/admin/pages/[type].vue +267 -83
  43. package/dist/runtime/pages/admin/personas/[id].d.vue.ts +3 -0
  44. package/dist/runtime/pages/admin/personas/[id].vue +170 -0
  45. package/dist/runtime/pages/admin/personas/[id].vue.d.ts +3 -0
  46. package/dist/runtime/pages/admin/personas/index.d.vue.ts +3 -0
  47. package/dist/runtime/pages/admin/personas/index.vue +266 -0
  48. package/dist/runtime/pages/admin/personas/index.vue.d.ts +3 -0
  49. package/dist/runtime/pages/admin/products.d.vue.ts +3 -0
  50. package/dist/runtime/pages/admin/products.vue +300 -0
  51. package/dist/runtime/pages/admin/products.vue.d.ts +3 -0
  52. package/dist/runtime/pages/admin/strategies/[id].d.vue.ts +3 -0
  53. package/dist/runtime/pages/admin/strategies/[id].vue +237 -0
  54. package/dist/runtime/pages/admin/strategies/[id].vue.d.ts +3 -0
  55. package/dist/runtime/pages/admin/strategies/index.d.vue.ts +3 -0
  56. package/dist/runtime/pages/admin/strategies/index.vue +232 -0
  57. package/dist/runtime/pages/admin/strategies/index.vue.d.ts +3 -0
  58. package/dist/runtime/pages/admin/todos.d.vue.ts +3 -0
  59. package/dist/runtime/pages/admin/todos.vue +251 -0
  60. package/dist/runtime/pages/admin/todos.vue.d.ts +3 -0
  61. package/dist/runtime/stores/adminUser.d.ts +29 -1
  62. package/dist/runtime/stores/adminUser.js +2 -2
  63. package/dist/types.d.mts +1 -1
  64. package/package.json +6 -3
@@ -0,0 +1,115 @@
1
+ <template>
2
+ <aside data-admin-cms class="w-[220px] bg-[#0d0d0d] flex-shrink-0 flex flex-col overflow-y-auto">
3
+
4
+ <!-- Logo -->
5
+ <div class="px-5 pt-6 pb-5">
6
+ <NuxtLink to="/admin" class="flex items-center gap-2.5">
7
+ <img v-if="branding.logoUrl" :src="branding.logoUrl" class="h-8 w-auto" :alt="branding.name" />
8
+ <span v-else class="w-8 h-8 bg-[#3d35ff] rounded-lg flex items-center justify-center text-white text-[10px] font-black tracking-tight shrink-0">
9
+ {{ brandInitials }}
10
+ </span>
11
+ <div>
12
+ <span class="text-white font-black text-sm leading-none block">{{ branding.name }}</span>
13
+ <span class="text-white/30 text-[9px] font-medium">Admin CMS</span>
14
+ </div>
15
+ </NuxtLink>
16
+ </div>
17
+
18
+ <!-- Nav -->
19
+ <nav class="flex-1 px-3 pb-4 space-y-0.5 overflow-y-auto">
20
+
21
+ <!-- Dashboard -->
22
+ <NuxtLink to="/admin"
23
+ :class="[
24
+ 'flex items-center gap-2.5 px-3 py-2 rounded-lg text-[11px] font-semibold transition-all',
25
+ route.path === '/admin' ? 'bg-white text-black' : 'text-white/50 hover:text-white hover:bg-white/[0.08]'
26
+ ]">
27
+ <svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
28
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
29
+ </svg>
30
+ Tableau de bord
31
+ </NuxtLink>
32
+
33
+ <!-- Pages section -->
34
+ <div v-if="pageTypes.length" class="pt-4 pb-1.5 px-3">
35
+ <p class="text-[8px] font-black uppercase text-white/20 tracking-[0.15em]">Pages</p>
36
+ </div>
37
+
38
+ <NuxtLink v-for="pt in pageTypes" :key="pt.id" :to="`/admin/pages/${pt.id}`"
39
+ :class="[
40
+ 'flex items-center gap-2.5 px-3 py-2 rounded-lg text-[11px] font-semibold transition-all',
41
+ route.path === `/admin/pages/${pt.id}` ? 'bg-white text-black' : 'text-white/50 hover:text-white hover:bg-white/[0.08]'
42
+ ]">
43
+ <svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"
44
+ v-html="pt.svgPath || defaultIcons[pt.id] || defaultIcons.blog_article">
45
+ </svg>
46
+ {{ pt.label }}
47
+ </NuxtLink>
48
+
49
+ <!-- Extra nav sections (e.g. Templates, Messages) -->
50
+ <template v-if="navSections.length">
51
+ <div class="pt-4 pb-1.5 px-3">
52
+ <p class="text-[8px] font-black uppercase text-white/20 tracking-[0.15em]">Contenu</p>
53
+ </div>
54
+ <NuxtLink v-for="item in navSections" :key="item.to" :to="item.to"
55
+ :class="[
56
+ 'flex items-center gap-2.5 px-3 py-2 rounded-lg text-[11px] font-semibold transition-all',
57
+ route.path.startsWith(item.to) ? 'bg-white text-black' : 'text-white/50 hover:text-white hover:bg-white/[0.08]'
58
+ ]">
59
+ <svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"
60
+ v-html="item.svgPath || defaultNavIcon">
61
+ </svg>
62
+ {{ item.label }}
63
+ </NuxtLink>
64
+ </template>
65
+
66
+ </nav>
67
+
68
+ <!-- Bottom -->
69
+ <div class="px-3 py-4 border-t border-white/[0.06] space-y-0.5">
70
+ <NuxtLink to="/admin/account"
71
+ :class="[
72
+ 'flex items-center gap-2.5 px-3 py-2 rounded-lg text-[11px] font-semibold transition-all',
73
+ route.path === '/admin/account' ? 'bg-white text-black' : 'text-white/50 hover:text-white hover:bg-white/[0.08]'
74
+ ]">
75
+ <svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
76
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
77
+ </svg>
78
+ Mon compte
79
+ </NuxtLink>
80
+ <button @click="handleLogout" class="w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-[11px] font-semibold text-white/30 hover:text-red-400 hover:bg-red-500/5 transition-all">
81
+ <svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
82
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
83
+ </svg>
84
+ Déconnexion
85
+ </button>
86
+ </div>
87
+ </aside>
88
+ </template>
89
+
90
+ <script setup>
91
+ import { computed, useRoute, useRuntimeConfig, useSupabaseClient, navigateTo } from "#imports";
92
+ const route = useRoute();
93
+ const config = useRuntimeConfig().public.adminCms;
94
+ const branding = computed(() => ({
95
+ name: config?.branding?.name || "Admin",
96
+ logoUrl: config?.branding?.logoUrl || ""
97
+ }));
98
+ const brandInitials = computed(() => branding.value.name.substring(0, 2).toUpperCase());
99
+ const pageTypes = computed(() => config?.pageTypes || []);
100
+ const navSections = computed(() => config?.navSections || []);
101
+ const defaultIcons = {
102
+ landing_page: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>',
103
+ service_page: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>',
104
+ about_page: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/>',
105
+ product_page: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"/>',
106
+ blog_article: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>',
107
+ portfolio_item: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>'
108
+ };
109
+ const defaultNavIcon = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>';
110
+ const handleLogout = async () => {
111
+ const supabase = useSupabaseClient();
112
+ await supabase.auth.signOut();
113
+ navigateTo(config?.loginRoute || "/");
114
+ };
115
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: any;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,4 @@
1
+ export declare const useAdminBranding: () => {
2
+ fetchBranding: () => Promise<any>;
3
+ saveBranding: (brandingData: any) => Promise<any>;
4
+ };
@@ -0,0 +1,22 @@
1
+ import { useSupabaseClient } from "#imports";
2
+ export const useAdminBranding = () => {
3
+ const supabase = useSupabaseClient();
4
+ const fetchBranding = async () => {
5
+ const { data, error } = await supabase.from("branding").select("*").limit(1).single();
6
+ if (error && error.code !== "PGRST116") throw error;
7
+ return data || null;
8
+ };
9
+ const saveBranding = async (brandingData) => {
10
+ const existing = await fetchBranding();
11
+ if (existing?.id) {
12
+ const { data, error } = await supabase.from("branding").update(brandingData).eq("id", existing.id).select().single();
13
+ if (error) throw error;
14
+ return data;
15
+ } else {
16
+ const { data, error } = await supabase.from("branding").insert(brandingData).select().single();
17
+ if (error) throw error;
18
+ return data;
19
+ }
20
+ };
21
+ return { fetchBranding, saveBranding };
22
+ };
@@ -0,0 +1,9 @@
1
+ export declare const useAdminContent: () => {
2
+ fetchContent: () => Promise<any>;
3
+ getContentByPersona: (personaId: string) => Promise<any>;
4
+ getContentByStatus: (status: string) => Promise<any>;
5
+ createContent: (content: any) => Promise<any>;
6
+ updateContent: (id: string, updates: any) => Promise<any>;
7
+ deleteContent: (id: string) => Promise<boolean>;
8
+ publishContent: (id: string) => Promise<any>;
9
+ };
@@ -0,0 +1,46 @@
1
+ import { useSupabaseClient } from "#imports";
2
+ export const useAdminContent = () => {
3
+ const supabase = useSupabaseClient();
4
+ const fetchContent = async () => {
5
+ const { data, error } = await supabase.from("generated_content").select("*").order("created_at", { ascending: false });
6
+ if (error) throw error;
7
+ return data || [];
8
+ };
9
+ const getContentByPersona = async (personaId) => {
10
+ const { data, error } = await supabase.from("generated_content").select("*").eq("persona_id", personaId).order("created_at", { ascending: false });
11
+ if (error) throw error;
12
+ return data || [];
13
+ };
14
+ const getContentByStatus = async (status) => {
15
+ const { data, error } = await supabase.from("generated_content").select("*").eq("status", status).order("created_at", { ascending: false });
16
+ if (error) throw error;
17
+ return data || [];
18
+ };
19
+ const createContent = async (content) => {
20
+ const { data, error } = await supabase.from("generated_content").insert(content).select().single();
21
+ if (error) throw error;
22
+ return data;
23
+ };
24
+ const updateContent = async (id, updates) => {
25
+ const { data, error } = await supabase.from("generated_content").update(updates).eq("id", id).select().single();
26
+ if (error) throw error;
27
+ return data;
28
+ };
29
+ const deleteContent = async (id) => {
30
+ const { error } = await supabase.from("generated_content").delete().eq("id", id);
31
+ if (error) throw error;
32
+ return true;
33
+ };
34
+ const publishContent = async (id) => {
35
+ return updateContent(id, { status: "published", published_at: (/* @__PURE__ */ new Date()).toISOString() });
36
+ };
37
+ return {
38
+ fetchContent,
39
+ getContentByPersona,
40
+ getContentByStatus,
41
+ createContent,
42
+ updateContent,
43
+ deleteContent,
44
+ publishContent
45
+ };
46
+ };
@@ -0,0 +1,8 @@
1
+ export declare const useAdminPersonas: () => {
2
+ fetchPersonas: () => Promise<any>;
3
+ fetchPersonaById: (id: string) => Promise<any>;
4
+ createPersona: (persona: any) => Promise<any>;
5
+ updatePersona: (id: string, updates: any) => Promise<any>;
6
+ deletePersona: (id: string) => Promise<boolean>;
7
+ archivePersona: (id: string) => Promise<any>;
8
+ };
@@ -0,0 +1,31 @@
1
+ import { useSupabaseClient } from "#imports";
2
+ export const useAdminPersonas = () => {
3
+ const supabase = useSupabaseClient();
4
+ const fetchPersonas = async () => {
5
+ const { data, error } = await supabase.from("personas").select("*").eq("is_archived", false).order("created_at", { ascending: false });
6
+ if (error) throw error;
7
+ return data || [];
8
+ };
9
+ const fetchPersonaById = async (id) => {
10
+ const { data, error } = await supabase.from("personas").select("*").eq("id", id).single();
11
+ if (error) throw error;
12
+ return data;
13
+ };
14
+ const createPersona = async (persona) => {
15
+ const { data, error } = await supabase.from("personas").insert(persona).select().single();
16
+ if (error) throw error;
17
+ return data;
18
+ };
19
+ const updatePersona = async (id, updates) => {
20
+ const { data, error } = await supabase.from("personas").update(updates).eq("id", id).select().single();
21
+ if (error) throw error;
22
+ return data;
23
+ };
24
+ const deletePersona = async (id) => {
25
+ const { error } = await supabase.from("personas").delete().eq("id", id);
26
+ if (error) throw error;
27
+ return true;
28
+ };
29
+ const archivePersona = async (id) => updatePersona(id, { is_archived: true });
30
+ return { fetchPersonas, fetchPersonaById, createPersona, updatePersona, deletePersona, archivePersona };
31
+ };
@@ -0,0 +1,6 @@
1
+ export declare const useAdminProducts: () => {
2
+ fetchProducts: () => Promise<any>;
3
+ createProduct: (product: any) => Promise<any>;
4
+ updateProduct: (id: string, product: any) => Promise<any>;
5
+ deleteProduct: (id: string) => Promise<void>;
6
+ };
@@ -0,0 +1,24 @@
1
+ import { useSupabaseClient } from "#imports";
2
+ export const useAdminProducts = () => {
3
+ const supabase = useSupabaseClient();
4
+ const fetchProducts = async () => {
5
+ const { data, error } = await supabase.from("products").select("*").order("priority", { ascending: false }).order("created_at", { ascending: false });
6
+ if (error) throw error;
7
+ return data || [];
8
+ };
9
+ const createProduct = async (product) => {
10
+ const { data, error } = await supabase.from("products").insert([product]).select().single();
11
+ if (error) throw error;
12
+ return data;
13
+ };
14
+ const updateProduct = async (id, product) => {
15
+ const { data, error } = await supabase.from("products").update(product).eq("id", id).select().single();
16
+ if (error) throw error;
17
+ return data;
18
+ };
19
+ const deleteProduct = async (id) => {
20
+ const { error } = await supabase.from("products").delete().eq("id", id);
21
+ if (error) throw error;
22
+ };
23
+ return { fetchProducts, createProduct, updateProduct, deleteProduct };
24
+ };
@@ -0,0 +1,10 @@
1
+ export declare const useAdminStrategies: () => {
2
+ fetchStrategies: () => Promise<any>;
3
+ fetchStrategyById: (id: string) => Promise<any>;
4
+ createStrategy: (strategy: any) => Promise<any>;
5
+ updateStrategy: (id: string, updates: any) => Promise<any>;
6
+ deleteStrategy: (id: string) => Promise<boolean>;
7
+ fetchStrategyContents: (strategyId: string) => Promise<any>;
8
+ fetchStrategyPersonas: (strategyId: string) => Promise<any>;
9
+ fetchCompleteStrategy: (strategyId: string) => Promise<any>;
10
+ };
@@ -0,0 +1,57 @@
1
+ import { useSupabaseClient } from "#imports";
2
+ export const useAdminStrategies = () => {
3
+ const supabase = useSupabaseClient();
4
+ const fetchStrategies = async () => {
5
+ const { data, error } = await supabase.from("strategies").select("*").order("created_at", { ascending: false });
6
+ if (error) throw error;
7
+ return data || [];
8
+ };
9
+ const fetchStrategyById = async (id) => {
10
+ const { data, error } = await supabase.from("strategies").select("*").eq("id", id).single();
11
+ if (error) throw error;
12
+ return data;
13
+ };
14
+ const createStrategy = async (strategy) => {
15
+ const { data, error } = await supabase.from("strategies").insert(strategy).select().single();
16
+ if (error) throw error;
17
+ return data;
18
+ };
19
+ const updateStrategy = async (id, updates) => {
20
+ const { data, error } = await supabase.from("strategies").update(updates).eq("id", id).select().single();
21
+ if (error) throw error;
22
+ return data;
23
+ };
24
+ const deleteStrategy = async (id) => {
25
+ const { error } = await supabase.from("strategies").delete().eq("id", id);
26
+ if (error) throw error;
27
+ return true;
28
+ };
29
+ const fetchStrategyContents = async (strategyId) => {
30
+ const { data, error } = await supabase.from("strategy_contents").select("*, content:generated_content(*)").eq("strategy_id", strategyId).order("position", { ascending: true });
31
+ if (error) throw error;
32
+ return data || [];
33
+ };
34
+ const fetchStrategyPersonas = async (strategyId) => {
35
+ const { data, error } = await supabase.from("strategy_personas").select("*, persona:personas(*)").eq("strategy_id", strategyId);
36
+ if (error) throw error;
37
+ return data || [];
38
+ };
39
+ const fetchCompleteStrategy = async (strategyId) => {
40
+ const [strategy, contents, personas] = await Promise.all([
41
+ fetchStrategyById(strategyId),
42
+ fetchStrategyContents(strategyId),
43
+ fetchStrategyPersonas(strategyId)
44
+ ]);
45
+ return { ...strategy, contents, personas };
46
+ };
47
+ return {
48
+ fetchStrategies,
49
+ fetchStrategyById,
50
+ createStrategy,
51
+ updateStrategy,
52
+ deleteStrategy,
53
+ fetchStrategyContents,
54
+ fetchStrategyPersonas,
55
+ fetchCompleteStrategy
56
+ };
57
+ };
@@ -0,0 +1,10 @@
1
+ export declare const useAdminTodos: () => {
2
+ fetchTodos: () => Promise<any>;
3
+ getActiveTodos: () => Promise<any>;
4
+ getTodosByDateRange: (startDate: Date, endDate: Date) => Promise<any>;
5
+ createTodo: (todo: any) => Promise<any>;
6
+ updateTodo: (id: string, updates: any) => Promise<any>;
7
+ toggleTodo: (id: string) => Promise<any>;
8
+ deleteTodo: (id: string) => Promise<boolean>;
9
+ isOverdue: (todo: any) => boolean;
10
+ };
@@ -0,0 +1,55 @@
1
+ import { useSupabaseClient } from "#imports";
2
+ export const useAdminTodos = () => {
3
+ const supabase = useSupabaseClient();
4
+ const fetchTodos = async () => {
5
+ const { data, error } = await supabase.from("todos").select("*").order("created_at", { ascending: false });
6
+ if (error) throw error;
7
+ return data || [];
8
+ };
9
+ const getActiveTodos = async () => {
10
+ const { data, error } = await supabase.from("todos").select("*").eq("completed", false).order("due_date", { ascending: true, nullsFirst: false });
11
+ if (error) throw error;
12
+ return data || [];
13
+ };
14
+ const getTodosByDateRange = async (startDate, endDate) => {
15
+ const { data, error } = await supabase.from("todos").select("*").gte("due_date", startDate.toISOString()).lte("due_date", endDate.toISOString()).order("due_date", { ascending: true });
16
+ if (error) throw error;
17
+ return data || [];
18
+ };
19
+ const createTodo = async (todo) => {
20
+ const { data, error } = await supabase.from("todos").insert(todo).select().single();
21
+ if (error) throw error;
22
+ return data;
23
+ };
24
+ const updateTodo = async (id, updates) => {
25
+ const { data, error } = await supabase.from("todos").update(updates).eq("id", id).select().single();
26
+ if (error) throw error;
27
+ return data;
28
+ };
29
+ const toggleTodo = async (id) => {
30
+ const { data: current, error: fetchErr } = await supabase.from("todos").select("completed").eq("id", id).single();
31
+ if (fetchErr) throw fetchErr;
32
+ const { data, error } = await supabase.from("todos").update({ completed: !current.completed }).eq("id", id).select().single();
33
+ if (error) throw error;
34
+ return data;
35
+ };
36
+ const deleteTodo = async (id) => {
37
+ const { error } = await supabase.from("todos").delete().eq("id", id);
38
+ if (error) throw error;
39
+ return true;
40
+ };
41
+ const isOverdue = (todo) => {
42
+ if (!todo.due_date || todo.completed) return false;
43
+ return new Date(todo.due_date) < /* @__PURE__ */ new Date();
44
+ };
45
+ return {
46
+ fetchTodos,
47
+ getActiveTodos,
48
+ getTodosByDateRange,
49
+ createTodo,
50
+ updateTodo,
51
+ toggleTodo,
52
+ deleteTodo,
53
+ isOverdue
54
+ };
55
+ };
@@ -13,10 +13,10 @@ const truncatedId = computed(() => {
13
13
  return id ? `${id.substring(0, 6)}...${id.substring(id.length - 4)}` : "";
14
14
  });
15
15
  const createdAt = computed(
16
- () => user.value?.created_at ? new Date(user.value.created_at).toLocaleDateString(void 0, { dateStyle: "medium" }) : "\u2014"
16
+ () => user.value?.created_at ? new Date(user.value.created_at).toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" }) : "\u2014"
17
17
  );
18
18
  const lastSignIn = computed(
19
- () => user.value?.last_sign_in_at ? new Date(user.value.last_sign_in_at).toLocaleDateString(void 0, { dateStyle: "medium" }) : "\u2014"
19
+ () => user.value?.last_sign_in_at ? new Date(user.value.last_sign_in_at).toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" }) : "\u2014"
20
20
  );
21
21
  const currentPlan = ref("");
22
22
  const selectedPlan = ref("");
@@ -48,53 +48,59 @@ const updatePlan = async () => {
48
48
  updateSuccess.value = false;
49
49
  }, 3e3);
50
50
  } catch (e) {
51
- updateError.value = e.message || "Error updating plan.";
51
+ updateError.value = e.message || "Erreur lors de la mise \xE0 jour du plan.";
52
52
  } finally {
53
53
  isUpdating.value = false;
54
54
  }
55
55
  };
56
- useHead({ title: "Account \u2014 Admin" });
56
+ useHead({ title: "Mon Compte \u2014 Admin" });
57
57
  </script>
58
58
 
59
59
  <template>
60
- <div data-admin-cms>
61
- <AdminNavbar />
62
- <div class="max-w-3xl mx-auto p-6 space-y-6">
60
+ <AdminDashboardLayout>
61
+ <div class="p-8 max-w-4xl mx-auto space-y-6">
62
+
63
+ <!-- Header -->
64
+ <div class="mb-2">
65
+ <h1 class="text-2xl font-black text-gray-900">Mon compte</h1>
66
+ <p class="text-sm text-gray-400 mt-1">Informations de votre profil et gestion du plan.</p>
67
+ </div>
63
68
 
64
69
  <!-- Profile card -->
65
- <div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
66
- <div class="bg-black px-6 py-5">
67
- <h1 class="text-xl font-bold text-white">Profile</h1>
70
+ <div class="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden">
71
+ <div class="px-7 py-5 border-b border-gray-100">
72
+ <h2 class="text-sm font-black text-gray-900 uppercase tracking-widest text-[11px] text-gray-400">Profil</h2>
68
73
  </div>
69
- <div class="p-6">
70
- <div v-if="!user" class="text-center py-8 text-gray-400">Not logged in.</div>
74
+ <div class="p-7">
75
+ <div v-if="!user" class="py-10 text-center text-gray-400 text-sm">Non connecté.</div>
71
76
  <div v-else class="space-y-6">
72
- <div class="flex items-center gap-4 pb-4 border-b border-gray-100">
73
- <div class="w-14 h-14 rounded-full bg-black flex items-center justify-center text-white font-bold text-lg">
77
+ <div class="flex items-center gap-4 pb-5 border-b border-gray-100">
78
+ <div class="w-14 h-14 rounded-2xl bg-[#0d0d0d] flex items-center justify-center text-white font-black text-lg shrink-0">
74
79
  {{ userInitials }}
75
80
  </div>
76
81
  <div>
77
- <p class="font-semibold text-gray-800">{{ user.email }}</p>
78
- <p class="text-xs text-gray-400">ID: {{ truncatedId }}</p>
82
+ <p class="font-bold text-gray-900">{{ user.email }}</p>
83
+ <p class="text-[11px] text-gray-400 font-mono mt-0.5">{{ truncatedId }}</p>
79
84
  </div>
80
85
  </div>
81
86
  <div class="grid sm:grid-cols-2 gap-4">
82
- <div class="bg-gray-50 p-4 rounded-lg">
83
- <p class="text-xs text-gray-500 mb-1">Account created</p>
84
- <p class="font-medium text-gray-800">{{ createdAt }}</p>
87
+ <div class="bg-gray-50 rounded-xl p-4">
88
+ <p class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1.5">Compte créé le</p>
89
+ <p class="text-sm font-semibold text-gray-800">{{ createdAt }}</p>
85
90
  </div>
86
- <div class="bg-gray-50 p-4 rounded-lg">
87
- <p class="text-xs text-gray-500 mb-1">Last sign in</p>
88
- <p class="font-medium text-gray-800">{{ lastSignIn }}</p>
91
+ <div class="bg-gray-50 rounded-xl p-4">
92
+ <p class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1.5">Dernière connexion</p>
93
+ <p class="text-sm font-semibold text-gray-800">{{ lastSignIn }}</p>
89
94
  </div>
90
- <div class="bg-gray-50 p-4 rounded-lg">
91
- <p class="text-xs text-gray-500 mb-1">Auth method</p>
92
- <p class="font-medium text-gray-800 capitalize">{{ user.app_metadata?.provider || "email" }}</p>
95
+ <div class="bg-gray-50 rounded-xl p-4">
96
+ <p class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1.5">Méthode d'auth</p>
97
+ <p class="text-sm font-semibold text-gray-800 capitalize">{{ user.app_metadata?.provider || "email" }}</p>
93
98
  </div>
94
- <div class="bg-gray-50 p-4 rounded-lg">
95
- <p class="text-xs text-gray-500 mb-1">Email verified</p>
96
- <p :class="['font-medium', user.email_confirmed_at ? 'text-green-600' : 'text-amber-600']">
97
- {{ user.email_confirmed_at ? "\u2713 Verified" : "\u26A0 Not verified" }}
99
+ <div class="bg-gray-50 rounded-xl p-4">
100
+ <p class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-1.5">Email vérifié</p>
101
+ <p :class="['text-sm font-semibold flex items-center gap-1.5', user.email_confirmed_at ? 'text-green-600' : 'text-amber-500']">
102
+ <span :class="['w-1.5 h-1.5 rounded-full', user.email_confirmed_at ? 'bg-green-500' : 'bg-amber-400']"></span>
103
+ {{ user.email_confirmed_at ? "V\xE9rifi\xE9" : "Non v\xE9rifi\xE9" }}
98
104
  </p>
99
105
  </div>
100
106
  </div>
@@ -102,18 +108,19 @@ useHead({ title: "Account \u2014 Admin" });
102
108
  </div>
103
109
  </div>
104
110
 
105
- <!-- Plan management (only shown if plans are configured) -->
106
- <div v-if="user && plans.length" class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
107
- <div class="bg-gradient-to-r from-blue-600 to-violet-600 px-6 py-5">
108
- <h2 class="text-xl font-bold text-white">Plan</h2>
111
+ <!-- Plan management (only if plans are configured) -->
112
+ <div v-if="user && plans.length" class="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden">
113
+ <div class="px-7 py-5 border-b border-gray-100">
114
+ <h2 class="text-[11px] font-black uppercase tracking-widest text-gray-400">Gestion du plan</h2>
109
115
  </div>
110
- <div class="p-6">
116
+ <div class="p-7">
111
117
  <!-- Feedback -->
112
- <div v-if="updateError" class="mb-4 p-3 bg-red-50 border border-red-100 text-red-700 rounded-lg text-sm">
118
+ <div v-if="updateError" class="mb-5 p-3.5 bg-red-50 border border-red-100 text-red-600 rounded-xl text-sm font-semibold">
113
119
  {{ updateError }}
114
120
  </div>
115
- <div v-if="updateSuccess" class="mb-4 p-3 bg-green-50 border border-green-100 text-green-700 rounded-lg text-sm">
116
- Plan updated successfully.
121
+ <div v-if="updateSuccess" class="mb-5 p-3.5 bg-green-50 border border-green-100 text-green-600 rounded-xl text-sm font-semibold flex items-center gap-2">
122
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>
123
+ Plan mis à jour avec succès.
117
124
  </div>
118
125
 
119
126
  <!-- Plan cards -->
@@ -123,33 +130,34 @@ useHead({ title: "Account \u2014 Admin" });
123
130
  :key="plan.value"
124
131
  @click="selectedPlan = plan.value"
125
132
  :class="[
126
- 'relative border-2 rounded-xl p-5 cursor-pointer transition-all',
127
- 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'
133
+ 'relative border-2 rounded-2xl p-5 cursor-pointer transition-all',
134
+ selectedPlan === plan.value ? 'border-[#3d35ff] bg-[#3d35ff]/5' : currentPlan === plan.value ? 'border-green-500 bg-green-50' : 'border-gray-100 hover:border-[#3d35ff]/30 hover:shadow-md'
128
135
  ]"
129
136
  >
130
- <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>
131
- <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>
132
- <h3 class="font-bold text-gray-900 mb-1">{{ plan.title }}</h3>
133
- <p v-if="plan.subtitle" class="text-xs text-gray-500 mb-3">{{ plan.subtitle }}</p>
134
- <p class="text-xl font-bold text-gray-900 mb-3">{{ plan.price }}</p>
135
- <ul class="space-y-1">
136
- <li v-for="feature in plan.features" :key="feature" class="text-xs text-gray-600 flex items-start gap-1.5">
137
- <span class="text-green-500 mt-0.5">✓</span> {{ feature }}
137
+ <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">Actuel</div>
138
+ <div v-else-if="plan.popular" class="absolute -top-2 -right-2 bg-[#3d35ff] text-white text-[9px] font-black px-2 py-0.5 rounded-full uppercase">Populaire</div>
139
+ <h3 class="font-black text-gray-900 mb-1">{{ plan.title }}</h3>
140
+ <p v-if="plan.subtitle" class="text-[11px] text-gray-400 mb-3">{{ plan.subtitle }}</p>
141
+ <p class="text-xl font-black text-gray-900 mb-4">{{ plan.price }}</p>
142
+ <ul class="space-y-1.5">
143
+ <li v-for="feature in plan.features" :key="feature" class="text-[11px] text-gray-600 flex items-start gap-1.5">
144
+ <svg class="w-3 h-3 text-green-500 mt-0.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>
145
+ {{ feature }}
138
146
  </li>
139
147
  </ul>
140
148
  </div>
141
149
  </div>
142
150
 
143
151
  <!-- Update button -->
144
- <div v-if="selectedPlan && selectedPlan !== currentPlan" class="mt-6 text-center">
152
+ <div v-if="selectedPlan && selectedPlan !== currentPlan" class="mt-6">
145
153
  <button @click="updatePlan" :disabled="isUpdating"
146
- class="bg-blue-600 text-white px-8 py-3 rounded-lg font-medium hover:bg-blue-700 transition disabled:opacity-50">
147
- {{ isUpdating ? "Updating..." : `Switch to ${plans.find((p) => p.value === selectedPlan)?.title}` }}
154
+ class="px-6 py-2.5 bg-[#3d35ff] text-white rounded-xl text-sm font-bold hover:bg-[#2f28cc] transition disabled:opacity-50 shadow-lg shadow-[#3d35ff]/20">
155
+ {{ isUpdating ? "Mise \xE0 jour..." : `Changer pour ${plans.find((p) => p.value === selectedPlan)?.title}` }}
148
156
  </button>
149
157
  </div>
150
158
  </div>
151
159
  </div>
152
160
 
153
161
  </div>
154
- </div>
162
+ </AdminDashboardLayout>
155
163
  </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: any;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;