From 2e83d37ef12d8c0b7c549a9f9fb268525751ac75 Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Sat, 11 Jan 2025 12:16:06 +0100 Subject: [PATCH 01/22] feat(chat): init base layout for project content --- apps/chat/src/i18n/packs/i18n-lang-en.ts | 5 ++ apps/chat/src/i18n/packs/i18n-lang-pl.ts | 5 ++ apps/chat/src/modules/index.ts | 7 +- apps/chat/src/modules/permissions/index.ts | 2 + .../src/modules/permissions/list/index.ts | 1 + .../list/permission-avatars-list.tsx | 38 ++++++++++ .../list/permission-group-avatar.tsx | 22 ++++++ .../list/permission-user-avatar.tsx | 22 ++++++ .../permissions/share-resource/index.ts | 4 ++ .../share-resource/share-resource-button.tsx | 55 ++++++++++++++ .../share-resource-form-modal.tsx | 58 +++++++++++++++ .../use-share-resource-form.tsx | 18 +++++ .../use-share-resource-modal.tsx | 26 +++++++ .../src/routes/project/project-content.tsx | 46 ++++++++---- packages/ui/package.json | 1 + packages/ui/src/components/avatar/avatar.tsx | 54 ++++++++++++++ packages/ui/src/components/avatar/index.ts | 1 + packages/ui/src/components/index.ts | 2 + packages/ui/src/components/tooltip/index.ts | 1 + .../ui/src/components/tooltip/tooltip.tsx | 72 +++++++++++++++++++ yarn.lock | 72 +++++++++++-------- 21 files changed, 471 insertions(+), 41 deletions(-) create mode 100644 apps/chat/src/modules/permissions/index.ts create mode 100644 apps/chat/src/modules/permissions/list/index.ts create mode 100644 apps/chat/src/modules/permissions/list/permission-avatars-list.tsx create mode 100644 apps/chat/src/modules/permissions/list/permission-group-avatar.tsx create mode 100644 apps/chat/src/modules/permissions/list/permission-user-avatar.tsx create mode 100644 apps/chat/src/modules/permissions/share-resource/index.ts create mode 100644 apps/chat/src/modules/permissions/share-resource/share-resource-button.tsx create mode 100644 apps/chat/src/modules/permissions/share-resource/share-resource-form-modal.tsx create mode 100644 apps/chat/src/modules/permissions/share-resource/use-share-resource-form.tsx create mode 100644 apps/chat/src/modules/permissions/share-resource/use-share-resource-modal.tsx create mode 100644 packages/ui/src/components/avatar/avatar.tsx create mode 100644 packages/ui/src/components/avatar/index.ts create mode 100644 packages/ui/src/components/tooltip/index.ts create mode 100644 packages/ui/src/components/tooltip/tooltip.tsx diff --git a/apps/chat/src/i18n/packs/i18n-lang-en.ts b/apps/chat/src/i18n/packs/i18n-lang-en.ts index 43d42a8a..de0882ce 100644 --- a/apps/chat/src/i18n/packs/i18n-lang-en.ts +++ b/apps/chat/src/i18n/packs/i18n-lang-en.ts @@ -469,6 +469,11 @@ export const I18N_PACK_EN = deepmerge(I18N_FORWARDED_EN_PACK, { totalUsers: 'Total users', }, }, + permissions: { + shareResource: { + share: 'Share', + }, + }, footer: { copyright: 'Open Source AI Platform', madeWith: 'Made with', diff --git a/apps/chat/src/i18n/packs/i18n-lang-pl.ts b/apps/chat/src/i18n/packs/i18n-lang-pl.ts index ecd0c906..f28a1043 100644 --- a/apps/chat/src/i18n/packs/i18n-lang-pl.ts +++ b/apps/chat/src/i18n/packs/i18n-lang-pl.ts @@ -471,6 +471,11 @@ export const I18N_PACK_PL: I18nLangPack = deepmerge(I18N_FORWARDED_PL_PACK, { totalUsers: 'Użytkowników', }, }, + permissions: { + shareResource: { + share: 'Udostępnij', + }, + }, footer: { copyright: 'Platforma AI Open Source', madeWith: 'Stworzone z', diff --git a/apps/chat/src/modules/index.ts b/apps/chat/src/modules/index.ts index b73cf1d5..1aca776b 100644 --- a/apps/chat/src/modules/index.ts +++ b/apps/chat/src/modules/index.ts @@ -1,9 +1,14 @@ export * from './ai-models'; export * from './apps'; -export * from './apps-creator/creator'; +export * from './apps-categories'; +export * from './apps-creator'; export * from './chats'; export * from './experts'; export * from './organizations'; +export * from './permissions'; export * from './projects'; +export * from './projects-embeddings'; export * from './shared'; +export * from './users'; +export * from './users-groups'; export * from './workspace'; diff --git a/apps/chat/src/modules/permissions/index.ts b/apps/chat/src/modules/permissions/index.ts new file mode 100644 index 00000000..2cdbd7a2 --- /dev/null +++ b/apps/chat/src/modules/permissions/index.ts @@ -0,0 +1,2 @@ +export * from './list'; +export * from './share-resource'; diff --git a/apps/chat/src/modules/permissions/list/index.ts b/apps/chat/src/modules/permissions/list/index.ts new file mode 100644 index 00000000..f8542952 --- /dev/null +++ b/apps/chat/src/modules/permissions/list/index.ts @@ -0,0 +1 @@ +export * from './permission-avatars-list'; diff --git a/apps/chat/src/modules/permissions/list/permission-avatars-list.tsx b/apps/chat/src/modules/permissions/list/permission-avatars-list.tsx new file mode 100644 index 00000000..366548f3 --- /dev/null +++ b/apps/chat/src/modules/permissions/list/permission-avatars-list.tsx @@ -0,0 +1,38 @@ +import clsx from 'clsx'; + +import type { SdkPermissionT } from '@llm/sdk'; + +import { PermissionGroupAvatar } from './permission-group-avatar'; +import { PermissionUserAvatar } from './permission-user-avatar'; + +type Props = { + permissions: SdkPermissionT[]; + className?: string; +}; + +export function PermissionAvatarsList({ permissions, className }: Props) { + return ( +
+ {permissions.map(permission => ( +
+ {'user' in permission.target + ? ( + + ) + : ( + + )} +
+ ))} +
+ ); +} diff --git a/apps/chat/src/modules/permissions/list/permission-group-avatar.tsx b/apps/chat/src/modules/permissions/list/permission-group-avatar.tsx new file mode 100644 index 00000000..d692aae1 --- /dev/null +++ b/apps/chat/src/modules/permissions/list/permission-group-avatar.tsx @@ -0,0 +1,22 @@ +import { Users2 } from 'lucide-react'; + +import type { SdkTableRowWithIdNameT } from '@llm/sdk'; + +import { Avatar, Tooltip } from '@llm/ui'; + +type Props = { + group: SdkTableRowWithIdNameT; + accessLevel: string; +}; + +export function PermissionGroupAvatar({ group, accessLevel }: 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 new file mode 100644 index 00000000..ae741862 --- /dev/null +++ b/apps/chat/src/modules/permissions/list/permission-user-avatar.tsx @@ -0,0 +1,22 @@ +import { UserCircle2 } from 'lucide-react'; + +import type { SdkUserListItemT } from '@llm/sdk'; + +import { Avatar, Tooltip } from '@llm/ui'; + +type Props = { + user: SdkUserListItemT; + accessLevel: string; +}; + +export function PermissionUserAvatar({ user, accessLevel }: Props) { + return ( + + } + /> + + ); +} diff --git a/apps/chat/src/modules/permissions/share-resource/index.ts b/apps/chat/src/modules/permissions/share-resource/index.ts new file mode 100644 index 00000000..7a090d05 --- /dev/null +++ b/apps/chat/src/modules/permissions/share-resource/index.ts @@ -0,0 +1,4 @@ +export * from './share-resource-button'; +export * from './share-resource-form-modal'; +export * from './use-share-resource-form'; +export * from './use-share-resource-modal'; diff --git a/apps/chat/src/modules/permissions/share-resource/share-resource-button.tsx b/apps/chat/src/modules/permissions/share-resource/share-resource-button.tsx new file mode 100644 index 00000000..02a18ccc --- /dev/null +++ b/apps/chat/src/modules/permissions/share-resource/share-resource-button.tsx @@ -0,0 +1,55 @@ +import { taskEither as TE } from 'fp-ts'; +import { flow, pipe } from 'fp-ts/lib/function'; +import { Share2Icon } from 'lucide-react'; + +import type { SdkPermissionT } from '@llm/sdk'; + +import { type TaggedError, tapTaskEitherError } from '@llm/commons'; +import { useAsyncCallback } from '@llm/commons-front'; +import { FormSpinnerCTA, useSaveErrorNotification } from '@llm/ui'; +import { useI18n } from '~/i18n'; + +import { useShareResourceModal } from './use-share-resource-modal'; + +type Props = { + className?: string; + defaultValue: SdkPermissionT[]; + onSubmit: (value: SdkPermissionT[]) => TE.TaskEither, unknown>; +}; + +export function ShareResourceButton( + { + className, + defaultValue, + onSubmit, + }: Props, +) { + const t = useI18n().pack.permissions.shareResource; + const modal = useShareResourceModal(); + const showErrorNotification = useSaveErrorNotification(); + + const [onOpen, submitState] = useAsyncCallback( + pipe( + modal.showAsOptional({ + defaultValue, + }), + TE.fromTaskOption(() => TE.left(undefined)), + TE.tap(flow( + onSubmit, + tapTaskEitherError(showErrorNotification), + )), + ), + ); + + return ( + + + + {t.share} + + ); +} diff --git a/apps/chat/src/modules/permissions/share-resource/share-resource-form-modal.tsx b/apps/chat/src/modules/permissions/share-resource/share-resource-form-modal.tsx new file mode 100644 index 00000000..2adf3457 --- /dev/null +++ b/apps/chat/src/modules/permissions/share-resource/share-resource-form-modal.tsx @@ -0,0 +1,58 @@ +import type { CanBePromise } from '@llm/commons'; +import type { SdkPermissionT } from '@llm/sdk'; + +import { + CancelButton, + Modal, + type ModalProps, + ModalTitle, + UpdateButton, +} from '@llm/ui'; +import { useI18n } from '~/i18n'; + +import { useShareResourceForm } from './use-share-resource-form'; + +export type ShareResourceFormModalProps = + & Omit + & { + defaultValue: SdkPermissionT[]; + onSubmit: (value: SdkPermissionT[]) => CanBePromise; + }; + +export function ShareResourceFormModal( + { + defaultValue, + onSubmit, + onClose, + ...props + }: ShareResourceFormModalProps, +) { + const t = useI18n().pack.projects.form; + const { handleSubmitEvent, submitState } = useShareResourceForm({ + defaultValue, + onSubmit, + }); + + return ( + + {t.title.edit} + + )} + footer={( + <> + + + + )} + > + AVC + + ); +} diff --git a/apps/chat/src/modules/permissions/share-resource/use-share-resource-form.tsx b/apps/chat/src/modules/permissions/share-resource/use-share-resource-form.tsx new file mode 100644 index 00000000..08ba1978 --- /dev/null +++ b/apps/chat/src/modules/permissions/share-resource/use-share-resource-form.tsx @@ -0,0 +1,18 @@ +import { type FormHookAttrs, useForm } from '@under-control/forms'; + +import type { SdkPermissionT } from '@llm/sdk'; + +type ShareResourceFormHookAttrs = Omit< + FormHookAttrs, + 'validation' +>; + +export function useShareResourceForm(props: ShareResourceFormHookAttrs) { + return useForm({ + resetAfterSubmit: false, + validation: { + mode: ['blur', 'submit'], + }, + ...props, + }); +} diff --git a/apps/chat/src/modules/permissions/share-resource/use-share-resource-modal.tsx b/apps/chat/src/modules/permissions/share-resource/use-share-resource-modal.tsx new file mode 100644 index 00000000..ecdff60c --- /dev/null +++ b/apps/chat/src/modules/permissions/share-resource/use-share-resource-modal.tsx @@ -0,0 +1,26 @@ +import type { SdkPermissionT } from '@llm/sdk'; + +import { useAnimatedModal } from '@llm/commons-front'; + +import { ShareResourceFormModal } from './share-resource-form-modal'; + +type ProjectShowModalProps = { + defaultValue: SdkPermissionT[]; +}; + +export function useShareResourceModal() { + return useAnimatedModal({ + renderModalContent: ({ showProps, hiding, onAnimatedClose }) => ( + { + void onAnimatedClose(value); + }} + onClose={() => { + void onAnimatedClose(); + }} + /> + ), + }); +} diff --git a/apps/chat/src/routes/project/project-content.tsx b/apps/chat/src/routes/project/project-content.tsx index b7275509..35beda62 100644 --- a/apps/chat/src/routes/project/project-content.tsx +++ b/apps/chat/src/routes/project/project-content.tsx @@ -1,7 +1,13 @@ +import { taskEither as TE } from 'fp-ts'; + import type { SdkProjectT } from '@llm/sdk'; import { useI18n } from '~/i18n'; import { ChatsContainer, StartChatForm } from '~/modules'; +import { + PermissionAvatarsList, + ShareResourceButton, +} from '~/modules/permissions'; import { ProjectFilesListContainer } from '~/modules/projects/files'; type Props = { @@ -10,27 +16,43 @@ type Props = { export function ProjectContent({ project }: Props) { const t = useI18n().pack.routes.project; + const { permissions } = project; return ( -
-
-

+
+
+

{t.hello}

- +
+ + TE.fromTask(async () => { + // eslint-disable-next-line no-console + console.log(permissions); + })} + /> +
+
-
+
+
+ -

- {t.chats} -

+
- -
+

+ {t.chats} +

+ + +
-
- +
+ +
); diff --git a/packages/ui/package.json b/packages/ui/package.json index 008b8816..b63078b8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -25,6 +25,7 @@ "check:types": "tsc" }, "dependencies": { + "@floating-ui/react": "^0.27.3", "@llm/commons": "*", "@llm/commons-front": "*", "@llm/sdk": "*", diff --git a/packages/ui/src/components/avatar/avatar.tsx b/packages/ui/src/components/avatar/avatar.tsx new file mode 100644 index 00000000..35a79e2e --- /dev/null +++ b/packages/ui/src/components/avatar/avatar.tsx @@ -0,0 +1,54 @@ +import type { ReactNode } from 'react'; + +import clsx from 'clsx'; + +type AvatarSize = 'sm' | 'md' | 'lg'; + +const sizeClasses: Record = { + sm: 'w-8 h-8', + md: 'w-10 h-10', + lg: 'w-12 h-12', +}; + +type Props = { + size?: AvatarSize; + name: string; + src?: string | null; + fallback?: ReactNode; + className?: string; +}; + +export function Avatar({ size = 'md', name, src, fallback, className }: Props) { + const initials = name + .split(' ') + .map(part => part[0]) + .join('') + .toUpperCase() + .slice(0, 2); + + return ( +
+ {src + ? ( + {name} + ) + : fallback + ? ( +
{fallback}
+ ) + : ( + {initials} + )} +
+ ); +} diff --git a/packages/ui/src/components/avatar/index.ts b/packages/ui/src/components/avatar/index.ts new file mode 100644 index 00000000..a7424ca3 --- /dev/null +++ b/packages/ui/src/components/avatar/index.ts @@ -0,0 +1 @@ +export * from './avatar'; diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 34b393d8..58ff07b8 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -1,4 +1,5 @@ export * from './alert'; +export * from './avatar'; export * from './balloon'; export * from './card'; export * from './collapsible-panel'; @@ -16,4 +17,5 @@ export * from './skeleton'; export * from './spinner-container'; export * from './table'; export * from './tabs'; +export * from './tooltip'; export * from './tutorial-box'; diff --git a/packages/ui/src/components/tooltip/index.ts b/packages/ui/src/components/tooltip/index.ts new file mode 100644 index 00000000..ed8326d5 --- /dev/null +++ b/packages/ui/src/components/tooltip/index.ts @@ -0,0 +1 @@ +export * from './tooltip'; diff --git a/packages/ui/src/components/tooltip/tooltip.tsx b/packages/ui/src/components/tooltip/tooltip.tsx new file mode 100644 index 00000000..c69dd4e2 --- /dev/null +++ b/packages/ui/src/components/tooltip/tooltip.tsx @@ -0,0 +1,72 @@ +import { + autoUpdate, + flip, + FloatingPortal, + offset, + shift, + useDismiss, + useFloating, + useFocus, + useHover, + useInteractions, + useRole, +} from '@floating-ui/react'; +import clsx from 'clsx'; +import { type ReactNode, useState } from 'react'; + +type Props = { + children: ReactNode; + content: ReactNode; + className?: string; +}; + +export function Tooltip({ children, content, className }: Props) { + const [isOpen, setIsOpen] = useState(false); + + const { refs, floatingStyles, context } = useFloating({ + open: isOpen, + onOpenChange: setIsOpen, + placement: 'top', + whileElementsMounted: autoUpdate, + middleware: [ + offset(5), + flip(), + shift(), + ], + }); + + const hover = useHover(context); + const focus = useFocus(context); + const dismiss = useDismiss(context); + const role = useRole(context); + + const { getReferenceProps, getFloatingProps } = useInteractions([ + hover, + focus, + dismiss, + role, + ]); + + return ( + <> +
+ {children} +
+ {isOpen && ( + +
+ {content} +
+
+ )} + + ); +} diff --git a/yarn.lock b/yarn.lock index 2cd8f438..71b49cfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1110,6 +1110,42 @@ resolved "https://registry.yarnpkg.com/@fastify/deepmerge/-/deepmerge-2.0.0.tgz#794e98a4eefd6e1edde852e924e25f47b0fed7d8" integrity sha512-fsaybTGDyQ5KpPsplQqb9yKdCf2x/pbNpMNk8Tvp3rRz7lVcupKysH4b2ELMN2P4Hak1+UqTYdTj/u4FNV2p0g== +"@floating-ui/core@^1.6.0": + version "1.6.9" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.9.tgz#64d1da251433019dafa091de9b2886ff35ec14e6" + integrity sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw== + dependencies: + "@floating-ui/utils" "^0.2.9" + +"@floating-ui/dom@^1.0.0": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.13.tgz#a8a938532aea27a95121ec16e667a7cbe8c59e34" + integrity sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.9" + +"@floating-ui/react-dom@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" + integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/react@^0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.27.3.tgz#f9a30583eddd5770f3a6e1f3479a258f3df0c8c8" + integrity sha512-CLHnes3ixIFFKVQDdICjel8muhFLOBdQH7fgtHNPY8UbCNqbeKZ262G7K66lGQOUQWWnYocf7ZbUsLJgGfsLHg== + dependencies: + "@floating-ui/react-dom" "^2.1.2" + "@floating-ui/utils" "^0.2.9" + tabbable "^6.0.0" + +"@floating-ui/utils@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429" + integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== + "@hono/node-server@^1.13.3": version "1.13.3" resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.13.3.tgz#6df4fe3db244718299621708702f3858e5b6a3e7" @@ -9234,16 +9270,7 @@ string-ts@^2.2.0: resolved "https://registry.yarnpkg.com/string-ts/-/string-ts-2.2.0.tgz#46573f475f90f9b43c50cd01c9a603c83426bd25" integrity sha512-VTP0LLZo4Jp9Gz5IiDVMS9WyLx/3IeYh0PXUn0NdPqusUFNgkHPWiEdbB9TU2Iv3myUskraD5WtYEdHUrQEIlQ== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9327,14 +9354,7 @@ stringify-object@3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9461,6 +9481,11 @@ synckit@^0.9.0, synckit@^0.9.1: "@pkgr/core" "^0.1.0" tslib "^2.6.2" +tabbable@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" + integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + tailwind@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tailwind/-/tailwind-4.0.0.tgz#070b5e5f1c2c190e4c0d1280a46b36c7369ea46e" @@ -10540,16 +10565,7 @@ wouter@^3.3.5: regexparam "^3.0.0" use-sync-external-store "^1.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 95c1b24d2396f626c3f47ddca42e10c2ff4062cf Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Sat, 11 Jan 2025 12:30:50 +0100 Subject: [PATCH 02/22] feat(chat): change layout of project route, add share button --- apps/chat/src/i18n/packs/i18n-lang-en.ts | 1 + apps/chat/src/i18n/packs/i18n-lang-pl.ts | 1 + .../share-resource/share-resource-button.tsx | 1 + .../files/project-files-list-container.tsx | 6 +--- .../src/routes/project/project-content.tsx | 34 +++++++++++-------- .../src/components/form/form-spinner-cta.tsx | 13 +++++-- 6 files changed, 35 insertions(+), 21 deletions(-) diff --git a/apps/chat/src/i18n/packs/i18n-lang-en.ts b/apps/chat/src/i18n/packs/i18n-lang-en.ts index de0882ce..60eed80c 100644 --- a/apps/chat/src/i18n/packs/i18n-lang-en.ts +++ b/apps/chat/src/i18n/packs/i18n-lang-en.ts @@ -134,6 +134,7 @@ export const I18N_PACK_EN = deepmerge(I18N_FORWARDED_EN_PACK, { hello: 'Hello, how can we help you?', title: 'Project', chats: 'Chats in project', + files: 'Files in project', }, experts: { meta: { diff --git a/apps/chat/src/i18n/packs/i18n-lang-pl.ts b/apps/chat/src/i18n/packs/i18n-lang-pl.ts index f28a1043..85d13fa2 100644 --- a/apps/chat/src/i18n/packs/i18n-lang-pl.ts +++ b/apps/chat/src/i18n/packs/i18n-lang-pl.ts @@ -136,6 +136,7 @@ export const I18N_PACK_PL: I18nLangPack = deepmerge(I18N_FORWARDED_PL_PACK, { hello: 'Cześć, jak możemy Ci pomóc?', title: 'Projekt', chats: 'Czaty w projekcie', + files: 'Pliki w projekcie', }, experts: { meta: { diff --git a/apps/chat/src/modules/permissions/share-resource/share-resource-button.tsx b/apps/chat/src/modules/permissions/share-resource/share-resource-button.tsx index 02a18ccc..2b057e6f 100644 --- a/apps/chat/src/modules/permissions/share-resource/share-resource-button.tsx +++ b/apps/chat/src/modules/permissions/share-resource/share-resource-button.tsx @@ -44,6 +44,7 @@ export function ShareResourceButton( return ( diff --git a/apps/chat/src/modules/projects/files/project-files-list-container.tsx b/apps/chat/src/modules/projects/files/project-files-list-container.tsx index 23dbb785..9b661639 100644 --- a/apps/chat/src/modules/projects/files/project-files-list-container.tsx +++ b/apps/chat/src/modules/projects/files/project-files-list-container.tsx @@ -37,11 +37,7 @@ export function ProjectFilesListContainer({ projectId }: Props) { return (
-
-

- {t.title} -

- +
+
-

+

{t.hello}

-
+
-
-
- +
+ -
+
-

- {t.chats} -

+
+
+

+ {t.chats} +

- -
+ +
+ +
+

+ {t.files} +

-
- + +
diff --git a/packages/ui/src/components/form/form-spinner-cta.tsx b/packages/ui/src/components/form/form-spinner-cta.tsx index 0a494774..1d0f079f 100644 --- a/packages/ui/src/components/form/form-spinner-cta.tsx +++ b/packages/ui/src/components/form/form-spinner-cta.tsx @@ -4,13 +4,22 @@ import clsx from 'clsx'; export type FormSpinnerCTAProps = ComponentProps<'button'> & { loading: boolean; + buttonTypeClass?: string; }; -export function FormSpinnerCTA({ loading, className, children, ...props }: FormSpinnerCTAProps) { +export function FormSpinnerCTA( + { + loading, + className, + buttonTypeClass = 'uk-button-primary', + children, + ...props + }: FormSpinnerCTAProps, +) { return ( + ); +} diff --git a/apps/chat/src/modules/permissions/share-resource/autocomplete/parts/index.ts b/apps/chat/src/modules/permissions/share-resource/autocomplete/parts/index.ts new file mode 100644 index 00000000..488b6985 --- /dev/null +++ b/apps/chat/src/modules/permissions/share-resource/autocomplete/parts/index.ts @@ -0,0 +1,3 @@ +export * from './autocomplete-group-item'; +export * from './autocomplete-user-item'; +export * from './choose-button'; diff --git a/apps/chat/src/modules/permissions/share-resource/autocomplete/search-users-groups-input.tsx b/apps/chat/src/modules/permissions/share-resource/autocomplete/search-users-groups-input.tsx new file mode 100644 index 00000000..437e4b08 --- /dev/null +++ b/apps/chat/src/modules/permissions/share-resource/autocomplete/search-users-groups-input.tsx @@ -0,0 +1,125 @@ +import { useControlStrict } from '@under-control/forms'; +import { flow } from 'fp-ts/lib/function'; +import { useEffect, useRef, useState } from 'react'; + +import { runTask, tryOrThrowTE } from '@llm/commons'; +import { + useAsyncDebounce, + useAsyncValue, + useOutsideClickRef, + useUpdateEffect, + useWindowListener, +} from '@llm/commons-front'; +import { useSdkForLoggedIn } from '@llm/sdk'; +import { useI18n } from '~/i18n'; +import { useWorkspaceOrganizationOrThrow } from '~/modules/workspace'; + +import { DropdownContent } from './dropdown-content'; + +export function SearchUsersGroupsInput() { + const t = useI18n().pack.permissions.modal.autocomplete; + const { sdks } = useSdkForLoggedIn(); + const { organization } = useWorkspaceOrganizationOrThrow(); + + const [isOpened, setIsOpened] = useState(false); + const phrase = useControlStrict({ + defaultValue: '', + }); + + const onDebouncedFetchItems = useAsyncDebounce( + flow( + sdks.dashboard.shareResource.searchUsersAndGroups, + tryOrThrowTE, + runTask, + ), + { + delay: 100, + }, + ); + + const result = useAsyncValue( + async () => { + if (!phrase.value) { + return null; + } + + return onDebouncedFetchItems( + { + phrase: phrase.value, + organizationId: organization.id, + }, + ); + }, + [phrase.value], + ); + + const outsideClickRef = useOutsideClickRef(() => { + phrase.setValue({ + value: '', + }); + }); + + const [position, setPosition] = useState({ top: 0, left: 0, width: 0 }); + const inputRef = useRef(null); + + const updatePosition = () => { + if (inputRef.current) { + const rect = inputRef.current.getBoundingClientRect(); + + setPosition({ + top: rect.top + rect.height + window.scrollY + 5, + left: rect.left + window.scrollX, + width: rect.width, + }); + } + }; + + useWindowListener({ + resize: updatePosition, + scroll: updatePosition, + }); + + useEffect(updatePosition, [isOpened]); + useUpdateEffect(() => { + setIsOpened(phrase.value.length > 0); + }, [phrase]); + + const handleSelect = () => { + phrase.setValue({ value: '' }); + }; + + return ( +
+
+ + + {result.status === 'loading' && ( + + )} +
+ + {isOpened && ( + + )} +
+ ); +} diff --git a/apps/chat/src/modules/permissions/share-resource/share-resource-form-modal.tsx b/apps/chat/src/modules/permissions/share-resource/share-resource-form-modal.tsx index 366b7e82..fc323444 100644 --- a/apps/chat/src/modules/permissions/share-resource/share-resource-form-modal.tsx +++ b/apps/chat/src/modules/permissions/share-resource/share-resource-form-modal.tsx @@ -10,6 +10,7 @@ import { } from '@llm/ui'; import { useI18n } from '~/i18n'; +import { SearchUsersGroupsInput } from './autocomplete'; import { useShareResourceForm } from './use-share-resource-form'; export type ShareResourceFormModalProps = @@ -48,13 +49,13 @@ export function ShareResourceFormModal( footer={( <> - + {t.submit} )} > - AVC + ); } diff --git a/packages/sdk/src/modules/dashboard/share-resource/share-resource.dto.ts b/packages/sdk/src/modules/dashboard/share-resource/share-resource.dto.ts index 6889b9da..3d4c1169 100644 --- a/packages/sdk/src/modules/dashboard/share-resource/share-resource.dto.ts +++ b/packages/sdk/src/modules/dashboard/share-resource/share-resource.dto.ts @@ -9,7 +9,7 @@ import type { export class ShareResourceSdk extends AbstractNestedSdkWithAuth { protected endpointPrefix = '/dashboard/share-resource'; - search = (dto: SdkSearchShareResourceUsersGroupsInputT) => + searchUsersAndGroups = (dto: SdkSearchShareResourceUsersGroupsInputT) => this.fetch({ url: this.endpoint('/users-groups/search'), query: dto, diff --git a/packages/ui/src/i18n/packs/i18n-forwarded-en-pack.tsx b/packages/ui/src/i18n/packs/i18n-forwarded-en-pack.tsx index 2d78c4d0..e80e94df 100644 --- a/packages/ui/src/i18n/packs/i18n-forwarded-en-pack.tsx +++ b/packages/ui/src/i18n/packs/i18n-forwarded-en-pack.tsx @@ -47,6 +47,7 @@ export const I18N_FORWARDED_EN_PACK = { download: 'Download', select: 'Select', selected: 'Selected', + choose: 'Choose', expand: { more: 'More', less: 'Less', diff --git a/packages/ui/src/i18n/packs/i18n-forwarded-pl-pack.tsx b/packages/ui/src/i18n/packs/i18n-forwarded-pl-pack.tsx index 9d17a5b1..57734c37 100644 --- a/packages/ui/src/i18n/packs/i18n-forwarded-pl-pack.tsx +++ b/packages/ui/src/i18n/packs/i18n-forwarded-pl-pack.tsx @@ -49,6 +49,7 @@ export const I18N_FORWARDED_PL_PACK: typeof I18N_FORWARDED_EN_PACK = { download: 'Pobierz', select: 'Wybierz', selected: 'Wybrano', + choose: 'Wybierz', expand: { more: 'Więcej', less: 'Mniej', From e712aa9a04d56224325aa71276f314299b190133 Mon Sep 17 00:00:00 2001 From: Mateusz Baginski Date: Sat, 11 Jan 2025 15:59:22 +0100 Subject: [PATCH 06/22] feat(backend): add user name --- apps/admin/src/i18n/packs/i18n-lang-en.ts | 4 +++ apps/admin/src/i18n/packs/i18n-lang-pl.ts | 4 +++ .../logged-in/logged-in-user-item.tsx | 8 +++--- .../form/shared/user-shared-form-fields.tsx | 16 +++++++++++- .../form/update/user-update-form-modal.tsx | 1 + .../users/table/users-table-container.tsx | 1 + .../src/migrations/0033-add-name-to-users.ts | 26 +++++++++++++++++++ apps/backend/src/migrations/index.ts | 2 ++ .../modules/auth/services/auth-jwt.service.ts | 3 ++- .../src/modules/messages/messages.repo.ts | 16 +++++++++--- .../users-groups/repo/users-groups.repo.ts | 4 +++ .../elasticsearch/users-es-index.repo.ts | 1 + .../elasticsearch/users-es-search.repo.ts | 10 ++++++- apps/backend/src/modules/users/users.boot.ts | 1 + apps/backend/src/modules/users/users.repo.ts | 2 ++ .../backend/src/modules/users/users.tables.ts | 3 ++- apps/chat/src/i18n/packs/i18n-lang-en.ts | 4 +++ apps/chat/src/i18n/packs/i18n-lang-pl.ts | 4 +++ .../logged-in/logged-in-user-item.tsx | 2 +- .../hooks/use-optimistic-response-creator.tsx | 1 + .../form/create/use-user-create-form.tsx | 3 ++- .../form/shared/user-shared-form-fields.tsx | 16 +++++++++++- .../form/update/use-user-update-form.tsx | 3 ++- .../form/update/user-update-form-modal.tsx | 1 + .../users/table/users-table-container.tsx | 1 + .../sdk/src/modules/auth/dto/sdk-jwt.dto.ts | 1 + .../users/dto/sdk-create-user.dto.ts | 1 + .../users/dto/sdk-update-user.dto.ts | 1 + .../users/dto/sdk-user-list-item.dto.ts | 1 + .../dashboard/users/dto/sdk-user.dto.ts | 1 + 30 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 apps/backend/src/migrations/0033-add-name-to-users.ts diff --git a/apps/admin/src/i18n/packs/i18n-lang-en.ts b/apps/admin/src/i18n/packs/i18n-lang-en.ts index 40964886..5c37fb43 100644 --- a/apps/admin/src/i18n/packs/i18n-lang-en.ts +++ b/apps/admin/src/i18n/packs/i18n-lang-en.ts @@ -331,6 +331,10 @@ export const I18N_PACK_EN = deepmerge(I18N_FORWARDED_EN_PACK, { label: 'E-Mail', placeholder: 'Enter e-mail address', }, + name: { + label: 'Name and surname', + placeholder: 'Enter name and surname', + }, flags: { label: 'Flags', }, diff --git a/apps/admin/src/i18n/packs/i18n-lang-pl.ts b/apps/admin/src/i18n/packs/i18n-lang-pl.ts index d3056fbb..fa117faa 100644 --- a/apps/admin/src/i18n/packs/i18n-lang-pl.ts +++ b/apps/admin/src/i18n/packs/i18n-lang-pl.ts @@ -333,6 +333,10 @@ export const I18N_PACK_PL: I18nLangPack = deepmerge(I18N_FORWARDED_PL_PACK, { label: 'E-mail', placeholder: 'Wpisz adres e-mail', }, + name: { + label: 'Imię i nazwisko', + placeholder: 'Wpisz imię i nazwisko', + }, flags: { label: 'Flagi', }, diff --git a/apps/admin/src/layouts/navigation/logged-in/logged-in-user-item.tsx b/apps/admin/src/layouts/navigation/logged-in/logged-in-user-item.tsx index a07a832f..97469ab7 100644 --- a/apps/admin/src/layouts/navigation/logged-in/logged-in-user-item.tsx +++ b/apps/admin/src/layouts/navigation/logged-in/logged-in-user-item.tsx @@ -14,10 +14,10 @@ export function LoggedInUserItem() {