feat: notifications, redux, tailwind

This commit is contained in:
2025-07-27 11:41:43 +04:00
parent 5e6cfe8070
commit 948480c219
38 changed files with 9594 additions and 2229 deletions

16
src/.storybook/main.ts Normal file
View File

@ -0,0 +1,16 @@
import type { StorybookConfig } from '@storybook/nextjs';
const config: StorybookConfig = {
core: {
disableWhatsNewNotifications: true,
disableTelemetry: true,
enableCrashReports: false,
},
stories: ['../components/**/*.(stories|story).@(js|jsx|ts|tsx)'],
addons: ['storybook-dark-mode'],
framework: {
name: '@storybook/nextjs',
options: {},
},
};
export default config;

View File

@ -0,0 +1,36 @@
import '@mantine/core/styles.css';
import React, { useEffect } from 'react';
import { addons } from '@storybook/preview-api';
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
import { MantineProvider, useMantineColorScheme } from '@mantine/core';
import { theme } from '../theme';
export const parameters = {
layout: 'fullscreen',
options: {
showPanel: false,
storySort: (a, b) => {
return a.title.localeCompare(b.title, undefined, { numeric: true });
},
},
};
const channel = addons.getChannel();
function ColorSchemeWrapper({ children }: { children: React.ReactNode }) {
const { setColorScheme } = useMantineColorScheme();
const handleColorScheme = (value: boolean) => setColorScheme(value ? 'dark' : 'light');
useEffect(() => {
channel.on(DARK_MODE_EVENT_NAME, handleColorScheme);
return () => channel.off(DARK_MODE_EVENT_NAME, handleColorScheme);
}, [channel]);
return <>{children}</>;
}
export const decorators = [
(renderStory: any) => <ColorSchemeWrapper>{renderStory()}</ColorSchemeWrapper>,
(renderStory: any) => <MantineProvider theme={theme}>{renderStory()}</MantineProvider>,
];

7
src/app/global.css Normal file
View File

@ -0,0 +1,7 @@
@import "tailwindcss";
body {
@mixin light {
background-color: whitesmoke;
}
}

View File

@ -1,2 +0,0 @@
@import "tailwind-preset-mantine";
@import "./theme.css";

View File

@ -1,41 +1,54 @@
import "@mantine/core/styles.css"
import "@mantine/dates/styles.css"
import "@mantine/notifications/styles.css"
import '@mantine/dropzone/styles.css';
import React from "react"
import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
import React, { ReactNode } from "react";
import {
ColorSchemeScript,
mantineHtmlProps,
MantineProvider,
} from "@mantine/core"
import { theme } from "./theme"
import "./globals.css"
import { Notifications } from "@mantine/notifications"
import { ModalsProvider } from "@mantine/modals"
} from "@mantine/core";
import { theme } from "@/theme";
import "@/app/global.css";
import { Notifications } from "@mantine/notifications";
import ReduxProvider from "@/providers/ReduxProvider";
export const metadata = {
title: "Mantine Next.js template",
description: "I am using Mantine with Next.js!",
}
title: "CRM LogiDex",
description: "CRM LogiDex",
};
export default function RootLayout({ children }: { children: any }) {
type Props = {
children: ReactNode;
};
export default function RootLayout({ children }: Props) {
return (
<html lang="en" {...mantineHtmlProps}>
<html
lang="ru"
{...mantineHtmlProps}>
<head>
<ColorSchemeScript />
<link rel="shortcut icon" href="/favicon.svg" />
<ColorSchemeScript defaultColorScheme={"auto"} />
<link
rel="shortcut icon"
href="/favicon.svg"
/>
<link
rel="stylesheet"
href="global.css"
/>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, user-scalable=no"
/>
<title />
</head>
<body>
<MantineProvider theme={theme}>
<Notifications />
<ModalsProvider>{children}</ModalsProvider>
<MantineProvider
theme={theme}
defaultColorScheme={"auto"}>
<ReduxProvider>{children}</ReduxProvider>
<Notifications position="bottom-right" />
</MantineProvider>
</body>
</html>
)
);
}

View File

@ -1,52 +1,11 @@
import { ColorSchemesSwitcher } from "@/components/color-schemes-switcher"
import {
AppShell,
AppShellHeader,
AppShellMain,
Text,
Title,
} from "@mantine/core"
import { ColorSchemeToggle } from "@/components/ColorSchemeToggle/ColorSchemeToggle";
import { Welcome } from "@/components/Welcome/Welcome";
export default function Home() {
export default function HomePage() {
return (
<AppShell header={{ height: 60 }} padding="md">
<AppShellHeader></AppShellHeader>
<AppShellMain>
<Title className="text-center mt-20">
Welcome to{" "}
<Text
inherit
variant="gradient"
component="span"
gradient={{ from: "pink", to: "yellow" }}
>
Mantine
</Text>{" "}
+
<Text
inherit
variant="gradient"
component="span"
gradient={{ from: "blue", to: "green" }}
>
TailwindCSS
</Text>
</Title>
<Text
className="text-bold text-center text-gray-700 dark:text-gray-300 max-w-[500px] mx-auto mt-xl"
ta="center"
size="lg"
maw={580}
mx="auto"
mt="xl"
>
This starter Next.js project includes a minimal setup for
Mantine with TailwindCSS. To get started edit page.tsx file.
</Text>
<div className="flex justify-center mt-10">
<ColorSchemesSwitcher />
</div>
</AppShellMain>
</AppShell>
)
<>
<Welcome />
<ColorSchemeToggle />
</>
);
}

View File

@ -1,133 +0,0 @@
/** This file is autogenerated by the script. Do not edit it manually. */
@theme {
/* colors - all */
/* colors - variant specific */
/* breakpoints */
--breakpoint-*: initial;
--breakpoint-xs: 36em;
--breakpoint-sm: 48em;
--breakpoint-md: 62em;
--breakpoint-lg: 75em;
--breakpoint-xl: 88em;
/* readd back tailwind's default containers vars to fix #24 */
--size-3xs: 16rem;
--size-2xs: 18rem;
--size-xs: 20rem;
--size-sm: 24rem;
--size-md: 28rem;
--size-lg: 32rem;
--size-xl: 36rem;
--size-2xl: 42rem;
--size-3xl: 48rem;
--size-4xl: 56rem;
--size-5xl: 64rem;
--size-6xl: 72rem;
--size-7xl: 80rem;
--container-3xs: var(--size-3xs);
--container-2xs: var(--size-2xs);
--container-xs: var(--size-xs);
--container-sm: var(--size-sm);
--container-md: var(--size-md);
--container-lg: var(--size-lg);
--container-xl: var(--size-xl);
--container-2xl: var(--size-2xl);
--container-3xl: var(--size-3xl);
--container-4xl: var(--size-4xl);
--container-5xl: var(--size-5xl);
--container-6xl: var(--size-6xl);
--container-7xl: var(--size-7xl);
--width-3xs: var(--size-3xs);
--width-2xs: var(--size-2xs);
--width-xs: var(--size-xs);
--width-sm: var(--size-sm);
--width-md: var(--size-md);
--width-lg: var(--size-lg);
--width-xl: var(--size-xl);
--width-2xl: var(--size-2xl);
--width-3xl: var(--size-3xl);
--width-4xl: var(--size-4xl);
--width-5xl: var(--size-5xl);
--width-6xl: var(--size-6xl);
--width-7xl: var(--size-7xl);
--min-width-3xs: var(--size-3xs);
--min-width-2xs: var(--size-2xs);
--min-width-xs: var(--size-xs);
--min-width-sm: var(--size-sm);
--min-width-md: var(--size-md);
--min-width-lg: var(--size-lg);
--min-width-xl: var(--size-xl);
--min-width-2xl: var(--size-2xl);
--min-width-3xl: var(--size-3xl);
--min-width-4xl: var(--size-4xl);
--min-width-5xl: var(--size-5xl);
--min-width-6xl: var(--size-6xl);
--min-width-7xl: var(--size-7xl);
--max-width-3xs: var(--size-3xs);
--max-width-2xs: var(--size-2xs);
--max-width-xs: var(--size-xs);
--max-width-sm: var(--size-sm);
--max-width-md: var(--size-md);
--max-width-lg: var(--size-lg);
--max-width-xl: var(--size-xl);
--max-width-2xl: var(--size-2xl);
--max-width-3xl: var(--size-3xl);
--max-width-4xl: var(--size-4xl);
--max-width-5xl: var(--size-5xl);
--max-width-6xl: var(--size-6xl);
--max-width-7xl: var(--size-7xl);
--height-3xs: var(--size-3xs);
--height-2xs: var(--size-2xs);
--height-xs: var(--size-xs);
--height-sm: var(--size-sm);
--height-md: var(--size-md);
--height-lg: var(--size-lg);
--height-xl: var(--size-xl);
--height-2xl: var(--size-2xl);
--height-3xl: var(--size-3xl);
--height-4xl: var(--size-4xl);
--height-5xl: var(--size-5xl);
--height-6xl: var(--size-6xl);
--height-7xl: var(--size-7xl);
--min-height-3xs: var(--size-3xs);
--min-height-2xs: var(--size-2xs);
--min-height-xs: var(--size-xs);
--min-height-sm: var(--size-sm);
--min-height-md: var(--size-md);
--min-height-lg: var(--size-lg);
--min-height-xl: var(--size-xl);
--min-height-2xl: var(--size-2xl);
--min-height-3xl: var(--size-3xl);
--min-height-4xl: var(--size-4xl);
--min-height-5xl: var(--size-5xl);
--min-height-6xl: var(--size-6xl);
--min-height-7xl: var(--size-7xl);
--max-height-3xs: var(--size-3xs);
--max-height-2xs: var(--size-2xs);
--max-height-xs: var(--size-xs);
--max-height-sm: var(--size-sm);
--max-height-md: var(--size-md);
--max-height-lg: var(--size-lg);
--max-height-xl: var(--size-xl);
--max-height-2xl: var(--size-2xl);
--max-height-3xl: var(--size-3xl);
--max-height-4xl: var(--size-4xl);
--max-height-5xl: var(--size-5xl);
--max-height-6xl: var(--size-6xl);
--max-height-7xl: var(--size-7xl);
}

View File

@ -1,15 +0,0 @@
"use client"
import { createTheme } from "@mantine/core"
export const theme = createTheme({
breakpoints: {
xs: "36em",
sm: "48em",
md: "62em",
lg: "75em",
xl: "88em",
},
})
export default theme

View File

@ -0,0 +1,17 @@
"use client";
import { Button, Group, useMantineColorScheme } from "@mantine/core";
export function ColorSchemeToggle() {
const { setColorScheme } = useMantineColorScheme();
return (
<Group
justify="center"
mt="xl">
<Button onClick={() => setColorScheme("light")}>Light</Button>
<Button onClick={() => setColorScheme("dark")}>Dark</Button>
<Button onClick={() => setColorScheme("auto")}>Auto</Button>
</Group>
);
}

View File

@ -0,0 +1,31 @@
import { Anchor, Text, Title } from "@mantine/core";
export function Welcome() {
return (
<>
<Title
ta="center"
mt={100}
className={"font-bold underline"}>
Welcome to Mantine
</Title>
<Text
c="dimmed"
ta="center"
size="lg"
maw={580}
mx="auto"
mt="xl">
This starter Next.js project includes a minimal setup for server
side rendering, if you want to learn more on Mantine + Next.js
integration follow{" "}
<Anchor
href="https://mantine.dev/guides/next/"
size="lg">
this guide
</Anchor>
. To get started edit page.tsx file.
</Text>
</>
);
}

View File

@ -1,18 +0,0 @@
"use client"
import { Button, Group, useMantineColorScheme } from "@mantine/core"
export function ColorSchemesSwitcher() {
const { setColorScheme, clearColorScheme } = useMantineColorScheme()
return (
<Group>
<Button variant={"filled"} onClick={() => setColorScheme("light")}>
Light
</Button>
<Button onClick={() => setColorScheme("dark")}>Dark</Button>
<Button onClick={() => setColorScheme("auto")}>Auto</Button>
<Button onClick={clearColorScheme}>Clear</Button>
</Group>
)
}

View File

@ -0,0 +1,23 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface AuthState {
phoneNumber: string | null;
}
const initialState: AuthState = {
phoneNumber: null,
};
export const authSlice = createSlice({
name: "authentication",
initialState,
reducers: {
setPhoneNumber: (state, action: PayloadAction<string | null>) => {
state.phoneNumber = action.payload;
},
},
});
export const { setPhoneNumber } = authSlice.actions;
export default authSlice.reducer;

View File

@ -0,0 +1,8 @@
import { combineReducers } from "@reduxjs/toolkit";
import authReducer from "@/lib/features/auth/authSlice";
const rootReducer = combineReducers({
auth: authReducer,
});
export default rootReducer;

View File

@ -0,0 +1 @@
export * from "./notifications";

View File

@ -0,0 +1,46 @@
import { notifications } from "@mantine/notifications";
type CustomNotifications = {
notify: (...params: Parameters<typeof notifications.show>) => void;
success: (...params: Parameters<typeof notifications.show>) => void;
warn: (...params: Parameters<typeof notifications.show>) => void;
error: (...params: Parameters<typeof notifications.show>) => void;
guess: (
ok: boolean,
...params: Parameters<typeof notifications.show>
) => void;
} & typeof notifications;
const customNotifications: CustomNotifications = {
...notifications,
notify: params => {
return notifications.show({
...params,
color: "blue",
});
},
success: params => {
return notifications.show({
...params,
color: "green",
});
},
warn: params => {
return notifications.show({
...params,
color: "yellow",
});
},
error: params => {
return notifications.show({
...params,
color: "red",
});
},
guess: (ok: boolean, params) => {
if (ok) return customNotifications.success(params);
return customNotifications.error(params);
},
};
export { customNotifications as notifications };

28
src/lib/store.ts Normal file
View File

@ -0,0 +1,28 @@
import { configureStore } from "@reduxjs/toolkit";
import { useDispatch } from "react-redux";
import { persistReducer, persistStore } from "redux-persist";
import storage from "redux-persist/lib/storage";
import rootReducer from "@/lib/features/rootReducer";
const persistConfig = {
key: "root",
storage,
whitelist: ["targetService", "verification", "auth"],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();

View File

@ -0,0 +1,23 @@
"use client";
import { ReactNode } from "react";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { persistor, store } from "@/lib/store";
type Props = {
children: ReactNode;
};
export default function ReduxProvider({ children }: Props) {
return (
<Provider store={store}>
{" "}
<PersistGate
loading={null}
persistor={persistor}>
{children}
</PersistGate>
</Provider>
);
}

5
src/redux-persist.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module "redux-persist/lib/storage" {
import { WebStorage } from "redux-persist/es/types";
const localStorage: WebStorage;
export default localStorage;
}

38
src/theme.ts Normal file
View File

@ -0,0 +1,38 @@
import { createTheme, MantineColorsTuple } from "@mantine/core";
export const myColor: MantineColorsTuple = [
"#e2faff",
"#d4eff8",
"#afdce9",
"#87c8db",
"#65b7cf",
"#4aaac7",
"#3fa7c6",
"#2c92af",
"#1b829e",
"#00718c",
];
const radius = "lg";
const size = "lg";
export const theme = createTheme({
colors: {
myColor,
},
primaryColor: "myColor",
components: {
Button: {
defaultProps: {
radius,
size,
},
},
InputBase: {
defaultProps: {
radius,
size,
},
},
},
});