fixes
This commit is contained in:
parent
8aad71643f
commit
7d0daef923
8 changed files with 216 additions and 151 deletions
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,31 @@
|
|||
gap: 14px;
|
||||
}
|
||||
|
||||
/* Cards in a row are stretched to match the row's tallest card (see
|
||||
.action-groups > .section-block below), which otherwise leaves a card with
|
||||
little content top-heavy with empty space beneath it. .card-body wraps
|
||||
everything after a card's SectionHead so that content can be centered in
|
||||
the remaining height while the header itself stays pinned to the top and
|
||||
aligned across the row. Its own flex gap also keeps adjacent blocks (e.g.
|
||||
a verified-toggle stack followed by ScamFakeActions' own stack) from
|
||||
sitting flush against each other the way plain block-flow siblings would. */
|
||||
.action-groups > .section-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.action-groups > .section-block > .section-head {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-groups > .section-block > .card-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.section-block,
|
||||
.action-dock,
|
||||
.surface {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue