Marketing site (bargevasat.ir) + admin-editable store links + subdomain split
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 4m40s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m7s
CI/CD / Deploy - local stack (db + server + web) (push) Failing after 41s

- New standalone Next.js marketing site under site/ (static export, SEO):
  landing, download/install guide (Bazaar/Myket/iOS-PWA/web), FAQ (JSON-LD),
  privacy, terms, support, /admin link editor. fa RTL, sitemap/robots/manifest.
- Backend: SiteLinksService (JSON-file persisted) + GET /api/site/links (public)
  + POST /api/admin/site/links (X-Admin-Token). ADMIN_TOKEN + Site__DataDir via env.
- compose: hokm-site service (:1520) + hokm_data volume for links JSON.
- CI deploy job builds + deploys the site container.
- deploy/SUBDOMAIN_SPLIT.md: nginx blocks, cert reissue, DNS, ENV split.
- Exclude site/ from root tsc + web docker context.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-08 07:19:43 +03:30
parent 8d0d4dc991
commit 5d38312ef0
39 changed files with 8207 additions and 2 deletions
+85
View File
@@ -0,0 +1,85 @@
"use client";
import { useEffect, useState } from "react";
import { Play, Smartphone, Download } from "lucide-react";
import { fetchLinks, FALLBACK_LINKS, type SiteLinks } from "@/lib/links";
import { APP_URL } from "@/lib/site";
function BazaarIcon() {
return <span className="text-lg">🛒</span>;
}
function MyketIcon() {
return <span className="text-lg">🟢</span>;
}
export function DownloadButtons({ variant = "hero" }: { variant?: "hero" | "full" }) {
const [links, setLinks] = useState<SiteLinks>(FALLBACK_LINKS);
useEffect(() => {
let on = true;
fetchLinks().then((l) => on && setLinks(l));
return () => {
on = false;
};
}, []);
const webUrl = links.webPlayUrl || APP_URL;
return (
<div className={variant === "hero" ? "flex flex-wrap gap-3" : "grid gap-3 sm:grid-cols-2"}>
{/* Always available: play in browser */}
<a href={webUrl} className="flex items-center justify-center gap-2 rounded-2xl btn-gold px-6 py-3.5 text-base">
<Play size={18} /> بازی در مرورگر (رایگان)
</a>
{links.bazaarEnabled && links.bazaarUrl && (
<a
href={links.bazaarUrl}
target="_blank"
rel="noopener"
className="flex items-center justify-center gap-2 rounded-2xl glass px-6 py-3.5 text-base text-cream hover:border-gold/40"
>
<BazaarIcon /> کافهبازار
</a>
)}
{links.myketEnabled && links.myketUrl && (
<a
href={links.myketUrl}
target="_blank"
rel="noopener"
className="flex items-center justify-center gap-2 rounded-2xl glass px-6 py-3.5 text-base text-cream hover:border-gold/40"
>
<MyketIcon /> مایکت
</a>
)}
{links.iosPwaEnabled && (
<a
href="/download#ios"
className="flex items-center justify-center gap-2 rounded-2xl glass px-6 py-3.5 text-base text-cream hover:border-gold/40"
>
<span className="text-lg">🍏</span> نصب روی آیفون (iOS)
</a>
)}
{variant === "full" && links.iosPwaEnabled && (
<a
href="/download#android"
className="flex items-center justify-center gap-2 rounded-2xl glass px-6 py-3.5 text-base text-cream hover:border-gold/40"
>
<Smartphone size={18} /> نصب روی اندروید (PWA)
</a>
)}
{links.directApkEnabled && links.directApkUrl && (
<a
href={links.directApkUrl}
className="flex items-center justify-center gap-2 rounded-2xl glass px-6 py-3.5 text-base text-cream hover:border-gold/40"
>
<Download size={18} /> دانلود مستقیم APK
</a>
)}
</div>
);
}