@osimart/create-storefront 1.0.9 → 1.0.10
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.
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* /categories/<slug> CLASSIFICATION (resource.type = CATEGORY)
|
|
14
14
|
* /collections/<slug> CLASSIFICATION (resource.type = COLLECTION)
|
|
15
15
|
* /products/<slug> PRODUCT
|
|
16
|
-
* /pages/<handle>
|
|
16
|
+
* /<handle> PAGE (one segment; /pages/<handle> 301s here)
|
|
17
17
|
* /search SEARCH
|
|
18
18
|
* /cart CART
|
|
19
19
|
* /account ACCOUNT
|
|
@@ -45,7 +45,7 @@ export function useMenu() {
|
|
|
45
45
|
return `/products/${relation.product.slugified_name}`;
|
|
46
46
|
}
|
|
47
47
|
if (relation.page?.handle) {
|
|
48
|
-
return
|
|
48
|
+
return `/${relation.page.handle}`;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
return "/";
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* /categories/<slug> CLASSIFICATION (already matched)
|
|
19
19
|
* /collections/<slug> CLASSIFICATION (already matched)
|
|
20
|
-
*
|
|
20
|
+
* /<handle> PAGE (one segment only)
|
|
21
21
|
* /<category>/<slug> PRODUCT ← needs adding, matched LAST so it
|
|
22
22
|
* can't shadow the prefixes above
|
|
23
23
|
*
|
|
@@ -14,7 +14,7 @@ import { useProductResource } from "#osi/composables/useProductDetail";
|
|
|
14
14
|
*
|
|
15
15
|
* /categories/<slug> CLASSIFICATION
|
|
16
16
|
* /collections/<slug> CLASSIFICATION
|
|
17
|
-
*
|
|
17
|
+
* /<handle> PAGE (one segment, never meets this route)
|
|
18
18
|
* /products/<slug> PRODUCT (keep — uncategorised products use it)
|
|
19
19
|
* /<category>/<slug> PRODUCT ← last
|
|
20
20
|
*
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<SectionRenderer v-if="data?.sections?.length" :sections="data.sections" />
|
|
4
|
+
|
|
5
|
+
<div v-else-if="!error" class="osi-empty">
|
|
6
|
+
<p>This page hasn't been set up yet.</p>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div v-else class="osi-empty">
|
|
10
|
+
<p>We couldn't load this page.</p>
|
|
11
|
+
<pre v-if="isDev" class="osi-empty-detail">{{ error.message }}</pre>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
<script setup>
|
|
16
|
+
/**
|
|
17
|
+
* A merchant page — `/<handle>` (Shopify's /pages/<handle>, without the prefix).
|
|
18
|
+
*
|
|
19
|
+
* One segment only. Nuxt matches static routes first (checkout, login,
|
|
20
|
+
* search…), then this, then [...slug] — so a platform route always wins, and
|
|
21
|
+
* the backend refuses page handles that would collide with one
|
|
22
|
+
* (RESERVED_HANDLES).
|
|
23
|
+
*
|
|
24
|
+
* Because this catches every unknown one-segment path (/whatever), only a PAGE
|
|
25
|
+
* payload counts. Anything else — NOT_FOUND, a 404 — is a real 404, drawn by
|
|
26
|
+
* error.vue with the theme's 404 page and a 404 status.
|
|
27
|
+
*
|
|
28
|
+
* The old /pages/<handle> URLs 301 here (pages/pages/[handle].vue).
|
|
29
|
+
*/
|
|
30
|
+
import { useRenderContext } from '#osi/composables/useRenderContext';
|
|
31
|
+
|
|
32
|
+
definePageMeta({ name: 'page' });
|
|
33
|
+
|
|
34
|
+
const route = useRoute();
|
|
35
|
+
const { render, store } = useStorefront();
|
|
36
|
+
const { setRenderContext, resource } = useRenderContext();
|
|
37
|
+
const origin = useRequestURL().origin;
|
|
38
|
+
|
|
39
|
+
const { data, error } = await useAsyncData(
|
|
40
|
+
`render:${route.path}`,
|
|
41
|
+
() => render(route.path)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const templateName = (payload) => {
|
|
45
|
+
const t = payload?.template;
|
|
46
|
+
return String((t && typeof t === 'object' ? t.name : t) ?? '').toUpperCase();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const status = error.value?.status ?? error.value?.statusCode;
|
|
50
|
+
const isPage = !!data.value && templateName(data.value) === 'PAGE';
|
|
51
|
+
|
|
52
|
+
if (status === 404 || (!error.value && !isPage)) {
|
|
53
|
+
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setRenderContext(data.value);
|
|
57
|
+
watch(data, (payload) => setRenderContext(payload));
|
|
58
|
+
|
|
59
|
+
const isDev = import.meta.dev;
|
|
60
|
+
|
|
61
|
+
const seo = computed(() => data.value?.seo ?? {});
|
|
62
|
+
|
|
63
|
+
useHead({
|
|
64
|
+
title: () => seo.value.title_full ?? seo.value.title ?? resource.value?.name ?? store.value?.name ?? '',
|
|
65
|
+
titleTemplate: (title) => title ?? '',
|
|
66
|
+
meta: () => [
|
|
67
|
+
{ name: 'description', content: seo.value.description ?? undefined },
|
|
68
|
+
{ property: 'og:title', content: seo.value.title_full ?? seo.value.title ?? undefined },
|
|
69
|
+
{ property: 'og:description', content: seo.value.description ?? undefined },
|
|
70
|
+
{ property: 'og:image', content: seo.value.image ?? undefined },
|
|
71
|
+
{ name: 'robots', content: seo.value.no_index ? 'noindex, nofollow' : 'index, follow' },
|
|
72
|
+
],
|
|
73
|
+
link: () => seo.value.canonical_path
|
|
74
|
+
? [{ rel: 'canonical', href: `${origin}${seo.value.canonical_path}` }]
|
|
75
|
+
: [],
|
|
76
|
+
});
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<style scoped>
|
|
80
|
+
.osi-empty { padding: 6rem 1rem; text-align: center; color: var(--osi-text); opacity: 0.7; }
|
|
81
|
+
.osi-empty-detail { margin-top: 1rem; font-size: 12px; white-space: pre-wrap; }
|
|
82
|
+
</style>
|
|
@@ -1,58 +1,20 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<SectionRenderer v-if="data?.sections?.length" :sections="data.sections" />
|
|
4
|
-
|
|
5
|
-
<div v-else-if="!error" class="osi-empty">
|
|
6
|
-
<p>This page hasn't been set up yet.</p>
|
|
7
|
-
</div>
|
|
8
|
-
|
|
9
|
-
<div v-else class="osi-empty">
|
|
10
|
-
<p>We couldn't load this page.</p>
|
|
11
|
-
<pre v-if="isDev" class="osi-empty-detail">{{ error.message }}</pre>
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
</template>
|
|
15
1
|
<script setup>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
() =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
setRenderContext(data.value);
|
|
32
|
-
watch(data, (payload) => setRenderContext(payload));
|
|
33
|
-
|
|
34
|
-
const isDev = import.meta.dev;
|
|
35
|
-
|
|
36
|
-
const seo = computed(() => data.value?.seo ?? {});
|
|
37
|
-
const origin = useRequestURL().origin;
|
|
38
|
-
|
|
39
|
-
useHead({
|
|
40
|
-
title: () => seo.value.title_full ?? seo.value.title ?? resource.value?.name ?? store.value?.name ?? '',
|
|
41
|
-
titleTemplate: (title) => title ?? '',
|
|
42
|
-
meta: () => [
|
|
43
|
-
{ name: 'description', content: seo.value.description ?? undefined },
|
|
44
|
-
{ property: 'og:title', content: seo.value.title_full ?? seo.value.title ?? undefined },
|
|
45
|
-
{ property: 'og:description', content: seo.value.description ?? undefined },
|
|
46
|
-
{ property: 'og:image', content: seo.value.image ?? undefined },
|
|
47
|
-
{ name: 'robots', content: seo.value.no_index ? 'noindex, nofollow' : 'index, follow' },
|
|
48
|
-
],
|
|
49
|
-
link: () => seo.value.canonical_path
|
|
50
|
-
? [{ rel: 'canonical', href: `${origin}${seo.value.canonical_path}` }]
|
|
51
|
-
: [],
|
|
2
|
+
/**
|
|
3
|
+
* Old page URL — `/pages/<handle>` → `/<handle>`, 301.
|
|
4
|
+
*
|
|
5
|
+
* Kept so links already shared, indexed or saved in a menu's custom URL keep
|
|
6
|
+
* working. The redirect happens in route middleware, before anything renders,
|
|
7
|
+
* so the server answers with the 301 itself (good for search engines).
|
|
8
|
+
*/
|
|
9
|
+
definePageMeta({
|
|
10
|
+
middleware: (to) =>
|
|
11
|
+
navigateTo(
|
|
12
|
+
{ path: `/${to.params.handle}`, query: to.query, hash: to.hash },
|
|
13
|
+
{ redirectCode: 301 }
|
|
14
|
+
),
|
|
52
15
|
});
|
|
53
16
|
</script>
|
|
54
17
|
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
</style>
|
|
18
|
+
<template>
|
|
19
|
+
<div />
|
|
20
|
+
</template>
|
package/package.json
CHANGED