Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ce490f0ef | ||
|
|
c5f8c848a5 | ||
|
|
6c0664d05a | ||
|
|
43cc39fd62 |
@@ -18,6 +18,8 @@ import Volcano from "@/pages/volcano/Volcano.jsx";
|
||||
import GlobalMap from "./pages/maps/GlobalMap.jsx";
|
||||
import Admin from "./pages/admin/Admin.jsx";
|
||||
import MyDashboard from "./pages/home/tabs/MyDashboard.jsx";
|
||||
import Settings from "./pages/auth/Settings.jsx";
|
||||
import WebAdmin from "./pages/auth/WebAdmin.jsx";
|
||||
|
||||
function AppFoundation() {
|
||||
const recipe = useRecipe({ key: "dashboard" });
|
||||
@@ -81,6 +83,9 @@ function App() {
|
||||
<Route path="daily" element={<Placeholder name="Daily Activity" />} />
|
||||
</Route>
|
||||
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="/web-admin" element={<WebAdmin />} />
|
||||
|
||||
<Route path="/global-map" element={<GlobalMap />} />
|
||||
<Route path="/regional-map" element={<RegionalMap />} />
|
||||
<Route path="/volcano" element={<Volcano />} />
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { Flex, Text, Input, Field, Button, Separator, NativeSelect, Image } from '@chakra-ui/react';
|
||||
import useAuthStore from "../../store/authStore.js";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function Settings() {
|
||||
const { user } = useAuthStore();
|
||||
const { register, handleSubmit, errors } = useForm();
|
||||
const [value, setValue] = useState(undefined);
|
||||
const updateProfile = useAuthStore((state) => state.updateProfile);
|
||||
|
||||
const onSubmit = (data, event) => {
|
||||
// TODO: check for identical usernames
|
||||
event.preventDefault();
|
||||
console.log(data);
|
||||
if (data.username && data.username.length !== "") updateProfile(data);
|
||||
}
|
||||
|
||||
const ivansRequest = () => {
|
||||
console.log(user.username, "requested ivans permissions");
|
||||
// TODO: log users request in database here which will then display request in web-admin page
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Flex direction="column" w="100%" h="100%" p="15rem" pt="3rem" gap="5rem">
|
||||
|
||||
{/* CONTENT */}
|
||||
<Flex direction="column" w="100%" h="100%" gap={4}>
|
||||
{/* header */}
|
||||
<Text fontWeight="semibold" fontSize="xl">Settings</Text>
|
||||
|
||||
<Separator borderColor="gray.300"/>
|
||||
|
||||
{/* personal info */}
|
||||
<Flex direction="column" w="100%" gap={4}>
|
||||
<Text fontWeight="semibold" fontSize="lg">Personal Information</Text>
|
||||
{/* profile picture & name */}
|
||||
<Flex direction="row" w="100%" align="center" justify="space-between" gap={5}>
|
||||
<Flex direction="row" w="100%" align="center" gap={5}>
|
||||
<Image borderRadius="full" boxSize="100px" src="https://i.pravatar.cc/300?img=10" />
|
||||
<Flex direction="column">
|
||||
<Text fontWeight="semibold">Name</Text>
|
||||
<Text pl={2}>{user.firstName || "first name"}, {user.lastName || "last name"}</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Button variant="subtle" size="sm">Update photo</Button>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="row" w="100%" gap={5}>
|
||||
<Field.Root>
|
||||
<Field.Label fontWeight="semibold">Username</Field.Label>
|
||||
<Input {...register("username")} placeholder={user.username} variant="subtle" />
|
||||
</Field.Root>
|
||||
|
||||
<Field.Root>
|
||||
<Field.Label fontWeight="semibold">Email</Field.Label>
|
||||
<Input {...register("email")} type="email" placeholder={user.email || "example@usgs.gov"} variant="subtle" />
|
||||
</Field.Root>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="row" w="100%" gap={5}>
|
||||
<Field.Root pb={8}>
|
||||
<Field.Label fontWeight="semibold">Descipline</Field.Label>
|
||||
<NativeSelect.Root size="sm" variant="subtle">
|
||||
<NativeSelect.Field
|
||||
w="100%"
|
||||
h="2.5rem"
|
||||
placeholder="Select option"
|
||||
{...register("discipline")}
|
||||
>
|
||||
<option value="RS">Remote Sensing</option>
|
||||
<option value="CS">Computer Science</option>
|
||||
<option value="Engineering">Engineering</option>
|
||||
<option value="HR">Human Resources (HR)</option>
|
||||
</NativeSelect.Field>
|
||||
<NativeSelect.Indicator />
|
||||
</NativeSelect.Root>
|
||||
<Field.ErrorText></Field.ErrorText>
|
||||
</Field.Root>
|
||||
<Flex w="100%"></Flex> {/* filler so that discipline dropdown sizes correctly */}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Separator borderColor="gray.300"/>
|
||||
|
||||
{/* permission requests */}
|
||||
<Flex direction="column" gap={2}>
|
||||
<Text fontWeight="semibold" fontSize="lg">Permissions</Text>
|
||||
<Flex direction="row" w="100%" align="center" justify="space-between" pl={2}>
|
||||
<Text>Request access to IVANS</Text>
|
||||
<Button variant="subtle" size="sm" onClick={ivansRequest}>IVANS Request</Button>
|
||||
</Flex>
|
||||
<Flex direction="row" w="100%" align="center" justify="space-between" pl={2}>
|
||||
<Text>Request access to Remote Sensing</Text>
|
||||
<Button variant="subtle" size="sm" onClick={ivansRequest}>Remote Sensing Request</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Separator borderColor="gray.300" />
|
||||
|
||||
{/* themes */}
|
||||
<Flex direction="column">
|
||||
<Text fontWeight="semibold" fontSize="lg">Theme</Text>
|
||||
<Flex direction="row" align="center" justify="space-between" pl={2}>
|
||||
<Text fontSize="md">Appearance</Text>
|
||||
<NativeSelect.Root size="sm" w="10rem" variant="subtle">
|
||||
<NativeSelect.Field
|
||||
placeholder="Select option"
|
||||
{...register("theme")}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.currentTarget.value)}
|
||||
>
|
||||
<option value="Light">Light</option>
|
||||
<option value="Dark">Dark</option>
|
||||
</NativeSelect.Field>
|
||||
<NativeSelect.Indicator />
|
||||
</NativeSelect.Root>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{/* SAVE-ALL PREFERENCES BUTTON */}
|
||||
{/* TODO: grey out button until changes are detected */}
|
||||
<Button type="submit" w="150px" variant="subtle" borderRadius="lg" alignSelf="center">
|
||||
Save Changes
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
import {Flex, Separator, Text, Table, IconButton, NativeSelect, Button} from "@chakra-ui/react";
|
||||
import { useMemo } from "react";
|
||||
import { useForm, useFieldArray } from "react-hook-form";
|
||||
import { Check, X, PenLine } from 'lucide-react';
|
||||
import VdapApi from "../../hooks/useVdapApi.js";
|
||||
import useBuildUrl from "../../hooks/useBuildUrl.js";
|
||||
|
||||
export default function WebAdmin() {
|
||||
// TODO: add below api endpoint
|
||||
// const {data: users, isLoading, error} = VdapApi('/api/v2/vdap_web_users');
|
||||
|
||||
const users = useMemo(() => [
|
||||
{
|
||||
"username": "vkuchenik",
|
||||
"firstName": "Venessa",
|
||||
"lastName": "Kuchenik",
|
||||
"permissions": {
|
||||
"ivans": 2, // 2 => requested permission
|
||||
"rs": 0, // 0 => no permissions and not requested
|
||||
},
|
||||
"team": "Computer Science",
|
||||
"group": "Super User",
|
||||
},
|
||||
{
|
||||
"username": "amack",
|
||||
"firstName": "Adeiline",
|
||||
"lastName": "Mack",
|
||||
"permissions": {
|
||||
"ivans": 2, // 0 => no permissions and not requested
|
||||
"rs": 2, // 1 => has permission
|
||||
},
|
||||
"team": "Remote Sensing",
|
||||
"group": "Member",
|
||||
}
|
||||
], []);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
control,
|
||||
errors,
|
||||
formState: { isDirty }
|
||||
} = useForm({
|
||||
defaultValues: {users: users}
|
||||
});
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name: "users" // Matches the key in defaultValues
|
||||
})
|
||||
|
||||
const grant = (user, category) => {
|
||||
// 1. post data via api
|
||||
// 2. fetch the new data via api again to force re-render
|
||||
console.log(user.username, "was granted", category, "permission");
|
||||
}
|
||||
|
||||
const deny = (user, category) => {
|
||||
console.log(user.username, "was denied", category, "permission");
|
||||
}
|
||||
|
||||
const users_req_ivans = useMemo(() => {
|
||||
return users.filter(user => user.permissions.ivans === 2);
|
||||
}, [users]);
|
||||
|
||||
const users_req_rs = useMemo(() => {
|
||||
return users.filter(user => user.permissions.rs === 2);
|
||||
}, [users]);
|
||||
|
||||
const onSubmit = (data, event) => {
|
||||
event.preventDefault();
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Flex direction="column" w="100%" h="100%" p="15rem" pt="3rem" gap="3rem">
|
||||
|
||||
{/* CONTENT */}
|
||||
<Flex direction="column" w="100%" gap={4}>
|
||||
{/* Header */}
|
||||
<Text fontSize="xl" fontWeight="bold">Web Admin Panel</Text>
|
||||
|
||||
<Separator borderColor="gray.300"/>
|
||||
|
||||
{/* Permission Requests */}
|
||||
<Flex direction="column" w="100%" gap={2} pb={4}>
|
||||
<Text fontSize="lg" fontWeight="bold">Incoming Permission Requests</Text>
|
||||
<Flex direction="row" w="100%" gap={5}>
|
||||
{/* ivans requests table */}
|
||||
<Flex direction="column" w="100%" gap={2}>
|
||||
<Text fontSize="sm" fontWeight="bold">IVANS</Text>
|
||||
<Table.Root w="100%" size="sm" variant="outline" borderRadius="lg">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeader w="100%" fontWeight="bold">User</Table.ColumnHeader>
|
||||
<Table.ColumnHeader fontWeight="bold" textAlign="left">Permission</Table.ColumnHeader>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{users_req_ivans.map((user) => (
|
||||
<Table.Row key={user.username}>
|
||||
<Table.Cell w="100%">{user.lastName}, {user.firstName}</Table.Cell>
|
||||
<Table.Cell >
|
||||
<Flex w="100%" gap={1} justify="left">
|
||||
<IconButton borderRadius="xl" size="xs" variant="subtle" onClick={() => grant(user, "ivans")}>
|
||||
<Check />
|
||||
</IconButton>
|
||||
<IconButton borderRadius="xl" size="xs" variant="subtle" onClick={() => deny(user, "ivans")}>
|
||||
<X />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Flex>
|
||||
{/* rs requests table */}
|
||||
<Flex direction="column" w="100%" gap={2}>
|
||||
<Text fontSize="sm" fontWeight="bold">Remote Sensing</Text>
|
||||
<Table.Root w="100%" size="sm" variant="outline" borderRadius="lg">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeader w="100%" fontWeight="bold">User</Table.ColumnHeader>
|
||||
<Table.ColumnHeader fontWeight="bold" textAlign="left">Permission</Table.ColumnHeader>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{users_req_rs.map((user) => (
|
||||
<Table.Row key={user.username}>
|
||||
<Table.Cell>{user.lastName}, {user.firstName}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Flex w="100%" gap={1} justify="left">
|
||||
<IconButton borderRadius="xl" size="xs" variant="subtle" onClick={() => grant(user, "rs")}>
|
||||
<Check />
|
||||
</IconButton>
|
||||
<IconButton borderRadius="xl" size="xs" variant="subtle" onClick={() => deny(user, "rs")}>
|
||||
<X />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Separator borderColor="gray.300"/>
|
||||
|
||||
{/* Group Assignment */}
|
||||
<Flex direction="column" w="100%" gap={2} pb={4}>
|
||||
<Text fontWeight="bold" fontSize="lg">Groups</Text>
|
||||
<Table.Root w="50%" size="sm" variant="outline" borderRadius="lg">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeader fontWeight="bold">User</Table.ColumnHeader>
|
||||
<Table.ColumnHeader fontWeight="bold" textAlign="left">Group</Table.ColumnHeader>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{users.map((user, index) => (
|
||||
<Table.Row key={user.username}>
|
||||
<Table.Cell w="100%">{user.lastName}, {user.firstName}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Flex w="100%" justify="left">
|
||||
<NativeSelect.Root size="sm" w="175px" variant="subtle">
|
||||
<NativeSelect.Field
|
||||
defaultValue={user.group}
|
||||
{...register(`users.${index}.group`)}
|
||||
>
|
||||
<option value="Super User">Super User</option>
|
||||
<option value="Member">Member</option>
|
||||
</NativeSelect.Field>
|
||||
<NativeSelect.Indicator />
|
||||
</NativeSelect.Root>
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Flex>
|
||||
|
||||
<Separator borderColor="gray.300"/>
|
||||
|
||||
{/* Team Assignment */}
|
||||
<Flex direction="column" w="100%" gap={2} pb={4}>
|
||||
<Text fontWeight="bold" fontSize="lg">Teams</Text>
|
||||
<Table.Root w="50%" size="sm" variant="outline" borderRadius="lg">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeader fontWeight="bold" w="100%">User</Table.ColumnHeader>
|
||||
<Table.ColumnHeader fontWeight="bold" textAlign="left">Team</Table.ColumnHeader>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{users.map((user, index) => {
|
||||
console.log("user in team: ", user.team);
|
||||
return(
|
||||
<Table.Row key={user.username}>
|
||||
<Table.Cell>
|
||||
{user.lastName}, {user.firstName}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Flex w="100%" justify="right">
|
||||
<NativeSelect.Root size="sm" w="175px" variant="subtle">
|
||||
<NativeSelect.Field
|
||||
defaultValue={user.team}
|
||||
{...register(`users.${index}.team`)}
|
||||
>
|
||||
<option value="Computer Science">Computer Science</option>
|
||||
<option value="Remote Sensing">Remote Sensing</option>
|
||||
</NativeSelect.Field>
|
||||
<NativeSelect.Indicator />
|
||||
</NativeSelect.Root>
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)})}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Button type="submit" w="100px" variant="subtle" alignSelf="center">
|
||||
Save
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -86,7 +86,22 @@ const useAuthStore = create((set) => ({
|
||||
} catch (error) {
|
||||
console.error("Failed to save layout to database", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// updateProfile: async (newProfile) => {
|
||||
// try {
|
||||
// set((state) => ({
|
||||
// user: {
|
||||
// ...state.user,
|
||||
// username: newProfile.username,
|
||||
// }
|
||||
// }));
|
||||
//
|
||||
// await api.post('profile/update/', { profile: newProfile });
|
||||
// } catch (error) {
|
||||
// console.error("Failed to update profile", error);
|
||||
// }
|
||||
// }
|
||||
}));
|
||||
|
||||
export default useAuthStore;
|
||||
Reference in New Issue
Block a user