Defer user-owned S3 setup until first media upload

Hop-State: A_06FP6DP9ENXP2WBSR2YPNA8
Hop-Proposal: R_06FP6DM6S5P96EFX90PH2F8
Hop-Task: T_06FP6C52Z3JHQBQ5XNNHZ3R
Hop-Attempt: AT_06FP6C52Z14V6CZJQTJN4PR
This commit is contained in:
2026-07-14 17:39:22 -07:00
committed by Hop
parent 0ecb622565
commit 752298a405
10 changed files with 304 additions and 274 deletions
+10 -174
View File
@@ -38,13 +38,6 @@ export function AuthScreen({ modal = false, onClose, onSuccess }: AuthScreenProp
const [confirmPassword, setConfirmPassword] = useState('');
const [handle, setHandle] = useState('');
const [displayName, setDisplayName] = useState('');
const [storageProvider, setStorageProvider] = useState('r2');
const [storageEndpoint, setStorageEndpoint] = useState('');
const [storagePublicBaseUrl, setStoragePublicBaseUrl] = useState('');
const [storageRegion, setStorageRegion] = useState('auto');
const [storageBucket, setStorageBucket] = useState('');
const [storageAccessKey, setStorageAccessKey] = useState('');
const [storageSecretKey, setStorageSecretKey] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const [nodeInfoLoaded, setNodeInfoLoaded] = useState(false);
@@ -262,13 +255,6 @@ export function AuthScreen({ modal = false, onClose, onSuccess }: AuthScreenProp
password,
handle,
displayName,
storageProvider,
storageEndpoint: storageEndpoint || null,
storagePublicBaseUrl: storagePublicBaseUrl || null,
storageRegion,
storageBucket,
storageAccessKey,
storageSecretKey,
...(nodeInfo.turnstileSiteKey ? { turnstileToken } : {})
};
@@ -565,8 +551,7 @@ export function AuthScreen({ modal = false, onClose, onSuccess }: AuthScreenProp
</div>
</div>
{/* Row 3: Email | Storage Provider */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
<div style={{ marginBottom: '16px' }}>
<div>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
Email
@@ -580,159 +565,11 @@ export function AuthScreen({ modal = false, onClose, onSuccess }: AuthScreenProp
required
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
Storage Provider
</label>
<select
className="input"
value={storageProvider}
onChange={(e) => setStorageProvider(e.target.value)}
style={{ cursor: 'pointer' }}
>
<option value="r2">Cloudflare R2 (10GB free)</option>
<option value="b2">Backblaze B2 (10GB free)</option>
<option value="wasabi">Wasabi</option>
<option value="contabo">Contabo S3</option>
<option value="s3">AWS S3</option>
</select>
</div>
</div>
{/* Storage Explainer Section */}
<div style={{
marginBottom: '20px',
padding: '16px',
background: 'var(--background-secondary)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
}}>
<div style={{ fontSize: '14px', fontWeight: 600, marginBottom: '12px', color: 'var(--foreground)' }}>
🗄 Why do I need to provide storage?
</div>
<div style={{ fontSize: '13px', color: 'var(--foreground-secondary)', lineHeight: 1.5, marginBottom: '12px' }}>
Synapsis is <strong>decentralized</strong> unlike other platforms, we don't store your photos, videos, or files on our servers.
Instead, you connect your own S3-compatible storage bucket (like Cloudflare R2 or Backblaze B2) where your media lives.
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '12px' }}>
<div style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>
<strong style={{ color: 'var(--success)' }}>✓ You own your data</strong><br/>
Your files stay in your bucket, not ours
</div>
<div style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>
<strong style={{ color: 'var(--success)' }}>✓ Portable</strong><br/>
Take your media with you if you leave
</div>
<div style={{ fontSize: '12px', color: 'var(--foreground-tertiary)' }}>
<strong style={{ color: 'var(--success)' }}>✓ Free options</strong><br/>
R2 and B2 offer 10GB free
</div>
</div>
</div>
{/* Endpoint URL - only show for providers that need it (R2, B2, Contabo) */}
{(storageProvider === 'r2' || storageProvider === 'b2' || storageProvider === 'contabo') && (
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
Endpoint URL
</label>
<input
type="text"
className="input"
value={storageEndpoint}
onChange={(e) => setStorageEndpoint(e.target.value)}
placeholder={storageProvider === 'contabo' ? 'https://s3.eu2.contabo.com' : 'https://<account>.r2.cloudflarestorage.com'}
required
/>
<div style={{ fontSize: '11px', color: 'var(--foreground-tertiary)', marginTop: '4px' }}>
{storageProvider === 'contabo' ? 'S3 API endpoint from Contabo dashboard (e.g., https://s3.eu2.contabo.com)' :
'S3-compatible endpoint URL for uploads.'}
</div>
</div>
)}
{/* Public Base URL - only show for providers that need it (R2, B2, Contabo) */}
{(storageProvider === 'r2' || storageProvider === 'b2' || storageProvider === 'contabo') && (
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
Public Base URL
</label>
<input
type="text"
className="input"
value={storagePublicBaseUrl}
onChange={(e) => setStoragePublicBaseUrl(e.target.value)}
placeholder={storageProvider === 'contabo' ? 'https://usc1.contabostorage.com/d5bec82ae49b444d8314dcb13654dd1d:your-bucket' : 'https://pub-xxx.r2.dev'}
required
/>
<div style={{ fontSize: '11px', color: 'var(--foreground-tertiary)', marginTop: '4px' }}>
{storageProvider === 'contabo' ? 'Public URL from Contabo (format: https://region.contabostorage.com/account-id:bucket)' :
storageProvider === 'r2' ? 'Public bucket URL from R2 dashboard (e.g., https://pub-xxx.r2.dev)' :
storageProvider === 'b2' ? 'Public bucket URL from B2 dashboard (e.g., https://f000.backblazeb2.com/file/bucket)' :
'Public URL where files are accessible (without trailing slash)'}
</div>
</div>
)}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
<div>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
Region
</label>
<input
type="text"
className="input"
value={storageRegion}
onChange={(e) => setStorageRegion(e.target.value)}
placeholder="auto"
required
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
Bucket Name
</label>
<input
type="text"
className="input"
value={storageBucket}
onChange={(e) => setStorageBucket(e.target.value)}
placeholder="my-synapsis-bucket"
required
/>
</div>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
Access Key ID
</label>
<input
type="password"
className="input"
value={storageAccessKey}
onChange={(e) => setStorageAccessKey(e.target.value)}
placeholder="AKIA..."
required
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>
Secret Access Key
</label>
<input
type="password"
className="input"
value={storageSecretKey}
onChange={(e) => setStorageSecretKey(e.target.value)}
placeholder="••••••••"
required
/>
<div style={{ fontSize: '12px', color: 'var(--foreground-tertiary)', marginTop: '4px' }}>
Get free S3 storage at <a href="https://dash.cloudflare.com/sign-up/r2" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)' }}>Cloudflare R2</a> (10GB free) or <a href="https://www.backblaze.com/b2/sign-up.html" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)' }}>Backblaze B2</a>
</div>
</div>
<p style={{ fontSize: '12px', color: 'var(--foreground-tertiary)', marginBottom: '16px' }}>
You can connect your own S3-compatible storage when you first upload media.
</p>
</>
)}
@@ -812,11 +649,6 @@ export function AuthScreen({ modal = false, onClose, onSuccess }: AuthScreenProp
!password || password.length < 8 ||
!confirmPassword ||
password !== confirmPassword ||
!storageProvider ||
!storageRegion ||
!storageBucket ||
!storageAccessKey ||
!storageSecretKey ||
(nodeInfo.isNsfw && !ageVerified)
))}
>
@@ -882,8 +714,12 @@ export function AuthScreen({ modal = false, onClose, onSuccess }: AuthScreenProp
Browse
</span>
</label>
</div>
</div>
</div>
</div>
<p style={{ fontSize: '12px', color: 'var(--foreground-tertiary)', marginBottom: '16px' }}>
You can connect your own S3-compatible storage when you first upload media.
</p>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', fontWeight: 500 }}>