diff --git a/apps/studio/components/interfaces/Auth/Users/UserHeader.tsx b/apps/studio/components/interfaces/Auth/Users/UserHeader.tsx index 1b1a721d56c..4cc9279e644 100644 --- a/apps/studio/components/interfaces/Auth/Users/UserHeader.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UserHeader.tsx @@ -16,26 +16,28 @@ export const UserHeader = ({ user }: { user: User }) => { return (
{isPhoneAuth ? ( -
-

{user.phone}

+
+

{user.phone}

} - className="px-1" + className="shrink-0 px-1" text={user?.phone ?? ''} />
) : isAnonUser ? ( <>

Anonymous user

-
-

{user.id}

+
+

+ {user.id} +

} - className="px-1" + className="shrink-0 px-1" text={user?.id ?? ''} />
@@ -43,15 +45,20 @@ export const UserHeader = ({ user }: { user: User }) => { ) : ( <> {hasDisplayName &&

{displayName}

} -
-

+

+

{user.email}

} - className="px-1" + className="shrink-0 px-1" text={user?.email ?? ''} />
diff --git a/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx b/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx index 1a4da14e9e0..cebe8c3325f 100644 --- a/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx @@ -31,8 +31,9 @@ import { timeout } from '@/lib/helpers' const DATE_FORMAT = 'DD MMM, YYYY HH:mm' const CONTAINER_CLASS = cn( - 'bg-surface-100 border-default text-foreground flex items-center justify-between', - 'gap-x-4 border px-5 py-4 text-sm first:rounded-tr first:rounded-tl last:rounded-br last:rounded-bl' + 'bg-surface-100 border-default text-foreground flex flex-col items-stretch justify-between', + 'gap-y-3 border px-5 py-4 text-sm sm:flex-row sm:items-center sm:gap-x-4 sm:gap-y-0', + 'first:rounded-tr first:rounded-tl last:rounded-br last:rounded-bl' ) interface UserOverviewProps { @@ -485,10 +486,10 @@ export const UserOverview = ({ user, onDeleteSuccess }: UserOverviewProps) => { export const RowData = ({ property, value }: { property: string; value?: string | boolean }) => { return ( <> -
-

{property}

+
+

{property}

{typeof value === 'boolean' ? ( -
+
{value ? (
@@ -500,14 +501,16 @@ export const RowData = ({ property, value }: { property: string; value?: string )}
) : ( -
-

{!value ? '-' : value}

+
+

+ {!value ? '-' : value} +

{!!value && ( } - className="transition opacity-0 group-hover:opacity-100 px-1" + className="shrink-0 px-1 opacity-100 transition sm:opacity-0 sm:group-hover:opacity-100" text={value} /> )} diff --git a/apps/studio/components/interfaces/Auth/Users/UserPanel.test.tsx b/apps/studio/components/interfaces/Auth/Users/UserPanel.test.tsx index 23b4caea738..94dc0eeb142 100644 --- a/apps/studio/components/interfaces/Auth/Users/UserPanel.test.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UserPanel.test.tsx @@ -1,7 +1,7 @@ import { screen } from '@testing-library/react' import { http, HttpResponse } from 'msw' import { ResizablePanelGroup } from 'ui' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { UserPanel } from './UserPanel' import type { User } from '@/data/auth/users-infinite-query' @@ -69,10 +69,16 @@ const renderPanel = (disabledFeatures: string[] = []) => { } describe('UserPanel', () => { + const desktopWidth = window.innerWidth + beforeEach(() => { vi.clearAllMocks() }) + afterEach(() => { + Object.defineProperty(window, 'innerWidth', { configurable: true, value: desktopWidth }) + }) + it('shows the Logs tab when logs:all is enabled', async () => { renderPanel([]) @@ -88,4 +94,14 @@ describe('UserPanel', () => { expect(screen.queryByRole('tab', { name: 'Logs' })).not.toBeInTheDocument() expect(screen.getByRole('tab', { name: 'Raw JSON' })).toBeInTheDocument() }) + + it('uses a full-width dialog on mobile', async () => { + Object.defineProperty(window, 'innerWidth', { configurable: true, value: 390 }) + + renderPanel([]) + + const dialog = await screen.findByRole('dialog', { name: 'User details' }) + expect(dialog).toHaveClass('w-screen') + expect(screen.getByRole('button', { name: 'Close user details' })).toBeInTheDocument() + }) }) diff --git a/apps/studio/components/interfaces/Auth/Users/UserPanel.tsx b/apps/studio/components/interfaces/Auth/Users/UserPanel.tsx index f9d7342f6ed..25a5e6d4df3 100644 --- a/apps/studio/components/interfaces/Auth/Users/UserPanel.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UserPanel.tsx @@ -1,5 +1,6 @@ import { X } from 'lucide-react' import { parseAsString, useQueryState } from 'nuqs' +import { Dialog, VisuallyHidden } from 'radix-ui' import { useState } from 'react' import { Button, @@ -7,10 +8,12 @@ import { Input, ResizableHandle, ResizablePanel, + SidePanel, Tabs, TabsContent, TabsList, TabsTrigger, + useIsMobile, } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { SimpleCodeBlock } from 'ui-patterns/SimpleCodeBlock' @@ -26,6 +29,7 @@ import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' export const UserPanel = () => { const { data: project } = useSelectedProjectQuery() const showLogs = useIsFeatureEnabled('logs:all') + const isMobile = useIsMobile() const [selectedId, setSelectedId] = useQueryState( 'show', @@ -56,99 +60,131 @@ export const UserPanel = () => { }, {} as Partial) : {} + const content = ( + <> + +
+ + {JSON.stringify(filteredProperties, null, 2)} + + + + ) : ( +
+

+ Unable to find user with the following ID in project +

+

ID: {selectedId}

+
+ )} + + + ) + + if (isMobile) { + return ( + setSelectedId(null)} + > + + User details + + {content} + + ) + } + return ( <> - - -
- - {JSON.stringify(filteredProperties, null, 2)} - - - - ) : ( -
-

- Unable to find user with the following ID in project -

-

ID: {selectedId}

-
- )} - + + {content} )