From 72f102a03446f0f7ac2aae637faceed0bb69c750 Mon Sep 17 00:00:00 2001 From: leonardo <leomilho@gmail.com> Date: Tue, 25 Apr 2023 11:48:47 +0200 Subject: [PATCH] feat(navbar): partial navbar porting --- .../add-database-form.module.scss | 75 +++++++ .../navbar/AddDatabaseForm/index.tsx | 205 ++++++++++++++++++ libs/shared/lib/data-access/api/schema.ts | 7 + .../lib/data-access/store/sessionSlice.ts | 35 +++ 4 files changed, 322 insertions(+) create mode 100644 apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss create mode 100644 apps/web/src/components/navbar/AddDatabaseForm/index.tsx create mode 100644 libs/shared/lib/data-access/api/schema.ts create mode 100644 libs/shared/lib/data-access/store/sessionSlice.ts diff --git a/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss b/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss new file mode 100644 index 000000000..97642ae37 --- /dev/null +++ b/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss @@ -0,0 +1,75 @@ +.wrapper { + height: 100vh; + display: flex; + align-items: center; + width: 100vw; + background-color: rgba(0, 0, 0, 0.87); + position: absolute; + z-index: 1225; +} + +.authWrapper { + font-family: Poppins, sans-serif; + display: flex; + flex-direction: column; + flex-wrap: wrap; + justify-content: center; + max-width: 400px; + margin: auto; + overflow: auto; + min-height: 300px; + background-color: #f7f8fc; + padding: 30px; + border-radius: 5px; + padding-bottom: 300px; +} + +.header { + text-align: center; +} + +.formWrapper { +} + +.loginContainer { + margin: 5px 0px !important; +} + +.loginContainerRow { + display: flex; + flex-direction: row; + margin: 5px 0px; +} + +.loginContainerButton { + margin-top: 10px !important; + + & button { + width: 100%; + } +} + +.hostLabel { + flex: 1 0 66.66667%; + + & div { + width: 95%; + } +} + +.portLabel { + flex: 1 0 33.33334%; +} + +.userLabel { + width: 100%; +} + +passLabel { + width: 100%; +} + +cancelButton { + margin-top: 2.5%; +} + diff --git a/apps/web/src/components/navbar/AddDatabaseForm/index.tsx b/apps/web/src/components/navbar/AddDatabaseForm/index.tsx new file mode 100644 index 000000000..54c1b0ac0 --- /dev/null +++ b/apps/web/src/components/navbar/AddDatabaseForm/index.tsx @@ -0,0 +1,205 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ +import React, { useState } from 'react'; +import { TextField, Button, NativeSelect } from '@mui/material'; +import styles from './add-database-form.module.scss'; +import { + AddDatabaseRequest, + DatabaseType, + databaseNameMapping, +} from '@graphpolaris/shared/lib/data-access'; + +/** AddDatabaseFormProps is an interface containing the AuthViewModel. */ +export interface AddDatabaseFormProps { + open: boolean; + onClose(): void; + onSubmit(data: AddDatabaseRequest): void; +} + +/** AddDatabaseFormState is an interface containing the databasehost information. */ +export interface AddDatabaseFormState extends AddDatabaseRequest { + // username: string; + // password: string; + // hostname: string; + // port: number; + // databaseName: string; + // internalDatabase: string; + // databaseType: string; + // styles: Record<string, string>; // TODO: check if needed +} + +/** AddDatabaseForm is the View implementation for the connect screen that will be rendered. */ +export default function AddDatabaseForm(props: AddDatabaseFormProps) { + const [state, setState] = useState<AddDatabaseFormState>({ + username: 'root', + password: 'DikkeDraak', + url: 'https://datastrophe.science.uu.nl/', + port: 8529, + name: 'Tweede Kamer Dataset', + internal_database_name: 'TweedeKamer', + // styles: props.styles, // FIXME + type: DatabaseType.ArangoDB, + }); + + /** + * Validates if the port value is numerical. Only then will the state be updated. + * @param port The new port value. + */ + function handlePortChanged(port: string): void { + if (!isNaN(Number(port))) setState({ ...state, port: Number(port) }); + } + + /** Handles the submit button click. Calls the onSubmit in the props with all the fields. */ + function handleSubmitClicked(): void { + props.onSubmit(state); + } + + return props.open ? ( + <div + className={styles.wrapper} + onMouseDown={() => { + props.onClose(); + }} + > + <div + className={styles.authWrapper} + onMouseDown={(e) => { + e.stopPropagation(); + }} + > + <h1 className={styles.header}>Database Connect</h1> + <form + className={styles.formWrapper} + onSubmit={(event: React.FormEvent) => { + event.preventDefault(); + handleSubmitClicked(); + }} + > + <div className={styles.loginContainer}> + <TextField + className={styles.passLabel} + label="Database name" + type="databaseName" + value={state.name} + onChange={(event) => + setState({ + ...state, + name: event.currentTarget.value, + }) + } + required + /> + </div> + <div className={styles.loginContainer}> + <NativeSelect + className={styles.passLabel} + value={databaseNameMapping[state.type]} + onChange={(event) => { + setState({ + ...state, + type: databaseNameMapping.indexOf(event.currentTarget.value), + }); + }} + > + {databaseNameMapping.map((dbName) => ( + <option value={dbName}>{dbName}</option> + ))} + </NativeSelect> + </div> + <div className={styles.loginContainerRow}> + <TextField + className={styles.hostLabel} + label="Hostname/IP" + type="hostname" + value={state.url} + onChange={(event) => + setState({ + ...state, + url: event.currentTarget.value, + }) + } + required + /> + <TextField + className={styles.portLabel} + label="Port" + type="port" + value={state.port} + onChange={(event) => handlePortChanged(event.currentTarget.value)} + required + /> + </div> + <div className={styles.loginContainer}> + <TextField + className={styles.userLabel} + label="Username" + type="username" + value={state.username} + onChange={(event) => + setState({ + ...state, + username: event.currentTarget.value, + }) + } + required + /> + </div> + <div className={styles.loginContainer}> + <TextField + className={styles.passLabel} + label="Password" + type="password" + value={state.password} + onChange={(event) => + setState({ + ...state, + password: event.currentTarget.value, + }) + } + required + /> + </div> + <div className={styles.loginContainer}> + <TextField + className={styles.passLabel} + label="Internal database" + type="internalDatabaseName" + value={state.internal_database_name} + onChange={(event) => + setState({ + ...state, + internal_database_name: event.currentTarget.value, + }) + } + required + /> + </div> + <div className={styles.loginContainerButton}> + <Button variant="outlined" type="submit"> + Submit + </Button> + <Button + className={styles.cancelButton} + variant="outlined" + onClick={() => { + props.onClose(); + }} + > + Cancel + </Button> + </div> + </form> + </div> + </div> + ) : ( + <></> + ); +} diff --git a/libs/shared/lib/data-access/api/schema.ts b/libs/shared/lib/data-access/api/schema.ts new file mode 100644 index 000000000..004a8b8e8 --- /dev/null +++ b/libs/shared/lib/data-access/api/schema.ts @@ -0,0 +1,7 @@ +// All database related API calls + +import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access/authorization'; + + +export function RequestSchema(databaseName: string) { +} \ No newline at end of file diff --git a/libs/shared/lib/data-access/store/sessionSlice.ts b/libs/shared/lib/data-access/store/sessionSlice.ts new file mode 100644 index 000000000..979f7f028 --- /dev/null +++ b/libs/shared/lib/data-access/store/sessionSlice.ts @@ -0,0 +1,35 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type { RootState } from './store'; + +// Define the initial state using that type +export const initialState: { + currentDatabase: string; + databases: string[]; +} = { + currentDatabase: '', + databases: [], +}; + +export const sessionSlice = createSlice({ + name: 'session', + // `createSlice` will infer the state type from the `initialState` argument + initialState, + reducers: { + updateCurrentDatabase(state, action: PayloadAction<string>) { + state.currentDatabase = action.payload; + }, + updateDatabaseList(state, action: PayloadAction<string[]>) { + state.databases = action.payload; + } + }, +}); + +export const { + updateCurrentDatabase, + updateDatabaseList +} = sessionSlice.actions; + +// Other code such as selectors can use the imported `RootState` type +export const sessionCacheState = (state: RootState) => state.sessionCache; + +export default sessionSlice.reducer; -- GitLab