Files
Synapsis/src/app/settings/storage/page.tsx
T
cyph3rasi 9dc416b5f5 Make Stuffbox the exclusive media provider and simplify encrypted messaging copy
Hop-State: A_06FPCP16ZEAQDRH1JF5JRZG
Hop-Proposal: R_06FPCP0618KWV4X8SK82048
Hop-Task: T_06FPCM7MFZHQDCR4JBRHMG0
Hop-Attempt: AT_06FPCM7MFW92T7J4T312R08
2026-07-15 08:14:41 -07:00

102 lines
4.9 KiB
TypeScript

'use client';
import { useCallback, useEffect, useState } from 'react';
import Link from 'next/link';
import { Box, ExternalLink } from 'lucide-react';
import { ArrowLeftIcon } from '@/components/Icons';
import { StorageConfigurationPrompt } from '@/components/StorageConfigurationPrompt';
interface StorageStatus {
provider: 'stuffbox' | null;
stuffboxAvailable: boolean;
stuffboxBaseUrl: string | null;
}
export default function StorageSettingsPage() {
const [status, setStatus] = useState<StorageStatus | null>(null);
const [error, setError] = useState('');
const [isDisconnecting, setIsDisconnecting] = useState(false);
const loadStatus = useCallback(async () => {
try {
const response = await fetch('/api/storage/configuration', { cache: 'no-store' });
const data = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(data.error || 'Unable to load storage settings');
setStatus(data);
setError('');
} catch (loadError) {
setError(loadError instanceof Error ? loadError.message : 'Unable to load storage settings');
}
}, []);
useEffect(() => { void loadStatus(); }, [loadStatus]);
const disconnectStuffbox = async () => {
if (!window.confirm('Disconnect Stuffbox from this Synapsis account? Existing media links will keep working.')) return;
setIsDisconnecting(true);
setError('');
try {
const response = await fetch('/api/storage/stuffbox/disconnect', { method: 'POST' });
const data = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(data.error || 'Unable to disconnect Stuffbox');
await loadStatus();
} catch (disconnectError) {
setError(disconnectError instanceof Error ? disconnectError.message : 'Unable to disconnect Stuffbox');
} finally {
setIsDisconnecting(false);
}
};
return (
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '24px 16px 64px' }}>
<header style={{ display: 'flex', alignItems: 'center', gap: '16px', marginBottom: '32px' }}>
<Link href="/settings" style={{ color: 'var(--foreground)' }} aria-label="Back to settings">
<ArrowLeftIcon />
</Link>
<div>
<h1 style={{ fontSize: '24px', fontWeight: 700 }}>Media Storage</h1>
<p style={{ color: 'var(--foreground-tertiary)', fontSize: '14px' }}>
Manage where your account stores uploaded media
</p>
</div>
</header>
<p style={{ color: 'var(--foreground-secondary)', lineHeight: 1.5, marginBottom: '20px' }}>
Storage belongs to your account rather than this node. That keeps your media available if you move your account elsewhere.
</p>
<div className="card" style={{ padding: '20px', marginBottom: '16px' }}>
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '12px' }}>
<Box size={22} />
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 600 }}>{!status ? 'Loading…' : status.provider === 'stuffbox' ? 'Stuffbox.xyz connected' : 'Stuffbox not connected'}</div>
</div>
</div>
{status?.provider === 'stuffbox' && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', marginTop: '18px' }}>
<a className="btn btn-primary" href="https://stuffbox.xyz" target="_blank" rel="noopener noreferrer">
Manage Storage <ExternalLink size={15} />
</a>
<button className="btn btn-ghost" type="button" onClick={disconnectStuffbox} disabled={isDisconnecting}>
{isDisconnecting ? 'Disconnecting…' : 'Disconnect Stuffbox'}
</button>
</div>
)}
</div>
{status?.provider !== 'stuffbox' && (
<div className="card" style={{ padding: '20px' }}>
<h2 style={{ fontSize: '18px', fontWeight: 600, marginBottom: '8px' }}>
Connect Stuffbox
</h2>
<p style={{ color: 'var(--foreground-secondary)', fontSize: '14px', lineHeight: 1.5, marginBottom: '20px' }}>
Connect the official Synapsis storage partner to upload portable media from your account.
</p>
<StorageConfigurationPrompt open onConfigured={loadStatus} onCancel={() => {}} variant="inline" />
</div>
)}
{error && <p style={{ color: 'var(--error)', fontSize: '14px', marginTop: '14px' }}>{error}</p>}
</div>
);
}