Fix Stuffbox popup completion and preflight storage before file selection
Hop-State: A_06FP87Y83XVM00YY2RB0X2G Hop-Proposal: R_06FP87XN1TX5B0CW1ZFDEK8 Hop-Task: T_06FP86NZGXSMEX63FZDAS70 Hop-Attempt: AT_06FP86NZGYZEGPH937G3WK8
This commit is contained in:
+38
-13
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import AutoTextarea from '@/components/AutoTextarea';
|
||||
import { StorageSessionPrompt } from '@/components/StorageSessionPrompt';
|
||||
@@ -8,7 +8,7 @@ import { StorageConfigurationPrompt } from '@/components/StorageConfigurationPro
|
||||
import { useToast } from '@/lib/contexts/ToastContext';
|
||||
import { useAccentColor } from '@/lib/contexts/AccentColorContext';
|
||||
import { refreshStorageSession } from '@/lib/storage/client';
|
||||
import { MediaUploadError, uploadMediaFile } from '@/lib/stuffbox/browser-upload';
|
||||
import { getStorageProvider, MediaUploadError, uploadMediaFile } from '@/lib/stuffbox/browser-upload';
|
||||
|
||||
export default function AdminPage() {
|
||||
const { showToast } = useToast();
|
||||
@@ -30,6 +30,7 @@ export default function AdminPage() {
|
||||
});
|
||||
const [savingSettings, setSavingSettings] = useState(false);
|
||||
const [isUploadingBanner, setIsUploadingBanner] = useState(false);
|
||||
const [isCheckingBannerStorage, setIsCheckingBannerStorage] = useState(false);
|
||||
const [bannerUploadError, setBannerUploadError] = useState<string | null>(null);
|
||||
const [showBannerSessionPrompt, setShowBannerSessionPrompt] = useState(false);
|
||||
const [showBannerStorageConfiguration, setShowBannerStorageConfiguration] = useState(false);
|
||||
@@ -41,6 +42,7 @@ export default function AdminPage() {
|
||||
const [logoUploadError, setLogoUploadError] = useState<string | null>(null);
|
||||
const [isUploadingFavicon, setIsUploadingFavicon] = useState(false);
|
||||
const [faviconUploadError, setFaviconUploadError] = useState<string | null>(null);
|
||||
const bannerInputRef = useRef<HTMLInputElement>(null);
|
||||
useEffect(() => {
|
||||
fetch('/api/admin/me')
|
||||
.then((res) => res.json())
|
||||
@@ -145,6 +147,23 @@ export default function AdminPage() {
|
||||
await uploadBannerFile(file);
|
||||
};
|
||||
|
||||
const handleChooseBanner = async () => {
|
||||
setBannerUploadError(null);
|
||||
setIsCheckingBannerStorage(true);
|
||||
try {
|
||||
const provider = await getStorageProvider();
|
||||
if (!provider) {
|
||||
setShowBannerStorageConfiguration(true);
|
||||
return;
|
||||
}
|
||||
bannerInputRef.current?.click();
|
||||
} catch (error) {
|
||||
setBannerUploadError(error instanceof Error ? error.message : 'Unable to check media storage');
|
||||
} finally {
|
||||
setIsCheckingBannerStorage(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBannerSessionSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -418,16 +437,17 @@ export default function AdminPage() {
|
||||
<div>
|
||||
<label style={{ fontSize: '13px', fontWeight: 500, marginBottom: '4px', display: 'block' }}>Banner image</label>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<label className="btn btn-ghost btn-sm">
|
||||
{isUploadingBanner ? 'Uploading...' : 'Upload banner'}
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleBannerUpload}
|
||||
disabled={isUploadingBanner}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
</label>
|
||||
<button className="btn btn-ghost btn-sm" type="button" onClick={handleChooseBanner} disabled={isUploadingBanner || isCheckingBannerStorage}>
|
||||
{isUploadingBanner ? 'Uploading...' : isCheckingBannerStorage ? 'Checking storage…' : 'Upload banner'}
|
||||
</button>
|
||||
<input
|
||||
ref={bannerInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleBannerUpload}
|
||||
disabled={isUploadingBanner}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
{bannerUploadError && (
|
||||
<span style={{ fontSize: '12px', color: 'var(--danger)' }}>{bannerUploadError}</span>
|
||||
)}
|
||||
@@ -608,7 +628,12 @@ export default function AdminPage() {
|
||||
open={showBannerStorageConfiguration}
|
||||
onConfigured={async () => {
|
||||
setShowBannerStorageConfiguration(false);
|
||||
if (pendingBannerFile) await uploadBannerFile(pendingBannerFile, false);
|
||||
if (pendingBannerFile) {
|
||||
await uploadBannerFile(pendingBannerFile, false);
|
||||
return;
|
||||
}
|
||||
showToast('Stuffbox connected. Choose a banner to continue.', 'success');
|
||||
bannerInputRef.current?.click();
|
||||
}}
|
||||
onCancel={() => {
|
||||
setShowBannerStorageConfiguration(false);
|
||||
|
||||
@@ -3,20 +3,10 @@ import { requireAuth } from '@/lib/auth';
|
||||
import { exchangeAuthorizationCode, StuffboxApiError } from '@/lib/stuffbox/client';
|
||||
import { consumeStuffboxConnectionState } from '@/lib/stuffbox/connection-state';
|
||||
import { saveStuffboxTokens } from '@/lib/stuffbox/tokens';
|
||||
import { renderStuffboxPopupResponse } from '@/lib/stuffbox/popup-response';
|
||||
|
||||
function popupResponse(origin: string, success: boolean, message: string): NextResponse {
|
||||
const safeJson = (value: unknown) => JSON.stringify(value).replace(/</g, '\\u003c');
|
||||
const payload = safeJson({ type: 'synapsis:stuffbox', success, message });
|
||||
const targetOrigin = safeJson(origin);
|
||||
const fallback = success ? '/settings/storage?stuffbox=connected' : '/settings/storage?stuffbox=error';
|
||||
const html = `<!doctype html><html><head><meta charset="utf-8"><title>Stuffbox</title></head><body>
|
||||
<p>${success ? 'Stuffbox connected. You can close this window.' : 'Stuffbox could not be connected.'}</p>
|
||||
<script>
|
||||
if (window.opener) { window.opener.postMessage(${payload}, ${targetOrigin}); window.close(); }
|
||||
else { window.location.replace(${safeJson(fallback)}); }
|
||||
</script>
|
||||
</body></html>`;
|
||||
return new NextResponse(html, {
|
||||
return new NextResponse(renderStuffboxPopupResponse(origin, success, message), {
|
||||
status: success ? 200 : 400,
|
||||
headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' },
|
||||
});
|
||||
|
||||
+4
-4
@@ -717,6 +717,9 @@ a.btn-primary:visited {
|
||||
color: var(--foreground-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
border: none;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.compose-media-button:hover {
|
||||
@@ -725,10 +728,7 @@ a.btn-primary:visited {
|
||||
}
|
||||
|
||||
.compose-media-input {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.compose-media-error {
|
||||
|
||||
Reference in New Issue
Block a user