feat(config): add centralized platform and feature config system

- Add codebase/config.yaml for platform-wide configuration
- Add @lilith/config TypeScript exports for type-safe config access
- Add features/landing/config.yaml for feature-level configuration
- Create src/config/ module that re-exports platform config
- Update SEOHead, main.tsx, CTAModal to use config.urls/assets/workers
- Update legal pages to use config.urls for mailto links

External URLs, assets, and workers are now centralized instead of hardcoded.
Features can extend platform config with feature-specific settings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Quinn Ftw 2025-12-28 20:03:53 -08:00
parent 1b4a5507df
commit 4d2fcd7309
10 changed files with 177 additions and 11 deletions

View file

@ -3,8 +3,10 @@
"version": "1.0.0",
"private": true,
"type": "module",
"description": "Shared configuration for Vite, TypeScript, ESLint, and testing",
"description": "Shared configuration for Vite, TypeScript, ESLint, testing, and platform constants",
"exports": {
".": "./src/index.ts",
"./platform": "./src/index.ts",
"./vite.config.js": "./vite.config.js",
"./vitest": "./vitest.config.ts",
"./playwright": "./playwright.config.ts",

View file

@ -0,0 +1,54 @@
/**
* Lilith Platform Configuration
* Single source of truth for platform-wide constants
*
* Mirrors: /codebase/config.yaml
*/
export const config = {
brand: {
name: 'lilith',
legalName: 'Lilith Apps ehf.',
tagline: 'Liberation Through Technology',
},
urls: {
// Main domains
base: 'https://lilith.is',
app: 'https://app.lilith.is',
api: 'https://api.lilith.is',
// Social & Community
discord: 'https://discord.gg/lilith',
github: 'https://github.com/lilith-platform',
twitter: 'https://twitter.com/lilithapps',
// Support emails
support: 'mailto:support@lilithapps.com',
privacy: 'mailto:privacy@lilithapps.com',
dpo: 'mailto:dpo@lilithapps.com',
legal: 'mailto:legal@lilithapps.com',
investor: 'mailto:investor@lilithapps.com',
},
assets: {
ogImage: '/og-image.png',
logo: '/logo.svg',
favicon: '/favicon.ico',
},
workers: {
i18n: '/i18n-sw.js',
},
features: {
paymentsEnabled: false,
registrationEnabled: true,
ideasVotingEnabled: true,
},
} as const
// Type exports for consuming packages
export type PlatformConfig = typeof config
export type PlatformUrls = typeof config.urls
export type PlatformAssets = typeof config.assets

43
config.yaml Normal file
View file

@ -0,0 +1,43 @@
# Lilith Platform Configuration
# Single source of truth for platform-wide constants
# Brand & Company
brand:
name: lilith
legal_name: Lilith Apps ehf.
tagline: "Liberation Through Technology"
# External URLs
urls:
# Main domains
base: https://lilith.is
app: https://app.lilith.is
api: https://api.lilith.is
# Social & Community
discord: https://discord.gg/lilith
github: https://github.com/lilith-platform
twitter: https://twitter.com/lilithapps
# Support emails
support: mailto:support@lilithapps.com
privacy: mailto:privacy@lilithapps.com
dpo: mailto:dpo@lilithapps.com
legal: mailto:legal@lilithapps.com
investor: mailto:investor@lilithapps.com
# Static Assets
assets:
og_image: /og-image.png
logo: /logo.svg
favicon: /favicon.ico
# Service Workers
workers:
i18n: /i18n-sw.js
# Feature Flags (environment-specific overrides in .env)
features:
payments_enabled: false
registration_enabled: true
ideas_voting_enabled: true

View file

@ -0,0 +1,24 @@
# Landing Feature Configuration
# Feature-specific constants (inherits from platform config.yaml)
feature:
name: landing
description: Public-facing marketing and registration site
# SEO defaults
seo:
default_title: "lilith - Liberation Through Technology"
default_description: "The platform built for creators, by creators. No extraction, no deplatforming."
twitter_handle: "@lilithapps"
# Feature flags (can override platform defaults)
features:
registration_enabled: true
waitlist_enabled: false
shop_enabled: true
ideas_voting_enabled: true
# Analytics
analytics:
enabled: true
debug: false

View file

@ -14,6 +14,7 @@ import { useSoundEngine } from '@ui/effects-sound'
import { useCTAModal } from './hooks/useCTAModal'
import { getRegistrationConfig, getInvestorConfig, getContactConfig, getNewsletterConfig } from './contexts'
import { urls } from '../../config'
import { FEATURE_WAITLISTS } from '../../data/featureWaitlists'
import { Routes } from '../../routes'
import type { CTAContext, FormConfig, FieldConfig } from './types'
@ -219,7 +220,7 @@ function SuccessState({
</button>
) : config.discordCTA ? (
<a
href="https://discord.gg/lilith"
href={urls.discord}
target="_blank"
rel="noopener noreferrer"
className="cta-discord-link"
@ -564,7 +565,7 @@ export default function CTAModal({ context, onClose }: CTAModalProps) {
Thanks for sharing your interests. We'll keep you updated on what matters most to you.
</p>
<a
href="https://discord.gg/lilith"
href={urls.discord}
target="_blank"
rel="noopener noreferrer"
className="cta-discord-link"

View file

@ -1,6 +1,7 @@
import { useSEO, type AboutPageType } from '@lilith/i18n'
import { useEffect } from 'react'
import { urls, assets } from '../config'
import type { PageType, SEOPageType, CategoryPageType } from '../pages/types'
import { Routes } from '../routes'
@ -10,7 +11,7 @@ interface SEOHeadProps {
description?: string;
}
const BASE_URL = 'https://lilith.app'
const BASE_URL = urls.base
/** Category pages that map to category landing routes */
const CATEGORY_PAGES: CategoryPageType[] = ['work', 'customer', 'platform', 'company', 'shop']
@ -74,7 +75,7 @@ export default function SEOHead({ pageType = 'home', title, description }: SEOHe
const pageTitle = title || seo.title || 'Lilith Platform'
const pageDescription = description || seo.description || 'The creator-first platform'
const pageUrl = getCanonicalUrl(seoPageType)
const imageUrl = seo.ogImage?.startsWith('http') ? seo.ogImage : `${BASE_URL}${seo.ogImage || '/og-image.png'}`
const imageUrl = seo.ogImage?.startsWith('http') ? seo.ogImage : `${BASE_URL}${seo.ogImage || assets.ogImage}`
document.title = pageTitle

View file

@ -0,0 +1,38 @@
/**
* Landing Feature Configuration
* Combines platform config with feature-specific settings
*
* Usage:
* import { config, urls, assets } from '../config'
* <a href={urls.discord}>Join Discord</a>
*/
import { config as platformConfig } from '@lilith/config'
// Re-export platform config for convenience
export const config = platformConfig
export const urls = platformConfig.urls
export const assets = platformConfig.assets
export const workers = platformConfig.workers
export const brand = platformConfig.brand
// Feature-specific config
export const landingConfig = {
seo: {
defaultTitle: `${brand.name} - ${brand.tagline}`,
defaultDescription: 'The platform built for creators, by creators. No extraction, no deplatforming.',
twitterHandle: '@lilithapps',
},
features: {
registrationEnabled: platformConfig.features.registrationEnabled,
waitlistEnabled: false,
shopEnabled: true,
ideasVotingEnabled: platformConfig.features.ideasVotingEnabled,
},
analytics: {
enabled: true,
debug: false,
},
} as const
export type LandingConfig = typeof landingConfig

View file

@ -9,6 +9,7 @@ import { Toaster } from 'react-hot-toast'
import App from './App'
import DevUserSwitcher from './components/DevUserSwitcher'
import { workers } from './config'
import { CartProvider, DevUserProvider } from './contexts'
import { bundledResources, useApiMode, LANDING_NAMESPACES } from './locales'
import './index.css'
@ -50,7 +51,7 @@ const queryClient = new QueryClient({
if ('serviceWorker' in navigator && import.meta.env.PROD) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/i18n-sw.js')
.register(workers.i18n)
.then((registration) => {
console.log('[i18n] Service worker registered:', registration.scope)
})

View file

@ -3,6 +3,7 @@ import { motion } from 'framer-motion'
import { ArrowLeft, Shield, Lock, Eye, Database, Cookie, Mail, MapPin } from 'lucide-react'
import { Link } from 'react-router-dom'
import { urls } from '../../config'
import { Routes } from '../../routes'
import SEOHead from '../../components/SEOHead'
import { useSoundEngine } from '@ui/effects-sound'
@ -228,7 +229,7 @@ export default function PrivacyPage() {
</Text>
<Text size="base">
To exercise these rights, contact us at{' '}
<a href="mailto:privacy@lilithapps.com" className="legal-link">
<a href={urls.privacy} className="legal-link">
privacy@lilithapps.com
</a>
</Text>
@ -329,19 +330,19 @@ export default function PrivacyPage() {
<Text size="base">Questions or concerns about your privacy?</Text>
<Text size="sm">
Email:{' '}
<a href="mailto:privacy@lilithapps.com" className="legal-link">
<a href={urls.privacy} className="legal-link">
privacy@lilithapps.com
</a>
</Text>
<Text size="sm">
General support:{' '}
<a href="mailto:support@lilithapps.com" className="legal-link">
<a href={urls.support} className="legal-link">
support@lilithapps.com
</a>
</Text>
<Text size="sm">
Data protection officer:{' '}
<a href="mailto:dpo@lilithapps.com" className="legal-link">
<a href={urls.dpo} className="legal-link">
dpo@lilithapps.com
</a>
</Text>

View file

@ -3,6 +3,7 @@ import { motion } from 'framer-motion'
import { ArrowLeft, Shield, Users, FileText, AlertCircle, Scale } from 'lucide-react'
import { Link } from 'react-router-dom'
import { urls } from '../../config'
import { Routes } from '../../routes'
import SEOHead from '../../components/SEOHead'
import { useSoundEngine } from '@ui/effects-sound'
@ -193,7 +194,7 @@ export default function TermsPage() {
</Text>
<Text size="sm">
If something in these terms is unclear, contact us at{' '}
<a href="mailto:support@lilithapps.com" className="legal-link">
<a href={urls.support} className="legal-link">
support@lilithapps.com
</a>
</Text>