261 lines
8.1 KiB
Bash
261 lines
8.1 KiB
Bash
#!/bin/bash
|
|
# ============================================================================
|
|
# add-anchor-links.sh (macOS + Linux compatible)
|
|
# Run from your project root: bash add-anchor-links.sh
|
|
# ============================================================================
|
|
set -e
|
|
|
|
echo "🔗 Adding anchor link support to all FD blocks..."
|
|
echo ""
|
|
|
|
# ── Step 1: Create shared anchorField ──────────────────────────────────────
|
|
|
|
mkdir -p src/fields
|
|
|
|
cat > src/fields/anchorField.ts << 'FIELDEOF'
|
|
import type { Field } from 'payload'
|
|
|
|
/**
|
|
* Shared anchorId field for all FD blocks.
|
|
* Allows editors to set an anchor link ID on any section,
|
|
* enabling direct linking like /page#section-name
|
|
*
|
|
* Usage in block config:
|
|
* import { anchorField } from '@/fields/anchorField'
|
|
* fields: [ ...contentFields, anchorField ]
|
|
*
|
|
* Usage in block component:
|
|
* <section id={anchorId || undefined} ...>
|
|
*/
|
|
export const anchorField: Field = {
|
|
name: 'anchorId',
|
|
type: 'text',
|
|
label: 'Ankarlänk-ID',
|
|
admin: {
|
|
description:
|
|
'Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.',
|
|
position: 'sidebar',
|
|
},
|
|
validate: (value: unknown) => {
|
|
if (!value) return true
|
|
if (typeof value === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(value)) return true
|
|
return 'Använd bara små bokstäver (a-z), siffror (0-9) och bindestreck (-). Inga mellanslag.'
|
|
},
|
|
}
|
|
FIELDEOF
|
|
|
|
echo "✅ Created src/fields/anchorField.ts"
|
|
|
|
# ── Step 2: Add anchorField to all block configs ───────────────────────────
|
|
|
|
BLOCKS=(
|
|
FDHeroBlock
|
|
FDAlternateHeroBlock
|
|
FDTextBlock
|
|
FDPricingCardBlock
|
|
FDFaqBlock
|
|
FDContactBlock
|
|
FDContactFormBlock
|
|
FDDataTableBlock
|
|
FDCardGridBlock
|
|
FDCtaSideImageBlock
|
|
FDCtaBannerBlock
|
|
FDCodeEmbedBlock
|
|
FDFeatureAnnouncementBlock
|
|
FDHeaderTextImageBlock
|
|
FDServicesGridBlock
|
|
FDServiceChooserBlock
|
|
FDUspChecklistBlock
|
|
FDUspTableBlock
|
|
FDTechPropertiesBlock
|
|
FDStatisticsBlock
|
|
FDTestimonialBlock
|
|
FDTeamBlock
|
|
FDVideoBlock
|
|
FDVpsCalculatorBlock
|
|
FDWideCardBlock
|
|
FDServiceCalculatorBlock
|
|
)
|
|
|
|
for BLOCK in "${BLOCKS[@]}"; do
|
|
CONFIG="src/blocks/$BLOCK/config.ts"
|
|
if [ ! -f "$CONFIG" ]; then
|
|
echo "⚠️ Skipped $BLOCK — config not found"
|
|
continue
|
|
fi
|
|
|
|
# Skip if already has anchorField
|
|
if grep -q "anchorField" "$CONFIG"; then
|
|
echo "⏭️ Skipped $BLOCK — anchorField already present"
|
|
continue
|
|
fi
|
|
|
|
# Add import after the first line
|
|
perl -i -pe 'if ($. == 1) { $_ .= "import { anchorField } from '\''\@/fields/anchorField'\''\n" }' "$CONFIG"
|
|
|
|
# Add anchorField before the last "]," in the file
|
|
perl -i -0pe 's/( \],\n\})\s*$/ anchorField,\n$1/s' "$CONFIG"
|
|
|
|
echo "✅ Updated $CONFIG"
|
|
done
|
|
|
|
echo ""
|
|
|
|
# ── Step 3: Add anchorId to all block components ──────────────────────────
|
|
|
|
echo "🔧 Updating block components..."
|
|
|
|
for BLOCK in "${BLOCKS[@]}"; do
|
|
COMP="src/blocks/$BLOCK/Component.tsx"
|
|
if [ ! -f "$COMP" ]; then
|
|
echo "⚠️ Skipped $BLOCK component — not found"
|
|
continue
|
|
fi
|
|
|
|
# Skip if already has anchorId
|
|
if grep -q "anchorId" "$COMP"; then
|
|
echo "⏭️ Skipped $BLOCK component — anchorId already present"
|
|
continue
|
|
fi
|
|
|
|
# Add anchorId to destructured props: insert before "}) => {"
|
|
perl -i -pe 's/\}\) => \{/ anchorId,\n}) => {/' "$COMP"
|
|
|
|
# Add id={anchorId || undefined} to the first <section
|
|
# Handle single-line: <section className=...
|
|
perl -i -pe 'if (!$done && s/<section className/<section id={anchorId || undefined} className/) { $done=1 }' "$COMP"
|
|
|
|
# Handle multi-line: <section alone on a line (like FDHeroBlock)
|
|
if ! grep -q 'id={anchorId' "$COMP"; then
|
|
perl -i -pe 'if (!$done && /^\s+<section\s*$/) { $done=1; s|<section|<section id={anchorId \|\| undefined}| }' "$COMP"
|
|
fi
|
|
|
|
echo "✅ Updated $COMP"
|
|
done
|
|
|
|
echo ""
|
|
|
|
# ── Step 4: Add smooth scrolling to globals.css ───────────────────────────
|
|
|
|
GLOBALS_CSS="src/app/(frontend)/globals.css"
|
|
|
|
if grep -q "scroll-behavior" "$GLOBALS_CSS" 2>/dev/null; then
|
|
echo "⏭️ Smooth scroll already in globals.css"
|
|
else
|
|
TMPFILE=$(mktemp)
|
|
cat > "$TMPFILE" << 'CSSEOF'
|
|
/* Smooth scroll + header offset for anchor links */
|
|
html {
|
|
scroll-behavior: smooth;
|
|
scroll-padding-top: 100px;
|
|
}
|
|
|
|
CSSEOF
|
|
cat "$GLOBALS_CSS" >> "$TMPFILE"
|
|
mv "$TMPFILE" "$GLOBALS_CSS"
|
|
echo "✅ Added smooth scroll to globals.css"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── Step 5: Create BackToTop component ────────────────────────────────────
|
|
|
|
mkdir -p src/components/BackToTop
|
|
|
|
cat > src/components/BackToTop/index.tsx << 'BTEOF'
|
|
'use client'
|
|
import React, { useState, useEffect, useCallback } from 'react'
|
|
import { ChevronUpIcon } from 'lucide-react'
|
|
|
|
/**
|
|
* Floating "back to top" button.
|
|
* Appears after scrolling down 400px, smooth-scrolls to top on click.
|
|
* Styled to match FD design system.
|
|
*/
|
|
export const BackToTop: React.FC = () => {
|
|
const [visible, setVisible] = useState(false)
|
|
|
|
useEffect(() => {
|
|
const onScroll = () => {
|
|
setVisible(window.scrollY > 400)
|
|
}
|
|
window.addEventListener('scroll', onScroll, { passive: true })
|
|
return () => window.removeEventListener('scroll', onScroll)
|
|
}, [])
|
|
|
|
const scrollToTop = useCallback(() => {
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
}, [])
|
|
|
|
return (
|
|
<button
|
|
onClick={scrollToTop}
|
|
aria-label="Tillbaka till toppen"
|
|
className={`
|
|
fixed bottom-6 right-6 z-40
|
|
w-11 h-11 rounded-full
|
|
bg-fd-navy/80 backdrop-blur-sm
|
|
text-white hover:text-fd-yellow
|
|
border border-white/10 hover:border-fd-yellow/30
|
|
shadow-lg hover:shadow-xl
|
|
flex items-center justify-center
|
|
transition-all duration-300 ease-in-out cursor-pointer
|
|
${visible
|
|
? 'opacity-100 translate-y-0 pointer-events-auto'
|
|
: 'opacity-0 translate-y-4 pointer-events-none'
|
|
}
|
|
`}
|
|
>
|
|
<ChevronUpIcon className="w-5 h-5" />
|
|
</button>
|
|
)
|
|
}
|
|
BTEOF
|
|
|
|
echo "✅ Created src/components/BackToTop/index.tsx"
|
|
|
|
# ── Step 6: Add BackToTop to layout ───────────────────────────────────────
|
|
|
|
LAYOUT="src/app/(frontend)/layout.tsx"
|
|
|
|
if grep -q "BackToTop" "$LAYOUT" 2>/dev/null; then
|
|
echo "⏭️ BackToTop already in layout"
|
|
else
|
|
# Add import after Footer import
|
|
perl -i -pe 'if (/import \{ Footer \}/) { $_ .= "import { BackToTop } from '\''\@/components/BackToTop'\''\n" }' "$LAYOUT"
|
|
|
|
# Add component after <Footer />
|
|
perl -i -pe 's|<Footer />|<Footer />\n <BackToTop />|' "$LAYOUT"
|
|
|
|
echo "✅ Added BackToTop to layout.tsx"
|
|
fi
|
|
|
|
echo ""
|
|
echo "════════════════════════════════════════════════════════════════"
|
|
echo "✅ All done! Now follow the deployment steps below."
|
|
echo "════════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
echo "LOCAL:"
|
|
echo " 1. Review changes: git diff"
|
|
echo " 2. Clean local migrations:"
|
|
echo " rm -f src/migrations/2025*.ts src/migrations/2026*.ts"
|
|
echo " echo 'export {}' > src/migrations/index.ts"
|
|
echo " 3. Commit and push:"
|
|
echo " git add -A"
|
|
echo " git commit -m 'feat: anchor links, smooth scroll, back-to-top'"
|
|
echo " git push origin main"
|
|
echo ""
|
|
echo "SERVER:"
|
|
echo " cd /var/www/fiberdirekt"
|
|
echo " git pull origin main"
|
|
echo " npm install"
|
|
echo " npx payload generate:types"
|
|
echo " npx payload migrate:create add-anchor-links"
|
|
echo " npx payload migrate"
|
|
echo " npm run build && pm2 restart fiberdirekt"
|
|
echo ""
|
|
echo "If migrate:create says 'no changes detected', skip migrate"
|
|
echo "steps and go straight to build. Block fields are stored as"
|
|
echo "JSON so new optional fields usually don't need SQL migration."
|
|
echo ""
|