diff --git a/App.tsx b/App.tsx
index 445d4a6..196efb2 100644
--- a/App.tsx
+++ b/App.tsx
@@ -74,8 +74,8 @@ const App: React.FC = () => {
} />
} />
} />
- } />
- } />
+ } />
+ } />
} />
} />
} />
diff --git a/GEMINI.md b/GEMINI.md
index 14659b8..e5ae2f0 100644
--- a/GEMINI.md
+++ b/GEMINI.md
@@ -1,33 +1,30 @@
# Fasto Project Documentation
## Overview
-Fasto is a commercial team management system built with React (Vite) on the frontend and Node.js (Express) on the backend. It uses a MySQL database.
+Fasto is a commercial team management system built with React (Vite) on the frontend and Node.js (Express) on the backend. It uses a MySQL database. It features a complete multi-tenant architecture designed to securely host multiple client organizations within a single deployment.
## 🚀 Recent Major Changes (March 2026)
-We have transitioned from a mock-based frontend to a fully functional, production-ready system:
+We have transitioned from a mock-based prototype to a **secure, multi-tenant production architecture**:
-- **Authentication:** Implemented real JWT-based authentication with password hashing (bcryptjs).
-- **Backend Integration:** Replaced all hardcoded constants with real API calls to a Node.js/Express backend connected to a MySQL 8.0 database.
-- **RBAC (Role-Based Access Control):** Implemented permissions for `super_admin`, `admin`, `manager`, and `agent`.
-- **Membros (Members):** Enhanced to manage roles, teams, and status. Includes a safety modal for deletion.
-- **Times (Teams):** Created a new dashboard to manage sales groups with real-time performance metrics.
-- **UI/UX:** Standardized PT-BR translations and refined modal layouts.
+- **Multi-Tenancy & Data Isolation:** All backend routes (Users, Teams, Attendances) now strictly enforce `tenant_id` checks. It is technically impossible for one organization to query data from another.
+- **Role-Based Access Control (RBAC):**
+ - **Super Admin:** Global management of all tenants and users (via the hidden `system` tenant).
+ - **Admin/Manager:** Full control over members and teams within their specific organization.
+ - **Agent:** Restricted access. Can only view their own performance metrics and historical attendances.
+- **Premium "Onyx & Gold" UI/UX:** Completely redesigned the dark mode using a true neutral Charcoal (Zinc) palette, high-contrast text, and brand Yellow accents.
+- **Dynamic KPI Dashboard:** Implemented true period-over-period trend calculations for Leads, Quality Scores, and Response Times.
+- **Secure File Uploads:** Profile avatars are now securely uploaded using `multer` with strict mimetype validation (JPG/PNG/WEBP), 2MB size limits, and UUID generation to prevent path traversal.
+- **Enhanced Security Flows:**
+ - User routing uses secure `slugs` instead of exposing raw UUIDs.
+ - All password reset and setup tokens strictly expire in 15 minutes and are destroyed upon use.
+ - Separated the "Reset Password" and "Setup Account" (for new admins) flows for better UX.
## 🛠 Architecture
- **Frontend**: React 19, TypeScript, Vite, TailwindCSS (CDN).
- **Backend**: Node.js, Express, MySQL2 (Pool-based).
-- **Database**: MySQL 8.0 (Schema: `agenciac_comia`).
+- **Database**: MySQL 8.0 (Schema: `fasto_db`).
- **Deployment**: Docker Compose for local development; Gitea Actions for CI/CD pushing to a Gitea Registry and deploying via Portainer webhook.
-## ⚠️ Current Error: Build Instability
-We are currently resolving a recurring build error: `Unexpected end of file` or `Expected ">" but found "\"`.
-
-### Technical Root Cause:
-This is a **tool-level synchronization issue**:
-1. **Truncation:** The file-writing tool (`write_file`) occasionally truncates code before the final braces (`}`) or tags (``) are written.
-2. **Escaping Glitches:** In long JSX strings (like Tailwind class lists), the system sometimes inserts accidental characters that break the JavaScript syntax.
-3. **Result:** The Vite/Esbuild compiler fails because it reaches the end of an incomplete or syntactically broken file.
-
## 📋 Prerequisites
- Docker & Docker Compose
- Node.js (for local development outside Docker)
@@ -39,17 +36,18 @@ Copy `.env.example` to `.env` and adjust values:
```bash
cp .env.example .env
```
-Ensure you set the database credentials and `GITEA_RUNNER_REGISTRATION_TOKEN`.
+Ensure you set the database credentials (`DB_NAME=fasto_db` for production) and `GITEA_RUNNER_REGISTRATION_TOKEN`.
### 2. Database
-The project expects a MySQL database. The `docker-compose.yml` initializes it with `agenciac_comia.sql`.
+The project expects a MySQL database. The `docker-compose.local.yml` initializes it with `agenciac_comia.sql`.
+*Note for Production:* If migrating from an old version, you must manually run the SQL to create the `password_resets` and `pending_registrations` tables, or rebuild the volume.
-### 3. Running with Docker Compose
-To start the application, database, and runner:
+### 3. Running Locally (Docker Compose)
+To start the application and database locally:
```bash
-docker-compose up -d --build
+docker-compose -f docker-compose.local.yml up -d --build
```
-- **Frontend/Backend**: http://localhost:3001
+- **App**: http://localhost:3001
- **Database**: Port 3306
### 4. Gitea Runner
@@ -64,9 +62,6 @@ The project uses Gitea Actions defined in `.gitea/workflows/build-deploy.yaml`.
2. Build Docker image.
3. Push to `gitea.blyzer.com.br`.
4. Trigger Portainer webhook.
-- **Secrets Required in Gitea**:
- `REGISTRY_USERNAME`, `REGISTRY_TOKEN`, `PORTAINER_WEBHOOK`, `API_KEY`.
## 💻 Development
-- **Frontend**: `npm run dev` (Port 3000)
-- **Backend**: `node backend/index.js` (Port 3001)
+The Dockerfile uses a unified root structure. Both the frontend build and the backend Node.js server are hosted from the same container image.
diff --git a/backend/index.js b/backend/index.js
index 385571c..e24e156 100644
--- a/backend/index.js
+++ b/backend/index.js
@@ -358,7 +358,7 @@ apiRouter.post('/users', requireRole(['admin', 'owner', 'super_admin']), async (
});
apiRouter.put('/users/:id', async (req, res) => {
- const { name, bio, role, team_id, status } = req.body;
+ const { name, bio, role, team_id, status, email } = req.body;
try {
const [existing] = await pool.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
if (existing.length === 0) return res.status(404).json({ error: 'Not found' });
@@ -377,10 +377,16 @@ apiRouter.put('/users/:id', async (req, res) => {
const finalRole = isManagerOrAdmin && role !== undefined ? role : existing[0].role;
const finalTeamId = isManagerOrAdmin && team_id !== undefined ? team_id : existing[0].team_id;
const finalStatus = isManagerOrAdmin && status !== undefined ? status : existing[0].status;
+ const finalEmail = email !== undefined ? email : existing[0].email;
+
+ if (finalEmail !== existing[0].email) {
+ const [emailCheck] = await pool.query('SELECT id FROM users WHERE email = ? AND id != ?', [finalEmail, req.params.id]);
+ if (emailCheck.length > 0) return res.status(400).json({ error: 'E-mail já está em uso.' });
+ }
await pool.query(
- 'UPDATE users SET name = ?, bio = ?, role = ?, team_id = ?, status = ? WHERE id = ?',
- [name || existing[0].name, bio !== undefined ? bio : existing[0].bio, finalRole, finalTeamId || null, finalStatus, req.params.id]
+ 'UPDATE users SET name = ?, bio = ?, email = ?, role = ?, team_id = ?, status = ? WHERE id = ?',
+ [name || existing[0].name, bio !== undefined ? bio : existing[0].bio, finalEmail, finalRole, finalTeamId || null, finalStatus, req.params.id]
);
res.json({ message: 'User updated successfully.' });
} catch (error) {
diff --git a/components/Layout.tsx b/components/Layout.tsx
index 2a92708..1b249e0 100644
--- a/components/Layout.tsx
+++ b/components/Layout.tsx
@@ -112,8 +112,12 @@ export const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) =>
{!isSuperAdmin && (
<>
-
-
+ {currentUser.role !== 'agent' && (
+ <>
+
+
+ >
+ )}
>
)}
diff --git a/pages/TeamManagement.tsx b/pages/TeamManagement.tsx
index e5ebe47..701dcd6 100644
--- a/pages/TeamManagement.tsx
+++ b/pages/TeamManagement.tsx
@@ -232,10 +232,19 @@ export const TeamManagement: React.FC = () => {