본문으로 건너뛰기

앨범 사진 선택 UI 패턴

앨범을 열고 → 사진을 Base64로 받아 → 썸네일 그리드로 표시하고 → 선택한 사진을 서버에 올리는 흐름.

사진 선택 후 그리드 표시

import { fetchAlbumPhotos } from '@apps-in-toss/web-framework';
import { useState } from 'react';

interface Photo {
id: string;
dataUri: string;
}

export function PhotoPicker() {
const [photos, setPhotos] = useState<Photo[]>([]);
const [selected, setSelected] = useState<Set<string>>(new Set());
const [message, setMessage] = useState('');

async function openAlbum() {
setMessage('');
const status = await fetchAlbumPhotos.getPermission();
if (status === 'denied') {
setMessage('설정에서 사진 접근 권한을 허용해 주세요.');
return;
}
if (status === 'notDetermined') {
await fetchAlbumPhotos.openPermissionDialog();
}

try {
const result = await fetchAlbumPhotos({ maxCount: 9, maxWidth: 360, base64: true });
setPhotos(result);
setSelected(new Set());
} catch {
setMessage('앨범을 불러오지 못했어요.');
}
}

function toggle(id: string) {
setSelected((prev) => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
}

return (
<div>
<button type="button" onClick={openAlbum}>앨범 열기</button>
{message && <p role="status">{message}</p>}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }}>
{photos.map((photo) => (
<div
key={photo.id}
onClick={() => toggle(photo.id)}
style={{ outline: selected.has(photo.id) ? '3px solid #3182F6' : 'none', cursor: 'pointer' }}
>
<img
src={`data:image/jpeg;base64,${photo.dataUri}`}
alt=""
style={{ width: '100%', aspectRatio: '1', objectFit: 'cover', display: 'block' }}
/>
</div>
))}
</div>
</div>
);
}

base64: true로 받은 dataUridata:image/jpeg;base64, prefix를 붙여야 <img src>에서 동작한다.

선택 후 서버 업로드

async function uploadSelected(photos: Photo[], selected: Set<string>) {
const targets = photos.filter((p) => selected.has(p.id));
const form = new FormData();

for (const photo of targets) {
// Base64 → Blob 변환
const res = await fetch(`data:image/jpeg;base64,${photo.dataUri}`);
const blob = await res.blob();
form.append('photos', blob, `${photo.id}.jpg`);
}

await fetch('/api/upload', { method: 'POST', body: form });
}

fetch('data:...')로 Base64를 Blob으로 바꿔 FormData에 붙이면 별도 디코딩 라이브러리 없이 처리할 수 있다.

관련 API

관련 가이드