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,137 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const route = useRoute();
|
|
3
|
+
const supabase = useSupabaseClient();
|
|
4
|
+
const config = useRuntimeConfig().public.adminCms;
|
|
5
|
+
const pageType = route.params.type;
|
|
6
|
+
const table = config?.tables?.pages || "pages";
|
|
7
|
+
const typeLabel = computed(() => {
|
|
8
|
+
return config?.pageTypes?.find((t) => t.id === pageType)?.label || pageType.replace(/_/g, " ");
|
|
9
|
+
});
|
|
10
|
+
const { data: pages, refresh } = await useAsyncData(`admin-pages-${pageType}`, async () => {
|
|
11
|
+
const { data } = await supabase.from(table).select("id, title, slug, status, updated_at, seo_config").eq("type", pageType).order("updated_at", { ascending: false });
|
|
12
|
+
return data;
|
|
13
|
+
});
|
|
14
|
+
const isCreating = ref(false);
|
|
15
|
+
const newPageTitle = ref("");
|
|
16
|
+
const handleCreate = async () => {
|
|
17
|
+
if (!newPageTitle.value.trim()) return;
|
|
18
|
+
const slug = newPageTitle.value.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
19
|
+
const { data, error } = await supabase.from(table).insert([{ title: newPageTitle.value, slug, type: pageType, status: "draft", content: [] }]).select();
|
|
20
|
+
if (!error && data) {
|
|
21
|
+
navigateTo(`/admin/editor/${data[0].id}`);
|
|
22
|
+
} else {
|
|
23
|
+
alert(error?.message || "Error: slug may already be in use.");
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const handleDelete = async (id, title) => {
|
|
27
|
+
if (!confirm(`Delete "${title}"? This cannot be undone.`)) return;
|
|
28
|
+
const { error } = await supabase.from(table).delete().eq("id", id);
|
|
29
|
+
if (error) alert("Error: " + error.message);
|
|
30
|
+
else await refresh();
|
|
31
|
+
};
|
|
32
|
+
useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<div data-admin-cms>
|
|
37
|
+
<AdminNavbar />
|
|
38
|
+
<div class="p-8">
|
|
39
|
+
<div class="mb-8">
|
|
40
|
+
<NuxtLink to="/admin" class="text-sm text-gray-500 hover:text-blue-600">← Back to dashboard</NuxtLink>
|
|
41
|
+
<div class="flex justify-between items-end mt-4">
|
|
42
|
+
<div>
|
|
43
|
+
<h1 class="text-3xl font-bold text-gray-900">{{ typeLabel }}</h1>
|
|
44
|
+
<p class="text-gray-500 mt-1">Manage your content and internal linking.</p>
|
|
45
|
+
</div>
|
|
46
|
+
<button
|
|
47
|
+
@click="isCreating = true"
|
|
48
|
+
class="bg-black text-white px-6 py-2.5 rounded-lg font-medium hover:bg-gray-800 transition shadow-lg"
|
|
49
|
+
>
|
|
50
|
+
+ New page
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
|
|
56
|
+
<div v-if="!pages?.length" class="p-20 text-center">
|
|
57
|
+
<p class="text-gray-400">No pages created yet in this category.</p>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<table v-else class="w-full text-left">
|
|
61
|
+
<thead class="bg-gray-50 border-b border-gray-200">
|
|
62
|
+
<tr>
|
|
63
|
+
<th class="px-6 py-4 text-xs font-bold text-gray-400 uppercase">Title & URL</th>
|
|
64
|
+
<th class="px-6 py-4 text-xs font-bold text-gray-400 uppercase">SEO</th>
|
|
65
|
+
<th class="px-6 py-4 text-xs font-bold text-gray-400 uppercase">Status</th>
|
|
66
|
+
<th class="px-6 py-4 text-xs font-bold text-gray-400 uppercase text-right">Actions</th>
|
|
67
|
+
</tr>
|
|
68
|
+
</thead>
|
|
69
|
+
<tbody class="divide-y divide-gray-100">
|
|
70
|
+
<tr v-for="page in pages" :key="page.id" class="hover:bg-gray-50 group transition-colors">
|
|
71
|
+
<td class="px-6 py-4">
|
|
72
|
+
<div class="font-semibold text-gray-800">{{ page.title }}</div>
|
|
73
|
+
<div class="text-xs text-blue-500 font-mono italic">/{{ page.slug }}</div>
|
|
74
|
+
</td>
|
|
75
|
+
<td class="px-6 py-4">
|
|
76
|
+
<span v-if="page.seo_config?.meta_title" class="text-green-600 text-xs flex items-center gap-1.5">
|
|
77
|
+
<span class="w-2 h-2 bg-green-500 rounded-full"></span> Optimized
|
|
78
|
+
</span>
|
|
79
|
+
<span v-else class="text-amber-500 text-xs flex items-center gap-1.5">
|
|
80
|
+
<span class="w-2 h-2 bg-amber-500 rounded-full"></span> Missing meta title
|
|
81
|
+
</span>
|
|
82
|
+
</td>
|
|
83
|
+
<td class="px-6 py-4">
|
|
84
|
+
<span :class="[
|
|
85
|
+
'px-2 py-1 rounded text-[10px] font-bold uppercase tracking-wider',
|
|
86
|
+
page.status === 'published' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600'
|
|
87
|
+
]">{{ page.status }}</span>
|
|
88
|
+
</td>
|
|
89
|
+
<td class="px-6 py-4 text-right">
|
|
90
|
+
<div class="flex justify-end items-center gap-2">
|
|
91
|
+
<NuxtLink
|
|
92
|
+
:to="`/admin/editor/${page.id}`"
|
|
93
|
+
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md text-sm font-medium hover:bg-black hover:text-white transition"
|
|
94
|
+
>
|
|
95
|
+
Edit
|
|
96
|
+
</NuxtLink>
|
|
97
|
+
<button
|
|
98
|
+
@click="handleDelete(page.id, page.title)"
|
|
99
|
+
class="p-2 text-gray-400 hover:text-red-600 transition-colors"
|
|
100
|
+
title="Delete page"
|
|
101
|
+
>
|
|
102
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
103
|
+
<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/>
|
|
104
|
+
</svg>
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
</td>
|
|
108
|
+
</tr>
|
|
109
|
+
</tbody>
|
|
110
|
+
</table>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<!-- Create modal -->
|
|
114
|
+
<div v-if="isCreating" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50">
|
|
115
|
+
<div class="bg-white p-8 rounded-2xl max-w-md w-full shadow-2xl">
|
|
116
|
+
<h3 class="text-xl font-bold mb-4">New {{ typeLabel }}</h3>
|
|
117
|
+
<input
|
|
118
|
+
v-model="newPageTitle"
|
|
119
|
+
type="text"
|
|
120
|
+
placeholder="e.g. My Premium SEO Service"
|
|
121
|
+
class="w-full border border-gray-300 p-3 rounded-lg mb-6 focus:ring-2 focus:ring-black outline-none transition"
|
|
122
|
+
@keyup.enter="handleCreate"
|
|
123
|
+
/>
|
|
124
|
+
<div class="flex gap-3">
|
|
125
|
+
<button @click="isCreating = false;
|
|
126
|
+
newPageTitle = ''" class="flex-1 px-4 py-2 border border-gray-200 rounded-lg hover:bg-gray-50 transition">
|
|
127
|
+
Cancel
|
|
128
|
+
</button>
|
|
129
|
+
<button @click="handleCreate" class="flex-1 px-4 py-2 bg-black text-white rounded-lg hover:bg-gray-800 transition">
|
|
130
|
+
Create
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Captures the host project's body font-family BEFORE the admin scope
|
|
3
|
+
* (data-admin-cms) can shadow it, then stores it in --admin-site-font so
|
|
4
|
+
* the section preview area can revert to the correct host font.
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: any;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useAdminUserStore: any;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineStore } from "pinia";
|
|
2
|
+
export const useAdminUserStore = defineStore("adminUser", {
|
|
3
|
+
state: () => ({
|
|
4
|
+
userData: null,
|
|
5
|
+
loading: false,
|
|
6
|
+
error: null
|
|
7
|
+
}),
|
|
8
|
+
getters: {
|
|
9
|
+
isSubscriptionActive: (state) => state.userData?.is_subscription_active ?? false,
|
|
10
|
+
trialDaysRemaining: (state) => {
|
|
11
|
+
if (!state.userData?.trial_expires_at) return 0;
|
|
12
|
+
const diff = new Date(state.userData.trial_expires_at).getTime() - Date.now();
|
|
13
|
+
return Math.max(0, Math.ceil(diff / (1e3 * 60 * 60 * 24)));
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
actions: {
|
|
17
|
+
async fetchUserData(userId) {
|
|
18
|
+
if (!userId) return;
|
|
19
|
+
this.loading = true;
|
|
20
|
+
this.error = null;
|
|
21
|
+
try {
|
|
22
|
+
const supabase = useSupabaseClient();
|
|
23
|
+
const config = useRuntimeConfig().public.adminCms;
|
|
24
|
+
const table = config?.tables?.users || "users";
|
|
25
|
+
const { data, error } = await supabase.from(table).select("*").eq("id", userId).single();
|
|
26
|
+
if (error) throw error;
|
|
27
|
+
this.userData = data;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
this.error = e.message;
|
|
30
|
+
} finally {
|
|
31
|
+
this.loading = false;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
async signOut() {
|
|
35
|
+
this.loading = true;
|
|
36
|
+
try {
|
|
37
|
+
const supabase = useSupabaseClient();
|
|
38
|
+
await supabase.auth.signOut();
|
|
39
|
+
this.userData = null;
|
|
40
|
+
this.error = null;
|
|
41
|
+
} catch (e) {
|
|
42
|
+
this.error = e.message;
|
|
43
|
+
} finally {
|
|
44
|
+
this.loading = false;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
clearUserData() {
|
|
48
|
+
this.userData = null;
|
|
49
|
+
this.error = null;
|
|
50
|
+
},
|
|
51
|
+
updateUserData(data) {
|
|
52
|
+
this.userData = data;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
package/dist/types.d.mts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
export { default } from './module.mjs'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
interface RuntimeNuxtHooks extends ModuleRuntimeHooks {}
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
declare module '@nuxt/schema' {
|
|
8
|
-
interface NuxtHooks extends ModuleHooks {}
|
|
9
|
-
interface RuntimeConfig extends ModuleRuntimeConfig {}
|
|
10
|
-
interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export * from "./module.mjs"
|
|
3
|
+
export { type ModuleOptions, type PageTypeConfig, type PlanConfig, type SectionConfig, type SectionFieldConfig } from './module.mjs'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cinqcinqdev-seo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "A reusable Nuxt 3 admin CMS module with visual page editor powered by Supabase",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"module": "./dist/module.mjs",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"build": "npm run build:css && nuxt-module-build build",
|
|
18
18
|
"dev": "nuxt-module-build build --stub && nuxt dev playground",
|
|
19
19
|
"dev:build": "nuxt build playground",
|
|
20
|
-
"prepare": "nuxt-module-build build --stub",
|
|
20
|
+
"prepare": "node -e \"if(process.env.npm_command!=='publish')require('child_process').execSync('nuxt-module-build build --stub',{stdio:'inherit'})\"",
|
|
21
21
|
"prepack": "npm run build",
|
|
22
22
|
"release": "npm version patch && npm publish"
|
|
23
23
|
},
|
package/dist/module.d.cts
DELETED