eionet2-dashboard 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.fx/configs/azure.parameters.Prod_EEA.json +15 -0
  2. package/.fx/configs/azure.parameters.dev.json +15 -0
  3. package/.fx/configs/config.Prod_EEA.json +10 -0
  4. package/.fx/configs/config.dev.json +10 -0
  5. package/.fx/configs/projectSettings.json +83 -0
  6. package/.fx/states/state.Prod_EEA.json +47 -0
  7. package/.fx/states/state.dev.json +47 -0
  8. package/.vscode/launch.json +91 -0
  9. package/.vscode/settings.json +6 -0
  10. package/.vscode/tasks.json +63 -0
  11. package/CHANGELOG.md +140 -0
  12. package/Jenkinsfile +166 -0
  13. package/LICENSE.md +9 -0
  14. package/README.md +55 -0
  15. package/api/.funcignore +11 -0
  16. package/api/extensions.csproj +11 -0
  17. package/api/getGraphData/function.json +27 -0
  18. package/api/getGraphData/index.js +147 -0
  19. package/api/host.json +11 -0
  20. package/api/package-lock.json +1546 -0
  21. package/api/package.json +17 -0
  22. package/api/proxies.json +4 -0
  23. package/package.json +25 -0
  24. package/tabs/.env.teamsfx.Prod_EEA +11 -0
  25. package/tabs/.env.teamsfx.dev +11 -0
  26. package/tabs/.eslintrc.json +48 -0
  27. package/tabs/.prettierrc +7 -0
  28. package/tabs/.stylelintrc.json +6 -0
  29. package/tabs/babel.config.js +3 -0
  30. package/tabs/package-lock.json +15564 -0
  31. package/tabs/package.json +88 -0
  32. package/tabs/public/auth-end.html +76 -0
  33. package/tabs/public/auth-start.html +178 -0
  34. package/tabs/public/deploy.png +0 -0
  35. package/tabs/public/favicon.ico +0 -0
  36. package/tabs/public/hello.png +0 -0
  37. package/tabs/public/index.html +20 -0
  38. package/tabs/public/publish.png +0 -0
  39. package/tabs/src/components/App.jsx +36 -0
  40. package/tabs/src/components/CustomColumnResizeIcon.jsx +68 -0
  41. package/tabs/src/components/CustomDrawer.jsx +51 -0
  42. package/tabs/src/components/EventDialogTitle.jsx +29 -0
  43. package/tabs/src/components/HtmlBox.jsx +18 -0
  44. package/tabs/src/components/Privacy.jsx +17 -0
  45. package/tabs/src/components/ResizableGrid.jsx +44 -0
  46. package/tabs/src/components/Tab.jsx +477 -0
  47. package/tabs/src/components/Tab.scss +138 -0
  48. package/tabs/src/components/TabConfig.jsx +51 -0
  49. package/tabs/src/components/TabPanel.jsx +29 -0
  50. package/tabs/src/components/TermsOfUse.jsx +17 -0
  51. package/tabs/src/components/UnderConstruction.jsx +24 -0
  52. package/tabs/src/components/UserMenu.jsx +109 -0
  53. package/tabs/src/components/_variables.scss +10 -0
  54. package/tabs/src/components/activity/Activity.jsx +301 -0
  55. package/tabs/src/components/activity/ConsultationList.jsx +297 -0
  56. package/tabs/src/components/activity/EventList.jsx +463 -0
  57. package/tabs/src/components/activity/GroupsTags.jsx +26 -0
  58. package/tabs/src/components/activity/Reporting.jsx +13 -0
  59. package/tabs/src/components/activity/activity.scss +153 -0
  60. package/tabs/src/components/event_rating/EventRating.jsx +92 -0
  61. package/tabs/src/components/event_rating/EventRatingDialog.jsx +46 -0
  62. package/tabs/src/components/event_registration/Approval.jsx +80 -0
  63. package/tabs/src/components/event_registration/ApprovalDialog.jsx +30 -0
  64. package/tabs/src/components/event_registration/ApprovalList.jsx +62 -0
  65. package/tabs/src/components/event_registration/EventRegistration.jsx +214 -0
  66. package/tabs/src/components/lib/useData.js +33 -0
  67. package/tabs/src/components/lib/useGraph.js +39 -0
  68. package/tabs/src/components/lib/useTeamsFx.js +55 -0
  69. package/tabs/src/components/my_country/AtAGlance.jsx +151 -0
  70. package/tabs/src/components/my_country/CountryProgress.jsx +92 -0
  71. package/tabs/src/components/my_country/DataReporters.jsx +13 -0
  72. package/tabs/src/components/my_country/GroupView.jsx +54 -0
  73. package/tabs/src/components/my_country/GroupsBoard.jsx +52 -0
  74. package/tabs/src/components/my_country/IndicatorCard.jsx +60 -0
  75. package/tabs/src/components/my_country/ManagementBoard.jsx +109 -0
  76. package/tabs/src/components/my_country/MyCountry.jsx +186 -0
  77. package/tabs/src/components/my_country/ProgressGauge.jsx +125 -0
  78. package/tabs/src/components/my_country/ScientificCommittee.jsx +13 -0
  79. package/tabs/src/components/my_country/YearlyProgress.jsx +41 -0
  80. package/tabs/src/components/my_country/my_country.scss +81 -0
  81. package/tabs/src/components/publications/Publications.jsx +13 -0
  82. package/tabs/src/components/self_service/UserEdit.jsx +334 -0
  83. package/tabs/src/components/self_service/UserEdit.scss +107 -0
  84. package/tabs/src/data/apiProvider.js +153 -0
  85. package/tabs/src/data/constants.json +7 -0
  86. package/tabs/src/data/hooks/useConfiguration.js +18 -0
  87. package/tabs/src/data/icsHelper.js +38 -0
  88. package/tabs/src/data/messages.json +39 -0
  89. package/tabs/src/data/provider.js +199 -0
  90. package/tabs/src/data/selfServiceProvider.js +59 -0
  91. package/tabs/src/data/selfServiceSharepointProvider.js +68 -0
  92. package/tabs/src/data/sharepointProvider.js +729 -0
  93. package/tabs/src/data/validator.js +25 -0
  94. package/tabs/src/data/validator.test.js +9 -0
  95. package/tabs/src/index.css +16 -0
  96. package/tabs/src/index.jsx +6 -0
  97. package/tabs/src/static/images/teams-icon.svg +1 -0
  98. package/tabs/src/utils/uiHelper.js +6 -0
  99. package/templates/appPackage/aad.template.json +133 -0
  100. package/templates/appPackage/manifest.template.json +58 -0
  101. package/templates/appPackage/resources/color.png +0 -0
  102. package/templates/appPackage/resources/outline.png +0 -0
  103. package/templates/azure/config.bicep +27 -0
  104. package/templates/azure/main.bicep +20 -0
  105. package/templates/azure/provision/frontendHosting.bicep +23 -0
  106. package/templates/azure/provision/function.bicep +82 -0
  107. package/templates/azure/provision/identity.bicep +14 -0
  108. package/templates/azure/provision/simpleAuth.bicep +44 -0
  109. package/templates/azure/provision.bicep +58 -0
  110. package/templates/azure/teamsFx/function.bicep +76 -0
  111. package/templates/azure/teamsFx/simpleAuth.bicep +43 -0
@@ -0,0 +1,92 @@
1
+ import { React, useState } from 'react';
2
+ import { Box, Button, CircularProgress, Backdrop, Rating, Typography } from '@mui/material';
3
+
4
+ import CheckIcon from '@mui/icons-material/Check';
5
+ import SaveIcon from '@mui/icons-material/Save';
6
+ import StarIcon from '@mui/icons-material/Star';
7
+
8
+ import { postRating } from '../../data/sharepointProvider';
9
+
10
+ const labels = {
11
+ 1: 'Useless',
12
+ 2: 'Poor',
13
+ 3: 'Ok',
14
+ 4: 'Good',
15
+ 5: 'Excellent',
16
+ };
17
+
18
+ function getLabelText(value) {
19
+ return `${value} Star${value !== 1 ? 's' : ''}, ${labels[value]}`;
20
+ }
21
+
22
+ export function EventRating({ configuration, participant, event, onRate }) {
23
+ const [loading, setLoading] = useState(false),
24
+ [ratingValue, setRatingValue] = useState(5),
25
+ [hover, setHover] = useState(-1),
26
+ [successUpdate, setSuccessUpdate] = useState(false);
27
+
28
+ const handleRating = async () => {
29
+ setSuccessUpdate(false);
30
+ setLoading(true);
31
+ const result = await postRating(event, participant, ratingValue);
32
+
33
+ setSuccessUpdate(true);
34
+ setLoading(false);
35
+ onRate && onRate(result);
36
+ };
37
+
38
+ return (
39
+ <div className="">
40
+ <Box className="popup" sx={{ maxHeight: '900px', marginTop: '0.5rem' }}>
41
+ <Backdrop
42
+ sx={{ color: '#6b32a8', zIndex: (theme) => theme.zIndex.drawer + 1 }}
43
+ open={loading}
44
+ >
45
+ <CircularProgress color="primary" />
46
+ </Backdrop>
47
+ {configuration.EventRatingModalText && (
48
+ <Typography sx={{ width: '100%' }} variant="subtitle2">
49
+ {configuration.EventRatingModalText}
50
+ </Typography>
51
+ )}
52
+ <Box
53
+ sx={{
54
+ width: 200,
55
+ display: 'flex',
56
+ alignItems: 'center',
57
+ }}
58
+ >
59
+ <Rating
60
+ name="hover-feedback"
61
+ value={ratingValue}
62
+ precision={1}
63
+ getLabelText={getLabelText}
64
+ onChange={(_event, newValue) => {
65
+ setRatingValue(newValue);
66
+ }}
67
+ onChangeActive={(_event, newHover) => {
68
+ setHover(newHover);
69
+ }}
70
+ emptyIcon={<StarIcon style={{ opacity: 0.55 }} fontSize="inherit" />}
71
+ />
72
+ {ratingValue !== null && (
73
+ <Box sx={{ ml: 2 }}>{labels[hover !== -1 ? hover : ratingValue]}</Box>
74
+ )}
75
+ </Box>
76
+ <Box sx={{ marginTop: '1rem' }}>
77
+ <Button
78
+ onClick={handleRating}
79
+ variant="contained"
80
+ color="primary"
81
+ size="medium"
82
+ className="button"
83
+ disabled={loading}
84
+ endIcon={successUpdate ? <CheckIcon /> : <SaveIcon />}
85
+ >
86
+ Confirm rating
87
+ </Button>
88
+ </Box>
89
+ </Box>
90
+ </div>
91
+ );
92
+ }
@@ -0,0 +1,46 @@
1
+ import { React } from 'react';
2
+ import { Dialog, DialogTitle, IconButton } from '@mui/material';
3
+
4
+ import CloseIcon from '@mui/icons-material/Close';
5
+
6
+ import { EventRating } from '../event_rating/EventRating';
7
+ import { EventDialogTitle } from '../EventDialogTitle';
8
+
9
+ import { useConfiguration } from '../../data/hooks/useConfiguration';
10
+
11
+ export function EventRatingDialog({ open, handleClose, event, participant }) {
12
+ const configuration = useConfiguration();
13
+ return (
14
+ <Dialog
15
+ className="dialog"
16
+ open={open}
17
+ onClose={() => handleClose(false)}
18
+ maxWidth="md"
19
+ fullWidth
20
+ >
21
+ {event && (
22
+ <DialogTitle>
23
+ <IconButton
24
+ aria-label="close"
25
+ onClick={() => handleClose(false)}
26
+ sx={{
27
+ position: 'absolute',
28
+ right: 8,
29
+ top: 8,
30
+ color: (theme) => theme.palette.grey[500],
31
+ }}
32
+ >
33
+ <CloseIcon />
34
+ </IconButton>
35
+ <EventDialogTitle title={'RATING OF THE EVENT'} event={event}></EventDialogTitle>
36
+ </DialogTitle>
37
+ )}
38
+ <EventRating
39
+ configuration={configuration}
40
+ event={event}
41
+ participant={participant}
42
+ onRate={handleClose}
43
+ ></EventRating>
44
+ </Dialog>
45
+ );
46
+ }
@@ -0,0 +1,80 @@
1
+ import { React, useState } from 'react';
2
+ import { Box, TextField, Checkbox, Autocomplete, FormControlLabel } from '@mui/material';
3
+ import AssignmentTurnedInIcon from '@mui/icons-material/AssignmentTurnedIn';
4
+ import AssignmentLateIcon from '@mui/icons-material/AssignmentLate';
5
+
6
+ export function Approval({ participant }) {
7
+ const [approvalStatus, setApprovalStatus] = useState(participant.NFPApproved),
8
+ approvalOptions = ['No value', 'Approved', 'Declined'];
9
+
10
+ return (
11
+ <Box className="row box approval-row">
12
+ <Box className="row fixed">
13
+ <TextField
14
+ disabled
15
+ className="control w50"
16
+ id="name"
17
+ label="Name"
18
+ variant="standard"
19
+ defaultValue={participant.ParticipantName}
20
+ />
21
+ <TextField
22
+ disabled
23
+ className="control w50"
24
+ id="email"
25
+ label="Email"
26
+ variant="standard"
27
+ defaultValue={participant.Email}
28
+ />
29
+ </Box>
30
+ <Box className="row fixed">
31
+ <FormControlLabel
32
+ sx={{ fontSize: '12px' }}
33
+ className="control"
34
+ control={<Checkbox disabled checked={participant.PhysicalParticipation} />}
35
+ label="Physical participation"
36
+ labelPlacement="end"
37
+ />
38
+ <FormControlLabel
39
+ sx={{ fontSize: '12px' }}
40
+ className="control"
41
+ control={<Checkbox disabled checked={participant.EEAReimbursementRequested} />}
42
+ label="Reimbursement requested"
43
+ labelPlacement="end"
44
+ />
45
+ {approvalStatus == 'Approved' && (
46
+ <AssignmentTurnedInIcon
47
+ sx={{ alignSelf: 'center' }}
48
+ color="primary"
49
+ ></AssignmentTurnedInIcon>
50
+ )}
51
+ {approvalStatus == 'Declined' && (
52
+ <AssignmentLateIcon sx={{ alignSelf: 'center' }} color="error"></AssignmentLateIcon>
53
+ )}
54
+ {approvalStatus != 'Approved' && approvalStatus != 'Declined' && (
55
+ <AssignmentLateIcon sx={{ alignSelf: 'center' }} color="warning"></AssignmentLateIcon>
56
+ )}
57
+ <Autocomplete
58
+ id="nfp-approval"
59
+ className="control"
60
+ defaultValue={participant.NFPApproved || ''}
61
+ options={approvalOptions}
62
+ onChange={(_e, value) => {
63
+ participant.NFPApproved = value;
64
+ participant.NFPApprovalChanged = true;
65
+ setApprovalStatus(value);
66
+ }}
67
+ renderInput={(params) => (
68
+ <TextField
69
+ {...params}
70
+ autoComplete="off"
71
+ className="small-width"
72
+ label="Approval status"
73
+ variant="standard"
74
+ />
75
+ )}
76
+ />
77
+ </Box>
78
+ </Box>
79
+ );
80
+ }
@@ -0,0 +1,30 @@
1
+ import { React } from 'react';
2
+ import { Dialog, DialogTitle, IconButton } from '@mui/material';
3
+
4
+ import CloseIcon from '@mui/icons-material/Close';
5
+
6
+ import { ApprovalList } from './ApprovalList';
7
+ import { EventDialogTitle } from '../EventDialogTitle';
8
+
9
+ export function ApprovalDialog({ open, handleClose, event, userInfo }) {
10
+ return (
11
+ <Dialog className="dialog" open={open} onClose={handleClose} maxWidth="xl" fullWidth>
12
+ <DialogTitle>
13
+ <IconButton
14
+ aria-label="close"
15
+ onClick={handleClose}
16
+ sx={{
17
+ position: 'absolute',
18
+ right: 8,
19
+ top: 8,
20
+ color: (theme) => theme.palette.grey[500],
21
+ }}
22
+ >
23
+ <CloseIcon />
24
+ </IconButton>
25
+ <EventDialogTitle title={'APPROVALS FOR EVENT'} event={event}></EventDialogTitle>
26
+ </DialogTitle>
27
+ <ApprovalList event={event} userInfo={userInfo}></ApprovalList>
28
+ </Dialog>
29
+ );
30
+ }
@@ -0,0 +1,62 @@
1
+ import { React, useRef, useState } from 'react';
2
+ import { Box, Button, CircularProgress, Backdrop } from '@mui/material';
3
+ import CheckIcon from '@mui/icons-material/Check';
4
+ import SaveIcon from '@mui/icons-material/Save';
5
+ import { patchParticipants } from '../../data/sharepointProvider';
6
+ import { Approval } from './Approval';
7
+ import { HtmlBox } from '../HtmlBox';
8
+ import { useConfiguration } from '../../data/hooks/useConfiguration';
9
+
10
+ export function ApprovalList({ event }) {
11
+ const configuration = useConfiguration(),
12
+ editEvent = useRef(JSON.parse(JSON.stringify(event))),
13
+ [loading, setLoading] = useState(false),
14
+ [success, setSuccess] = useState(false),
15
+ handleUpdate = async () => {
16
+ const currentEvent = editEvent.current;
17
+ setSuccess(false);
18
+ setLoading(true);
19
+ await patchParticipants(currentEvent.Participants, currentEvent);
20
+ event.Participants = currentEvent.Participants;
21
+ setSuccess(true);
22
+ setLoading(false);
23
+ };
24
+
25
+ return (
26
+ <Box className="popup">
27
+ <Box sx={{ minHeight: '150px' }}>
28
+ {editEvent.current.Participants.map((participant) => {
29
+ return <Approval key={participant.id} participant={participant}></Approval>;
30
+ })}
31
+ </Box>
32
+ <HtmlBox html={configuration?.EventApprovalInfo}></HtmlBox>
33
+ <Button
34
+ sx={{ marginTop: '1rem' }}
35
+ onClick={handleUpdate}
36
+ variant="contained"
37
+ color="secondary"
38
+ size="medium"
39
+ className="button"
40
+ disabled={loading}
41
+ endIcon={success ? <CheckIcon /> : <SaveIcon />}
42
+ >
43
+ Update
44
+ </Button>
45
+ <Backdrop
46
+ sx={{ color: '#6b32a8', zIndex: (theme) => theme.zIndex.drawer + 1 }}
47
+ open={loading}
48
+ >
49
+ <CircularProgress
50
+ color="primary"
51
+ sx={{
52
+ position: 'absolute',
53
+ top: '50%',
54
+ left: '50%',
55
+ marginTop: '-12px',
56
+ marginLeft: '-12px',
57
+ }}
58
+ />
59
+ </Backdrop>
60
+ </Box>
61
+ );
62
+ }
@@ -0,0 +1,214 @@
1
+ import { React, useState } from 'react';
2
+ import {
3
+ Box,
4
+ Checkbox,
5
+ TextField,
6
+ Button,
7
+ FormControlLabel,
8
+ CircularProgress,
9
+ Backdrop,
10
+ Typography,
11
+ } from '@mui/material';
12
+
13
+ import CheckIcon from '@mui/icons-material/Check';
14
+ import SaveIcon from '@mui/icons-material/Save';
15
+
16
+ import {
17
+ postParticipant,
18
+ patchParticipant,
19
+ deleteParticipant,
20
+ } from '../../data/sharepointProvider';
21
+ import { useConfiguration } from '../../data/hooks/useConfiguration';
22
+ import { HtmlBox } from '../HtmlBox';
23
+
24
+ export function EventRegistration({ participant, event }) {
25
+ const configuration = useConfiguration();
26
+
27
+ const [loading, setLoading] = useState(false),
28
+ [successRegister, setSuccessRegister] = useState(false),
29
+ [successUpdate, setSuccessUpdate] = useState(false),
30
+ [successUnregister, setSuccessUnregister] = useState(false),
31
+ [physical, setPhysical] = useState(participant.PhysicalParticipation),
32
+ [reimbursement, setReimbursement] = useState(participant.EEAReimbursementRequested);
33
+
34
+ const handleRegister = async () => {
35
+ setSuccessRegister(false);
36
+ setLoading(true);
37
+ participant.Registered = true;
38
+ participant.RegistrationDate = new Date();
39
+ const response = await postParticipant(participant, event);
40
+ if (response) {
41
+ participant.id = response.id;
42
+ event.Participants.push(participant);
43
+ setEventProperties(true);
44
+ }
45
+ setSuccessRegister(true);
46
+ setLoading(false);
47
+ },
48
+ handleUpdateRegistration = async () => {
49
+ setSuccessUpdate(false);
50
+ setLoading(true);
51
+ participant.NFPApproved = 'No value';
52
+ participant.Registered = true;
53
+ await patchParticipant(participant, event);
54
+ setEventProperties(true);
55
+ setSuccessUpdate(true);
56
+ setLoading(false);
57
+ },
58
+ handleUnregister = async () => {
59
+ setSuccessUnregister(false);
60
+ setLoading(true);
61
+ participant.Registered = false;
62
+ await deleteParticipant(participant, event);
63
+ setEventProperties(false);
64
+ setSuccessUnregister(true);
65
+ setLoading(false);
66
+ },
67
+ setEventProperties = (hasRegistered) => {
68
+ event.HasRegistered = hasRegistered;
69
+ event.NoOfRegistered = event.Participants.filter((p) => {
70
+ return p.Registered;
71
+ }).length;
72
+ };
73
+
74
+ return (
75
+ <Box className="popup" sx={{ maxHeight: '900px' }}>
76
+ <Backdrop
77
+ sx={{ color: '#6b32a8', zIndex: (theme) => theme.zIndex.drawer + 1 }}
78
+ open={loading}
79
+ >
80
+ <CircularProgress color="primary" />
81
+ </Backdrop>
82
+ <Box className="row">
83
+ <TextField
84
+ variant="standard"
85
+ className="control"
86
+ disabled
87
+ label="Name"
88
+ defaultValue={participant.ParticipantName}
89
+ ></TextField>
90
+ <TextField
91
+ variant="standard"
92
+ className="control"
93
+ disabled
94
+ label="Email"
95
+ defaultValue={participant.Email}
96
+ ></TextField>
97
+ <TextField
98
+ variant="standard"
99
+ className="control"
100
+ disabled
101
+ label="Country"
102
+ defaultValue={participant.Country}
103
+ ></TextField>
104
+ </Box>
105
+ {event.IsOffline && (
106
+ <Box>
107
+ <FormControlLabel
108
+ sx={{ marginLeft: '0.5rem' }}
109
+ control={
110
+ <Checkbox
111
+ checked={physical}
112
+ color="secondary"
113
+ onChange={(_e, value) => {
114
+ setPhysical(value);
115
+ participant.PhysicalParticipation = value;
116
+ if (!value) {
117
+ setReimbursement(false);
118
+ participant.EEAReimbursementRequested = false;
119
+ }
120
+ }}
121
+ />
122
+ }
123
+ label="Physical participation"
124
+ labelPlacement="end"
125
+ />
126
+ <FormControlLabel
127
+ control={
128
+ <Checkbox
129
+ checked={reimbursement}
130
+ disabled={!physical}
131
+ color="secondary"
132
+ onChange={(_e, value) => {
133
+ setReimbursement(value);
134
+ participant.EEAReimbursementRequested = value;
135
+ }}
136
+ />
137
+ }
138
+ label="Reimbursement requested"
139
+ labelPlacement="end"
140
+ />
141
+ </Box>
142
+ )}
143
+ {event.IsOffline && (
144
+ <Box className="row">
145
+ {event.CustomMeetingRequest && (
146
+ <Typography
147
+ style={{ whiteSpace: 'pre-line' }}
148
+ id="eventCustomInfo"
149
+ label="Meeting requests info"
150
+ className="control w100"
151
+ >
152
+ {event.CustomMeetingRequest}
153
+ </Typography>
154
+ )}
155
+ <TextField
156
+ multiline
157
+ label="Custom meeting request"
158
+ className="control w100"
159
+ variant="standard"
160
+ minRows={3}
161
+ defaultValue={participant.CustomMeetingRequest}
162
+ onChange={(event) => {
163
+ const { value } = event.target;
164
+ participant.CustomMeetingRequest = value;
165
+ }}
166
+ />
167
+ </Box>
168
+ )}
169
+ <HtmlBox html={configuration?.EventRegistrationInfo}></HtmlBox>
170
+
171
+ <Box sx={{ marginTop: '1rem' }}>
172
+ {!participant.Registered && (
173
+ <Button
174
+ onClick={handleRegister}
175
+ variant="contained"
176
+ color="primary"
177
+ size="medium"
178
+ className="button"
179
+ disabled={loading}
180
+ endIcon={successRegister ? <CheckIcon /> : <SaveIcon />}
181
+ >
182
+ Register
183
+ </Button>
184
+ )}
185
+ {participant.Registered && event.IsOffline && (
186
+ <Button
187
+ onClick={handleUpdateRegistration}
188
+ variant="contained"
189
+ color="primary"
190
+ size="medium"
191
+ className="button"
192
+ disabled={loading}
193
+ endIcon={successUpdate ? <CheckIcon /> : <SaveIcon />}
194
+ >
195
+ Update registration
196
+ </Button>
197
+ )}
198
+ {participant.Registered && (
199
+ <Button
200
+ onClick={handleUnregister}
201
+ variant="contained"
202
+ color="error"
203
+ size="medium"
204
+ className="button"
205
+ disabled={loading || !participant.Registered}
206
+ endIcon={successUnregister ? <CheckIcon /> : <SaveIcon />}
207
+ >
208
+ Unregister
209
+ </Button>
210
+ )}
211
+ </Box>
212
+ </Box>
213
+ );
214
+ }
@@ -0,0 +1,33 @@
1
+ import { useEffect, useReducer } from 'react';
2
+
3
+ export function useData(asyncFn, options) {
4
+ const { auto } = { auto: true, ...options };
5
+ const [{ data, loading, error }, dispatch] = useReducer(
6
+ ({ data: oldData }, { type, data, error }) => {
7
+ switch (type) {
8
+ case 'loading':
9
+ return { data: oldData, loading: true, error: null };
10
+ case 'result':
11
+ return { data, loading: false, error: null };
12
+ case 'error':
13
+ return { data: null, loading: false, error };
14
+ default:
15
+ return {};
16
+ }
17
+ },
18
+ { data: null, loading: !!auto, error: null },
19
+ );
20
+ function reload() {
21
+ if (!loading) dispatch({ type: 'loading' });
22
+ if (typeof asyncFn != 'function') {
23
+ throw new Error('invalid argument to useData, a function is required');
24
+ }
25
+ asyncFn()
26
+ .then((data) => dispatch({ type: 'result', data }))
27
+ .catch((error) => dispatch({ type: 'error', error }));
28
+ }
29
+ useEffect(() => {
30
+ if (auto) reload();
31
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
32
+ return { data, loading, error, reload };
33
+ }
@@ -0,0 +1,39 @@
1
+ import { useData } from './useData';
2
+ import { TeamsUserCredential, createMicrosoftGraphClient } from '@microsoft/teamsfx';
3
+
4
+ export function useGraph(asyncFunc, options) {
5
+ const { scope } = { scope: ['User.Read'], ...options };
6
+ const initial = useData(async () => {
7
+ try {
8
+ const credential = new TeamsUserCredential();
9
+ const graph = createMicrosoftGraphClient(credential, scope);
10
+ return await asyncFunc(graph);
11
+ } catch (err) {
12
+ if (err.code.includes('UiRequiredError')) {
13
+ // Silently fail for user didn't login error
14
+ } else {
15
+ throw err;
16
+ }
17
+ }
18
+ });
19
+
20
+ const { data, error, loading, reload } = useData(
21
+ async () => {
22
+ const credential = new TeamsUserCredential();
23
+ await credential.login(scope);
24
+ // Important: tokens are stored in sessionStorage, read more here: https://aka.ms/teamsfx-session-storage-notice
25
+ const graph = createMicrosoftGraphClient(credential, scope);
26
+ return await asyncFunc(graph);
27
+ },
28
+ { auto: false },
29
+ );
30
+
31
+ return data || error || loading
32
+ ? { data, error, loading, reload }
33
+ : {
34
+ data: initial.data,
35
+ error: initial.error,
36
+ loading: initial.loading,
37
+ reload,
38
+ };
39
+ }
@@ -0,0 +1,55 @@
1
+ import {
2
+ loadConfiguration,
3
+ ResourceType,
4
+ LogLevel,
5
+ setLogLevel,
6
+ setLogFunction,
7
+ TeamsUserCredential,
8
+ } from '@microsoft/teamsfx';
9
+ import { useData } from './useData';
10
+ import { useTeams } from 'msteams-react-base-component';
11
+
12
+ const teamsfxEndpoint = process.env.REACT_APP_TEAMSFX_ENDPOINT;
13
+ const startLoginPageUrl = process.env.REACT_APP_START_LOGIN_PAGE_URL;
14
+ const functionEndpoint = process.env.REACT_APP_FUNC_ENDPOINT;
15
+ const clientId = process.env.REACT_APP_CLIENT_ID;
16
+
17
+ // TODO fix this when the SDK stops hiding global state!
18
+ let initialized = false;
19
+
20
+ export function useTeamsFx() {
21
+ const [result] = useTeams({});
22
+ const { error, loading } = useData(async () => {
23
+ if (!initialized) {
24
+ if (process.env.NODE_ENV === 'development') {
25
+ setLogLevel(LogLevel.Verbose);
26
+ setLogFunction((level, message) => {
27
+ console.log(message);
28
+ });
29
+ }
30
+ loadConfiguration({
31
+ authentication: {
32
+ initiateLoginEndpoint: startLoginPageUrl,
33
+ simpleAuthEndpoint: teamsfxEndpoint,
34
+ clientId: clientId,
35
+ },
36
+ resources: [
37
+ {
38
+ type: ResourceType.API,
39
+ name: 'default',
40
+ properties: {
41
+ endpoint: functionEndpoint,
42
+ },
43
+ },
44
+ ],
45
+ });
46
+
47
+ initialized = true;
48
+ const credential = new TeamsUserCredential();
49
+ // Get the user info from access token
50
+ await credential.getUserInfo();
51
+ }
52
+ });
53
+ const isInTeams = true;
54
+ return { error, loading, isInTeams, ...result };
55
+ }