Sync dev changes into release-1.0

This commit is contained in:
2026-01-24 18:51:15 +13:00
parent 52e3d680f7
commit d2ff2b3e41
17 changed files with 2227 additions and 5 deletions

View File

@@ -10,12 +10,25 @@ type ProfileInfo = {
auth_provider: string
}
type ContactInfo = {
email?: string | null
discord?: string | null
telegram?: string | null
matrix?: string | null
}
export default function ProfilePage() {
const router = useRouter()
const [profile, setProfile] = useState<ProfileInfo | null>(null)
const [contact, setContact] = useState<ContactInfo>({})
const [currentPassword, setCurrentPassword] = useState('')
const [newPassword, setNewPassword] = useState('')
const [status, setStatus] = useState<string | null>(null)
const [contactStatus, setContactStatus] = useState<string | null>(null)
const [referrals, setReferrals] = useState<
{ code: string; uses_count?: number; max_uses?: number | null }[]
>([])
const [referralStatus, setReferralStatus] = useState<string | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
@@ -38,6 +51,23 @@ export default function ProfilePage() {
role: data?.role ?? 'user',
auth_provider: data?.auth_provider ?? 'local',
})
const contactResponse = await authFetch(`${baseUrl}/auth/contact`)
if (contactResponse.ok) {
const contactData = await contactResponse.json()
setContact({
email: contactData?.contact?.email ?? '',
discord: contactData?.contact?.discord ?? '',
telegram: contactData?.contact?.telegram ?? '',
matrix: contactData?.contact?.matrix ?? '',
})
}
const referralResponse = await authFetch(`${baseUrl}/auth/referrals`)
if (referralResponse.ok) {
const referralData = await referralResponse.json()
if (Array.isArray(referralData?.invites)) {
setReferrals(referralData.invites)
}
}
} catch (err) {
console.error(err)
setStatus('Could not load your profile.')
@@ -78,6 +108,55 @@ export default function ProfilePage() {
}
}
const submitContact = async (event: React.FormEvent) => {
event.preventDefault()
setContactStatus(null)
try {
const baseUrl = getApiBase()
const response = await authFetch(`${baseUrl}/auth/contact`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: contact.email,
discord: contact.discord,
telegram: contact.telegram,
matrix: contact.matrix,
}),
})
if (!response.ok) {
const text = await response.text()
throw new Error(text || 'Update failed')
}
setContactStatus('Contact details saved.')
} catch (err) {
console.error(err)
setContactStatus('Could not update contact details.')
}
}
const createReferral = async () => {
setReferralStatus(null)
try {
const baseUrl = getApiBase()
const response = await authFetch(`${baseUrl}/auth/referrals`, { method: 'POST' })
if (!response.ok) {
const text = await response.text()
throw new Error(text || 'Could not create referral invite')
}
const data = await response.json()
if (data?.code) {
setReferrals((current) => [
{ code: data.code, uses_count: 0, max_uses: 1 },
...current,
])
}
setReferralStatus('Referral invite created.')
} catch (err) {
console.error(err)
setReferralStatus('Could not create a referral invite.')
}
}
if (loading) {
return <main className="card">Loading profile...</main>
}
@@ -121,6 +200,82 @@ export default function ProfilePage() {
</div>
</form>
)}
<form onSubmit={submitContact} className="auth-form">
<h2>Contact details</h2>
<label>
Email address
<input
type="email"
value={contact.email || ''}
onChange={(event) => setContact((current) => ({ ...current, email: event.target.value }))}
autoComplete="email"
/>
</label>
<label>
Discord handle
<input
type="text"
value={contact.discord || ''}
onChange={(event) =>
setContact((current) => ({ ...current, discord: event.target.value }))
}
/>
</label>
<label>
Telegram ID
<input
type="text"
value={contact.telegram || ''}
onChange={(event) =>
setContact((current) => ({ ...current, telegram: event.target.value }))
}
/>
</label>
<label>
Matrix ID
<input
type="text"
value={contact.matrix || ''}
onChange={(event) =>
setContact((current) => ({ ...current, matrix: event.target.value }))
}
/>
</label>
{contactStatus && <div className="status-banner">{contactStatus}</div>}
<div className="auth-actions">
<button type="submit">Save contact details</button>
</div>
</form>
<section className="summary-card">
<h2>Referral invites</h2>
<p className="meta">
Share a referral invite with friends or family. Each invite has limited uses.
</p>
{referralStatus && <div className="status-banner">{referralStatus}</div>}
<div className="auth-actions">
<button type="button" onClick={createReferral}>
Create referral invite
</button>
</div>
{referrals.length === 0 ? (
<div className="meta">No referral invites yet.</div>
) : (
<div className="cache-table">
<div className="cache-row cache-head">
<span>Code</span>
<span>Uses</span>
</div>
{referrals.map((invite) => (
<div key={invite.code} className="cache-row">
<span>{invite.code}</span>
<span>
{invite.uses_count ?? 0}/{invite.max_uses ?? '∞'}
</span>
</div>
))}
</div>
)}
</section>
</main>
)
}