regform.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. import React, {useState} from "react";
  2. import axios from "axios";
  3. import DateFnsUtils from '@date-io/date-fns';
  4. import {nb} from 'date-fns/locale'
  5. import {DateTimePicker, MuiPickersUtilsProvider} from '@material-ui/pickers';
  6. import {makeStyles} from '@material-ui/core/styles';
  7. import ReCAPTCHA from "react-google-recaptcha";
  8. import {
  9. Button,
  10. ButtonGroup,
  11. Container,
  12. Checkbox,
  13. CssBaseline,
  14. Dialog,
  15. DialogTitle,
  16. DialogContent,
  17. DialogContentText,
  18. DialogActions,
  19. FormControl,
  20. FormControlLabel,
  21. FormGroup,
  22. Grid,
  23. IconButton,
  24. Paper,
  25. TextField,
  26. Typography,
  27. } from '@material-ui/core';
  28. import {
  29. Alert,
  30. AlertTitle
  31. } from '@material-ui/lab';
  32. import {
  33. Add,
  34. Remove,
  35. } from '@material-ui/icons'
  36. const useStyles = makeStyles((theme) => ({
  37. fieldset: {
  38. //border: 'none',
  39. marginBottom: theme.spacing(3),
  40. paddingBottom: theme.spacing(3),
  41. },
  42. form: {
  43. width: '100%',
  44. marginTop: theme.spacing(1),
  45. flexGrow: 1,
  46. },
  47. paper: {
  48. padding: theme.spacing(2),
  49. alignItems: 'center',
  50. display: 'flex',
  51. flexDirection: 'column',
  52. }
  53. }));
  54. const ReCaptchaSubmitButton = (props) => {
  55. const recaptchaRef = React.createRef();
  56. const [response, setResponse] = useState(null);
  57. const onChange = (value) => {
  58. if (value) {
  59. const request = {
  60. organizer: props.inputs.organizer || null,
  61. organizationNumber: props.inputs.organization_number || null,
  62. contactPerson: props.inputs.contact_name || null,
  63. contactEmail: props.inputs.contact_email || null,
  64. contactPhone: props.inputs.contact_phone || null,
  65. websiteUrl: props.inputs.contact_homepage || null,
  66. place: props.inputs.event_location || null,
  67. capacity: props.inputs.event_capacity || null,
  68. happensOn: props.inputs.dates || [],
  69. title: props.inputs.event_name || null,
  70. image: props.inputs.event_photo || null,
  71. description: props.inputs.event_description || null,
  72. bookingUrl: props.inputs.event_booking || null,
  73. audiences: (props.inputs.audiences || []).map(i => i.id),
  74. categories: (props.inputs.categories || []).map(i => i.id),
  75. captcha: value,
  76. };
  77. axios.post("https://magy.giaever.online/tff/api/webflow_collection_events", request).then(resp => {
  78. setResponse({type: 'success', title: "Takk for ditt bidrag!", description: "Vi ser frem til din deltagelse."});
  79. props.clearHandler();
  80. }).catch(err => {
  81. if (err.response) {
  82. setResponse({type: 'error', title: err.response.data['hydra:title'], description: err.response.data['hydra:description']});
  83. } else if (err.request)
  84. setResponse({type: 'error', title: "En feil oppstod.", description: "Vennligst prøv igjen senere."});
  85. else
  86. setResponse({type: 'error', title: "En feil oppstod,", description: err.message});
  87. });
  88. }
  89. }
  90. return (
  91. <React.Fragment>
  92. <center>
  93. <Dialog open={response!= null} onClose={() => {
  94. setResponse(null);
  95. recaptchaRef.current.reset();
  96. }}>
  97. <DialogContent>
  98. <Alert severity={response ? response.type : "info"}>
  99. <AlertTitle>{response ? response.title : "unknown title"}</AlertTitle>
  100. {response ? (
  101. response.type == "error" ?
  102. <ul>{response.description.split("\n").map((l, i) => <li key={i}>{l}</li>)}</ul>
  103. :
  104. response.description
  105. )
  106. : "this is the body"}
  107. </Alert>
  108. </DialogContent>
  109. <DialogActions>
  110. <Button onClick={() => {
  111. setResponse(null);
  112. recaptchaRef.current.reset();
  113. if (response.type != 'error')
  114. window.location.reload();
  115. }} color="secondary">
  116. Ok!
  117. </Button>
  118. </DialogActions>
  119. </Dialog>
  120. <ReCAPTCHA
  121. onChange={onChange}
  122. ref={recaptchaRef}
  123. sitekey={"6LfRxgAVAAAAALlMOyD5h_a6gvWulxGqCC5gKJvd"}
  124. size={"invisible"}
  125. />
  126. </center>
  127. <Button
  128. color={"primary"}
  129. variant={"contained"}
  130. fullWidth
  131. type={"submit"}
  132. onClick={(e) => {
  133. e.preventDefault();
  134. recaptchaRef.current.execute();
  135. }}
  136. >Send skjema</Button>
  137. </React.Fragment>
  138. );
  139. }
  140. const RegForm = (props) => {
  141. const classes = useStyles();
  142. const [inputs, updateInputs] = useState({
  143. dates: [{date: new Date(), duration: 0.5}],
  144. });
  145. const inputHandler = (event) => {
  146. const target = event.target
  147. updateInputs((prevState) => ({...prevState, [target.id]: target.value}));
  148. };
  149. const inputCategoryHandler = (type, event, category) => {
  150. const keys = Object.keys(inputs).filter(idx => idx == type);
  151. let arr = keys.length == 0 ? [] : inputs[keys[0]];
  152. if (event.target.checked)
  153. arr.push(category);
  154. else
  155. arr = arr.filter(item => item.id != category.id);
  156. updateInputs((prevState) => ({...prevState, [type]: arr}));
  157. }
  158. const dateAddHandler = (idx) => {
  159. inputs.dates.splice(idx, 0, {...inputs.dates[idx]});
  160. updateInputs((prevState) => ({...prevState, dates: inputs.dates}));
  161. }
  162. const dateRemoveHandler = (idx) => {
  163. if (inputs.dates.length == 1)
  164. return;
  165. if (!inputs.dates[idx])
  166. return;
  167. updateInputs(prevState => ({...prevState, dates: [...inputs.dates.slice(0, idx), ...inputs.dates.slice(idx+1)]}));
  168. }
  169. const dateSaveHandler = (idx, e) => {
  170. if (!inputs.dates[idx])
  171. return;
  172. inputs.dates[idx].date = e;
  173. updateInputs((prevState) => ({...prevState, dates: inputs.dates}));
  174. }
  175. const dateDurationSaveHandler = (idx, e) => {
  176. if (!inputs.dates[idx])
  177. return;
  178. const target = e.target;
  179. inputs.dates[idx].duration = (target.value >= 0 ? target.value * 1.0 : 0.5).toFixed(2);
  180. updateInputs((prevState) => ({...prevState, dates: inputs.dates}));
  181. }
  182. let formRef = React.createRef();
  183. const clearInputs = () => {
  184. formRef.current.reset();
  185. updateInputs((prevState) => ({
  186. dates: [{date: new Date(), duration: 0.5}]
  187. }));
  188. }
  189. return (
  190. <Container component="main" maxWidth="md">
  191. <CssBaseline />
  192. <Paper className={classes.paper}>
  193. <form className={classes.form} ref={formRef}>
  194. <FormControl component={"fieldset"} className={classes.fieldset}>
  195. <Typography component={"legend"} variant={"h5"}>
  196. Om arrangøren
  197. </Typography>
  198. <Grid container spacing={3}>
  199. <Grid item xs={12} sm={7} md={8}>
  200. <TextField
  201. id={"organizer"}
  202. label={"Navn"}
  203. onChange={inputHandler}
  204. required
  205. fullWidth
  206. autoFocus
  207. />
  208. </Grid>
  209. <Grid item xs={12} sm={5} md={4}>
  210. <TextField
  211. id={"organization_number"}
  212. label={"Org.nummer"}
  213. onChange={inputHandler}
  214. required
  215. fullWidth
  216. />
  217. </Grid>
  218. <Grid item xs={12} sm={6}>
  219. <TextField
  220. id={"contact_name"}
  221. label={"Kontakt person"}
  222. onChange={inputHandler}
  223. required
  224. fullWidth
  225. />
  226. </Grid>
  227. <Grid item xs={12} sm={6}>
  228. <TextField
  229. id={"contact_email"}
  230. label={"Epostadresse"}
  231. onChange={inputHandler}
  232. type={"email"}
  233. required
  234. fullWidth
  235. />
  236. </Grid>
  237. <Grid item xs={12} sm={6}>
  238. <TextField
  239. id={"contact_phone"}
  240. label={"Telefonnummer"}
  241. onChange={inputHandler}
  242. type={"tel"}
  243. required
  244. fullWidth
  245. />
  246. </Grid>
  247. <Grid item xs={12} sm={6}>
  248. <TextField
  249. id={"contact_homepage"}
  250. label={"Hjemmesideadresse (husk «http://»)"}
  251. placeholder={"https:// ...."}
  252. onChange={inputHandler}
  253. required
  254. fullWidth
  255. />
  256. </Grid>
  257. </Grid>
  258. </FormControl>
  259. <FormControl component={"fieldset"} className={classes.fieldset}>
  260. <Typography component={"legend"} variant={"h5"}>
  261. Om arrangementet
  262. </Typography>
  263. <Grid container spacing={3}>
  264. <Grid item xs={12}>
  265. <TextField
  266. id={"event_name"}
  267. label={"Tittel på arrangement"}
  268. onChange={inputHandler}
  269. required
  270. fullWidth
  271. />
  272. </Grid>
  273. {inputs.dates.map((date, idx) => (
  274. <React.Fragment key={idx}>
  275. <Grid item xs={6}>
  276. <MuiPickersUtilsProvider utils={DateFnsUtils} locale={nb}>
  277. <DateTimePicker
  278. fullWidth
  279. format={"do MMMM y HH:mm (h:mm a)"}
  280. ampm={false}
  281. value={date.date}
  282. onChange={e => dateSaveHandler(idx, e)}
  283. margin={"normal"}
  284. />
  285. </MuiPickersUtilsProvider>
  286. </Grid>
  287. <Grid item xs={4}>
  288. <TextField
  289. id={"event_duration"}
  290. label={`Varighet (${date.duration} t = ${(date.duration * 60.0).toFixed(0)} min)`}
  291. onChange={e => dateDurationSaveHandler(idx, e)}
  292. inputProps={{step: 0.25}}
  293. type={"number"}
  294. placeholder={"antall timer"}
  295. value={date.duration ? date.duration : 0.5}
  296. fullWidth
  297. />
  298. </Grid>
  299. <Grid item xs={1}>
  300. <IconButton onClick={() => dateAddHandler(idx)}><Add /></IconButton>
  301. </Grid>
  302. <Grid item xs={1}>
  303. <IconButton onClick={() => dateRemoveHandler(idx)}><Remove /></IconButton>
  304. </Grid>
  305. </React.Fragment>
  306. ))}
  307. <Grid item xs={12} sm={8}>
  308. <TextField
  309. id={'event_location'}
  310. label={"Sted"}
  311. onChange={inputHandler}
  312. placeholder={"Hvor skjer arrangementet?"}
  313. fullWidth
  314. required
  315. />
  316. </Grid>
  317. <Grid item xs={12} sm={4}>
  318. <TextField
  319. id={"event_capacity"}
  320. label={"Kapasitet"}
  321. placeholder={"For eks. antall personer"}
  322. onChange={inputHandler}
  323. required
  324. fullWidth
  325. />
  326. </Grid>
  327. <Grid item xs={12}>
  328. <TextField
  329. id={"event_description"}
  330. label={"Beskrivelse av arrangement"}
  331. onChange={inputHandler}
  332. placeholder={"Forklar detaljert om ditt arrangement - dette skal besøkende lese!"}
  333. multiline
  334. fullWidth
  335. required
  336. />
  337. </Grid>
  338. <Grid item xs={12} sm={6}>
  339. <TextField
  340. id="event_photo"
  341. label={"Photo-link til arrangement (husk «http://»)"}
  342. onChange={inputHandler}
  343. placeholder={"Kan for eks. være en bilde-fil eller mappe med bilder i Dropbox."}
  344. fullWidth
  345. required
  346. />
  347. </Grid>
  348. <Grid item xs={12} sm={6}>
  349. <TextField
  350. id="event_booking"
  351. label={"Link til booking (husk «http://»)"}
  352. onChange={inputHandler}
  353. placeholder={"https:// ..."}
  354. fullWidth
  355. required
  356. />
  357. </Grid>
  358. <Grid item xs={12} sm={6}>
  359. <CategorySelection
  360. cid={"/tff/api/webflow_collection_categories"}
  361. category="Kategorier"
  362. name="categories"
  363. selected={inputs.categories || []}
  364. inputCategoryHandler={inputCategoryHandler}
  365. />
  366. </Grid>
  367. <Grid item xs={12} sm={6}>
  368. <CategorySelection
  369. cid={"/tff/api/webflow_collection_audiences"}
  370. category="Målgrupper"
  371. name="audiences"
  372. selected={inputs.audiences || []}
  373. inputCategoryHandler={inputCategoryHandler}
  374. />
  375. </Grid>
  376. </Grid>
  377. </FormControl>
  378. <Grid container justify={"center"}>
  379. <Grid item xs={8}>
  380. <ReCaptchaSubmitButton text={"Send skjema"}
  381. inputs={inputs}
  382. clearHandler={clearInputs}
  383. />
  384. </Grid>
  385. </Grid>
  386. </form>
  387. </Paper>
  388. </Container>
  389. )
  390. }
  391. const CategorySelection = (props) => {
  392. const [error, setError] = useState(null);
  393. const [isLoading, setIsLoading] = useState(true);
  394. const [availableItems, setAvailableItems] = useState([]);
  395. React.useEffect(() => {
  396. axios.get("https://magy.giaever.online" + props.cid)
  397. .then((response) => {
  398. const data = response.data['hydra:member'];
  399. setAvailableItems(response.data['hydra:member']);
  400. })
  401. .catch((error) => {
  402. setError(error);
  403. })
  404. .then(() => {
  405. setIsLoading(false);
  406. });
  407. }, []);
  408. return (
  409. <React.Fragment>
  410. <DialogCancelOk
  411. buttonDesc={isLoading ? `Laster ${props.category}` : `Velg ${props.category}`}
  412. buttonDisabled={isLoading}
  413. dialogTitle={`Velg ${props.category}`}
  414. >
  415. {isLoading ?
  416. <p>
  417. Laster...
  418. </p>
  419. :
  420. <FormGroup>
  421. {availableItems.map(item => <FormControlLabel
  422. key={item.id}
  423. control={
  424. <Checkbox
  425. checked={props.selected.filter(sitem => sitem.id == item.id).length > 0}
  426. value={item.id}
  427. onChange={() => props.inputCategoryHandler(props.name, event, item)}
  428. />
  429. }
  430. label={item.name}
  431. />
  432. )}
  433. </FormGroup>
  434. }
  435. </DialogCancelOk>
  436. <ul>{props.selected.map(item => <li key={item.id}>{item.name}</li>)}</ul>
  437. </React.Fragment>
  438. )
  439. }
  440. const DialogCancelOk = (props) => {
  441. const [open, setOpen] = useState(false);
  442. const handleClickOpen = () => {
  443. setOpen(true);
  444. };
  445. const handleClickClose = () => {
  446. setOpen(false);
  447. };
  448. return (
  449. <div>
  450. <Button
  451. fullWidth
  452. disabled={props.buttonDisabled}
  453. color={"secondary"}
  454. variant={props.buttonDisabled ? "outlined" : "contained"}
  455. onClick={handleClickOpen}>
  456. {props.buttonDesc}
  457. </Button>
  458. <Dialog
  459. disableBackdropClick
  460. disableEscapeKeyDown
  461. open={open}
  462. onClose={handleClickClose}
  463. >
  464. <DialogTitle>{props.dialogTitle}</DialogTitle>
  465. <DialogContent>
  466. {props.children}
  467. </DialogContent>
  468. <DialogActions>
  469. <Button onClick={handleClickClose} color={"primary"}>
  470. Ferdig
  471. </Button>
  472. </DialogActions>
  473. </Dialog>
  474. </div>
  475. )
  476. }
  477. export default RegForm;