diff --git a/.env.development b/.env.development
index e6c84a3..fbd7112 100644
--- a/.env.development
+++ b/.env.development
@@ -1 +1,4 @@
-API_HOST=localhost
\ No newline at end of file
+API_HOST=localhost
+API_PORT=8090
+API_PROTOCOL=http
+NEXTAUTH_SECRET=secret
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 020c9fc..b0f42d7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,13 +11,16 @@
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
- "@fontsource/roboto": "^5.0.8",
+ "@fontsource/inter": "^5.0.16",
+ "@fontsource/plus-jakarta-sans": "^5.0.18",
"@mui/icons-material": "^5.15.0",
"@mui/material": "^5.15.0",
"@mui/material-nextjs": "^5.15.0",
"next": "14.0.4",
+ "next-auth": "^4.24.5",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "react-hook-form": "^7.49.2"
},
"devDependencies": {
"@types/node": "^20",
@@ -285,10 +288,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
- "node_modules/@fontsource/roboto": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.8.tgz",
- "integrity": "sha512-XxPltXs5R31D6UZeLIV1td3wTXU3jzd3f2DLsXI8tytMGBkIsGcc9sIyiupRtA8y73HAhuSCeweOoBqf6DbWCA=="
+ "node_modules/@fontsource/inter": {
+ "version": "5.0.16",
+ "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.0.16.tgz",
+ "integrity": "sha512-qF0aH5UiZvCmneX5orJbVRoc2VTyLTV3X/7laMp03Qt28L+B9tFlZODOGUL64wDWc69YVdi1LeJB0cIgd51lvw=="
+ },
+ "node_modules/@fontsource/plus-jakarta-sans": {
+ "version": "5.0.18",
+ "resolved": "https://registry.npmjs.org/@fontsource/plus-jakarta-sans/-/plus-jakarta-sans-5.0.18.tgz",
+ "integrity": "sha512-poMuIcQ8F7WGXF4mNUviDk49Ewdf0pU7wmCzWQNbWEtus+L46BSp+4OqbWy0LWJEmMLI9F5hUHaSo2maLJwrQw=="
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
@@ -662,6 +670,126 @@
"node": ">= 10"
}
},
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "14.0.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz",
+ "integrity": "sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "14.0.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz",
+ "integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "14.0.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz",
+ "integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "14.0.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz",
+ "integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "14.0.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz",
+ "integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "14.0.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz",
+ "integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.0.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz",
+ "integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "14.0.4",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz",
+ "integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@nodelib/fs.walk": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
diff --git a/package.json b/package.json
index 5ad444a..f83f509 100644
--- a/package.json
+++ b/package.json
@@ -12,13 +12,16 @@
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
- "@fontsource/roboto": "^5.0.8",
+ "@fontsource/inter": "^5.0.16",
+ "@fontsource/plus-jakarta-sans": "^5.0.18",
"@mui/icons-material": "^5.15.0",
"@mui/material": "^5.15.0",
"@mui/material-nextjs": "^5.15.0",
"next": "14.0.4",
+ "next-auth": "^4.24.5",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "react-hook-form": "^7.49.2"
},
"devDependencies": {
"@types/node": "^20",
diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 0000000..2fa7df5
--- /dev/null
+++ b/src/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,6 @@
+import { authOptions } from "@/config/authConfig";
+import NextAuth from "next-auth";
+
+const handler = NextAuth(authOptions);
+
+export { handler as GET, handler as POST };
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
new file mode 100644
index 0000000..59ee896
--- /dev/null
+++ b/src/app/dashboard/page.tsx
@@ -0,0 +1,5 @@
+const Dashboard: React.FC = () => {
+ return "Dashboard (protected)";
+};
+
+export default Dashboard;
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 1a4652a..705cdc8 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,11 +1,6 @@
import type { Metadata } from "next";
-import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
-import { CssBaseline } from "@mui/material";
-import "@fontsource/roboto/300.css";
-import "@fontsource/roboto/400.css";
-import "@fontsource/roboto/500.css";
-import "@fontsource/roboto/700.css";
+import ThemeRegistry from "@/theme/ThemeRegistry";
export const metadata: Metadata = {
title: "Create Next App",
@@ -20,8 +15,7 @@ export default function RootLayout({
return (
-
- {children}
+ {children}
);
diff --git a/src/app/login/LoginForm.tsx b/src/app/login/LoginForm.tsx
new file mode 100644
index 0000000..9a8244a
--- /dev/null
+++ b/src/app/login/LoginForm.tsx
@@ -0,0 +1,94 @@
+"use client";
+
+import { FormHelperText } from "@mui/material";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import TextField from "@mui/material/TextField";
+import Typography from "@mui/material/Typography";
+import { signIn } from "next-auth/react";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { SubmitHandler, useForm } from "react-hook-form";
+
+type LoginFields = {
+ username: string;
+ password: string;
+};
+
+// Error codes in https://next-auth.js.org/configuration/pages#sign-in-page
+const getHumanFriendlyErrorMessage = (serverError: string): string => {
+ switch (serverError) {
+ case "CredentialsSignin":
+ return "Invalid username or password.";
+ case "Default":
+ default:
+ return "Something went wrong. Please try again later.";
+ }
+};
+
+const LoginForm: React.FC = () => {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm();
+
+ const [serverError, setServerError] = useState();
+
+ const router = useRouter();
+
+ const onSubmit: SubmitHandler = async (data) => {
+ const res = await signIn("credentials", {
+ redirect: false,
+ ...data,
+ });
+
+ if (res?.error) {
+ setServerError(res.error);
+ return;
+ }
+ const callbackUrl =
+ new URLSearchParams(window.location.search).get("callbackUrl") || "/";
+
+ router.push(callbackUrl);
+ };
+
+ return (
+
+ Sign In
+
+
+ {serverError && (
+
+ {getHumanFriendlyErrorMessage(serverError)}
+
+ )}
+
+
+ );
+};
+
+export default LoginForm;
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
new file mode 100644
index 0000000..1708823
--- /dev/null
+++ b/src/app/login/page.tsx
@@ -0,0 +1,28 @@
+import Grid from "@mui/material/Grid";
+import Paper from "@mui/material/Paper";
+import LoginForm from "./LoginForm";
+import { getServerSession } from "next-auth";
+import { redirect } from "next/navigation";
+import { authOptions } from "@/config/authConfig";
+
+const Login: React.FC = async () => {
+ const session = await getServerSession(authOptions);
+ if (session?.user) {
+ redirect("/");
+ }
+
+ return (
+
+
+ Hero
+
+
+
+
+
+
+
+ );
+};
+
+export default Login;
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 4743b42..9271349 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,5 +1,7 @@
-const Home: React.FC = () => {
- return "home page";
+import { permanentRedirect } from "next/navigation";
+
+const Home: React.FC = async () => {
+ permanentRedirect("/dashboard");
};
export default Home;
diff --git a/src/config/api.ts b/src/config/api.ts
new file mode 100644
index 0000000..d920df6
--- /dev/null
+++ b/src/config/api.ts
@@ -0,0 +1,2 @@
+export const BASE_API_URL = `${process.env.API_PROTOCOL}://${process.env.API_HOST}:${process.env.API_PORT}`;
+export const LOGIN_API_PATH = `${BASE_API_URL}/api/login`;
diff --git a/src/config/authConfig.ts b/src/config/authConfig.ts
new file mode 100644
index 0000000..f2b689e
--- /dev/null
+++ b/src/config/authConfig.ts
@@ -0,0 +1,42 @@
+import { AuthOptions } from "next-auth";
+import CredentialsProvider from "next-auth/providers/credentials";
+import { LOGIN_API_PATH } from "./api";
+
+export const authOptions: AuthOptions = {
+ debug: process.env.NODE_ENV === "development",
+ providers: [
+ CredentialsProvider({
+ id: "credentials",
+ name: "Credentials",
+ credentials: {
+ username: { label: "Username", type: "text" },
+ password: { label: "Password", type: "password" },
+ },
+ async authorize(credentials, req) {
+ const res = await fetch(LOGIN_API_PATH, {
+ method: "POST",
+ body: JSON.stringify(credentials),
+ headers: { "Content-Type": "application/json" },
+ });
+
+ const user = await res.json();
+
+ if (res.ok && user) {
+ return user;
+ }
+ return null;
+ },
+ }),
+ ],
+ pages: {
+ signIn: "/login",
+ },
+ callbacks: {
+ jwt(params) {
+ return params.token;
+ },
+ session(params) {
+ return params.session;
+ },
+ },
+};
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..856504c
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,8 @@
+import { withAuth } from "next-auth/middleware";
+import { authOptions } from "@/config/authConfig";
+
+export default withAuth({
+ pages: authOptions.pages,
+});
+
+export const config = { matcher: ["/dashboard"] };
diff --git a/src/theme/EmotionCache.tsx b/src/theme/EmotionCache.tsx
new file mode 100644
index 0000000..8f13bef
--- /dev/null
+++ b/src/theme/EmotionCache.tsx
@@ -0,0 +1,100 @@
+"use client";
+import * as React from "react";
+import createCache from "@emotion/cache";
+import { useServerInsertedHTML } from "next/navigation";
+import { CacheProvider as DefaultCacheProvider } from "@emotion/react";
+import type {
+ EmotionCache,
+ Options as OptionsOfCreateCache,
+} from "@emotion/cache";
+
+// Copied from https://github.com/mui/material-ui/blob/master/examples/material-ui-nextjs-ts/src/components/ThemeRegistry/EmotionCache.tsx
+export type NextAppDirEmotionCacheProviderProps = {
+ /** This is the options passed to createCache() from 'import createCache from "@emotion/cache"' */
+ options: Omit;
+ /** By default from 'import { CacheProvider } from "@emotion/react"' */
+ CacheProvider?: (props: {
+ value: EmotionCache;
+ children: React.ReactNode;
+ }) => React.JSX.Element | null;
+ children: React.ReactNode;
+};
+
+// Adapted from https://github.com/garronej/tss-react/blob/main/src/next/appDir.tsx
+export default function NextAppDirEmotionCacheProvider(
+ props: NextAppDirEmotionCacheProviderProps,
+) {
+ const { options, CacheProvider = DefaultCacheProvider, children } = props;
+
+ const [registry] = React.useState(() => {
+ const cache = createCache(options);
+ cache.compat = true;
+ const prevInsert = cache.insert;
+ let inserted: { name: string; isGlobal: boolean }[] = [];
+ cache.insert = (...args) => {
+ const [selector, serialized] = args;
+ if (cache.inserted[serialized.name] === undefined) {
+ inserted.push({
+ name: serialized.name,
+ isGlobal: !selector,
+ });
+ }
+ return prevInsert(...args);
+ };
+ const flush = () => {
+ const prevInserted = inserted;
+ inserted = [];
+ return prevInserted;
+ };
+ return { cache, flush };
+ });
+
+ useServerInsertedHTML(() => {
+ const inserted = registry.flush();
+ if (inserted.length === 0) {
+ return null;
+ }
+ let styles = "";
+ let dataEmotionAttribute = registry.cache.key;
+
+ const globals: {
+ name: string;
+ style: string;
+ }[] = [];
+
+ inserted.forEach(({ name, isGlobal }) => {
+ const style = registry.cache.inserted[name];
+
+ if (typeof style !== "boolean") {
+ if (isGlobal) {
+ globals.push({ name, style });
+ } else {
+ styles += style;
+ dataEmotionAttribute += ` ${name}`;
+ }
+ }
+ });
+
+ return (
+
+ {globals.map(({ name, style }) => (
+
+ ))}
+ {styles && (
+
+ )}
+
+ );
+ });
+
+ return {children};
+}
diff --git a/src/theme/ThemeRegistry.tsx b/src/theme/ThemeRegistry.tsx
new file mode 100644
index 0000000..1c3f593
--- /dev/null
+++ b/src/theme/ThemeRegistry.tsx
@@ -0,0 +1,22 @@
+"use client";
+import * as React from "react";
+import { ThemeProvider } from "@mui/material/styles";
+import CssBaseline from "@mui/material/CssBaseline";
+import NextAppDirEmotionCacheProvider from "./EmotionCache";
+import theme from "./devias-material-kit";
+
+// Copied from https://github.com/mui/material-ui/blob/master/examples/material-ui-nextjs-ts/src/components/ThemeRegistry/ThemeRegistry.tsx
+export default function ThemeRegistry({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/theme/devias-material-kit/colors.ts b/src/theme/devias-material-kit/colors.ts
new file mode 100644
index 0000000..013151f
--- /dev/null
+++ b/src/theme/devias-material-kit/colors.ts
@@ -0,0 +1,57 @@
+export const neutral = {
+ 50: "#F8F9FA",
+ 100: "#F3F4F6",
+ 200: "#E5E7EB",
+ 300: "#D2D6DB",
+ 400: "#9DA4AE",
+ 500: "#6C737F",
+ 600: "#4D5761",
+ 700: "#2F3746",
+ 800: "#1C2536",
+ 900: "#111927",
+};
+
+export const indigo = {
+ lightest: "#F5F7FF",
+ light: "#EBEEFE",
+ main: "#6366F1",
+ dark: "#4338CA",
+ darkest: "#312E81",
+ contrastText: "#FFFFFF",
+};
+
+export const success = {
+ lightest: "#F0FDF9",
+ light: "#3FC79A",
+ main: "#10B981",
+ dark: "#0B815A",
+ darkest: "#134E48",
+ contrastText: "#FFFFFF",
+};
+
+export const info = {
+ lightest: "#ECFDFF",
+ light: "#CFF9FE",
+ main: "#06AED4",
+ dark: "#0E7090",
+ darkest: "#164C63",
+ contrastText: "#FFFFFF",
+};
+
+export const warning = {
+ lightest: "#FFFAEB",
+ light: "#FEF0C7",
+ main: "#F79009",
+ dark: "#B54708",
+ darkest: "#7A2E0E",
+ contrastText: "#FFFFFF",
+};
+
+export const error = {
+ lightest: "#FEF3F2",
+ light: "#FEE4E2",
+ main: "#F04438",
+ dark: "#B42318",
+ darkest: "#7A271A",
+ contrastText: "#FFFFFF",
+};
diff --git a/src/theme/devias-material-kit/components.ts b/src/theme/devias-material-kit/components.ts
new file mode 100644
index 0000000..e84e364
--- /dev/null
+++ b/src/theme/devias-material-kit/components.ts
@@ -0,0 +1,288 @@
+import { ThemeOptions, createTheme } from "@mui/material";
+import palette from "./palette";
+
+// Used only to create transitions
+const muiTheme = createTheme();
+
+const components: ThemeOptions["components"] = {
+ MuiAvatar: {
+ styleOverrides: {
+ root: {
+ fontSize: 14,
+ fontWeight: 600,
+ letterSpacing: 0,
+ },
+ },
+ },
+ MuiButton: {
+ styleOverrides: {
+ root: {
+ borderRadius: "12px",
+ textTransform: "none",
+ },
+ sizeSmall: {
+ padding: "6px 16px",
+ },
+ sizeMedium: {
+ padding: "8px 20px",
+ },
+ sizeLarge: {
+ padding: "11px 24px",
+ },
+ textSizeSmall: {
+ padding: "7px 12px",
+ },
+ textSizeMedium: {
+ padding: "9px 16px",
+ },
+ textSizeLarge: {
+ padding: "12px 16px",
+ },
+ },
+ },
+ MuiCard: {
+ styleOverrides: {
+ root: {
+ borderRadius: 20,
+ [`&.MuiPaper-elevation1`]: {
+ boxShadow:
+ "0px 5px 22px rgba(0, 0, 0, 0.04), 0px 0px 0px 0.5px rgba(0, 0, 0, 0.03)",
+ },
+ },
+ },
+ },
+ MuiCardContent: {
+ styleOverrides: {
+ root: {
+ padding: "32px 24px",
+ "&:last-child": {
+ paddingBottom: "32px",
+ },
+ },
+ },
+ },
+ MuiCardHeader: {
+ defaultProps: {
+ titleTypographyProps: {
+ variant: "h6",
+ },
+ subheaderTypographyProps: {
+ variant: "body2",
+ },
+ },
+ styleOverrides: {
+ root: {
+ padding: "32px 24px 16px",
+ },
+ },
+ },
+ MuiCssBaseline: {
+ styleOverrides: {
+ "*": {
+ boxSizing: "border-box",
+ },
+ html: {
+ MozOsxFontSmoothing: "grayscale",
+ WebkitFontSmoothing: "antialiased",
+ display: "flex",
+ flexDirection: "column",
+ minHeight: "100%",
+ width: "100%",
+ },
+ body: {
+ display: "flex",
+ flex: "1 1 auto",
+ flexDirection: "column",
+ minHeight: "100%",
+ width: "100%",
+ },
+ "#__next": {
+ display: "flex",
+ flex: "1 1 auto",
+ flexDirection: "column",
+ height: "100%",
+ width: "100%",
+ },
+ "#nprogress": {
+ pointerEvents: "none",
+ },
+ "#nprogress .bar": {
+ backgroundColor: palette.primary.main,
+ height: 3,
+ left: 0,
+ position: "fixed",
+ top: 0,
+ width: "100%",
+ zIndex: 2000,
+ },
+ },
+ },
+ MuiInputBase: {
+ styleOverrides: {
+ input: {
+ "&::placeholder": {
+ opacity: 1,
+ },
+ },
+ },
+ },
+ MuiInput: {
+ styleOverrides: {
+ input: {
+ fontSize: 14,
+ fontWeight: 500,
+ lineHeight: "24px",
+ "&::placeholder": {
+ color: palette.text.secondary,
+ },
+ },
+ },
+ },
+ MuiFilledInput: {
+ styleOverrides: {
+ root: {
+ backgroundColor: "transparent",
+ borderRadius: 8,
+ borderStyle: "solid",
+ borderWidth: 1,
+ overflow: "hidden",
+ borderColor: palette.neutral[200],
+ transition: muiTheme.transitions.create(["border-color", "box-shadow"]),
+ "&:hover": {
+ backgroundColor: palette.action.hover,
+ },
+ "&:before": {
+ display: "none",
+ },
+ "&:after": {
+ display: "none",
+ },
+ [`&.Mui-disabled`]: {
+ backgroundColor: "transparent",
+ },
+ [`&.Mui-focused`]: {
+ backgroundColor: "transparent",
+ borderColor: palette.primary.main,
+ boxShadow: `${palette.primary.main} 0 0 0 2px`,
+ },
+ [`&.Mui-error`]: {
+ borderColor: palette.error.main,
+ boxShadow: `${palette.error.main} 0 0 0 2px`,
+ },
+ },
+ input: {
+ fontSize: 14,
+ fontWeight: 500,
+ lineHeight: "24px",
+ },
+ },
+ },
+ MuiOutlinedInput: {
+ styleOverrides: {
+ root: {
+ "&:hover": {
+ backgroundColor: palette.action.hover,
+ [`& .MuiOutlinedInput-notchedOutline`]: {
+ borderColor: palette.neutral[200],
+ },
+ },
+ [`&.Mui-focused`]: {
+ backgroundColor: "transparent",
+ [`& .MuiOutlinedInput-notchedOutline`]: {
+ borderColor: palette.primary.main,
+ boxShadow: `${palette.primary.main} 0 0 0 2px`,
+ },
+ },
+ [`&.Mui-error`]: {
+ [`& .MuiOutlinedInput-notchedOutline`]: {
+ borderColor: palette.error.main,
+ boxShadow: `${palette.error.main} 0 0 0 2px`,
+ },
+ },
+ },
+ input: {
+ fontSize: 14,
+ fontWeight: 500,
+ lineHeight: "24px",
+ },
+ notchedOutline: {
+ borderColor: palette.neutral[200],
+ transition: muiTheme.transitions.create(["border-color", "box-shadow"]),
+ },
+ },
+ },
+ MuiFormLabel: {
+ styleOverrides: {
+ root: {
+ fontSize: 14,
+ fontWeight: 500,
+ [`&.MuiInputLabel-filled`]: {
+ transform: "translate(12px, 18px) scale(1)",
+ },
+ [`&.MuiInputLabel-shrink`]: {
+ [`&.MuiInputLabel-standard`]: {
+ transform: "translate(0, -1.5px) scale(0.85)",
+ },
+ [`&.MuiInputLabel-filled`]: {
+ transform: "translate(12px, 6px) scale(0.85)",
+ },
+ [`&.MuiInputLabel-outlined`]: {
+ transform: "translate(14px, -9px) scale(0.85)",
+ },
+ },
+ },
+ },
+ },
+ MuiTab: {
+ styleOverrides: {
+ root: {
+ fontSize: 14,
+ fontWeight: 500,
+ lineHeight: 1.71,
+ minWidth: "auto",
+ paddingLeft: 0,
+ paddingRight: 0,
+ textTransform: "none",
+ "& + &": {
+ marginLeft: 24,
+ },
+ },
+ },
+ },
+ MuiTableCell: {
+ styleOverrides: {
+ root: {
+ borderBottomColor: palette.divider,
+ padding: "15px 16px",
+ },
+ },
+ },
+ MuiTableHead: {
+ styleOverrides: {
+ root: {
+ borderBottom: "none",
+ [`& .MuiTableCell-root`]: {
+ borderBottom: "none",
+ backgroundColor: palette.neutral[50],
+ color: palette.neutral[700],
+ fontSize: 12,
+ fontWeight: 600,
+ lineHeight: 1,
+ letterSpacing: 0.5,
+ textTransform: "uppercase",
+ },
+ [`& .MuiTableCell-paddingCheckbox`]: {
+ paddingTop: 4,
+ paddingBottom: 4,
+ },
+ },
+ },
+ },
+ MuiTextField: {
+ defaultProps: {
+ variant: "filled",
+ },
+ },
+};
+
+export default components;
diff --git a/src/theme/devias-material-kit/index.ts b/src/theme/devias-material-kit/index.ts
new file mode 100644
index 0000000..c3959f1
--- /dev/null
+++ b/src/theme/devias-material-kit/index.ts
@@ -0,0 +1,26 @@
+import { createTheme } from "@mui/material";
+import { paletteOptions as palette } from "./palette";
+import components from "./components";
+import shadows from "./shadows";
+import typography from "./typography";
+
+const theme = createTheme({
+ breakpoints: {
+ values: {
+ xs: 0,
+ sm: 600,
+ md: 900,
+ lg: 1200,
+ xl: 1440,
+ },
+ },
+ components,
+ palette,
+ shadows,
+ shape: {
+ borderRadius: 8,
+ },
+ typography,
+});
+
+export default theme;
diff --git a/src/theme/devias-material-kit/palette.ts b/src/theme/devias-material-kit/palette.ts
new file mode 100644
index 0000000..ca6a95c
--- /dev/null
+++ b/src/theme/devias-material-kit/palette.ts
@@ -0,0 +1,35 @@
+import { common } from "@mui/material/colors";
+import { PaletteOptions } from "@mui/material/styles";
+import { error, indigo, info, neutral, success, warning } from "./colors";
+
+const palette = {
+ action: {
+ active: neutral[500],
+ disabled: "rgba(17,25,39,0.38)",
+ disabledBackground: "rgba(17,25,39,0.12)",
+ focus: "rgba(17,25,39,0.16)",
+ hover: "rgba(17,25,39,0.04)",
+ selected: "rgba(17,25,39,0.12)",
+ },
+ background: {
+ default: common.white,
+ paper: common.white,
+ },
+ divider: "#F2F4F7",
+ error,
+ info,
+ mode: "light",
+ primary: indigo,
+ success,
+ text: {
+ primary: neutral[900],
+ secondary: neutral[500],
+ disabled: "rgba(17,25,39,0.38)",
+ },
+ warning,
+ neutral,
+};
+
+export const paletteOptions: PaletteOptions = { ...palette, mode: "light" };
+
+export default palette;
diff --git a/src/theme/devias-material-kit/shadows.ts b/src/theme/devias-material-kit/shadows.ts
new file mode 100644
index 0000000..12db82f
--- /dev/null
+++ b/src/theme/devias-material-kit/shadows.ts
@@ -0,0 +1,31 @@
+import { Shadows } from "@mui/material";
+
+const shadows: Shadows = [
+ "none",
+ "0px 1px 2px rgba(0, 0, 0, 0.08)",
+ "0px 1px 5px rgba(0, 0, 0, 0.08)",
+ "0px 1px 8px rgba(0, 0, 0, 0.08)",
+ "0px 1px 10px rgba(0, 0, 0, 0.08)",
+ "0px 1px 14px rgba(0, 0, 0, 0.08)",
+ "0px 1px 18px rgba(0, 0, 0, 0.08)",
+ "0px 2px 16px rgba(0, 0, 0, 0.08)",
+ "0px 3px 14px rgba(0, 0, 0, 0.08)",
+ "0px 3px 16px rgba(0, 0, 0, 0.08)",
+ "0px 4px 18px rgba(0, 0, 0, 0.08)",
+ "0px 4px 20px rgba(0, 0, 0, 0.08)",
+ "0px 5px 22px rgba(0, 0, 0, 0.08)",
+ "0px 5px 24px rgba(0, 0, 0, 0.08)",
+ "0px 5px 26px rgba(0, 0, 0, 0.08)",
+ "0px 6px 28px rgba(0, 0, 0, 0.08)",
+ "0px 6px 30px rgba(0, 0, 0, 0.08)",
+ "0px 6px 32px rgba(0, 0, 0, 0.08)",
+ "0px 7px 34px rgba(0, 0, 0, 0.08)",
+ "0px 7px 36px rgba(0, 0, 0, 0.08)",
+ "0px 8px 38px rgba(0, 0, 0, 0.08)",
+ "0px 8px 40px rgba(0, 0, 0, 0.08)",
+ "0px 8px 42px rgba(0, 0, 0, 0.08)",
+ "0px 9px 44px rgba(0, 0, 0, 0.08)",
+ "0px 9px 46px rgba(0, 0, 0, 0.08)",
+];
+
+export default shadows;
diff --git a/src/theme/devias-material-kit/typography.ts b/src/theme/devias-material-kit/typography.ts
new file mode 100644
index 0000000..1f2c7f2
--- /dev/null
+++ b/src/theme/devias-material-kit/typography.ts
@@ -0,0 +1,90 @@
+import { TypographyOptions } from "@mui/material/styles/createTypography";
+
+import "@fontsource/inter/400.css";
+import "@fontsource/inter/500.css";
+import "@fontsource/inter/600.css";
+import "@fontsource/plus-jakarta-sans/700.css";
+
+const typography: TypographyOptions = {
+ fontFamily:
+ '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
+ body1: {
+ fontSize: "1rem",
+ fontWeight: 400,
+ lineHeight: 1.5,
+ },
+ body2: {
+ fontSize: "0.875rem",
+ fontWeight: 400,
+ lineHeight: 1.57,
+ },
+ button: {
+ fontWeight: 600,
+ },
+ caption: {
+ fontSize: "0.75rem",
+ fontWeight: 500,
+ lineHeight: 1.66,
+ },
+ subtitle1: {
+ fontSize: "1rem",
+ fontWeight: 500,
+ lineHeight: 1.57,
+ },
+ subtitle2: {
+ fontSize: "0.875rem",
+ fontWeight: 500,
+ lineHeight: 1.57,
+ },
+ overline: {
+ fontSize: "0.75rem",
+ fontWeight: 600,
+ letterSpacing: "0.5px",
+ lineHeight: 2.5,
+ textTransform: "uppercase",
+ },
+ h1: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "3.5rem",
+ lineHeight: 1.2,
+ },
+ h2: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "3rem",
+ lineHeight: 1.2,
+ },
+ h3: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "2.25rem",
+ lineHeight: 1.2,
+ },
+ h4: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "2rem",
+ lineHeight: 1.2,
+ },
+ h5: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "1.5rem",
+ lineHeight: 1.2,
+ },
+ h6: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "1.125rem",
+ lineHeight: 1.2,
+ },
+ fontSize: 0,
+ fontWeightLight: undefined,
+ fontWeightRegular: undefined,
+ fontWeightMedium: undefined,
+ fontWeightBold: undefined,
+ htmlFontSize: 0,
+};
+
+export default typography;