minor ui improvements for profile
This commit is contained in:
@@ -1,29 +1,25 @@
|
|||||||
import { Suspense, useMemo } from 'react';
|
import { Suspense } from 'react';
|
||||||
import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
|
||||||
|
import ClickToCopy from '@/components/click-to-copy';
|
||||||
import { ListPropertiesIcon } from '@/components/events/list-properties-icon';
|
import { ListPropertiesIcon } from '@/components/events/list-properties-icon';
|
||||||
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
|
|
||||||
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
|
|
||||||
import { ProfileAvatar } from '@/components/profiles/profile-avatar';
|
import { ProfileAvatar } from '@/components/profiles/profile-avatar';
|
||||||
import { ChartSwitch } from '@/components/report/chart';
|
import { ChartSwitch } from '@/components/report/chart';
|
||||||
import { SerieIcon } from '@/components/report/chart/SerieIcon';
|
import { Tooltiper } from '@/components/ui/tooltip';
|
||||||
import { Widget, WidgetBody, WidgetHead } from '@/components/widget';
|
import { Widget, WidgetBody, WidgetHead } from '@/components/widget';
|
||||||
import {
|
import {
|
||||||
eventQueryFiltersParser,
|
eventQueryFiltersParser,
|
||||||
eventQueryNamesFilter,
|
eventQueryNamesFilter,
|
||||||
} from '@/hooks/useEventQueryFilters';
|
} from '@/hooks/useEventQueryFilters';
|
||||||
|
import { clipboard } from '@/utils/clipboard';
|
||||||
import { getProfileName } from '@/utils/getters';
|
import { getProfileName } from '@/utils/getters';
|
||||||
|
import { CopyIcon } from 'lucide-react';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { parseAsInteger, parseAsString } from 'nuqs';
|
import { parseAsInteger, parseAsString } from 'nuqs';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import type { GetEventListOptions } from '@openpanel/db';
|
import type { GetEventListOptions } from '@openpanel/db';
|
||||||
import {
|
import { getEventList, getEventsCount, getProfileById } from '@openpanel/db';
|
||||||
getConversionEventNames,
|
import type { IChartInput } from '@openpanel/validation';
|
||||||
getEventList,
|
|
||||||
getEventsCount,
|
|
||||||
getProfileById,
|
|
||||||
getProfileMetrics,
|
|
||||||
} from '@openpanel/db';
|
|
||||||
import type { IChartEvent, IChartInput } from '@openpanel/validation';
|
|
||||||
|
|
||||||
import { EventList } from '../../events/event-list';
|
import { EventList } from '../../events/event-list';
|
||||||
import { StickyBelowHeader } from '../../layout-sticky-below-header';
|
import { StickyBelowHeader } from '../../layout-sticky-below-header';
|
||||||
@@ -147,10 +143,12 @@ export default async function Page({
|
|||||||
<div className="flex flex-1 gap-4">
|
<div className="flex flex-1 gap-4">
|
||||||
<ProfileAvatar {...profile} size={'lg'} />
|
<ProfileAvatar {...profile} size={'lg'} />
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
|
<ClickToCopy value={profile.id}>
|
||||||
<h1 className="max-w-full overflow-hidden text-ellipsis break-words text-lg font-semibold md:max-w-sm md:whitespace-nowrap md:text-2xl">
|
<h1 className="max-w-full overflow-hidden text-ellipsis break-words text-lg font-semibold md:max-w-sm md:whitespace-nowrap md:text-2xl">
|
||||||
{getProfileName(profile)}
|
{getProfileName(profile)}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex items-center gap-4">
|
</ClickToCopy>
|
||||||
|
<div className="mt-1 flex items-center gap-4">
|
||||||
<ListPropertiesIcon {...profile.properties} />
|
<ListPropertiesIcon {...profile.properties} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export function ProfileList({ data, count, limit = 50 }: ProfileListProps) {
|
|||||||
<Link
|
<Link
|
||||||
href={`/${organizationSlug}/${projectId}/profiles/${profile.id}`}
|
href={`/${organizationSlug}/${projectId}/profiles/${profile.id}`}
|
||||||
className="flex items-center gap-2 font-medium"
|
className="flex items-center gap-2 font-medium"
|
||||||
|
title={getProfileName(profile, false)}
|
||||||
>
|
>
|
||||||
<ProfileAvatar size="sm" {...profile} />
|
<ProfileAvatar size="sm" {...profile} />
|
||||||
{getProfileName(profile)}
|
{getProfileName(profile)}
|
||||||
|
|||||||
30
apps/dashboard/src/components/click-to-copy.tsx
Normal file
30
apps/dashboard/src/components/click-to-copy.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { clipboard } from '@/utils/clipboard';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Tooltiper } from './ui/tooltip';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClickToCopy = ({ children, value }: Props) => {
|
||||||
|
return (
|
||||||
|
<Tooltiper
|
||||||
|
content="Click to copy"
|
||||||
|
asChild
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
clipboard(value);
|
||||||
|
toast('Copied to clipboard');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Tooltiper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClickToCopy;
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
import { SerieIcon } from '../report/chart/SerieIcon';
|
import { SerieIcon } from '../report/chart/SerieIcon';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
Tooltiper,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '../ui/tooltip';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
country?: string;
|
country?: string;
|
||||||
@@ -23,46 +28,26 @@ export function ListPropertiesIcon({
|
|||||||
referrer_type,
|
referrer_type,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1.5">
|
||||||
{country && (
|
{country && (
|
||||||
<Tooltip>
|
<Tooltiper content={[country, city].filter(Boolean).join(', ')}>
|
||||||
<TooltipTrigger>
|
|
||||||
<SerieIcon name={country} />
|
<SerieIcon name={country} />
|
||||||
</TooltipTrigger>
|
</Tooltiper>
|
||||||
<TooltipContent>
|
|
||||||
{country}, {city}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
{os && (
|
{os && (
|
||||||
<Tooltip>
|
<Tooltiper content={`${os} (${os_version})`}>
|
||||||
<TooltipTrigger>
|
|
||||||
<SerieIcon name={os} />
|
<SerieIcon name={os} />
|
||||||
</TooltipTrigger>
|
</Tooltiper>
|
||||||
<TooltipContent>
|
|
||||||
{os} ({os_version})
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
{browser && (
|
{browser && (
|
||||||
<Tooltip>
|
<Tooltiper content={`${browser} (${browser_version})`}>
|
||||||
<TooltipTrigger>
|
|
||||||
<SerieIcon name={browser} />
|
<SerieIcon name={browser} />
|
||||||
</TooltipTrigger>
|
</Tooltiper>
|
||||||
<TooltipContent>
|
|
||||||
{browser} ({browser_version})
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
{referrer_name && (
|
{referrer_name && (
|
||||||
<Tooltip>
|
<Tooltiper content={`${referrer_name} (${referrer_type})`}>
|
||||||
<TooltipTrigger>
|
|
||||||
<SerieIcon name={referrer_name} />
|
<SerieIcon name={referrer_name} />
|
||||||
</TooltipTrigger>
|
</Tooltiper>
|
||||||
<TooltipContent>
|
|
||||||
{referrer_name} ({referrer_type})
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -38,20 +38,22 @@ interface TooltiperProps {
|
|||||||
content: string;
|
content: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
export function Tooltiper({
|
export function Tooltiper({
|
||||||
asChild,
|
asChild,
|
||||||
content,
|
content,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
|
onClick,
|
||||||
}: TooltiperProps) {
|
}: TooltiperProps) {
|
||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip delayDuration={0}>
|
||||||
<TooltipTrigger asChild={asChild} className={className}>
|
<TooltipTrigger asChild={asChild} className={className} onClick={onClick}>
|
||||||
{children}
|
{children}
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
<TooltipContent>{content}</TooltipContent>
|
<TooltipContent sideOffset={10}>{content}</TooltipContent>
|
||||||
</TooltipPortal>
|
</TooltipPortal>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export function WidgetTable<T>({
|
|||||||
keyExtractor,
|
keyExtractor,
|
||||||
}: Props<T>) {
|
}: Props<T>) {
|
||||||
return (
|
return (
|
||||||
|
<div className="w-full overflow-x-auto">
|
||||||
<table className={cn('w-full', className)}>
|
<table className={cn('w-full', className)}>
|
||||||
<WidgetTableHead>
|
<WidgetTableHead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -57,5 +58,6 @@ export function WidgetTable<T>({
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import type { IServiceProfile } from '@openpanel/db';
|
import type { IServiceProfile } from '@openpanel/db';
|
||||||
|
|
||||||
export function getProfileName(profile: IServiceProfile | undefined | null) {
|
export function getProfileName(
|
||||||
|
profile: IServiceProfile | undefined | null,
|
||||||
|
short = true
|
||||||
|
) {
|
||||||
if (!profile) return 'Unknown';
|
if (!profile) return 'Unknown';
|
||||||
|
|
||||||
if (!profile.isExternal) {
|
if (!profile.isExternal) {
|
||||||
|
if (short) {
|
||||||
|
return profile.id.slice(0, 4) + '...' + profile.id.slice(-4);
|
||||||
|
}
|
||||||
return profile.id;
|
return profile.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user