diff --git a/src/cms/config/collections/defaultPagesCollection/badgeQrPage/index.js b/src/cms/config/collections/defaultPagesCollection/badgeQrPage/index.js new file mode 100644 index 00000000..1d743705 --- /dev/null +++ b/src/cms/config/collections/defaultPagesCollection/badgeQrPage/index.js @@ -0,0 +1,23 @@ +import { + booleanField +} from "../../../fields"; + +import { + BADGE_QR_PAGE_FILE_PATH +} from "@utils/filePath"; + +const badgeQrPage = { + label: "Badge QR Page", + name: "badge-qr-page", + file: BADGE_QR_PAGE_FILE_PATH, + fields: [ + booleanField({ + label: "Enable Badge QR Page", + name: "enabled", + required: false + }) + ] +}; + +export default badgeQrPage; + \ No newline at end of file diff --git a/src/cms/config/collections/defaultPagesCollection/badgeQrPage/typeDefs.js b/src/cms/config/collections/defaultPagesCollection/badgeQrPage/typeDefs.js new file mode 100644 index 00000000..910bc4dd --- /dev/null +++ b/src/cms/config/collections/defaultPagesCollection/badgeQrPage/typeDefs.js @@ -0,0 +1,6 @@ + +module.exports = ` + type BadgeQrPageJson implements Node { + enabled: Boolean + } +`; \ No newline at end of file diff --git a/src/cms/config/collections/defaultPagesCollection/index.js b/src/cms/config/collections/defaultPagesCollection/index.js index a715aead..d45c1c6a 100644 --- a/src/cms/config/collections/defaultPagesCollection/index.js +++ b/src/cms/config/collections/defaultPagesCollection/index.js @@ -7,6 +7,7 @@ import lobbyPage from "./lobbyPage"; import expoHallPage from "./expoHallPage"; import invitationsRejectPage from "./invitationsRejectPage"; import mySchedulePage from "./mySchedulePage"; +import badgeQrPage from "./badgeQrPage"; const defaultPagesCollection = { ...collectionDefaults({ @@ -18,7 +19,8 @@ const defaultPagesCollection = { lobbyPage, expoHallPage, invitationsRejectPage, - mySchedulePage + mySchedulePage, + badgeQrPage ] }; diff --git a/src/cms/config/collections/defaultPagesCollection/typeDefs.js b/src/cms/config/collections/defaultPagesCollection/typeDefs.js index 3861514e..b7e06bc4 100644 --- a/src/cms/config/collections/defaultPagesCollection/typeDefs.js +++ b/src/cms/config/collections/defaultPagesCollection/typeDefs.js @@ -3,11 +3,13 @@ const lobbyPageTypeDefs = require("./lobbyPage/typeDefs"); const expoHallPageTypeDefs = require("./expoHallPage/typeDefs"); const invitationsRejectPageTypeDefs = require("./invitationsRejectPage/typeDefs"); const mySchedulePageTypeDefs = require("./mySchedulePage/typeDefs"); +const badgeQrPageTypeDefs = require("./badgeQrPage/typeDefs") module.exports = [ marketingPageTypeDefs, lobbyPageTypeDefs, expoHallPageTypeDefs, invitationsRejectPageTypeDefs, - mySchedulePageTypeDefs + mySchedulePageTypeDefs, + badgeQrPageTypeDefs ].join(""); diff --git a/src/components/Navbar/index.js b/src/components/Navbar/index.js index e40c15b4..8952128d 100644 --- a/src/components/Navbar/index.js +++ b/src/components/Navbar/index.js @@ -4,7 +4,7 @@ import { connect } from "react-redux"; import { navigate } from "gatsby"; import NavbarTemplate from "./template"; -import { userHasAccessLevel, VIRTUAL_ACCESS_LEVEL } from "@utils/authorizedGroups"; +import { userHasAccessLevel, VIRTUAL_ACCESS_LEVEL, userHasCheckedInBadge } from "@utils/authorizedGroups"; import { getDefaultLocation } from "@utils/loginUtils"; import { PHASES } from "@utils/phasesUtils"; @@ -29,6 +29,8 @@ const Navbar = ({ userProfile ? userHasAccessLevel(userProfile.summit_tickets, VIRTUAL_ACCESS_LEVEL) : false , [userProfile]); + const hasSummitHallCheckedIn = userProfile ? userHasCheckedInBadge(userProfile.summit_tickets) : false; + const defaultPath = getDefaultLocation(eventRedirect, hasVirtualBadge); const meetsUserRequirement = (userRequirement) => { @@ -84,7 +86,9 @@ const Navbar = ({ (item.pageRestriction.includes(PAGE_RESTRICTIONS.marketing) && isMarketingPage(currentPath)) || (item.pageRestriction.includes(PAGE_RESTRICTIONS.lobby) && isLobbyPage(currentPath)) || (item.pageRestriction.includes(PAGE_RESTRICTIONS.show) && isShowPage(currentPath)) || - (item.pageRestriction.includes(PAGE_RESTRICTIONS.customPage) && isCustomPage(currentPath)); + (item.pageRestriction.includes(PAGE_RESTRICTIONS.badge) && hasSummitHallCheckedIn) || + (item.pageRestriction.includes(PAGE_RESTRICTIONS.customPage) && isCustomPage(currentPath)) + ; return item.display && meetsUserRequirement(item.userRequirement) && diff --git a/src/content/badge-qr-page/index.json b/src/content/badge-qr-page/index.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/src/content/badge-qr-page/index.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/content/navbar/index.json b/src/content/navbar/index.json index 348cf82a..e13955d9 100644 --- a/src/content/navbar/index.json +++ b/src/content/navbar/index.json @@ -9,6 +9,15 @@ "ANY" ] }, + { + "title": "Show Badge QR", + "link": "/a/badge", + "display": true, + "requiresAuth": true, + "pageRestriction": [ + "BADGE" + ] + }, { "title": "My Tickets", "link": "/a/my-tickets", diff --git a/src/pages/a/[...].js b/src/pages/a/[...].js index 8e04658d..d5ea58d0 100644 --- a/src/pages/a/[...].js +++ b/src/pages/a/[...].js @@ -15,12 +15,14 @@ import ShowOpenRoute from "../../routes/ShowOpenRoute"; import WithBadgeRoute from "../../routes/WithBadgeRoute"; import PosterDetailPage from "../../templates/poster-detail-page"; import MyTicketsPage from "../../templates/my-tickets-page"; +import BadgePage from "../../templates/badge-page"; import withRealTimeUpdates from "../../utils/real_time_updates/withRealTimeUpdates"; import withFeedsWorker from "../../utils/withFeedsWorker"; import Seo from "../../components/Seo"; import Link from "../../components/Link"; import { titleFromPathname } from "../../utils/urlFormating"; import {graphql} from "gatsby"; +import WithAttendeeCheckedInRoute from "../../routes/WithAttendeeCheckedInRoute"; const mySchedulePage = ({ location, summitPhase,isLoggedUser, user, allowClick, title, key }) => { return { - const { mySchedulePageJson } = data; + const { mySchedulePageJson, badgeQrPageJson } = data; + + console.log("CHECK!", badgeQrPageJson); return ( @@ -88,13 +95,18 @@ const App = ({ isLoggedUser, user, summitPhase, allowClick = true, data }) => { { mySchedulePageJson.needsTicketAuthz && mySchedulePage({location, summitPhase,isLoggedUser, user, allowClick, title: mySchedulePageJson.title, key: mySchedulePageJson.key }) } + {badgeQrPageJson.enabled && + + + + } - + diff --git a/src/routes/WithAttendeeCheckedInRoute.js b/src/routes/WithAttendeeCheckedInRoute.js new file mode 100644 index 00000000..8e40981d --- /dev/null +++ b/src/routes/WithAttendeeCheckedInRoute.js @@ -0,0 +1,41 @@ +import React, {useEffect, useState, useMemo} from "react"; +import {connect} from "react-redux"; +import {navigate} from "gatsby"; +import { userHasCheckedInBadge } from "../utils/authorizedGroups"; + +/** + * + * @param children + * @param isLoggedIn + * @param location + * @param userProfile + * @returns {JSX.Element|null|*} + * @constructor + */ +const withAttendeeCheckedIn = ({ + children, + isLoggedIn, + location, + userProfile +}) => { + + const isAttendeeCheckedIn = userHasCheckedInBadge(userProfile.summit_tickets); + + if (!isLoggedIn) { + navigate("/", {state: {backUrl: `${location.pathname}`,},}); + return null; + } + + // has no checked badge -> redirect + if (!isAttendeeCheckedIn) { + navigate("/", {state: {backUrl: `${location.pathname}`,},}); + } + + return children; +}; + +const mapStateToProps = ({userState}) => ({ + userProfile: userState.userProfile +}); + +export default connect(mapStateToProps, {})(withAttendeeCheckedIn); diff --git a/src/templates/badge-page.js b/src/templates/badge-page.js new file mode 100644 index 00000000..cecc3b40 --- /dev/null +++ b/src/templates/badge-page.js @@ -0,0 +1,114 @@ +import React, { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' + +import Dropdown from 'openstack-uicore-foundation/lib/components/inputs/dropdown' +import QRCode from "react-qr-code"; + +import Layout from '../components/Layout' +import { userHasCheckedInBadge } from '../utils/authorizedGroups'; + +export const BadgePageTemplate = ({ user }) => { + + const hasBadgeChecked = userHasCheckedInBadge(user.summit_tickets); + + const [currentTicket, setCurrentTicket] = useState(null); + const [userTickets, setUserTickets] = useState([]); + const [badgesDDL, setBadgeDDL] = useState([]); + + useEffect(() => { + // filter tickets with a badge that has access level IN_PERSON + const inPersonTickets = user?.summit_tickets.filter(t => t.badge?.type?.access_levels.some((al) => al.name.includes("IN_PERSON"))); + setUserTickets(inPersonTickets); + const formattedTickets = inPersonTickets.map(e => ({ label: e.number, value: e.id })); + setBadgeDDL(formattedTickets || []); + }, []); + + useEffect(() => { + const firstTicket = userTickets.find(e => e.qr_code); + setCurrentTicket(firstTicket); + }, [userTickets]); + + const handleTicketChange = (ev) => { + const { target: { value } } = ev; + const newTicket = user.summit_tickets.find(e => e.id === value); + setCurrentTicket(newTicket); + } + + return ( +
+ +

Badge QR

+
+
+ ({ + ...base, + width: "100%", + }), + control: (base) => ({ + ...base, + width: "100%", + }), + option: (base) => ({ + ...base, + width: 'max-content', + minWidth: '100%' + }), + menu: (base) => ({ + ...base, + maxWidth: "100%", + }), + }} + /> +
+
+
+
+ {currentTicket && + + } +
+
+
+ ) +}; + +const BadgePage = ( + { + location, + user, + } +) => { + return ( + + + + ) +}; + +BadgePage.propTypes = { + user: PropTypes.object, +}; + +BadgePageTemplate.propTypes = { + user: PropTypes.object +}; + +const mapStateToProps = ({ userState }) => ({ + user: userState.userProfile, +}); + +export default connect(mapStateToProps, {})(BadgePage); \ No newline at end of file diff --git a/src/utils/authorizedGroups.js b/src/utils/authorizedGroups.js index 333493f0..6fdad347 100644 --- a/src/utils/authorizedGroups.js +++ b/src/utils/authorizedGroups.js @@ -53,3 +53,7 @@ export const filterEventsByAccessLevels = (originalEvents , user) => { return isAuthorizedBadge(ev, summitTickets); }); } + +export const userHasCheckedInBadge = (summitTickets) => { + return summitTickets.some(t => t.owner.summit_hall_checked_in === true); +} \ No newline at end of file diff --git a/src/utils/filePath.js b/src/utils/filePath.js index 1971c41c..69f01b2f 100644 --- a/src/utils/filePath.js +++ b/src/utils/filePath.js @@ -38,6 +38,7 @@ const MARKETING_SETTINGS_FILE_PATH = `${DATA_DIR_PATH}/marketing-settings.json`; const MAINTENANCE_PATH_NAME = `maintenance`; const EXPO_HALL_PAGE_FILE_PATH = `${STATIC_CONTENT_DIR_PATH}/expo-hall-page/index.json`; const INVITATIONS_REJECT_PAGE_FILE_PATH = `${STATIC_CONTENT_DIR_PATH}/invitations-reject-page/index.json`; +const BADGE_QR_PAGE_FILE_PATH = `${STATIC_CONTENT_DIR_PATH}/badge-qr-page/index.json`; const SPONSORS_FILE_NAME = "sponsors.json"; const SPONSORS_FILE_PATH = `${STATIC_CONTENT_DIR_PATH}/${SPONSORS_FILE_NAME}`; const CMS_FONT_FILE_PATH = "/static/fonts/" @@ -94,6 +95,7 @@ exports.MARKETING_SETTINGS_FILE_PATH = MARKETING_SETTINGS_FILE_PATH; exports.MAINTENANCE_PATH_NAME = MAINTENANCE_PATH_NAME; exports.EXPO_HALL_PAGE_FILE_PATH = EXPO_HALL_PAGE_FILE_PATH; exports.INVITATIONS_REJECT_PAGE_FILE_PATH = INVITATIONS_REJECT_PAGE_FILE_PATH; +exports.BADGE_QR_PAGE_FILE_PATH = BADGE_QR_PAGE_FILE_PATH; exports.SPONSORS_FILE_PATH = SPONSORS_FILE_PATH; exports.CMS_FONT_FILE_PATH = CMS_FONT_FILE_PATH; exports.PAYMENTS_FILE_PATH = PAYMENTS_FILE_PATH; diff --git a/src/utils/pageAccessConstants.js b/src/utils/pageAccessConstants.js index 1a0cdb17..ba1adc39 100644 --- a/src/utils/pageAccessConstants.js +++ b/src/utils/pageAccessConstants.js @@ -10,6 +10,7 @@ const PAGE_RESTRICTIONS = { marketing: "MARKETING", lobby: "LOBBY", show: "SHOW", + badge: "BADGE", customPage: "CUSTOM_PAGE" };