create-velocity-astro 1.0.2 → 1.0.4
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/README.md +34 -9
- package/dist/index.js +125 -41
- package/dist/index.js.map +1 -1
- package/package.json +4 -5
- package/templates/base/src/pages/404.astro +29 -0
- package/templates/base/src/pages/blog/[...slug].astro +32 -0
- package/templates/base/src/pages/blog/index.astro +58 -0
- package/templates/base/src/pages/index.astro +36 -0
- package/templates/i18n/src/components/blog/RelatedPosts.astro +73 -0
- package/templates/i18n/src/content/blog/en/welcome-to-velocity.mdx +40 -0
- package/templates/i18n/src/content/blog/es/bienvenido-a-velocity.mdx +40 -0
- package/templates/i18n/src/content/blog/fr/bienvenue-sur-velocity.mdx +40 -0
- package/templates/i18n/src/i18n/translations/en.ts +15 -0
- package/templates/i18n/src/i18n/translations/es.ts +15 -0
- package/templates/i18n/src/i18n/translations/fr.ts +15 -0
- package/templates/i18n/src/layouts/BlogLayout.astro +133 -0
- package/templates/i18n/src/pages/[lang]/blog/[...slug].astro +55 -0
- package/templates/i18n/src/pages/[lang]/blog/index.astro +176 -0
- package/templates/i18n/src/pages/blog/[...slug].astro +36 -0
- package/templates/i18n/src/pages/blog/index.astro +160 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
import BlogLayout from '@/layouts/BlogLayout.astro';
|
|
3
|
+
import { getCollection, render } from 'astro:content';
|
|
4
|
+
import { defaultLocale } from '@/i18n/config';
|
|
5
|
+
|
|
6
|
+
const locale = defaultLocale;
|
|
7
|
+
|
|
8
|
+
export async function getStaticPaths() {
|
|
9
|
+
const posts = await getCollection('blog', ({ data }) => {
|
|
10
|
+
return data.locale === 'en' && (import.meta.env.PROD ? data.draft !== true : true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return posts.map((post) => ({
|
|
14
|
+
params: { slug: post.id.replace('en/', '') },
|
|
15
|
+
props: { post },
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { post } = Astro.props;
|
|
20
|
+
const { Content } = await render(post);
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<BlogLayout
|
|
24
|
+
title={post.data.title}
|
|
25
|
+
description={post.data.description}
|
|
26
|
+
publishedAt={post.data.publishedAt}
|
|
27
|
+
updatedAt={post.data.updatedAt}
|
|
28
|
+
author={post.data.author}
|
|
29
|
+
image={post.data.image}
|
|
30
|
+
imageAlt={post.data.imageAlt}
|
|
31
|
+
tags={post.data.tags}
|
|
32
|
+
slug={post.id}
|
|
33
|
+
locale={locale}
|
|
34
|
+
>
|
|
35
|
+
<Content />
|
|
36
|
+
</BlogLayout>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
import BaseLayout from '@/layouts/BaseLayout.astro';
|
|
3
|
+
import Navbar from '@/components/landing/Navbar.astro';
|
|
4
|
+
import LandingFooter from '@/components/landing/LandingFooter.astro';
|
|
5
|
+
import BlogCard from '@/components/blog/BlogCard.astro';
|
|
6
|
+
import Button from '@/components/ui/Button.astro';
|
|
7
|
+
import Icon from '@/components/ui/Icon.astro';
|
|
8
|
+
import { getCollection } from 'astro:content';
|
|
9
|
+
import { defaultLocale } from '@/i18n/config';
|
|
10
|
+
import { useTranslations } from '@/i18n/index';
|
|
11
|
+
|
|
12
|
+
const locale = defaultLocale;
|
|
13
|
+
const t = useTranslations(locale);
|
|
14
|
+
|
|
15
|
+
// Get all published posts
|
|
16
|
+
const allPosts = await getCollection('blog', ({ data }) => {
|
|
17
|
+
return import.meta.env.PROD ? data.draft !== true : true;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Filter by default locale and sort by date
|
|
21
|
+
const posts = allPosts
|
|
22
|
+
.filter((post) => post.data.locale === locale)
|
|
23
|
+
.sort((a, b) => b.data.publishedAt.valueOf() - a.data.publishedAt.valueOf());
|
|
24
|
+
|
|
25
|
+
// Separate featured posts (shown in hero section)
|
|
26
|
+
const featuredPosts = posts.filter((post) => post.data.featured);
|
|
27
|
+
// Non-featured posts for the main listing
|
|
28
|
+
const nonFeaturedPosts = posts.filter((post) => !post.data.featured);
|
|
29
|
+
// If ALL posts are featured, show them all in the main listing too (to avoid empty section)
|
|
30
|
+
const regularPosts = nonFeaturedPosts.length > 0 ? nonFeaturedPosts : posts;
|
|
31
|
+
|
|
32
|
+
const getPostUrl = (postId: string) => {
|
|
33
|
+
// Remove locale prefix from id (e.g., "en/welcome-to-velocity" -> "welcome-to-velocity")
|
|
34
|
+
const slug = postId.replace(`${locale}/`, '');
|
|
35
|
+
return `/blog/${slug}`;
|
|
36
|
+
};
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<BaseLayout
|
|
40
|
+
title={t('blog.title')}
|
|
41
|
+
description={t('blog.description')}
|
|
42
|
+
>
|
|
43
|
+
<Navbar slot="header" />
|
|
44
|
+
|
|
45
|
+
<!-- Hero Section with decorative background -->
|
|
46
|
+
<section class="relative overflow-hidden bg-background-secondary border-b border-border">
|
|
47
|
+
<!-- Decorative grid pattern -->
|
|
48
|
+
<div class="absolute inset-0 bg-grid-pattern [mask-image:linear-gradient(to_bottom,white_50%,transparent)] pointer-events-none opacity-30"></div>
|
|
49
|
+
|
|
50
|
+
<!-- Decorative blur blob -->
|
|
51
|
+
<div class="absolute top-0 left-1/4 w-96 h-96 bg-brand-200/20 dark:bg-brand-800/10 rounded-full blur-3xl pointer-events-none"></div>
|
|
52
|
+
|
|
53
|
+
<div class="relative mx-auto max-w-6xl px-6 py-20 md:py-28">
|
|
54
|
+
<div class="max-w-3xl">
|
|
55
|
+
<div class="mb-4 inline-flex items-center rounded-full border border-border bg-background px-3 py-1 shadow-sm">
|
|
56
|
+
<Icon name="book" size="sm" class="text-brand-500 mr-2" />
|
|
57
|
+
<span class="text-xs font-medium text-foreground-secondary">{t('blog.allPosts')}</span>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<h1 class="font-display text-4xl font-bold tracking-tight text-foreground md:text-5xl lg:text-6xl mb-6">
|
|
61
|
+
{t('blog.title')}
|
|
62
|
+
</h1>
|
|
63
|
+
|
|
64
|
+
<p class="text-xl text-foreground-muted leading-relaxed">
|
|
65
|
+
{t('blog.description')}
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<!-- Featured Posts Section -->
|
|
72
|
+
{featuredPosts.length > 0 && (
|
|
73
|
+
<section class="py-16 bg-background">
|
|
74
|
+
<div class="mx-auto max-w-6xl px-6">
|
|
75
|
+
<h2 class="font-display text-2xl font-bold text-foreground mb-8 flex items-center gap-2">
|
|
76
|
+
<svg class="h-6 w-6 text-brand-500" fill="currentColor" viewBox="0 0 20 20">
|
|
77
|
+
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
|
78
|
+
</svg>
|
|
79
|
+
{t('blog.featured')}
|
|
80
|
+
</h2>
|
|
81
|
+
|
|
82
|
+
<div class="grid gap-6 md:grid-cols-2">
|
|
83
|
+
{featuredPosts.map((post) => (
|
|
84
|
+
<BlogCard
|
|
85
|
+
title={post.data.title}
|
|
86
|
+
description={post.data.description}
|
|
87
|
+
href={getPostUrl(post.id)}
|
|
88
|
+
publishedAt={post.data.publishedAt}
|
|
89
|
+
tags={post.data.tags}
|
|
90
|
+
author={post.data.author}
|
|
91
|
+
image={post.data.image}
|
|
92
|
+
featured={true}
|
|
93
|
+
/>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</section>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
<!-- All Posts Section -->
|
|
101
|
+
<section class="py-16 bg-background-secondary">
|
|
102
|
+
<div class="mx-auto max-w-6xl px-6">
|
|
103
|
+
{(featuredPosts.length > 0 && nonFeaturedPosts.length > 0) && (
|
|
104
|
+
<h2 class="font-display text-2xl font-bold text-foreground mb-8">
|
|
105
|
+
{t('blog.allPosts')}
|
|
106
|
+
</h2>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
{regularPosts.length > 0 ? (
|
|
110
|
+
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
111
|
+
{regularPosts.map((post) => (
|
|
112
|
+
<BlogCard
|
|
113
|
+
title={post.data.title}
|
|
114
|
+
description={post.data.description}
|
|
115
|
+
href={getPostUrl(post.id)}
|
|
116
|
+
publishedAt={post.data.publishedAt}
|
|
117
|
+
tags={post.data.tags}
|
|
118
|
+
author={post.data.author}
|
|
119
|
+
image={post.data.image}
|
|
120
|
+
/>
|
|
121
|
+
))}
|
|
122
|
+
</div>
|
|
123
|
+
) : posts.length === 0 ? (
|
|
124
|
+
<div class="text-center py-16">
|
|
125
|
+
<div class="mx-auto w-16 h-16 rounded-full bg-background flex items-center justify-center mb-4">
|
|
126
|
+
<Icon name="file-text" size="lg" class="text-foreground-muted" />
|
|
127
|
+
</div>
|
|
128
|
+
<p class="text-foreground-muted text-lg">{t('blog.noPosts')}</p>
|
|
129
|
+
</div>
|
|
130
|
+
) : null}
|
|
131
|
+
</div>
|
|
132
|
+
</section>
|
|
133
|
+
|
|
134
|
+
<!-- Newsletter CTA Section -->
|
|
135
|
+
<section class="py-20 bg-surface-invert text-on-invert">
|
|
136
|
+
<div class="mx-auto max-w-3xl px-6 text-center">
|
|
137
|
+
<h2 class="font-display text-3xl font-bold mb-4">
|
|
138
|
+
{t('blog.subscribe')}
|
|
139
|
+
</h2>
|
|
140
|
+
<p class="text-on-invert-secondary text-lg mb-8">
|
|
141
|
+
{t('blog.subscribeDescription')}
|
|
142
|
+
</p>
|
|
143
|
+
|
|
144
|
+
<form class="flex flex-col sm:flex-row gap-4 max-w-md mx-auto">
|
|
145
|
+
<input
|
|
146
|
+
type="email"
|
|
147
|
+
placeholder={t('blog.emailPlaceholder')}
|
|
148
|
+
class="flex-1 h-12 px-4 rounded-md bg-surface-invert-secondary border border-border-invert text-on-invert placeholder:text-on-invert-muted focus:outline-none focus:ring-2 focus:ring-brand-500"
|
|
149
|
+
required
|
|
150
|
+
/>
|
|
151
|
+
<Button variant="primary" size="lg" type="submit">
|
|
152
|
+
{t('blog.subscribeButton')}
|
|
153
|
+
<Icon name="arrow-right" size="sm" />
|
|
154
|
+
</Button>
|
|
155
|
+
</form>
|
|
156
|
+
</div>
|
|
157
|
+
</section>
|
|
158
|
+
|
|
159
|
+
<LandingFooter slot="footer" />
|
|
160
|
+
</BaseLayout>
|