From 6851624dbebbb7bc09866e2a6a5a9104fdb898db Mon Sep 17 00:00:00 2001 From: GareArc Date: Mon, 18 May 2026 20:23:44 -0700 Subject: [PATCH] =?UTF-8?q?feat(web):=20device-flow=20authorize-account=20?= =?UTF-8?q?=E2=80=94=20Avatar=20card=20+=20accountName/avatarUrl=20props?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/authorize-account.spec.tsx | 69 +++++++++++++++++++ .../device/components/authorize-account.tsx | 66 ++++++++++++------ web/app/device/page.tsx | 1 - 3 files changed, 114 insertions(+), 22 deletions(-) create mode 100644 web/app/device/components/__tests__/authorize-account.spec.tsx diff --git a/web/app/device/components/__tests__/authorize-account.spec.tsx b/web/app/device/components/__tests__/authorize-account.spec.tsx new file mode 100644 index 0000000000..60dcd39993 --- /dev/null +++ b/web/app/device/components/__tests__/authorize-account.spec.tsx @@ -0,0 +1,69 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import AuthorizeAccount from '../authorize-account' + +const mockApproveAccount = vi.fn().mockResolvedValue(undefined) +const mockDenyAccount = vi.fn().mockResolvedValue(undefined) + +vi.mock('@/service/device-flow', () => ({ + deviceApproveAccount: (...args: unknown[]) => mockApproveAccount(...args), + deviceDenyAccount: (...args: unknown[]) => mockDenyAccount(...args), + DeviceFlowError: class extends Error { + code: string + status: number + constructor(code: string, status = 400) { + super(code) + this.code = code + this.status = status + } + }, +})) + +const makeProps = () => ({ + userCode: 'ABCD-3456', + accountEmail: 'gareth@example.com', + accountName: 'Gareth Chen', + accountAvatarUrl: null, + defaultWorkspace: 'Dify Enterprise', + onApproved: vi.fn(), + onDenied: vi.fn(), + onError: vi.fn(), +}) + +describe('AuthorizeAccount', () => { + beforeEach(() => vi.clearAllMocks()) + + it('renders accountName', () => { + render() + expect(screen.getByText('Gareth Chen')).toBeInTheDocument() + }) + + it('renders accountEmail', () => { + render() + expect(screen.getByText('gareth@example.com')).toBeInTheDocument() + }) + + it('renders defaultWorkspace', () => { + render() + expect(screen.getByText(/Dify Enterprise/)).toBeInTheDocument() + }) + + it('calls deviceApproveAccount with userCode on Authorize click', async () => { + render() + fireEvent.click(screen.getByRole('button', { name: /Authorize/i })) + await waitFor(() => expect(mockApproveAccount).toHaveBeenCalledWith('ABCD-3456')) + }) + + it('calls onApproved after successful approve', async () => { + const props = makeProps() + render() + fireEvent.click(screen.getByRole('button', { name: /Authorize/i })) + await waitFor(() => expect(props.onApproved).toHaveBeenCalled()) + }) + + it('calls deviceDenyAccount with userCode on Cancel click', async () => { + render() + fireEvent.click(screen.getByRole('button', { name: /Cancel/i })) + await waitFor(() => expect(mockDenyAccount).toHaveBeenCalledWith('ABCD-3456')) + }) +}) diff --git a/web/app/device/components/authorize-account.tsx b/web/app/device/components/authorize-account.tsx index 8bdc6ce03c..267ef3d9a5 100644 --- a/web/app/device/components/authorize-account.tsx +++ b/web/app/device/components/authorize-account.tsx @@ -1,6 +1,8 @@ 'use client' import type { FC } from 'react' +import { Avatar } from '@langgenius/dify-ui/avatar' +import { Button } from '@langgenius/dify-ui/button' import { useState } from 'react' import { deviceApproveAccount, deviceDenyAccount } from '@/service/device-flow' import { approveErrorCopy } from '../utils/error-copy' @@ -8,6 +10,8 @@ import { approveErrorCopy } from '../utils/error-copy' type Props = { userCode: string accountEmail?: string + accountName?: string + accountAvatarUrl?: string | null defaultWorkspace?: string onApproved: () => void onDenied: () => void @@ -21,7 +25,14 @@ type Props = { * the dfoa_ token server-side. */ const AuthorizeAccount: FC = ({ - userCode, accountEmail, defaultWorkspace, onApproved, onDenied, onError, + userCode, + accountEmail, + accountName, + accountAvatarUrl, + defaultWorkspace, + onApproved, + onDenied, + onError, }) => { const [busy, setBusy] = useState(false) @@ -54,41 +65,54 @@ const AuthorizeAccount: FC = ({ } return ( -
+

Authorize Dify CLI

- Dify CLI (difyctl) is requesting access to your account. - {' '}If you did not start this from your terminal, click Cancel. + difyctl is requesting access. If you didn't start this from your terminal, click Cancel.

-
- {accountEmail && ( -

- Signed in as {accountEmail} -

- )} - {defaultWorkspace && ( -

- Default workspace: {defaultWorkspace} -

- )} +
+ +
+ {accountName && ( +

{accountName}

+ )} + {accountEmail && ( +

{accountEmail}

+ )} +
+ {defaultWorkspace && ( +
+ Workspace: + {' '} + {defaultWorkspace} +
+ )}
- - +
) diff --git a/web/app/device/page.tsx b/web/app/device/page.tsx index 9efff20563..83def36a75 100644 --- a/web/app/device/page.tsx +++ b/web/app/device/page.tsx @@ -164,7 +164,6 @@ export default function DevicePage() {