Skip to content

Commit

Permalink
Merge pull request #1 from OpenDDD/feature/voting-with-ticket
Browse files Browse the repository at this point in the history
Feature/voting with ticket
  • Loading branch information
dooman87 authored Nov 6, 2023
2 parents 91dec6a + 8d5ffd7 commit a328ea6
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 53 deletions.
12 changes: 6 additions & 6 deletions .env.local
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
NEXT_PUBLIC_APPINSIGHTS_INSTRUMENTATIONKEY=2f1d6a9c-1b88-4f3d-bba1-64b11ffd2362
NEXT_PUBLIC_GET_SUBMISSIONS_URL=https://dddperth-functions-test.azurewebsites.net/api/GetSubmissions
NEXT_PUBLIC_SUBMIT_VOTE_URL=https://dddperth-functions-test.azurewebsites.net/api/SubmitVote
NEXT_PUBLIC_GET_AGENDA_URL=https://dddperth-functions-test.azurewebsites.net/api/GetAgenda
NEXT_PUBLIC_SUBMIT_FEEDBACK_URL=https://dddperth-functions-test.azurewebsites.net/api/SubmitFeedback
NEXT_PUBLIC_GET_SUBMISSIONS_URL=https://dddmelb-2024.azurewebsites.net/api/GetSubmissions
NEXT_PUBLIC_SUBMIT_VOTE_URL=https://dddmelb-2024.azurewebsites.net/api/SubmitVote
NEXT_PUBLIC_GET_AGENDA_URL=https://dddmelb-2024.azurewebsites.net/api/GetAgenda
NEXT_PUBLIC_SUBMIT_FEEDBACK_URL=https://dddmelb-2024.azurewebsites.net/api/SubmitFeedback
NEXT_PUBLIC_TESTING_MODE=false
NEXT_PUBLIC_BASE_URL=https://www.dddmelbourne.com
NEXT_PUBLIC_ELO_PAIR=https://dddperth-functions-test.azurewebsites.net/api/EloVotingGetPair
NEXT_PUBLIC_ELO_VOTE=https://dddperth-functions-test.azurewebsites.net/api/EloVotingSubmitPair
NEXT_PUBLIC_ELO_PAIR=https://dddmelb-2024.azurewebsites.net/api/EloVotingGetPair
NEXT_PUBLIC_ELO_VOTE=https://dddmelb-2024.azurewebsites.net/api/EloVotingSubmitPair
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ on:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- melb-2024

jobs:
build_and_deploy_job:
Expand Down
1 change: 1 addition & 0 deletions components/Voting/EloVote.styled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const StyledEloVoteContainer = styled('div')<StyledEloVoteContainerProps>
marginInlineStart: 'auto',
marginInlineEnd: 'auto',
maxBlockSize: '65vh',
minHeight: '70vh',
}),
({ variant }) => ({
[breakpointMax('md')]: {
Expand Down
29 changes: 29 additions & 0 deletions components/Voting/landing.styled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import styled from '@emotion/styled'
import { Button } from 'components/global/Button/Button'
import { Text } from 'components/global/text'
import { calcRem } from 'components/utils/styles/calcRem'
import { DialogContent } from '@reach/dialog'
import { breakpointMax } from '../utils/styles/breakpoints'

export const StyledLandingContainer = styled('div')(() => ({
inlineSize: '100%',
Expand Down Expand Up @@ -29,4 +31,31 @@ export const StyledButton = styled(Button)({
display: 'block',
marginInlineStart: 'auto',
marginInlineEnd: 'auto',
marginTop: calcRem(20),
})

export const StyledOverlayButtons = styled('div')(() => ({
padding: calcRem(20),
'*:first-of-type': {
marginRight: calcRem(20),
},
}))

export const StyledForm = styled('form')(() => ({
padding: `${calcRem(20)} 0`,
label: {
display: 'inline-block',
width: 100,
},
input: '100%',
}))

export const StyledFormRow = styled('div')({
marginBottom: calcRem(30),
})

export const StyledDialogContent = styled(DialogContent)({
[breakpointMax('sm')]: {
width: '100%',
},
})
16 changes: 8 additions & 8 deletions config/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ export default function getConferenceActions(conference: Conference, dates: Date
})
}

if (dates.VotingOpen) {
actions.push({
Category: 'voting',
Title: 'Vote for agenda',
Url: '/vote',
})
}

if (dates.RegistrationOpen) {
actions.push({
Category: 'tickets',
Expand All @@ -27,6 +19,14 @@ export default function getConferenceActions(conference: Conference, dates: Date
})
}

if (dates.VotingOpen) {
actions.push({
Category: 'voting',
Title: 'Vote for agenda',
Url: '/vote',
})
}

if (dates.RegistrationOpen) {
actions.push({
Category: 'training',
Expand Down
2 changes: 1 addition & 1 deletion config/conference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const registrationOpenWave2From = zonedTimeToUtc('2023-10-15T08:00:00', tz)
const registrationOpenUntil = null
const presentationSubmissionsOpenFrom = zonedTimeToUtc('2023-09-01T08:00:00', tz)
const presentationSubmissionsOpenUntil = zonedTimeToUtc('2023-11-01T23:59:59', tz)
const votingOpenFrom = zonedTimeToUtc('2023-11-08T17:00:00', tz)
const votingOpenFrom = zonedTimeToUtc('2023-11-08T00:00:00', tz)
const votingOpenUntil = zonedTimeToUtc('2023-11-20T23:59:59', tz)
const agendaPublishedFrom = zonedTimeToUtc('2023-12-01T17:00:00', tz)
const feedbackOpenFrom = toDate(date)
Expand Down
36 changes: 33 additions & 3 deletions pages/vote/elo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ async function postPair(winningSessionId: string, losingSessionId: string, isDra
LoserSessionId: losingSessionId,
IsDraw: isDraw,
VoterSessionId: getSessionId(),
VoterTicket: Cookies.get('vote-ticket'),
VoterLastname: Cookies.get('vote-lastname'),
}

try {
Expand All @@ -66,11 +68,29 @@ async function postPair(winningSessionId: string, losingSessionId: string, isDra
}
}

export default function Elo({ sessions, votingSessionId, userDefinedLayout = 'stacked' }: EloProps): JSX.Element {
export default function Elo({ sessions, votingSessionId, userDefinedLayout = 'expanded' }: EloProps): JSX.Element {
const { conference } = useConfig()
const [sessionPair, setSessionPair] = useState<SessionPair>(sessions)
const [nextPair, setNextPair] = useState<SessionPair | undefined>(undefined)
const [layoutVariant, setLayoutVariant] = useState<LayoutVariant>(userDefinedLayout)
const [loading, setLoading] = useState(false)

const spinner =
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: rgb(255, 255, 255); display: block; shape-rendering: auto;" width="50px" height="50px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">\n' +
' <g>\n' +
' <g transform="translate(50 50)">\n' +
' <g transform="scale(0.8)">\n' +
' <g transform="translate(-50 -58)">\n' +
' <path fill="#4450a2" d="M27.1,79.4c-1.1,0.6-2.4,1-3.7,1c-2.6,0-5.1-1.4-6.4-3.7c-2-3.5-0.8-8,2.7-10.1c1.1-0.6,2.4-1,3.7-1c2.6,0,5.1,1.4,6.4,3.7 C31.8,72.9,30.6,77.4,27.1,79.4z"></path>\n' +
' <path fill="#c66aa9" d="M72.9,79.4c1.1,0.6,2.4,1,3.7,1c2.6,0,5.1-1.4,6.4-3.7c2-3.5,0.8-8-2.7-10.1c-1.1-0.6-2.4-1-3.7-1c-2.6,0-5.1,1.4-6.4,3.7 C68.2,72.9,69.4,77.4,72.9,79.4z"></path>\n' +
' <circle fill="#f8a23b" cx="50" cy="27" r="7.4"></circle>\n' +
' <path fill="#4450a2" d="M86.5,57.5c-3.1-1.9-6.4-2.8-9.8-2.8c-0.5,0-0.9,0-1.4,0c-0.4,0-0.8,0-1.1,0c-2.1,0-4.2-0.4-6.2-1.2 c-0.8-3.6-2.8-6.9-5.4-9.3c0.4-2.5,1.3-4.8,2.7-6.9c2-2.9,3.2-6.5,3.2-10.4c0-10.2-8.2-18.4-18.4-18.4c-0.3,0-0.6,0-0.9,0 C39.7,9,32,16.8,31.6,26.2c-0.2,4.1,1,7.9,3.2,11c1.4,2.1,2.3,4.5,2.7,6.9c-2.6,2.5-4.6,5.7-5.4,9.3c-1.9,0.7-4,1.1-6.1,1.1 c-0.4,0-0.8,0-1.2,0c-0.5,0-0.9-0.1-1.4-0.1c-3.1,0-6.3,0.8-9.2,2.5c-9.1,5.2-12,17-6.3,25.9c3.5,5.4,9.5,8.4,15.6,8.4 c2.9,0,5.8-0.7,8.5-2.1c3.6-1.9,6.3-4.9,8-8.3c1.1-2.3,2.7-4.2,4.6-5.8c1.7,0.5,3.5,0.8,5.4,0.8c1.9,0,3.7-0.3,5.4-0.8 c1.9,1.6,3.5,3.5,4.6,5.7c1.5,3.2,4,6,7.4,8c2.9,1.7,6.1,2.5,9.2,2.5c6.6,0,13.1-3.6,16.4-10C97.3,73.1,94.4,62.5,86.5,57.5z M29.6,83.7c-1.9,1.1-4,1.6-6.1,1.6c-4.2,0-8.4-2.2-10.6-6.1c-3.4-5.9-1.4-13.4,4.5-16.8c1.9-1.1,4-1.6,6.1-1.6 c4.2,0,8.4,2.2,10.6,6.1C37.5,72.8,35.4,80.3,29.6,83.7z M50,39.3c-6.8,0-12.3-5.5-12.3-12.3S43.2,14.7,50,14.7 c6.8,0,12.3,5.5,12.3,12.3S56.8,39.3,50,39.3z M87.2,79.2c-2.3,3.9-6.4,6.1-10.6,6.1c-2.1,0-4.2-0.5-6.1-1.6 c-5.9-3.4-7.9-10.9-4.5-16.8c2.3-3.9,6.4-6.1,10.6-6.1c2.1,0,4.2,0.5,6.1,1.6C88.6,65.8,90.6,73.3,87.2,79.2z"></path>\n' +
' </g>\n' +
' </g>\n' +
' </g>\n' +
' <animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="3.571428571428571s" keyTimes="0;1" values="0 50 50;360 50 50"></animateTransform>\n' +
' </g>\n' +
' </svg>\n'

useEffect(() => {
// the cookie is used for reloads, it is read in `getServerSideProps`
Expand All @@ -87,8 +107,10 @@ export default function Elo({ sessions, votingSessionId, userDefinedLayout = 'st

useEffect(() => {
async function getPair() {
setLoading(true)
const data = await fetchPair(votingSessionId)
setNextPair(data)
setLoading(false)
}

if (typeof nextPair === 'undefined') {
Expand All @@ -97,6 +119,7 @@ export default function Elo({ sessions, votingSessionId, userDefinedLayout = 'st
}, [next, nextPair, votingSessionId])

async function sessionChoiceHandler(winningSession: EloSession, losingSession: EloSession, isDraw = false) {
setLoading(true)
await postPair(winningSession.Id, losingSession.Id, isDraw)
logEvent('voting', 'vote', {
variant: layoutVariant,
Expand Down Expand Up @@ -127,7 +150,6 @@ export default function Elo({ sessions, votingSessionId, userDefinedLayout = 'st
onSessionChoice={sessionChoiceHandler}
layout={layoutVariant}
/>

<StyledEloVoteFooter>
<StyledEloButtonContainer>
<StyledVoteButton
Expand All @@ -138,6 +160,7 @@ export default function Elo({ sessions, votingSessionId, userDefinedLayout = 'st
window.scrollTo(0, 0)
sessionChoiceHandler(sessionPair.SubmissionA, sessionPair.SubmissionB, false)
}}
disabled={loading}
>
Option 1
</StyledVoteButton>
Expand All @@ -149,6 +172,7 @@ export default function Elo({ sessions, votingSessionId, userDefinedLayout = 'st
window.scrollTo(0, 0)
sessionChoiceHandler(sessionPair.SubmissionA, sessionPair.SubmissionB, true)
}}
disabled={loading}
>
It's a Draw!
</StyledVoteButton>
Expand All @@ -160,6 +184,7 @@ export default function Elo({ sessions, votingSessionId, userDefinedLayout = 'st
window.scrollTo(0, 0)
sessionChoiceHandler(sessionPair.SubmissionB, sessionPair.SubmissionA, false)
}}
disabled={loading}
>
Option 2
</StyledVoteButton>
Expand All @@ -173,6 +198,11 @@ export default function Elo({ sessions, votingSessionId, userDefinedLayout = 'st
/>
Change layout? <span>{layoutVariant === 'stacked' ? 'Expand abstracts' : 'Stack talks'}</span>
</StyledLayoutLabel>
<div style={{ minHeight: 50 }}>{loading && <span dangerouslySetInnerHTML={{ __html: spinner }} />}</div>
<small>
Keep voting for as many talks as you like. You can leave and come back any time until the closing date and
your votes will be saved.
</small>
</StyledEloVoteFooter>
</Main>
)
Expand All @@ -189,7 +219,7 @@ export const getServerSideProps: GetServerSideProps<EloProps> = async (context)
const validLayouts: LayoutVariant[] = ['expanded', 'stacked']
const userDefinedLayout: LayoutVariant = [...(validLayouts as string[])].includes(layoutCookie)
? (layoutCookie as LayoutVariant)
: 'stacked'
: 'expanded'

if (!dates.VotingOpen) {
return { notFound: true }
Expand Down
147 changes: 114 additions & 33 deletions pages/vote/landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,25 @@ import { Main } from 'layouts/main'
import { GetServerSideProps } from 'next'
import { getCommonServerSideProps } from 'components/utils/getCommonServerSideProps'
import { Text } from 'components/global/text'
import { useRouter } from 'next/router'
import { PRIVACY_ACCEPTED } from '../../components/Voting/VoteConst'
import Cookies from 'js-cookie'
import { StyledButton, StyledHeader, StyledIntro, StyledLandingContainer } from '../../components/Voting/landing.styled'
import {
StyledButton,
StyledDialogContent,
StyledForm,
StyledFormRow,
StyledHeader,
StyledIntro,
StyledLandingContainer,
StyledOverlayButtons,
} from '../../components/Voting/landing.styled'
import { formatInTimeZone } from 'date-fns-tz'
import React, { FormEvent, Fragment } from 'react'
import { DialogOverlay } from '@reach/dialog'
import { Button, ButtonAnchor } from '../../components/global/Button/Button'
import '@reach/dialog/styles.css'
import Link from 'next/link'
import { useRouter } from 'next/router'

type VoteLandingProps = {
instance: string
Expand All @@ -16,46 +30,113 @@ type VoteLandingProps = {

const BUTTON_LABEL = 'Start Voting!'

export default function VoteLanding({ instance, votingFinished }: VoteLandingProps): JSX.Element {
// export default function VoteLanding({ instance, votingFinished }: VoteLandingProps): JSX.Element {
export default function VoteLanding({ instance }: VoteLandingProps): JSX.Element {
const { conference } = useConfig()
const router = useRouter()

function onClickHandler() {
function onSubmitForm(e: FormEvent) {
e.preventDefault()
const formData = new FormData(e.target as HTMLFormElement)

Cookies.set(PRIVACY_ACCEPTED, 'true', { expires: 90 })
Cookies.set('vote-ticket', formData.get('ticket'), { expires: 90 })
Cookies.set('vote-lastname', formData.get('lastname'), { expires: 90 })
router.push(`/vote/voting`)
}

return (
<Main title="Vote" description={`${conference.Name} voting page.`}>
<StyledLandingContainer>
<StyledHeader tag="h1">{`${instance} Conference Voting`}</StyledHeader>
<StyledIntro>Here's how voting works:</StyledIntro>
<Text>
You'll be presented with a couple of talk options. Have a read of the abstract and simply select the talk
which sounds the best to you based on your interests. If you really can't pick between the two, simply choose
"It's a draw!".
</Text>
<Text>
Once you've made your selection, two new options will appear. You can continue to vote on the options
presented for as long as you like - every vote will count towards formulating the best agenda possible for
this year.
</Text>
<Text>
Voting closes on {votingFinished}, so you have between now and then to have your say. You can leave and come
back any time until the closing day to get your votes in.
</Text>
<Text>Happy Voting!</Text>
const [showDialog, setShowDialog] = React.useState(true)
const [showBuyTicket, setShowBuyTicket] = React.useState(false)
const [showBoughtTicket, setShowBoughtTicket] = React.useState(false)
const close = () => setShowDialog(false)

<Text>
By selecting <em>'{BUTTON_LABEL}'</em> I have read and accepted the{' '}
<a href="/privacy">DDDPerth Privacy statement</a>.
</Text>
return (
<Fragment>
<DialogOverlay isOpen={showDialog} onDismiss={close}>
<StyledDialogContent style={{ textAlign: 'center' }}>
{!showBuyTicket && !showBoughtTicket && (
<Fragment>
<p>Have you bought your ticket to DDD Melbourne?</p>

<StyledButton kind="primary" onClick={onClickHandler}>
{BUTTON_LABEL}
</StyledButton>
</StyledLandingContainer>
</Main>
<StyledOverlayButtons>
<Button kind="primary" onClick={() => setShowBoughtTicket(true)}>
Yes
</Button>
<Button kind="secondary" onClick={() => setShowBuyTicket(true)}>
No
</Button>
</StyledOverlayButtons>
</Fragment>
)}
{showBuyTicket && (
<Fragment>
<Text>
Did you know that ticket holder votes count more? You can buy your ticket{' '}
<Link href="/tickets">here</Link> for only {conference.TicketPrice}.
</Text>
<StyledOverlayButtons>
<ButtonAnchor kind="primary" href="/tickets">
Get Ticket
</ButtonAnchor>
<Button kind="secondary" onClick={() => close()}>
Be Like That
</Button>
</StyledOverlayButtons>
</Fragment>
)}
{showBoughtTicket && (
<Fragment>
<Text>Wonderful! Enjoy voting! :)</Text>
<Button kind="primary" onClick={() => close()}>
Close
</Button>
</Fragment>
)}
</StyledDialogContent>
</DialogOverlay>
<Main title="Vote" description={`${conference.Name} voting page.`}>
<StyledLandingContainer>
<StyledHeader tag="h1">{`${instance} Conference Voting`}</StyledHeader>
<StyledIntro>Here's how voting works:</StyledIntro>
<ol>
<li>Enter your {conference.Name} ticket number and last name, then press ‘Start voting!’. </li>
<li>
No ticket? Just press ‘Start voting!’ to begin. (<Link href="/tickets">Buying a ticket</Link> helps your
vote count more!)
</li>
<li>
You’ll see two talks next. Read the session information and pick your favourite of the two. If you can’t
decide, choose “It’s a draw!”
</li>
<li>Once you’ve made your selection, you’ll get two new talks to pick from. </li>
<li>
Keep voting for as many talks as you like. You can leave and come back any time until the closing date and
your votes will be saved.
</li>
</ol>
<p>
<strong>
Voting closes on{' '}
{formatInTimeZone(conference.VotingOpenUntil, conference.TimeZone, "iiii, d MMMM 'at' hh:mma")}
</strong>
</p>
<StyledForm onSubmit={onSubmitForm}>
<StyledFormRow>
<label htmlFor="ticket">Ticket: </label>
<input type="text" id="ticket" name="ticket" />
</StyledFormRow>
<StyledFormRow>
<label htmlFor="lastname">Last Name: </label>
<input type="text" id="lastname" name="lastname" />
</StyledFormRow>
<img src="/static/voting/ticket-example.png" alt="Ticket number example" />
<StyledButton kind="primary" type="submit">
{BUTTON_LABEL}
</StyledButton>
</StyledForm>
</StyledLandingContainer>
</Main>
</Fragment>
)
}

Expand Down
Binary file added public/static/voting/ticket-example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit a328ea6

Please sign in to comment.