diff --git a/apps/platform/trpc/routers/convoRouter/convoRouter.ts b/apps/platform/trpc/routers/convoRouter/convoRouter.ts index ae08642e..b03516f5 100644 --- a/apps/platform/trpc/routers/convoRouter/convoRouter.ts +++ b/apps/platform/trpc/routers/convoRouter/convoRouter.ts @@ -1426,7 +1426,9 @@ export const convoRouter = router({ }, with: { author: { - columns: {}, + columns: { + publicId: true + }, with: { orgMember: { columns: { diff --git a/apps/web/components.json b/apps/web/components.json index 60709867..7e061b10 100644 --- a/apps/web/components.json +++ b/apps/web/components.json @@ -1,6 +1,6 @@ { "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", + "style": "new-york", "rsc": false, "tsx": true, "tailwind": { diff --git a/apps/web/package.json b/apps/web/package.json index 3978ad3f..7b4064f5 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,15 +15,16 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toggle-group": "^1.0.4", - "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/themes": "^3.0.2", "@simplewebauthn/browser": "^10.0.0", diff --git a/apps/web/src/app/[orgShortCode]/_components/sidebar-content.tsx b/apps/web/src/app/[orgShortCode]/_components/sidebar-content.tsx index 3b92bd08..e4a35c91 100644 --- a/apps/web/src/app/[orgShortCode]/_components/sidebar-content.tsx +++ b/apps/web/src/app/[orgShortCode]/_components/sidebar-content.tsx @@ -3,7 +3,6 @@ import useLoading from '@/src/hooks/use-loading'; import { cn, generateAvatarUrl, getInitials } from '@/src/lib/utils'; import { useGlobalStore } from '@/src/providers/global-store-provider'; -import { Button } from '@/src/components/shadcn-ui/button'; import { Avatar, AvatarFallback, @@ -25,31 +24,20 @@ import { import { Check, - AddressBook, SignOut, - ChatCircle, MoonStars, Gear, - SpinnerGap, Sun, - CaretRight, - CaretLeft, - SquaresFour, - Shield, - CaretUp, Book, QuestionMark, MapPin, Activity, Megaphone, - PushPin, - CaretDoubleLeft, CaretUpDown, Plus, Palette, Monitor, Question, - Paperclip, User } from '@phosphor-icons/react'; import { env } from 'next-runtime-env'; @@ -59,11 +47,11 @@ import { useMemo } from 'react'; import { toast } from 'sonner'; import { useQueryClient } from '@tanstack/react-query'; import React from 'react'; -import { SidebarNavButton } from './sidebar-nav-button'; + import { useTheme } from 'next-themes'; import { sidebarSubmenuOpenAtom } from './atoms'; import { useAtom } from 'jotai'; -import { usePreferencesState } from '@/src/stores/preferences-store'; + import { ToggleGroup, ToggleGroupItem @@ -102,7 +90,7 @@ export default function SidebarContent() { UnInbox - v0.1.23 + v0.1.0 ); diff --git a/apps/web/src/app/[orgShortCode]/_components/sidebar-nav-button.tsx b/apps/web/src/app/[orgShortCode]/_components/sidebar-nav-button.tsx index 79d91a41..5ad2edaf 100644 --- a/apps/web/src/app/[orgShortCode]/_components/sidebar-nav-button.tsx +++ b/apps/web/src/app/[orgShortCode]/_components/sidebar-nav-button.tsx @@ -53,14 +53,14 @@ export function SidebarNavButton({
{children ? (
{ setExpanded(!expanded); }}> @@ -89,14 +89,14 @@ export function SidebarNavButton({ asChild>
{icon}
-
- +
+ {label} {badge && {badge}} diff --git a/apps/web/src/app/[orgShortCode]/_components/sidebar.tsx b/apps/web/src/app/[orgShortCode]/_components/sidebar.tsx index 6bc81204..7f5720ad 100644 --- a/apps/web/src/app/[orgShortCode]/_components/sidebar.tsx +++ b/apps/web/src/app/[orgShortCode]/_components/sidebar.tsx @@ -2,13 +2,7 @@ import { cn } from '@/src/lib/utils'; import { usePreferencesState } from '@/src/stores/preferences-store'; -import { - CaretDoubleLeft, - CaretLineRight, - Cross, - PushPin, - X -} from '@phosphor-icons/react'; +import { CaretDoubleLeft, PushPin, X } from '@phosphor-icons/react'; import SidebarContent from './sidebar-content'; import { sidebarSubmenuOpenAtom } from './atoms'; import { useAtom } from 'jotai'; diff --git a/apps/web/src/app/[orgShortCode]/convo/[convoId]/_components/top-bar.tsx b/apps/web/src/app/[orgShortCode]/convo/[convoId]/_components/top-bar.tsx index 81a2ac41..22161932 100644 --- a/apps/web/src/app/[orgShortCode]/convo/[convoId]/_components/top-bar.tsx +++ b/apps/web/src/app/[orgShortCode]/convo/[convoId]/_components/top-bar.tsx @@ -34,7 +34,7 @@ export default function TopBar({ const toggleConvoHiddenState = useToggleConvoHidden$Cache(); return ( -
+
state.currentOrg.shortCode); + + const timeAgo = useTimeAgo(convo.lastUpdatedAt); + + const authorAsParticipant = useMemo(() => { + return ( + convo.participants.find( + (participant) => + participant.publicId === convo.entries[0]?.author.publicId + ) ?? convo.participants[0] + ); + }, [convo.participants, convo.entries]); + + const authorAvatarData = useMemo(() => { + return formatParticipantData(authorAsParticipant!); + }, [authorAsParticipant]); + + const participantData = useMemo(() => { + const allParticipants = convo.participants + .map((participant) => formatParticipantData(participant)) + .filter(Boolean) as NonNullable< + ReturnType + >[]; + const participantsWithoutAuthor = allParticipants.filter( + (participant) => + participant.participantPublicId !== authorAsParticipant?.publicId + ); + const author = allParticipants.find( + (participant) => + participant.participantPublicId === authorAsParticipant?.publicId + )!; + return [author].concat(participantsWithoutAuthor); + }, [convo.participants, authorAsParticipant]); + + const participantNames = useMemo(() => { + return participantData.map((participant) => participant.name); + }, [participantData]); + + const currentPath = usePathname(); + const link = `/${orgShortCode}/convo/${convo.publicId}`; + + const isActive = currentPath === link; + + return ( + + +
+
+ + {participantNames.join(', ')} + + + {timeAgo} + +
+ + + {convo.subjects[0]?.subject} + + +
+
+ {authorAvatarData && ( + + )} +
+ + + {convo.entries[0]?.bodyPlainText ?? ''} + +
+
+ + ); +} diff --git a/apps/web/src/app/[orgShortCode]/convo/_components/convo-list.tsx b/apps/web/src/app/[orgShortCode]/convo/_components/convo-list.tsx index 84471e78..08e3e277 100644 --- a/apps/web/src/app/[orgShortCode]/convo/_components/convo-list.tsx +++ b/apps/web/src/app/[orgShortCode]/convo/_components/convo-list.tsx @@ -1,21 +1,19 @@ 'use client'; -import { type RouterOutputs, api } from '@/src/lib/trpc'; +import { api } from '@/src/lib/trpc'; import { useGlobalStore } from '@/src/providers/global-store-provider'; -import { useState, useEffect, useMemo, useRef } from 'react'; +import { useEffect, useRef } from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; -import useTimeAgo from '@/src/hooks/use-time-ago'; -import { formatParticipantData } from '../utils'; -import Link from 'next/link'; -import AvatarPlus from '@/src/components/avatar-plus'; -import { Button } from '@/src/components/shadcn-ui/button'; import { ms } from '@u22n/utils/ms'; +import { ConvoItem } from './convo-list-item'; -export default function ConvoList() { +type Props = { + hidden: boolean; +}; + +export function ConvoList(props: Props) { const orgShortCode = useGlobalStore((state) => state.currentOrg.shortCode); const scrollableRef = useRef(null); - const [showHidden, setShowHidden] = useState(false); - const { data: convos, fetchNextPage, @@ -25,7 +23,7 @@ export default function ConvoList() { } = api.convos.getOrgMemberConvos.useInfiniteQuery( { orgShortCode, - includeHidden: showHidden ? true : undefined + includeHidden: props.hidden ? true : undefined }, { getNextPageParam: (lastPage) => lastPage.cursor ?? undefined, @@ -62,24 +60,16 @@ export default function ConvoList() { ]); return ( -
+
{isLoading ? (
Loading...
) : ( <> - {/* TODO: Replace this according to designs later */} -
- -
{convosVirtualizer.getVirtualItems().map((virtualItem) => { const isLoader = virtualItem.index > allConvos.length - 1; @@ -113,81 +103,3 @@ export default function ConvoList() {
); } - -function ConvoItem({ - convo -}: { - convo: RouterOutputs['convos']['getOrgMemberConvos']['data'][number]; -}) { - const orgShortCode = useGlobalStore((state) => state.currentOrg.shortCode); - - const timeAgo = useTimeAgo(convo.lastUpdatedAt); - - const author = convo.entries[0]?.author ?? convo.participants[0]; - - const authorPublicId = useMemo(() => { - if (!author) return null; - return ( - author.orgMember?.publicId ?? - author.team?.publicId ?? - author.contact?.publicId ?? - null - ); - }, [author]); - - const authorName = useMemo(() => { - if (!author) return null; - return ( - author.team?.name ?? - author.contact?.setName ?? - author.contact?.name ?? - `${author.orgMember?.profile.firstName ?? ''} ${author.orgMember?.profile.lastName ?? ''}`.trim() ?? - 'Participant' - ); - }, [author]); - - const participantData = useMemo(() => { - const allParticipants = convo.participants - .map((participant) => formatParticipantData(participant)) - .filter(Boolean) as NonNullable< - ReturnType - >[]; - const participantsWithoutAuthor = allParticipants.filter( - (participant) => participant.typePublicId !== authorPublicId - ); - const author = allParticipants.find( - (participant) => participant.typePublicId === authorPublicId - )!; - return [author].concat(participantsWithoutAuthor); - }, [convo.participants, authorPublicId]); - - return ( - - -
- - {convo.subjects[0]?.subject} - -
- - {authorName}:{' '} - - {convo.entries[0]?.bodyPlainText ?? ''} - - -
-
- - {timeAgo} - -
-
- - ); -} diff --git a/apps/web/src/app/[orgShortCode]/convo/layout.tsx b/apps/web/src/app/[orgShortCode]/convo/layout.tsx index 28e4e323..d65d3ac6 100644 --- a/apps/web/src/app/[orgShortCode]/convo/layout.tsx +++ b/apps/web/src/app/[orgShortCode]/convo/layout.tsx @@ -1,24 +1,112 @@ 'use client'; import { Button } from '@/src/components/shadcn-ui/button'; -import ConvoList from './_components/convo-list'; +import { ConvoList } from './_components/convo-list'; import { usePreferencesState } from '@/src/stores/preferences-store'; +import { useIsMobile } from '@/src/hooks/is-mobile'; +import { + CaretRight, + ChatCircle, + Eye, + EyeSlash, + List, + User +} from '@phosphor-icons/react'; +import { + Breadcrumb, + BreadcrumbEllipsis, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator +} from '@/src/components/shadcn-ui/breadcrumb'; +import { useState } from 'react'; +import Link from 'next/link'; +import { useGlobalStore } from '@/src/providers/global-store-provider'; export default function Layout({ children }: Readonly<{ children: React.ReactNode }>) { - const { - sidebarDocked, - sidebarExpanded, - setSidebarExpanded, - setSidebarDocking - } = usePreferencesState(); + const orgShortCode = useGlobalStore((state) => state.currentOrg.shortCode); + const { setSidebarExpanded } = usePreferencesState(); + const isMobile = useIsMobile(); + const [showHidden, setShowHidden] = useState(false); + return (
-
- {/* */} - +
+
+ {isMobile && ( + + )} +
+ + + +
+ +
+ +
+ {/* +
+ +
+ My personal space +
*/} + + + + +
+ +
+ + {showHidden ? 'Hidden Conversations' : 'Conversations'} + +
+
+
+
+
+ + +
+
+
+
+ {children}
-
{children}
); } diff --git a/apps/web/src/app/[orgShortCode]/layout.tsx b/apps/web/src/app/[orgShortCode]/layout.tsx index 120207c8..452ce8f4 100644 --- a/apps/web/src/app/[orgShortCode]/layout.tsx +++ b/apps/web/src/app/[orgShortCode]/layout.tsx @@ -20,7 +20,7 @@ export default function Layout({ if (storeDataLoading) { return (
- +
); } @@ -64,7 +64,7 @@ export default function Layout({ return ( -
+
diff --git a/apps/web/src/app/[orgShortCode]/settings/_components/settings-sidebar.tsx b/apps/web/src/app/[orgShortCode]/settings/_components/settings-sidebar.tsx index e64fdfb5..b1201897 100644 --- a/apps/web/src/app/[orgShortCode]/settings/_components/settings-sidebar.tsx +++ b/apps/web/src/app/[orgShortCode]/settings/_components/settings-sidebar.tsx @@ -97,7 +97,7 @@ export default function SettingsSidebar() { return ( + className="bg-base-2 dark:bg-slatedark-2 h-full w-[400px] flex-col p-2 px-4"> ; avatarTimestamp: Date | null; name: string; + color?: AvatarProps['color']; }[]; }; -export default function AvatarPlus({ - imageSize, - size, - users -}: AvatarPlusProps) { +export default function AvatarPlus({ size, users }: AvatarPlusProps) { const [primary, ...rest] = users; if (!primary) { return null; } return ( -
- - - - - - - - -
+
+ + +
+ + +
+ {`${rest.length}`} +
+
+
+ +
{rest.map((user) => ( - - - + ))}
- - +
+
); } diff --git a/apps/web/src/components/avatar.tsx b/apps/web/src/components/avatar.tsx new file mode 100644 index 00000000..38bc5e3d --- /dev/null +++ b/apps/web/src/components/avatar.tsx @@ -0,0 +1,137 @@ +'use client'; + +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn, generateAvatarUrl, getInitials } from '@/src/lib/utils'; +import { type TypeId, inferTypeId } from '@u22n/utils/typeid'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger +} from './shadcn-ui/tooltip'; +import { + Avatar as AvatarShad, + AvatarFallback, + AvatarImage +} from './shadcn-ui/avatar'; +import { + AddressBook, + BuildingOffice, + User, + UsersThree +} from '@phosphor-icons/react'; + +export type AvatarProps = { + avatarProfilePublicId: TypeId< + 'orgMemberProfile' | 'org' | 'teams' | 'contacts' + >; + avatarTimestamp: Date | null; + name: string; + hideTooltip?: boolean; + tooltipOverride?: string; +} & VariantProps; + +const avatarVariants = cva( + 'flex items-center justify-center font-medium aspect-square bg-base-5 text-base-11 h-6 w-6 text-sm rounded-md', + { + variants: { + size: { + sm: 'h-4 w-4 text-[9px] rounded-sm', + md: 'h-6 w-6 text-xs rounded-md', + lg: 'h-8 w-8 text-sm rounded-lg', + xl: 'h-10 w-10 text-md rounded-lg' + }, + color: { + base: 'bg-base-5 text-base-11', + bronze: 'bg-bronze-5 text-bronze-11', + gold: 'bg-gold-5 text-gold-11', + brown: 'bg-brown-5 text-brown-11', + orange: 'bg-orange-5 text-orange-11', + tomato: 'bg-tomato-5 text-tomato-11', + red: 'bg-red-5 text-red-11', + ruby: 'bg-ruby-5 text-ruby-11', + crimson: 'bg-crimson-5 text-crimson-11', + pink: 'bg-pink-5 text-pink-11', + plum: 'bg-plum-5 text-plum-11', + purple: 'bg-purple-5 text-purple-11', + violet: 'bg-violet-5 text-violet-11', + iris: 'bg-iris-5 text-iris-11', + indigo: 'bg-indigo-5 text-indigo-11', + blue: 'bg-blue-5 text-blue-11', + cyan: 'bg-cyan-5 text-cyan-11', + teal: 'bg-teal-5 text-teal-11', + jade: 'bg-jade-5 text-jade-11', + green: 'bg-green-5 text-green-11', + grass: 'bg-grass-5 text-grass-11' + } + }, + defaultVariants: { + size: 'md', + color: 'base' + } + } +); + +export function Avatar(props: AvatarProps) { + const avatarUrl = + generateAvatarUrl({ + publicId: props.avatarProfilePublicId, + avatarTimestamp: props.avatarTimestamp, + size: props.size ?? 'lg' + }) ?? ''; + const altText = props.name; + const withoutTooltip = props.hideTooltip ?? false; + + function AvatarIcon() { + const type = inferTypeId(props.avatarProfilePublicId); + switch (type) { + case 'orgMemberProfile': + return ; + case 'org': + return ; + case 'teams': + return ; + case 'contacts': + return ; + default: + return null; + } + } + + return withoutTooltip ? ( + + + {getInitials(altText)} + + ) : ( + + + + + + {getInitials(altText)} + + + + + {altText} + + + + ); +} diff --git a/apps/web/src/components/shadcn-ui/avatar.tsx b/apps/web/src/components/shadcn-ui/avatar.tsx index 9221512b..c5231b36 100644 --- a/apps/web/src/components/shadcn-ui/avatar.tsx +++ b/apps/web/src/components/shadcn-ui/avatar.tsx @@ -3,6 +3,8 @@ import * as AvatarPrimitive from '@radix-ui/react-avatar'; import { cn } from '@/src/lib/utils'; +//! This component has been modified + const Avatar = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -37,7 +39,7 @@ const AvatarFallback = React.forwardRef< & { + separator?: React.ReactNode; + } +>(({ ...props }, ref) => ( +