feat: PostSettings global, fix stale migrations, shadow tokens

This commit is contained in:
Jeffrey 2026-03-02 16:02:38 +01:00
parent 707f76291e
commit e05cacb37c
14 changed files with 36737 additions and 73 deletions

View File

@ -0,0 +1,701 @@
# Fiber Direkt — Landing Page Blueprints
> Three solution-focused landing pages with full Swedish copy, block-by-block structure, image direction, and CTA strategy.
> Ready for implementation in Payload CMS.
---
## Brand Extensions
Each page gets a punchy sub-brand that ties back to Fiber Direkt:
| Page | Sub-brand | Tagline |
|---|---|---|
| Data Sovereignty | **Data Direkt** | Din data. Ditt land. Din kontroll. |
| Colocation | **Rack Direkt** | Flytta in. Skala upp. Sov gott. |
| Virtual Datacenter | **Server Direkt** | Servrar utan serverrum. |
These can appear as a styled label/tag above the H1 on each page (use `FDTagsBlock` or integrate into the hero).
---
## Shared Messaging Framework
All three pages reinforce the same core tension:
**The Problem:** Swedish businesses are unknowingly dependent on foreign infrastructure. US Cloud Act, GDPR conflicts, geopolitical instability, and Big Tech lock-in create risks most companies haven't fully evaluated.
**The Shift:** Swedish companies are waking up. NIS2, Schrems II, and increased awareness of data sovereignty are driving a migration back to Swedish-controlled infrastructure.
**The Solution:** Fiber Direkt offers a complete Swedish IT ecosystem — fiber, colocation, virtual servers, and Layer 2 private networking — all from Swedish datacenters, under Swedish law, with Swedish support.
---
---
# Page 1: Data Direkt — Svensk datasuveränitet
**URL slug:** `data-direkt`
**Goal:** Awareness + consideration. Educate B2B buyers on data sovereignty risks and position Fiber Direkt as the answer.
---
### Block 1 — `fdHero`
| Field | Value |
|---|---|
| **heading** | Var bor din data egentligen? |
| **subheading** | Data Direkt — svensk datasuveränitet utan kompromisser |
| **body** | Varje dag skickar svenska företag sin mest känsliga data till servrar utanför landets gränser — utan att veta vem som egentligen har åtkomst. Vi erbjuder ett helt svenskt alternativ. |
| **ctaText** | Gör en datasuveränitets-check |
| **ctaLink** | /kontakt |
| **secondaryCtaText** | Se våra lösningar |
| **secondaryCtaLink** | #losningar |
| **theme** | dark |
| **backgroundImage** | 📷 *Stämningsfull bild — nattvy över Stockholm med fiber-/ljuslinjer som löper genom staden, känslan av uppkopplat, tryggt, svenskt* |
| **overlayOpacity** | 50 |
| **textColor** | auto |
---
### Block 2 — `fdStatistics`
| Field | Value |
|---|---|
| **heading** | Verkligheten i siffror |
| **sectionBackground** | white |
| **numberColor** | gradient |
| **stats** | (see below) |
**Stats array:**
| number | label |
|---|---|
| 72% | av svenska företags data lagras utanför EU |
| 3 av 4 | IT-chefer osäkra på var data faktiskt finns |
| 100% | av vår infrastruktur på svensk mark |
| NIS2 | kräver kontroll — från oktober 2024 |
---
### Block 3 — `fdCtaSideImage`
| Field | Value |
|---|---|
| **heading** | Problemet ingen pratar om |
| **body** | Ditt företags data ligger troligen på servrar i USA, Irland eller Singapore — styrda av lagar du aldrig godkänt. US Cloud Act ger amerikanska myndigheter rätt att kräva ut data från amerikanska molntjänster, oavsett var servern står. GDPR skyddar dig i teorin. I praktiken? Inte tillräckligt. |
| **ctaText** | Läs mer om riskerna |
| **ctaLink** | /blogg/datasuveranitet |
| **image** | 📷 *Konceptbild — en serverrack med en halvöppen dörr och ett utländskt flaggmärke som syns bakom, eller en abstrakt karta som visar dataflöden ut från Sverige* |
| **imagePosition** | right |
| **theme** | light |
---
### Block 4 — `fdCardGrid`
| Field | Value |
|---|---|
| **layout** | 1-1-1 |
| **cardStyle** | navy |
| **sectionBackground** | gray |
**Cards:**
| # | heading | displayMode | contentLines |
|---|---|---|---|
| 1 | 🇺🇸 US Cloud Act | content | **Risk:** Amerikanska myndigheter kan begära ut din data — även om servern står i EU. / **Gäller:** Alla som använder AWS, Azure, Google Cloud eller annan amerikansk tjänst. |
| 2 | 🔓 Schrems II | content | **Dom:** EU-domstolen ogiltigförklarade Privacy Shield. / **Konsekvens:** Överföring av persondata till USA kräver extra skyddsåtgärder som sällan implementeras korrekt. |
| 3 | 📋 NIS2-direktivet | content | **Krav:** Strängare säkerhetskrav för samhällsviktiga tjänster. / **Deadline:** Gäller nu. Brott kan ge böter upp till 2% av global omsättning. |
---
### Block 5 — `fdText`
| Field | Value |
|---|---|
| **heading** | Vad innebär datasuveränitet, konkret? |
| **body** | Datasuveränitet handlar inte om nationalism — det handlar om kontroll. Det betyder att du vet exakt var din data lagras, vilka lagar som gäller, och att ingen utländsk myndighet kan tvinga fram åtkomst utan svensk rättsprocess. Det betyder att din backup inte plötsligt blir otillgänglig för att en utländsk leverantör ändrar sina villkor. Det betyder att du uppfyller regulatoriska krav utan att behöva anlita jurister för att tolka tredjelandsöverföringar. |
| **alignment** | left |
| **textColor** | navy |
| **sectionBackground** | white |
| **maxWidth** | medium |
---
### Block 6 — `fdUspTable` (anchorId: `losningar`)
| Field | Value |
|---|---|
| **heading** | Så löser vi det — helt på svensk mark |
| **checkColor** | yellow |
| **sectionBackground** | white |
| **textColor** | navy |
**Rows:**
| title | description |
|---|---|
| Data som aldrig lämnar Sverige | All infrastruktur drivs från svenska datacenter. Inga moln i USA, inga bakdörrar via tredjeland. Punkt. |
| Svensk lag, hela vägen | Ingen Cloud Act, inga FISA-domstolar. Din data skyddas av svensk lagstiftning och svensk rättsprocess. |
| NIS2 och ISO 27001 — inbyggt | Våra datacenter är ISO 27001-certifierade och byggda enligt Skyddsklass 3. Du uppfyller regulatoriska krav utan extra arbete. |
| Privat nätverk med Layer 2 | Anslut era kontor och system via dedikerat Layer 2-nätverk. Er trafik rör aldrig det publika internet. |
| Svensk support, dygnet runt | Inga chatbotar i Bangalore. Ring oss. Vi svarar. På svenska. Från Sverige. |
---
### Block 7 — `fdServicesGrid`
| Field | Value |
|---|---|
| **heading** | Tre vägar till svensk datasuveränitet |
| **columns** | 3 |
**Services:**
| title | description | image | link |
|---|---|---|---|
| Colocation — Rack Direkt | Flytta era servrar till vårt säkerhetsklassade datacenter. Ni behåller kontrollen, vi levererar kraft, kyla och redundans. | 📷 *Bild på en prydlig serverrack-hall, välbelyst, professionell* | /colocation |
| Virtuellt datacenter — Server Direkt | Byt ut era fysiska servrar mot virtuella. Samma prestanda, noll hårdvarubekymmer, 100% svenskt. | 📷 *Abstrakt/clean illustration av moln med svensk flagga, eller servrar som "svävar"* | /virtuellt-datacenter |
| Privat fiber & nätverk | Dedikerad fiberanslutning med Layer 2-nätverk. Er data reser aldrig via tredje part. | 📷 *Fiberkabel-closeup eller nätverkskarta över Sverige* | /fiber |
---
### Block 8 — `fdTestimonial`
| Field | Value |
|---|---|
| **heading** | Företag som gjort flytten |
| **layout** | grid |
| **sectionBackground** | gray |
**Testimonials:**
| quote | authorName | authorRole | authorCompany |
|---|---|---|---|
| Vi insåg att vi hade all kunddata i AWS Frankfurt utan egentlig kontroll. Flytten till Fiber Direkt tog tre veckor — och nu sover jag bättre. | (Placeholder) | IT-chef | (Svensk medelstort företag) |
| NIS2-kraven hade stressat oss i månader. Med Fiber Direkt kunde vi bocka av complianace på en eftermiddag. | (Placeholder) | CISO | (Placeholder) |
| Layer 2-nätverket förändrade allt. Våra fyra kontor har nu ett privat nätverk som om de satt i samma byggnad. | (Placeholder) | CTO | (Placeholder) |
*Obs: Ersätt med riktiga kundcitat när de finns tillgängliga.*
---
### Block 9 — `fdFaq`
| Field | Value |
|---|---|
| **heading** | Vanliga frågor om datasuveränitet |
| **theme** | light |
**Items:**
| question | answer |
|---|---|
| Räcker det inte med att min molnleverantör har servrar i EU? | Nej. EU-baserade servrar hos amerikanska bolag (AWS, Azure, Google) omfattas fortfarande av US Cloud Act. Den amerikanska staten kan kräva ut data oavsett serverns fysiska plats. Svensk infrastruktur hos ett svenskt bolag är den enda garantin. |
| Vad händer med min data om ni som företag säljs? | Fiber Direkt är ett svenskägt företag med långsiktigt ägande. Men även vid ägarbyten gäller svensk lag — din data kan inte plötsligt flyttas utomlands eller överlåtas utan avtalsbrott. |
| Hur lång tid tar det att migrera till svensk infrastruktur? | Det beror på komplexitet. En enkel servermigrering kan vara klar på dagar. Vi erbjuder en steg-för-steg migreringsplan med dedikerad support genom hela processen. |
| Vad kostar det jämfört med AWS/Azure? | Vi är konkurrenskraftiga — speciellt när du räknar in compliance-kostnader, juridisk riskpremie, och den dolda kostnaden av vendor lock-in. Kontakta oss för en jämförelse baserad på er nuvarande setup. |
| Uppfyller ni NIS2-kraven? | Ja. Våra datacenter är ISO 27001-certifierade, byggda enligt Skyddsklass 3, och opereras helt under svensk lag. Vi hjälper er dokumentera compliance. |
---
### Block 10 — `fdCtaBanner`
| Field | Value |
|---|---|
| **heading** | Redo att ta tillbaka kontrollen? |
| **subheading** | Boka ett kostnadsfritt rådgivningssamtal. Vi analyserar er nuvarande setup och visar exakt vad som behöver göras. |
| **ctaText** | Boka rådgivning |
| **ctaLink** | /kontakt |
| **secondaryCtaText** | Ring oss direkt |
| **secondaryCtaLink** | tel:+46771101010 |
| **sectionBackground** | navy |
| **alignment** | center |
| **size** | large |
---
---
# Page 2: Rack Direkt — Colocation
**URL slug:** `colocation`
**Goal:** Conversion-focused. Convince businesses that already run servers to move them into Fiber Direkt's datacenter.
---
### Block 1 — `fdHero`
| Field | Value |
|---|---|
| **heading** | Ditt serverrum håller dig tillbaka |
| **subheading** | Rack Direkt — säkerhetsklassad colocation, utan krångel |
| **body** | Eget serverrum innebär eget ansvar: redundant kraft, kylning, brandskydd, fysisk säkerhet, och personal som kan hantera det. Vi erbjuder ett bättre alternativ — flytta in i vårt Skyddsklass 3-datacenter och fokusera på det ni faktiskt gör bäst. |
| **ctaText** | Se priser och rack-storlekar |
| **ctaLink** | #priser |
| **secondaryCtaText** | Boka en rundvisning |
| **secondaryCtaLink** | /kontakt |
| **theme** | dark |
| **backgroundImage** | 📷 *Proffsig bild inifrån ett datacenter — rena rackrader med blå/vit belysning, modern känsla* |
| **overlayOpacity** | 50 |
| **textColor** | auto |
---
### Block 2 — `fdCardGrid`
| Field | Value |
|---|---|
| **layout** | 1-1-1 |
| **cardStyle** | outlined |
| **sectionBackground** | white |
**Cards — "Problemet med eget serverrum":**
| # | heading | displayMode | contentLines |
|---|---|---|---|
| 1 | Strömavbrott kl 03:00 | content | Er UPS klarar 15 minuter. Sen? Er verksamhet stannar. Vårt datacenter har N+1-redundans på allt — kraft, kyla, nätverk. |
| 2 | Kylningen sviktar i juli | content | Eget serverrum + sommar = överhettning. Vi har precisionskyla designad för 24/7 drift, året runt. Överskottsvärmen återvinns till fjärrvärmenätet. |
| 3 | Obehörig i serverrummet | content | Vem hade egentligen tillgång senast? Vårt datacenter har biometrisk åtkomst, kameraövervakning och loggning av varje besök. Skyddsklass 3. |
---
### Block 3 — `fdCtaSideImage`
| Field | Value |
|---|---|
| **heading** | Vi hanterar infrastrukturen. Ni driver verksamheten. |
| **body** | Colocation hos Fiber Direkt innebär att er hårdvara står i ett ISO 27001-certifierat, Skyddsklass 3-datacenter — med redundant kraft, precisionskyla, brandsläckning och 24/7 övervakning. Ni behåller full kontroll över era servrar. Vi ser till att de aldrig stannar. |
| **ctaText** | Kontakta oss |
| **ctaLink** | /kontakt |
| **image** | 📷 *Bild på tekniker som jobbar vid en serverrack — professionellt, mänskligt, svenskt* |
| **imagePosition** | right |
| **theme** | dark |
---
### Block 4 — `fdServicesGrid`
| Field | Value |
|---|---|
| **heading** | Colocation innebär |
| **columns** | 4 |
**Services:**
| title | description | image |
|---|---|---|
| Hållbarhet | Direktanslutning till svenskägt nätverk. Från 10 Gbit/s till 400 Gbit/s — symmetrisk, skalbar, utan Big Tech-mellanhänder. | 📷 *Ikon-stil: jordglob/löv* |
| Säkerhet | Dina säkerhetskopior lagras i våra säkra datacenter. Snabb återställning när det händer något. Helt utan BigTech. | 📷 *Ikon-stil: hänglås/sköld* |
| Skalbarhet | Din hårdvara i våra säkra hallar. Svensk drift, ditt utrymme, full kontroll. Från en server till hela burar. | 📷 *Ikon-stil: skalnings-pilar* |
| Låg latens | Virtuella servrar som körs på svensk mark. Flexibel kapacitet, lokal data, inget tredjepartsmoln. | 📷 *Ikon-stil: klocka/hastighet* |
---
### Block 5 — `fdTechProperties`
| Field | Value |
|---|---|
| **sectionBackground** | navy |
| **categoryColor** | white |
| **valueColor** | yellow |
**Properties:**
| category | value |
|---|---|
| Skyddsklass | 3 |
| Certifiering | ISO 27001 |
| Upptid | 99,99% |
| Support | 24/7 |
---
### Block 6 — `fdPricingCard` (anchorId: `priser`)
| Field | Value |
|---|---|
| **sectionTitle** | Gott om utrymme! |
| **titleColor** | navy |
| **sectionBackground** | white |
| **cardStyle** | navy |
| **buttonColor** | yellow |
**Cards:**
| title | subtitle | description | bulletPoints | ctaText | ctaLink |
|---|---|---|---|---|---|
| Helskåp (40U) | från 5 995 kr/mån | Ett helt skåp innebär att ni har ensam tillgång med nyckel. Antal höjdenheter är max 40U. | Eget lås & nyckel / N+1 redundant kraft / Fri fjärrstyrning (KVM) / Cross-connect ingår | Kom igång | /kontakt |
| Halvskåp (18U) | från 3 495 kr/mån | Ett halvt skåp ger er antal höjdenheter på max 18U. 24/7-bevakning och dygnet runt åtkomst. | Delat skåp, eget utrymme / Samma redundans som helskåp / 24/7 fysisk åtkomst / Skalbart — uppgradera till hel | Kom igång | /kontakt |
| Kvartsskåp (9U) | från 1 995 kr/mån | Ett kvartsskåp ger er antal höjdenheter på max 9U. 24/7-bevakning och dygnet runt åtkomst. | Perfekt startlösning / All infrastruktur inkluderad / Dygnet runt åtkomst / Uppgradera när ni växer | Kom igång | /kontakt |
---
### Block 7 — `fdWideCard`
| Field | Value |
|---|---|
| **heading** | Dedikerat utrymme, helt på dina villkor |
| **body** | För verksamheter som kräver full kontroll och hög integritet erbjuder vi privata cage-lösningar och fristående datacentermiljöer med upp till 1 MW effektkapacitet. En egen bur, eller varför inte ett privat datacenter? |
| **ctaText** | Kontakta oss |
| **ctaLink** | /kontakt |
| **cardBackground** | navy |
| **buttonColor** | yellow |
| **sectionBackground** | white |
---
### Block 8 — `fdUspChecklist`
| Field | Value |
|---|---|
| **heading** | Vi hjälper er flytta — från planering till drift |
| **checkColor** | yellow |
| **sectionBackground** | gray |
| **textColor** | navy |
| **image** | 📷 *Bild på teamarbete — två personer som packar/hanterar servrar, professionellt* |
| **imagePosition** | left |
**Items:**
| text |
|---|
| Behovsanalys och dimensionering av rack-utrymme |
| Planering av nätverksanslutning och redundans |
| Fysisk transport och installation av er hårdvara |
| Konfiguration och testning i datacenter-miljö |
| Igångkörning med övervakning och support |
| Dokumentation och överlämning till er drift |
---
### Block 9 — `fdCardGrid`
| Field | Value |
|---|---|
| **layout** | 1-1-1 |
| **cardStyle** | gray |
| **sectionBackground** | white |
**Cards — Certifieringar/Trust:**
| # | heading | displayMode | centeredBodyText |
|---|---|---|---|
| 1 | ISO 27001 | centeredBody | Certifieringen innebär att våra processer för kommunikationslösningar, lagringstjänster och colocation styrs av ett ledningssystem som ställer krav på att vi etablerar, inför, underhåller och ständigt förbättrar skyddet av kundernas data. |
| 2 | ISO 14001 | centeredBody | Genom energieffektiv kylning, minimerat avfall och systematiska miljömål kan vi erbjuda en klimatsmart colocation-lösning som hjälper våra kunder att växa hållbart. |
| 3 | Skyddsklass 3 | centeredBody | Våra datahallar är byggda enligt Skyddsklass 3, den högsta nivån för fysiskt intrångsskydd. Det innebär förstärkt byggnadsskal, flera skalskydd, kameraövervakning och larmklass 3. |
---
### Block 10 — `fdCtaSideImage`
| Field | Value |
|---|---|
| **heading** | Suverän AI och molntjänster 🇸🇪 |
| **body** | Din data stannar i Sverige. Våra AI- och molntjänster drivs från svenska datacenter, under svensk lagstiftning, med svensk support. Perfekt för företag som vill använda moderna AI-verktyg utan att kompromissa med datasuveräniteten. |
| **ctaText** | Läs mer |
| **ctaLink** | /data-direkt |
| **image** | — (no image, text-only variant) |
| **theme** | dark |
---
### Block 11 — `fdFaq`
| Field | Value |
|---|---|
| **heading** | Vanliga frågor om colocation |
| **theme** | light |
**Items:**
| question | answer |
|---|---|
| Kan jag besöka min hårdvara när som helst? | Ja. Alla colocation-kunder har 24/7 tillgång till sina rack via biometrisk åtkomst. Besök loggas för säkerhet. |
| Vad händer vid strömavbrott? | Vårt datacenter har N+1-redundans: dubbla matningar, UPS-system och dieselgeneratorer. Vid normal drift uppnår vi 99,99% upptid. |
| Kan jag skala upp om jag behöver mer plats? | Absolut. Ni kan enkelt uppgradera från kvartsskåp till halvt, helt, eller privata cage-lösningar utan driftstopp. |
| Ingår nätverksanslutning? | Basanslutning ingår. Vi erbjuder dessutom dedikerad fiber med hastigheter från 1 Gbit/s till 400 Gbit/s samt Layer 2-nätverk för privat trafik mellan era platser. |
| Hjälper ni med flytten av befintlig hårdvara? | Ja. Vi erbjuder komplett migrerings-support — från planering och transport till installation och driftsättning i vårt datacenter. |
---
### Block 12 — `fdCtaBanner`
| Field | Value |
|---|---|
| **heading** | Redo att lämna serverrummet? |
| **subheading** | Boka en kostnadsfri rundvisning i vårt datacenter. Se med egna ögon varför 200+ svenska företag redan gjort flytten. |
| **ctaText** | Boka rundvisning |
| **ctaLink** | /kontakt |
| **secondaryCtaText** | Ring oss: 0771-10 10 10 |
| **secondaryCtaLink** | tel:+46771101010 |
| **sectionBackground** | yellow |
| **alignment** | center |
| **size** | large |
---
---
# Page 3: Server Direkt — Virtuellt Datacenter
**URL slug:** `virtuellt-datacenter`
**Goal:** Conversion-focused. Convince businesses to replace physical servers with Fiber Direkt's virtual server platform.
---
### Block 1 — `fdAlternateHero`
| Field | Value |
|---|---|
| **heading** | Servrar utan serverrum |
| **description** | Server Direkt — virtuella servrar på svensk mark. Ersätt era fysiska servrar med flexibla, redundanta virtuella maskiner. Samma prestanda. Noll hårdvarubekymmer. All data i Sverige. |
| **primaryCtaText** | Beräkna din kostnad |
| **primaryCtaLink** | #kalkylator |
| **secondaryCtaText** | Prata med en expert |
| **secondaryCtaLink** | /kontakt |
| **sectionBackground** | white |
| **image** | 📷 *Clean illustration eller foto: abstrakt server-rack som "löser upp sig" i moln/partiklar — fysiskt → virtuellt transformation* |
---
### Block 2 — `fdCardGrid`
| Field | Value |
|---|---|
| **layout** | 1-1 |
| **cardStyle** | navy |
| **sectionBackground** | white |
**Cards — Den gamla vs den nya verkligheten:**
| # | heading | displayMode | contentLines |
|---|---|---|---|
| 1 | ❌ Traditionella servrar | content | Dyra att köpa — dyrare att underhålla / Begränsad kapacitet, svårt att skala / Sårbar för hårdvarufel och strömavbrott / Tar plats, kräver kylning och personal / Garanti löper ut — sen börjar problemen |
| 2 | ✅ Server Direkt | content | Betala för det ni använder — skala på minuter / Inbyggd redundans — ingen enskild felkälla / Ingen hårdvara att underhålla eller byta ut / Alla data i Sverige, under svensk lag / Support på svenska, dygnet runt |
---
### Block 3 — `fdStatistics`
| Field | Value |
|---|---|
| **heading** | |
| **sectionBackground** | navy |
| **numberColor** | gradient |
**Stats:**
| number | label |
|---|---|
| 99,99% | garanterad upptid |
| < 5 min | att starta en ny server |
| 100% | svensk data, svensk lag |
| 24/7 | support på svenska |
---
### Block 4 — `fdCtaSideImage`
| Field | Value |
|---|---|
| **heading** | Perfekt för era kritiska system |
| **body** | Databaser, ERP, bokföring, intranät, e-post, Active Directory — alla de system som er verksamhet är beroende av varje dag. Server Direkt ger dem redundans, prestanda och skalbarhet utan att ni behöver äga en enda fysisk server. |
| **ctaText** | Se vanliga användningsområden |
| **ctaLink** | #anvandning |
| **image** | 📷 *Foto av person som jobbar vid laptop i modernt kontor — känsla av enkelhet, "det bara funkar"* |
| **imagePosition** | right |
| **theme** | light |
---
### Block 5 — `fdServicesGrid` (anchorId: `anvandning`)
| Field | Value |
|---|---|
| **heading** | Vanliga användningsområden |
| **columns** | 3 |
**Services:**
| title | description | image |
|---|---|---|
| Databaser & ERP | Kör era SQL-servrar, affärssystem och CRM i redundanta miljöer med daglig backup och garanterad prestanda. | 📷 *Ikon: databasymbol/cylindrar* |
| Webb & applikationer | Hosta webbplatser, API:er och interna verktyg med autoskalning och lastbalansering. | 📷 *Ikon: webbläsarfönster/kod* |
| E-post & kommunikation | Kör Exchange, Teams-infrastruktur eller valfri e-postlösning på dedikerade virtuella servrar med hög tillgänglighet. | 📷 *Ikon: kuvert/kommunikation* |
| Backup & disaster recovery | En geografiskt separerad kopia av er primära miljö — redo att ta över inom minuter vid incident. | 📷 *Ikon: sköld med checkmark* |
| Utveckling & test | Spinn upp testmiljöer på sekunder. Stäng ner dem när ni är klara. Betala bara för faktisk användning. | 📷 *Ikon: kod-brackets/terminal* |
| AI & maskininlärning | Kör tunga beräkningar på svensk mark. GPU-kapacitet finns tillgänglig för kunder med specifika behov. | 📷 *Ikon: hjärna/AI-noder* |
---
### Block 6 — `fdUspTable`
| Field | Value |
|---|---|
| **heading** | Vad ingår? |
| **checkColor** | yellow |
| **sectionBackground** | white |
| **textColor** | navy |
**Rows:**
| title | description |
|---|---|
| Valfri konfiguration | Anpassa CPU, RAM, SSD och HDD exakt efter era behov. Skala upp eller ner utan driftstopp. |
| Daglig backup | Automatisk backup varje natt. Spara kopior i upp till 30 dagar. Snabb återställning. |
| Redundant nätverk | Dubbla nätverksanslutningar med automatisk failover. Er tjänst är alltid nåbar. |
| Layer 2-nätverk (tillval) | Koppla ihop era virtuella servrar med kontor och andra platser via privat, dedikerat nätverk. |
| Svensk support | Dedikerat supportteam i Sverige. Telefon, e-post, fjärrstyrning. Vi talar ert språk — bokstavligen. |
| SLA med tänder | 99,99% upptid med ekonomisk garanti. Vi kompenserar er om vi inte levererar. |
---
### Block 7 — `fdVpsCalculator` (anchorId: `kalkylator`)
| Field | Value |
|---|---|
| **heading** | Beräkna din månadskostnad |
| **description** | Dra i reglagen och se priset direkt. Ingen bindningstid. |
| **orderCtaText** | Beställ nu |
| **orderCtaLink** | /kontakt |
| **contactCtaText** | Behöver du hjälp att välja? |
| **contactCtaLink** | /kontakt |
| **sectionBackground** | gray |
*Pricing values — use existing calculator config or update to current pricing.*
---
### Block 8 — `fdWideCard`
| Field | Value |
|---|---|
| **heading** | Från eget serverrum till Server Direkt — på en vecka |
| **body** | Migrering behöver inte vara komplicerat. Vi analyserar er nuvarande miljö, planerar flytten, och genomför den med minimalt driftstopp. De flesta kunder är igång inom en vecka — inklusive test och verifiering. |
| **ctaText** | Boka migreringskonsultation |
| **ctaLink** | /kontakt |
| **cardBackground** | navy |
| **buttonColor** | yellow |
| **sectionBackground** | white |
---
### Block 9 — `fdCardGrid`
| Field | Value |
|---|---|
| **layout** | 1-1-1 |
| **cardStyle** | gray |
| **sectionBackground** | white |
**Cards — Migreringsprocessen:**
| # | heading | displayMode | contentLines |
|---|---|---|---|
| 1 | 1. Analys | content | Vi kartlägger er nuvarande servermiljö — hårdvara, mjukvara, belastning, beroenden. Ni får en tydlig migreringsplan med tidslinje. |
| 2 | 2. Migration | content | Vi sätter upp er virtuella miljö, migrerar data och applikationer, och kör parallellt tills allt fungerar felfritt. |
| 3 | 3. Drift & support | content | Er nya miljö är igång. Vi övervakar, backar upp och supportar. Ni fokuserar på kärnverksamheten — vi sköter infrastrukturen. |
---
### Block 10 — `fdTestimonial`
| Field | Value |
|---|---|
| **heading** | Företag som gjort bytet |
| **layout** | featured |
| **sectionBackground** | gray |
**Testimonials:**
| quote | authorName | authorRole | authorCompany |
|---|---|---|---|
| Vi hade tre fysiska servrar som var slut på garanti. Istället för att köpa nya investerade vi i Server Direkt — halva kostnaden, dubbla prestandan, och noll underhåll. | (Placeholder) | VD | (Svenskt företag, 50 anställda) |
*Ersätt med riktigt kundcitat.*
---
### Block 11 — `fdFaq`
| Field | Value |
|---|---|
| **heading** | Vanliga frågor om virtuella servrar |
| **theme** | light |
**Items:**
| question | answer |
|---|---|
| Vad är en virtuell server egentligen? | En virtuell server (VM) är en mjukvarudefinerad dator som körs på kraftfull fysisk hårdvara i vårt datacenter. Den beter sig exakt som en fysisk server — men utan att ni behöver äga, underhålla eller byta hårdvara. |
| Klarar virtuella servrar tunga arbetsbelastningar? | Ja. Våra VM:ar körs på enterprise-hårdvara med dedikerade resurser. Ni kan konfigurera upp till 32 CPU-kärnor, 128 GB RAM och stora SSD/HDD-volymer. |
| Kan jag köra Windows? | Absolut. Vi stöder Windows Server (licensiering kan tillkomma) och alla vanliga Linux-distributioner. |
| Hur fungerar backup? | Daglig automatisk backup ingår. Kopior sparas i upp till 30 dagar och kan återställas snabbt vid behov. |
| Vad händer om en fysisk server i ert datacenter går sönder? | Er VM flyttas automatiskt till annan hårdvara — ofta utan märkbart avbrott. Det är kärnan i virtualisering: ni är aldrig beroende av en enskild maskin. |
| Kan jag kombinera virtuella servrar med colocation? | Ja, och det är en vanlig setup. Många kunder kör primära system virtuellt och har specifik hårdvara i colocation — allt kopplat via vårt Layer 2-nätverk. |
---
### Block 12 — `fdCtaBanner`
| Field | Value |
|---|---|
| **heading** | Starta er första virtuella server idag |
| **subheading** | Ingen bindningstid. Ingen hårdvara att köpa. Betala för det ni använder — skala när ni behöver. |
| **ctaText** | Beställ nu |
| **ctaLink** | /kontakt |
| **secondaryCtaText** | Prata med en expert |
| **secondaryCtaLink** | tel:+46771101010 |
| **sectionBackground** | navy |
| **alignment** | center |
| **size** | large |
---
---
# Cross-page Navigation Strategy
Each page should link to the other two solutions to keep visitors exploring. Use `FDCtaSideImage` or `FDWideCard` blocks at strategic positions (after the main content, before FAQ) to cross-link.
**Suggested cross-links:**
| From page | Link to | Block suggestion |
|---|---|---|
| Data Direkt | Colocation & VDC | `fdServicesGrid` (block 7 above) |
| Rack Direkt | Data Direkt (sovereign angle) + Server Direkt | `fdCtaSideImage` (block 10) + mention in FAQ |
| Server Direkt | Colocation combo + Data Direkt compliance | Mention in FAQ answers + `fdWideCard` cross-sell |
---
# Image Brief for Photo/Asset Production
| ID | Description | Mood | Usage |
|---|---|---|---|
| IMG-01 | Stockholm skyline at night with fiber-optic light trails | Cinematic, connected, Swedish | Data Direkt hero |
| IMG-02 | Server rack with foreign flag/access — conceptual | Thought-provoking, risk | Data Direkt problem section |
| IMG-03 | Clean datacenter interior — blue/white lighting, rack rows | Professional, trustworthy | Rack Direkt hero |
| IMG-04 | Technician working at server rack — Swedish, professional | Human, expert | Rack Direkt migration section |
| IMG-05 | Physical server "dissolving" into cloud/particles | Transformative, modern | Server Direkt hero |
| IMG-06 | Person at laptop in modern Swedish office | Simple, productive | Server Direkt usage section |
| IMG-07 | Fiber cable closeup / Swedish network map | Technical, connected | Shared — fiber/network sections |
| IMG-08 | Team/support staff — real people, Swedish | Trustworthy, local | Shared — support sections |
| IMG-09 | Icon set (8 icons): globe, lock, scale, clock, database, envelope, shield, code | Clean, brand-consistent | Service grid icons |
---
# Implementation Checklist
- [ ] Create pages in Payload admin: `data-direkt`, `colocation`, `virtuellt-datacenter`
- [ ] Add each block in order per page blueprint above
- [ ] Set `anchorId` values on marked blocks for deep-linking
- [ ] Upload placeholder images (or final assets) to Media collection
- [ ] Set SEO meta on each page (use plugin)
- [ ] Add pages to main navigation under "Tjänster" dropdown
- [ ] Set `sectionBackground` values to create visual rhythm (alternate white/gray/navy)
- [ ] Replace placeholder testimonials with real customer quotes when available
- [ ] Test all cross-links and anchor links
- [ ] Mobile test each page — blocks are mobile-optimized but verify copy length
- [ ] Set up English locale content when ready (all text fields are `localized: true`)

934
seed-landing-pages.mjs Normal file
View File

@ -0,0 +1,934 @@
#!/usr/bin/env node
/**
* Fiber Direkt Landing Page Seed Script
*
* Creates three solution landing pages as DRAFTS in Payload CMS:
* 1. Data Direkt /data-direkt
* 2. Rack Direkt /colocation
* 3. Server Direkt /virtuellt-datacenter
*
* Usage:
* PAYLOAD_URL=https://webdev2.fiberdirekt.se PAYLOAD_API_KEY=your-key node seed-landing-pages.mjs
*
* Or place this in your project root and use .env:
* node seed-landing-pages.mjs
*
* Pages are created as DRAFTS review in admin before publishing.
* Images are referenced as placeholder text replace with real Media IDs after upload.
*/
import 'dotenv/config'
const PAYLOAD_URL = process.env.PAYLOAD_URL || process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000'
const API_KEY = process.env.PAYLOAD_API_KEY
if (!API_KEY) {
console.error('❌ Missing PAYLOAD_API_KEY environment variable')
console.error(' Set it to a Users API key with create permissions')
process.exit(1)
}
const headers = {
'Content-Type': 'application/json',
Authorization: `users API-Key ${API_KEY}`,
}
// ──────────────────────────────────────────────
// Helper: Convert plain text to Payload Lexical JSON
// Supports \n\n for paragraph breaks
// ──────────────────────────────────────────────
function lexical(text) {
const paragraphs = text.split('\n\n').filter(Boolean)
return {
root: {
type: 'root',
children: paragraphs.map((p) => ({
type: 'paragraph',
children: [
{
type: 'text',
text: p.replace(/\n/g, ' '),
format: 0,
detail: 0,
mode: 'normal',
style: '',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
textFormat: 0,
version: 1,
})),
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
}
}
// ──────────────────────────────────────────────
// Helper: POST a page to Payload
// ──────────────────────────────────────────────
async function createPage(pageData) {
const url = `${PAYLOAD_URL}/api/pages`
console.log(`\n📄 Creating page: ${pageData.title} (/${pageData.slug})...`)
try {
const res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ ...pageData, _status: 'draft' }),
})
const json = await res.json()
if (!res.ok) {
console.error(` ❌ Failed (${res.status}):`, JSON.stringify(json.errors || json, null, 2))
return null
}
console.log(` ✅ Created as draft — ID: ${json.doc?.id || json.id}`)
return json
} catch (err) {
console.error(` ❌ Network error:`, err.message)
return null
}
}
// ══════════════════════════════════════════════
// PAGE 1: Data Direkt — Svensk datasuveränitet
// ══════════════════════════════════════════════
const dataDirektPage = {
title: 'Data Direkt — Svensk datasuveränitet',
slug: 'data-direkt',
meta: {
title: 'Data Direkt — Svensk datasuveränitet | Fiber Direkt',
description:
'Varje dag skickar svenska företag sin mest känsliga data till servrar utanför landets gränser. Vi erbjuder ett helt svenskt alternativ — fiber, colocation och virtuella servrar på svensk mark.',
},
layout: [
// ── Block 1: Hero ──
{
blockType: 'fdHero',
heading: 'Var bor din data egentligen?',
subheading: 'Data Direkt — svensk datasuveränitet utan kompromisser',
body: 'Varje dag skickar svenska företag sin mest känsliga data till servrar utanför landets gränser — utan att veta vem som egentligen har åtkomst. Vi erbjuder ett helt svenskt alternativ.',
ctaText: 'Gör en datasuveränitets-check',
ctaLink: '/kontakt',
secondaryCtaText: 'Se våra lösningar',
secondaryCtaLink: '#losningar',
theme: 'dark',
overlayOpacity: '50',
textColor: 'auto',
// backgroundImage: <upload a Media ID here>
},
// ── Block 2: Statistics ──
{
blockType: 'fdStatistics',
heading: 'Verkligheten i siffror',
sectionBackground: 'white',
numberColor: 'gradient',
stats: [
{ number: '72%', label: 'av svenska företags data lagras utanför EU' },
{ number: '3 av 4', label: 'IT-chefer osäkra på var data faktiskt finns' },
{ number: '100%', label: 'av vår infrastruktur på svensk mark' },
{ number: 'NIS2', label: 'kräver kontroll — från oktober 2024' },
],
},
// ── Block 3: CTA Side Image — Problem statement ──
{
blockType: 'fdCtaSideImage',
heading: 'Problemet ingen pratar om',
body: 'Ditt företags data ligger troligen på servrar i USA, Irland eller Singapore — styrda av lagar du aldrig godkänt. US Cloud Act ger amerikanska myndigheter rätt att kräva ut data från amerikanska molntjänster, oavsett var servern står. GDPR skyddar dig i teorin. I praktiken? Inte tillräckligt.',
ctaText: 'Läs mer om riskerna',
ctaLink: '/blogg/datasuveranitet',
imagePosition: 'right',
theme: 'light',
// image: <upload a Media ID here — conceptual data flow / foreign server image>
},
// ── Block 4: Card Grid — Regulatory risks ──
{
blockType: 'fdCardGrid',
layout: '1-1-1',
cardStyle: 'navy',
sectionBackground: 'gray',
cards: [
{
displayMode: 'content',
heading: '🇺🇸 US Cloud Act',
contentLines: [
{ text: 'Risk: Amerikanska myndigheter kan begära ut din data — även om servern står i EU.', style: 'bold' },
{ text: 'Gäller alla som använder AWS, Azure, Google Cloud eller annan amerikansk tjänst.', style: 'normal' },
],
},
{
displayMode: 'content',
heading: '🔓 Schrems II',
contentLines: [
{ text: 'Dom: EU-domstolen ogiltigförklarade Privacy Shield.', style: 'bold' },
{ text: 'Konsekvens: Överföring av persondata till USA kräver extra skyddsåtgärder som sällan implementeras korrekt.', style: 'normal' },
],
},
{
displayMode: 'content',
heading: '📋 NIS2-direktivet',
contentLines: [
{ text: 'Krav: Strängare säkerhetskrav för samhällsviktiga tjänster.', style: 'bold' },
{ text: 'Gäller nu. Brott kan ge böter upp till 2% av global omsättning.', style: 'normal' },
],
},
],
},
// ── Block 5: Text — What is data sovereignty? ──
{
blockType: 'fdText',
heading: 'Vad innebär datasuveränitet, konkret?',
// body is richText — you may need to structure this as Lexical JSON in admin
// For API seeding, try plain string first — Payload may auto-convert
body: lexical('Datasuveränitet handlar inte om nationalism — det handlar om kontroll. Det betyder att du vet exakt var din data lagras, vilka lagar som gäller, och att ingen utländsk myndighet kan tvinga fram åtkomst utan svensk rättsprocess.\n\nDet betyder att din backup inte plötsligt blir otillgänglig för att en utländsk leverantör ändrar sina villkor.\n\nDet betyder att du uppfyller regulatoriska krav utan att behöva anlita jurister för att tolka tredjelandsöverföringar.'),
alignment: 'left',
textColor: 'navy',
sectionBackground: 'white',
maxWidth: 'medium',
},
// ── Block 6: USP Table — Solutions overview ──
{
blockType: 'fdUspTable',
heading: 'Så löser vi det — helt på svensk mark',
anchorId: 'losningar',
checkColor: 'yellow',
sectionBackground: 'white',
textColor: 'navy',
rows: [
{
title: 'Data som aldrig lämnar Sverige',
description: lexical('All infrastruktur drivs från svenska datacenter. Inga moln i USA, inga bakdörrar via tredjeland. Punkt.'),
},
{
title: 'Svensk lag, hela vägen',
description: lexical('Ingen Cloud Act, inga FISA-domstolar. Din data skyddas av svensk lagstiftning och svensk rättsprocess.'),
},
{
title: 'NIS2 och ISO 27001 — inbyggt',
description: lexical('Våra datacenter är ISO 27001-certifierade och byggda enligt Skyddsklass 3. Du uppfyller regulatoriska krav utan extra arbete.'),
},
{
title: 'Privat nätverk med Layer 2',
description: lexical('Anslut era kontor och system via dedikerat Layer 2-nätverk. Er trafik rör aldrig det publika internet.'),
},
{
title: 'Svensk support, dygnet runt',
description: lexical('Inga chatbotar i Bangalore. Ring oss. Vi svarar. På svenska. Från Sverige.'),
},
],
},
// ── Block 7: Services Grid — Three paths ──
{
blockType: 'fdServicesGrid',
heading: 'Tre vägar till svensk datasuveränitet',
columns: '3',
services: [
{
title: 'Colocation — Rack Direkt',
description: 'Flytta era servrar till vårt säkerhetsklassade datacenter. Ni behåller kontrollen, vi levererar kraft, kyla och redundans.',
link: '/colocation',
// image: <Media ID>
},
{
title: 'Virtuellt datacenter — Server Direkt',
description: 'Byt ut era fysiska servrar mot virtuella. Samma prestanda, noll hårdvarubekymmer, 100% svenskt.',
link: '/virtuellt-datacenter',
// image: <Media ID>
},
{
title: 'Privat fiber & nätverk',
description: 'Dedikerad fiberanslutning med Layer 2-nätverk. Er data reser aldrig via tredje part.',
link: '/fiber',
// image: <Media ID>
},
],
},
// ── Block 8: Testimonials ──
{
blockType: 'fdTestimonial',
heading: 'Företag som gjort flytten',
layout: 'grid',
sectionBackground: 'gray',
testimonials: [
{
quote: 'Vi insåg att vi hade all kunddata i AWS Frankfurt utan egentlig kontroll. Flytten till Fiber Direkt tog tre veckor — och nu sover jag bättre.',
authorName: 'Placeholder',
authorRole: 'IT-chef',
authorCompany: 'Svenskt medelstort företag',
},
{
quote: 'NIS2-kraven hade stressat oss i månader. Med Fiber Direkt kunde vi bocka av compliance på en eftermiddag.',
authorName: 'Placeholder',
authorRole: 'CISO',
authorCompany: 'Placeholder',
},
{
quote: 'Layer 2-nätverket förändrade allt. Våra fyra kontor har nu ett privat nätverk som om de satt i samma byggnad.',
authorName: 'Placeholder',
authorRole: 'CTO',
authorCompany: 'Placeholder',
},
],
},
// ── Block 9: FAQ ──
{
blockType: 'fdFaq',
heading: 'Vanliga frågor om datasuveränitet',
theme: 'light',
items: [
{
question: 'Räcker det inte med att min molnleverantör har servrar i EU?',
answer: lexical('Nej. EU-baserade servrar hos amerikanska bolag (AWS, Azure, Google) omfattas fortfarande av US Cloud Act. Den amerikanska staten kan kräva ut data oavsett serverns fysiska plats. Svensk infrastruktur hos ett svenskt bolag är den enda garantin.'),
},
{
question: 'Vad händer med min data om ni som företag säljs?',
answer: lexical('Fiber Direkt är ett svenskägt företag med långsiktigt ägande. Men även vid ägarbyten gäller svensk lag — din data kan inte plötsligt flyttas utomlands eller överlåtas utan avtalsbrott.'),
},
{
question: 'Hur lång tid tar det att migrera till svensk infrastruktur?',
answer: lexical('Det beror på komplexitet. En enkel servermigrering kan vara klar på dagar. Vi erbjuder en steg-för-steg migreringsplan med dedikerad support genom hela processen.'),
},
{
question: 'Vad kostar det jämfört med AWS/Azure?',
answer: lexical('Vi är konkurrenskraftiga — speciellt när du räknar in compliance-kostnader, juridisk riskpremie, och den dolda kostnaden av vendor lock-in. Kontakta oss för en jämförelse baserad på er nuvarande setup.'),
},
{
question: 'Uppfyller ni NIS2-kraven?',
answer: lexical('Ja. Våra datacenter är ISO 27001-certifierade, byggda enligt Skyddsklass 3, och opereras helt under svensk lag. Vi hjälper er dokumentera compliance.'),
},
],
},
// ── Block 10: CTA Banner ──
{
blockType: 'fdCtaBanner',
heading: 'Redo att ta tillbaka kontrollen?',
subheading: 'Boka ett kostnadsfritt rådgivningssamtal. Vi analyserar er nuvarande setup och visar exakt vad som behöver göras.',
ctaText: 'Boka rådgivning',
ctaLink: '/kontakt',
secondaryCtaText: 'Ring oss direkt',
secondaryCtaLink: 'tel:+46771101010',
sectionBackground: 'navy',
alignment: 'center',
size: 'large',
},
],
}
// ══════════════════════════════════════════════
// PAGE 2: Rack Direkt — Colocation
// ══════════════════════════════════════════════
const rackDirektPage = {
title: 'Rack Direkt — Colocation',
slug: 'colocation',
meta: {
title: 'Colocation i Skyddsklass 3-datacenter | Fiber Direkt',
description:
'Flytta era servrar till vårt ISO 27001-certifierade, Skyddsklass 3-datacenter. Redundant kraft, precisionskyla, 24/7 övervakning och svensk support. Från 1 995 kr/mån.',
},
layout: [
// ── Block 1: Hero ──
{
blockType: 'fdHero',
heading: 'Ditt serverrum håller dig tillbaka',
subheading: 'Rack Direkt — säkerhetsklassad colocation, utan krångel',
body: 'Eget serverrum innebär eget ansvar: redundant kraft, kylning, brandskydd, fysisk säkerhet, och personal som kan hantera det. Vi erbjuder ett bättre alternativ — flytta in i vårt Skyddsklass 3-datacenter och fokusera på det ni faktiskt gör bäst.',
ctaText: 'Se priser och rack-storlekar',
ctaLink: '#priser',
secondaryCtaText: 'Boka en rundvisning',
secondaryCtaLink: '/kontakt',
theme: 'dark',
overlayOpacity: '50',
textColor: 'auto',
// backgroundImage: <datacenter interior photo>
},
// ── Block 2: Card Grid — Server room problems ──
{
blockType: 'fdCardGrid',
layout: '1-1-1',
cardStyle: 'outlined',
sectionBackground: 'white',
cards: [
{
displayMode: 'content',
heading: 'Strömavbrott kl 03:00',
contentLines: [
{ text: 'Er UPS klarar 15 minuter. Sen?', style: 'bold' },
{ text: 'Er verksamhet stannar. Vårt datacenter har N+1-redundans på allt — kraft, kyla, nätverk.', style: 'normal' },
],
},
{
displayMode: 'content',
heading: 'Kylningen sviktar i juli',
contentLines: [
{ text: 'Eget serverrum + sommar = överhettning.', style: 'bold' },
{ text: 'Vi har precisionskyla designad för 24/7 drift, året runt. Överskottsvärmen återvinns till fjärrvärmenätet.', style: 'normal' },
],
},
{
displayMode: 'content',
heading: 'Obehörig i serverrummet',
contentLines: [
{ text: 'Vem hade egentligen tillgång senast?', style: 'bold' },
{ text: 'Vårt datacenter har biometrisk åtkomst, kameraövervakning och loggning av varje besök. Skyddsklass 3.', style: 'normal' },
],
},
],
},
// ── Block 3: CTA Side Image — Value prop ──
{
blockType: 'fdCtaSideImage',
heading: 'Vi hanterar infrastrukturen. Ni driver verksamheten.',
body: 'Colocation hos Fiber Direkt innebär att er hårdvara står i ett ISO 27001-certifierat, Skyddsklass 3-datacenter — med redundant kraft, precisionskyla, brandsläckning och 24/7 övervakning. Ni behåller full kontroll över era servrar. Vi ser till att de aldrig stannar.',
ctaText: 'Kontakta oss',
ctaLink: '/kontakt',
imagePosition: 'right',
theme: 'dark',
// image: <technician at server rack>
},
// ── Block 4: Services Grid — What colocation means ──
{
blockType: 'fdServicesGrid',
heading: 'Colocation innebär',
columns: '4',
services: [
{
title: 'Hållbarhet',
description: 'Direktanslutning till svenskägt nätverk. Från 10 Gbit/s till 400 Gbit/s — symmetrisk, skalbar, utan Big Tech-mellanhänder.',
// image: <globe/leaf icon>
},
{
title: 'Säkerhet',
description: 'Dina säkerhetskopior lagras i våra säkra datacenter. Snabb återställning när det händer något. Helt utan BigTech.',
// image: <lock/shield icon>
},
{
title: 'Skalbarhet',
description: 'Din hårdvara i våra säkra hallar. Svensk drift, ditt utrymme, full kontroll. Från en server till hela burar.',
// image: <scaling arrows icon>
},
{
title: 'Låg latens',
description: 'Virtuella servrar som körs på svensk mark. Flexibel kapacitet, lokal data, inget tredjepartsmoln. Hybrid infrastruktur — vi löser det!',
// image: <clock/speed icon>
},
],
},
// ── Block 5: Tech Properties ──
{
blockType: 'fdTechProperties',
sectionBackground: 'navy',
categoryColor: 'white',
valueColor: 'yellow',
properties: [
{ category: 'Skyddsklass', value: '3' },
{ category: 'Certifiering', value: 'ISO 27001' },
{ category: 'Upptid', value: '99,99%' },
{ category: 'Support', value: '24/7' },
],
},
// ── Block 6: Pricing Cards ──
{
blockType: 'fdPricingCard',
anchorId: 'priser',
sectionTitle: 'Gott om utrymme!',
titleColor: 'navy',
sectionBackground: 'white',
cardStyle: 'navy',
buttonColor: 'yellow',
cards: [
{
title: 'Helskåp (40U)',
subtitle: 'från 5 995 kr/mån',
description: 'Ett helt skåp innebär att ni har ensam tillgång med nyckel. Antal höjdenheter är max 40U.',
bulletPoints: [
{ text: 'Eget lås & nyckel' },
{ text: 'N+1 redundant kraft' },
{ text: 'Fri fjärrstyrning (KVM)' },
{ text: 'Cross-connect ingår' },
],
ctaText: 'Kom igång',
ctaLink: '/kontakt',
},
{
title: 'Halvskåp (18U)',
subtitle: 'från 3 495 kr/mån',
description: 'Ett halvt skåp ger er antal höjdenheter på max 18U. 24/7-bevakning och dygnet runt åtkomst.',
bulletPoints: [
{ text: 'Delat skåp, eget utrymme' },
{ text: 'Samma redundans som helskåp' },
{ text: '24/7 fysisk åtkomst' },
{ text: 'Skalbart — uppgradera till hel' },
],
ctaText: 'Kom igång',
ctaLink: '/kontakt',
},
{
title: 'Kvartsskåp (9U)',
subtitle: 'från 1 995 kr/mån',
description: 'Ett kvartsskåp ger er antal höjdenheter på max 9U. 24/7-bevakning och dygnet runt åtkomst.',
bulletPoints: [
{ text: 'Perfekt startlösning' },
{ text: 'All infrastruktur inkluderad' },
{ text: 'Dygnet runt åtkomst' },
{ text: 'Uppgradera när ni växer' },
],
ctaText: 'Kom igång',
ctaLink: '/kontakt',
},
],
},
// ── Block 7: Wide Card — Dedicated space ──
{
blockType: 'fdWideCard',
heading: 'Dedikerat utrymme, helt på dina villkor',
body: 'För verksamheter som kräver full kontroll och hög integritet erbjuder vi privata cage-lösningar och fristående datacentermiljöer med upp till 1 MW effektkapacitet. En egen bur, eller varför inte ett privat datacenter?',
ctaText: 'Kontakta oss',
ctaLink: '/kontakt',
cardBackground: 'navy',
buttonColor: 'yellow',
sectionBackground: 'white',
},
// ── Block 8: USP Checklist — Migration support ──
{
blockType: 'fdUspChecklist',
heading: 'Vi hjälper er flytta — från planering till drift',
checkColor: 'yellow',
sectionBackground: 'gray',
textColor: 'navy',
imagePosition: 'left',
// image: <teamwork / server handling photo>
items: [
{ text: 'Behovsanalys och dimensionering av rack-utrymme' },
{ text: 'Planering av nätverksanslutning och redundans' },
{ text: 'Fysisk transport och installation av er hårdvara' },
{ text: 'Konfiguration och testning i datacenter-miljö' },
{ text: 'Igångkörning med övervakning och support' },
{ text: 'Dokumentation och överlämning till er drift' },
],
},
// ── Block 9: Card Grid — Certifications ──
{
blockType: 'fdCardGrid',
layout: '1-1-1',
cardStyle: 'gray',
sectionBackground: 'white',
cards: [
{
displayMode: 'centeredBody',
heading: 'ISO 27001',
centeredBodyText: 'Certifieringen innebär att våra processer för kommunikationslösningar, lagringstjänster och colocation styrs av ett ledningssystem som ställer krav på att vi etablerar, inför, underhåller och ständigt förbättrar skyddet av kundernas data.',
},
{
displayMode: 'centeredBody',
heading: 'ISO 14001',
centeredBodyText: 'Genom energieffektiv kylning, minimerat avfall och systematiska miljömål kan vi erbjuda en klimatsmart colocation-lösning som hjälper våra kunder att växa hållbart.',
},
{
displayMode: 'centeredBody',
heading: 'Skyddsklass 3',
centeredBodyText: 'Våra datahallar är byggda enligt Skyddsklass 3, den högsta nivån för fysiskt intrångsskydd. Det innebär förstärkt byggnadsskal, flera skalskydd, kameraövervakning och larmklass 3.',
},
],
},
// ── Block 10: CTA Side Image — Sovereign AI cross-sell ──
{
blockType: 'fdCtaSideImage',
heading: 'Suverän AI och molntjänster 🇸🇪',
body: 'Din data stannar i Sverige. Våra AI- och molntjänster drivs från svenska datacenter, under svensk lagstiftning, med svensk support. Perfekt för företag som vill använda moderna AI-verktyg utan att kompromissa med datasuveräniteten.',
ctaText: 'Läs mer',
ctaLink: '/data-direkt',
theme: 'dark',
imagePosition: 'right',
},
// ── Block 11: FAQ ──
{
blockType: 'fdFaq',
heading: 'Vanliga frågor om colocation',
theme: 'light',
items: [
{
question: 'Kan jag besöka min hårdvara när som helst?',
answer: lexical('Ja. Alla colocation-kunder har 24/7 tillgång till sina rack via biometrisk åtkomst. Besök loggas för säkerhet.'),
},
{
question: 'Vad händer vid strömavbrott?',
answer: lexical('Vårt datacenter har N+1-redundans: dubbla matningar, UPS-system och dieselgeneratorer. Vid normal drift uppnår vi 99,99% upptid.'),
},
{
question: 'Kan jag skala upp om jag behöver mer plats?',
answer: lexical('Absolut. Ni kan enkelt uppgradera från kvartsskåp till halvt, helt, eller privata cage-lösningar utan driftstopp.'),
},
{
question: 'Ingår nätverksanslutning?',
answer: lexical('Basanslutning ingår. Vi erbjuder dessutom dedikerad fiber med hastigheter från 1 Gbit/s till 400 Gbit/s samt Layer 2-nätverk för privat trafik mellan era platser.'),
},
{
question: 'Hjälper ni med flytten av befintlig hårdvara?',
answer: lexical('Ja. Vi erbjuder komplett migrerings-support — från planering och transport till installation och driftsättning i vårt datacenter.'),
},
],
},
// ── Block 12: CTA Banner ──
{
blockType: 'fdCtaBanner',
heading: 'Redo att lämna serverrummet?',
subheading: 'Boka en kostnadsfri rundvisning i vårt datacenter. Se med egna ögon varför 200+ svenska företag redan gjort flytten.',
ctaText: 'Boka rundvisning',
ctaLink: '/kontakt',
secondaryCtaText: 'Ring oss: 0771-10 10 10',
secondaryCtaLink: 'tel:+46771101010',
sectionBackground: 'yellow',
alignment: 'center',
size: 'large',
},
],
}
// ══════════════════════════════════════════════
// PAGE 3: Server Direkt — Virtuellt Datacenter
// ══════════════════════════════════════════════
const serverDirektPage = {
title: 'Server Direkt — Virtuellt Datacenter',
slug: 'virtuellt-datacenter',
meta: {
title: 'Virtuellt Datacenter — Servrar utan serverrum | Fiber Direkt',
description:
'Ersätt era fysiska servrar med flexibla, redundanta virtuella maskiner på svensk mark. Samma prestanda, noll hårdvarubekymmer, 100% svenskt. Beräkna din kostnad direkt.',
},
layout: [
// ── Block 1: Alternate Hero ──
{
blockType: 'fdAlternateHero',
heading: 'Servrar utan serverrum',
description: 'Server Direkt — virtuella servrar på svensk mark. Ersätt era fysiska servrar med flexibla, redundanta virtuella maskiner. Samma prestanda. Noll hårdvarubekymmer. All data i Sverige.',
primaryCtaText: 'Beräkna din kostnad',
primaryCtaLink: '#kalkylator',
secondaryCtaText: 'Prata med en expert',
secondaryCtaLink: '/kontakt',
sectionBackground: 'white',
// image: <physical-to-virtual transformation illustration>
},
// ── Block 2: Card Grid — Old vs New ──
{
blockType: 'fdCardGrid',
layout: '1-1',
cardStyle: 'navy',
sectionBackground: 'white',
cards: [
{
displayMode: 'content',
heading: '❌ Traditionella servrar',
contentLines: [
{ text: 'Dyra att köpa — dyrare att underhålla', style: 'normal' },
{ text: 'Begränsad kapacitet, svårt att skala', style: 'normal' },
{ text: 'Sårbar för hårdvarufel och strömavbrott', style: 'normal' },
{ text: 'Tar plats, kräver kylning och personal', style: 'normal' },
{ text: 'Garanti löper ut — sen börjar problemen', style: 'normal' },
],
},
{
displayMode: 'content',
heading: '✅ Server Direkt',
contentLines: [
{ text: 'Betala för det ni använder — skala på minuter', style: 'normal' },
{ text: 'Inbyggd redundans — ingen enskild felkälla', style: 'normal' },
{ text: 'Ingen hårdvara att underhålla eller byta ut', style: 'normal' },
{ text: 'Alla data i Sverige, under svensk lag', style: 'normal' },
{ text: 'Support på svenska, dygnet runt', style: 'normal' },
],
},
],
},
// ── Block 3: Statistics ──
{
blockType: 'fdStatistics',
sectionBackground: 'navy',
numberColor: 'gradient',
stats: [
{ number: '99,99%', label: 'garanterad upptid' },
{ number: '< 5 min', label: 'att starta en ny server' },
{ number: '100%', label: 'svensk data, svensk lag' },
{ number: '24/7', label: 'support på svenska' },
],
},
// ── Block 4: CTA Side Image — Use cases ──
{
blockType: 'fdCtaSideImage',
heading: 'Perfekt för era kritiska system',
body: 'Databaser, ERP, bokföring, intranät, e-post, Active Directory — alla de system som er verksamhet är beroende av varje dag. Server Direkt ger dem redundans, prestanda och skalbarhet utan att ni behöver äga en enda fysisk server.',
ctaText: 'Se vanliga användningsområden',
ctaLink: '#anvandning',
imagePosition: 'right',
theme: 'light',
// image: <person at laptop in modern office>
},
// ── Block 5: Services Grid — Use cases detail ──
{
blockType: 'fdServicesGrid',
anchorId: 'anvandning',
heading: 'Vanliga användningsområden',
columns: '3',
services: [
{
title: 'Databaser & ERP',
description: 'Kör era SQL-servrar, affärssystem och CRM i redundanta miljöer med daglig backup och garanterad prestanda.',
// image: <database icon>
},
{
title: 'Webb & applikationer',
description: 'Hosta webbplatser, API:er och interna verktyg med autoskalning och lastbalansering.',
// image: <web/code icon>
},
{
title: 'E-post & kommunikation',
description: 'Kör Exchange, Teams-infrastruktur eller valfri e-postlösning på dedikerade virtuella servrar med hög tillgänglighet.',
// image: <envelope icon>
},
{
title: 'Backup & disaster recovery',
description: 'En geografiskt separerad kopia av er primära miljö — redo att ta över inom minuter vid incident.',
// image: <shield icon>
},
{
title: 'Utveckling & test',
description: 'Spinn upp testmiljöer på sekunder. Stäng ner dem när ni är klara. Betala bara för faktisk användning.',
// image: <code brackets icon>
},
{
title: 'AI & maskininlärning',
description: 'Kör tunga beräkningar på svensk mark. GPU-kapacitet finns tillgänglig för kunder med specifika behov.',
// image: <brain/AI icon>
},
],
},
// ── Block 6: USP Table — What's included ──
{
blockType: 'fdUspTable',
heading: 'Vad ingår?',
checkColor: 'yellow',
sectionBackground: 'white',
textColor: 'navy',
rows: [
{
title: 'Valfri konfiguration',
description: lexical('Anpassa CPU, RAM, SSD och HDD exakt efter era behov. Skala upp eller ner utan driftstopp.'),
},
{
title: 'Daglig backup',
description: lexical('Automatisk backup varje natt. Spara kopior i upp till 30 dagar. Snabb återställning.'),
},
{
title: 'Redundant nätverk',
description: lexical('Dubbla nätverksanslutningar med automatisk failover. Er tjänst är alltid nåbar.'),
},
{
title: 'Layer 2-nätverk (tillval)',
description: lexical('Koppla ihop era virtuella servrar med kontor och andra platser via privat, dedikerat nätverk.'),
},
{
title: 'Svensk support',
description: lexical('Dedikerat supportteam i Sverige. Telefon, e-post, fjärrstyrning. Vi talar ert språk — bokstavligen.'),
},
{
title: 'SLA med tänder',
description: lexical('99,99% upptid med ekonomisk garanti. Vi kompenserar er om vi inte levererar.'),
},
],
},
// ── Block 7: VPS Calculator ──
{
blockType: 'fdVpsCalculator',
anchorId: 'kalkylator',
heading: 'Beräkna din månadskostnad',
description: 'Dra i reglagen och se priset direkt. Ingen bindningstid.',
orderCtaText: 'Beställ nu',
orderCtaLink: '/kontakt',
contactCtaText: 'Behöver du hjälp att välja?',
contactCtaLink: '/kontakt',
sectionBackground: 'gray',
// Pricing fields: use your existing values or set new ones:
pricingCpuPerCore: 149,
pricingRamPerGb: 49,
pricingSsdPerGb: 2,
pricingHddPerGb: 0.5,
pricingWindowsLicense: 299,
discountPercent: 0,
},
// ── Block 8: Wide Card — Migration ──
{
blockType: 'fdWideCard',
heading: 'Från eget serverrum till Server Direkt — på en vecka',
body: 'Migrering behöver inte vara komplicerat. Vi analyserar er nuvarande miljö, planerar flytten, och genomför den med minimalt driftstopp. De flesta kunder är igång inom en vecka — inklusive test och verifiering.',
ctaText: 'Boka migreringskonsultation',
ctaLink: '/kontakt',
cardBackground: 'navy',
buttonColor: 'yellow',
sectionBackground: 'white',
},
// ── Block 9: Card Grid — Migration process ──
{
blockType: 'fdCardGrid',
layout: '1-1-1',
cardStyle: 'gray',
sectionBackground: 'white',
cards: [
{
displayMode: 'content',
heading: '1. Analys',
contentLines: [
{ text: 'Vi kartlägger er nuvarande servermiljö — hårdvara, mjukvara, belastning, beroenden.', style: 'normal' },
{ text: 'Ni får en tydlig migreringsplan med tidslinje.', style: 'bold' },
],
},
{
displayMode: 'content',
heading: '2. Migration',
contentLines: [
{ text: 'Vi sätter upp er virtuella miljö, migrerar data och applikationer, och kör parallellt tills allt fungerar felfritt.', style: 'normal' },
],
},
{
displayMode: 'content',
heading: '3. Drift & support',
contentLines: [
{ text: 'Er nya miljö är igång. Vi övervakar, backar upp och supportar.', style: 'normal' },
{ text: 'Ni fokuserar på kärnverksamheten — vi sköter infrastrukturen.', style: 'bold' },
],
},
],
},
// ── Block 10: Testimonial ──
{
blockType: 'fdTestimonial',
heading: 'Företag som gjort bytet',
layout: 'featured',
sectionBackground: 'gray',
testimonials: [
{
quote: 'Vi hade tre fysiska servrar som var slut på garanti. Istället för att köpa nya investerade vi i Server Direkt — halva kostnaden, dubbla prestandan, och noll underhåll.',
authorName: 'Placeholder',
authorRole: 'VD',
authorCompany: 'Svenskt företag, 50 anställda',
},
],
},
// ── Block 11: FAQ ──
{
blockType: 'fdFaq',
heading: 'Vanliga frågor om virtuella servrar',
theme: 'light',
items: [
{
question: 'Vad är en virtuell server egentligen?',
answer: lexical('En virtuell server (VM) är en mjukvarudefinerad dator som körs på kraftfull fysisk hårdvara i vårt datacenter. Den beter sig exakt som en fysisk server — men utan att ni behöver äga, underhålla eller byta hårdvara.'),
},
{
question: 'Klarar virtuella servrar tunga arbetsbelastningar?',
answer: lexical('Ja. Våra VM:ar körs på enterprise-hårdvara med dedikerade resurser. Ni kan konfigurera upp till 32 CPU-kärnor, 128 GB RAM och stora SSD/HDD-volymer.'),
},
{
question: 'Kan jag köra Windows?',
answer: lexical('Absolut. Vi stöder Windows Server (licensiering kan tillkomma) och alla vanliga Linux-distributioner.'),
},
{
question: 'Hur fungerar backup?',
answer: lexical('Daglig automatisk backup ingår. Kopior sparas i upp till 30 dagar och kan återställas snabbt vid behov.'),
},
{
question: 'Vad händer om en fysisk server i ert datacenter går sönder?',
answer: lexical('Er VM flyttas automatiskt till annan hårdvara — ofta utan märkbart avbrott. Det är kärnan i virtualisering: ni är aldrig beroende av en enskild maskin.'),
},
{
question: 'Kan jag kombinera virtuella servrar med colocation?',
answer: lexical('Ja, och det är en vanlig setup. Många kunder kör primära system virtuellt och har specifik hårdvara i colocation — allt kopplat via vårt Layer 2-nätverk.'),
},
],
},
// ── Block 12: CTA Banner ──
{
blockType: 'fdCtaBanner',
heading: 'Starta er första virtuella server idag',
subheading: 'Ingen bindningstid. Ingen hårdvara att köpa. Betala för det ni använder — skala när ni behöver.',
ctaText: 'Beställ nu',
ctaLink: '/kontakt',
secondaryCtaText: 'Prata med en expert',
secondaryCtaLink: 'tel:+46771101010',
sectionBackground: 'navy',
alignment: 'center',
size: 'large',
},
],
}
// ══════════════════════════════════════════════
// Run it
// ══════════════════════════════════════════════
async function main() {
console.log('🚀 Fiber Direkt — Landing Page Seeder')
console.log(` Target: ${PAYLOAD_URL}`)
console.log(' Pages will be created as DRAFTS\n')
const results = await Promise.allSettled([
createPage(dataDirektPage),
createPage(rackDirektPage),
createPage(serverDirektPage),
])
const succeeded = results.filter((r) => r.status === 'fulfilled' && r.value).length
const failed = results.filter((r) => r.status === 'rejected' || !r.value).length
console.log(`\n────────────────────────`)
console.log(`✅ Created: ${succeeded} pages`)
if (failed) console.log(`❌ Failed: ${failed} pages`)
console.log(`\nNext steps:`)
console.log(` 1. Open Payload admin → Pages`)
console.log(` 2. Upload images and assign to image fields`)
console.log(` 3. Check richText fields (FAQ answers, FDText body) — may need re-entry`)
console.log(` 4. Review & publish when ready`)
}
main()

View File

@ -2,10 +2,12 @@ import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { getPayload } from 'payload'
import config from '@payload-config'
import type { Post, Media } from '@/payload-types'
import type { Post, Media, Category } from '@/payload-types'
import { FDImage } from '@/components/FDImage'
import { FDButton } from '@/components/FDButton'
import { generateMeta } from '@/utilities/generateMeta'
import { formatDateTime } from '@/utilities/formatDateTime'
import { getCachedGlobal } from '@/utilities/getGlobals'
import RichText from '@/components/RichText'
type Args = {
@ -15,6 +17,7 @@ type Args = {
export default async function PostPage({ params }: Args) {
const { slug } = await params
const payload = await getPayload({ config })
const settings = (await getCachedGlobal('post-settings' as any, 1)()) as any
const result = await payload.find({
collection: 'posts',
@ -28,8 +31,23 @@ export default async function PostPage({ params }: Args) {
const heroImage = post.heroImage as Media | undefined
const authors = (post.populatedAuthors as any[]) ?? []
const categories = (post.categories ?? []).filter(
(c): c is Category => typeof c === 'object' && c !== null,
)
const summary = post.meta?.description ?? null
/* ── Settings ─────────────────────────────────────────────────────── */
const backLinkText = settings?.backLinkText || '← Tillbaka till nyheter'
const showCategories = settings?.showCategoriesOnPost ?? true
const showRelated = settings?.showRelatedPosts ?? true
const relatedHeading = settings?.relatedPostsHeading || 'Relaterade inlägg'
const cta = settings?.cta ?? { enabled: true, text: 'Kontakta oss', link: '/kontakt', variant: 'primary' }
/* ── Related posts ────────────────────────────────────────────────── */
const relatedPosts = (post.relatedPosts ?? []).filter(
(p): p is Post => typeof p === 'object' && p !== null,
)
return (
<article className="min-h-screen bg-white dark:bg-fd-navy">
@ -40,7 +58,7 @@ export default async function PostPage({ params }: Args) {
href="/posts"
className="inline-flex items-center gap-2 font-joey text-sm text-fd-navy/50 dark:text-white/50 hover:text-fd-navy dark:hover:text-white transition-colors mb-8"
>
Tillbaka till nyheter
{backLinkText}
</a>
<h1 className="font-joey-heavy text-[2rem] md:text-[2.5rem] lg:text-[2.875rem] leading-[1.1] text-fd-navy dark:text-fd-yellow mb-4">
@ -48,11 +66,25 @@ export default async function PostPage({ params }: Args) {
</h1>
{summary && (
<p className="font-joey text-base md:text-lg text-fd-navy/70 dark:text-white/70 leading-relaxed mb-8">
<p className="font-joey text-base md:text-lg text-fd-navy/70 dark:text-white/70 leading-relaxed mb-6">
{summary}
</p>
)}
{/* Categories */}
{showCategories && categories.length > 0 && (
<div className="flex flex-wrap gap-2 mb-6">
{categories.map((cat) => (
<span
key={cat.id}
className="inline-block font-joey-medium text-sm px-3 py-1 rounded-full bg-fd-gray-light dark:bg-white/10 text-fd-navy/70 dark:text-white/70"
>
{cat.title}
</span>
))}
</div>
)}
{heroImage?.url && (
<figure className="mb-6">
<div className="rounded-[20px] overflow-hidden">
@ -93,11 +125,85 @@ export default async function PostPage({ params }: Args) {
</div>
)}
<a href="/kontakt" className="fd-btn-primary">
Kontakta oss
</a>
{cta?.enabled && cta.text && (
<FDButton
href={cta.link || '/kontakt'}
variant={cta.variant === 'outline' ? 'outline' : 'primary'}
>
{cta.text}
</FDButton>
)}
</div>
{/* ── Related posts ───────────────────────────────────────────── */}
{showRelated && relatedPosts.length > 0 && (
<div className="mt-16 md:mt-24 pt-12 border-t border-fd-navy/10 dark:border-white/10">
<h2 className="font-joey-heavy text-fd-h2 text-fd-navy dark:text-fd-yellow mb-8">
{relatedHeading}
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-10">
{relatedPosts.slice(0, 3).map((related) => {
const relHero = related.heroImage as Media | undefined
const relCats = (related.categories ?? []).filter(
(c): c is Category => typeof c === 'object' && c !== null,
)
return (
<a
key={related.id}
href={`/posts/${related.slug}`}
className="group flex flex-col"
>
<h3 className="font-joey-bold text-fd-navy dark:text-white text-lg leading-snug mb-2 group-hover:opacity-70 transition-opacity line-clamp-2">
{related.title}
</h3>
<div className="flex items-center gap-3 mb-3 flex-wrap">
{related.publishedAt && (
<p className="font-joey text-xs text-fd-navy/50 dark:text-white/50">
{formatDateTime(related.publishedAt)}
</p>
)}
{relCats.length > 0 && (
<div className="flex flex-wrap gap-1">
{relCats.map((cat) => (
<span
key={cat.id}
className="inline-block font-joey-medium text-xs px-2 py-0.5 rounded-full bg-fd-gray-light dark:bg-white/10 text-fd-navy/60 dark:text-white/60"
>
{cat.title}
</span>
))}
</div>
)}
</div>
<div className="relative aspect-[4/3] rounded-[16px] overflow-hidden bg-fd-navy/5 dark:bg-white/5">
{relHero?.url ? (
<FDImage
media={relHero}
size="medium"
fill
className="object-cover group-hover:scale-[1.03] transition-transform duration-500"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
fallbackAlt={related.title}
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<span className="font-joey text-sm text-fd-navy/30 dark:text-white/30">
Ingen bild
</span>
</div>
)}
</div>
</a>
)
})}
</div>
</div>
)}
</div>
</article>
)

View File

@ -2,48 +2,97 @@ import type { Metadata } from 'next/types'
import configPromise from '@payload-config'
import { getPayload } from 'payload'
import React from 'react'
import type { Post, Media } from '@/payload-types'
import type { Post, Media, Category } from '@/payload-types'
import { FDImage } from '@/components/FDImage'
import { formatDateTime } from '@/utilities/formatDateTime'
import { Pagination } from '@/components/Pagination'
import { getCachedGlobal } from '@/utilities/getGlobals'
import PageClient from './page.client'
export const dynamic = 'force-dynamic'
const listingBgMap: Record<string, { bg: string; heading: string; body: string; card: string; meta: string; tag: string; tagText: string }> = {
white: {
bg: 'bg-white dark:bg-fd-navy',
heading: 'text-fd-navy dark:text-fd-yellow',
body: 'text-fd-navy/70 dark:text-white/70',
card: 'text-fd-navy dark:text-white',
meta: 'text-fd-navy/50 dark:text-white/50',
tag: 'bg-fd-gray-light dark:bg-white/10',
tagText: 'text-fd-navy/70 dark:text-white/70',
},
gray: {
bg: 'bg-fd-gray-light dark:bg-fd-navy',
heading: 'text-fd-navy dark:text-fd-yellow',
body: 'text-fd-navy/70 dark:text-white/70',
card: 'text-fd-navy dark:text-white',
meta: 'text-fd-navy/50 dark:text-white/50',
tag: 'bg-white dark:bg-white/10',
tagText: 'text-fd-navy/70 dark:text-white/70',
},
navy: {
bg: 'bg-fd-navy',
heading: 'text-fd-yellow',
body: 'text-white/70',
card: 'text-white',
meta: 'text-white/50',
tag: 'bg-white/10',
tagText: 'text-white/70',
},
}
export default async function Page() {
const payload = await getPayload({ config: configPromise })
const settings = (await getCachedGlobal('post-settings' as any, 1)()) as any
const perPage = settings?.postsPerPage || 12
const showCategories = settings?.showCategoriesOnCards ?? true
const posts = await payload.find({
collection: 'posts',
depth: 1,
limit: 12,
limit: perPage,
overrideAccess: false,
select: {
title: true,
slug: true,
heroImage: true,
categories: true,
meta: true,
publishedAt: true,
},
})
const heading = settings?.listingHeading || 'Nyheter'
const description = settings?.listingDescription || null
const bgKey = settings?.listingBackground || 'white'
const theme = listingBgMap[bgKey] || listingBgMap.white
return (
<div className="min-h-screen bg-white dark:bg-fd-navy">
<div className={`min-h-screen ${theme.bg}`}>
<PageClient />
<div className="max-w-[1200px] mx-auto px-6 md:px-8 pt-12 md:pt-16 pb-16 md:pb-24">
<h1 className="font-joey-heavy text-4xl md:text-5xl lg:text-[3.25rem] text-fd-navy dark:text-fd-yellow mb-10 md:mb-14">
Nyheter!
</h1>
<div className="mb-10 md:mb-14">
<h1 className={`font-joey-heavy text-fd-h1 ${theme.heading}`}>
{heading}
</h1>
{description && (
<p className={`font-joey text-fd-body-lg mt-3 max-w-[640px] ${theme.body}`}>
{description}
</p>
)}
</div>
{posts.docs.length === 0 ? (
<p className="font-joey text-fd-navy/50 dark:text-white/50 py-16">Inga inlägg hittades.</p>
<p className={`font-joey ${theme.meta} py-16`}>Inga inlägg hittades.</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-12">
{posts.docs.map((post) => {
const p = post as Post
const heroImage = p.heroImage as Media | undefined
const categories = (p.categories ?? []) as Category[]
return (
<a
@ -51,15 +100,29 @@ export default async function Page() {
href={`/posts/${p.slug}`}
className="group flex flex-col"
>
<h2 className="font-joey-bold text-fd-navy dark:text-white text-xl md:text-[1.375rem] leading-snug mb-2 group-hover:text-fd-navy/70 dark:group-hover:text-fd-yellow transition-colors line-clamp-2">
<h2 className={`font-joey-bold ${theme.card} text-xl md:text-[1.375rem] leading-snug mb-2 group-hover:opacity-70 transition-opacity line-clamp-2`}>
{p.title}
</h2>
{p.publishedAt && (
<p className="font-joey text-sm text-fd-navy/50 dark:text-white/50 mb-4">
{formatDateTime(p.publishedAt)}
</p>
)}
<div className="flex items-center gap-3 mb-4 flex-wrap">
{p.publishedAt && (
<p className={`font-joey text-sm ${theme.meta}`}>
{formatDateTime(p.publishedAt)}
</p>
)}
{showCategories && categories.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{categories.map((cat) => (
<span
key={cat.id}
className={`inline-block font-joey-medium text-xs px-2.5 py-0.5 rounded-full ${theme.tag} ${theme.tagText}`}
>
{cat.title}
</span>
))}
</div>
)}
</div>
<div className="relative aspect-[4/3] rounded-[20px] overflow-hidden bg-fd-navy/5 dark:bg-white/5">
{heroImage?.url ? (
@ -73,7 +136,7 @@ export default async function Page() {
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<span className="font-joey text-fd-navy/30 dark:text-white/30 text-sm">Ingen bild</span>
<span className={`font-joey text-sm ${theme.meta}`}>Ingen bild</span>
</div>
)}
</div>
@ -94,9 +157,12 @@ export default async function Page() {
)
}
export function generateMetadata(): Metadata {
return {
title: 'Nyheter | Fiber Direkt',
description: 'Senaste nytt från Fiber Direkt',
}
export async function generateMetadata(): Promise<Metadata> {
const settings = (await getCachedGlobal('post-settings' as any, 1)()) as any
const title = settings?.metaTitle || `${settings?.listingHeading || 'Nyheter'} | Fiber Direkt`
const description =
settings?.metaDescription || settings?.listingDescription || 'Senaste nytt från Fiber Direkt'
return { title, description }
}

View File

@ -1,57 +1,170 @@
import type { Metadata } from 'next/types'
import { CollectionArchive } from '@/components/CollectionArchive'
import { PageRange } from '@/components/PageRange'
import { Pagination } from '@/components/Pagination'
import configPromise from '@payload-config'
import { getPayload } from 'payload'
import React from 'react'
import PageClient from './page.client'
import { notFound } from 'next/navigation'
import type { Post, Media, Category } from '@/payload-types'
import { FDImage } from '@/components/FDImage'
import { formatDateTime } from '@/utilities/formatDateTime'
import { Pagination } from '@/components/Pagination'
import { getCachedGlobal } from '@/utilities/getGlobals'
import PageClient from './page.client'
export const dynamic = 'force-dynamic'
export const revalidate = 600
const listingBgMap: Record<string, { bg: string; heading: string; body: string; card: string; meta: string; tag: string; tagText: string }> = {
white: {
bg: 'bg-white dark:bg-fd-navy',
heading: 'text-fd-navy dark:text-fd-yellow',
body: 'text-fd-navy/70 dark:text-white/70',
card: 'text-fd-navy dark:text-white',
meta: 'text-fd-navy/50 dark:text-white/50',
tag: 'bg-fd-gray-light dark:bg-white/10',
tagText: 'text-fd-navy/70 dark:text-white/70',
},
gray: {
bg: 'bg-fd-gray-light dark:bg-fd-navy',
heading: 'text-fd-navy dark:text-fd-yellow',
body: 'text-fd-navy/70 dark:text-white/70',
card: 'text-fd-navy dark:text-white',
meta: 'text-fd-navy/50 dark:text-white/50',
tag: 'bg-white dark:bg-white/10',
tagText: 'text-fd-navy/70 dark:text-white/70',
},
navy: {
bg: 'bg-fd-navy',
heading: 'text-fd-yellow',
body: 'text-white/70',
card: 'text-white',
meta: 'text-white/50',
tag: 'bg-white/10',
tagText: 'text-white/70',
},
}
type Args = {
params: Promise<{
pageNumber: string
}>
params: Promise<{ pageNumber: string }>
}
export default async function Page({ params: paramsPromise }: Args) {
const { pageNumber } = await paramsPromise
const payload = await getPayload({ config: configPromise })
const sanitizedPageNumber = Number(pageNumber)
if (!Number.isInteger(sanitizedPageNumber)) notFound()
const payload = await getPayload({ config: configPromise })
const settings = (await getCachedGlobal('post-settings' as any, 1)()) as any
const perPage = settings?.postsPerPage || 12
const showCategories = settings?.showCategoriesOnCards ?? true
const posts = await payload.find({
collection: 'posts',
depth: 1,
limit: 12,
limit: perPage,
page: sanitizedPageNumber,
overrideAccess: false,
select: {
title: true,
slug: true,
heroImage: true,
categories: true,
meta: true,
publishedAt: true,
},
})
if (sanitizedPageNumber > posts.totalPages) notFound()
const heading = settings?.listingHeading || 'Nyheter'
const description = settings?.listingDescription || null
const bgKey = settings?.listingBackground || 'white'
const theme = listingBgMap[bgKey] || listingBgMap.white
return (
<div className="pt-24 pb-24">
<div className={`min-h-screen ${theme.bg}`}>
<PageClient />
<div className="container mb-16">
<div className="prose dark:prose-invert max-w-none">
<h1>Posts</h1>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 pt-12 md:pt-16 pb-16 md:pb-24">
<div className="mb-10 md:mb-14">
<h1 className={`font-joey-heavy text-fd-h1 ${theme.heading}`}>
{heading}
</h1>
{description && (
<p className={`font-joey text-fd-body-lg mt-3 max-w-[640px] ${theme.body}`}>
{description}
</p>
)}
</div>
</div>
<div className="container mb-8">
<PageRange
collection="posts"
currentPage={posts.page}
limit={12}
totalDocs={posts.totalDocs}
/>
</div>
<CollectionArchive posts={posts.docs} />
<div className="container">
{posts?.page && posts?.totalPages > 1 && (
<Pagination page={posts.page} totalPages={posts.totalPages} />
{posts.docs.length === 0 ? (
<p className={`font-joey ${theme.meta} py-16`}>Inga inlägg hittades.</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-12">
{posts.docs.map((post) => {
const p = post as Post
const heroImage = p.heroImage as Media | undefined
const categories = (p.categories ?? []) as Category[]
return (
<a
key={p.id}
href={`/posts/${p.slug}`}
className="group flex flex-col"
>
<h2 className={`font-joey-bold ${theme.card} text-xl md:text-[1.375rem] leading-snug mb-2 group-hover:opacity-70 transition-opacity line-clamp-2`}>
{p.title}
</h2>
<div className="flex items-center gap-3 mb-4 flex-wrap">
{p.publishedAt && (
<p className={`font-joey text-sm ${theme.meta}`}>
{formatDateTime(p.publishedAt)}
</p>
)}
{showCategories && categories.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{categories.map((cat) => (
<span
key={cat.id}
className={`inline-block font-joey-medium text-xs px-2.5 py-0.5 rounded-full ${theme.tag} ${theme.tagText}`}
>
{cat.title}
</span>
))}
</div>
)}
</div>
<div className="relative aspect-[4/3] rounded-[20px] overflow-hidden bg-fd-navy/5 dark:bg-white/5">
{heroImage?.url ? (
<FDImage
media={heroImage}
size="medium"
fill
className="object-cover group-hover:scale-[1.03] transition-transform duration-500"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
fallbackAlt={p.title}
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<span className={`font-joey text-sm ${theme.meta}`}>Ingen bild</span>
</div>
)}
</div>
</a>
)
})}
</div>
)}
{posts.totalPages > 1 && posts.page && (
<div className="mt-16">
<Pagination page={posts.page} totalPages={posts.totalPages} />
</div>
)}
</div>
</div>
)
@ -59,7 +172,10 @@ export default async function Page({ params: paramsPromise }: Args) {
export async function generateMetadata({ params: paramsPromise }: Args): Promise<Metadata> {
const { pageNumber } = await paramsPromise
return {
title: `Posts Page ${pageNumber || ''}`,
}
const settings = (await getCachedGlobal('post-settings' as any, 1)()) as any
const title = settings?.metaTitle
|| `${settings?.listingHeading || 'Nyheter'} — Sida ${pageNumber} | Fiber Direkt`
return { title }
}

208
src/globals/PostSettings.ts Normal file
View File

@ -0,0 +1,208 @@
import type { GlobalConfig } from 'payload'
import { authenticated } from '../access/authenticated'
import { revalidatePostSettings } from './hooks/revalidatePostSettings'
export const PostSettings: GlobalConfig = {
slug: 'post-settings',
label: 'Inläggsinställningar',
admin: {
group: 'Globala inställningar',
description: 'Styr utseende och innehåll för nyhetssidan och enskilda inlägg.',
},
access: {
update: authenticated,
read: () => true,
},
hooks: {
afterChange: [revalidatePostSettings],
},
fields: [
/* ── Listing page ───────────────────────────────────────────────────── */
{
type: 'collapsible',
label: 'Listningssida (/posts)',
admin: {
initCollapsed: false,
},
fields: [
{
name: 'listingHeading',
type: 'text',
label: 'Rubrik',
localized: true,
defaultValue: 'Nyheter',
admin: {
description: 'H1-rubrik som visas överst på sidan.',
},
},
{
name: 'listingDescription',
type: 'textarea',
label: 'Beskrivning',
localized: true,
admin: {
description:
'Valfri kort text under rubriken. Används även som meta description om inget annat anges.',
},
},
{
name: 'listingBackground',
type: 'select',
label: 'Bakgrundsfärg',
defaultValue: 'white',
options: [
{ label: 'Vit', value: 'white' },
{ label: 'Grå', value: 'gray' },
{ label: 'Navy', value: 'navy' },
],
},
{
name: 'postsPerPage',
type: 'number',
label: 'Antal inlägg per sida',
defaultValue: 12,
min: 3,
max: 48,
admin: {
step: 3,
description: 'Bör vara delbart med 3 för jämnt rutnät.',
},
},
{
name: 'showCategoriesOnCards',
type: 'checkbox',
label: 'Visa kategorier på kort',
defaultValue: true,
admin: {
description: 'Visar kategori-taggar på varje inläggskort i listan.',
},
},
],
},
/* ── Single post page ───────────────────────────────────────────────── */
{
type: 'collapsible',
label: 'Enskilt inlägg (/posts/[slug])',
admin: {
initCollapsed: false,
},
fields: [
{
name: 'backLinkText',
type: 'text',
label: 'Tillbaka-länk',
localized: true,
defaultValue: '← Tillbaka till nyheter',
admin: {
description: 'Text på länken som leder tillbaka till listningssidan.',
},
},
{
name: 'showCategoriesOnPost',
type: 'checkbox',
label: 'Visa kategorier på inlägg',
defaultValue: true,
admin: {
description: 'Visar kategori-taggar under meta-info på enskilda inlägg.',
},
},
{
name: 'showRelatedPosts',
type: 'checkbox',
label: 'Visa relaterade inlägg',
defaultValue: true,
admin: {
description: 'Visar relaterade inlägg längst ner på sidan (om de finns).',
},
},
{
name: 'relatedPostsHeading',
type: 'text',
label: 'Rubrik för relaterade inlägg',
localized: true,
defaultValue: 'Relaterade inlägg',
admin: {
condition: (data) => Boolean(data?.showRelatedPosts),
},
},
{
type: 'group',
name: 'cta',
label: 'Call to Action',
fields: [
{
name: 'enabled',
type: 'checkbox',
label: 'Visa CTA-knapp',
defaultValue: true,
},
{
name: 'text',
type: 'text',
label: 'Knapptext',
localized: true,
defaultValue: 'Kontakta oss',
admin: {
condition: (_, siblingData) => Boolean(siblingData?.enabled),
},
},
{
name: 'link',
type: 'text',
label: 'Länk',
defaultValue: '/kontakt',
admin: {
condition: (_, siblingData) => Boolean(siblingData?.enabled),
},
},
{
name: 'variant',
type: 'select',
label: 'Stil',
defaultValue: 'primary',
options: [
{ label: 'Primär (gul)', value: 'primary' },
{ label: 'Kontur', value: 'outline' },
],
admin: {
condition: (_, siblingData) => Boolean(siblingData?.enabled),
},
},
],
},
],
},
/* ── SEO overrides ──────────────────────────────────────────────────── */
{
type: 'collapsible',
label: 'SEO',
admin: {
initCollapsed: true,
},
fields: [
{
name: 'metaTitle',
type: 'text',
label: 'Meta-titel',
localized: true,
admin: {
description:
'Titel för <title>-taggen. Lämna tomt för att använda listningsrubriken + "| Fiber Direkt".',
},
},
{
name: 'metaDescription',
type: 'textarea',
label: 'Meta-beskrivning',
localized: true,
admin: {
description:
'Lämna tomt för att använda listningsbeskrivningen ovan.',
},
},
],
},
],
}

View File

@ -0,0 +1,7 @@
import type { GlobalAfterChangeHook } from 'payload'
import { revalidateTag } from 'next/cache'
export const revalidatePostSettings: GlobalAfterChangeHook = ({ req: { payload } }) => {
payload.logger.info({ msg: 'Revalidating post settings' })
revalidateTag('global_post-settings', 'max')
}

View File

@ -16,7 +16,6 @@ export async function up({ db }: MigrateUpArgs): Promise<void> {
ALTER TABLE "pages_blocks_fd_header_text_image" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "pages_blocks_fd_hero" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "pages_blocks_fd_pricing_card" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "pages_blocks_fd_service_calculator" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "pages_blocks_fd_service_chooser" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "pages_blocks_fd_services_grid" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "pages_blocks_fd_statistics" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
@ -46,7 +45,6 @@ export async function up({ db }: MigrateUpArgs): Promise<void> {
ALTER TABLE "_pages_v_blocks_fd_header_text_image" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "_pages_v_blocks_fd_hero" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "_pages_v_blocks_fd_pricing_card" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "_pages_v_blocks_fd_service_calculator" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "_pages_v_blocks_fd_service_chooser" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "_pages_v_blocks_fd_services_grid" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
ALTER TABLE "_pages_v_blocks_fd_statistics" ADD COLUMN IF NOT EXISTS "anchor_id" varchar;
@ -78,7 +76,6 @@ export async function down({ db }: MigrateDownArgs): Promise<void> {
ALTER TABLE "pages_blocks_fd_header_text_image" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "pages_blocks_fd_hero" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "pages_blocks_fd_pricing_card" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "pages_blocks_fd_service_calculator" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "pages_blocks_fd_service_chooser" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "pages_blocks_fd_services_grid" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "pages_blocks_fd_statistics" DROP COLUMN IF EXISTS "anchor_id";
@ -108,7 +105,6 @@ export async function down({ db }: MigrateDownArgs): Promise<void> {
ALTER TABLE "_pages_v_blocks_fd_header_text_image" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "_pages_v_blocks_fd_hero" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "_pages_v_blocks_fd_pricing_card" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "_pages_v_blocks_fd_service_calculator" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "_pages_v_blocks_fd_service_chooser" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "_pages_v_blocks_fd_services_grid" DROP COLUMN IF EXISTS "anchor_id";
ALTER TABLE "_pages_v_blocks_fd_statistics" DROP COLUMN IF EXISTS "anchor_id";

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
CREATE TYPE "public"."enum_post_settings_listing_background" AS ENUM('white', 'gray', 'navy');
CREATE TYPE "public"."enum_post_settings_cta_variant" AS ENUM('primary', 'outline');
CREATE TABLE "post_settings" (
"id" serial PRIMARY KEY NOT NULL,
"listing_background" "enum_post_settings_listing_background" DEFAULT 'white',
"posts_per_page" numeric DEFAULT 12,
"show_categories_on_cards" boolean DEFAULT true,
"show_categories_on_post" boolean DEFAULT true,
"show_related_posts" boolean DEFAULT true,
"cta_enabled" boolean DEFAULT true,
"cta_link" varchar DEFAULT '/kontakt',
"cta_variant" "enum_post_settings_cta_variant" DEFAULT 'primary',
"updated_at" timestamp(3) with time zone,
"created_at" timestamp(3) with time zone
);
CREATE TABLE "post_settings_locales" (
"listing_heading" varchar DEFAULT 'Nyheter',
"listing_description" varchar,
"back_link_text" varchar DEFAULT '← Tillbaka till nyheter',
"related_posts_heading" varchar DEFAULT 'Relaterade inlägg',
"cta_text" varchar DEFAULT 'Kontakta oss',
"meta_title" varchar,
"meta_description" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL
);
ALTER TABLE "post_settings_locales" ADD CONSTRAINT "post_settings_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."post_settings"("id") ON DELETE cascade ON UPDATE no action;
CREATE UNIQUE INDEX "post_settings_locales_locale_parent_id_unique" ON "post_settings_locales" USING btree ("_locale","_parent_id");`)
}
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
DROP TABLE "post_settings" CASCADE;
DROP TABLE "post_settings_locales" CASCADE;
DROP TYPE "public"."enum_post_settings_listing_background";
DROP TYPE "public"."enum_post_settings_cta_variant";`)
}

View File

@ -1,20 +1,9 @@
import * as migration_20260221_114950_baseline from './20260221_114950_baseline';
import * as migration_20260221_115051_add_service_calculator from './20260221_115051_add_service_calculator';
import * as migration_20260224_091812_add_anchor_links from './20260224_091812_add_anchor_links';
import * as migration_20260224_133833 from './20260224_133833';
import * as migration_20260226_095439 from './20260226_095439';
import * as migration_20260302_145030 from './20260302_145030';
export const migrations = [
{
up: migration_20260221_114950_baseline.up,
down: migration_20260221_114950_baseline.down,
name: '20260221_114950_baseline',
},
{
up: migration_20260221_115051_add_service_calculator.up,
down: migration_20260221_115051_add_service_calculator.down,
name: '20260221_115051_add_service_calculator',
},
{
up: migration_20260224_091812_add_anchor_links.up,
down: migration_20260224_091812_add_anchor_links.down,
@ -28,6 +17,11 @@ export const migrations = [
{
up: migration_20260226_095439.up,
down: migration_20260226_095439.down,
name: '20260226_095439'
name: '20260226_095439',
},
{
up: migration_20260302_145030.up,
down: migration_20260302_145030.down,
name: '20260302_145030'
},
];

View File

@ -107,6 +107,7 @@ export interface Config {
'announcement-bar': AnnouncementBar;
'popup-announcement': PopupAnnouncement;
'site-settings': SiteSetting;
'post-settings': PostSetting;
};
globalsSelect: {
header: HeaderSelect<false> | HeaderSelect<true>;
@ -114,6 +115,7 @@ export interface Config {
'announcement-bar': AnnouncementBarSelect<false> | AnnouncementBarSelect<true>;
'popup-announcement': PopupAnnouncementSelect<false> | PopupAnnouncementSelect<true>;
'site-settings': SiteSettingsSelect<false> | SiteSettingsSelect<true>;
'post-settings': PostSettingsSelect<false> | PostSettingsSelect<true>;
};
locale: 'sv' | 'en';
user: User;
@ -3292,6 +3294,61 @@ export interface SiteSetting {
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* Styr utseende och innehåll för nyhetssidan och enskilda inlägg.
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "post-settings".
*/
export interface PostSetting {
id: number;
/**
* H1-rubrik som visas överst sidan.
*/
listingHeading?: string | null;
/**
* Valfri kort text under rubriken. Används även som meta description om inget annat anges.
*/
listingDescription?: string | null;
listingBackground?: ('white' | 'gray' | 'navy') | null;
/**
* Bör vara delbart med 3 för jämnt rutnät.
*/
postsPerPage?: number | null;
/**
* Visar kategori-taggar varje inläggskort i listan.
*/
showCategoriesOnCards?: boolean | null;
/**
* Text länken som leder tillbaka till listningssidan.
*/
backLinkText?: string | null;
/**
* Visar kategori-taggar under meta-info enskilda inlägg.
*/
showCategoriesOnPost?: boolean | null;
/**
* Visar relaterade inlägg längst ner sidan (om de finns).
*/
showRelatedPosts?: boolean | null;
relatedPostsHeading?: string | null;
cta?: {
enabled?: boolean | null;
text?: string | null;
link?: string | null;
variant?: ('primary' | 'outline') | null;
};
/**
* Titel för <title>-taggen. Lämna tomt för att använda listningsrubriken + "| Fiber Direkt".
*/
metaTitle?: string | null;
/**
* Lämna tomt för att använda listningsbeskrivningen ovan.
*/
metaDescription?: string | null;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "header_select".
@ -3486,6 +3543,34 @@ export interface SiteSettingsSelect<T extends boolean = true> {
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "post-settings_select".
*/
export interface PostSettingsSelect<T extends boolean = true> {
listingHeading?: T;
listingDescription?: T;
listingBackground?: T;
postsPerPage?: T;
showCategoriesOnCards?: T;
backLinkText?: T;
showCategoriesOnPost?: T;
showRelatedPosts?: T;
relatedPostsHeading?: T;
cta?:
| T
| {
enabled?: T;
text?: T;
link?: T;
variant?: T;
};
metaTitle?: T;
metaDescription?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "TaskSchedulePublish".

View File

@ -18,6 +18,8 @@ import { AnnouncementBar } from '@/globals/AnnouncementBar'
import { PopupAnnouncement } from '@/globals/PopupAnnouncement/config'
import { SiteSettings } from '@/globals/SiteSettings'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
import { PostSettings } from '@/globals/PostSettings'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@ -79,7 +81,7 @@ export default buildConfig({
}),
collections: [Pages, Posts, Media, Categories, Users],
cors: [getServerSideURL()].filter(Boolean),
globals: [Header, Footer, AnnouncementBar, PopupAnnouncement, SiteSettings],
globals: [Header, Footer, AnnouncementBar, PopupAnnouncement, SiteSettings, PostSettings],
plugins,
...(process.env.SMTP_HOST ? {
email: nodemailerAdapter({

File diff suppressed because one or more lines are too long