diff --git a/src/app/(app)/[lang]/kozelet/ontevekenykorok/page.tsx b/src/app/(app)/[lang]/kozelet/ontevekenykorok/page.tsx index 469c6ed..be1bcd9 100644 --- a/src/app/(app)/[lang]/kozelet/ontevekenykorok/page.tsx +++ b/src/app/(app)/[lang]/kozelet/ontevekenykorok/page.tsx @@ -1,28 +1,64 @@ +import { + OrganizationCard, + type OrganizationCardProps, +} from "@/components/common/OrganizationCard"; import { PageHeader } from "@/components/common/PageHeader"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Separator } from "@/components/ui/separator"; -import { Button } from "@/components/ui/button"; import { getDictionary } from "@/get-dictionary"; import { Locale } from "@/i18n-config"; -import Image from "next/image"; -import { getSocialIcon, getSocialPriority, getSocialName } from "@/lib/social-utils"; -type Kor = { +type Organization = { id: string; title: string; description: string[]; social_title: string; social_links: { label: string; url: string }[]; images?: string[]; + stats?: OrganizationCardProps["stats"]; + events?: OrganizationCardProps["events"]; + activities?: OrganizationCardProps["activities"]; + departments?: OrganizationCardProps["departments"]; + target_audience?: OrganizationCardProps["targetAudience"]; + targetAudience?: OrganizationCardProps["targetAudience"]; + join_url?: string; + joinUrl?: string; + join_text?: string; + joinText?: string; }; +function getContactLabel(label: string) { + return label.replace(/:$/, ""); +} + +function getOrganizationCardProps( + organization: Organization, + locale: Locale, +): OrganizationCardProps { + return { + name: organization.title, + stats: organization.stats, + presentation: organization.description, + events: organization.events, + activities: organization.activities, + departments: organization.departments, + targetAudience: + organization.targetAudience ?? organization.target_audience, + socialLinks: organization.social_links, + galleryImages: organization.images, + imageBasePath: "/ontevekenykorok", + joinUrl: organization.joinUrl ?? organization.join_url, + joinText: organization.joinText ?? organization.join_text, + labels: { contacts: getContactLabel(organization.social_title) }, + locale, + }; +} + export default async function OntevekenyKorokPage({ params, }: { params: Promise<{ lang: Locale }>; }) { const { lang } = await params; - const dictionary = await getDictionary(lang, 'ontevekeny_korok'); + const dictionary = await getDictionary(lang, "ontevekeny_korok"); const data = dictionary.ontevekeny_korok; if (!data) { @@ -31,81 +67,15 @@ export default async function OntevekenyKorokPage({ return (
-
+
- {data.korok.map((kor: Kor) => ( - - - - {kor.title} - - - -
- {kor.description.map((para: string, pIdx: number) => ( -

{para}

- ))} -
- - {/* Image Grid / Gallery */} - {kor.images && kor.images.length > 0 && ( -
- {kor.images.map((img: string, imgIdx: number) => ( -
- {kor.images!.length === 1 ? ( - /* eslint-disable-next-line @next/next/no-img-element */ - {`${kor.title} - ) : ( - {`${kor.title} - )} -
- ))} -
- )} - - - -
-

{kor.social_title}

-
- {[...kor.social_links] - .sort((a: {label: string, url: string}, b: {label: string, url: string}) => getSocialPriority(a.label) - getSocialPriority(b.label)) - .map((link: {label: string, url: string}, lIdx: number) => ( - - ))} -
-
-
-
+ {(data.korok as Organization[]).map((kor) => ( + ))}
diff --git a/src/app/(app)/[lang]/kozelet/szakkollegiumok/page.tsx b/src/app/(app)/[lang]/kozelet/szakkollegiumok/page.tsx index 70e5f1b..b182145 100644 --- a/src/app/(app)/[lang]/kozelet/szakkollegiumok/page.tsx +++ b/src/app/(app)/[lang]/kozelet/szakkollegiumok/page.tsx @@ -1,28 +1,66 @@ +import { + OrganizationCard, + type OrganizationCardProps, +} from "@/components/common/OrganizationCard"; import { PageHeader } from "@/components/common/PageHeader"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Separator } from "@/components/ui/separator"; -import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; import { getDictionary } from "@/get-dictionary"; import { Locale } from "@/i18n-config"; -import Image from "next/image"; -import { getSocialIcon, getSocialPriority, getSocialName } from "@/lib/social-utils"; -type Team = { +type Organization = { id: string; title: string; description: string[]; social_title: string; social_links: { label: string; url: string }[]; images?: string[]; + stats?: OrganizationCardProps["stats"]; + events?: OrganizationCardProps["events"]; + activities?: OrganizationCardProps["activities"]; + departments?: OrganizationCardProps["departments"]; + target_audience?: OrganizationCardProps["targetAudience"]; + targetAudience?: OrganizationCardProps["targetAudience"]; + join_url?: string; + joinUrl?: string; + join_text?: string; + joinText?: string; }; +function getContactLabel(label: string) { + return label.replace(/:$/, ""); +} + +function getOrganizationCardProps( + organization: Organization, + locale: Locale, + imageBasePath: string, +): OrganizationCardProps { + return { + name: organization.title, + stats: organization.stats, + presentation: organization.description, + events: organization.events, + activities: organization.activities, + departments: organization.departments, + targetAudience: + organization.targetAudience ?? organization.target_audience, + socialLinks: organization.social_links, + galleryImages: organization.images, + imageBasePath, + joinUrl: organization.joinUrl ?? organization.join_url, + joinText: organization.joinText ?? organization.join_text, + labels: { contacts: getContactLabel(organization.social_title) }, + locale, + }; +} + export default async function SzakkollegiumokPage({ params, }: { params: Promise<{ lang: Locale }>; }) { const { lang } = await params; - const dictionary = await getDictionary(lang, 'advanced_colleges'); + const dictionary = await getDictionary(lang, "advanced_colleges"); const data = dictionary.advanced_colleges; if (!data) { @@ -31,159 +69,36 @@ export default async function SzakkollegiumokPage({ return (
-
+
- - {/* Intro section, if any */} + {data.description && data.description.length > 0 && ( - + -
- {data.description.map((para: string) => ( -

{para}

+
+ {data.description.map((paragraph: string) => ( +

{paragraph}

))}
)} - - - {data.muszak.title} - - -
- {data.muszak.description.map((para: string) => ( -

{para}

- ))} -
- - {data.muszak.images && data.muszak.images.length > 0 && ( -
- {/* eslint-disable-next-line @next/next/no-img-element */} - {data.muszak.title} -
- )} - -
-

{data.muszak.social_title}

-
- {[...data.muszak.social_links] - .sort((a: {label: string, url: string}, b: {label: string, url: string}) => getSocialPriority(a.label) - getSocialPriority(b.label)) - .map((link: {label: string, url: string}) => ( - - ))} -
-
-
-
-
- {data.teams.map((team: Team) => { - const imageCount = team.images?.length || 0; - let gridColsClass = 'grid-cols-1 md:grid-cols-3'; - if (imageCount === 1) { - gridColsClass = 'grid-cols-1'; - } else if (imageCount === 2) { - gridColsClass = 'grid-cols-1 md:grid-cols-2'; - } - - return ( - - - - {team.title} - - - -
- {team.description.map((para: string) => ( -

{para}

- ))} -
- - {/* Image Grid / Gallery */} - {team.images && team.images.length > 0 && ( -
- {team.images.map((img: string, imgIdx: number) => { - let containerClass = 'aspect-square bg-slate-50 flex items-center justify-center p-4'; - if (imageCount === 1) { - containerClass = 'bg-slate-50 flex items-center justify-center'; - } else if (imageCount === 2) { - containerClass = 'aspect-square md:aspect-[4/3] bg-slate-50 flex items-center justify-center p-4'; - } + - return ( -
- {imageCount === 1 ? ( - /* eslint-disable-next-line @next/next/no-img-element */ - {`${team.title} - ) : ( - {`${team.title} - )} -
- ); - })} -
- )} - - - -
-

{team.social_title}

-
- {[...team.social_links] - .sort((a: {label: string, url: string}, b: {label: string, url: string}) => getSocialPriority(a.label) - getSocialPriority(b.label)) - .map((link: {label: string, url: string}) => ( - - ))} -
-
-
-
- ); - })} + {(data.teams as Organization[]).map((team) => ( + + ))}
diff --git a/src/app/(app)/[lang]/kozelet/versenycsapatok/page.tsx b/src/app/(app)/[lang]/kozelet/versenycsapatok/page.tsx index 1c1c332..7f50d80 100644 --- a/src/app/(app)/[lang]/kozelet/versenycsapatok/page.tsx +++ b/src/app/(app)/[lang]/kozelet/versenycsapatok/page.tsx @@ -1,28 +1,65 @@ +import { + OrganizationCard, + type OrganizationCardProps, +} from "@/components/common/OrganizationCard"; import { PageHeader } from "@/components/common/PageHeader"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Separator } from "@/components/ui/separator"; -import { Button } from "@/components/ui/button"; import { getDictionary } from "@/get-dictionary"; import { Locale } from "@/i18n-config"; -import Image from "next/image"; -import { getSocialIcon, getSocialPriority, getSocialName } from "@/lib/social-utils"; -type Team = { +type Organization = { id: string; title: string; description: string[]; social_title: string; social_links: { label: string; url: string }[]; images?: string[]; + stats?: OrganizationCardProps["stats"]; + events?: OrganizationCardProps["events"]; + activities?: OrganizationCardProps["activities"]; + departments?: OrganizationCardProps["departments"]; + target_audience?: OrganizationCardProps["targetAudience"]; + targetAudience?: OrganizationCardProps["targetAudience"]; + join_url?: string; + joinUrl?: string; + join_text?: string; + joinText?: string; }; +function getContactLabel(label: string) { + return label.replace(/:$/, ""); +} + +function getOrganizationCardProps( + organization: Organization, + locale: Locale, + imageBasePath?: string, +): OrganizationCardProps { + return { + name: organization.title, + stats: organization.stats, + presentation: organization.description, + events: organization.events, + activities: organization.activities, + departments: organization.departments, + targetAudience: + organization.targetAudience ?? organization.target_audience, + socialLinks: organization.social_links, + galleryImages: organization.images, + imageBasePath, + joinUrl: organization.joinUrl ?? organization.join_url, + joinText: organization.joinText ?? organization.join_text, + labels: { contacts: getContactLabel(organization.social_title) }, + locale, + }; +} + export default async function VersenycsapatokPage({ params, }: { params: Promise<{ lang: Locale }>; }) { const { lang } = await params; - const dictionary = await getDictionary(lang, 'competition_teams'); + const dictionary = await getDictionary(lang, "competition_teams"); const data = dictionary.competition_teams; if (!data) { @@ -31,135 +68,20 @@ export default async function VersenycsapatokPage({ return (
-
+
- - - {data.mvk.title} - - -
- {data.mvk.description.map((para: string) => ( -

{para}

- ))} -
- -
-

{data.mvk.social_title}

-
- {[...data.mvk.social_links] - .sort((a: {label: string, url: string}, b: {label: string, url: string}) => getSocialPriority(a.label) - getSocialPriority(b.label)) - .map((link: {label: string, url: string}) => ( - - ))} -
-
-
-
-
- {data.teams.map((team: Team) => { - const imageCount = team.images?.length || 0; - let gridColsClass = 'grid-cols-1 md:grid-cols-3'; - if (imageCount === 1) { - gridColsClass = 'grid-cols-1'; - } else if (imageCount === 2) { - gridColsClass = 'grid-cols-1 md:grid-cols-2'; - } - - return ( - - - - {team.title} - - - -
- {team.description.map((para: string) => ( -

{para}

- ))} -
- - {/* Image Grid / Gallery */} - {team.images && team.images.length > 0 && ( -
- {team.images.map((img: string, imgIdx: number) => { - let containerClass = 'aspect-square bg-slate-50 flex items-center justify-center p-4'; - if (imageCount === 1) { - containerClass = 'bg-slate-50 flex items-center justify-center'; - } else if (imageCount === 2) { - containerClass = 'aspect-square md:aspect-[4/3] bg-slate-50 flex items-center justify-center p-4'; - } + - return ( -
- {imageCount === 1 ? ( - /* eslint-disable-next-line @next/next/no-img-element */ - {`${team.title} - ) : ( - {`${team.title} - )} -
- ); - })} -
- )} - - - -
-

{team.social_title}

-
- {[...team.social_links] - .sort((a: {label: string, url: string}, b: {label: string, url: string}) => getSocialPriority(a.label) - getSocialPriority(b.label)) - .map((link: {label: string, url: string}) => ( - - ))} -
-
-
-
- ); - })} + {(data.teams as Organization[]).map((team) => ( + + ))}
diff --git a/src/components/common/OrganizationCard.tsx b/src/components/common/OrganizationCard.tsx new file mode 100644 index 0000000..750a95f --- /dev/null +++ b/src/components/common/OrganizationCard.tsx @@ -0,0 +1,93 @@ +import { + ContactsSection, + DepartmentsSection, + EventsSection, + GallerySection, + InlineListSection, + JoinCta, + OrganizationCardHeader, + PresentationSection, + RichTextSection, +} from "@/components/common/organization-card/sections"; +import type { OrganizationCardProps } from "@/components/common/organization-card/types"; +import { getOrganizationCardLabels } from "@/components/common/organization-card/utils"; +import { getSocialPriority } from "@/lib/social-utils"; +import { cn } from "@/lib/utils"; + +export type { + OrganizationCardLabels, + OrganizationCardProps, + OrganizationEvent, + OrganizationGalleryImage, + OrganizationSocialLink, + OrganizationStat, +} from "@/components/common/organization-card/types"; + +export function OrganizationCard({ + name, + stats, + presentation, + events, + activities, + departments, + targetAudience, + socialLinks, + galleryImages, + imageBasePath = "", + joinUrl, + joinText, + labels, + locale = "hu", + className, +}: Readonly) { + const sectionLabels = { ...getOrganizationCardLabels(locale), ...labels }; + const sortedSocialLinks = socialLinks + ? [...socialLinks].sort( + (a, b) => getSocialPriority(a.label) - getSocialPriority(b.label), + ) + : []; + + return ( +
+ + +
+ + + + + + + +
+ + {joinText ?? sectionLabels.join} +
+ ); +} diff --git a/src/components/common/organization-card/sections/ContactsSection.tsx b/src/components/common/organization-card/sections/ContactsSection.tsx new file mode 100644 index 0000000..1b48ebb --- /dev/null +++ b/src/components/common/organization-card/sections/ContactsSection.tsx @@ -0,0 +1,43 @@ +import type { Locale } from "@/i18n-config"; +import { getSocialIcon, getSocialName } from "@/lib/social-utils"; + +import type { OrganizationSocialLink } from "../types"; +import { Section } from "./Section"; + +export function ContactsSection({ + title, + links, + locale, +}: Readonly<{ + title: string; + links: readonly OrganizationSocialLink[]; + locale: Locale; +}>) { + if (!links.length) { + return null; + } + + return ( +
+
+ {links.map((link) => { + const socialName = getSocialName(link.label, locale); + + return ( + + {getSocialIcon(link.label, "h-5 w-5")} + + ); + })} +
+
+ ); +} diff --git a/src/components/common/organization-card/sections/EventsSection.tsx b/src/components/common/organization-card/sections/EventsSection.tsx new file mode 100644 index 0000000..f6235c9 --- /dev/null +++ b/src/components/common/organization-card/sections/EventsSection.tsx @@ -0,0 +1,71 @@ +import { CalendarDays, MapPin } from "lucide-react"; + +import { parseFormattedText } from "@/utils/parseFormattedText"; + +import type { OrganizationEvent } from "../types"; +import { Section } from "./Section"; + +function EventCard({ event }: Readonly<{ event: OrganizationEvent }>) { + return ( +
+

{event.title}

+ {event.description && ( +

+ {parseFormattedText(event.description)} +

+ )} + {(event.date || event.location) && ( +
+ {event.date && ( + + + {event.date} + + )} + {event.location && ( + + + {event.location} + + )} +
+ )} +
+ ); +} + +export function EventsSection({ + title, + events, +}: Readonly<{ + title: string; + events?: readonly OrganizationEvent[]; +}>) { + if (!events?.length) { + return null; + } + + return ( +
+
+ {events.map((event) => + event.href ? ( + + + + ) : ( +
+ +
+ ), + )} +
+
+ ); +} diff --git a/src/components/common/organization-card/sections/GallerySection.tsx b/src/components/common/organization-card/sections/GallerySection.tsx new file mode 100644 index 0000000..3dbad47 --- /dev/null +++ b/src/components/common/organization-card/sections/GallerySection.tsx @@ -0,0 +1,45 @@ +import Image from "next/image"; + +import type { OrganizationGalleryImage } from "../types"; +import { + getImageAlt, + resolveImageSrc, +} from "../utils"; +import { Section } from "./Section"; + +export function GallerySection({ + title, + images, + imageBasePath, + organizationName, +}: Readonly<{ + title: string; + images?: readonly OrganizationGalleryImage[]; + imageBasePath: string; + organizationName: string; +}>) { + if (!images?.length) { + return null; + } + + return ( +
+
+ {images.map((image, index) => ( +
+ {getImageAlt(image, +
+ ))} +
+
+ ); +} diff --git a/src/components/common/organization-card/sections/Header.tsx b/src/components/common/organization-card/sections/Header.tsx new file mode 100644 index 0000000..f08c8f2 --- /dev/null +++ b/src/components/common/organization-card/sections/Header.tsx @@ -0,0 +1,36 @@ +import type { OrganizationStat } from "../types"; +import { getStatLabel } from "../utils"; + +export function OrganizationCardHeader({ + name, + stats, +}: Readonly<{ + name: string; + stats?: readonly OrganizationStat[]; +}>) { + return ( +
+
+

+ {name} +

+ + {Boolean(stats?.length) && ( +
+ {stats?.map((stat) => ( +
+
+ {getStatLabel(stat.label)}: +
+
{stat.value}
+
+ ))} +
+ )} +
+
+ ); +} diff --git a/src/components/common/organization-card/sections/JoinCta.tsx b/src/components/common/organization-card/sections/JoinCta.tsx new file mode 100644 index 0000000..c1014d4 --- /dev/null +++ b/src/components/common/organization-card/sections/JoinCta.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { ArrowRight, Users } from "lucide-react"; + +export function JoinCta({ + href, + children, +}: Readonly<{ + href?: string; + children: React.ReactNode; +}>) { + if (!href) { + return null; + } + + return ( + + + + {children} + + + + ); +} diff --git a/src/components/common/organization-card/sections/ListSections.tsx b/src/components/common/organization-card/sections/ListSections.tsx new file mode 100644 index 0000000..0a79300 --- /dev/null +++ b/src/components/common/organization-card/sections/ListSections.tsx @@ -0,0 +1,55 @@ +import { Section } from "./Section"; + +export function InlineListSection({ + title, + items, +}: Readonly<{ + title: string; + items?: readonly string[]; +}>) { + if (!items?.length) { + return null; + } + + return ( +
+
    + {items.map((item, index) => ( +
  • + {item} +
  • + ))} +
+
+ ); +} + +export function DepartmentsSection({ + title, + departments, +}: Readonly<{ + title: string; + departments?: readonly string[]; +}>) { + if (!departments?.length) { + return null; + } + + return ( +
+
    + {departments.map((department) => ( +
  • + {department} +
  • + ))} +
+
+ ); +} diff --git a/src/components/common/organization-card/sections/Section.tsx b/src/components/common/organization-card/sections/Section.tsx new file mode 100644 index 0000000..daafad4 --- /dev/null +++ b/src/components/common/organization-card/sections/Section.tsx @@ -0,0 +1,32 @@ +import React from "react"; + +import { cn } from "@/lib/utils"; + +type SectionProps = Readonly<{ + title: string; + children: React.ReactNode; + className?: string; + bordered?: boolean; +}>; + +export function Section({ + title, + children, + className, + bordered = true, +}: SectionProps) { + return ( +
+

+ {title} +

+ {children} +
+ ); +} diff --git a/src/components/common/organization-card/sections/TextSections.tsx b/src/components/common/organization-card/sections/TextSections.tsx new file mode 100644 index 0000000..32afe71 --- /dev/null +++ b/src/components/common/organization-card/sections/TextSections.tsx @@ -0,0 +1,43 @@ +import type { TextContent } from "../types"; +import { renderTextContent } from "../utils"; +import { Section } from "./Section"; + +export function PresentationSection({ + title, + content, +}: Readonly<{ + title: string; + content?: TextContent; +}>) { + if (!content) { + return null; + } + + return ( +
+
+ {renderTextContent(content)} +
+
+ ); +} + +export function RichTextSection({ + title, + content, +}: Readonly<{ + title: string; + content?: TextContent; +}>) { + if (!content) { + return null; + } + + return ( +
+
+ {renderTextContent(content)} +
+
+ ); +} diff --git a/src/components/common/organization-card/sections/index.ts b/src/components/common/organization-card/sections/index.ts new file mode 100644 index 0000000..614d730 --- /dev/null +++ b/src/components/common/organization-card/sections/index.ts @@ -0,0 +1,7 @@ +export { ContactsSection } from "./ContactsSection"; +export { DepartmentsSection, InlineListSection } from "./ListSections"; +export { EventsSection } from "./EventsSection"; +export { GallerySection } from "./GallerySection"; +export { JoinCta } from "./JoinCta"; +export { OrganizationCardHeader } from "./Header"; +export { PresentationSection, RichTextSection } from "./TextSections"; diff --git a/src/components/common/organization-card/types.ts b/src/components/common/organization-card/types.ts new file mode 100644 index 0000000..da98ef2 --- /dev/null +++ b/src/components/common/organization-card/types.ts @@ -0,0 +1,59 @@ +import type { ReactNode } from "react"; + +import type { Locale } from "@/i18n-config"; + +export type TextContent = string | readonly string[] | ReactNode; + +export type OrganizationStat = { + label: string; + value: string | number; +}; + +export type OrganizationEvent = { + title: string; + description?: string; + date?: string; + location?: string; + href?: string; +}; + +export type OrganizationGalleryImage = + | string + | { + src: string; + alt?: string; + }; + +export type OrganizationSocialLink = { + label: string; + url: string; +}; + +export type OrganizationCardLabels = { + presentation: string; + events: string; + activities: string; + departments: string; + targetAudience: string; + contacts: string; + gallery: string; + join: string; +}; + +export type OrganizationCardProps = { + name: string; + stats?: readonly OrganizationStat[]; + presentation?: TextContent; + events?: readonly OrganizationEvent[]; + activities?: readonly string[]; + departments?: readonly string[]; + targetAudience?: TextContent; + socialLinks?: readonly OrganizationSocialLink[]; + galleryImages?: readonly OrganizationGalleryImage[]; + imageBasePath?: string; + joinUrl?: string; + joinText?: string; + labels?: Partial; + locale?: Locale; + className?: string; +}; diff --git a/src/components/common/organization-card/utils.tsx b/src/components/common/organization-card/utils.tsx new file mode 100644 index 0000000..bd8ab80 --- /dev/null +++ b/src/components/common/organization-card/utils.tsx @@ -0,0 +1,85 @@ +import React from "react"; + +import type { Locale } from "@/i18n-config"; +import { parseFormattedText } from "@/utils/parseFormattedText"; + +import type { + OrganizationCardLabels, + OrganizationGalleryImage, + TextContent, +} from "./types"; + +const labelsByLocale: Record = { + hu: { + presentation: "Bemutatás", + events: "Kiemelt események", + activities: "Tevékenységek", + departments: "Tagozatok", + targetAudience: "Kinek ajánljuk?", + contacts: "Elérhetőségek", + gallery: "Galéria", + join: "Csatlakozom!", + }, + en: { + presentation: "Presentation", + events: "Featured events", + activities: "Activities", + departments: "Departments", + targetAudience: "Who is it for?", + contacts: "Contact", + gallery: "Gallery", + join: "Join us", + }, +}; + +export function getOrganizationCardLabels(locale: Locale) { + return labelsByLocale[locale] ?? labelsByLocale.hu; +} + +export function renderTextContent(content: TextContent) { + if (Array.isArray(content)) { + return ( +
+ {content.map((paragraph, index) => ( +

+ {parseFormattedText(paragraph)} +

+ ))} +
+ ); + } + + if (typeof content === "string") { + return

{parseFormattedText(content)}

; + } + + return content; +} + +export function resolveImageSrc( + image: OrganizationGalleryImage, + imageBasePath: string, +) { + const source = typeof image === "string" ? image : image.src; + if (source.startsWith("/") || source.startsWith("http")) { + return source; + } + + return `${imageBasePath}/${source}`; +} + +export function getImageAlt( + image: OrganizationGalleryImage, + organizationName: string, + index: number, +) { + if (typeof image !== "string" && image.alt) { + return image.alt; + } + + return `${organizationName} - ${index + 1}`; +} + +export function getStatLabel(label: string) { + return label.replace(/:$/, ""); +} diff --git a/src/lib/social-utils.tsx b/src/lib/social-utils.tsx index 9ce858b..94034bd 100644 --- a/src/lib/social-utils.tsx +++ b/src/lib/social-utils.tsx @@ -1,72 +1,201 @@ -import { - ExternalLink, - Facebook, - Instagram, - Linkedin, - Youtube, - Globe, - Link as LinkIcon, - MessageCircle -} from "lucide-react"; -import { Locale } from "@/i18n-config"; +import { ExternalLink, Globe, Mail } from "lucide-react"; import React from "react"; -export function TiktokIcon({ className }: Readonly<{ className?: string }>) { +import { Locale } from "@/i18n-config"; + +type SocialIconProps = Readonly<{ className?: string }>; + +function FacebookIcon({ className }: SocialIconProps) { + return ( + + ); +} + +function InstagramIcon({ className }: SocialIconProps) { + return ( + + ); +} + +function LinkedInIcon({ className }: SocialIconProps) { + return ( + + ); +} + +function YouTubeIcon({ className }: SocialIconProps) { + return ( + + ); +} + +export function TiktokIcon({ className }: SocialIconProps) { + return ( + + ); +} + +export function XSocialIcon({ className }: SocialIconProps) { return ( -