First commit
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
1
dev-dist/registerSW.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' })
|
||||||
92
dev-dist/sw.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// If the loader is already loaded, just stop.
|
||||||
|
if (!self.define) {
|
||||||
|
let registry = {};
|
||||||
|
|
||||||
|
// Used for `eval` and `importScripts` where we can't get script URL by other means.
|
||||||
|
// In both cases, it's safe to use a global var because those functions are synchronous.
|
||||||
|
let nextDefineUri;
|
||||||
|
|
||||||
|
const singleRequire = (uri, parentUri) => {
|
||||||
|
uri = new URL(uri + ".js", parentUri).href;
|
||||||
|
return registry[uri] || (
|
||||||
|
|
||||||
|
new Promise(resolve => {
|
||||||
|
if ("document" in self) {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = uri;
|
||||||
|
script.onload = resolve;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
} else {
|
||||||
|
nextDefineUri = uri;
|
||||||
|
importScripts(uri);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.then(() => {
|
||||||
|
let promise = registry[uri];
|
||||||
|
if (!promise) {
|
||||||
|
throw new Error(`Module ${uri} didn’t register its module`);
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.define = (depsNames, factory) => {
|
||||||
|
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
|
||||||
|
if (registry[uri]) {
|
||||||
|
// Module is already loading or loaded.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let exports = {};
|
||||||
|
const require = depUri => singleRequire(depUri, uri);
|
||||||
|
const specialDeps = {
|
||||||
|
module: { uri },
|
||||||
|
exports,
|
||||||
|
require
|
||||||
|
};
|
||||||
|
registry[uri] = Promise.all(depsNames.map(
|
||||||
|
depName => specialDeps[depName] || require(depName)
|
||||||
|
)).then(deps => {
|
||||||
|
factory(...deps);
|
||||||
|
return exports;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
||||||
|
|
||||||
|
self.skipWaiting();
|
||||||
|
workbox.clientsClaim();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The precacheAndRoute() method efficiently caches and responds to
|
||||||
|
* requests for URLs in the manifest.
|
||||||
|
* See https://goo.gl/S9QRab
|
||||||
|
*/
|
||||||
|
workbox.precacheAndRoute([{
|
||||||
|
"url": "registerSW.js",
|
||||||
|
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||||
|
}, {
|
||||||
|
"url": "index.html",
|
||||||
|
"revision": "0.r4rebmaausg"
|
||||||
|
}], {});
|
||||||
|
workbox.cleanupOutdatedCaches();
|
||||||
|
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||||
|
allowlist: [/^\/$/]
|
||||||
|
}));
|
||||||
|
|
||||||
|
}));
|
||||||
3391
dev-dist/workbox-54d0af47.js
Normal file
28
eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['dist'] },
|
||||||
|
{
|
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>ReGroup Admin</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10076
package-lock.json
generated
Normal file
39
package.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "regroup-admin",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@chakra-ui/react": "^3.2.3",
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"framer-motion": "^11.18.0",
|
||||||
|
"next-themes": "^0.4.4",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-hook-form": "^7.54.2",
|
||||||
|
"react-icons": "^5.4.0",
|
||||||
|
"react-router-dom": "^7.1.1",
|
||||||
|
"vite-plugin-pwa": "^0.21.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@chakra-ui/cli": "^3.2.3",
|
||||||
|
"@eslint/js": "^9.17.0",
|
||||||
|
"@types/react": "^18.3.18",
|
||||||
|
"@types/react-dom": "^18.3.5",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.16",
|
||||||
|
"globals": "^15.14.0",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
|
"typescript-eslint": "^8.18.2",
|
||||||
|
"vite": "^6.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/icon-128x128.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/icon-144x144.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/icon-152x152.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/icon-192x192.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
public/icon-384x384.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
public/icon-512x512.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
public/icon-72x72.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icon-96x96.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
32
public/manifest.webmanifest
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "ReGroup",
|
||||||
|
"short_name": "RG",
|
||||||
|
"description": "Join your community now",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#222935",
|
||||||
|
"background_color": "#222935",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/icon-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-256x256.png",
|
||||||
|
"sizes": "256x256",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-384x384.png",
|
||||||
|
"sizes": "384x384",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
1
public/vite copy.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/vite.png
Normal file
|
After Width: | Height: | Size: 850 B |
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
24
src/App.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
|
||||||
|
import GlobalStateContext from './Contexts/GlobalStateContext';
|
||||||
|
import DefaultLayout from './Layouts/DefaultLayout';
|
||||||
|
import Login from './Pages/Login';
|
||||||
|
import { RouteLink } from './Routes/Routes';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const context = useContext(GlobalStateContext);
|
||||||
|
if (!context) throw new Error('App must be used within a GlobalStateProvider');
|
||||||
|
const { isAuthenticate } = context;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/login" element={<Login />} />
|
||||||
|
<Route path="/*" element={isAuthenticate === true ? (<DefaultLayout><Routes>{RouteLink.map(({ path, Component }, index) => (<Route key={index} path={path} element={<Component />} />))}</Routes></DefaultLayout>) : (<Login />)} />
|
||||||
|
<Route path="*" element={<Login />} />
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
13
src/Contexts/GlobalStateContext.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// GlobalStateContext.ts
|
||||||
|
import { createContext, Dispatch, SetStateAction } from 'react';
|
||||||
|
|
||||||
|
// Define the shape of your context value
|
||||||
|
type GlobalStateContextType = {
|
||||||
|
isAuthenticate: boolean;
|
||||||
|
setIsAuthenticate: Dispatch<SetStateAction<boolean>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the context with a default value of `undefined`
|
||||||
|
const GlobalStateContext = createContext<GlobalStateContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export default GlobalStateContext;
|
||||||
17
src/Contexts/GlobalStateProvider.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { ReactNode, useState } from 'react';
|
||||||
|
import GlobalStateContext from './GlobalStateContext';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const GlobalStateProvider = ({ children }:{children:ReactNode}) => {
|
||||||
|
const [isAuthenticate, setIsAuthenticate] = useState<boolean>(true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GlobalStateContext.Provider value={{ isAuthenticate, setIsAuthenticate }}>
|
||||||
|
{children}
|
||||||
|
</GlobalStateContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlobalStateProvider;
|
||||||
62
src/Layouts/DefaultLayout.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { HStack, Image, Text, VStack } from "@chakra-ui/react";
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { GiHamburgerMenu } from "react-icons/gi";
|
||||||
|
import { RiNotificationLine } from "react-icons/ri";
|
||||||
|
import { nav } from "../Routes/Nav";
|
||||||
|
import logo from '../assets/redogo.svg';
|
||||||
|
import { Avatar } from "../components/ui/avatar";
|
||||||
|
import { NavLink, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { AccordionItem, AccordionItemContent, AccordionItemTrigger, AccordionRoot } from "../components/ui/accordion";
|
||||||
|
|
||||||
|
const DefaultLayout: FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack position={'relative'} bg="#222935" backgroundPosition="center" backgroundRepeat="repeat" backgroundSize="cover" gap={0} w="100%" h="100vh" p={2}>
|
||||||
|
{/* ✅ Spheres */}
|
||||||
|
<span className="red-sphere-1" />
|
||||||
|
<span className="blue-sphere-1" />
|
||||||
|
<span className="red-sphere-2" />
|
||||||
|
<span className="blue-sphere-2" />
|
||||||
|
<span className="red-sphere-3" />
|
||||||
|
<span className="blue-sphere-2" />
|
||||||
|
|
||||||
|
<VStack shadow={'xs'} zIndex={1} gap={0} rounded={'lg'} h="100%" w="15%" bg="#222935" >
|
||||||
|
<HStack w={'100%'} p={3} h={'6.5%'} justifyContent={'space-between'}>
|
||||||
|
<Image w={8} src={logo} />
|
||||||
|
<GiHamburgerMenu cursor={'pointer'} style={{ fontSize: '20px' }} />
|
||||||
|
</HStack>
|
||||||
|
<VStack w={'100%'} p={3}>
|
||||||
|
<Text ps={'6px'} fontSize={'11px'} w={'100%'} fontWeight={'bold'}>Menu</Text>
|
||||||
|
{nav?.map(({ title, path, Icon, type, children }, index) => type === 'single' ?
|
||||||
|
<NavLink className="link" key={index} to={path} style={{ cursor: 'pointer', borderRadius: '8px', padding: '6px', width: '100%', display: 'flex', alignItems: 'center', gap: 6, border: '1px solid #222935' }} ><Icon style={{ fontSize: '20px' }} /> <Text fontSize={'xs'} w={'100%'}>{title}</Text></NavLink> :
|
||||||
|
<AccordionRoot>
|
||||||
|
<AccordionItem p={0} key={index} value={title}>
|
||||||
|
<AccordionItemTrigger className={`link ${location?.pathname === path && 'active'}`} onClick={() => navigate(path)} gap={0} style={{ cursor: 'pointer', borderRadius: '8px', padding: '5px', width: '100%', display: 'flex', alignItems: 'center', border: '1px solid #222935', fontSize: '14px' }}> <Text fontSize={'xs'} gap={1} display={'flex'} alignItems={'center'} ><Icon style={{ fontSize: '20px' }} />{title}</Text></AccordionItemTrigger>
|
||||||
|
{children?.map(({ title, path, Icon }, index) => <AccordionItemContent ps={4}><NavLink className="link" key={index} to={path} style={{ marginTop: 6, cursor: 'pointer', borderRadius: '8px', padding: '6px', width: '100%', display: 'flex', alignItems: 'center', gap: 6, border: '1px solid #222935' }} ><Icon style={{ fontSize: '20px' }} /> <Text fontSize={'xs'} w={'100%'}>{title}</Text></NavLink></AccordionItemContent>)}
|
||||||
|
</AccordionItem>
|
||||||
|
</AccordionRoot>)}
|
||||||
|
</VStack>
|
||||||
|
</VStack>
|
||||||
|
<VStack gap={0} h="100%" w="85%" >
|
||||||
|
<HStack h={'6%'} w={'100%'} justifyContent={'flex-end'} pe={3} gap={6}>
|
||||||
|
<RiNotificationLine cursor={'pointer'} style={{ fontSize: '22px' }} />
|
||||||
|
<HStack cursor={'pointer'} onClick={() => navigate('/profile')} >
|
||||||
|
<Avatar size={'sm'} src="https://i.pinimg.com/736x/d6/cd/0f/d6cd0ffd4634b0763d3958a7325ce26e.jpg" />
|
||||||
|
<VStack gap={0} alignItems={'flex-start'}>
|
||||||
|
<Text fontSize={'sm'} fontWeight={'bold'}>Ritesh Pandey</Text>
|
||||||
|
<Text fontSize={'xs'} >ritesh.pandey@wdimails.com</Text>
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
{children}
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DefaultLayout;
|
||||||
23
src/Layouts/animations.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
export const OPACITY_ON_LOAD = {
|
||||||
|
as: motion.div,
|
||||||
|
initial: { opacity: 0 },
|
||||||
|
animate: { opacity: 1 },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SLIDE_IN_BOTTOM = {
|
||||||
|
as: motion.div,
|
||||||
|
initial: { opacity: 0, y: 50 },
|
||||||
|
animate: { opacity: 1, y: 0 },
|
||||||
|
transition: { duration: 1, ease: "easeInOut" }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const FADE_IN_SCALE_UP = {
|
||||||
|
as: motion.div,
|
||||||
|
initial: { opacity: 0, scale: 0.9 },
|
||||||
|
animate: { opacity: 1, scale: 1 },
|
||||||
|
transition: { duration: 0.5, ease: "easeInOut" }
|
||||||
|
};
|
||||||
|
|
||||||
6
src/Pages/CMS/CMS.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const CMS = () => {
|
||||||
|
return (
|
||||||
|
<div>CMS</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default CMS
|
||||||
8
src/Pages/Dashboard/Dashboard.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
const Dashboard = () => {
|
||||||
|
return (
|
||||||
|
<div>Dashboard</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dashboard
|
||||||
93
src/Pages/Login.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { Center, HStack, Image, Input, Text, VStack } from "@chakra-ui/react"
|
||||||
|
import logo from '../assets/logo.svg'
|
||||||
|
import bgImage from '../assets/bgImage.png'
|
||||||
|
import { Field } from "../components/ui/field"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { InputGroup } from "../components/ui/input-group"
|
||||||
|
import { MdEmail } from "react-icons/md"
|
||||||
|
import { BiLock } from "react-icons/bi"
|
||||||
|
import { PasswordInput } from "../components/ui/password-input"
|
||||||
|
import GlobalStateContext from "../Contexts/GlobalStateContext"
|
||||||
|
import { useContext, useState } from "react"
|
||||||
|
import { Button } from "../components/ui/button"
|
||||||
|
import { Toaster, toaster } from "../components/ui/toaster"
|
||||||
|
|
||||||
|
interface FormValues {
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Login = () => {
|
||||||
|
|
||||||
|
const [ isLoading, setIsLoading ] = useState<boolean>(false)
|
||||||
|
const context = useContext(GlobalStateContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('App must be used within a GlobalStateProvider');
|
||||||
|
}
|
||||||
|
const { setIsAuthenticate } = context;
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<FormValues>()
|
||||||
|
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit((data) => {
|
||||||
|
setIsLoading(true)
|
||||||
|
if (data?.email === 'Admin' && data?.password === 'Admin') {
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsAuthenticate(true);
|
||||||
|
setIsLoading(false)
|
||||||
|
}, 3000); // 3-second delay
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toaster.create({
|
||||||
|
title: `Invalid Credentials`,
|
||||||
|
type: "error",
|
||||||
|
})
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack bg={'#222935'}
|
||||||
|
backgroundImage={`url(${bgImage})`}
|
||||||
|
backgroundPosition="center"
|
||||||
|
backgroundRepeat="repeat"
|
||||||
|
backgroundSize="cover"
|
||||||
|
w={'100%'} h={'100vh'}>
|
||||||
|
<Center display={{base:'none', md:'flex'}} bg={'#222935'} w={'55%'} h={'100%'}>
|
||||||
|
<Image w={150} src={logo} />
|
||||||
|
</Center>
|
||||||
|
<Center as={'form'} onSubmit={onSubmit} p={{base:4,md:16}} w={{base:'100%',md:'45%'}} h={'100%'}>
|
||||||
|
<VStack gap={2} w={'100%'} alignItems={'flex-start'}>
|
||||||
|
<Text w={'100%'} textAlign={'center'} fontSize={'24px'} fontWeight={'bold'}>Hello Again!</Text>
|
||||||
|
<Text w={'100%'} textAlign={'center'} fontSize={'sm'}>Welcome Back</Text>
|
||||||
|
|
||||||
|
<VStack mt={6} gap={4} w={'100%'}>
|
||||||
|
<Field invalid={!!errors.email} errorText={errors.email?.message} bgSize={'sm'} >
|
||||||
|
<InputGroup w={'100%'} flex="1" startElement={<MdEmail color="#fff" />}>
|
||||||
|
<Input fontSize={'sm'} {...register("email", { required: "Email address is required" })} placeholder="Email Address" focusRingColor={'#D90B2E'} variant="subtle" rounded={'full'} bg={'#434A53'} />
|
||||||
|
</InputGroup>
|
||||||
|
</Field>
|
||||||
|
<Field invalid={!!errors.password} errorText={errors.password?.message} >
|
||||||
|
<InputGroup w={'100%'} flex="1" startElement={<BiLock color="#fff" />}>
|
||||||
|
<PasswordInput fontSize={'sm'} {...register("password", { required: "Password address is required" })} placeholder="Password" focusRingColor={'#D90B2E'} variant="subtle" rounded={'full'} bg={'#434A53'} />
|
||||||
|
|
||||||
|
</InputGroup>
|
||||||
|
</Field>
|
||||||
|
<Button loading={isLoading} mt={4} size={'sm'} bg={'#D90B2E'} rounded={'full'} w={'100%'} color={'#ffffff'} type="submit">Send OTP</Button>
|
||||||
|
|
||||||
|
<Text>Forgot password</Text>
|
||||||
|
</VStack>
|
||||||
|
|
||||||
|
|
||||||
|
</VStack>
|
||||||
|
</Center>
|
||||||
|
<Toaster />
|
||||||
|
</HStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login
|
||||||
10
src/Pages/ManageCommunity/ManageCommunity.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import MainFrame from "../../components/MainFrame"
|
||||||
|
|
||||||
|
const ManageCommunity = () => {
|
||||||
|
return (
|
||||||
|
<MainFrame title="Manage Community">
|
||||||
|
|
||||||
|
</MainFrame>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ManageCommunity
|
||||||
11
src/Pages/ManageCommunity/ManagePost.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import MainFrame from "../../components/MainFrame"
|
||||||
|
|
||||||
|
const ManagePost = () => {
|
||||||
|
return (
|
||||||
|
<MainFrame title="Manage Post">
|
||||||
|
|
||||||
|
</MainFrame>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ManagePost
|
||||||
11
src/Pages/ManageGroups/ManageGroups.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import MainFrame from "../../components/MainFrame"
|
||||||
|
|
||||||
|
const ManageGroups = () => {
|
||||||
|
return (
|
||||||
|
<MainFrame title="Manage Group">
|
||||||
|
|
||||||
|
</MainFrame>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ManageGroups
|
||||||
11
src/Pages/ManageUsers/ManageUsers.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import MainFrame from "../../components/MainFrame"
|
||||||
|
|
||||||
|
const ManageUsers = () => {
|
||||||
|
return (
|
||||||
|
<MainFrame title="Manage User">
|
||||||
|
|
||||||
|
</MainFrame>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ManageUsers
|
||||||
8
src/Pages/NotFound.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
const NotFound = () => {
|
||||||
|
return (
|
||||||
|
<div>NotFound</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotFound
|
||||||
6
src/Pages/Notification/Notification.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const Notification = () => {
|
||||||
|
return (
|
||||||
|
<div>Notification</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Notification
|
||||||
11
src/Pages/Profile/Profile.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import MainFrame from "../../components/MainFrame"
|
||||||
|
|
||||||
|
const Profile = () => {
|
||||||
|
return (
|
||||||
|
<MainFrame title="Pofile">
|
||||||
|
|
||||||
|
</MainFrame>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Profile
|
||||||
11
src/Pages/Reporting/Reporting.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import MainFrame from "../../components/MainFrame"
|
||||||
|
|
||||||
|
const Reporting = () => {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<MainFrame title="Manage Report">
|
||||||
|
|
||||||
|
</MainFrame>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Reporting
|
||||||
11
src/Pages/SubAdmin/SubAdmin.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import MainFrame from "../../components/MainFrame"
|
||||||
|
|
||||||
|
const SubAdmin = () => {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<MainFrame title="Manage Subadmin">
|
||||||
|
|
||||||
|
</MainFrame>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default SubAdmin
|
||||||
11
src/Pages/Support/Support.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import MainFrame from "../../components/MainFrame"
|
||||||
|
|
||||||
|
const Support = () => {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<MainFrame title="Manage Support">
|
||||||
|
|
||||||
|
</MainFrame>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Support
|
||||||
75
src/Routes/Nav.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { LiaUsersSolid } from "react-icons/lia";
|
||||||
|
import { LuBellDot } from "react-icons/lu";
|
||||||
|
import { MdOutlineSupportAgent, MdPostAdd } from "react-icons/md";
|
||||||
|
import { RiUserSettingsLine } from "react-icons/ri";
|
||||||
|
import { TbFileSettings, TbLayoutDashboard, TbReport, TbUsers, TbUsersGroup } from "react-icons/tb";
|
||||||
|
|
||||||
|
export const nav = [
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "Dashboard",
|
||||||
|
path: "/",
|
||||||
|
Icon: TbLayoutDashboard,
|
||||||
|
type:'single'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Manage Users",
|
||||||
|
path: "/manage-user",
|
||||||
|
Icon: TbUsers,
|
||||||
|
type:'single'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Manage Groups",
|
||||||
|
path: "/manage-groups",
|
||||||
|
Icon: TbUsersGroup,
|
||||||
|
type:'single'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Community",
|
||||||
|
path: "/manage-community",
|
||||||
|
Icon: LiaUsersSolid,
|
||||||
|
type:'multiple',
|
||||||
|
children: [
|
||||||
|
// {
|
||||||
|
// title: "Manage Community",
|
||||||
|
// path: "/manage-community",
|
||||||
|
// Icon: RiUserCommunityLine,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: "Manage Post",
|
||||||
|
path: "/manage-post",
|
||||||
|
Icon: MdPostAdd,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Support",
|
||||||
|
path: "/support",
|
||||||
|
Icon: MdOutlineSupportAgent,
|
||||||
|
type:'single'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Sub-Admin",
|
||||||
|
path: "/sub-admin",
|
||||||
|
Icon: RiUserSettingsLine,
|
||||||
|
type:'single'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Reporting",
|
||||||
|
path: "/reporting",
|
||||||
|
Icon: TbReport,
|
||||||
|
type:'single'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "CMS",
|
||||||
|
path: "/cms",
|
||||||
|
Icon: TbFileSettings,
|
||||||
|
type:'single'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Manage Notifications",
|
||||||
|
path: "/manage-notification",
|
||||||
|
Icon: LuBellDot,
|
||||||
|
type:'single'
|
||||||
|
}
|
||||||
|
];
|
||||||
24
src/Routes/Routes.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
import CMS from "../Pages/CMS/CMS";
|
||||||
|
import Dashboard from "../Pages/Dashboard/Dashboard";
|
||||||
|
import ManageCommunity from "../Pages/ManageCommunity/ManageCommunity";
|
||||||
|
import ManagePost from "../Pages/ManageCommunity/ManagePost";
|
||||||
|
import ManageGroups from "../Pages/ManageGroups/ManageGroups";
|
||||||
|
import ManageUsers from "../Pages/ManageUsers/ManageUsers";
|
||||||
|
import Profile from "../Pages/Profile/Profile";
|
||||||
|
import Reporting from "../Pages/Reporting/Reporting";
|
||||||
|
import SubAdmin from "../Pages/SubAdmin/SubAdmin";
|
||||||
|
import Support from "../Pages/Support/Support";
|
||||||
|
export const RouteLink = [
|
||||||
|
{ path: "/", Component: Dashboard },
|
||||||
|
{ path: "/manage-user", Component: ManageUsers },
|
||||||
|
{ path: "/manage-groups", Component: ManageGroups },
|
||||||
|
{ path: "/manage-community", Component: ManageCommunity},
|
||||||
|
{ path: "/manage-post", Component: ManagePost},
|
||||||
|
{ path: "/support", Component: Support},
|
||||||
|
{ path: "/sub-admin", Component: SubAdmin},
|
||||||
|
{ path: "/reporting", Component: Reporting},
|
||||||
|
{ path: "/cms", Component: CMS},
|
||||||
|
{ path: "/manage-notification", Component: CMS},
|
||||||
|
{ path: "/profile", Component: Profile},
|
||||||
|
]
|
||||||
BIN
src/assets/b2fb21f206c56acc2007ed7e587d9770.jpg
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/bgImage.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
src/assets/favIcon.png
Normal file
|
After Width: | Height: | Size: 850 B |
11
src/assets/logo.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<svg width="253" height="167" viewBox="0 0 253 167" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M167.181 42.4477H159.188H137.084C137.083 45.3569 136.481 48.0573 135.237 50.4247H158.33C154.826 65.8862 140.985 77.4928 124.479 77.5324C124.45 77.5324 124.421 77.5324 124.393 77.5324C115.154 77.5324 106.468 73.9464 99.9309 67.4294C93.3498 60.8702 89.7216 52.1222 89.712 42.7957C89.7107 41.9292 89.7461 41.0682 89.8089 40.2126H81.7855C81.7418 40.985 81.7132 41.76 81.7118 42.5378C81.6818 54.0094 86.0933 64.785 94.1344 72.878C102.177 80.9723 112.914 85.4425 124.368 85.4657C124.397 85.4657 124.424 85.4657 124.453 85.4657C145.419 85.4657 162.919 70.3222 166.509 50.4234C166.944 48.0109 167.187 45.5329 167.195 42.999C167.195 42.8148 167.184 42.6319 167.181 42.4477Z" fill="#D90B2E"/>
|
||||||
|
<path d="M124.481 69.5375C125.387 69.5348 126.283 69.4925 127.173 69.4215C123.824 64.8067 120.657 60.4389 117.418 55.9741C126.226 53.0118 129.917 46.2587 129.244 38.1521C128.521 29.4328 123.018 23.695 114.177 23.5107C111.912 23.4644 109.647 23.4412 107.382 23.4261H95.5595C96.8094 21.5622 98.2476 19.8047 99.8673 18.1796C106.422 11.6053 115.145 7.9811 124.426 7.97564C124.434 7.97564 124.44 7.97564 124.448 7.97564C133.728 7.97564 142.453 11.5875 149.013 18.1454C151.667 20.7981 153.829 23.8096 155.476 27.0653H164.162C157.881 11.2996 142.513 0.0846005 124.549 0C124.482 0 124.415 0 124.348 0C113.102 0 102.467 4.41287 94.3805 12.4404C88.8638 17.9176 85.0254 24.5955 83.1123 31.8575H91.4564C91.4577 31.8548 91.4577 31.8521 91.4591 31.8494C94.6343 31.848 97.8082 31.8562 100.983 31.8575H105.024V31.8603C106.809 31.863 108.592 31.8589 110.377 31.8644C111.699 31.8685 113.032 31.9762 114.339 32.1768C117.232 32.623 119.298 34.4624 119.701 37.0646C119.919 38.4809 119.989 39.9614 119.845 41.3833C119.44 45.3991 117.517 47.585 113.504 48.0708C109.988 48.4965 106.41 48.431 102.86 48.573C102.239 48.5975 101.617 48.577 100.661 48.577L114.975 68.2371C118.003 69.0858 121.163 69.5348 124.395 69.5348C124.422 69.5375 124.451 69.5375 124.481 69.5375Z" fill="#D90B2E"/>
|
||||||
|
<path d="M0 156.088V116.713H17.0443C20.5688 116.713 23.6063 117.285 26.1566 118.428C28.7069 119.573 30.6745 121.213 32.0622 123.35C33.4486 125.487 34.1431 128.018 34.1431 130.944C34.1431 133.906 33.4486 136.447 32.0622 138.566C30.6745 140.685 28.7055 142.298 26.1566 143.403C23.6063 144.51 20.5688 145.062 17.0443 145.062H5.00644L9.1696 141.124V156.086H0V156.088ZM9.16824 142.137L5.00507 137.806H16.5367C19.3107 137.806 21.4107 137.207 22.8367 136.006C24.2612 134.806 24.9749 133.119 24.9749 130.944C24.9749 128.768 24.2626 127.09 22.8367 125.908C21.4107 124.727 19.3107 124.136 16.5367 124.136H5.00507L9.16824 119.805V142.137ZM25.0868 156.088L15.1872 141.8H24.9749L34.8745 156.088H25.0868Z" fill="#D90B2E"/>
|
||||||
|
<path d="M55.7437 156.538C52.2929 156.538 49.2745 155.862 46.6874 154.513C44.1003 153.163 42.093 151.316 40.6685 148.973C39.2426 146.63 38.5303 143.958 38.5303 140.958C38.5303 137.958 39.2139 135.276 40.5839 132.914C41.9525 130.552 43.8642 128.704 46.3217 127.374C48.7778 126.043 51.5437 125.377 54.618 125.377C57.5804 125.377 60.2521 126.016 62.6332 127.291C65.0143 128.566 66.8987 130.366 68.2864 132.69C69.6728 135.015 70.3673 137.808 70.3673 141.071C70.3673 141.408 70.3482 141.793 70.3114 142.224C70.2732 142.655 70.2364 143.059 70.1995 143.433H45.6176V138.315H65.5874L62.1556 139.833C62.1925 138.259 61.8923 136.89 61.2551 135.727C60.6165 134.565 59.7363 133.656 58.6106 133C57.4862 132.343 56.1722 132.016 54.6726 132.016C53.1716 132.016 51.8508 132.345 50.7073 133C49.5624 133.656 48.6728 134.574 48.0355 135.756C47.397 136.938 47.079 138.335 47.079 139.946V141.296C47.079 142.984 47.4529 144.447 48.2034 145.683C48.9525 146.921 49.9841 147.868 51.2968 148.524C52.6094 149.18 54.1841 149.508 56.0221 149.508C57.5968 149.508 58.994 149.265 60.2126 148.776C61.4311 148.289 62.5473 147.558 63.5597 146.582L68.2291 151.645C66.8414 153.219 65.0976 154.43 62.9976 155.273C60.8989 156.115 58.4796 156.538 55.7437 156.538Z" fill="#D90B2E"/>
|
||||||
|
<path d="M95.8495 156.762C92.737 156.762 89.8674 156.264 87.2434 155.272C84.6181 154.278 82.3502 152.862 80.4372 151.025C78.5241 149.189 77.034 147.031 75.9656 144.556C74.8972 142.081 74.3623 139.363 74.3623 136.4C74.3623 133.438 74.8972 130.718 75.9656 128.244C77.034 125.769 78.5432 123.613 80.4945 121.775C82.4444 119.939 84.7313 118.522 87.3567 117.529C89.9807 116.535 92.8693 116.039 96.0187 116.039C99.5064 116.039 102.646 116.62 105.441 117.783C108.234 118.945 110.588 120.633 112.501 122.845L106.707 128.246C105.244 126.708 103.67 125.574 101.982 124.843C100.294 124.111 98.438 123.746 96.413 123.746C94.5368 123.746 92.8134 124.055 91.2374 124.674C89.6627 125.292 88.3023 126.164 87.1588 127.289C86.014 128.414 85.1339 129.745 84.5144 131.283C83.8962 132.821 83.5865 134.527 83.5865 136.402C83.5865 138.201 83.8962 139.88 84.5144 141.437C85.1325 142.994 86.014 144.334 87.1588 145.458C88.3023 146.582 89.6518 147.465 91.2087 148.102C92.7643 148.741 94.4795 149.059 96.3557 149.059C98.1555 149.059 99.9089 148.759 101.615 148.158C103.32 147.559 104.979 146.565 106.592 145.177L111.768 151.758C109.593 153.409 107.099 154.655 104.286 155.498C101.474 156.341 98.6618 156.762 95.8495 156.762ZM103.443 150.575V135.781H111.768V151.755L103.443 150.575Z" fill="#D90B2E"/>
|
||||||
|
<path d="M119.587 156.088V125.825H127.968V134.431L126.786 131.9C127.686 129.762 129.129 128.141 131.117 127.034C133.104 125.929 135.523 125.375 138.373 125.375V133.531C138.036 133.456 137.708 133.4 137.39 133.362C137.07 133.325 136.761 133.306 136.462 133.306C133.986 133.306 132.017 134 130.555 135.387C129.092 136.774 128.361 138.912 128.361 141.8V156.088H119.587Z" fill="#D90B2E"/>
|
||||||
|
<path d="M158.174 156.538C154.987 156.538 152.146 155.862 149.653 154.513C147.159 153.163 145.199 151.316 143.775 148.973C142.349 146.63 141.636 143.958 141.636 140.958C141.636 137.92 142.349 135.229 143.775 132.885C145.199 130.542 147.159 128.704 149.653 127.373C152.146 126.042 154.987 125.376 158.174 125.376C161.399 125.376 164.268 126.042 166.781 127.373C169.293 128.704 171.262 130.542 172.688 132.885C174.112 135.229 174.826 137.92 174.826 140.958C174.826 143.995 174.113 146.676 172.688 149.002C171.262 151.327 169.293 153.163 166.781 154.514C164.268 155.862 161.399 156.538 158.174 156.538ZM158.174 149.337C159.674 149.337 160.996 149.01 162.14 148.353C163.283 147.698 164.202 146.732 164.896 145.457C165.589 144.182 165.937 142.682 165.937 140.956C165.937 139.193 165.591 137.694 164.896 136.456C164.202 135.219 163.283 134.272 162.14 133.615C160.995 132.96 159.693 132.631 158.23 132.631C156.768 132.631 155.455 132.96 154.292 133.615C153.13 134.272 152.202 135.219 151.507 136.456C150.813 137.694 150.466 139.193 150.466 140.956C150.466 142.681 150.813 144.182 151.507 145.457C152.201 146.732 153.128 147.697 154.292 148.353C155.455 149.01 156.749 149.337 158.174 149.337Z" fill="#D90B2E"/>
|
||||||
|
<path d="M193.443 156.538C190.893 156.538 188.642 156.051 186.693 155.075C184.743 154.101 183.224 152.6 182.136 150.575C181.049 148.55 180.504 146 180.504 142.926V125.825H189.28V141.576C189.28 144.126 189.815 145.992 190.883 147.173C191.951 148.355 193.461 148.946 195.412 148.946C196.761 148.946 197.962 148.646 199.011 148.045C200.061 147.446 200.896 146.546 201.514 145.345C202.132 144.146 202.442 142.645 202.442 140.845V125.825H211.161V156.088H202.836V147.707L204.355 150.182C203.304 152.282 201.805 153.866 199.855 154.935C197.905 156.003 195.768 156.538 193.443 156.538Z" fill="#D90B2E"/>
|
||||||
|
<path d="M219.319 167V125.824H227.7V132.012L227.53 141.012L228.092 149.956V167H219.319ZM237.15 156.538C234.637 156.538 232.425 155.976 230.513 154.85C228.6 153.726 227.11 152.009 226.042 149.703C224.973 147.397 224.438 144.481 224.438 140.957C224.438 137.394 224.945 134.47 225.957 132.181C226.969 129.894 228.44 128.187 230.373 127.063C232.303 125.939 234.563 125.375 237.15 125.375C240.038 125.375 242.615 126.022 244.884 127.315C247.152 128.609 248.953 130.418 250.285 132.743C251.616 135.069 252.281 137.806 252.281 140.957C252.281 144.144 251.616 146.891 250.285 149.197C248.953 151.503 247.154 153.303 244.884 154.598C242.615 155.891 240.036 156.538 237.15 156.538ZM235.687 149.337C237.15 149.337 238.452 149 239.597 148.325C240.74 147.649 241.659 146.685 242.353 145.428C243.046 144.171 243.394 142.681 243.394 140.957C243.394 139.194 243.048 137.694 242.353 136.456C241.659 135.219 240.74 134.272 239.597 133.615C238.452 132.96 237.15 132.632 235.687 132.632C234.225 132.632 232.912 132.96 231.749 133.615C230.587 134.272 229.659 135.219 228.964 136.456C228.27 137.694 227.923 139.194 227.923 140.957C227.923 142.681 228.27 144.173 228.964 145.428C229.658 146.685 230.585 147.649 231.749 148.325C232.912 149 234.225 149.337 235.687 149.337Z" fill="#D90B2E"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.5 KiB |
1
src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
4
src/assets/redogo.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="45" height="45" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M44.5262 22.1134H40.362H28.8468C28.8461 23.6289 28.5326 25.0357 27.8843 26.2691H39.9149C38.0894 34.3238 30.8791 40.3704 22.2799 40.391C22.2649 40.391 22.25 40.391 22.2351 40.391C17.4218 40.391 12.8972 38.5229 9.49148 35.1278C6.063 31.7107 4.17283 27.1534 4.16785 22.2946C4.16714 21.8432 4.18562 21.3947 4.21832 20.949H0.0384642C0.0157166 21.3513 0.000790152 21.7551 7.9292e-05 22.1603C-0.0155596 28.1365 2.28265 33.7501 6.47175 37.9662C10.6616 42.1831 16.2553 44.5118 22.2223 44.5239C22.2372 44.5239 22.2514 44.5239 22.2663 44.5239C33.1887 44.5239 42.3055 36.6348 44.1758 26.2683C44.4025 25.0115 44.529 23.7206 44.5333 22.4006C44.5333 22.3046 44.5276 22.2093 44.5262 22.1134Z" fill="#D90B2E"/>
|
||||||
|
<path d="M22.2806 36.2261C22.7526 36.2247 23.2197 36.2027 23.6832 36.1657C21.9387 33.7616 20.2888 31.4861 18.6012 29.1602C23.1898 27.6169 25.1127 24.0989 24.7622 19.8756C24.3855 15.3332 21.5186 12.3441 16.9129 12.2481C15.7329 12.2239 14.5529 12.2119 13.3728 12.204H7.21395C7.8651 11.233 8.61435 10.3174 9.45814 9.47079C12.8731 6.04586 17.4169 4.15782 22.2522 4.15497C22.2565 4.15497 22.2593 4.15497 22.2636 4.15497C27.0981 4.15497 31.6434 6.03662 35.0612 9.45302C36.4438 10.8349 37.5698 12.4038 38.4278 14.0999H42.9531C39.6811 5.88663 31.6746 0.0440733 22.3162 0C22.2813 0 22.2465 0 22.2117 0C16.3528 0 10.8123 2.29892 6.59977 6.48091C3.72576 9.3343 1.72612 12.8132 0.729492 16.5964H5.0764C5.07711 16.595 5.07711 16.5936 5.07782 16.5922C6.73199 16.5915 8.38545 16.5957 10.0396 16.5964H12.1445V16.5979C13.0743 16.5993 14.0034 16.5972 14.9332 16.6C15.622 16.6021 16.3165 16.6583 16.9975 16.7628C18.5045 16.9952 19.5808 17.9535 19.7905 19.3091C19.9042 20.047 19.9405 20.8182 19.8658 21.559C19.6547 23.651 18.6531 24.7898 16.5625 25.0429C14.7306 25.2647 12.8667 25.2306 11.0171 25.3045C10.6936 25.3173 10.3695 25.3066 9.87186 25.3066L17.3288 35.5487C18.9062 35.9908 20.5525 36.2247 22.2358 36.2247C22.2501 36.2261 22.265 36.2261 22.2806 36.2261Z" fill="#D90B2E"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
35
src/components/MainFrame.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Box, Text, VStack } from "@chakra-ui/react"
|
||||||
|
import { motion } from "framer-motion"
|
||||||
|
import React, { FC } from "react"
|
||||||
|
import { OPACITY_ON_LOAD } from "../Layouts/animations"
|
||||||
|
|
||||||
|
// ✅ Wrap Chakra components with Framer Motion
|
||||||
|
const MotionVStack = motion(VStack)
|
||||||
|
|
||||||
|
interface MainFrameProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const MainFrame: FC<MainFrameProps> = ({ children, title }) => {
|
||||||
|
return (
|
||||||
|
<MotionVStack {...OPACITY_ON_LOAD} w="100%" h="94%" p={4} pt={0} pb={0} >
|
||||||
|
<Text color={'#fff'} w="100%" fontSize="sm" display="flex" alignItems="center" h="3%">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Box
|
||||||
|
w="100%"
|
||||||
|
h="97%"
|
||||||
|
border="1px solid #ffffff30"
|
||||||
|
bg="#434A5330"
|
||||||
|
rounded="md"
|
||||||
|
backdropFilter="blur(10px)"
|
||||||
|
shadow={'xs'}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</MotionVStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MainFrame
|
||||||
47
src/components/ui/accordion.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Accordion, HStack } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
import { LuChevronDown } from "react-icons/lu"
|
||||||
|
|
||||||
|
interface AccordionItemTriggerProps extends Accordion.ItemTriggerProps {
|
||||||
|
indicatorPlacement?: "start" | "end"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AccordionItemTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
AccordionItemTriggerProps
|
||||||
|
>(function AccordionItemTrigger(props, ref) {
|
||||||
|
const { children, indicatorPlacement = "end", ...rest } = props
|
||||||
|
return (
|
||||||
|
<Accordion.ItemTrigger {...rest} ref={ref}>
|
||||||
|
{indicatorPlacement === "start" && (
|
||||||
|
<Accordion.ItemIndicator rotate={{ base: "-90deg", _open: "0deg" }}>
|
||||||
|
<LuChevronDown />
|
||||||
|
</Accordion.ItemIndicator>
|
||||||
|
)}
|
||||||
|
<HStack gap="4" flex="1" textAlign="start" width="full">
|
||||||
|
{children}
|
||||||
|
</HStack>
|
||||||
|
{indicatorPlacement === "end" && (
|
||||||
|
<Accordion.ItemIndicator>
|
||||||
|
<LuChevronDown />
|
||||||
|
</Accordion.ItemIndicator>
|
||||||
|
)}
|
||||||
|
</Accordion.ItemTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
interface AccordionItemContentProps extends Accordion.ItemContentProps {}
|
||||||
|
|
||||||
|
export const AccordionItemContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
AccordionItemContentProps
|
||||||
|
>(function AccordionItemContent(props, ref) {
|
||||||
|
return (
|
||||||
|
<Accordion.ItemContent>
|
||||||
|
<Accordion.ItemBody {...props} ref={ref} />
|
||||||
|
</Accordion.ItemContent>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const AccordionRoot = Accordion.Root
|
||||||
|
export const AccordionItem = Accordion.Item
|
||||||
74
src/components/ui/avatar.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type { GroupProps, SlotRecipeProps } from "@chakra-ui/react"
|
||||||
|
import { Avatar as ChakraAvatar, Group } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
type ImageProps = React.ImgHTMLAttributes<HTMLImageElement>
|
||||||
|
|
||||||
|
export interface AvatarProps extends ChakraAvatar.RootProps {
|
||||||
|
name?: string
|
||||||
|
src?: string
|
||||||
|
srcSet?: string
|
||||||
|
loading?: ImageProps["loading"]
|
||||||
|
icon?: React.ReactElement
|
||||||
|
fallback?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
|
||||||
|
function Avatar(props, ref) {
|
||||||
|
const { name, src, srcSet, loading, icon, fallback, children, ...rest } =
|
||||||
|
props
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.Root ref={ref} {...rest}>
|
||||||
|
<AvatarFallback name={name} icon={icon}>
|
||||||
|
{fallback}
|
||||||
|
</AvatarFallback>
|
||||||
|
<ChakraAvatar.Image src={src} srcSet={srcSet} loading={loading} />
|
||||||
|
{children}
|
||||||
|
</ChakraAvatar.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
interface AvatarFallbackProps extends ChakraAvatar.FallbackProps {
|
||||||
|
name?: string
|
||||||
|
icon?: React.ReactElement
|
||||||
|
}
|
||||||
|
|
||||||
|
const AvatarFallback = React.forwardRef<HTMLDivElement, AvatarFallbackProps>(
|
||||||
|
function AvatarFallback(props, ref) {
|
||||||
|
const { name, icon, children, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.Fallback ref={ref} {...rest}>
|
||||||
|
{children}
|
||||||
|
{name != null && children == null && <>{getInitials(name)}</>}
|
||||||
|
{name == null && children == null && (
|
||||||
|
<ChakraAvatar.Icon asChild={!!icon}>{icon}</ChakraAvatar.Icon>
|
||||||
|
)}
|
||||||
|
</ChakraAvatar.Fallback>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function getInitials(name: string) {
|
||||||
|
const names = name.trim().split(" ")
|
||||||
|
const firstName = names[0] != null ? names[0] : ""
|
||||||
|
const lastName = names.length > 1 ? names[names.length - 1] : ""
|
||||||
|
return firstName && lastName
|
||||||
|
? `${firstName.charAt(0)}${lastName.charAt(0)}`
|
||||||
|
: firstName.charAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AvatarGroupProps extends GroupProps, SlotRecipeProps<"avatar"> {}
|
||||||
|
|
||||||
|
export const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
|
||||||
|
function AvatarGroup(props, ref) {
|
||||||
|
const { size, variant, borderless, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraAvatar.PropsProvider value={{ size, variant, borderless }}>
|
||||||
|
<Group gap="0" spaceX="-3" ref={ref} {...rest} />
|
||||||
|
</ChakraAvatar.PropsProvider>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
40
src/components/ui/button.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type { ButtonProps as ChakraButtonProps } from "@chakra-ui/react"
|
||||||
|
import {
|
||||||
|
AbsoluteCenter,
|
||||||
|
Button as ChakraButton,
|
||||||
|
Span,
|
||||||
|
Spinner,
|
||||||
|
} from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
interface ButtonLoadingProps {
|
||||||
|
loading?: boolean
|
||||||
|
loadingText?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps {}
|
||||||
|
|
||||||
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
function Button(props, ref) {
|
||||||
|
const { loading, disabled, loadingText, children, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraButton disabled={loading || disabled} ref={ref} {...rest}>
|
||||||
|
{loading && !loadingText ? (
|
||||||
|
<>
|
||||||
|
<AbsoluteCenter display="inline-flex">
|
||||||
|
<Spinner size="inherit" color="inherit" />
|
||||||
|
</AbsoluteCenter>
|
||||||
|
<Span opacity={0}>{children}</Span>
|
||||||
|
</>
|
||||||
|
) : loading && loadingText ? (
|
||||||
|
<>
|
||||||
|
<Spinner size="inherit" color="inherit" />
|
||||||
|
{loadingText}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</ChakraButton>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
25
src/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Checkbox as ChakraCheckbox } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface CheckboxProps extends ChakraCheckbox.RootProps {
|
||||||
|
icon?: React.ReactNode
|
||||||
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
|
||||||
|
rootRef?: React.Ref<HTMLLabelElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||||
|
function Checkbox(props, ref) {
|
||||||
|
const { icon, children, inputProps, rootRef, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraCheckbox.Root ref={rootRef} {...rest}>
|
||||||
|
<ChakraCheckbox.HiddenInput ref={ref} {...inputProps} />
|
||||||
|
<ChakraCheckbox.Control>
|
||||||
|
{icon || <ChakraCheckbox.Indicator />}
|
||||||
|
</ChakraCheckbox.Control>
|
||||||
|
{children != null && (
|
||||||
|
<ChakraCheckbox.Label>{children}</ChakraCheckbox.Label>
|
||||||
|
)}
|
||||||
|
</ChakraCheckbox.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
17
src/components/ui/close-button.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { ButtonProps } from "@chakra-ui/react"
|
||||||
|
import { IconButton as ChakraIconButton } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
import { LuX } from "react-icons/lu"
|
||||||
|
|
||||||
|
export type CloseButtonProps = ButtonProps
|
||||||
|
|
||||||
|
export const CloseButton = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
CloseButtonProps
|
||||||
|
>(function CloseButton(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraIconButton variant="ghost" aria-label="Close" ref={ref} {...props}>
|
||||||
|
{props.children ?? <LuX />}
|
||||||
|
</ChakraIconButton>
|
||||||
|
)
|
||||||
|
})
|
||||||
67
src/components/ui/color-mode.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type { IconButtonProps } from "@chakra-ui/react"
|
||||||
|
import { ClientOnly, IconButton, Skeleton } from "@chakra-ui/react"
|
||||||
|
import { ThemeProvider, useTheme } from "next-themes"
|
||||||
|
import type { ThemeProviderProps } from "next-themes"
|
||||||
|
import * as React from "react"
|
||||||
|
import { LuMoon, LuSun } from "react-icons/lu"
|
||||||
|
|
||||||
|
export interface ColorModeProviderProps extends ThemeProviderProps {}
|
||||||
|
|
||||||
|
export function ColorModeProvider(props: ColorModeProviderProps) {
|
||||||
|
return (
|
||||||
|
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColorMode() {
|
||||||
|
const { resolvedTheme, setTheme } = useTheme()
|
||||||
|
const toggleColorMode = () => {
|
||||||
|
setTheme(resolvedTheme === "light" ? "dark" : "light")
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
colorMode: resolvedTheme,
|
||||||
|
setColorMode: setTheme,
|
||||||
|
toggleColorMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColorModeValue<T>(light: T, dark: T) {
|
||||||
|
const { colorMode } = useColorMode()
|
||||||
|
return colorMode === "light" ? light : dark
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ColorModeIcon() {
|
||||||
|
const { colorMode } = useColorMode()
|
||||||
|
return colorMode === "light" ? <LuSun /> : <LuMoon />
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ColorModeButtonProps extends Omit<IconButtonProps, "aria-label"> {}
|
||||||
|
|
||||||
|
export const ColorModeButton = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ColorModeButtonProps
|
||||||
|
>(function ColorModeButton(props, ref) {
|
||||||
|
const { toggleColorMode } = useColorMode()
|
||||||
|
return (
|
||||||
|
<ClientOnly fallback={<Skeleton boxSize="8" />}>
|
||||||
|
<IconButton
|
||||||
|
onClick={toggleColorMode}
|
||||||
|
variant="ghost"
|
||||||
|
aria-label="Toggle color mode"
|
||||||
|
size="sm"
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
css={{
|
||||||
|
_icon: {
|
||||||
|
width: "5",
|
||||||
|
height: "5",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ColorModeIcon />
|
||||||
|
</IconButton>
|
||||||
|
</ClientOnly>
|
||||||
|
)
|
||||||
|
})
|
||||||
62
src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react"
|
||||||
|
import { CloseButton } from "./close-button"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
interface DialogContentProps extends ChakraDialog.ContentProps {
|
||||||
|
portalled?: boolean
|
||||||
|
portalRef?: React.RefObject<HTMLElement>
|
||||||
|
backdrop?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DialogContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
DialogContentProps
|
||||||
|
>(function DialogContent(props, ref) {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
portalled = true,
|
||||||
|
portalRef,
|
||||||
|
backdrop = true,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
{backdrop && <ChakraDialog.Backdrop />}
|
||||||
|
<ChakraDialog.Positioner>
|
||||||
|
<ChakraDialog.Content ref={ref} {...rest} asChild={false}>
|
||||||
|
{children}
|
||||||
|
</ChakraDialog.Content>
|
||||||
|
</ChakraDialog.Positioner>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DialogCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraDialog.CloseTriggerProps
|
||||||
|
>(function DialogCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraDialog.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="2"
|
||||||
|
insetEnd="2"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" ref={ref}>
|
||||||
|
{props.children}
|
||||||
|
</CloseButton>
|
||||||
|
</ChakraDialog.CloseTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DialogRoot = ChakraDialog.Root
|
||||||
|
export const DialogFooter = ChakraDialog.Footer
|
||||||
|
export const DialogHeader = ChakraDialog.Header
|
||||||
|
export const DialogBody = ChakraDialog.Body
|
||||||
|
export const DialogBackdrop = ChakraDialog.Backdrop
|
||||||
|
export const DialogTitle = ChakraDialog.Title
|
||||||
|
export const DialogDescription = ChakraDialog.Description
|
||||||
|
export const DialogTrigger = ChakraDialog.Trigger
|
||||||
|
export const DialogActionTrigger = ChakraDialog.ActionTrigger
|
||||||
52
src/components/ui/drawer.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Drawer as ChakraDrawer, Portal } from "@chakra-ui/react"
|
||||||
|
import { CloseButton } from "./close-button"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
interface DrawerContentProps extends ChakraDrawer.ContentProps {
|
||||||
|
portalled?: boolean
|
||||||
|
portalRef?: React.RefObject<HTMLElement>
|
||||||
|
offset?: ChakraDrawer.ContentProps["padding"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DrawerContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
DrawerContentProps
|
||||||
|
>(function DrawerContent(props, ref) {
|
||||||
|
const { children, portalled = true, portalRef, offset, ...rest } = props
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraDrawer.Positioner padding={offset}>
|
||||||
|
<ChakraDrawer.Content ref={ref} {...rest} asChild={false}>
|
||||||
|
{children}
|
||||||
|
</ChakraDrawer.Content>
|
||||||
|
</ChakraDrawer.Positioner>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DrawerCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraDrawer.CloseTriggerProps
|
||||||
|
>(function DrawerCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraDrawer.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="2"
|
||||||
|
insetEnd="2"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" ref={ref} />
|
||||||
|
</ChakraDrawer.CloseTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DrawerTrigger = ChakraDrawer.Trigger
|
||||||
|
export const DrawerRoot = ChakraDrawer.Root
|
||||||
|
export const DrawerFooter = ChakraDrawer.Footer
|
||||||
|
export const DrawerHeader = ChakraDrawer.Header
|
||||||
|
export const DrawerBody = ChakraDrawer.Body
|
||||||
|
export const DrawerBackdrop = ChakraDrawer.Backdrop
|
||||||
|
export const DrawerDescription = ChakraDrawer.Description
|
||||||
|
export const DrawerTitle = ChakraDrawer.Title
|
||||||
|
export const DrawerActionTrigger = ChakraDrawer.ActionTrigger
|
||||||
33
src/components/ui/field.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Field as ChakraField } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface FieldProps extends Omit<ChakraField.RootProps, "label"> {
|
||||||
|
label?: React.ReactNode
|
||||||
|
helperText?: React.ReactNode
|
||||||
|
errorText?: React.ReactNode
|
||||||
|
optionalText?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
||||||
|
function Field(props, ref) {
|
||||||
|
const { label, children, helperText, errorText, optionalText, ...rest } =
|
||||||
|
props
|
||||||
|
return (
|
||||||
|
<ChakraField.Root ref={ref} {...rest}>
|
||||||
|
{label && (
|
||||||
|
<ChakraField.Label>
|
||||||
|
{label}
|
||||||
|
<ChakraField.RequiredIndicator fallback={optionalText} />
|
||||||
|
</ChakraField.Label>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
{helperText && (
|
||||||
|
<ChakraField.HelperText>{helperText}</ChakraField.HelperText>
|
||||||
|
)}
|
||||||
|
{errorText && (
|
||||||
|
<ChakraField.ErrorText>{errorText}</ChakraField.ErrorText>
|
||||||
|
)}
|
||||||
|
</ChakraField.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
53
src/components/ui/input-group.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import type { BoxProps, InputElementProps } from "@chakra-ui/react"
|
||||||
|
import { Group, InputElement } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface InputGroupProps extends BoxProps {
|
||||||
|
startElementProps?: InputElementProps
|
||||||
|
endElementProps?: InputElementProps
|
||||||
|
startElement?: React.ReactNode
|
||||||
|
endElement?: React.ReactNode
|
||||||
|
children: React.ReactElement<InputElementProps>
|
||||||
|
startOffset?: InputElementProps["paddingStart"]
|
||||||
|
endOffset?: InputElementProps["paddingEnd"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
|
||||||
|
function InputGroup(props, ref) {
|
||||||
|
const {
|
||||||
|
startElement,
|
||||||
|
startElementProps,
|
||||||
|
endElement,
|
||||||
|
endElementProps,
|
||||||
|
children,
|
||||||
|
startOffset = "6px",
|
||||||
|
endOffset = "6px",
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const child =
|
||||||
|
React.Children.only<React.ReactElement<InputElementProps>>(children)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group ref={ref} {...rest}>
|
||||||
|
{startElement && (
|
||||||
|
<InputElement pointerEvents="none" {...startElementProps}>
|
||||||
|
{startElement}
|
||||||
|
</InputElement>
|
||||||
|
)}
|
||||||
|
{React.cloneElement(child, {
|
||||||
|
...(startElement && {
|
||||||
|
ps: `calc(var(--input-height) - ${startOffset})`,
|
||||||
|
}),
|
||||||
|
...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }),
|
||||||
|
...children.props,
|
||||||
|
})}
|
||||||
|
{endElement && (
|
||||||
|
<InputElement placement="end" {...endElementProps}>
|
||||||
|
{endElement}
|
||||||
|
</InputElement>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
148
src/components/ui/password-input.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ButtonProps,
|
||||||
|
GroupProps,
|
||||||
|
InputProps,
|
||||||
|
StackProps,
|
||||||
|
} from "@chakra-ui/react"
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
HStack,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
Stack,
|
||||||
|
mergeRefs,
|
||||||
|
useControllableState,
|
||||||
|
} from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
import { LuEye, LuEyeOff } from "react-icons/lu"
|
||||||
|
import { InputGroup } from "./input-group"
|
||||||
|
|
||||||
|
export interface PasswordVisibilityProps {
|
||||||
|
defaultVisible?: boolean
|
||||||
|
visible?: boolean
|
||||||
|
onVisibleChange?: (visible: boolean) => void
|
||||||
|
visibilityIcon?: { on: React.ReactNode; off: React.ReactNode }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PasswordInputProps
|
||||||
|
extends InputProps,
|
||||||
|
PasswordVisibilityProps {
|
||||||
|
rootProps?: GroupProps
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PasswordInput = React.forwardRef<
|
||||||
|
HTMLInputElement,
|
||||||
|
PasswordInputProps
|
||||||
|
>(function PasswordInput(props, ref) {
|
||||||
|
const {
|
||||||
|
rootProps,
|
||||||
|
defaultVisible,
|
||||||
|
visible: visibleProp,
|
||||||
|
onVisibleChange,
|
||||||
|
visibilityIcon = { on: <LuEye />, off: <LuEyeOff /> },
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const [visible, setVisible] = useControllableState({
|
||||||
|
value: visibleProp,
|
||||||
|
defaultValue: defaultVisible || false,
|
||||||
|
onChange: onVisibleChange,
|
||||||
|
})
|
||||||
|
|
||||||
|
const inputRef = React.useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputGroup
|
||||||
|
width="full"
|
||||||
|
endElement={
|
||||||
|
<VisibilityTrigger
|
||||||
|
disabled={rest.disabled}
|
||||||
|
onPointerDown={(e) => {
|
||||||
|
if (rest.disabled) return
|
||||||
|
if (e.button !== 0) return
|
||||||
|
e.preventDefault()
|
||||||
|
setVisible(!visible)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{visible ? visibilityIcon.off : visibilityIcon.on}
|
||||||
|
</VisibilityTrigger>
|
||||||
|
}
|
||||||
|
{...rootProps}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...rest}
|
||||||
|
ref={mergeRefs(ref, inputRef)}
|
||||||
|
type={visible ? "text" : "password"}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const VisibilityTrigger = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
function VisibilityTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
tabIndex={-1}
|
||||||
|
ref={ref}
|
||||||
|
me="-2"
|
||||||
|
aspectRatio="square"
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
height="calc(100% - {spacing.2})"
|
||||||
|
aria-label="Toggle password visibility"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
interface PasswordStrengthMeterProps extends StackProps {
|
||||||
|
max?: number
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PasswordStrengthMeter = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
PasswordStrengthMeterProps
|
||||||
|
>(function PasswordStrengthMeter(props, ref) {
|
||||||
|
const { max = 4, value, ...rest } = props
|
||||||
|
|
||||||
|
const percent = (value / max) * 100
|
||||||
|
const { label, colorPalette } = getColorPalette(percent)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack align="flex-end" gap="1" ref={ref} {...rest}>
|
||||||
|
<HStack width="full" ref={ref} {...rest}>
|
||||||
|
{Array.from({ length: max }).map((_, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
height="1"
|
||||||
|
flex="1"
|
||||||
|
rounded="sm"
|
||||||
|
data-selected={index < value ? "" : undefined}
|
||||||
|
layerStyle="fill.subtle"
|
||||||
|
colorPalette="gray"
|
||||||
|
_selected={{
|
||||||
|
colorPalette,
|
||||||
|
layerStyle: "fill.solid",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
|
{label && <HStack textStyle="xs">{label}</HStack>}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
function getColorPalette(percent: number) {
|
||||||
|
switch (true) {
|
||||||
|
case percent < 33:
|
||||||
|
return { label: "Low", colorPalette: "red" }
|
||||||
|
case percent < 66:
|
||||||
|
return { label: "Medium", colorPalette: "orange" }
|
||||||
|
default:
|
||||||
|
return { label: "High", colorPalette: "green" }
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/components/ui/popover.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Popover as ChakraPopover, Portal } from "@chakra-ui/react"
|
||||||
|
import { CloseButton } from "./close-button"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
interface PopoverContentProps extends ChakraPopover.ContentProps {
|
||||||
|
portalled?: boolean
|
||||||
|
portalRef?: React.RefObject<HTMLElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PopoverContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
PopoverContentProps
|
||||||
|
>(function PopoverContent(props, ref) {
|
||||||
|
const { portalled = true, portalRef, ...rest } = props
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraPopover.Positioner>
|
||||||
|
<ChakraPopover.Content ref={ref} {...rest} />
|
||||||
|
</ChakraPopover.Positioner>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PopoverArrow = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ChakraPopover.ArrowProps
|
||||||
|
>(function PopoverArrow(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraPopover.Arrow {...props} ref={ref}>
|
||||||
|
<ChakraPopover.ArrowTip />
|
||||||
|
</ChakraPopover.Arrow>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PopoverCloseTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraPopover.CloseTriggerProps
|
||||||
|
>(function PopoverCloseTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraPopover.CloseTrigger
|
||||||
|
position="absolute"
|
||||||
|
top="1"
|
||||||
|
insetEnd="1"
|
||||||
|
{...props}
|
||||||
|
asChild
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<CloseButton size="sm" />
|
||||||
|
</ChakraPopover.CloseTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PopoverTitle = ChakraPopover.Title
|
||||||
|
export const PopoverDescription = ChakraPopover.Description
|
||||||
|
export const PopoverFooter = ChakraPopover.Footer
|
||||||
|
export const PopoverHeader = ChakraPopover.Header
|
||||||
|
export const PopoverRoot = ChakraPopover.Root
|
||||||
|
export const PopoverBody = ChakraPopover.Body
|
||||||
|
export const PopoverTrigger = ChakraPopover.Trigger
|
||||||
15
src/components/ui/provider.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { ChakraProvider, defaultSystem } from "@chakra-ui/react"
|
||||||
|
import {
|
||||||
|
ColorModeProvider,
|
||||||
|
type ColorModeProviderProps,
|
||||||
|
} from "./color-mode"
|
||||||
|
|
||||||
|
export function Provider(props: ColorModeProviderProps) {
|
||||||
|
return (
|
||||||
|
<ChakraProvider value={defaultSystem}>
|
||||||
|
<ColorModeProvider {...props} />
|
||||||
|
</ChakraProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
24
src/components/ui/radio.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface RadioProps extends ChakraRadioGroup.ItemProps {
|
||||||
|
rootRef?: React.Ref<HTMLDivElement>
|
||||||
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
|
||||||
|
function Radio(props, ref) {
|
||||||
|
const { children, inputProps, rootRef, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraRadioGroup.Item ref={rootRef} {...rest}>
|
||||||
|
<ChakraRadioGroup.ItemHiddenInput ref={ref} {...inputProps} />
|
||||||
|
<ChakraRadioGroup.ItemIndicator />
|
||||||
|
{children && (
|
||||||
|
<ChakraRadioGroup.ItemText>{children}</ChakraRadioGroup.ItemText>
|
||||||
|
)}
|
||||||
|
</ChakraRadioGroup.Item>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const RadioGroup = ChakraRadioGroup.Root
|
||||||
143
src/components/ui/select.tsx
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import type { CollectionItem } from "@chakra-ui/react"
|
||||||
|
import { Select as ChakraSelect, Portal } from "@chakra-ui/react"
|
||||||
|
import { CloseButton } from "./close-button"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
interface SelectTriggerProps extends ChakraSelect.ControlProps {
|
||||||
|
clearable?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SelectTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
SelectTriggerProps
|
||||||
|
>(function SelectTrigger(props, ref) {
|
||||||
|
const { children, clearable, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraSelect.Control {...rest}>
|
||||||
|
<ChakraSelect.Trigger ref={ref}>{children}</ChakraSelect.Trigger>
|
||||||
|
<ChakraSelect.IndicatorGroup>
|
||||||
|
{clearable && <SelectClearTrigger />}
|
||||||
|
<ChakraSelect.Indicator />
|
||||||
|
</ChakraSelect.IndicatorGroup>
|
||||||
|
</ChakraSelect.Control>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const SelectClearTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ChakraSelect.ClearTriggerProps
|
||||||
|
>(function SelectClearTrigger(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraSelect.ClearTrigger asChild {...props} ref={ref}>
|
||||||
|
<CloseButton
|
||||||
|
size="xs"
|
||||||
|
variant="plain"
|
||||||
|
focusVisibleRing="inside"
|
||||||
|
focusRingWidth="2px"
|
||||||
|
pointerEvents="auto"
|
||||||
|
/>
|
||||||
|
</ChakraSelect.ClearTrigger>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
interface SelectContentProps extends ChakraSelect.ContentProps {
|
||||||
|
portalled?: boolean
|
||||||
|
portalRef?: React.RefObject<HTMLElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SelectContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
SelectContentProps
|
||||||
|
>(function SelectContent(props, ref) {
|
||||||
|
const { portalled = true, portalRef, ...rest } = props
|
||||||
|
return (
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraSelect.Positioner>
|
||||||
|
<ChakraSelect.Content {...rest} ref={ref} />
|
||||||
|
</ChakraSelect.Positioner>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const SelectItem = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ChakraSelect.ItemProps
|
||||||
|
>(function SelectItem(props, ref) {
|
||||||
|
const { item, children, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraSelect.Item key={item.value} item={item} {...rest} ref={ref}>
|
||||||
|
{children}
|
||||||
|
<ChakraSelect.ItemIndicator />
|
||||||
|
</ChakraSelect.Item>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
interface SelectValueTextProps
|
||||||
|
extends Omit<ChakraSelect.ValueTextProps, "children"> {
|
||||||
|
children?(items: CollectionItem[]): React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SelectValueText = React.forwardRef<
|
||||||
|
HTMLSpanElement,
|
||||||
|
SelectValueTextProps
|
||||||
|
>(function SelectValueText(props, ref) {
|
||||||
|
const { children, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraSelect.ValueText {...rest} ref={ref}>
|
||||||
|
<ChakraSelect.Context>
|
||||||
|
{(select) => {
|
||||||
|
const items = select.selectedItems
|
||||||
|
if (items.length === 0) return props.placeholder
|
||||||
|
if (children) return children(items)
|
||||||
|
if (items.length === 1)
|
||||||
|
return select.collection.stringifyItem(items[0])
|
||||||
|
return `${items.length} selected`
|
||||||
|
}}
|
||||||
|
</ChakraSelect.Context>
|
||||||
|
</ChakraSelect.ValueText>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const SelectRoot = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ChakraSelect.RootProps
|
||||||
|
>(function SelectRoot(props, ref) {
|
||||||
|
return (
|
||||||
|
<ChakraSelect.Root
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
positioning={{ sameWidth: true, ...props.positioning }}
|
||||||
|
>
|
||||||
|
{props.asChild ? (
|
||||||
|
props.children
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ChakraSelect.HiddenSelect />
|
||||||
|
{props.children}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ChakraSelect.Root>
|
||||||
|
)
|
||||||
|
}) as ChakraSelect.RootComponent
|
||||||
|
|
||||||
|
interface SelectItemGroupProps extends ChakraSelect.ItemGroupProps {
|
||||||
|
label: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SelectItemGroup = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
SelectItemGroupProps
|
||||||
|
>(function SelectItemGroup(props, ref) {
|
||||||
|
const { children, label, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ChakraSelect.ItemGroup {...rest} ref={ref}>
|
||||||
|
<ChakraSelect.ItemGroupLabel>{label}</ChakraSelect.ItemGroupLabel>
|
||||||
|
{children}
|
||||||
|
</ChakraSelect.ItemGroup>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const SelectLabel = ChakraSelect.Label
|
||||||
|
export const SelectItemText = ChakraSelect.ItemText
|
||||||
82
src/components/ui/slider.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { Slider as ChakraSlider, For, HStack } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface SliderProps extends ChakraSlider.RootProps {
|
||||||
|
marks?: Array<number | { value: number; label: React.ReactNode }>
|
||||||
|
label?: React.ReactNode
|
||||||
|
showValue?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
||||||
|
function Slider(props, ref) {
|
||||||
|
const { marks: marksProp, label, showValue, ...rest } = props
|
||||||
|
const value = props.defaultValue ?? props.value
|
||||||
|
|
||||||
|
const marks = marksProp?.map((mark) => {
|
||||||
|
if (typeof mark === "number") return { value: mark, label: undefined }
|
||||||
|
return mark
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasMarkLabel = !!marks?.some((mark) => mark.label)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraSlider.Root ref={ref} thumbAlignment="center" {...rest}>
|
||||||
|
{label && !showValue && (
|
||||||
|
<ChakraSlider.Label>{label}</ChakraSlider.Label>
|
||||||
|
)}
|
||||||
|
{label && showValue && (
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<ChakraSlider.Label>{label}</ChakraSlider.Label>
|
||||||
|
<ChakraSlider.ValueText />
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
<ChakraSlider.Control data-has-mark-label={hasMarkLabel || undefined}>
|
||||||
|
<ChakraSlider.Track>
|
||||||
|
<ChakraSlider.Range />
|
||||||
|
</ChakraSlider.Track>
|
||||||
|
<SliderThumbs value={value} />
|
||||||
|
<SliderMarks marks={marks} />
|
||||||
|
</ChakraSlider.Control>
|
||||||
|
</ChakraSlider.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function SliderThumbs(props: { value?: number[] }) {
|
||||||
|
const { value } = props
|
||||||
|
return (
|
||||||
|
<For each={value}>
|
||||||
|
{(_, index) => (
|
||||||
|
<ChakraSlider.Thumb key={index} index={index}>
|
||||||
|
<ChakraSlider.HiddenInput />
|
||||||
|
</ChakraSlider.Thumb>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SliderMarksProps {
|
||||||
|
marks?: Array<number | { value: number; label: React.ReactNode }>
|
||||||
|
}
|
||||||
|
|
||||||
|
const SliderMarks = React.forwardRef<HTMLDivElement, SliderMarksProps>(
|
||||||
|
function SliderMarks(props, ref) {
|
||||||
|
const { marks } = props
|
||||||
|
if (!marks?.length) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraSlider.MarkerGroup ref={ref}>
|
||||||
|
{marks.map((mark, index) => {
|
||||||
|
const value = typeof mark === "number" ? mark : mark.value
|
||||||
|
const label = typeof mark === "number" ? undefined : mark.label
|
||||||
|
return (
|
||||||
|
<ChakraSlider.Marker key={index} value={value}>
|
||||||
|
<ChakraSlider.MarkerIndicator />
|
||||||
|
{label}
|
||||||
|
</ChakraSlider.Marker>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ChakraSlider.MarkerGroup>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
44
src/components/ui/toaster.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Toaster as ChakraToaster,
|
||||||
|
Portal,
|
||||||
|
Spinner,
|
||||||
|
Stack,
|
||||||
|
Toast,
|
||||||
|
createToaster,
|
||||||
|
} from "@chakra-ui/react"
|
||||||
|
|
||||||
|
export const toaster = createToaster({
|
||||||
|
placement:'bottom',
|
||||||
|
pauseOnPageIdle: true,
|
||||||
|
max:1
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Toaster = () => {
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<ChakraToaster toaster={toaster} insetInline={{ mdDown: "4" }}>
|
||||||
|
{(toast) => (
|
||||||
|
<Toast.Root width={{ md: "sm" }}>
|
||||||
|
{toast.type === "loading" ? (
|
||||||
|
<Spinner size="sm" color="blue.solid" />
|
||||||
|
) : (
|
||||||
|
<Toast.Indicator />
|
||||||
|
)}
|
||||||
|
<Stack rounded={'full'} gap="1" flex="1" maxWidth="100%">
|
||||||
|
{toast.title && <Toast.Title>{toast.title}</Toast.Title>}
|
||||||
|
{toast.description && (
|
||||||
|
<Toast.Description>{toast.description}</Toast.Description>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
{toast.action && (
|
||||||
|
<Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger>
|
||||||
|
)}
|
||||||
|
{toast.meta?.closable && <Toast.CloseTrigger />}
|
||||||
|
</Toast.Root>
|
||||||
|
)}
|
||||||
|
</ChakraToaster>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
46
src/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
export interface TooltipProps extends ChakraTooltip.RootProps {
|
||||||
|
showArrow?: boolean
|
||||||
|
portalled?: boolean
|
||||||
|
portalRef?: React.RefObject<HTMLElement>
|
||||||
|
content: React.ReactNode
|
||||||
|
contentProps?: ChakraTooltip.ContentProps
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
||||||
|
function Tooltip(props, ref) {
|
||||||
|
const {
|
||||||
|
showArrow,
|
||||||
|
children,
|
||||||
|
disabled,
|
||||||
|
portalled,
|
||||||
|
content,
|
||||||
|
contentProps,
|
||||||
|
portalRef,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
if (disabled) return children
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChakraTooltip.Root {...rest}>
|
||||||
|
<ChakraTooltip.Trigger asChild>{children}</ChakraTooltip.Trigger>
|
||||||
|
<Portal disabled={!portalled} container={portalRef}>
|
||||||
|
<ChakraTooltip.Positioner>
|
||||||
|
<ChakraTooltip.Content ref={ref} {...contentProps}>
|
||||||
|
{showArrow && (
|
||||||
|
<ChakraTooltip.Arrow>
|
||||||
|
<ChakraTooltip.ArrowTip />
|
||||||
|
</ChakraTooltip.Arrow>
|
||||||
|
)}
|
||||||
|
{content}
|
||||||
|
</ChakraTooltip.Content>
|
||||||
|
</ChakraTooltip.Positioner>
|
||||||
|
</Portal>
|
||||||
|
</ChakraTooltip.Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
105
src/index.css
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
*{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: #434A53;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #ffffff20 !important;
|
||||||
|
transition: all 0.5s;
|
||||||
|
border-radius: 8px;
|
||||||
|
/* background-color: #e2e8f01c; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.active:hover {
|
||||||
|
background-color: #434A53;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #ffffff20 !important;
|
||||||
|
transition: all 0.5s;
|
||||||
|
border-radius: 8px;
|
||||||
|
/* background-color: #e2e8f01c !important; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.link{
|
||||||
|
transition: all 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
background-color: #434A53;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #ffffff20 !important;
|
||||||
|
/* color: #fff; */
|
||||||
|
/* background-color: #e2e8f01c !important; */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Sphere.css */
|
||||||
|
|
||||||
|
/* ✅ Red Spheres */
|
||||||
|
.red-sphere-1,
|
||||||
|
.red-sphere-2,
|
||||||
|
.red-sphere-3{
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #D90B2E46;
|
||||||
|
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
filter: blur(100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ✅ Blue Spheres */
|
||||||
|
.blue-sphere-1,
|
||||||
|
.blue-sphere-2,
|
||||||
|
.blue-sphere-3{
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #009DAB46;
|
||||||
|
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
filter: blur(100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 🔧 Positioning for Spheres */
|
||||||
|
.red-sphere-1 {
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
top: 10%;
|
||||||
|
left: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue-sphere-1 {
|
||||||
|
width: 320px;
|
||||||
|
height: 320px;
|
||||||
|
top: 30%;
|
||||||
|
right: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-sphere-2 {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
bottom: 15%;
|
||||||
|
left: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue-sphere-2 {
|
||||||
|
width: 140px;
|
||||||
|
height: 140px;
|
||||||
|
bottom: 5%;
|
||||||
|
right: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-sphere-3 {
|
||||||
|
width: 480px;
|
||||||
|
height: 480px;
|
||||||
|
bottom: 55%;
|
||||||
|
left: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue-sphere-3{
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
bottom: 10%;
|
||||||
|
right: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
17
src/main.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import App from './App'
|
||||||
|
import { Provider } from './components/ui/provider'
|
||||||
|
import GlobalStateProvider from './Contexts/GlobalStateProvider'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
|
||||||
|
<GlobalStateProvider>
|
||||||
|
<Provider>
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
|
</GlobalStateProvider>
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
||||||
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
27
tsconfig.app.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"incremental": true,
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
// "noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
24
tsconfig.node.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
// "noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
51
vite.config.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// vite.config.js
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
host: "0.0.0.0",
|
||||||
|
port: 3001, // You can use any port
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
VitePWA({
|
||||||
|
registerType: "autoUpdate",
|
||||||
|
devOptions: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
manifest: {
|
||||||
|
name: "Re Group",
|
||||||
|
short_name: "RG",
|
||||||
|
description: "Join your community now",
|
||||||
|
start_url: "/",
|
||||||
|
display: "standalone",
|
||||||
|
theme_color: "#222935",
|
||||||
|
background_color: "#222935",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/icon-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/icon-256x256.png",
|
||||||
|
sizes: "256x256",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/icon-384x384.png",
|
||||||
|
sizes: "384x384",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/icon-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||