Compare commits
2 Commits
e4aafb998a
...
ce0ad656e7
| Author | SHA1 | Date | |
|---|---|---|---|
| ce0ad656e7 | |||
| cd020e3eb8 |
1279
client/package-lock.json
generated
1279
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,8 +15,10 @@
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"firebase": "^12.2.1",
|
||||
"framer-motion": "^12.23.12",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.19.0",
|
||||
|
||||
1193
client/src/App.jsx
1193
client/src/App.jsx
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,14 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
VStack,
|
||||
HStack,
|
||||
Input,
|
||||
Text,
|
||||
Heading,
|
||||
Flex
|
||||
} from '@chakra-ui/react';
|
||||
import { signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth';
|
||||
import { auth, googleProvider } from '../firebaseConfig';
|
||||
|
||||
@@ -36,100 +46,107 @@ const Login = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: '400px', margin: '100px auto', padding: '20px' }}>
|
||||
<h2>Login to DZTPS</h2>
|
||||
<Flex
|
||||
minH="100vh"
|
||||
align="center"
|
||||
justify="center"
|
||||
bg="gray.50"
|
||||
px={6}
|
||||
>
|
||||
<Box
|
||||
maxW="400px"
|
||||
w="full"
|
||||
bg="white"
|
||||
boxShadow="2xl"
|
||||
rounded="xl"
|
||||
p={8}
|
||||
borderWidth="1px"
|
||||
borderColor="gray.200"
|
||||
>
|
||||
<VStack gap={6}>
|
||||
<Heading
|
||||
size="lg"
|
||||
textAlign="center"
|
||||
color="gray.700"
|
||||
>
|
||||
Login to DZTPS
|
||||
</Heading>
|
||||
|
||||
{error && (
|
||||
<div style={{
|
||||
color: 'red',
|
||||
marginBottom: '10px',
|
||||
padding: '10px',
|
||||
border: '1px solid red',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#ffe6e6'
|
||||
}}>
|
||||
<Box
|
||||
p={3}
|
||||
bg="red.50"
|
||||
border="1px"
|
||||
borderColor="red.200"
|
||||
borderRadius="md"
|
||||
color="red.800"
|
||||
w="full"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleEmailLogin}>
|
||||
<div style={{ marginBottom: '15px' }}>
|
||||
<label htmlFor="email">Email:</label>
|
||||
<input
|
||||
<Box as="form" onSubmit={handleEmailLogin} w="full">
|
||||
<VStack gap={4}>
|
||||
<Box w="full">
|
||||
<Text mb={2} fontSize="sm" fontWeight="medium">Email</Text>
|
||||
<Input
|
||||
type="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
disabled={loading}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '8px',
|
||||
marginTop: '5px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px'
|
||||
}}
|
||||
placeholder="Enter your email"
|
||||
bg="white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div style={{ marginBottom: '15px' }}>
|
||||
<label htmlFor="password">Password:</label>
|
||||
<input
|
||||
<Box w="full">
|
||||
<Text mb={2} fontSize="sm" fontWeight="medium">Password</Text>
|
||||
<Input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={loading}
|
||||
placeholder="Enter your password"
|
||||
bg="white"
|
||||
required
|
||||
disabled={loading}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '8px',
|
||||
marginTop: '5px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
colorPalette="blue"
|
||||
size="lg"
|
||||
w="full"
|
||||
disabled={loading}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
backgroundColor: '#007bff',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
opacity: loading ? 0.6 : 1
|
||||
}}
|
||||
>
|
||||
{loading ? 'Signing in...' : 'Sign in with Email'}
|
||||
</button>
|
||||
</form>
|
||||
</Button>
|
||||
</VStack>
|
||||
</Box>
|
||||
|
||||
<div style={{ margin: '20px 0', textAlign: 'center' }}>
|
||||
<span>OR</span>
|
||||
</div>
|
||||
<HStack w="full">
|
||||
<Box flex="1" h="1px" bg="gray.200" />
|
||||
<Text fontSize="sm" px={3} color="gray.500">
|
||||
OR
|
||||
</Text>
|
||||
<Box flex="1" h="1px" bg="gray.200" />
|
||||
</HStack>
|
||||
|
||||
<button
|
||||
<Button
|
||||
onClick={handleGoogleLogin}
|
||||
colorPalette="red"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
w="full"
|
||||
disabled={loading}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
backgroundColor: '#db4437',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
opacity: loading ? 0.6 : 1
|
||||
}}
|
||||
>
|
||||
{loading ? 'Signing in...' : 'Sign in with Google'}
|
||||
</button>
|
||||
</div>
|
||||
</Button>
|
||||
</VStack>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
90
client/src/components/ui/color-mode.jsx
Normal file
90
client/src/components/ui/color-mode.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
'use client'
|
||||
|
||||
import { ClientOnly, IconButton, Skeleton, Span } from '@chakra-ui/react'
|
||||
import { ThemeProvider, useTheme } from 'next-themes'
|
||||
|
||||
import * as React from 'react'
|
||||
import { LuMoon, LuSun } from 'react-icons/lu'
|
||||
|
||||
export function ColorModeProvider(props) {
|
||||
return (
|
||||
<ThemeProvider attribute='class' disableTransitionOnChange {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export function useColorMode() {
|
||||
const { resolvedTheme, setTheme, forcedTheme } = useTheme()
|
||||
const colorMode = forcedTheme || resolvedTheme
|
||||
const toggleColorMode = () => {
|
||||
setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')
|
||||
}
|
||||
return {
|
||||
colorMode: colorMode,
|
||||
setColorMode: setTheme,
|
||||
toggleColorMode,
|
||||
}
|
||||
}
|
||||
|
||||
export function useColorModeValue(light, dark) {
|
||||
const { colorMode } = useColorMode()
|
||||
return colorMode === 'dark' ? dark : light
|
||||
}
|
||||
|
||||
export function ColorModeIcon() {
|
||||
const { colorMode } = useColorMode()
|
||||
return colorMode === 'dark' ? <LuMoon /> : <LuSun />
|
||||
}
|
||||
|
||||
export const ColorModeButton = React.forwardRef(
|
||||
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>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export const LightMode = React.forwardRef(function LightMode(props, ref) {
|
||||
return (
|
||||
<Span
|
||||
color='fg'
|
||||
display='contents'
|
||||
className='chakra-theme light'
|
||||
colorPalette='gray'
|
||||
colorScheme='light'
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export const DarkMode = React.forwardRef(function DarkMode(props, ref) {
|
||||
return (
|
||||
<Span
|
||||
color='fg'
|
||||
display='contents'
|
||||
className='chakra-theme dark'
|
||||
colorPalette='gray'
|
||||
colorScheme='dark'
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
12
client/src/components/ui/provider.jsx
Normal file
12
client/src/components/ui/provider.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import { ChakraProvider, defaultSystem } from '@chakra-ui/react'
|
||||
import { ColorModeProvider } from './color-mode'
|
||||
|
||||
export function Provider(props) {
|
||||
return (
|
||||
<ChakraProvider value={defaultSystem}>
|
||||
<ColorModeProvider {...props} />
|
||||
</ChakraProvider>
|
||||
)
|
||||
}
|
||||
43
client/src/components/ui/toaster.jsx
Normal file
43
client/src/components/ui/toaster.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Toaster as ChakraToaster,
|
||||
Portal,
|
||||
Spinner,
|
||||
Stack,
|
||||
Toast,
|
||||
createToaster,
|
||||
} from '@chakra-ui/react'
|
||||
|
||||
export const toaster = createToaster({
|
||||
placement: 'bottom-end',
|
||||
pauseOnPageIdle: true,
|
||||
})
|
||||
|
||||
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 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.closable && <Toast.CloseTrigger />}
|
||||
</Toast.Root>
|
||||
)}
|
||||
</ChakraToaster>
|
||||
</Portal>
|
||||
)
|
||||
}
|
||||
35
client/src/components/ui/tooltip.jsx
Normal file
35
client/src/components/ui/tooltip.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Tooltip as ChakraTooltip, Portal } from '@chakra-ui/react'
|
||||
import * as React from 'react'
|
||||
|
||||
export const Tooltip = React.forwardRef(function Tooltip(props, ref) {
|
||||
const {
|
||||
showArrow,
|
||||
children,
|
||||
disabled,
|
||||
portalled = true,
|
||||
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>
|
||||
)
|
||||
})
|
||||
@@ -3,9 +3,9 @@
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
color-scheme: light;
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
@@ -24,8 +24,6 @@ a:hover {
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -42,7 +40,8 @@ button {
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
background-color: #f9f9f9;
|
||||
color: #213547;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
@@ -54,15 +53,3 @@ button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { ChakraProvider, createSystem, defaultConfig } from "@chakra-ui/react"
|
||||
import './index.css'
|
||||
import App from './App.jsx'
|
||||
|
||||
const system = createSystem(defaultConfig)
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<ChakraProvider value={system}>
|
||||
<App />
|
||||
</ChakraProvider>
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
11
client/src/tsconfig.app.json
Normal file
11
client/src/tsconfig.app.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user