fix(sid): harden admin assignment flow
This commit is contained in:
+12
-12
@@ -50,7 +50,7 @@ export interface paths {
|
|||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
/**
|
/**
|
||||||
* @example archived
|
* @example all
|
||||||
* @enum {string|null}
|
* @enum {string|null}
|
||||||
*/
|
*/
|
||||||
state?: "active" | "archived" | "trashed" | "all" | null;
|
state?: "active" | "archived" | "trashed" | "all" | null;
|
||||||
@@ -539,7 +539,7 @@ export interface paths {
|
|||||||
*/
|
*/
|
||||||
language_code?: string;
|
language_code?: string;
|
||||||
/**
|
/**
|
||||||
* @example draft
|
* @example published
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
status?: "draft" | "awaiting_publish" | "published";
|
status?: "draft" | "awaiting_publish" | "published";
|
||||||
@@ -4685,7 +4685,7 @@ export interface paths {
|
|||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
/**
|
/**
|
||||||
* @example viewed
|
* @example taken_by_other
|
||||||
* @enum {string|null}
|
* @enum {string|null}
|
||||||
*/
|
*/
|
||||||
status?: "sent" | "delivered" | "viewed" | "accepted" | "rejected" | "ignored" | "expired" | "taken_by_other" | null;
|
status?: "sent" | "delivered" | "viewed" | "accepted" | "rejected" | "ignored" | "expired" | "taken_by_other" | null;
|
||||||
@@ -5163,7 +5163,7 @@ export interface paths {
|
|||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
/**
|
/**
|
||||||
* @example inactive
|
* @example busy
|
||||||
* @enum {string|null}
|
* @enum {string|null}
|
||||||
*/
|
*/
|
||||||
status?: "offline" | "online" | "inactive" | "busy" | null;
|
status?: "offline" | "online" | "inactive" | "busy" | null;
|
||||||
@@ -5448,12 +5448,12 @@ export interface paths {
|
|||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
/**
|
/**
|
||||||
* @example private
|
* @example friends
|
||||||
* @enum {string|null}
|
* @enum {string|null}
|
||||||
*/
|
*/
|
||||||
visibility?: "public" | "friends" | "private" | null;
|
visibility?: "public" | "friends" | "private" | null;
|
||||||
/**
|
/**
|
||||||
* @example blocked
|
* @example visible
|
||||||
* @enum {string|null}
|
* @enum {string|null}
|
||||||
*/
|
*/
|
||||||
status?: "blocked" | "visible" | "all" | null;
|
status?: "blocked" | "visible" | "all" | null;
|
||||||
@@ -5779,7 +5779,7 @@ export interface paths {
|
|||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
/**
|
/**
|
||||||
* @example all
|
* @example pending
|
||||||
* @enum {string|null}
|
* @enum {string|null}
|
||||||
*/
|
*/
|
||||||
status?: "pending" | "blocked" | "all" | null;
|
status?: "pending" | "blocked" | "all" | null;
|
||||||
@@ -9744,7 +9744,7 @@ export interface paths {
|
|||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
/**
|
/**
|
||||||
* @example all
|
* @example blocked
|
||||||
* @enum {string|null}
|
* @enum {string|null}
|
||||||
*/
|
*/
|
||||||
status?: "pending" | "blocked" | "all" | null;
|
status?: "pending" | "blocked" | "all" | null;
|
||||||
@@ -10107,11 +10107,11 @@ export interface paths {
|
|||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
/**
|
/**
|
||||||
* @example web
|
* @example android
|
||||||
* @enum {string|null}
|
* @enum {string|null}
|
||||||
*/
|
*/
|
||||||
platform?: "ios" | "android" | "web" | "desktop" | null;
|
platform?: "ios" | "android" | "web" | "desktop" | null;
|
||||||
/** @example false */
|
/** @example true */
|
||||||
is_online?: boolean | null;
|
is_online?: boolean | null;
|
||||||
/**
|
/**
|
||||||
* @description Must be at least 1.
|
* @description Must be at least 1.
|
||||||
@@ -11170,7 +11170,7 @@ export interface operations {
|
|||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
/**
|
/**
|
||||||
* @description SID 值,支持带/不带横杠。
|
* @description SID 值,格式为 3 位非歧义字母加 6 位数字,支持带/不带横杠。
|
||||||
* @example ABC-888888
|
* @example ABC-888888
|
||||||
*/
|
*/
|
||||||
sid: string;
|
sid: string;
|
||||||
@@ -11214,7 +11214,7 @@ export interface operations {
|
|||||||
*/
|
*/
|
||||||
user_uid: string;
|
user_uid: string;
|
||||||
/**
|
/**
|
||||||
* @description SID 值,支持带/不带横杠。
|
* @description SID 值,格式为 3 位非歧义字母加 6 位数字,支持带/不带横杠。
|
||||||
* @example ABC-168888
|
* @example ABC-168888
|
||||||
*/
|
*/
|
||||||
sid: string;
|
sid: string;
|
||||||
|
|||||||
@@ -70,6 +70,18 @@ function RulesTab() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rules.isPending) return <p className="text-sm text-muted-foreground">正在加载保留规则…</p>
|
||||||
|
if (rules.isError) {
|
||||||
|
return (
|
||||||
|
<div className="text-sm text-destructive">
|
||||||
|
<p>保留规则加载失败。</p>
|
||||||
|
<Button className="mt-2" variant="outline" size="sm" onClick={() => void rules.refetch()}>
|
||||||
|
重新加载
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-3 md:grid-cols-2">
|
<div className="grid gap-3 md:grid-cols-2">
|
||||||
{(rules.data?.data ?? []).map((rule) => (
|
{(rules.data?.data ?? []).map((rule) => (
|
||||||
@@ -131,6 +143,18 @@ function LuckyTab() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (numbers.isPending) return <p className="text-sm text-muted-foreground">正在加载幸运号码…</p>
|
||||||
|
if (numbers.isError) {
|
||||||
|
return (
|
||||||
|
<div className="text-sm text-destructive">
|
||||||
|
<p>幸运号码加载失败。</p>
|
||||||
|
<Button className="mt-2" variant="outline" size="sm" onClick={() => void numbers.refetch()}>
|
||||||
|
重新加载
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-wrap items-end gap-3">
|
<div className="flex flex-wrap items-end gap-3">
|
||||||
@@ -140,8 +164,10 @@ function LuckyTab() {
|
|||||||
id="lucky-number"
|
id="lucky-number"
|
||||||
className="w-32 font-mono"
|
className="w-32 font-mono"
|
||||||
maxLength={3}
|
maxLength={3}
|
||||||
|
inputMode="numeric"
|
||||||
|
pattern="[0-9]{3}"
|
||||||
value={draft.number}
|
value={draft.number}
|
||||||
onChange={(e) => setDraft((d) => ({ ...d, number: e.target.value }))}
|
onChange={(e) => setDraft((d) => ({ ...d, number: e.target.value.replace(/\D/g, '') }))}
|
||||||
placeholder="888"
|
placeholder="888"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -198,6 +224,8 @@ function AssignTab() {
|
|||||||
const [checkResult, setCheckResult] = useState<SidCheckResult | null>(null)
|
const [checkResult, setCheckResult] = useState<SidCheckResult | null>(null)
|
||||||
const [userUid, setUserUid] = useState('')
|
const [userUid, setUserUid] = useState('')
|
||||||
const [busy, setBusy] = useState(false)
|
const [busy, setBusy] = useState(false)
|
||||||
|
const validSid = /^[A-HJ-NPR-Z]{3}-?\d{6}$/i.test(sid)
|
||||||
|
const validUid = /^[0-9A-HJKMNP-TV-Z]{26}$/i.test(userUid)
|
||||||
|
|
||||||
const check = async () => {
|
const check = async () => {
|
||||||
setBusy(true)
|
setBusy(true)
|
||||||
@@ -238,11 +266,14 @@ function AssignTab() {
|
|||||||
id="sid-input"
|
id="sid-input"
|
||||||
className="font-mono"
|
className="font-mono"
|
||||||
value={sid}
|
value={sid}
|
||||||
onChange={(e) => setSid(e.target.value)}
|
onChange={(e) => {
|
||||||
placeholder="668888"
|
setSid(e.target.value.toUpperCase())
|
||||||
|
setCheckResult(null)
|
||||||
|
}}
|
||||||
|
placeholder="ABC-668888"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" onClick={check} disabled={!sid || busy}>
|
<Button variant="outline" onClick={check} disabled={!validSid || busy}>
|
||||||
查询
|
查询
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -270,14 +301,21 @@ function AssignTab() {
|
|||||||
<Input
|
<Input
|
||||||
id="assign-uid"
|
id="assign-uid"
|
||||||
className="font-mono"
|
className="font-mono"
|
||||||
|
maxLength={26}
|
||||||
value={userUid}
|
value={userUid}
|
||||||
onChange={(e) => setUserUid(e.target.value)}
|
onChange={(e) => setUserUid(e.target.value.toUpperCase())}
|
||||||
placeholder="01JXXXXXXXXXXXXXXXXXXXXXXX"
|
placeholder="01JXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={assign}
|
onClick={assign}
|
||||||
disabled={!sid || !userUid || busy || (checkResult?.reserved === true && !canOverride)}
|
disabled={
|
||||||
|
!validSid ||
|
||||||
|
!validUid ||
|
||||||
|
!checkResult ||
|
||||||
|
busy ||
|
||||||
|
(checkResult.reserved && !canOverride)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
分配 SID
|
分配 SID
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user