fixes
This commit is contained in:
parent
8aad71643f
commit
7d0daef923
8 changed files with 216 additions and 151 deletions
1
cmd/telesrv-admin/web/dist/assets/index-C7vmH2zd.css
vendored
Normal file
1
cmd/telesrv-admin/web/dist/assets/index-C7vmH2zd.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
cmd/telesrv-admin/web/dist/index.html
vendored
4
cmd/telesrv-admin/web/dist/index.html
vendored
|
|
@ -23,8 +23,8 @@
|
|||
})();
|
||||
</script>
|
||||
|
||||
<script type="module" crossorigin src="/assets/index-DL3Lv8wS.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D8Q_54bE.css">
|
||||
<script type="module" crossorigin src="/assets/index-Defd2Shn.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C7vmH2zd.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
|
|
@ -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