diff --git a/src/app/administrator/page.tsx b/src/app/administrator/page.tsx new file mode 100644 index 0000000..97b85b9 --- /dev/null +++ b/src/app/administrator/page.tsx @@ -0,0 +1,359 @@ +"use client"; +import React, { useState, useRef } from "react"; +type Role = "admin" | "user" | "editor"; +type User = { + email: string; + name: string; + role: Role; + password: string; +}; + +const initialUsers: User[] = [ // todo - add user reading function + { email: "john@example.com", name: "John Doe", role: "admin", password: "secret1" }, + { email: "jane@example.com", name: "Jane Smith", role: "user", password: "secret2" }, + { email: "bob@example.com", name: "Bob Brown", role: "editor", password: "secret3" }, + { email: "alice@example.com", name: "Alice Johnson", role: "user", password: "secret4" }, + { email: "eve@example.com", name: "Eve Black", role: "admin", password: "secret5" }, + { email: "dave@example.com", name: "Dave Clark", role: "user", password: "pw" }, + { email: "fred@example.com", name: "Fred Fox", role: "user", password: "pw" }, + { email: "ginny@example.com", name: "Ginny Hall", role: "editor", password: "pw" }, + { email: "harry@example.com", name: "Harry Lee", role: "admin", password: "pw" }, + { email: "ivy@example.com", name: "Ivy Volt", role: "admin", password: "pw" }, + { email: "kate@example.com", name: "Kate Moss", role: "editor", password: "pw" }, + { email: "leo@example.com", name: "Leo Garrison", role: "user", password: "pw" }, + { email: "isaac@example.com", name: "Isaac Yang", role: "user", password: "pw" }, +]; +const sortFields = [ // Sort box options + { label: "Name", value: "name" }, + { label: "Email", value: "email" }, +] as const; + +type SortField = typeof sortFields[number]["value"]; +type SortDir = "asc" | "desc"; +const dirLabels: Record = { asc: "ascending", desc: "descending" }; +const fieldLabels: Record = { name: "Name", email: "Email" }; + +export default function AdminPage() { + const [users, setUsers] = useState(initialUsers); + const [selectedEmail, setSelectedEmail] = useState(null); + + // Local edit state for editor form + const [editUser, setEditUser] = useState(null); + // Reset editUser when the selected user changes + React.useEffect(() => { + if (!selectedEmail) setEditUser(null); + else { + const user = users.find(u => u.email === selectedEmail); + setEditUser(user ? { ...user } : null); + } + }, [selectedEmail, users]); + + // Search/filter/sort state + const [searchField, setSearchField] = useState<"name" | "email">("name"); + const [searchText, setSearchText] = useState(""); + const [roleFilter, setRoleFilter] = useState("all"); + const [sortField, setSortField] = useState("name"); + const [sortDir, setSortDir] = useState("asc"); + // Dropdown states + const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); + const [sortDropdownOpen, setSortDropdownOpen] = useState(false); + const filterDropdownRef = useRef(null); + const sortDropdownRef = useRef(null); + + React.useEffect(() => { + const handleClick = (e: MouseEvent) => { + if ( + filterDropdownRef.current && !filterDropdownRef.current.contains(e.target as Node) + ) setFilterDropdownOpen(false); + if ( + sortDropdownRef.current && !sortDropdownRef.current.contains(e.target as Node) + ) setSortDropdownOpen(false); + }; + document.addEventListener("mousedown", handleClick); + return () => document.removeEventListener("mousedown", handleClick); + }, []); + + // Filtering, searching, sorting logic + const filteredUsers = users.filter( + (user) => roleFilter === "all" || user.role === roleFilter + ); + const searchedUsers = filteredUsers.filter(user => + user[searchField].toLowerCase().includes(searchText.toLowerCase()) + ); + const sortedUsers = [...searchedUsers].sort((a, b) => { + let cmp = a[sortField].localeCompare(b[sortField]); + return sortDir === "asc" ? cmp : -cmp; + }); + + // Form input change handler + const handleEditChange = (e: React.ChangeEvent) => { + if (!editUser) return; + const { name, value } = e.target; + setEditUser(prev => + prev ? { ...prev, [name]: value } : null + ); + }; + + // Update button logic (compare original selectedUser and editUser) + const selectedUser = users.find((u) => u.email === selectedEmail); + const isEditChanged = React.useMemo(() => { + if (!editUser || !selectedUser) return false; + // Compare primitive fields + return ( + editUser.name !== selectedUser.name || + editUser.role !== selectedUser.role || + editUser.password !== selectedUser.password + ); + }, [editUser, selectedUser]); + + // Update/save changes + const handleUpdate = (e: React.FormEvent) => { + e.preventDefault(); + if (!editUser) return; + setUsers(prev => + prev.map(u => + u.email === editUser.email ? { ...editUser } : u + ) + ); + // After successful update, update selectedUser local state + // (editUser will auto-sync due to useEffect on users) + }; + + // Delete user logic + const handleDelete = () => { + if (!selectedUser) return; + if (!window.confirm(`Are you sure you want to delete "${selectedUser.name}"? This cannot be undone.`)) return; + setUsers(prev => prev.filter(u => u.email !== selectedUser.email)); + setSelectedEmail(null); + setEditUser(null); + }; + + const allRoles: Role[] = ["admin", "user", "editor"]; + + // Tooltip handling for email field + const [showEmailTooltip, setShowEmailTooltip] = useState(false); + + return ( +
+
+ {/* SIDEBAR */} +
+
+ {/* Search Bar */} +
+ setSearchText(e.target.value)} + /> + +
+ {/* Filter and Sort Buttons */} +
+ {/* Filter */} +
+ + {filterDropdownOpen && ( +
+ + {allRoles.map(role => ( + + ))} +
+ )} +
+ {/* Sort */} +
+ + {sortDropdownOpen && ( +
+ {sortFields.map(opt => ( + + ))} +
+ )} +
+ {/* Asc/Desc Toggle */} + +
+ {/* Sort status text */} + + Users sorted by {fieldLabels[sortField]} {dirLabels[sortDir]} + + {/* USERS LIST: full height, scrollable */} +
    + {sortedUsers.map((user) => ( +
  • setSelectedEmail(user.email)} + className={`rounded-lg cursor-pointer border + ${selectedEmail === user.email ? "bg-blue-100 border-blue-400" : "hover:bg-gray-200 border-transparent"} + transition px-2 py-1 mb-1`} + > +
    + {user.name} + {user.role} +
    +
    + {user.email} +
    +
  • + ))} + {sortedUsers.length === 0 && ( +
  • No users found.
  • + )} +
+
+
+ {/* MAIN PANEL */} +
+ {editUser ? ( +
+

Edit User

+
+
+ + setShowEmailTooltip(true)} + onMouseLeave={() => setShowEmailTooltip(false)} + /> + {/* Custom tooltip */} + {showEmailTooltip && ( +
+ This field cannot be changed.
+ To change the email, delete and re-add the user. +
+ )} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ ) : ( +
+ Select a user... +
+ )} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/earthquakes/page.tsx b/src/app/earthquakes/page.tsx index 3b53d75..cd07d6c 100644 --- a/src/app/earthquakes/page.tsx +++ b/src/app/earthquakes/page.tsx @@ -3,7 +3,7 @@ import { useMemo, useState } from "react"; import useSWR from "swr"; -import Map from "@components/Map"; +import Map from "@components/map"; import Sidebar from "@components/Sidebar"; import { fetcher } from "@utils/fetcher"; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index de01756..c953752 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,7 +6,7 @@ import { action, createStore, StoreProvider } from 'easy-peasy'; import { Inter } from 'next/font/google'; import { StoreModel } from '@appTypes/StoreModel'; -import Navbar from '@components/Navbar'; +import Navbar from '@components/navbar'; const inter = Inter({ subsets: ["latin"], diff --git a/src/app/observatories/page.tsx b/src/app/observatories/page.tsx index f608af9..6b3d030 100644 --- a/src/app/observatories/page.tsx +++ b/src/app/observatories/page.tsx @@ -4,7 +4,7 @@ import { useMemo, useState } from "react"; import useSWR from "swr"; import Sidebar from "@/components/Sidebar"; -import Map from "@components/Map"; +import Map from "@components/map"; import { fetcher } from "@utils/fetcher"; export default function Observatories() { diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index d8722e8..b8a2414 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -18,7 +18,7 @@ export default function Navbar({}: // currencySelector, const user = useStoreState((state) => state.user); const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency); const setSelectedCurrency = useStoreActions((actions) => actions.currency.setSelectedCurrency); - const navOptions = useMemo(() => ["Earthquakes", "Observatories", "Shop"], []); + const navOptions = useMemo(() => ["Earthquakes", "Observatories", "Shop","Administrator"], []); // const navOptions = useMemo(() => ["Earthquakes"], []); const aboutDropdown = ["Contact Us", "Our Mission", "The Team"]; // { label: "Our Mission", path: "/our-mission" },