diff --git a/src/app/adherents/[id]/page.tsx b/src/app/adherents/[id]/page.tsx index 4ca7316..6b6ca5f 100644 --- a/src/app/adherents/[id]/page.tsx +++ b/src/app/adherents/[id]/page.tsx @@ -7,6 +7,13 @@ import { SiteFooter } from "@/components/SiteFooter"; import { VitrineImage } from "@/components/VitrineImage"; import { PromoImage } from "@/components/PromoImage"; import { getMemberLivePromotions, getPublicMember } from "@/lib/queries"; +import { + formatSlots, + getOpenStatus, + normalizeWebsite, + parseMemberHours, + websiteLabel, +} from "@/lib/member-profile"; export const dynamic = "force-dynamic"; @@ -31,6 +38,52 @@ function initialsOf(name: string) { return name.split(/\s+/).map((w) => w[0]).join("").slice(0, 2).toUpperCase(); } +type ContactIconName = "phone" | "mail" | "website" | "pin"; + +function ContactIcon({ name }: { name: ContactIconName }) { + const paths: Record = { + phone: , + mail: <>, + website: <>, + pin: <>, + }; + return ( + + ); +} + +function ContactAction({ + href, + icon, + label, + detail, + external = false, +}: { + href: string; + icon: ContactIconName; + label: string; + detail: string; + external?: boolean; +}) { + return ( + + + + {label} + {detail} + + + + ); +} + export async function generateMetadata({ params, }: { @@ -65,10 +118,11 @@ export default async function FicheAdherentPage({ ); const paragraphs = (member.description ?? "").split("\n").map((s) => s.trim()).filter(Boolean); const tags = (member.tags ?? "").split(",").map((s) => s.trim()).filter(Boolean); - const hours = (member.hours ?? "") - .split("\n") - .map((line) => line.split("|").map((s) => s.trim())) - .filter((p) => p[0]); + const hours = parseMemberHours(member.hours); + const openStatus = getOpenStatus(hours); + const normalizedWebsite = normalizeWebsite(member.website); + const shortWebsite = websiteLabel(member.website); + const phoneHref = member.phone ? `tel:${member.phone.replace(/[^\d+]/g, "")}` : null; const sectionCard: React.CSSProperties = { background: "#fff", @@ -149,11 +203,14 @@ export default async function FicheAdherentPage({
- {member.phone && ( - Appeler + {phoneHref && ( + Appeler + )} + {normalizedWebsite && ( + Site web ↗ )} {mapQuery && ( - Itinéraire + Itinéraire )}
@@ -186,44 +243,90 @@ export default async function FicheAdherentPage({ {/* practical info */} -
-

Informations pratiques

-
+
+
-
Horaires
+ Préparer votre visite +

Informations pratiques

+
+ {openStatus && ( +
+ +
+ {openStatus.label} + {openStatus.detail && {openStatus.detail}} +
+
+ )} +
+ +
+
+
+ +
+ Cette semaine +

Horaires d'ouverture

+
+
{hours.length > 0 ? ( -
- {hours.map(([label, range], i) => { - const closed = (range ?? "").toLowerCase().includes("ferm"); +
+ {hours.map((day) => { + const isToday = openStatus?.todayKey === day.key; return ( -
- {label} - {range} +
+ + {day.label} + {isToday && Aujourd'hui} + + + {day.closed ? "Fermé" : formatSlots(day.slots)} +
); })}
) : ( -
Horaires non communiqués.
+
Horaires non communiqués.
)} -
-
-
Contact
-
- {member.phone && ( -
{member.phone}
+
+ +
+
+ +
+ Contact direct +

Joindre l'établissement

+
+
+
+ {phoneHref && member.phone && ( + )} {member.email && ( - {member.email} + )} - {member.website && ( - {member.website.replace(/^https?:\/\//, "")} + {normalizedWebsite && shortWebsite && ( + )} - {!member.phone && !member.email && !member.website && ( -
Coordonnées non communiquées.
+ {mapQuery && fullAddress && ( + + )} + {!phoneHref && !member.email && !normalizedWebsite && !fullAddress && ( +
Coordonnées non communiquées.
)}
-
+
@@ -287,11 +390,21 @@ export default async function FicheAdherentPage({ {/* contact CTA */} -
-

Une question pour ce commerçant ?

-

Contactez {member.name} directement.

- Envoyer un message -
+ {(member.email || phoneHref || normalizedWebsite) && ( +
+

Une question pour ce commerçant ?

+

Contactez {member.name} directement.

+ + {member.email ? "Envoyer un message" : phoneHref ? "Appeler maintenant" : "Visiter le site"} + +
+ )} diff --git a/src/app/backend/actions.ts b/src/app/backend/actions.ts index c165934..26d10b1 100644 --- a/src/app/backend/actions.ts +++ b/src/app/backend/actions.ts @@ -23,6 +23,7 @@ import { users, } from "@/db/schema"; import { can, LABEL_TO_ROLE } from "@/lib/rbac"; +import { normalizeWebsite } from "@/lib/member-profile"; import { SITE_SETTING_DEFAULTS } from "@/lib/site-settings"; import type { AppRole } from "@/types/next-auth"; @@ -267,7 +268,7 @@ export async function updateMember(formData: FormData) { description: String(formData.get("description") ?? "").trim() || null, postalCode: String(formData.get("postalCode") ?? "").trim() || null, phone: String(formData.get("phone") ?? "").trim() || null, - website: String(formData.get("website") ?? "").trim() || null, + website: normalizeWebsite(String(formData.get("website") ?? "")), memberSince: formData.get("memberSince") ? Number(formData.get("memberSince")) : null, coverUrl: String(formData.get("coverUrl") ?? "").trim() || null, logoUrl: String(formData.get("logoUrl") ?? "").trim() || null, @@ -797,7 +798,7 @@ export async function updateOwnProfile(formData: FormData) { postalCode: String(formData.get("postalCode") ?? "").trim() || null, city: String(formData.get("city") ?? "").trim() || null, phone: String(formData.get("phone") ?? "").trim() || null, - website: String(formData.get("website") ?? "").trim() || null, + website: normalizeWebsite(String(formData.get("website") ?? "")), hours: String(formData.get("hours") ?? "").trim() || null, tags: String(formData.get("tags") ?? "").trim() || null, coverUrl: String(formData.get("coverUrl") ?? "").trim() || null, diff --git a/src/app/backend/adherents/[id]/page.tsx b/src/app/backend/adherents/[id]/page.tsx index 4088162..a538a51 100644 --- a/src/app/backend/adherents/[id]/page.tsx +++ b/src/app/backend/adherents/[id]/page.tsx @@ -6,6 +6,7 @@ import { db } from "@/db"; import { categories, members, users } from "@/db/schema"; import { can } from "@/lib/rbac"; import { ImageField } from "@/components/ImageField"; +import { HoursEditor } from "@/components/HoursEditor"; import { communeOptions } from "@/lib/communes"; import { deleteMember, resetMemberPassword, updateMember } from "../../actions"; @@ -148,8 +149,7 @@ export default async function EditMemberPage({
- -