Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
BesOlivierBouchard committed Mar 27, 2024
2 parents 753e8e4 + 3ffc427 commit caa5f07
Show file tree
Hide file tree
Showing 12 changed files with 157 additions and 73 deletions.
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',
},
}
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"
}
}
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 @@ const Login = () => {
}
}, [isAuthenticated, navigate])

const onLoginClick = () => {
const onLoginClick = async () => {
if (!userName) {
setUserNameInError(true)
}
Expand All @@ -34,24 +33,25 @@ const Login = () => {
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 @@ const Login = () => {
<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 @@ const Login = () => {
<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
26 changes: 13 additions & 13 deletions canopeum_frontend/src/pages/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ import EducationalFacilityPin from '@assets/icons/pins/educational-facility-pin.
import FarmsLandPin from '@assets/icons/pins/farms-land-pin.svg'
import IndegeniousCommunityPin from '@assets/icons/pins/indegenious-community-pin.svg'
import ParkPin from '@assets/icons/pins/park-pin.svg'
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import ReactMap, { GeolocateControl, Marker, NavigationControl, ScaleControl, type ViewState } from 'react-map-gl/maplibre'
import { Link } from 'react-router-dom'

import type { SiteMap } from '../services/api'
import api from '../services/apiInterface'

const pinMap = {
'1': CanopeumPin,
'2': ParkPin,
'3': IndegeniousCommunityPin,
'4': EducationalFacilityPin,
'5': FarmsLandPin,
'6': CorporateLotPin,
const pinMap: Record<number, string> = {
1: CanopeumPin,
2: ParkPin,
3: IndegeniousCommunityPin,
4: EducationalFacilityPin,
5: FarmsLandPin,
6: CorporateLotPin,
}

type MarkerEvent = {
Expand All @@ -44,11 +44,11 @@ const Map = () => {
setSites(response)
}

const getBrowserLocation = () =>
const getBrowserLocation = useCallback(() =>
navigator.geolocation.getCurrentPosition(position => {
const { latitude, longitude } = position.coords
setMapViewState({ ...mapViewState, latitude, longitude })
})
}), [mapViewState])

const onMarkerClick = (event: MarkerEvent, site: SiteMap) => {
const { lat, lng } = event.target._lngLat
Expand All @@ -69,7 +69,7 @@ const Map = () => {
useEffect(() => {
void fetchData()
getBrowserLocation()
}, [])
}, [getBrowserLocation])

return (
<div className='container-fluid p-0'>
Expand Down Expand Up @@ -118,8 +118,8 @@ const Map = () => {
<Marker
anchor='bottom'
key={`${site.id}-${site.coordinates.latitude}-${site.coordinates.longitude}`}
latitude={site.coordinates.latitude}
longitude={site.coordinates.longitude}
latitude={Number(site.coordinates.latitude)}
longitude={Number(site.coordinates.longitude)}
onClick={event => onMarkerClick(event, site)}
style={{ cursor: 'pointer' }}
>
Expand Down
11 changes: 9 additions & 2 deletions canopeum_frontend/src/pages/MapSite.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { AuthenticationContext } from '@components/context/AuthenticationContext'
import SiteSummaryCard from '@components/site/SiteSummaryCard'
import type { SiteSocial } from '@services/api'
import api from '@services/apiInterface'
import { ensureError } from '@services/errors'
import { useEffect, useState } from 'react'
import { useContext, useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'

const MapSite = () => {
const { siteId } = useParams()
const { currentUser } = useContext(AuthenticationContext)
const [isLoadingSite, setIsLoadingSite] = useState(true)
const [error, setError] = useState<Error | undefined>(undefined)
const [site, setSite] = useState<SiteSocial>()
const viewMode = currentUser
? currentUser.role === 'RegularUser'
? 'user'
: 'admin'
: 'visitor'

const fetchSiteData = async (parsedSiteId: number) => {
setIsLoadingSite(true)
Expand Down Expand Up @@ -41,7 +48,7 @@ const MapSite = () => {
<p>{error.message}</p>
</div>
)
: (site && <SiteSummaryCard site={site} />)}
: (site && <SiteSummaryCard site={site} viewMode={viewMode} />)}

<div className='container px-0'>
<div className='row'>
Expand Down
Loading

0 comments on commit caa5f07

Please sign in to comment.