Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Chain mail to use new accounts system #962

Merged
merged 8 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 167 additions & 39 deletions services/user/Chainmail/ui/src/components/account-switcher.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,180 @@
import { useIncomingMessages, useUser } from "@hooks";
import {
useCreateConnectionToken,
useCurrentAccounts,
useLoggedInUser,
useLogout,
useSelectAccount,
} from "@hooks";
import { cn } from "@lib/utils";
import { Avatar, AvatarFallback } from "@shadcn/avatar";
import { Button } from "@shadcn/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@shadcn/dropdown-menu";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@shadcn/select";
ChevronsUpDown,
LogIn,
LogOut,
PlusCircle,
User,
UserPlus,
} from "lucide-react";

interface AccountSwitcherProps {
isCollapsed: boolean;
}

export function AccountSwitcher({ isCollapsed }: AccountSwitcherProps) {
const { setSelectedMessageId } = useIncomingMessages();
const { availableAccounts, user, setUser } = useUser();
const { mutateAsync: onLogin } = useCreateConnectionToken();

const { data: loggedInUser } = useLoggedInUser();
const isLoggedIn = !!loggedInUser;

const { data: currentAccounts, isPending: isLoadingAccounts } =
useCurrentAccounts();

const isNoOptions = !isLoggedIn && currentAccounts.length == 0;

const { mutateAsync: onLogout } = useLogout();
const { mutateAsync: selectAccount } = useSelectAccount();

const otherConnectedAccounts = currentAccounts.filter(
(account) => account !== loggedInUser,
);

return (
<Select
defaultValue={user}
onValueChange={(value) => {
setSelectedMessageId("");
setUser(value);
}}
>
<SelectTrigger
className={cn(
"flex items-center gap-2 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0",
isCollapsed &&
"flex h-9 w-9 shrink-0 items-center justify-center p-0 [&>span]:w-auto [&>svg]:hidden",
)}
aria-label="Select account"
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="lg"
variant="ghost"
className={cn("w-full", !isCollapsed && "pl-1 pr-2")}
>
<Avatar className="h-8 w-8 rounded-lg">
<AvatarFallback className="rounded-lg">
{loggedInUser?.[0] ?? <LogIn className="h-4 w-4" />}
</AvatarFallback>
</Avatar>
{isCollapsed ? null : (
<>
<div className="ml-2 grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{loggedInUser ?? "Get started"}
</span>
</div>
<ChevronsUpDown className="ml-auto size-4" />
</>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
// side={isMobile ? "bottom" : "right"}
side="right"
align="end"
sideOffset={4}
>
<SelectValue placeholder="Select an account">
<span className={cn("ml-2", isCollapsed && "hidden")}>
{user}
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
{availableAccounts.map((account) => (
<SelectItem key={account} value={account}>
<div className="flex items-center gap-3 [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0 [&_svg]:text-foreground">
{account}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
{loggedInUser ? (
<>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarFallback className="rounded-lg">
{loggedInUser[0]}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{loggedInUser}
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
</>
) : null}
{isNoOptions && (
<DropdownMenuItem
onClick={() => {
onLogin();
}}
>
<LogIn className="mr-2 h-4 w-4" />
<span>
{isLoadingAccounts ? "Loading..." : "Log in"}
</span>
</DropdownMenuItem>
)}
{!isNoOptions && !otherConnectedAccounts.length ? (
<DropdownMenuItem
onClick={() => {
onLogin();
}}
>
<UserPlus className="mr-2 h-4 w-4" />
<span>Switch account</span>
</DropdownMenuItem>
) : null}
{!isNoOptions && otherConnectedAccounts.length ? (
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<UserPlus className="mr-2 h-4 w-4" />
<span>
{loggedInUser
? "Switch account"
: "Select an account"}
</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
{otherConnectedAccounts.map((account) => (
<DropdownMenuItem
key={account}
onClick={() => {
selectAccount(account);
}}
>
<User className="mr-2 h-4 w-4" />
<span>{account}</span>
</DropdownMenuItem>
))}
{otherConnectedAccounts.length ? (
<DropdownMenuSeparator />
) : null}
<DropdownMenuItem
onClick={() => {
onLogin();
}}
>
<PlusCircle className="mr-2 h-4 w-4" />
<span>More...</span>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
) : null}
<DropdownMenuItem
disabled={!isLoggedIn}
onClick={() => {
onLogout();
}}
>
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
9 changes: 4 additions & 5 deletions services/user/Chainmail/ui/src/components/compose-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { toast } from "sonner";

import { type PluginId } from "@psibase/common-lib";

import { getSupervisor } from "@lib/supervisor";

import { Button } from "@shadcn/button";
import {
Dialog,
Expand All @@ -27,7 +25,7 @@ import { Separator } from "@shadcn/separator";

import { MarkdownEditor } from "@components";
import { ControlBar } from "@components/editor";
import { type Message, useDraftMessages, useUser } from "@hooks";
import { type Message, useDraftMessages, useLoggedInUser } from "@hooks";

import {
Form,
Expand All @@ -38,6 +36,8 @@ import {
} from "@shadcn/form";
import { Input } from "@shadcn/input";

import { supervisor } from "src/main";

interface SupervisorError {
code: number;
producer: PluginId;
Expand All @@ -58,7 +58,7 @@ export const ComposeDialog = ({
}) => {
const [open, setOpen] = useState(false);
const isSent = useRef(false);
const { user } = useUser();
const { data: user } = useLoggedInUser();
const { drafts, setDrafts, getDrafts, deleteDraftById } =
useDraftMessages();

Expand Down Expand Up @@ -122,7 +122,6 @@ export const ComposeDialog = ({
}

try {
const supervisor = await getSupervisor();
// TODO: Improve error detection. This promise resolves with success before the transaction is pushed.
await supervisor.functionCall({
service: "chainmail",
Expand Down
5 changes: 2 additions & 3 deletions services/user/Chainmail/ui/src/components/mail-display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ import {
ReplyDialogTriggerIconWithTooltip,
} from "@components";
import { Message, useDraftMessages, useIncomingMessages } from "@hooks";
import { getSupervisor } from "@lib/supervisor";
import { wait } from "@lib/utils";

import { supervisor } from "src/main";

export function MailDisplay({
message,
mailbox,
Expand Down Expand Up @@ -97,7 +98,6 @@ const ActionBar = ({

const onArchive = async (itemId: string) => {
let id = parseInt(itemId);
const supervisor = await getSupervisor();
// TODO: Improve error detection. This promise resolves with success before the transaction is pushed.
await supervisor.functionCall({
service: "chainmail",
Expand All @@ -119,7 +119,6 @@ const ActionBar = ({

const onSave = async (itemId: string) => {
let id = parseInt(itemId);
const supervisor = await getSupervisor();
await supervisor.functionCall({
service: "chainmail",
intf: "api",
Expand Down
2 changes: 1 addition & 1 deletion services/user/Chainmail/ui/src/components/mode-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function ModeToggle() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" className="my-2">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
Expand Down
12 changes: 9 additions & 3 deletions services/user/Chainmail/ui/src/components/nav-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import { cn } from "@lib/utils";

import { AccountSwitcher } from "./account-switcher";
import { Nav } from "./nav";
import { useNavigate, useLocation } from "react-router-dom";
import { useIncomingMessages } from "@hooks";
import { useLocation } from "react-router-dom";
import { useIncomingMessages, useLoggedInUser } from "@hooks";

interface Props {
isCollapsed?: boolean;
}

export const NavMenu = ({ isCollapsed = false }: Props) => {
const navigate = useNavigate();
const { data: loggedInUser } = useLoggedInUser();
const isLoggedIn = !!loggedInUser;

const { pathname } = useLocation();
const at = pathname;
Expand Down Expand Up @@ -41,30 +42,35 @@ export const NavMenu = ({ isCollapsed = false }: Props) => {
icon: Inbox,
variant: at === "/" ? "default" : "ghost",
href: "/",
disabled: !isLoggedIn,
},
{
title: "Drafts",
icon: PencilLine,
variant: at === "/drafts" ? "default" : "ghost",
href: "/drafts",
disabled: !isLoggedIn,
},
{
title: "Saved",
icon: Pin,
variant: at === "/saved" ? "default" : "ghost",
href: "/saved",
disabled: !isLoggedIn,
},
{
title: "Sent",
icon: Send,
variant: at === "/sent" ? "default" : "ghost",
href: "/sent",
disabled: !isLoggedIn,
},
{
title: "Archived",
icon: Archive,
variant: at === "/archived" ? "default" : "ghost",
href: "/archived",
disabled: !isLoggedIn,
},
]}
/>
Expand Down
16 changes: 8 additions & 8 deletions services/user/Chainmail/ui/src/components/nav.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { useNavigate } from "react-router-dom";
import { LucideIcon } from "lucide-react";

import { cn } from "../lib/utils";
import { buttonVariants } from "../shad/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "../shad/components/ui/tooltip";
import { useNavigate } from "react-router-dom";
import { buttonVariants } from "@shadcn/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@shadcn/tooltip";
import { cn } from "@lib/utils";

interface NavProps {
isCollapsed: boolean;
Expand All @@ -17,6 +13,7 @@ interface NavProps {
icon: LucideIcon;
variant: "default" | "ghost";
href: string;
disabled?: boolean;
}[];
}

Expand All @@ -42,6 +39,8 @@ export function Nav({ links, isCollapsed }: NavProps) {
"h-9 w-9",
link.variant === "default" &&
"dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white",
link.disabled &&
"pointer-events-none opacity-50",
)}
>
<link.icon className="h-4 w-4" />
Expand Down Expand Up @@ -77,6 +76,7 @@ export function Nav({ links, isCollapsed }: NavProps) {
"dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white",
"justify-start",
)}
disabled={link.disabled}
>
<link.icon className="mr-2 h-4 w-4" />
{link.title}
Expand Down
Loading
Loading