Skip to main content
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:
  1. Admin Login (src/pages/AdminLogin.tsx) - Authentication gateway
  2. 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

  1. Access: Navigate to /admin/login
  2. Enter Password: Type admin password
  3. Toggle Visibility: Click eye icon to show/hide password
  4. Submit: Click “Entrar” button
  5. 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

  1. User enters password
  2. Frontend sends POST to /api/auth/login
  3. Backend validates password against ADMIN_PASSWORD env var
  4. Backend generates JWT token
  5. Token returned to frontend
  6. Token stored in localStorage
  7. 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
For project management implementation, see Adding Admin CRUD guide.

Troubleshooting

Can’t Log In

  1. Check password: Verify ADMIN_PASSWORD in .env
  2. Check backend: Ensure API is running
  3. Check network: Open browser DevTools → Network tab
  4. Check CORS: Ensure backend allows frontend origin

Token Expired

  1. Log out and log back in
  2. Token is automatically refreshed
  3. Consider increasing expiration time in backend

Email Preview Not Loading

  1. Check authentication: Token may be invalid
  2. Check backend routes: Verify email preview endpoints
  3. Check template files: Ensure HTML templates exist
  4. 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.

Build docs developers (and LLMs) love