Add pages in project

This commit is contained in:
Rayan 2025-02-16 21:30:09 -05:00
parent fde68975a7
commit ed8efb6ab4
24 changed files with 1572 additions and 166 deletions

1143
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,13 +11,24 @@
"format": "prettier --write ."
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
"@reduxjs/toolkit": "^2.5.1",
"@tailwindcss/postcss": "^4.0.6",
"@tanstack/react-query": "^5.66.3",
"axios": "^1.7.9",
"lucide-react": "^0.475.0",
"postcss": "^8.5.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.2.0",
"react-router-dom": "^7.1.5",
"tailwindcss": "^4.0.6",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",

4
postcss.config.js Normal file
View file

@ -0,0 +1,4 @@
export const plugins = {
tailwindcss: {},
autoprefixer: {},
};

View file

@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View file

@ -1,12 +1,47 @@
import { useState } from 'react'
import * as React from 'react'
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
} from 'react-router-dom'
import Header from './components/Header'
import Sidebar from './components/Sidebar'
import ThemeToggle from './components/ThemeToggle'
import Dashboard from './pages/Dashboard'
import LoginPage from './pages/LoginPage'
import NotFound from './pages/NotFound'
import { ThemeProvider } from './context/ThemeContext'
import useAuthStore from './store/authStore' // Importando autenticação
function App() {
const [name, setName] = useState('')
const App: React.FC = () => {
const isAuthenticated = useAuthStore((state) => state.isAuthenticated) // Obtendo estado global de autenticação
return (
<>
<p>Init panel branch</p>
</>
<ThemeProvider>
<Router>
{!isAuthenticated ? (
<Routes>
<Route path="*" element={<Navigate to="/login" />} />
<Route path="/login" element={<LoginPage />} />
</Routes>
) : (
<>
<Header />
<div>
<Sidebar />
<div>
<Routes>
<Route path="/" element={<Navigate to="/dashboard" />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</div>
</div>
<ThemeToggle />
</>
)}
</Router>
</ThemeProvider>
)
}

11
src/components/Header.tsx Normal file
View file

@ -0,0 +1,11 @@
import * as React from 'react'
const Header: React.FC = () => {
return (
<header className="bg-gray-900 text-white p-4 flex justify-between items-center">
<h1 className="text-xl font-bold">Painel de Controle</h1>
</header>
)
}
export default Header

View file

@ -0,0 +1,31 @@
import * as React from 'react'
import { Link } from 'react-router-dom'
const Sidebar: React.FC = () => {
return (
<aside className="bg-gray-800 text-white w-64 min-h-screen p-4">
<nav>
<ul>
<li className="mb-2">
<Link
to="/dashboard"
className="block p-2 rounded hover:bg-gray-700"
>
Dashboard
</Link>
</li>
<li>
<Link
to="/settings"
className="block p-2 rounded hover:bg-gray-700"
>
Configurações
</Link>
</li>
</ul>
</nav>
</aside>
)
}
export default Sidebar

View file

@ -0,0 +1,17 @@
import * as React from 'react'
import { useTheme } from '../context/ThemeContext'
const ThemeToggle: React.FC = () => {
const { theme, toggleTheme } = useTheme()
return (
<button
onClick={toggleTheme}
className="fixed bottom-4 right-4 bg-gray-700 text-white p-2 rounded"
>
{theme === 'light' ? '🌙 Modo Escuro' : '☀️ Modo Claro'}
</button>
)
}
export default ThemeToggle

View file

@ -0,0 +1,46 @@
import * as React from 'react'
import { createContext, useContext, useState, ReactNode } from 'react'
interface ThemeContextType {
theme: string
toggleTheme: () => void
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
export const ThemeProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const [theme, setTheme] = useState('light')
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'))
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div className={theme}>{children}</div>
</ThemeContext.Provider>
)
}
export const useTheme = () => {
const context = useContext(ThemeContext)
if (!context) throw new Error('useTheme must be used within a ThemeProvider')
return context
}
const ThemeToggle: React.FC = () => {
const { theme, toggleTheme } = useTheme()
return (
<button
onClick={toggleTheme}
className="fixed bottom-4 right-4 bg-gray-700 text-white p-2 rounded"
>
{theme === 'light' ? '🌙 Modo Escuro' : '☀️ Modo Claro'}
</button>
)
}
export default ThemeToggle

1
src/global.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare module '*.css'

View file

@ -1,68 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
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;
}
}

View file

@ -1,7 +1,9 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import * as React from 'react'
import './styles/index.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>

103
src/pages/Dashboard.tsx Normal file
View file

@ -0,0 +1,103 @@
import { useState } from 'react'
import { Link, Navigate } from 'react-router-dom'
import { Menu, LogOut, Home, User, Settings } from 'lucide-react'
import { create } from 'zustand'
import * as React from 'react'
interface AuthState {
user: object | null
isAuthenticated: boolean
login: (token: string) => void
logout: () => void
}
const useAuthStore = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
login: (token: string) => {
localStorage.setItem('token', token)
set({ isAuthenticated: true })
},
logout: () => {
localStorage.removeItem('token')
set({ user: null, isAuthenticated: false })
},
}))
const Dashboard: React.FC = (): JSX.Element => {
const [sidebarOpen, setSidebarOpen] = useState<boolean>(false)
const { isAuthenticated, logout } = useAuthStore()
if (!isAuthenticated) {
console.log(isAuthenticated)
return <Navigate to="/login" />
}
return (
<div className="flex h-screen bg-gray-100">
{/* Sidebar */}
<aside
className={`bg-gray-800 text-white w-64 space-y-6 py-7 px-2 absolute inset-y-0 left-0 transform ${sidebarOpen ? 'translate-x-0' : '-translate-x-full'} transition duration-200 ease-in-out md:relative md:translate-x-0`}
>
<h1 className="text-2xl font-bold text-center">Dashboard</h1>
<nav>
<Link
to="/home"
className="flex items-center space-x-2 px-4 py-2 hover:bg-gray-700 rounded"
>
<Home size={20} />
<span>Home</span>
</Link>
<Link
to="/profile"
className="flex items-center space-x-2 px-4 py-2 hover:bg-gray-700 rounded"
>
<User size={20} />
<span>Perfil</span>
</Link>
<Link
to="/settings"
className="flex items-center space-x-2 px-4 py-2 hover:bg-gray-700 rounded"
>
<Settings size={20} />
<span>Configurações</span>
</Link>
<button
className="flex items-center space-x-2 px-4 py-2 w-full text-left hover:bg-red-600 rounded mt-5"
onClick={logout}
>
<LogOut size={20} />
<span>Sair</span>
</button>
</nav>
</aside>
{/* Main content */}
<div className="flex-1 flex flex-col">
{/* Header */}
<header className="bg-white shadow-md p-4 flex justify-between items-center">
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="md:hidden"
>
<Menu size={24} />
</button>
<h2 className="text-xl font-semibold">Painel de Controle</h2>
</header>
{/* Conteúdo principal */}
<main className="p-6 flex-1 overflow-auto">
<h3 className="text-lg font-semibold mb-4">
Bem-vindo ao Dashboard!
</h3>
<p>
Aqui você pode gerenciar suas informações e visualizar os dados do
sistema.
</p>
</main>
</div>
</div>
)
}
export default Dashboard

61
src/pages/LoginPage.tsx Normal file
View file

@ -0,0 +1,61 @@
import * as React from 'react'
import { useState } from 'react'
import useAuthStore from '../store/authStore'
import { useNavigate } from 'react-router-dom'
import { loginUser } from '../services/api'
const LoginPage: React.FC = () => {
const [username, setUsername] = useState<string>('')
const [password, setPassword] = useState<string>('')
const login = useAuthStore((state) => state.login)
const navigate = useNavigate()
const handleLogin = async () => {
try {
console.log(username);
if (!username || !password) {
alert('Favor informar username e password')
return // Interrompe a execução da função
}
const data = await loginUser({ username, password })
login(data.token)
navigate('/dashboard')
alert('Favor informar username e password')
} catch (error) {
alert('Erro ao fazer login!')
}
}
return (
<div className="flex items-center justify-center h-screen bg-gray-100">
<div className="bg-white shadow-lg rounded-lg p-6 w-full max-w-sm flex flex-col gap-4">
<h1 className="text-2xl font-bold text-center">Login</h1>
<input
className="border border-gray-300 p-2 rounded-md w-full"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Usuário"
/>
<input
className="border border-gray-300 p-2 rounded-md w-full"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Senha"
/>
<button
onClick={handleLogin}
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md w-full"
>
Entrar
</button>
</div>
</div>
)
}
export default LoginPage

15
src/pages/NotFound.tsx Normal file
View file

@ -0,0 +1,15 @@
import { Link } from 'react-router-dom'
const NotFound = () => {
return (
<div className="flex flex-col items-center justify-center h-screen text-center">
<h1 className="text-4xl font-bold text-red-600">404</h1>
<p className="text-lg">Página não encontrada</p>
<Link to="/" className="mt-4 text-blue-500">
Voltar para o início
</Link>
</div>
)
}
export default NotFound

11
src/services/api.ts Normal file
View file

@ -0,0 +1,11 @@
export const loginUser = async (credentials: {
username: string
password: string
}) => {
return {
login: credentials.username,
token:
'asdasd7DGVTeBxO7bELfKr2KwVveTKXjmq9RxphemZDfbr0f5VpKQUheZ7CIFek24Vd4tzadads',
}
}

23
src/store/authStore.ts Normal file
View file

@ -0,0 +1,23 @@
import { create } from 'zustand'
interface AuthState {
user: any
isAuthenticated: boolean
login: (token: string) => void
logout: () => void
}
const useAuthStore = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
login: (token) => {
localStorage.setItem('token', token)
set({ isAuthenticated: true })
},
logout: () => {
localStorage.removeItem('token')
set({ user: null, isAuthenticated: false })
},
}))
export default useAuthStore

View file

@ -0,0 +1,25 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
// Definição do estado inicial
interface PermissionsState {
roles: string[]
}
const initialState: PermissionsState = {
roles: [],
}
// Criando o slice corretamente
const permissionsSlice = createSlice({
name: 'permissions',
initialState,
reducers: {
setPermissions: (state, action: PayloadAction<string[]>) => {
state.roles = action.payload
},
},
})
// Exportando o reducer e a action corretamente
export const { setPermissions } = permissionsSlice.actions
export default permissionsSlice.reducer

11
src/styles/index.css Normal file
View file

@ -0,0 +1,11 @@
@tailwind base;
@tailwind utilities;
/* Estilos personalizados */
body {
@apply bg-gray-100 text-gray-900;
}
.dark body {
@apply bg-gray-900 text-white;
}

1
src/vite-env.d.ts vendored
View file

@ -1 +0,0 @@
/// <reference types="vite/client" />

8
tailwind.config.js Normal file
View file

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx,html}'],
theme: {
extend: {},
},
plugins: [],
}

View file

@ -1,9 +1,8 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
@ -12,8 +11,13 @@
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Não referencie noEmit se estiver em um projeto referenciado */
"declaration": true,
"emitDeclarationOnly": true,
/* Necessário para referências */
"composite": true,
/* Linting */
"strict": true,
@ -22,5 +26,5 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
"include": ["vite.config.ts"]
}

View file

@ -1,5 +1,11 @@
{
"files": [],
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"esModuleInterop": true
},
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }

View file

@ -1,17 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"jsx": "react",
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"moduleResolution": "node",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Removendo "noEmit": true para evitar erro */
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
/* Necessário para referências */
"composite": true,
/* Linting */
"strict": true,
@ -20,5 +29,5 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
"include": ["src"]
}