Compare commits

..

No commits in common. "7be806a81ec43f2b9ab9c5970df142a5cfd6fe6f" and "c665aa18ea70e5da1ea43b728921fbc108709efe" have entirely different histories.

19 changed files with 113 additions and 218 deletions

View File

@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.png" sizes="96x96" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hit Servers Manager</title> <title>frontned</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -1,31 +1,3 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
:root {
--color-bg: 250 250 249;
--color-card: 244 244 242;
--color-card-border: 229 231 235;
--color-text: 26 26 26;
--color-text-secondary: 107 114 128;
--color-hover: 208 74 15;
--color-accent: 233 90 27;
color-scheme: light;
}
.dark {
--color-bg: 2 6 23;
--color-card: 15 23 42;
--color-card-border: 51 65 85;
--color-text: 241 245 249;
--color-text-secondary: 148 163 184;
--color-hover: 249 115 22;
--color-accent: 233 90 27;
color-scheme: dark;
}
@layer base {
body {
@apply bg-bg text-text antialiased transition-colors duration-200;
}
}

View File

@ -5,19 +5,18 @@ import { Dashboard } from './pages/Dashboard';
import { ProtectedRoute } from './routes/ProtectedRoute'; import { ProtectedRoute } from './routes/ProtectedRoute';
function App() { function App() {
return ( return (
<div className="min-h-screen bg-bg text-text transition-colors duration-200 dark:bg-slate-950 dark:text-slate-100"> <BrowserRouter>
<BrowserRouter> <Routes>
<Routes> <Route path="/" element={
<Route path="/" element={ <ProtectedRoute>
<ProtectedRoute> <Dashboard />
<Dashboard /> </ProtectedRoute>
</ProtectedRoute> } />
} /> <Route path="/login" element={<Login />} />
<Route path="/login" element={<Login />} /> </Routes>
</Routes> </BrowserRouter>
</BrowserRouter>
</div>
); );
} }

View File

@ -254,5 +254,5 @@ export const Header = ({
}; };
const Styles = { const Styles = {
wrapper: "flex items-center justify-between rounded-xl border border-cardBorder bg-card px-6 py-4 shadow-sm dark:border-slate-800 dark:bg-slate-900 dark:shadow-slate-900/50", wrapper: "flex items-center justify-between rounded-xl border border-cardBorder bg-card px-6 py-4 shadow-sm",
}; };

View File

@ -74,11 +74,11 @@ export const ServerCardMetrics = () => {
const Styles = { const Styles = {
grid: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", grid: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
card: "flex items-center gap-4 rounded-xl border border-cardBorder bg-gradient-to-br from-white/90 to-card p-5 shadow-sm dark:border-slate-800 dark:from-slate-900 dark:to-slate-950 dark:shadow-slate-900/40", card: "flex items-center gap-4 rounded-xl border border-cardBorder bg-gradient-to-br from-white/90 to-card p-5 shadow-sm",
iconWrapper: "flex h-14 w-14 items-center justify-center rounded-lg border border-accent/20 bg-accent/10 text-accent dark:border-slate-800 dark:bg-slate-800/60", iconWrapper: "flex h-14 w-14 items-center justify-center rounded-lg border border-accent/20 bg-accent/10 text-accent",
textGroup: "flex flex-col", textGroup: "flex flex-col",
label: "text-xs font-medium uppercase tracking-wide text-text-secondary dark:text-slate-400", label: "text-xs font-medium uppercase tracking-wide text-text-secondary",
value: "text-3xl font-semibold leading-tight text-text dark:text-white", value: "text-3xl font-semibold text-text leading-tight",
placeholder: "rounded-lg border border-cardBorder bg-card p-4 text-sm text-text-secondary dark:border-slate-800 dark:bg-slate-900 dark:text-slate-400", placeholder: "p-4 rounded-lg border border-cardBorder bg-card text-text-secondary text-sm",
error: "rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-600 dark:border-red-400/30 dark:bg-red-950/40 dark:text-red-300", error: "p-4 rounded-lg border border-red-200 bg-red-50 text-red-600 text-sm",
}; };

View File

@ -15,8 +15,6 @@ interface Props {
serverTypeOptions?: ServersType[]; serverTypeOptions?: ServersType[];
applicationOptions?: Applications[]; applicationOptions?: Applications[];
databaseOptions?: DatabaseType[]; databaseOptions?: DatabaseType[];
hideSensitive: boolean;
onToggleSensitive: () => void;
} }
const withAllOption = <T extends string>(options?: T[]): Array<T | OptionAll> => { const withAllOption = <T extends string>(options?: T[]): Array<T | OptionAll> => {
@ -39,8 +37,6 @@ export const ServersFilterBar = ({
serverTypeOptions, serverTypeOptions,
applicationOptions, applicationOptions,
databaseOptions, databaseOptions,
hideSensitive,
onToggleSensitive,
}: Props) => { }: Props) => {
const typeOptions = withAllOption(serverTypeOptions); const typeOptions = withAllOption(serverTypeOptions);
const applicationOptionsList = withAllOption(applicationOptions); const applicationOptionsList = withAllOption(applicationOptions);
@ -77,11 +73,6 @@ export const ServersFilterBar = ({
onChange={(event) => onDbTypeChange(event.target.value as DatabaseType | OptionAll)} onChange={(event) => onDbTypeChange(event.target.value as DatabaseType | OptionAll)}
options={databaseOptionsList} options={databaseOptionsList}
/> />
<div className={Styles.toggleWrapper}>
<button type="button" className={Styles.toggleButton} onClick={onToggleSensitive}>
{hideSensitive ? "Mostrar dados confidenciais" : "Ocultar dados confidenciais"}
</button>
</div>
</div> </div>
); );
}; };
@ -109,12 +100,10 @@ const Select = <T extends string>({ label, value, onChange, options }: SelectPro
}; };
const Styles = { const Styles = {
wrapper: "flex flex-wrap items-end gap-4 rounded-lg border border-cardBorder bg-white/70 p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900 dark:shadow-slate-900/40", wrapper: "flex flex-wrap gap-4 rounded-lg border border-cardBorder bg-white/70 p-4 shadow-sm",
searchGroup: "flex-1 min-w-[220px]", searchGroup: "flex-1 min-w-[220px]",
selectGroup: "flex min-w-[150px] flex-col gap-1", selectGroup: "flex min-w-[150px] flex-col gap-1",
label: "text-xs font-semibold uppercase tracking-wide text-text-secondary dark:text-slate-400", label: "text-xs font-semibold uppercase tracking-wide text-text-secondary",
input: "w-full rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100", input: "w-full rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent",
select: "w-full rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent capitalize dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100", select: "w-full rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent capitalize",
toggleWrapper: "flex min-w-[200px] items-center justify-end",
toggleButton: "rounded-md border border-cardBorder px-4 py-2 text-xs font-semibold uppercase tracking-wide text-text-secondary hover:bg-bg dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800",
}; };

View File

@ -1,3 +1,4 @@
import { useState } from "react";
import type { Server } from "../types/Server"; import type { Server } from "../types/Server";
interface Props { interface Props {
@ -8,7 +9,6 @@ interface Props {
pageSize: number; pageSize: number;
totalPages: number; totalPages: number;
totalItems: number; totalItems: number;
hideSensitive: boolean;
onPageChange: (page: number) => void; onPageChange: (page: number) => void;
onPageSizeChange: (size: number) => void; onPageSizeChange: (size: number) => void;
} }
@ -23,36 +23,38 @@ export const ServersTable = ({
pageSize, pageSize,
totalPages, totalPages,
totalItems, totalItems,
hideSensitive,
onPageChange, onPageChange,
onPageSizeChange, onPageSizeChange,
}: Props) => { }: Props) => {
const [hideSensitive, setHideSensitive] = useState(false);
const showingFrom = totalItems === 0 ? 0 : page * pageSize + 1; const showingFrom = totalItems === 0 ? 0 : page * pageSize + 1;
const showingTo = totalItems === 0 ? 0 : Math.min((page + 1) * pageSize, totalItems); const showingTo = totalItems === 0 ? 0 : Math.min((page + 1) * pageSize, totalItems);
const hasResults = servers.length > 0; const hasResults = servers.length > 0;
const isInitialLoad = loading && !hasResults;
const showOverlay = loading && hasResults;
const formatSensitiveValue = (value: string | number) => (hideSensitive ? "••••" : value); const formatSensitiveValue = (value: string | number) => (hideSensitive ? "••••" : value);
return ( return (
<div className={Styles.card}> <div className={Styles.card}>
{isInitialLoad && <div className={Styles.status}>Carregando servidores...</div>} <button
type="button"
className={Styles.hideButton}
onClick={() => setHideSensitive((prev) => !prev)}
disabled={loading || (!hasResults && !error)}
aria-pressed={hideSensitive}
>
{hideSensitive ? "Mostrar dados sensíveis" : "Ocultar dados sensíveis"}
</button>
{loading && <div className={Styles.status}>Carregando servidores...</div>}
{error && <div className={Styles.error}>{error}</div>} {error && <div className={Styles.error}>{error}</div>}
{!loading && !error && servers.length === 0 && ( {!loading && !error && servers.length === 0 && (
<div className={Styles.status}>Nenhum servidor encontrado.</div> <div className={Styles.status}>Nenhum servidor encontrado.</div>
)} )}
{hasResults && ( {!loading && !error && hasResults && (
<div className="relative space-y-4"> <>
{showOverlay && (
<div className={Styles.loadingOverlay}>
<span>Atualizando lista...</span>
</div>
)}
<div className={Styles.tableWrapper}> <div className={Styles.tableWrapper}>
<table className={Styles.table}> <table className={Styles.table}>
<thead className={Styles.tableHead}> <thead className={Styles.tableHead}>
<tr className={Styles.tableHeadRow}> <tr className="text-left">
<th className={Styles.tableHeadCell}>Nome</th> <th className={Styles.tableHeadCell}>Nome</th>
<th className={Styles.tableHeadCell}>IP</th> <th className={Styles.tableHeadCell}>IP</th>
<th className={Styles.tableHeadCell}>Porta</th> <th className={Styles.tableHeadCell}>Porta</th>
@ -67,14 +69,14 @@ export const ServersTable = ({
{servers.map((server) => ( {servers.map((server) => (
<tr <tr
key={server.id} key={server.id}
className={Styles.tableRow} className="hover:bg-gray-50 transition-colors even:bg-gray-50/50"
> >
<td className={Styles.rowCell}>{server.name}</td> <td className={Styles.rowCell}>{server.name}</td>
<td className={Styles.rowCell}>{formatSensitiveValue(server.ip)}</td> <td className={Styles.rowCell}>{formatSensitiveValue(server.ip)}</td>
<td className={Styles.rowCell}>{formatSensitiveValue(server.port)}</td> <td className={Styles.rowCell}>{formatSensitiveValue(server.port)}</td>
<td className={Styles.rowCell}>{formatSensitiveValue(server.user)}</td> <td className={Styles.rowCell}>{formatSensitiveValue(server.user)}</td>
<td className={Styles.rowCell}> <td className={Styles.rowCell}>
<code className={Styles.password}> <code className="text-xs bg-gray-100 px-2 py-1 rounded">
{hideSensitive ? "••••" : server.password} {hideSensitive ? "••••" : server.password}
</code> </code>
</td> </td>
@ -128,34 +130,29 @@ export const ServersTable = ({
</div> </div>
</div> </div>
</div> </div>
</div> </>
)} )}
<div className={Styles.footerSpacer} aria-hidden />
</div> </div>
); );
}; };
const Styles = { const Styles = {
card: "overflow-hidden rounded-lg border border-cardBorder bg-card shadow-sm dark:border-slate-800 dark:bg-slate-900 dark:shadow-slate-900/40", card: "relative bg-card border border-cardBorder shadow-sm rounded-lg overflow-hidden",
status: "p-4 text-sm text-text-secondary dark:text-slate-400", status: "p-4 text-text-secondary text-sm",
error: "p-4 text-sm text-red-600 dark:text-red-400", error: "p-4 text-red-600 text-sm",
tableWrapper: "overflow-x-auto rounded-lg border border-cardBorder shadow-sm dark:border-slate-800 dark:bg-slate-950/40", tableWrapper: "overflow-x-auto rounded-lg shadow-sm border border-cardBorder",
table: "min-w-full table-auto divide-y divide-cardBorder dark:divide-slate-800", table: "min-w-full divide-y divide-cardBorder table-auto",
tableHead: "sticky top-0 bg-gray-50 dark:bg-slate-800", tableHead: "bg-gray-50 sticky top-0",
tableHeadRow: "text-left text-text dark:text-slate-200", tableHeadCell: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-text-secondary",
tableHeadCell: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-text-secondary dark:text-slate-300", tableBody: "bg-white divide-y divide-cardBorder",
tableBody: "divide-y divide-cardBorder bg-white dark:divide-slate-800 dark:bg-slate-900", rowCell: "px-4 py-3 text-sm text-text",
tableRow: "transition-colors even:bg-gray-50/50 hover:bg-gray-50 dark:even:bg-slate-900 dark:hover:bg-slate-800", pagination: "flex flex-col gap-2 border-t border-cardBorder bg-white px-4 py-3 sm:flex-row sm:items-center sm:justify-between",
rowCell: "px-4 py-3 text-sm text-text dark:text-slate-100", pageInfo: "text-sm text-text-secondary",
password: "rounded bg-gray-100 px-2 py-1 text-xs dark:bg-slate-800 dark:text-slate-100",
pagination: "flex flex-col gap-2 border-t border-cardBorder bg-white px-4 py-3 dark:border-slate-800 dark:bg-slate-900 sm:flex-row sm:items-center sm:justify-between",
pageInfo: "text-sm text-text-secondary dark:text-slate-400",
paginationControls: "flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4", paginationControls: "flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4",
pageSizeLabel: "text-sm text-text dark:text-slate-100 flex items-center gap-2", pageSizeLabel: "text-sm text-text flex items-center gap-2",
pageSizeSelect: "rounded-md border border-cardBorder bg-white px-2 py-1 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100", pageSizeSelect: "rounded-md border border-cardBorder bg-white px-2 py-1 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent",
pageButtons: "flex items-center gap-3", pageButtons: "flex items-center gap-3",
pageButton: "rounded-md border border-cardBorder px-3 py-1.5 text-sm font-medium text-text transition-colors hover:bg-bg disabled:opacity-50 disabled:hover:bg-transparent dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-800", pageButton: "rounded-md border border-cardBorder px-3 py-1.5 text-sm font-medium text-text hover:bg-bg disabled:opacity-50 disabled:hover:bg-transparent",
pageIndicator: "text-sm text-text-secondary dark:text-slate-400", pageIndicator: "text-sm text-text-secondary",
footerSpacer: "h-4", hideButton: "absolute right-3 top-3 rounded-md border border-cardBorder px-3 py-1.5 text-xs font-semibold uppercase tracking-wide text-text-secondary hover:bg-bg disabled:opacity-50 disabled:hover:bg-transparent bg-white shadow-sm",
loadingOverlay: "absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-white/70 text-sm font-medium text-text backdrop-blur-sm dark:bg-slate-900/80 dark:text-slate-100",
}; };

View File

@ -64,11 +64,11 @@ export const Modal = ({ isOpen, title, onClose, children, description, bodyClass
}; };
const Styles = { const Styles = {
overlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4 backdrop-blur-sm !mt-0 dark:bg-black/60", overlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 backdrop-blur-sm px-4 !mt-0",
dialog: "w-full max-w-2xl transform animate-fade-up rounded-2xl border border-cardBorder bg-card p-6 shadow-xl transition-all duration-200 dark:border-slate-800 dark:bg-slate-900 dark:shadow-slate-900/70", dialog: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl transform transition-all duration-200 animate-fade-up",
header: "flex items-start justify-between gap-4 border-b border-cardBorder pb-4 dark:border-slate-800", header: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder",
title: "text-lg font-semibold text-text dark:text-white", title: "text-lg font-semibold text-text",
closeButton: "text-2xl leading-none text-text-secondary hover:text-text dark:text-slate-400 dark:hover:text-white", closeButton: "text-2xl leading-none text-text-secondary hover:text-text",
description: "pt-3 text-sm text-text-secondary dark:text-slate-400", description: "pt-3 text-sm text-text-secondary",
body: "pt-4", body: "pt-4",
}; };

View File

@ -34,7 +34,7 @@ export const BulkImportModal = ({
<Modal isOpen={isOpen} title="Cadastro em massa" onClose={onClose} bodyClassName={Styles.modalBody}> <Modal isOpen={isOpen} title="Cadastro em massa" onClose={onClose} bodyClassName={Styles.modalBody}>
<div className={Styles.uploadCard}> <div className={Styles.uploadCard}>
<label htmlFor="bulk-file" className={Styles.dropLabel}> <label htmlFor="bulk-file" className={Styles.dropLabel}>
<span className="text-base font-medium text-text dark:text-white">Selecionar arquivo CSV</span> <span className="text-base font-medium text-text">Selecionar arquivo CSV</span>
<span className="text-xs text-text-secondary"> <span className="text-xs text-text-secondary">
Arraste e solte ou clique para procurar Arraste e solte ou clique para procurar
</span> </span>
@ -115,19 +115,19 @@ const Stat = ({
const Styles = { const Styles = {
modalBody: "space-y-5", modalBody: "space-y-5",
uploadCard: "space-y-4 rounded-xl border border-dashed border-cardBorder bg-white/70 p-6 shadow-inner dark:border-slate-800 dark:bg-slate-900/70", uploadCard: "rounded-xl border border-dashed border-cardBorder bg-white/70 p-6 shadow-inner space-y-4",
dropLabel: "flex flex-col gap-1 text-center text-text dark:text-white", dropLabel: "flex flex-col gap-1 text-center",
dropzone: "flex cursor-pointer flex-col items-center justify-center rounded-lg border border-cardBorder bg-bg px-4 py-6 text-center transition-colors hover:border-accent hover:bg-white dark:border-slate-700 dark:bg-slate-900 dark:hover:bg-slate-800", dropzone: "flex flex-col items-center justify-center rounded-lg border border-cardBorder bg-bg px-4 py-6 text-center cursor-pointer hover:border-accent hover:bg-white transition-colors",
helperText: "text-xs text-text-secondary dark:text-slate-400", helperText: "text-xs text-text-secondary",
actionsRow: "flex flex-wrap items-center gap-3", actionsRow: "flex flex-wrap items-center gap-3",
secondaryButton: "rounded-md border border-cardBorder px-4 py-2 text-sm font-medium text-text hover:bg-bg dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-800", secondaryButton: "rounded-md border border-cardBorder px-4 py-2 text-sm font-medium text-text hover:bg-bg",
primaryButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white hover:bg-hover disabled:opacity-70 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200", primaryButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white hover:bg-hover disabled:opacity-70",
errorText: "text-sm text-red-600 dark:text-red-400", errorText: "text-sm text-red-600",
resultCard: "space-y-4 rounded-xl border border-cardBorder bg-white/90 p-5 dark:border-slate-800 dark:bg-slate-900/80", resultCard: "rounded-xl border border-cardBorder bg-white/90 p-5 space-y-4",
statsGrid: "flex flex-wrap gap-4", statsGrid: "flex flex-wrap gap-4",
failureList: "max-h-40 list-disc space-y-1 overflow-auto pl-5 text-sm text-text-secondary dark:text-slate-400", failureList: "list-disc pl-5 space-y-1 text-sm text-text-secondary max-h-40 overflow-auto",
statBase: "flex flex-col rounded-lg border px-4 py-3 text-sm", statBase: "flex flex-col rounded-lg border px-4 py-3 text-sm",
statDefault: "border-cardBorder bg-white text-text dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100", statDefault: "border-cardBorder bg-white text-text",
statAccent: "border-accent/40 bg-accent/10 text-accent dark:border-accent/20 dark:bg-accent/20 dark:text-white", statAccent: "border-accent/40 bg-accent/10 text-accent",
statDanger: "border-red-200 bg-red-50 text-red-600 dark:border-red-400/40 dark:bg-red-500/10 dark:text-red-300", statDanger: "border-red-200 bg-red-50 text-red-600",
}; };

View File

@ -50,8 +50,8 @@ export const HeaderActions = ({
const Styles = { const Styles = {
actions: "flex items-center gap-3", actions: "flex items-center gap-3",
menuTrigger: "flex items-center gap-2 rounded-lg border border-cardBorder bg-white/70 px-4 py-2 text-sm font-medium text-text transition-colors hover:bg-white dark:border-slate-700 dark:bg-slate-800/80 dark:text-slate-100 dark:hover:bg-slate-800", menuTrigger: "rounded-lg border border-cardBorder bg-white/70 px-4 py-2 text-sm font-medium text-text flex items-center gap-2 hover:bg-white transition-colors",
dropdown: "absolute right-0 z-10 mt-2 w-48 rounded-lg border border-cardBorder bg-white py-2 shadow-lg dark:border-slate-700 dark:bg-slate-900 dark:shadow-black/40", dropdown: "absolute right-0 mt-2 w-48 rounded-lg border border-cardBorder bg-white py-2 shadow-lg z-10",
dropdownItem: "w-full px-4 py-2 text-left text-sm text-text-secondary transition-colors hover:bg-bg hover:text-text dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-white", dropdownItem: "w-full px-4 py-2 text-left text-sm text-text-secondary hover:bg-bg hover:text-text transition-colors",
logoutButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-hover dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200", logoutButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-hover",
}; };

View File

@ -13,6 +13,6 @@ export const HeaderBrand = () => {
const Styles = { const Styles = {
brand: "flex items-center gap-3", brand: "flex items-center gap-3",
logo: "h-10 w-10 object-contain", logo: "h-10 w-10 object-contain",
title: "text-base font-semibold text-text dark:text-white", title: "text-base font-semibold text-text",
subtitle: "text-xs uppercase tracking-wide text-text-secondary dark:text-slate-400", subtitle: "text-xs uppercase tracking-wide text-text-secondary",
}; };

View File

@ -65,14 +65,14 @@ export const ProfileModal = ({
const Styles = { const Styles = {
modalBody: "space-y-4", modalBody: "space-y-4",
helperText: "text-sm text-text-secondary dark:text-slate-400", helperText: "text-sm text-text-secondary",
errorMessage: "text-sm text-red-600 dark:text-red-400", errorMessage: "text-sm text-red-600",
form: "space-y-4", form: "space-y-4",
formGrid: "grid gap-4 md:grid-cols-2", formGrid: "grid gap-4 md:grid-cols-2",
field: "flex flex-col gap-2", field: "flex flex-col gap-2",
label: "text-xs font-semibold uppercase tracking-wide text-text-secondary dark:text-slate-400", label: "text-xs font-semibold uppercase tracking-wide text-text-secondary",
input: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent disabled:opacity-70 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100", input: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent disabled:opacity-70",
modalActions: "flex justify-end gap-3 pt-2", modalActions: "flex justify-end gap-3 pt-2",
secondaryButton: "rounded-md border border-cardBorder px-4 py-2 text-sm font-medium text-text hover:bg-bg disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-800", secondaryButton: "rounded-md border border-cardBorder px-4 py-2 text-sm font-medium text-text hover:bg-bg disabled:opacity-50",
primaryButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white hover:bg-hover disabled:opacity-70 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200", primaryButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white hover:bg-hover disabled:opacity-70",
}; };

View File

@ -95,10 +95,10 @@ const Styles = {
form: "space-y-4", form: "space-y-4",
formGrid: "grid gap-4 md:grid-cols-2", formGrid: "grid gap-4 md:grid-cols-2",
field: "flex flex-col gap-2", field: "flex flex-col gap-2",
label: "text-xs font-semibold uppercase tracking-wide text-text-secondary dark:text-slate-400", label: "text-xs font-semibold uppercase tracking-wide text-text-secondary",
input: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100", input: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent",
select: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent capitalize dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100", select: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent capitalize",
modalActions: "flex justify-end gap-3 pt-2", modalActions: "flex justify-end gap-3 pt-2",
secondaryButton: "rounded-md border border-cardBorder px-4 py-2 text-sm font-medium text-text hover:bg-bg disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-800", secondaryButton: "rounded-md border border-cardBorder px-4 py-2 text-sm font-medium text-text hover:bg-bg disabled:opacity-50",
primaryButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white hover:bg-hover disabled:opacity-70 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200", primaryButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white hover:bg-hover disabled:opacity-70",
}; };

View File

@ -2,21 +2,12 @@ import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import App from './App.tsx'; import App from './App.tsx';
import { Toaster } from 'react-hot-toast'; import { Toaster } from 'react-hot-toast';
import { initSystemThemeWatcher } from './theme.ts';
initSystemThemeWatcher();
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<> <>
<App /> <App />
<Toaster <Toaster position="top-right" toastOptions={{ duration: 4000 }} />
position="top-right"
toastOptions={{
duration: 4000,
className: "bg-white text-text shadow-lg dark:!bg-slate-800 dark:!text-slate-100",
}}
/>
</> </>
</StrictMode>, </StrictMode>,
); );

View File

@ -26,7 +26,6 @@ export const Dashboard = () => {
const [pageSize, setPageSize] = useState<number>(10); const [pageSize, setPageSize] = useState<number>(10);
const [totalPages, setTotalPages] = useState<number>(0); const [totalPages, setTotalPages] = useState<number>(0);
const [totalItems, setTotalItems] = useState<number>(0); const [totalItems, setTotalItems] = useState<number>(0);
const [hideSensitive, setHideSensitive] = useState<boolean>(true);
const [serverTypeOptions, setServerTypeOptions] = useState<string[]>([]); const [serverTypeOptions, setServerTypeOptions] = useState<string[]>([]);
const [applicationOptions, setApplicationOptions] = useState<string[]>([]); const [applicationOptions, setApplicationOptions] = useState<string[]>([]);
const [databaseOptions, setDatabaseOptions] = useState<string[]>([]); const [databaseOptions, setDatabaseOptions] = useState<string[]>([]);
@ -151,16 +150,12 @@ export const Dashboard = () => {
setPageSize(nextSize); setPageSize(nextSize);
}; };
const handleToggleSensitive = () => {
setHideSensitive((prev) => !prev);
};
useEffect(() => { useEffect(() => {
fetchTypeOptions(); fetchTypeOptions();
}, [fetchTypeOptions]); }, [fetchTypeOptions]);
return ( return (
<Layout className="h-full py-10"> <Layout className="h-screen py-10">
<div className="space-y-6"> <div className="space-y-6">
<Header <Header
currentUser={currentUser} currentUser={currentUser}
@ -181,8 +176,6 @@ export const Dashboard = () => {
onTypeChange={handleTypeChange} onTypeChange={handleTypeChange}
onApplicationChange={handleApplicationChange} onApplicationChange={handleApplicationChange}
onDbTypeChange={handleDbTypeChange} onDbTypeChange={handleDbTypeChange}
hideSensitive={hideSensitive}
onToggleSensitive={handleToggleSensitive}
serverTypeOptions={serverTypeOptions} serverTypeOptions={serverTypeOptions}
applicationOptions={applicationOptions} applicationOptions={applicationOptions}
databaseOptions={databaseOptions} databaseOptions={databaseOptions}
@ -195,7 +188,6 @@ export const Dashboard = () => {
pageSize={pageSize} pageSize={pageSize}
totalPages={totalPages} totalPages={totalPages}
totalItems={totalItems} totalItems={totalItems}
hideSensitive={hideSensitive}
onPageChange={handlePageChange} onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange} onPageSizeChange={handlePageSizeChange}
/> />

View File

@ -50,11 +50,11 @@ export const Login = () => {
return ( return (
<Layout className={Styles.layout}> <Layout className={Styles.layout}>
<img src="/logo.webp" alt="Logo" className="mx-auto mb-4 h-36 w-36" /> <img src="/logo.webp " alt="Logo" className="w-36 h-36 mx-auto mb-4" />
<div className={Styles.card}> <div className={Styles.card}>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div> <div>
<label htmlFor="email" className={Styles.label}>Email:</label> <label htmlFor="email" className="block mb-2 text-md font-medium text-text">Email:</label>
<input <input
type="email" type="email"
id="email" id="email"
@ -65,7 +65,7 @@ export const Login = () => {
onChange={handleChange}/> onChange={handleChange}/>
</div> </div>
<div className="relative"> <div className="relative">
<label htmlFor="password" className={Styles.label}>Password:</label> <label htmlFor="password" className="block mb-2 text-md font-medium text-text">Password:</label>
<input <input
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
id="password" id="password"
@ -84,7 +84,7 @@ export const Login = () => {
{showPassword ? <Eye size={18} /> : <EyeOff size={18} />} {showPassword ? <Eye size={18} /> : <EyeOff size={18} />}
</button> </button>
</div> </div>
{error && <p className="mt-2 text-sm text-red-500 dark:text-red-400">{error}</p>} {error && <p className="text-red-500 text-sm mt-2">{error}</p>}
<button type="submit" disabled={loading} className={Styles.button}> <button type="submit" disabled={loading} className={Styles.button}>
{loading ? "Autenticando..." : "Login"} {loading ? "Autenticando..." : "Login"}
</button> </button>
@ -95,10 +95,9 @@ export const Login = () => {
}; };
const Styles = { const Styles = {
layout: "flex h-screen animate-fade-up flex-col justify-center gap-4 px-4", layout: "h-screen flex flex-col gap-4 justify-center animate-fade-up",
card: "w-full max-w-md rounded-lg border border-cardBorder bg-card p-8 shadow-lg dark:border-slate-800 dark:bg-slate-900 dark:shadow-slate-900/60", card: "w-96 p-8 shadow-lg rounded-lg border border-cardBorder bg-card",
label: "mb-2 block text-md font-medium text-text dark:text-slate-100", input: "bg-gray-50 border border-cardBorder text-text text-md rounded-lg outline-none block w-full p-2.5",
input: "block w-full rounded-lg border border-cardBorder bg-gray-50 p-2.5 text-md text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100", iconButton: "absolute right-3 top-1/2 text-text p-1 focus:outline-none",
iconButton: "absolute right-3 top-9 p-1 text-text focus:outline-none dark:text-slate-200", button: "w-full bg-accent p-2 rounded-md mt-4 text-white font-bold text-lg hover:bg-hover transition duration-150",
button: "mt-4 w-full rounded-md bg-accent p-2 text-lg font-bold text-white transition duration-150 hover:bg-hover dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200",
}; };

View File

@ -1,34 +0,0 @@
const applyThemeToDocument = (isDark: boolean) => {
if (typeof document === "undefined") {
return;
}
const theme = isDark ? "dark" : "light";
const root = document.documentElement;
const body = document.body;
root.classList.toggle("dark", isDark);
root.setAttribute("data-theme", theme);
if (body) {
body.classList.toggle("dark", isDark);
body.setAttribute("data-theme", theme);
body.style.colorScheme = isDark ? "dark" : "light";
}
};
export const initSystemThemeWatcher = () => {
if (typeof window === "undefined" || typeof window.matchMedia === "undefined") {
return;
}
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const applyMatch = (matches: boolean) => applyThemeToDocument(matches);
const handleChange = (event: MediaQueryListEvent) => applyMatch(event.matches);
applyMatch(mediaQuery.matches);
if (typeof mediaQuery.addEventListener === "function") {
mediaQuery.addEventListener("change", handleChange);
} else if (typeof mediaQuery.addListener === "function") {
mediaQuery.addListener(handleChange);
}
};

View File

@ -1,15 +1,5 @@
const withOpacity = (variable) => {
return ({ opacityValue } = {}) => {
if (opacityValue !== undefined) {
return `rgb(var(${variable}) / ${opacityValue})`;
}
return `rgb(var(${variable}))`;
};
};
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
darkMode: "class",
content: [ content: [
"./index.html", "./index.html",
"./src/**/*.{js,ts,jsx,tsx}", "./src/**/*.{js,ts,jsx,tsx}",
@ -17,13 +7,13 @@ export default {
theme: { theme: {
extend: { extend: {
colors: { colors: {
bg: withOpacity("--color-bg"), bg: '#FAFAF9',
card: withOpacity("--color-card"), card: '#F4F4F2',
cardBorder: withOpacity("--color-card-border"), cardBorder: '#E5E7EB',
text: withOpacity("--color-text"), text: '#1A1A1A',
"text-secondary": withOpacity("--color-text-secondary"), 'text-secondary': '#6B7280',
hover: withOpacity("--color-hover"), hover: '#D04A0F',
accent: withOpacity("--color-accent"), accent: '#E95A1B',
}, },
keyframes: { keyframes: {
'fade-up': { 'fade-up': {