Skip to content

Commit 82d9f7c

Browse files
authored
Prototype 2 (#23)
fix: provide data to sentry only in dev and stage Ensure GDPR compliance for standard users fix: add en as fallback lang provide a few french translations feat: add new idea button fix: validation when all ideas are rated fix: grid layout for ideas feat: state control ⏯️ refactor: ideation view feat: state management and waiting screen feat: add final anonymous ideas view fix: change ratings nomenclature
1 parent c793556 commit 82d9f7c

File tree

22 files changed

+433
-147
lines changed

22 files changed

+433
-147
lines changed

src/config/appDataTypes.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ import { AppDataRecord } from '@graasp/sdk/frontend';
33

44
import { List } from 'immutable';
55

6+
import { IdeationState } from '@/interfaces/ideation';
7+
8+
export enum AppDataTypes {
9+
Idea = 'idea',
10+
IdeaSet = 'idea-set',
11+
CurrentState = 'current-state',
12+
Ratings = 'ratings',
13+
}
14+
615
export type IdeaData = {
716
idea: string;
817
round?: number;
@@ -16,23 +25,24 @@ export type AnonymousIdeaData = IdeaData & { id: string };
1625
export type IdeasData = List<AnonymousIdeaData>;
1726

1827
export type IdeaAppData = AppDataRecord & {
19-
type: 'idea';
28+
type: AppDataTypes.Idea;
2029
data: IdeaData;
2130
};
2231

2332
export type IdeaSetAppData = AppDataRecord & {
24-
type: 'idea-set';
33+
type: AppDataTypes.IdeaSet;
2534
data: {
2635
ideas: IdeasData;
2736
};
2837
};
2938

3039
export type CurrentStateData = {
31-
round: number;
40+
// round: number;
41+
state: IdeationState;
3242
};
3343

3444
export type CurrentStateAppData = AppDataRecord & {
35-
type: 'current-state';
45+
type: AppDataTypes.CurrentState;
3646
data: CurrentStateData;
3747
};
3848

@@ -42,7 +52,7 @@ export type RatingsData<T> = {
4252
};
4353

4454
export type RatingsAppData<T> = AppDataRecord & {
45-
type: 'ratings';
55+
type: AppDataTypes.Ratings;
4656
data: RatingsData<T>;
4757
visibility: AppDataVisibility.Member;
4858
};

src/config/constants.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import { AppDataVisibility } from '@graasp/sdk';
22

3-
export const INITIAL_STATE = {
3+
import { IdeationState } from '@/interfaces/ideation';
4+
5+
import { CurrentStateData } from './appDataTypes';
6+
7+
export const INITIAL_STATE: {
8+
[key: string]: unknown;
9+
type: string;
10+
data: CurrentStateData;
11+
} = {
412
type: 'current-state',
513
data: {
6-
round: 0,
14+
state: IdeationState.WaitingForStart,
715
},
816
visibility: AppDataVisibility.Item,
917
};

src/config/i18n.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ i18n.use(initReactI18next).init({
3030
escapeValue: false,
3131
formatSeparator: ',',
3232
},
33+
fallbackLng: 'en',
3334
});
3435

3536
export default i18n;

src/interfaces/ideation.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ export enum IdeationPhases {
44
Input = 1,
55
Choose = 0,
66
// Add = 1,
7-
Wait = 3,
7+
}
8+
9+
export enum IdeationState {
10+
WaitingForStart = 'waiting-for-start',
11+
End = 'end',
12+
Pause = 'pause',
13+
Play = 'play',
814
}
915

1016
export type Phase = {

src/interfaces/ratings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ export interface LikertScale extends Rating {
1616

1717
export type NoveltyRelevanceRatings = {
1818
novelty: number | undefined;
19-
relevance: number | undefined;
19+
usefulness: number | undefined;
2020
};

src/langs/en.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
"SYNC_ALERT_SUCCESS": "All ideas are sync.",
2222
"CUE_PARENT_IDEA_TITLE": "Previous idea",
2323
"IDEA_BEING_SUBMITTED_ALERT": "Your idea is being submitted, please wait for a few seconds.",
24-
"CHECK_FOR_NEW_IDEAS": "Check for new ideas"
24+
"CHECK_FOR_NEW_IDEAS": "Check for new ideas",
25+
"PROPOSE_NEW_IDEA": "Propose a new idea",
26+
"STATE_CONTROL_TITLE": "Controls",
27+
"PAUSE_MESSAGE": "The process has been paused by the admin. Please, wait for a moment.",
28+
"WAIT_TO_START_SCREEN_MAIN_INSTRUCTION": "The ideation will start soon. In the meantime, please, read the following instructions:",
29+
"WAIT_TO_START_SCREEN_STEP1": "At the beginning, you will be presented with a generative design question and asked to provide an answer to it.",
30+
"WAIT_TO_START_SCREEN_STEP2": "After submitting a first answer, you will see some ideas provided by the other participants. Rate all of them and then propose a new idea, either by choosing one to build upon or by proposing a completely new one."
2531
}
2632
}

src/langs/fr.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
{
22
"translations": {
3-
"Welcome to the Graasp App Starter Kit": "Bienvenue dans le kit de démarrage de l'application Graasp"
3+
"Welcome to the Graasp App Starter Kit": "Bienvenue dans le kit de démarrage de l'application Graasp",
4+
"IDEATION_TAB": "Idéation",
5+
"IDEAS_VIEW_TAB": "Vue des idées",
6+
"SETTINGS_TAB": "Paramètres",
7+
"BUILD_ON_THIS_IDEA": "Élaborer cette idée",
8+
"IDEA_TOO_LONG_ALERT": "Votre idée est trop longue. Veuillez essayer d'être plus concis.",
9+
"IDEA_SUBMITTED_SUCCESS": "Votre idée a été enregistrée avec succés",
10+
"SUBMIT_NEW_IDEA_STEP_TITLE": "Soumettre une nouvelle idée",
11+
"READ_IDEAS_STEP_TITLE": "Trouver de l'inspiration",
12+
"ADMIN_PANE_TITLE": "Panneau d'aministration",
13+
"SETTINGS_TITLE": "Paramètres",
14+
"SAVE": "Sauvegarder",
15+
"SYNC_ALERT_SUCCESS": "Toutes les idées ont été synchronisées",
16+
"CUE_PARENT_IDEA_TITLE": "Idée précédente"
417
}
518
}

src/modules/main/AdminControl.tsx renamed to src/modules/admin-control/AdminControl.tsx

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC, useEffect, useState } from 'react';
1+
import { FC, useCallback, useEffect, useState } from 'react';
22
import { useTranslation } from 'react-i18next';
33

44
import {
@@ -25,7 +25,9 @@ import { hooks } from '@/config/queryClient';
2525
import { useAppDataContext } from '@/modules/context/AppDataContext';
2626

2727
import Synchronizer from '../common/Synchronizer';
28-
import IdeaInput from './ideationView/IdeaInput';
28+
import IdeaInput from '../main/ideation/IdeaInput';
29+
import SectionTitle from './SectionTitle';
30+
import StateControl from './StateControl';
2931

3032
interface AdminControlProps {
3133
width?: string;
@@ -40,6 +42,16 @@ const AdminControl: FC<AdminControlProps> = ({ width }): JSX.Element => {
4042
postAppData(INITIAL_STATE);
4143
};
4244
const { data: appContext } = hooks.useAppContext();
45+
const getNumberOfIdeas = useCallback(
46+
(member: Member): number => {
47+
const ideasForMember = appData.filter(
48+
({ type, member: memberData }) =>
49+
type === 'idea' && memberData.id === member.id,
50+
);
51+
return ideasForMember.size;
52+
},
53+
[appData],
54+
);
4355

4456
useEffect(() => {
4557
const state = appData.find(
@@ -52,14 +64,6 @@ const AdminControl: FC<AdminControlProps> = ({ width }): JSX.Element => {
5264
}
5365
const members = appContext?.members;
5466

55-
const getNumberOfIdeas = (member: Member): number => {
56-
const ideasForMember = appData.filter(
57-
({ type, member: memberData }) =>
58-
type === 'idea' && memberData.id === member.id,
59-
);
60-
return ideasForMember.size;
61-
};
62-
6367
const handleSyncChange = (
6468
_event: React.ChangeEvent,
6569
checked: boolean,
@@ -86,9 +90,8 @@ const AdminControl: FC<AdminControlProps> = ({ width }): JSX.Element => {
8690
{t('ADMIN_PANE_TITLE')}
8791
</Typography>
8892
<Divider />
89-
<Typography variant="h4" fontSize="14pt">
90-
Participants
91-
</Typography>
93+
<StateControl />
94+
<SectionTitle>Participants</SectionTitle>
9295
<Stack sx={{ m: 1 }} direction="row" spacing={2}>
9396
{members?.map((member) => (
9497
<Badge
@@ -100,9 +103,7 @@ const AdminControl: FC<AdminControlProps> = ({ width }): JSX.Element => {
100103
</Badge>
101104
))}
102105
</Stack>
103-
<Typography variant="h4" fontSize="14pt">
104-
Orchestration
105-
</Typography>
106+
<SectionTitle>Orchestration</SectionTitle>
106107
<FormGroup>
107108
<FormHelperText>
108109
When enabled, the applications distribute ideas to the participants
@@ -116,9 +117,7 @@ const AdminControl: FC<AdminControlProps> = ({ width }): JSX.Element => {
116117
<Collapse in={sync} mountOnEnter unmountOnExit>
117118
<Synchronizer sync={sync} />
118119
</Collapse>
119-
<Typography variant="h4" fontSize="14pt">
120-
Act as a bot
121-
</Typography>
120+
<SectionTitle>Act as a bot</SectionTitle>
122121
<Typography>
123122
With the following field, you can insert new ideas in the ideation
124123
process under the identity of the virtual agent.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { FC } from 'react';
2+
3+
import { Typography, TypographyProps } from '@mui/material';
4+
5+
const SectionTitle: FC<TypographyProps> = (props) => (
6+
<Typography variant="h4" fontSize="14pt" {...props}>
7+
{props.children}
8+
</Typography>
9+
);
10+
11+
export default SectionTitle;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { FC, useEffect, useState } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
4+
import PauseCircleOutlineIcon from '@mui/icons-material/PauseCircleOutline';
5+
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline';
6+
import ReplayOutlinedIcon from '@mui/icons-material/ReplayOutlined';
7+
import StopCircleOutlinedIcon from '@mui/icons-material/StopCircleOutlined';
8+
import { IconButton, Stack } from '@mui/material';
9+
10+
import { CurrentStateAppData } from '@/config/appDataTypes';
11+
import { INITIAL_STATE } from '@/config/constants';
12+
import { IdeationState } from '@/interfaces/ideation';
13+
import { getCurrentState } from '@/utils/ideas';
14+
15+
import { useAppDataContext } from '../context/AppDataContext';
16+
import { useSettings } from '../context/SettingsContext';
17+
import SectionTitle from './SectionTitle';
18+
19+
interface StateControlProps {
20+
onChange?: (state: IdeationState) => void;
21+
}
22+
23+
const StateControl: FC<StateControlProps> = ({ onChange }) => {
24+
const { t } = useTranslation();
25+
const { appData, postAppData, patchAppData } = useAppDataContext();
26+
const { orchestrator } = useSettings();
27+
const [currentState, setCurrentState] = useState<CurrentStateAppData>();
28+
const [processState, setProcessState] = useState<IdeationState>();
29+
30+
useEffect(() => {
31+
const tmpCurrentState = getCurrentState(appData, orchestrator.id);
32+
setCurrentState(tmpCurrentState);
33+
setProcessState(tmpCurrentState?.data.state);
34+
}, [appData, orchestrator.id]);
35+
36+
const updateState = async (
37+
newProcessState?: IdeationState,
38+
): Promise<void> => {
39+
if (currentState?.id) {
40+
patchAppData({
41+
id: currentState.id,
42+
data: {
43+
...currentState.data.toJS(),
44+
state: newProcessState,
45+
},
46+
});
47+
} else {
48+
postAppData({
49+
...INITIAL_STATE,
50+
data: {
51+
...INITIAL_STATE.data,
52+
state: newProcessState,
53+
},
54+
});
55+
}
56+
};
57+
const handleChange = (newState: IdeationState): void => {
58+
setProcessState(newState);
59+
updateState(newState);
60+
if (onChange) {
61+
onChange(newState);
62+
}
63+
};
64+
return (
65+
<>
66+
<SectionTitle>{t('STATE_CONTROL_TITLE')}</SectionTitle>
67+
<Stack direction="row" spacing={1}>
68+
{processState === IdeationState.Play ? (
69+
<IconButton onClick={() => handleChange(IdeationState.Pause)}>
70+
<PauseCircleOutlineIcon />
71+
</IconButton>
72+
) : (
73+
<IconButton onClick={() => handleChange(IdeationState.Play)}>
74+
<PlayCircleOutlineIcon />
75+
</IconButton>
76+
)}
77+
<IconButton onClick={() => handleChange(IdeationState.End)}>
78+
<StopCircleOutlinedIcon />
79+
</IconButton>
80+
{processState === IdeationState.End && (
81+
<IconButton
82+
onClick={() => handleChange(IdeationState.WaitingForStart)}
83+
>
84+
<ReplayOutlinedIcon />
85+
</IconButton>
86+
)}
87+
</Stack>
88+
</>
89+
);
90+
};
91+
92+
export default StateControl;

0 commit comments

Comments
 (0)