The Admin Panel provides secure access to administrative features, including email template previewing and project management. It uses password-based authentication with JWT tokens.
Overview
The admin system consists of two main pages:
Admin Login (src/pages/AdminLogin.tsx) - Authentication gateway
Email Preview (src/pages/EmailPreview.tsx) - Email template viewer
Security The admin panel uses JWT token authentication stored in localStorage. Access is restricted to authorized users only.
Admin Login
User Experience
Access : Navigate to /admin/login
Enter Password : Type admin password
Toggle Visibility : Click eye icon to show/hide password
Submit : Click “Entrar” button
Redirect : On success, redirect to email preview
Login Component
const AdminLogin = () => {
const [ password , setPassword ] = useState ( "" );
const [ error , setError ] = useState ( "" );
const [ isLoading , setIsLoading ] = useState ( false );
const [ showPassword , setShowPassword ] = useState ( false );
const navigate = useNavigate ();
const handleSubmit = async ( e : React . FormEvent ) => {
e . preventDefault ();
setIsLoading ( true );
setError ( "" );
try {
const response = await fetch ( "/api/auth/login" , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({ password }),
});
if ( response . ok ) {
const data = await response . json ();
localStorage . setItem ( "admin_token" , data . token );
navigate ( "/admin/email-preview" );
} else {
setError ( "Senha incorreta" );
}
} catch ( error ) {
setError ( "Erro ao fazer login. Tente novamente." );
} finally {
setIsLoading ( false );
}
};
};
Login UI
< div className = "min-h-screen flex items-center justify-center bg-gradient-to-br from-background via-card to-background" >
< div className = "w-full max-w-md p-8" >
< div className = "bg-card rounded-2xl border border-border shadow-xl p-8" >
{ /* Header with lock icon */ }
< div className = "text-center mb-8" >
< div className = "inline-flex items-center justify-center w-16 h-16 rounded-full bg-gradient-to-br from-primary/20 to-primary/10 mb-4" >
< Lock className = "w-8 h-8 text-primary" />
</ div >
< h1 className = "text-2xl font-bold" > Admin Login </ h1 >
< p className = "text-muted-foreground mt-2" >
Acesso restrito ao preview de emails
</ p >
</ div >
{ /* Password input with toggle */ }
< form onSubmit = { handleSubmit } >
< div className = "relative" >
< input
type = { showPassword ? "text" : "password" }
value = { password }
onChange = { ( e ) => setPassword ( e . target . value ) }
className = "w-full px-4 py-3 pr-12 rounded-xl bg-secondary border border-border"
placeholder = "Digite a senha de admin"
/>
< button
type = "button"
onClick = { () => setShowPassword ( ! showPassword ) }
className = "absolute right-3 top-1/2 -translate-y-1/2"
>
{ showPassword ? < EyeOff /> : < Eye /> }
</ button >
</ div >
{ /* Error message */ }
{ error && (
< div className = "p-3 rounded-xl bg-red-500/10 border border-red-500/20 text-red-500" >
{ error }
</ div >
) }
{ /* Submit button */ }
< Button type = "submit" disabled = { isLoading } >
< LogIn className = "w-4 h-4" />
{ isLoading ? "Entrando..." : "Entrar" }
</ Button >
</ form >
{ /* Back link */ }
< button onClick = { () => navigate ( "/" ) } >
← Voltar para o portfolio
</ button >
</ div >
</ div >
</ div >
The password input includes a show/hide toggle for better UX, allowing users to verify their input before submitting.
Email Preview Dashboard
Features
Template Switching : Toggle between notification and confirmation emails
Live Preview : View actual rendered HTML templates
Logout : Secure session termination
Navigation : Return to portfolio or admin login
Protected Route
The Email Preview page checks for authentication on mount:
const EmailPreview = () => {
const [ activeTemplate , setActiveTemplate ] = useState < "notification" | "confirmation" >( "notification" );
const [ isLoading , setIsLoading ] = useState ( true );
const navigate = useNavigate ();
useEffect (() => {
const token = localStorage . getItem ( "admin_token" );
if ( ! token ) {
navigate ( "/admin/login" ); // Redirect if not authenticated
} else {
setIsLoading ( false );
}
}, [ navigate ]);
// Component content...
};
The token is stored in localStorage, which means it persists across browser sessions. Users remain logged in until they explicitly log out.
Template Selector
Two buttons allow switching between email templates:
< div className = "flex gap-4" >
{ /* Notification template button */ }
< button
onClick = { () => setActiveTemplate ( "notification" ) }
className = { activeTemplate === "notification"
? "bg-primary/10 border-primary"
: "bg-card border-border"
}
>
< Mail className = "w-5 h-5 text-primary" />
< div >
< h3 > Email de Notificação </ h3 >
< p > Email que você recebe quando alguém envia uma mensagem </ p >
</ div >
</ button >
{ /* Confirmation template button */ }
< button
onClick = { () => setActiveTemplate ( "confirmation" ) }
className = { activeTemplate === "confirmation"
? "bg-primary/10 border-primary"
: "bg-card border-border"
}
>
< CheckCircle className = "w-5 h-5 text-primary" />
< div >
< h3 > Email de Confirmação </ h3 >
< p > Email automático enviado para quem enviou a mensagem </ p >
</ div >
</ button >
</ div >
Email Preview Iframe
Templates are loaded in an iframe for isolation:
const getIframeSrc = () => {
const token = localStorage . getItem ( "admin_token" );
const endpoint = activeTemplate === "notification"
? "/api/admin/email-preview/notification"
: "/api/admin/email-preview/confirmation" ;
return ` ${ endpoint } ?token= ${ token } ` ;
};
// Render iframe
< iframe
key = { activeTemplate } // Force re-render on template change
src = { getIframeSrc ()}
className = "w-full h-full border-0"
title = { `Preview do template ${ activeTemplate } ` }
sandbox = "allow-same-origin" // Security restriction
/>
The iframe uses the sandbox="allow-same-origin" attribute for security, preventing scripts from executing in the preview.
Email Viewer UI
< div className = "bg-card rounded-2xl border border-border shadow-xl overflow-hidden" >
{ /* Browser chrome mockup */ }
< div className = "bg-secondary/50 p-4 border-b border-border" >
< div className = "flex items-center gap-2" >
{ /* Traffic lights */ }
< div className = "flex gap-1" >
< div className = "w-3 h-3 rounded-full bg-red-500" ></ div >
< div className = "w-3 h-3 rounded-full bg-yellow-500" ></ div >
< div className = "w-3 h-3 rounded-full bg-green-500" ></ div >
</ div >
{ /* Recipient email */ }
< span className = "ml-2" >
{ activeTemplate === "notification"
? "[email protected] "
: "[email protected] " }
</ span >
</ div >
</ div >
{ /* Email content iframe */ }
< div className = "bg-white" style = { { height: "calc(100vh - 300px)" } } >
< iframe src = { getIframeSrc () } />
</ div >
</ div >
Info Box
< div className = "mt-6 p-4 rounded-xl bg-blue-500/10 border border-blue-500/20" >
< p className = "text-sm text-blue-600 dark:text-blue-400" >
< strong > 💡 Dica: </ strong > Estes são os templates que serão enviados
automaticamente quando alguém preencher o formulário de contato.
</ p >
</ div >
Authentication Flow
Login Process
User enters password
Frontend sends POST to /api/auth/login
Backend validates password against ADMIN_PASSWORD env var
Backend generates JWT token
Token returned to frontend
Token stored in localStorage
User redirected to email preview
Token Structure
// Backend generates token
const token = jwt . sign (
{ role: 'admin' },
process . env . JWT_SECRET ,
{ expiresIn: '7d' } // Token expires after 7 days
);
Protected Endpoint Access
// Frontend includes token in requests
const token = localStorage . getItem ( "admin_token" );
fetch ( `/api/admin/email-preview/notification?token= ${ token } ` );
// Backend validates token
app . get ( '/api/admin/email-preview/:type' , ( req , res ) => {
const token = req . query . token ;
try {
jwt . verify ( token , process . env . JWT_SECRET );
// Render email template
} catch ( error ) {
res . status ( 401 ). json ({ error: 'Unauthorized' });
}
});
Logout Process
const handleLogout = () => {
localStorage . removeItem ( "admin_token" );
navigate ( "/admin/login" );
};
Logout only removes the token from localStorage. It doesn’t invalidate the token server-side. If the token is compromised before logout, it remains valid until expiration.
Email Templates
The admin panel previews two email types:
1. Notification Email
Sent to: Portfolio owner (you)
Triggered by: New contact form submission
Purpose: Alert you of new inquiries
Sample data:
{
"name" : "João Silva" ,
"email" : "[email protected] " ,
"subject" : "Orçamento para desenvolvimento de site" ,
"message" : "Olá, gostaria de um orçamento..."
}
2. Confirmation Email
Sent to: Contact form submitter
Triggered by: Successful form submission
Purpose: Confirm receipt of message
Features:
Professional HTML template
Personalized with sender’s name
Includes submitted subject
Sets response time expectations
Email Customization Email templates can be customized in the backend. See Email Templates Guide for HTML editing instructions.
Security Considerations
Password Storage
# .env file
ADMIN_PASSWORD = your-secure-password-here
JWT_SECRET = your-jwt-secret-key-here
NEVER commit .env files to version control. Use strong, unique passwords for production deployments.
Token Security
Best practices:
Use HTTPS in production
Set reasonable token expiration (7 days default)
Don’t expose tokens in URLs (use headers when possible)
Clear tokens on logout
Validate tokens on every request
CSRF Protection
For enhanced security, implement CSRF tokens:
// Backend: Generate CSRF token
const csrfToken = crypto . randomBytes ( 32 ). toString ( 'hex' );
// Frontend: Include in requests
fetch ( '/api/auth/login' , {
headers: {
'X-CSRF-Token' : csrfToken
}
});
Routing Configuration
Add admin routes to your React Router setup:
import { BrowserRouter , Routes , Route } from 'react-router-dom' ;
import AdminLogin from './pages/AdminLogin' ;
import EmailPreview from './pages/EmailPreview' ;
import Home from './pages/Home' ;
function App () {
return (
< BrowserRouter >
< Routes >
< Route path = "/" element = { < Home /> } />
< Route path = "/admin/login" element = { < AdminLogin /> } />
< Route path = "/admin/email-preview" element = { < EmailPreview /> } />
</ Routes >
</ BrowserRouter >
);
}
Future Admin Features
Potential expansions for the admin panel:
Project Management : Add/edit/delete projects via UI
Contact Form Submissions : View all received messages
Analytics Dashboard : Traffic and engagement metrics
Content Editor : Edit hero section text and images
Media Library : Upload and manage images/videos
Settings Panel : Configure site-wide options
Troubleshooting
Can’t Log In
Check password : Verify ADMIN_PASSWORD in .env
Check backend : Ensure API is running
Check network : Open browser DevTools → Network tab
Check CORS : Ensure backend allows frontend origin
Token Expired
Log out and log back in
Token is automatically refreshed
Consider increasing expiration time in backend
Email Preview Not Loading
Check authentication : Token may be invalid
Check backend routes : Verify email preview endpoints
Check template files : Ensure HTML templates exist
Check browser console : Look for iframe errors
Stuck in Loading State
// Add timeout to prevent infinite loading
useEffect (() => {
const timer = setTimeout (() => {
if ( isLoading ) {
setError ( 'Timeout: Unable to verify authentication' );
setIsLoading ( false );
}
}, 5000 );
return () => clearTimeout ( timer );
}, [ isLoading ]);
API Endpoints
Authentication
POST /api/auth/login
Body: { password: string }
Response: { token: string }
Email Previews
GET /api/admin/email-preview/notification?token={jwt}
Response: HTML email template
GET /api/admin/email-preview/confirmation?token={jwt}
Response: HTML email template
For full API documentation, see Admin API Reference .