From f44cf642bc7f7165804d91ad08e0440ce1b6f94d Mon Sep 17 00:00:00 2001 From: Edric Li Date: Thu, 1 Jan 2026 18:59:06 +0800 Subject: [PATCH] feat(frontend): add user attributes management UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Vue components and API client for managing user custom attributes. - Add userAttributes API client with CRUD operations - Add UserAttributeForm component for displaying/editing attribute values - Add UserAttributesConfigModal for attribute definition management - Support all attribute types: text, textarea, number, email, url, date, select, multi_select 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- frontend/src/api/admin/index.ts | 7 +- frontend/src/api/admin/userAttributes.ts | 131 ++++++ .../src/components/user/UserAttributeForm.vue | 207 +++++++++ .../user/UserAttributesConfigModal.vue | 404 ++++++++++++++++++ 4 files changed, 747 insertions(+), 2 deletions(-) create mode 100644 frontend/src/api/admin/userAttributes.ts create mode 100644 frontend/src/components/user/UserAttributeForm.vue create mode 100644 frontend/src/components/user/UserAttributesConfigModal.vue diff --git a/frontend/src/api/admin/index.ts b/frontend/src/api/admin/index.ts index 7c98b74e..ea12f6d2 100644 --- a/frontend/src/api/admin/index.ts +++ b/frontend/src/api/admin/index.ts @@ -15,6 +15,7 @@ import subscriptionsAPI from './subscriptions' import usageAPI from './usage' import geminiAPI from './gemini' import antigravityAPI from './antigravity' +import userAttributesAPI from './userAttributes' /** * Unified admin API object for convenient access @@ -31,7 +32,8 @@ export const adminAPI = { subscriptions: subscriptionsAPI, usage: usageAPI, gemini: geminiAPI, - antigravity: antigravityAPI + antigravity: antigravityAPI, + userAttributes: userAttributesAPI } export { @@ -46,7 +48,8 @@ export { subscriptionsAPI, usageAPI, geminiAPI, - antigravityAPI + antigravityAPI, + userAttributesAPI } export default adminAPI diff --git a/frontend/src/api/admin/userAttributes.ts b/frontend/src/api/admin/userAttributes.ts new file mode 100644 index 00000000..304aa828 --- /dev/null +++ b/frontend/src/api/admin/userAttributes.ts @@ -0,0 +1,131 @@ +/** + * Admin User Attributes API endpoints + * Handles user custom attribute definitions and values + */ + +import { apiClient } from '../client' +import type { + UserAttributeDefinition, + UserAttributeValue, + CreateUserAttributeRequest, + UpdateUserAttributeRequest, + UserAttributeValuesMap +} from '@/types' + +/** + * Get all attribute definitions + */ +export async function listDefinitions(): Promise { + const { data } = await apiClient.get('/admin/user-attributes') + return data +} + +/** + * Get enabled attribute definitions only + */ +export async function listEnabledDefinitions(): Promise { + const { data } = await apiClient.get('/admin/user-attributes', { + params: { enabled: true } + }) + return data +} + +/** + * Create a new attribute definition + */ +export async function createDefinition( + request: CreateUserAttributeRequest +): Promise { + const { data } = await apiClient.post('/admin/user-attributes', request) + return data +} + +/** + * Update an attribute definition + */ +export async function updateDefinition( + id: number, + request: UpdateUserAttributeRequest +): Promise { + const { data } = await apiClient.put( + `/admin/user-attributes/${id}`, + request + ) + return data +} + +/** + * Delete an attribute definition + */ +export async function deleteDefinition(id: number): Promise<{ message: string }> { + const { data } = await apiClient.delete<{ message: string }>(`/admin/user-attributes/${id}`) + return data +} + +/** + * Reorder attribute definitions + */ +export async function reorderDefinitions(ids: number[]): Promise<{ message: string }> { + const { data } = await apiClient.put<{ message: string }>('/admin/user-attributes/reorder', { + ids + }) + return data +} + +/** + * Get user's attribute values + */ +export async function getUserAttributeValues(userId: number): Promise { + const { data } = await apiClient.get( + `/admin/users/${userId}/attributes` + ) + return data +} + +/** + * Update user's attribute values (batch) + */ +export async function updateUserAttributeValues( + userId: number, + values: UserAttributeValuesMap +): Promise<{ message: string }> { + const { data } = await apiClient.put<{ message: string }>( + `/admin/users/${userId}/attributes`, + { values } + ) + return data +} + +/** + * Batch response type + */ +export interface BatchUserAttributesResponse { + attributes: Record> +} + +/** + * Get attribute values for multiple users + */ +export async function getBatchUserAttributes( + userIds: number[] +): Promise { + const { data } = await apiClient.post( + '/admin/user-attributes/batch', + { user_ids: userIds } + ) + return data +} + +export const userAttributesAPI = { + listDefinitions, + listEnabledDefinitions, + createDefinition, + updateDefinition, + deleteDefinition, + reorderDefinitions, + getUserAttributeValues, + updateUserAttributeValues, + getBatchUserAttributes +} + +export default userAttributesAPI diff --git a/frontend/src/components/user/UserAttributeForm.vue b/frontend/src/components/user/UserAttributeForm.vue new file mode 100644 index 00000000..68807c5d --- /dev/null +++ b/frontend/src/components/user/UserAttributeForm.vue @@ -0,0 +1,207 @@ +