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.
This commit is contained in:
adilallo committed 2026-11-05 20:02:40 -07:00
1 parent 2348a9e94f
commit 8212423ec3
19 files changed
+780 -61

No files matched your search

+97
View File
@@ -0,0 +1,97 @@
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 };
}
+2 -2
View File
@@ -28,8 +28,8 @@ function reasonFromUnknown(value: unknown): CreateFlowUploadValidationReason | n
}
/**
* Authenticated multipart upload to `POST /api/uploads`.
* Caller must have a session cookie (same-origin fetch).
* Multipart upload to `POST /api/uploads`.
* `communityAvatar` is accepted for a guest; other purposes need a session.
*/
export async function uploadCreateFlowFile(
file: File,