Skip to content

Celebrations

This section provides detailed reference documentation for working with the Celebrations module.

The Celebrations reference documentation is broken down into functional subsections: Celebrations

Celebrations features:

  • Module Overview — What the module does and key capabilities.

  • Endpoints — API functions used by the module.

  • Client Workflows — Client-side functions that orchestrate endpoint calls (example: creating a post).

The module allows users to create celebration posts with a main image and attachments. Other users can react to posts and reply in a comment thread.

Admins/Super Admins can add, edit, and archive different celebration types.

The module also includes a Birthdays section to view upcoming birthdays and search for text inside main posts and replies.

This section displays the endpoints being used.

  • The Post Celebrations endpoint creates a new main celebration.
export async function postCelebrations(
payload: CelebrationPayload
): Promise<any> {
const response = await Api.post("/celebrations", payload);
return response?.ok ? response.data?.data : null;
}
  • Fetches the list of celebrations and returns an array of celebration items.
export const getCelebrations = async (): Promise<any[] | null> => {
const res: any = await Api.get("/celebrations");
if (!res?.ok) return null;
const body = res?.data ?? res;
if (body?.success === false) return null;
return body?.data?.celebrations ?? [];
};

This function fetches the full thread for a single celebration post — meaning:

  • The main celebration post (returned under thread.celebration)

  • The comment thread / replies for that post (usually under thread.replies)

export const getCelebrationThread = async (
celebrationId: number
): Promise<{ celebration: any; replies: any[] } | null> => {
const res: any = await Api.get(`/celebrations/${celebrationId}`);
if (!res?.ok) return null;
const body = res?.data ?? res;
if (body?.success === false) return null;
return body?.data ?? null;
};

This function uploads an attachment file to an existing celebration post.

export async function postCelebrationAttachments(
celebrationId: number,
payload: any
): Promise<any> {
const response = await Api.post(
`/celebrations/${celebrationId}/attachments`,
payload
);
return response?.ok ? response.data?.data ?? response.data ?? null : null;
}

This function uploads an images to an existing celebration post

This function uploads an Image to an existing celebration post.
export async function postCelebrationImages(
celebrationId: number,
payload: any
): Promise<any> {
const response = await Api.post(
`/celebrations/${celebrationId}/images`,
payload
);
return response?.ok ? response.data?.data ?? response.data ?? null : null;
}

this function should fetch the images that a user posted

export async function getCelebrationImages(
celebrationId: number
): Promise<any[] | null> {
const response = await Api.get(`/celebrations/${celebrationId}/images`);
return response?.ok ? response.data?.data ?? response.data ?? null : null;
}

this function should fetch the attachments user posted

export async function getCelebrationAttachments(
celebrationId: number
): Promise<any[] | null> {
const response = await Api.get(
`/celebrations/${celebrationId}/attachments`
);
return response?.ok ? response.data?.data ?? response.data ?? null : null;
}

this function should fetch the celebration type.It can optionally ask the backend to include archived and/or inactive types.

export async function getCelebrationType(options?: {
includeArchived?: boolean;
includeInactive?: boolean;
}): Promise<any[] | null> {
const qs = new URLSearchParams();
if (options?.includeArchived) qs.set("include_archived", "1");
if (options?.includeInactive) qs.set("include_inactive", "1");
const query = qs.toString() ? `?${qs.toString()}` : "";
const res: any = await Api.get(`/celebrations/types${query}`);
if (!res?.ok) return null;
const body = res?.data ?? res;
const types = body?.data?.types ?? body?.types ?? body?.data ?? [];
return Array.isArray(types) ? types : [];
}

handleSendPost function

  • This handleSendPost function is the client-side Create Celebration Post workflow. It controls everything that happens when a user clicks Post—validation → post creation → media upload → feed refresh → UI reset—and it uses the postCelebrations endpoint to create the main post.
const handleSendPost = async () => {
if (!canCreateTopLevel || isSubmitting) return;
if (!selectedTypeValue) {
toast.error("Please select a celebration type.");
return;
}
const titleText = title.trim();
const bodyHtml = (body ?? "").trim();
const bodyPlain = bodyHtml
.replace(/<[^>]+>/g, "")
.replace(/\s+/g, " ")
.trim();
if (!titleText && !bodyPlain && !attachmentFile && !imageFile) return;
const payload = {
contentText: titleText || "Untitled",
contentJson: { blocks: [{ type: "text", value: bodyHtml }] },
celebrationType: selectedTypeValue,
};
setIsSubmitting(true);
try {
const created = await postCelebrations(payload);
const celebrationId =
created?.celebrationId ??
created?.id ??
created?.data?.celebrationId ??
created?.data?.id ??
created?.celebration?.id ??
created?.data?.celebration?.id ??
null;
if (celebrationId && imageFile) {
const imageForm = new FormData();
imageForm.append("file", imageFile);
const uploadedImages = await postCelebrationImages(celebrationId, imageForm);
if (!uploadedImages) toast.warning("Post created, but image failed to upload");
else toast.success("Image uploaded successfully");
}
if (celebrationId && attachmentFile) {
const fileForm = new FormData();
fileForm.append("file", attachmentFile);
const uploadedFiles = await postCelebrationAttachments(celebrationId, fileForm);
if (!uploadedFiles) toast.warning("Post created, but attachment failed to upload");
else toast.success("Attachment uploaded successfully");
}
toast.success("Post created");
setTitle("");
setBody("");
setAttachmentFile(null);
setImageFile(null);
setShowEmojiPicker(false);
setShowMentionPicker(false);
setMentionQuery("");
setShowTypePicker(false);
setVisibleCount(BATCH_SIZE);
await refetchCelebrations();
if (scrollContainerRef.current) scrollContainerRef.current.scrollTop = 0;
} catch {
toast.error("Failed to create post");
} finally {
setIsSubmitting(false);
}
};