moving frontend to a seperate repo
Brijesh ops@brijesh.dev
Thu, 04 Jul 2024 07:18:12 +0530
26 files changed,
1024 insertions(+),
0 deletions(-)
jump to
A
.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts
A
next.config.mjs
@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +}; + +export default nextConfig;
A
package.json
@@ -0,0 +1,32 @@
+{ + "name": "client", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@headlessui/react": "^2.1.1", + "@headlessui/tailwindcss": "^0.2.1", + "@remixicon/react": "^4.2.0", + "@tremor/react": "^3.17.4", + "next": "14.2.4", + "react": "^18", + "react-dom": "^18", + "recharts": "^2.12.7" + }, + "devDependencies": { + "@tailwindcss/forms": "^0.5.7", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "eslint": "^8", + "eslint-config-next": "14.2.4", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5" + } +}
A
postcss.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config;
A
src/api/projects.ts
@@ -0,0 +1,78 @@
+// type APIResponseType = { +// Status: string; +// Message: string; +// RequestID: string; +// Data: any; +// }; + +type ProjectType = { + ID?: string; + Name?: string; +}; + +async function CreatePorject( + projectObject: ProjectType, + setter: (response: any) => void, +) { + fetch("http://127.0.0.1:4000/projects", { + method: "POST", + body: JSON.stringify(projectObject), + }) + .then((res) => res.json()) + .then((data) => { + setter(data); + }); +} + +function ListProjects(setter: any) { + fetch("http://127.0.0.1:4000/projects") + .then((res) => res.json()) + .then((data) => { + setter(data); + }); +} + +function GetProjectById(projectID: string, setter: any) { + projectID && + fetch("http://127.0.0.1:4000/project?id=" + projectID) + .then((res) => res.json()) + .then((data) => { + setter(data); + }); +} + +function UpdateProject( + projectObject: ProjectType, + setter: (response: any) => void, +) { + fetch("http://127.0.0.1:4000/project", { + method: "PUT", + body: JSON.stringify(projectObject), + }) + .then((res) => res.json()) + .then((data) => { + setter(data); + }); +} + +function DeleteProject( + projectObject: ProjectType, + setter: (response: any) => void, +) { + fetch("http://127.0.0.1:4000/project", { + method: "DELETE", + body: JSON.stringify(projectObject), + }) + .then((res) => res.json()) + .then((data) => { + setter(data); + }); +} + +export { + CreatePorject, + ListProjects, + GetProjectById, + UpdateProject, + DeleteProject, +};
A
src/components/Breadcrumb.tsx
@@ -0,0 +1,43 @@
+import Link from "next/link"; +import { useRouter } from "next/router"; + +const Breadcrumb = () => { + const router = useRouter(); + return ( + <div className="bg-slate-100 py-1 px-2 border-b border-slate-200"> + {router.pathname.startsWith("/projects") && ( + <div className="flex gap-0.5 items-center"> + <p>/</p> + <Link className="hover:underline" href="/projects"> + Projects + </Link> + {router.pathname.replace("/projects", "") === "/create" && ( + <> + <p>/</p> + <Link className="hover:underline" href={"/projects/create"}> + Create Project + </Link> + </> + )} + {/* if path contains only one slash, it means page is project details */} + {(router.pathname.match(new RegExp("/", "g")) || []).length == 2 && + !router.pathname.includes("/create") && + !router.pathname.includes("/update") && + !router.pathname.includes("/delete") && ( + <> + <p>/</p> + <Link + className="hover:underline" + href={`/projects/${router.query.id}`} + > + Project Details + </Link> + </> + )} + </div> + )} + </div> + ); +}; + +export default Breadcrumb;
A
src/components/Button.tsx
@@ -0,0 +1,32 @@
+const PrimaryButton = ({ + text, + onClick = () => {}, + disabled = false, + type = "button", + intent = "primary", +}: { + text: string; + onClick?: () => void; + disabled?: boolean; + type?: "button" | "submit" | "reset"; + intent?: "primary" | "danger"; +}) => { + return ( + <div> + <button + onClick={onClick} + disabled={disabled} + type={type} + className={`text-white font-medium px-2 py-1 rounded ${ + intent === "primary" + ? "bg-slate-700 disabled:bg-slate-500" + : "bg-red-700 disabled:bg-red-500" + }`} + > + {text} + </button> + </div> + ); +}; + +export { PrimaryButton };
A
src/components/MainLayout.tsx
@@ -0,0 +1,94 @@
+import { Kode_Mono } from "next/font/google"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import Breadcrumb from "./Breadcrumb"; + +const LogoFont = Kode_Mono({ subsets: ["latin"] }); + +type SidebarOptionType = { + name: string; + link: string; +}; + +const SidebarOptions: SidebarOptionType[] = [ + { name: "Home", link: "/" }, + { name: "Projects", link: "/projects" }, + { name: "Logs", link: "/logs" }, + { name: "Metrics", link: "/metrics" }, + { name: "Analytics", link: "/analytics" }, +]; + +const BookmarkOptions: SidebarOptionType[] = [ + { name: "Example Project", link: "/projects/1" }, +]; + +const MainLayout = ({ children }: { children: React.ReactNode }) => { + const router = useRouter(); + return ( + <main className="text-slate-600 text-sm"> + <div className="flex"> + <div className="w-72 h-[calc(100vh-0px)] bg-white border-r border-slate-200 flex flex-col justify-between"> + <div> + <div className="h-14 w-full px-4 border-b border-slate-200 flex items-center justify-between"> + <div> + <p + className={`text-xl text-black font-semibold ${LogoFont.className}`} + > + WATCHMAN + </p> + </div> + </div> + <div className="flex flex-col"> + <div className="bg-slate-400 text-white text-xs font-bold py-0.5 px-3"> + MENU + </div> + {SidebarOptions.map((option) => ( + <Link + key={option.name} + href={`${option.link}`} + className={`border-b border-slate-200 py-2 px-3 hover:bg-slate-100 ${ + router.pathname.startsWith(option.link) && + option.link !== "/" + ? "bg-slate-200" + : "" + } ${ + router.pathname === option.link && option.link == "/" + ? "bg-slate-200" + : "" + }`} + > + {option.name} + </Link> + ))} + <div className="bg-slate-400 text-white text-xs font-bold py-0.5 px-3"> + BOOKMARKS + </div> + {BookmarkOptions.map((option) => ( + <Link + key={option.name} + href={option.link} + className={`border-b border-slate-200 py-2 px-3 hover:bg-slate-100 ${ + router.pathname == option.link ? "bg-slate-200" : "" + }`} + > + {option.name} + </Link> + ))} + </div> + </div> + + <div className="border-b border-slate-200 py-2 px-3"> + <p>Settings</p> + </div> + </div> + <div className="w-[calc(100vw-286px)]"> + <Breadcrumb /> + + <div className="p-4">{children}</div> + </div> + </div> + </main> + ); +}; + +export default MainLayout;
A
src/components/table.tsx
@@ -0,0 +1,49 @@
+export default function Table({ + columns, + data, +}: { + columns: string[]; + data: any[]; +}) { + return ( + <div> + <div className="flow-root"> + <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> + <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> + <div className="overflow-hidden shadow ring-1 ring-slate-200 sm:-lg"> + <table className="min-w-full divide-y divide-slate-200"> + <thead className="bg-slate-100"> + <tr> + {columns.map((column: string) => ( + <th + key={column} + scope="col" + className="px-3 py-2 text-left text-sm font-semibold text-slate-500" + > + {column} + </th> + ))} + </tr> + </thead> + <tbody className="divide-y divide-slate-200 bg-white"> + {data.map((row: any) => ( + <tr key={row.id}> + {columns.map((column: string) => ( + <td + key={column} + className="whitespace-nowrap py-2 px-3 text-sm text-slate-600" + > + {row[column.toLowerCase()]} + </td> + ))} + </tr> + ))} + </tbody> + </table> + </div> + </div> + </div> + </div> + </div> + ); +}
A
src/components/visualisation/Bar-Chart.tsx
@@ -0,0 +1,124 @@
+import { BarChart, Card, Divider, Switch } from "@tremor/react"; +import { useState } from "react"; + +function valueFormatter(number: any) { + return new Intl.NumberFormat("en-US", { + maximumFractionDigits: 0, + notation: "compact", + compactDisplay: "short", + style: "currency", + currency: "INR", + }).format(number); +} + +export default function BarChartExample() { + const [showComparison, setShowComparison] = useState(false); + return ( + <> + {/* <Card className="ax-w-2xl"> */} + <BarChart + data={data} + index="date" + categories={["value"]} + colors={["slate"]} + valueFormatter={valueFormatter} + showYAxis={false} + className="my-4 h-40" + showLegend={false} + showGridLines={false} + showAnimation={true} + animationDuration={100} + tickGap={30} + barCategoryGap={3} + /> + {/* </Card> */} + </> + ); +} + +const data = [ + { date: "May 23", value: 56000 }, + { date: "Jun 23", value: 30000 }, + { date: "Jul 23", value: 85390 }, + { date: "Aug 23", value: 80100 }, + { date: "Sep 23", value: 75090 }, + { date: "Oct 23", value: 71080 }, + { date: "Nov 23", value: 61210 }, + { date: "Dec 23", value: 60143 }, + { date: "Jan 24", value: 10000 }, + { date: "Feb 24", value: 10000 }, + { date: "Mar 24", value: 10000 }, + { date: "Apr 24", value: 10000 }, + { date: "May 24", value: 10000 }, + { date: "Jun 24", value: 10000 }, + { date: "Jul 24", value: 10000 }, + { date: "Aug 24", value: 10000 }, + { date: "Sep 24", value: 10000 }, + { date: "Oct 24", value: 10000 }, + { date: "Nov 24", value: 10000 }, + { date: "Dec 24", value: 10000 }, + { date: "Jan 25", value: 10000 }, + { date: "Feb 25", value: 10000 }, + { date: "Mar 25", value: 10000 }, + { date: "Apr 25", value: 10000 }, + { date: "May 25", value: 10000 }, + { date: "Jun 25", value: 10000 }, + { date: "Jul 25", value: 10000 }, + { date: "Aug 25", value: 10000 }, + { date: "Sep 25", value: 10000 }, + { date: "Oct 25", value: 10000 }, + { date: "Nov 25", value: 10000 }, + { date: "Dec 25", value: 10000 }, + { date: "Jan 26", value: 10000 }, + { date: "Feb 26", value: 10000 }, + { date: "Mar 26", value: 10000 }, + { date: "Apr 26", value: 10000 }, + { date: "May 26", value: 10000 }, + { date: "Jun 26", value: 10000 }, + { date: "Jul 26", value: 10000 }, + { date: "Aug 26", value: 10000 }, + { date: "Sep 26", value: 10000 }, + { date: "Oct 26", value: 10000 }, + { date: "Nov 26", value: 10000 }, + { date: "Dec 26", value: 10000 }, + { date: "Jan 27", value: 10000 }, + { date: "Feb 27", value: 10000 }, + { date: "Mar 27", value: 10000 }, + { date: "Apr 27", value: 10000 }, + { date: "May 27", value: 10000 }, + { date: "Jun 27", value: 10000 }, + { date: "Jul 27", value: 43523 }, + { date: "Aug 27", value: 10000 }, + { date: "Sep 27", value: 10000 }, + { date: "Oct 27", value: 10000 }, + { date: "Nov 27", value: 10000 }, + { date: "Dec 27", value: 10000 }, + { date: "Jan 28", value: 10000 }, + { date: "Feb 28", value: 10000 }, + { date: "Mar 28", value: 10000 }, + { date: "Apr 28", value: 10000 }, + { date: "May 28", value: 10000 }, + { date: "Jun 28", value: 10000 }, + { date: "Jul 28", value: 10000 }, + { date: "Aug 28", value: 10000 }, + { date: "Sep 28", value: 10000 }, + { date: "Oct 28", value: 10000 }, + { date: "Nov 28", value: 10000 }, + { date: "Dec 28", value: 10000 }, + { date: "Jan 29", value: 10000 }, + { date: "Feb 29", value: 10000 }, + { date: "Mar 29", value: 10000 }, + { date: "Apr 29", value: 10000 }, + { date: "May 29", value: 10000 }, + { date: "Jun 29", value: 10000 }, + { date: "Jul 29", value: 10000 }, + { date: "Aug 29", value: 10000 }, + { date: "Sep 29", value: 10000 }, + { date: "Oct 29", value: 10000 }, + { date: "Nov 29", value: 10000 }, + { date: "Dec 29", value: 10000 }, + { date: "Jan 30", value: 10000 }, + { date: "Feb 30", value: 10000 }, + { date: "Mar 30", value: 10000 }, + { date: "Apr 30", value: 10000 }, +];
A
src/pages/_app.tsx
@@ -0,0 +1,6 @@
+import "@/styles/globals.css"; +import type { AppProps } from "next/app"; + +export default function App({ Component, pageProps }: AppProps) { + return <Component {...pageProps} />; +}
A
src/pages/_document.tsx
@@ -0,0 +1,13 @@
+import { Html, Head, Main, NextScript } from "next/document"; + +export default function Document() { + return ( + <Html lang="en"> + <Head /> + <body> + <Main /> + <NextScript /> + </body> + </Html> + ); +}
A
src/pages/api/hello.ts
@@ -0,0 +1,13 @@
+// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from "next"; + +type Data = { + name: string; +}; + +export default function handler( + req: NextApiRequest, + res: NextApiResponse<Data>, +) { + res.status(200).json({ name: "John Doe" }); +}
A
src/pages/index.tsx
@@ -0,0 +1,26 @@
+import MainLayout from "@/components/MainLayout"; +import BarChartExample from "@/components/visualisation/Bar-Chart"; +import { BarChart } from "@tremor/react"; +import Head from "next/head"; +import Link from "next/link"; + +const Homepage = () => { + return ( + <MainLayout> + <Head> + <title>Watchman</title> + <Link + rel="icon" + href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎯</text></svg>" + /> + </Head> + <h1 className="text-xl font-medium">Homepage</h1> + <p className="my-2"> + Please check this page later, as it is still under construction. + </p> + <BarChartExample /> + </MainLayout> + ); +}; + +export default Homepage;
A
src/pages/logs/index.tsx
@@ -0,0 +1,23 @@
+import MainLayout from "@/components/MainLayout"; +import Head from "next/head"; +import Link from "next/link"; + +const LogsIndexPage = () => { + return ( + <MainLayout> + <Head> + <title>Watchman</title> + <Link + rel="icon" + href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎯</text></svg>" + /> + </Head> + <h1 className="text-xl font-medium">Logs Index Page</h1> + <p className="my-2"> + Please check this page later, as it is still under construction. + </p> + </MainLayout> + ); +}; + +export default LogsIndexPage;
A
src/pages/projects/[id]/delete.tsx
@@ -0,0 +1,53 @@
+import { DeleteProject, GetProjectById, ListProjects } from "@/api/projects"; +import { PrimaryButton } from "@/components/Button"; +import MainLayout from "@/components/MainLayout"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +const ProjectDeletePage = () => { + const router = useRouter(); + const { id } = router.query; + + const [project, setProject] = useState<any>(); + const [apiResponse, setApiResponse] = useState<any>(); + + useEffect(() => { + GetProjectById(id ? id.toString() : "", setProject); + }, [id]); + + function deleteProject() { + let project = { ID: id ? id.toString() : "" }; + + DeleteProject(project, setApiResponse); + } + + useEffect(() => { + apiResponse && apiResponse.status === "OK" && router.push("/projects"); + }, [apiResponse]); + + return ( + <MainLayout> + <Head> + <title>Delete Project | Watchman</title> + </Head> + <div className="mb-4 flex justify-between items-center uppercase"> + {project?.data?.name && ( + <h1 className="text-xl font-medium">{project.data.name}</h1> + )} + </div> + {project && project.data && project.data.name && ( + <p>Are you sure you want to delete this {project.data.name} project?</p> + )} + <div className="my-4 flex gap-2"> + <PrimaryButton + text="Delete" + intent="danger" + onClick={() => deleteProject()} + /> + <PrimaryButton text="Cancel" onClick={() => router.push("/projects")} /> + </div> + </MainLayout> + ); +}; +export default ProjectDeletePage;
A
src/pages/projects/[id]/index.tsx
@@ -0,0 +1,54 @@
+import { GetProjectById, ListProjects } from "@/api/projects"; +import { PrimaryButton } from "@/components/Button"; +import MainLayout from "@/components/MainLayout"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +const ProjectDetailsPage = () => { + const router = useRouter(); + const { id } = router.query; + + const [project, setProject] = useState<any>(); + + useEffect(() => { + GetProjectById(id ? id.toString() : "", setProject); + }, [id]); + + return ( + <MainLayout> + <Head> + <title>Project Details | Watchman</title> + </Head> + <div className="mb-4 flex justify-between items-center uppercase"> + {project?.data?.name && ( + <h1 className="text-xl font-medium">{project.data.name}</h1> + )} + <div className="flex items-center gap-2"> + <PrimaryButton + text="Update" + onClick={() => router.push("/projects/" + id + "/update")} + /> + <PrimaryButton + text="Delete" + intent="danger" + onClick={() => router.push("/projects/" + id + "/delete")} + /> + </div> + </div> + {project && project.data && project.data.name && ( + <> + <div className="flex justify-start"> + <div className="w-48">Name</div> + <p>{project.data.name}</p> + </div> + <div className="flex justify-start"> + <div className="w-48">ID</div> + <p>{project.data.id}</p> + </div> + </> + )} + </MainLayout> + ); +}; +export default ProjectDetailsPage;
A
src/pages/projects/[id]/update.tsx
@@ -0,0 +1,57 @@
+import { GetProjectById, UpdateProject } from "@/api/projects"; +import { PrimaryButton } from "@/components/Button"; +import MainLayout from "@/components/MainLayout"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +const UpdateProjectPage = () => { + const router = useRouter(); + const { id } = router.query; + + const [project, setProject] = useState<any>(); + + useEffect(() => { + GetProjectById(id ? id.toString() : "", setProject); + }, [id]); + + const [name, setName] = useState(project?.data?.name || ""); + const [apiResponse, setApiResponse] = useState<any>(); + + async function formSubmit(e: React.FormEvent) { + e.preventDefault(); + let project = { Name: name, Id: id }; + + UpdateProject(project, setApiResponse); + } + + useEffect(() => { + apiResponse && apiResponse.status === "OK" && router.push("/projects"); + }, [apiResponse]); + + useEffect(() => { + setName(project?.data?.name || ""); + }, [project]); + + return ( + <MainLayout> + <Head> + <title>Update Project | Watchman</title> + </Head> + <h1 className="text-xl font-medium">Update Project</h1> + + <form onSubmit={formSubmit} className="my-4 flex flex-col gap-4"> + <div> + <label className="block text-sm font-medium">Name</label> + <input + value={name} + onChange={(e) => setName(e.target.value)} + className="w-80 border border-slate-200 p-1" + /> + </div> + <PrimaryButton type="submit" text="Update Project" /> + </form> + </MainLayout> + ); +}; +export default UpdateProjectPage;
A
src/pages/projects/create.tsx
@@ -0,0 +1,46 @@
+import { CreatePorject } from "@/api/projects"; +import { PrimaryButton } from "@/components/Button"; +import MainLayout from "@/components/MainLayout"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +const CreateProjectPage = () => { + const router = useRouter(); + + const [name, setName] = useState(""); + const [apiResponse, setApiResponse] = useState<any>(); + + async function formSubmit(e: React.FormEvent) { + e.preventDefault(); + let project = { Name: name }; + + CreatePorject(project, setApiResponse); + } + + useEffect(() => { + apiResponse && apiResponse.status === "OK" && router.push("/projects"); + }, [apiResponse]); + + return ( + <MainLayout> + <Head> + <title>Create Projects | Watchman</title> + </Head> + <h1 className="text-xl font-medium">Create Project</h1> + + <form onSubmit={formSubmit} className="my-4 flex flex-col gap-4"> + <div> + <label className="block text-sm font-medium">Name</label> + <input + value={name} + onChange={(e) => setName(e.target.value)} + className="w-80 border border-slate-200 p-1" + /> + </div> + <PrimaryButton type="submit" text="Create Project" /> + </form> + </MainLayout> + ); +}; +export default CreateProjectPage;
A
src/pages/projects/index.tsx
@@ -0,0 +1,47 @@
+import { ListProjects } from "@/api/projects"; +import { PrimaryButton } from "@/components/Button"; +import MainLayout from "@/components/MainLayout"; +import Table from "@/components/table"; +import Head from "next/head"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +const ProjectsHomePage = () => { + const router = useRouter(); + + const [projects, setProjects] = useState<any>([]); + + useEffect(() => { + ListProjects(setProjects); + }, []); + + return ( + <MainLayout> + <Head> + <title>Projects | Watchman</title> + </Head> + <div className="mb-4 flex justify-between items-center"> + <h1 className="text-xl font-medium">Projects</h1> + <PrimaryButton + text="Create Project" + onClick={() => router.push("/projects/create")} + /> + </div> + {projects.data !== undefined && ( + <Table + columns={["Name", "ID"]} + data={projects.data.map((project: any) => { + return { + name: ( + <Link href={"/projects/" + project.id}>{project.name}</Link> + ), + id: project.id, + }; + })} + /> + )} + </MainLayout> + ); +}; +export default ProjectsHomePage;
A
src/styles/globals.css
@@ -0,0 +1,3 @@
+@tailwind base; +@tailwind components; +@tailwind utilities;
A
tailwind.config.ts
@@ -0,0 +1,157 @@
+// import type { Config } from "tailwindcss"; +// +// const config: Config = { +// content: [ +// "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", +// "./src/components/**/*.{js,ts,jsx,tsx,mdx}", +// "./src/app/**/*.{js,ts,jsx,tsx,mdx}", +// ], +// theme: { +// extend: { +// backgroundImage: { +// "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", +// "gradient-conic": +// "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", +// }, +// }, +// }, +// plugins: [], +// }; +// export default config; + +import type { Config } from "tailwindcss"; +import colors from "tailwindcss/colors"; + +const config: Config = { + content: [ + "./src/**/*.{js,ts,jsx,tsx}", + + // Path to Tremor module + "./node_modules/@tremor/**/*.{js,ts,jsx,tsx}", + ], + theme: { + transparent: "transparent", + current: "currentColor", + extend: { + colors: { + // light mode + tremor: { + brand: { + faint: colors.blue[50], + muted: colors.blue[200], + subtle: colors.blue[400], + DEFAULT: colors.blue[500], + emphasis: colors.blue[700], + inverted: colors.white, + }, + background: { + muted: colors.gray[50], + subtle: colors.gray[100], + DEFAULT: colors.white, + emphasis: colors.gray[700], + }, + border: { + DEFAULT: colors.gray[200], + }, + ring: { + DEFAULT: colors.gray[200], + }, + content: { + subtle: colors.gray[400], + DEFAULT: colors.gray[500], + emphasis: colors.gray[700], + strong: colors.gray[900], + inverted: colors.white, + }, + }, + // dark mode + // "dark-tremor": { + // brand: { + // faint: "#0B1229", + // muted: colors.blue[950], + // subtle: colors.blue[800], + // DEFAULT: colors.blue[500], + // emphasis: colors.blue[400], + // inverted: colors.blue[950], + // }, + // background: { + // muted: "#131A2B", + // subtle: colors.gray[800], + // DEFAULT: colors.gray[900], + // emphasis: colors.gray[300], + // }, + // border: { + // DEFAULT: colors.gray[800], + // }, + // ring: { + // DEFAULT: colors.gray[800], + // }, + // content: { + // subtle: colors.gray[600], + // DEFAULT: colors.gray[500], + // emphasis: colors.gray[200], + // strong: colors.gray[50], + // inverted: colors.gray[950], + // }, + // }, + }, + boxShadow: { + // light + "tremor-input": "0 1px 2px 0 rgb(0 0 0 / 0.05)", + "tremor-card": + "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", + "tremor-dropdown": + "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)", + // dark + // "dark-tremor-input": "0 1px 2px 0 rgb(0 0 0 / 0.05)", + // "dark-tremor-card": + // "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", + // "dark-tremor-dropdown": + // "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)", + }, + borderRadius: { + "tremor-small": "0.375rem", + "tremor-default": "0.2rem", + "tremor-full": "9999px", + }, + fontSize: { + "tremor-label": ["0.75rem", { lineHeight: "1rem" }], + "tremor-default": ["0.875rem", { lineHeight: "1.25rem" }], + "tremor-title": ["1.125rem", { lineHeight: "1.75rem" }], + "tremor-metric": ["1.875rem", { lineHeight: "2.25rem" }], + }, + }, + }, + safelist: [ + { + pattern: + /^(bg-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + variants: ["hover", "ui-selected"], + }, + { + pattern: + /^(text-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + variants: ["hover", "ui-selected"], + }, + { + pattern: + /^(border-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + variants: ["hover", "ui-selected"], + }, + { + pattern: + /^(ring-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + }, + { + pattern: + /^(stroke-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + }, + { + pattern: + /^(fill-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + }, + ], + plugins: [require("@headlessui/tailwindcss"), require("@tailwindcss/forms")], +}; + +export default config;
A
tsconfig.json
@@ -0,0 +1,21 @@
+{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +}