Update carousels, card hover, and IranBay branding.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-04 13:21:27 +03:30
co-authored by Cursor
commit 1153e8e78f
178 changed files with 30517 additions and 0 deletions
+193
View File
@@ -0,0 +1,193 @@
"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>
);
}