Files

194 lines
5.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useId, useState } from "react";
import {
getSpecialBusinesses,
saveSpecialBusinesses,
type SpecialCarousel,
} from "@/services/special-businesses";
import type { MarketplaceBusiness } from "@/types/business";
import styles from "./MakeSpecialModal.module.css";
export default function MakeSpecialModal({
business,
open,
onClose,
onSaved,
}: {
business: MarketplaceBusiness | null;
open: boolean;
onClose: () => void;
onSaved?: () => void;
}) {
const titleId = useId();
const [carousels, setCarousels] = useState<SpecialCarousel[]>([]);
const [loading, setLoading] = useState(false);
const [savingId, setSavingId] = useState<string | null>(null);
const [error, setError] = useState("");
const [status, setStatus] = useState("");
useEffect(() => {
if (!open) return;
let cancelled = false;
void (async () => {
setLoading(true);
setError("");
setStatus("");
try {
const config = await getSpecialBusinesses();
if (!cancelled) setCarousels(config.carousels);
} catch (err) {
if (!cancelled) {
setError(
err instanceof Error
? err.message
: "Could not load carousels.",
);
}
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => {
cancelled = true;
};
}, [open]);
useEffect(() => {
if (!open) return;
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
const onKey = (event: KeyboardEvent) => {
if (event.key === "Escape" && !savingId) onClose();
};
window.addEventListener("keydown", onKey);
return () => {
document.body.style.overflow = previousOverflow;
window.removeEventListener("keydown", onKey);
};
}, [open, savingId, onClose]);
if (!open || !business) return null;
const name = business.name || business.slug;
async function pickCarousel(carousel: SpecialCarousel) {
if (!business) return;
setSavingId(carousel.id);
setError("");
setStatus("");
try {
const config = await getSpecialBusinesses();
const nextCarousels = config.carousels.map((item) => {
if (item.id !== carousel.id) return item;
if (item.businessIds.includes(business.id)) return item;
return {
...item,
businessIds: [...item.businessIds, business.id],
};
});
await saveSpecialBusinesses({ carousels: nextCarousels });
setStatus(`Added to ${carousel.title}`);
onSaved?.();
window.setTimeout(() => onClose(), 500);
} catch (err) {
setError(
err instanceof Error
? err.message
: "Could not add business to carousel.",
);
} finally {
setSavingId(null);
}
}
return (
<div
className={styles.overlay}
role="presentation"
onMouseDown={(event) => {
if (event.target === event.currentTarget && !savingId) onClose();
}}
>
<div
className={styles.modal}
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
>
<div className={styles.head}>
<div>
<p className={styles.kicker}>Make special</p>
<h2 id={titleId} className={styles.title}>
Choose a carousel
</h2>
<p className={styles.hint}>Add {name} to one of the homepage carousels.</p>
</div>
<button
type="button"
className={styles.closeBtn}
aria-label="Close"
disabled={Boolean(savingId)}
onClick={onClose}
>
×
</button>
</div>
{loading ? (
<p className={styles.empty}>Loading carousels…</p>
) : error && !carousels.length ? (
<p className={styles.error} role="alert">
{error}
</p>
) : carousels.length ? (
<ul className={styles.list}>
{carousels.map((carousel) => {
const alreadyIn = carousel.businessIds.includes(business.id);
return (
<li key={carousel.id}>
<button
type="button"
className={styles.item}
disabled={Boolean(savingId) || alreadyIn}
onClick={() => void pickCarousel(carousel)}
>
<span className={styles.itemMain}>
<span className={styles.itemTitle}>
{carousel.title || "Untitled"}
</span>
{carousel.subtext ? (
<span className={styles.itemSubtext}>
{carousel.subtext}
</span>
) : null}
</span>
<span className={styles.itemMeta}>
{savingId === carousel.id
? "Adding…"
: alreadyIn
? "Already in"
: `${carousel.businessIds.length} businesses`}
</span>
</button>
</li>
);
})}
</ul>
) : (
<p className={styles.empty}>
No carousels yet. Create one under Website → Carousels.
</p>
)}
{error && carousels.length ? (
<p className={styles.error} role="alert">
{error}
</p>
) : null}
{status ? <p className={styles.status}>{status}</p> : null}
</div>
</div>
);
}