Skip to content

Commit

Permalink
design, cleanup, domain config
Browse files Browse the repository at this point in the history
  • Loading branch information
Niklas Kerkhoff committed Nov 25, 2024
1 parent 5fd0dfa commit c5fc384
Show file tree
Hide file tree
Showing 63 changed files with 611 additions and 670 deletions.
10 changes: 5 additions & 5 deletions tutor-assistant-web/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { BrowserRouter } from 'react-router-dom'
import { JoyTheme } from './JoyTheme'
import { configureI18n } from './config/i18n-config.ts'
import { Main } from './MainStyle.tsx'
import { HStack, MainContent } from '../lib/components/flex-layout.tsx'
import { HStack, MainContent } from '../common/components/containers/flex-layout.tsx'
import { CalendarBar } from '../modules/calendar/components/CalendarBar.tsx'

configureI18n(texts)


const App = () => {
/**
* Configures routing and theme; renders CalendarBar for global access and Routing
*/
export function App() {
return (
<JoyTheme>
<BrowserRouter>
Expand All @@ -30,5 +32,3 @@ const App = () => {
</JoyTheme>
)
}

export default App
6 changes: 5 additions & 1 deletion tutor-assistant-web/src/app/JoyTheme.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import * as React from 'react'
import { CssVarsProvider } from '@mui/joy/styles'
import { ChildrenProps } from '../lib/types.ts'
import { ChildrenProps } from '../common/types.ts'

/**
* Provides the base theme for the app
* @param children to be rendered
*/
export function JoyTheme({ children }: ChildrenProps) {
return (
<CssVarsProvider defaultMode='system' disableNestedContext>
Expand Down
3 changes: 3 additions & 0 deletions tutor-assistant-web/src/app/MainStyle.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Box, styled } from '@mui/joy'

/**
* Applies global styling according to the theme
*/
export const Main = styled(Box)`
width: 100%;
height: 100%;
Expand Down
19 changes: 17 additions & 2 deletions tutor-assistant-web/src/app/auth/Auth.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createContext } from 'react'
import axios, { AxiosInstance } from 'axios'
import { chill, isNotPresent } from '../../lib/utils/utils.ts'
import { ChildrenProps } from '../../lib/types.ts'
import { chill, isNotPresent } from '../../common/utils/utils.ts'
import { ChildrenProps } from '../../common/types.ts'
import { useKeycloak } from '@react-keycloak/web'

type AuthContextType = {
Expand All @@ -12,6 +12,16 @@ type AuthContextType = {
getRoles: () => string[]
}

/**
* Use only through useAuth
*
* Provides:
* getAuthHttp: returns a REST client with authorization headers set if the user is logged in
* isLoggedIn: if the user is logged in.
* openLogin: opens the login page of the identity provider.
* logout: logouts the user
* getRoles: returns the users roles
*/
export const AuthContext = createContext<AuthContextType>({
getAuthHttp: () => axios,
isLoggedIn: () => false,
Expand All @@ -21,6 +31,11 @@ export const AuthContext = createContext<AuthContextType>({
})


/**
* Applies AuthContext
*
* @param children wrapped by this context provider
*/
export function Auth({ children }: ChildrenProps) {

const { keycloak, initialized } = useKeycloak()
Expand Down
12 changes: 9 additions & 3 deletions tutor-assistant-web/src/app/auth/Authenticated.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { ChildrenProps } from '../../lib/types.ts'
import { ChildrenProps } from '../../common/types.ts'
import { useAuth } from './useAuth.ts'
import { isPresent } from '../../lib/utils/utils.ts'
import { haveCommonElements } from '../../lib/utils/array-utils.ts'
import { isPresent } from '../../common/utils/utils.ts'
import { haveCommonElements } from '../../common/utils/array-utils.ts'

interface Props extends ChildrenProps {
roles?: string[]
}

/**
* Protects a component from rendering based on the users authentication and authorization
*
* @param children rendered iff the user is logged in and has the required roles
* @param roles the user must have in order to render the children
*/
export function Authenticated({ children, roles }: Props) {
const { isLoggedIn, openLogin, getRoles } = useAuth()

Expand Down
5 changes: 4 additions & 1 deletion tutor-assistant-web/src/app/auth/useAuth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useContext } from 'react'
import { AuthContext } from './Auth.tsx'

/**
* Provides common authentication functionality and common functionality that requires authentication
*/
export function useAuth() {
return useContext(AuthContext)
return useContext(AuthContext)
}
11 changes: 10 additions & 1 deletion tutor-assistant-web/src/app/base.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { getCurrentBaseUrl } from '../lib/utils/utils.ts'
import { getCurrentBaseUrl } from '../common/utils/utils.ts'

const envApiBaseUrl = import.meta.env.VITE_API_BASE_URL as string
const envKeycloakBaseUrl = import.meta.env.VITE_KEYCLOAK_BASE_URL as string

const currentBaseUrl = getCurrentBaseUrl()

/**
* Backend api url the web frontend is supposed to communicate with
* Must be configured through env. 'default' uses the same protocol and host as this web frontend
*/
export const apiBaseUrl = envApiBaseUrl !== 'default' ? envApiBaseUrl : `${currentBaseUrl}/api`

/**
* Keycloak url for authentication and authorization
* Must be configured through env. 'default' uses the same protocol and host as this web frontend
*/
export const keycloakBaseUrl = envKeycloakBaseUrl !== 'default' ? envKeycloakBaseUrl : `${currentBaseUrl}/auth`
57 changes: 35 additions & 22 deletions tutor-assistant-web/src/app/config/i18n-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,44 @@ import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'

/**
* Applies internationalization
*
* @param texts to be translated, requires the format:
* {
* textKey1: {
* "en": "value",
* "de": "value",
* ...
* },
* ...
* }
*/
export function configureI18n(texts: any) {

function restructureTranslations(texts: any) {
const result = {} as any
function restructureTranslations(texts: any) {
const result = {} as any

Object.keys(texts).forEach(key => {
Object.keys(texts[key]).forEach(lang => {
if (!result[lang]) {
result[lang] = { translation: {} }
}
result[lang].translation[key] = texts[key][lang]
})
})
Object.keys(texts).forEach(key => {
Object.keys(texts[key]).forEach(lang => {
if (!result[lang]) {
result[lang] = { translation: {} }
}
result[lang].translation[key] = texts[key][lang]
})
})

return result
}
return result
}

i18n
.use(initReactI18next)
.use(LanguageDetector)
.init({
resources: restructureTranslations(texts),
fallbackLng: 'en',
interpolation: {
escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
},
})
i18n
.use(initReactI18next)
.use(LanguageDetector)
.init({
resources: restructureTranslations(texts),
fallbackLng: 'en',
interpolation: {
escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
},
})
}
3 changes: 3 additions & 0 deletions tutor-assistant-web/src/app/config/keycloak-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Keycloak from 'keycloak-js'
import { keycloakBaseUrl } from '../base.ts'

/**
* Returns configuration for accessing keycloak
*/
export const keycloak = new Keycloak({
url: keycloakBaseUrl,
realm: 'tutor-assistant',
Expand Down
19 changes: 5 additions & 14 deletions tutor-assistant-web/src/app/routing/Routing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { Navigate, Route, Routes } from 'react-router-dom'
import { ChatPage } from '../../modules/chat/ChatPage.tsx'
import { Authenticated } from '../auth/Authenticated.tsx'
import { DocumentsPage } from '../../modules/documents/DocumentsPage.tsx'
import { HelperPage } from '../../modules/helper/HelperPage.tsx'
import { ChatProvider } from '../../modules/chat/ChatProvider.tsx'

interface Props {

}

export function Routing({}: Props) {
/**
* Configures routing
* Sub routes might be configured in child components
*/
export function Routing() {

return (
<Routes>
Expand All @@ -34,14 +33,6 @@ export function Routing({}: Props) {
}
/>

<Route
path='/helper/*' element={
<Authenticated roles={['document-manager']}>
<HelperPage />
</Authenticated>
}
/>

</Routes>
)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { styled } from '@mui/joy'
import { VStack } from '../../lib/components/flex-layout.tsx'
import { VStack } from './flex-layout.tsx'

const barWidth = '380px'

/**
* Sidebar with surface background based on a VStack
*
* Supports className 'right' to change styling when used as a bar on the right
*/
export const Bar = styled(VStack)`
min-width: ${barWidth};
width: ${barWidth};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { byNumberReverse, isNotPresent, stringToNumber } from '../utils/utils.ts'
import { last } from '../utils/array-utils.ts'
import { byNumberReverse, isNotPresent, stringToNumber } from '../../utils/utils.ts'
import { last } from '../../utils/array-utils.ts'
import { HStack, MainContent, VStack } from './flex-layout.tsx'
import { Scroller } from './Scroller.tsx'

Expand All @@ -14,6 +14,17 @@ interface Props<T> {
spacing?: number
}

/**
* Renders items in a column layout
*
* @param columnCounts defines the number of columns absolutely or dependent of the window width (in px):
* Examples: 3, {0: 1, 400: 2, 800: 3}
* @param fill either vertical-wise or horizontal-wise
* @param values to be rendered
* @param render applied on each value
* @param spacing between the rendered items. Defaults to 0
* @constructor
*/
export function ColumnLayout<T>({ columnCounts, fill, values, render, spacing }: Props<T>) {

if (isNotPresent(spacing)) spacing = 0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { Row, Spacer } from '../../lib/components/flex-layout.tsx'
import { Row, Spacer } from './flex-layout.tsx'
import { Divider, Typography } from '@mui/joy'
import React, { ReactNode } from 'react'
import { isPresent } from '../../lib/utils/utils.ts'
import { isPresent } from '../../utils/utils.ts'

interface Props {
title: ReactNode | string
leftNode?: ReactNode
rightNode?: ReactNode
}


/**
* Horizontal bar to render a title with additional items at the top
*
* @param title rendered in the middle of the bar
* @param leftNode rendered at the left border
* @param rightNode rendered at the right border
*/
export function Header({ title, leftNode, rightNode }: Props) {
return (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { Box } from '@mui/joy'
import { ReactNode, useEffect, useRef } from 'react'
import { isNotPresent } from '../utils/utils.ts'
import { empty } from '../utils/array-utils.ts'
import { isNotPresent } from '../../utils/utils.ts'
import { empty } from '../../utils/array-utils.ts'

interface Props {
children: ReactNode
padding?: number
scrollToBottomOnChange?: unknown[]
}

/**
* Renders items in a vertically scrollable container
*
* @param children to be rendered inside this wrapper
* @param padding between this wrapper and its content
* @param scrollToBottomOnChange dependencies on whose change to scroll down to the bottom
* @constructor
*/
export function Scroller({ children, padding, scrollToBottomOnChange }: Props) {
if (isNotPresent(scrollToBottomOnChange)) scrollToBottomOnChange = []

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Box, Stack, styled } from '@mui/joy'

/**
* Full-width, full-height vertical stack with flex layout
*/
export const VStack = styled(Stack)`
margin: 0 !important;
flex-direction: column;
Expand All @@ -8,6 +11,9 @@ export const VStack = styled(Stack)`
overflow-y: hidden;
`

/**
* Full-width, full-height horizontal stack with flex layout
*/
export const HStack = styled(Stack)`
margin: 0 !important;
flex-direction: row;
Expand All @@ -23,19 +29,28 @@ export const Column = styled(Stack)`
overflow-y: hidden;
`

/**
* Full-width, min-height horizontal stack with flex layout
*/
export const Row = styled(Stack)`
margin: 0 !important;
flex-direction: row;
width: 100%;
`

/**
* Extending content inside a flex container
*/
export const MainContent = styled(Box)`
width: 100%;
height: 100%;
flex: 1;
overflow: hidden;
`

/**
* Creating empty space between items in a flex container in order to create maximum space between items
*/
export const Spacer = styled('span')`
flex: 1;
`
Loading

0 comments on commit c5fc384

Please sign in to comment.