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.
98 lines
2.9 KiB
TypeScript
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 };
|
|
}
|