Initial commit: dashboard

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-03 23:17:36 +03:30
co-authored by Cursor
commit 3db32212ad
177 changed files with 29988 additions and 0 deletions
+221
View File
@@ -0,0 +1,221 @@
"use client";
import { useEffect, useId, useMemo, useState } from "react";
import CategoryTreeSelect from "@/components/CategoryTreeSelect";
import {
searchMeshkeeCategories,
type MeshkeeCategory,
} from "@/services/meshkee-categories";
import styles from "./WebsiteLinkGenerator.module.css";
export type WebsiteLinkKind = "businesses" | "blog";
function buildCategoryLink(kind: WebsiteLinkKind, category: MeshkeeCategory) {
const path = kind === "blog" ? "/blog" : "/businesses";
const query = category.slug
? `category=${encodeURIComponent(category.slug)}`
: `categoryId=${encodeURIComponent(category.id)}`;
return `${path}?${query}`;
}
function LinkIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden
>
<path d="M10 13a5 5 0 0 0 7.54.54l1.92-1.92a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54L4.54 12.38a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
);
}
export default function WebsiteLinkGenerator({
onPick,
disabled = false,
}: {
onPick: (url: string) => void;
disabled?: boolean;
}) {
const titleId = useId();
const [open, setOpen] = useState(false);
const [kind, setKind] = useState<WebsiteLinkKind>("businesses");
const [categories, setCategories] = useState<MeshkeeCategory[]>([]);
const [categoryId, setCategoryId] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
if (!open) return;
setKind("businesses");
setCategoryId("");
setError("");
}, [open]);
useEffect(() => {
if (!open) return;
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
const onKey = (event: KeyboardEvent) => {
if (event.key === "Escape") setOpen(false);
};
window.addEventListener("keydown", onKey);
return () => {
document.body.style.overflow = previousOverflow;
window.removeEventListener("keydown", onKey);
};
}, [open]);
useEffect(() => {
if (!open) return;
const controller = new AbortController();
setLoading(true);
setError("");
void searchMeshkeeCategories("", controller.signal)
.then((items) => {
if (controller.signal.aborted) return;
setCategories(items);
})
.catch((err) => {
if (controller.signal.aborted) return;
setCategories([]);
setError(
err instanceof Error ? err.message : "Could not load categories.",
);
})
.finally(() => {
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort();
}, [open]);
const selected = useMemo(
() => categories.find((item) => item.id === categoryId) ?? null,
[categories, categoryId],
);
const preview = selected ? buildCategoryLink(kind, selected) : "";
return (
<>
<button
type="button"
className={styles.trigger}
aria-label="Generate link"
title="Generate link"
disabled={disabled}
onClick={() => setOpen(true)}
>
<LinkIcon />
</button>
{open ? (
<div
className={styles.overlay}
role="presentation"
onClick={() => setOpen(false)}
>
<div
className={styles.modal}
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
onClick={(event) => event.stopPropagation()}
>
<div className={styles.head}>
<div>
<p className={styles.kicker}>Link generator</p>
<h3 id={titleId} className={styles.title}>
Build category link
</h3>
</div>
<button
type="button"
className={styles.closeBtn}
aria-label="Close"
onClick={() => setOpen(false)}
>
×
</button>
</div>
<div className={styles.kindSwitch} role="tablist" aria-label="Link type">
<button
type="button"
role="tab"
aria-selected={kind === "businesses"}
className={`${styles.kindBtn} ${
kind === "businesses" ? styles.kindBtnActive : ""
}`}
onClick={() => setKind("businesses")}
>
Businesses category
</button>
<button
type="button"
role="tab"
aria-selected={kind === "blog"}
className={`${styles.kindBtn} ${
kind === "blog" ? styles.kindBtnActive : ""
}`}
onClick={() => setKind("blog")}
>
Blog category
</button>
</div>
{loading ? (
<p className={styles.status}>Loading categories…</p>
) : error ? (
<p className={styles.error}>{error}</p>
) : (
<CategoryTreeSelect
categories={categories}
value={categoryId ? [categoryId] : []}
onChange={(ids) => setCategoryId(ids[0] ?? "")}
selection="single"
dropdownVariant="inFlow"
label="Category"
placeholder="Search categories…"
/>
)}
<div className={styles.preview}>
<span>Preview</span>
<code dir="ltr">{preview || "Select a category"}</code>
</div>
<div className={styles.actions}>
<button
type="button"
className={`outline-btn ${styles.secondaryBtn}`}
onClick={() => setOpen(false)}
>
Cancel
</button>
<button
type="button"
className={`outline-btn ${styles.primaryBtn}`}
disabled={!preview}
onClick={() => {
if (!preview) return;
onPick(preview);
setOpen(false);
}}
>
Use link
</button>
</div>
</div>
</div>
) : null}
</>
);
}