mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-22 04:54:18 +00:00
💥 Sorry! I couldn't make an atomic commit!
This commit is contained in:
parent
58064323c9
commit
3e5d39132a
24 changed files with 367 additions and 90 deletions
|
@ -4,12 +4,16 @@ import Layout from "./components/Layout"
|
|||
import ContextTheme from "./contexts/ContextTheme"
|
||||
import { BrowserRouter } from "react-router-dom"
|
||||
import { Route, Switch } from "react-router"
|
||||
import PageHome from "./routes/PageHome"
|
||||
import PageDashboard from "./routes/PageDashboard"
|
||||
import PageRepositories from "./routes/PageRepositories"
|
||||
import PageAlerts from "./routes/PageAlerts"
|
||||
import PageSettings from "./routes/PageSettings"
|
||||
import PageSandbox from "./routes/PageSandbox"
|
||||
import useSavedTheme from "./hooks/useSavedTheme"
|
||||
import PageLogin from "./routes/PageLogin"
|
||||
import useSavedLogin from "./hooks/useSavedLogin"
|
||||
import ContextLogin from "./contexts/ContextLogin"
|
||||
import PageRoot from "./routes/PageRoot"
|
||||
|
||||
|
||||
/**
|
||||
|
@ -19,15 +23,20 @@ import useSavedTheme from "./hooks/useSavedTheme"
|
|||
* @constructor
|
||||
*/
|
||||
export default function App() {
|
||||
const [theme, setAndSaveTheme] = useSavedTheme();
|
||||
const theme = useSavedTheme();
|
||||
const login = useSavedLogin();
|
||||
|
||||
return (
|
||||
<ContextTheme.Provider value={[theme, setAndSaveTheme]}>
|
||||
<ContextTheme.Provider value={theme}>
|
||||
<ContextLogin.Provider value={login}>
|
||||
<BrowserRouter>
|
||||
|
||||
<div className={classNames(Style.App, theme)}>
|
||||
<Layout>
|
||||
<Switch>
|
||||
<Route path={"/login"} exact={true}>
|
||||
<PageLogin/>
|
||||
</Route>
|
||||
<Route path={"/repositories"} exact={true}>
|
||||
<PageRepositories/>
|
||||
</Route>
|
||||
|
@ -40,14 +49,18 @@ export default function App() {
|
|||
<Route path={"/sandbox"} exact={true}>
|
||||
<PageSandbox/>
|
||||
</Route>
|
||||
<Route path={"/"} exact={true}>
|
||||
<PageHome/>
|
||||
<Route path={"/dashboard"} exact={true}>
|
||||
<PageDashboard/>
|
||||
</Route>
|
||||
<Route path={"/"}>
|
||||
<PageRoot/>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Layout>
|
||||
</div>
|
||||
|
||||
</BrowserRouter>
|
||||
</ContextLogin.Provider>
|
||||
</ContextTheme.Provider>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ import BoxFull from "./BoxFull"
|
|||
/**
|
||||
* A {@link BoxFull} whose body does not grow automatically but instead supports scrolling.
|
||||
*
|
||||
* @todo Is there a way to allow the box body to grow automatically...?
|
||||
*
|
||||
* @param children - The contents of the box body.
|
||||
* @param childrenClassName - Additional class(es) added to the inner `<div>` acting as the body.
|
||||
* @param props - Additional props to pass to the box.
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
}
|
||||
|
||||
.Input:focus {
|
||||
outline: 0; /* TODO: Questo è sconsigliato dalle linee guida sull'accessibilità! */
|
||||
outline: 0;
|
||||
|
||||
color: var(--fg-field-on);
|
||||
background-color: var(--bg-field-on);
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
background-color: var(--bg-field-off);
|
||||
border-width: 0;
|
||||
|
||||
outline: 0; /* TODO: Questo è sconsigliato dalle linee guida sull'accessibilità! */
|
||||
outline: 0;
|
||||
|
||||
/* Repeat properties overridden by <input> */
|
||||
font-family: var(--font-regular);
|
||||
|
|
|
@ -8,14 +8,14 @@ import Style from "./Label.module.css"
|
|||
*
|
||||
* @param children - The labelled element.
|
||||
* @param text - Text to be displayed in the label.
|
||||
* @param for_ - The `[id]` of the labelled element.
|
||||
* @param htmlFor - The `[id]` of the labelled element.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Label({ children, text, for_ }) {
|
||||
export default function Label({ children, text, htmlFor }) {
|
||||
return (
|
||||
<Fragment>
|
||||
<label for={for_} className={Style.LabelText} >
|
||||
<label htmlFor={htmlFor} className={Style.LabelText} >
|
||||
{text}
|
||||
</label>
|
||||
<div className={Style.LabelContent}>
|
||||
|
|
25
code/frontend/src/components/LoggedInUser.js
Normal file
25
code/frontend/src/components/LoggedInUser.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React, { useContext } from "react"
|
||||
import Style from "./LoggedInUser.module.css"
|
||||
import classNames from "classnames"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faUser } from "@fortawesome/free-solid-svg-icons"
|
||||
import ContextLogin from "../contexts/ContextLogin"
|
||||
|
||||
|
||||
export default function LoggedInUser({ children, className, ...props }) {
|
||||
const {state} = useContext(ContextLogin);
|
||||
|
||||
if(!state) {
|
||||
return (
|
||||
<i className={classNames(Style.LoggedInUser, className)} {...props}>
|
||||
Not logged in
|
||||
</i>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<b className={classNames(Style.LoggedInUser, className)} {...props}>
|
||||
<FontAwesomeIcon icon={faUser}/> {state.username}
|
||||
</b>
|
||||
)
|
||||
}
|
3
code/frontend/src/components/LoggedInUser.module.css
Normal file
3
code/frontend/src/components/LoggedInUser.module.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.LoggedInUser {
|
||||
|
||||
}
|
|
@ -29,7 +29,7 @@ export default function Logo({ className, ...props }) {
|
|||
logo = LogoLight
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unknown theme: ${theme}`)
|
||||
logo = "#"
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,7 +6,25 @@ import Button from "./Button"
|
|||
import { faArchive, faPencilAlt, faTrash } from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
|
||||
export default function RepositorySummary({ className, icon, title, details, buttons, startDate, canDelete, canEdit, canArchive, ...props }) {
|
||||
/**
|
||||
* A long line representing a repository in a list.
|
||||
*
|
||||
* @param icon - The FontAwesome IconDefinition that represents the repository.
|
||||
* @param title - The title of the repository.
|
||||
* @todo What goes in the details field?
|
||||
* @param details - Whatever should be rendered in the details field.
|
||||
* @param startDate - The start date of the repository.
|
||||
* @param canDelete - If the Delete button should be displayed or not.
|
||||
* @param canEdit - If the Edit button should be displayed or not.
|
||||
* @param canArchive - If the Archive button should be displayed or not.
|
||||
* @param className - Additional class(es) to be added to the outer box.
|
||||
* @param props - Additional props to pass to the outer box.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function RepositorySummaryBase(
|
||||
{ icon, title, details, startDate, canDelete, canEdit, canArchive, className, ...props }
|
||||
) {
|
||||
return (
|
||||
<div className={classNames(Style.RepositorySummary, className)} {...props}>
|
||||
<div className={Style.Left}>
|
|
@ -16,7 +16,7 @@
|
|||
}
|
||||
|
||||
.Select:focus {
|
||||
outline: 0; /* TODO: Questo è sconsigliato dalle linee guida sull'accessibilità! */
|
||||
outline: 0;
|
||||
|
||||
color: var(--fg-field-on);
|
||||
background-color: var(--bg-field-on);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react"
|
||||
import React, { Fragment, useContext } from "react"
|
||||
import Style from "./Sidebar.module.css"
|
||||
import classNames from "classnames"
|
||||
import Logo from "./Logo"
|
||||
import ButtonSidebar from "./ButtonSidebar"
|
||||
import { faCog, faExclamationTriangle, faFolder, faHome, faWrench } from "@fortawesome/free-solid-svg-icons"
|
||||
import { faCog, faExclamationTriangle, faFolder, faHome, faKey, faWrench } from "@fortawesome/free-solid-svg-icons"
|
||||
import ContextLogin from "../contexts/ContextLogin"
|
||||
|
||||
|
||||
/**
|
||||
|
@ -16,13 +17,24 @@ import { faCog, faExclamationTriangle, faFolder, faHome, faWrench } from "@forta
|
|||
* @constructor
|
||||
*/
|
||||
export default function Sidebar({ className, ...props }) {
|
||||
const {state} = useContext(ContextLogin)
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.Sidebar, className)} {...props}>
|
||||
<Logo/>
|
||||
<ButtonSidebar to={"/"} icon={faHome}>Dashboard</ButtonSidebar>
|
||||
<ButtonSidebar to={"/repositories"} icon={faFolder}>Repositories</ButtonSidebar>
|
||||
<ButtonSidebar to={"/alerts"} icon={faExclamationTriangle}>Alerts</ButtonSidebar>
|
||||
<ButtonSidebar to={"/settings"} icon={faCog}>Settings</ButtonSidebar>
|
||||
{
|
||||
state ?
|
||||
<Fragment>
|
||||
<ButtonSidebar to={"/dashboard"} icon={faHome}>Dashboard</ButtonSidebar>
|
||||
<ButtonSidebar to={"/repositories"} icon={faFolder}>Repositories</ButtonSidebar>
|
||||
<ButtonSidebar to={"/alerts"} icon={faExclamationTriangle}>Alerts</ButtonSidebar>
|
||||
<ButtonSidebar to={"/settings"} icon={faCog}>Settings</ButtonSidebar>
|
||||
</Fragment>
|
||||
:
|
||||
<Fragment>
|
||||
<ButtonSidebar to={"/login"} icon={faKey}>Login</ButtonSidebar>
|
||||
</Fragment>
|
||||
}
|
||||
{
|
||||
process.env.NODE_ENV === "development" ?
|
||||
<ButtonSidebar to={"/sandbox"} icon={faWrench}>Sandbox</ButtonSidebar>
|
||||
|
|
19
code/frontend/src/contexts/ContextLogin.js
Normal file
19
code/frontend/src/contexts/ContextLogin.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import {createContext} from "react";
|
||||
|
||||
|
||||
/**
|
||||
* A context containing an object with the following values:
|
||||
* - `state`: `null` if the user is not logged in, otherwise an object containing:
|
||||
* - `server`: The base url of the N.E.S.T. backend
|
||||
* - `email`: The email of the account the user is logged in as
|
||||
* - `token`: The bearer token to use in authenticated API requests
|
||||
* - `login`: an async function which logs in the user if given the following parameters: `server, email, password`
|
||||
* - `logout`: a function which logs out the user
|
||||
* - `fetch_unauth`: a variant of {@link fetch} which uses `state.server` as the base url, allowing only the API path
|
||||
* to be passed
|
||||
* - `fetch_auth`: a variant of {@link fetch_unauth} which automatically passes the correct `Authentication` header
|
||||
*
|
||||
* @type {React.Context}
|
||||
*/
|
||||
const ContextLogin = createContext(null)
|
||||
export default ContextLogin
|
52
code/frontend/src/hooks/useLocalStorageState.js
Normal file
52
code/frontend/src/hooks/useLocalStorageState.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { useEffect, useState } from "react"
|
||||
|
||||
|
||||
/**
|
||||
* Hook with the same API as {@link React.useState} which stores its value in the browser's {@link localStorage}.
|
||||
*/
|
||||
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))
|
||||
|
||||
if(value) {
|
||||
console.debug(`Loaded ${key} from localStorage!`)
|
||||
return value
|
||||
}
|
||||
else {
|
||||
console.debug(`There is no value ${key} stored, defaulting...`)
|
||||
return def
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.warn(`Can't load value as localStorage doesn't seem to be available, defaulting...`)
|
||||
return def
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(!value) {
|
||||
setValue(load())
|
||||
}
|
||||
}, [value])
|
||||
|
||||
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...`)
|
||||
}
|
||||
}
|
||||
|
||||
const setAndSave = (value) => {
|
||||
setValue(value)
|
||||
save(value)
|
||||
}
|
||||
|
||||
return [value, setAndSave]
|
||||
}
|
69
code/frontend/src/hooks/useSavedLogin.js
Normal file
69
code/frontend/src/hooks/useSavedLogin.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
import useLocalStorageState from "./useLocalStorageState"
|
||||
|
||||
|
||||
/**
|
||||
* Hook to place at the root to generate the contents of {@link ContextLogin}.
|
||||
*/
|
||||
export default function useSavedLogin() {
|
||||
const [state, setState] = useLocalStorageState("login", null)
|
||||
|
||||
const login = async (server, email, password) => {
|
||||
console.debug("Contacting server to login...")
|
||||
const response = await fetch(`${server}/api/login`, {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"email": email,
|
||||
"password": password,
|
||||
})
|
||||
})
|
||||
|
||||
console.debug("Decoding server response...")
|
||||
const data = await response.json()
|
||||
|
||||
console.debug("Ensuring the request was a success...")
|
||||
if(data["result"] !== "success") {
|
||||
console.error(`Login failed: ${data["msg"]}`)
|
||||
return
|
||||
}
|
||||
|
||||
console.debug("Storing login state...")
|
||||
setState({
|
||||
server: server,
|
||||
email: data["user"]["email"],
|
||||
isAdmin: data["user"]["isAdmin"],
|
||||
username: data["user"]["username"],
|
||||
token: data["access_token"],
|
||||
})
|
||||
console.debug("Stored login state!")
|
||||
|
||||
console.info("Login successful!")
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
console.debug("Clearing login state...")
|
||||
setState(null)
|
||||
console.debug("Cleared login state!")
|
||||
|
||||
console.info("Logout successful!")
|
||||
}
|
||||
|
||||
const fetch_unauth = async (path, init) => {
|
||||
return await fetch(`${state["server"]}${path}`, init)
|
||||
}
|
||||
|
||||
const fetch_auth = async (path, init) => {
|
||||
if(typeof init["headers"] != "object") {
|
||||
init["headers"] = {}
|
||||
}
|
||||
if(state) {
|
||||
init["Authorization"] = `Bearer ${state["token"]}`
|
||||
}
|
||||
return await fetch_unauth(path, init)
|
||||
}
|
||||
|
||||
return {state, login, logout, fetch_unauth, fetch_auth}
|
||||
}
|
|
@ -1,55 +1,12 @@
|
|||
import { useState } from "react"
|
||||
import useLocalStorageState from "./useLocalStorageState"
|
||||
|
||||
|
||||
/**
|
||||
* Hook with the same API as {@link React.useState} which stores the user's current theme setting, and syncs it to the
|
||||
* browser's {@link localStorage}.
|
||||
*
|
||||
* @todo Perhaps this could be refactored into a general "useLocalStorageState" component?
|
||||
* @returns {[string, function]}
|
||||
*/
|
||||
export default function useSavedTheme() {
|
||||
const loadTheme = () => {
|
||||
if(localStorage) {
|
||||
console.debug(`Loading theme from localStorage...`)
|
||||
let value = localStorage.getItem("theme")
|
||||
|
||||
if(value) {
|
||||
console.debug(`Loaded theme ${value}!`)
|
||||
return value
|
||||
}
|
||||
else {
|
||||
console.debug(`There is no theme stored in the localStorage; setting to "ThemeDark"...`)
|
||||
return "ThemeDark"
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.warn(`Can't load theme; localStorage doesn't seem to be available; setting to "ThemeDark"...`)
|
||||
return "ThemeDark"
|
||||
}
|
||||
}
|
||||
|
||||
const [theme, _setTheme] = useState(loadTheme());
|
||||
|
||||
const setTheme = (value) => {
|
||||
console.debug(`Changing theme to ${value}...`)
|
||||
_setTheme(value)
|
||||
}
|
||||
|
||||
const saveTheme = (value) => {
|
||||
if(localStorage) {
|
||||
console.debug(`Saving theme ${value} to localStorage...`)
|
||||
localStorage.setItem("theme", value)
|
||||
}
|
||||
else {
|
||||
console.warn(`Can't save theme; localStorage doesn't seem to be available...`)
|
||||
}
|
||||
}
|
||||
|
||||
const setAndSaveTheme = (value) => {
|
||||
setTheme(value)
|
||||
saveTheme(value)
|
||||
}
|
||||
|
||||
return [theme, setAndSaveTheme]
|
||||
}
|
||||
return useLocalStorageState("theme", "ThemeDark")
|
||||
}
|
||||
|
|
|
@ -60,8 +60,8 @@ h1, h2, h3, h4, h5, h6 {
|
|||
--bg-dark: #92B5D5;
|
||||
--bg-accent: #D1DEE8;
|
||||
|
||||
--bg-button-on: #A3BDA2;
|
||||
--bg-button-off: #FFFFFF; /* TODO: Change this, it's too light! */
|
||||
--bg-button-on: #536841;
|
||||
--bg-button-off: #FFFFFF;
|
||||
--fg-button-on: #FFFFFF;
|
||||
--fg-button-off: #536841;
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ export default function PageAlerts({ children, className, ...props }) {
|
|||
return (
|
||||
<div className={classNames(Style.PageAlerts, className)} {...props}>
|
||||
<BoxFull header={"Your alerts"} className={Style.YourAlerts}>
|
||||
a
|
||||
🚧 Not implemented.
|
||||
</BoxFull>
|
||||
<BoxFull header={"Create new alert"} className={Style.CreateAlert}>
|
||||
b
|
||||
🚧 Not implemented.
|
||||
</BoxFull>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react"
|
||||
import Style from "./PageHome.module.css"
|
||||
import Style from "./PageDashboard.module.css"
|
||||
import classNames from "classnames"
|
||||
import BoxHeader from "../components/BoxHeader"
|
||||
import BoxFull from "../components/BoxFull"
|
||||
|
@ -12,7 +12,7 @@ import LabelledForm from "../components/LabelledForm"
|
|||
import Label from "../components/Label"
|
||||
|
||||
|
||||
export default function PageHome({ children, className, ...props }) {
|
||||
export default function PageDashboard({ children, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.PageHome, className)} {...props}>
|
||||
<BoxHeader className={Style.Header}>
|
||||
|
@ -21,24 +21,24 @@ export default function PageHome({ children, className, ...props }) {
|
|||
<BoxFull className={Style.SearchByZone} header={
|
||||
<span><Checkbox/> Search by zone</span>
|
||||
}>
|
||||
|
||||
🚧 Not implemented.
|
||||
</BoxFull>
|
||||
<BoxFull className={Style.SearchByHashtags} header={
|
||||
<span><Checkbox/> Search by hashtag</span>
|
||||
}>
|
||||
|
||||
🚧 Not implemented.
|
||||
</BoxFull>
|
||||
<BoxFull className={Style.SearchByTimePeriod} header={
|
||||
<span><Checkbox/> Search by time period</span>
|
||||
}>
|
||||
|
||||
🚧 Not implemented.
|
||||
</BoxFull>
|
||||
<BoxFull className={Style.CreateDialog} header={"Create repository"}>
|
||||
<LabelledForm>
|
||||
<Label for_={"repo-name"} text={"Repository name"}>
|
||||
<Label htmlFor={"repo-name"} text={"Repository name"}>
|
||||
<InputWithIcon id={"repo-name"} icon={faFolder}/>
|
||||
</Label>
|
||||
<Label for={"filter-mode"} text={"Add tweets if they satisfy"}>
|
||||
<Label htmlFor={"filter-mode"} text={"Add tweets if they satisfy"}>
|
||||
<label>
|
||||
<Radio name={"filter-mode"} value={"or"}/> At least one filter
|
||||
</label>
|
||||
|
@ -47,7 +47,7 @@ export default function PageHome({ children, className, ...props }) {
|
|||
<Radio name={"filter-mode"} value={"and"}/> Every filter
|
||||
</label>
|
||||
</Label>
|
||||
<Button style={{"grid-column": "1 / 3"}} icon={faPlus} color={"Green"}>
|
||||
<Button style={{"gridColumn": "1 / 3"}} icon={faPlus} color={"Green"}>
|
||||
Create repository
|
||||
</Button>
|
||||
</LabelledForm>
|
71
code/frontend/src/routes/PageLogin.js
Normal file
71
code/frontend/src/routes/PageLogin.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
import React, { useContext, useState } from "react"
|
||||
import Style from "./PageLogin.module.css"
|
||||
import classNames from "classnames"
|
||||
import BoxFull from "../components/BoxFull"
|
||||
import LabelledForm from "../components/LabelledForm"
|
||||
import Label from "../components/Label"
|
||||
import InputWithIcon from "../components/InputWithIcon"
|
||||
import { faArrowRight, faEnvelope, faGlobe, faKey } from "@fortawesome/free-solid-svg-icons"
|
||||
import Button from "../components/Button"
|
||||
import ContextLogin from "../contexts/ContextLogin"
|
||||
import { useHistory } from "react-router"
|
||||
|
||||
|
||||
export default function PageLogin({ className, ...props }) {
|
||||
// TODO: Change these presets before production
|
||||
const [server, setServer] = useState("http://localhost:5000")
|
||||
const [email, setEmail] = useState("admin@admin.com")
|
||||
const [password, setPassword] = useState("password")
|
||||
const {login} = useContext(ContextLogin)
|
||||
const history = useHistory()
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.PageLogin, className)} {...props}>
|
||||
<BoxFull header={"Login"}>
|
||||
<LabelledForm>
|
||||
<Label text={"Server"} htmlFor={"login-server"}>
|
||||
<InputWithIcon
|
||||
id={"login-server"}
|
||||
name={"login-server"}
|
||||
value={server}
|
||||
onChange={e => setServer(e.target.value)}
|
||||
type={"url"}
|
||||
icon={faGlobe}
|
||||
/>
|
||||
</Label>
|
||||
<Label text={"Email"} htmlFor={"login-email"}>
|
||||
<InputWithIcon
|
||||
id={"login-email"}
|
||||
name={"login-email"}
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
type={"email"}
|
||||
icon={faEnvelope}
|
||||
/>
|
||||
</Label>
|
||||
<Label text={"Password"} htmlFor={"login-password"}>
|
||||
<InputWithIcon
|
||||
id={"login-password"}
|
||||
name={"login-password"}
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
type={"password"}
|
||||
icon={faKey}
|
||||
/>
|
||||
</Label>
|
||||
<Button
|
||||
style={{"gridColumn": "1 / 3"}}
|
||||
onClick={async e => {
|
||||
await login(server, email, password)
|
||||
history.push("/dashboard")
|
||||
}}
|
||||
icon={faArrowRight}
|
||||
color={"Green"}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</LabelledForm>
|
||||
</BoxFull>
|
||||
</div>
|
||||
)
|
||||
}
|
9
code/frontend/src/routes/PageLogin.module.css
Normal file
9
code/frontend/src/routes/PageLogin.module.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
.PageLogin {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -8,10 +8,10 @@ export default function PageRepositories({ children, className, ...props }) {
|
|||
return (
|
||||
<div className={classNames(Style.PageRepositories, className)} {...props}>
|
||||
<BoxFull header={"Your active repositories"} className={Style.ActiveRepositories}>
|
||||
a
|
||||
🚧 Not implemented.
|
||||
</BoxFull>
|
||||
<BoxFull header={"Your archived repositories"} className={Style.ArchivedRepositories}>
|
||||
b
|
||||
🚧 Not implemented.
|
||||
</BoxFull>
|
||||
</div>
|
||||
)
|
||||
|
|
14
code/frontend/src/routes/PageRoot.js
Normal file
14
code/frontend/src/routes/PageRoot.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import React, { useContext } from "react"
|
||||
import ContextLogin from "../contexts/ContextLogin"
|
||||
import { Redirect } from "react-router"
|
||||
|
||||
|
||||
export default function PageRoot() {
|
||||
const {state} = useContext(ContextLogin)
|
||||
|
||||
if(!state) {
|
||||
return <Redirect to={"/login"}/>
|
||||
}
|
||||
|
||||
return <Redirect to={"/dashboard"}/>
|
||||
}
|
|
@ -1,28 +1,45 @@
|
|||
import React from "react"
|
||||
import React, { useContext } from "react"
|
||||
import Style from "./PageSettings.module.css"
|
||||
import classNames from "classnames"
|
||||
import BoxHeader from "../components/BoxHeader"
|
||||
import BoxFull from "../components/BoxFull"
|
||||
import SelectTheme from "../components/SelectTheme"
|
||||
import ContextLogin from "../contexts/ContextLogin"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import { faSignOutAlt, faUser } from "@fortawesome/free-solid-svg-icons"
|
||||
import Button from "../components/Button"
|
||||
import LoggedInUser from "../components/LoggedInUser"
|
||||
import { useHistory } from "react-router"
|
||||
|
||||
|
||||
export default function PageSettings({ children, className, ...props }) {
|
||||
const {logout} = useContext(ContextLogin)
|
||||
const history = useHistory()
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.PageSettings, className)} {...props}>
|
||||
<BoxHeader>
|
||||
You are currently logged in as:
|
||||
</BoxHeader>
|
||||
<BoxFull header={"Logged in"}>
|
||||
<div>
|
||||
You are currently logged in as <LoggedInUser/>.
|
||||
</div>
|
||||
<div>
|
||||
<Button color={"Red"} onClick={e => {
|
||||
logout()
|
||||
history.push("/login")
|
||||
}} icon={faSignOutAlt}>Logout</Button>
|
||||
</div>
|
||||
</BoxFull>
|
||||
<BoxHeader>
|
||||
Switch theme: <SelectTheme/>
|
||||
</BoxHeader>
|
||||
<BoxFull header={"Change your email address"}>
|
||||
|
||||
</BoxFull>
|
||||
<BoxFull header={"Alert settings"}>
|
||||
|
||||
🚧 Not implemented.
|
||||
</BoxFull>
|
||||
<BoxFull header={"Change your email address"}>
|
||||
🚧 Not implemented.
|
||||
</BoxFull>
|
||||
<BoxFull header={"Change your password"}>
|
||||
|
||||
🚧 Not implemented.
|
||||
</BoxFull>
|
||||
</div>
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue