1
This commit is contained in:
@@ -154,6 +154,7 @@ export interface AdminUpsertProfileRedeemCodeRequest {
|
||||
export interface AdminUpsertProfileInviteCodeRequest {
|
||||
inviteCode: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
grantedUserTags: string[];
|
||||
startsAt?: string | null;
|
||||
expiresAt?: string | null;
|
||||
}
|
||||
@@ -200,6 +201,7 @@ export interface ProfileInviteCodeAdminResponse {
|
||||
userId: string;
|
||||
inviteCode: string;
|
||||
metadata: Record<string, unknown>;
|
||||
grantedUserTags: string[];
|
||||
startsAt?: string | null;
|
||||
expiresAt?: string | null;
|
||||
status: 'pending' | 'active' | 'expired';
|
||||
|
||||
@@ -28,6 +28,7 @@ export function AdminInviteCodePage({
|
||||
const [inviteCode, setInviteCode] = useState('');
|
||||
const [startsAt, setStartsAt] = useState('');
|
||||
const [expiresAt, setExpiresAt] = useState('');
|
||||
const [grantedTagsText, setGrantedTagsText] = useState('');
|
||||
const [metadataText, setMetadataText] = useState('{}');
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [listErrorMessage, setListErrorMessage] = useState('');
|
||||
@@ -80,6 +81,7 @@ export function AdminInviteCodePage({
|
||||
const payload: AdminUpsertProfileInviteCodeRequest = {
|
||||
inviteCode: inviteCode.trim(),
|
||||
metadata: parseMetadata(metadataText),
|
||||
grantedUserTags: parseUserTags(grantedTagsText),
|
||||
startsAt: startsAt ? toIsoDateTime(startsAt) : null,
|
||||
expiresAt: expiresAt ? toIsoDateTime(expiresAt) : null,
|
||||
};
|
||||
@@ -115,6 +117,7 @@ export function AdminInviteCodePage({
|
||||
setInviteCode(entry.inviteCode);
|
||||
setStartsAt(toDateTimeLocalValue(entry.startsAt));
|
||||
setExpiresAt(toDateTimeLocalValue(entry.expiresAt));
|
||||
setGrantedTagsText(entry.grantedUserTags.join('、'));
|
||||
setMetadataText(JSON.stringify(entry.metadata, null, 2));
|
||||
}
|
||||
|
||||
@@ -174,6 +177,15 @@ export function AdminInviteCodePage({
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="admin-field">
|
||||
<span>用户标签</span>
|
||||
<input
|
||||
autoComplete="off"
|
||||
value={grantedTagsText}
|
||||
onChange={(event) => setGrantedTagsText(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="admin-field">
|
||||
<span>Metadata JSON</span>
|
||||
<textarea
|
||||
@@ -222,6 +234,7 @@ export function AdminInviteCodePage({
|
||||
<thead>
|
||||
<tr>
|
||||
<th>邀请码</th>
|
||||
<th>标签</th>
|
||||
<th>有效期</th>
|
||||
<th>创建</th>
|
||||
</tr>
|
||||
@@ -238,6 +251,9 @@ export function AdminInviteCodePage({
|
||||
{entry.inviteCode}
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<TagList tags={entry.grantedUserTags} />
|
||||
</td>
|
||||
<td>
|
||||
<span className={`admin-status ${inviteValidityClass(entry)}`}>
|
||||
{inviteValidityLabel(entry)}
|
||||
@@ -272,6 +288,12 @@ export function AdminInviteCodePage({
|
||||
<dt>有效期</dt>
|
||||
<dd>{formatValidityWindow(result)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>标签</dt>
|
||||
<dd>
|
||||
<TagList tags={result.grantedUserTags} />
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>创建</dt>
|
||||
<dd>{result.createdAt}</dd>
|
||||
@@ -300,6 +322,33 @@ export function AdminInviteCodePage({
|
||||
);
|
||||
}
|
||||
|
||||
function TagList({tags}: {tags: string[]}) {
|
||||
if (!tags.length) {
|
||||
return <span className="admin-muted-text">-</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="admin-tag-list">
|
||||
{tags.map((tag) => (
|
||||
<span className="admin-tag" key={tag}>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function parseUserTags(value: string) {
|
||||
const tags: string[] = [];
|
||||
for (const raw of value.split(/[\n,,;;、]+/)) {
|
||||
const tag = raw.trim();
|
||||
if (tag && !tags.includes(tag)) {
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
function parseMetadata(value: string): Record<string, unknown> {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
|
||||
@@ -684,6 +684,32 @@ button:disabled {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.admin-muted-text {
|
||||
color: #86939c;
|
||||
}
|
||||
|
||||
.admin-tag-list {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.admin-tag {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
border: 1px solid #cbdfe6;
|
||||
border-radius: 999px;
|
||||
background: #eef7f8;
|
||||
color: #0f5666;
|
||||
padding: 3px 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 750;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-table-compact {
|
||||
min-width: 360px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user