diff --git a/code/frontend/src/components/Loading.js b/code/frontend/src/components/Loading.js
new file mode 100644
index 0000000..b076e4d
--- /dev/null
+++ b/code/frontend/src/components/Loading.js
@@ -0,0 +1,12 @@
+import React from "react"
+import { faSpinner } from "@fortawesome/free-solid-svg-icons"
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
+
+
+export default function Loading({ ...props }) {
+ return (
+
+ Loading...
+
+ )
+}
diff --git a/code/frontend/src/hooks/useAsyncEffect.js b/code/frontend/src/hooks/useAsyncEffect.js
new file mode 100644
index 0000000..7595c3b
--- /dev/null
+++ b/code/frontend/src/hooks/useAsyncEffect.js
@@ -0,0 +1,17 @@
+/* eslint-disable */
+import { useEffect } from "react"
+
+
+/**
+ * {@link useEffect}, but with an async effect.
+ *
+ * @warning Breaks `react-hooks/exaustive-deps`.
+ *
+ * @param effect - The async effect.
+ * @param deps - The dependencies of the hook.
+ */
+export default function useAsyncEffect(effect, deps) {
+ useEffect(() => {
+ effect()
+ }, [effect, ...deps])
+}
diff --git a/code/frontend/src/hooks/useData.js b/code/frontend/src/hooks/useData.js
new file mode 100644
index 0000000..0fd3507
--- /dev/null
+++ b/code/frontend/src/hooks/useData.js
@@ -0,0 +1,69 @@
+import { useCallback, useContext, useEffect, useState } from "react"
+import ContextUser from "../contexts/ContextUser"
+
+
+/**
+ * Hook which fetches data from the backend on the first render of a component.
+ *
+ * @param fetchData - The function to use when fetching data.
+ * @param method - The HTTP method to use.
+ * @param path - The HTTP path to fetch the data at.
+ * @param body - The body of the HTTP request (it will be JSONified before being sent).
+ * @param init - Additional `init` parameters to pass to `fetch`.
+ * @returns {{data: *, refresh: function, error: Error}}
+ */
+export default function useData(fetchData, method, path, body, init) {
+ const [error, setError] = useState(null)
+ const [data, setData] = useState(null)
+ const [loading, setLoading] = useState(false)
+ const started = (loading || data || error)
+
+ /**
+ * Load data from the API.
+ */
+ const load = useCallback(
+ async () => {
+ console.debug(`Trying to ${method} ${path}...`)
+
+ setLoading(true)
+
+ try {
+ const _data = await fetchData(method, path, body, init)
+ setData(_data)
+ } catch(e) {
+ setError(e)
+ } finally {
+ setLoading(false)
+ }
+ },
+ [fetchData, method, path, body, init, setData, setError, setLoading]
+ )
+
+ /**
+ * Invalidate the data loaded from the API and try to load it again.
+ */
+ const refresh = useCallback(
+ async () => {
+ console.debug("Clearing data...")
+ setData(null)
+
+ console.debug("Clearing error...")
+ setError(null)
+
+ await load()
+ },
+ [load, setData, setError]
+ )
+
+ useEffect(
+ () => {
+ if(!started) {
+ // noinspection JSIgnoredPromiseFromCall
+ load()
+ }
+ },
+ [load, started]
+ )
+
+ return {data, error, loading, started, refresh}
+}
\ No newline at end of file
diff --git a/code/frontend/src/hooks/useLocalStorageState.js b/code/frontend/src/hooks/useLocalStorageState.js
index 5de5662..1acc7ba 100644
--- a/code/frontend/src/hooks/useLocalStorageState.js
+++ b/code/frontend/src/hooks/useLocalStorageState.js
@@ -1,4 +1,4 @@
-import { useEffect, useState } from "react"
+import { useCallback, useEffect, useState } from "react"
/**
@@ -7,46 +7,71 @@ import { useEffect, useState } from "react"
export default function useLocalStorageState(key, def) {
const [value, setValue] = useState(null);
- const load = () => {
- if(localStorage) {
- console.debug(`Loading ${key} from localStorage...`)
- let value = JSON.parse(localStorage.getItem(key))
+ /**
+ * Load the `key` from the {@link localStorage} into `value`, defaulting to `def` if it is not found.
+ */
+ const load = useCallback(
+ () => {
+ if(localStorage) {
+ console.debug(`Loading ${key} from localStorage...`)
+ let _value = JSON.parse(localStorage.getItem(key))
- if(value) {
- console.debug(`Loaded ${key} from localStorage!`)
- return value
+ if(_value) {
+ console.info(`Loaded ${key} from localStorage!`)
+ return _value
+ }
+ else {
+ console.info(`There is no value ${key} stored, defaulting...`)
+ return def
+ }
}
else {
- console.debug(`There is no value ${key} stored, defaulting...`)
+ console.warn(`Can't load value as localStorage doesn't seem to be available, defaulting...`)
return def
}
- }
- else {
- console.warn(`Can't load value as localStorage doesn't seem to be available, defaulting...`)
- return def
- }
- }
+ },
+ [key, def]
+ )
- useEffect(() => {
- if(!value) {
- setValue(load())
- }
- }, [value])
+ /**
+ * Save a value to the {@link localStorage}.
+ */
+ const save = useCallback(
+ (value) => {
+ if(localStorage) {
+ console.debug(`Saving ${key} to localStorage...`)
+ localStorage.setItem(key, JSON.stringify(value))
+ }
+ else {
+ console.warn(`Can't save ${key}; localStorage doesn't seem to be available...`)
+ }
+ },
+ [key]
+ )
- const save = (value) => {
- if(localStorage) {
- console.debug(`Saving ${key} to localStorage...`)
- localStorage.setItem(key, JSON.stringify(value))
- }
- else {
- console.warn(`Can't save theme; localStorage doesn't seem to be available...`)
- }
- }
+ /**
+ * Set `value` and save it to the {@link localStorage}.
+ */
+ const setAndSave = useCallback(
+ (value) => {
+ setValue(value)
+ save(value)
+ },
+ [setValue, save]
+ )
- const setAndSave = (value) => {
- setValue(value)
- save(value)
- }
+ /*
+ * When the component first renders, try to load the value from the localStorage.
+ */
+ useEffect(
+ () => {
+ if(!value) {
+ console.debug(`This is the first render, loading ${key} from the localStorage...`)
+ setValue(load())
+ }
+ },
+ [value, setValue, load, key],
+ )
return [value, setAndSave]
}
\ No newline at end of file
diff --git a/code/frontend/src/routes/PageLogin.js b/code/frontend/src/routes/PageLogin.js
index 7961ad5..9392428 100644
--- a/code/frontend/src/routes/PageLogin.js
+++ b/code/frontend/src/routes/PageLogin.js
@@ -1,9 +1,6 @@
-import React, { useContext, useState } from "react"
+import React from "react"
import Style from "./PageLogin.module.css"
import classNames from "classnames"
-import BoxFull from "../components/BoxFull"
-import ContextUser from "../contexts/ContextUser"
-import { useHistory } from "react-router"
import BoxSetServer from "../components/BoxSetServer"
import BoxLogin from "../components/BoxLogin"
diff --git a/code/frontend/src/routes/PageRepositories.js b/code/frontend/src/routes/PageRepositories.js
index d3d6dbf..eb3c7db 100644
--- a/code/frontend/src/routes/PageRepositories.js
+++ b/code/frontend/src/routes/PageRepositories.js
@@ -1,18 +1,15 @@
import React from "react"
import Style from "./PageRepositories.module.css"
import classNames from "classnames"
-import BoxFull from "../components/BoxFull"
+import BoxRepositoriesActive from "../components/BoxRepositoriesActive"
+import BoxRepositoriesArchived from "../components/BoxRepositoriesArchived"
export default function PageRepositories({ children, className, ...props }) {
return (
-
- 🚧 Not implemented.
-
-
- 🚧 Not implemented.
-
+
+
)
}