Compare commits
6
Commits
cdf0380f79
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a5c275c52 | ||
|
|
b2b90bd1bf | ||
|
|
aadc93e870 | ||
|
|
0f436ec05a | ||
|
|
1264b577a1 | ||
|
|
61a58b31a7 |
@@ -18,3 +18,14 @@ ADMIN_PASSWORD="change-me"
|
||||
ADMIN_AUTH_SECRET="replace-with-a-long-random-secret"
|
||||
ADMIN_BASIC_AUTH_USER="change-me"
|
||||
ADMIN_BASIC_AUTH_PASS="change-me"
|
||||
|
||||
# --- Production only (server .env) ---
|
||||
# The full stack runs via docker-compose.yml behind Traefik. On the server set:
|
||||
# NEXT_PUBLIC_SITE_URL="https://mohfarawati.de"
|
||||
# NEXT_PUBLIC_ADMIN_URL="https://root.mohfarawati.de"
|
||||
# ADMIN_HOST="root.mohfarawati.de"
|
||||
# Traefik wiring (override only if your server differs from the defaults):
|
||||
# TRAEFIK_NETWORK="proxy" # the external network Traefik is on
|
||||
# TRAEFIK_ENTRYPOINT="websecure" # HTTPS entrypoint name
|
||||
# TRAEFIK_CERTRESOLVER="cf" # cert resolver that covers *.mohfarawati.de
|
||||
# DATABASE_URL is set by docker-compose.yml to the internal db service — do not set it here.
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
quality:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/moh_sass?schema=public
|
||||
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/moh_sass
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import type { Metadata } from "next";
|
||||
import { unstable_noStore as noStore } from "next/cache";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft, ArrowRight, Mail } from "lucide-react";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
|
||||
import { FloatingPreferences } from "@/components/layout/floating-preferences";
|
||||
import { HeroContentMotion, HeroMotionItem, HeroShell, HeroTitle } from "@/components/layout/site-hero";
|
||||
import { LaunchCountdown } from "@/components/site/launch-countdown";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getSiteSettings } from "@/lib/app-config";
|
||||
import { buildLocalizedMetadata } from "@/lib/metadata";
|
||||
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { resolveLocale } from "@/lib/locale";
|
||||
|
||||
// Target launch date for the countdown. Edit this single line to change it.
|
||||
const LAUNCH_DATE_ISO = "2026-08-28T12:00:00Z";
|
||||
@@ -48,7 +44,6 @@ export default async function ComingSoonPage({ params }: ComingSoonPageProps) {
|
||||
const localeKey = resolveLocale(await getLocale().catch(() => siteSettings.defaultLocale), siteSettings.defaultLocale);
|
||||
const t = await getTranslations({ locale: localeKey, namespace: "comingSoon" });
|
||||
const isArabic = localeKey === "ar";
|
||||
const DirectionIcon = isArabic ? ArrowLeft : ArrowRight;
|
||||
|
||||
const lines = [
|
||||
{
|
||||
@@ -68,63 +63,16 @@ export default async function ComingSoonPage({ params }: ComingSoonPageProps) {
|
||||
},
|
||||
];
|
||||
|
||||
const trackedEyebrow = isArabic ? "tracking-normal" : "uppercase tracking-[0.28em]";
|
||||
const trackedLabel = isArabic ? "tracking-normal" : "uppercase tracking-[0.24em]";
|
||||
const trackedPill = isArabic ? "tracking-normal" : "uppercase tracking-[0.12em]";
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen overflow-hidden">
|
||||
<div className="relative h-screen overflow-hidden">
|
||||
<FloatingPreferences locale={localeKey} defaultLocale={siteSettings.defaultLocale} />
|
||||
|
||||
<HeroShell className="min-h-screen" showBridges>
|
||||
<HeroShell className="h-screen" showBridges>
|
||||
<HeroContentMotion className="relative z-10 w-full">
|
||||
<div className="mx-auto w-full max-w-[60rem]">
|
||||
<div className="relative overflow-hidden rounded-[calc(var(--radius-surface)+10px)] border border-foreground/10 bg-background/45 px-6 py-12 shadow-panel backdrop-blur-[26px] dark:border-foreground/12 dark:bg-background/25 sm:px-14 sm:py-16">
|
||||
{/* top hairline highlight */}
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-x-8 top-0 h-px bg-gradient-to-r from-transparent via-foreground/25 to-transparent"
|
||||
/>
|
||||
{/* soft brand glow */}
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute -top-28 left-1/2 h-64 w-[38rem] max-w-[120%] -translate-x-1/2 rounded-full bg-[radial-gradient(closest-side,hsl(var(--brand-primary)/0.18),transparent)] blur-2xl"
|
||||
/>
|
||||
<div className="mx-auto flex w-full max-w-[60rem] flex-col items-center text-center">
|
||||
<HeroTitle locale={localeKey} lines={lines} className="mx-auto tracking-normal" />
|
||||
|
||||
<div className="relative flex flex-col items-center text-center">
|
||||
<HeroMotionItem>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2.5 rounded-pill border border-foreground/10 bg-background/55 px-4 py-2 text-xs font-medium text-[hsl(var(--hero-ink)/0.82)] shadow-[var(--shadow-sm)] backdrop-blur-[18px] dark:border-foreground/12 dark:bg-background/20 dark:text-foreground/82",
|
||||
trackedPill,
|
||||
)}
|
||||
>
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-brand-secondary opacity-70" />
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-brand-secondary" />
|
||||
</span>
|
||||
{t("status")}
|
||||
</span>
|
||||
</HeroMotionItem>
|
||||
|
||||
<HeroMotionItem>
|
||||
<p className={cn("mt-7 text-xs font-medium text-foreground/45", trackedEyebrow)}>
|
||||
{t("kicker")}
|
||||
</p>
|
||||
</HeroMotionItem>
|
||||
|
||||
<HeroTitle locale={localeKey} lines={lines} className="mx-auto mt-4 tracking-normal" />
|
||||
|
||||
<HeroMotionItem>
|
||||
<p className="mx-auto mt-8 max-w-[36rem] text-sm leading-7 text-foreground/68 sm:text-base">
|
||||
{t("description")}
|
||||
</p>
|
||||
</HeroMotionItem>
|
||||
|
||||
<HeroMotionItem className="mt-11 flex flex-col items-center gap-4">
|
||||
<span className={cn("text-xs font-medium text-foreground/45", trackedLabel)}>
|
||||
{t("countdownLabel")}
|
||||
</span>
|
||||
<HeroMotionItem className="mt-12">
|
||||
<LaunchCountdown
|
||||
targetIso={LAUNCH_DATE_ISO}
|
||||
arabic={isArabic}
|
||||
@@ -137,23 +85,6 @@ export default async function ComingSoonPage({ params }: ComingSoonPageProps) {
|
||||
}}
|
||||
/>
|
||||
</HeroMotionItem>
|
||||
|
||||
<HeroMotionItem className="mt-12 flex w-full flex-col items-center justify-center gap-3 sm:w-auto sm:flex-row">
|
||||
<Button asChild size="lg" className="w-full sm:w-auto">
|
||||
<Link href={getLocalizedPath(localeKey, "/contact", siteSettings.defaultLocale)}>
|
||||
<Mail className="h-4 w-4" />
|
||||
{t("primaryCta")}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" size="lg" className="w-full sm:w-auto">
|
||||
<Link href={getLocalizedPath(localeKey, "/", siteSettings.defaultLocale)}>
|
||||
{t("secondaryCta")}
|
||||
<DirectionIcon className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</HeroMotionItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</HeroContentMotion>
|
||||
</HeroShell>
|
||||
|
||||
+23
-3
@@ -12,8 +12,9 @@ services:
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-https://mohfarawati.de}
|
||||
NEXT_PUBLIC_ADMIN_URL: ${NEXT_PUBLIC_ADMIN_URL:-https://root.mohfarawati.de}
|
||||
ADMIN_HOST: ${ADMIN_HOST:-root.mohfarawati.de}
|
||||
SITE_RUNTIME_ORIGIN: ${SITE_RUNTIME_ORIGIN:-http://127.0.0.1:3000}
|
||||
DATABASE_URL: postgresql://postgres:postgres@db:5432/moh_sass?schema=public
|
||||
DATABASE_URL: postgresql://postgres:postgres@db:5432/moh_sass
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?ADMIN_PASSWORD must be set in .env}
|
||||
ADMIN_AUTH_SECRET: ${ADMIN_AUTH_SECRET:?ADMIN_AUTH_SECRET must be set in .env (use a long random string)}
|
||||
ADMIN_BASIC_AUTH_USER: ${ADMIN_BASIC_AUTH_USER:?ADMIN_BASIC_AUTH_USER must be set in .env}
|
||||
@@ -27,11 +28,25 @@ services:
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
ports:
|
||||
- "${PORT:-3014}:3000"
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.network=${TRAEFIK_NETWORK:-proxy}
|
||||
# HTTPS router — public site + www + admin subdomain all hit this one app;
|
||||
# the app's middleware routes root.mohfarawati.de to the admin internally.
|
||||
- "traefik.http.routers.sass.rule=Host(`mohfarawati.de`) || Host(`www.mohfarawati.de`) || Host(`root.mohfarawati.de`)"
|
||||
- traefik.http.routers.sass.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}
|
||||
- traefik.http.routers.sass.tls=true
|
||||
- traefik.http.routers.sass.tls.certresolver=${TRAEFIK_CERTRESOLVER:-cf}
|
||||
- traefik.http.services.sass.loadbalancer.server.port=3000
|
||||
# HTTP router → redirect to HTTPS
|
||||
- "traefik.http.routers.sass-http.rule=Host(`mohfarawati.de`) || Host(`www.mohfarawati.de`) || Host(`root.mohfarawati.de`)"
|
||||
- traefik.http.routers.sass-http.entrypoints=web
|
||||
- traefik.http.routers.sass-http.middlewares=sass-redirect
|
||||
- traefik.http.middlewares.sass-redirect.redirectscheme.scheme=https
|
||||
volumes:
|
||||
- media_uploads:/app/public/uploads/media
|
||||
networks:
|
||||
- proxy
|
||||
- appnet
|
||||
|
||||
db:
|
||||
@@ -57,3 +72,8 @@ volumes:
|
||||
|
||||
networks:
|
||||
appnet:
|
||||
# Shared external Traefik network (same one the other projects use).
|
||||
# Override the name per server with TRAEFIK_NETWORK in .env.
|
||||
proxy:
|
||||
external: true
|
||||
name: ${TRAEFIK_NETWORK:-proxy}
|
||||
|
||||
+6
-3
@@ -8,9 +8,12 @@ import * as schema from "./schema";
|
||||
* used by the other projects. Replaces the old Prisma client (`lib/prisma.ts`).
|
||||
* A single connection is reused across hot reloads in dev.
|
||||
*/
|
||||
const connectionString =
|
||||
process.env.DATABASE_URL ??
|
||||
"postgresql://postgres:postgres@localhost:5432/moh_sass?schema=public";
|
||||
// Strip any query string (e.g. a leftover Prisma `?schema=public`) — postgres.js
|
||||
// forwards unknown params to the server as startup options and Postgres rejects
|
||||
// them ("unrecognized configuration parameter"). `public` is the default schema.
|
||||
const connectionString = (
|
||||
process.env.DATABASE_URL ?? "postgresql://postgres:postgres@localhost:5432/moh_sass"
|
||||
).split("?")[0];
|
||||
|
||||
const globalForDb = globalThis as unknown as {
|
||||
dbClient: ReturnType<typeof postgres> | undefined;
|
||||
|
||||
+9
-7
@@ -21,16 +21,10 @@
|
||||
}
|
||||
},
|
||||
"comingSoon": {
|
||||
"badge": "تحديث مهني",
|
||||
"kicker": "الموقع قيد إعادة البناء",
|
||||
"titleLineOne": "الموقع",
|
||||
"titleLineTwo": "قيد التطوير",
|
||||
"titleLineThree": "وسيعود قريباً",
|
||||
"description": "أعيد بناء الموقع ليعرض الخدمات، الأعمال المختارة، وطريقة التعاون بشكل مباشر ومنظم.",
|
||||
"primaryCta": "ابدأ مشروعاً",
|
||||
"secondaryCta": "العودة للرئيسية",
|
||||
"status": "قيد التطوير · يعود قريباً",
|
||||
"countdownLabel": "الإطلاق خلال",
|
||||
"unitDays": "أيام",
|
||||
"unitHours": "ساعات",
|
||||
"unitMinutes": "دقائق",
|
||||
@@ -306,7 +300,15 @@
|
||||
"tools": {
|
||||
"eyebrow": "الأدوات",
|
||||
"title": "الأدوات اليومية",
|
||||
"items": ["Figma", "Adobe Illustrator", "Photoshop", "Next.js", "TypeScript", "Tailwind CSS", "Docker"]
|
||||
"items": [
|
||||
"Figma",
|
||||
"Adobe Illustrator",
|
||||
"Photoshop",
|
||||
"Next.js",
|
||||
"TypeScript",
|
||||
"Tailwind CSS",
|
||||
"Docker"
|
||||
]
|
||||
},
|
||||
"process": {
|
||||
"eyebrow": "كيف بشتغل",
|
||||
|
||||
+9
-7
@@ -21,16 +21,10 @@
|
||||
}
|
||||
},
|
||||
"comingSoon": {
|
||||
"badge": "Professionelles Update",
|
||||
"kicker": "Die Website wird neu aufgebaut",
|
||||
"titleLineOne": "Website",
|
||||
"titleLineTwo": "in Bearbeitung",
|
||||
"titleLineThree": "bald wieder online",
|
||||
"description": "Ich baue die Website neu auf, damit Leistungen, ausgewählte Arbeiten und Zusammenarbeit klarer, direkter und ohne Wiederholungen sichtbar werden.",
|
||||
"primaryCta": "Projekt starten",
|
||||
"secondaryCta": "Zur Startseite",
|
||||
"status": "In Entwicklung · bald zurück",
|
||||
"countdownLabel": "Start in",
|
||||
"unitDays": "Tage",
|
||||
"unitHours": "Stunden",
|
||||
"unitMinutes": "Minuten",
|
||||
@@ -306,7 +300,15 @@
|
||||
"tools": {
|
||||
"eyebrow": "Tools",
|
||||
"title": "Täglicher Stack",
|
||||
"items": ["Figma", "Adobe Illustrator", "Photoshop", "Next.js", "TypeScript", "Tailwind CSS", "Docker"]
|
||||
"items": [
|
||||
"Figma",
|
||||
"Adobe Illustrator",
|
||||
"Photoshop",
|
||||
"Next.js",
|
||||
"TypeScript",
|
||||
"Tailwind CSS",
|
||||
"Docker"
|
||||
]
|
||||
},
|
||||
"process": {
|
||||
"eyebrow": "Wie ich arbeite",
|
||||
|
||||
+9
-7
@@ -21,16 +21,10 @@
|
||||
}
|
||||
},
|
||||
"comingSoon": {
|
||||
"badge": "Professional update",
|
||||
"kicker": "The site is being rebuilt",
|
||||
"titleLineOne": "Website",
|
||||
"titleLineTwo": "under development",
|
||||
"titleLineThree": "returning soon",
|
||||
"description": "I am rebuilding the site to present services, selected work, and collaboration details with sharper structure and less noise.",
|
||||
"primaryCta": "Start a project",
|
||||
"secondaryCta": "Back to homepage",
|
||||
"status": "In development · back soon",
|
||||
"countdownLabel": "Launching in",
|
||||
"unitDays": "Days",
|
||||
"unitHours": "Hours",
|
||||
"unitMinutes": "Minutes",
|
||||
@@ -306,7 +300,15 @@
|
||||
"tools": {
|
||||
"eyebrow": "Tools",
|
||||
"title": "Daily stack",
|
||||
"items": ["Figma", "Adobe Illustrator", "Photoshop", "Next.js", "TypeScript", "Tailwind CSS", "Docker"]
|
||||
"items": [
|
||||
"Figma",
|
||||
"Adobe Illustrator",
|
||||
"Photoshop",
|
||||
"Next.js",
|
||||
"TypeScript",
|
||||
"Tailwind CSS",
|
||||
"Docker"
|
||||
]
|
||||
},
|
||||
"process": {
|
||||
"eyebrow": "How I work",
|
||||
|
||||
Reference in New Issue
Block a user