diff --git a/package-lock.json b/package-lock.json
index 5f7156d..ec363f9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14101,12 +14101,15 @@
"node": ">= 0.8.0"
}
},
+<<<<<<< HEAD
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"optional": true
},
+=======
+>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
diff --git a/src/App.js b/src/App.js
index f324308..8d7c5d9 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,14 +1,45 @@
+<<<<<<< HEAD
import { Routes, Route } from "react-router-dom";
+=======
+import "./App.css";
+
+import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
+
+import Dashboard from "./Dashboard";
+
+import Login from "./Login";
+>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
import CameraKtp from "./KTPScanner";
import Dashboard from "./Dashboard";
+import "./App.css";
+
+// ✅ Komponen proteksi route
+const ProtectedRoute = ({ element }) => {
+ const token = localStorage.getItem("token");
+ return token ? element : ;
+};
+
function App() {
return (
+<<<<<<< HEAD
} />
} />
+=======
+
+
+ } />
+ } />
+ } />}
+ />
+
+
+>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
);
}
diff --git a/src/Dashboard.js b/src/Dashboard.js
index 92a1a20..f349952 100644
--- a/src/Dashboard.js
+++ b/src/Dashboard.js
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
import React from "react";
import styles from "./Dashboard.module.css";
import Header from "./components/Header";
@@ -17,8 +18,280 @@ const Dashboard = () => {
+=======
+import React, { useState, useRef, useEffect } from "react";
+import styles from "./Dashboard.module.css";
+import { useNavigate } from "react-router-dom";
+// Pastikan Anda sudah menginstal Recharts: npm install recharts
+// import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'; // Contoh Recharts
+
+const Dashboard = () => {
+ const navigate = useNavigate();
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+ const menuRef = useRef(null);
+
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ const [selectedRole, setSelectedRole] = useState("officer");
+ const [successMessage, setSuccessMessage] = useState("");
+ const [errorMessage, setErrorMessage] = useState("");
+ const [user, setUser] = useState({});
+ const [totalFilesSentToday, setTotalFilesSentToday] = useState(0);
+ const [totalFilesSentMonth, setTotalFilesSentMonth] = useState(0);
+ const [totalFilesSentOverall, setTotalFilesSentOverall] = useState(0);
+ const [officerPerformanceData, setOfficerPerformanceData] = useState([]);
+
+ useEffect(() => {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ window.location.href = "/login";
+ }
+ }, []);
+
+ useEffect(() => {
+ const verifyTokenAndFetchData = async () => {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ window.location.href = "/login";
+ return;
+ }
+
+ try {
+ const response = await fetch(
+ "https://bot.kediritechnopark.com/webhook/dashboard/psi",
+ {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+
+ const data = await response.json();
+
+ if (!response.ok || !data[0].payload.username) {
+ throw new Error("Unauthorized");
+ }
+
+ setUser(data[0].payload);
+
+ // Pastikan API Anda mengembalikan data ini, contoh:
+ // data[0].payload.stats = { today: 120, month: 2500, overall: 15000 };
+ // data[0].payload.officerPerformance = [{ name: "Budi", filesSent: 50 }, { name: "Ani", filesSent: 70 }];
+
+ if (data[0].payload.stats) {
+ setTotalFilesSentToday(data[0].payload.stats.today);
+ setTotalFilesSentMonth(data[0].payload.stats.month);
+ setTotalFilesSentOverall(data[0].payload.stats.overall);
+ }
+
+ if (data[0].payload.officerPerformance) {
+ setOfficerPerformanceData(data[0].payload.officerPerformance);
+ }
+
+ } catch (error) {
+ console.error("Token tidak valid:", error.message);
+ localStorage.removeItem("token");
+ window.location.href = "/login";
+ }
+ };
+
+ verifyTokenAndFetchData();
+ }, []);
+
+ const handleLogout = () => {
+ localStorage.removeItem("token");
+ localStorage.removeItem("user");
+ localStorage.removeItem("role");
+ window.location.reload();
+ };
+
+ const handleAddOfficer = async (e) => {
+ e.preventDefault();
+
+ const token = localStorage.getItem("token");
+
+ try {
+ const response = await fetch(
+ "https://bot.kediritechnopark.com/webhook/add-officer",
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ username,
+ password,
+ role: selectedRole,
+ }),
+ }
+ );
+
+ const data = await response.json();
+
+ if (!response.ok || data.success === false) {
+ throw new Error(data.message || "Gagal menambahkan officer");
+ }
+
+ setSuccessMessage("Officer berhasil ditambahkan");
+ setUsername("");
+ setPassword("");
+ setSelectedRole("officer");
+ setErrorMessage("");
+ // Pertimbangkan untuk memuat ulang data performa jika penambahan officer baru mempengaruhi grafik
+ } catch (error) {
+ setErrorMessage(error.message || "Gagal menambahkan officer");
+ setSuccessMessage("");
+ }
+ };
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (menuRef.current && !menuRef.current.contains(event.target)) {
+ setIsMenuOpen(false);
+ }
+ };
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, []);
+
+ return (
+
+
+
+

+
PSI Dashboard
+
+
+
+
{user.username || "Guest"}
+
+ {isMenuOpen && (
+
+
+
+
+ )}
+
+
+
+
+ {/* Summary Cards */}
+
+
+
Total Hari Ini
+
{totalFilesSentToday}
+
+
+
Total Bulan Ini
+
{totalFilesSentMonth}
+
+
+
Total Keseluruhan
+
{totalFilesSentOverall}
+
+
+
+ {/* Grid for Form (Admin) and Chart (Admin & Officer) */}
+
+ {user.role === "admin" && ( /* Render form hanya jika admin */
+
+
Tambah Officer Baru
+
+
+ {successMessage &&
{successMessage}
}
+ {errorMessage &&
{errorMessage}
}
+
+ )}
+
+ {/* Chart Section - Visible to both Admin and Officer */}
+
+
Performa Pengiriman File Petugas
+ {officerPerformanceData.length > 0 ? (
+ // Contoh implementasi Recharts:
+ /*
+
+
+
+
+
+
+
+
+ */
+
+ Grafik performa petugas akan ditampilkan di sini.
+ (Integrasikan library grafik seperti Recharts/Chart.js)
+
+ ) : (
+
Tidak ada data performa petugas untuk ditampilkan.
+ )}
+
+
+
+
+
© 2025 Kediri Technopark
+>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
);
};
+<<<<<<< HEAD
export default Dashboard;
+=======
+export default Dashboard;
+>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
diff --git a/src/Dashboard.module.css b/src/Dashboard.module.css
index 18c95cf..619d416 100644
--- a/src/Dashboard.module.css
+++ b/src/Dashboard.module.css
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
.dashboard {
display: flex;
width: 100%;
@@ -24,3 +25,464 @@
flex-direction: column;
}
}
+=======
+/* Variabel Warna */
+:root {
+ --primary-red: #E53935;
+ --secondary-red: #EF5350;
+ --dark-red: #C62828;
+ --light-gray: #EEEEEE;
+ --dark-gray: #424242;
+ --white: #FFFFFF;
+ --success-green: #4CAF50;
+ --warning-yellow: #FFC107;
+ --text-color-light: #F5F5F5; /* Teks terang di latar belakang gelap */
+ --text-color-dark: #212121; /* Teks gelap di latar belakang terang */
+}
+
+/* Base Styles & Reset */
+* {
+ box-sizing: border-box; /* Pastikan padding dan border tidak menambah lebar elemen */
+}
+
+body {
+ margin: 0;
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* Font lebih modern */
+ line-height: 1.6;
+ color: var(--text-color-dark);
+}
+
+.dashboardContainer {
+ background-color: var(--light-gray);
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+/* --- Header --- */
+.dashboardHeader {
+ background-color: var(--primary-red);
+ color: var(--text-color-light);
+ padding: 1rem; /* Menggunakan rem */
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+}
+
+.logoAndTitle {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0; /* Mencegah logo dan judul mengecil terlalu banyak */
+}
+
+.logoAndTitle img {
+ width: 2.5rem; /* Ukuran rem */
+ height: 2.5rem;
+ border-radius: 50%;
+ margin-right: 0.8rem;
+}
+
+.dashboardHeader .h1 {
+ margin: 0;
+ font-size: 1.5rem; /* Ukuran rem untuk mobile */
+ font-weight: bold;
+ white-space: nowrap; /* Mencegah judul patah baris */
+}
+
+/* Dropdown Menu */
+.dropdownContainer {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: 0.8rem;
+ flex-shrink: 0; /* Mencegah mengecil */
+}
+
+.userDisplayName {
+ color: var(--text-color-light);
+ font-weight: bold;
+ font-size: 0.9rem;
+ white-space: nowrap; /* Pastikan nama pengguna tidak patah baris */
+}
+
+.dropdownToggle {
+ background-color: var(--dark-red);
+ color: var(--text-color-light);
+ border: none;
+ padding: 0.5rem 0.75rem; /* Padding rem */
+ border-radius: 0.3rem;
+ cursor: pointer;
+ font-size: 1rem;
+ transition: background-color 0.3s ease;
+ min-width: 3rem; /* Pastikan tombol tidak terlalu kecil */
+}
+
+.dropdownToggle:hover {
+ background-color: var(--secondary-red);
+}
+
+.dropdownMenu {
+ position: absolute;
+ top: calc(100% + 0.5rem); /* Posisikan di bawah tombol dengan jarak */
+ right: 0;
+ background-color: var(--white);
+ border-radius: 0.3rem;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+ z-index: 10;
+ display: flex;
+ flex-direction: column;
+ min-width: 8rem; /* Lebar minimum */
+ overflow: hidden; /* Pastikan item tidak keluar */
+}
+
+.dropdownItem {
+ background: none;
+ border: none;
+ padding: 0.6rem 0.8rem;
+ text-align: left;
+ cursor: pointer;
+ color: var(--text-color-dark);
+ transition: background-color 0.3s ease;
+ font-size: 0.9rem;
+ white-space: nowrap; /* Mencegah item menu patah baris */
+}
+
+.dropdownItem:hover {
+ background-color: var(--light-gray);
+}
+
+/* --- Main Content --- */
+.mainContent {
+ flex-grow: 1;
+ padding: 1.25rem; /* Padding rem */
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem; /* Jarak antar bagian utama */
+}
+
+/* Summary Cards Container */
+.summaryCardsContainer {
+ display: flex;
+ flex-wrap: wrap; /* Mengizinkan kartu melipat */
+ gap: 1rem; /* Jarak antar kartu */
+ justify-content: center; /* Tengahkan kartu di mobile */
+ padding: 1rem;
+ background-color: var(--white);
+ border-radius: 0.6rem;
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
+}
+
+.summaryCard {
+ background-color: var(--light-gray);
+ color: var(--text-color-dark);
+ padding: 1rem;
+ border-radius: 0.5rem;
+ border: 1px solid var(--secondary-red);
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ text-align: center;
+ flex: 1 1 calc(50% - 0.75rem); /* Di layar mobile, 2 kartu per baris */
+ min-width: 120px; /* Batasan agar tidak terlalu kecil */
+ max-width: 250px; /* Batasan agar tidak terlalu lebar di mobile */
+}
+
+.summaryCard h3 {
+ margin-top: 0;
+ font-size: 0.9rem;
+ color: var(--dark-red);
+}
+
+.summaryCard p {
+ font-size: 1.6rem;
+ font-weight: bold;
+ color: var(--dark-gray);
+ margin-bottom: 0;
+}
+
+/* Dashboard Grid for Form and Chart */
+.dashboardGrid {
+ display: grid;
+ grid-template-columns: 1fr; /* Default: satu kolom untuk mobile */
+ gap: 1.5rem; /* Jarak antar bagian */
+ flex-grow: 1; /* Agar grid bisa membesar mengisi sisa ruang */
+}
+
+.formSection,
+.chartSection {
+ background-color: var(--white);
+ padding: 1.5rem; /* Padding rem */
+ border-radius: 0.6rem;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
+}
+
+.formSection h2,
+.chartSection h2 {
+ color: var(--dark-red);
+ margin-top: 0;
+ margin-bottom: 1.25rem;
+ text-align: center;
+ font-size: 1.4rem;
+}
+
+.form label {
+ display: block;
+ margin-bottom: 0.8rem;
+ color: var(--text-color-dark);
+ font-weight: bold;
+ font-size: 0.95rem;
+}
+
+.form input[type="text"],
+.form input[type="password"],
+.form select {
+ width: 100%;
+ padding: 0.75rem;
+ margin-top: 0.3rem;
+ border: 1px solid var(--light-gray);
+ border-radius: 0.3rem;
+ font-size: 1rem;
+ transition: border-color 0.3s ease, box-shadow 0.3s ease;
+}
+
+.form input[type="text"]:focus,
+.form input[type="password"]:focus,
+.form select:focus {
+ border-color: var(--primary-red);
+ box-shadow: 0 0 0 3px rgba(229, 57, 53, 0.2); /* Ring focus */
+ outline: none;
+}
+
+.submitButton {
+ background-color: var(--primary-red);
+ color: var(--white);
+ border: none;
+ padding: 0.8rem 1.5rem;
+ border-radius: 0.5rem;
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: bold;
+ margin-top: 1.25rem;
+ width: 100%;
+ transition: background-color 0.3s ease, transform 0.2s ease;
+}
+
+.submitButton:hover {
+ background-color: var(--secondary-red);
+ transform: translateY(-2px); /* Efek sedikit naik */
+}
+
+/* Messages */
+.success {
+ background-color: #e6ffe6;
+ color: var(--success-green);
+ border: 1px solid var(--success-green);
+ padding: 0.8rem;
+ border-radius: 0.3rem;
+ margin-top: 1rem;
+ text-align: center;
+ font-size: 0.9rem;
+}
+
+.error {
+ background-color: #ffe6e6;
+ color: var(--primary-red);
+ border: 1px solid var(--primary-red);
+ padding: 0.8rem;
+ border-radius: 0.3rem;
+ margin-top: 1rem;
+ text-align: center;
+ font-size: 0.9rem;
+}
+
+.warning {
+ background-color: #fffbe6;
+ color: var(--warning-yellow);
+ border: 1px solid var(--warning-yellow);
+ padding: 1rem;
+ border-radius: 0.3rem;
+ margin-top: 1.25rem;
+ text-align: center;
+ font-weight: bold;
+ font-size: 1rem;
+}
+
+/* Footer */
+.footer {
+ background-color: var(--dark-red);
+ color: var(--text-color-light);
+ text-align: center;
+ padding: 0.75rem;
+ margin-top: auto;
+ font-size: 0.85rem;
+}
+
+.chartPlaceholder {
+ background-color: var(--light-gray);
+ height: 20rem; /* Menggunakan rem */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: var(--dark-gray);
+ font-style: italic;
+ border-radius: 0.6rem;
+ border: 1px dashed var(--secondary-red);
+ font-size: 1rem;
+}
+
+/* --- Media Queries for Tablets and Desktops --- */
+
+/* Tablet-sized screens and up */
+@media (min-width: 768px) {
+ .dashboardHeader {
+ padding: 1rem 2rem;
+ }
+
+ .logoAndTitle img {
+ width: 3rem;
+ height: 3rem;
+ }
+
+ .dashboardHeader .h1 {
+ font-size: 1.8rem;
+ }
+
+ .userDisplayName {
+ font-size: 1rem;
+ }
+
+ .dropdownToggle {
+ padding: 0.6rem 1rem;
+ font-size: 1rem;
+ }
+
+ .dropdownMenu {
+ min-width: 10rem;
+ }
+
+ .dropdownItem {
+ font-size: 1rem;
+ }
+
+ .mainContent {
+ padding: 1.5rem 2.5rem; /* Padding lebih besar di tablet/desktop */
+ gap: 2rem;
+ }
+
+ .summaryCardsContainer {
+ justify-content: flex-start; /* Sejajarkan ke kiri di tablet/desktop */
+ padding: 1.5rem;
+ }
+
+ .summaryCard {
+ flex: 1 1 calc(33.33% - 1rem); /* 3 kartu per baris di tablet */
+ max-width: 200px; /* Batasi lebar kartu agar tetap rapi */
+ }
+
+ .summaryCard h3 {
+ font-size: 1rem;
+ }
+
+ .summaryCard p {
+ font-size: 2rem;
+ }
+
+ .dashboardGrid {
+ grid-template-columns: 1fr 2fr; /* Dua kolom: form (1/3), chart (2/3) */
+ gap: 2rem;
+ }
+
+ .formSection,
+ .chartSection {
+ padding: 2rem;
+ }
+
+ .formSection h2,
+ .chartSection h2 {
+ font-size: 1.6rem;
+ }
+
+ .form label {
+ font-size: 1rem;
+ }
+
+ .form input[type="text"],
+ .form input[type="password"],
+ .form select {
+ padding: 0.8rem;
+ font-size: 1rem;
+ }
+
+ .submitButton {
+ padding: 1rem 1.8rem;
+ font-size: 1.1rem;
+ }
+
+ .warning {
+ font-size: 1rem;
+ }
+
+ .chartPlaceholder {
+ height: 25rem; /* Tinggi placeholder lebih besar di tablet/desktop */
+ }
+}
+
+/* Desktop-sized screens and up */
+@media (min-width: 1024px) {
+ .dashboardHeader {
+ padding: 1.5rem 3rem;
+ }
+
+ .logoAndTitle img {
+ width: 3.5rem;
+ height: 3.5rem;
+ }
+
+ .dashboardHeader .h1 {
+ font-size: 2.2rem;
+ }
+
+ .mainContent {
+ padding: 2rem 4rem;
+ gap: 2.5rem;
+ }
+
+ .summaryCardsContainer {
+ padding: 2rem;
+ }
+
+ .summaryCard {
+ flex: 0 0 auto; /* Ukuran tetap di desktop */
+ min-width: 180px;
+ }
+
+ .dashboardGrid {
+ gap: 2.5rem;
+ }
+
+ .formSection,
+ .chartSection {
+ padding: 2.5rem;
+ }
+
+ .formSection h2,
+ .chartSection h2 {
+ font-size: 1.8rem;
+ }
+
+ .submitButton {
+ padding: 1.1rem 2rem;
+ }
+
+ .chartPlaceholder {
+ height: 30rem;
+ }
+}
+
+/* Jika hanya officer, pastikan chart mengambil seluruh lebar kolom */
+/* Ini sebenarnya sudah ditangani oleh grid secara default, tapi bisa diperjelas */
+@media (min-width: 768px) {
+ .dashboardGrid > *:only-child { /* Jika hanya ada satu anak elemen di dalam dashboardGrid */
+ grid-column: 1 / -1; /* Ambil seluruh lebar dari kolom pertama hingga terakhir */
+ }
+}
+>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
diff --git a/src/Login.js b/src/Login.js
new file mode 100644
index 0000000..fd879d3
--- /dev/null
+++ b/src/Login.js
@@ -0,0 +1,84 @@
+import React, { useState } from "react";
+import styles from "./Login.module.css";
+
+const Login = () => {
+ const [formData, setFormData] = useState({
+ username: "",
+ password: "",
+ });
+
+ const [error, setError] = useState("");
+
+ const handleChange = (e) => {
+ setFormData({ ...formData, [e.target.name]: e.target.value });
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ try {
+ const loginResponse = await fetch(
+ "https://bot.kediritechnopark.com/webhook/login/psi",
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(formData),
+ }
+ );
+
+ const loginDataRaw = await loginResponse.json();
+ const loginData = Array.isArray(loginDataRaw)
+ ? loginDataRaw[0]
+ : loginDataRaw;
+
+ if (loginData?.success && loginData?.token) {
+ localStorage.setItem("token", loginData.token);
+ window.location.href = "/dashboard";
+ } else {
+ setError(loginData?.message || "Username atau password salah");
+ }
+ } catch (err) {
+ console.error("Login Error:", err);
+ setError("Gagal terhubung ke server");
+ }
+ };
+
+ return (
+
+
+

+
Dermalounge AI Admin Login
+
+ Silakan masuk untuk melanjutkan ke dashboard
+
+
+
© 2025 Kediri Technopark
+
+
+ );
+};
+
+export default Login;
diff --git a/src/Login.module.css b/src/Login.module.css
new file mode 100644
index 0000000..3ee3f1f
--- /dev/null
+++ b/src/Login.module.css
@@ -0,0 +1,73 @@
+.loginContainer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ background: #f0f2f5;
+ width: 100vw;
+}
+
+.loginBox {
+ background: #fff;
+ padding: 40px;
+ border-radius: 10px;
+ box-shadow: 0 4px 25px rgba(0, 0, 0, 0.1);
+ text-align: center;
+ width: 100%;
+ max-width: 400px;
+}
+
+.logo {
+ width: 80px;
+ margin-bottom: 20px;
+}
+
+.h1 {
+ margin-bottom: 10px;
+ font-size: 24px;
+ color: #333;
+}
+
+.subtitle {
+ font-size: 14px;
+ color: #777;
+ margin-bottom: 30px;
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+}
+
+.input {
+ padding: 10px 15px;
+ margin-bottom: 15px;
+ border: 1px solid #ccc;
+ border-radius: 6px;
+ font-size: 16px;
+}
+
+.button {
+ background-color: #337f83;
+ color: white;
+ border: none;
+ padding: 12px;
+ border-radius: 6px;
+ font-size: 16px;
+ cursor: pointer;
+}
+
+.button:hover {
+ background-color: #3c9a9f;
+}
+
+.error {
+ color: red;
+ margin-bottom: 10px;
+}
+
+.footer {
+ margin-top: 20px;
+ font-size: 12px;
+ color: #aaa;
+}