Compare commits

..

No commits in common. "riobuenoDevelops/dashboard" and "master" have entirely different histories.

33 changed files with 1503 additions and 11445 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 Hammer Technologies LLC Copyright (c) 2024 LumeWeb
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@ -1,82 +0,0 @@
import {
ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
getPaginationRowModel,
} from "@tanstack/react-table"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "./ui/table"
import { DataTablePagination } from "./table-pagination"
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
})
return (
<div className="rounded-lg">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<DataTablePagination table={table} />
</div>
)
}

View File

@ -1,108 +0,0 @@
export enum FileTypes {
Folder = "FOLDER",
Document = "DOCUMENT",
Image = "IMAGE",
}
interface FileCardProps {
type: FileTypes;
fileName: string;
createdAt: string;
size: string;
}
export const FileCardList = ({ children }: React.PropsWithChildren<{}>) => {
return <div className="flex flex-row gap-x-8">{children}</div>;
};
export const FileCard = ({ type, fileName, createdAt, size }: FileCardProps) => {
return (
<div className="border-1 rounded-lg p-4 w-[calc((100%/4))]">
<div className="flex justify-end">
<MoreIcon className="text-ring/50" />
</div>
<FolderIcon className="text-ring" />
<span className="block font-semibold my-4">{fileName}</span>
<div className="flex justify-between items-center">
<span className="text-primary-2 font-semibold text-sm">{size}</span>
<div className="flex items-center space-x-2 text-primary-2">
<RecentIcon />
<span className="font-semibold text-sm">{createdAt}</span>
</div>
</div>
</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

@ -64,7 +64,6 @@ export const FieldCheckbox = ({
const id = inputProps.id ?? fallbackId const id = inputProps.id ?? fallbackId
const errorId = errors?.length ? `${id}-error` : undefined const errorId = errors?.length ? `${id}-error` : undefined
return ( return (
<>
<div <div
className={cn("space-x-2 flex items-center text-primary-2", className)} className={cn("space-x-2 flex items-center text-primary-2", className)}
> >
@ -89,11 +88,10 @@ export const FieldCheckbox = ({
type="button" type="button"
/> />
<Label {...labelProps} htmlFor={id} /> <Label {...labelProps} htmlFor={id} />
</div>
<div className="min-h-[32px] px-4 pb-3 pt-1"> <div className="min-h-[32px] px-4 pb-3 pt-1">
{errorId ? <ErrorList id={errorId} errors={errors} /> : null} {errorId ? <ErrorList id={errorId} errors={errors} /> : null}
</div> </div>
</> </div>
) )
} }
@ -110,7 +108,7 @@ export function ErrorList({
return ( return (
<ul id={id} className="flex flex-col gap-1"> <ul id={id} className="flex flex-col gap-1">
{errorsToRender.map((e) => ( {errorsToRender.map((e) => (
<li key={e} className="text-[12px] text-destructive-foreground"> <li key={e} className="text-[10px] text-foreground-destructive">
{e} {e}
</li> </li>
))} ))}

View File

@ -1,464 +0,0 @@
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 {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger
} from "./ui/dialog"
import { useUppy } from "./lib/uppy"
import { UppyFile } from "@uppy/core"
import { Progress } from "./ui/progress"
import { DialogClose } from "@radix-ui/react-dialog"
export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
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">
<img src={logoPng} alt="Lume logo" className="h-10 w-32" />
<nav className="my-10 flex-1">
<ul>
<li>
<Link to="/dashboard">
<NavigationButton>
<ClockIcon className="w-5 h-5 mr-2" />
Dashboard
</NavigationButton>
</Link>
</li>
<li>
<Link to="/file-manager">
<NavigationButton>
<DriveIcon className="w-5 h-5 mr-2" />
File Manager
</NavigationButton>
</Link>
</li>
<li>
<Link to="/account">
<NavigationButton>
<CircleLockIcon className="w-5 h-5 mr-2" />
Account
</NavigationButton>
</Link>
</li>
</ul>
</nav>
<span className="text-primary-2 mb-3 -space-y-1 opacity-40">
<p>Freedom</p>
<p>Privacy</p>
<p>Ownership</p>
</span>
<Dialog>
<DialogTrigger asChild>
<Button size={"lg"} className="w-[calc(100%-3rem)] font-semibold">
<CloudUploadIcon className="w-6 h-6 -ml-3 mr-4" />
Upload Files
</Button>
</DialogTrigger>
<DialogContent>
<UploadFileForm />
</DialogContent>
</Dialog>
</header>
<div className="flex-1 overflow-y-auto p-10">
{children}
<footer className="mt-5">
<ul className="flex flex-row">
<li>
<Link to="https://discord.lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={discordLogoPng} alt="Discord Logo" />
Connect with us
</Button>
</Link>
</li>
<li>
<Link to="https://lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
Connect with us
</Button>
</Link>
</li>
</ul>
</footer>
</div>
</div>
)
}
const UploadFileForm = () => {
const {
getRootProps,
getInputProps,
getFiles,
upload,
state,
removeFile,
cancelAll
} = useUppy({
uploader: "tus",
endpoint: import.meta.env.VITE_PUBLIC_TUS_ENDPOINT
})
console.log({ state, files: getFiles() })
const isUploading = state === "uploading"
const isCompleted = state === "completed"
const hasStarted = state !== "idle" && state !== "initializing"
return (
<>
<DialogHeader className="mb-6">
<DialogTitle>Upload Files</DialogTitle>
</DialogHeader>
{!hasStarted ? (
<div
{...getRootProps()}
className="border border-border rounded text-primary-2 bg-primary-dark h-48 flex flex-col items-center justify-center"
>
<input
hidden
aria-hidden
name="uppyFiles[]"
key={new Date().toISOString()}
multiple
{...getInputProps()}
/>
<CloudUploadIcon className="w-24 h-24 stroke stroke-primary-dark" />
<p>Drag & Drop Files or Browse</p>
</div>
) : null}
<div className="w-full space-y-3 max-h-48 overflow-y-auto">
{getFiles().map((file) => (
<UploadFileItem
key={file.id}
file={file}
onRemove={(id) => {
removeFile(id)
}}
/>
))}
</div>
{hasStarted ? (
<div className="flex flex-col items-center gap-y-2 w-full text-primary-1">
<CloudCheckIcon className="w-32 h-32" />
{isCompleted
? "Upload completed"
: `${getFiles().length} files being uploaded`}
</div>
) : null}
{isUploading ? (
<DialogClose asChild onClick={cancelAll}>
<Button size={"lg"} className="mt-6">
Cancel
</Button>
</DialogClose>
) : null}
{isCompleted ? (
<DialogClose asChild>
<Button size={"lg"} className="mt-6">
Close
</Button>
</DialogClose>
) : null}
{!hasStarted && !isCompleted && !isUploading ? (
<Button size={"lg"} className="mt-6" onClick={upload}>
Upload
</Button>
) : null}
</>
)
}
function bytestoMegabytes(bytes: number) {
return bytes / 1024 / 1024
}
const UploadFileItem = ({
file,
onRemove
}: {
file: UppyFile
onRemove: (id: string) => void
}) => {
return (
<div className="flex flex-col w-full py-4 px-2 bg-primary-dark">
<div className="flex text-primary-1 items-center justify-between">
<div className="flex items-center">
<div className="p-2">
{file.progress?.uploadComplete ? (
<BoxCheckedIcon className="w-4 h-4" />
) : (
<PageIcon className="w-4 h-4" />
)}
</div>
<p className="w-full flex justify-between items-center">
<span className="truncate text-ellipsis max-w-[30ch]">
{file.name}
</span>{" "}
<span>({bytestoMegabytes(file.size).toFixed(2)} MB)</span>
</p>
</div>
<Button
size={"icon"}
variant={"ghost"}
className="!text-inherit"
onClick={() => onRemove(file.id)}
>
<TrashIcon className="w-4 h-4" />
</Button>
</div>
{file.progress?.uploadStarted && !file.progress.uploadComplete ? (
<Progress max={100} value={file.progress.percentage} className="mt-2" />
) : null}
</div>
)
}
const NavigationButton = ({ children }: React.PropsWithChildren) => {
return (
<Button
variant="ghost"
className="justify-start h-14 w-full font-semibold"
>
{children}
</Button>
)
}
const ClockIcon = ({ className }: { className?: string }) => {
return (
<svg
aria-hidden="true"
width="23"
height="23"
viewBox="0 0 23 23"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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>
)
}
const CircleLockIcon = ({ className }: { className?: string }) => {
return (
<svg
aria-hidden="true"
width="23"
height="24"
viewBox="0 0 23 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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>
)
}
const DriveIcon = ({ className }: { className?: string }) => {
return (
<svg
aria-hidden="true"
width="23"
height="24"
viewBox="0 0 23 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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>
)
}
const CloudUploadIcon = ({ className }: { className?: string }) => {
return (
<svg
aria-hidden="true"
width="24"
height="24"
viewBox="0 0 24 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M17.6461 8.20671C17.0642 6.80272 16.0599 5.63779 14.7871 4.89044C13.5144 4.14309 12.0433 3.85454 10.5994 4.06902C9.15553 4.2835 7.81844 4.98916 6.79304 6.07789C5.76764 7.16661 5.1105 8.57833 4.92232 10.0967C4.01431 10.325 3.21777 10.8955 2.68397 11.6999C2.15018 12.5043 1.9163 13.4864 2.02676 14.4599C2.13722 15.4334 2.58431 16.3304 3.28314 16.9806C3.98198 17.6307 4.88387 17.9888 5.81756 17.9867C6.07014 17.9867 6.31239 17.8814 6.49099 17.6938C6.6696 17.5063 6.76994 17.2519 6.76994 16.9867C6.76994 16.7215 6.6696 16.4671 6.49099 16.2796C6.31239 16.0921 6.07014 15.9867 5.81756 15.9867C5.31238 15.9867 4.8279 15.776 4.47069 15.4009C4.11347 15.0259 3.9128 14.5171 3.9128 13.9867C3.9128 13.4563 4.11347 12.9476 4.47069 12.5725C4.8279 12.1974 5.31238 11.9867 5.81756 11.9867C6.07014 11.9867 6.31239 11.8814 6.49099 11.6938C6.6696 11.5063 6.76994 11.2519 6.76994 10.9867C6.77237 9.80399 7.17402 8.66046 7.90353 7.75929C8.63304 6.85813 9.64317 6.25766 10.7545 6.06459C11.8658 5.87151 13.0063 6.09832 13.9733 6.70472C14.9404 7.31112 15.6715 8.25785 16.0366 9.37671C16.091 9.54855 16.1889 9.70164 16.3197 9.81964C16.4506 9.93765 16.6095 10.0161 16.7795 10.0467C17.4138 10.1726 17.9889 10.5203 18.4109 11.0332C18.833 11.5462 19.0771 12.1941 19.1036 12.8714C19.1302 13.5486 18.9374 14.2154 18.5569 14.7629C18.1763 15.3105 17.6304 15.7066 17.008 15.8867C16.763 15.953 16.5531 16.1188 16.4245 16.3476C16.2959 16.5764 16.2592 16.8495 16.3223 17.1067C16.3855 17.364 16.5434 17.5844 16.7613 17.7194C16.9792 17.8544 17.2392 17.893 17.4842 17.8267C18.4865 17.5486 19.375 16.9347 20.0147 16.0782C20.6544 15.2217 21.0105 14.1693 21.0288 13.081C21.0471 11.9926 20.7267 10.9278 20.1162 10.048C19.5057 9.16831 18.6384 8.52181 17.6461 8.20671ZM12.208 10.2767C12.1175 10.1857 12.0107 10.1143 11.8937 10.0667C11.6619 9.96669 11.4018 9.96669 11.1699 10.0667C11.053 10.1143 10.9462 10.1857 10.8557 10.2767L7.99851 13.2767C7.81917 13.465 7.71842 13.7204 7.71842 13.9867C7.71842 14.253 7.81917 14.5084 7.99851 14.6967C8.17785 14.885 8.42108 14.9908 8.6747 14.9908C8.92832 14.9908 9.17155 14.885 9.35089 14.6967L10.5795 13.3967V18.9867C10.5795 19.2519 10.6798 19.5063 10.8584 19.6938C11.037 19.8814 11.2793 19.9867 11.5318 19.9867C11.7844 19.9867 12.0267 19.8814 12.2053 19.6938C12.3839 19.5063 12.4842 19.2519 12.4842 18.9867V13.3967L13.7128 14.6967C13.8013 14.7904 13.9067 14.8648 14.0227 14.9156C14.1388 14.9664 14.2633 14.9925 14.389 14.9925C14.5147 14.9925 14.6392 14.9664 14.7552 14.9156C14.8713 14.8648 14.9766 14.7904 15.0652 14.6967C15.1544 14.6037 15.2253 14.4931 15.2736 14.3713C15.322 14.2494 15.3469 14.1187 15.3469 13.9867C15.3469 13.8547 15.322 13.724 15.2736 13.6021C15.2253 13.4803 15.1544 13.3697 15.0652 13.2767L12.208 10.2767Z"
fill="currentColor"
/>
</svg>
)
}
const PageIcon = ({ className }: { className?: string }) => {
return (
<svg
aria-hidden="true"
width="21"
height="21"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M10.3276 4.21337V15.0287H0.59375V0.96875H5.46067"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5.46191 0.96875L10.3288 4.21337"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5.46191 0.96875V4.21337"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10.3288 4.21289H5.46191"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.75684 8.53906H5.46068"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.75684 10.7021H7.62376"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
const TrashIcon = ({ className }: { className?: string }) => {
return (
<svg
aria-hidden="true"
width="21"
height="21"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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"
/>
<path
d="M11.9237 3.23172H0.846206C0.723792 3.23172 0.606392 3.18309 0.519832 3.09653C0.433272 3.00997 0.384644 2.89257 0.384644 2.77016C0.384644 2.64774 0.433272 2.53034 0.519832 2.44378C0.606392 2.35722 0.723792 2.30859 0.846206 2.30859H11.9237C12.0461 2.30859 12.1635 2.35722 12.2501 2.44378C12.3366 2.53034 12.3853 2.64774 12.3853 2.77016C12.3853 2.89257 12.3366 3.00997 12.2501 3.09653C12.1635 3.18309 12.0461 3.23172 11.9237 3.23172Z"
fill="currentColor"
/>
<path
d="M8.23121 3.23129C8.1088 3.23129 7.9914 3.18266 7.90484 3.0961C7.81828 3.00954 7.76965 2.89214 7.76965 2.76973V1.38504H5.00027V2.76973C5.00027 2.89214 4.95164 3.00954 4.86508 3.0961C4.77853 3.18266 4.66112 3.23129 4.53871 3.23129C4.4163 3.23129 4.2989 3.18266 4.21234 3.0961C4.12578 3.00954 4.07715 2.89214 4.07715 2.76973V0.923477C4.07715 0.801063 4.12578 0.683662 4.21234 0.597103C4.2989 0.510543 4.4163 0.461914 4.53871 0.461914H8.23121C8.35362 0.461914 8.47103 0.510543 8.55759 0.597103C8.64415 0.683662 8.69277 0.801063 8.69277 0.923477V2.76973C8.69277 2.89214 8.64415 3.00954 8.55759 3.0961C8.47103 3.18266 8.35362 3.23129 8.23121 3.23129Z"
fill="currentColor"
/>
<path
d="M6.3849 12.0012C6.26249 12.0012 6.14509 11.9526 6.05853 11.866C5.97197 11.7795 5.92334 11.6621 5.92334 11.5396V5.07777C5.92334 4.95536 5.97197 4.83796 6.05853 4.7514C6.14509 4.66484 6.26249 4.61621 6.3849 4.61621C6.50732 4.61621 6.62472 4.66484 6.71128 4.7514C6.79784 4.83796 6.84646 4.95536 6.84646 5.07777V11.5396C6.84646 11.6621 6.79784 11.7795 6.71128 11.866C6.62472 11.9526 6.50732 12.0012 6.3849 12.0012Z"
fill="currentColor"
/>
<path
d="M8.69313 11.0778C8.57072 11.0778 8.45332 11.0292 8.36676 10.9426C8.2802 10.8561 8.23157 10.7387 8.23157 10.6163V6.00062C8.23157 5.87821 8.2802 5.76081 8.36676 5.67425C8.45332 5.58769 8.57072 5.53906 8.69313 5.53906C8.81554 5.53906 8.93294 5.58769 9.0195 5.67425C9.10606 5.76081 9.15469 5.87821 9.15469 6.00062V10.6163C9.15469 10.7387 9.10606 10.8561 9.0195 10.9426C8.93294 11.0292 8.81554 11.0778 8.69313 11.0778Z"
fill="currentColor"
/>
<path
d="M4.07716 11.0778C3.95475 11.0778 3.83735 11.0292 3.75079 10.9426C3.66423 10.8561 3.6156 10.7387 3.6156 10.6163V6.00062C3.6156 5.87821 3.66423 5.76081 3.75079 5.67425C3.83735 5.58769 3.95475 5.53906 4.07716 5.53906C4.19958 5.53906 4.31698 5.58769 4.40354 5.67425C4.4901 5.76081 4.53873 5.87821 4.53873 6.00062V10.6163C4.53873 10.7387 4.4901 10.8561 4.40354 10.9426C4.31698 11.0292 4.19958 11.0778 4.07716 11.0778Z"
fill="currentColor"
/>
</svg>
)
}
const CloudCheckIcon = ({ className }: { className?: string }) => {
return (
<svg
aria-hidden="true"
width="21"
height="21"
viewBox="0 0 72 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M58.2 18C56.1 7.8 47.1 0 36 0C27.3 0 19.8 4.8 16.2 12C6.9 13.2 0 20.7 0 30C0 39.9 8.1 48 18 48H57C65.4 48 72 41.4 72 33C72 25.2 65.7 18.6 58.2 18ZM30 39L19.5 28.5L23.7 24.3L30 30.6L45.6 15L49.8 19.2L30 39Z"
fill="currentColor"
/>
</svg>
)
}
const BoxCheckedIcon = ({ className }: { className?: string }) => {
return (
<svg
aria-hidden="true"
width="21"
height="21"
viewBox="0 0 21 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.5 4.5H15.5C16.6046 4.5 17.5 5.39543 17.5 6.5V16.5C17.5 17.6046 16.6046 18.5 15.5 18.5H5.5C4.39543 18.5 3.5 17.6046 3.5 16.5V6.5C3.5 5.39543 4.39543 4.5 5.5 4.5Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.5 11.5L9.5 13.5L13.5 9.5"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}

View File

@ -1,206 +0,0 @@
// Copied from https://github.com/transloadit/uppy/blob/main/packages/%40uppy/drop-target/src/index.ts
// Is a less invasive implementation that allows for better unstyled integration
import {
type Uppy,
type PluginOptions,
type BasePlugin as TUppyBasePlugin
} from "@uppy/core"
// @ts-expect-error -- Uppy types are all over the place it really is weird
import UppyBasePlugin from "@uppy/core/lib/BasePlugin"
import type { IndexedObject } from "@uppy/utils"
import getDroppedFiles from "@uppy/utils/lib/getDroppedFiles"
import toArray from "@uppy/utils/lib/toArray"
export type DropTargetOptions = PluginOptions & {
target?: HTMLElement | string | null
onDrop?: (event: DragEvent) => void
onDragOver?: (event: DragEvent) => void
onDragLeave?: (event: DragEvent) => void
}
const BasePlugin = UppyBasePlugin as typeof TUppyBasePlugin<DropTargetOptions>
type Meta = {
relativePath?: string | null
}
type Body = IndexedObject<unknown>
// Default options
const defaultOpts = {
target: null,
} satisfies DropTargetOptions
interface DragEventWithFileTransfer extends DragEvent {
dataTransfer: NonNullable<DragEvent["dataTransfer"]>
}
function isFileTransfer(event: DragEvent): event is DragEventWithFileTransfer {
return event.dataTransfer?.types?.some((type) => type === "Files") ?? false
}
/**
* Drop Target plugin
*
*/
class DropTarget<M extends Meta, B extends Body> extends BasePlugin {
static VERSION = "lume-internal"
private removeDragOverDataAttr: ReturnType<typeof setTimeout> | undefined
private nodes?: Array<HTMLElement>
public opts: DropTargetOptions
constructor(uppy: Uppy<M, B>, opts?: DropTargetOptions) {
super(uppy, { ...defaultOpts, ...opts })
this.opts = opts || defaultOpts
this.type = "acquirer"
this.id = this.opts.id || "DropTarget"
// @ts-expect-error TODO: remove in major
this.title = "Drop Target"
}
addFiles = (files: Array<File>): void => {
const descriptors = files.map((file) => ({
source: this.id,
name: file.name,
type: file.type,
data: file,
meta: {
// path of the file relative to the ancestor directory the user selected.
// e.g. 'docs/Old Prague/airbnb.pdf'
relativePath: (file as { relativePath?: string }).relativePath || null
} as Meta
}))
try {
this.uppy.addFiles(descriptors)
} catch (err) {
this.uppy.log(err as string)
}
}
handleDrop = async (event: DragEvent): Promise<void> => {
if (!isFileTransfer(event)) {
return
}
event.preventDefault()
event.stopPropagation()
clearTimeout(this.removeDragOverDataAttr)
// Remove dragover class
if (event.currentTarget) {
(event.currentTarget as HTMLElement).dataset.uppyIsDragOver = "false"
this.setPluginState({ isDraggingOver: false })
}
// Let any acquirer plugin (Url/Webcam/etc.) handle drops to the root
this.uppy.iteratePlugins((plugin) => {
if (plugin.type === "acquirer") {
// @ts-expect-error Every Plugin with .type acquirer can define handleRootDrop(event)
plugin.handleRootDrop?.(event)
}
})
// Add all dropped files, handle errors
let executedDropErrorOnce = false
const logDropError = (error: Error): void => {
this.uppy.log(error.message, "error")
// In practice all drop errors are most likely the same,
// so let's just show one to avoid overwhelming the user
if (!executedDropErrorOnce) {
this.uppy.info(error.message, "error")
executedDropErrorOnce = true
}
}
const files = await getDroppedFiles(event.dataTransfer, { logDropError })
if (files.length > 0) {
this.uppy.log("[DropTarget] Files were dropped")
this.addFiles(files)
}
this.opts.onDrop?.(event)
}
handleDragOver = (event: DragEvent): void => {
if (!isFileTransfer(event)) {
return
}
event.preventDefault()
event.stopPropagation()
// Add a small (+) icon on drop
// (and prevent browsers from interpreting this as files being _moved_ into the browser,
// https://github.com/transloadit/uppy/issues/1978)
event.dataTransfer.dropEffect = "copy" // eslint-disable-line no-param-reassign
clearTimeout(this.removeDragOverDataAttr)
;(event.currentTarget as HTMLElement).dataset.uppyIsDragOver = "true"
this.setPluginState({ isDraggingOver: true })
this.opts.onDragOver?.(event)
}
handleDragLeave = (event: DragEvent): void => {
if (!isFileTransfer(event)) {
return
}
event.preventDefault()
event.stopPropagation()
const { currentTarget } = event
clearTimeout(this.removeDragOverDataAttr)
// Timeout against flickering, this solution is taken from drag-drop library.
// Solution with 'pointer-events: none' didn't work across browsers.
this.removeDragOverDataAttr = setTimeout(() => {
(currentTarget as HTMLElement).dataset.uppyIsDragOver = "false"
this.setPluginState({ isDraggingOver: false })
}, 50)
this.opts.onDragLeave?.(event)
}
addListeners = (): void => {
const { target } = this.opts
if (target instanceof Element) {
this.nodes = [target]
} else if (typeof target === "string") {
this.nodes = toArray(document.querySelectorAll(target))
}
if (!this.nodes || this.nodes.length === 0) {
throw new Error(`"${target}" does not match any HTML elements`)
}
for (const node of this.nodes) {
node.addEventListener("dragover", this.handleDragOver, false)
node.addEventListener("dragleave", this.handleDragLeave, false)
node.addEventListener("drop", this.handleDrop, false)
}
}
removeListeners = (): void => {
if (this.nodes) {
for (const node of this.nodes) {
node.removeEventListener("dragover", this.handleDragOver, false)
node.removeEventListener("dragleave", this.handleDragLeave, false)
node.removeEventListener("drop", this.handleDrop, false)
}
}
}
install(): void {
this.setPluginState({ isDraggingOver: false })
this.addListeners()
}
uninstall(): void {
this.removeListeners()
}
}
export default DropTarget

View File

@ -1,173 +0,0 @@
import Uppy, { State, debugLogger } from "@uppy/core"
import Tus from "@uppy/tus"
import toArray from "@uppy/utils/lib/toArray"
import {
ChangeEvent,
useCallback,
useEffect,
useMemo,
useRef,
useState
} from "react"
import DropTarget, { DropTargetOptions } from "./uppy-dropzone"
const LISTENING_EVENTS = [
"upload",
"upload-success",
"upload-error",
"file-added",
"file-removed",
"files-added"
] as const
export function useUppy({
uploader,
endpoint
}: {
uploader: "tus"
endpoint: string
}) {
const inputRef = useRef<HTMLInputElement>(null)
const [targetRef, _setTargetRef] = useState<HTMLElement | null>(null)
const uppyInstance = useRef<Uppy>()
const setRef = useCallback(
(element: HTMLElement | null) => _setTargetRef(element),
[]
)
const [uppyState, setUppyState] = useState<State>()
const [state, setState] = useState<
"completed" | "idle" | "initializing" | "error" | "uploading"
>("initializing")
const [inputProps, setInputProps] = useState<
| {
ref: typeof inputRef
type: "file"
onChange: (event: ChangeEvent<HTMLInputElement>) => void
}
| object
>({})
const getRootProps = useMemo(
() => () => {
return {
ref: setRef,
onClick: () => {
if (inputRef.current) {
//@ts-expect-error -- dumb html
inputRef.current.value = null
inputRef.current.click()
console.log("clicked", { input: inputRef.current })
}
},
role: "presentation"
}
},
[setRef]
)
const removeFile = useCallback(
(id: string) => {
uppyInstance.current?.removeFile(id)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[targetRef, uppyInstance]
)
const cancelAll = useCallback(
() => uppyInstance.current?.cancelAll({ reason: "user" }),
// eslint-disable-next-line react-hooks/exhaustive-deps
[targetRef, uppyInstance]
)
useEffect(() => {
if (!targetRef) return
const uppy = new Uppy({ logger: debugLogger }).use(DropTarget, {
target: targetRef
} as DropTargetOptions)
uppyInstance.current = uppy
setInputProps({
ref: inputRef,
type: "file",
onChange: (event) => {
const files = toArray(event.target.files)
if (files.length > 0) {
uppyInstance.current?.log("[DragDrop] Files selected through input")
uppyInstance.current?.addFiles(files)
}
// We clear the input after a file is selected, because otherwise
// change event is not fired in Chrome and Safari when a file
// with the same name is selected.
// ___Why not use value="" on <input/> instead?
// Because if we use that method of clearing the input,
// Chrome will not trigger change if we drop the same file twice (Issue #768).
// @ts-expect-error TS freaks out, but this is fine
// eslint-disable-next-line no-param-reassign
event.target.value = null
}
})
switch (uploader) {
case "tus":
uppy.use(Tus, { endpoint: endpoint, limit: 6 })
break
default:
}
uppy.on("complete", (result) => {
if (result.failed.length === 0) {
console.log("Upload successful üòÄ")
setState("completed")
} else {
console.warn("Upload failed üòû")
setState("error")
}
console.log("successful files:", result.successful)
console.log("failed files:", result.failed)
})
const setStateCb = (event: (typeof LISTENING_EVENTS)[number]) => {
switch (event) {
case "upload":
setState("uploading")
break
case "upload-error":
setState("error")
break
default:
break
}
setUppyState(uppy.getState())
}
for (const event of LISTENING_EVENTS) {
uppy.on(event, function cb() {
setStateCb(event)
})
}
setState("idle")
}, [targetRef, endpoint, uploader])
useEffect(() => {
return () => {
uppyInstance.current?.cancelAll({ reason: "unmount" })
uppyInstance.current?.logout()
uppyInstance.current?.close()
uppyInstance.current = undefined
}
}, [])
return {
getFiles: () => uppyInstance.current?.getFiles() ?? [],
error: uppyInstance.current?.getState,
state,
upload: () =>
uppyInstance.current?.upload() ??
new Error("[useUppy] Uppy has not initialized yet."),
getInputProps: () => inputProps,
getRootProps,
removeFile,
cancelAll
}
}

View File

@ -1,90 +0,0 @@
import {
ChevronLeftIcon,
ChevronRightIcon,
DoubleArrowLeftIcon,
DoubleArrowRightIcon,
} from "@radix-ui/react-icons";
import { Table } from "@tanstack/react-table";
import { Button } from "./ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
interface DataTablePaginationProps<TData> {
table: Table<TData>;
}
export function DataTablePagination<TData>({ table }: DataTablePaginationProps<TData>) {
return (
<div className="flex items-center justify-between px-2 border border-t-2 border-x-0 h-14">
<div className="flex items-center space-x-2">
<p className="text-sm font-bold text-primary-1">Rows per page</p>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
}}
>
<SelectTrigger className="h-8 w-[70px]">
<SelectValue placeholder={table.getState().pagination.pageSize} />
</SelectTrigger>
<SelectContent side="top">
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={`${pageSize}`}>
{pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-center space-x-6 lg:space-x-8">
<div className="flex items-center justify-center text-sm font-bold text-primary-1">
Showing
<span className="text-white">
{` ${table.getState().pagination.pageSize -
(table.getState().pagination.pageSize - 1)
} to ${(table.getState().pagination.pageIndex + 1) * table.getRowCount()} `}
</span>
of {table.getRowCount()}
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to first page</span>
<DoubleArrowLeftIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<ChevronLeftIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
<ChevronRightIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
<DoubleArrowRightIcon className="h-4 w-4" />
</Button>
</div>
</div>
</div>
);
}

View File

@ -1,48 +0,0 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "~/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }

View File

@ -11,21 +11,19 @@ const buttonVariants = cva(
variant: { variant: {
default: default:
"bg-primary text-primary-foreground hover:bg-primary/50", "bg-primary text-primary-foreground hover:bg-primary/50",
// TODO: name it better
accent: "bg-ring text-primary-1-foreground hover:bg-ring/75 font-bold",
destructive: destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: outline:
"border border-input bg-background shadow-sm hover:bg-primary-2/5", "border border-input bg-background shadow-sm hover:bg-primary-2/5",
secondary: secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-secondary-1 hover:text-secondary-1-foreground text-primary-2", ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary-1 underline-offset-4 hover:underline", link: "text-primary-1 underline-offset-4 hover:underline",
}, },
size: { size: {
default: "h-9 px-4 py-2", default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs", sm: "h-8 rounded-md px-3 text-xs",
lg: "h-16 rounded-md px-8", lg: "h-10 rounded-md px-8",
icon: "h-9 w-9", icon: "h-9 w-9",
}, },
}, },

View File

@ -7,7 +7,7 @@ import { cn } from "~/utils"
const Checkbox = React.forwardRef< const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>, React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> & { React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> & {
className?: string className: string
} }
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root <CheckboxPrimitive.Root

View File

@ -1,120 +0,0 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { Cross2Icon } from "@radix-ui/react-icons"
import { cn } from "~/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View File

@ -4,13 +4,10 @@ import { cn } from "~/utils"
import { EyeOpenIcon, EyeNoneIcon } from "@radix-ui/react-icons" import { EyeOpenIcon, EyeNoneIcon } from "@radix-ui/react-icons"
export interface InputProps export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> { extends React.InputHTMLAttributes<HTMLInputElement> {}
fullWidth: boolean,
leftIcon?: React.ReactNode
}
const Input = React.forwardRef<HTMLInputElement, InputProps>( const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, fullWidth, leftIcon, ...props }, ref) => { ({ className, type, ...props }, ref) => {
const [showPassword, setShowPassword] = React.useState<boolean>(false) const [showPassword, setShowPassword] = React.useState<boolean>(false)
const [mask, setMask] = React.useState<boolean>(false) const [mask, setMask] = React.useState<boolean>(false)
const toggleShowPassword = () => { const toggleShowPassword = () => {
@ -18,18 +15,12 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
setMask((mask) => !mask) setMask((mask) => !mask)
} }
return ( return (
<div className={`relative ${fullWidth ? "w-full" : ""}`}> <div className="relative">
{leftIcon && (
<div className="absolute left-4 top-1/2 -translate-y-1/2">
{leftIcon}
</div>
)}
<input <input
type={type && !mask ? type : "text"} type={type && !mask ? type : "text"}
className={cn( className={cn(
"flex h-14 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-input-placeholder focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", "flex h-14 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-input-placeholder focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className, className
leftIcon && "pl-10"
)} )}
ref={ref} ref={ref}
{...props} {...props}

View File

@ -1,26 +0,0 @@
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "~/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, max, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-primary",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className={`h-full w-full flex-1 bg-ring rounded-r-full transition-all`}
style={{ transform: `translateX(-${100 - Math.floor(((value || 0)*100)/(max || 100))}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }

View File

@ -1,162 +0,0 @@
import * as React from "react"
import {
CaretSortIcon,
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from "@radix-ui/react-icons"
import * as SelectPrimitive from "@radix-ui/react-select"
import { cn } from "~/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<CaretSortIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover 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",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}

View File

@ -1,120 +0,0 @@
import * as React from "react"
import { cn } from "~/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm rounded-lg border-x-0", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("border-b-2", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0 rounded-lg border-x-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"h-14 border border-t-2 border-x-0 bg-secondary-2 font-medium",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-y-1 border-x-0 transition-colors hover:bg-secondary-1 text-primary-2 data-[state=selected]:text-ring ",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-14 px-6 border-x-1 first:border-l-0 last:border-r-0 text-left align-middle font-bold text-ring [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"h-14 px-6 align-middle border-x-1 first:border-l-0 last:border-r-0 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@ -1,185 +0,0 @@
import { Avatar } from "./ui/avatar";
import { Button } from "./ui/button";
export const UpgradeAccountBanner = () => {
return (
<div className="flex items-center justify-between p-8 border border-ring rounded-lg bg-secondary-1">
<div className="flex items-center gap-x-4">
<Avatar className="border-2 border-ring h-20 w-20" />
<div>
<div className="flex items-center gap-x-2 font-bold">
wirtly
<CrownIcon className="text-ring" />
</div>
<div className="flex gap-x-5 mt-2">
<div className="flex items-center gap-x-2 text-white text-sm">
<PersonIcon />
Lite Account (upgrade)
</div>
<div className="flex items-center gap-x-2 text-white text-sm">
<CloudIcon />
120 GB / 130 GB
</div>
<div className="flex items-center gap-x-2 text-white text-sm">
<CloudDonwloadIcon />
10 GB / 25 GB
</div>
<div className="flex items-center gap-x-2 text-white text-sm">
<CheckRoundedIcon />
0% Free Usage
</div>
</div>
</div>
</div>
<Button className="gap-x-2 py-6" variant="accent">
<AddIcon />
Upgrade to Premium
</Button>
</div>
);
};
const CrownIcon = ({ 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}
>
<g clipPath="url(#clip0_320_192)">
<path
d="M18.649 5.74215C18.4702 5.59292 18.2531 5.49694 18.0224 5.46517C17.7917 5.4334 17.5568 5.46711 17.3443 5.56246L13.3912 7.32028L11.0943 3.17965C10.9845 2.98634 10.8255 2.82557 10.6334 2.71373C10.4412 2.60189 10.2229 2.54297 10.0006 2.54297C9.77826 2.54297 9.55992 2.60189 9.36779 2.71373C9.17566 2.82557 9.0166 2.98634 8.90682 3.17965L6.60995 7.32028L2.65682 5.56246C2.44394 5.46725 2.20866 5.43349 1.97759 5.465C1.74652 5.49651 1.52888 5.59203 1.34926 5.74077C1.16964 5.8895 1.03521 6.08552 0.961163 6.30666C0.887119 6.5278 0.876414 6.76525 0.930259 6.99215L2.91463 15.4531C2.95258 15.6169 3.02338 15.7713 3.12276 15.9069C3.22213 16.0426 3.34801 16.1566 3.49276 16.2422C3.68873 16.3595 3.9128 16.4215 4.1412 16.4218C4.25222 16.4216 4.36268 16.4059 4.46932 16.375C8.08637 15.3749 11.907 15.3749 15.524 16.375C15.8543 16.4618 16.2055 16.414 16.5006 16.2422C16.6462 16.1577 16.7728 16.044 16.8723 15.9082C16.9718 15.7724 17.0421 15.6174 17.0787 15.4531L19.0709 6.99215C19.1241 6.76518 19.1128 6.52785 19.0383 6.30696C18.9637 6.08608 18.8289 5.89044 18.649 5.74215Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_320_192">
<rect width="20" height="20" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};
const PersonIcon = ({ 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}
>
<g clipPath="url(#clip0_290_800)">
<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"
/>
</g>
<defs>
<clipPath id="clip0_290_800">
<rect width="20" height="20" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};
const CloudIcon = ({ className }: { className?: string }) => {
return (
<svg
width="27"
height="27"
viewBox="0 0 27 27"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_290_790)">
<path
d="M23.2503 12.7002C23.247 12.7002 23.2419 12.7019 23.2385 12.7019C23.5271 11.3418 23.4697 9.85681 22.739 8.27731C21.7046 6.04137 19.5868 4.37075 17.145 4.06025C13.7497 3.62825 10.7477 5.56381 9.55122 8.441C9.14791 8.28575 8.70916 8.19969 8.25016 8.19969C6.1796 8.19969 4.50054 9.87875 4.50054 11.9493C4.50054 12.2463 4.54272 12.5298 4.60854 12.8066C3.7091 12.5956 2.7236 12.6192 1.52885 13.5845C0.641224 14.3034 -0.0118379 15.3496 -2.54025e-05 16.4921C0.0235996 18.5441 1.69254 20.1995 3.7496 20.1995H23.0698C24.8231 20.1995 26.4768 19.0841 26.8869 17.3797C27.4826 14.9058 25.6247 12.7002 23.2503 12.7002Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_290_790">
<rect width="27" height="27" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};
const CloudDonwloadIcon = ({ className }: { className?: string }) => {
return (
<svg
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_290_792)">
<path
d="M18.9 7C18.2 3.6 15.2 1 11.5 1C8.6 1 6.1 2.6 4.9 5C1.8 5.4 -0.5 7.9 -0.5 11C-0.5 14.3 2.2 17 5.5 17H18.5C21.3 17 23.5 14.8 23.5 12C23.5 9.4 21.4 7.2 18.9 7ZM16.5 10L11.5 15L6.5 10H9.5V6H13.5V10H16.5Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_290_792">
<rect width="25" height="24" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};
const CheckRoundedIcon = ({ className }: { className?: string }) => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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>
);
};
// TODO: Discuss duplicated icon definitions
const AddIcon = ({ className }: { className?: string }) => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_323_1258">
<rect width="18" height="18" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};

View File

@ -1,67 +0,0 @@
import { Button } from "./ui/button";
import { Progress } from "./ui/progress";
interface UsageCardProps {
usageName: string,
monthlyUsage: number, // Asumming that the minimium is 1GB
currentUsage: number,
icon: React.ReactNode
}
export const UsageList = ({ children }: React.PropsWithChildren<{}>) => {
return (
<div className="grid grid-cols-2 gap-8 flex-wrap w-full">
{children}
</div>
)
}
export const UsageCard = ({ usageName, monthlyUsage, currentUsage, icon }: UsageCardProps) => {
return (
<div className="p-8 border rounded-lg w-full">
<div className="flex items-center justify-between mb-8">
<div className="text-primary-2 text-sm">
<div className="flex items-center gap-x-2 text-lg font-bold text-white mb-2">
{icon}
{usageName}
</div>
Montly {usageName.toLocaleLowerCase()} limit is {monthlyUsage} GB
</div>
<Button className="gap-x-2 h-12">
<AddIcon />
Add More
</Button>
</div>
<Progress max={monthlyUsage} value={currentUsage} />
<div className="flex items-center justify-between mt-4 font-bold text-sm">
<span className="text-primary-2">{currentUsage} GB used</span>
<span className="text-white">{monthlyUsage - currentUsage} GB left</span>
</div>
</div>
)
}
const AddIcon = ({ className }: { className?: string }) => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_323_1258">
<rect width="18" height="18" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};

View File

@ -1,95 +0,0 @@
import {
AnimatedAxis, // any of these can be non-animated equivalents
AnimatedLineSeries,
XYChart,
buildChartTheme,
} from "@visx/xychart";
import { curveCardinal } from "@visx/curve";
interface UsageChartProps {
usageName: string;
dataset: {
x: string;
y: number;
}[];
}
const accessors = {
xAccessor: (d) => d.x,
yAccessor: (d) => d.y,
};
const customTheme = buildChartTheme({
colors: ["hsl(var(--ring))"],
backgroundColor: "hsl(var(--primary-2))",
gridColor: "hsl(var(--primary-2))",
gridColorDark: "hsl(var(--primary-2))",
tickLength: 8,
xAxisLineStyles: {
strokeWidth: 1,
}
});
export const UsageChart = ({ usageName, dataset }: UsageChartProps) => {
return (
<div className="p-8 border rounded-lg w-full">
<div className="flex items-center justify-between">
<span className="font-bold text-lg">{usageName}</span>
<InfoIcon className="text-ring" />
</div>
<div>
<XYChart
height={400}
xScale={{ type: "band" }}
yScale={{ type: "linear" }}
theme={customTheme}
>
<AnimatedAxis
orientation="bottom"
hideTicks
tickTransform="translateX(50%)"
tickLabelProps={{ className: "text-sm" }}
/>
<AnimatedAxis
orientation="left"
hideTicks
tickLabelProps={{ className: "text-sm" }}
/>
<AnimatedLineSeries
className="stroke-ring"
curve={curveCardinal}
strokeWidth={4}
dataKey="usage"
data={dataset}
{...accessors}
/>
</XYChart>
</div>
</div>
);
};
const InfoIcon = ({ className }: { className?: string }) => {
return (
<svg
width="23"
height="24"
viewBox="0 0 23 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_295_884">
<rect width="22.99" height="24" fill="currentColor" transform="translate(0.0117188)" />
</clipPath>
</defs>
</svg>
);
};

View File

@ -1,36 +0,0 @@
import { AuthProvider } from "@refinedev/core"
import type {
AuthActionResponse,
CheckResponse,
OnErrorResponse
} from "@refinedev/core/dist/interfaces"
export const authProvider: AuthProvider = {
login: async (params: any) => {
return { success: true } satisfies AuthActionResponse
},
logout: async (params: any) => {
return { success: true } satisfies AuthActionResponse
},
check: async (params?: any) => {
return { authenticated: true } satisfies CheckResponse
},
onError: async (error: any) => {
return { logout: true } satisfies OnErrorResponse
},
register: async (params: any) => {
return { success: true } satisfies AuthActionResponse
},
forgotPassword: async (params: any) => {
return { success: true } satisfies AuthActionResponse
},
updatePassword: async (params: any) => {
return { success: true } satisfies AuthActionResponse
},
getPermissions: async (params: any) => {
return { success: true } satisfies AuthActionResponse
},
getIdentity: async (params: any) => {
return { id: "1", fullName: "John Doe", avatar: "https://via.placeholder.com/150" }
}
}

BIN
app/images/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1,13 +1,80 @@
import Login from "./login" import type { MetaFunction } from "@remix-run/node"
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 lumeBgPng from "~/images/lume-bg-image.png?url"
import { Field, FieldCheckbox } from "~/components/forms"
export const meta: MetaFunction = () => {
return [
{ title: "New Remix SPA" },
{ name: "description", content: "Welcome to Remix (SPA Mode)!" }
]
}
export default function Index() { export default function Index() {
const isLogged = false return (
<div className="p-10 h-screen relative overflow-clip">
if (isLogged) { <header>
window.location.href = "/dashboard" <img src={logoPng} alt="Lume logo" className="h-10"></img>
} else { </header>
window.location.href = "/login" <form className="w-full p-2 max-w-md space-y-4 mt-12 bg-background">
} <h2 className="text-3xl font-bold !mb-12">Welcome back! 🎉</h2>
<Field
return isLogged ? <div>Dashboard</div> : <Login /> inputProps={{ name: "email" }}
labelProps={{ children: "Email" }}
/>
<Field
inputProps={{ name: "password", type: "password" }}
labelProps={{ children: "Password" }}
/>
<FieldCheckbox
inputProps={{ name: "rememberMe" }}
labelProps={{ children: "Remember Me" }}
/>
<Button className="w-full h-14">Login</Button>
<p className="text-input-placeholder">
Forgot your password?{" "}
<Link
to="/sign-up"
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
>
Reset Password
</Link>
</p>
<Button className="w-full h-14" variant={"outline"}>
Create an Account
</Button>
</form>
<img src={lumeBgPng} alt="Lume background" className="absolute top-0 right-0 md:w-2/3 object-cover z-[-1]"></img>
<footer className="absolute bottom-5">
<ul className="flex flex-row">
<li>
<Link to="https://discord.lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={discordLogoPng} alt="Discord Logo" />
Connect with us
</Button>
</Link>
</li>
<li>
<Link to="https://lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
Connect with us
</Button>
</Link>
</li>
</ul>
</footer>
</div>
)
} }

View File

@ -1,134 +0,0 @@
import { GeneralLayout } from "~/components/general-layout";
import { UpgradeAccountBanner } from "~/components/upgrade-account-banner";
import { UsageCard, UsageList } from "~/components/usage-card";
import { UsageChart } from "~/components/usage-chart";
export default function Dashboard() {
const isLogged = true;
if (!isLogged) {
window.location.href = "/login";
}
return (
<GeneralLayout>
<h1 className="font-bold mb-4 text-lg">Dashboard</h1>
<UpgradeAccountBanner />
<h2 className="font-bold mb-8 mt-10">Current Usage</h2>
<UsageList>
<UsageCard
usageName="Storage"
currentUsage={120}
monthlyUsage={130}
icon={<CloudIcon className="text-ring" />}
/>
<UsageCard
usageName="Download"
currentUsage={2}
monthlyUsage={10}
icon={<CloudDonwloadIcon className="text-ring" />}
/>
<UsageCard
usageName="Upload"
currentUsage={5}
monthlyUsage={15}
icon={<CloudUploadIcon className="text-ring" />}
/>
</UsageList>
<h2 className="font-bold mb-8 mt-10">Historical Usage</h2>
<div className="grid gap-8 grid-cols-2">
<UsageChart
dataset={[
{ x: "3/2", y: 50 },
{ x: "3/3", y: 10 },
{ x: "3/4", y: 20 },
]}
usageName="Storage"
/>
<UsageChart
dataset={[
{ x: "3/2", y: 50 },
{ x: "3/3", y: 10 },
{ x: "3/4", y: 20 },
]}
usageName="Download"
/>
<UsageChart
dataset={[
{ x: "3/2", y: 50 },
{ x: "3/3", y: 10 },
{ x: "3/4", y: 20 },
]}
usageName="Upload"
/>
</div>
</GeneralLayout>
);
}
const CloudIcon = ({ className }: { className?: string }) => {
return (
<svg
width="27"
height="27"
viewBox="0 0 27 27"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_290_790)">
<path
d="M23.2503 12.7002C23.247 12.7002 23.2419 12.7019 23.2385 12.7019C23.5271 11.3418 23.4697 9.85681 22.739 8.27731C21.7046 6.04137 19.5868 4.37075 17.145 4.06025C13.7497 3.62825 10.7477 5.56381 9.55122 8.441C9.14791 8.28575 8.70916 8.19969 8.25016 8.19969C6.1796 8.19969 4.50054 9.87875 4.50054 11.9493C4.50054 12.2463 4.54272 12.5298 4.60854 12.8066C3.7091 12.5956 2.7236 12.6192 1.52885 13.5845C0.641224 14.3034 -0.0118379 15.3496 -2.54025e-05 16.4921C0.0235996 18.5441 1.69254 20.1995 3.7496 20.1995H23.0698C24.8231 20.1995 26.4768 19.0841 26.8869 17.3797C27.4826 14.9058 25.6247 12.7002 23.2503 12.7002Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_290_790">
<rect width="27" height="27" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};
const CloudDonwloadIcon = ({ className }: { className?: string }) => {
return (
<svg
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_290_792)">
<path
d="M18.9 7C18.2 3.6 15.2 1 11.5 1C8.6 1 6.1 2.6 4.9 5C1.8 5.4 -0.5 7.9 -0.5 11C-0.5 14.3 2.2 17 5.5 17H18.5C21.3 17 23.5 14.8 23.5 12C23.5 9.4 21.4 7.2 18.9 7ZM16.5 10L11.5 15L6.5 10H9.5V6H13.5V10H16.5Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_290_792">
<rect width="25" height="24" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};
const CloudUploadIcon = ({ className }: { className?: string }) => {
return (
<svg
width="24"
height="16"
viewBox="0 0 24 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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>
);
};

View File

@ -1,109 +0,0 @@
import { ColumnDef } from "@tanstack/react-table";
import { Checkbox } from "~/components/ui/checkbox";
// This type is used to define the shape of our data.
// You can use a Zod schema here if you want.
export type File = {
name: string;
cid: string;
size: string;
createdOn: string;
};
export const columns: ColumnDef<File>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "name",
header: "Name",
cell: ({ row }) => (
<div className="flex items-center gap-x-2">
<FileIcon />
{row.getValue("name")}
</div>
)
},
{
accessorKey: "cid",
header: "CID",
},
{
accessorKey: "size",
header: "Size",
},
{
accessorKey: "createdOn",
header: "Created On",
cell: ({ row }) => (
<div className="flex items-center justify-between">
{row.getValue("createdOn")}
{row.getIsSelected() && <MoreIcon />}
</div>
)
}
];
const FileIcon = ({ className }: { className?: string }) => {
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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>
);
};
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>
);
};

View File

@ -1,104 +0,0 @@
import { GeneralLayout } from "~/components/general-layout";
import { FileCard, FileCardList, FileTypes } from "~/components/file-card";
import { DataTable } from "~/components/data-table";
import { columns } from "./columns";
import { Input } from "~/components/ui/input";
import { Button } from "~/components/ui/button";
export default function FileManager() {
const isLogged = true;
if (!isLogged) {
window.location.href = "/login";
}
return (
<GeneralLayout>
<h1 className="font-bold mb-4 text-lg">File Manager</h1>
<FileCardList>
<FileCard
fileName="Backups"
size="33 files"
type={FileTypes.Folder}
createdAt="2 days ago"
/>
<FileCard
fileName="Backups"
size="33 files"
type={FileTypes.Folder}
createdAt="2 days ago"
/>
<FileCard
fileName="Backups"
size="33 files"
type={FileTypes.Folder}
createdAt="2 days ago"
/>
<FileCard
fileName="Backups"
size="33 files"
type={FileTypes.Folder}
createdAt="2 days ago"
/>
</FileCardList>
<h2 className="font-bold text-l mt-8">Files</h2>
<div className="flex items-center space-x-4 my-6 w-full">
<Input
fullWidth
leftIcon={<AddIcon />}
placeholder="Search files by name or CID"
className="border-ring font-bold w-full grow h-12 flex-1"
/>
<Button className="h-12 gap-x-2">
<AddIcon />
Select All
</Button>
<Button className="h-12 gap-x-2">
<AddIcon />
New Folder
</Button>
</div>
<DataTable
columns={columns}
data={[
{
name: "whirly-final-draft.psd",
cid: "0xB45165ED3CD437B",
size: "1.89 MB",
createdOn: "03/02/2024 at 13:29 PM",
},
{
name: "whirly-final-draft.psd",
cid: "0xB45165ED3CD437B",
size: "1.89 MB",
createdOn: "03/02/2024 at 13:29 PM",
},
]}
/>
</GeneralLayout>
);
}
const AddIcon = ({ className }: { className?: string }) => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
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"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_323_1258">
<rect width="18" height="18" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
};

View File

@ -1,179 +0,0 @@
import type { MetaFunction } from "@remix-run/node"
import { Link, useLocation } from "@remix-run/react"
import { z } from "zod"
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 lumeBgPng from "~/images/lume-bg-image.png?url"
import { Field, FieldCheckbox } from "~/components/forms"
import { getFormProps, useForm } from "@conform-to/react"
import { getZodConstraint, parseWithZod } from "@conform-to/zod"
export const meta: MetaFunction = () => {
return [
{ title: "Login" },
{ name: "description", content: "Welcome to Lume!" }
]
}
export default function Login() {
const location = useLocation()
const hash = location.hash
return (
<div className="p-10 h-screen relative">
<header>
<img src={logoPng} alt="Lume logo" className="h-10" />
</header>
<div className="fixed inset-0 -z-10 overflow-clip">
<img
src={lumeBgPng}
alt="Lume background"
className="absolute top-0 right-0 md:w-2/3 object-cover z-[-1]"
/>
</div>
{hash === "" && <LoginForm />}
{hash === "#otp" && <OtpForm />}
<footer className="my-5">
<ul className="flex flex-row">
<li>
<Link to="https://discord.lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={discordLogoPng} alt="Discord Logo" />
Connect with us
</Button>
</Link>
</li>
<li>
<Link to="https://lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
Connect with us
</Button>
</Link>
</li>
</ul>
</footer>
</div>
)
}
const LoginSchema = z.object({
email: z.string().email(),
password: z.string(),
rememberMe: z.boolean()
})
const LoginForm = () => {
const [form, fields] = useForm({
id: "login",
constraint: getZodConstraint(LoginSchema),
onValidate({ formData }) {
return parseWithZod(formData, { schema: LoginSchema })
},
shouldValidate: "onSubmit"
})
return (
<form
className="w-full p-2 max-w-md space-y-3 mt-12 bg-background"
{...getFormProps(form)}
>
<h2 className="text-3xl font-bold !mb-12">Welcome back! 🎉</h2>
<Field
inputProps={{ name: fields.email.name }}
labelProps={{ children: "Email" }}
errors={fields.email.errors}
/>
<Field
inputProps={{ name: fields.password.name, type: "password" }}
labelProps={{ children: "Password" }}
errors={fields.password.errors}
/>
<FieldCheckbox
inputProps={{ name: fields.rememberMe.name, form: form.id }}
labelProps={{ children: "Remember Me" }}
errors={fields.rememberMe.errors}
/>
<Button className="w-full h-14">Login</Button>
<p className="inline-block text-input-placeholder">
Forgot your password?{" "}
<Link
to="/reset-password"
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
>
Reset Password
</Link>
</p>
<Link to="/sign-up" className="block">
<Button type="button" className="w-full h-14" variant={"outline"}>
Create an Account
</Button>
</Link>
</form>
)
}
const OtpSchema = z.object({
otp: z.string().length(6, { message: "OTP must be 6 characters" })
})
const OtpForm = () => {
// TODO: Add support for resending the OTP
const [form, fields] = useForm({
id: "otp",
constraint: getZodConstraint(OtpSchema),
onValidate({ formData }) {
return parseWithZod(formData, { schema: OtpSchema })
},
shouldValidate: "onSubmit"
})
const valid = false // TODO: some sort of logic to verify user is on OTP state validly
if (!valid) {
location.hash = ""
return null
}
return (
<form
className="w-full p-2 max-w-md mt-12 bg-background"
{...getFormProps(form)}
>
<span className="block !mb-8 space-y-2">
<h2 className="text-3xl font-bold">Check your inbox</h2>
<p className="text-input-placeholder">
We will need the six digit confirmation code you received in your
email in order to verify your account and get started. Didnt receive
a code?{" "}
<Button type="button" variant={"link"} className="text-md h-0">
Resend now
</Button>
</p>
</span>
<Field
inputProps={{ name: fields.otp.name }}
labelProps={{ children: "Confirmation Code" }}
errors={fields.otp.errors}
/>
<Button className="w-full h-14">Verify</Button>
<p className="text-input-placeholder w-full text-left">
<Link
to="/login"
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
>
Back to Login
</Link>
</p>
</form>
)
}

View File

@ -1,92 +0,0 @@
import type { MetaFunction } from "@remix-run/node"
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 lumeBgPng from "~/images/lume-bg-image.png?url"
import { Field } from "~/components/forms"
import { getFormProps, useForm } from "@conform-to/react"
import { z } from "zod"
import { getZodConstraint, parseWithZod } from "@conform-to/zod"
export const meta: MetaFunction = () => {
return [{ title: "Sign Up" }]
}
const RecoverPasswordSchema = z
.object({
email: z.string().email(),
})
export default function RecoverPassword() {
const [form, fields] = useForm({
id: "sign-up",
constraint: getZodConstraint(RecoverPasswordSchema),
onValidate({ formData }) {
return parseWithZod(formData, { schema: RecoverPasswordSchema })
}
})
return (
<div className="p-10 h-screen relative">
<header>
<img src={logoPng} alt="Lume logo" className="h-10" />
</header>
<form
className="w-full p-2 max-w-md space-y-4 mt-12 bg-background"
{...getFormProps(form)}
>
<span className="!mb-12 space-y-2">
<h2 className="text-3xl font-bold">Reset your password</h2>
</span>
<Field
inputProps={{ name: fields.email.name }}
labelProps={{ children: "Email Address" }}
errors={fields.email.errors}
/>
<Button className="w-full h-14">Create Account</Button>
<p className="text-input-placeholder w-full text-left">
<Link
to="/login"
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
>
Back to Login
</Link>
</p>
</form>
<div className="fixed inset-0 -z-10 overflow-clip">
<img
src={lumeBgPng}
alt="Lume background"
className="absolute top-0 right-0 md:w-2/3 object-cover z-[-1]"
/>
</div>
<footer className="my-5">
<ul className="flex flex-row">
<li>
<Link to="https://discord.lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={discordLogoPng} alt="Discord Logo" />
Connect with us
</Button>
</Link>
</li>
<li>
<Link to="https://lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
Connect with us
</Button>
</Link>
</li>
</ul>
</footer>
</div>
)
}

View File

@ -1,157 +0,0 @@
import type { MetaFunction } from "@remix-run/node"
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 lumeBgPng from "~/images/lume-bg-image.png?url"
import { Field, FieldCheckbox } from "~/components/forms"
import { getFormProps, useForm } from "@conform-to/react"
import { z } from "zod"
import { getZodConstraint, parseWithZod } from "@conform-to/zod"
export const meta: MetaFunction = () => {
return [{ title: "Sign Up" }]
}
const SignUpSchema = z
.object({
email: z.string().email(),
password: z
.string()
.min(8, { message: "Password must be at least 8 characters" }),
confirmPassword: z
.string()
.min(8, { message: "Password must be at least 8 characters" }),
termsOfService: z.boolean({
required_error: "You must agree to the terms of service"
})
})
.superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["confirmPassword"],
message: "Passwords do not match"
})
}
return true
})
export default function SignUp() {
const [form, fields] = useForm({
id: "sign-up",
constraint: getZodConstraint(SignUpSchema),
onValidate({ formData }) {
return parseWithZod(formData, { schema: SignUpSchema })
}
})
return (
<div className="p-10 h-screen relative">
<header>
<img src={logoPng} alt="Lume logo" className="h-10" />
</header>
<form
className="w-full p-2 max-w-md space-y-4 mt-12 bg-background"
{...getFormProps(form)}
>
<span className="!mb-12 space-y-2">
<h2 className="text-3xl font-bold">All roads lead to Lume</h2>
<p className="text-input-placeholder">
🤘 Get 50 GB free storage and download for free,{" "}
<b
className="text-primar
y-2"
>
forever
</b>
.{" "}
</p>
</span>
<Field
inputProps={{ name: fields.email.name }}
labelProps={{ children: "Email" }}
errors={fields.email.errors}
/>
<Field
inputProps={{ name: fields.password.name, type: "password" }}
labelProps={{ children: "Password" }}
errors={fields.password.errors}
/>
<Field
inputProps={{ name: fields.confirmPassword.name, type: "password" }}
labelProps={{ children: "Confirm Password" }}
errors={fields.confirmPassword.errors}
/>
<FieldCheckbox
inputProps={{ name: fields.termsOfService.name, form: form.id }}
labelProps={{
children: (
<span>
I agree to the
<Link
to="/terms-of-service"
className="text-primary-1 text-md hover:underline hover:underline-offset-4 mx-1"
>
Terms of Service
</Link>
and
<Link
to="/privacy-policy"
className="text-primary-1 text-md hover:underline hover:underline-offset-4 mx-1"
>
Privacy Policy
</Link>
</span>
)
}}
errors={fields.termsOfService.errors}
/>
<Button className="w-full h-14">Create Account</Button>
<p className="text-input-placeholder w-full text-right">
Already have an account?{" "}
<Link
to="/login"
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
>
Login here instead
</Link>
</p>
</form>
<div className="fixed inset-0 -z-10 overflow-clip">
<img
src={lumeBgPng}
alt="Lume background"
className="absolute top-0 right-0 md:w-2/3 object-cover z-[-1]"
/>
</div>
<footer className="my-5">
<ul className="flex flex-row">
<li>
<Link to="https://discord.lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={discordLogoPng} alt="Discord Logo" />
Connect with us
</Button>
</Link>
</li>
<li>
<Link to="https://lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
Connect with us
</Button>
</Link>
</li>
</ul>
</footer>
</div>
)
}

View File

@ -16,14 +16,11 @@
--primary: 242 51% 14%; --primary: 242 51% 14%;
--primary-1: 241 90% 82%; --primary-1: 241 90% 82%;
--primary-2: 241 21% 42%; --primary-2: 241 21% 42%;
--primary-dark: 240 33% 4%;
--primary-foreground: 0 0% 88%; --primary-foreground: 0 0% 88%;
--primary-1-foreground: 240 50% 9%; --primary-1-foreground: 240 50% 9%;
--secondary: 0 0% 96.1%; --secondary: 0 0% 96.1%;
--secondary-1: 242 52% 9%;
--secondary-foreground: 0 0% 9%; --secondary-foreground: 0 0% 9%;
--secondary-1-foreground: 0 0% 100%;
--muted: 0 0% 96.1%; --muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%; --muted-foreground: 0 0% 45.1%;
@ -32,12 +29,12 @@
--accent-foreground: 0 0% 9%; --accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 72% 51%; --destructive-foreground: 0 0% 98%;
--border: 240 50% 17%; --border: 240 50% 17%;
--input: 240 50% 17%; --input: 240 50% 17%;
--input-placeholder: 241 21% 42%; --input-placeholder: 241 21% 42%;
--ring: 241 90% 82%; --ring: 0 0% 3.9%;
--radius: 5px; --radius: 5px;
} }
@ -78,6 +75,6 @@
@apply border-border; @apply border-border;
} }
body { body {
@apply bg-background text-foreground font-sans h-screen; @apply bg-background text-foreground font-sans;
} }
} }

9956
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{ {
"name": "lume-portal-dashboard", "name": "",
"private": true, "private": true,
"sideEffects": false, "sideEffects": false,
"type": "module", "type": "module",
@ -14,32 +14,18 @@
"@conform-to/react": "^1.0.2", "@conform-to/react": "^1.0.2",
"@conform-to/zod": "^1.0.2", "@conform-to/zod": "^1.0.2",
"@fontsource-variable/manrope": "^5.0.19", "@fontsource-variable/manrope": "^5.0.19",
"@lumeweb/portal-sdk": "^0.0.0-20240306231947",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@refinedev/cli": "^2.16.1",
"@refinedev/core": "^4.47.2",
"@refinedev/remix-router": "^3.0.0",
"@remix-run/node": "^2.8.0", "@remix-run/node": "^2.8.0",
"@remix-run/react": "^2.8.0", "@remix-run/react": "^2.8.0",
"@tanstack/react-table": "^8.13.2",
"@visx/visx": "^3.10.2",
"@uppy/core": "^3.9.3",
"@uppy/tus": "^3.5.3",
"@uppy/utils": "^5.7.4",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"tailwind-merge": "^2.2.1", "tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7"
"zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@remix-run/dev": "^2.8.0", "@remix-run/dev": "^2.8.0",

View File

@ -20,7 +20,7 @@ const config = {
border: "hsl(var(--border))", border: "hsl(var(--border))",
input: { input: {
DEFAULT: "hsl(var(--input))", DEFAULT: "hsl(var(--input))",
placeholder: "hsl(var(--input-placeholder))" placeholder: "hsl(var(--input-placeholder))",
}, },
ring: "hsl(var(--ring))", ring: "hsl(var(--ring))",
background: "hsl(var(--background))", background: "hsl(var(--background))",
@ -34,19 +34,12 @@ const config = {
foreground: "hsl(var(--primary-1-foreground))" foreground: "hsl(var(--primary-1-foreground))"
}, },
"primary-2": { "primary-2": {
DEFAULT: "hsl(var(--primary-2))" DEFAULT: "hsl(var(--primary-2))",
},
"primary-dark": {
DEFAULT: "hsl(var(--primary-dark))"
}, },
secondary: { secondary: {
DEFAULT: "hsl(var(--secondary))", DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))" foreground: "hsl(var(--secondary-foreground))"
}, },
"secondary-1": {
DEFAULT: "hsl(var(--secondary-1))",
foreground: "hsl(var(--secondary-1-foreground))"
},
destructive: { destructive: {
DEFAULT: "hsl(var(--destructive))", DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))" foreground: "hsl(var(--destructive-foreground))"
@ -73,9 +66,6 @@ const config = {
md: "calc(var(--radius) - 2px)", md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)" sm: "calc(var(--radius) - 4px)"
}, },
borderWidth: {
1: '1px'
},
keyframes: { keyframes: {
"accordion-down": { "accordion-down": {
from: { height: "0" }, from: { height: "0" },

View File

@ -1,14 +1,15 @@
import { vitePlugin as remix } from "@remix-run/dev" import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite" import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths" import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
remix({ remix({
ssr: false, ssr: false,
ignoredRouteFiles: ["**/*.css"] ignoredRouteFiles: ["**/*.css"],
}), }),
tsconfigPaths() tsconfigPaths(),
], ],
server: { server: {
fs: { fs: {
@ -18,10 +19,7 @@ export default defineConfig({
// If you're comfortable with Vite's dev server making any file within the // If you're comfortable with Vite's dev server making any file within the
// project root available, you can remove this option. See more: // project root available, you can remove this option. See more:
// https://vitejs.dev/config/server-options.html#server-fs-allow // https://vitejs.dev/config/server-options.html#server-fs-allow
allow: [ allow: ["app", "node_modules/@fontsource-variable/manrope"],
"app", },
"node_modules/@fontsource-variable/manrope", },
] });
}
}
})