From 434f7f3bcbb007fc6af33ef88add81f459211ea3 Mon Sep 17 00:00:00 2001 From: hjlarry Date: Sat, 17 Jan 2026 22:10:10 +0800 Subject: [PATCH] fix web style --- .../icons/src/public/common/EnterKey.json | 68 +++++++++---------- .../base/icons/src/public/common/EnterKey.tsx | 8 +-- .../base/icons/src/public/other/Comment.json | 48 ++++++------- .../base/icons/src/public/other/Comment.tsx | 8 +-- .../base/user-avatar-list/index.tsx | 14 ++-- .../collaboration/components/user-cursors.tsx | 2 +- .../collaboration/core/event-emitter.ts | 6 +- .../collaboration/core/websocket-manager.ts | 2 +- .../collaboration/hooks/use-collaboration.ts | 8 +-- .../workflow/collaboration/index.ts | 2 +- .../collaboration/services/cursor-service.ts | 10 +-- .../collaboration/types/collaboration.ts | 20 +++--- .../workflow/collaboration/types/index.ts | 2 +- .../components/workflow/comment-manager.tsx | 2 +- .../workflow/comment/comment-icon.tsx | 31 +++++---- .../workflow/comment/comment-preview.tsx | 2 +- .../components/workflow/comment/cursor.tsx | 2 +- web/app/components/workflow/comment/index.tsx | 6 +- .../hooks/use-collaborative-workflow.ts | 2 +- web/service/workflow-comment.ts | 2 +- 20 files changed, 127 insertions(+), 118 deletions(-) diff --git a/web/app/components/base/icons/src/public/common/EnterKey.json b/web/app/components/base/icons/src/public/common/EnterKey.json index 17c8e645ae..1c2b0903ee 100644 --- a/web/app/components/base/icons/src/public/common/EnterKey.json +++ b/web/app/components/base/icons/src/public/common/EnterKey.json @@ -1,36 +1,36 @@ { - "icon": { - "type": "element", - "isRootNode": true, - "name": "svg", - "attributes": { - "width": "16", - "height": "16", - "viewBox": "0 0 16 16", - "fill": "none", - "xmlns": "http://www.w3.org/2000/svg" - }, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "d": "M0 4C0 1.79086 1.79086 0 4 0H12C14.2091 0 16 1.79086 16 4V12C16 14.2091 14.2091 16 12 16H4C1.79086 16 0 14.2091 0 12V4Z", - "fill": "white", - "fill-opacity": "0.12" - }, - "children": [] - }, - { - "type": "element", - "name": "path", - "attributes": { - "d": "M3.42756 8.7358V7.62784H10.8764C11.2003 7.62784 11.4957 7.5483 11.7628 7.3892C12.0298 7.23011 12.2415 7.01705 12.3977 6.75C12.5568 6.48295 12.6364 6.1875 12.6364 5.86364C12.6364 5.53977 12.5568 5.24574 12.3977 4.98153C12.2386 4.71449 12.0256 4.50142 11.7585 4.34233C11.4943 4.18324 11.2003 4.10369 10.8764 4.10369H10.3991V3H10.8764C11.4048 3 11.8849 3.12926 12.3168 3.38778C12.7486 3.64631 13.0938 3.99148 13.3523 4.4233C13.6108 4.85511 13.7401 5.33523 13.7401 5.86364C13.7401 6.25852 13.6648 6.62926 13.5142 6.97585C13.3665 7.32244 13.1619 7.62784 12.9006 7.89205C12.6392 8.15625 12.3352 8.36364 11.9886 8.5142C11.642 8.66193 11.2713 8.7358 10.8764 8.7358H3.42756ZM6.16761 12.0554L2.29403 8.18182L6.16761 4.30824L6.9304 5.07102L3.81534 8.18182L6.9304 11.2926L6.16761 12.0554Z", - "fill": "white" - }, - "children": [] - } - ] - }, - "name": "EnterKey" + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M0 4C0 1.79086 1.79086 0 4 0H12C14.2091 0 16 1.79086 16 4V12C16 14.2091 14.2091 16 12 16H4C1.79086 16 0 14.2091 0 12V4Z", + "fill": "white", + "fill-opacity": "0.12" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M3.42756 8.7358V7.62784H10.8764C11.2003 7.62784 11.4957 7.5483 11.7628 7.3892C12.0298 7.23011 12.2415 7.01705 12.3977 6.75C12.5568 6.48295 12.6364 6.1875 12.6364 5.86364C12.6364 5.53977 12.5568 5.24574 12.3977 4.98153C12.2386 4.71449 12.0256 4.50142 11.7585 4.34233C11.4943 4.18324 11.2003 4.10369 10.8764 4.10369H10.3991V3H10.8764C11.4048 3 11.8849 3.12926 12.3168 3.38778C12.7486 3.64631 13.0938 3.99148 13.3523 4.4233C13.6108 4.85511 13.7401 5.33523 13.7401 5.86364C13.7401 6.25852 13.6648 6.62926 13.5142 6.97585C13.3665 7.32244 13.1619 7.62784 12.9006 7.89205C12.6392 8.15625 12.3352 8.36364 11.9886 8.5142C11.642 8.66193 11.2713 8.7358 10.8764 8.7358H3.42756ZM6.16761 12.0554L2.29403 8.18182L6.16761 4.30824L6.9304 5.07102L3.81534 8.18182L6.9304 11.2926L6.16761 12.0554Z", + "fill": "white" + }, + "children": [] + } + ] + }, + "name": "EnterKey" } diff --git a/web/app/components/base/icons/src/public/common/EnterKey.tsx b/web/app/components/base/icons/src/public/common/EnterKey.tsx index 5365f48344..2bf2d4fb19 100644 --- a/web/app/components/base/icons/src/public/common/EnterKey.tsx +++ b/web/app/components/base/icons/src/public/common/EnterKey.tsx @@ -1,17 +1,17 @@ // GENERATE BY script // DON NOT EDIT IT MANUALLY -import * as React from 'react' -import data from './EnterKey.json' -import IconBase from '@/app/components/base/icons/IconBase' import type { IconData } from '@/app/components/base/icons/IconBase' +import * as React from 'react' +import IconBase from '@/app/components/base/icons/IconBase' +import data from './EnterKey.json' const Icon = ( { ref, ...props }: React.SVGProps & { - ref?: React.RefObject>; + ref?: React.RefObject> }, ) => diff --git a/web/app/components/base/icons/src/public/other/Comment.json b/web/app/components/base/icons/src/public/other/Comment.json index c4865a010c..780ddc4cf5 100644 --- a/web/app/components/base/icons/src/public/other/Comment.json +++ b/web/app/components/base/icons/src/public/other/Comment.json @@ -1,26 +1,26 @@ { - "icon": { - "type": "element", - "isRootNode": true, - "name": "svg", - "attributes": { - "xmlns": "http://www.w3.org/2000/svg", - "width": "14", - "height": "12", - "viewBox": "0 0 14 12", - "fill": "none" - }, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "d": "M12.3334 4C12.3334 2.52725 11.1395 1.33333 9.66671 1.33333H4.33337C2.86062 1.33333 1.66671 2.52724 1.66671 4V10.6667H9.66671C11.1395 10.6667 12.3334 9.47274 12.3334 8V4ZM7.66671 6.66667V8H4.33337V6.66667H7.66671ZM9.66671 4V5.33333H4.33337V4H9.66671ZM13.6667 8C13.6667 10.2091 11.8758 12 9.66671 12H0.333374V4C0.333374 1.79086 2.12424 0 4.33337 0H9.66671C11.8758 0 13.6667 1.79086 13.6667 4V8Z", - "fill": "currentColor" - }, - "children": [] - } - ] - }, - "name": "Comment" + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "xmlns": "http://www.w3.org/2000/svg", + "width": "14", + "height": "12", + "viewBox": "0 0 14 12", + "fill": "none" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M12.3334 4C12.3334 2.52725 11.1395 1.33333 9.66671 1.33333H4.33337C2.86062 1.33333 1.66671 2.52724 1.66671 4V10.6667H9.66671C11.1395 10.6667 12.3334 9.47274 12.3334 8V4ZM7.66671 6.66667V8H4.33337V6.66667H7.66671ZM9.66671 4V5.33333H4.33337V4H9.66671ZM13.6667 8C13.6667 10.2091 11.8758 12 9.66671 12H0.333374V4C0.333374 1.79086 2.12424 0 4.33337 0H9.66671C11.8758 0 13.6667 1.79086 13.6667 4V8Z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + "name": "Comment" } diff --git a/web/app/components/base/icons/src/public/other/Comment.tsx b/web/app/components/base/icons/src/public/other/Comment.tsx index 85c2559b76..887754e48e 100644 --- a/web/app/components/base/icons/src/public/other/Comment.tsx +++ b/web/app/components/base/icons/src/public/other/Comment.tsx @@ -1,17 +1,17 @@ // GENERATE BY script // DON NOT EDIT IT MANUALLY -import * as React from 'react' -import data from './Comment.json' -import IconBase from '@/app/components/base/icons/IconBase' import type { IconData } from '@/app/components/base/icons/IconBase' +import * as React from 'react' +import IconBase from '@/app/components/base/icons/IconBase' +import data from './Comment.json' const Icon = ( { ref, ...props }: React.SVGProps & { - ref?: React.RefObject>; + ref?: React.RefObject> }, ) => diff --git a/web/app/components/base/user-avatar-list/index.tsx b/web/app/components/base/user-avatar-list/index.tsx index b0d1989521..f16ef3b7ff 100644 --- a/web/app/components/base/user-avatar-list/index.tsx +++ b/web/app/components/base/user-avatar-list/index.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react' import { memo } from 'react' +import Avatar from '@/app/components/base/avatar' import { getUserColor } from '@/app/components/workflow/collaboration/utils/user-color' import { useAppContext } from '@/context/app-context' -import Avatar from '@/app/components/base/avatar' type User = { id: string @@ -26,7 +26,8 @@ export const UserAvatarList: FC = memo(({ showCount = true, }) => { const { userProfile } = useAppContext() - if (!users.length) return null + if (!users.length) + return null const shouldShowCount = showCount && users.length > maxVisible const actualMaxVisible = shouldShowCount ? Math.max(1, maxVisible - 1) : maxVisible @@ -43,14 +44,14 @@ export const UserAvatarList: FC = memo(({ return (
@@ -60,14 +61,15 @@ export const UserAvatarList: FC = memo(({ )} {shouldShowCount && remainingCount > 0 && (
- +{remainingCount} + + + {remainingCount}
)} diff --git a/web/app/components/workflow/collaboration/components/user-cursors.tsx b/web/app/components/workflow/collaboration/components/user-cursors.tsx index fff4d5bb8c..488de1530d 100644 --- a/web/app/components/workflow/collaboration/components/user-cursors.tsx +++ b/web/app/components/workflow/collaboration/components/user-cursors.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' -import { useViewport } from 'reactflow' import type { CursorPosition, OnlineUser } from '@/app/components/workflow/collaboration/types' +import { useViewport } from 'reactflow' import { getUserColor } from '../utils/user-color' type UserCursorsProps = { diff --git a/web/app/components/workflow/collaboration/core/event-emitter.ts b/web/app/components/workflow/collaboration/core/event-emitter.ts index 250b344b12..efe7ef81d9 100644 --- a/web/app/components/workflow/collaboration/core/event-emitter.ts +++ b/web/app/components/workflow/collaboration/core/event-emitter.ts @@ -13,7 +13,8 @@ export class EventEmitter { } off(event: string, handler?: EventHandler): void { - if (!this.events.has(event)) return + if (!this.events.has(event)) + return const handlers = this.events.get(event)! if (handler) @@ -26,7 +27,8 @@ export class EventEmitter { } emit(event: string, data: T): void { - if (!this.events.has(event)) return + if (!this.events.has(event)) + return const handlers = this.events.get(event)! handlers.forEach((handler) => { diff --git a/web/app/components/workflow/collaboration/core/websocket-manager.ts b/web/app/components/workflow/collaboration/core/websocket-manager.ts index bef68e5269..73c08035cb 100644 --- a/web/app/components/workflow/collaboration/core/websocket-manager.ts +++ b/web/app/components/workflow/collaboration/core/websocket-manager.ts @@ -1,7 +1,7 @@ import type { Socket } from 'socket.io-client' +import type { DebugInfo, WebSocketConfig } from '../types/websocket' import { io } from 'socket.io-client' import { ACCESS_TOKEN_LOCAL_STORAGE_NAME } from '@/config' -import type { DebugInfo, WebSocketConfig } from '../types/websocket' const isUnauthorizedAck = (...ackArgs: any[]): boolean => { const [first, second] = ackArgs diff --git a/web/app/components/workflow/collaboration/hooks/use-collaboration.ts b/web/app/components/workflow/collaboration/hooks/use-collaboration.ts index 6404a3ae72..a6526def45 100644 --- a/web/app/components/workflow/collaboration/hooks/use-collaboration.ts +++ b/web/app/components/workflow/collaboration/hooks/use-collaboration.ts @@ -1,10 +1,10 @@ -import { useEffect, useRef, useState } from 'react' import type { ReactFlowInstance } from 'reactflow' +import type { CollaborationState } from '../types/collaboration' +import { useEffect, useRef, useState } from 'react' +import Toast from '@/app/components/base/toast' +import { useGlobalPublicStore } from '@/context/global-public-context' import { collaborationManager } from '../core/collaboration-manager' import { CursorService } from '../services/cursor-service' -import type { CollaborationState } from '../types/collaboration' -import { useGlobalPublicStore } from '@/context/global-public-context' -import Toast from '@/app/components/base/toast' export function useCollaboration(appId: string, reactFlowStore?: any) { const [state, setState] = useState>({ diff --git a/web/app/components/workflow/collaboration/index.ts b/web/app/components/workflow/collaboration/index.ts index 89cbb3aa43..dd283f9d2e 100644 --- a/web/app/components/workflow/collaboration/index.ts +++ b/web/app/components/workflow/collaboration/index.ts @@ -1,5 +1,5 @@ export { collaborationManager } from './core/collaboration-manager' export { webSocketClient } from './core/websocket-manager' -export { CursorService } from './services/cursor-service' export { useCollaboration } from './hooks/use-collaboration' +export { CursorService } from './services/cursor-service' export * from './types' diff --git a/web/app/components/workflow/collaboration/services/cursor-service.ts b/web/app/components/workflow/collaboration/services/cursor-service.ts index 73e1c6cbd1..7af6f2f27f 100644 --- a/web/app/components/workflow/collaboration/services/cursor-service.ts +++ b/web/app/components/workflow/collaboration/services/cursor-service.ts @@ -1,6 +1,6 @@ import type { RefObject } from 'react' -import type { CursorPosition } from '../types/collaboration' import type { ReactFlowInstance } from 'reactflow' +import type { CursorPosition } from '../types/collaboration' const CURSOR_MIN_MOVE_DISTANCE = 10 const CURSOR_THROTTLE_MS = 300 @@ -12,14 +12,15 @@ export class CursorService { private onCursorUpdate: ((cursors: Record) => void) | null = null private onEmitPosition: ((position: CursorPosition) => void) | null = null private lastEmitTime = 0 - private lastPosition: { x: number; y: number } | null = null + private lastPosition: { x: number, y: number } | null = null startTracking( containerRef: RefObject, onEmitPosition: (position: CursorPosition) => void, reactFlowInstance?: ReactFlowInstance, ): void { - if (this.isTracking) this.stopTracking() + if (this.isTracking) + this.stopTracking() this.containerRef = containerRef this.onEmitPosition = onEmitPosition @@ -51,7 +52,8 @@ export class CursorService { } private handleMouseMove = (event: MouseEvent): void => { - if (!this.containerRef?.current || !this.onEmitPosition) return + if (!this.containerRef?.current || !this.onEmitPosition) + return const rect = this.containerRef.current.getBoundingClientRect() let x = event.clientX - rect.left diff --git a/web/app/components/workflow/collaboration/types/collaboration.ts b/web/app/components/workflow/collaboration/types/collaboration.ts index dc154d3888..f117fc3e07 100644 --- a/web/app/components/workflow/collaboration/types/collaboration.ts +++ b/web/app/components/workflow/collaboration/types/collaboration.ts @@ -51,16 +51,16 @@ export type GraphSyncData = { export type CollaborationEventType = | 'mouse_move' - | 'vars_and_features_update' - | 'sync_request' - | 'app_state_update' - | 'app_meta_update' - | 'mcp_server_update' - | 'workflow_update' - | 'comments_update' - | 'node_panel_presence' - | 'app_publish_update' - | 'graph_resync_request' + | 'vars_and_features_update' + | 'sync_request' + | 'app_state_update' + | 'app_meta_update' + | 'mcp_server_update' + | 'workflow_update' + | 'comments_update' + | 'node_panel_presence' + | 'app_publish_update' + | 'graph_resync_request' export type CollaborationUpdate = { type: CollaborationEventType diff --git a/web/app/components/workflow/collaboration/types/index.ts b/web/app/components/workflow/collaboration/types/index.ts index e79ed35da0..bcbe7353e1 100644 --- a/web/app/components/workflow/collaboration/types/index.ts +++ b/web/app/components/workflow/collaboration/types/index.ts @@ -1,3 +1,3 @@ -export * from './websocket' export * from './collaboration' export * from './events' +export * from './websocket' diff --git a/web/app/components/workflow/comment-manager.tsx b/web/app/components/workflow/comment-manager.tsx index e5817eb6b1..7d5d5f1653 100644 --- a/web/app/components/workflow/comment-manager.tsx +++ b/web/app/components/workflow/comment-manager.tsx @@ -1,6 +1,6 @@ import { useEventListener } from 'ahooks' -import { useWorkflowStore } from './store' import { useWorkflowComment } from './hooks/use-workflow-comment' +import { useWorkflowStore } from './store' const CommentManager = () => { const workflowStore = useWorkflowStore() diff --git a/web/app/components/workflow/comment/comment-icon.tsx b/web/app/components/workflow/comment/comment-icon.tsx index f2b3a785b1..301d7decfa 100644 --- a/web/app/components/workflow/comment/comment-icon.tsx +++ b/web/app/components/workflow/comment/comment-icon.tsx @@ -1,18 +1,18 @@ 'use client' import type { FC, PointerEvent as ReactPointerEvent } from 'react' +import type { WorkflowCommentList } from '@/service/workflow-comment' import { memo, useCallback, useMemo, useRef, useState } from 'react' import { useReactFlow, useViewport } from 'reactflow' import { UserAvatarList } from '@/app/components/base/user-avatar-list' -import CommentPreview from './comment-preview' -import type { WorkflowCommentList } from '@/service/workflow-comment' import { useAppContext } from '@/context/app-context' +import CommentPreview from './comment-preview' type CommentIconProps = { comment: WorkflowCommentList onClick: () => void isActive?: boolean - onPositionUpdate?: (position: { x: number; y: number }) => void + onPositionUpdate?: (position: { x: number, y: number }) => void } export const CommentIcon: FC = memo(({ comment, onClick, isActive = false, onPositionUpdate }) => { @@ -21,7 +21,7 @@ export const CommentIcon: FC = memo(({ comment, onClick, isAct const { userProfile } = useAppContext() const isAuthor = comment.created_by_account?.id === userProfile?.id const [showPreview, setShowPreview] = useState(false) - const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null) + const [dragPosition, setDragPosition] = useState<{ x: number, y: number } | null>(null) const [isDragging, setIsDragging] = useState(false) const dragStateRef = useRef<{ offsetX: number @@ -181,8 +181,7 @@ export const CommentIcon: FC = memo(({ comment, onClick, isAct // Width calculation: first avatar + (additional avatars * (size - spacing)) + padding const dynamicWidth = Math.max(40, // minimum width - 8 + avatarSize + Math.max(0, (showCount ? 2 : maxVisible - 1)) * (avatarSize - avatarSpacing) + 8, - ) + 8 + avatarSize + Math.max(0, (showCount ? 2 : maxVisible - 1)) * (avatarSize - avatarSpacing) + 8) const pointerEventHandlers = useMemo(() => ({ onPointerDown: handlePointerDown, @@ -200,7 +199,7 @@ export const CommentIcon: FC = memo(({ comment, onClick, isAct top: canvasPosition.y, transform: 'translate(-50%, -50%)', }} - data-role='comment-marker' + data-role="comment-marker" {...pointerEventHandlers} >
= memo(({ comment, onClick, isAct onMouseLeave={handleMouseLeave} >
+ }`} + >
= memo(({ comment, onClick, isAct top: (effectiveScreenPosition.y - containerTop) + 20, transform: 'translateY(-100%)', }} - data-role='comment-preview' + data-role="comment-preview" {...pointerEventHandlers} onMouseEnter={() => setShowPreview(true)} onMouseLeave={() => setShowPreview(false)} > - { - setShowPreview(false) - onClick() - }} /> + { + setShowPreview(false) + onClick() + }} + />
)} diff --git a/web/app/components/workflow/comment/comment-preview.tsx b/web/app/components/workflow/comment/comment-preview.tsx index be43434dd5..30816d9cf3 100644 --- a/web/app/components/workflow/comment/comment-preview.tsx +++ b/web/app/components/workflow/comment/comment-preview.tsx @@ -1,9 +1,9 @@ 'use client' import type { FC } from 'react' +import type { WorkflowCommentList } from '@/service/workflow-comment' import { memo, useEffect, useMemo } from 'react' import { UserAvatarList } from '@/app/components/base/user-avatar-list' -import type { WorkflowCommentList } from '@/service/workflow-comment' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import { useStore } from '../store' diff --git a/web/app/components/workflow/comment/cursor.tsx b/web/app/components/workflow/comment/cursor.tsx index 56b5c24f16..9afd4f1d8b 100644 --- a/web/app/components/workflow/comment/cursor.tsx +++ b/web/app/components/workflow/comment/cursor.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react' import { memo } from 'react' +import { Comment } from '@/app/components/base/icons/src/public/other' import { useStore } from '../store' import { ControlMode } from '../types' -import { Comment } from '@/app/components/base/icons/src/public/other' export const CommentCursor: FC = memo(() => { const controlMode = useStore(s => s.controlMode) diff --git a/web/app/components/workflow/comment/index.tsx b/web/app/components/workflow/comment/index.tsx index f80ddac5bf..cb8e6ab04d 100644 --- a/web/app/components/workflow/comment/index.tsx +++ b/web/app/components/workflow/comment/index.tsx @@ -1,5 +1,5 @@ -export { CommentCursor } from './cursor' -export { CommentInput } from './comment-input' export { CommentIcon } from './comment-icon' -export { CommentThread } from './thread' +export { CommentInput } from './comment-input' +export { CommentCursor } from './cursor' export { MentionInput } from './mention-input' +export { CommentThread } from './thread' diff --git a/web/app/components/workflow/hooks/use-collaborative-workflow.ts b/web/app/components/workflow/hooks/use-collaborative-workflow.ts index fe032d22bc..9d019f981f 100644 --- a/web/app/components/workflow/hooks/use-collaborative-workflow.ts +++ b/web/app/components/workflow/hooks/use-collaborative-workflow.ts @@ -1,6 +1,6 @@ +import type { Edge, Node } from '../types' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import type { Edge, Node } from '../types' import { collaborationManager } from '../collaboration/core/collaboration-manager' const sanitizeNodeForBroadcast = (node: Node): Node => { diff --git a/web/service/workflow-comment.ts b/web/service/workflow-comment.ts index 4eec0f0662..4dd407eabf 100644 --- a/web/service/workflow-comment.ts +++ b/web/service/workflow-comment.ts @@ -1,5 +1,5 @@ -import { del, get, post, put } from './base' import type { CommonResponse } from '@/models/common' +import { del, get, post, put } from './base' export type UserProfile = { id: string