Skip to content

Commit

Permalink
Fix ticket interface for sellers (#665)
Browse files Browse the repository at this point in the history
* Fix ticket interface for sellers.

* Clarify type for buyersPerm.

* 🎨 Yay

* Frontend for admin ticket transfering and base props fix.

* 🎉 Add issue ticket

* Frontend for marking tickets as attended/not attended

* 🎨 Split String and handle input edge case

* 🧹 Hehe

* 🚨 Some changes with a fake api endpoint

* 🎉 Some fire UI

* 🐛 Error Response not showing correctly

* 🎨 Brr

* Lint, improve UI language, fix items remaining in cart after deletion through button, fix updating item quantities in cart through button, fix success vs error toast for adding tickets to cart.

* API integration for issuing tickets

* Ticket transfer interface

* Integrate attendance into frontend and add warning popup for un-attending people

* 🎨 Nit

* 🐛 Joy fixes everything

---------

Co-authored-by: Joy Liu <34288846+joyliu-q@users.noreply.github.com>
Co-authored-by: Joy Liu <joyliu.q@gmail.com>
  • Loading branch information
3 people committed Apr 26, 2024
1 parent b57d797 commit 14ae31e
Show file tree
Hide file tree
Showing 15 changed files with 659 additions and 276 deletions.
6 changes: 4 additions & 2 deletions backend/clubs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2540,14 +2540,16 @@ def buyers(self, request, *args, **kwargs):
type: integer
type:
type: string
attended:
type: boolean
---
"""
tickets = Ticket.objects.filter(event=self.get_object()).annotate(
fullname=Concat("owner__first_name", Value(" "), "owner__last_name")
)

buyers = tickets.filter(owner__isnull=False).values(
"fullname", "id", "owner_id", "type", "owner__email"
"fullname", "id", "owner_id", "type", "attended", "owner__email"
)

return Response({"buyers": buyers})
Expand Down Expand Up @@ -5031,7 +5033,7 @@ def partial_update(self, request, *args, **kwargs):
schema:
type: object
properties:
attendance:
attended:
type: boolean
responses:
"200":
Expand Down
6 changes: 1 addition & 5 deletions frontend/components/ClubEditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,7 @@ const ClubForm = ({
{
name: 'tickets',
label: 'Tickets',
content: (
<>
<TicketsViewCard club={club} />
</>
),
content: <TicketsViewCard club={club} />,
},
{
name: 'recruitment',
Expand Down
5 changes: 4 additions & 1 deletion frontend/components/ClubEditPage/EventsImportCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { doApiRequest } from '../../utils'
import { Contact, Icon, Text } from '../common'
import { TextField } from '../FormComponents'
import BaseCard from './BaseCard'
import { WHITE } from '~/constants'

type EventsImportCardProps = {
club: Club
Expand All @@ -29,7 +30,9 @@ export default function EventsImportCard({
onFetchEvents && onFetchEvents()
})
.catch(() => {
toast.error('Failed to fetch events, an unknown error occured.')
toast.error('Failed to fetch events, an unknown error occured.', {
style: { color: WHITE },
})
})
.finally(() => setFetching(false))
}
Expand Down
37 changes: 15 additions & 22 deletions frontend/components/ClubEditPage/TicketsViewCard.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,31 @@
import Link from 'next/link'
import React, { ReactElement } from 'react'

import { doApiRequest } from '~/utils'
import { Club } from '~/types'

import Table from '../common/Table'
import BaseCard from './BaseCard'

export default function TicketsViewCard({ club }): ReactElement {
const GetTicketsHolders = (id) => {
doApiRequest(`/events/${id}/tickets?format=json`, {
method: 'GET',
})
.then((resp) => resp.json())
.then((res) => {
// console.log(res)
})
}

export default function TicketsViewCard({
club,
}: {
club: Club
}): ReactElement {
const eventsTableFields = [
{ label: 'Event Name', name: 'name' },
{
label: '',
name: 'view',
render: (id) => (
<button className="button is-primary is-pulled-right">
<Link href={'/tickets/' + id}>
<a style={{ color: 'white' }} target="blank">
View
</a>
<Link style={{ color: 'white' }} href={`/tickets/${id}`}>
View
</Link>
</button>
),
},
]

// console.log(club.events)
const ticketEvents = club.events.filter((event) => event.ticketed)

return (
Expand All @@ -47,14 +38,16 @@ export default function TicketsViewCard({ club }): ReactElement {
columns={eventsTableFields}
searchableColumns={['name']}
filterOptions={[]}
hideSearch={true}
focusable={true}
hideSearch
focusable
/>
) : (
<>
You don't have any ticketed events, to add create ticketed events or
add ticket offerings, to existing events, go to the events, click
create on the tickets section below the event details.
You don't have any ticketed events. To create a ticketed event or add
ticket offerings to existing events, go to{' '}
<Link href={`/club/${club.code}/edit/events`}>Events</Link> within
this dashboard and click "Create" in the tickets section below event
details.
</>
)}
</BaseCard>
Expand Down
5 changes: 4 additions & 1 deletion frontend/components/EventPage/SyncModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { ReactElement, useEffect, useState } from 'react'
import { toast } from 'react-toastify'
import styled from 'styled-components'

import { CLUBS_GREY, CLUBS_NAVY } from '../../constants'
import { CLUBS_GREY, CLUBS_NAVY, WHITE } from '../../constants'
import { Club } from '../../types'
import { doApiRequest, intersperse } from '../../utils'
import {
Expand Down Expand Up @@ -80,6 +80,9 @@ const SyncModal = (): ReactElement | null => {
} catch (error) {
toast.error(
'Failed to copy! You need to manually copy the URL.',
{
style: { color: WHITE },
},
)
}
}}
Expand Down
6 changes: 5 additions & 1 deletion frontend/components/Settings/BulkEditTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { Icon, Text } from '../common'
import { DateTimeField, SelectField, TextField } from '../FormComponents'
import { fixDeserialize } from '../reports/ReportForm'
import { WHITE } from '~/constants'

/**
* A component where the user can enter a list of club names and get a list of club codes in response.
Expand Down Expand Up @@ -78,7 +79,10 @@ const BulkEditTab = ({ tags, clubfairs, badges }: BulkEditTabProps) => {
if (contents.message) {
toast.info(contents.message, { hideProgressBar: true })
} else if (contents.error) {
toast.error(contents.error, { hideProgressBar: true })
toast.error(contents.error, {
hideProgressBar: true,
style: { color: WHITE },
})
}
} finally {
setSubmitting(false)
Expand Down
123 changes: 52 additions & 71 deletions frontend/components/Settings/TicketTransferModal.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,65 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { toast, TypeOptions } from 'react-toastify'
import styled from 'styled-components'
import { ReactElement, useEffect, useState } from 'react'

import {
ALLBIRDS_GRAY,
CLUBS_GREY,
FOCUS_GRAY,
WHITE,
} from '../../constants/colors'
import { BORDER_RADIUS } from '../../constants/measurements'
import { BODY_FONT } from '../../constants/styles'
import { ClubEvent } from '../../types'
import { SearchInput } from '../SearchBar'
import { doApiRequest } from '~/utils'

const ModalContainer = styled.div`
text-align: left;
position: relative;
`
const ModalBody = styled.div`
padding: 2rem;
`
const SectionContainer = styled.div`
margin-bottom: 1.5rem;
`
import BaseCard from '../ClubEditPage/BaseCard'

const Input = styled.input`
border: 1px solid ${ALLBIRDS_GRAY};
outline: none;
color: ${CLUBS_GREY};
width: 100%;
font-size: 1em;
padding: 8px 10px;
margin: 0px 5px 0px 0px;
background: ${WHITE};
border-radius: ${BORDER_RADIUS};
font-family: ${BODY_FONT};
&:hover,
&:active,
&:focus {
background: ${FOCUS_GRAY};
}
`

const notify = (
msg: ReactElement | string,
type: TypeOptions = 'info',
): void => {
toast[type](msg)
type TicketTransferModalProps = {
id: string
onSuccessfulTransfer: (id: string) => void
}

const TicketTransferModal = (props: {
event: ClubEvent | null
}): ReactElement => {
const [searchInput, setSearchInput] = useState<SearchInput>({})
const TicketTransferModal = ({
id,
onSuccessfulTransfer,
}: TicketTransferModalProps): ReactElement => {
const [recipient, setRecipient] = useState<string | undefined>()
const [recipientError, setRecipientError] = useState<string | undefined>()

const search = () => {
/*
return doApiRequest('/users?search=bfranklin')
.then((resp) => resp.json())
.then(() => {})
*/
}
useEffect(() => {
search()
}, [])
/*
<CoverPhoto
image={large_image_url ?? image_url}
fallback={
<p>{club_name != null ? club_name.toLocaleUpperCase() : 'Event'}</p>
}
/>
setRecipientError(undefined)
}, [id])

<Title>{name}</Title>
*/
function transferTicket() {
if (!recipient) {
setRecipientError('Recipient PennKey is required')
return
}
setRecipientError(undefined)
doApiRequest(`/tickets/${id}/transfer/?format=json`, {
method: 'POST',
body: { username: recipient },
}).then(async (resp) => {
if (resp.ok) {
onSuccessfulTransfer(id)
} else {
setRecipientError((await resp.json()).detail)
}
})
}

return (
<ModalContainer>
<ModalBody></ModalBody>
</ModalContainer>
<BaseCard title="Ticket Transfer">
<input
className="input"
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
onKeyUp={(e) => {
if (e.key === 'Enter') {
transferTicket()
}
}}
placeholder="Recipient PennKey"
/>
{recipientError && <p className="has-text-danger">{recipientError}</p>}
<button
className="button is-success"
onClick={transferTicket}
style={{ marginTop: '15px' }}
>
Transfer Ticket
</button>
</BaseCard>
)
}

Expand Down
Loading

0 comments on commit 14ae31e

Please sign in to comment.