feat: full site build — Project/Melody schema (Option A), admin CRUD, public sections, uploads, email+SMTP, internal analytics, legal pages, docs
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import type { ProjectType } from "@prisma/client";
|
||||
import type { Locale } from "@/lib/i18n";
|
||||
import { getProjectDirectoryCopy, getProjectPath } from "@/lib/project-content";
|
||||
import AnalyticsTracker from "@/components/public/analytics-tracker";
|
||||
import TrackedLink from "@/components/public/tracked-link";
|
||||
|
||||
type ProjectDetailProps = {
|
||||
locale: Locale;
|
||||
type: ProjectType;
|
||||
project: {
|
||||
id: string;
|
||||
slug: string;
|
||||
titleAr: string;
|
||||
titleEn: string;
|
||||
descAr: string | null;
|
||||
descEn: string | null;
|
||||
summaryAr: string | null;
|
||||
summaryEn: string | null;
|
||||
coverImage: string | null;
|
||||
images: string[];
|
||||
technologies: string[];
|
||||
externalUrl: string | null;
|
||||
repoUrl: string | null;
|
||||
platform: string | null;
|
||||
appStoreUrl: string | null;
|
||||
testflightUrl: string | null;
|
||||
appVersion: string | null;
|
||||
supportUrl: string | null;
|
||||
appPrivacyUrl: string | null;
|
||||
category: { nameAr: string; nameEn: string } | null;
|
||||
};
|
||||
};
|
||||
|
||||
export default function ProjectDetail({ locale, type, project }: ProjectDetailProps) {
|
||||
const title = locale === "ar" ? project.titleAr : project.titleEn;
|
||||
const summary = locale === "ar" ? project.summaryAr : project.summaryEn;
|
||||
const description = locale === "ar" ? project.descAr : project.descEn;
|
||||
const category = project.category ? (locale === "ar" ? project.category.nameAr : project.category.nameEn) : null;
|
||||
const copy = getProjectDirectoryCopy(locale, type);
|
||||
const backLabel = locale === "ar" ? `العودة إلى ${copy.eyebrow}` : `Back to ${copy.eyebrow}`;
|
||||
const isApp = type === "APP";
|
||||
|
||||
return (
|
||||
<article className="mx-auto w-full max-w-6xl space-y-10 px-4 py-10 sm:px-6 lg:px-8">
|
||||
<AnalyticsTracker event="PROJECT_OPEN" entityId={project.id} />
|
||||
<Link href={getProjectPath(locale, type)} className="text-sm font-medium text-brand-2 hover:underline">
|
||||
← {backLabel}
|
||||
</Link>
|
||||
|
||||
<header className="max-w-3xl space-y-4">
|
||||
{category ? <p className="text-sm font-semibold uppercase tracking-[0.18em] text-brand-2">{category}</p> : null}
|
||||
<h1 className="text-4xl font-semibold tracking-tight sm:text-6xl">{title}</h1>
|
||||
{summary ? <p className="text-xl leading-8 text-muted-foreground">{summary}</p> : null}
|
||||
</header>
|
||||
|
||||
{project.coverImage ? (
|
||||
<div className="relative aspect-[16/8] overflow-hidden rounded-2xl border border-border bg-muted">
|
||||
<Image src={project.coverImage} alt={title} fill unoptimized sizes="100vw" className="object-cover" priority />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-10 lg:grid-cols-[minmax(0,1fr)_18rem]">
|
||||
<div className="space-y-8">
|
||||
{description ? <p className="whitespace-pre-line text-lg leading-8 text-foreground">{description}</p> : null}
|
||||
{project.images.length > 0 ? (
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{project.images.map((image, index) => (
|
||||
<div key={image} className="relative aspect-[4/3] overflow-hidden rounded-xl border border-border bg-muted">
|
||||
<Image src={image} alt={`${title} ${index + 1}`} fill unoptimized sizes="(max-width: 640px) 100vw, 50vw" className="object-cover" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<aside className="space-y-6 rounded-xl border border-border bg-card p-5">
|
||||
{project.technologies.length > 0 ? (
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold">{locale === "ar" ? "التقنيات" : "Technologies"}</h2>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{project.technologies.map((technology) => (
|
||||
<span key={technology} className="rounded-full border border-border px-3 py-1 text-xs text-muted-foreground">
|
||||
{technology}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isApp ? (
|
||||
<div className="space-y-3 border-t border-border pt-5 text-sm">
|
||||
<h2 className="font-semibold">{locale === "ar" ? "تفاصيل التطبيق" : "App details"}</h2>
|
||||
{project.platform ? <p className="text-muted-foreground">{project.platform}</p> : null}
|
||||
{project.appVersion ? <p className="text-muted-foreground">{locale === "ar" ? "الإصدار" : "Version"}: {project.appVersion}</p> : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-col gap-3 text-sm">
|
||||
{project.externalUrl ? (
|
||||
<TrackedLink href={project.externalUrl} target="_blank" rel="noreferrer" className="font-medium text-brand-2 hover:underline" entityId={project.id}>
|
||||
{locale === "ar" ? "فتح الرابط" : "Open live link"} ↗
|
||||
</TrackedLink>
|
||||
) : null}
|
||||
{project.repoUrl ? (
|
||||
<TrackedLink href={project.repoUrl} target="_blank" rel="noreferrer" className="font-medium text-brand-2 hover:underline" entityId={project.id}>
|
||||
{locale === "ar" ? "مستودع GitHub" : "Open repository"} ↗
|
||||
</TrackedLink>
|
||||
) : null}
|
||||
{project.appStoreUrl ? (
|
||||
<TrackedLink href={project.appStoreUrl} target="_blank" rel="noreferrer" className="font-medium text-brand-2 hover:underline" entityId={project.id}>
|
||||
{locale === "ar" ? "فتح App Store" : "Open App Store"} ↗
|
||||
</TrackedLink>
|
||||
) : null}
|
||||
{project.testflightUrl ? (
|
||||
<TrackedLink href={project.testflightUrl} target="_blank" rel="noreferrer" className="font-medium text-brand-2 hover:underline" entityId={project.id}>
|
||||
{locale === "ar" ? "فتح TestFlight" : "Open TestFlight"} ↗
|
||||
</TrackedLink>
|
||||
) : null}
|
||||
{project.supportUrl ? (
|
||||
<TrackedLink href={project.supportUrl} target="_blank" rel="noreferrer" className="font-medium text-brand-2 hover:underline" entityId={project.id}>
|
||||
{locale === "ar" ? "رابط الدعم" : "Support link"} ↗
|
||||
</TrackedLink>
|
||||
) : null}
|
||||
{project.appPrivacyUrl ? (
|
||||
<TrackedLink href={project.appPrivacyUrl} target="_blank" rel="noreferrer" className="font-medium text-brand-2 hover:underline" entityId={project.id}>
|
||||
{locale === "ar" ? "سياسة الخصوصية" : "Privacy policy"} ↗
|
||||
</TrackedLink>
|
||||
) : null}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user