Skip to content

Commit

Permalink
extended device requests v0.01
Browse files Browse the repository at this point in the history
  • Loading branch information
mluukkai committed Jan 26, 2024
1 parent 662c03e commit f09744d
Show file tree
Hide file tree
Showing 18 changed files with 441 additions and 33 deletions.
3 changes: 3 additions & 0 deletions client/components/AdminPage/AdminFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export default function AdminFilter({
}, {
key: 'currentYearEligible',
name: 'Current years eligible students',
}, {
key: 'extendedRequester',
name: 'Extended requester',
}, {
key: 'allStaff',
name: 'All staff (Admin/staff/distributor/reclaimer)',
Expand Down
11 changes: 8 additions & 3 deletions client/components/AdminPage/AllUsersTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,26 @@ export default () => {
switch (filter) {
case 'all':
filtered = users
hiddenColumns = ['ext_wants_device', 'ext_eligible']
break
case 'deviceHolders':
filtered = users.filter(u => !!u.deviceGivenAt && !u.deviceReturned)
hiddenColumns = ['admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'mark_eligible', 'device_returned_at', 'device_returned_by']
hiddenColumns = ['ext_wants_device', 'ext_eligible', 'admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'mark_eligible', 'device_returned_at', 'device_returned_by']
break
case 'returnedDevices':
filtered = users.filter(u => u.deviceReturned)
hiddenColumns = ['admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'mark_eligible']
hiddenColumns = ['ext_wants_device', 'ext_eligible', 'admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'mark_eligible']
break
case 'currentYearEligible':
filtered = users.filter(u => u.signupYear === settings.currentYear && u.eligible)
hiddenColumns = ['admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'mark_eligible', 'device_returned_at', 'device_returned_by']
hiddenColumns = ['ext_wants_device', 'ext_eligible', 'admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'mark_eligible', 'device_returned_at', 'device_returned_by']
break
case 'allStaff':
filtered = users.filter(u => u.admin || u.staff || u.distributor || u.reclaimer)
hiddenColumns = ['ext_wants_device', 'ext_eligible', 'student_number', 'studyPrograms', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'device_given_at', 'device_id', 'device_distributed_by', 'mark_eligible', 'mark_returned', 'device_returned_at', 'device_returned_by']
break
case 'extendedRequester':
filtered = users.filter(u => u.extendedEligible || u.extendedWantsDevice)
hiddenColumns = ['student_number', 'studyPrograms', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'device_given_at', 'device_id', 'device_distributed_by', 'mark_eligible', 'mark_returned', 'device_returned_at', 'device_returned_by']
break
default:
Expand Down
14 changes: 14 additions & 0 deletions client/components/AdminPage/UserTable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ const UserTable = ({
getCellVal: ({ eligible }) => eligible,
width: 80,
},
{
key: 'ext_eligible',
label: 'ExtEligible',
renderCell: ({ extendedEligible }) => boolToString(extendedEligible),
getCellVal: ({ extendedEligible }) => extendedEligible,
width: 80,
},
{
key: 'digitaidot',
label: 'Digi skills',
Expand All @@ -134,6 +141,13 @@ const UserTable = ({
getCellVal: ({ wantsDevice }) => !!wantsDevice,
width: 130,
},
{
key: 'ext_wants_device',
label: 'ExtWants',
renderCell: ({ extendedWantsDevice }) => boolToString(extendedWantsDevice),
getCellVal: ({ extendedWantsDevice }) => extendedWantsDevice,
width: 80,
},
{
key: 'device_given_at',
label: 'Device given at',
Expand Down
95 changes: 95 additions & 0 deletions client/components/StudentPage/ExtendedRequestDeviceForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import {
Button, Segment, Form, Checkbox,
} from 'semantic-ui-react'
import { deviceRequestAction } from 'Utilities/redux/deviceRequestReducer'
import { localeSelector } from 'Utilities/redux/localeReducer'
import InstructionModal from './InstructionModal'

const translations = {
iWantDevice: {
en: 'I want a device',
fi: 'Haluan laitteen',
},
hello: {
en: 'Hello',
fi: 'Hei',
},
youAreEntitledToADevice: {
en: '...',
fi: 'Olet oikeutettu normaalin jakelun ulkopuolella annettavaan fuksilaitteeseen',
},
email: {
en: 'Email',
fi: 'Sähköposti',
},
termsAndConditions: {
en: 'Read the instructions',
fi: 'Lue ohjeet',
},
areYouSure: {
en: 'Are you sure?',
fi: 'Oletko varma?',
},
iHaveRead: {
en: 'I have read and understood the instructions',
fi: 'Olen lukenut ja ymmärtänyt ohjeet',
},
}

const ExtendedRequestDeviceForm = () => {
const [termsOpen, setTermsOpen] = useState(false)
const [termsHaveBeenOpened, setTermsHaveBeenOpened] = useState(false)
const [termsAccepted, setTermsAccepted] = useState(false)
const dispatch = useDispatch()
const user = useSelector(state => state.user.data)
const locale = useSelector(localeSelector)

const handleRequestClick = () => {
dispatch(deviceRequestAction({ extended: true, email: null }))
}

const handleTermsClose = () => setTermsOpen(false)

const primaryButtonDisabled = !termsAccepted // Always disable if not valid

const handleTermsOpen = () => {
setTermsOpen(true)
setTermsHaveBeenOpened(true)
}

return (
<div>
<InstructionModal open={termsOpen} handleClose={handleTermsClose} />
<Segment>
<p>{`${translations.hello[locale]} ${user.name},`}</p>
<p>{translations.youAreEntitledToADevice[locale]}</p>
<Form>
<div style={{ display: 'flex', flexDirection: 'column', padding: '1em 0em' }}>
<span
role="button"
tabIndex={0}
style={{
width: '100%', marginBottom: '1em', cursor: 'pointer', color: 'blue',
}}
onKeyPress={handleTermsOpen}
onClick={handleTermsOpen}
data-cy="terms"
>
{translations.termsAndConditions[locale]}
</span>
<Checkbox data-cy="acceptTerms" checked={termsAccepted} disabled={!termsHaveBeenOpened} onClick={() => setTermsAccepted(!termsAccepted)} label={translations.iHaveRead[locale]} />
</div>
<div style={{ display: 'flex' }}>
<Button style={{ flex: 1, paddingTop: '2em', paddingBottom: '2em' }} color="purple" onClick={handleRequestClick} disabled={primaryButtonDisabled} data-cy="getDevicePrimary">
{translations.iWantDevice[locale]}
</Button>
</div>
</Form>
</Segment>
</div>
)
}

export default ExtendedRequestDeviceForm
78 changes: 78 additions & 0 deletions client/components/StudentPage/ExtendedTaskStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react'
import { useSelector } from 'react-redux'
import { Segment, Icon, Header } from 'semantic-ui-react'
import { localeSelector } from 'Utilities/redux/localeReducer'
import TranslatedMarkdown from 'Components/TranslatedMarkdown'
import StudentInfo from './StudentInfo'
// import TaskInfo from './TaskInfo'

const translations = {
taskStatus: {
en: 'Task status:',
fi: 'Tehtävien tila:',
},
beFuksi: {
en: 'Be a fresher',
fi: 'Ole fuksi',
},
registeredToRelevant: {
en: 'Registered to relevant course',
fi: 'Rekisteröitynyt relevantille kurssille',
},
digiSkillsCompleted: {
en: 'DIGI-A completed',
fi: 'DIGI-A suoritettu',
},
tasksFinished: {
en: 'You have completed the tasks required for fresher device. Info about device distribution will be sent to you by email.',
fi: 'Olet suorittanut fuksilaitteeseen vaadittavat tehtävät. Saat tiedot laitteiden jakelusta sähköpostitse.',
},
}

const Task = ({ task, completed }) => {
if (completed) {
return (
<Segment style={{ background: '#d2f3db' }} textAlign="center">
{task}
<Icon name="checkmark" size="large" style={{ marginLeft: '10px' }} />
</Segment>
)
}
return (
<Segment style={{ background: '#fddede' }} textAlign="center">
{task}
<Icon name="cancel" size="large" style={{ marginLeft: '10px' }} />
</Segment>
)
}

const fake = {
user: {
eligible: true,
courseRegistrationCompleted: true,
digiSkillsCompleted: true,
},
}

const StudentStatusPage = ({ faking }) => {
const user = faking ? fake.user : useSelector(state => state.user.data)
const locale = useSelector(localeSelector)

return (
<Segment.Group>
<StudentInfo faking={faking} />
{/* <TaskInfo
courseRegistrationCompleted={user.courseRegistrationCompleted}
digiSkillsCompleted={user.digiSkillsCompleted}
/> */}
<Segment>
You will get an email when the device is ready!
</Segment>
<Segment>
<TranslatedMarkdown textKey="deviceSpecs" />
</Segment>
</Segment.Group>
)
}

export default StudentStatusPage
8 changes: 7 additions & 1 deletion client/components/StudentPage/NotEligible.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ const translations = {
en: 'Name',
fi: 'Nimi',
},
hasNotDeviceGiven: {
en: 'Has not a device given already',
fi: 'Et ole vielä saanut laitetta',
},
}

const fake = {
Expand All @@ -65,13 +69,15 @@ export default function NotEligible({ user, notCurrentYearsFuksi, faking }) {
const EligibilityBreakdown = () => {
if (!Object.entries(eligibilityReasons).length) return null // Only users starting from 2020 have eligibilityReasons (unless updated).

const reasons = Object.entries(eligibilityReasons).filter(r => r[0] !== 'hasNotDeviceGiven')

return (
<>
<Header as="h3">
{translations.header[locale]}
</Header>
<List>
{Object.entries(eligibilityReasons).map(([key, status]) => {
{reasons.map(([key, status]) => {
if (key === 'signedUpForFreshmanDeviceThisYear' && notCurrentYearsFuksi) {
// eslint-disable-next-line no-param-reassign
status = false
Expand Down
4 changes: 4 additions & 0 deletions client/components/StudentPage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import RequestDeviceForm from './RequestDeviceForm'
import TaskStatus from './TaskStatus'
import DeviceInfo from './DeviceInfo'
import NotEligible from './NotEligible'
import ExtendedRequestDeviceForm from './ExtendedRequestDeviceForm'
import ExtendedTaskStatus from './ExtendedTaskStatus'

const StudentPage = () => {
const user = useSelector(state => state.user.data)
Expand All @@ -13,6 +15,8 @@ const StudentPage = () => {

if (!user) return null
if (user.deviceSerial) return <DeviceInfo />
if (user.extendedEligible && !user.extendedWantsDevice) return <ExtendedRequestDeviceForm />
if (user.extendedEligible) return <ExtendedTaskStatus />
if (!user.eligible || notCurrentYearsFuksi) return <NotEligible user={user} notCurrentYearsFuksi={notCurrentYearsFuksi} />
if (!user.wantsDevice) return <RequestDeviceForm />
return <TaskStatus />
Expand Down
2 changes: 2 additions & 0 deletions client/util/fakeShibboleth.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const possibleUsers = [
schacDateOfBirth: undefined,
hyPersonStudentId: undefined,
sn: 'admin',
hygroupcn: 'grp-toska',
},
{
uid: 'jakelija',
Expand Down Expand Up @@ -45,6 +46,7 @@ const possibleUsers = [
schacDateOfBirth: 19850806,
hyPersonStudentId: 'non-fuksi',
sn: 'non-fuksi',
hygroupcn: 'grp-toska',
},
{
uid: 'fuksi_without_digiskills',
Expand Down
4 changes: 2 additions & 2 deletions client/util/redux/deviceRequestReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import callBuilder from '../apiConnection'
/**
* Actions and reducers are in the same file for readability
*/
export const deviceRequestAction = ({ email }) => {
export const deviceRequestAction = ({ email, extended }) => {
const route = '/request_device'
const prefix = 'NEW_DEVICE_REQUEST'
return callBuilder(route, prefix, 'post', { email })
return callBuilder(route, prefix, 'post', { email, extended })
}

// Reducer
Expand Down
3 changes: 2 additions & 1 deletion client/util/useTranslation.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { customTextSelector } from 'Utilities/redux/serviceStatusReducer'
export default function useTranslation(translationKey) {
const customTexts = useSelector(customTextSelector)
const locale = useSelector(localeSelector)
const translatedText = customTexts && customTexts[translationKey][locale]

const translatedText = customTexts && customTexts[translationKey] && customTexts[translationKey][locale]

return translatedText
}
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ services:
- POSTGRES_PASSWORD=postgres
volumes:
- pg_data:/data
adminer:
container_name: adminer
adminer_fular:
container_name: adminer_fukrek
environment:
- ADMINER_DESIGN=pepa-linha
- ADMINER_DEFAULT_SERVER=db
Expand Down
17 changes: 17 additions & 0 deletions migrations/20240125203210-extended-device-fields-to-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@


module.exports = {
up: (queryInterface, Sequelize) => queryInterface.sequelize.transaction(t => Promise.all([
queryInterface.addColumn('users', 'extended_wants_device', {
type: Sequelize.BOOLEAN,
}, { transaction: t }),
queryInterface.addColumn('users', 'extended_eligible', {
type: Sequelize.BOOLEAN,
}, { transaction: t }),
])),

down: queryInterface => queryInterface.sequelize.transaction(t => Promise.all([
queryInterface.removeColumn('users', 'extended_wants_device', { transaction: t }),
queryInterface.removeColumn('users', 'extended_eligible', { transaction: t }),
])),
}
10 changes: 5 additions & 5 deletions server/controllers/userController.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const getUser = async (req, res) => {
const userIsPotentiallyEligible = user.isStudent && !user.hasDeviceGiven && !userIsEligibleThisYear

if (userIsPotentiallyEligible) {
await user.checkAndUpdateEligibility()
userIsEligibleThisYear = user.eligible && user.signupYear === settings.currentYear
await user.checkAndUpdateEligibility(req.toska)
userIsEligibleThisYear = user.extendedEligible || (user.eligible && user.signupYear === settings.currentYear)
}

if (userIsEligibleThisYear && !user.hasCompletedAllTasks) {
Expand Down Expand Up @@ -49,16 +49,16 @@ const getLogoutUrl = async (req, res) => {
}

const requestDevice = async (req, res) => {
const { email } = req.body
const { email, extended } = req.body
const { user } = req
const settings = await ServiceStatus.getObject()

const userIsEligibleThisYear = user.eligible && user.signupYear === settings.currentYear
const userIsEligibleThisYear = extended || (user.eligible && user.signupYear === settings.currentYear)
if (!userIsEligibleThisYear) throw new ForbiddenError('Not eligible')

if (email !== null && !validateEmail(email)) throw new ParameterError('Invalid email')

await user.requestDevice(email)
await user.requestDevice(email, extended)
await completionChecker(user)
return res.json(user)
}
Expand Down
Loading

0 comments on commit f09744d

Please sign in to comment.