This commit is contained in:
onysd 2026-08-05 22:45:12 +03:00
parent 8aad71643f
commit 7d0daef923
8 changed files with 216 additions and 151 deletions

View file

@ -149,151 +149,169 @@ export function AccountDetailPage({ id, navigate }: { id: number; navigate: Navi
<div className="action-groups">
<section className="section-block">
<SectionHead title={"Freeze & Restriction"} text={"Blocks sign-in and marks the account for appeal review."} />
<div className="attr-block">
<label className="duration-field">
<span>{"Appeal deadline"}</span>
<input
aria-label={"Freeze appeal deadline"}
value={freezeUntil}
onChange={(event) => setFreezeUntil(event.target.value)}
type="datetime-local"
/>
</label>
<label className="duration-field">
<span>{"Appeal URL"}</span>
<input
aria-label={"Freeze appeal URL"}
value={freezeAppealURL}
onChange={(event) => setFreezeAppealURL(event.target.value)}
type="url"
placeholder="https://..."
/>
</label>
<ActionButton
label={account.Frozen ? "Update freeze" : "Freeze account"}
icon={<CircleAlert size={15} />}
tone="danger"
path="/api/actions/set-frozen"
payload={() => ({
user_id: account.ID,
frozen: true,
freeze_until: new Date(freezeUntil).toISOString(),
freeze_appeal_url: freezeAppealURL.trim()
})}
onDone={load}
/>
{account.Frozen && (
<div className="card-body">
<div className="attr-block">
<label className="duration-field">
<span>{"Appeal deadline"}</span>
<input
aria-label={"Freeze appeal deadline"}
value={freezeUntil}
onChange={(event) => setFreezeUntil(event.target.value)}
type="datetime-local"
/>
</label>
<label className="duration-field">
<span>{"Appeal URL"}</span>
<input
aria-label={"Freeze appeal URL"}
value={freezeAppealURL}
onChange={(event) => setFreezeAppealURL(event.target.value)}
type="url"
placeholder="https://..."
/>
</label>
<ActionButton
label={"Unfreeze account"}
label={account.Frozen ? "Update freeze" : "Freeze account"}
icon={<CircleAlert size={15} />}
tone="danger"
path="/api/actions/set-frozen"
payload={() => ({ user_id: account.ID, frozen: false })}
payload={() => ({
user_id: account.ID,
frozen: true,
freeze_until: new Date(freezeUntil).toISOString(),
freeze_appeal_url: freezeAppealURL.trim()
})}
onDone={load}
/>
)}
{account.Frozen && (
<ActionButton
label={"Unfreeze account"}
icon={<CircleAlert size={15} />}
path="/api/actions/set-frozen"
payload={() => ({ user_id: account.ID, frozen: false })}
onDone={load}
/>
)}
</div>
</div>
</section>
<section className="section-block">
<SectionHead title={"Premium & Stars"} />
<div className="attr-block">
<label className="duration-field">
<span>{"Premium duration (months)"}</span>
<input
aria-label={"Set premium duration in months"}
value={months}
onChange={(event) => setMonths(event.target.value)}
type="number"
min="1"
max="120"
/>
</label>
<div className="action-stack">
<div className="card-body">
<div className="attr-block">
<label className="duration-field">
<span>{"Premium duration (months)"}</span>
<input
aria-label={"Set premium duration in months"}
value={months}
onChange={(event) => setMonths(event.target.value)}
type="number"
min="1"
max="120"
/>
</label>
<div className="action-stack">
<ActionButton
label={"Set premium"}
icon={<Sparkles size={15} />}
tone="warn"
path="/api/actions/grant-premium"
payload={() => ({ user_id: account.ID, months: toInt(months) })}
onDone={load}
/>
<ActionButton
label={"Clear premium"}
icon={<Sparkles size={15} />}
tone="warn"
path="/api/actions/grant-premium"
payload={() => ({ user_id: account.ID, months: 0 })}
onDone={load}
/>
</div>
</div>
<div className="attr-block">
<label className="duration-field">
<span>{"Stars to grant"}</span>
<input
aria-label={"Set Stars amount to grant"}
value={starsAmount}
onChange={(event) => setStarsAmount(event.target.value)}
type="number"
min="1"
max="1000000000"
/>
</label>
<ActionButton
label={"Set premium"}
icon={<Sparkles size={15} />}
label={"Grant Stars"}
icon={<Star size={15} />}
tone="warn"
path="/api/actions/grant-premium"
payload={() => ({ user_id: account.ID, months: toInt(months) })}
onDone={load}
/>
<ActionButton
label={"Clear premium"}
icon={<Sparkles size={15} />}
tone="warn"
path="/api/actions/grant-premium"
payload={() => ({ user_id: account.ID, months: 0 })}
path="/api/actions/grant-stars"
payload={() => ({ user_id: account.ID, amount: toInt(starsAmount) })}
onDone={load}
/>
</div>
</div>
<div className="attr-block">
<label className="duration-field">
<span>{"Stars to grant"}</span>
<input
aria-label={"Set Stars amount to grant"}
value={starsAmount}
onChange={(event) => setStarsAmount(event.target.value)}
type="number"
min="1"
max="1000000000"
/>
</label>
<ActionButton
label={"Grant Stars"}
icon={<Star size={15} />}
tone="warn"
path="/api/actions/grant-stars"
payload={() => ({ user_id: account.ID, amount: toInt(starsAmount) })}
onDone={load}
/>
</div>
</section>
<section className="section-block">
<SectionHead title={"Verification & Moderation Flags"} />
<div className="action-stack">
<ActionButton
label={detail.Verified ? "Clear verified" : "Set verified"}
icon={<BadgeCheck size={15} />}
tone="warn"
path="/api/actions/set-verified"
payload={() => ({ user_id: account.ID, verified: !detail.Verified })}
onDone={load}
/>
<ScamFakeActions idKey="user_id" id={account.ID} path="/api/actions/set-account-flags" scam={detail.Scam} fake={detail.Fake} onDone={load} />
<SupportAction id={account.ID} support={detail.Support} onDone={load} />
<div className="card-body">
<div className="action-stack">
<ActionButton
label={detail.Verified ? "Clear verified" : "Set verified"}
icon={<BadgeCheck size={15} />}
tone="warn"
path="/api/actions/set-verified"
payload={() => ({ user_id: account.ID, verified: !detail.Verified })}
onDone={load}
/>
<ScamFakeActions idKey="user_id" id={account.ID} path="/api/actions/set-account-flags" scam={detail.Scam} fake={detail.Fake} onDone={load} />
<SupportAction id={account.ID} support={detail.Support} onDone={load} />
</div>
</div>
</section>
<section className="section-block">
<SectionHead title={"Username"} />
<UsernameAction idKey="user_id" id={account.ID} path="/api/actions/set-account-username" current={account.Username} onDone={load} />
<div className="card-body">
<UsernameAction idKey="user_id" id={account.ID} path="/api/actions/set-account-username" current={account.Username} onDone={load} />
</div>
</section>
<section className="section-block">
<SectionHead title={"Name"} />
<ProfileNameAction id={account.ID} path="/api/actions/set-account-profile" currentFirstName={account.FirstName} currentLastName={account.LastName} onDone={load} />
<div className="card-body">
<ProfileNameAction id={account.ID} path="/api/actions/set-account-profile" currentFirstName={account.FirstName} currentLastName={account.LastName} onDone={load} />
</div>
</section>
<section className="section-block">
<SectionHead title={"Phone Number"} />
<PhoneAction id={account.ID} path="/api/actions/set-account-phone" current={account.Phone} onDone={load} />
<div className="card-body">
<PhoneAction id={account.ID} path="/api/actions/set-account-phone" current={account.Phone} onDone={load} />
</div>
</section>
<section className="section-block">
<SectionHead title={"Login Email"} text={"The email used for sign-in / password-recovery, not a contact address."} />
<LoginEmailAction id={account.ID} path="/api/actions/set-account-login-email" current={account.LoginEmail} onDone={load} />
<div className="card-body">
<LoginEmailAction id={account.ID} path="/api/actions/set-account-login-email" current={account.LoginEmail} onDone={load} />
</div>
</section>
<section className="section-block">
<SectionHead title={"Profile Color"} />
<ColorAction idKey="user_id" id={account.ID} path="/api/actions/set-account-color" onDone={load} />
<div className="card-body">
<ColorAction idKey="user_id" id={account.ID} path="/api/actions/set-account-color" onDone={load} />
</div>
</section>
<section className="section-block">
<SectionHead title={"Emoji Status"} />
<EmojiStatusAction idKey="user_id" id={account.ID} path="/api/actions/set-account-emoji-status" onDone={load} />
<div className="card-body">
<EmojiStatusAction idKey="user_id" id={account.ID} path="/api/actions/set-account-emoji-status" onDone={load} />
</div>
</section>
</div>

View file

@ -119,63 +119,75 @@ export function BotDetailPage({ id, navigate }: { id: number; navigate: Navigate
<div className="action-groups">
<section className="section-block">
<SectionHead title={"Verification & Moderation Flags"} />
<div className="action-stack">
<ActionButton
label={bot.Verified ? "Clear verified" : "Set verified"}
icon={<BadgeCheck size={15} />}
tone="neutral"
path="/api/actions/set-verified"
payload={() => ({ user_id: bot.ID, verified: !bot.Verified })}
onDone={load}
/>
<div className="card-body">
<div className="action-stack">
<ActionButton
label={bot.Verified ? "Clear verified" : "Set verified"}
icon={<BadgeCheck size={15} />}
tone="neutral"
path="/api/actions/set-verified"
payload={() => ({ user_id: bot.ID, verified: !bot.Verified })}
onDone={load}
/>
<ScamFakeActions idKey="user_id" id={bot.ID} path="/api/actions/set-account-flags" scam={bot.Scam} fake={bot.Fake} onDone={load} />
</div>
</div>
<ScamFakeActions idKey="user_id" id={bot.ID} path="/api/actions/set-account-flags" scam={bot.Scam} fake={bot.Fake} onDone={load} />
</section>
<section className="section-block">
<SectionHead title={"Username"} />
<UsernameAction idKey="user_id" id={bot.ID} path="/api/actions/set-account-username" current={bot.Username} onDone={load} />
<div className="card-body">
<UsernameAction idKey="user_id" id={bot.ID} path="/api/actions/set-account-username" current={bot.Username} onDone={load} />
</div>
</section>
<section className="section-block">
<SectionHead title={"Profile Color"} />
<ColorAction idKey="user_id" id={bot.ID} path="/api/actions/set-account-color" onDone={load} />
<div className="card-body">
<ColorAction idKey="user_id" id={bot.ID} path="/api/actions/set-account-color" onDone={load} />
</div>
</section>
<section className="section-block">
<SectionHead title={"Emoji Status"} />
<EmojiStatusAction idKey="user_id" id={bot.ID} path="/api/actions/set-account-emoji-status" onDone={load} />
<div className="card-body">
<EmojiStatusAction idKey="user_id" id={bot.ID} path="/api/actions/set-account-emoji-status" onDone={load} />
</div>
</section>
{!bot.System && (
<section className="section-block">
<SectionHead title={"Credentials"} />
<div className="action-stack">
<button className="btn icon-text" type="button" onClick={() => setCopyTokenModalOpen(true)}>
<Copy size={15} /> {"Copy token"}
</button>
<div className="card-body">
<div className="action-stack">
<button className="btn icon-text" type="button" onClick={() => setCopyTokenModalOpen(true)}>
<Copy size={15} /> {"Copy token"}
</button>
</div>
<p className="bot-create-note">{"Copies straight to the clipboard through a dedicated confirmation step -- the token itself is never shown on this page."}</p>
</div>
<p className="bot-create-note">{"Copies straight to the clipboard through a dedicated confirmation step -- the token itself is never shown on this page."}</p>
</section>
)}
<section className="section-block">
<SectionHead title={"Danger Zone"} />
{bot.System ? (
<p className="bot-create-note">{"System bots are built in and cannot be deleted."}</p>
) : (
<div className="danger-zone">
<ActionButton
label={"Delete bot"}
icon={<Trash2 size={15} />}
tone="danger"
path="/api/actions/delete-bot"
payload={() => ({ bot_user_id: bot.ID })}
onDone={() => navigate("/bots")}
/>
<p className="bot-create-note">{"Permanently deletes this user-created bot and invalidates its token. This cannot be undone."}</p>
</div>
)}
<div className="card-body">
{bot.System ? (
<p className="bot-create-note">{"System bots are built in and cannot be deleted."}</p>
) : (
<div className="danger-zone">
<ActionButton
label={"Delete bot"}
icon={<Trash2 size={15} />}
tone="danger"
path="/api/actions/delete-bot"
payload={() => ({ bot_user_id: bot.ID })}
onDone={() => navigate("/bots")}
/>
<p className="bot-create-note">{"Permanently deletes this user-created bot and invalidates its token. This cannot be undone."}</p>
</div>
)}
</div>
</section>
</div>

View file

@ -124,37 +124,47 @@ export function ChannelDetailPage({ id, navigate }: { id: number; navigate: Navi
<div className="action-groups">
<section className="section-block">
<SectionHead title={"Verification & Moderation Flags"} />
<div className="action-stack">
<ActionButton
label={ch.Verified ? "Clear verified" : "Set verified"}
icon={<BadgeCheck size={15} />}
tone="warn"
path="/api/actions/set-channel-verified"
payload={() => ({ channel_id: ch.ID, verified: !ch.Verified })}
onDone={load}
/>
<ScamFakeActions idKey="channel_id" id={ch.ID} path="/api/actions/set-channel-flags" scam={ch.Scam} fake={ch.Fake} onDone={load} />
<div className="card-body">
<div className="action-stack">
<ActionButton
label={ch.Verified ? "Clear verified" : "Set verified"}
icon={<BadgeCheck size={15} />}
tone="warn"
path="/api/actions/set-channel-verified"
payload={() => ({ channel_id: ch.ID, verified: !ch.Verified })}
onDone={load}
/>
<ScamFakeActions idKey="channel_id" id={ch.ID} path="/api/actions/set-channel-flags" scam={ch.Scam} fake={ch.Fake} onDone={load} />
</div>
</div>
</section>
<section className="section-block">
<SectionHead title={"Settings"} />
<ChannelSettingsAction channel={ch} onDone={load} />
<div className="card-body">
<ChannelSettingsAction channel={ch} onDone={load} />
</div>
</section>
<section className="section-block">
<SectionHead title={"Username"} />
<UsernameAction idKey="channel_id" id={ch.ID} path="/api/actions/set-channel-username" current={ch.Username} onDone={load} />
<div className="card-body">
<UsernameAction idKey="channel_id" id={ch.ID} path="/api/actions/set-channel-username" current={ch.Username} onDone={load} />
</div>
</section>
<section className="section-block">
<SectionHead title={"Profile Color"} />
<ColorAction idKey="channel_id" id={ch.ID} path="/api/actions/set-channel-color" onDone={load} />
<div className="card-body">
<ColorAction idKey="channel_id" id={ch.ID} path="/api/actions/set-channel-color" onDone={load} />
</div>
</section>
<section className="section-block">
<SectionHead title={"Emoji Status"} />
<EmojiStatusAction idKey="channel_id" id={ch.ID} path="/api/actions/set-channel-emoji-status" onDone={load} />
<div className="card-body">
<EmojiStatusAction idKey="channel_id" id={ch.ID} path="/api/actions/set-channel-emoji-status" onDone={load} />
</div>
</section>
</div>