168 lines
5.8 KiB
TypeScript
168 lines
5.8 KiB
TypeScript
export type EventAvailabilityInput = {
|
|
status?: string | null;
|
|
registration_start_date?: string | null;
|
|
registration_end_date?: string | null;
|
|
start_time?: string | null;
|
|
end_time?: string | null;
|
|
capacity?: number | null;
|
|
registration_count?: number | null;
|
|
};
|
|
|
|
export function getEventPath(event: { id: number; slug?: string | null }) {
|
|
const segment = event.slug?.trim() || String(event.id);
|
|
return `/events/${encodeURIComponent(segment)}`;
|
|
}
|
|
|
|
export type EventRegistrationState =
|
|
| "open"
|
|
| "not_started"
|
|
| "closed"
|
|
| "full"
|
|
| "unavailable";
|
|
|
|
function timestamp(value?: string | null) {
|
|
if (!value) return null;
|
|
const parsed = new Date(value).getTime();
|
|
return Number.isFinite(parsed) ? parsed : null;
|
|
}
|
|
|
|
export function getEventRegistrationState(
|
|
event: EventAvailabilityInput,
|
|
now = Date.now(),
|
|
): { state: EventRegistrationState; remaining: number | null } {
|
|
if (event.status !== "published") {
|
|
return { state: "unavailable", remaining: null };
|
|
}
|
|
|
|
const endsAt = timestamp(event.registration_end_date);
|
|
if (endsAt !== null && now > endsAt) {
|
|
return { state: "closed", remaining: getRemainingCapacity(event) };
|
|
}
|
|
|
|
const startsAt = timestamp(event.registration_start_date);
|
|
if (startsAt !== null && now < startsAt) {
|
|
return { state: "not_started", remaining: getRemainingCapacity(event) };
|
|
}
|
|
|
|
const remaining = getRemainingCapacity(event);
|
|
if (remaining !== null && remaining <= 0) {
|
|
return { state: "full", remaining: 0 };
|
|
}
|
|
|
|
return { state: "open", remaining };
|
|
}
|
|
|
|
function getRemainingCapacity(event: EventAvailabilityInput) {
|
|
if (event.capacity == null || !Number.isFinite(Number(event.capacity))) {
|
|
return null;
|
|
}
|
|
|
|
const used = Number.isFinite(Number(event.registration_count))
|
|
? Number(event.registration_count)
|
|
: 0;
|
|
return Math.max(0, Number(event.capacity) - used);
|
|
}
|
|
|
|
export function getEventCardState(
|
|
event: EventAvailabilityInput,
|
|
now = Date.now(),
|
|
): "past" | "full" | "upcoming" {
|
|
const endsAt = timestamp(event.end_time);
|
|
if (event.status === "completed" || (endsAt !== null && endsAt < now)) {
|
|
return "past";
|
|
}
|
|
|
|
if (getEventRegistrationState(event, now).state === "full") {
|
|
return "full";
|
|
}
|
|
|
|
return "upcoming";
|
|
}
|
|
|
|
export function getEventBrowseLabel(event: EventAvailabilityInput, now = Date.now()) {
|
|
if (getEventCardState(event, now) === "past") return "برگزار شده";
|
|
if (getEventRegistrationState(event, now).state === "open") return "درحال برگزاری";
|
|
const startsAt = timestamp(event.start_time);
|
|
if (startsAt !== null && startsAt <= now) return "درحال برگزاری";
|
|
return "بهزودی";
|
|
}
|
|
|
|
export function getEventRegistrationMessage(event: EventAvailabilityInput, now = Date.now()) {
|
|
if (getEventCardState(event, now) === "past") return "این رویداد برگزار شده است";
|
|
const registration = getEventRegistrationState(event, now);
|
|
if (registration.state === "not_started") return "ثبتنام هنوز آغاز نشده است";
|
|
if (registration.state === "closed") return "زمان ثبتنام به پایان رسیده است";
|
|
if (registration.state === "full") return "ظرفیت ثبتنام تکمیل شده است";
|
|
if (registration.state === "unavailable") return "امکان ثبتنام وجود ندارد";
|
|
if (registration.remaining === null) return "ثبتنام در حال انجام است";
|
|
return `${registration.remaining.toLocaleString("fa-IR")} نفر ظرفیت باقیمانده`;
|
|
}
|
|
|
|
export type RegistrationErrorKind =
|
|
| "already_registered"
|
|
| "not_started"
|
|
| "closed"
|
|
| "full"
|
|
| "unknown";
|
|
|
|
function normalizeApiMessage(message: unknown) {
|
|
return String(message ?? "")
|
|
.replace(/[يى]/g, "ی")
|
|
.replace(/\u200c/g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim()
|
|
.toLocaleLowerCase();
|
|
}
|
|
|
|
export function classifyRegistrationError(message: unknown): RegistrationErrorKind {
|
|
const normalized = normalizeApiMessage(message);
|
|
if (normalized.includes("already registered") || normalized.includes("قبلا در این ایونت ثبت نام کرده اید")) {
|
|
return "already_registered";
|
|
}
|
|
if (normalized.includes("زمان ثبت نام هنوز آغاز نشده") || normalized.includes("registration has not started")) {
|
|
return "not_started";
|
|
}
|
|
if (normalized.includes("مهلت ثبت نام به پایان رسیده") || normalized.includes("registration period has ended")) {
|
|
return "closed";
|
|
}
|
|
if (normalized.includes("ظرفیت شرکت کنندگان تکمیل است") || normalized.includes("event is full")) {
|
|
return "full";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
export type PaymentOutcome = "paid" | "pending" | "failed" | "canceled" | "unknown";
|
|
|
|
export function getPaymentOutcome(status: unknown): PaymentOutcome {
|
|
const normalized = normalizeApiMessage(status);
|
|
if (["success", "paid", "ok", "پرداخت شده", "تایید شده"].includes(normalized)) {
|
|
return "paid";
|
|
}
|
|
if (["pending", "initiated", "init", "در انتظار", "در حال پردازش"].includes(normalized)) {
|
|
return "pending";
|
|
}
|
|
if (["canceled", "cancelled", "لغو شده"].includes(normalized)) {
|
|
return "canceled";
|
|
}
|
|
if (["failed", "failure", "ناموفق"].includes(normalized)) {
|
|
return "failed";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
export type RegistrationOutcome = "confirmed" | "pending" | "canceled" | "unknown";
|
|
|
|
export function getRegistrationOutcome(status: unknown): RegistrationOutcome {
|
|
const normalized = normalizeApiMessage(status);
|
|
if (["confirmed", "attended", "approved", "تایید شده"].includes(normalized)) {
|
|
return "confirmed";
|
|
}
|
|
if (["pending", "در انتظار"].includes(normalized)) {
|
|
return "pending";
|
|
}
|
|
if (["canceled", "cancelled", "لغو شده"].includes(normalized)) {
|
|
return "canceled";
|
|
}
|
|
return "unknown";
|
|
}
|