Compare commits

...

8 Commits

12 changed files with 913 additions and 212 deletions

View File

@ -1,3 +1,5 @@
import { FolderIcon, MoreIcon, RecentIcon } from "./icons";
export enum FileTypes {
Folder = "FOLDER",
Document = "DOCUMENT",
@ -33,76 +35,3 @@ export const FileCard = ({ type, fileName, createdAt, size }: FileCardProps) =>
</div>
);
};
const FolderIcon = ({ className }: { className?: string }) => {
return (
<svg
width="28"
height="22"
viewBox="0 0 28 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M11 0H2.75C1.2375 0 0 1.2375 0 2.75V19.25C0 20.7625 1.2375 22 2.75 22H24.75C26.2625 22 27.5 20.7625 27.5 19.25V5.5C27.5 3.9875 26.2625 2.75 24.75 2.75H13.75L11 0Z"
fill="currentColor"
/>
</svg>
);
};
const MoreIcon = ({ className }: { className?: string }) => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M19 8H5C4.73478 8 4.48043 7.89464 4.29289 7.70711C4.10536 7.51957 4 7.26522 4 7C4 6.73478 4.10536 6.48043 4.29289 6.29289C4.48043 6.10536 4.73478 6 5 6H19C19.2652 6 19.5196 6.10536 19.7071 6.29289C19.8946 6.48043 20 6.73478 20 7C20 7.26522 19.8946 7.51957 19.7071 7.70711C19.5196 7.89464 19.2652 8 19 8Z"
fill="currentColor"
/>
<path
d="M19 13H5C4.73478 13 4.48043 12.8946 4.29289 12.7071C4.10536 12.5196 4 12.2652 4 12C4 11.7348 4.10536 11.4804 4.29289 11.2929C4.48043 11.1054 4.73478 11 5 11H19C19.2652 11 19.5196 11.1054 19.7071 11.2929C19.8946 11.4804 20 11.7348 20 12C20 12.2652 19.8946 12.5196 19.7071 12.7071C19.5196 12.8946 19.2652 13 19 13Z"
fill="currentColor"
/>
<path
d="M19 18H5C4.73478 18 4.48043 17.8946 4.29289 17.7071C4.10536 17.5196 4 17.2652 4 17C4 16.7348 4.10536 16.4804 4.29289 16.2929C4.48043 16.1054 4.73478 16 5 16H19C19.2652 16 19.5196 16.1054 19.7071 16.2929C19.8946 16.4804 20 16.7348 20 17C20 17.2652 19.8946 17.5196 19.7071 17.7071C19.5196 17.8946 19.2652 18 19 18Z"
fill="currentColor"
/>
</svg>
);
};
const RecentIcon = ({ className }: { className?: string }) => {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_323_1182)">
<path
d="M6 0C4.81331 0 3.65328 0.351894 2.66658 1.01118C1.67989 1.67047 0.910851 2.60754 0.456726 3.7039C0.00259972 4.80026 -0.11622 6.00666 0.115291 7.17054C0.346802 8.33443 0.918247 9.40353 1.75736 10.2426C2.59648 11.0818 3.66558 11.6532 4.82946 11.8847C5.99335 12.1162 7.19975 11.9974 8.2961 11.5433C9.39246 11.0892 10.3295 10.3201 10.9888 9.33342C11.6481 8.34673 12 7.18669 12 6C11.9983 4.40923 11.3656 2.88411 10.2407 1.75926C9.1159 0.634414 7.59077 0.00172054 6 0ZM6 11C5.0111 11 4.0444 10.7068 3.22215 10.1573C2.39991 9.60794 1.75904 8.82705 1.38061 7.91342C1.00217 6.99979 0.90315 5.99445 1.09608 5.02455C1.289 4.05464 1.76521 3.16373 2.46447 2.46447C3.16373 1.7652 4.05465 1.289 5.02455 1.09607C5.99446 0.903148 6.99979 1.00216 7.91342 1.3806C8.82705 1.75904 9.60794 2.3999 10.1574 3.22215C10.7068 4.04439 11 5.01109 11 6C10.9985 7.32564 10.4713 8.59656 9.53393 9.53393C8.59656 10.4713 7.32564 10.9985 6 11Z"
fill="currentColor"
/>
<path
d="M6.5 5.793V3C6.5 2.86739 6.44732 2.74021 6.35355 2.64645C6.25979 2.55268 6.13261 2.5 6 2.5C5.86739 2.5 5.74021 2.55268 5.64645 2.64645C5.55268 2.74021 5.5 2.86739 5.5 3V6C5.50003 6.1326 5.55273 6.25975 5.6465 6.3535L7.1465 7.8535C7.2408 7.94458 7.3671 7.99498 7.4982 7.99384C7.6293 7.9927 7.75471 7.94011 7.84741 7.84741C7.94011 7.75471 7.9927 7.6293 7.99384 7.4982C7.99498 7.3671 7.94458 7.2408 7.8535 7.1465L6.5 5.793Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_323_1182">
<rect width="12" height="12" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};

View File

@ -23,9 +23,10 @@ export const Field = ({
const errorId = errors?.length ? `${id}-error` : undefined
return (
<div className={className}>
<Label {...labelProps} htmlFor={id} />
<Label {...labelProps} htmlFor={id} className="font-semibold text-sm" />
<Input
{...inputProps}
className="mt-4"
id={id}
aria-invalid={errorId ? true : undefined}
aria-describedby={errorId}

View File

@ -1,20 +1,20 @@
import { Button } from "~/components/ui/button"
import logoPng from "~/images/lume-logo.png?url"
import lumeColorLogoPng from "~/images/lume-color-logo.png?url"
import discordLogoPng from "~/images/discord-logo.png?url"
import { Link } from "@remix-run/react"
import { Button } from "~/components/ui/button";
import logoPng from "~/images/lume-logo.png?url";
import lumeColorLogoPng from "~/images/lume-color-logo.png?url";
import discordLogoPng from "~/images/discord-logo.png?url";
import { Link, useLocation } from "@remix-run/react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger
} from "~/components/ui/dialog"
import { useUppy } from "./lib/uppy"
import type { UppyFile } from "@uppy/core"
import { Progress } from "~/components/ui/progress"
import { DialogClose } from "@radix-ui/react-dialog"
import { TrashIcon } from "@radix-ui/react-icons"
DialogTrigger,
} from "~/components/ui/dialog";
import { useUppy } from "./lib/uppy";
import type { UppyFile } from "@uppy/core";
import { Progress } from "~/components/ui/progress";
import { DialogClose } from "@radix-ui/react-dialog";
import { ChevronDownIcon, TrashIcon } from "@radix-ui/react-icons";
import {
ClockIcon,
DriveIcon,
@ -22,10 +22,15 @@ import {
CloudUploadIcon,
CloudCheckIcon,
BoxCheckedIcon,
PageIcon
} from "./icons"
PageIcon,
ThemeIcon,
} from "./icons";
import { DropdownMenu, DropdownMenuTrigger } from "./ui/dropdown-menu";
import { Avatar } from "@radix-ui/react-avatar";
import { cn } from "~/utils";
export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
const location = useLocation();
return (
<div className="h-full flex flex-row">
<header className="p-10 pr-0 flex flex-col w-[240px] h-full scroll-m-0 overflow-hidden">
@ -35,7 +40,8 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
<ul>
<li>
<Link to="/dashboard">
<NavigationButton>
<NavigationButton
active={location.pathname.includes("dashboard")}>
<ClockIcon className="w-5 h-5 mr-2" />
Dashboard
</NavigationButton>
@ -43,7 +49,8 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
</li>
<li>
<Link to="/file-manager">
<NavigationButton>
<NavigationButton
active={location.pathname.includes("file-manager")}>
<DriveIcon className="w-5 h-5 mr-2" />
File Manager
</NavigationButton>
@ -51,7 +58,8 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
</li>
<li>
<Link to="/account">
<NavigationButton>
<NavigationButton
active={location.pathname.includes("account")}>
<CircleLockIcon className="w-5 h-5 mr-2" />
Account
</NavigationButton>
@ -71,13 +79,29 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
Upload Files
</Button>
</DialogTrigger>
<DialogContent>
<DialogContent className="border rounded-lg p-8">
<UploadFileForm />
</DialogContent>
</Dialog>
</header>
<div className="flex-1 overflow-y-auto p-10">
<div className="flex items-center gap-x-4 justify-end">
<Button variant="ghost" className="rounded-full w-fit">
<ThemeIcon className="text-ring" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger>
<Button className="border rounded-full h-auto p-2 gap-x-2 text-ring font-semibold">
<Avatar className="bg-ring h-7 w-7 rounded-full" />
whirly10
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
</DropdownMenu>
</div>
{children}
<footer className="mt-5">
@ -86,8 +110,7 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
<Link to="https://discord.lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
className="flex flex-row gap-x-2 text-input-placeholder">
<img
className="h-5"
src={discordLogoPng}
@ -101,8 +124,7 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
<Link to="https://lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
className="flex flex-row gap-x-2 text-input-placeholder">
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
Connect with us
</Button>
@ -112,8 +134,8 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
</footer>
</div>
</div>
)
}
);
};
const UploadFileForm = () => {
const {
@ -123,17 +145,17 @@ const UploadFileForm = () => {
upload,
state,
removeFile,
cancelAll
cancelAll,
} = useUppy({
uploader: "tus",
endpoint: import.meta.env.VITE_PUBLIC_TUS_ENDPOINT
})
endpoint: import.meta.env.VITE_PUBLIC_TUS_ENDPOINT,
});
console.log({ state, files: getFiles() })
console.log({ state, files: getFiles() });
const isUploading = state === "uploading"
const isCompleted = state === "completed"
const hasStarted = state !== "idle" && state !== "initializing"
const isUploading = state === "uploading";
const isCompleted = state === "completed";
const hasStarted = state !== "idle" && state !== "initializing";
return (
<>
@ -143,8 +165,7 @@ const UploadFileForm = () => {
{!hasStarted ? (
<div
{...getRootProps()}
className="border border-border rounded text-primary-2 bg-primary-dark h-48 flex flex-col items-center justify-center"
>
className="border border-border rounded text-primary-2 bg-primary-dark h-48 flex flex-col items-center justify-center">
<input
hidden
aria-hidden
@ -164,7 +185,7 @@ const UploadFileForm = () => {
key={file.id}
file={file}
onRemove={(id) => {
removeFile(id)
removeFile(id);
}}
/>
))}
@ -201,19 +222,19 @@ const UploadFileForm = () => {
</Button>
) : null}
</>
)
}
);
};
function bytestoMegabytes(bytes: number) {
return bytes / 1024 / 1024
return bytes / 1024 / 1024;
}
const UploadFileItem = ({
file,
onRemove
onRemove,
}: {
file: UppyFile
onRemove: (id: string) => void
file: UppyFile;
onRemove: (id: string) => void;
}) => {
return (
<div className="flex flex-col w-full py-4 px-2 bg-primary-dark">
@ -237,8 +258,7 @@ const UploadFileItem = ({
size={"icon"}
variant={"ghost"}
className="!text-inherit"
onClick={() => onRemove(file.id)}
>
onClick={() => onRemove(file.id)}>
<TrashIcon className="w-4 h-4" />
</Button>
</div>
@ -247,13 +267,21 @@ const UploadFileItem = ({
<Progress max={100} value={file.progress.percentage} className="mt-2" />
) : null}
</div>
)
}
);
};
const NavigationButton = ({ children }: React.PropsWithChildren) => {
const NavigationButton = ({
children,
active,
}: React.PropsWithChildren<{ active?: boolean }>) => {
return (
<Button variant="ghost" className="justify-start h-14 w-full font-semibold">
<Button
variant="ghost"
className={cn(
"justify-start h-14 w-full font-semibold",
active && "bg-secondary-1 text-secondary-1-foreground",
)}>
{children}
</Button>
)
}
);
};

View File

@ -7,8 +7,7 @@ export const InfoIcon = ({ className }: { className?: string }) => {
viewBox="0 0 23 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<g clipPath="url(#clip0_295_884)">
<path
d="M11.5067 2C6.21422 2 1.92755 6.475 1.92755 12C1.92755 17.525 6.21422 22 11.5067 22C16.7992 22 21.0859 17.525 21.0859 12C21.0859 6.475 16.7992 2 11.5067 2ZM12.4646 17H10.5488V11H12.4646V17ZM12.4646 9H10.5488V7H12.4646V9Z"
@ -26,8 +25,8 @@ export const InfoIcon = ({ className }: { className?: string }) => {
</clipPath>
</defs>
</svg>
)
}
);
};
export const AddIcon = ({ className }: { className?: string }) => {
return (
@ -38,8 +37,7 @@ export const AddIcon = ({ className }: { className?: string }) => {
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<g clipPath="url(#clip0_323_1258)">
<path
d="M9 1.5C4.85625 1.5 1.5 4.85625 1.5 9C1.5 13.1438 4.85625 16.5 9 16.5C13.1438 16.5 16.5 13.1438 16.5 9C16.5 4.85625 13.1438 1.5 9 1.5ZM12.75 9.75H9.75V12.75H8.25V9.75H5.25V8.25H8.25V5.25H9.75V8.25H12.75V9.75Z"
@ -52,8 +50,8 @@ export const AddIcon = ({ className }: { className?: string }) => {
</clipPath>
</defs>
</svg>
)
}
);
};
export const CloudIcon = ({ className }: { className?: string }) => {
return (
@ -64,15 +62,14 @@ export const CloudIcon = ({ className }: { className?: string }) => {
viewBox="0 0 23 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M19.5194 13.3041C19.5166 13.3041 19.5124 13.3056 19.5095 13.3056C19.7518 12.1637 19.7036 10.917 19.0902 9.59094C18.2217 7.7138 16.4438 6.31126 14.3938 6.05058C11.5434 5.6879 9.02304 7.31287 8.01859 9.72836C7.68 9.59802 7.31165 9.52577 6.92631 9.52577C5.188 9.52577 3.77838 10.9354 3.77838 12.6737C3.77838 12.923 3.81379 13.1611 3.86905 13.3934C3.11394 13.2163 2.28658 13.2361 1.28355 14.0465C0.53836 14.65 -0.00990665 15.5284 1.03265e-05 16.4875C0.0198443 18.2102 1.42097 19.6 3.14794 19.6H19.3679C20.8398 19.6 22.2282 18.6636 22.5725 17.2327C23.0726 15.1558 21.5128 13.3041 19.5194 13.3041Z"
fill="currentColor"
/>
</svg>
)
}
);
};
export const CloudDownloadIcon = ({ className }: { className?: string }) => {
return (
@ -83,8 +80,7 @@ export const CloudDownloadIcon = ({ className }: { className?: string }) => {
viewBox="0 0 21 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
fillRule="evenodd"
clipRule="evenodd"
@ -92,8 +88,8 @@ export const CloudDownloadIcon = ({ className }: { className?: string }) => {
fill="currentColor"
/>
</svg>
)
}
);
};
export const CloudUploadIcon = ({ className }: { className?: string }) => {
return (
@ -104,15 +100,14 @@ export const CloudUploadIcon = ({ className }: { className?: string }) => {
viewBox="-0.5 0 21 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M16.6551 4.54509C16.0058 3.1411 14.8851 1.97617 13.4647 1.22882C12.0444 0.481467 10.4029 0.192923 8.79158 0.4074C7.18031 0.621877 5.68822 1.32754 4.54396 2.41627C3.39969 3.50499 2.66638 4.91671 2.45638 6.43509C1.44311 6.66341 0.554244 7.23392 -0.0414276 8.03828C-0.637099 8.84264 -0.898085 9.82482 -0.774825 10.7983C-0.651564 11.7718 -0.15264 12.6688 0.627199 13.319C1.40704 13.9691 2.41348 14.3272 3.4554 14.3251C3.73727 14.3251 4.00759 14.2197 4.2069 14.0322C4.40621 13.8447 4.51818 13.5903 4.51818 13.3251C4.51818 13.0599 4.40621 12.8055 4.2069 12.618C4.00759 12.4304 3.73727 12.3251 3.4554 12.3251C2.89166 12.3251 2.35102 12.1144 1.9524 11.7393C1.55378 11.3642 1.32984 10.8555 1.32984 10.3251C1.32984 9.79466 1.55378 9.28595 1.9524 8.91088C2.35102 8.5358 2.89166 8.32509 3.4554 8.32509C3.73727 8.32509 4.00759 8.21973 4.2069 8.0322C4.40621 7.84466 4.51818 7.59031 4.51818 7.32509C4.52089 6.14237 4.9691 4.99884 5.78317 4.09767C6.59725 3.1965 7.72447 2.59604 8.96458 2.40296C10.2047 2.20989 11.4774 2.4367 12.5566 3.0431C13.6358 3.6495 14.4516 4.59623 14.859 5.71509C14.9198 5.88693 15.029 6.04002 15.175 6.15802C15.321 6.27603 15.4983 6.35451 15.688 6.38509C16.3959 6.51096 17.0376 6.85869 17.5086 7.37162C17.9795 7.88456 18.252 8.53245 18.2816 9.20973C18.3112 9.88701 18.0961 10.5538 17.6715 11.1013C17.2468 11.6489 16.6376 12.045 15.9431 12.2251C15.6697 12.2914 15.4354 12.4572 15.2919 12.686C15.1484 12.9148 15.1074 13.1878 15.1779 13.4451C15.2483 13.7024 15.4245 13.9227 15.6677 14.0578C15.9108 14.1928 16.201 14.2314 16.4745 14.1651C17.5929 13.887 18.5844 13.2731 19.2983 12.4166C20.0122 11.56 20.4095 10.5077 20.4299 9.41936C20.4504 8.33102 20.0928 7.26613 19.4115 6.38641C18.7302 5.50669 17.7624 4.86019 16.6551 4.54509ZM10.5867 6.61509C10.4856 6.52405 10.3664 6.45268 10.2359 6.40509C9.97719 6.30507 9.68697 6.30507 9.42822 6.40509C9.29777 6.45268 9.17858 6.52405 9.07751 6.61509L5.88916 9.61509C5.68904 9.80339 5.57661 10.0588 5.57661 10.3251C5.57661 10.5914 5.68904 10.8468 5.88916 11.0351C6.08929 11.2234 6.36072 11.3292 6.64374 11.3292C6.92676 11.3292 7.19819 11.2234 7.39831 11.0351L8.7693 9.73509V15.3251C8.7693 15.5903 8.88127 15.8447 9.08058 16.0322C9.27989 16.2197 9.55021 16.3251 9.83208 16.3251C10.1139 16.3251 10.3843 16.2197 10.5836 16.0322C10.7829 15.8447 10.8949 15.5903 10.8949 15.3251V9.73509L12.2658 11.0351C12.3646 11.1288 12.4822 11.2032 12.6117 11.254C12.7412 11.3048 12.8801 11.3309 13.0204 11.3309C13.1607 11.3309 13.2996 11.3048 13.4291 11.254C13.5587 11.2032 13.6762 11.1288 13.775 11.0351C13.8746 10.9421 13.9537 10.8315 14.0076 10.7097C14.0616 10.5878 14.0894 10.4571 14.0894 10.3251C14.0894 10.1931 14.0616 10.0624 14.0076 9.94051C13.9537 9.81865 13.8746 9.70805 13.775 9.61509L10.5867 6.61509Z"
fill="currentColor"
/>
</svg>
)
}
);
};
export const CloudUploadSolidIcon = ({ className }: { className?: string }) => {
return (
@ -123,15 +118,14 @@ export const CloudUploadSolidIcon = ({ className }: { className?: string }) => {
viewBox="0 0 24 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M19.4 6C18.7 2.6 15.7 0 12 0C9.1 0 6.6 1.6 5.4 4C2.3 4.4 0 6.9 0 10C0 13.3 2.7 16 6 16H19C21.8 16 24 13.8 24 11C24 8.4 21.9 6.2 19.4 6ZM14 9V13H10V9H7L12 4L17 9H14Z"
fill="currentColor"
/>
</svg>
)
}
);
};
export const CrownIcon = ({ className }: { className?: string }) => {
return (
@ -142,15 +136,14 @@ export const CrownIcon = ({ className }: { className?: string }) => {
viewBox="0 0 20 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M18.649 3.74215C18.4702 3.59292 18.2531 3.49694 18.0224 3.46517C17.7917 3.4334 17.5568 3.46711 17.3443 3.56246L13.3912 5.32028L11.0943 1.17965C10.9845 0.986336 10.8255 0.825572 10.6334 0.713731C10.4412 0.60189 10.2229 0.542969 10.0006 0.542969C9.77826 0.542969 9.55992 0.60189 9.36779 0.713731C9.17566 0.825572 9.0166 0.986336 8.90682 1.17965L6.60995 5.32028L2.65682 3.56246C2.44394 3.46725 2.20866 3.43349 1.97759 3.465C1.74652 3.49651 1.52888 3.59203 1.34926 3.74077C1.16964 3.8895 1.03521 4.08552 0.961163 4.30666C0.887119 4.5278 0.876414 4.76525 0.930259 4.99215L2.91463 13.4531C2.95258 13.6169 3.02338 13.7713 3.12276 13.9069C3.22213 14.0426 3.34801 14.1566 3.49276 14.2422C3.68873 14.3595 3.9128 14.4215 4.1412 14.4218C4.25222 14.4216 4.36268 14.4059 4.46932 14.375C8.08637 13.3749 11.907 13.3749 15.524 14.375C15.8543 14.4618 16.2055 14.414 16.5006 14.2422C16.6462 14.1577 16.7728 14.044 16.8723 13.9082C16.9718 13.7724 17.0421 13.6174 17.0787 13.4531L19.0709 4.99215C19.1241 4.76518 19.1128 4.52785 19.0383 4.30696C18.9637 4.08608 18.8289 3.89044 18.649 3.74215Z"
fill="currentColor"
/>
</svg>
)
}
);
};
export const PersonIcon = ({ className }: { className?: string }) => {
return (
@ -161,15 +154,14 @@ export const PersonIcon = ({ className }: { className?: string }) => {
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M9.99935 1.6665C5.39518 1.6665 1.66602 5.39567 1.66602 9.99984C1.66602 14.604 5.39518 18.3332 9.99935 18.3332C14.6035 18.3332 18.3327 14.604 18.3327 9.99984C18.3327 5.39567 14.6035 1.6665 9.99935 1.6665ZM9.99935 4.1665C11.3785 4.1665 12.4993 5.28734 12.4993 6.6665C12.4993 8.04984 11.3785 9.1665 9.99935 9.1665C8.62018 9.1665 7.49935 8.04984 7.49935 6.6665C7.49935 5.28734 8.62018 4.1665 9.99935 4.1665ZM9.99935 15.9998C7.91185 15.9998 6.07852 14.9332 4.99935 13.3165C5.02018 11.6623 8.33685 10.7498 9.99935 10.7498C11.6618 10.7498 14.9743 11.6623 14.9993 13.3165C13.9202 14.9332 12.0868 15.9998 9.99935 15.9998Z"
fill="currentColor"
/>
</svg>
)
}
);
};
export const CheckRoundedIcon = ({ className }: { className?: string }) => {
return (
@ -180,15 +172,14 @@ export const CheckRoundedIcon = ({ className }: { className?: string }) => {
viewBox="0 0 18 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M9 1C7.36831 1 5.77325 1.48385 4.41655 2.39038C3.05984 3.2969 2.00242 4.58537 1.378 6.09286C0.753575 7.60035 0.590197 9.25915 0.908525 10.8595C1.22685 12.4598 2.01259 13.9298 3.16637 15.0836C4.32016 16.2374 5.79017 17.0231 7.39051 17.3415C8.99085 17.6598 10.6497 17.4964 12.1571 16.872C13.6646 16.2476 14.9531 15.1902 15.8596 13.8335C16.7661 12.4767 17.25 10.8817 17.25 9.25C17.25 7.06196 16.3808 4.96354 14.8336 3.41637C13.2865 1.86919 11.188 1 9 1ZM13.2803 7.53025L8.03025 12.7802C7.88961 12.9209 7.69888 12.9998 7.5 12.9998C7.30113 12.9998 7.1104 12.9209 6.96975 12.7802L4.71975 10.5302C4.58314 10.3888 4.50754 10.1993 4.50925 10.0027C4.51096 9.80605 4.58983 9.61794 4.72889 9.47889C4.86795 9.33983 5.05606 9.26095 5.2527 9.25924C5.44935 9.25753 5.6388 9.33313 5.78025 9.46975L7.5 11.1895L12.2198 6.46975C12.3612 6.33313 12.5507 6.25754 12.7473 6.25924C12.944 6.26095 13.1321 6.33983 13.2711 6.47889C13.4102 6.61794 13.4891 6.80605 13.4908 7.0027C13.4925 7.19935 13.4169 7.3888 13.2803 7.53025Z"
fill="currentColor"
/>
</svg>
)
}
);
};
export const ClockIcon = ({ className }: { className?: string }) => {
return (
@ -199,15 +190,14 @@ export const ClockIcon = ({ className }: { className?: string }) => {
viewBox="0 0 23 23"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M11.1703 1.8623C16.3127 1.8623 20.4812 6.03079 20.4812 11.1732C20.4812 16.3156 16.3127 20.4841 11.1703 20.4841C6.02786 20.4841 1.85938 16.3156 1.85938 11.1732C1.85938 6.03079 6.02786 1.8623 11.1703 1.8623ZM15.4496 6.89391C15.2596 6.70304 14.9598 6.67697 14.7391 6.83153C12.0483 8.71978 10.5306 9.83895 10.1824 10.1853C9.63769 10.7309 9.63769 11.6155 10.1824 12.1611C10.728 12.7058 11.6125 12.7058 12.1582 12.1611C12.3621 11.9562 13.4784 10.4376 15.5082 7.60154C15.6646 7.38366 15.6395 7.08385 15.4496 6.89391ZM16.2913 10.2421C15.7773 10.2421 15.3602 10.6592 15.3602 11.1732C15.3602 11.6872 15.7773 12.1043 16.2913 12.1043C16.8052 12.1043 17.2223 11.6872 17.2223 11.1732C17.2223 10.6592 16.8052 10.2421 16.2913 10.2421ZM6.04928 10.2421C5.53532 10.2421 5.11819 10.6592 5.11819 11.1732C5.11819 11.6872 5.53532 12.1043 6.04928 12.1043C6.56324 12.1043 6.98037 11.6872 6.98037 11.1732C6.98037 10.6592 6.56324 10.2421 6.04928 10.2421ZM8.20754 6.89391C7.84442 6.53079 7.25411 6.53079 6.89098 6.89391C6.52786 7.25704 6.52786 7.84642 6.89098 8.21047C7.25411 8.5736 7.84349 8.5736 8.20754 8.21047C8.57067 7.84735 8.57067 7.25704 8.20754 6.89391ZM11.1703 5.12112C10.6563 5.12112 10.2392 5.53825 10.2392 6.05221C10.2392 6.56617 10.6563 6.9833 11.1703 6.9833C11.6842 6.9833 12.1014 6.56617 12.1014 6.05221C12.1014 5.53825 11.6842 5.12112 11.1703 5.12112Z"
fill="currentColor"
/>
</svg>
)
}
);
};
export const CircleLockIcon = ({ className }: { className?: string }) => {
return (
@ -218,15 +208,14 @@ export const CircleLockIcon = ({ className }: { className?: string }) => {
viewBox="0 0 23 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M11.1722 2.56738C16.3146 2.56738 20.4831 6.73587 20.4831 11.8783C20.4831 13.8671 19.8593 15.7106 18.7969 17.2237L15.8277 11.8783H18.6209C18.6208 10.1615 18.0277 8.49755 16.9419 7.16779C15.8561 5.83804 14.3443 4.92415 12.6623 4.58073C10.9802 4.23731 9.23121 4.48544 7.71106 5.28315C6.19092 6.08086 4.99298 7.37917 4.31989 8.95846C3.64681 10.5377 3.5399 12.301 4.01726 13.9501C4.49461 15.5991 5.52693 17.0326 6.93957 18.0082C8.3522 18.9837 10.0584 19.4413 11.7697 19.3036C13.4809 19.1659 15.092 18.4414 16.3305 17.2525L17.2597 18.9238C15.5699 20.388 13.4081 21.1925 11.1722 21.1892C6.02982 21.1892 1.86133 17.0207 1.86133 11.8783C1.86133 6.73587 6.02982 2.56738 11.1722 2.56738ZM11.1722 7.22283C11.913 7.22283 12.6235 7.51712 13.1474 8.04096C13.6712 8.5648 13.9655 9.27528 13.9655 10.0161V10.9472H14.8966V15.6026H7.44786V10.9472H8.37895V10.0161C8.37895 9.27528 8.67324 8.5648 9.19708 8.04096C9.72092 7.51712 10.4314 7.22283 11.1722 7.22283ZM11.1722 9.08501C10.9442 9.08504 10.7241 9.16877 10.5536 9.32031C10.3832 9.47185 10.2743 9.68067 10.2477 9.90716L10.2411 10.0161V10.9472H12.1033V10.0161C12.1033 9.78804 12.0196 9.56793 11.868 9.39751C11.7165 9.22709 11.5076 9.11821 11.2812 9.09153L11.1722 9.08501Z"
fill="currentColor"
/>
</svg>
)
}
);
};
export const DriveIcon = ({ className }: { className?: string }) => {
return (
@ -237,15 +226,14 @@ export const DriveIcon = ({ className }: { className?: string }) => {
viewBox="0 0 23 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M10.2417 2.68262V7.33806H7.44842L11.1728 11.0624L14.8971 7.33806H12.1039V2.68262H18.6215C18.8684 2.68262 19.1053 2.78071 19.2799 2.95533C19.4545 3.12994 19.5526 3.36677 19.5526 3.61371V20.3733C19.5526 20.6203 19.4545 20.8571 19.2799 21.0317C19.1053 21.2063 18.8684 21.3044 18.6215 21.3044H3.72406C3.47712 21.3044 3.24029 21.2063 3.06568 21.0317C2.89107 20.8571 2.79297 20.6203 2.79297 20.3733V3.61371C2.79297 3.36677 2.89107 3.12994 3.06568 2.95533C3.24029 2.78071 3.47712 2.68262 3.72406 2.68262H10.2417ZM17.6904 15.7179H4.65515V19.4422H17.6904V15.7179ZM15.8282 16.649V18.5111H13.966V16.649H15.8282Z"
fill="currentColor"
/>
</svg>
)
}
);
};
export const PageIcon = ({ className }: { className?: string }) => {
return (
@ -256,8 +244,7 @@ export const PageIcon = ({ className }: { className?: string }) => {
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M10.3276 4.21337V15.0287H0.59375V0.96875H5.46067"
stroke="currentColor"
@ -295,8 +282,8 @@ export const PageIcon = ({ className }: { className?: string }) => {
strokeLinejoin="round"
/>
</svg>
)
}
);
};
export const TrashIcon = ({ className }: { className?: string }) => {
return (
@ -307,8 +294,7 @@ export const TrashIcon = ({ className }: { className?: string }) => {
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M10.0769 14.3093H2.69194C2.3247 14.3093 1.9725 14.1634 1.71282 13.9037C1.45314 13.6441 1.30725 13.2919 1.30725 12.9246V4.15492C1.30725 4.03251 1.35588 3.91511 1.44244 3.82855C1.529 3.74199 1.6464 3.69336 1.76881 3.69336C1.89123 3.69336 2.00863 3.74199 2.09519 3.82855C2.18175 3.91511 2.23038 4.03251 2.23038 4.15492V12.9246C2.23038 13.047 2.279 13.1644 2.36556 13.251C2.45212 13.3375 2.56952 13.3862 2.69194 13.3862H10.0769C10.1994 13.3862 10.3168 13.3375 10.4033 13.251C10.4899 13.1644 10.5385 13.047 10.5385 12.9246V4.15492C10.5385 4.03251 10.5871 3.91511 10.6737 3.82855C10.7602 3.74199 10.8776 3.69336 11.0001 3.69336C11.1225 3.69336 11.2399 3.74199 11.3264 3.82855C11.413 3.91511 11.4616 4.03251 11.4616 4.15492V12.9246C11.4616 13.2919 11.3157 13.6441 11.0561 13.9037C10.7964 14.1634 10.4442 14.3093 10.0769 14.3093Z"
fill="currentColor"
@ -334,8 +320,8 @@ export const TrashIcon = ({ className }: { className?: string }) => {
fill="currentColor"
/>
</svg>
)
}
);
};
export const CloudCheckIcon = ({ className }: { className?: string }) => {
return (
@ -346,8 +332,7 @@ export const CloudCheckIcon = ({ className }: { className?: string }) => {
viewBox="0 0 72 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
fillRule="evenodd"
clipRule="evenodd"
@ -355,8 +340,8 @@ export const CloudCheckIcon = ({ className }: { className?: string }) => {
fill="currentColor"
/>
</svg>
)
}
);
};
export const BoxCheckedIcon = ({ className }: { className?: string }) => {
return (
@ -367,8 +352,7 @@ export const BoxCheckedIcon = ({ className }: { className?: string }) => {
viewBox="0 0 21 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
fillRule="evenodd"
clipRule="evenodd"
@ -384,8 +368,8 @@ export const BoxCheckedIcon = ({ className }: { className?: string }) => {
strokeLinejoin="round"
/>
</svg>
)
}
);
};
export const PictureIcon = ({ className }: { className?: string }) => {
return (
@ -396,8 +380,7 @@ export const PictureIcon = ({ className }: { className?: string }) => {
viewBox="0 0 22 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
fillRule="evenodd"
clipRule="evenodd"
@ -405,8 +388,8 @@ export const PictureIcon = ({ className }: { className?: string }) => {
fill="currentColor"
/>
</svg>
)
}
);
};
export const XlsxIcon = ({ className }: { className?: string }) => {
return (
@ -417,8 +400,7 @@ export const XlsxIcon = ({ className }: { className?: string }) => {
viewBox="0 0 22 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
fillRule="evenodd"
clipRule="evenodd"
@ -426,8 +408,8 @@ export const XlsxIcon = ({ className }: { className?: string }) => {
fill="currentColor"
/>
</svg>
)
}
);
};
export const FileIcon = ({ className }: { className?: string }) => {
return (
@ -438,15 +420,14 @@ export const FileIcon = ({ className }: { className?: string }) => {
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M10.791 13.7082H3.20768C2.56339 13.7082 2.04102 13.1858 2.04102 12.5415V1.45817C2.04102 0.813879 2.56339 0.291504 3.20768 0.291504H8.74147C8.74206 0.291504 8.74293 0.291504 8.74352 0.291504H8.74935C8.84268 0.291504 8.92231 0.338462 8.97568 0.406712L11.8425 3.2735C11.911 3.32688 11.9577 3.4065 11.9577 3.49984V3.50596C11.9577 3.50655 11.9577 3.50684 11.9577 3.50742V12.5415C11.9577 13.1858 11.4353 13.7082 10.791 13.7082ZM9.04102 1.27763V3.20817H10.9716L9.04102 1.27763ZM11.3743 3.7915H8.74935C8.58806 3.7915 8.45768 3.66084 8.45768 3.49984V0.874837H3.20768C2.88568 0.874837 2.62435 1.13617 2.62435 1.45817V12.5415C2.62435 12.8635 2.88568 13.1248 3.20768 13.1248H10.791C11.113 13.1248 11.3743 12.8635 11.3743 12.5415V3.7915ZM9.62435 11.3748H4.37435C4.21306 11.3748 4.08268 11.2445 4.08268 11.0832C4.08268 10.9222 4.21306 10.7915 4.37435 10.7915H9.62435C9.78564 10.7915 9.91602 10.9222 9.91602 11.0832C9.91602 11.2445 9.78564 11.3748 9.62435 11.3748ZM9.62435 9.0415H4.37435C4.21306 9.0415 4.08268 8.91113 4.08268 8.74984C4.08268 8.58884 4.21306 8.45817 4.37435 8.45817H9.62435C9.78564 8.45817 9.91602 8.58884 9.91602 8.74984C9.91602 8.91113 9.78564 9.0415 9.62435 9.0415ZM9.62435 6.70817H4.37435C4.21306 6.70817 4.08268 6.5778 4.08268 6.4165C4.08268 6.2555 4.21306 6.12484 4.37435 6.12484H9.62435C9.78564 6.12484 9.91602 6.2555 9.91602 6.4165C9.91602 6.5778 9.78564 6.70817 9.62435 6.70817Z"
fill="currentColor"
/>
</svg>
)
}
);
};
export const MoreIcon = ({ className }: { className?: string }) => {
return (
@ -457,8 +438,7 @@ export const MoreIcon = ({ className }: { className?: string }) => {
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
className={className}>
<path
d="M19 8H5C4.73478 8 4.48043 7.89464 4.29289 7.70711C4.10536 7.51957 4 7.26522 4 7C4 6.73478 4.10536 6.48043 4.29289 6.29289C4.48043 6.10536 4.73478 6 5 6H19C19.2652 6 19.5196 6.10536 19.7071 6.29289C19.8946 6.48043 20 6.73478 20 7C20 7.26522 19.8946 7.51957 19.7071 7.70711C19.5196 7.89464 19.2652 8 19 8Z"
fill="currentColor"
@ -472,5 +452,140 @@ export const MoreIcon = ({ className }: { className?: string }) => {
fill="currentColor"
/>
</svg>
)
}
);
};
export const FingerPrintIcon = ({ className }: { className?: string }) => {
return (
<svg
width="23"
height="23"
viewBox="0 0 23 23"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}>
<g clipPath="url(#clip0_343_2984)">
<path
d="M11.1728 0.931152C13.3952 0.931152 15.5267 1.81402 17.0982 3.38554C18.6697 4.95706 19.5526 7.0885 19.5526 9.31096V13.0353C19.5535 14.3774 19.2318 15.7001 18.6144 16.8918C17.997 18.0835 17.1021 19.1092 16.0051 19.8826C16.4465 18.4654 16.7035 16.9673 16.7519 15.4161L16.7593 14.8975V13.0344H14.8971V14.8975L14.8944 15.1908C14.8574 17.2213 14.4039 19.2225 13.562 21.0706C12.4834 21.3893 11.3519 21.4887 10.2342 21.363C11.3872 19.5425 12.0306 17.4461 12.0974 15.2923L12.1039 14.8975V8.37987H10.2417V14.8975L10.238 15.1657C10.1874 17.2324 9.5097 19.235 8.29479 20.9077C7.40642 20.5826 6.57967 20.1091 5.84975 19.5073C6.82259 18.2772 7.38005 16.77 7.44191 15.2029L7.44843 14.8975V9.31096L7.45308 9.12474C7.47869 8.5973 7.61679 8.08142 7.85811 7.61172L7.96425 7.41899L6.61883 6.07356C5.99989 6.94167 5.64371 7.96949 5.59277 9.03443L5.58625 9.31096V14.8975L5.58252 15.107C5.54313 16.2036 5.17995 17.264 4.53877 18.1545C3.40438 16.6894 2.79013 14.8882 2.79298 13.0353V9.31096C2.79298 7.0885 3.67585 4.95706 5.24737 3.38554C6.81889 1.81402 8.95032 0.931152 11.1728 0.931152ZM11.1728 3.72442C10.0592 3.72442 9.02197 4.0503 8.15047 4.61175L7.93632 4.757L9.28081 6.10243C9.7871 5.80291 10.3575 5.62832 10.9447 5.59312L11.1728 5.5866L11.359 5.59126C12.2803 5.63731 13.1517 6.02362 13.8045 6.67537C14.4573 7.32712 14.845 8.19794 14.8925 9.11916L14.8971 9.31096V11.1731H16.7593V9.31096C16.7593 7.82932 16.1707 6.40836 15.1231 5.36068C14.0754 4.313 12.6544 3.72442 11.1728 3.72442Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_343_2984">
<rect width="22.3461" height="22.3461" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};
export const EditIcon = ({ className }: { className?: string }) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}>
<g clipPath="url(#clip0_355_3166)">
<path
d="M7.72289 2.8954L9.96834 5.14086L3.06357 12.0456H0.818115V9.80018L7.72289 2.8954ZM8.5088 2.1095L9.79993 0.818359L12.0454 3.06381L10.7543 4.35495L8.5088 2.1095Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_355_3166">
<rect
width="16"
height="16"
fill="currentColor"
transform="translate(0.818115 0.818359)"
/>
</clipPath>
</defs>
</svg>
);
};
export const ThemeIcon = ({ className }: { className?: string }) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}>
<path
d="M9.99935 18.3332C14.6017 18.3332 18.3327 14.6022 18.3327 9.99984C18.3327 5.39746 14.6017 1.6665 9.99935 1.6665C5.39697 1.6665 1.66602 5.39746 1.66602 9.99984C1.66602 14.6022 5.39697 18.3332 9.99935 18.3332ZM9.99935 16.6665V3.33317C13.6813 3.33317 16.666 6.31794 16.666 9.99984C16.666 13.6818 13.6813 16.6665 9.99935 16.6665Z"
fill="currentColor"
/>
</svg>
);
};
export const ChevronDownIcon = ({ className }: { className?: string }) => {
return (
<svg
width="13"
height="7"
viewBox="0 0 13 7"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M3.00406 0.571289C2.11315 0.571289 1.66699 1.64843 2.29695 2.2784L5.59966 5.5811C5.99018 5.97163 6.62335 5.97163 7.01387 5.58111L10.3166 2.2784C10.9465 1.64843 10.5004 0.571289 9.60948 0.571289H3.00406Z"
fill="currentColor"
/>
</svg>
);
};
export const FolderIcon = ({ className }: { className?: string }) => {
return (
<svg
width="28"
height="22"
viewBox="0 0 28 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M11 0H2.75C1.2375 0 0 1.2375 0 2.75V19.25C0 20.7625 1.2375 22 2.75 22H24.75C26.2625 22 27.5 20.7625 27.5 19.25V5.5C27.5 3.9875 26.2625 2.75 24.75 2.75H13.75L11 0Z"
fill="currentColor"
/>
</svg>
);
};
export const RecentIcon = ({ className }: { className?: string }) => {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_323_1182)">
<path
d="M6 0C4.81331 0 3.65328 0.351894 2.66658 1.01118C1.67989 1.67047 0.910851 2.60754 0.456726 3.7039C0.00259972 4.80026 -0.11622 6.00666 0.115291 7.17054C0.346802 8.33443 0.918247 9.40353 1.75736 10.2426C2.59648 11.0818 3.66558 11.6532 4.82946 11.8847C5.99335 12.1162 7.19975 11.9974 8.2961 11.5433C9.39246 11.0892 10.3295 10.3201 10.9888 9.33342C11.6481 8.34673 12 7.18669 12 6C11.9983 4.40923 11.3656 2.88411 10.2407 1.75926C9.1159 0.634414 7.59077 0.00172054 6 0ZM6 11C5.0111 11 4.0444 10.7068 3.22215 10.1573C2.39991 9.60794 1.75904 8.82705 1.38061 7.91342C1.00217 6.99979 0.90315 5.99445 1.09608 5.02455C1.289 4.05464 1.76521 3.16373 2.46447 2.46447C3.16373 1.7652 4.05465 1.289 5.02455 1.09607C5.99446 0.903148 6.99979 1.00216 7.91342 1.3806C8.82705 1.75904 9.60794 2.3999 10.1574 3.22215C10.7068 4.04439 11 5.01109 11 6C10.9985 7.32564 10.4713 8.59656 9.53393 9.53393C8.59656 10.4713 7.32564 10.9985 6 11Z"
fill="currentColor"
/>
<path
d="M6.5 5.793V3C6.5 2.86739 6.44732 2.74021 6.35355 2.64645C6.25979 2.55268 6.13261 2.5 6 2.5C5.86739 2.5 5.74021 2.55268 5.64645 2.64645C5.55268 2.74021 5.5 2.86739 5.5 3V6C5.50003 6.1326 5.55273 6.25975 5.6465 6.3535L7.1465 7.8535C7.2408 7.94458 7.3671 7.99498 7.4982 7.99384C7.6293 7.9927 7.75471 7.94011 7.84741 7.84741C7.94011 7.75471 7.9927 7.6293 7.99384 7.4982C7.99498 7.3671 7.94458 7.2408 7.8535 7.1465L6.5 5.793Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_323_1182">
<rect width="12" height="12" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};

View File

@ -0,0 +1,123 @@
import { cn } from "~/utils";
import { Avatar } from "./ui/avatar";
import { Button } from "./ui/button";
import { AddIcon, EditIcon, FingerPrintIcon } from "./icons";
import { Dialog, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
import { DialogContent, Portal } from "@radix-ui/react-dialog";
interface ManagementCardProps {
title?: string;
value?: string;
subtitle?: string;
isInviteCard?: boolean;
isPasswordCard?: boolean;
isAvatarCard?: boolean;
isDeleteCard?: boolean
buttonText?: string;
buttonOnClick?: () => void
dialogNode?: React.ReactNode
}
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 }) => {
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>
);
};

View File

@ -14,7 +14,7 @@ const buttonVariants = cva(
// TODO: name it better
accent: "bg-ring text-primary-1-foreground hover:bg-ring/75 font-bold",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
"bg-destructive text-white shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-primary-2/5",
secondary:

View File

@ -0,0 +1,203 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from "@radix-ui/react-icons"
import { cn } from "~/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<DotFilledIcon className="h-4 w-4 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View File

@ -5,7 +5,7 @@ import { EyeOpenIcon, EyeNoneIcon } from "@radix-ui/react-icons"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
fullWidth: boolean,
fullWidth?: boolean,
leftIcon?: React.ReactNode
}
@ -36,7 +36,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
/>
{type === "password" ? <button
type="button"
className="absolute right-4 top-5 text-input"
className="absolute right-4 top-5 text-ring"
onClick={toggleShowPassword}
onKeyDown={toggleShowPassword}
>

View File

@ -6,10 +6,11 @@ interface UsageCardProps {
label: string,
monthlyUsage: number, // Asumming that the minimium is 1GB
currentUsage: number,
icon: React.ReactNode
icon: React.ReactNode,
button?: React.ReactNode;
}
export const UsageCard = ({ label, monthlyUsage, currentUsage, icon }: UsageCardProps) => {
export const UsageCard = ({ label, monthlyUsage, currentUsage, icon, button }: UsageCardProps) => {
return (
<div className="p-8 border rounded-lg w-full">
<div className="flex items-center justify-between mb-8">
@ -20,10 +21,14 @@ export const UsageCard = ({ label, monthlyUsage, currentUsage, icon }: UsageCard
</div>
Montly {label.toLocaleLowerCase()} limit is {monthlyUsage} GB
</div>
<Button className="gap-x-2 h-12">
<AddIcon />
Add More
</Button>
{!button ? (
<Button className="gap-x-2 h-12">
<AddIcon />
Add More
</Button>
) : (
button
)}
</div>
<Progress max={monthlyUsage} value={currentUsage} />
<div className="flex items-center justify-between mt-4 font-semibold text-sm">
@ -32,4 +37,4 @@ export const UsageCard = ({ label, monthlyUsage, currentUsage, icon }: UsageCard
</div>
</div>
)
}
}

BIN
app/images/QR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

296
app/routes/account.tsx Normal file
View File

@ -0,0 +1,296 @@
import { getFormProps, useForm } from "@conform-to/react";
import { getZodConstraint, parseWithZod } from "@conform-to/zod";
import { useState } from "react";
import { z } from "zod";
import { Field } from "~/components/forms";
import { GeneralLayout } from "~/components/general-layout";
import { AddIcon, CloudIcon } from "~/components/icons";
import { ManagementCard } from "~/components/management-card";
import { Button } from "~/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "~/components/ui/dialog";
import { Input } from "~/components/ui/input";
import { UsageCard } from "~/components/usage-card";
import QRImg from "~/images/QR.png";
export default function MyAccount() {
const isLogged = true;
if (!isLogged) {
window.location.href = "/login";
}
const [openModal, setModal] = useState({
changeEmail: false,
changePassword: false,
setupTwoFactor: false,
});
return (
<GeneralLayout>
<h1 className="text-lg font-bold mb-4">My Account</h1>
<UsageCard
label="Usage"
currentUsage={2}
monthlyUsage={10}
icon={<CloudIcon className="text-ring" />}
button={
<Button variant="accent" className="gap-x-2 h-12">
<AddIcon />
Upgrade to Premium
</Button>
}
/>
<h2 className="font-bold my-8">Account Management</h2>
<div className="grid grid-cols-3 gap-x-8">
<ManagementCard isAvatarCard />
<ManagementCard
title="Email Address"
value="bsimpson@springfield.oh.gov.com"
buttonText="Change Email Address"
buttonOnClick={() => setModal({ ...openModal, changeEmail: true })}
/>
<ManagementCard
title="Account Type"
value="Lite Premium Account"
buttonText="Upgrade to Premium"
/>
</div>
<h2 className="font-bold my-8">Security</h2>
<div className="grid grid-cols-3 gap-x-8">
<ManagementCard
isPasswordCard
title="Password"
buttonText="Change Password"
buttonOnClick={() => setModal({ ...openModal, changePassword: true })}
/>
<ManagementCard
title="Two-Factor Authentication"
subtitle="Improve security by enabling 2FA."
buttonText="Enable Two-Factor Authorization"
buttonOnClick={() => setModal({ ...openModal, setupTwoFactor: true })}
/>
<ManagementCard
title="Backup Key"
subtitle="Never share this code with anyone."
buttonText="Export Backup Key"
/>
</div>
<h2 className="font-bold my-8">More</h2>
<div className="grid grid-cols-3 gap-x-8">
<ManagementCard
isInviteCard
title="Invite a Friend"
subtitle="Get 1 GB per friend invited for free (max 5 GB)."
buttonText="Send Invitation"
/>
<ManagementCard
title="Read our Resources"
subtitle="Navigate helpful articles or get assistance."
buttonText="Open Support Centre"
/>
<ManagementCard
isDeleteCard
title="Delete Account"
subtitle="Once initiated, this action cannot be undone."
buttonText="Delete my Account"
/>
</div>
{/* Dialogs must be near to body as possible to open the modal, otherwise will be restricted to parent height-width */}
<ChangeEmailForm
open={openModal["changeEmail"]}
setOpen={(value: boolean) =>
setModal({ ...openModal, changeEmail: value })
}
currentValue="bsimpson@springfield.oh.gov.com"
/>
<ChangePasswordForm
open={openModal["changePassword"]}
setOpen={(value: boolean) =>
setModal({ ...openModal, changePassword: value })
}
/>
<SetupTwoFactorDialog
open={openModal["setupTwoFactor"]}
setOpen={(value: boolean) =>
setModal({ ...openModal, setupTwoFactor: value })
}
/>
</GeneralLayout>
);
}
const ChangeEmailSchema = z.object({
email: z.string().email(),
password: z.string(),
retypePassword: z.string(),
});
const ChangeEmailForm = ({
open,
setOpen,
currentValue,
}: {
open: boolean;
setOpen: (value: boolean) => void;
currentValue: string;
}) => {
const [form, fields] = useForm({
id: "login",
constraint: getZodConstraint(ChangeEmailSchema),
onValidate({ formData }) {
return parseWithZod(formData, { schema: ChangeEmailSchema });
},
shouldValidate: "onSubmit",
});
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="p-8">
<DialogHeader>
<DialogTitle className="mb-8">Change Email</DialogTitle>
<div className="rounded-full px-4 py-2 w-fit text-sm bg-ring font-bold text-secondary-1">
{currentValue}
</div>
<form {...getFormProps(form)}>
<Field
className="mt-8"
inputProps={{ name: fields.email.name }}
labelProps={{ children: "New Email Address" }}
errors={fields.email.errors}
/>
<Field
inputProps={{ name: fields.password.name, type: "password" }}
labelProps={{ children: "Password" }}
errors={fields.password.errors}
/>
<Field
inputProps={{
name: fields.retypePassword.name,
type: "password",
}}
labelProps={{ children: "Retype Password" }}
errors={fields.retypePassword.errors}
/>
<Button className="w-full h-14">Change Email Address</Button>
</form>
</DialogHeader>
</DialogContent>
</Dialog>
);
};
const ChangePasswordSchema = z.object({
currentPassword: z.string().email(),
newPassword: z.string(),
retypePassword: z.string(),
});
const ChangePasswordForm = ({
open,
setOpen,
}: {
open: boolean;
setOpen: (value: boolean) => void;
}) => {
const [form, fields] = useForm({
id: "login",
constraint: getZodConstraint(ChangeEmailSchema),
onValidate({ formData }) {
return parseWithZod(formData, { schema: ChangePasswordSchema });
},
shouldValidate: "onSubmit",
});
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="p-8">
<DialogHeader>
<DialogTitle className="mb-8">Change Password</DialogTitle>
<form {...getFormProps(form)}>
<Field
inputProps={{
name: fields.currentPassword.name,
type: "password",
}}
labelProps={{ children: "Current Password" }}
errors={fields.currentPassword.errors}
/>
<Field
inputProps={{ name: fields.newPassword.name, type: "password" }}
labelProps={{ children: "Password" }}
errors={fields.newPassword.errors}
/>
<Field
inputProps={{
name: fields.retypePassword.name,
type: "password",
}}
labelProps={{ children: "Retype Password" }}
errors={fields.retypePassword.errors}
/>
<Button className="w-full h-14">Change Password</Button>
</form>
</DialogHeader>
</DialogContent>
</Dialog>
);
};
const SetupTwoFactorDialog = ({
open,
setOpen,
}: {
open: boolean;
setOpen: (value: boolean) => void;
}) => {
const [continueModal, setContinue] = useState<boolean>(false);
return (
<Dialog
open={open}
onOpenChange={(value) => {
setOpen(value);
setContinue(false);
}}>
<DialogContent className="p-8">
<DialogHeader>
<DialogTitle className="mb-8">Setup Two-Factor</DialogTitle>
<div className="flex flex-col gap-y-6">
{continueModal ? (
<>
<p className="text-sm text-primary-2">
Enter the authentication code generated in your two-factor
application to confirm your setup.
</p>
<Input fullWidth className="text-center" />
<Button className="w-full h-14">Confirm</Button>
</>
) : (
<>
<div className="p-6 flex justify-center border bg-secondary-2">
<img src={QRImg} alt="QR" className="h-36 w-36" />
</div>
<p className="font-semibold">
Dont have access to scan? Use this code instead.
</p>
<div className="p-4 border text-primary-2 text-center font-bold">
HHH7MFGAMPJ44OM44FGAMPJ44O232
</div>
<Button
className="w-full h-14"
onClick={() => setContinue(true)}>
Continue
</Button>
</>
)}
</div>
</DialogHeader>
</DialogContent>
</Dialog>
);
};

View File

@ -18,6 +18,7 @@
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-progress": "^1.0.3",