Fix file sharing load speed and move error; misc updates
- Remove recursive directory size calculations (single Seafile API call per list) - Remove 'Used in this location' usage display - Fix move using v2 per-type endpoints instead of broken batch endpoint - Send entry type from frontend for correct move routing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
|
||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
||||
const currencyFormatter = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
});
|
||||
|
||||
export default function PayInvoice() {
|
||||
const { id } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const success = searchParams.get('success') === '1';
|
||||
const cancelled = searchParams.get('cancelled') === '1';
|
||||
|
||||
const [invoice, setInvoice] = useState(null);
|
||||
const [company, setCompany] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [paying, setPaying] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const totalLabel = currencyFormatter.format(Number(invoice?.total || 0));
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
try {
|
||||
const response = await fetch(`${supabaseUrl}/functions/v1/get-public-invoice`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
apikey: supabaseAnonKey,
|
||||
},
|
||||
body: JSON.stringify({ invoice_ref: id }),
|
||||
});
|
||||
|
||||
const body = await response.json();
|
||||
if (!response.ok || !body?.invoice) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setInvoice(body.invoice);
|
||||
setCompany(body.invoice.companies || null);
|
||||
} catch (err) {
|
||||
console.error('PayInvoice load failed:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
load();
|
||||
}, [id]);
|
||||
|
||||
const handlePay = async () => {
|
||||
setPaying(true);
|
||||
setError('');
|
||||
try {
|
||||
const response = await fetch(`${supabaseUrl}/functions/v1/create-checkout-session`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
apikey: supabaseAnonKey,
|
||||
},
|
||||
body: JSON.stringify({ invoice_ref: id }),
|
||||
});
|
||||
|
||||
const body = await response.json();
|
||||
if (!response.ok || !body?.url) throw new Error(body?.error || 'Could not create payment session.');
|
||||
window.location.href = body.url;
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
setPaying(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', background: '#f5f5f5', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
|
||||
<div style={{ width: '100%', maxWidth: 480 }}>
|
||||
{/* Logo */}
|
||||
<div style={{ textAlign: 'center', marginBottom: 32 }}>
|
||||
<img src="/fourge-logo.png" alt="Fourge Branding" style={{ height: 36, filter: 'invert(1)' }} />
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', color: '#666' }}>Loading...</div>
|
||||
) : !invoice ? (
|
||||
<div style={{ background: '#fff', color: '#141414', borderRadius: 12, padding: 32, textAlign: 'center', boxShadow: '0 2px 12px rgba(0,0,0,0.08)' }}>
|
||||
<div style={{ fontSize: 18, fontWeight: 700, marginBottom: 8 }}>Invoice not found</div>
|
||||
<div style={{ color: '#666' }}>This payment link may be invalid or expired.</div>
|
||||
</div>
|
||||
) : success || invoice.status === 'paid' ? (
|
||||
<div style={{ background: '#fff', color: '#141414', borderRadius: 12, padding: 32, textAlign: 'center', boxShadow: '0 2px 12px rgba(0,0,0,0.08)' }}>
|
||||
<div style={{ fontSize: 32, marginBottom: 12 }}>✓</div>
|
||||
<div style={{ fontSize: 20, fontWeight: 700, marginBottom: 8 }}>Payment received</div>
|
||||
<div style={{ color: '#666', marginBottom: 4 }}>{invoice.invoice_number}</div>
|
||||
<div style={{ fontSize: 24, fontWeight: 700, color: '#16a34a', marginTop: 16 }}>{totalLabel}</div>
|
||||
<div style={{ color: '#666', marginTop: 6, fontSize: 12, letterSpacing: '0.3px' }}>Charged in USD</div>
|
||||
<div style={{ color: '#666', marginTop: 8, fontSize: 13 }}>Thank you for your payment. We'll be in touch!</div>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ background: '#fff', color: '#141414', borderRadius: 12, padding: 32, boxShadow: '0 2px 12px rgba(0,0,0,0.08)' }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px', color: '#999', marginBottom: 4 }}>Invoice</div>
|
||||
<div style={{ fontSize: 22, fontWeight: 700, marginBottom: 4 }}>{invoice.invoice_number}</div>
|
||||
<div style={{ color: '#666', marginBottom: 24 }}>{invoice.bill_to || company?.name}</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '12px 0', borderTop: '1px solid #eee', borderBottom: '1px solid #eee', marginBottom: 24 }}>
|
||||
<div>
|
||||
<div style={{ fontSize: 12, color: '#999', marginBottom: 2 }}>Invoice Date</div>
|
||||
<div style={{ fontWeight: 600, color: '#141414' }}>{new Date(invoice.invoice_date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}</div>
|
||||
</div>
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<div style={{ fontSize: 12, color: '#999', marginBottom: 2 }}>Due Date</div>
|
||||
<div style={{ fontWeight: 600, color: '#141414' }}>{new Date(invoice.due_date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
|
||||
<div style={{ fontSize: 14, color: '#666' }}>Total Due</div>
|
||||
<div style={{ fontSize: 28, fontWeight: 700, color: '#141414' }}>{totalLabel}</div>
|
||||
</div>
|
||||
|
||||
{cancelled && (
|
||||
<div style={{ background: '#fef2f2', border: '1px solid #fecaca', borderRadius: 8, padding: '10px 14px', fontSize: 13, color: '#dc2626', marginBottom: 16 }}>
|
||||
Payment was cancelled. You can try again below.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div style={{ background: '#fef2f2', border: '1px solid #fecaca', borderRadius: 8, padding: '10px 14px', fontSize: 13, color: '#dc2626', marginBottom: 16 }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={handlePay}
|
||||
disabled={paying}
|
||||
style={{
|
||||
width: '100%', padding: '14px', borderRadius: 8, border: 'none',
|
||||
background: paying ? '#999' : '#141414', color: '#fff',
|
||||
fontSize: 16, fontWeight: 700, cursor: paying ? 'not-allowed' : 'pointer',
|
||||
}}
|
||||
>
|
||||
{paying ? 'Redirecting to payment...' : `Pay ${totalLabel}`}
|
||||
</button>
|
||||
|
||||
<div style={{ textAlign: 'center', marginTop: 16, fontSize: 12, color: '#999' }}>
|
||||
Secured by Stripe · Charged in USD · hello@fourgebranding.com
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user