@crm-market/template-shared 1.0.5 → 1.0.6
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/composables/useCategories.ts +10 -6
- package/composables/useSiteConfig.ts +30 -19
- package/package.json +1 -1
- package/plugins/site-init.ts +33 -0
- package/plugins/site-init.client.ts +0 -29
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useRuntimeConfig } from '#imports';
|
|
1
|
+
import { computed } from 'vue';
|
|
2
|
+
import { useState, useRuntimeConfig } from '#imports';
|
|
3
3
|
import { getApiBaseUrl } from '../utils/api';
|
|
4
4
|
import { useSiteConfig } from './useSiteConfig';
|
|
5
5
|
|
|
@@ -15,16 +15,20 @@ interface Category {
|
|
|
15
15
|
order?: number;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const categories = ref<Category[]>([]);
|
|
19
|
-
const loading = ref(false);
|
|
20
|
-
const error = ref<string | null>(null);
|
|
21
|
-
|
|
22
18
|
export const useCategories = () => {
|
|
19
|
+
// useState — SSR-safe, дані передаються клієнту через гідрацію
|
|
20
|
+
const categories = useState<Category[]>('categories', () => []);
|
|
21
|
+
const loading = useState<boolean>('categories-loading', () => false);
|
|
22
|
+
const error = useState<string | null>('categories-error', () => null);
|
|
23
|
+
|
|
23
24
|
const config = useRuntimeConfig();
|
|
24
25
|
const { publicApiToken } = useSiteConfig();
|
|
25
26
|
|
|
26
27
|
// Fetch all categories
|
|
27
28
|
const fetchCategories = async () => {
|
|
29
|
+
// Якщо вже завантажено — не перезавантажуємо
|
|
30
|
+
if (categories.value.length > 0) return categories.value;
|
|
31
|
+
|
|
28
32
|
loading.value = true;
|
|
29
33
|
error.value = null;
|
|
30
34
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useRuntimeConfig, useRoute } from '#imports';
|
|
1
|
+
import { computed } from 'vue';
|
|
2
|
+
import { useState, useRuntimeConfig, useRoute, useRequestHeaders } from '#imports';
|
|
3
3
|
import { getApiBaseUrl } from '../utils/api';
|
|
4
4
|
|
|
5
5
|
interface ColorScheme {
|
|
@@ -55,29 +55,40 @@ interface SiteConfig {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
const siteConfig = ref<SiteConfig | null>(null);
|
|
59
|
-
const loading = ref(false);
|
|
60
|
-
const error = ref<string | null>(null);
|
|
61
|
-
|
|
62
58
|
export const useSiteConfig = () => {
|
|
59
|
+
// useState — SSR-safe, дані передаються клієнту через гідрацію
|
|
60
|
+
const siteConfig = useState<SiteConfig | null>('site-config', () => null);
|
|
61
|
+
const loading = useState<boolean>('site-config-loading', () => false);
|
|
62
|
+
const error = useState<string | null>('site-config-error', () => null);
|
|
63
|
+
|
|
63
64
|
const config = useRuntimeConfig();
|
|
64
|
-
const route = useRoute();
|
|
65
65
|
|
|
66
|
-
//
|
|
66
|
+
// Отримати subdomain з host — працює і на SSR, і на клієнті
|
|
67
67
|
const getSubdomain = () => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
let hostname = '';
|
|
69
|
+
|
|
70
|
+
if (import.meta.server) {
|
|
71
|
+
// SSR: отримуємо host з request headers
|
|
72
|
+
const headers = useRequestHeaders(['host', 'x-forwarded-host']);
|
|
73
|
+
hostname = (headers['x-forwarded-host'] || headers['host'] || '').split(':')[0];
|
|
74
|
+
} else {
|
|
75
|
+
// Client: з window.location
|
|
76
|
+
hostname = window.location.hostname;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const parts = hostname.split('.');
|
|
80
|
+
// Формат: subdomain.domain.com або subdomain.domain.com.ua
|
|
81
|
+
if (parts.length >= 3) {
|
|
82
|
+
return parts[0];
|
|
75
83
|
}
|
|
76
84
|
return null;
|
|
77
85
|
};
|
|
78
86
|
|
|
79
87
|
// Fetch site configuration from API
|
|
80
88
|
const fetchConfig = async (subdomain?: string) => {
|
|
89
|
+
// Якщо вже завантажено — не перезавантажуємо
|
|
90
|
+
if (siteConfig.value && !subdomain) return siteConfig.value;
|
|
91
|
+
|
|
81
92
|
loading.value = true;
|
|
82
93
|
error.value = null;
|
|
83
94
|
|
|
@@ -113,7 +124,7 @@ export const useSiteConfig = () => {
|
|
|
113
124
|
|
|
114
125
|
const { colors, fonts, customCSS } = siteConfig.value.customization;
|
|
115
126
|
|
|
116
|
-
if (
|
|
127
|
+
if (import.meta.client && colors) {
|
|
117
128
|
const root = document.documentElement;
|
|
118
129
|
|
|
119
130
|
if (colors.primary) {
|
|
@@ -132,7 +143,7 @@ export const useSiteConfig = () => {
|
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
145
|
|
|
135
|
-
if (
|
|
146
|
+
if (import.meta.client && fonts) {
|
|
136
147
|
const root = document.documentElement;
|
|
137
148
|
|
|
138
149
|
if (fonts.headingFont) {
|
|
@@ -150,7 +161,7 @@ export const useSiteConfig = () => {
|
|
|
150
161
|
}
|
|
151
162
|
|
|
152
163
|
// Inject custom CSS
|
|
153
|
-
if (
|
|
164
|
+
if (import.meta.client && customCSS) {
|
|
154
165
|
let styleEl = document.getElementById('custom-site-css');
|
|
155
166
|
if (!styleEl) {
|
|
156
167
|
styleEl = document.createElement('style');
|
|
@@ -168,7 +179,7 @@ export const useSiteConfig = () => {
|
|
|
168
179
|
const { siteName, siteDescription, logoUrl } = siteConfig.value;
|
|
169
180
|
const seo = siteConfig.value.customization?.content?.seo;
|
|
170
181
|
|
|
171
|
-
if (
|
|
182
|
+
if (import.meta.client) {
|
|
172
183
|
if (siteName) {
|
|
173
184
|
document.title = siteName;
|
|
174
185
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from '#app';
|
|
2
|
+
import { useSiteConfig } from '../composables/useSiteConfig';
|
|
3
|
+
import { useCategories } from '../composables/useCategories';
|
|
4
|
+
|
|
5
|
+
export default defineNuxtPlugin(async () => {
|
|
6
|
+
const { fetchConfig, siteConfig, error, applyCustomization } = useSiteConfig();
|
|
7
|
+
const { fetchCategories } = useCategories();
|
|
8
|
+
|
|
9
|
+
// SSR: завантажуємо конфіг і категорії — дані передаються клієнту через useState
|
|
10
|
+
// Client: якщо дані вже є від SSR (гідрація) — fetchConfig пропускає повторний запит
|
|
11
|
+
try {
|
|
12
|
+
await fetchConfig();
|
|
13
|
+
await fetchCategories();
|
|
14
|
+
} catch (e) {
|
|
15
|
+
console.error('Failed to initialize site:', e);
|
|
16
|
+
|
|
17
|
+
if (import.meta.client && error.value) {
|
|
18
|
+
console.error('Site configuration error:', error.value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Client-only: застосувати CSS кастомізацію після гідрації
|
|
23
|
+
if (import.meta.client && siteConfig.value) {
|
|
24
|
+
applyCustomization();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Client-only: завантажити кошик з localStorage
|
|
28
|
+
if (import.meta.client) {
|
|
29
|
+
const { useCart } = await import('../composables/useCart');
|
|
30
|
+
const { loadCart } = useCart();
|
|
31
|
+
loadCart();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { defineNuxtPlugin } from '#app';
|
|
2
|
-
import { useSiteConfig } from '../composables/useSiteConfig';
|
|
3
|
-
import { useCart } from '../composables/useCart';
|
|
4
|
-
import { useCategories } from '../composables/useCategories';
|
|
5
|
-
|
|
6
|
-
export default defineNuxtPlugin(async () => {
|
|
7
|
-
const { fetchConfig, error } = useSiteConfig();
|
|
8
|
-
const { loadCart } = useCart();
|
|
9
|
-
const { fetchCategories } = useCategories();
|
|
10
|
-
|
|
11
|
-
// Initialize cart from localStorage
|
|
12
|
-
loadCart();
|
|
13
|
-
|
|
14
|
-
// Fetch site configuration
|
|
15
|
-
try {
|
|
16
|
-
await fetchConfig();
|
|
17
|
-
|
|
18
|
-
// Load categories after config is loaded
|
|
19
|
-
await fetchCategories();
|
|
20
|
-
} catch (e) {
|
|
21
|
-
console.error('Failed to initialize site:', e);
|
|
22
|
-
|
|
23
|
-
// If site config fails to load, redirect to error page or show message
|
|
24
|
-
if (process.client && error.value) {
|
|
25
|
-
// You can customize this behavior
|
|
26
|
-
console.error('Site configuration error:', error.value);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
});
|