From 7471db16b22b9b7c14e8d3d1d2051f5a27894cca Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Sun, 12 Jan 2025 13:12:31 +0100 Subject: [PATCH] feat(chat): add permission card avatars --- apps/chat/src/i18n/packs/i18n-lang-en.ts | 7 ++-- apps/chat/src/i18n/packs/i18n-lang-pl.ts | 7 ++-- .../card/card-record-permissions.tsx | 17 ++++++++++ .../permissions/card/card-record-public.tsx | 26 ++++++++++++++ .../card/card-record-shared-with.tsx | 31 +++++++++++++++++ .../src/modules/permissions/card/index.ts | 3 ++ apps/chat/src/modules/permissions/index.ts | 1 + .../list/permission-avatars-list.tsx | 6 +++- .../list/permission-group-avatar.tsx | 7 ++-- .../list/permission-user-avatar.tsx | 7 ++-- .../status/permissions-status-icon.tsx | 4 +-- .../modules/projects/grid/project-card.tsx | 34 ++++++++++++------- packages/ui/src/components/avatar/avatar.tsx | 3 +- .../components/avatar/colorized-avatar.tsx | 15 ++++---- 14 files changed, 135 insertions(+), 33 deletions(-) create mode 100644 apps/chat/src/modules/permissions/card/card-record-permissions.tsx create mode 100644 apps/chat/src/modules/permissions/card/card-record-public.tsx create mode 100644 apps/chat/src/modules/permissions/card/card-record-shared-with.tsx create mode 100644 apps/chat/src/modules/permissions/card/index.ts diff --git a/apps/chat/src/i18n/packs/i18n-lang-en.ts b/apps/chat/src/i18n/packs/i18n-lang-en.ts index cab1f108..191eb55c 100644 --- a/apps/chat/src/i18n/packs/i18n-lang-en.ts +++ b/apps/chat/src/i18n/packs/i18n-lang-en.ts @@ -482,9 +482,12 @@ export const I18N_PACK_EN = deepmerge(I18N_FORWARDED_EN_PACK, { permissions: { share: 'Share', accessLevels: I18N_ACCESS_LEVELS_EN, + card: { + sharedWith: 'Shared with', + }, status: { - publicTooltip: 'Everyone in organization can see this project', - privateTooltip: 'Only shared users and groups can see this project', + publicTooltip: 'Everyone in organization can see this', + privateTooltip: 'Only shared users and groups can see this', }, modal: { title: 'Share resource', diff --git a/apps/chat/src/i18n/packs/i18n-lang-pl.ts b/apps/chat/src/i18n/packs/i18n-lang-pl.ts index 2ab9bb25..fc19165b 100644 --- a/apps/chat/src/i18n/packs/i18n-lang-pl.ts +++ b/apps/chat/src/i18n/packs/i18n-lang-pl.ts @@ -484,9 +484,12 @@ export const I18N_PACK_PL: I18nLangPack = deepmerge(I18N_FORWARDED_PL_PACK, { permissions: { share: 'Udostępnij', accessLevels: I18N_ACCESS_LEVELS_PL, + card: { + sharedWith: 'Udostępniono dla', + }, status: { - publicTooltip: 'Wszyscy w organizacji widzą ten projekt', - privateTooltip: 'Tylko udostępnieni użytkownicy i grupy widzą ten projekt', + publicTooltip: 'Wszyscy w organizacji to widzą', + privateTooltip: 'Tylko udostępnieni użytkownicy i grupy to widzą', }, modal: { title: 'Udostępnij', diff --git a/apps/chat/src/modules/permissions/card/card-record-permissions.tsx b/apps/chat/src/modules/permissions/card/card-record-permissions.tsx new file mode 100644 index 00000000..11dc86ef --- /dev/null +++ b/apps/chat/src/modules/permissions/card/card-record-permissions.tsx @@ -0,0 +1,17 @@ +import { isSdkPublicPermissions, type SdkPermissionT } from '@llm/sdk'; + +import { CardRecordPublic } from './card-record-public'; +import { CardRecordSharedWith } from './card-record-shared-with'; + +type Props = { + permissions: SdkPermissionT[]; + className?: string; +}; + +export function CardRecordPermissions({ permissions, ...props }: Props) { + if (isSdkPublicPermissions(permissions)) { + return ; + } + + return ; +} diff --git a/apps/chat/src/modules/permissions/card/card-record-public.tsx b/apps/chat/src/modules/permissions/card/card-record-public.tsx new file mode 100644 index 00000000..82e8318a --- /dev/null +++ b/apps/chat/src/modules/permissions/card/card-record-public.tsx @@ -0,0 +1,26 @@ +import clsx from 'clsx'; +import { GlobeIcon } from 'lucide-react'; + +import type { SdkPermissionT } from '@llm/sdk'; + +import { useI18n } from '~/i18n'; + +type Props = { + permissions: SdkPermissionT[]; + className?: string; +}; + +export function CardRecordPublic({ className }: Props) { + const t = useI18n().pack.permissions.status; + + return ( +
+
+ + + {t.publicTooltip} + +
+
+ ); +} diff --git a/apps/chat/src/modules/permissions/card/card-record-shared-with.tsx b/apps/chat/src/modules/permissions/card/card-record-shared-with.tsx new file mode 100644 index 00000000..9ed927d3 --- /dev/null +++ b/apps/chat/src/modules/permissions/card/card-record-shared-with.tsx @@ -0,0 +1,31 @@ +import clsx from 'clsx'; +import { UsersIcon } from 'lucide-react'; + +import type { SdkPermissionT } from '@llm/sdk'; + +import { useI18n } from '~/i18n'; + +import { PermissionAvatarsList } from '../list/permission-avatars-list'; + +type Props = { + permissions: SdkPermissionT[]; + className?: string; +}; + +export function CardRecordSharedWith({ permissions, className }: Props) { + const t = useI18n().pack.permissions.card; + + return ( +
+
+ + + {t.sharedWith} + : + +
+ + +
+ ); +} diff --git a/apps/chat/src/modules/permissions/card/index.ts b/apps/chat/src/modules/permissions/card/index.ts new file mode 100644 index 00000000..98d67e36 --- /dev/null +++ b/apps/chat/src/modules/permissions/card/index.ts @@ -0,0 +1,3 @@ +export * from './card-record-permissions'; +export * from './card-record-public'; +export * from './card-record-shared-with'; diff --git a/apps/chat/src/modules/permissions/index.ts b/apps/chat/src/modules/permissions/index.ts index afb1624f..c13b1a1e 100644 --- a/apps/chat/src/modules/permissions/index.ts +++ b/apps/chat/src/modules/permissions/index.ts @@ -1,3 +1,4 @@ +export * from './card'; export * from './controls'; export * from './list'; export * from './share-resource'; diff --git a/apps/chat/src/modules/permissions/list/permission-avatars-list.tsx b/apps/chat/src/modules/permissions/list/permission-avatars-list.tsx index a3031156..cc9e3b75 100644 --- a/apps/chat/src/modules/permissions/list/permission-avatars-list.tsx +++ b/apps/chat/src/modules/permissions/list/permission-avatars-list.tsx @@ -1,6 +1,7 @@ import clsx from 'clsx'; import type { SdkPermissionT } from '@llm/sdk'; +import type { ColorizedAvatarSize } from '@llm/ui'; import { PermissionGroupAvatar } from './permission-group-avatar'; import { PermissionUserAvatar } from './permission-user-avatar'; @@ -8,9 +9,10 @@ import { PermissionUserAvatar } from './permission-user-avatar'; type Props = { permissions: SdkPermissionT[]; className?: string; + size?: ColorizedAvatarSize; }; -export function PermissionAvatarsList({ permissions, className }: Props) { +export function PermissionAvatarsList({ permissions, className, size }: Props) { return (
{permissions.map((permission) => { @@ -23,6 +25,7 @@ export function PermissionAvatarsList({ permissions, className }: Props) {
); @@ -33,6 +36,7 @@ export function PermissionAvatarsList({ permissions, className }: Props) { ); diff --git a/apps/chat/src/modules/permissions/list/permission-group-avatar.tsx b/apps/chat/src/modules/permissions/list/permission-group-avatar.tsx index d692aae1..3d074b2e 100644 --- a/apps/chat/src/modules/permissions/list/permission-group-avatar.tsx +++ b/apps/chat/src/modules/permissions/list/permission-group-avatar.tsx @@ -2,18 +2,19 @@ import { Users2 } from 'lucide-react'; import type { SdkTableRowWithIdNameT } from '@llm/sdk'; -import { Avatar, Tooltip } from '@llm/ui'; +import { Avatar, type AvatarSize, Tooltip } from '@llm/ui'; type Props = { group: SdkTableRowWithIdNameT; accessLevel: string; + size?: AvatarSize; }; -export function PermissionGroupAvatar({ group, accessLevel }: Props) { +export function PermissionGroupAvatar({ group, accessLevel, size = 'sm' }: Props) { return ( } /> diff --git a/apps/chat/src/modules/permissions/list/permission-user-avatar.tsx b/apps/chat/src/modules/permissions/list/permission-user-avatar.tsx index db407432..dfcb63a1 100644 --- a/apps/chat/src/modules/permissions/list/permission-user-avatar.tsx +++ b/apps/chat/src/modules/permissions/list/permission-user-avatar.tsx @@ -1,14 +1,15 @@ import type { SdkPermissionAccessLevelT, SdkUserListItemT } from '@llm/sdk'; -import { ColorizedAvatar, Tooltip } from '@llm/ui'; +import { ColorizedAvatar, type ColorizedAvatarSize, Tooltip } from '@llm/ui'; import { useI18n } from '~/i18n'; type Props = { user: SdkUserListItemT; accessLevel: SdkPermissionAccessLevelT; + size?: ColorizedAvatarSize; }; -export function PermissionUserAvatar({ user, accessLevel }: Props) { +export function PermissionUserAvatar({ user, accessLevel, size = 'sm' }: Props) { const { accessLevels } = useI18n().pack.permissions; return ( @@ -17,7 +18,7 @@ export function PermissionUserAvatar({ user, accessLevel }: Props) { diff --git a/apps/chat/src/modules/permissions/status/permissions-status-icon.tsx b/apps/chat/src/modules/permissions/status/permissions-status-icon.tsx index e55c763d..5f864ca5 100644 --- a/apps/chat/src/modules/permissions/status/permissions-status-icon.tsx +++ b/apps/chat/src/modules/permissions/status/permissions-status-icon.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import { EyeIcon, LockIcon } from 'lucide-react'; +import { EyeIcon, UsersIcon } from 'lucide-react'; import { isSdkPublicPermissions, type SdkPermissionT } from '@llm/sdk'; import { Tooltip } from '@llm/ui'; @@ -22,7 +22,7 @@ export function PermissionsStatusIcon({ permissions, className }: Props) { {( isPublic ? - : + : )} ); diff --git a/apps/chat/src/modules/projects/grid/project-card.tsx b/apps/chat/src/modules/projects/grid/project-card.tsx index 75446852..dabf55d9 100644 --- a/apps/chat/src/modules/projects/grid/project-card.tsx +++ b/apps/chat/src/modules/projects/grid/project-card.tsx @@ -16,6 +16,7 @@ import { CardTitle, useArchiveWithNotifications, } from '@llm/ui'; +import { CardRecordPermissions } from '~/modules/permissions/card'; import { useSitemap } from '~/routes'; import { useProjectUpdateModal } from '../form'; @@ -48,21 +49,28 @@ export function ProjectCard({ project, onAfterEdit, onAfterArchive }: ProjectCar {project.name} - {description - ? ( - - {description} - - ) - :
} +
+ {project.permissions && ( + + )} + + {description && ( + + {description} + + )} - -
- {formatDate(project.updatedAt)} -
+ +
+ {formatDate(project.updatedAt)} +
- -
+ +
+
{!project.archived && ( diff --git a/packages/ui/src/components/avatar/avatar.tsx b/packages/ui/src/components/avatar/avatar.tsx index 35a79e2e..e81134fb 100644 --- a/packages/ui/src/components/avatar/avatar.tsx +++ b/packages/ui/src/components/avatar/avatar.tsx @@ -2,9 +2,10 @@ import type { ReactNode } from 'react'; import clsx from 'clsx'; -type AvatarSize = 'sm' | 'md' | 'lg'; +export type AvatarSize = 'sm' | 'md' | 'lg' | 'xs'; const sizeClasses: Record = { + xs: 'w-6 h-6', sm: 'w-8 h-8', md: 'w-10 h-10', lg: 'w-12 h-12', diff --git a/packages/ui/src/components/avatar/colorized-avatar.tsx b/packages/ui/src/components/avatar/colorized-avatar.tsx index 3489bd5b..d9d8c8f0 100644 --- a/packages/ui/src/components/avatar/colorized-avatar.tsx +++ b/packages/ui/src/components/avatar/colorized-avatar.tsx @@ -3,13 +3,14 @@ import { useMemo } from 'react'; import type { SdkTableRowIdT } from '@llm/sdk'; -const sizeClasses = { +const SIZE_CLASSES = { + xs: 'h-6 w-6 text-xs', sm: 'h-8 w-8 text-sm', md: 'h-10 w-10 text-base', lg: 'h-12 w-12 text-lg', } as const; -const colors = [ +const COLORS = [ 'bg-red-100 text-red-800 border-2 border-red-200', 'bg-blue-100 text-blue-800 border-2 border-blue-200', 'bg-green-100 text-green-800 border-2 border-green-200', @@ -22,11 +23,13 @@ const colors = [ let COLOR_COUNTER = 0; const COLOR_CACHE = new Map(); +export type ColorizedAvatarSize = keyof typeof SIZE_CLASSES; + type Props = { className?: string; id: SdkTableRowIdT; name: string; - size?: keyof typeof sizeClasses; + size?: ColorizedAvatarSize; }; export function ColorizedAvatar({ id, className, name, size = 'md' }: Props) { @@ -35,9 +38,9 @@ export function ColorizedAvatar({ id, className, name, size = 'md' }: Props) { const cacheKey = `${name}#${id}`; if (!COLOR_CACHE.has(cacheKey)) { - const colorIndex = COLOR_COUNTER % colors.length; + const colorIndex = COLOR_COUNTER % COLORS.length; - COLOR_CACHE.set(cacheKey, colors[colorIndex]); + COLOR_CACHE.set(cacheKey, COLORS[colorIndex]); COLOR_COUNTER++; } @@ -48,7 +51,7 @@ export function ColorizedAvatar({ id, className, name, size = 'md' }: Props) {