555 lines
23 KiB
JavaScript
555 lines
23 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/*
|
||
* LM USA — Seed Script
|
||
*
|
||
* Usage:
|
||
* PAYLOAD_URL=http://localhost:3000 PAYLOAD_API_KEY=your-key node seed.mjs
|
||
*
|
||
* Creates:
|
||
* - Contact form
|
||
* - Home page /
|
||
* - Virtuell Fiber /virtuell-fiber
|
||
* - VPS Server /vps
|
||
* - Contact /contact
|
||
* - Header navigation
|
||
* - Footer
|
||
*/
|
||
|
||
const PAYLOAD_URL = process.env.PAYLOAD_URL || 'http://localhost:3000'
|
||
const API_KEY = process.env.PAYLOAD_API_KEY
|
||
|
||
if (!API_KEY) {
|
||
console.error('❌ Missing PAYLOAD_API_KEY environment variable')
|
||
process.exit(1)
|
||
}
|
||
|
||
const headers = {
|
||
'Content-Type': 'application/json',
|
||
Authorization: `users API-Key ${API_KEY}`,
|
||
}
|
||
|
||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||
|
||
function richText(text) {
|
||
const paragraphs = text.split('\n').filter(Boolean)
|
||
return {
|
||
root: {
|
||
type: 'root',
|
||
children: paragraphs.map((p) => ({
|
||
type: 'paragraph',
|
||
children: [
|
||
{ type: 'text', text: p, format: 0, detail: 0, mode: 'normal', style: '', version: 1 },
|
||
],
|
||
direction: 'ltr',
|
||
format: '',
|
||
indent: 0,
|
||
version: 1,
|
||
})),
|
||
direction: 'ltr',
|
||
format: '',
|
||
indent: 0,
|
||
version: 1,
|
||
},
|
||
}
|
||
}
|
||
|
||
async function apiPost(collection, data) {
|
||
const res = await fetch(`${PAYLOAD_URL}/api/${collection}`, {
|
||
method: 'POST',
|
||
headers,
|
||
body: JSON.stringify(data),
|
||
})
|
||
if (!res.ok) {
|
||
const err = await res.text()
|
||
throw new Error(`POST /api/${collection} failed (${res.status}): ${err}`)
|
||
}
|
||
return res.json()
|
||
}
|
||
|
||
async function apiGet(collection, query = '') {
|
||
const res = await fetch(`${PAYLOAD_URL}/api/${collection}${query}`, { headers })
|
||
if (!res.ok) {
|
||
const err = await res.text()
|
||
throw new Error(`GET /api/${collection} failed (${res.status}): ${err}`)
|
||
}
|
||
return res.json()
|
||
}
|
||
|
||
async function apiUpdateGlobal(slug, data) {
|
||
const res = await fetch(`${PAYLOAD_URL}/api/globals/${slug}`, {
|
||
method: 'POST',
|
||
headers,
|
||
body: JSON.stringify(data),
|
||
})
|
||
if (!res.ok) {
|
||
const err = await res.text()
|
||
throw new Error(`POST /api/globals/${slug} failed (${res.status}): ${err}`)
|
||
}
|
||
return res.json()
|
||
}
|
||
|
||
async function createPageIfNotExists(slug, data) {
|
||
const existing = await apiGet('pages', `?where[slug][equals]=${slug}&limit=1`)
|
||
if (existing.docs?.length > 0) {
|
||
console.log(` ⏭ Page "${slug}" already exists — skipping`)
|
||
return existing.docs[0]
|
||
}
|
||
const result = await apiPost('pages', data)
|
||
return result.doc
|
||
}
|
||
|
||
// ─── Main ─────────────────────────────────────────────────────────────────────
|
||
|
||
async function seed() {
|
||
console.log(`🌱 Seeding LM USA database at ${PAYLOAD_URL}...`)
|
||
|
||
// ─── 1. Contact Form ───────────────────────────────────────────────────────
|
||
console.log(' → Creating contact form...')
|
||
let contactForm
|
||
const existingForms = await apiGet('forms', '?where[title][equals]=Contact Form&limit=1')
|
||
if (existingForms.docs?.length > 0) {
|
||
console.log(' ⏭ Contact form already exists — skipping')
|
||
contactForm = existingForms.docs[0]
|
||
} else {
|
||
const result = await apiPost('forms', {
|
||
title: 'Contact Form',
|
||
fields: [
|
||
{ name: 'name', label: 'Name', blockType: 'text', required: true, width: 50 },
|
||
{ name: 'company', label: 'Company', blockType: 'text', required: false, width: 50 },
|
||
{ name: 'email', label: 'Email', blockType: 'email', required: true, width: 50 },
|
||
{ name: 'phone', label: 'Phone', blockType: 'text', required: false, width: 50 },
|
||
{ name: 'message', label: 'Message', blockType: 'textarea', required: true },
|
||
],
|
||
submitButtonLabel: 'Send Message',
|
||
confirmationType: 'message',
|
||
confirmationMessage: richText(
|
||
"Thanks for reaching out. We'll get back to you within 24 hours.",
|
||
),
|
||
})
|
||
contactForm = result.doc
|
||
}
|
||
|
||
// ─── 1b. Cart Order Form ──────────────────────────────────────────────────
|
||
console.log(' → Creating cart order form...')
|
||
const existingCartForms = await apiGet('forms', '?where[title][equals]=Cart Order&limit=1')
|
||
if (existingCartForms.docs?.length > 0) {
|
||
console.log(' ⏭ Cart order form already exists — skipping')
|
||
} else {
|
||
await apiPost('forms', {
|
||
title: 'Cart Order',
|
||
fields: [
|
||
{ name: 'company', label: 'Company', blockType: 'text', required: true, width: 50 },
|
||
{ name: 'name', label: 'Contact Name', blockType: 'text', required: true, width: 50 },
|
||
{ name: 'email', label: 'Email', blockType: 'email', required: true, width: 50 },
|
||
{ name: 'phone', label: 'Phone', blockType: 'text', required: false, width: 50 },
|
||
{ name: 'message', label: 'Notes', blockType: 'textarea', required: false },
|
||
{ name: 'items', label: 'Cart Items (JSON)', blockType: 'text', required: false },
|
||
{ name: 'totalMonthly', label: 'Monthly Total', blockType: 'text', required: false },
|
||
{ name: 'currency', label: 'Currency', blockType: 'text', required: false },
|
||
],
|
||
submitButtonLabel: 'Submit Order',
|
||
confirmationType: 'message',
|
||
confirmationMessage: richText(
|
||
"We've received your order and will contact you within 24 hours to confirm.",
|
||
),
|
||
})
|
||
}
|
||
|
||
// ─── 2. Home Page ──────────────────────────────────────────────────────────
|
||
console.log(' → Creating Home page...')
|
||
await createPageIfNotExists('index', {
|
||
title: 'Home',
|
||
slug: 'index',
|
||
_status: 'published',
|
||
publishedAt: new Date().toISOString(),
|
||
layout: [
|
||
// Hero
|
||
// theme: 'light' | 'dark'
|
||
// textColor: 'auto' | 'white' | 'dark'
|
||
{
|
||
blockType: 'lmHero',
|
||
heading: 'Infrastructure Without Borders',
|
||
subheading: 'Layer & Mesh USA',
|
||
body: 'Edge compute, virtual fiber, and managed servers — built for businesses that need sovereign, reliable infrastructure. Configure your setup and get started in minutes.',
|
||
ctaText: 'Explore Products',
|
||
ctaLink: '#products',
|
||
secondaryCtaText: 'Contact Us',
|
||
secondaryCtaLink: '/contact',
|
||
theme: 'dark',
|
||
textColor: 'auto',
|
||
},
|
||
// Link Cards
|
||
// cardStyle: 'outlined' | 'dark' | 'gray' | 'yellow'
|
||
// sectionBackground: 'white' | 'gray' | 'dark'
|
||
// title is textarea (supports \n)
|
||
{
|
||
blockType: 'lmLinkCards',
|
||
heading: 'Choose Your Infrastructure',
|
||
description: 'Two paths to managed IT — hardware at your location or virtual in our cloud.',
|
||
columns: '2',
|
||
cardStyle: 'outlined',
|
||
sectionBackground: 'dark',
|
||
anchorId: 'products',
|
||
cards: [
|
||
{
|
||
title: 'Virtuell Fiber\nEdge compute hardware deployed at your location. Plug in, connect to the mesh — instant private infrastructure.',
|
||
linkLabel: 'Explore VF Enhet',
|
||
linkUrl: '/virtuell-fiber',
|
||
},
|
||
{
|
||
title: 'VPS Servers\nCloud-hosted virtual servers on Layer & Mesh infrastructure. Full root access, instant provisioning, sovereign data.',
|
||
linkLabel: 'Configure a VPS',
|
||
linkUrl: '/vps',
|
||
},
|
||
],
|
||
},
|
||
// Statistics
|
||
// numberColor: 'gradient' | 'yellow' | 'mint' | 'dark' | 'white'
|
||
// sectionBackground: 'white' | 'dark' | 'gray'
|
||
{
|
||
blockType: 'lmStatistics',
|
||
heading: 'Built for Reliability',
|
||
sectionBackground: 'dark',
|
||
numberColor: 'gradient',
|
||
stats: [
|
||
{ number: '99.99%', label: 'Uptime SLA' },
|
||
{ number: '<5ms', label: 'Edge Latency' },
|
||
{ number: '24/7', label: 'Support' },
|
||
{ number: '100%', label: 'Swedish Sovereign' },
|
||
],
|
||
},
|
||
// Swedish CTA
|
||
// sectionBackground: 'dark' | 'elevated' | 'gray'
|
||
{
|
||
blockType: 'lmSwedishCta',
|
||
headingText: 'Söker du Fiber Direkt i Sverige?',
|
||
subText: 'Visit our Swedish site for local fiber and hosting services.',
|
||
ctaLabel: 'fiberdirekt.se',
|
||
ctaUrl: 'https://fiberdirekt.se',
|
||
sectionBackground: 'dark',
|
||
},
|
||
// Bottom CTA
|
||
// sectionBackground: 'yellow' | 'dark' | 'gray' | 'white'
|
||
// alignment: 'center' | 'left'
|
||
// size: 'small' | 'medium' | 'large'
|
||
{
|
||
blockType: 'lmCtaBanner',
|
||
heading: 'Ready to get started?',
|
||
subheading: 'Configure your infrastructure or talk to our team — no commitment required.',
|
||
ctaText: 'Contact Us',
|
||
ctaLink: '/contact',
|
||
secondaryCtaText: 'View VPS Pricing',
|
||
secondaryCtaLink: '/vps',
|
||
sectionBackground: 'yellow',
|
||
alignment: 'center',
|
||
size: 'medium',
|
||
},
|
||
],
|
||
})
|
||
|
||
// ─── 3. Virtuell Fiber Page ────────────────────────────────────────────────
|
||
console.log(' → Creating Virtuell Fiber page...')
|
||
await createPageIfNotExists('virtuell-fiber', {
|
||
title: 'Virtuell Fiber',
|
||
slug: 'virtuell-fiber',
|
||
_status: 'published',
|
||
publishedAt: new Date().toISOString(),
|
||
layout: [
|
||
// Product Detail — eyebrow as breadcrumb, specs in richText body (right col)
|
||
// sectionBackground: 'white' | 'dark'
|
||
// cartProductType: 'hardware' | 'vps' | 'service'
|
||
{
|
||
blockType: 'lmProductDetail',
|
||
eyebrow: 'Platform / Network / Virtual Fiber',
|
||
productName: 'VF Standard',
|
||
description: 'Virtual Fiber is not a VPN. It creates an encrypted, transparent Layer 2 network, built on top of any available internet connection, that behaves exactly like a private fiber link between your locations. The VF Standard unit ships pre-configured and ready. Connect it to your existing broadband, fiber, or 4G connection and it joins the Layer & Mesh mesh automatically. Your devices get full Layer 2 transport to every other point on the network — with VLAN, QinQ, and Full MTU support — without a single line of configuration. No fixed IP address needed. No IT team required on site. Works as a permanent connection or deployed temporarily. Contract terms start at one day.',
|
||
body: richText('$90 / month\nShipping additional. No setup fee. Cancel anytime.\nPerformance\nThroughput: Up to 1,000 Mbit/s\nTransport: Encrypted Layer 2 over any internet\nRedundancy: Automatic failover via 4G or secondary ISP\nHardware\nForm factor: 1U rack-mounted\nCooling: Active (fan)\nWAN: 2 × 1GbE RJ-45\nLAN: 2 × 1GbE RJ-45\nFeatures\nPlug and play — pre-configured at dispatch\nVLAN · QinQ · Full MTU\nNo public IP address required\nWorld-class Layer 2 encryption'),
|
||
ctaText: 'Order Now — $90/month',
|
||
ctaLink: '/contact',
|
||
secondaryCtaText: 'Download Datasheet',
|
||
secondaryCtaLink: '#',
|
||
enableCart: true,
|
||
cartProductName: 'VF Standard',
|
||
cartPrice: 90,
|
||
cartProductType: 'hardware',
|
||
sectionBackground: 'dark',
|
||
},
|
||
// Service Cards — "What Virtual Fiber Makes Possible"
|
||
// cardSurface: 'outlined-teal' | 'dark' | 'elevated' | 'teal' | 'light' | 'cyan'
|
||
// sectionBackground: 'dark' | 'gray' | 'white' | 'teal'
|
||
// iconSlug: 'backup' | 'colocation' | 'disaster-recovery' | 'connectivity' | 'connectivity-grid' | 'storage' | 'virtual-server'
|
||
{
|
||
blockType: 'lmServiceCards',
|
||
heading: 'What Virtual Fiber Makes Possible',
|
||
columns: '3',
|
||
cardSurface: 'outlined-teal',
|
||
sectionBackground: 'dark',
|
||
cards: [
|
||
{
|
||
eyebrow: 'Reach',
|
||
title: 'Access Resources From Anywhere',
|
||
description: 'Your team in New York reaches your Stockholm servers the same way they reach the printer down the hall — over a private Layer 2 network, without a VPN client or public IP.',
|
||
iconSlug: 'connectivity',
|
||
},
|
||
{
|
||
eyebrow: 'Simplicity',
|
||
title: 'No Configuration Required',
|
||
description: 'Units arrive ready. Connect to any internet access — fiber, broadband, 4G — and the device joins the mesh automatically. Dual transport paths for redundancy are supported out of the box.',
|
||
iconSlug: 'connectivity-grid',
|
||
},
|
||
{
|
||
eyebrow: 'Security',
|
||
title: 'World-Class Layer 2 Encryption',
|
||
description: 'All traffic is encrypted using state-of-the-art technology. Secure channels are established over public networks without any configuration — a clear replacement for traditional VPN.',
|
||
iconSlug: 'storage',
|
||
},
|
||
],
|
||
},
|
||
// USP Checklist — "VF Standard at a Glance"
|
||
// checkColor: 'dark' | 'yellow' | 'gray'
|
||
// sectionBackground: 'white' | 'gray' | 'dark'
|
||
// textColor: 'dark' | 'white'
|
||
{
|
||
blockType: 'lmUspChecklist',
|
||
heading: 'VF Standard at a Glance',
|
||
sectionBackground: 'dark',
|
||
textColor: 'white',
|
||
checkColor: 'dark',
|
||
items: [
|
||
{ text: 'Micro PC portable unit, ships pre-configured, online in minutes' },
|
||
{ text: 'Up to 1,000 Mbit/s Layer 2 encrypted transport, not a VPN, a dedicated private network' },
|
||
{ text: '2 WAN + 2 LAN ports (RJ-45, 1GbE), or failover over 4G/5G or secondary ISP' },
|
||
{ text: 'Full VLAN, QinQ, and MTU support, works with all Layer & Mesh services natively' },
|
||
{ text: 'No public IP address needed, works over any available internet connection' },
|
||
{ text: '$90/month subscription, no long-term commitment required, shipping additional' },
|
||
],
|
||
},
|
||
// FAQ — matching VF-specific questions from screenshot
|
||
// theme: 'gray' | 'light' | 'dark'
|
||
{
|
||
blockType: 'lmFaq',
|
||
heading: 'Virtual Fiber Questions',
|
||
theme: 'dark',
|
||
items: [
|
||
{
|
||
question: "What's the difference between Virtual Fiber and a VPN?",
|
||
answer: richText(
|
||
'Virtual Fiber creates a true Layer 2 network — your devices see each other as if they were on the same physical switch. A VPN tunnels Layer 3 traffic and requires client software, configuration, and a public IP. Virtual Fiber needs none of that.',
|
||
),
|
||
},
|
||
{
|
||
question: 'What internet connection does the VF Standard need?',
|
||
answer: richText(
|
||
'Any internet connection works — broadband, fiber, 4G/5G, or even satellite. The VF Standard automatically establishes encrypted transport over whatever is available. For redundancy, connect a second WAN port to a backup ISP or 4G modem.',
|
||
),
|
||
},
|
||
{
|
||
question: 'Does it need any configuration on site?',
|
||
answer: richText(
|
||
'No. Units are fully configured before dispatch. Connect the WAN port to your internet access and the LAN ports to your network — the unit joins the Layer & Mesh mesh automatically. There is nothing to configure.',
|
||
),
|
||
},
|
||
{
|
||
question: 'Can I connect multiple offices?',
|
||
answer: richText(
|
||
'Yes. Each office gets a VF Standard unit. All units on your account see each other over a single flat Layer 2 network. Add or remove locations at any time — no reconfiguration needed.',
|
||
),
|
||
},
|
||
{
|
||
question: 'What are the contract terms?',
|
||
answer: richText(
|
||
'Month-to-month with no long-term commitment. Contract terms start at one day. Give us 30 days notice and we will arrange return of the hardware. Shipping is additional.',
|
||
),
|
||
},
|
||
],
|
||
},
|
||
// CTA
|
||
{
|
||
blockType: 'lmCtaBanner',
|
||
heading: 'Need a custom deployment?',
|
||
subheading: 'Talk to our team about multi-unit rollouts, custom configurations, or enterprise pricing.',
|
||
ctaText: 'Contact Us',
|
||
ctaLink: '/contact',
|
||
sectionBackground: 'yellow',
|
||
alignment: 'left',
|
||
size: 'small',
|
||
},
|
||
],
|
||
})
|
||
|
||
// ─── 4. VPS Server Page ────────────────────────────────────────────────────
|
||
console.log(' → Creating VPS page...')
|
||
await createPageIfNotExists('vps', {
|
||
title: 'VPS Servers',
|
||
slug: 'vps',
|
||
_status: 'published',
|
||
publishedAt: new Date().toISOString(),
|
||
layout: [
|
||
// Alternate Hero
|
||
// sectionBackground: 'white' | 'dark' | 'gray'
|
||
{
|
||
blockType: 'lmAlternateHero',
|
||
heading: 'Virtual Private Servers',
|
||
description: 'Cloud-hosted servers on Layer & Mesh infrastructure. Full root access, instant provisioning, and Swedish-sovereign data. Configure your resources below — pricing updates in real-time.',
|
||
primaryCtaText: 'Configure Below',
|
||
primaryCtaLink: '#calculator',
|
||
secondaryCtaText: 'Contact Sales',
|
||
secondaryCtaLink: '/contact',
|
||
sectionBackground: 'dark',
|
||
},
|
||
// VPS Calculator
|
||
// sectionBackground: 'dark' | 'gray' | 'teal'
|
||
// currency: 'usd' | 'sek' | 'eur'
|
||
{
|
||
blockType: 'lmVpsCalculator',
|
||
heading: 'Estimate your cost',
|
||
orderCtaText: 'Add to Cart',
|
||
contactCtaText: 'Questions?',
|
||
contactCtaLink: '/contact',
|
||
currency: 'usd',
|
||
sectionBackground: 'dark',
|
||
pricingCpuPerCore: 12,
|
||
pricingRamPerGb: 5,
|
||
pricingSsdPerGb: 0.08,
|
||
pricingHddPerGb: 0.02,
|
||
pricingWindowsLicense: 25,
|
||
anchorId: 'calculator',
|
||
},
|
||
// USP Table — description is richText
|
||
// checkColor: 'dark' | 'yellow' | 'gray'
|
||
// textColor: 'dark' | 'white'
|
||
// sectionBackground: 'white' | 'gray' | 'dark'
|
||
{
|
||
blockType: 'lmUspTable',
|
||
heading: "What's Included",
|
||
sectionBackground: 'dark',
|
||
checkColor: 'dark',
|
||
textColor: 'white',
|
||
rows: [
|
||
{
|
||
title: 'Full Root Access',
|
||
description: richText('SSH, VNC, or console — full control of your server.'),
|
||
},
|
||
{
|
||
title: 'Unmetered Bandwidth',
|
||
description: richText('No traffic caps or overage fees.'),
|
||
},
|
||
{
|
||
title: 'NVMe Storage',
|
||
description: richText('Enterprise-grade SSDs for maximum I/O.'),
|
||
},
|
||
{
|
||
title: 'Instant Provisioning',
|
||
description: richText('Your server is ready in under 60 seconds.'),
|
||
},
|
||
{
|
||
title: '99.99% Uptime SLA',
|
||
description: richText('Guaranteed availability with automatic failover.'),
|
||
},
|
||
{
|
||
title: 'Swedish Sovereignty',
|
||
description: richText('Data stored and processed in Sweden.'),
|
||
},
|
||
],
|
||
},
|
||
// CTA
|
||
{
|
||
blockType: 'lmCtaBanner',
|
||
heading: 'Need dedicated hardware?',
|
||
subheading: 'Check out the VF Enhet — edge compute deployed at your location.',
|
||
ctaText: 'Learn About VF Enhet',
|
||
ctaLink: '/virtuell-fiber',
|
||
secondaryCtaText: 'Contact Sales',
|
||
secondaryCtaLink: '/contact',
|
||
sectionBackground: 'yellow',
|
||
alignment: 'left',
|
||
size: 'small',
|
||
},
|
||
],
|
||
})
|
||
|
||
// ─── 5. Contact Page ───────────────────────────────────────────────────────
|
||
console.log(' → Creating Contact page...')
|
||
await createPageIfNotExists('contact', {
|
||
title: 'Contact',
|
||
slug: 'contact',
|
||
_status: 'published',
|
||
publishedAt: new Date().toISOString(),
|
||
layout: [
|
||
// Contact Form
|
||
// sectionBackground: 'white' | 'gray' | 'dark' | 'navyGradient'
|
||
// layout: 'standard' | 'withImage' | 'card'
|
||
{
|
||
blockType: 'lmContactForm',
|
||
heading: 'Get in Touch',
|
||
description: 'Tell us about your infrastructure needs — our team will get back to you within 24 hours.',
|
||
form: contactForm.id,
|
||
submitText: 'Send Message',
|
||
sectionBackground: 'dark',
|
||
layout: 'card',
|
||
},
|
||
// FAQ
|
||
{
|
||
blockType: 'lmFaq',
|
||
heading: 'Common Questions',
|
||
theme: 'dark',
|
||
items: [
|
||
{
|
||
question: 'Where are you located?',
|
||
answer: richText(
|
||
'Layer & Mesh operates infrastructure in Sweden with support available globally. Our US operations serve North American customers.',
|
||
),
|
||
},
|
||
{
|
||
question: 'How quickly can I get started?',
|
||
answer: richText(
|
||
'VPS servers are provisioned instantly. VF Enhet hardware typically ships within 5–7 business days.',
|
||
),
|
||
},
|
||
{
|
||
question: 'Do you offer enterprise pricing?',
|
||
answer: richText(
|
||
'Yes. Contact us for volume pricing, custom SLAs, and dedicated account management.',
|
||
),
|
||
},
|
||
],
|
||
},
|
||
],
|
||
})
|
||
|
||
// ─── 6. Header Navigation ─────────────────────────────────────────────────
|
||
// Header navItems are flat: { label, type, url } directly on each item
|
||
console.log(' → Setting up header...')
|
||
await apiUpdateGlobal('header', {
|
||
logoLink: { type: 'custom', url: '/' },
|
||
navItems: [
|
||
{ label: 'Virtuell Fiber', type: 'custom', url: '/virtuell-fiber' },
|
||
{ label: 'VPS Servers', type: 'custom', url: '/vps' },
|
||
{ label: 'Contact', type: 'custom', url: '/contact' },
|
||
],
|
||
})
|
||
|
||
// ─── 7. Footer ────────────────────────────────────────────────────────────
|
||
// Footer navItems use link() field: { link: { type, label, url } }
|
||
console.log(' → Setting up footer...')
|
||
await apiUpdateGlobal('footer', {
|
||
logoLink: { type: 'custom', url: '/' },
|
||
navItems: [
|
||
{ link: { type: 'custom', label: 'Virtuell Fiber', url: '/virtuell-fiber' } },
|
||
{ link: { type: 'custom', label: 'VPS Servers', url: '/vps' } },
|
||
{ link: { type: 'custom', label: 'Contact', url: '/contact' } },
|
||
{ link: { type: 'custom', label: 'Fiber Direkt (Sweden)', url: 'https://fiberdirekt.se', newTab: true } },
|
||
],
|
||
})
|
||
|
||
console.log('')
|
||
console.log('✅ Seed complete!')
|
||
console.log(' Pages: /, /virtuell-fiber, /vps, /contact')
|
||
}
|
||
|
||
seed().catch((err) => {
|
||
console.error('❌ Seed failed:', err.message || err)
|
||
process.exit(1)
|
||
})
|