- Error loading activity. Please try again later.
+ return (
+ //
+
+
+
+
+ {/* Could put something here, like "Keep up the good work!" */}
+
+
+
Less
+ {legendNodes.map((node) => node)}
+
More
+
-
- ) : isLoading ? (
-
-
-
- ) : (
- data && (
-
-
+ {/*
*/}
+
+ {error ? (
+
+
+ Error loading activity. Please try again later.
+
+
+ ) : isLoading ? (
+
+
+
+ ) : (
+ data && (
+
+ )
+ )}
- )
- )}
-
-
- );
+
+ );
}
/* component for the cells of our activity map */
function Node({
- quartile,
- date,
- activity_time,
+ quartile,
+ date,
+ activity_time
}: {
- quartile: number;
- date?: Date;
- activity_time?: string;
+ quartile: number;
+ date?: Date;
+ activity_time?: string;
}) {
- let tooltipString = "";
- if (!date) {
- /* if node is from legend */
+ let tooltipString = '';
+ if (!date) {
+ /* if node is from legend */
+ return (
+
+ );
+ }
+ if (!activity_time) {
+ tooltipString = 'No activity on ' + date.toISOString().split('T')[0];
+ } else {
+ tooltipString = `${activity_time} on ${date?.toISOString().split('T')[0]}`;
+ }
return (
-
+
);
- }
- if (!activity_time) {
- tooltipString = "No activity on " + date.toISOString().split("T")[0];
- } else {
- tooltipString = `${activity_time} on ${date?.toISOString().split("T")[0]}`;
- }
- return (
-
- );
}
/* component that builds the activity map table */
function ActivityMapTable({
- data,
- end,
- error,
- isLoading,
- range,
+ data,
+ end,
+ error,
+ isLoading,
+ range
}: {
- data: ActivityMapData[];
- end: Date;
- error: any;
- isLoading: boolean;
- range: string;
+ data: ActivityMapData[];
+ end: Date;
+ error: any;
+ isLoading: boolean;
+ range: string;
}) {
- /* array that holds cells for the table */
- const tableData: JSX.Element[] = [];
- const tableMonths: string[] = [];
- let i;
- const len = data?.length;
- // const now = new Date();
- let aggregateActivityTime = 0;
- let oldMonth, newMonth;
+ /* array that holds cells for the table */
+ const tableData: JSX.Element[] = [];
+ const tableMonths: string[] = [];
+ let i;
+ const len = data?.length;
+ // const now = new Date();
+ let aggregateActivityTime = 0;
+ let oldMonth, newMonth;
- const getAggregateActivityTime = (time: number) => {
- if (error) {
- return "Error fetching activity data";
- }
- if (isLoading) {
- return "Loading...";
- } else if (time == 0) {
- return "No activity ";
- } else if (time == 1) {
- return "You have completed one hour of activity";
- } else {
- return "You have completed " + String(time) + " hours of activity";
- }
- };
+ const getAggregateActivityTime = (time: number) => {
+ if (error) {
+ return 'Error fetching activity data';
+ }
+ if (isLoading) {
+ return 'Loading...';
+ } else if (time == 0) {
+ return 'No activity ';
+ } else if (time == 1) {
+ return 'You have completed one hour of activity';
+ } else {
+ return 'You have completed ' + String(time) + ' hours of activity';
+ }
+ };
- const insertMonthHeader = (date: Date) => {
- const months = [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ];
- tableMonths.push(months[date.getUTCMonth()]);
- };
+ const insertMonthHeader = (date: Date) => {
+ const months = [
+ 'Jan',
+ 'Feb',
+ 'Mar',
+ 'Apr',
+ 'May',
+ 'Jun',
+ 'Jul',
+ 'Aug',
+ 'Sep',
+ 'Oct',
+ 'Nov',
+ 'Dec'
+ ];
+ tableMonths.push(months[date.getUTCMonth()]);
+ };
- const dateCount = subtractYear(end);
+ const dateCount = subtractYear(end);
- /* add spacers for days of week before activity range */
- for (i = 0; i < dateCount.getUTCDay(); i++) {
- tableData.push(
-
-
- ,
- );
- }
+ /* add spacers for days of week before activity range */
+ for (i = 0; i < dateCount.getUTCDay(); i++) {
+ tableData.push(
+
+
+
+ );
+ }
- i = new Date().getDay() + 1;
+ i = new Date().getDay() + 1;
- /* fill first months array element if range starts midweek */
- if (dateCount.getUTCDay() != 0) {
- if (dateCount.getUTCDate() < 8) {
- insertMonthHeader(dateCount);
- } else {
- tableMonths.push("");
+ /* fill first months array element if range starts midweek */
+ if (dateCount.getUTCDay() != 0) {
+ if (dateCount.getUTCDate() < 8) {
+ insertMonthHeader(dateCount);
+ } else {
+ tableMonths.push('');
+ }
}
- }
- /* fill cells with Nodes for days in range */
- while (dateCount <= end) {
- /* at start of new week, if new, push month, else push empty string */
- if (dateCount.getUTCDay() == 0) {
- newMonth = dateCount.getUTCMonth();
- if (
- newMonth != oldMonth &&
- dateCount.getUTCDate() < 8 &&
- tableMonths[tableMonths.length - 1] == ""
- ) {
- insertMonthHeader(dateCount);
- } else {
- tableMonths.push("");
- }
- oldMonth = newMonth;
- }
+ /* fill cells with Nodes for days in range */
+ while (dateCount <= end) {
+ /* at start of new week, if new, push month, else push empty string */
+ if (dateCount.getUTCDay() == 0) {
+ newMonth = dateCount.getUTCMonth();
+ if (
+ newMonth != oldMonth &&
+ dateCount.getUTCDate() < 8 &&
+ tableMonths[tableMonths.length - 1] == ''
+ ) {
+ insertMonthHeader(dateCount);
+ } else {
+ tableMonths.push('');
+ }
+ oldMonth = newMonth;
+ }
- /* in range and activity on date */
- if (
- i < len &&
- dateCount.toISOString().split("T")[0] == data[i].date.split("T")[0]
- ) {
- const activityTime = convertSeconds(Number(data[i].total_time));
- tableData.push(
-
-
- ,
- );
- aggregateActivityTime += Number(data[i].total_time);
- i += 1;
- } else {
- tableData.push(
-
-
- ,
- );
+ /* in range and activity on date */
+ if (
+ i < len &&
+ dateCount.toISOString().split('T')[0] == data[i].date.split('T')[0]
+ ) {
+ const activityTime = convertSeconds(Number(data[i].total_time));
+ tableData.push(
+
+
+
+ );
+ aggregateActivityTime += Number(data[i].total_time);
+ i += 1;
+ } else {
+ tableData.push(
+
+
+
+ );
+ }
+ dateCount.setUTCDate(dateCount.getUTCDate() + 1);
}
- dateCount.setUTCDate(dateCount.getUTCDate() + 1);
- }
- /* add empty cells for days of week after activity range */
- for (i = dateCount.getUTCDay(); i < 7; i++) {
- tableData.push(
-
-
- ,
- );
- }
- return (
- <>
-
-
-
- {tableMonths.map((node, index) => {
- return (
-
- {node}
-
- );
- })}
-
-
-
-
-
-
-
-
-
-
- M
-
-
-
+ /* add empty cells for days of week after activity range */
+ for (i = dateCount.getUTCDay(); i < 7; i++) {
+ tableData.push(
+
+
-
- W
-
-
-
-
-
- F
-
-
-
-
-
- {tableData.map((_, index) => {
- if (index % 7 == 0) {
- return (
-
- {tableData.slice(index, index + 7).map((node) => {
- return node;
- })}
-
- );
- }
- })}
-
-
-
- {isLoading || error
- ? null
- : getAggregateActivityTime(Math.floor(aggregateActivityTime / 3600)) +
- " in " +
- range +
- "."}
-
- >
- );
+ );
+ }
+ return (
+ <>
+
+
+
+ {tableMonths.map((node, index) => {
+ return (
+
+ {node}
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+ M
+
+
+
+
+
+ W
+
+
+
+
+
+ F
+
+
+
+
+
+ {tableData.map((_, index) => {
+ if (index % 7 == 0) {
+ return (
+
+ {tableData
+ .slice(index, index + 7)
+ .map((node) => {
+ return node;
+ })}
+
+ );
+ }
+ })}
+
+
+
+ {isLoading || error
+ ? null
+ : getAggregateActivityTime(
+ Math.floor(aggregateActivityTime / 3600)
+ ) +
+ ' in ' +
+ range +
+ '.'}
+
+ >
+ );
}
diff --git a/frontend/src/Components/WeeklyActivity.tsx b/frontend/src/Components/WeeklyActivity.tsx
index b19ae5a0..10f46a4d 100644
--- a/frontend/src/Components/WeeklyActivity.tsx
+++ b/frontend/src/Components/WeeklyActivity.tsx
@@ -1,104 +1,105 @@
import {
- LineChart,
- Line,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- ResponsiveContainer,
-} from "recharts";
-import { ThemeContext } from "./ThemeContext";
-import { useContext } from "react";
-import { RecentActivity } from "@/common";
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer
+} from 'recharts';
+import { ThemeContext } from './ThemeContext';
+import { useContext } from 'react';
+import { RecentActivity } from '@/common';
const WeekActivityChart = ({ data }: { data: any }) => {
- const { theme } = useContext(ThemeContext);
- var lineColor = theme == "light" ? "#18ABA0" : "#61BAB2";
- var gridColor = theme == "light" ? "#ECECEC" : "#737373";
- var backgroundColor = theme == "light" ? "#FFFFFF" : "#0F2926";
+ const { theme } = useContext(ThemeContext);
+ var lineColor = theme == 'light' ? '#18ABA0' : '#61BAB2';
+ var gridColor = theme == 'light' ? '#ECECEC' : '#737373';
+ var backgroundColor = theme == 'light' ? '#FFFFFF' : '#0F2926';
- const result: RecentActivity[] = new Array(7);
- let currentDate = new Date();
+ const result: RecentActivity[] = Array.from({ length: 7 });
+ let currentDate = new Date();
- for (let i = 6; i >= 0; i--) {
- let date = new Date(currentDate);
- date.setDate(date.getDate() - i);
- const dateString = date.toISOString().split("T")[0];
- let entry = data.find(
- (activity: RecentActivity) => activity.date.split("T")[0] === dateString,
- );
- entry
- ? (entry = {
- date: entry.date.split("T")[0],
- delta: Math.round(entry.delta / 60),
- })
- : (entry = { date: dateString, delta: 0 });
- result[6 - i] = entry;
- }
+ for (let i = 6; i >= 0; i--) {
+ let date = new Date(currentDate);
+ date.setDate(date.getDate() - i);
+ const dateString = date.toISOString().split('T')[0];
+ let entry = data.find(
+ (activity: RecentActivity) =>
+ activity.date.split('T')[0] === dateString
+ );
+ entry
+ ? (entry = {
+ date: entry.date.split('T')[0],
+ delta: Math.round(entry.delta / 60)
+ })
+ : (entry = { date: dateString, delta: 0 });
+ result[6 - i] = entry;
+ }
- const weekdays = [
- "Sun",
- "Mon",
- "Tues",
- "Wed",
- "Thurs",
- "Fri",
- "Sat",
- "Today",
- ];
- const XAxisTick = (props) => {
- const { x, y, payload } = props;
- let day = new Date(payload.value).getDay() + 1;
- if (new Date().getDay() == day) day = 7;
+ const weekdays = [
+ 'Sun',
+ 'Mon',
+ 'Tues',
+ 'Wed',
+ 'Thurs',
+ 'Fri',
+ 'Sat',
+ 'Today'
+ ];
+ const XAxisTick = (props) => {
+ const { x, y, payload } = props;
+ let day = new Date(payload.value).getDay() + 1;
+ if (new Date().getDay() == day) day = 7;
+ return (
+
+
+ {weekdays[day]}
+
+
+ );
+ };
return (
-
-
- {weekdays[day]}
-
-
+
+
+
+ } />
+
+
+
+
+
);
- };
- return (
-
-
-
- } />
-
-
-
-
-
- );
};
export default WeekActivityChart;
diff --git a/frontend/src/Components/forms/AddCategoryForm.tsx b/frontend/src/Components/forms/AddCategoryForm.tsx
index 8bfbcf73..0816631b 100644
--- a/frontend/src/Components/forms/AddCategoryForm.tsx
+++ b/frontend/src/Components/forms/AddCategoryForm.tsx
@@ -1,44 +1,44 @@
-import { useState } from "react";
-import { useForm, SubmitHandler } from "react-hook-form";
-import { TextInput, CloseX, SubmitButton } from "../inputs";
+import { useState } from 'react';
+import { useForm, SubmitHandler } from 'react-hook-form';
+import { TextInput, CloseX, SubmitButton } from '../inputs';
type Inputs = {
- title: string;
+ title: string;
};
export default function AddCategoryForm({
- onSuccess,
+ onSuccess
}: {
- onSuccess: (title: string) => void;
+ onSuccess: (title: string) => void;
}) {
- const [errorMessage, _] = useState("");
+ const [errorMessage, _] = useState('');
- const {
- register,
- handleSubmit,
- reset,
- formState: { errors },
- } = useForm
();
+ const {
+ register,
+ handleSubmit,
+ reset,
+ formState: { errors }
+ } = useForm();
- const onSubmit: SubmitHandler = async (data) => {
- onSuccess(data.title);
- reset();
- };
+ const onSubmit: SubmitHandler = async (data) => {
+ onSuccess(data.title);
+ reset();
+ };
- return (
-
- reset()} />
-
-
- );
+ return (
+
+ reset()} />
+
+
+ );
}
diff --git a/frontend/src/Components/forms/AddLinkForm.tsx b/frontend/src/Components/forms/AddLinkForm.tsx
index ccde44a7..018eabe3 100644
--- a/frontend/src/Components/forms/AddLinkForm.tsx
+++ b/frontend/src/Components/forms/AddLinkForm.tsx
@@ -1,54 +1,54 @@
-import { useState } from "react";
-import { useForm, SubmitHandler } from "react-hook-form";
-import { SubmitButton } from "../inputs/SubmitButton";
-import { TextInput } from "../inputs/TextInput";
-import { CloseX } from "../inputs/CloseX";
+import { useState } from 'react';
+import { useForm, SubmitHandler } from 'react-hook-form';
+import { SubmitButton } from '../inputs/SubmitButton';
+import { TextInput } from '../inputs/TextInput';
+import { CloseX } from '../inputs/CloseX';
type Inputs = {
- title: string;
- url: string;
+ title: string;
+ url: string;
};
export default function AddLinkForm({
- onSuccess,
+ onSuccess
}: {
- onSuccess: (title: string, url: string) => void;
+ onSuccess: (title: string, url: string) => void;
}) {
- const [errorMessage, _] = useState("");
+ const [errorMessage, _] = useState('');
- const {
- register,
- handleSubmit,
- reset,
- formState: { errors },
- } = useForm();
+ const {
+ register,
+ handleSubmit,
+ reset,
+ formState: { errors }
+ } = useForm();
- const onSubmit: SubmitHandler = async (data) => {
- onSuccess(data.title, data.url);
- reset();
- };
+ const onSubmit: SubmitHandler = async (data) => {
+ onSuccess(data.title, data.url);
+ reset();
+ };
- return (
-
- reset()} />
-
-
- );
+ return (
+
+ reset()} />
+
+
+ );
}
diff --git a/frontend/src/Components/forms/AddProviderForm.tsx b/frontend/src/Components/forms/AddProviderForm.tsx
index 5df49013..5df77bf8 100644
--- a/frontend/src/Components/forms/AddProviderForm.tsx
+++ b/frontend/src/Components/forms/AddProviderForm.tsx
@@ -1,128 +1,131 @@
-import { ProviderPlatformState, ProviderPlatformType } from "@/common";
-import axios from "axios";
-import { useState } from "react";
-import { SubmitHandler, useForm } from "react-hook-form";
+import { ProviderPlatformState, ProviderPlatformType } from '@/common';
+import axios from 'axios';
+import { useState } from 'react';
+import { SubmitHandler, useForm } from 'react-hook-form';
import {
- CloseX,
- DropdownInput,
- SubmitButton,
- TextAreaInput,
- TextInput,
-} from "../inputs";
-import { ToastState } from "../Toast";
+ CloseX,
+ DropdownInput,
+ SubmitButton,
+ TextAreaInput,
+ TextInput
+} from '../inputs';
+import { ToastState } from '../Toast';
type ProviderInputs = {
- name: string;
- type: ProviderPlatformType;
- description: string;
- base_url: string;
- account_id: string;
- access_key: string;
- icon_url: string;
- state: ProviderPlatformState;
+ name: string;
+ type: ProviderPlatformType;
+ description: string;
+ base_url: string;
+ account_id: string;
+ access_key: string;
+ icon_url: string;
+ state: ProviderPlatformState;
};
export default function AddProviderForm({
- onSuccess,
+ onSuccess
}: {
- onSuccess: Function;
+ onSuccess: Function;
}) {
- const [errorMessage, setErrorMessage] = useState("");
+ const [errorMessage, setErrorMessage] = useState('');
- const {
- register,
- handleSubmit,
- reset,
- formState: { errors },
- } = useForm();
+ const {
+ register,
+ handleSubmit,
+ reset,
+ formState: { errors }
+ } = useForm();
- const onSubmit: SubmitHandler = async (data) => {
- try {
- if (!data.base_url.startsWith("http")) {
- data.base_url = "https://" + data.base_url;
- }
- setErrorMessage("");
- const response = await axios.post("/api/provider-platforms", data);
- if (response.status !== 201) {
- onSuccess(ToastState.error, "Failed to add provider platform");
- }
- reset();
- onSuccess(ToastState.success, "Provider platform created successfully");
- } catch (error: any) {
- setErrorMessage(error.response.data.message);
- }
- };
+ const onSubmit: SubmitHandler = async (data) => {
+ try {
+ if (!data.base_url.startsWith('http')) {
+ data.base_url = 'https://' + data.base_url;
+ }
+ setErrorMessage('');
+ const response = await axios.post('/api/provider-platforms', data);
+ if (response.status !== 201) {
+ onSuccess(ToastState.error, 'Failed to add provider platform');
+ }
+ reset();
+ onSuccess(
+ ToastState.success,
+ 'Provider platform created successfully'
+ );
+ } catch (error: any) {
+ setErrorMessage(error.response.data.message);
+ }
+ };
- return (
-
- reset()} />
-
-
- );
+ return (
+
+ reset()} />
+
+
+ );
}
diff --git a/frontend/src/Components/forms/AddUserForm.tsx b/frontend/src/Components/forms/AddUserForm.tsx
index 3dc6b56a..83ceb3be 100644
--- a/frontend/src/Components/forms/AddUserForm.tsx
+++ b/frontend/src/Components/forms/AddUserForm.tsx
@@ -1,142 +1,150 @@
-import { ProviderPlatform, UserRole } from "../../common";
-import axios from "axios";
-import { useEffect, useState } from "react";
-import { useForm, SubmitHandler } from "react-hook-form";
-import { ToastState } from "../Toast";
-import { CloseX } from "../inputs/CloseX";
-import { TextInput } from "../inputs/TextInput";
-import { DropdownInput } from "../inputs/DropdownInput";
-import { SubmitButton } from "../inputs/SubmitButton";
+import { ProviderPlatform, UserRole } from '../../common';
+import axios from 'axios';
+import { useEffect, useState } from 'react';
+import { useForm, SubmitHandler } from 'react-hook-form';
+import { ToastState } from '../Toast';
+import { CloseX } from '../inputs/CloseX';
+import { TextInput } from '../inputs/TextInput';
+import { DropdownInput } from '../inputs/DropdownInput';
+import { SubmitButton } from '../inputs/SubmitButton';
type Inputs = {
- name_first: string;
- name_last: string;
- username: string;
- role: UserRole;
+ name_first: string;
+ name_last: string;
+ username: string;
+ role: UserRole;
};
export default function AddUserForm({
- onSuccess,
+ onSuccess
}: {
- onSuccess: (psw: string, msg: string, err: ToastState) => void;
+ onSuccess: (psw: string, msg: string, err: ToastState) => void;
}) {
- const [errorMessage, setErrorMessage] = useState("");
- const [providers, setProviders] = useState([]);
- const [selectedProviders, setSelectedProviders] = useState([]);
- const {
- reset,
- register,
- handleSubmit,
- formState: { errors },
- } = useForm();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [providers, setProviders] = useState([]);
+ const [selectedProviders, setSelectedProviders] = useState([]);
+ const {
+ reset,
+ register,
+ handleSubmit,
+ formState: { errors }
+ } = useForm();
- const onSubmit: SubmitHandler = async (data) => {
- try {
- setErrorMessage("");
- const response = await axios.post("/api/users", {
- user: data,
- provider_platforms: selectedProviders,
- });
+ const onSubmit: SubmitHandler = async (data) => {
+ try {
+ setErrorMessage('');
+ const response = await axios.post('/api/users', {
+ user: data,
+ provider_platforms: selectedProviders
+ });
- if (response.status !== 201) {
- onSuccess("", "Failed to create user", ToastState.error);
- }
- reset();
- onSuccess(
- response.data.temp_password,
- "User created successfully with temporary password",
- ToastState.success,
- );
- } catch (error: any) {
- setErrorMessage(error.response.data.message);
- }
- };
-
- const handleAddUserToProviderList = (providerId: number) => {
- if (selectedProviders.includes(providerId)) {
- setSelectedProviders(selectedProviders.filter((id) => id !== providerId));
- } else {
- setSelectedProviders([...selectedProviders, providerId]);
- }
- };
+ if (response.status !== 201) {
+ onSuccess('', 'Failed to create user', ToastState.error);
+ }
+ reset();
+ onSuccess(
+ response.data.temp_password,
+ 'User created successfully with temporary password',
+ ToastState.success
+ );
+ } catch (error: any) {
+ setErrorMessage(error.response.data.message);
+ }
+ };
- useEffect(() => {
- const fetchActiveProviders = async () => {
- try {
- const resp = await axios.get(
- `/api/provider-platforms?only=oidc_enabled`,
- );
- if (resp.status === 200) {
- setProviders(resp.data.data);
+ const handleAddUserToProviderList = (providerId: number) => {
+ if (selectedProviders.includes(providerId)) {
+ setSelectedProviders(
+ selectedProviders.filter((id) => id !== providerId)
+ );
+ } else {
+ setSelectedProviders([...selectedProviders, providerId]);
}
- } catch (error: any) {
- setErrorMessage(error.response.data.message);
- }
};
- fetchActiveProviders();
- }, []);
- return (
-
- );
+ useEffect(() => {
+ const fetchActiveProviders = async () => {
+ try {
+ const resp = await axios.get(
+ `/api/provider-platforms?only=oidc_enabled`
+ );
+ if (resp.status === 200) {
+ setProviders(resp.data.data);
+ }
+ } catch (error: any) {
+ setErrorMessage(error.response.data.message);
+ }
+ };
+ fetchActiveProviders();
+ }, []);
+
+ return (
+
+ );
}
diff --git a/frontend/src/Components/forms/ChangePasswordForm.tsx b/frontend/src/Components/forms/ChangePasswordForm.tsx
index d9abdcef..0bdc9ae7 100644
--- a/frontend/src/Components/forms/ChangePasswordForm.tsx
+++ b/frontend/src/Components/forms/ChangePasswordForm.tsx
@@ -1,200 +1,200 @@
-import { useEffect, useState } from "react";
-import { useForm, useWatch, SubmitHandler } from "react-hook-form";
-import InputError from "../../Components/inputs/InputError";
-import PrimaryButton from "../../Components/PrimaryButton";
-import { TextInput } from "../../Components/inputs/TextInput";
-import { CheckIcon, XMarkIcon } from "@heroicons/react/24/solid";
+import { useEffect, useState } from 'react';
+import { useForm, useWatch, SubmitHandler } from 'react-hook-form';
+import InputError from '../../Components/inputs/InputError';
+import PrimaryButton from '../../Components/PrimaryButton';
+import { TextInput } from '../../Components/inputs/TextInput';
+import { CheckIcon, XMarkIcon } from '@heroicons/react/24/solid';
-import axios from "axios";
-import { useAuth } from "@/AuthContext";
+import axios from 'axios';
+import { useAuth } from '@/AuthContext';
type Inputs = {
- password: string;
- confirm: string;
- facility_name: string;
+ password: string;
+ confirm: string;
+ facility_name: string;
};
export default function ChangePasswordForm() {
- const [errorMessage, setErrorMessage] = useState("");
- const [processing, setProcessing] = useState(false);
- const [isFacilityDefault, setIsFacilityDefault] = useState(null);
- const auth = useAuth();
+ const [errorMessage, setErrorMessage] = useState('');
+ const [processing, setProcessing] = useState(false);
+ const [isFacilityDefault, setIsFacilityDefault] = useState(null);
+ const auth = useAuth();
- const {
- control,
- register,
- handleSubmit,
- reset,
- formState: { errors },
- } = useForm();
+ const {
+ control,
+ register,
+ handleSubmit,
+ reset,
+ formState: { errors }
+ } = useForm();
- const password = useWatch({
- control,
- name: "password",
- });
+ const password = useWatch({
+ control,
+ name: 'password'
+ });
- const confirm = useWatch({
- control,
- name: "confirm",
- });
+ const confirm = useWatch({
+ control,
+ name: 'confirm'
+ });
- const facility = useWatch({
- control,
- name: "facility_name",
- });
- useEffect(() => {
- const checkFacilityName = async () => {
- try {
- const resp = await axios.get("/api/facilities/1");
- if (resp.status === 200) {
- setIsFacilityDefault(resp.data.data[0].name === "Default");
- return;
- }
- } catch (error: any) {
- setIsFacilityDefault(false);
- }
- };
- checkFacilityName();
- }, []);
+ const facility = useWatch({
+ control,
+ name: 'facility_name'
+ });
+ useEffect(() => {
+ const checkFacilityName = async () => {
+ try {
+ const resp = await axios.get('/api/facilities/1');
+ if (resp.status === 200) {
+ setIsFacilityDefault(resp.data.data[0].name === 'Default');
+ return;
+ }
+ } catch (error: any) {
+ setIsFacilityDefault(false);
+ }
+ };
+ checkFacilityName();
+ }, []);
- const isLengthValid = password && password.length >= 8;
- const hasNumber = /\d/.test(password);
- const passwordsMatch = password === confirm;
- const isValid = isLengthValid && hasNumber && passwordsMatch;
- const validFacility =
- facility && facility.length > 2 && facility.trim().length > 2;
- const isFirstAdminLogin =
- auth.user.id === 1 && auth.user.role === "admin" && isFacilityDefault;
+ const isLengthValid = password && password.length >= 8;
+ const hasNumber = /\d/.test(password);
+ const passwordsMatch = password === confirm;
+ const isValid = isLengthValid && hasNumber && passwordsMatch;
+ const validFacility =
+ facility && facility.length > 2 && facility.trim().length > 2;
+ const isFirstAdminLogin =
+ auth.user.id === 1 && auth.user.role === 'admin' && isFacilityDefault;
- const submit: SubmitHandler = async (data) => {
- try {
- setErrorMessage("");
- setProcessing(true);
- if (data.facility_name) {
- data.facility_name = data.facility_name.trim();
- }
- const response = await axios.post("/api/reset-password", data);
- if (response.status === 200) {
- window.location.replace("dashboard");
- } else {
- setErrorMessage(`Your passwords did not pass validation,
+ const submit: SubmitHandler = async (data) => {
+ try {
+ setErrorMessage('');
+ setProcessing(true);
+ if (data.facility_name) {
+ data.facility_name = data.facility_name.trim();
+ }
+ const response = await axios.post('/api/reset-password', data);
+ if (response.status === 200) {
+ window.location.replace('dashboard');
+ } else {
+ setErrorMessage(`Your passwords did not pass validation,
please check that they match and are 8 or more characters with at least 1 number.`);
- }
- } catch (error: any) {
- setProcessing(false);
- setErrorMessage(
- error.response.data ||
- "Your passwords didn't pass validation, please try again.",
- );
- reset();
- }
- };
+ }
+ } catch (error: any) {
+ setProcessing(false);
+ setErrorMessage(
+ error.response.data ||
+ "Your passwords didn't pass validation, please try again."
+ );
+ reset();
+ }
+ };
- return (
-
- );
+
+
+ {processing ? (
+
+ ) : (
+ Reset Password
+ )}
+
+
+
+ );
}
diff --git a/frontend/src/Components/forms/ConfirmImportAllUsersForm.tsx b/frontend/src/Components/forms/ConfirmImportAllUsersForm.tsx
index 2ad757f0..71caf548 100644
--- a/frontend/src/Components/forms/ConfirmImportAllUsersForm.tsx
+++ b/frontend/src/Components/forms/ConfirmImportAllUsersForm.tsx
@@ -1,29 +1,31 @@
-import { CloseX } from "../inputs/CloseX";
+import { CloseX } from '../inputs/CloseX';
export default function ConfirmImportAllUsersForm({
- onCancel,
- onSuccess,
+ onCancel,
+ onSuccess
}: {
- onCancel: Function;
- onSuccess: Function;
+ onCancel: Function;
+ onSuccess: Function;
}) {
- return (
-
-
onCancel()} />
- Are you sure you would like to import all users?
-
-
- );
+ return (
+
+
onCancel()} />
+
+ Are you sure you would like to import all users?
+
+
+
+ );
}
diff --git a/frontend/src/Components/forms/ConsentForm.tsx b/frontend/src/Components/forms/ConsentForm.tsx
index 5c48590d..5f12a7fd 100644
--- a/frontend/src/Components/forms/ConsentForm.tsx
+++ b/frontend/src/Components/forms/ConsentForm.tsx
@@ -1,50 +1,52 @@
-import axios from "axios";
+import axios from 'axios';
export default function ConsentForm() {
- const accept = async () => {
- try {
- const urlParams = new URLSearchParams(window.location.search);
- const consent = urlParams.get("consent_challenge");
- await axios(`/api/consent/accept`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- data: { consent_challenge: consent },
- });
- return;
- } catch (error: any) {
- console.error(error.response);
- }
- };
- const deny = () => {
- window.location.replace("/");
- };
- return (
-
-
-
Consent Form
-
- Do you consent to give this external application access to your
- account?
-
-
-
- Accept
-
-
- Decline
-
+ const accept = async () => {
+ try {
+ const urlParams = new URLSearchParams(window.location.search);
+ const consent = urlParams.get('consent_challenge');
+ await axios(`/api/consent/accept`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ data: { consent_challenge: consent }
+ });
+ return;
+ } catch (error: any) {
+ console.error(error.response);
+ }
+ };
+ const deny = () => {
+ window.location.replace('/');
+ };
+ return (
+
+
+
+ Consent Form
+
+
+ Do you consent to give this external application access to
+ your account?
+
+
+
+ Accept
+
+
+ Decline
+
+
+
-
-
- );
+ );
}
diff --git a/frontend/src/Components/forms/DeleteForm.tsx b/frontend/src/Components/forms/DeleteForm.tsx
index 2116a690..596f000f 100644
--- a/frontend/src/Components/forms/DeleteForm.tsx
+++ b/frontend/src/Components/forms/DeleteForm.tsx
@@ -1,32 +1,32 @@
-import { CloseX } from "../inputs";
+import { CloseX } from '../inputs';
interface DeleteProps {
- item: string;
- onCancel: () => void;
- onSuccess: () => void;
+ item: string;
+ onCancel: () => void;
+ onSuccess: () => void;
}
export default function DeleteForm({ item, onCancel, onSuccess }: DeleteProps) {
- return (
-
-
onCancel()} />
-
- Are you sure you would like to delete this {item.toLowerCase()}?
- This action cannot be undone.
-
-
-
- );
+ return (
+
+
onCancel()} />
+
+ Are you sure you would like to delete this {item.toLowerCase()}?
+ This action cannot be undone.
+
+
+
+ );
}
diff --git a/frontend/src/Components/forms/EditProviderForm.tsx b/frontend/src/Components/forms/EditProviderForm.tsx
index ac2c6b9d..22e73eb2 100644
--- a/frontend/src/Components/forms/EditProviderForm.tsx
+++ b/frontend/src/Components/forms/EditProviderForm.tsx
@@ -1,238 +1,247 @@
import {
- ProviderPlatform,
- ProviderPlatformState,
- ProviderPlatformType,
-} from "@/common";
-import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
-import axios from "axios";
-import { useState } from "react";
-import { SubmitHandler, useForm } from "react-hook-form";
+ ProviderPlatform,
+ ProviderPlatformState,
+ ProviderPlatformType
+} from '@/common';
+import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/solid';
+import axios from 'axios';
+import { useState } from 'react';
+import { SubmitHandler, useForm } from 'react-hook-form';
import {
- CloseX,
- TextInput,
- TextAreaInput,
- DropdownInput,
- SubmitButton,
-} from "../inputs";
-import { ToastState } from "../Toast";
+ CloseX,
+ TextInput,
+ TextAreaInput,
+ DropdownInput,
+ SubmitButton
+} from '../inputs';
+import { ToastState } from '../Toast';
type ProviderInputs = {
- id: number;
- name: string;
- type: ProviderPlatformType;
- description: string;
- base_url: string;
- account_id: string;
- access_key: string;
- icon_url: string;
- state: ProviderPlatformState;
+ id: number;
+ name: string;
+ type: ProviderPlatformType;
+ description: string;
+ base_url: string;
+ account_id: string;
+ access_key: string;
+ icon_url: string;
+ state: ProviderPlatformState;
};
export default function EditProviderForm({
- onSuccess,
- provider,
+ onSuccess,
+ provider
}: {
- onSuccess: Function;
- provider: ProviderPlatform;
+ onSuccess: Function;
+ provider: ProviderPlatform;
}) {
- const [errorMessage, setErrorMessage] = useState("");
- const [showAdditionalFields, setShowAdditionalFields] = useState(false);
- const [showAccessKey, setShowAccessKey] = useState(false);
- const [accessKey, setAccessKey] = useState("");
- const {
- register,
- handleSubmit,
- reset,
- formState: { errors },
- } = useForm
({
- defaultValues: {
- name: provider.name,
- description: provider.description,
- type: provider.type,
- base_url: provider.base_url,
- account_id: provider.account_id,
- icon_url: provider.icon_url,
- state: provider.state,
- },
- });
-
- const getAccessKey = async () => {
- if (showAccessKey) {
- setShowAccessKey(false);
- return;
- }
- if (accessKey) {
- setShowAccessKey(true);
- return;
- }
- try {
- const response = await axios.get(
- `/api/provider-platforms/${provider.id}`,
- );
- setAccessKey(response.data.data[0]["access_key"]);
- setShowAccessKey(true);
- } catch (error) {
- console.log(error);
- setErrorMessage(error.response.data.message);
- }
- };
-
- function diffFormData(formData: any, currentUserData: any) {
- const changes: Partial = {};
- Object.keys(formData).forEach((key) => {
- if (
- formData[key] !== currentUserData[key] &&
- formData[key] !== undefined
- ) {
- changes[key] = formData[key];
- }
+ const [errorMessage, setErrorMessage] = useState('');
+ const [showAdditionalFields, setShowAdditionalFields] = useState(false);
+ const [showAccessKey, setShowAccessKey] = useState(false);
+ const [accessKey, setAccessKey] = useState('');
+ const {
+ register,
+ handleSubmit,
+ reset,
+ formState: { errors }
+ } = useForm({
+ defaultValues: {
+ name: provider.name,
+ description: provider.description,
+ type: provider.type,
+ base_url: provider.base_url,
+ account_id: provider.account_id,
+ icon_url: provider.icon_url,
+ state: provider.state
+ }
});
- return changes;
- }
- const onSubmit: SubmitHandler = async (data) => {
- const cleanData = diffFormData(data, provider);
- try {
- setErrorMessage("");
- const response = await axios.patch(
- `/api/provider-platforms/${provider?.id}`,
- cleanData,
- );
- if (response.status !== 201) {
- onSuccess(ToastState.error, "Failed to update provider platform");
- }
- reset();
- onSuccess(ToastState.success, "Provider platform updated successfully");
- } catch (error: any) {
- setErrorMessage(error.response.data.message);
- }
- };
+ const getAccessKey = async () => {
+ if (showAccessKey) {
+ setShowAccessKey(false);
+ return;
+ }
+ if (accessKey) {
+ setShowAccessKey(true);
+ return;
+ }
+ try {
+ const response = await axios.get(
+ `/api/provider-platforms/${provider.id}`
+ );
+ setAccessKey(response.data.data[0]['access_key']);
+ setShowAccessKey(true);
+ } catch (error) {
+ console.log(error);
+ setErrorMessage(error.response.data.message);
+ }
+ };
- function closeAndReset() {
- onSuccess();
- reset();
- }
+ function diffFormData(formData: any, currentUserData: any) {
+ const changes: Partial = {};
+ Object.keys(formData).forEach((key) => {
+ if (
+ formData[key] !== currentUserData[key] &&
+ formData[key] !== undefined
+ ) {
+ changes[key] = formData[key];
+ }
+ });
+ return changes;
+ }
- return (
-
-
closeAndReset()} />
-
+ );
}
diff --git a/frontend/src/Components/forms/MapUserForm.tsx b/frontend/src/Components/forms/MapUserForm.tsx
index cd4f7f43..ccb3685d 100644
--- a/frontend/src/Components/forms/MapUserForm.tsx
+++ b/frontend/src/Components/forms/MapUserForm.tsx
@@ -1,176 +1,181 @@
-import axios from "axios";
-import { useEffect, useState } from "react";
-import { ToastState } from "../Toast";
-import { ProviderUser } from "@/common";
-import { User } from "@/types";
-import { CloseX } from "../inputs/CloseX";
+import axios from 'axios';
+import { useEffect, useState } from 'react';
+import { ToastState } from '../Toast';
+import { ProviderUser } from '@/common';
+import { User } from '@/types';
+import { CloseX } from '../inputs/CloseX';
interface Props {
- externalUser: ProviderUser;
- providerId: number;
- onSubmit: (msg: string, err: ToastState) => void;
- onCancel: () => void;
+ externalUser: ProviderUser;
+ providerId: number;
+ onSubmit: (msg: string, err: ToastState) => void;
+ onCancel: () => void;
}
export default function MapUserForm({
- externalUser,
- providerId,
- onSubmit,
- onCancel,
+ externalUser,
+ providerId,
+ onSubmit,
+ onCancel
}: Props) {
- const [errorMessage, setErrorMessage] = useState("");
- const [usersToMap, setUsersToMap] = useState([]);
- const [totalUnmapped, setTotalUnmapped] = useState([]);
- const [displayUsers, setDisplayUsers] = useState([]);
- const [currentPage, setCurrentPage] = useState(1);
- const [selectedUser, setSelectedUser] = useState(null);
- const usersPerPage = 5;
+ const [errorMessage, setErrorMessage] = useState('');
+ const [usersToMap, setUsersToMap] = useState([]);
+ const [totalUnmapped, setTotalUnmapped] = useState([]);
+ const [displayUsers, setDisplayUsers] = useState([]);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [selectedUser, setSelectedUser] = useState(null);
+ const usersPerPage = 5;
- const handlePageChange = (newPage: number) => {
- setCurrentPage(newPage);
- };
+ const handlePageChange = (newPage: number) => {
+ setCurrentPage(newPage);
+ };
- const handleUserSelection = (userId: number) => {
- setSelectedUser(userId);
- };
+ const handleUserSelection = (userId: number) => {
+ setSelectedUser(userId);
+ };
- const handleSubmit = async (userId: number) => {
- try {
- setErrorMessage("");
- const response = await axios.post(
- `/api/provider-platforms/${providerId}/map-user/${userId}`,
- externalUser,
- );
+ const handleSubmit = async (userId: number) => {
+ try {
+ setErrorMessage('');
+ const response = await axios.post(
+ `/api/provider-platforms/${providerId}/map-user/${userId}`,
+ externalUser
+ );
- if (response.status !== 201) {
- onSubmit("", ToastState.error);
- }
- onSubmit(
- "User created successfully with temporary password",
- ToastState.success,
- );
- } catch (error: any) {
- setErrorMessage(error.response.data.message);
- onSubmit("Failed to map user", ToastState.error);
- return;
- }
- };
-
- function handleGetAllUnmappedUsers() {
- if (usersToMap.length === totalUnmapped.length) {
- return;
- }
- if (displayUsers.length < totalUnmapped.length) {
- totalUnmapped.length === 0
- ? onSubmit("No unmapped users in UnlockEd", ToastState.error)
- : setDisplayUsers(totalUnmapped);
- } else {
- setDisplayUsers(usersToMap);
- }
- }
+ if (response.status !== 201) {
+ onSubmit('', ToastState.error);
+ }
+ onSubmit(
+ 'User created successfully with temporary password',
+ ToastState.success
+ );
+ } catch (error: any) {
+ setErrorMessage(error.response.data.message);
+ onSubmit('Failed to map user', ToastState.error);
+ return;
+ }
+ };
- useEffect(() => {
- async function fetchUsers() {
- try {
- const all = await axios.get(
- `/api/users?include=only_unmapped&provider_id=${providerId}`,
- );
- if (all.status !== 200) {
- onSubmit("failed to fetch users, please try again", ToastState.error);
- return;
+ function handleGetAllUnmappedUsers() {
+ if (usersToMap.length === totalUnmapped.length) {
+ return;
}
- setTotalUnmapped(all.data.data);
- const response = await axios.get(
- `/api/users?include=only_unmapped&provider_id=${providerId}&search=${externalUser.username}&search=${externalUser.email}`,
- );
- if (response.status !== 200) {
- onSubmit("Failed to fetch users", ToastState.error);
- return;
+ if (displayUsers.length < totalUnmapped.length) {
+ totalUnmapped.length === 0
+ ? onSubmit('No unmapped users in UnlockEd', ToastState.error)
+ : setDisplayUsers(totalUnmapped);
+ } else {
+ setDisplayUsers(usersToMap);
}
- setUsersToMap(response.data.data);
- setDisplayUsers(response.data.data);
- } catch (error: any) {
- console.log(error);
- setErrorMessage(error);
- onSubmit("Failed to fetch users", ToastState.error);
- return;
- }
}
- externalUser && fetchUsers();
- }, [externalUser]);
- const indexOfLastUser = currentPage * usersPerPage;
- const indexOfFirstUser = indexOfLastUser - usersPerPage;
- const currentUsers = displayUsers.slice(indexOfFirstUser, indexOfLastUser);
- const totalPages = Math.ceil(displayUsers.length / usersPerPage);
+ useEffect(() => {
+ async function fetchUsers() {
+ try {
+ const all = await axios.get(
+ `/api/users?include=only_unmapped&provider_id=${providerId}`
+ );
+ if (all.status !== 200) {
+ onSubmit(
+ 'failed to fetch users, please try again',
+ ToastState.error
+ );
+ return;
+ }
+ setTotalUnmapped(all.data.data);
+ const response = await axios.get(
+ `/api/users?include=only_unmapped&provider_id=${providerId}&search=${externalUser.username}&search=${externalUser.email}`
+ );
+ if (response.status !== 200) {
+ onSubmit('Failed to fetch users', ToastState.error);
+ return;
+ }
+ setUsersToMap(response.data.data);
+ setDisplayUsers(response.data.data);
+ } catch (error: any) {
+ console.log(error);
+ setErrorMessage(error);
+ onSubmit('Failed to fetch users', ToastState.error);
+ return;
+ }
+ }
+ externalUser && fetchUsers();
+ }, [externalUser]);
+
+ const indexOfLastUser = currentPage * usersPerPage;
+ const indexOfFirstUser = indexOfLastUser - usersPerPage;
+ const currentUsers = displayUsers.slice(indexOfFirstUser, indexOfLastUser);
+ const totalPages = Math.ceil(displayUsers.length / usersPerPage);
- return (
-
-
-
-
Provider Username:
-
- {externalUser?.username ?? errorMessage}
-
+ return (
+
+
+
+
Provider Username:
+
+ {externalUser?.username ?? errorMessage}
+
+
+
Provider Email:
+
+ {externalUser?.email ?? ''}
+
+
to UnlockEd Users:
+
+
+ {displayUsers.length !== totalUnmapped.length
+ ? 'see all'
+ : 'see search results'}
+
+
+ {currentUsers.map((user) => (
+
+ handleUserSelection(user.id)}
+ />
+
+ {user.username} - {user.name_first} {user.name_last}{' '}
+ - {user.email}
+
+
+ ))}
+
+
+ handlePageChange(currentPage - 1)}
+ >
+ Previous
+
+ handlePageChange(currentPage + 1)}
+ >
+ Next
+
+
+
+
+ handleSubmit(selectedUser)}
+ disabled={!selectedUser}
+ >
+ Map to student
+
+
-
Provider Email:
-
{externalUser?.email ?? ""}
-
to UnlockEd Users:
-
-
- {displayUsers.length !== totalUnmapped.length
- ? "see all"
- : "see search results"}
-
-
- {currentUsers.map((user) => (
-
- handleUserSelection(user.id)}
- />
-
- {user.username} - {user.name_first} {user.name_last} -{" "}
- {user.email}
-
-
- ))}
-
-
- handlePageChange(currentPage - 1)}
- >
- Previous
-
- handlePageChange(currentPage + 1)}
- >
- Next
-
-
-
-
- handleSubmit(selectedUser)}
- disabled={!selectedUser}
- >
- Map to student
-
-
-
- );
+ );
}
diff --git a/frontend/src/Components/forms/RegisterOidcClientForm.tsx b/frontend/src/Components/forms/RegisterOidcClientForm.tsx
index 95dc7278..a5d817b1 100644
--- a/frontend/src/Components/forms/RegisterOidcClientForm.tsx
+++ b/frontend/src/Components/forms/RegisterOidcClientForm.tsx
@@ -1,90 +1,94 @@
-import { OidcClient, ProviderPlatform, ServerResponse } from "../../common";
-import axios from "axios";
-import { useState } from "react";
-import { useForm, SubmitHandler } from "react-hook-form";
-import { TextInput } from "../inputs/TextInput";
-import { SubmitButton } from "../inputs/SubmitButton";
-import { CloseX } from "../inputs/CloseX";
-import { ToastState } from "../Toast";
+import { OidcClient, ProviderPlatform, ServerResponse } from '../../common';
+import axios from 'axios';
+import { useState } from 'react';
+import { useForm, SubmitHandler } from 'react-hook-form';
+import { TextInput } from '../inputs/TextInput';
+import { SubmitButton } from '../inputs/SubmitButton';
+import { CloseX } from '../inputs/CloseX';
+import { ToastState } from '../Toast';
type Inputs = {
- redirect_url: string;
- provider_platform_id: number;
- auto_register: boolean;
+ redirect_url: string;
+ provider_platform_id: number;
+ auto_register: boolean;
};
export default function RegisterOidcClientForm({
- onSuccess,
- provider,
- onClose,
+ onSuccess,
+ provider,
+ onClose
}: {
- onSuccess: (response: ServerResponse
, state: ToastState) => void;
- provider: ProviderPlatform;
- onClose: () => void;
+ onSuccess: (
+ response: ServerResponse,
+ state: ToastState
+ ) => void;
+ provider: ProviderPlatform;
+ onClose: () => void;
}) {
- const [errorMessage, setErrorMessage] = useState("");
- const [hasAuto, setHasAuto] = useState(false);
- const {
- register,
- handleSubmit,
- formState: { errors },
- } = useForm({
- defaultValues: {
- redirect_url: "",
- provider_platform_id: provider.id,
- },
- });
+ const [errorMessage, setErrorMessage] = useState('');
+ const [hasAuto, setHasAuto] = useState(false);
+ const {
+ register,
+ handleSubmit,
+ formState: { errors }
+ } = useForm({
+ defaultValues: {
+ redirect_url: '',
+ provider_platform_id: provider.id
+ }
+ });
- const onSubmit: SubmitHandler = async (data) => {
- try {
- setErrorMessage("");
- data.auto_register = hasAuto;
- const response = await axios.post("/api/oidc/clients", data);
- if (response.status !== 201) {
- setErrorMessage("Failed to register OIDC client.");
- onSuccess(response.data, ToastState.error);
- }
- const client = response.data as ServerResponse;
- onSuccess(client, ToastState.success);
- } catch (error: any) {
- setErrorMessage(error);
- onSuccess(error.response.data, ToastState.error);
- }
- };
+ const onSubmit: SubmitHandler = async (data) => {
+ try {
+ setErrorMessage('');
+ data.auto_register = hasAuto;
+ const response = await axios.post('/api/oidc/clients', data);
+ if (response.status !== 201) {
+ setErrorMessage('Failed to register OIDC client.');
+ onSuccess(response.data, ToastState.error);
+ }
+ const client = response.data as ServerResponse;
+ onSuccess(client, ToastState.success);
+ } catch (error: any) {
+ setErrorMessage(error);
+ onSuccess(error.response.data, ToastState.error);
+ }
+ };
- return (
- <>
- onClose()} />
-
- {!hasAuto && (
-
- If you do not choose to auto register, you must manually setup
- authentication for UnlockEd the provider platform's settings.
-
- )}
-
- Auto Register:
-
-
- setHasAuto(!hasAuto)}
- />
-
-
- If you are unsure about the redirect URL, leave it blank.
-
-
-
- >
- );
+ return (
+ <>
+ onClose()} />
+
+ {!hasAuto && (
+
+ If you do not choose to auto register, you must manually
+ setup authentication for UnlockEd the provider
+ platform's settings.
+
+ )}
+
+ Auto Register:
+
+
+ setHasAuto(!hasAuto)}
+ />
+
+
+ If you are unsure about the redirect URL, leave it blank.
+
+
+
+ >
+ );
}
diff --git a/frontend/src/Components/forms/ResetPasswordForm.tsx b/frontend/src/Components/forms/ResetPasswordForm.tsx
index bea151a4..42169c8e 100644
--- a/frontend/src/Components/forms/ResetPasswordForm.tsx
+++ b/frontend/src/Components/forms/ResetPasswordForm.tsx
@@ -1,64 +1,68 @@
-import axios from "axios";
-import { User, UserRole } from "../../common";
-import { CloseX } from "../inputs/CloseX";
+import axios from 'axios';
+import { User, UserRole } from '../../common';
+import { CloseX } from '../inputs/CloseX';
interface ResetPasswordFormProps {
- onCancel: (message: string, is_err: boolean) => void;
- onSuccess: (psw: string) => void;
- user: User | null;
+ onCancel: (message: string, is_err: boolean) => void;
+ onSuccess: (psw: string) => void;
+ user: User | null;
}
export default function ResetPasswordForm({
- user,
- onSuccess,
- onCancel,
+ user,
+ onSuccess,
+ onCancel
}: ResetPasswordFormProps) {
- const getTempPassword = async () => {
- try {
- const response = await axios.post("/api/users/student-password", {
- user_id: user?.id,
- });
- if (response.status !== 200) {
- onCancel("Failed to reset password", true);
- return;
- }
- console.log(response.data["temp_password"]);
- onSuccess(response.data["temp_password"]);
- return;
- } catch (error: any) {
- onCancel(error.response.data.message, true);
- return;
- }
- };
- return (
-
-
onCancel("", false)} />
- {user?.role == UserRole.Admin ? (
-
- You may only reset the password for non-administrator accounts.
-
- ) : (
+ const getTempPassword = async () => {
+ try {
+ const response = await axios.post('/api/users/student-password', {
+ user_id: user?.id
+ });
+ if (response.status !== 200) {
+ onCancel('Failed to reset password', true);
+ return;
+ }
+ console.log(response.data['temp_password']);
+ onSuccess(response.data['temp_password']);
+ return;
+ } catch (error: any) {
+ onCancel(error.response.data.message, true);
+ return;
+ }
+ };
+ return (
-
- Are you sure you would like to reset {user?.name_first}{" "}
- {user?.name_last}'s password?
-
-
-
- onCancel("", false)}>
- Cancel
-
- {
- getTempPassword();
- }}
- >
- Reset Password
-
-
+
onCancel('', false)} />
+ {user?.role == UserRole.Admin ? (
+
+ You may only reset the password for non-administrator
+ accounts.
+
+ ) : (
+
+
+ Are you sure you would like to reset {user?.name_first}{' '}
+ {user?.name_last}'s password?
+
+
+
+ onCancel('', false)}
+ >
+ Cancel
+
+ {
+ getTempPassword();
+ }}
+ >
+ Reset Password
+
+
+
+ )}
- )}
-
- );
+ );
}
diff --git a/frontend/src/Components/forms/ShowImportedUsers.tsx b/frontend/src/Components/forms/ShowImportedUsers.tsx
index 98a536c1..4f4bb09e 100644
--- a/frontend/src/Components/forms/ShowImportedUsers.tsx
+++ b/frontend/src/Components/forms/ShowImportedUsers.tsx
@@ -1,69 +1,73 @@
-import { useState } from "react";
-import { CloseX } from "../inputs";
-import { UserImports } from "@/common";
+import { useState } from 'react';
+import { CloseX } from '../inputs';
+import { UserImports } from '@/common';
interface ImportedUserProps {
- users: UserImports[];
- onExit: () => void;
+ users: UserImports[];
+ onExit: () => void;
}
const PaginatedUserTable = ({ users, onExit }: ImportedUserProps) => {
- const [currentPage, setCurrentPage] = useState(1);
- const usersPerPage = 5;
- const indexOfLastUser = currentPage * usersPerPage;
- const indexOfFirstUser = indexOfLastUser - usersPerPage;
- const currentUsers = users.slice(indexOfFirstUser, indexOfLastUser);
- const totalPages = Math.ceil(users.length / usersPerPage);
+ const [currentPage, setCurrentPage] = useState(1);
+ const usersPerPage = 5;
+ const indexOfLastUser = currentPage * usersPerPage;
+ const indexOfFirstUser = indexOfLastUser - usersPerPage;
+ const currentUsers = users.slice(indexOfFirstUser, indexOfLastUser);
+ const totalPages = Math.ceil(users.length / usersPerPage);
- const handlePageChange = (newPage: number) => {
- setCurrentPage(newPage);
- };
+ const handlePageChange = (newPage: number) => {
+ setCurrentPage(newPage);
+ };
- return (
-
-
-
Imported Users
-
-
-
-
- Username
- Temp Password
- Error
-
-
-
- {currentUsers.map((user, idx) => (
-
- {user.username}
- {user.temp_password ?? "n/a"}
- {user.error ?? "n/a"}
-
- ))}
-
-
-
-
- handlePageChange(currentPage - 1)}
- >
- Previous
-
-
- Page {currentPage} of {totalPages}
-
- handlePageChange(currentPage + 1)}
- >
- Next
-
-
-
- );
+ return (
+
+
+
Imported Users
+
+
+
+
+ Username
+ Temp Password
+ Error
+
+
+
+ {currentUsers.map((user, idx) => (
+
+ {user.username}
+
+ {user.temp_password ?? 'n/a'}
+
+
+ {user.error ?? 'n/a'}
+
+
+ ))}
+
+
+
+
+ handlePageChange(currentPage - 1)}
+ >
+ Previous
+
+
+ Page {currentPage} of {totalPages}
+
+ handlePageChange(currentPage + 1)}
+ >
+ Next
+
+
+
+ );
};
export default PaginatedUserTable;
diff --git a/frontend/src/Components/forms/ShowTempPasswordForm.tsx b/frontend/src/Components/forms/ShowTempPasswordForm.tsx
index 5c5db8aa..1d5165ce 100644
--- a/frontend/src/Components/forms/ShowTempPasswordForm.tsx
+++ b/frontend/src/Components/forms/ShowTempPasswordForm.tsx
@@ -1,40 +1,42 @@
-import { CloseX } from "../inputs";
+import { CloseX } from '../inputs';
export interface TempPasswordProps {
- tempPassword: string;
- userName: string | null;
- onClose: () => void;
+ tempPassword: string;
+ userName: string | null;
+ onClose: () => void;
}
export default function ShowTempPasswordForm({
- tempPassword,
- userName,
- onClose,
+ tempPassword,
+ userName,
+ onClose
}: TempPasswordProps) {
- return (
-
-
onClose()} />
- {userName == null ? (
- You have successfully created a new user.
- ) : (
- <>
-
- You have successfully reset {userName}'s password.
-
-
- Copy this password now. If you lose it, you'll need to regenerate it
- to get a new one.
-
- >
- )}
-
-
-
-
Temporary Password
-
{tempPassword}
-
+ return (
+
+
onClose()} />
+ {userName == null ? (
+
+ You have successfully created a new user.
+
+ ) : (
+ <>
+
+ You have successfully reset {userName}'s password.
+
+
+ Copy this password now. If you lose it, you'll need to
+ regenerate it to get a new one.
+
+ >
+ )}
+
+
+
+
Temporary Password
+
{tempPassword}
+
+
+
+
-
-
-
- );
+ );
}
diff --git a/frontend/src/Components/inputs/Checkbox.tsx b/frontend/src/Components/inputs/Checkbox.tsx
index 0133c474..c88fe95c 100644
--- a/frontend/src/Components/inputs/Checkbox.tsx
+++ b/frontend/src/Components/inputs/Checkbox.tsx
@@ -1,24 +1,24 @@
interface CheckboxProps {
- label: string;
- interfaceRef: string;
- register: Function;
+ label: string;
+ interfaceRef: string;
+ register: Function;
}
export default function Checkbox({
- label,
- interfaceRef,
- register,
+ label,
+ interfaceRef,
+ register
}: CheckboxProps) {
- return (
-
-
- {`${label}`}
-
-
-
- );
+ return (
+
+
+ {`${label}`}
+
+
+
+ );
}
diff --git a/frontend/src/Components/inputs/CloseX.tsx b/frontend/src/Components/inputs/CloseX.tsx
index 24b8ddbf..e8fa26f2 100644
--- a/frontend/src/Components/inputs/CloseX.tsx
+++ b/frontend/src/Components/inputs/CloseX.tsx
@@ -1,12 +1,12 @@
export function CloseX({ close }: { close: () => void }) {
- return (
-
- close()}
- >
- ✕
-
-
- );
+ return (
+
+ close()}
+ >
+ ✕
+
+
+ );
}
diff --git a/frontend/src/Components/inputs/DropdownControl.tsx b/frontend/src/Components/inputs/DropdownControl.tsx
index d8349228..f8943314 100644
--- a/frontend/src/Components/inputs/DropdownControl.tsx
+++ b/frontend/src/Components/inputs/DropdownControl.tsx
@@ -1,34 +1,34 @@
interface DropdownControlProps {
- label?: string;
- callback: Function;
- enumType: Record;
+ label?: string;
+ callback: Function;
+ enumType: Record;
}
/* a dropdown that executes a callback function on change */
export default function DropdownControl({
- label,
- callback,
- enumType,
+ label,
+ callback,
+ enumType
}: DropdownControlProps) {
- return (
-
- callback(e.target.value)}
- >
- {label ? (
-
- {label}
-
- ) : (
- ""
- )}
- {Object.entries(enumType).map(([key, value]) => (
-
- {key}
-
- ))}
-
-
- );
+ return (
+
+ callback(e.target.value)}
+ >
+ {label ? (
+
+ {label}
+
+ ) : (
+ ''
+ )}
+ {Object.entries(enumType).map(([key, value]) => (
+
+ {key}
+
+ ))}
+
+
+ );
}
diff --git a/frontend/src/Components/inputs/DropdownInput.tsx b/frontend/src/Components/inputs/DropdownInput.tsx
index fa16ca64..d77a9fac 100644
--- a/frontend/src/Components/inputs/DropdownInput.tsx
+++ b/frontend/src/Components/inputs/DropdownInput.tsx
@@ -1,45 +1,45 @@
-import { FieldErrors } from "react-hook-form";
+import { FieldErrors } from 'react-hook-form';
interface DropdownProps {
- label: string;
- interfaceRef: string;
- required: boolean;
- errors: FieldErrors;
- register: Function;
- enumType: Record;
+ label: string;
+ interfaceRef: string;
+ required: boolean;
+ errors: FieldErrors;
+ register: Function;
+ enumType: Record;
}
export function DropdownInput({
- label,
- interfaceRef,
- required,
- errors,
- register,
- enumType,
+ label,
+ interfaceRef,
+ required,
+ errors,
+ register,
+ enumType
}: DropdownProps) {
- return (
-
-
- {label}
-
-
- {Object.entries(enumType).map(([key, value]) => (
-
- {value}
-
- ))}
-
-
- {errors[interfaceRef]?.message?.toString()}
-
-
- );
+ return (
+
+
+ {label}
+
+
+ {Object.entries(enumType).map(([key, value]) => (
+
+ {value}
+
+ ))}
+
+
+ {errors[interfaceRef]?.message?.toString()}
+
+
+ );
}
diff --git a/frontend/src/Components/inputs/InputError.tsx b/frontend/src/Components/inputs/InputError.tsx
index 8e344162..34db8c6f 100644
--- a/frontend/src/Components/inputs/InputError.tsx
+++ b/frontend/src/Components/inputs/InputError.tsx
@@ -1,16 +1,16 @@
-import { HTMLAttributes } from "react";
+import { HTMLAttributes } from 'react';
export default function InputError({
- message,
- className = "",
- ...props
+ message,
+ className = '',
+ ...props
}: HTMLAttributes & { message?: string }) {
- return message ? (
-
- {message}
-
- ) : null;
+ return message ? (
+
+ {message}
+
+ ) : null;
}
diff --git a/frontend/src/Components/inputs/SearchBar.tsx b/frontend/src/Components/inputs/SearchBar.tsx
index dc2b3d66..7393e980 100644
--- a/frontend/src/Components/inputs/SearchBar.tsx
+++ b/frontend/src/Components/inputs/SearchBar.tsx
@@ -1,25 +1,25 @@
-import { MagnifyingGlassIcon } from "@heroicons/react/24/solid";
+import { MagnifyingGlassIcon } from '@heroicons/react/24/solid';
export default function SearchBar({
- searchTerm,
- changeCallback,
+ searchTerm,
+ changeCallback
}: {
- searchTerm: string;
- changeCallback: (arg: string) => void;
+ searchTerm: string;
+ changeCallback: (arg: string) => void;
}) {
- return (
-
-
-
- changeCallback(e.target.value)}
- autoFocus={true}
- />
-
-
- );
+ return (
+
+
+
+ changeCallback(e.target.value)}
+ autoFocus={true}
+ />
+
+
+ );
}
diff --git a/frontend/src/Components/inputs/SubmitButton.tsx b/frontend/src/Components/inputs/SubmitButton.tsx
index aea2ea21..18b4297e 100644
--- a/frontend/src/Components/inputs/SubmitButton.tsx
+++ b/frontend/src/Components/inputs/SubmitButton.tsx
@@ -1,8 +1,8 @@
export function SubmitButton({ errorMessage }: { errorMessage: string }) {
- return (
-
-
- {errorMessage}
-
- );
+ return (
+
+
+ {errorMessage}
+
+ );
}
diff --git a/frontend/src/Components/inputs/TextAreaInput.tsx b/frontend/src/Components/inputs/TextAreaInput.tsx
index b903779f..e21b149e 100644
--- a/frontend/src/Components/inputs/TextAreaInput.tsx
+++ b/frontend/src/Components/inputs/TextAreaInput.tsx
@@ -1,46 +1,46 @@
-import { FieldErrors } from "react-hook-form";
+import { FieldErrors } from 'react-hook-form';
interface TextAreaProps {
- label: string;
- interfaceRef: string;
- required: boolean;
- length: number | null;
- errors: FieldErrors;
- register: Function;
+ label: string;
+ interfaceRef: string;
+ required: boolean;
+ length: number | null;
+ errors: FieldErrors;
+ register: Function;
}
export function TextAreaInput({
- label,
- interfaceRef,
- required,
- length,
- errors,
- register,
+ label,
+ interfaceRef,
+ required,
+ length,
+ errors,
+ register
}: TextAreaProps) {
- const options = {
- required: {
- value: required,
- message: `${label} is required`,
- },
- ...(length !== null && {
- maxLength: {
- value: length,
- message: `${label} should be ${length} characters or less`,
- },
- }),
- };
- return (
-
-
- {label}
-
-
-
- {errors[interfaceRef]?.message?.toString()}
-
-
- );
+ const options = {
+ required: {
+ value: required,
+ message: `${label} is required`
+ },
+ ...(length !== null && {
+ maxLength: {
+ value: length,
+ message: `${label} should be ${length} characters or less`
+ }
+ })
+ };
+ return (
+
+
+ {label}
+
+
+
+ {errors[interfaceRef]?.message?.toString()}
+
+
+ );
}
diff --git a/frontend/src/Components/inputs/TextInput.tsx b/frontend/src/Components/inputs/TextInput.tsx
index 361fba2f..9970937f 100644
--- a/frontend/src/Components/inputs/TextInput.tsx
+++ b/frontend/src/Components/inputs/TextInput.tsx
@@ -1,55 +1,55 @@
-import { FieldErrors } from "react-hook-form";
+import { FieldErrors } from 'react-hook-form';
interface TextProps {
- label: string;
- interfaceRef: string;
- required: boolean;
- length: number | null;
- errors: FieldErrors;
- register: Function;
- password?: boolean;
- isFocused?: boolean;
- autoComplete?: string;
+ label: string;
+ interfaceRef: string;
+ required: boolean;
+ length: number | null;
+ errors: FieldErrors;
+ register: Function;
+ password?: boolean;
+ isFocused?: boolean;
+ autoComplete?: string;
}
export function TextInput({
- label,
- interfaceRef,
- required,
- length,
- errors,
- register,
- password = false,
- isFocused = false,
- autoComplete = "on",
+ label,
+ interfaceRef,
+ required,
+ length,
+ errors,
+ register,
+ password = false,
+ isFocused = false,
+ autoComplete = 'on'
}: TextProps) {
- const options = {
- required: {
- value: required,
- message: `${label} is required`,
- },
- ...(length !== null && {
- maxLength: {
- value: length,
- message: `${label} should be ${length} characters or less`,
- },
- }),
- };
- return (
-
-
- {label}
-
-
-
- {errors[interfaceRef]?.message?.toString()}
-
-
- );
+ const options = {
+ required: {
+ value: required,
+ message: `${label} is required`
+ },
+ ...(length !== null && {
+ maxLength: {
+ value: length,
+ message: `${label} should be ${length} characters or less`
+ }
+ })
+ };
+ return (
+
+
+ {label}
+
+
+
+ {errors[interfaceRef]?.message?.toString()}
+
+
+ );
}
diff --git a/frontend/src/Components/inputs/index.js b/frontend/src/Components/inputs/index.js
index ba60b494..1afc6901 100644
--- a/frontend/src/Components/inputs/index.js
+++ b/frontend/src/Components/inputs/index.js
@@ -1,5 +1,5 @@
-export { CloseX } from "./CloseX";
-export { TextAreaInput } from "./TextAreaInput";
-export { DropdownInput } from "./DropdownInput";
-export { TextInput } from "./TextInput";
-export { SubmitButton } from "./SubmitButton";
+export { CloseX } from './CloseX';
+export { TextAreaInput } from './TextAreaInput';
+export { DropdownInput } from './DropdownInput';
+export { TextInput } from './TextInput';
+export { SubmitButton } from './SubmitButton';
diff --git a/frontend/src/Components/pill-labels/DarkGreenPill.tsx b/frontend/src/Components/pill-labels/DarkGreenPill.tsx
index e7e0fbc7..39028b08 100644
--- a/frontend/src/Components/pill-labels/DarkGreenPill.tsx
+++ b/frontend/src/Components/pill-labels/DarkGreenPill.tsx
@@ -1,5 +1,5 @@
export default function DarkGreenPill(props) {
- return (
- {props.children}
- );
+ return (
+ {props.children}
+ );
}
diff --git a/frontend/src/Components/pill-labels/GreyPill.tsx b/frontend/src/Components/pill-labels/GreyPill.tsx
index c36005a3..e2dab250 100644
--- a/frontend/src/Components/pill-labels/GreyPill.tsx
+++ b/frontend/src/Components/pill-labels/GreyPill.tsx
@@ -1,5 +1,7 @@
export default function GreyPill(props) {
- return (
- {props.children}
- );
+ return (
+
+ {props.children}
+
+ );
}
diff --git a/frontend/src/Components/pill-labels/LightGreenPill.tsx b/frontend/src/Components/pill-labels/LightGreenPill.tsx
index cc50fe2f..75749c37 100644
--- a/frontend/src/Components/pill-labels/LightGreenPill.tsx
+++ b/frontend/src/Components/pill-labels/LightGreenPill.tsx
@@ -1,5 +1,7 @@
export default function LightGreenPill(props) {
- return (
- {props.children}
- );
+ return (
+
+ {props.children}
+
+ );
}
diff --git a/frontend/src/Components/pill-labels/RedPill.tsx b/frontend/src/Components/pill-labels/RedPill.tsx
index fff6d7f1..68885dba 100644
--- a/frontend/src/Components/pill-labels/RedPill.tsx
+++ b/frontend/src/Components/pill-labels/RedPill.tsx
@@ -1,5 +1,7 @@
export default function RedPill(props) {
- return (
- {props.children}
- );
+ return (
+
+ {props.children}
+
+ );
}
diff --git a/frontend/src/Components/pill-labels/TealPill.tsx b/frontend/src/Components/pill-labels/TealPill.tsx
index 212ff110..47a19046 100644
--- a/frontend/src/Components/pill-labels/TealPill.tsx
+++ b/frontend/src/Components/pill-labels/TealPill.tsx
@@ -1,5 +1,7 @@
export default function TealPill(props) {
- return (
- {props.children}
- );
+ return (
+
+ {props.children}
+
+ );
}
diff --git a/frontend/src/Components/pill-labels/YellowPill.tsx b/frontend/src/Components/pill-labels/YellowPill.tsx
index 3803116b..25983f4a 100644
--- a/frontend/src/Components/pill-labels/YellowPill.tsx
+++ b/frontend/src/Components/pill-labels/YellowPill.tsx
@@ -1,5 +1,7 @@
export default function YellowPill(props) {
- return (
- {props.children}
- );
+ return (
+
+ {props.children}
+
+ );
}
diff --git a/frontend/src/Layouts/AuthenticatedLayout.tsx b/frontend/src/Layouts/AuthenticatedLayout.tsx
index b2f9a225..03e55a45 100644
--- a/frontend/src/Layouts/AuthenticatedLayout.tsx
+++ b/frontend/src/Layouts/AuthenticatedLayout.tsx
@@ -1,18 +1,20 @@
-import { PropsWithChildren } from "react";
-import Navbar from "@/Components/Navbar";
+import { PropsWithChildren } from 'react';
+import Navbar from '@/Components/Navbar';
export default function AuthenticatedLayout({
- title,
- children,
+ title,
+ children
}: PropsWithChildren<{ title: string }>) {
- return (
-
- );
+ return (
+
+ );
}
diff --git a/frontend/src/Layouts/GuestLayout.tsx b/frontend/src/Layouts/GuestLayout.tsx
index 54e7eb89..b479e9ba 100644
--- a/frontend/src/Layouts/GuestLayout.tsx
+++ b/frontend/src/Layouts/GuestLayout.tsx
@@ -1,18 +1,18 @@
-import ApplicationLogo from "../Components/ApplicationLogo";
-import { PropsWithChildren } from "react";
+import ApplicationLogo from '../Components/ApplicationLogo';
+import { PropsWithChildren } from 'react';
export default function Guest({ children }: PropsWithChildren) {
- return (
-
-
+ return (
+
- );
+
+ {children}
+
+
+ );
}
diff --git a/frontend/src/Pages/AdminDashboard.tsx b/frontend/src/Pages/AdminDashboard.tsx
index 1aa88d1e..7f684998 100644
--- a/frontend/src/Pages/AdminDashboard.tsx
+++ b/frontend/src/Pages/AdminDashboard.tsx
@@ -1,110 +1,129 @@
-import { useAuth } from "@/AuthContext";
-import MilestonesBarChart from "@/Components/MilestonesBarChart";
-import ActivityChart from "@/Components/MonthActivityChart";
-import StatsCard from "@/Components/StatsCard";
-import TopProgPieChart from "@/Components/TopProgActivityPieChart";
-import { AdminDashboardJoin } from "@/common";
-import useSWR from "swr";
-import convertSeconds from "../Components/ConvertSeconds";
-import { useContext } from "react";
-import { ThemeContext } from "@/Components/ThemeContext";
+import { useAuth } from '@/AuthContext';
+import MilestonesBarChart from '@/Components/MilestonesBarChart';
+import ActivityChart from '@/Components/MonthActivityChart';
+import StatsCard from '@/Components/StatsCard';
+import TopProgPieChart from '@/Components/TopProgActivityPieChart';
+import { AdminDashboardJoin } from '@/common';
+import useSWR from 'swr';
+import convertSeconds from '../Components/ConvertSeconds';
+import { useContext } from 'react';
+import { ThemeContext } from '@/Components/ThemeContext';
export default function AdminDashboard() {
- const { user } = useAuth();
- const { data, error, isLoading } = useSWR(
- `/api/users/${user.id}/admin-dashboard`,
- );
- const { theme } = useContext(ThemeContext);
+ const { user } = useAuth();
+ const { data, error, isLoading } = useSWR(
+ `/api/users/${user.id}/admin-dashboard`
+ );
+ const { theme } = useContext(ThemeContext);
- if (error || isLoading) return
;
- const avgActivity = convertSeconds(data.avg_daily_activity);
- const totalActivity = convertSeconds(data.total_weekly_activity);
- console.log(data);
+ if (error || isLoading) return
;
+ const avgActivity = convertSeconds(data.avg_daily_activity);
+ const totalActivity = convertSeconds(data.total_weekly_activity);
+ console.log(data);
- return (
-
-
{data.facility_name}
-
-
-
-
Overall Platform Engagement
-
-
-
-
-
-
-
-
-
Top Milestone Completion Per Course
-
-
+ return (
+
+
{data.facility_name}
+
+
+
+
+ Overall Platform Engagement
+
+
+
+
+
+
+
+
+
+
Top Milestone Completion Per Course
+
+
+
+ {/* Top course engagement */}
+
+
Top Course Engagement
+
+
+ {/* TO DO: caption needs to be added */}
+
+
+
+ Course Name
+ Time Spent
+
+
+
+ {!error &&
+ !isLoading &&
+ data.top_program_activity.map(
+ (course: any, index: number) => {
+ var courseTime: string;
+ if (course.hours_engaged < 1)
+ courseTime =
+ Math.round(
+ course.hours_engaged *
+ 60
+ ) + ' min';
+ else {
+ const hours = Math.floor(
+ course.hours_engaged
+ );
+ const leftoverMins =
+ Math.round(
+ course.hours_engaged *
+ 60
+ ) % 60;
+ if (leftoverMins == 0)
+ courseTime = hours + ' hrs';
+ else
+ courseTime =
+ hours +
+ ' hr ' +
+ leftoverMins +
+ ' min';
+ }
+ var legendColor =
+ 'bg-teal-' +
+ (index + 1).toString();
+ // TO DO: temporary fix... figure out why teal-5 doesnt render immediately
+ if (index == 4)
+ legendColor =
+ theme == 'light'
+ ? 'bg-[#002E2A]'
+ : 'bg-[#B0DFDA]';
+ return (
+
+
+
+ {course.program_name}
+
+ {courseTime}
+
+ );
+ }
+ )}
+
+
+
+
+
- {/* Top course engagement */}
-
-
Top Course Engagement
-
-
- {/* TO DO: caption needs to be added */}
-
-
-
- Course Name
- Time Spent
-
-
-
- {!error &&
- !isLoading &&
- data.top_program_activity.map(
- (course: any, index: number) => {
- var courseTime: string;
- if (course.hours_engaged < 1)
- courseTime =
- Math.round(course.hours_engaged * 60) + " min";
- else {
- const hours = Math.floor(course.hours_engaged);
- const leftoverMins =
- Math.round(course.hours_engaged * 60) % 60;
- if (leftoverMins == 0) courseTime = hours + " hrs";
- else
- courseTime = hours + " hr " + leftoverMins + " min";
- }
- var legendColor = "bg-teal-" + (index + 1).toString();
- // TO DO: temporary fix... figure out why teal-5 doesnt render immediately
- if (index == 4)
- legendColor =
- theme == "light" ? "bg-[#002E2A]" : "bg-[#B0DFDA]";
- return (
-
-
-
- {course.program_name}
-
- {courseTime}
-
- );
- },
- )}
-
-
-
-
-
-
- );
+ );
}
diff --git a/frontend/src/Pages/Auth/Consent.tsx b/frontend/src/Pages/Auth/Consent.tsx
index d27c413c..fb040dcc 100644
--- a/frontend/src/Pages/Auth/Consent.tsx
+++ b/frontend/src/Pages/Auth/Consent.tsx
@@ -1,9 +1,9 @@
-import ConsentForm from "../../Components/forms/ConsentForm";
+import ConsentForm from '../../Components/forms/ConsentForm';
export default function Consent() {
- return (
-
-
-
- );
+ return (
+
+
+
+ );
}
diff --git a/frontend/src/Pages/Auth/Login.tsx b/frontend/src/Pages/Auth/Login.tsx
index b14362b9..d718c71f 100644
--- a/frontend/src/Pages/Auth/Login.tsx
+++ b/frontend/src/Pages/Auth/Login.tsx
@@ -1,24 +1,24 @@
-import { useEffect } from "react";
-import LoginForm from "@/Components/forms/LoginForm";
-import GuestLayout from "@/Layouts/GuestLayout";
+import { useEffect } from 'react';
+import LoginForm from '@/Components/forms/LoginForm';
+import GuestLayout from '@/Layouts/GuestLayout';
export default function Login({ status }: { status?: string }) {
- useEffect(() => {
- // if there is no ?flow= query parameter, redirect to /self-service/login/browser
- if (!window.location.search.includes("flow=")) {
- window.location.href = "/self-service/login/browser";
- }
- }, []);
- return (
- <>
-
-
- {status && (
-
- {status}
-
- )}
-
-
- >
- );
+ useEffect(() => {
+ // if there is no ?flow= query parameter, redirect to /self-service/login/browser
+ if (!window.location.search.includes('flow=')) {
+ window.location.href = '/self-service/login/browser';
+ }
+ }, []);
+ return (
+ <>
+
+
+ {status && (
+
+ {status}
+
+ )}
+
+
+ >
+ );
}
diff --git a/frontend/src/Pages/Auth/ResetPassword.tsx b/frontend/src/Pages/Auth/ResetPassword.tsx
index 075b31f1..a0a468c0 100644
--- a/frontend/src/Pages/Auth/ResetPassword.tsx
+++ b/frontend/src/Pages/Auth/ResetPassword.tsx
@@ -1,11 +1,11 @@
-import ChangePasswordForm from "../../Components/forms/ChangePasswordForm";
-import GuestLayout from "../..//Layouts/GuestLayout";
+import ChangePasswordForm from '../../Components/forms/ChangePasswordForm';
+import GuestLayout from '../..//Layouts/GuestLayout';
export default function ResetPassword() {
- return (
-
-
-
-
- );
+ return (
+
+
+
+
+ );
}
diff --git a/frontend/src/Pages/CourseCatalog.tsx b/frontend/src/Pages/CourseCatalog.tsx
index 4f9834a6..8f68c30e 100644
--- a/frontend/src/Pages/CourseCatalog.tsx
+++ b/frontend/src/Pages/CourseCatalog.tsx
@@ -1,77 +1,83 @@
-import { useAuth } from "@/AuthContext";
-import PageNav from "@/Components/PageNav";
-import ToggleView, { ViewType } from "@/Components/ToggleView";
-import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
-import { useEffect, useState } from "react";
-import CatalogCourseCard from "@/Components/CatalogCourseCard";
-import SearchBar from "@/Components/inputs/SearchBar";
-import { CourseCatalogue, Program, ServerResponse } from "@/common";
-import useSWR from "swr";
-import DropdownControl from "@/Components/inputs/DropdownControl";
+import { useAuth } from '@/AuthContext';
+import PageNav from '@/Components/PageNav';
+import ToggleView, { ViewType } from '@/Components/ToggleView';
+import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
+import { useEffect, useState } from 'react';
+import CatalogCourseCard from '@/Components/CatalogCourseCard';
+import SearchBar from '@/Components/inputs/SearchBar';
+import { CourseCatalogue, Program, ServerResponse } from '@/common';
+import useSWR from 'swr';
+import DropdownControl from '@/Components/inputs/DropdownControl';
// TO DO: make it paginated
// TO DO: mutate the data on save so it stays the same across both views
export default function CourseCatalog() {
- const { user } = useAuth();
- const [activeView, setActiveView] = useState
(ViewType.Grid);
- const [searchTerm, setSearchTerm] = useState("");
- const [order, setOrder] = useState("asc");
- const { data, mutate } = useSWR>(
- `/api/users/${user.id}/catalogue?search=${searchTerm}&order=${order}`,
- );
+ const { user } = useAuth();
+ const [activeView, setActiveView] = useState(ViewType.Grid);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [order, setOrder] = useState('asc');
+ const { data, mutate } = useSWR>(
+ `/api/users/${user.id}/catalogue?search=${searchTerm}&order=${order}`
+ );
- useEffect(() => {
- console.log(data);
- }, [data]);
+ useEffect(() => {
+ console.log(data);
+ }, [data]);
- function callMutate() {
- console.log("called");
- mutate();
- }
+ function callMutate() {
+ console.log('called');
+ mutate();
+ }
- function handleSearch(newSearch: string) {
- setSearchTerm(newSearch);
- // setPageQuery(1);
- }
+ function handleSearch(newSearch: string) {
+ setSearchTerm(newSearch);
+ // setPageQuery(1);
+ }
- if (!data) return
;
+ if (!data) return
;
- return (
-
-
-
-
-
Course Catalog
-
-
-
-
-
-
- {/* render on gallery or list view */}
-
- {data?.map((course: CourseCatalogue) => {
- return (
-
- );
- })}
-
-
-
- );
+ return (
+
+
+
+
+
Course Catalog
+
+
+
+
+
+
+ {/* render on gallery or list view */}
+
+ {data?.map((course: CourseCatalogue) => {
+ return (
+
+ );
+ })}
+
+
+
+ );
}
diff --git a/frontend/src/Pages/Dashboard.tsx b/frontend/src/Pages/Dashboard.tsx
index 5ac130ad..ccfafbea 100644
--- a/frontend/src/Pages/Dashboard.tsx
+++ b/frontend/src/Pages/Dashboard.tsx
@@ -1,21 +1,21 @@
-import PageNav from "@/Components/PageNav";
-import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
-import { useAuth } from "@/AuthContext";
-import { UserRole } from "@/common";
-import StudentDashboard from "./StudentDashboard";
-import AdminDashboard from "./AdminDashboard";
+import PageNav from '@/Components/PageNav';
+import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
+import { useAuth } from '@/AuthContext';
+import { UserRole } from '@/common';
+import StudentDashboard from './StudentDashboard';
+import AdminDashboard from './AdminDashboard';
export default function Dashboard() {
- const { user } = useAuth();
+ const { user } = useAuth();
- return (
-
-
- {user.role == UserRole.Student ? (
-
- ) : (
-
- )}
-
- );
+ return (
+
+
+ {user.role == UserRole.Student ? (
+
+ ) : (
+
+ )}
+
+ );
}
diff --git a/frontend/src/Pages/Error.tsx b/frontend/src/Pages/Error.tsx
index 7a6b67e4..8ec84ef3 100644
--- a/frontend/src/Pages/Error.tsx
+++ b/frontend/src/Pages/Error.tsx
@@ -1,25 +1,25 @@
-import GuestLayout from "@/Layouts/GuestLayout";
+import GuestLayout from '@/Layouts/GuestLayout';
export default function Error() {
- return (
- <>
-
-
-
-
- Either there has been an unexpected error, or the page you requested
- was not found.
-
-
{
- window.location.href = "/";
- }}
- >
- Home Page
-
-
-
- >
- );
+ return (
+ <>
+
+
+
+
+ Either there has been an unexpected error, or the page
+ you requested was not found.
+
+
{
+ window.location.href = '/';
+ }}
+ >
+ Home Page
+
+
+
+ >
+ );
}
diff --git a/frontend/src/Pages/MyCourses.tsx b/frontend/src/Pages/MyCourses.tsx
index 53e9fb74..0a61ea4b 100644
--- a/frontend/src/Pages/MyCourses.tsx
+++ b/frontend/src/Pages/MyCourses.tsx
@@ -1,114 +1,125 @@
-import { useAuth } from "@/AuthContext";
-import PageNav from "@/Components/PageNav";
-import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
-import EnrolledCourseCard from "@/Components/EnrolledCourseCard";
-import { useEffect, useState } from "react";
-import ToggleView, { ViewType } from "@/Components/ToggleView";
-import SearchBar from "@/Components/inputs/SearchBar";
-import DropdownControl from "@/Components/inputs/DropdownControl";
-import { Program, ServerResponse } from "@/common";
-import useSWR from "swr";
+import { useAuth } from '@/AuthContext';
+import PageNav from '@/Components/PageNav';
+import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
+import EnrolledCourseCard from '@/Components/EnrolledCourseCard';
+import { useEffect, useState } from 'react';
+import ToggleView, { ViewType } from '@/Components/ToggleView';
+import SearchBar from '@/Components/inputs/SearchBar';
+import DropdownControl from '@/Components/inputs/DropdownControl';
+import { Program, ServerResponse } from '@/common';
+import useSWR from 'swr';
// TO DO: make sure this lives in the right place
export enum CourseStatus {
- Current = "Current",
- Completed = "Completed",
- Pending = "Pending",
- Recent = "Recent",
+ Current = 'Current',
+ Completed = 'Completed',
+ Pending = 'Pending',
+ Recent = 'Recent'
}
enum TabType {
- Current = "in_progress",
- Completed = "completed",
- Favorited = "is_favorited",
- All = "all",
+ Current = 'in_progress',
+ Completed = 'completed',
+ Favorited = 'is_favorited',
+ All = 'all'
}
// TO DO: go back and fix all "key" values that are mapped and make them intentional
export default function MyCourses() {
- const { user } = useAuth();
- const [searchTerm, setSearchTerm] = useState("");
- const [sort, setSort] = useState("order=asc&order_by=program_name");
- const [activeTab, setActiveTab] = useState(TabType.Current);
- const [activeView, setActiveView] = useState(ViewType.Grid);
+ const { user } = useAuth();
+ const [searchTerm, setSearchTerm] = useState('');
+ const [sort, setSort] = useState('order=asc&order_by=program_name');
+ const [activeTab, setActiveTab] = useState(TabType.Current);
+ const [activeView, setActiveView] = useState(ViewType.Grid);
- const { data, mutate } = useSWR>(
- `/api/users/${user.id}/programs?${
- sort +
- (activeTab !== TabType.All ? `&tags=${activeTab}` : "") +
- (searchTerm ? `&search=${searchTerm}` : "")
- }`,
- );
+ const { data, mutate } = useSWR>(
+ `/api/users/${user.id}/programs?${
+ sort +
+ (activeTab !== TabType.All ? `&tags=${activeTab}` : '') +
+ (searchTerm ? `&search=${searchTerm}` : '')
+ }`
+ );
- useEffect(() => {
- console.log(data);
- }, [data]);
+ useEffect(() => {
+ console.log(data);
+ }, [data]);
- function callMutate() {
- console.log("called");
- mutate();
- }
+ function callMutate() {
+ console.log('called');
+ mutate();
+ }
- const handleChange = (newSearch: string) => {
- setSearchTerm(newSearch);
- //setPageQuery(1);
- };
+ const handleChange = (newSearch: string) => {
+ setSearchTerm(newSearch);
+ //setPageQuery(1);
+ };
- function handleDropdownChange(value: string) {
- setSort(value);
- }
+ function handleDropdownChange(value: string) {
+ setSort(value);
+ }
- return (
-
-
-
-
My Courses
-
- {Object.entries(TabType).map(([key]) => (
- setActiveTab(TabType[key])}
- key={Math.random()}
- >
- {key}
-
- ))}
-
-
- {/* render on gallery or list view */}
-
- {data?.programs?.map((course: any, index: number) => {
- return (
-
- );
- })}
-
-
-
- );
+ return (
+
+
+
+
My Courses
+
+ {Object.entries(TabType).map(([key]) => (
+ setActiveTab(TabType[key])}
+ key={Math.random()}
+ >
+ {key}
+
+ ))}
+
+
+ {/* render on gallery or list view */}
+
+ {data?.programs?.map((course: any, index: number) => {
+ return (
+
+ );
+ })}
+
+
+
+ );
}
diff --git a/frontend/src/Pages/MyProgress.tsx b/frontend/src/Pages/MyProgress.tsx
index a2788003..ee482961 100644
--- a/frontend/src/Pages/MyProgress.tsx
+++ b/frontend/src/Pages/MyProgress.tsx
@@ -1,127 +1,164 @@
-import { useAuth } from "@/AuthContext";
-import PageNav from "@/Components/PageNav";
-import StatsCard from "@/Components/StatsCard";
-import UserActivityMap from "@/Components/UserActivityMap";
-import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
-import DarkGreenPill from "@/Components/pill-labels/DarkGreenPill";
-import TealPill from "@/Components/pill-labels/TealPill";
-import useSWR from "swr";
-import { ServerResponse } from "@/common";
-import convertSeconds from "@/Components/ConvertSeconds";
+import { useAuth } from '@/AuthContext';
+import PageNav from '@/Components/PageNav';
+import StatsCard from '@/Components/StatsCard';
+import UserActivityMap from '@/Components/UserActivityMap';
+import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
+import DarkGreenPill from '@/Components/pill-labels/DarkGreenPill';
+import TealPill from '@/Components/pill-labels/TealPill';
+import useSWR from 'swr';
+import { ServerResponse } from '@/common';
+import convertSeconds from '@/Components/ConvertSeconds';
export default function MyProgress() {
- const { user } = useAuth();
- const { data } = useSWR>(
- `/api/users/${user.id}/programs`,
- );
- const { data: certificates } = useSWR>(
- `/api/users/${user.id}/outcomes?type=certificate`,
- );
+ const { user } = useAuth();
+ const { data } = useSWR>(
+ `/api/users/${user.id}/programs`
+ );
+ const { data: certificates } = useSWR>(
+ `/api/users/${user.id}/outcomes?type=certificate`
+ );
- console.log(data);
+ console.log(data);
- return (
-
-
-
-
My Progress
- {data && (
- <>
-
-
-
-
-
-
-
-
+ return (
+
+
+
+
My Progress
+ {data && (
+ <>
+
+
+
+
+
All Courses
+ {/* dropdown will go here */}
+
+
+
+
+
+ Course Name
+
+ Status
+ Grade
+
+ Hours Spent
+
+
+
+
+ {data.programs.map(
+ (course: any, index: number) => {
+ const courseTotalTime =
+ convertSeconds(
+ course.total_time
+ );
+ return (
+
+
+ {
+ course.program_name
+ }
+
+
+ {course.course_progress ==
+ 100 ? (
+
+ completed
+
+ ) : (
+
+ in progress
+
+ )}
+
+
+ {course?.grade ||
+ '-'}
+
+
+ {courseTotalTime.number +
+ ' ' +
+ courseTotalTime.label}
+
+
+ );
+ }
+ )}
+
+
+
+
+
+
Certificates Earned
+ {/* dropdown will go here */}
+
+
+
+
+
+ Certificate
+
+
+ Date Recieved
+
+
+
+
+ {certificates?.data.map(
+ (certificate: any) => {
+ return (
+
+
+ {
+ certificate.program_name
+ }
+
+
+ {new Date(
+ certificate.created_at.split(
+ 'T'
+ )[0]
+ ).toLocaleDateString(
+ 'en-US'
+ )}
+
+
+ );
+ }
+ )}
+
+
+
+
+ >
+ )}
-
-
-
-
All Courses
- {/* dropdown will go here */}
-
-
-
-
- Course Name
- Status
- Grade
- Hours Spent
-
-
-
- {data.programs.map((course: any, index: number) => {
- const courseTotalTime = convertSeconds(course.total_time);
- return (
-
- {course.program_name}
-
- {course.course_progress == 100 ? (
- completed
- ) : (
- in progress
- )}
-
- {course?.grade || "-"}
-
- {courseTotalTime.number +
- " " +
- courseTotalTime.label}
-
-
- );
- })}
-
-
-
-
-
-
Certificates Earned
- {/* dropdown will go here */}
-
-
-
-
- Certificate
- Date Recieved
-
-
-
- {certificates?.data.map((certificate: any) => {
- return (
-
- {certificate.program_name}
-
- {new Date(
- certificate.created_at.split("T")[0],
- ).toLocaleDateString("en-US")}
-
-
- );
- })}
-
-
-
-
- >
- )}
-
-
- );
+
+ );
}
diff --git a/frontend/src/Pages/ProviderPlatformManagement.tsx b/frontend/src/Pages/ProviderPlatformManagement.tsx
index 0bb8e322..e702ba57 100644
--- a/frontend/src/Pages/ProviderPlatformManagement.tsx
+++ b/frontend/src/Pages/ProviderPlatformManagement.tsx
@@ -1,259 +1,270 @@
-import PageNav from "@/Components/PageNav";
-import ProviderCard from "@/Components/ProviderCard";
-import AddProviderForm from "@/Components/forms/AddProviderForm";
-import EditProviderForm from "@/Components/forms/EditProviderForm";
-import Modal, { ModalType } from "@/Components/Modal";
-import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
-import { OidcClient, ProviderPlatform, ServerResponse } from "@/common";
-import { PlusCircleIcon } from "@heroicons/react/24/outline";
-import { useRef, useState } from "react";
-import useSWR from "swr";
-import Toast, { ToastState } from "@/Components/Toast";
-import { useAuth } from "../AuthContext";
-import RegisterOidcClientForm from "@/Components/forms/RegisterOidcClientForm";
-import NewOidcClientNotification from "@/Components/NewOidcClientNotification";
-import axios from "axios";
+import PageNav from '@/Components/PageNav';
+import ProviderCard from '@/Components/ProviderCard';
+import AddProviderForm from '@/Components/forms/AddProviderForm';
+import EditProviderForm from '@/Components/forms/EditProviderForm';
+import Modal, { ModalType } from '@/Components/Modal';
+import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
+import { OidcClient, ProviderPlatform, ServerResponse } from '@/common';
+import { PlusCircleIcon } from '@heroicons/react/24/outline';
+import { useRef, useState } from 'react';
+import useSWR from 'swr';
+import Toast, { ToastState } from '@/Components/Toast';
+import { useAuth } from '../AuthContext';
+import RegisterOidcClientForm from '@/Components/forms/RegisterOidcClientForm';
+import NewOidcClientNotification from '@/Components/NewOidcClientNotification';
+import axios from 'axios';
interface ToastProps {
- state: ToastState;
- message: string;
+ state: ToastState;
+ message: string;
}
export default function ProviderPlatformManagement() {
- const { user } = useAuth();
- const addProviderModal = useRef
(null);
- const editProviderModal = useRef(null);
- const [editProvider, setEditProvider] = useState(
- null,
- );
- const openOidcClientModal = useRef(null);
- const openOidcRegistrationModal = useRef(null);
- const [oidcClient, setOidcClient] = useState(null);
- const [toast, setToast] = useState({
- state: ToastState.null,
- message: "",
- });
+ const { user } = useAuth();
+ const addProviderModal = useRef(null);
+ const editProviderModal = useRef(null);
+ const [editProvider, setEditProvider] = useState(
+ null
+ );
+ const openOidcClientModal = useRef(null);
+ const openOidcRegistrationModal = useRef(null);
+ const [oidcClient, setOidcClient] = useState(null);
+ const [toast, setToast] = useState({
+ state: ToastState.null,
+ message: ''
+ });
- const {
- data: providers,
- mutate,
- error,
- isLoading,
- } = useSWR(`/api/provider-platforms`);
+ const {
+ data: providers,
+ mutate,
+ error,
+ isLoading
+ } = useSWR(`/api/provider-platforms`);
- // TO DO: SORT THIS IN THE BACKEND AND RETURN SORTED
- providers?.data.sort(function (
- providerA: ProviderPlatform,
- providerB: ProviderPlatform,
- ) {
- if (providerA.state === "enabled" && providerB.state !== "enabled") {
- return -1;
- } else if (providerA.state !== "enabled" && providerB.state === "enabled") {
- return 1;
- } else if (
- providerA.state === "archived" &&
- providerB.state !== "archived"
+ // TO DO: SORT THIS IN THE BACKEND AND RETURN SORTED
+ providers?.data.sort(function (
+ providerA: ProviderPlatform,
+ providerB: ProviderPlatform
) {
- return 1;
- } else if (
- providerA.state !== "archived" &&
- providerB.state === "archived"
- ) {
- return -1;
- } else {
- return 0;
- }
- });
-
- function resetModal() {
- setTimeout(() => {
- setEditProvider(null);
- }, 200);
- }
-
- function openEditProvider(provider: ProviderPlatform) {
- setEditProvider(provider);
- editProviderModal.current?.showModal();
- }
+ if (providerA.state === 'enabled' && providerB.state !== 'enabled') {
+ return -1;
+ } else if (
+ providerA.state !== 'enabled' &&
+ providerB.state === 'enabled'
+ ) {
+ return 1;
+ } else if (
+ providerA.state === 'archived' &&
+ providerB.state !== 'archived'
+ ) {
+ return 1;
+ } else if (
+ providerA.state !== 'archived' &&
+ providerB.state === 'archived'
+ ) {
+ return -1;
+ } else {
+ return 0;
+ }
+ });
- function updateProvider(state: ToastState, message: string) {
- mutate();
- if (state && message) {
- setToast({
- state: state,
- message: message,
- });
+ function resetModal() {
+ setTimeout(() => {
+ setEditProvider(null);
+ }, 200);
}
- editProviderModal.current?.close();
- addProviderModal.current?.close();
- resetModal();
- }
- const registerOidcClient = (prov: ProviderPlatform) => {
- openOidcClientModal.current?.showModal();
- setEditProvider(prov);
- };
-
- const onRegisterOidcClientClose = (
- response: ServerResponse,
- state: ToastState,
- ) => {
- openOidcClientModal.current?.close();
- setEditProvider(null);
- if (!response && state == ToastState.success) {
- setToast({
- state: state,
- message: "OIDC client registered successfully.",
- });
- } else if (!response && state == ToastState.error) {
- setToast({
- state: state,
- message: "Failed to register OIDC client.",
- });
- } else {
- console.log(response.data[0]);
- setOidcClient(response.data[0] as OidcClient);
- openOidcRegistrationModal.current?.showModal();
+ function openEditProvider(provider: ProviderPlatform) {
+ setEditProvider(provider);
+ editProviderModal.current?.showModal();
}
- mutate();
- state &&
- response &&
- setToast({
- state: state,
- message: response.message,
- });
- };
- const showAuthorizationInfo = async (provider: ProviderPlatform) => {
- const resp = await axios(`/api/oidc/clients/${provider.oidc_id}`);
- if (resp.data) {
- setOidcClient(resp.data.data[0] as OidcClient);
- openOidcRegistrationModal.current?.showModal();
- return;
+ function updateProvider(state: ToastState, message: string) {
+ mutate();
+ if (state && message) {
+ setToast({
+ state: state,
+ message: message
+ });
+ }
+ editProviderModal.current?.close();
+ addProviderModal.current?.close();
+ resetModal();
}
- };
- return (
-
-
-
-
Provider Platforms
-
-
{/* TO DO: this is where SEARCH and SORT will go */}
-
{
- addProviderModal.current?.showModal();
- }}
- >
-
- Add Provider
-
-
-
-
-
- Name
- Registered
- Status
- Actions
-
-
-
- {!isLoading && !error ? (
- providers.data.map((provider: ProviderPlatform) => {
- return (
- registerOidcClient(provider)}
- showAuthorizationInfo={() =>
- showAuthorizationInfo(provider)
- }
- />
- );
- })
- ) : (
-
- No provider platforms
-
- )}
-
-
-
- {/* Modals */}
- {
- updateProvider(state, message);
- }}
- />
+ const registerOidcClient = (prov: ProviderPlatform) => {
+ openOidcClientModal.current?.showModal();
+ setEditProvider(prov);
+ };
+
+ const onRegisterOidcClientClose = (
+ response: ServerResponse,
+ state: ToastState
+ ) => {
+ openOidcClientModal.current?.close();
+ setEditProvider(null);
+ if (!response && state == ToastState.success) {
+ setToast({
+ state: state,
+ message: 'OIDC client registered successfully.'
+ });
+ } else if (!response && state == ToastState.error) {
+ setToast({
+ state: state,
+ message: 'Failed to register OIDC client.'
+ });
+ } else {
+ console.log(response.data[0]);
+ setOidcClient(response.data[0] as OidcClient);
+ openOidcRegistrationModal.current?.showModal();
}
- ref={addProviderModal}
- />
- {
- updateProvider(state, message);
- }}
- provider={editProvider}
- />
- ) : (
-
- )
+ mutate();
+ state &&
+ response &&
+ setToast({
+ state: state,
+ message: response.message
+ });
+ };
+
+ const showAuthorizationInfo = async (provider: ProviderPlatform) => {
+ const resp = await axios(`/api/oidc/clients/${provider.oidc_id}`);
+ if (resp.data) {
+ setOidcClient(resp.data.data[0] as OidcClient);
+ openOidcRegistrationModal.current?.showModal();
+ return;
}
- ref={editProviderModal}
- />
- openOidcClientModal.current?.close()}
+ };
+
+ return (
+
+
- ) : (
-
- )
- }
- ref={openOidcClientModal}
- />
- openOidcRegistrationModal.current?.close()}
+
+
Provider Platforms
+
+
+ {/* TO DO: this is where SEARCH and SORT will go */}
+
+
{
+ addProviderModal.current?.showModal();
+ }}
+ >
+
+ Add Provider
+
+
+
+
+
+ Name
+ Registered
+ Status
+ Actions
+
+
+
+ {!isLoading && !error ? (
+ providers.data.map((provider: ProviderPlatform) => {
+ return (
+
+ registerOidcClient(provider)
+ }
+ showAuthorizationInfo={() =>
+ showAuthorizationInfo(provider)
+ }
+ />
+ );
+ })
+ ) : (
+
+ No provider platforms
+
+ )}
+
+
+
+ {/* Modals */}
+ {
+ updateProvider(state, message);
+ }}
+ />
+ }
+ ref={addProviderModal}
/>
- ) : (
-
- )
- }
- ref={openOidcRegistrationModal}
- />
- {/* Toasts */}
- {toast.state !== ToastState.null && (
- setToast({ state: ToastState.null, message: "" })}
- />
- )}
-
- );
+ {
+ updateProvider(state, message);
+ }}
+ provider={editProvider}
+ />
+ ) : (
+
+ )
+ }
+ ref={editProviderModal}
+ />
+ openOidcClientModal.current?.close()}
+ />
+ ) : (
+
+ )
+ }
+ ref={openOidcClientModal}
+ />
+
+ openOidcRegistrationModal.current?.close()
+ }
+ />
+ ) : (
+
+ )
+ }
+ ref={openOidcRegistrationModal}
+ />
+ {/* Toasts */}
+ {toast.state !== ToastState.null && (
+
+ setToast({ state: ToastState.null, message: '' })
+ }
+ />
+ )}
+
+ );
}
diff --git a/frontend/src/Pages/ProviderUserManagement.tsx b/frontend/src/Pages/ProviderUserManagement.tsx
index bb41fd65..d3b78909 100644
--- a/frontend/src/Pages/ProviderUserManagement.tsx
+++ b/frontend/src/Pages/ProviderUserManagement.tsx
@@ -1,382 +1,404 @@
-import { useEffect, useRef, useState } from "react";
-import axios from "axios";
-import AuthenticatedLayout from "../Layouts/AuthenticatedLayout";
-import { useParams } from "react-router-dom";
+import { useEffect, useRef, useState } from 'react';
+import axios from 'axios';
+import AuthenticatedLayout from '../Layouts/AuthenticatedLayout';
+import { useParams } from 'react-router-dom';
import {
- PaginatedResponse,
- PaginationMeta,
- ProviderPlatform,
- ProviderUser,
- UserImports,
-} from "../common";
-import PageNav from "../Components/PageNav";
-import Toast, { ToastState } from "../Components/Toast";
-import Modal, { ModalType } from "../Components/Modal";
-import { useAuth } from "../AuthContext";
-import MapUserForm from "@/Components/forms/MapUserForm";
-import PrimaryButton from "@/Components/PrimaryButton";
-import ShowImportedUsers from "@/Components/forms/ShowImportedUsers";
-import Pagination from "@/Components/Pagination";
-import useSWR from "swr";
-import ConfirmImportAllUsersForm from "@/Components/forms/ConfirmImportAllUsersForm";
+ PaginatedResponse,
+ PaginationMeta,
+ ProviderPlatform,
+ ProviderUser,
+ UserImports
+} from '../common';
+import PageNav from '../Components/PageNav';
+import Toast, { ToastState } from '../Components/Toast';
+import Modal, { ModalType } from '../Components/Modal';
+import { useAuth } from '../AuthContext';
+import MapUserForm from '@/Components/forms/MapUserForm';
+import PrimaryButton from '@/Components/PrimaryButton';
+import ShowImportedUsers from '@/Components/forms/ShowImportedUsers';
+import Pagination from '@/Components/Pagination';
+import useSWR from 'swr';
+import ConfirmImportAllUsersForm from '@/Components/forms/ConfirmImportAllUsersForm';
export default function ProviderUserManagement() {
- const auth = useAuth();
- const mapUserModal = useRef(null);
- const importedUsersModal = useRef(null);
- const importAllUsersModal = useRef(null);
- const [displayToast, setDisplayToast] = useState(false);
- const [searchTerm, setSearchTerm] = useState("");
- const [usersToImport, setUsersToImport] = useState([]);
- const [userToMap, setUserToMap] = useState(null);
- const [perPage, setPerPage] = useState(10);
- const [currentPage, setCurrentPage] = useState(1);
- const { providerId } = useParams();
- const [meta, setMeta] = useState({
- current_page: 1,
- per_page: 10,
- total: 0,
- last_page: 0,
- });
- const [provider, setProvider] = useState(null);
- const [importedUsers, setImportedUsers] = useState([]);
- const [cache, setCache] = useState(false);
- const [isLoading, setIsLoading] = useState(true);
- const [error, setError] = useState(false);
+ const auth = useAuth();
+ const mapUserModal = useRef(null);
+ const importedUsersModal = useRef(null);
+ const importAllUsersModal = useRef(null);
+ const [displayToast, setDisplayToast] = useState(false);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [usersToImport, setUsersToImport] = useState([]);
+ const [userToMap, setUserToMap] = useState(null);
+ const [perPage, setPerPage] = useState(10);
+ const [currentPage, setCurrentPage] = useState(1);
+ const { providerId } = useParams();
+ const [meta, setMeta] = useState({
+ current_page: 1,
+ per_page: 10,
+ total: 0,
+ last_page: 0
+ });
+ const [provider, setProvider] = useState(null);
+ const [importedUsers, setImportedUsers] = useState([]);
+ const [cache, setCache] = useState(false);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(false);
- const [toast, setToast] = useState({
- state: ToastState.null,
- message: "",
- reset: () => {},
- });
- const { data, mutate } = useSWR>(
- `/api/actions/provider-platforms/${providerId}/get-users?page=${currentPage}&per_page=${perPage}&clear_cache=${cache}`,
- );
+ const [toast, setToast] = useState({
+ state: ToastState.null,
+ message: '',
+ reset: () => {}
+ });
+ const { data, mutate } = useSWR>(
+ `/api/actions/provider-platforms/${providerId}/get-users?page=${currentPage}&per_page=${perPage}&clear_cache=${cache}`
+ );
- const changePage = (page: number) => {
- setCurrentPage(page);
- };
+ const changePage = (page: number) => {
+ setCurrentPage(page);
+ };
- const handleChangeUsersPerPage = (
- e: React.ChangeEvent,
- ) => {
- setPerPage(parseInt(e.target.value));
- setCurrentPage(1); // Reset to the first page when changing per page
- };
+ const handleChangeUsersPerPage = (
+ e: React.ChangeEvent
+ ) => {
+ setPerPage(parseInt(e.target.value));
+ setCurrentPage(1); // Reset to the first page when changing per page
+ };
- const handleRefetch = () => {
- setCache(true);
- mutate();
- };
+ const handleRefetch = () => {
+ setCache(true);
+ mutate();
+ };
- const showToast = (message: string, state: ToastState) => {
- setToast({
- state,
- message,
- reset: () => {
+ const showToast = (message: string, state: ToastState) => {
setToast({
- state: ToastState.success,
- message: "",
- reset: () => {
- setDisplayToast(false);
- },
+ state,
+ message,
+ reset: () => {
+ setToast({
+ state: ToastState.success,
+ message: '',
+ reset: () => {
+ setDisplayToast(false);
+ }
+ });
+ }
});
- },
- });
- setDisplayToast(true);
- };
+ setDisplayToast(true);
+ };
- async function handleImportAllPrograms() {
- try {
- let resp = await axios.post(
- `/api/actions/provider-platforms/${providerId}/import-programs`,
- );
- if (resp.status != 200) {
- showToast(
- "error importing all or some programs, please try again later",
- ToastState.error,
- );
- return;
- } else {
- showToast(
- "Programs imported successfully from provider",
- ToastState.success,
- );
- return;
- }
- } catch (err: any) {
- showToast(
- "error importing all or some programs, please try again later",
- ToastState.error,
- );
- return;
+ async function handleImportAllPrograms() {
+ try {
+ let resp = await axios.post(
+ `/api/actions/provider-platforms/${providerId}/import-programs`
+ );
+ if (resp.status != 200) {
+ showToast(
+ 'error importing all or some programs, please try again later',
+ ToastState.error
+ );
+ return;
+ } else {
+ showToast(
+ 'Programs imported successfully from provider',
+ ToastState.success
+ );
+ return;
+ }
+ } catch (err: any) {
+ showToast(
+ 'error importing all or some programs, please try again later',
+ ToastState.error
+ );
+ return;
+ }
}
- }
- async function handleImportAllUsers() {
- try {
- let res = await axios.post(
- `/api/actions/provider-platforms/${providerId}/import-users`,
- );
- if (res.status === 200) {
- showToast(
- "Users imported successfully, please check for accounts not created",
- ToastState.success,
- );
- window.location.reload();
- }
- } catch (error: any) {
- setError(true);
- showToast("Failed to import users", ToastState.error);
+ async function handleImportAllUsers() {
+ try {
+ let res = await axios.post(
+ `/api/actions/provider-platforms/${providerId}/import-users`
+ );
+ if (res.status === 200) {
+ showToast(
+ 'Users imported successfully, please check for accounts not created',
+ ToastState.success
+ );
+ window.location.reload();
+ }
+ } catch (error: any) {
+ setError(true);
+ showToast('Failed to import users', ToastState.error);
+ }
}
- }
- async function handleImportSelectedUsers() {
- try {
- let res = await axios.post(
- `/api/provider-platforms/${providerId}/users/import`,
- { users: usersToImport },
- );
- if (res.status === 200) {
- showToast(res.data.message, ToastState.success);
- console.log(res.data.data);
- setImportedUsers(res.data.data);
- importedUsersModal.current?.showModal();
- setUsersToImport([]);
- mutate();
- return;
- }
- } catch (err: any) {
- setUsersToImport([]);
- showToast(
- "error importing users, please check accounts",
- ToastState.error,
- );
+ async function handleImportSelectedUsers() {
+ try {
+ let res = await axios.post(
+ `/api/provider-platforms/${providerId}/users/import`,
+ { users: usersToImport }
+ );
+ if (res.status === 200) {
+ showToast(res.data.message, ToastState.success);
+ console.log(res.data.data);
+ setImportedUsers(res.data.data);
+ importedUsersModal.current?.showModal();
+ setUsersToImport([]);
+ mutate();
+ return;
+ }
+ } catch (err: any) {
+ setUsersToImport([]);
+ showToast(
+ 'error importing users, please check accounts',
+ ToastState.error
+ );
+ }
}
- }
-
- function handleSubmitMapUser(msg: string, toastState: ToastState) {
- showToast(msg, toastState);
- mapUserModal.current?.close();
- }
- function handleCloseImportedUsers() {
- importedUsersModal.current?.close();
- setImportedUsers([]);
- return;
- }
+ function handleSubmitMapUser(msg: string, toastState: ToastState) {
+ showToast(msg, toastState);
+ mapUserModal.current?.close();
+ }
- function handleCloseMapUser() {
- mapUserModal.current?.close();
- setUserToMap(null);
- }
+ function handleCloseImportedUsers() {
+ importedUsersModal.current?.close();
+ setImportedUsers([]);
+ return;
+ }
- async function handleMapUser(user: ProviderUser) {
- setUserToMap(user);
- mapUserModal.current?.showModal();
- }
+ function handleCloseMapUser() {
+ mapUserModal.current?.close();
+ setUserToMap(null);
+ }
- function handleAddImportUser(user: ProviderUser) {
- if (usersToImport.includes(user)) {
- setUsersToImport(usersToImport.filter((u) => u !== user));
- } else {
- setUsersToImport([...usersToImport, user]);
+ async function handleMapUser(user: ProviderUser) {
+ setUserToMap(user);
+ mapUserModal.current?.showModal();
}
- }
- useEffect(() => {
- if (data) {
- setMeta(data.meta);
- setCache(false);
+ function handleAddImportUser(user: ProviderUser) {
+ if (usersToImport.includes(user)) {
+ setUsersToImport(usersToImport.filter((u) => u !== user));
+ } else {
+ setUsersToImport([...usersToImport, user]);
+ }
}
- }, [data]);
- useEffect(() => {
- const getData = async () => {
- try {
- const res = await axios.get(`/api/provider-platforms/${providerId}`);
- if (res.status === 200) {
- setProvider(res.data.data);
- setIsLoading(false);
+ useEffect(() => {
+ if (data) {
+ setMeta(data.meta);
+ setCache(false);
}
- } catch (error: any) {
- showToast("Failed to fetch provider users", ToastState.error);
- }
- };
- getData();
- }, [providerId]);
+ }, [data]);
+
+ useEffect(() => {
+ const getData = async () => {
+ try {
+ const res = await axios.get(
+ `/api/provider-platforms/${providerId}`
+ );
+ if (res.status === 200) {
+ setProvider(res.data.data);
+ setIsLoading(false);
+ }
+ } catch (error: any) {
+ showToast('Failed to fetch provider users', ToastState.error);
+ }
+ };
+ getData();
+ }, [providerId]);
- console.log(provider);
+ console.log(provider);
- return (
-
-
-
-
- {
- setSearchTerm(e.target.value);
- setCurrentPage(1);
- }}
- />
-
-
-
- Refresh
-
-
importAllUsersModal.current?.showModal()}
- disabled={!provider}
- >
- Import All Users
-
-
handleImportAllPrograms()}
- disabled={!provider}
- >
- Import Programs from Provider
-
-
handleImportSelectedUsers()}
- disabled={usersToImport.length === 0}
- >
- Import Selected Users
-
-
-
-
-
-
- Name
- Username
- Email
- Import
- Associate
-
-
-
- {!isLoading &&
- !error &&
- data &&
- data.data.map((user: any) => (
-
- {user.name_first + " " + user.name_last}
- {user.username}
- {user.email}
-
-
-
-
- Import User
- handleAddImportUser(user)}
- checked={usersToImport.includes(user)}
- />
-
-
-
-
-
-
- handleMapUser(user)}
- className="btn btn-xs btn-primary"
- >
- Map User
-
-
-
-
- ))}
-
-
-
-
-
-
- per page:
-
- {!isLoading && data && (
-
- {[10, 15, 20, 30, 50].map((value) => (
-
- {value}
-
- ))}
-
+ return (
+
+
+
+
+ {
+ setSearchTerm(e.target.value);
+ setCurrentPage(1);
+ }}
+ />
+
+
+
+ Refresh
+
+
importAllUsersModal.current?.showModal()}
+ disabled={!provider}
+ >
+ Import All Users
+
+
handleImportAllPrograms()}
+ disabled={!provider}
+ >
+ Import Programs from Provider
+
+
handleImportSelectedUsers()}
+ disabled={usersToImport.length === 0}
+ >
+ Import Selected Users
+
+
+
+
+
+
+ Name
+ Username
+ Email
+ Import
+ Associate
+
+
+
+ {!isLoading &&
+ !error &&
+ data &&
+ data.data.map((user: any) => (
+
+
+ {user.name_first +
+ ' ' +
+ user.name_last}
+
+ {user.username}
+ {user.email}
+
+
+
+
+ Import User
+
+ handleAddImportUser(
+ user
+ )
+ }
+ checked={usersToImport.includes(
+ user
+ )}
+ />
+
+
+
+
+
+
+
+ handleMapUser(user)
+ }
+ className="btn btn-xs btn-primary"
+ >
+ Map User
+
+
+
+
+ ))}
+
+
+
+
+
+
+ per page:
+
+ {!isLoading && data && (
+
+ {[10, 15, 20, 30, 50].map((value) => (
+
+ {value}
+
+ ))}
+
+ )}
+
+
+ {error && (
+
+ Failed to load users.
+
+ )}
+ {!isLoading && !error && data && data.meta.total === 0 && (
+
No results
+ )}
+
+ {provider && (
+
+ }
+ />
)}
-
-
- {error && (
-
Failed to load users.
- )}
- {!isLoading && !error && data && data.meta.total === 0 && (
-
No results
- )}
-
- {provider && (
-
+ }
/>
- }
- />
- )}
-
- }
- />
- importAllUsersModal.current?.close()}
- onSuccess={handleImportAllUsers}
- />
- }
- />
- {displayToast && }
-
- );
+ importAllUsersModal.current?.close()}
+ onSuccess={handleImportAllUsers}
+ />
+ }
+ />
+ {displayToast && }
+
+ );
}
diff --git a/frontend/src/Pages/ResourcesManagement.tsx b/frontend/src/Pages/ResourcesManagement.tsx
index 1ce77776..5aadf6c9 100644
--- a/frontend/src/Pages/ResourcesManagement.tsx
+++ b/frontend/src/Pages/ResourcesManagement.tsx
@@ -1,329 +1,350 @@
-import CategoryItem from "../Components/CategoryItem";
-import Modal, { ModalType } from "../Components/Modal";
-import PageNav from "../Components/PageNav";
-import Toast, { ToastState } from "../Components/Toast";
-import AddCategoryForm from "../Components/forms/AddCategoryForm";
-import AuthenticatedLayout from "../Layouts/AuthenticatedLayout";
-import { Category, CategoryLink, Resource } from "../common";
-import { PlusCircleIcon, DocumentCheckIcon } from "@heroicons/react/24/outline";
-import { TrashIcon } from "@heroicons/react/24/solid";
-import axios from "axios";
-import { useEffect, useMemo, useRef, useState } from "react";
-import useSWR from "swr";
-import DeleteForm from "../Components/forms/DeleteForm";
-import { useDebounceValue } from "usehooks-ts";
-import { useAuth } from "../AuthContext";
+import CategoryItem from '../Components/CategoryItem';
+import Modal, { ModalType } from '../Components/Modal';
+import PageNav from '../Components/PageNav';
+import Toast, { ToastState } from '../Components/Toast';
+import AddCategoryForm from '../Components/forms/AddCategoryForm';
+import AuthenticatedLayout from '../Layouts/AuthenticatedLayout';
+import { Category, CategoryLink, Resource } from '../common';
+import { PlusCircleIcon, DocumentCheckIcon } from '@heroicons/react/24/outline';
+import { TrashIcon } from '@heroicons/react/24/solid';
+import axios from 'axios';
+import { useEffect, useMemo, useRef, useState } from 'react';
+import useSWR from 'swr';
+import DeleteForm from '../Components/forms/DeleteForm';
+import { useDebounceValue } from 'usehooks-ts';
+import { useAuth } from '../AuthContext';
interface ToastProps {
- state: ToastState;
- message: string;
+ state: ToastState;
+ message: string;
}
export default function ResourcesManagement() {
- const auth = useAuth();
- const { data, error, mutate, isLoading } = useSWR("/api/left-menu");
+ const auth = useAuth();
+ const { data, error, mutate, isLoading } = useSWR('/api/left-menu');
- const [categoryList, setCategoryList] = useState(Array);
- const [categoryToDelete, setCategoryToDelete] = useState(null);
- const addCategoryModal = useRef(null);
- const deleteCategoryModal = useRef(null);
+ const [categoryList, setCategoryList] = useState(Array);
+ const [categoryToDelete, setCategoryToDelete] = useState(
+ null
+ );
+ const addCategoryModal = useRef(null);
+ const deleteCategoryModal = useRef(null);
- const [toast, setToast] = useState({
- state: ToastState.null,
- message: "",
- });
+ const [toast, setToast] = useState({
+ state: ToastState.null,
+ message: ''
+ });
- const draggedItem = useRef(null);
- const [draggedOverItem, setDraggedOverItem] = useState(null);
- const dragOverItem = useDebounceValue(draggedOverItem, 100);
+ const draggedItem = useRef(null);
+ const [draggedOverItem, setDraggedOverItem] = useState(null);
+ const dragOverItem = useDebounceValue(draggedOverItem, 100);
- useEffect(() => {
- if (data !== undefined) {
- const updatedData = data.data.map(
- (category: Resource) =>
- ({
- ...category,
- id: Math.random(),
- }) as Resource,
- );
- setCategoryList(updatedData);
- }
- }, [data]);
-
- const MemoizedCategoryList = useMemo(() => {
- if (error) return failed to load
;
- if (isLoading) return loading...
;
- return categoryList.map((category, index) => {
- return (
-
-
-
{
- e.preventDefault(), setDraggedOverItem(index);
- }}
- onDragLeave={(e) => {
- e.preventDefault(), setDraggedOverItem(null);
- }}
- onDrop={(e) => {
- e.preventDefault(), draggedItem.current == null;
- }}
- onDragStart={() => (draggedItem.current = index)}
- onDragEnd={(e) => {
- e.preventDefault();
- if (dragOverItem[0] == null) setDraggedOverItem(-1);
- else handleSort();
- }}
- >
-
- {
- deleteCategoryModal.current?.showModal(),
- setCategoryToDelete(category.id);
- }}
- />
-
-
-
-
-
-
- {index == categoryList.length - 1 ? (
-
{
- e.preventDefault(), setDraggedOverItem(index + 1);
- }}
- onDragLeave={(e) => {
- e.preventDefault(), setDraggedOverItem(null);
- }}
- onDrop={(e) => {
- e.preventDefault(), draggedItem.current == null;
- }}
- >
- ) : null}
-
- );
- });
- }, [categoryList, dragOverItem]);
+ useEffect(() => {
+ if (data !== undefined) {
+ const updatedData = data.data.map(
+ (category: Resource) =>
+ ({
+ ...category,
+ id: Math.random()
+ }) as Resource
+ );
+ setCategoryList(updatedData);
+ }
+ }, [data]);
- function addCategory(newCategoryTitle: string) {
- const newCategory = {
- id: Math.random(),
- name: newCategoryTitle,
- links: [],
- rank: categoryList.length + 1,
- };
- setCategoryList([...categoryList, newCategory]);
- addCategoryModal.current?.close();
- }
+ const MemoizedCategoryList = useMemo(() => {
+ if (error) return failed to load
;
+ if (isLoading) return loading...
;
+ return categoryList.map((category, index) => {
+ return (
+
+
+
{
+ e.preventDefault(), setDraggedOverItem(index);
+ }}
+ onDragLeave={(e) => {
+ e.preventDefault(), setDraggedOverItem(null);
+ }}
+ onDrop={(e) => {
+ e.preventDefault(), draggedItem.current == null;
+ }}
+ onDragStart={() => (draggedItem.current = index)}
+ onDragEnd={(e) => {
+ e.preventDefault();
+ if (dragOverItem[0] == null)
+ setDraggedOverItem(-1);
+ else handleSort();
+ }}
+ >
+
+ {
+ deleteCategoryModal.current?.showModal(),
+ setCategoryToDelete(category.id);
+ }}
+ />
+
+
+
+
+
+
+ {index == categoryList.length - 1 ? (
+
{
+ e.preventDefault(),
+ setDraggedOverItem(index + 1);
+ }}
+ onDragLeave={(e) => {
+ e.preventDefault(), setDraggedOverItem(null);
+ }}
+ onDrop={(e) => {
+ e.preventDefault(), draggedItem.current == null;
+ }}
+ >
+ ) : null}
+
+ );
+ });
+ }, [categoryList, dragOverItem]);
- function deleteCategory(id: number | null) {
- if (categoryToDelete == null) return;
- const newCategories = categoryList.filter((category) => category.id !== id);
- setCategoryList(newCategories);
- }
+ function addCategory(newCategoryTitle: string) {
+ const newCategory = {
+ id: Math.random(),
+ name: newCategoryTitle,
+ links: [],
+ rank: categoryList.length + 1
+ };
+ setCategoryList([...categoryList, newCategory]);
+ addCategoryModal.current?.close();
+ }
- function addLink(category: Category, newTitle: string, newURL: string) {
- const newLink: CategoryLink = {};
- newLink[newTitle] = newURL;
- const newCategoryList = categoryList.map((c, _) => {
- if (c == category) {
- // add link to the category
- c.links.push(newLink);
- return c;
- } else return c;
- });
- setCategoryList(newCategoryList);
- }
+ function deleteCategory(id: number | null) {
+ if (categoryToDelete == null) return;
+ const newCategories = categoryList.filter(
+ (category) => category.id !== id
+ );
+ setCategoryList(newCategories);
+ }
- function deleteLink(category: Category, activeLinkToDelete: CategoryLink) {
- const newCategoryList = categoryList.map((c, _) => {
- if (c == category) {
- // delete link of the category
- c.links = c.links.filter((link) => link !== activeLinkToDelete);
- return c;
- } else return c;
- });
- setCategoryList(newCategoryList);
- }
+ function addLink(category: Category, newTitle: string, newURL: string) {
+ const newLink: CategoryLink = {};
+ newLink[newTitle] = newURL;
+ const newCategoryList = categoryList.map((c, _) => {
+ if (c == category) {
+ // add link to the category
+ c.links.push(newLink);
+ return c;
+ } else return c;
+ });
+ setCategoryList(newCategoryList);
+ }
- function moveLink(
- category: Category,
- linkIndex: number,
- direction: "up" | "down",
- ) {
- let index: number;
- if (direction == "up") {
- if (linkIndex == 0) return;
- index = linkIndex - 1;
+ function deleteLink(category: Category, activeLinkToDelete: CategoryLink) {
+ const newCategoryList = categoryList.map((c, _) => {
+ if (c == category) {
+ // delete link of the category
+ c.links = c.links.filter((link) => link !== activeLinkToDelete);
+ return c;
+ } else return c;
+ });
+ setCategoryList(newCategoryList);
}
- if (direction == "down") {
- if (linkIndex == category.links.length - 1) return;
- index = linkIndex + 1;
+
+ function moveLink(
+ category: Category,
+ linkIndex: number,
+ direction: 'up' | 'down'
+ ) {
+ let index: number;
+ if (direction == 'up') {
+ if (linkIndex == 0) return;
+ index = linkIndex - 1;
+ }
+ if (direction == 'down') {
+ if (linkIndex == category.links.length - 1) return;
+ index = linkIndex + 1;
+ }
+ const newCategoryList = categoryList.map((c, _) => {
+ if (c == category) {
+ const linksArray = c.links;
+ const temp = linksArray[index];
+ linksArray[index] = linksArray[linkIndex];
+ linksArray[linkIndex] = temp;
+ c.links = linksArray;
+ return c;
+ } else return c;
+ });
+ setCategoryList(newCategoryList);
}
- const newCategoryList = categoryList.map((c, _) => {
- if (c == category) {
- const linksArray = c.links;
- const temp = linksArray[index];
- linksArray[index] = linksArray[linkIndex];
- linksArray[linkIndex] = temp;
- c.links = linksArray;
- return c;
- } else return c;
- });
- setCategoryList(newCategoryList);
- }
- function updateLink(category: Category, linkIndex: number, newLinkPair: any) {
- categoryList.map((c, _) => {
- if (c == category) {
- category.links[linkIndex] = newLinkPair;
- return c;
- } else return c;
- });
- }
+ function updateLink(
+ category: Category,
+ linkIndex: number,
+ newLinkPair: any
+ ) {
+ categoryList.map((c, _) => {
+ if (c == category) {
+ category.links[linkIndex] = newLinkPair;
+ return c;
+ } else return c;
+ });
+ }
- function handleSort() {
- if (draggedItem.current == null || dragOverItem == null) return;
+ function handleSort() {
+ if (draggedItem.current == null || dragOverItem == null) return;
- const insertAtIndex = dragOverItem;
- // if dragged item is higher in the list, then should subtract a number from where it needs to be placed
- if (draggedItem.current < dragOverItem[0]!)
- insertAtIndex[0] = insertAtIndex[0]! - 1;
+ const insertAtIndex = dragOverItem;
+ // if dragged item is higher in the list, then should subtract a number from where it needs to be placed
+ if (draggedItem.current < dragOverItem[0]!)
+ insertAtIndex[0] = insertAtIndex[0]! - 1;
- //duplicate items
- const newCategoryList = [...categoryList];
+ //duplicate items
+ const newCategoryList = [...categoryList];
- //remove and save the dragged item content
- const draggedItemContent = newCategoryList.splice(
- draggedItem.current,
- 1,
- )[0];
+ //remove and save the dragged item content
+ const draggedItemContent = newCategoryList.splice(
+ draggedItem.current,
+ 1
+ )[0];
- if (insertAtIndex[0] == categoryList.length)
- newCategoryList.push(draggedItemContent);
- else newCategoryList.splice(insertAtIndex[0]!, 0, draggedItemContent);
+ if (insertAtIndex[0] == categoryList.length)
+ newCategoryList.push(draggedItemContent);
+ else newCategoryList.splice(insertAtIndex[0]!, 0, draggedItemContent);
- //update the actual array
- setCategoryList(newCategoryList);
+ //update the actual array
+ setCategoryList(newCategoryList);
- draggedItem.current = null;
- setDraggedOverItem(null);
- }
+ draggedItem.current = null;
+ setDraggedOverItem(null);
+ }
- async function updateFinalState(e: any) {
- setToast({ state: ToastState.null, message: "" });
- e.preventDefault();
- const newCategoryList = categoryList.map((c, i) => {
- c.rank = i + 1;
- c.id = i;
- return c;
- });
- try {
- const response = await axios.put("/api/left-menu", newCategoryList);
- // check response is okay, and give notification
- if (response.status !== 201) {
- // show error
- setToast({
- state: ToastState.error,
- message: "Error Saving Categories",
- });
- } else {
- mutate();
- // show success
- setToast({
- state: ToastState.success,
- message: "Categories Saved!",
+ async function updateFinalState(e: any) {
+ setToast({ state: ToastState.null, message: '' });
+ e.preventDefault();
+ const newCategoryList = categoryList.map((c, i) => {
+ c.rank = i + 1;
+ c.id = i;
+ return c;
});
- }
- } catch (err: any) {
- console.log(err);
- if (err.response.status == 422) {
- setToast({
- state: ToastState.error,
- message: "All categories must have associated links",
- });
- } else {
- // show general error
- setToast({
- state: ToastState.error,
- message: "Error Saving Categories",
- });
- }
+ try {
+ const response = await axios.put('/api/left-menu', newCategoryList);
+ // check response is okay, and give notification
+ if (response.status !== 201) {
+ // show error
+ setToast({
+ state: ToastState.error,
+ message: 'Error Saving Categories'
+ });
+ } else {
+ mutate();
+ // show success
+ setToast({
+ state: ToastState.success,
+ message: 'Categories Saved!'
+ });
+ }
+ } catch (err: any) {
+ console.log(err);
+ if (err.response.status == 422) {
+ setToast({
+ state: ToastState.error,
+ message: 'All categories must have associated links'
+ });
+ } else {
+ // show general error
+ setToast({
+ state: ToastState.error,
+ message: 'Error Saving Categories'
+ });
+ }
+ }
}
- }
- return (
-
-
-
-
-
addCategoryModal.current?.showModal()}
- >
-
- Add Category
-
-
- updateFinalState(e)}
- >
-
-
-
-
-
{MemoizedCategoryList}
-
- {/* Modals */}
- addCategory(title)} />
- }
- ref={addCategoryModal}
- />
- setCategoryToDelete(null)}
- onSuccess={() => {
- deleteCategory(categoryToDelete),
- deleteCategoryModal.current?.close();
- }}
- />
- }
- ref={deleteCategoryModal}
- />
- {/* Toasts */}
- {toast.state !== ToastState.null && (
- setToast({ state: ToastState.null, message: "" })}
- />
- )}
-
- );
+ return (
+
+
+
+
+
addCategoryModal.current?.showModal()}
+ >
+
+ Add Category
+
+
+ updateFinalState(e)}
+ >
+
+
+
+
+
{MemoizedCategoryList}
+
+ {/* Modals */}
+ addCategory(title)}
+ />
+ }
+ ref={addCategoryModal}
+ />
+ setCategoryToDelete(null)}
+ onSuccess={() => {
+ deleteCategory(categoryToDelete),
+ deleteCategoryModal.current?.close();
+ }}
+ />
+ }
+ ref={deleteCategoryModal}
+ />
+ {/* Toasts */}
+ {toast.state !== ToastState.null && (
+
+ setToast({ state: ToastState.null, message: '' })
+ }
+ />
+ )}
+
+ );
}
diff --git a/frontend/src/Pages/StudentDashboard.tsx b/frontend/src/Pages/StudentDashboard.tsx
index 327e535f..f503ca78 100644
--- a/frontend/src/Pages/StudentDashboard.tsx
+++ b/frontend/src/Pages/StudentDashboard.tsx
@@ -1,158 +1,191 @@
-import CourseCard from "@/Components/EnrolledCourseCard";
-import CurrentlyEnrolledClass from "@/Components/CurrentlyEnrolledClass";
-import { useAuth } from "@/AuthContext";
-import useSWR from "swr";
-import { ServerResponse } from "@/common";
-import convertSeconds from "@/Components/ConvertSeconds";
-import ResourcesSideBar from "@/Components/ResourcesSideBar";
-import WeekActivityChart from "@/Components/WeeklyActivity";
-import Error from "./Error";
-import { useNavigate } from "react-router-dom";
+import CourseCard from '@/Components/EnrolledCourseCard';
+import CurrentlyEnrolledClass from '@/Components/CurrentlyEnrolledClass';
+import { useAuth } from '@/AuthContext';
+import useSWR from 'swr';
+import { ServerResponse } from '@/common';
+import convertSeconds from '@/Components/ConvertSeconds';
+import ResourcesSideBar from '@/Components/ResourcesSideBar';
+import WeekActivityChart from '@/Components/WeeklyActivity';
+import Error from './Error';
+import { useNavigate } from 'react-router-dom';
import {
- AcademicCapIcon,
- ArrowRightIcon,
- BuildingStorefrontIcon,
-} from "@heroicons/react/24/outline";
+ AcademicCapIcon,
+ ArrowRightIcon,
+ BuildingStorefrontIcon
+} from '@heroicons/react/24/outline';
export default function StudentDashboard() {
- const { user } = useAuth();
- const navigate = useNavigate();
- const { data, error, isLoading } = useSWR>(
- `/api/users/${user.id}/student-dashboard`,
- );
-
- if (isLoading) return
;
- if (error) return ;
- console.log(data);
-
- const ExploreCourseCatalogCard = () => {
- return (
-
+ const { user } = useAuth();
+ const navigate = useNavigate();
+ const { data, error, isLoading } = useSWR>(
+ `/api/users/${user.id}/student-dashboard`
);
- };
- const PopularCoursesCard = () => {
- return (
-
-
-
Popular Courses on UnlockEd
-
- {data.top_programs.map((name: string) => {
- return (
-
-
- {name}
-
- );
- })}
-
-
-
- );
- };
+ if (isLoading) return
;
+ if (error) return ;
+ console.log(data);
- return (
-
-
-
Hi, {user.name_first}!
-
Pick Up Where You Left Off
-
-
- {data?.recent_programs.map((course, index) => {
- return
;
- })}
- {data.recent_programs.length < 3 &&
}
- {data.recent_programs.length < 2 &&
}
-
-
-
-
-
My Activity
-
-
-
-
Learning Time
-
- {/* TO DO: caption needs to be added */}
-
-
-
- Course Name
- Hours Spent
-
-
-
- {!error && !isLoading && data.enrollments !== null ? (
- data?.enrollments?.map((course: any, index: number) => {
- const totalTime = convertSeconds(
- course.total_activity_time,
- );
- return (
-
- {course.name}
-
- {totalTime.number + " " + totalTime.label}
-
-
- );
- })
- ) : (
- No activity to show.
- )}
-
-
+ const ExploreCourseCatalogCard = () => {
+ return (
+
-
-
-
Currently Enrolled Classes
-
- {!error && !isLoading && (
-
- {data.enrollments ? (
- data?.enrollments?.map((course: any) => {
- return (
-
- );
- })
- ) : (
-
- You are currently not enrolled in any courses.
-
- )}
+ );
+ };
+
+ const PopularCoursesCard = () => {
+ return (
+
+
+
+ Popular Courses on UnlockEd
+
+
+ {data.top_programs.map((name: string) => {
+ return (
+
+
+ {name}
+
+ );
+ })}
+
+
- )}
+ );
+ };
+
+ return (
+
+
+
Hi, {user.name_first}!
+
Pick Up Where You Left Off
+
+
+ {data?.recent_programs.map((course, index) => {
+ return (
+
+ );
+ })}
+ {data.recent_programs.length < 3 && (
+
+ )}
+ {data.recent_programs.length < 2 && (
+
+ )}
+
+
+
+
+
My Activity
+
+
+
+
Learning Time
+
+ {/* TO DO: caption needs to be added */}
+
+
+
+
+ Course Name
+
+
+ Hours Spent
+
+
+
+
+ {!error &&
+ !isLoading &&
+ data.enrollments !== null ? (
+ data?.enrollments?.map(
+ (course: any, index: number) => {
+ const totalTime =
+ convertSeconds(
+ course.total_activity_time
+ );
+ return (
+
+
+ {course.name}
+
+
+ {totalTime.number +
+ ' ' +
+ totalTime.label}
+
+
+ );
+ }
+ )
+ ) : (
+
+ No activity to show.
+
+ )}
+
+
+
+
+
+
Currently Enrolled Classes
+
+ {!error && !isLoading && (
+
+ {data.enrollments ? (
+ data?.enrollments?.map((course: any) => {
+ return (
+
+ );
+ })
+ ) : (
+
+ You are currently not enrolled in any
+ courses.
+
+ )}
+
+ )}
+
+
+
+
-
-
-
-
- );
+ );
}
diff --git a/frontend/src/Pages/UserActivity.tsx b/frontend/src/Pages/UserActivity.tsx
index 3475f343..f2dfebac 100644
--- a/frontend/src/Pages/UserActivity.tsx
+++ b/frontend/src/Pages/UserActivity.tsx
@@ -1,116 +1,134 @@
-import PageNav from "../Components/PageNav";
-import Pagination from "../Components/Pagination";
-import AuthenticatedLayout from "../Layouts/AuthenticatedLayout";
-import DropdownControl from "@/Components/inputs/DropdownControl";
-import SearchBar from "../Components/inputs/SearchBar";
-import { Activity, PaginatedResponse } from "../common";
-import { useState } from "react";
-import useSWR from "swr";
+import PageNav from '../Components/PageNav';
+import Pagination from '../Components/Pagination';
+import AuthenticatedLayout from '../Layouts/AuthenticatedLayout';
+import DropdownControl from '@/Components/inputs/DropdownControl';
+import SearchBar from '../Components/inputs/SearchBar';
+import { Activity, PaginatedResponse } from '../common';
+import { useState } from 'react';
+import useSWR from 'swr';
// import { useDebounceValue } from "usehooks-ts";
-import { useAuth } from "../AuthContext";
+import { useAuth } from '../AuthContext';
export default function UserActivity() {
- const [searchTerm, setSearchTerm] = useState("");
- // const searchQuery = useDebounceValue(searchTerm, 300);
- const { user } = useAuth();
- // TO DO: come back and figure out pagequery
- const [pageQuery, setPageQuery] = useState(1);
- pageQuery;
+ const [searchTerm, setSearchTerm] = useState('');
+ // const searchQuery = useDebounceValue(searchTerm, 300);
+ const { user } = useAuth();
+ // TO DO: come back and figure out pagequery
+ const [pageQuery, setPageQuery] = useState(1);
+ pageQuery;
- const [sortQuery, setSortQuery] = useState("user_id DESC");
+ const [sortQuery, setSortQuery] = useState('user_id DESC');
- const { data, error, isLoading } = useSWR(
- `/api/users/activity-log?sort=${sortQuery}&page=${pageQuery}&search=${searchTerm}`,
- );
+ const { data, error, isLoading } = useSWR(
+ `/api/users/activity-log?sort=${sortQuery}&page=${pageQuery}&search=${searchTerm}`
+ );
- const userActivityData = data as PaginatedResponse
;
+ const userActivityData = data as PaginatedResponse;
- const handleChange = (newSearch: string) => {
- setSearchTerm(newSearch);
- setPageQuery(1);
- };
+ const handleChange = (newSearch: string) => {
+ setSearchTerm(newSearch);
+ setPageQuery(1);
+ };
- return (
-
-
-
-
-
-
-
-
- User
-
- Browser
- URL
- Time
-
-
-
- {!isLoading &&
- !error &&
- userActivityData.data.map((activityInstance) => {
- const dateTime = new Date(activityInstance.created_at);
- return (
-
-
- {activityInstance.user_name_first +
- " " +
- activityInstance.user_name_last}
-
- {activityInstance.browser_name}
-
-
- {activityInstance.clicked_url}
-
-
-
- {dateTime.toLocaleString("en-US")}
- {/*
+
+
+
+
+
+
+
+ User
+
+ Browser
+ URL
+ Time
+
+
+
+ {!isLoading &&
+ !error &&
+ userActivityData.data.map((activityInstance) => {
+ const dateTime = new Date(
+ activityInstance.created_at
+ );
+ return (
+
+
+ {activityInstance.user_name_first +
+ ' ' +
+ activityInstance.user_name_last}
+
+ {activityInstance.browser_name}
+
+
+
+ {
+ activityInstance.clicked_url
+ }
+
+
+
+
+ {dateTime.toLocaleString('en-US')}
+ {/*
*/}
-
-
- );
- })}
-
-
- {!isLoading && !error && data.data.length != 0 && (
-
- )}
- {error && (
-
Failed to load users.
- )}
- {!isLoading && !error && data.data.length == 0 && (
-
No results
- )}
-
-
- );
+
+
+ );
+ })}
+
+
+ {!isLoading && !error && data.data.length != 0 && (
+
+ )}
+ {error && (
+
+ Failed to load users.
+
+ )}
+ {!isLoading && !error && data.data.length == 0 && (
+
No results
+ )}
+
+
+ );
}
diff --git a/frontend/src/Pages/Users.tsx b/frontend/src/Pages/Users.tsx
index 3c8cb10f..4b7355b9 100644
--- a/frontend/src/Pages/Users.tsx
+++ b/frontend/src/Pages/Users.tsx
@@ -1,323 +1,352 @@
-import { useRef, useState } from "react";
-import useSWR from "swr";
-import axios from "axios";
+import { useRef, useState } from 'react';
+import useSWR from 'swr';
+import axios from 'axios';
-import AuthenticatedLayout from "../Layouts/AuthenticatedLayout";
+import AuthenticatedLayout from '../Layouts/AuthenticatedLayout';
import {
- ArrowPathRoundedSquareIcon,
- PencilIcon,
- TrashIcon,
- UserPlusIcon,
-} from "@heroicons/react/20/solid";
-import { DEFAULT_ADMIN_ID, PaginatedResponse, User } from "../common";
-import PageNav from "../Components/PageNav";
-import AddUserForm from "../Components/forms/AddUserForm";
-import EditUserForm from "../Components/forms/EditUserForm";
-import Toast, { ToastState } from "../Components/Toast";
-import Modal, { ModalType } from "../Components/Modal";
-import DeleteForm from "../Components/DeleteForm";
-import ResetPasswordForm from "../Components/forms/ResetPasswordForm";
-import ShowTempPasswordForm from "../Components/forms/ShowTempPasswordForm";
-import DropdownControl from "@/Components/inputs/DropdownControl";
-import SearchBar from "../Components/inputs/SearchBar";
-import { useAuth } from "../AuthContext";
-import { useDebounceValue } from "usehooks-ts";
-import Pagination from "@/Components/Pagination";
+ ArrowPathRoundedSquareIcon,
+ PencilIcon,
+ TrashIcon,
+ UserPlusIcon
+} from '@heroicons/react/20/solid';
+import { DEFAULT_ADMIN_ID, PaginatedResponse, User } from '../common';
+import PageNav from '../Components/PageNav';
+import AddUserForm from '../Components/forms/AddUserForm';
+import EditUserForm from '../Components/forms/EditUserForm';
+import Toast, { ToastState } from '../Components/Toast';
+import Modal, { ModalType } from '../Components/Modal';
+import DeleteForm from '../Components/DeleteForm';
+import ResetPasswordForm from '../Components/forms/ResetPasswordForm';
+import ShowTempPasswordForm from '../Components/forms/ShowTempPasswordForm';
+import DropdownControl from '@/Components/inputs/DropdownControl';
+import SearchBar from '../Components/inputs/SearchBar';
+import { useAuth } from '../AuthContext';
+import { useDebounceValue } from 'usehooks-ts';
+import Pagination from '@/Components/Pagination';
export default function Users() {
- const auth = useAuth();
- const addUserModal = useRef(null);
- const editUserModal = useRef(null);
- const resetUserPasswordModal = useRef(null);
- const deleteUserModal = useRef(null);
- const [displayToast, setDisplayToast] = useState(false);
- const [targetUser, setTargetUser] = useState(null);
- const [tempPassword, setTempPassword] = useState("");
- const showUserPassword = useRef(null);
- const [toast, setToast] = useState({
- state: ToastState.null,
- message: "",
- reset: () => {},
- });
+ const auth = useAuth();
+ const addUserModal = useRef(null);
+ const editUserModal = useRef(null);
+ const resetUserPasswordModal = useRef(null);
+ const deleteUserModal = useRef(null);
+ const [displayToast, setDisplayToast] = useState(false);
+ const [targetUser, setTargetUser] = useState(null);
+ const [tempPassword, setTempPassword] = useState('');
+ const showUserPassword = useRef(null);
+ const [toast, setToast] = useState({
+ state: ToastState.null,
+ message: '',
+ reset: () => {}
+ });
- const [searchTerm, setSearchTerm] = useState("");
- const searchQuery = useDebounceValue(searchTerm, 300);
- const [pageQuery, setPageQuery] = useState(1);
- const [sortQuery, setSortQuery] = useState("created_at DESC");
- const { data, mutate, error, isLoading } = useSWR(
- `/api/users?search=${searchQuery[0]}&page=${pageQuery}&order_by=${sortQuery}`,
- );
- const userData = data as PaginatedResponse;
- const showToast = (message: string, state: ToastState) => {
- setToast({
- state,
- message,
- reset: () => {
+ const [searchTerm, setSearchTerm] = useState('');
+ const searchQuery = useDebounceValue(searchTerm, 300);
+ const [pageQuery, setPageQuery] = useState(1);
+ const [sortQuery, setSortQuery] = useState('created_at DESC');
+ const { data, mutate, error, isLoading } = useSWR(
+ `/api/users?search=${searchQuery[0]}&page=${pageQuery}&order_by=${sortQuery}`
+ );
+ const userData = data as PaginatedResponse;
+ const showToast = (message: string, state: ToastState) => {
setToast({
- state: ToastState.success,
- message: "",
- reset: () => {
- setDisplayToast(false);
- },
+ state,
+ message,
+ reset: () => {
+ setToast({
+ state: ToastState.success,
+ message: '',
+ reset: () => {
+ setDisplayToast(false);
+ }
+ });
+ }
});
- },
- });
- setDisplayToast(true);
- };
-
- function resetModal() {
- setTimeout(() => {
- setTargetUser(null);
- }, 200);
- }
+ setDisplayToast(true);
+ };
- const deleteUser = async () => {
- if (targetUser?.id === DEFAULT_ADMIN_ID) {
- showToast(
- "This is the primary administrator and cannot be deleted",
- ToastState.error,
- );
- return;
+ function resetModal() {
+ setTimeout(() => {
+ setTargetUser(null);
+ }, 200);
}
- try {
- const response = await axios.delete("/api/users/" + targetUser?.id);
- const toastType =
- response.status == 204 ? ToastState.success : ToastState.error;
- const message =
- response.status == 204
- ? "User deleted successfully"
- : response.statusText;
- deleteUserModal.current?.close();
- showToast(message, toastType);
- resetModal();
- mutate();
- return;
- } catch (err: any) {
- showToast("Unable to delete user, please try again", ToastState.error);
- return;
- }
- };
- const onAddUserSuccess = (pswd = "", msg: string, type: ToastState) => {
- showToast(msg, type);
- setTempPassword(pswd);
- addUserModal.current?.close();
- showUserPassword.current?.showModal();
- mutate();
- };
+ const deleteUser = async () => {
+ if (targetUser?.id === DEFAULT_ADMIN_ID) {
+ showToast(
+ 'This is the primary administrator and cannot be deleted',
+ ToastState.error
+ );
+ return;
+ }
+ try {
+ const response = await axios.delete('/api/users/' + targetUser?.id);
+ const toastType =
+ response.status == 204 ? ToastState.success : ToastState.error;
+ const message =
+ response.status == 204
+ ? 'User deleted successfully'
+ : response.statusText;
+ deleteUserModal.current?.close();
+ showToast(message, toastType);
+ resetModal();
+ mutate();
+ return;
+ } catch (err: any) {
+ showToast(
+ 'Unable to delete user, please try again',
+ ToastState.error
+ );
+ return;
+ }
+ };
- const hanldleEditUser = () => {
- editUserModal.current?.close();
- resetModal();
- mutate();
- };
+ const onAddUserSuccess = (pswd = '', msg: string, type: ToastState) => {
+ showToast(msg, type);
+ setTempPassword(pswd);
+ addUserModal.current?.close();
+ showUserPassword.current?.showModal();
+ mutate();
+ };
- const handleDeleteUserCancel = () => {
- deleteUserModal.current?.close();
- resetModal();
- };
+ const hanldleEditUser = () => {
+ editUserModal.current?.close();
+ resetModal();
+ mutate();
+ };
- const handleResetPasswordCancel = (msg: string, err: boolean) => {
- const state = err ? ToastState.error : ToastState.success;
- if (msg === "" && !err) {
- resetUserPasswordModal.current?.close();
- resetModal();
- return;
- }
- showToast(msg, state);
- resetModal();
- };
+ const handleDeleteUserCancel = () => {
+ deleteUserModal.current?.close();
+ resetModal();
+ };
- const handleDisplayTempPassword = (psw: string) => {
- setTempPassword(psw);
- resetUserPasswordModal.current?.close();
- showUserPassword.current?.showModal();
- showToast("Password Successfully Reset", ToastState.success);
- };
+ const handleResetPasswordCancel = (msg: string, err: boolean) => {
+ const state = err ? ToastState.error : ToastState.success;
+ if (msg === '' && !err) {
+ resetUserPasswordModal.current?.close();
+ resetModal();
+ return;
+ }
+ showToast(msg, state);
+ resetModal();
+ };
- const handleShowPasswordClose = () => {
- showUserPassword.current?.close();
- setTempPassword("");
- resetModal();
- };
+ const handleDisplayTempPassword = (psw: string) => {
+ setTempPassword(psw);
+ resetUserPasswordModal.current?.close();
+ showUserPassword.current?.showModal();
+ showToast('Password Successfully Reset', ToastState.success);
+ };
- const handleChange = (newSearch: string) => {
- setSearchTerm(newSearch);
- setPageQuery(1);
- };
+ const handleShowPasswordClose = () => {
+ showUserPassword.current?.close();
+ setTempPassword('');
+ resetModal();
+ };
- return (
-
-
-
-
-
-
-
-
+ const handleChange = (newSearch: string) => {
+ setSearchTerm(newSearch);
+ setPageQuery(1);
+ };
-
- addUserModal.current?.showModal()}
- >
-
-
-
-
-
-
-
-
- Name
-
- Username
- Role
- Last Updated
- Actions
-
-
-
- {!isLoading &&
- !error &&
- userData.data.map((user: any) => {
- const updatedAt = new Date(user.updated_at);
- return (
-
-
- {user.name_first} {user.name_last}
-
- {user.username}
- {user.role}
-
-
-
-
-
-
-
{
- setTargetUser(user);
- editUserModal.current?.showModal();
- }}
- />
-
-
-
{
- setTargetUser(user);
- resetUserPasswordModal.current?.showModal();
- }}
- />
-
-
-
{
- setTargetUser(user);
- deleteUserModal.current?.showModal();
+ return (
+
+
+
-
- );
- })}
-
-
- {!isLoading && !error && data.data.length != 0 && (
-
- )}
- {error && (
-
Failed to load users.
- )}
- {!isLoading && !error && data.data.length == 0 && (
-
No results
- )}
-
+ />
+
-
}
- />
-
- ) : (
-
No user defined!
- )
- }
- />
-
- }
- />
-
- }
- />
-
- }
- />
- {/* Toasts */}
- {displayToast &&
}
-
- );
+
+ addUserModal.current?.showModal()}
+ >
+
+
+
+
+
+
+
+
+ Name
+
+ Username
+ Role
+ Last Updated
+ Actions
+
+
+
+ {!isLoading &&
+ !error &&
+ userData.data.map((user: any) => {
+ const updatedAt = new Date(user.updated_at);
+ return (
+
+
+ {user.name_first} {user.name_last}
+
+ {user.username}
+ {user.role}
+
+
+
+
+
+
+
{
+ setTargetUser(user);
+ editUserModal.current?.showModal();
+ }}
+ />
+
+
+
{
+ setTargetUser(user);
+ resetUserPasswordModal.current?.showModal();
+ }}
+ />
+
+
+ {
+ setTargetUser(user);
+ deleteUserModal.current?.showModal();
+ }}
+ />
+
+
+
+
+ );
+ })}
+
+
+ {!isLoading && !error && data.data.length != 0 && (
+
+ )}
+ {error && (
+
+ Failed to load users.
+
+ )}
+ {!isLoading && !error && data.data.length == 0 && (
+
No results
+ )}
+
+
+ }
+ />
+
+ ) : (
+ No user defined!
+ )
+ }
+ />
+
+ }
+ />
+
+ }
+ />
+
+ }
+ />
+ {/* Toasts */}
+ {displayToast && }
+
+ );
}
diff --git a/frontend/src/Pages/Welcome.tsx b/frontend/src/Pages/Welcome.tsx
index 42fe6299..62c73322 100644
--- a/frontend/src/Pages/Welcome.tsx
+++ b/frontend/src/Pages/Welcome.tsx
@@ -1,230 +1,251 @@
-import { useState, useEffect } from "react";
-import Brand from "../Components/Brand";
-import axios from "axios";
+import { useState, useEffect } from 'react';
+import Brand from '../Components/Brand';
+import axios from 'axios';
export default function Welcome() {
- const [imgSrc, setImgSrc] = useState("unlockedv1Sm.webp");
- const [isLoggedIn, setIsLoggedIn] = useState(false);
+ const [imgSrc, setImgSrc] = useState('unlockedv1Sm.webp');
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
- const checkLoggedIn = async () => {
- try {
- const response = await axios.get("/api/auth");
- if (response.status === 200) {
- console.log(response.data);
- setIsLoggedIn(true);
- return;
- }
- return;
- } catch (error) {
- console.log("not logged in");
- return;
- }
- };
- useEffect(() => {
- const img = new Image();
- img.src = "unlockedv1.png";
- img.onload = () => {
- setImgSrc("unlockedv1.png");
+ const checkLoggedIn = async () => {
+ try {
+ const response = await axios.get('/api/auth');
+ if (response.status === 200) {
+ console.log(response.data);
+ setIsLoggedIn(true);
+ return;
+ }
+ return;
+ } catch (error) {
+ console.log('not logged in');
+ return;
+ }
};
- checkLoggedIn();
- }, []);
+ useEffect(() => {
+ const img = new Image();
+ img.src = 'unlockedv1.png';
+ img.onload = () => {
+ setImgSrc('unlockedv1.png');
+ };
+ checkLoggedIn();
+ }, []);
- return (
-
-
+ return (
+
+
-
-
-
Built from the inside out...
+
+
+
Built from the inside out...
-
- Our mission is to make education accessible to all justice-impacted
- people, and to ensure that their educational progress is recorded
- and recognized by institutions allowing for a faster and more
- equitable re-entry process.
-
+
+ Our mission is to make education accessible to all
+ justice-impacted people, and to ensure that their
+ educational progress is recorded and recognized by
+ institutions allowing for a faster and more equitable
+ re-entry process.
+
-
-
-
- Version 1 of UnlockEd was built inside without the help of the
- internet.
-
-
+
+
+
+ Version 1 of UnlockEd was built inside without the
+ help of the internet.
+
+
-
-
-
-
-
1997
-
- Young Beginnings
-
- Co-Founders Jessica Hicklin and Chris Santillan met at Potosi
- Correctional Center before both of their 18th birthdays. They
- were sentenced to life without parole and told they would die
- behind bars.
-
-
-
-
-
-
-
-
- 1998 - 2017
-
-
- Education Against the Odds
-
- Despite residing in a facility without access to formal
- education for over a quarter of a century, they were determined
- to make a positive impact upon their own lives and upon those
- with whom they lived and worked with in their community. They
- both maximized their circumstances, spending years tutoring
- others to pass their GED/Hi-Set exams and organizing victim
- empathy and anger management courses for the community.
-
-
-
-
-
-
-
-
- 2012 - 2017
-
-
- Coding for Change
-
- With limited resources, Jessica and Chris both taught themselves
- how to code without the internet. They dreamed of a day when
- they could create a solution for ways to track rehabilitation
- and educational programs on the inside.
-
-
-
-
-
-
-
-
2022
-
- Establishing Unlocked Labs
-
- Their lives took a turn when a Supreme Court declared life
- sentences for juveniles unconstitutional. In 2017, Jessica and
- Chris shared their aspirations of reimagining education within
- the correctional system with Haley Shoaf. Released in early
- 2022, they teamed up with Haley, who helped them expand upon
- their vision, to form Unlocked Labs, a non-profit dedicated to
- consulting and developing products addressing challenges within
- the justice system.
-
-
-
-
-
-
-
-
- 2022 - present
-
-
- UnlockED: A Vision Realized
+
+
+
+
+
+ 1997
+
+
+ Young Beginnings
+
+ Co-Founders Jessica Hicklin and Chris Santillan
+ met at Potosi Correctional Center before both of
+ their 18th birthdays. They were sentenced to
+ life without parole and told they would die
+ behind bars.
+
+
+
+
+
+
+
+
+ 1998 - 2017
+
+
+ Education Against the Odds
+
+ Despite residing in a facility without access to
+ formal education for over a quarter of a
+ century, they were determined to make a positive
+ impact upon their own lives and upon those with
+ whom they lived and worked with in their
+ community. They both maximized their
+ circumstances, spending years tutoring others to
+ pass their GED/Hi-Set exams and organizing
+ victim empathy and anger management courses for
+ the community.
+
+
+
+
+
+
+
+
+ 2012 - 2017
+
+
+ Coding for Change
+
+ With limited resources, Jessica and Chris both
+ taught themselves how to code without the
+ internet. They dreamed of a day when they could
+ create a solution for ways to track
+ rehabilitation and educational programs on the
+ inside.
+
+
+
+
+
+
+
+
+ 2022
+
+
+ Establishing Unlocked Labs
+
+ Their lives took a turn when a Supreme Court
+ declared life sentences for juveniles
+ unconstitutional. In 2017, Jessica and Chris
+ shared their aspirations of reimagining
+ education within the correctional system with
+ Haley Shoaf. Released in early 2022, they teamed
+ up with Haley, who helped them expand upon their
+ vision, to form Unlocked Labs, a non-profit
+ dedicated to consulting and developing products
+ addressing challenges within the justice system.
+
+
+
+
+
+
+
+
+ 2022 - present
+
+
+ UnlockED: A Vision Realized
+
+ Teaming up with external partners, they
+ developed UnlockEd, a non-profit, open source
+ education access and program management system
+ providing free education to incarcerated
+ individuals across the country, fulfilling a
+ long time dream, and bringing the project full
+ circle.
+
+
+
- Teaming up with external partners, they developed UnlockEd, a
- non-profit, open source education access and program management
- system providing free education to incarcerated individuals
- across the country, fulfilling a long time dream, and bringing
- the project full circle.
-
-
-
+
-
-
- );
+ );
}
diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx
index 1f24c583..fe2a942c 100644
--- a/frontend/src/app.tsx
+++ b/frontend/src/app.tsx
@@ -1,102 +1,104 @@
-import "@/bootstrap";
-import "@/css/app.css";
-import { RouterProvider, createBrowserRouter } from "react-router-dom";
-import Welcome from "@/Pages/Welcome";
-import Dashboard from "@/Pages/Dashboard";
-import Login from "@/Pages/Auth/Login";
-import Users from "@/Pages/Users";
-import UserActivity from "@/Pages/UserActivity";
-import ResetPassword from "@/Pages/Auth/ResetPassword";
-import ProviderPlatformManagement from "./Pages/ProviderPlatformManagement";
-import { AuthProvider } from "./AuthContext";
-import Consent from "./Pages/Auth/Consent";
-import MyCourses from "./Pages/MyCourses";
-import MyProgress from "./Pages/MyProgress";
-import CourseCatalog from "./Pages/CourseCatalog";
-import ProviderUserManagement from "./Pages/ProviderUserManagement";
-import Error from "./Pages/Error";
-import ResourcesManagement from "./Pages/ResourcesManagement";
+import '@/bootstrap';
+import '@/css/app.css';
+import { RouterProvider, createBrowserRouter } from 'react-router-dom';
+import Welcome from '@/Pages/Welcome';
+import Dashboard from '@/Pages/Dashboard';
+import Login from '@/Pages/Auth/Login';
+import Users from '@/Pages/Users';
+import UserActivity from '@/Pages/UserActivity';
+import ResetPassword from '@/Pages/Auth/ResetPassword';
+import ProviderPlatformManagement from './Pages/ProviderPlatformManagement';
+import { AuthProvider } from './AuthContext';
+import Consent from './Pages/Auth/Consent';
+import MyCourses from './Pages/MyCourses';
+import MyProgress from './Pages/MyProgress';
+import CourseCatalog from './Pages/CourseCatalog';
+import ProviderUserManagement from './Pages/ProviderUserManagement';
+import Error from './Pages/Error';
+import ResourcesManagement from './Pages/ResourcesManagement';
function WithAuth({ children }) {
- return
{children} ;
+ return
{children} ;
}
export default function App() {
- const router = createBrowserRouter([
- {
- path: "/",
- element:
,
- errorElement:
,
- },
- {
- path: "/login",
- element:
,
- errorElement:
,
- },
- {
- path: "/dashboard",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/users",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/resources-management",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/user-activity",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/reset-password",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/consent",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/provider-platform-management",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/my-courses",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/my-progress",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/course-catalog",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/provider-users/:providerId",
- element: WithAuth({ children:
}),
- errorElement:
,
- },
- {
- path: "/error",
- element:
,
- },
- ]);
+ const router = createBrowserRouter([
+ {
+ path: '/',
+ element:
,
+ errorElement:
+ },
+ {
+ path: '/login',
+ element:
,
+ errorElement:
+ },
+ {
+ path: '/dashboard',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/users',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/resources-management',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/user-activity',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/reset-password',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/consent',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/provider-platform-management',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/my-courses',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/my-progress',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/course-catalog',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/provider-users/:providerId',
+ element: WithAuth({ children:
}),
+ errorElement:
+ },
+ {
+ path: '/error',
+ element:
+ }
+ ]);
- if (import.meta.hot) {
- import.meta.hot.dispose(() => router.dispose());
- }
+ if (import.meta.hot) {
+ import.meta.hot.dispose(() => router.dispose());
+ }
- return
Loading...} />;
+ return (
+ Loading...} />
+ );
}
diff --git a/frontend/src/bootstrap.ts b/frontend/src/bootstrap.ts
index 52a85a36..5e986159 100644
--- a/frontend/src/bootstrap.ts
+++ b/frontend/src/bootstrap.ts
@@ -1,4 +1,4 @@
-import axios from "axios";
+import axios from 'axios';
axios.defaults.withCredentials = true;
-axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
+axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.axios = axios;
diff --git a/frontend/src/common.ts b/frontend/src/common.ts
index f625f474..e4cab285 100644
--- a/frontend/src/common.ts
+++ b/frontend/src/common.ts
@@ -1,192 +1,192 @@
export enum UserRole {
- Admin = "admin",
- Student = "student",
+ Admin = 'admin',
+ Student = 'student'
}
export const DEFAULT_ADMIN_ID = 1;
export interface User {
- id: number;
- name_first: string;
- name_last: string;
- username: string;
- role: string;
- email: string;
- [key: string]: any;
+ id: number;
+ name_first: string;
+ name_last: string;
+ username: string;
+ role: string;
+ email: string;
+ [key: string]: any;
}
export interface UserWithMappings {
- User: User;
- logins: Array;
+ User: User;
+ logins: Array;
}
export interface ProviderUser {
- username: string;
- name_last: string;
- name_first: string;
- email: string;
- external_user_id: string;
- external_username: string;
+ username: string;
+ name_last: string;
+ name_first: string;
+ email: string;
+ external_user_id: string;
+ external_username: string;
}
export interface UserImports {
- username: string;
- temp_password: string;
- error?: string;
+ username: string;
+ temp_password: string;
+ error?: string;
}
export interface ProviderMapping {
- id: number;
- provider_platform_id: number;
- user_id: number;
- external_user_id: string;
- external_login_id: string;
- external_username: string;
- created_at: Date;
- updated_at: Date;
+ id: number;
+ provider_platform_id: number;
+ user_id: number;
+ external_user_id: string;
+ external_login_id: string;
+ external_username: string;
+ created_at: Date;
+ updated_at: Date;
}
export interface PaginatedResponse {
- message: string;
- data: Array;
- meta: PaginationMeta;
+ message: string;
+ data: Array;
+ meta: PaginationMeta;
}
export interface PaginationMeta {
- total: number;
- current_page: number;
- last_page: number;
- per_page: number;
+ total: number;
+ current_page: number;
+ last_page: number;
+ per_page: number;
}
export interface ServerResponse {
- [key: string]: any;
- message: string;
- data: Array;
+ [key: string]: any;
+ message: string;
+ data: Array;
}
export interface Category {
- id: number;
- name: string;
- links: Array;
- rank: number;
+ id: number;
+ name: string;
+ links: Array;
+ rank: number;
}
export interface CategoryLink {
- [linkName: string]: string;
+ [linkName: string]: string;
}
export interface OidcClient {
- client_id: string;
- client_secret: string;
- scopes: string;
- auth_url: string;
- token_url: string;
+ client_id: string;
+ client_secret: string;
+ scopes: string;
+ auth_url: string;
+ token_url: string;
}
export interface Activity {
- browser_name: string;
- clicked_url: string;
- created_at: string;
- device: string;
- id: number;
- platform: string;
- updated_at: Date;
- user_name_first: string;
- user_name_last: string;
+ browser_name: string;
+ clicked_url: string;
+ created_at: string;
+ device: string;
+ id: number;
+ platform: string;
+ updated_at: Date;
+ user_name_first: string;
+ user_name_last: string;
}
export interface Program {
- id: number;
- provider_platform_id: number;
- name: string;
- description: string;
- external_id: string;
- thumbnail_url: string;
- is_public: boolean;
- external_url: string;
- created_at: Date;
- updated_at: Date;
+ id: number;
+ provider_platform_id: number;
+ name: string;
+ description: string;
+ external_id: string;
+ thumbnail_url: string;
+ is_public: boolean;
+ external_url: string;
+ created_at: Date;
+ updated_at: Date;
}
export interface CourseCatalogue {
- key: [number, string, boolean];
- program_id: number;
- thumbnail_url: string;
- program_name: string;
- provider_name: string;
- external_url: string;
- program_type: string;
- description: string;
- is_favorited: boolean;
- outcome_types: string;
+ key: [number, string, boolean];
+ program_id: number;
+ thumbnail_url: string;
+ program_name: string;
+ provider_name: string;
+ external_url: string;
+ program_type: string;
+ description: string;
+ is_favorited: boolean;
+ outcome_types: string;
}
export interface Milestone {
- id: number;
- program_id: number;
- type: string;
- external_url: string;
- description: string;
- external_id: Date;
- created_at: Date;
- updated_at: Date;
+ id: number;
+ program_id: number;
+ type: string;
+ external_url: string;
+ description: string;
+ external_id: Date;
+ created_at: Date;
+ updated_at: Date;
}
export interface ProviderPlatform {
- access_key: string;
- account_id: string;
- base_url: string;
- description: string;
- icon_url: string;
- id: number;
- name: string;
- state: ProviderPlatformState;
- type: ProviderPlatformType;
- oidc_id: number;
- [key: string | ProviderPlatformState | ProviderPlatformType]: any;
+ access_key: string;
+ account_id: string;
+ base_url: string;
+ description: string;
+ icon_url: string;
+ id: number;
+ name: string;
+ state: ProviderPlatformState;
+ type: ProviderPlatformType;
+ oidc_id: number;
+ [key: string | ProviderPlatformState | ProviderPlatformType]: any;
}
export enum ProviderPlatformState {
- ENABLED = "enabled",
- DISABLED = "disabled",
- ARCHIVED = "archived",
+ ENABLED = 'enabled',
+ DISABLED = 'disabled',
+ ARCHIVED = 'archived'
}
export enum ProviderPlatformType {
- CANVAS_CLOUD = "canvas_cloud",
- CANVAS_OSS = "canvas_oss",
- KOLIBRI = "kolibri",
+ CANVAS_CLOUD = 'canvas_cloud',
+ CANVAS_OSS = 'canvas_oss',
+ KOLIBRI = 'kolibri'
}
export interface AdminDashboardJoin {
- monthly_activity: RecentActivity[];
- weekly_active_users: number;
- avg_daily_activity: number;
- total_weekly_activity: number;
- program_milestones: ProgramMilestones[];
- top_program_activity: ProgramActivity[];
- facility_name: string;
+ monthly_activity: RecentActivity[];
+ weekly_active_users: number;
+ avg_daily_activity: number;
+ total_weekly_activity: number;
+ program_milestones: ProgramMilestones[];
+ top_program_activity: ProgramActivity[];
+ facility_name: string;
}
export interface ProgramMilestones {
- name: string;
- milestones: number;
+ name: string;
+ milestones: number;
}
export interface ProgramActivity {
- program_name: string;
- hours_engaged: number;
+ program_name: string;
+ hours_engaged: number;
}
export interface RecentActivity {
- date: string;
- delta: number;
+ date: string;
+ delta: number;
}
export interface Link {
- [name: string]: string;
+ [name: string]: string;
}
export interface Resource {
- name: string;
- links: Array ;
- rank: number;
+ name: string;
+ links: Array ;
+ rank: number;
}
diff --git a/frontend/src/css/app.css b/frontend/src/css/app.css
index 9b76ba1b..82be1198 100644
--- a/frontend/src/css/app.css
+++ b/frontend/src/css/app.css
@@ -3,77 +3,77 @@
@tailwind utilities;
@layer base {
- h1 {
- @apply text-2xl text-header-text font-lexend font-semibold;
- }
- h2 {
- @apply text-base text-header-text font-lexend font-semibold;
- }
- h3 {
- @apply text-base text-header-text font-lexend font-semibold;
- }
- p {
- @apply text-body-text font-inter font-medium;
- }
- body {
- @apply text-body-text font-inter font-medium;
- }
+ h1 {
+ @apply text-2xl text-header-text font-lexend font-semibold;
+ }
+ h2 {
+ @apply text-base text-header-text font-lexend font-semibold;
+ }
+ h3 {
+ @apply text-base text-header-text font-lexend font-semibold;
+ }
+ p {
+ @apply text-body-text font-inter font-medium;
+ }
+ body {
+ @apply text-body-text font-inter font-medium;
+ }
}
@layer components {
- .body {
- @apply text-sm;
- }
- .body-small {
- @apply text-xs;
- }
- .card {
- @apply shadow-md rounded-lg border border-black border-opacity-[10%] bg-base-teal;
- }
- .card-h-padding {
- @apply mt-4 ml-4;
- }
- .catalog-pill {
- @apply text-xs px-3 py-1 rounded-2xl flex-grow-0 mx-2;
- }
- .table-2 {
- @apply w-full;
- }
- .table-2 thead tr {
- @apply grid grid-flow-col auto-cols-max justify-between border border-x-0 border-t-0 mt-2;
- }
- .table-2 th {
- @apply body text-grey-4 font-medium;
- }
- .table-2 tbody {
- @apply flex flex-col gap-4 mt-4;
- }
- .table-2 tbody tr {
- @apply grid grid-flow-col auto-cols-max justify-between mr-3;
- }
- .table-2 td {
- @apply body-small my-auto;
- }
- .button {
- @apply bg-teal-3 body text-white py-3 px-3 flex flex-row gap-3 rounded-lg;
- }
+ .body {
+ @apply text-sm;
+ }
+ .body-small {
+ @apply text-xs;
+ }
+ .card {
+ @apply shadow-md rounded-lg border border-black border-opacity-[10%] bg-base-teal;
+ }
+ .card-h-padding {
+ @apply mt-4 ml-4;
+ }
+ .catalog-pill {
+ @apply text-xs px-3 py-1 rounded-2xl flex-grow-0 mx-2;
+ }
+ .table-2 {
+ @apply w-full;
+ }
+ .table-2 thead tr {
+ @apply grid grid-flow-col auto-cols-max justify-between border border-x-0 border-t-0 mt-2;
+ }
+ .table-2 th {
+ @apply body text-grey-4 font-medium;
+ }
+ .table-2 tbody {
+ @apply flex flex-col gap-4 mt-4;
+ }
+ .table-2 tbody tr {
+ @apply grid grid-flow-col auto-cols-max justify-between mr-3;
+ }
+ .table-2 td {
+ @apply body-small my-auto;
+ }
+ .button {
+ @apply bg-teal-3 body text-white py-3 px-3 flex flex-row gap-3 rounded-lg;
+ }
}
@layer utilities {
- .font-lexend {
- font-family: "Lexend", sans-serif;
- }
- .font-inter {
- font-family: "Inter", sans-serif;
- }
- /* for Chrome, Safari and Opera */
- .scrollbar::-webkit-scrollbar {
- scrollbar-width: thin;
- scrollbar-color: var(--grey-2) transparent;
- }
- /* for IE, Edge and Firefox */
- .scrollbar {
- scrollbar-width: thin;
- scrollbar-color: var(--grey-2) transparent;
- }
+ .font-lexend {
+ font-family: 'Lexend', sans-serif;
+ }
+ .font-inter {
+ font-family: 'Inter', sans-serif;
+ }
+ /* for Chrome, Safari and Opera */
+ .scrollbar::-webkit-scrollbar {
+ scrollbar-width: thin;
+ scrollbar-color: var(--grey-2) transparent;
+ }
+ /* for IE, Edge and Firefox */
+ .scrollbar {
+ scrollbar-width: thin;
+ scrollbar-color: var(--grey-2) transparent;
+ }
}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 441559ec..85bd5bca 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,20 +1,20 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
-import App from "./app";
-import "./css/app.css";
-import { SWRConfig } from "swr";
-import { ThemeProvider } from "./Components/ThemeContext";
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './app';
+import './css/app.css';
+import { SWRConfig } from 'swr';
+import { ThemeProvider } from './Components/ThemeContext';
-ReactDOM.createRoot(document.getElementById("root")!).render(
-
- window.axios.get(url).then((res) => res.data),
- }}
- >
-
-
-
-
- ,
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+ window.axios.get(url).then((res) => res.data)
+ }}
+ >
+
+
+
+
+
);
diff --git a/frontend/src/types/global.d.ts b/frontend/src/types/global.d.ts
index 5fea1de6..29996e66 100644
--- a/frontend/src/types/global.d.ts
+++ b/frontend/src/types/global.d.ts
@@ -1,11 +1,11 @@
-import { AxiosInstance } from "axios";
+import { AxiosInstance } from 'axios';
declare global {
- interface Window {
- axios: AxiosInstance;
- }
+ interface Window {
+ axios: AxiosInstance;
+ }
- interface HTMLDialogElement {
- showModal: () => void;
- }
+ interface HTMLDialogElement {
+ showModal: () => void;
+ }
}
diff --git a/frontend/src/types/index.d.ts b/frontend/src/types/index.d.ts
index 709ea1d7..69caf43c 100644
--- a/frontend/src/types/index.d.ts
+++ b/frontend/src/types/index.d.ts
@@ -1,23 +1,23 @@
export enum UserRole {
- Admin = "admin",
- Student = "student",
+ Admin = 'admin',
+ Student = 'student'
}
export interface User {
- created_at: any;
- id: number;
- name_first: string;
- name_last: string;
- username: string;
- role: string;
- email: string;
- password_reset: boolean;
+ created_at: any;
+ id: number;
+ name_first: string;
+ name_last: string;
+ username: string;
+ role: string;
+ email: string;
+ password_reset: boolean;
}
export type PageProps<
- T extends Record = Record,
+ T extends Record = Record
> = T & {
- auth: {
- user: User;
- };
+ auth: {
+ user: User;
+ };
};
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index 6f6e48b0..1e44f6ae 100644
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -1,103 +1,103 @@
export default {
- content: ["./src/**/*.tsx"],
+ content: ['./src/**/*.tsx'],
- daisyui: {
- themes: [
- {
- light: {
- primary: "#14b8a6",
- secondary: "#2dd4bf",
- accent: "#d97706",
- neutral: "#4d5360",
- "base-100": "#f3f4f6",
- info: "#0ea5e9",
- success: "#22c55e",
- warning: "#e97356",
- error: "#d95566",
+ daisyui: {
+ themes: [
+ {
+ light: {
+ primary: '#14b8a6',
+ secondary: '#2dd4bf',
+ accent: '#d97706',
+ neutral: '#4d5360',
+ 'base-100': '#f3f4f6',
+ info: '#0ea5e9',
+ success: '#22c55e',
+ warning: '#e97356',
+ error: '#d95566',
- "--teal-1": "#D7F4F1",
- "--teal-2": "#B0DFDA",
- "--teal-3": "#18ABA0",
- "--teal-4": "#005952",
- "--teal-5": "#002E2A",
- "--base-teal": "#FFFFFF",
- "--background": "#F9FBFB",
- "--inner-background": "#FFFFFF",
- "--dark-yellow": "#EA9B00",
- "--primary-yellow": "#FDC601",
- "--pale-yellow": "#18ABA0",
- "--grey-1": "#ECECEC",
- "--grey-2": "#CCCCCC",
- "--grey-3": "#999999",
- "--grey-4": "#737373",
- "--body-text": "#222222",
- "--header-text": "#121212",
- "--fallback-b1": "#F9FBFB",
- },
- dark: {
- primary: "#14b8a6",
- secondary: "#2dd4bf",
- accent: "#ebaf24",
- neutral: "#7d8390",
- "base-100": "#1f2937",
- info: "#0ea5e9",
- success: "#22c55e",
- warning: "#e97356",
- error: "#d95566",
+ '--teal-1': '#D7F4F1',
+ '--teal-2': '#B0DFDA',
+ '--teal-3': '#18ABA0',
+ '--teal-4': '#005952',
+ '--teal-5': '#002E2A',
+ '--base-teal': '#FFFFFF',
+ '--background': '#F9FBFB',
+ '--inner-background': '#FFFFFF',
+ '--dark-yellow': '#EA9B00',
+ '--primary-yellow': '#FDC601',
+ '--pale-yellow': '#18ABA0',
+ '--grey-1': '#ECECEC',
+ '--grey-2': '#CCCCCC',
+ '--grey-3': '#999999',
+ '--grey-4': '#737373',
+ '--body-text': '#222222',
+ '--header-text': '#121212',
+ '--fallback-b1': '#F9FBFB'
+ },
+ dark: {
+ primary: '#14b8a6',
+ secondary: '#2dd4bf',
+ accent: '#ebaf24',
+ neutral: '#7d8390',
+ 'base-100': '#1f2937',
+ info: '#0ea5e9',
+ success: '#22c55e',
+ warning: '#e97356',
+ error: '#d95566',
- "--teal-1": "#11554E",
- "--teal-2": "#13746C",
- "--teal-3": "#14958A",
- "--teal-4": "#61BAB2",
- "--teal-5": "#B0DFDA",
- "--base-teal": "#103531",
- "--background": "#0F2926",
- "--inner-background": "#1A3F3B",
- "--dark-yellow": "#EA9B00",
- "--primary-yellow": "#FDC601",
- "--pale-yellow": "#18ABA0",
- "--grey-1": "#737373",
- "--grey-2": "#999999",
- "--grey-3": "#CCCCCC",
- "--grey-4": "#ECECEC",
- "--body-text": "#EEEEEE",
- "--header-text": "#CCCCCC",
- "--fallback-b1": "#0F2926",
- },
- },
- ],
- },
+ '--teal-1': '#11554E',
+ '--teal-2': '#13746C',
+ '--teal-3': '#14958A',
+ '--teal-4': '#61BAB2',
+ '--teal-5': '#B0DFDA',
+ '--base-teal': '#103531',
+ '--background': '#0F2926',
+ '--inner-background': '#1A3F3B',
+ '--dark-yellow': '#EA9B00',
+ '--primary-yellow': '#FDC601',
+ '--pale-yellow': '#18ABA0',
+ '--grey-1': '#737373',
+ '--grey-2': '#999999',
+ '--grey-3': '#CCCCCC',
+ '--grey-4': '#ECECEC',
+ '--body-text': '#EEEEEE',
+ '--header-text': '#CCCCCC',
+ '--fallback-b1': '#0F2926'
+ }
+ }
+ ]
+ },
- theme: {
- extend: {
- fontFamily: {
- lexend: ["Lexend", "sans-serif"],
- },
- colors: {
- "teal-1": "var(--teal-1)",
- "teal-2": "var(--teal-2)",
- "teal-3": "var(--teal-3)",
- "teal-4": "var(--teal-4)",
- "teal-5": "var(--teal-5)",
- "base-teal": "var(--base-teal)",
- background: "var(--background)",
- "inner-background": "var(--inner-background)",
- "dark-yellow": "var(--dark-yellow)",
- "primary-yellow": "var(--primary-yellow)",
- "pale-yellow": "var(--pale-yellow)",
- "grey-1": "var(--grey-1)",
- "grey-2": "var(--grey-2)",
- "grey-3": "var(--grey-3)",
- "grey-4": "var(--grey-4)",
- "body-text": "var(--body-text)",
- "header-text": "var(--header-text)",
- },
+ theme: {
+ extend: {
+ fontFamily: {
+ lexend: ['Lexend', 'sans-serif']
+ },
+ colors: {
+ 'teal-1': 'var(--teal-1)',
+ 'teal-2': 'var(--teal-2)',
+ 'teal-3': 'var(--teal-3)',
+ 'teal-4': 'var(--teal-4)',
+ 'teal-5': 'var(--teal-5)',
+ 'base-teal': 'var(--base-teal)',
+ background: 'var(--background)',
+ 'inner-background': 'var(--inner-background)',
+ 'dark-yellow': 'var(--dark-yellow)',
+ 'primary-yellow': 'var(--primary-yellow)',
+ 'pale-yellow': 'var(--pale-yellow)',
+ 'grey-1': 'var(--grey-1)',
+ 'grey-2': 'var(--grey-2)',
+ 'grey-3': 'var(--grey-3)',
+ 'grey-4': 'var(--grey-4)',
+ 'body-text': 'var(--body-text)',
+ 'header-text': 'var(--header-text)'
+ }
+ }
},
- },
- plugins: [
- require("@tailwindcss/forms"),
- require("@tailwindcss/typography"),
- require("daisyui"),
- ],
+ plugins: [
+ require('@tailwindcss/forms'),
+ require('@tailwindcss/typography'),
+ require('daisyui')
+ ]
};
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index f9c684b9..9ec261fd 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -1,26 +1,26 @@
{
- "compilerOptions": {
- "target": "ESNext",
- "useDefineForClassFields": true,
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "skipLibCheck": true,
- "baseUrl": ".",
- "paths": {
- "@": ["src"],
- "@/*": ["src/*"]
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "baseUrl": ".",
+ "paths": {
+ "@": ["src"],
+ "@/*": ["src/*"]
+ },
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ /* Linting */
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
},
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx",
- /* Linting */
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
- },
- "include": ["src/**/*"]
+ "include": ["src/**/*"]
}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index d755054e..afcdb09e 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,27 +1,27 @@
-import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react";
-import path from "path";
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react()],
- resolve: {
- alias: {
- "@": path.resolve(__dirname, "src"),
+ plugins: [react()],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, 'src')
+ }
},
- },
- server: {
- host: "0.0.0.0",
- port: 5173,
- proxy: {
- "/api": {
- target: "http://127.0.0.1",
- changeOrigin: true,
- },
- "/self-service": {
- target: "http://127.0.0.1",
- changeOrigin: true,
- },
- },
- },
+ server: {
+ host: '0.0.0.0',
+ port: 5173,
+ proxy: {
+ '/api': {
+ target: 'http://127.0.0.1',
+ changeOrigin: true
+ },
+ '/self-service': {
+ target: 'http://127.0.0.1',
+ changeOrigin: true
+ }
+ }
+ }
});