feat: all profile uploads through transferStore; delete legacy POST /api/uploads

Migrates the remaining 5 profile/space upload sites (CreateSpace, AccountPanel
avatar+banner, OverviewPanel icon+banner) to transferStore.startUpload with
tray:false. Space sites pass _instanceOrigin so uploads route to the space's
home instance.

Removes upload/uploadWithProgress from api.uploads (and their private
uploadFile/uploadFileWithProgress helpers); api.uploads.url is preserved for
GET-path URL building. Deletes the server-side POST /api/uploads handler and
the now-unused @fastify/multipart plugin registration. GET /api/uploads/:filename
remains intact.
This commit is contained in:
Jannis Braun
2026-05-02 16:46:29 +02:00
parent 727c51f659
commit 2be243336b
6 changed files with 28 additions and 230 deletions
@@ -3,8 +3,9 @@ import { Modal } from '../ui/Modal';
import { ImageCropModal } from '../ui/ImageCropModal';
import { useSpaceStore } from '../../stores/spaceStore';
import { useUIStore } from '../../stores/uiStore';
import { useTransferStore } from '../../stores/transferStore';
import { useNavigate } from 'react-router-dom';
import { api } from '../../api/client';
import { waitForTransferAttachment } from '../../utils/waitForTransfer';
import { AVATAR_COLORS } from '@backspace/shared';
import type { SpaceVisibility, AvatarColor } from '@backspace/shared';
import { SPACE_GRADIENT_MAP, getSpaceGradient } from '../../utils/gradients';
@@ -60,8 +61,9 @@ export function CreateSpaceModal() {
const file = new File([blob], 'icon.png', { type: 'image/png' });
setUploadingIcon(true);
try {
const attachment = await api.uploads.upload(file);
setIconFilename(attachment.filename);
const tid = await useTransferStore.getState().startUpload(file, { tray: false });
const { filename } = await waitForTransferAttachment(tid);
setIconFilename(filename);
} catch {
setError('Failed to upload icon');
setIconPreview(null);
@@ -6,6 +6,8 @@ import { Avatar } from '../../ui/Avatar';
import { ImageCropModal } from '../../ui/ImageCropModal';
import { DeleteAccountModal } from '../DeleteAccountModal';
import { api } from '../../../api/client';
import { useTransferStore } from '../../../stores/transferStore';
import { waitForTransferAttachment } from '../../../utils/waitForTransfer';
import { getAvatarGradient, adjustColor, mutedGradient, AVATAR_GRADIENT_MAP, BANNER_COLOR_PRESETS } from '../../../utils/gradients';
import { AVATAR_COLORS } from '@backspace/shared';
import type { User, UserStatus, AvatarColor } from '@backspace/shared';
@@ -140,8 +142,9 @@ export function AccountPanel() {
const file = new File([blob], 'avatar.webp', { type: blob.type || 'image/webp' });
setUploadingAvatar(true);
try {
const attachment = await api.uploads.upload(file);
setAvatarFilename(attachment.filename);
const tid = await useTransferStore.getState().startUpload(file, { tray: false });
const { filename } = await waitForTransferAttachment(tid);
setAvatarFilename(filename);
} catch {
setError('Failed to upload avatar');
setAvatarPreview(null);
@@ -159,8 +162,9 @@ export function AccountPanel() {
const file = new File([blob], 'banner.webp', { type: blob.type || 'image/webp' });
setUploadingBanner(true);
try {
const attachment = await api.uploads.upload(file);
setBannerFilename(attachment.filename);
const tid = await useTransferStore.getState().startUpload(file, { tray: false });
const { filename } = await waitForTransferAttachment(tid);
setBannerFilename(filename);
} catch {
setError('Failed to upload banner');
setBannerPreview(null);
@@ -8,7 +8,9 @@ import { useAuthStore } from '../../../stores/authStore';
import { useUIStore } from '../../../stores/uiStore';
import { useNavigate } from 'react-router-dom';
import { api } from '../../../api/client';
import { getApiForOrigin, getMyUserIdForOrigin } from '../../../stores/spaceStore';
import { useTransferStore } from '../../../stores/transferStore';
import { waitForTransferAttachment } from '../../../utils/waitForTransfer';
import { getMyUserIdForOrigin } from '../../../stores/spaceStore';
import { hasPermissionBit, PermissionBits } from '../../../utils/permissions';
interface OverviewPanelProps {
@@ -112,9 +114,12 @@ export function OverviewPanel({ spaceId }: OverviewPanelProps) {
const file = new File([blob], 'icon.webp', { type: blob.type || 'image/webp' });
setUploadingIcon(true);
try {
const spaceApi = getApiForOrigin(space._instanceOrigin);
const attachment = await spaceApi.uploads.upload(file);
setIconFilename(attachment.filename);
const tid = await useTransferStore.getState().startUpload(file, {
tray: false,
origin: space._instanceOrigin || undefined,
});
const { filename } = await waitForTransferAttachment(tid);
setIconFilename(filename);
} catch {
setSaveError('Failed to upload icon');
setIconPreview(null);
@@ -150,9 +155,12 @@ export function OverviewPanel({ spaceId }: OverviewPanelProps) {
const file = new File([blob], 'banner.webp', { type: blob.type || 'image/webp' });
setUploadingBanner(true);
try {
const spaceApi = getApiForOrigin(space._instanceOrigin);
const attachment = await spaceApi.uploads.upload(file);
setBannerFilename(attachment.filename);
const tid = await useTransferStore.getState().startUpload(file, {
tray: false,
origin: space._instanceOrigin || undefined,
});
const { filename } = await waitForTransferAttachment(tid);
setBannerFilename(filename);
} catch {
setSaveError('Failed to upload banner');
setBannerPreview(null);