Save workspace changes
This commit is contained in:
@@ -168,6 +168,8 @@ export default function ProfileEdit() {
|
||||
usernameCooldownDays = 30,
|
||||
usernameCooldownRemainingDays = 0,
|
||||
usernameCooldownActive = false,
|
||||
emailLoginUpgradeRequired: initialEmailLoginUpgradeRequired = false,
|
||||
forcedSection = null,
|
||||
captcha: initialCaptcha = {},
|
||||
flash = {},
|
||||
} = props
|
||||
@@ -178,7 +180,8 @@ export default function ProfileEdit() {
|
||||
birthYear ? String(birthYear) : '',
|
||||
)
|
||||
|
||||
const [activeSection, setActiveSection] = useState('profile')
|
||||
const [emailLoginUpgradeRequired, setEmailLoginUpgradeRequired] = useState(!!initialEmailLoginUpgradeRequired)
|
||||
const [activeSection, setActiveSection] = useState(forcedSection || 'profile')
|
||||
|
||||
const [profileForm, setProfileForm] = useState({
|
||||
display_name: user?.name || '',
|
||||
@@ -271,6 +274,14 @@ export default function ProfileEdit() {
|
||||
const [deleteError, setDeleteError] = useState('')
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
|
||||
const settingsSections = useMemo(() => {
|
||||
if (!emailLoginUpgradeRequired) {
|
||||
return SETTINGS_SECTIONS
|
||||
}
|
||||
|
||||
return SETTINGS_SECTIONS.filter((section) => section.key === 'account')
|
||||
}, [emailLoginUpgradeRequired])
|
||||
|
||||
const initialRef = useRef({
|
||||
profileForm,
|
||||
accountForm,
|
||||
@@ -316,12 +327,25 @@ export default function ProfileEdit() {
|
||||
return () => window.removeEventListener('beforeunload', beforeUnload)
|
||||
}, [hasUnsavedChanges])
|
||||
|
||||
useEffect(() => {
|
||||
if (!emailLoginUpgradeRequired) {
|
||||
return
|
||||
}
|
||||
|
||||
setActiveSection('account')
|
||||
}, [emailLoginUpgradeRequired])
|
||||
|
||||
useEffect(() => {
|
||||
if (usernameCooldownActive) {
|
||||
setUsernameAvailability({ status: 'idle', message: '' })
|
||||
return
|
||||
}
|
||||
|
||||
if (emailLoginUpgradeRequired) {
|
||||
setUsernameAvailability({ status: 'idle', message: '' })
|
||||
return
|
||||
}
|
||||
|
||||
const candidate = String(accountForm.username || '').trim().toLowerCase()
|
||||
const current = String(initialRef.current.accountForm.username || '').trim().toLowerCase()
|
||||
|
||||
@@ -370,7 +394,7 @@ export default function ProfileEdit() {
|
||||
controller.abort()
|
||||
window.clearTimeout(timeout)
|
||||
}
|
||||
}, [accountForm.username, usernameCooldownActive])
|
||||
}, [accountForm.username, usernameCooldownActive, emailLoginUpgradeRequired])
|
||||
|
||||
const openEmailChangeModal = () => {
|
||||
setShowEmailChangeModal(true)
|
||||
@@ -503,6 +527,10 @@ export default function ProfileEdit() {
|
||||
}
|
||||
|
||||
const switchSection = (nextSection) => {
|
||||
if (emailLoginUpgradeRequired && nextSection !== 'account') {
|
||||
return
|
||||
}
|
||||
|
||||
if (activeSection === nextSection) return
|
||||
if (dirtyMap[activeSection]) {
|
||||
const shouldContinue = window.confirm('You have unsaved changes in this section. Leave without saving?')
|
||||
@@ -593,6 +621,10 @@ export default function ProfileEdit() {
|
||||
const saveAccountSection = async (event) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (emailLoginUpgradeRequired) {
|
||||
return
|
||||
}
|
||||
|
||||
if (usernameCooldownActive && accountForm.username !== initialRef.current.accountForm.username) {
|
||||
updateSectionErrors('account', {
|
||||
username: [`Username can be changed again in ${usernameCooldownRemainingDays} days.`],
|
||||
@@ -701,6 +733,7 @@ export default function ProfileEdit() {
|
||||
const nextEmail = payload.email || emailChangeForm.new_email
|
||||
setAccountForm((prev) => ({ ...prev, email: nextEmail }))
|
||||
initialRef.current.accountForm = { ...initialRef.current.accountForm, email: nextEmail }
|
||||
setEmailLoginUpgradeRequired(false)
|
||||
setShowEmailChangeModal(false)
|
||||
setEmailChangeStep('request')
|
||||
setEmailChangeForm({ new_email: '', code: '' })
|
||||
@@ -910,7 +943,7 @@ export default function ProfileEdit() {
|
||||
return (
|
||||
<SettingsLayout
|
||||
title="Settings"
|
||||
sections={SETTINGS_SECTIONS}
|
||||
sections={settingsSections}
|
||||
activeSection={activeSection}
|
||||
onSectionChange={switchSection}
|
||||
dirtyMap={dirtyMap}
|
||||
@@ -1103,10 +1136,10 @@ export default function ProfileEdit() {
|
||||
{activeSection === 'account' ? (
|
||||
<form className="space-y-4" onSubmit={saveAccountSection}>
|
||||
<SectionCard
|
||||
title="Account"
|
||||
title={emailLoginUpgradeRequired ? 'Finish Email Upgrade' : 'Account'}
|
||||
icon="fa-solid fa-id-badge"
|
||||
description="Update your core account identity details."
|
||||
actionSlot={
|
||||
description={emailLoginUpgradeRequired ? 'Add and verify a real email address. Once completed, email becomes your only login identifier.' : 'Update your core account identity details.'}
|
||||
actionSlot={!emailLoginUpgradeRequired ? (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="accent"
|
||||
@@ -1116,11 +1149,25 @@ export default function ProfileEdit() {
|
||||
>
|
||||
Save Username
|
||||
</Button>
|
||||
}
|
||||
) : null}
|
||||
>
|
||||
<ErrorMessage text={errorsBySection.account._general?.[0]} className="mb-4" />
|
||||
<SuccessMessage text={sectionSaved} className="mb-4" />
|
||||
|
||||
{emailLoginUpgradeRequired ? (
|
||||
<div className="mb-4 rounded-2xl border border-amber-400/30 bg-amber-500/10 p-4 text-sm text-amber-100">
|
||||
<div className="flex items-start gap-3">
|
||||
<i className="fa-solid fa-envelope-circle-check mt-0.5 shrink-0 text-amber-300" />
|
||||
<div>
|
||||
<p className="font-semibold text-amber-200">Email upgrade required</p>
|
||||
<p className="mt-1 text-amber-100/90">
|
||||
This account was migrated without a real email address. Add a valid email, enter the verification code, and future sign-ins will use email only.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<TextInput
|
||||
label="Username"
|
||||
@@ -1129,23 +1176,26 @@ export default function ProfileEdit() {
|
||||
setAccountForm((prev) => ({ ...prev, username: e.target.value }))
|
||||
clearSectionStatus('account')
|
||||
}}
|
||||
disabled={usernameCooldownActive}
|
||||
disabled={emailLoginUpgradeRequired || usernameCooldownActive}
|
||||
error={errorsBySection.account.username?.[0]}
|
||||
hint={usernameCooldownActive ? `Username can be changed again in ${usernameCooldownRemainingDays} days.` : 'Allowed: letters, numbers, underscores (3-20).'}
|
||||
hint={emailLoginUpgradeRequired ? 'Username login is temporary. Complete the email upgrade to switch this account to email-only sign-in.' : usernameCooldownActive ? `Username can be changed again in ${usernameCooldownRemainingDays} days.` : 'Allowed: letters, numbers, underscores (3-20).'}
|
||||
required
|
||||
/>
|
||||
<div className="rounded-xl border border-white/10 bg-black/20 p-4">
|
||||
<p className="text-xs uppercase tracking-wide text-slate-400">Current Email</p>
|
||||
<p className="text-xs uppercase tracking-wide text-slate-400">{emailLoginUpgradeRequired ? 'Primary Login Email' : 'Current Email'}</p>
|
||||
<p className="mt-1 text-sm font-medium text-white">{accountForm.email || 'No email set'}</p>
|
||||
{emailLoginUpgradeRequired ? (
|
||||
<p className="mt-2 text-xs text-slate-400">The current value is temporary. Replace it with a real email address to finish the upgrade.</p>
|
||||
) : null}
|
||||
<div className="mt-3">
|
||||
<Button type="button" variant="secondary" size="sm" onClick={openEmailChangeModal}>
|
||||
Change Email
|
||||
<Button type="button" variant={emailLoginUpgradeRequired ? 'accent' : 'secondary'} size="sm" onClick={openEmailChangeModal}>
|
||||
{emailLoginUpgradeRequired ? 'Add Primary Email' : 'Change Email'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{usernameAvailability.status !== 'idle' ? (
|
||||
{!emailLoginUpgradeRequired && usernameAvailability.status !== 'idle' ? (
|
||||
<p
|
||||
className={`mt-4 flex items-center gap-2 rounded-xl border px-3 py-2 text-xs ${
|
||||
usernameAvailability.status === 'available'
|
||||
@@ -1164,10 +1214,12 @@ export default function ProfileEdit() {
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<p className="mt-4 flex items-center gap-2 rounded-xl border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-xs text-slate-400">
|
||||
<i className="fa-solid fa-clock shrink-0 text-slate-500" />
|
||||
You can change your username once every {usernameCooldownDays} days.
|
||||
</p>
|
||||
{!emailLoginUpgradeRequired ? (
|
||||
<p className="mt-4 flex items-center gap-2 rounded-xl border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-xs text-slate-400">
|
||||
<i className="fa-solid fa-clock shrink-0 text-slate-500" />
|
||||
You can change your username once every {usernameCooldownDays} days.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{renderCaptchaChallenge('account')}
|
||||
</SectionCard>
|
||||
@@ -1518,13 +1570,16 @@ export default function ProfileEdit() {
|
||||
<Modal
|
||||
open={showEmailChangeModal}
|
||||
onClose={closeEmailChangeModal}
|
||||
title="Change Email"
|
||||
title={emailLoginUpgradeRequired ? 'Finish Email Upgrade' : 'Change Email'}
|
||||
size="sm"
|
||||
closeOnBackdrop={!emailLoginUpgradeRequired}
|
||||
footer={
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button type="button" variant="ghost" size="sm" onClick={closeEmailChangeModal} disabled={emailChangeLoading}>
|
||||
Cancel
|
||||
</Button>
|
||||
{!emailLoginUpgradeRequired ? (
|
||||
<Button type="button" variant="ghost" size="sm" onClick={closeEmailChangeModal} disabled={emailChangeLoading}>
|
||||
Cancel
|
||||
</Button>
|
||||
) : null}
|
||||
{emailChangeStep === 'request' ? (
|
||||
<Button
|
||||
type="button"
|
||||
@@ -1550,6 +1605,12 @@ export default function ProfileEdit() {
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{emailLoginUpgradeRequired ? (
|
||||
<div className="rounded-lg border border-sky-400/30 bg-sky-500/10 px-3 py-2 text-sm text-sky-200">
|
||||
Add the email address you want to use for future sign-ins, then enter the verification code we send there.
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{emailChangeError ? (
|
||||
<div className="rounded-lg border border-red-400/30 bg-red-500/10 px-3 py-2 text-sm text-red-300">
|
||||
{emailChangeError}
|
||||
|
||||
Reference in New Issue
Block a user