feat: render CMS-driven public redesign
This commit is contained in:
@@ -1,384 +1,304 @@
|
||||
import React from 'react';
|
||||
import Image from "next/image";
|
||||
import {
|
||||
ArrowUpLeft,
|
||||
BookOpen,
|
||||
Camera,
|
||||
Eye,
|
||||
GraduationCap,
|
||||
Handshake,
|
||||
HeartHandshake,
|
||||
Lightbulb,
|
||||
Sparkles,
|
||||
UsersRound,
|
||||
} from "lucide-react";
|
||||
import ContentActionLink from "@/components/ContentActionLink";
|
||||
import type { AboutAudience, AboutContentPage, SiteContent } from "@/lib/content-cms";
|
||||
import AboutYearArchive from "@/views/about/YearArchive";
|
||||
|
||||
import { Helmet } from '@/lib/helmet';
|
||||
import { Link } from '@/lib/router';
|
||||
|
||||
// ------ دادههای قابل تنظیم ------
|
||||
const SITE_URL = 'https://east-guilan-ce.ir';
|
||||
const SITE_NAME_FA = 'انجمن علمی کامپیوتر شرق گیلان';
|
||||
const ABOUT_CANONICAL = `${SITE_URL}/about`;
|
||||
const ABOUT_TITLE = `درباره ما | ${SITE_NAME_FA}`;
|
||||
const ABOUT_DESCRIPTION =
|
||||
'آشنایی با تاریخچه، مأموریتها و دستاوردهای انجمن علمی کامپیوتر شرق گیلان و راههای مشارکت دانشجویان در برنامههای انجمن.';
|
||||
const ABOUT_KEYWORDS =
|
||||
'انجمن علمی کامپیوتر شرق گیلان, انجمن علمی مهندسی کامپیوتر دانشگاه گیلان، انجمن علمی علوم کامپیوتر، انجمن علمی شرق گیلان، انجمن علمی مهندسی کامپیوتر شرق گیلان، دانشکده فنی و مهندسی شرق گیلان، انجمن علمی کامپیوتر, فعالیتهای دانشجویی, انجمنهای علمی ایران, رویدادهای فناوری، برنامه نویسی، انجمن علمی دانشجویی، دانشگاه گیلان، فنی شرق';
|
||||
|
||||
const ABOUT_STRUCTURED_DATA = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'AboutPage',
|
||||
name: ABOUT_TITLE,
|
||||
description: ABOUT_DESCRIPTION,
|
||||
url: ABOUT_CANONICAL,
|
||||
mainEntity: {
|
||||
'@type': 'Organization',
|
||||
name: SITE_NAME_FA,
|
||||
url: SITE_URL,
|
||||
logo: `${SITE_URL}/favicon.ico`,
|
||||
sameAs: [
|
||||
'https://instagram.com/guilance.ir',
|
||||
'https://t.me/guilance',
|
||||
'https://t.me/guilancea'
|
||||
],
|
||||
areaServed: 'IR',
|
||||
contactPoint: [
|
||||
{
|
||||
'@type': 'ContactPoint',
|
||||
contactType: 'customer support',
|
||||
email: 'eastguilanceassociation@gmail.com',
|
||||
availableLanguage: ['fa'],
|
||||
areaServed: 'IR'
|
||||
}
|
||||
]
|
||||
}
|
||||
type AboutUsProps = {
|
||||
content: AboutContentPage;
|
||||
siteContent: SiteContent;
|
||||
};
|
||||
|
||||
const ORG = {
|
||||
title: 'انجمنهای علمی کامپیوتر گیلان', // تیتر کلی
|
||||
subtitle:
|
||||
'پیوند دانشگاه و صنعت با برگزاری رویدادها، کارگاهها و نشستهای تخصصی — با محوریت رشد مهارتهای فنی و مسیرهای شغلی.',
|
||||
foundedYear: '۱۴۰۴',
|
||||
membersApprox: '۱۰۰+',
|
||||
eventsCount: '۱۰+',
|
||||
volunteersCount: '۲۰+',
|
||||
};
|
||||
const audienceIcons = {
|
||||
students: GraduationCap,
|
||||
alumni: UsersRound,
|
||||
faculty: BookOpen,
|
||||
partners: Handshake,
|
||||
} satisfies Record<AboutAudience["icon"], typeof GraduationCap>;
|
||||
|
||||
// اگر چند انجمن زیرمجموعه دارید، اینجا معرفی کنید
|
||||
const ASSOCIATIONS = [
|
||||
{
|
||||
name: 'انجمن علمی مهندسی کامپیوتر دانشکدهی فنی شرق',
|
||||
university: 'دانشگاه گیلان',
|
||||
city: 'رودسر',
|
||||
foundedYear: '۱۳۹۲',
|
||||
about:
|
||||
'برگزاری رویدادهای آموزشی و صنعتی، منتورینگ دانشجویی، و اتصال دانشجویان به فرصتهای شغلی و پژوهشی.',
|
||||
focusAreas: ['مهندسی نرمافزار', 'هوش مصنوعی و داده', 'طراحی محصول', 'DevOps/امنیت'],
|
||||
links: {
|
||||
website: 'https://east-guilan-ce.ir',
|
||||
instagram: '"https://instagram.com/guilance.ir',
|
||||
telegram: 'https://t.me/guilance',
|
||||
email: 'eastguilanceassociation@gmail.com',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'انجمن علمی مهندسی کامپیوتر دانشکدهی فنی',
|
||||
university: 'دانشگاه گیلان',
|
||||
city: 'رشت',
|
||||
foundedYear: '۱۳۵۳',
|
||||
about: 'برگزاری رویدادهای آموزشی و صنعتی، منتورینگ دانشجویی، و اتصال دانشجویان به فرصتهای شغلی و پژوهشی.',
|
||||
focusAreas: ['مهندسی نرمافزار', 'هوش مصنوعی و داده', 'DevOps'],
|
||||
links: {
|
||||
instagram: 'https://instagram.com/ce.guilan',
|
||||
telegram: 'https://t.me/CSAOEF',
|
||||
email: 'cesa@guilan.ac.ir'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'انجمن علمی علوم کامپیوتر دانشکدهی علومپایه',
|
||||
university: 'دانشگاه گیلان',
|
||||
city: 'رشت',
|
||||
foundedYear: '۱۳۵۳',
|
||||
about: 'برگزاری رویدادهای آموزشی و صنعتی، منتورینگ دانشجویی، و اتصال دانشجویان به فرصتهای شغلی و پژوهشی.',
|
||||
focusAreas: ['امنیت', 'سیستمعامل', 'سختافزار'],
|
||||
links: {
|
||||
instagram: 'https://instagram.com/csguilan',
|
||||
telegram: 'https://t.me/guilanCS',
|
||||
},
|
||||
},
|
||||
const galleryTones = [
|
||||
"bg-gradient-to-br from-primary/20 via-brand-wash to-accent/30",
|
||||
"bg-gradient-to-br from-accent/50 via-secondary to-primary/15",
|
||||
"bg-gradient-to-br from-secondary via-brand-wash to-primary/20",
|
||||
"bg-gradient-to-br from-brand-wash via-accent/25 to-secondary",
|
||||
];
|
||||
|
||||
// کمیتهها/گروههای کاری
|
||||
const COMMITTEES = [
|
||||
{
|
||||
title: 'کمیته آموزش',
|
||||
desc: 'برنامهریزی و اجرای دورهها و کارگاههای مهارتی، هماهنگی با مدرسین و طراحی مسیرهای یادگیری.'
|
||||
},
|
||||
{
|
||||
title: 'کمیته صنعت و اشتغال',
|
||||
desc: 'تعامل با شرکتها، دعوت از متخصصان صنعت، و شبکهسازی برای فرصتهای کارآموزی و استخدام.'
|
||||
},
|
||||
{
|
||||
title: 'کمیته محتوای دیجیتال',
|
||||
desc: 'تولید محتوای آموزشی، خبرنامه، مدیریت شبکههای اجتماعی و پوشش رسانهای رویدادها.'
|
||||
},
|
||||
];
|
||||
function AboutHero({ content, siteContent }: AboutUsProps) {
|
||||
const associationImage = siteContent.logo || {
|
||||
url: "/favicon.ico",
|
||||
alt: siteContent.short_name || "نشان انجمن",
|
||||
};
|
||||
|
||||
// راههای ارتباطی اصلی صفحه
|
||||
const CONTACTS = [
|
||||
{
|
||||
label: 'ایمیل',
|
||||
value: 'eastguilanceassociation@gmail.com',
|
||||
href: 'mailto:eastguilanceassociation@gmail.com',
|
||||
},
|
||||
{
|
||||
label: 'تلگرام',
|
||||
value: '@GuilanCEA',
|
||||
href: 'https://t.me/guilancea',
|
||||
},
|
||||
{
|
||||
label: 'اینستاگرام',
|
||||
value: '@GuilanCE.ir',
|
||||
href: 'https://instagram.com/guilance.ir',
|
||||
},
|
||||
{
|
||||
label: 'وبسایت',
|
||||
value: 'east-guilan-ce.ir',
|
||||
href: 'https://east-guilan-ce.ir',
|
||||
},
|
||||
];
|
||||
|
||||
export default function AboutUs() {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{ABOUT_TITLE}</title>
|
||||
<meta name="description" content={ABOUT_DESCRIPTION} />
|
||||
<meta name="keywords" content={ABOUT_KEYWORDS} />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<link rel="canonical" href={ABOUT_CANONICAL} />
|
||||
<meta property="og:title" content={ABOUT_TITLE} />
|
||||
<meta property="og:description" content={ABOUT_DESCRIPTION} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={ABOUT_CANONICAL} />
|
||||
<meta property="og:site_name" content={SITE_NAME_FA} />
|
||||
<meta property="og:image" content={`${SITE_URL}/favicon.ico`} />
|
||||
<meta property="og:locale" content="fa_IR" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={ABOUT_TITLE} />
|
||||
<meta name="twitter:description" content={ABOUT_DESCRIPTION} />
|
||||
<meta name="twitter:image" content={`${SITE_URL}/favicon.ico`} />
|
||||
<script type="application/ld+json">{JSON.stringify(ABOUT_STRUCTURED_DATA)}</script>
|
||||
</Helmet>
|
||||
<div className="min-h-screen bg-background" dir="rtl">
|
||||
{/* Hero */}
|
||||
<section className="bg-gradient-to-b from-muted/40 to-transparent">
|
||||
<div className="container mx-auto max-w-6xl px-4 py-12">
|
||||
<h1 className="text-3xl md:text-4xl font-extrabold tracking-tight text-foreground">{ORG.title}</h1>
|
||||
<p className="mt-3 text-muted-foreground leading-7">{ORG.subtitle}</p>
|
||||
<section className="relative overflow-hidden border-b border-border bg-secondary/50" aria-labelledby="about-title">
|
||||
<div className="container mx-auto grid max-w-6xl items-start gap-9 px-4 py-12 md:py-16 lg:grid-cols-[1.08fr_0.92fr] lg:gap-14 lg:py-20">
|
||||
<div className="relative z-10 max-w-3xl">
|
||||
{siteContent.faculty_subline ? (
|
||||
<p className="mb-4 inline-flex items-center gap-2 text-sm font-semibold text-primary">
|
||||
<span className="h-2 w-2 rounded-full bg-accent" aria-hidden="true" />
|
||||
{siteContent.faculty_subline}
|
||||
</p>
|
||||
) : null}
|
||||
<h1 id="about-title" className="text-3xl font-extrabold leading-[1.45] tracking-tight text-foreground md:text-5xl">
|
||||
{content.title || content.identity_heading}
|
||||
</h1>
|
||||
<p className="mt-5 max-w-2xl whitespace-pre-line text-base leading-8 text-muted-foreground md:text-lg">
|
||||
{content.intro || content.identity_body}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* آمار کوتاه */}
|
||||
<div className="mt-6 grid grid-cols-2 gap-4 md:grid-cols-4">
|
||||
<Stat label="سال تأسیس" value={ORG.foundedYear} />
|
||||
<Stat label="اعضا" value={ORG.membersApprox} />
|
||||
<Stat label="رویدادها" value={ORG.eventsCount} />
|
||||
<Stat label="داوطلبان" value={ORG.volunteersCount} />
|
||||
<div className="relative mx-auto w-full max-w-xl">
|
||||
<div className="relative grid min-h-[260px] place-items-center overflow-hidden rounded-[2rem] border border-primary/20 bg-primary p-8 md:min-h-[340px]">
|
||||
<div className="absolute -left-10 -top-16 h-56 w-56 rounded-full border border-primary-foreground/20" aria-hidden="true" />
|
||||
<div className="absolute -bottom-24 -right-12 h-64 w-64 rounded-full border border-accent/45" aria-hidden="true" />
|
||||
<div className="relative grid h-36 w-36 place-items-center rounded-[2rem] border border-primary-foreground/25 bg-background/10 p-5 md:h-48 md:w-48">
|
||||
<Image
|
||||
src={associationImage.url}
|
||||
alt={associationImage.alt}
|
||||
width={192}
|
||||
height={192}
|
||||
unoptimized
|
||||
priority
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute bottom-4 left-4 right-4 flex items-center justify-between gap-3 rounded-xl border border-border/70 bg-card/95 px-4 py-3 shadow-sm backdrop-blur-sm md:bottom-6 md:left-6 md:right-6">
|
||||
<span className="inline-flex items-center gap-2 text-sm font-bold text-foreground">
|
||||
<Sparkles aria-hidden="true" className="h-4 w-4 text-primary" />
|
||||
{siteContent.short_name || siteContent.official_name}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">{content.identity_heading}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
{/* درباره ما */}
|
||||
<section className="container mx-auto max-w-6xl px-4 py-10">
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
<div className="md:col-span-2">
|
||||
<Card>
|
||||
<CardHeader title="ماموریت ما" subtitle="چرا اینجا هستیم؟" />
|
||||
<div className="p-6 pt-0 text-sm leading-7 text-muted-foreground">
|
||||
<p>
|
||||
توانمندسازی دانشجویان با مهارتهای موردنیاز صنعت، ایجاد شبکههای حرفهای و فراهمکردن مسیرهای یادگیری
|
||||
پروژهمحور؛ از معرفی نقشها و مسیرهای شغلی تا تمرین مهارتهای نرم و فنی.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
function PurposeSection({ content }: { content: AboutContentPage }) {
|
||||
const cards = [
|
||||
{ title: content.mission_heading, text: content.mission_body, Icon: Lightbulb, tone: "bg-primary text-primary-foreground" },
|
||||
{ title: content.vision_heading, text: content.vision_body, Icon: Eye, tone: "bg-card text-card-foreground" },
|
||||
{ title: content.values_heading, text: content.values_body, Icon: HeartHandshake, tone: "bg-accent text-accent-foreground" },
|
||||
].filter((card) => card.title || card.text);
|
||||
|
||||
<Card className="mt-6">
|
||||
<CardHeader title="ارزشهای ما" subtitle="چه چیزهایی برایمان مهم است؟" />
|
||||
<ul className="p-6 pt-0 grid grid-cols-1 gap-3 text-sm text-muted-foreground md:grid-cols-2">
|
||||
<li className="rounded-xl border p-3">یادگیری پیوسته و اشتراک دانش</li>
|
||||
<li className="rounded-xl border p-3">کیفیت، شفافیت و مسئولیتپذیری</li>
|
||||
<li className="rounded-xl border p-3">فرصت برابر برای مشارکت</li>
|
||||
<li className="rounded-xl border p-3">ارتباط مؤثر با صنعت و جامعه</li>
|
||||
</ul>
|
||||
</Card>
|
||||
</div>
|
||||
if (!cards.length && !content.activities_body) return null;
|
||||
|
||||
{/* تماس سریع */}
|
||||
<div>
|
||||
<Card>
|
||||
<CardHeader title="راههای ارتباطی" subtitle="با ما در ارتباط باشید" />
|
||||
<div className="p-6 pt-0 space-y-3">
|
||||
{CONTACTS.map((c) => (
|
||||
<a
|
||||
key={c.label}
|
||||
href={c.href}
|
||||
target={c.href?.startsWith('http') ? '_blank' : undefined}
|
||||
rel={c.href?.startsWith('http') ? 'noopener noreferrer' : undefined}
|
||||
className="flex items-center justify-between rounded-xl border p-3 transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<span className="text-sm text-foreground">{c.label}</span>
|
||||
<span className="text-xs text-muted-foreground ltr:ml-2 rtl:mr-2 truncate">{c.value}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="mt-6">
|
||||
<CardHeader title="ساعات پاسخگویی" subtitle="پشتیبانی داوطلبانه" />
|
||||
<div className="p-6 pt-0 text-sm text-muted-foreground">
|
||||
<p>روزهای شنبه تا چهارشنبه، ساعت ۱۰ تا ۱۸</p>
|
||||
<p className="mt-2">پاسخها توسط تیم داوطلبان انجام میشود.</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
return (
|
||||
<section aria-label="ماموریت، چشمانداز و ارزشهای انجمن">
|
||||
{cards.length ? (
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{cards.map(({ title, text, Icon, tone }, index) => (
|
||||
<article
|
||||
key={`${title}-${index}`}
|
||||
className={`relative overflow-hidden rounded-3xl border border-border/80 p-6 md:p-7 ${tone}`}
|
||||
>
|
||||
<span className="mb-5 grid h-11 w-11 place-items-center rounded-2xl border border-current/15 bg-background/15">
|
||||
<Icon aria-hidden="true" className="h-5 w-5" />
|
||||
</span>
|
||||
{title ? <h2 className="text-xl font-bold leading-8">{title}</h2> : null}
|
||||
{text ? <p className="mt-3 whitespace-pre-line text-sm leading-7 opacity-90 md:text-base">{text}</p> : null}
|
||||
{index === 0 ? <span className="pointer-events-none absolute -bottom-16 -left-10 h-40 w-40 rounded-full border border-current/15" aria-hidden="true" /> : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{/* انجمنها */}
|
||||
<section className="bg-muted/20">
|
||||
<div className="container mx-auto max-w-6xl px-4 py-10">
|
||||
<h2 className="text-2xl font-bold text-foreground">انجمنها</h2>
|
||||
<p className="mt-2 text-sm text-muted-foreground">اطلاعات انجمنهای زیرمجموعه/همکار</p>
|
||||
{content.activities_heading || content.activities_body ? (
|
||||
<details className="group mt-4 rounded-2xl border border-border bg-card px-5 py-4">
|
||||
<summary className="flex min-h-8 cursor-pointer list-none items-center justify-between gap-4 font-semibold text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
||||
<span className="inline-flex items-center gap-3">
|
||||
<BookOpen aria-hidden="true" className="h-5 w-5 text-primary" />
|
||||
{content.activities_heading}
|
||||
</span>
|
||||
<span className="text-xl text-primary transition-transform group-open:rotate-45" aria-hidden="true">+</span>
|
||||
</summary>
|
||||
{content.activities_body ? <p className="mt-3 whitespace-pre-line border-t border-border pt-3 text-sm leading-7 text-muted-foreground">{content.activities_body}</p> : null}
|
||||
</details>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
<div className="mt-6 grid gap-6 md:grid-cols-2">
|
||||
{ASSOCIATIONS.map((a) => (
|
||||
<Card key={a.name}>
|
||||
<div className="p-6">
|
||||
<div className="flex items-baseline justify-between gap-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-foreground">{a.name}</h3>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{a.university}{a.city ? ` • ${a.city}` : ''}{' '}
|
||||
{a.foundedYear && `• تأسیس: ${a.foundedYear}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-3 text-sm leading-7 text-muted-foreground">{a.about}</p>
|
||||
function AudienceSection({ content }: { content: AboutContentPage }) {
|
||||
if (!content.audiences.length) return null;
|
||||
|
||||
{a.focusAreas?.length ? (
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{a.focusAreas.map((f) => (
|
||||
<span key={f} className="rounded-full border px-3 py-1 text-xs text-muted-foreground">
|
||||
{f}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
return (
|
||||
<section aria-labelledby="about-audience-title">
|
||||
<div className="mb-6 max-w-2xl">
|
||||
<h2 id="about-audience-title" className="text-2xl font-bold leading-relaxed text-foreground md:text-3xl">
|
||||
{content.audience_heading}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{content.audiences.map((audience, index) => {
|
||||
const Icon = audienceIcons[audience.icon] || UsersRound;
|
||||
return (
|
||||
<article
|
||||
key={`${audience.position}-${audience.title}`}
|
||||
className={`flex min-h-44 flex-col justify-start rounded-3xl border border-border p-5 md:p-6 ${index === 1 ? "bg-primary text-primary-foreground" : "bg-card text-card-foreground"}`}
|
||||
>
|
||||
<span className="mb-5 grid h-12 w-12 shrink-0 place-items-center rounded-2xl border border-current/15 bg-background/15">
|
||||
<Icon aria-hidden="true" className="h-6 w-6" />
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="text-lg font-bold leading-7">{audience.title}</h3>
|
||||
<p className="mt-2 text-sm leading-7 opacity-90">{audience.text}</p>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
<div className="mt-4 grid grid-cols-2 gap-3 text-sm">
|
||||
{a.links?.website && (
|
||||
<a className="rounded-xl border p-3 hover:bg-muted/50" href={a.links.website} target="_blank" rel="noreferrer">وبسایت</a>
|
||||
)}
|
||||
{a.links?.instagram && (
|
||||
<a className="rounded-xl border p-3 hover:bg-muted/50" href={a.links.instagram} target="_blank" rel="noreferrer">اینستاگرام</a>
|
||||
)}
|
||||
{a.links?.telegram && (
|
||||
<a className="rounded-xl border p-3 hover:bg-muted/50" href={a.links.telegram} target="_blank" rel="noreferrer">تلگرام</a>
|
||||
)}
|
||||
{a.links?.email && (
|
||||
<a className="rounded-xl border p-3 hover:bg-muted/50" href={`mailto:${a.links.email}`}>ایمیل</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
function AssociationHistory({ content }: { content: AboutContentPage }) {
|
||||
if (!content.milestones.length) return null;
|
||||
|
||||
return (
|
||||
<section aria-labelledby="about-history-title" className="grid gap-8 lg:grid-cols-[0.72fr_1.28fr] lg:gap-14">
|
||||
<div className="max-w-md">
|
||||
<h2 id="about-history-title" className="text-2xl font-bold leading-relaxed text-foreground md:text-3xl">
|
||||
{content.history_heading}
|
||||
</h2>
|
||||
</div>
|
||||
<ol className="relative space-y-7 md:space-y-9">
|
||||
<span className="pointer-events-none absolute bottom-1 right-1.5 top-1 w-px bg-primary/25" aria-hidden="true" />
|
||||
{content.milestones.map((milestone, index) => (
|
||||
<li key={`${milestone.year_label}-${milestone.position}-${milestone.title}`} className="relative flex items-start gap-5">
|
||||
<span className={`relative z-10 mt-1.5 h-3 w-3 shrink-0 rounded-full border-2 border-background ${index === content.milestones.length - 1 ? "bg-accent" : "bg-primary"}`} aria-hidden="true" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="mb-2 text-sm font-bold text-primary">{milestone.year_label}</p>
|
||||
<h3 className="text-lg font-bold leading-7 text-foreground">{milestone.title}</h3>
|
||||
<p className="mt-2 max-w-2xl whitespace-pre-line text-sm leading-7 text-muted-foreground">{milestone.description}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function MemoriesGallery({ content }: { content: AboutContentPage }) {
|
||||
if (!content.gallery_images.length) return null;
|
||||
|
||||
return (
|
||||
<section aria-labelledby="about-gallery-title">
|
||||
<div className="mb-6 max-w-2xl">
|
||||
<h2 id="about-gallery-title" className="inline-flex items-center gap-3 text-2xl font-bold leading-relaxed text-foreground md:text-3xl">
|
||||
<Camera aria-hidden="true" className="h-6 w-6 text-primary" />
|
||||
{content.gallery_heading}
|
||||
</h2>
|
||||
{content.gallery_intro ? <p className="mt-2 text-sm leading-7 text-muted-foreground md:text-base">{content.gallery_intro}</p> : null}
|
||||
</div>
|
||||
<div className="grid auto-rows-[150px] grid-cols-2 gap-3 md:auto-rows-[190px] md:grid-cols-4 md:gap-4">
|
||||
{content.gallery_images.map((item, index) => (
|
||||
<figure key={`${item.position}-${item.caption}`} className={`group relative overflow-hidden rounded-3xl border border-border ${index % 5 === 0 ? "row-span-2 md:col-span-2" : ""}`}>
|
||||
{item.image ? (
|
||||
<Image
|
||||
src={item.image.url}
|
||||
alt={item.image.alt}
|
||||
fill
|
||||
unoptimized
|
||||
sizes="(max-width: 768px) 50vw, 25vw"
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-[1.03]"
|
||||
/>
|
||||
) : (
|
||||
<div className={`grid h-full place-items-center ${galleryTones[index % galleryTones.length]}`} role="img" aria-label={item.caption}>
|
||||
<span className="grid h-16 w-16 place-items-center rounded-2xl border border-primary/20 bg-card/75 text-primary">
|
||||
<Camera aria-hidden="true" className="h-7 w-7" />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{item.caption ? <figcaption className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/75 to-transparent px-4 pb-4 pt-10 text-sm font-semibold text-white">{item.caption}</figcaption> : null}
|
||||
</figure>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function ClosingSection({ content }: { content: AboutContentPage }) {
|
||||
const faqItems = content.items.filter((item) => item.kind === "faq");
|
||||
if (!faqItems.length && !content.cta_heading && !content.cta_body && !content.cta_action_label) return null;
|
||||
|
||||
return (
|
||||
<section className="grid gap-6 lg:grid-cols-[1.1fr_0.9fr]" aria-label="پرسشهای متداول و پیوستن به انجمن">
|
||||
{faqItems.length ? (
|
||||
<div>
|
||||
<h2 className="mb-4 text-2xl font-bold leading-relaxed text-foreground">{content.faq_heading}</h2>
|
||||
<div className="space-y-3">
|
||||
{faqItems.map((item) => (
|
||||
<details key={`${item.position}-${item.title}`} className="group rounded-2xl border border-border bg-card p-4 open:bg-brand-wash/55">
|
||||
<summary className="flex min-h-8 w-full cursor-pointer list-none items-center justify-between gap-4 font-semibold leading-7 text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
||||
<span className="min-w-0 flex-1">{item.title}</span>
|
||||
<span className="shrink-0 text-xl text-primary transition-transform group-open:rotate-45" aria-hidden="true">+</span>
|
||||
</summary>
|
||||
<p className="mt-3 whitespace-pre-line text-sm leading-7 text-muted-foreground">{item.text}</p>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{/* کمیتهها */}
|
||||
<section className="container mx-auto max-w-6xl px-4 py-10">
|
||||
<h2 className="text-2xl font-bold text-foreground">کمیتهها و گروههای کاری</h2>
|
||||
<div className="mt-6 grid gap-4 md:grid-cols-3">
|
||||
{COMMITTEES.map((c) => (
|
||||
<Card key={c.title}>
|
||||
<div className="p-5">
|
||||
<h3 className="text-base font-semibold text-foreground">{c.title}</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground leading-7">{c.desc}</p>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
<aside className="flex flex-col items-start justify-between gap-7 rounded-3xl bg-primary p-6 text-primary-foreground md:p-8">
|
||||
<div>
|
||||
<span className="mb-5 grid h-11 w-11 place-items-center rounded-2xl border border-primary-foreground/20 bg-background/10">
|
||||
<HeartHandshake aria-hidden="true" className="h-5 w-5" />
|
||||
</span>
|
||||
<h2 className="text-2xl font-bold leading-relaxed">{content.cta_heading}</h2>
|
||||
{content.cta_body ? <p className="mt-3 max-w-md text-sm leading-7 opacity-90">{content.cta_body}</p> : null}
|
||||
</div>
|
||||
</section>
|
||||
{content.cta_action_label ? (
|
||||
content.cta_action_url ? (
|
||||
<ContentActionLink
|
||||
href={content.cta_action_url}
|
||||
fallbackHref="/"
|
||||
className="inline-flex min-h-11 items-center gap-2 rounded-xl bg-accent px-5 font-bold text-accent-foreground transition-colors hover:bg-accent/85 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-foreground focus-visible:ring-offset-2 focus-visible:ring-offset-primary"
|
||||
>
|
||||
{content.cta_action_label}<ArrowUpLeft aria-hidden="true" className="h-4 w-4" />
|
||||
</ContentActionLink>
|
||||
) : (
|
||||
<span aria-disabled="true" className="inline-flex min-h-11 items-center gap-2 rounded-xl border border-primary-foreground/35 px-5 font-bold text-primary-foreground/80">
|
||||
{content.cta_action_label}<ArrowUpLeft aria-hidden="true" className="h-4 w-4" />
|
||||
</span>
|
||||
)
|
||||
) : null}
|
||||
</aside>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
{/* پرسشهای متداول */}
|
||||
<section className="bg-muted/20">
|
||||
<div className="container mx-auto max-w-6xl px-4 py-10">
|
||||
<h2 className="text-2xl font-bold text-foreground">پرسشهای متداول</h2>
|
||||
<div className="mt-6 space-y-3">
|
||||
<FAQ q="چطور در رویدادها شرکت کنم؟" a="برای ثبتنام در رویدادها ابتدا باید حسابکاربری با کددانشجویی خود ایجاد کنید و سپس از طریق صفحهی ثبتنام در رویداد موردنظرتان شرکت کنید :)" />
|
||||
<FAQ q="آیا امکان همکاری انجمنها/شرکتها هم وجود دارد؟" a="بله، ما همیشه آمادهی همکاری با سازمانها و انجمنهای مختلف دانشگاهها جهت توانمندسازی دانشجویان با مهارتهای موردنیاز صنعت و ایجاد شبکههای حرفهای هستیم." />
|
||||
<FAQ q="برای سخنرانی/منتورینگ به چه چیزهایی نیاز است؟" a="رزومه کوتاه، موضوع پیشنهادی و زمانهای دسترسپذیرتان را ارسال کنید تا هماهنگ کنیم." />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* نقشه و آدرس اختیاری */}
|
||||
<section className="container mx-auto max-w-6xl px-4 py-10">
|
||||
<h2 className="text-2xl font-bold text-foreground">نشانی دانشکده</h2>
|
||||
<div className="mt-4 grid gap-6 md:grid-cols-2">
|
||||
<Card>
|
||||
<div className="p-5 text-sm text-muted-foreground leading-7">
|
||||
<p>آدرس: گیلان، رودسر، واجارگاه، دانشکدهی فنی و مهندسی شرق گیلان</p>
|
||||
<p>کدپستی: ۴۴۹۱۸۹۸۵۶۶</p>
|
||||
<p>تلفن: ۰۱۳-۴۲۶۸۸۴۴۷</p>
|
||||
<p className="mt-3">برای هماهنگی حضوری از قبل پیام بدهید.</p>
|
||||
</div>
|
||||
</Card>
|
||||
<div className="rounded-xl border overflow-hidden min-h-[280px] bg-muted/40 flex items-center justify-center text-sm text-muted-foreground">
|
||||
<iframe
|
||||
src="https://maps.google.com/maps?q=37.06285,50.42324&hl=fa&z=16&output=embed"
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
className="w-full aspect-video border-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="bg-gradient-to-t from-muted/40 to-transparent">
|
||||
<div className="container mx-auto max-w-6xl px-4 py-12 text-center">
|
||||
<h3 className="text-xl md:text-2xl font-bold text-foreground">مایل به همکاری هستید؟</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">برای مشارکت داوطلبانه، سخنرانی، اسپانسری یا همکاری صنعتی به ما پیام دهید.</p>
|
||||
<div className="mt-4 inline-flex gap-3">
|
||||
<a href="mailto:eastguilanceassociation@gmail.com" className="rounded-xl border px-5 py-2 text-sm hover:bg-muted/50">ایجاد ارتباط</a>
|
||||
<a href="https://t.me/guilancea" target="_blank" rel="noreferrer" className="rounded-xl bg-primary px-5 py-2 text-sm text-primary-foreground">
|
||||
پیام در تلگرام
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
export default function AboutUs({ content, siteContent }: AboutUsProps) {
|
||||
return (
|
||||
<main className="bg-background" dir="rtl">
|
||||
<AboutHero content={content} siteContent={siteContent} />
|
||||
<div className="container mx-auto max-w-6xl space-y-16 px-4 py-12 md:space-y-20 md:py-16">
|
||||
<PurposeSection content={content} />
|
||||
<AudienceSection content={content} />
|
||||
<AssociationHistory content={content} />
|
||||
<AboutYearArchive
|
||||
years={content.years}
|
||||
heading={content.members_heading}
|
||||
intro={content.members_intro}
|
||||
eventsHeading={content.year_events_heading}
|
||||
/>
|
||||
<MemoriesGallery content={content} />
|
||||
<ClosingSection content={content} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// --------- اجزای کوچک ---------
|
||||
function Stat({ label, value }: { label: string; value: string }) {
|
||||
return (
|
||||
<div className="rounded-xl border bg-background p-4 text-center">
|
||||
<div className="text-2xl font-extrabold text-foreground">{value}</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">{label}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Card({ children, className = '' }: React.PropsWithChildren<{ className?: string }>) {
|
||||
return <div className={`rounded-2xl border bg-card text-card-foreground shadow-sm ${className}`}>{children}</div>;
|
||||
}
|
||||
|
||||
function CardHeader({ title, subtitle }: { title: string; subtitle?: string }) {
|
||||
return (
|
||||
<div className="p-6 pb-4">
|
||||
<h3 className="text-lg font-semibold text-foreground">{title}</h3>
|
||||
{subtitle ? <p className="mt-1 text-xs text-muted-foreground">{subtitle}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FAQ({ q, a }: { q: string; a: string }) {
|
||||
return (
|
||||
<details className="rounded-xl border bg-background p-4">
|
||||
<summary className="cursor-pointer select-none text-sm font-medium text-foreground">{q}</summary>
|
||||
<p className="mt-2 text-sm leading-7 text-muted-foreground">{a}</p>
|
||||
</details>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ import { Link, useLocation, useNavigate } from "@/lib/router";
|
||||
import { blogPostPath } from "@/lib/blog-routes";
|
||||
import { apiBaseUrl, toAbsoluteUrl } from "@/lib/site";
|
||||
import type * as Types from "@/lib/types";
|
||||
import type { BlogContentPage } from "@/lib/content-cms";
|
||||
import { cn, formatJalaliDate, getBlogCardImageUrl } from "@/lib/utils";
|
||||
|
||||
type BlogProps = {
|
||||
content: BlogContentPage;
|
||||
initialPosts?: Types.PostListSchema[];
|
||||
initialSearch?: string;
|
||||
initialCategory?: string;
|
||||
@@ -102,6 +104,7 @@ function BlogBannerSlider({ banners }: { banners: Types.BlogBannerSchema[] }) {
|
||||
}
|
||||
|
||||
export default function Blog({
|
||||
content,
|
||||
initialPosts = [],
|
||||
initialSearch = "",
|
||||
initialCategory = "",
|
||||
@@ -347,10 +350,9 @@ export default function Blog({
|
||||
|
||||
<div className="mb-8 flex flex-col gap-5 md:flex-row md:items-end md:justify-between">
|
||||
<div className="text-right">
|
||||
<p className="mb-2 text-sm font-medium text-primary">خواندنیهای انجمن</p>
|
||||
<h1 className="text-4xl font-black tracking-tight">بلاگ</h1>
|
||||
<h1 className="text-4xl font-black tracking-tight">{content.title}</h1>
|
||||
<p className="mt-3 max-w-2xl text-sm leading-7 text-muted-foreground">
|
||||
نوشتههای آموزشی، تجربههای دانشجویی و یادداشتهای تخصصی اعضای انجمن علمی.
|
||||
{content.intro}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex w-full max-w-md items-center gap-2">
|
||||
|
||||
@@ -12,8 +12,11 @@ import ProgressiveImage from "@/components/ProgressiveImage";
|
||||
import type * as Types from "@/lib/types";
|
||||
import { formatJalali, formatNumberPersian, formatToman, getEventCardImageUrl } from "@/lib/utils";
|
||||
import { siteUrl } from "@/lib/site";
|
||||
import type { EventsContentPage } from "@/lib/content-cms";
|
||||
|
||||
type EventsProps = {
|
||||
content: EventsContentPage;
|
||||
siteName: string;
|
||||
initialEvents?: Types.EventListItemSchema[];
|
||||
initialSearch?: string;
|
||||
};
|
||||
@@ -49,6 +52,8 @@ function notAvailableReasonFa(event: Types.EventListItemSchema) {
|
||||
}
|
||||
|
||||
export default function Events({
|
||||
content,
|
||||
siteName,
|
||||
initialEvents = [],
|
||||
initialSearch = "",
|
||||
}: EventsProps) {
|
||||
@@ -58,10 +63,8 @@ export default function Events({
|
||||
const [search, setSearch] = useState(initialSearch);
|
||||
const [loading, setLoading] = useState(!initialEvents.length && !initialSearch);
|
||||
|
||||
const siteName = "East Guilan CE";
|
||||
const pageTitle = `Events | ${siteName}`;
|
||||
const pageDescription =
|
||||
"Discover upcoming and past events organized by the East Guilan Computer Engineering Association, including workshops, competitions, and community programs.";
|
||||
const pageTitle = content.seo.title || content.title;
|
||||
const pageDescription = content.seo.description || content.intro;
|
||||
const canonicalUrl = `${siteUrl}/events`;
|
||||
|
||||
const toAbsoluteUrl = (url?: string | null) => {
|
||||
@@ -189,7 +192,8 @@ export default function Events({
|
||||
|
||||
<div className="min-h-screen bg-background" dir="rtl">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1 className="text-4xl font-bold mb-8">رویدادها</h1>
|
||||
<h1 className="mb-3 text-4xl font-bold">{content.title}</h1>
|
||||
{content.intro ? <p className="mb-8 max-w-3xl leading-8 text-muted-foreground">{content.intro}</p> : null}
|
||||
|
||||
<div className="mb-8">
|
||||
<Input
|
||||
|
||||
@@ -1,130 +1,121 @@
|
||||
/**
|
||||
* Home page highlighting the association mission, key offerings, and primary calls to action.
|
||||
*/
|
||||
|
||||
import Image from "next/image";
|
||||
import { ArrowUpLeft } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Helmet } from "@/lib/helmet";
|
||||
import { Link } from "@/lib/router";
|
||||
import FeaturedEvents from "@/components/home/FeaturedEvents";
|
||||
import FeaturedPosts from "@/components/home/FeaturedPosts";
|
||||
import ContentActionLink from "@/components/ContentActionLink";
|
||||
import type { HomeContentPage, SiteContent } from "@/lib/content-cms";
|
||||
import type { EventListItemSchema, PostListSchema } from "@/lib/types";
|
||||
|
||||
const heroTitle = "انجمن علمی کامپیوتر دانشگاه گیلان";
|
||||
const heroDescription =
|
||||
"با ما همراه شوید و در دنیای مهندسی/علوم کامپیوتر و فناوری پیشرفت کنید. رویدادها، محتوای آموزشی و جامعهای پویا برای رشد شما فراهم است.";
|
||||
|
||||
const structuredData = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
name: heroTitle,
|
||||
url: "https://east-guilan-ce.ir",
|
||||
sameAs: ["https://east-guilan-ce.ir/blog", "https://east-guilan-ce.ir/events"],
|
||||
description: heroDescription,
|
||||
logo: "https://east-guilan-ce.ir/favicon.ico",
|
||||
contactPoint: {
|
||||
"@type": "ContactPoint",
|
||||
email: "admin@east-guilan-ce.ir",
|
||||
contactType: "customer support",
|
||||
availableLanguage: ["fa", "en"]
|
||||
}
|
||||
type HomeProps = {
|
||||
content: HomeContentPage;
|
||||
siteContent: SiteContent;
|
||||
events: EventListItemSchema[];
|
||||
posts: PostListSchema[];
|
||||
isEventArchive: boolean;
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
export default function Home({ content, siteContent, events, posts, isEventArchive }: HomeProps) {
|
||||
const heroImage = content.hero_image || siteContent.logo || {
|
||||
url: "/favicon.ico",
|
||||
alt: siteContent.short_name || "نشان انجمن",
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{heroTitle}</title>
|
||||
<meta name="description" content={heroDescription} />
|
||||
<meta property="og:title" content={heroTitle} />
|
||||
<meta property="og:description" content={heroDescription} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://east-guilan-ce.ir" />
|
||||
<meta property="og:image" content="https://east-guilan-ce.ir/favicon.ico" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={heroTitle} />
|
||||
<meta name="twitter:description" content={heroDescription} />
|
||||
<script type="application/ld+json">{JSON.stringify(structuredData)}</script>
|
||||
</Helmet>
|
||||
|
||||
<main className="min-h-screen bg-background" dir="rtl">
|
||||
<header className="bg-gradient-to-b from-primary/10 to-background py-20">
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<h1 className="mb-6 text-4xl font-extrabold leading-tight text-primary sm:text-5xl">{heroTitle}</h1>
|
||||
<p className="mx-auto mb-10 max-w-2xl text-lg text-muted-foreground sm:text-xl">{heroDescription}</p>
|
||||
<div className="flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
<Link to="/events">
|
||||
<Button size="lg" aria-label="مشاهده رویدادهای انجمن">
|
||||
مشاهده رویدادها
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/blog">
|
||||
<Button size="lg" variant="outline" aria-label="مطالعه مقالات آموزشی">
|
||||
خواندن مقالات
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="py-16" aria-labelledby="about-section">
|
||||
<div className="container mx-auto px-4">
|
||||
<h2 id="about-section" className="mb-12 text-center text-3xl font-bold">
|
||||
درباره انجمن
|
||||
</h2>
|
||||
<div className="grid gap-8 md:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>رویدادهای تخصصی</CardTitle>
|
||||
<CardDescription>برگزاری کارگاهها، سمینارها و نشستهای علمی</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground">
|
||||
با شرکت در رویدادهای ما، دانش و مهارتهای خود را در کنار اساتید و متخصصان ارتقا دهید.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>مقالات آموزشی</CardTitle>
|
||||
<CardDescription>دسترسی به مطالب آموزشی و پژوهشی باکیفیت</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground">
|
||||
تازهترین مقالات تخصصی در حوزههای مختلف علوم کامپیوتر برای یادگیری و توسعه فردی.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>جامعه فعال</CardTitle>
|
||||
<CardDescription>ارتباط با دانشجویان و اساتید علاقهمند به فناوری</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground">
|
||||
به جامعهای پویا بپیوندید و از فرصتهای همکاری، شبکهسازی و رشد شخصی بهرهمند شوید.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-primary/5 py-16" aria-labelledby="cta-section">
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<h2 id="cta-section" className="mb-4 text-3xl font-bold">
|
||||
آماده عضویت هستید؟
|
||||
</h2>
|
||||
<p className="mx-auto mb-8 max-w-xl text-muted-foreground">
|
||||
همین حالا به جمع ما بپیوندید و از برنامهها و فرصتهای یادگیری انجمن علمی کامپیوتر دانشگاه گیلان استفاده کنید.
|
||||
<main className="bg-background" dir="rtl">
|
||||
<section className="border-b border-border/70 bg-brand-wash/45" aria-labelledby="home-hero-title">
|
||||
<div className="container mx-auto grid items-center gap-8 px-4 py-10 md:grid-cols-[1.15fr_0.85fr] md:gap-12 md:py-16">
|
||||
<div>
|
||||
<p className="mb-3 text-sm font-semibold text-brand-deep">
|
||||
{siteContent.faculty_subline}
|
||||
</p>
|
||||
<Link to="/auth">
|
||||
<Button size="lg" aria-label="ثبت نام در انجمن علمی کامپیوتر">
|
||||
ثبتنام کنید
|
||||
<h1 id="home-hero-title" className="max-w-3xl text-3xl font-extrabold leading-[1.45] tracking-tight text-brand-deep sm:text-4xl md:text-5xl">
|
||||
{content.hero_heading || content.title}
|
||||
</h1>
|
||||
<p className="mt-4 max-w-2xl text-base leading-8 text-muted-foreground md:text-lg">
|
||||
{content.intro}
|
||||
</p>
|
||||
{content.hero_body ? (
|
||||
<p className="mt-2 max-w-2xl text-sm leading-7 text-muted-foreground">
|
||||
{content.hero_body}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="mt-6 flex flex-col gap-3 sm:flex-row">
|
||||
<Button asChild className="min-h-11 rounded-[10px] px-5">
|
||||
<ContentActionLink
|
||||
href={content.hero_action_url}
|
||||
fallbackHref="/events"
|
||||
className="inline-flex items-center gap-2"
|
||||
>
|
||||
{content.hero_action_label}
|
||||
<ArrowUpLeft aria-hidden="true" className="h-4 w-4" />
|
||||
</ContentActionLink>
|
||||
</Button>
|
||||
</Link>
|
||||
<Button asChild variant="outline" className="home-hero-secondary min-h-11 rounded-[10px] border-primary/35 bg-background px-5 text-primary">
|
||||
<ContentActionLink href={content.hero_secondary_action_url} fallbackHref="/blog">
|
||||
{content.hero_secondary_action_label || siteContent.navigation.blog}
|
||||
</ContentActionLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</>
|
||||
|
||||
<div className="relative grid min-h-48 place-items-center overflow-hidden rounded-2xl border border-border/80 bg-brand-wash px-6 py-10 md:min-h-[320px]">
|
||||
<div className="pointer-events-none absolute inset-4 rounded-xl border border-primary/20" aria-hidden="true" />
|
||||
<div className="pointer-events-none absolute inset-x-0 top-0 h-1.5 bg-accent" aria-hidden="true" />
|
||||
<div className="pointer-events-none absolute -bottom-24 -left-16 h-56 w-56 rounded-full border border-primary/20" aria-hidden="true" />
|
||||
<Image
|
||||
src={heroImage.url}
|
||||
alt={heroImage.alt || "تصویر معرفی انجمن"}
|
||||
width={240}
|
||||
height={240}
|
||||
unoptimized
|
||||
className="relative z-10 h-36 w-36 object-contain drop-shadow-xl md:h-48 md:w-48"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<FeaturedEvents content={content} events={events} isArchive={isEventArchive} />
|
||||
<FeaturedPosts content={content} siteContent={siteContent} posts={posts} />
|
||||
|
||||
<section className="bg-primary text-primary-foreground" aria-labelledby="home-participation-title">
|
||||
<div className="container mx-auto flex flex-col gap-6 px-4 py-12 md:flex-row md:items-center md:justify-between md:py-16">
|
||||
{content.participation_image ? (
|
||||
<div className="hidden h-24 w-24 shrink-0 place-items-center overflow-hidden rounded-2xl border border-primary-foreground/20 bg-background/10 p-2 md:grid">
|
||||
<Image
|
||||
src={content.participation_image.url}
|
||||
alt={content.participation_image.alt || ""}
|
||||
width={96}
|
||||
height={96}
|
||||
unoptimized
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="max-w-2xl">
|
||||
<h2 id="home-participation-title" className="text-2xl font-bold leading-relaxed md:text-3xl">
|
||||
{content.participation_heading}
|
||||
</h2>
|
||||
<p className="mt-2 text-sm leading-7 opacity-90 md:text-base">
|
||||
{content.participation_body}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<Button asChild className="min-h-11 rounded-[10px] bg-accent px-5 text-accent-foreground hover:bg-accent/90">
|
||||
<ContentActionLink href={content.participation_action_url} fallbackHref="/auth" className="inline-flex items-center gap-2">
|
||||
{content.participation_action_label}
|
||||
<ArrowUpLeft aria-hidden="true" className="h-4 w-4" />
|
||||
</ContentActionLink>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="min-h-11 rounded-[10px] border-primary-foreground/45 bg-transparent px-5 text-primary-foreground hover:bg-primary-foreground/10 hover:text-primary-foreground">
|
||||
<ContentActionLink href={content.participation_secondary_action_url} fallbackHref="/about">
|
||||
{content.participation_secondary_action_label}
|
||||
</ContentActionLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
162
src/views/about/YearArchive.tsx
Normal file
162
src/views/about/YearArchive.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useState } from "react";
|
||||
import { ArrowUpLeft, CalendarDays } from "lucide-react";
|
||||
import { Link } from "@/lib/router";
|
||||
import type { AboutAssociationYear, AboutMember, AboutYearEvent } from "@/lib/content-cms";
|
||||
import { apiBaseUrl, toAbsoluteUrl } from "@/lib/site";
|
||||
import { formatJalali } from "@/lib/utils";
|
||||
|
||||
type YearArchiveProps = {
|
||||
years: AboutAssociationYear[];
|
||||
heading: string;
|
||||
intro: string;
|
||||
eventsHeading: string;
|
||||
};
|
||||
|
||||
function memberInitial(name: string) {
|
||||
return name.trim().slice(0, 1) || "؟";
|
||||
}
|
||||
|
||||
function MemberCard({ member }: { member: AboutMember }) {
|
||||
return (
|
||||
<article className="group overflow-hidden rounded-2xl border border-border bg-card transition-colors hover:border-primary/40">
|
||||
<div className="relative aspect-[4/3] overflow-hidden bg-brand-wash">
|
||||
{member.photo ? (
|
||||
<Image
|
||||
src={member.photo.url}
|
||||
alt={member.photo.alt || member.name}
|
||||
fill
|
||||
unoptimized
|
||||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-[1.03]"
|
||||
/>
|
||||
) : (
|
||||
<div className="grid h-full place-items-center bg-gradient-to-br from-accent/40 via-brand-wash to-secondary">
|
||||
<span className="grid h-20 w-20 place-items-center rounded-full border border-primary/20 bg-card/90 text-3xl font-bold text-primary shadow-sm" aria-label={member.name}>
|
||||
{memberInitial(member.name)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<p className="text-xs font-semibold text-primary">{member.role}</p>
|
||||
<h3 className="mt-1 text-lg font-bold leading-7 text-foreground">{member.name}</h3>
|
||||
{member.bio ? <p className="mt-2 line-clamp-3 text-sm leading-7 text-muted-foreground">{member.bio}</p> : null}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
function EventPoster({ event }: { event: AboutYearEvent }) {
|
||||
const imageUrl = event.featured_image
|
||||
? toAbsoluteUrl(event.featured_image.url, apiBaseUrl)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="group overflow-hidden rounded-2xl border border-border bg-card transition-colors hover:border-primary/40">
|
||||
<div className="relative aspect-[16/10] overflow-hidden bg-brand-wash">
|
||||
{imageUrl ? (
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={event.featured_image?.alt || `پوستر ${event.title}`}
|
||||
fill
|
||||
unoptimized
|
||||
sizes="(max-width: 768px) 90vw, 33vw"
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-[1.03]"
|
||||
/>
|
||||
) : (
|
||||
<div className="grid h-full place-items-center bg-gradient-to-br from-accent/40 via-brand-wash to-secondary text-primary">
|
||||
<CalendarDays aria-hidden="true" className="h-10 w-10" strokeWidth={1.5} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex min-h-24 items-center justify-between gap-3 p-4">
|
||||
<div className="min-w-0">
|
||||
<h4 className="line-clamp-2 font-bold leading-7 text-foreground">{event.title}</h4>
|
||||
{event.start_time ? <time className="mt-1 block text-xs text-muted-foreground" dateTime={event.start_time}>{formatJalali(event.start_time, false)}</time> : null}
|
||||
</div>
|
||||
{event.slug ? <ArrowUpLeft aria-hidden="true" className="h-5 w-5 shrink-0 text-primary transition-transform group-hover:-translate-x-1 group-hover:-translate-y-1" /> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function YearArchive({ years, heading, intro, eventsHeading }: YearArchiveProps) {
|
||||
const [selectedLabel, setSelectedLabel] = useState(years[years.length - 1]?.year_label ?? "");
|
||||
const selectedYear = years.find((year) => year.year_label === selectedLabel) ?? years[years.length - 1];
|
||||
|
||||
if (!years.length || !selectedYear) return null;
|
||||
|
||||
return (
|
||||
<section aria-labelledby="about-years-title">
|
||||
<div className="mb-7 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
||||
<div className="max-w-2xl">
|
||||
<h2 id="about-years-title" className="text-2xl font-bold leading-relaxed text-foreground md:text-3xl">{heading}</h2>
|
||||
{intro ? <p className="mt-2 text-sm leading-7 text-muted-foreground md:text-base">{intro}</p> : null}
|
||||
</div>
|
||||
<p className="shrink-0 rounded-full border border-border bg-card px-4 py-2 text-sm font-semibold text-muted-foreground">
|
||||
{selectedYear.year_label}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-7 overflow-x-auto pb-2" role="group" aria-label="انتخاب سال فعالیت انجمن">
|
||||
<div className="flex min-w-max gap-2">
|
||||
{years.map((year, index) => (
|
||||
<button
|
||||
key={`${year.year_label}-${index}`}
|
||||
type="button"
|
||||
aria-pressed={year.year_label === selectedYear.year_label}
|
||||
onClick={() => setSelectedLabel(year.year_label)}
|
||||
className={`min-h-11 rounded-full border px-5 text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
|
||||
year.year_label === selectedYear.year_label
|
||||
? "border-primary bg-primary text-primary-foreground"
|
||||
: "border-border bg-card text-muted-foreground hover:border-primary/40 hover:bg-brand-wash hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{year.year_label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-9">
|
||||
<div>
|
||||
<h3 className="sr-only">{selectedYear.year_label} — {heading}</h3>
|
||||
{selectedYear.members.length ? (
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{selectedYear.members.map((member, index) => <MemberCard key={`${member.name}-${member.role}-${index}`} member={member} />)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="rounded-2xl border border-dashed border-border bg-card/60 px-5 py-6 text-sm text-muted-foreground">برای این سال هنوز عضوی ثبت نشده است.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{selectedYear.selected_events.length ? (
|
||||
<div>
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<span className="h-px flex-1 bg-border" aria-hidden="true" />
|
||||
<h3 className="text-base font-bold text-foreground">{eventsHeading}</h3>
|
||||
<span className="h-px flex-1 bg-border" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{selectedYear.selected_events.map((event) => (
|
||||
event.slug ? (
|
||||
<Link
|
||||
key={event.id}
|
||||
to={`/events/${encodeURIComponent(event.slug)}`}
|
||||
aria-label={`${event.title}؛ مشاهده جزئیات رویداد`}
|
||||
className="block rounded-2xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
>
|
||||
<EventPoster event={event} />
|
||||
</Link>
|
||||
) : <EventPoster key={event.id} event={event} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user