Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/us 20 add card view mode and translations #73

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions canopeum_frontend/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,9 @@ module.exports = {
// Using Bootraps directly without a React wrapper will cause us to have to add classes to React Components
'react/forbid-component-props': 'off',
'@typescript-eslint/no-unsafe-member-access': 'warn',
// There is currently a bug with this rule causing the linter to crash with
// Until this is fixed or solved, we'll turn this one off to prevent blocking
// in PR with the exception
'etc/no-implicit-any-catch': 'off',
},
}
14 changes: 7 additions & 7 deletions canopeum_frontend/src/components/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ const MainLayout = () => {
<>
{location.pathname !== '/login' && <Navbar />}
<Routes>
<Route element={<Home />} path="/home" />
<Route element={<Home />} path="/" />
<Route element={<Analytics />} path="/analytics" />
<Route element={<Map />} path="/map" />
<Route element={<Home />} path='/home' />
<Route element={<Home />} path='/' />
<Route element={<Analytics />} path='/analytics' />
<Route element={<Map />} path='/map' />
<Route element={<MapSite />} path='/map/:siteId' />
<Route element={<UserManagement />} path="/user-management" />
<Route element={<Login />} path="/login" />
<Route element={<Utilities />} path="/utilities" />
<Route element={<UserManagement />} path='/user-management' />
<Route element={<Login />} path='/login' />
<Route element={<Utilities />} path='/utilities' />
</Routes>
</>
)
Expand Down
48 changes: 24 additions & 24 deletions canopeum_frontend/src/components/PrimaryIconBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
type Props = {
readonly type: 'account_circle'
| 'add_a_photo'
| 'add'
| 'cancel'
| 'donut_small'
| 'eco'
| 'edit_square'
| 'forest'
| 'home_work'
| 'home'
| 'location_on'
| 'mail'
| 'mood'
| 'perm_phone_msg'
| 'person'
| 'pin_drop'
| 'psychiatry'
| 'school'
| 'smart_display'
| 'sms'
| 'source_environment'
| 'workspaces'
readonly type:
| 'account_circle'
| 'add_a_photo'
| 'add'
| 'cancel'
| 'donut_small'
| 'eco'
| 'edit_square'
| 'forest'
| 'home_work'
| 'home'
| 'location_on'
| 'mail'
| 'mood'
| 'perm_phone_msg'
| 'person'
| 'pin_drop'
| 'psychiatry'
| 'school'
| 'smart_display'
| 'sms'
| 'source_environment'
| 'workspaces',
}

const PrimaryIconBadge = (props: Props) => (
<div className='text-bg-primary text-center rounded-circle'
style={{ height: '2em', width: '2em' }}>
<div className='text-bg-primary text-center rounded-circle' style={{ height: '2em', width: '2em' }}>
<span
className='material-symbols-outlined text-light align-middle'
style={{ fontSize: 24, marginTop: '0.15em' }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import type { FunctionComponent, ReactNode } from 'react'
import { createContext, memo, useCallback, useMemo, useState } from 'react'

export enum UserRole {
MegaAdmin = 'MegaAdmin',
MiniAdmin = 'MiniAdmin',
RegularUser = 'RegularUser',
}
export type UserRole = 'MegaAdmin' | 'MiniAdmin' | 'RegularUser'

type User = {
firstname: string,
Expand All @@ -30,7 +26,7 @@ export const AuthenticationContext = createContext<IAuthenticationContext>({
})

const AuthenticationContextProvider: FunctionComponent<{ readonly children?: ReactNode }> = memo(props => {
const [user, setUser] = useState<User | undefined>(undefined)
const [user, setUser] = useState<User>()

const authenticate = useCallback((newUser: User) => setUser(newUser), [setUser])

Expand Down
26 changes: 26 additions & 0 deletions canopeum_frontend/src/components/inputs/ToggleSwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type Props = {
readonly text: string,
readonly checked: boolean,
readonly onChange: (checked: boolean) => void,
readonly additionalClassNames?: string,
}

const ToggleSwitch = ({ text, checked, onChange, additionalClassNames }: Props) => (
<div className={`form-check form-switch ${additionalClassNames ?? ''}`}>
<input
checked={checked}
className={`form-check-input ${
checked
? 'text-bg-primary'
: ''
}`}
onChange={event =>
onChange(event.target.checked)}
role='switch'
type='checkbox'
/>
<label className='form-check-label'>{text}</label>
</div>
)

export default ToggleSwitch
83 changes: 58 additions & 25 deletions canopeum_frontend/src/components/site/SiteSummaryCard.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,73 @@
import headerLogo from '@assets/images/map/MARR4059.png'
import ToggleSwitch from '@components/inputs/ToggleSwitch'
import PrimaryIconBadge from '@components/PrimaryIconBadge'
import type { SiteSocial } from '@services/api'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'

type Props = {
readonly viewMode: 'admin' | 'user' | 'visitor',
readonly site: SiteSocial,
}

const SiteSummaryCard = ({ site }: Props) => (
<div className='card mb-3'>
<div className='row g-0'>
<div className='col-md-4'>
{/* TODO: replace img source when backend offers an image endpoint */}
<img alt='header logo' className='img-fluid' src={headerLogo} />
</div>
<div className='col-md-8'>
<div className='card-body'>
<h1 className='fw-bold card-title'>{site.name}</h1>
<div className='card-text d-flex flex-row gap-1'>
<PrimaryIconBadge type='school' />
<h4 className='fw-bold text-primary'>{site.type.en}</h4>
</div>
<p className='card-text'>{site.description ?? ''}</p>
<div className='container fw-bold'>
<div className='mb-2'>
<span className='material-symbols-outlined align-middle'>person</span>
<span>Sponsors:</span>
const onFollowClick = () => {
// TODO implement follow here when backend is available

Check warning on line 14 in canopeum_frontend/src/components/site/SiteSummaryCard.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected 'TODO' comment: 'TODO implement follow here when backend...'
}

const updateSiteIsPublic = async (_: boolean) => {
// TODO Implement site update when backend is ready

Check warning on line 18 in canopeum_frontend/src/components/site/SiteSummaryCard.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected 'TODO' comment: 'TODO Implement site update when backend...'
}
const SiteSummaryCard = ({ site, viewMode }: Props) => {
const { t } = useTranslation()
const [isPublic, setIsPublic] = useState(true)

useEffect(() => void updateSiteIsPublic(isPublic), [isPublic])

return (
<div className='card mb-3'>
<div className='row g-0'>
<div className='col-md-4'>
{/* TODO: replace img source when backend offers an image endpoint */}

Check warning on line 30 in canopeum_frontend/src/components/site/SiteSummaryCard.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected 'TODO' comment: 'TODO: replace img source when backend...'
<img alt='header logo' className='img-fluid' src={headerLogo} />
</div>
<div className='col-md-8'>
<div className='card-body'>
<div className='d-flex flex-row justify-content-between'>
<h1 className='fw-bold card-title'>{site.name}</h1>
{viewMode === 'user' && (
<button className='btn btn-secondary' onClick={onFollowClick} type='button'>
{t('mapSite.siteSummaryCard.follow')}
</button>
)}
{viewMode === 'admin' && (
<ToggleSwitch
additionalClassNames='fs-4'
checked={isPublic}
onChange={setIsPublic}
text={t('mapSite.siteSummaryCard.public')}
/>
)}
</div>
<div className='row'>
{site.sponsors.map(sponsorName => (
<div className='col-12 col-sm-6 col-md-4 col-lg-3 mb-3' key={sponsorName}>{sponsorName}</div>
))}
<div className='card-text d-flex flex-row gap-1'>
<PrimaryIconBadge type='school' />
<h4 className='fw-bold text-primary'>{site.type.en}</h4>

Check warning on line 53 in canopeum_frontend/src/components/site/SiteSummaryCard.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unsafe member access .en on an `any` value
</div>
<p className='card-text'>{site.description ?? ''}</p>
<div className='container fw-bold'>
<div className='mb-2'>
<span className='material-symbols-outlined align-middle'>person</span>
<span>{t('mapSite.siteSummaryCard.sponsors')}:</span>
</div>
<div className='row'>
{site.sponsors.map(sponsorName => (
<div className='col-12 col-sm-6 col-md-4 col-lg-3 mb-3' key={sponsorName}>{sponsorName}</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
)
}
export default SiteSummaryCard
2 changes: 2 additions & 0 deletions canopeum_frontend/src/locale/en/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import analyticsJSON from './analytics.json'
import homeJSON from './home.json'
import mapSiteJSON from './mapSite.json'

const enJSON = {
translation: {
home: { ...homeJSON },
analytics: { ...analyticsJSON },
mapSite: { ...mapSiteJSON },
},
}

Expand Down
7 changes: 7 additions & 0 deletions canopeum_frontend/src/locale/en/mapSite.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"siteSummaryCard": {
"follow": "Follow",
"sponsors": "Sponsors",
"public": "Public"
}
}
2 changes: 2 additions & 0 deletions canopeum_frontend/src/locale/fr/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import analyticsJSON from './analytics.json'
import homeJSON from './home.json'
import mapSiteJSON from './mapSite.json'

const frJSON = {
translation: {
home: { ...homeJSON },
analytics: { ...analyticsJSON },
mapSite: { ...mapSiteJSON },
},
}

Expand Down
7 changes: 7 additions & 0 deletions canopeum_frontend/src/locale/fr/mapSite.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"siteSummaryCard": {
"follow": "Suivre",
"sponsors": "Commanditaires",
"public": "Publique"
}
}
4 changes: 2 additions & 2 deletions canopeum_frontend/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const Home = () => {
const fetchData = async () => {
setIsLoading(true)
try {
const response = await api().analytics.batches();
setData(response);
const response = await api().analytics.batches()
setData(response)
} catch (error_: unknown) {
setError(ensureError(error_))
} finally {
Expand Down
44 changes: 22 additions & 22 deletions canopeum_frontend/src/pages/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { AuthenticationContext } from '@components/context/AuthenticationContext'
import { AuthUser } from '@services/api'
import api from '@services/apiInterface'
import { useContext, useEffect, useState } from 'react'

import { AuthenticationContext, UserRole } from '../components/context/AuthenticationContext'
import { AuthUser } from '../services/api'
import api from '../services/apiInterface'
import { useNavigate } from 'react-router-dom'

const isLoginEntryValide = (entry: string | undefined) => entry !== undefined && entry !== ''
const isLoginEntryValid = (entry: string | undefined) => entry !== undefined && entry !== ''

const Login = () => {
const [userName, setUserName] = useState('')
Expand All @@ -25,7 +24,7 @@
}
}, [isAuthenticated, navigate])

const onLoginClick = () => {
const onLoginClick = async () => {
if (!userName) {
setUserNameInError(true)
}
Expand All @@ -34,24 +33,25 @@
setPasswordInError(true)
}

if (isLoginEntryValide(userName) && isLoginEntryValide(password)) {
api().auth.login(
new AuthUser({
username: userName,
password,
id: 1,
}),
).then(response =>
if (isLoginEntryValid(userName) && isLoginEntryValid(password)) {
try {
const response = await api().auth.login(
new AuthUser({
username: userName,
password,
id: 1,
}),
)
authenticate({
email: response.email ?? '',
firstname: response.firstName ?? '',
image: '',
lastname: response.lastName ?? '',
role: UserRole.MegaAdmin, // TODO (Vincent) set the user role
role: 'MegaAdmin', // TODO (Vincent) set the user role

Check warning on line 50 in canopeum_frontend/src/pages/Login.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected 'TODO' comment: 'TODO (Vincent) set the user role'
})
).catch(_ => {
} catch {
setLoginError('Error while login')
})
}
}
}

Expand All @@ -68,12 +68,12 @@
<label htmlFor='exampleInputEmail1'>Email address</label>
<input
aria-describedby='emailHelp'
className={`form-control ${userNameInError && !isLoginEntryValide(userName) && 'is-invalid'} `}
className={`form-control ${userNameInError && !isLoginEntryValid(userName) && 'is-invalid'} `}
id='exampleInputEmail1'
onChange={event => setUserName(event.target.value)}
type='email'
/>
{userNameInError && !isLoginEntryValide(userName) && (
{userNameInError && !isLoginEntryValid(userName) && (
<span className='help-block text-danger'>
Please enter a email address
</span>
Expand All @@ -83,19 +83,19 @@
<div style={{ width: '100%', margin: '20px 0px 20px 0px' }}>
<label htmlFor='exampleInputPassword1'>Password</label>
<input
className={`form-control ${passwordInError && !isLoginEntryValide(password) && 'is-invalid'} `}
className={`form-control ${passwordInError && !isLoginEntryValid(password) && 'is-invalid'} `}
id='exampleInputPassword1'
onChange={event => setPassword(event.target.value)}
type='password'
/>
{passwordInError && !isLoginEntryValide(password) && (
{passwordInError && !isLoginEntryValid(password) && (
<span className='help-block text-danger'>Please enter a password</span>
)}
</div>
{loginError && <span className='help-block text-danger'>{loginError}</span>}
<button
className='btn btn-primary'
onClick={() => onLoginClick()}
onClick={onLoginClick}
style={{ margin: '40px 0px 10px' }}
type='submit'
>
Expand Down
Loading