Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/voting with ticket #1

Merged
merged 8 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Loading