fix: Applied Composition pattern to ManagementCard

This commit is contained in:
Tania Gutierrez 2024-03-14 13:45:21 -04:00
parent 83ddd0613b
commit ad033614f9
Signed by: riobuenoDevelops
GPG Key ID: 53133EB28EB7E801
2 changed files with 191 additions and 164 deletions

View File

@ -1,123 +1,73 @@
import { cn } from "~/utils"; import { cn } from "~/utils";
import { Avatar } from "./ui/avatar"; import { Avatar } from "./ui/avatar";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { AddIcon, EditIcon, FingerPrintIcon } from "./icons"; import { EditIcon, FingerPrintIcon } from "./icons";
import { Dialog, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
import { DialogContent, Portal } from "@radix-ui/react-dialog";
interface ManagementCardProps { const ManagementCardAvatar = ({ src }: { src?: string }) => {
title?: string; return (
value?: string; <div className="flex justify-center">
subtitle?: string; <div className="relative w-fit h-fit">
isInviteCard?: boolean; <Avatar className="border-2 border-ring h-28 w-28" />
isPasswordCard?: boolean; <Button
isAvatarCard?: boolean; variant="outline"
isDeleteCard?: boolean className="h-10 w-10 rounded-full hover:bg-secondary-2 border-white absolute bottom-0 right-0 z-50">
buttonText?: string; <EditIcon />
buttonOnClick?: () => void </Button>
dialogNode?: React.ReactNode </div>
} </div>
);
export const ManagementCard = ({
title,
isAvatarCard,
isInviteCard,
isPasswordCard,
isDeleteCard,
subtitle,
value,
buttonText,
buttonOnClick,
dialogNode
}: ManagementCardProps) => {
const buttonVariant: string = isInviteCard ? "accent" : isDeleteCard ? "destructive" : "default";
return (
<div
className={cn(
"rounded-lg p-8 border w-full",
isInviteCard && "border-ring",
isAvatarCard && "flex justify-center items-center"
)}
>
{isAvatarCard ? (
<div className="relative w-fit h-fit">
<Avatar className="border-2 border-ring h-28 w-28" />
<Button
variant="outline"
className="h-10 w-10 rounded-full hover:bg-secondary-2 border-white absolute bottom-0 right-0 z-50"
>
<EditIcon />
</Button>
</div>
) : (
<>
<div className="flex gap-x-2 items-center">
<FingerPrintIcon className="text-ring" />
<h4 className="font-bold">{title}</h4>
</div>
{subtitle && (
<span className="text-primary-2 mt-4 mb-8 block text-sm">{subtitle}</span>
)}
{value && (
<span className="text-ring font-bold mt-4 mb-8 block text-sm">{value}</span>
)}
{isPasswordCard && <PasswordDots className="mt-6 mb-8 text-primary-2" />}
{!dialogNode ? (
<Button
onClick={buttonOnClick}
className={`h-12 gap-x-2`}
variant={buttonVariant}
>
<AddIcon />
{buttonText}
</Button>
): (
<Dialog>
<DialogTrigger>Open</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your account
and remove your data from our servers.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
)}
</>
)}
</div>
);
}; };
const PasswordDots = ({ className }: { className?: string }) => { const ManagementCardTitle = ({
return ( children,
<svg className,
width="219" }: React.PropsWithChildren<{ className?: string }>) => {
height="7" return (
viewBox="0 0 219 7" <div className={cn("flex gap-x-2 items-center font-semibold", className)}>
fill="none" <FingerPrintIcon className="text-ring" />
xmlns="http://www.w3.org/2000/svg" {children}
className={className} </div>
> );
<circle cx="3.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="31.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="45.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="17.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="59.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="73.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="87.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="101.777" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="131.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="117.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="145.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="159.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="173.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="187.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="201.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="215.5" cy="3.5" r="3.5" fill="currentColor" />
</svg>
);
}; };
const ManagementCardContent = ({
children,
className,
}: React.PropsWithChildren<{ className?: string }>) => {
return (
<div className={cn("text-primary-2 mt-4 mb-8 block text-sm", className)}>
{children}
</div>
);
};
const ManagementCardFooter = ({
children,
className,
}: React.PropsWithChildren<{ className?: string }>) => {
return <div className={className}>{children}</div>;
};
const ManagementCard = ({
children,
variant,
}: React.PropsWithChildren<{ variant?: string }>) => {
return (
<div
className={cn(
"rounded-lg p-8 border w-full border-[--variant-color]",
!variant && "[--variant-color:theme(colors.border)]",
variant === "accent" && "[--variant-color:theme(colors.primary-1.DEFAULT)]",
)}>
{children}
</div>
);
};
export {
ManagementCard,
ManagementCardAvatar,
ManagementCardContent,
ManagementCardFooter,
ManagementCardTitle,
};

View File

@ -4,8 +4,8 @@ import { useState } from "react";
import { z } from "zod"; import { z } from "zod";
import { Field } from "~/components/forms"; import { Field } from "~/components/forms";
import { GeneralLayout } from "~/components/general-layout"; import { GeneralLayout } from "~/components/general-layout";
import { AddIcon, CloudIcon } from "~/components/icons"; import { AddIcon, CloudIcon, CrownIcon } from "~/components/icons";
import { ManagementCard } from "~/components/management-card"; import { ManagementCard, ManagementCardAvatar, ManagementCardContent, ManagementCardFooter, ManagementCardTitle } from "~/components/management-card";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { import {
Dialog, Dialog,
@ -47,58 +47,106 @@ export default function MyAccount() {
/> />
<h2 className="font-bold my-8">Account Management</h2> <h2 className="font-bold my-8">Account Management</h2>
<div className="grid grid-cols-3 gap-x-8"> <div className="grid grid-cols-3 gap-x-8">
<ManagementCard isAvatarCard /> <ManagementCard>
<ManagementCard <ManagementCardAvatar />
title="Email Address" </ManagementCard>
value="bsimpson@springfield.oh.gov.com" <ManagementCard>
buttonText="Change Email Address" <ManagementCardTitle>Email Address</ManagementCardTitle>
buttonOnClick={() => setModal({ ...openModal, changeEmail: true })} <ManagementCardContent className="text-ring font-semibold">
/> bsimpson@springfield.oh.gov.com
<ManagementCard </ManagementCardContent>
title="Account Type" <ManagementCardFooter>
value="Lite Premium Account" <Button className="h-12 gap-x-2" onClick={() => setModal({ ...openModal, changeEmail: true })}>
buttonText="Upgrade to Premium" <AddIcon />
/> Change Email Address
</Button>
</ManagementCardFooter>
</ManagementCard>
<ManagementCard>
<ManagementCardTitle>Account Type</ManagementCardTitle>
<ManagementCardContent className="text-ring font-semibold flex gap-x-2">
Lite Premium Account
<CrownIcon />
</ManagementCardContent>
<ManagementCardFooter>
<Button className="h-12 gap-x-2">
<AddIcon />
Upgrade to Premium
</Button>
</ManagementCardFooter>
</ManagementCard>
</div> </div>
<h2 className="font-bold my-8">Security</h2> <h2 className="font-bold my-8">Security</h2>
<div className="grid grid-cols-3 gap-x-8"> <div className="grid grid-cols-3 gap-x-8">
<ManagementCard <ManagementCard>
isPasswordCard <ManagementCardTitle>Password</ManagementCardTitle>
title="Password" <ManagementCardContent>
buttonText="Change Password" <PasswordDots className="mt-6" />
buttonOnClick={() => setModal({ ...openModal, changePassword: true })} </ManagementCardContent>
/> <ManagementCardFooter>
<ManagementCard <Button className="h-12 gap-x-2" onClick={() => setModal({ ...openModal, changePassword: true })}>
title="Two-Factor Authentication" <AddIcon />
subtitle="Improve security by enabling 2FA." Change Password
buttonText="Enable Two-Factor Authorization" </Button>
buttonOnClick={() => setModal({ ...openModal, setupTwoFactor: true })} </ManagementCardFooter>
/> </ManagementCard>
<ManagementCard <ManagementCard>
title="Backup Key" <ManagementCardTitle>Two-Factor Authentication</ManagementCardTitle>
subtitle="Never share this code with anyone." <ManagementCardContent>
buttonText="Export Backup Key" Improve security by enabling 2FA.
/> </ManagementCardContent>
<ManagementCardFooter>
<Button className="h-12 gap-x-2" onClick={() => setModal({ ...openModal, setupTwoFactor: true })}>
<AddIcon />
Enable Two-Factor Authorization
</Button>
</ManagementCardFooter>
</ManagementCard>
<ManagementCard>
<ManagementCardTitle>Backup Key</ManagementCardTitle>
<ManagementCardContent>
Never share this code with anyone.
</ManagementCardContent>
<ManagementCardFooter>
<Button className="h-12 gap-x-2">
<AddIcon />
Export Backup Key
</Button>
</ManagementCardFooter>
</ManagementCard>
</div> </div>
<h2 className="font-bold my-8">More</h2> <h2 className="font-bold my-8">More</h2>
<div className="grid grid-cols-3 gap-x-8"> <div className="grid grid-cols-3 gap-x-8">
<ManagementCard <ManagementCard variant="accent">
isInviteCard <ManagementCardTitle>Invite a Friend</ManagementCardTitle>
title="Invite a Friend" <ManagementCardContent>Get 1 GB per friend invited for free (max 5 GB).</ManagementCardContent>
subtitle="Get 1 GB per friend invited for free (max 5 GB)." <ManagementCardFooter>
buttonText="Send Invitation" <Button variant="accent" className="h-12 gap-x-2">
/> <AddIcon />
<ManagementCard Send Invitation
title="Read our Resources" </Button>
subtitle="Navigate helpful articles or get assistance." </ManagementCardFooter>
buttonText="Open Support Centre" </ManagementCard>
/> <ManagementCard>
<ManagementCard <ManagementCardTitle>Read our Resources</ManagementCardTitle>
isDeleteCard <ManagementCardContent>Navigate helpful articles or get assistance.</ManagementCardContent>
title="Delete Account" <ManagementCardFooter>
subtitle="Once initiated, this action cannot be undone." <Button className="h-12 gap-x-2">
buttonText="Delete my Account" <AddIcon />
/> Open Support Centre
</Button>
</ManagementCardFooter>
</ManagementCard>
<ManagementCard>
<ManagementCardTitle>Delete Account</ManagementCardTitle>
<ManagementCardContent>Once initiated, this action cannot be undone.</ManagementCardContent>
<ManagementCardFooter>
<Button className="h-12 gap-x-2" variant="destructive">
<AddIcon />
Delete my Account
</Button>
</ManagementCardFooter>
</ManagementCard>
</div> </div>
{/* Dialogs must be near to body as possible to open the modal, otherwise will be restricted to parent height-width */} {/* Dialogs must be near to body as possible to open the modal, otherwise will be restricted to parent height-width */}
<ChangeEmailForm <ChangeEmailForm
@ -294,3 +342,32 @@ const SetupTwoFactorDialog = ({
</Dialog> </Dialog>
); );
}; };
const PasswordDots = ({ className }: { className?: string }) => {
return (
<svg
width="219"
height="7"
viewBox="0 0 219 7"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}>
<circle cx="3.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="31.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="45.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="17.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="59.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="73.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="87.7771" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="101.777" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="131.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="117.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="145.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="159.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="173.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="187.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="201.5" cy="3.5" r="3.5" fill="currentColor" />
<circle cx="215.5" cy="3.5" r="3.5" fill="currentColor" />
</svg>
);
};