Merged develop into feature/level-file-upload

This commit is contained in:
carlos.mesquita
2024-08-19 22:47:48 +00:00
13 changed files with 865 additions and 417 deletions

17
components.json Normal file
View File

@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": false,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

View File

@@ -14,12 +14,13 @@
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@firebase/util": "^1.9.7",
"@headlessui/react": "^1.7.13",
"@headlessui/react": "^2.1.2",
"@mdi/js": "^7.1.96",
"@mdi/react": "^1.6.1",
"@next/font": "13.1.6",
"@paypal/paypal-js": "^7.1.0",
"@paypal/react-paypal-js": "^8.1.3",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-popover": "^1.1.1",
"@react-pdf/renderer": "^3.1.14",
"@react-spring/web": "^9.7.4",
"@tanstack/react-table": "^8.10.1",
@@ -30,7 +31,8 @@
"axios": "^1.3.5",
"bcrypt": "^5.1.1",
"chart.js": "^4.2.1",
"clsx": "^1.2.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"countries-list": "^3.0.1",
"country-codes-list": "^1.6.11",
"currency-symbol-map": "^5.1.0",
@@ -48,7 +50,7 @@
"lodash": "^4.17.21",
"moment": "^2.29.4",
"moment-timezone": "^0.5.44",
"next": "13.1.6",
"next": "^14.2.5",
"nodemailer": "^6.9.5",
"nodemailer-express-handlebars": "^6.1.0",
"primeicons": "^6.0.1",
@@ -77,7 +79,9 @@
"short-unique-id": "5.0.2",
"stripe": "^13.10.0",
"swr": "^2.1.3",
"tailwind-merge": "^2.5.2",
"tailwind-scrollbar-hide": "^1.1.7",
"tailwindcss-animate": "^1.0.7",
"typescript": "4.9.5",
"use-file-picker": "^2.1.0",
"uuid": "^9.0.0",

View File

@@ -1,10 +1,11 @@
import {Column, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
import {Column, flexRender, getCoreRowModel, getSortedRowModel, useReactTable} from "@tanstack/react-table";
export default function List<T>({data, columns}: {data: T[]; columns: any[]}) {
const table = useReactTable({
data,
columns: columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});
return (
@@ -13,8 +14,22 @@ export default function List<T>({data, columns}: {data: T[]; columns: any[]}) {
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th className="p-4 text-left" key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<>
<div
{...{
className: header.column.getCanSort() ? "cursor-pointer select-none py-4 text-left first:pl-4" : "",
onClick: header.column.getToggleSortingHandler(),
}}>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: " 🔼",
desc: " 🔽",
}[header.column.getIsSorted() as string] ?? null}
</div>
</>
)}
</th>
))}
</tr>

View File

@@ -0,0 +1,31 @@
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverAnchor = PopoverPrimitive.Anchor
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border border-neutral-200 bg-white p-4 text-neutral-950 shadow-md outline-none 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 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

View File

@@ -11,8 +11,10 @@ import {useAssignmentArchive} from "@/hooks/useAssignmentArchive";
import {uniqBy} from "lodash";
import {useAssignmentUnarchive} from "@/hooks/useAssignmentUnarchive";
import {getUserName} from "@/utils/users";
import {User} from "@/interfaces/user";
interface Props {
users: User[];
onClick?: () => void;
allowDownload?: boolean;
reload?: Function;
@@ -35,9 +37,8 @@ export default function AssignmentCard({
reload,
allowArchive,
allowUnarchive,
users,
}: Assignment & Props) {
const {users} = useUsers();
const renderPdfIcon = usePDFDownload("assignments");
const renderArchiveIcon = useAssignmentArchive(id, reload);
const renderUnarchiveIcon = useAssignmentUnarchive(id, reload);

View File

@@ -339,7 +339,7 @@ export default function CorporateDashboard({user}: Props) {
<h2 className="text-2xl font-semibold">Active Assignments ({assignments.filter(activeFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(activeFilter).map((a) => (
<AssignmentCard {...a} onClick={() => setSelectedAssignment(a)} key={a.id} />
<AssignmentCard {...a} users={users} onClick={() => setSelectedAssignment(a)} key={a.id} />
))}
</div>
</section>
@@ -355,6 +355,7 @@ export default function CorporateDashboard({user}: Props) {
{assignments.filter(futureFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => {
setSelectedAssignment(a);
setIsCreatingAssignment(true);
@@ -370,6 +371,7 @@ export default function CorporateDashboard({user}: Props) {
{assignments.filter(pastFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
@@ -385,6 +387,7 @@ export default function CorporateDashboard({user}: Props) {
{assignments.filter(archivedFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload

View File

@@ -21,6 +21,7 @@ import {
BsArrowRepeat,
BsPlus,
BsPersonFillGear,
BsFilter,
} from "react-icons/bs";
import UserCard from "@/components/UserCard";
import useGroups from "@/hooks/useGroups";
@@ -44,16 +45,23 @@ import {createColumn, createColumnHelper} from "@tanstack/react-table";
import List from "@/components/List";
import {getUserCorporate} from "@/utils/groups";
import {getCorporateUser, getUserCompanyName} from "@/resources/user";
import {Switch} from "@headlessui/react";
import Checkbox from "@/components/Low/Checkbox";
import {uniq, uniqBy} from "lodash";
import {groupBy, uniq, uniqBy} from "lodash";
import Select from "@/components/Low/Select";
import {Menu, MenuButton, MenuItem, MenuItems} from "@headlessui/react";
import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover";
interface Props {
user: MasterCorporateUser;
}
type StudentPerformanceItem = User & {corporate?: CorporateUser; group: string};
const activeFilter = (a: Assignment) =>
moment(a.endDate).isAfter(moment()) && moment(a.startDate).isBefore(moment()) && a.assignees.length > a.results.length;
const pastFilter = (a: Assignment) => (moment(a.endDate).isBefore(moment()) || a.assignees.length === a.results.length) && !a.archived;
const archivedFilter = (a: Assignment) => a.archived;
const futureFilter = (a: Assignment) => moment(a.startDate).isAfter(moment());
type StudentPerformanceItem = User & {corporate?: CorporateUser; group?: Group};
const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPerformanceItem[]; stats: Stat[]; users: User[]; groups: Group[]}) => {
const [isShowingAmount, setIsShowingAmount] = useState(false);
const [availableCorporates] = useState(
@@ -62,8 +70,15 @@ const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPe
"id",
),
);
const [availableGroups] = useState(
uniqBy(
items.map((x) => x.group),
"id",
),
);
const [selectedCorporate, setSelectedCorporate] = useState<CorporateUser | null | undefined>(null);
const [selectedGroup, setSelectedGroup] = useState<Group | null | undefined>(null);
const columnHelper = createColumnHelper<StudentPerformanceItem>();
@@ -82,7 +97,7 @@ const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPe
}),
columnHelper.accessor("group", {
header: "Group",
cell: (info) => info.getValue(),
cell: (info) => info.getValue()?.name || "N/A",
}),
columnHelper.accessor("corporate", {
header: "Corporate",
@@ -184,16 +199,30 @@ const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPe
const filterUsers = (data: StudentPerformanceItem[]) => {
console.log(data, selectedCorporate);
const filterByCorporate = (item: StudentPerformanceItem) => item.corporate?.id === selectedCorporate?.id;
const filterByGroup = (item: StudentPerformanceItem) => item.group?.id === selectedGroup?.id;
const filters: ((item: StudentPerformanceItem) => boolean)[] = [];
if (selectedCorporate !== null) filters.push(filterByCorporate);
if (selectedGroup !== null) filters.push(filterByGroup);
return filters.reduce((d, f) => d.filter(f), data);
};
return (
<div className="flex flex-col gap-4 w-full h-full">
<div className="w-full flex flex-col gap-4">
<div className="w-full flex gap-4 justify-between items-center">
<Checkbox isChecked={isShowingAmount} onChange={setIsShowingAmount}>
Show Utilization
</Checkbox>
<Popover>
<PopoverTrigger>
<div className="flex items-center justify-center p-2 hover:bg-neutral-300/50 rounded-full transition ease-in-out duration-300">
<BsFilter size={20} />
</div>
</PopoverTrigger>
<PopoverContent className="w-96">
<div className="flex flex-col gap-4">
<span className="font-bold text-lg">Filters</span>
<Select
options={availableCorporates.map((x) => ({
value: x?.id || "N/A",
@@ -205,19 +234,45 @@ const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPe
? null
: {
value: selectedCorporate?.id || "N/A",
label: selectedCorporate?.corporateInformation?.companyInformation?.name || selectedCorporate?.name || "N/A",
label:
selectedCorporate?.corporateInformation?.companyInformation?.name ||
selectedCorporate?.name ||
"N/A",
}
}
placeholder="Select a Corporate..."
onChange={(value) =>
!value
? setSelectedCorporate(null)
: setSelectedCorporate(value.value === "N/A" ? undefined : availableCorporates.find((x) => x?.id === value.value))
: setSelectedCorporate(
value.value === "N/A" ? undefined : availableCorporates.find((x) => x?.id === value.value),
)
}
/>
<Checkbox isChecked={isShowingAmount} onChange={setIsShowingAmount}>
Show Utilization
</Checkbox>
<Select
options={availableGroups.map((x) => ({
value: x?.id || "N/A",
label: x?.name || "N/A",
}))}
isClearable
value={
selectedGroup === null
? null
: {
value: selectedGroup?.id || "N/A",
label: selectedGroup?.name || "N/A",
}
}
placeholder="Select a Group..."
onChange={(value) =>
!value
? setSelectedGroup(null)
: setSelectedGroup(value.value === "N/A" ? undefined : availableGroups.find((x) => x?.id === value.value))
}
/>
</div>
</PopoverContent>
</Popover>
</div>
<List<StudentPerformanceItem>
data={filterUsers(
@@ -245,6 +300,7 @@ export default function MasterCorporateDashboard({user}: Props) {
const [showModal, setShowModal] = useState(false);
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
const [corporateAssignments, setCorporateAssignments] = useState<(Assignment & {corporate?: CorporateUser})[]>([]);
const {stats} = useStats();
const {users, reload} = useUsers();
@@ -263,6 +319,17 @@ export default function MasterCorporateDashboard({user}: Props) {
setShowModal(!!selectedUser && page === "");
}, [selectedUser, page]);
useEffect(() => {
setCorporateAssignments(
assignments.filter(activeFilter).map((a) => ({
...a,
corporate: !!users.find((x) => x.id === a.assigner)
? getCorporateUser(users.find((x) => x.id === a.assigner)!, users, groups)
: undefined,
})),
);
}, [assignments, groups, users]);
const studentFilter = (user: User) => user.type === "student" && corporateUserGroups.includes(user.id);
const teacherFilter = (user: User) => user.type === "teacher" && corporateUserGroups.includes(user.id);
@@ -372,7 +439,7 @@ export default function MasterCorporateDashboard({user}: Props) {
.filter((x) => x.type === "student" && groups.flatMap((g) => g.participants).includes(x.id))
.map((u) => ({
...u,
group: groups.find((x) => x.participants.includes(u.id))?.name || "N/A",
group: groups.find((x) => x.participants.includes(u.id)),
corporate: getCorporateUser(u, users, groups),
}));
@@ -398,12 +465,6 @@ export default function MasterCorporateDashboard({user}: Props) {
};
const AssignmentsPage = () => {
const activeFilter = (a: Assignment) =>
moment(a.endDate).isAfter(moment()) && moment(a.startDate).isBefore(moment()) && a.assignees.length > a.results.length;
const pastFilter = (a: Assignment) => (moment(a.endDate).isBefore(moment()) || a.assignees.length === a.results.length) && !a.archived;
const archivedFilter = (a: Assignment) => a.archived;
const futureFilter = (a: Assignment) => moment(a.startDate).isAfter(moment());
return (
<>
<AssignmentView
@@ -450,11 +511,29 @@ export default function MasterCorporateDashboard({user}: Props) {
<BsArrowRepeat className={clsx("text-xl", isAssignmentsLoading && "animate-spin")} />
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-lg font-bold">Active Assignments Status</span>
<div className="flex items-center gap-4">
<span>
<b>Total:</b> {assignments.filter(activeFilter).reduce((acc, curr) => acc + curr.results.length, 0)}/
{assignments.filter(activeFilter).reduce((acc, curr) => curr.exams.length + acc, 0)}
</span>
{Object.keys(groupBy(corporateAssignments, (x) => x.corporate?.id)).map((x) => (
<div key={x}>
<span className="font-semibold">{getUserCompanyName(users.find((u) => u.id === x)!, users, groups)}: </span>
<span>
{groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.results.length + acc, 0)}/
{groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.exams.length + acc, 0)}
</span>
</div>
))}
</div>
</div>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Active Assignments ({assignments.filter(activeFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(activeFilter).map((a) => (
<AssignmentCard {...a} onClick={() => setSelectedAssignment(a)} key={a.id} />
<AssignmentCard {...a} users={users} onClick={() => setSelectedAssignment(a)} key={a.id} />
))}
</div>
</section>
@@ -470,6 +549,7 @@ export default function MasterCorporateDashboard({user}: Props) {
{assignments.filter(futureFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => {
setSelectedAssignment(a);
setIsCreatingAssignment(true);
@@ -485,6 +565,7 @@ export default function MasterCorporateDashboard({user}: Props) {
{assignments.filter(pastFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
@@ -500,6 +581,7 @@ export default function MasterCorporateDashboard({user}: Props) {
{assignments.filter(archivedFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload

View File

@@ -63,7 +63,7 @@ export default function TeacherDashboard({user}: Props) {
const {stats} = useStats();
const {users, reload} = useUsers();
const {groups} = useGroups({admin: user.id});
const {groups} = useGroups({adminAdmins: user.id});
const {permissions} = usePermissions(user.id);
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assigner: user.id});
@@ -193,7 +193,7 @@ export default function TeacherDashboard({user}: Props) {
? groups
.filter((g) => g.admin === selectedUser.id)
.flatMap((g) => g.participants)
.includes(x.id) || false
.includes(x.id)
: groups.flatMap((g) => g.participants).includes(x.id)),
)}
assigner={user.id}
@@ -222,7 +222,7 @@ export default function TeacherDashboard({user}: Props) {
<h2 className="text-2xl font-semibold">Active Assignments ({assignments.filter(activeFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(activeFilter).map((a) => (
<AssignmentCard {...a} onClick={() => setSelectedAssignment(a)} key={a.id} />
<AssignmentCard {...a} users={users} onClick={() => setSelectedAssignment(a)} key={a.id} />
))}
</div>
</section>
@@ -238,6 +238,7 @@ export default function TeacherDashboard({user}: Props) {
{assignments.filter(futureFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => {
setSelectedAssignment(a);
setIsCreatingAssignment(true);
@@ -253,6 +254,7 @@ export default function TeacherDashboard({user}: Props) {
{assignments.filter(pastFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
@@ -268,6 +270,7 @@ export default function TeacherDashboard({user}: Props) {
{assignments.filter(archivedFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload

6
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -36,5 +36,5 @@ async function GET(req: NextApiRequest, res: NextApiResponse) {
const assigners = await getAllAssignersByCorporate(id);
const assignments = await getAssignmentsByAssigners([...assigners, id]);
res.status(200).json(assignments);
res.status(200).json(uniqBy(assignments, "id"));
}

View File

@@ -2,89 +2,3 @@
@tailwind components;
@tailwind utilities;
@layer utilities {
.scrollbar-hide {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
/* Chrome, Safari and Opera */
}
}
.training-scrollbar::-webkit-scrollbar {
@apply w-1.5;
}
.training-scrollbar::-webkit-scrollbar-track {
@apply bg-transparent;
}
.training-scrollbar::-webkit-scrollbar-thumb {
@apply bg-gray-400 hover:bg-gray-500 rounded-full transition-colors opacity-50 hover:opacity-75;
}
.training-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
}
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;
--foreground-rgb: 53, 51, 56;
--background-start-rgb: 245, 245, 245;
--background-end-rgb: 245, 245, 245;
--primary-glow: conic-gradient(from 180deg at 50% 50%, #16abff33 0deg, #0885ff33 55deg, #54d6ff33 120deg, #0071ff33 160deg, transparent 360deg);
--secondary-glow: radial-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
--tile-start-rgb: 239, 245, 249;
--tile-end-rgb: 228, 232, 233;
--tile-border: conic-gradient(#00000080, #00000040, #00000030, #00000020, #00000010, #00000010, #00000080);
--callout-rgb: 238, 240, 241;
--callout-border-rgb: 172, 175, 176;
--card-rgb: 180, 185, 188;
--card-border-rgb: 131, 134, 135;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
min-height: 100vh !important;
height: 100%;
max-width: 100vw;
overflow-x: hidden;
overflow-y: auto;
font-family: "Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif;
}
body {
min-height: 100vh !important;
height: 100%;
max-width: 100vw;
overflow-x: hidden;
font-family: "Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb));
}
a {
color: inherit;
text-decoration: none;
}

View File

@@ -4,15 +4,36 @@ module.exports = {
safelist: [
{
pattern: /bg-ai-detection-result-(ai|mixed|human)/,
}
},
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
keyframes: {
"accordion-down": {
from: {height: "0"},
to: {height: "var(--radix-accordion-content-height)"},
},
"accordion-up": {
from: {height: "var(--radix-accordion-content-height)"},
to: {height: "0"},
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
scale: {
'102': '1.02',
102: "1.02",
},
boxShadow: {
'training-inset': 'inset 0px 2px 18px 0px #00000029',
"training-inset": "inset 0px 2px 18px 0px #00000029",
},
colors: {
mti: {
@@ -46,17 +67,17 @@ module.exports = {
result: {
ai: {DEFAULT: "#f4bf4f", text: "#f0bc4f", bg: "#fff8e8"},
mixed: {DEFAULT: "#93aafb", bg: "rgba(147, 170, 251, 0.3)"},
human: {DEFAULT:"#50c08a", bg: "#e9f9ed"}
human: {DEFAULT: "#50c08a", bg: "#e9f9ed"},
},
confidence: {
high: {DEFAULT: "#84d1ac", transparent: "#daf0e3"},
medium: {DEFAULT: "#f7ec88", transparent: "#fcf8d8"},
low: {DEFAULT: "#ffc1c1", transparent: "#ffebe9"},
border: "#888888"
border: "#888888",
},
highlight: "#ffefb7",
text: "#8992B1"
}
text: "#8992B1",
},
},
screens: {
"-sm": {max: "639px"},
@@ -68,5 +89,5 @@ module.exports = {
},
},
},
plugins: [require("daisyui"), require("tailwind-scrollbar-hide"),],
plugins: [require("daisyui"), require("tailwind-scrollbar-hide"), require("tailwindcss-animate")],
};

895
yarn.lock

File diff suppressed because it is too large Load Diff