Files
community-rule/lib/create/flushPendingCommunityAvatar.ts
adilallo 8212423ec3 Keep a guest community logo on review and on the published rule.
Sign-in was clearing the staged file before it uploaded, and review only read the server URL, so a chosen logo disappeared after the upload step.
2026-11-05 20:02:40 -07:00

98 lines
2.9 KiB
TypeScript

import type { CreateFlowState } from "../../app/(app)/create/types";
import { CreateFlowUploadValidationError } from "./createFlowUploadValidation";
import {
clearPendingCommunityAvatarFile,
readPendingCommunityAvatarFile,
} from "./pendingCommunityAvatarUpload";
import { uploadCreateFlowFile } from "./uploadToServer";
export type FlushPendingCommunityAvatarResult =
| { status: "none" }
| { status: "uploaded"; url: string }
| { status: "invalid" }
| { status: "failed" };
/**
* URL from the last successful flush. The IndexedDB file is removed on
* success, so a second caller (publish, sign-in transfer) can still apply
* the same URL if React state has not caught up yet.
* Cleared when anonymous draft storage is wiped.
*/
let lastUploadedUrl: string | null = null;
let inFlight: Promise<FlushPendingCommunityAvatarResult> | null = null;
export function forgetFlushedCommunityAvatarUrl(): void {
lastUploadedUrl = null;
}
async function runFlush(): Promise<FlushPendingCommunityAvatarResult> {
let file: File | null;
try {
file = await readPendingCommunityAvatarFile();
} catch {
return lastUploadedUrl
? { status: "uploaded", url: lastUploadedUrl }
: { status: "none" };
}
if (!file) {
return lastUploadedUrl
? { status: "uploaded", url: lastUploadedUrl }
: { status: "none" };
}
try {
const { url } = await uploadCreateFlowFile(file, "communityAvatar");
lastUploadedUrl = url;
await clearPendingCommunityAvatarFile();
return { status: "uploaded", url };
} catch (err) {
if (err instanceof CreateFlowUploadValidationError) {
await clearPendingCommunityAvatarFile();
return { status: "invalid" };
}
return { status: "failed" };
}
}
/**
* Upload the staged community logo, once, even if review and sign-in both ask.
* A validation failure drops the file. A transient failure leaves it in place.
*/
export function flushPendingCommunityAvatar(): Promise<FlushPendingCommunityAvatarResult> {
if (!inFlight) {
inFlight = runFlush().finally(() => {
inFlight = null;
});
}
return inFlight;
}
/**
* Copy a staged logo onto `state` as `communityAvatarUrl` when upload works.
* `upload_failed` means the file is still staged and must not be published over.
*/
export async function stateWithPendingCommunityAvatar(
state: CreateFlowState,
): Promise<
| { ok: true; state: CreateFlowState }
| { ok: false; reason: "upload_failed" }
> {
const existing =
typeof state.communityAvatarUrl === "string"
? state.communityAvatarUrl.trim()
: "";
if (existing.length > 0) return { ok: true, state };
const flushed = await flushPendingCommunityAvatar();
if (flushed.status === "uploaded") {
return {
ok: true,
state: { ...state, communityAvatarUrl: flushed.url },
};
}
if (flushed.status === "failed") {
return { ok: false, reason: "upload_failed" };
}
return { ok: true, state };
}