Skip to content

Commit

Permalink
Merge branch 'develop' into feature/ticket-profile-view-todos
Browse files Browse the repository at this point in the history
  • Loading branch information
connorkm2 authored Nov 22, 2024
2 parents 437d256 + 4cc6bce commit 01b8ed4
Show file tree
Hide file tree
Showing 23 changed files with 718 additions and 95 deletions.
50 changes: 50 additions & 0 deletions app/api/admin/epos/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createClerkClient } from '@clerk/backend';
import { auth } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
// import Pusher from 'pusher'

// const pusher = new Pusher({
// appId: process.env.PUSHER_APP_ID,
// key: process.env.NEXT_PUBLIC_PUSHER_APP_KEY,
// secret: process.env.PUSHER_APP_SECRET,
// cluster: "eu",
// useTLS: true
// });


export async function POST(request: Request) {
const body = await request.json()
console.log(body)

const clerkClient = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY });
const {userId} = await auth();

if(!userId){
return Response.json({error: "User is not signed in."}, { status: 401 });
}

const requestingUser = await clerkClient.users.getUser(userId);
if(!requestingUser.publicMetadata.admin){
return Response.json({error: "User is does not have permissions."}, { status: 401 });
}

const checkoutUrl = process.env.LAMBDA_CHECKOUT_COMPLETE_INPERSON

const checkoutResponse = await fetch(checkoutUrl, {
method: 'POSt',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body)
})
if(!checkoutResponse.ok) {
const checkoutData = await checkoutResponse.json()
return NextResponse.json({...checkoutData, generated_at: new Date().toISOString() }, {status: checkoutResponse.status})
}
const checkoutData = await checkoutResponse.json()
const ticket_number = checkoutData.ticket_number
// const ticket_number = 2212652493 //! This should be returned
// console.log("checkoutData",checkoutData)
return NextResponse.json({ ticket_number: ticket_number, generated_at: new Date().toISOString() })
// }
}
45 changes: 43 additions & 2 deletions app/api/izettle/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
import { NextResponse} from 'next/server'
import Pusher from 'pusher'

const pusher = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.NEXT_PUBLIC_PUSHER_APP_KEY,
secret: process.env.PUSHER_APP_SECRET,
cluster: "eu",
useTLS: true
});


export async function POST(request: Request) {
console.log(request)
return NextResponse.json({ generated_at: new Date().toISOString() })
const body = await request.json()
const eventName = body.eventName
const payload = JSON.parse(body.payload)
console.log(eventName)
console.log(payload)
// console.log(JSON.stringify(JSON.stringify(payload)))

if(eventName == 'TestMessage') {
return NextResponse.json({ generated_at: new Date().toISOString() })
} else {
// console.log("request:", request)
// console.log("body:",body)
// console.log("payload:",payload, payload.products.map((product) => product.category))
const channel = "card-payments"

const tills = payload.products.map((product) => product?.sku) as string[]
const notification = {
amount: payload.amount,
created: payload.created,
timestamp: payload.timestamp,
payment_ref: payload.payments.map((payment) => payment.attributes.referenceNumber).join(' , '),
purchaseUuid: payload.purchaseUuid,
tills: tills
// products: payload.products.map((product) => { return { name: product.sku , till: product?.category?.name?.toLowerCase()?.replaceAll(" ", ""), Uuid: product['productUuid'] }} ) as string[],
}
console.log("notification:",notification)

tills.forEach(till => {
pusher.trigger(channel, till, notification);
})
pusher.trigger(channel, "all", notification);
return NextResponse.json({ generated_at: new Date().toISOString() })
}
}
11 changes: 9 additions & 2 deletions components/admin/lists/filterable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@ export const filterItems = (items, filters) => {
} else if (filter.field == 'passes') {
const matches = person[filter.field].map((pass) => pass.toLowerCase()).join(', ').includes(filterValue.toLowerCase())
excludePerson = excludePerson || (invertFilter ? matches : !matches)
} else if (filter.field == 'signed_in') {
excludePerson = excludePerson || (filterValue == true && !person[filter.field]) || (person[filter.field] && format(new Date(person[filter.field]), 'eee') != filterValue)
} else if (filter.field == 'checkin_at') {
excludePerson = excludePerson
|| (true == filterValue && false == person[filter.field]) // Exclude anyone who hasn't checked in
|| (false == filterValue && false != person[filter.field]) // Exclude anyone who has checked in
|| (!invertFilter && ![true,false].includes(filterValue) && (person[filter.field] == false || format(new Date(person[filter.field]), 'eee').toLowerCase() != filterValue.toLowerCase())) // Exlude anyone if we're not search for true or false and the day matches
|| (invertFilter && ![true,false].includes(filterValue) && (person[filter.field] == false || format(new Date(person[filter.field]), 'eee').toLowerCase() == filterValue.toLowerCase())) // Exlude anyonee if we're not search for true or false and the day DOESNT matche and we've inverted the filter
if(['2212652493','5060423521'].includes(person.ticket_number)) {
console.log("FILTER",person.name,filter.field,filterValue,person[filter.field],excludePerson,format(new Date(person[filter.field]), 'eee'))
}
} else if (filter.field == 'active') {
excludePerson = excludePerson || person[filter.field] != filterValue
} else {
Expand Down
9 changes: 5 additions & 4 deletions components/admin/lists/ticketRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { BiCreditCard, BiLogoSketch, BiLeftArrowCircle, BiSolidRightArrowSquare,
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
import { EllipsisVerticalIcon, CurrencyPoundIcon, ClipboardIcon, ExclamationTriangleIcon } from '@heroicons/react/24/solid'
import { format,fromUnixTime } from 'date-fns';
import { scanIn } from '@lib/fetchers';
import { mutate } from 'swr';
// import { scanIn } from '@lib/fetchers';
// import { mutate } from 'swr';

const TicketStatusIcon = ({attendee}) => {
const PaymentIcon = attendee.status === 'paid_stripe' ? <BiCreditCard title="Paid Online" className='w-6 h-6' />
Expand Down Expand Up @@ -43,8 +43,9 @@ export const TicketRow = ({attendee,setActiveTicket, setNameChangeModalActive, s
<td className="hidden px-3 py-4 text-sm text-inherit sm:table-cell">{passString}</td>
<td className="px-3 py-4 text-sm text-gray-200 text-nowrap align-center">
{attendee.checkin_at
? <button onClick={() => {scanIn(attendee.ticket_number, attendee.email, true); setTimeout(() => mutate('/api/admin/attendees'),200)}}>{format(fromUnixTime(attendee.checkin_at),'EEE HH:mm')}</button>
: <button className='bg-green-700 rounded-full px-4 py-1' onClick={() => {scanIn(attendee.ticket_number, attendee.email); setTimeout(() => mutate('/api/admin/attendees'),200)}}>Check in</button>}
// ? <button onClick={() => {scanIn(attendee.ticket_number, attendee.email, true); setTimeout(() => mutate('/api/admin/attendees'),200)}}>{format(fromUnixTime(attendee.checkin_at),'EEE HH:mm')}</button>
? <button onClick={() => { setActiveTicket(attendee);}}>{format(fromUnixTime(attendee.checkin_at),'EEE HH:mm')}</button>
: <button className='bg-green-700 rounded-full px-4 py-1' onClick={() => { console.log(attendee.ticket_number); setActiveTicket(attendee);}}>Check in</button>}
</td>
<td className='hidden sm:table-cell px-3 py-0 text-xl align-middle'>
<TicketStatusIcon attendee={attendee}/>
Expand Down
95 changes: 66 additions & 29 deletions components/admin/scan/ScanSuccessDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,46 @@
import useSWR from "swr";
import { BiSolidBadgeCheck, BiIdCard} from "react-icons/bi";
// import { itemsFromPassCombination} from '@components/ticketing/pricingUtilities'
import WristBandIcon from '@public/wristband.svg';
import TicketIcon from '@public/ticket.svg';

import { format, fromUnixTime } from "date-fns";
import { fetcher, scanIn } from "@lib/fetchers";

export type line_item = {
description: string,
}
// fri party, sat class, sat din, sat party, sun class, sun party
// 0 1 2 3 4 5
const accessToWristbands = (accesCode: number[], line_items: line_item[]) => {
let wristBands = []
const meal = accesCode[2] > 0
const friday = accesCode[0] > 0

const artist = line_items.filter((li) => { return /Artist/.test(li.description)}).length > 0
const staffPass = line_items.filter((li) => { return /Staff/.test(li.description) || /Volunteer/.test(li.description)}).length > 0
const fullPass = line_items.filter((li) => { return /Full/.test(li.description)}).length > 0
const partyPass = line_items.filter((li) => { return /Party\sPass/.test(li.description)}).length > 0
const classPass = line_items.filter((li) => { return /Class\sPass/.test(li.description)}).length > 0
const saturdayPass = line_items.filter((li) => { return /Saturday\sPass/.test(li.description) }).length > 0
const sundayPass = line_items.filter((li) => { return /Sunday\sPass/.test(li.description) }).length > 0
const saturdayClass = line_items.filter((li) => { return /Saturday\s-\sClass/.test(li.description) }).length > 0
const saturdayParty = line_items.filter((li) => { return /Saturday\s-\sParty/.test(li.description) || /Dine\sand\sDance\sPass/.test(li.description) }).length > 0
const sundayClass = line_items.filter((li) => { return /Sunday\s-\sClass/.test(li.description) }).length > 0
const sundayParty = line_items.filter((li) => { return /Sunday\s-\sParty/.test(li.description) }).length > 0
const fullPassLike = artist || staffPass || fullPass

const partyPass = line_items.filter((li) => { return /Party\sPass/.test(li.description)}).length > 0 || (accesCode[0] > 0 && accesCode[3] > 0 && accesCode[5] > 0) && !fullPassLike
const classPass = line_items.filter((li) => { return /Class\sPass/.test(li.description)}).length > 0 || (accesCode[1] > 0 && accesCode[4] > 0) && !fullPassLike
const saturdayPass = line_items.filter((li) => { return /Saturday\sPass/.test(li.description) }).length > 0 || (accesCode[1] > 0 && accesCode[3] > 0) && !fullPassLike
const sundayPass = line_items.filter((li) => { return /Sunday\sPass/.test(li.description) }).length > 0 || (accesCode[4] > 0 && accesCode[5] > 0) && !fullPassLike
const saturdayClass = line_items.filter((li) => { return /Saturday\s-\sClass/.test(li.description) }).length > 0 || (accesCode[1] > 0 && !saturdayPass && !classPass) && !fullPassLike
const saturdayParty = line_items.filter((li) => { return /Saturday\s-\sParty/.test(li.description) || /Dine\sand\sDance\sPass/.test(li.description) }).length > 0 || (accesCode[3] > 0 && !saturdayPass && !partyPass) && !fullPassLike
const sundayClass = line_items.filter((li) => { return /Sunday\s-\sClass/.test(li.description) }).length > 0 || (accesCode[4] > 0 && !sundayPass && !classPass) && !fullPassLike
const sundayParty = line_items.filter((li) => { return /Sunday\s-\sParty/.test(li.description) }).length > 0 || (accesCode[5] > 0 && !sundayPass && !partyPass) && !fullPassLike

if(meal) { wristBands.push({ colour: "bg-white text-black", name: "Dinner - Ticket"}) }
if(friday && !artist && !staffPass && !fullPass && !partyPass) { wristBands.push({ colour: "bg-black", name: "Friday - Stamp"}) }
if(meal) { wristBands.push({
colour: "bg-white text-black",
name: "Dinner - Ticket",
icon: <TicketIcon className="w-8 h-8"/>
}) }
if(friday && !artist && !staffPass && !fullPass && !partyPass) { wristBands.push({
colour: "bg-black border border-gray-600",
name: "Friday - Stamp",
icon: <BiSolidBadgeCheck className="w-8 h-8"/>
}) }
if(artist) { wristBands.push({ colour: "bg-blue-600", name: "Artist Pass - Plastic"}) }
if(staffPass) { wristBands.push({ colour: "bg-blue-600", name: "Staff/Volunteer Pass - Plastic"}) }
if(fullPass) { wristBands.push({ colour: "bg-gray-300 text-black", name: "Full Pass - Plastic"}) }
Expand All @@ -41,52 +57,73 @@ const accessToWristbands = (accesCode: number[], line_items: line_item[]) => {

const ScanSuccessDialog = ({scan,onClick}) => {
const {data, error, isLoading, isValidating} = useSWR(`/api/admin/scan/${scan}`, fetcher, { keepPreviousData: false });

// const wristBandIcon = <svg xmlns="http://www.w3.org/2000/svg" className="w-8 h-8 fill-black" viewBox="0 0 24 24">
// <path d="M18 8c0-.909-.613-1.67-1.445-1.912l-1.31-3.443A1 1 0 0 0 14.311 2H8.689a1 1 0 0 0-.934.645l-1.31 3.443A1.996 1.996 0 0 0 5 8v8c0 .909.613 1.67 1.445 1.912l1.31 3.443a1 1 0 0 0 .934.645h5.621c.415 0 .787-.257.935-.645l1.31-3.443A1.996 1.996 0 0 0 18 16v-2h1v-4h-1V8zm-1.998 8H7V8h9l.002 8z"></path>
// </svg>
const defaultWristBandIcon = <WristBandIcon className="w-8 h-8"/>
if(scan) {
if(isLoading) return <div>Loading</div>
if(isLoading) return <LoadingDialog onClick={onClick} />
if(error || data.error) return <div>Error: {error} {data?.error}</div>
if(!data.attendee ) return <div>No Attendee Data?</div>
if(isValidating) return <div>Validating...</div>
if(isValidating) return <LoadingDialog onClick={onClick} />

const attendee = data.attendee
const goodResult = attendee.active && !attendee.ticket_used
const student = attendee.student_ticket
const wristBands = accessToWristbands(attendee.access,attendee.line_items)
// const access = itemsFromPassCombination(attendee.line_items.map(item => item.description))
const checked_in = attendee.ticket_used ? format(fromUnixTime(attendee.ticket_used), 'HH:mm:ss do MMM') : attendee.ticket_used
const cardColor = isLoading ? "bg-gray-500" : goodResult ? student ? "bg-green-600" : "bg-green-500" : "bg-chillired-600"
const cancelButton = checked_in ? "border-red-900 text-red-900" : "border-green-900 text-green-900"
const checkinButton = checked_in ? "bg-red-950" : "bg-green-950"
const checked_in: string = attendee.ticket_used ? format(fromUnixTime(attendee.ticket_used), 'HH:mm:ss do MMM') : attendee.ticket_used
const cardColor = isLoading ? "bg-gray-900" : goodResult ? student ? "bg-green-900" : "bg-green-900" : "bg-chillired-800"
const cancelButton = checked_in ? "border-red-900 text-red-900" : "border-green-200 text-green-100 hover:text-white"
const checkinButton = checked_in ? "bg-red-600" : "bg-green-600 hover:bg-green-500"

return (<div className="absolute top-0 left-0 bg-gradient-to-b from-richblack-500 to-richblack-500 w-full px-3 mb-12">
<div className={`rounded-xl p-4 w-full flex flex-col justify-between ${cardColor}`}>
return (<div className="fixed top-0 left-0 w-full h-full bg-black/80 flex items-center justify-center px-3 mb-12">
<div className={`rounded-xl w-full flex flex-col max-w-128 justify-between ${cardColor}`}>
<div className="p-4">
<h1 className="text-2xl md:text-5xl font-bold leading-tight">{attendee.full_name}</h1>
<h1 className="text-2xl md:text-5xl font-bold leading-none break-words">{attendee.full_name}</h1>
<h2 className="text-lg md:text-2xl">{attendee.ticket_number}</h2>
{ checked_in ? <div className="text-xl">Checked in already: {checked_in}</div>: <div>
{/* <ul className="text-lg list-disc list-inside mt-3">
{access.map(item => <li className="text-sm" key={item}>{item}</li>)}
</ul> */}
</div>
<div className="-mx-1">
{ checked_in ? <div className="bg-chillired-300 text-xl flex flex-col justify-center items-center rounded w-full p-4 mb-2">
<p className="leading-0">ALREADY CHECKED IN:</p>
<p>{checked_in.toUpperCase()}</p>
</div>
: <div>
{ wristBands.map((wristBand) => {
return (<div key={wristBand.name} className={`${wristBand.colour} rounded w-full p-2`}>{wristBand.name}</div>)
return (<div key={wristBand.name} className={`${wristBand.colour} flex justify-start items-center gap-4 rounded w-full text-lg p-4 mb-2`}>
{wristBand.icon? wristBand.icon : defaultWristBandIcon}
{wristBand.name}
</div>)
})}
</div>
}

</div>
<div className="actions flex w-full justify-stretch gap-6 mt-6">
{student ? <div className="">Student Ticket Check ID</div> : null}
<div>
{student && goodResult ? <div className="bg-yellow-400 flex gap-2 items-center justify-center py-1 mx-4 mt-3 text-xl text-black">
<BiIdCard className="w-8 h-8"/>Check Student ID</div> : null}
</div>
<div className="actions flex justify-stretch gap-6 mt-6 mb-4 mx-4">

<button className={`${cancelButton} border rounded px-4 py-4 w-full text-lg md:text-xl`} onClick={onClick}>Cancel</button>
<button className={`${checkinButton} rounded px-4 py-4 w-full text-lg md:text-xl`} onClick={() => {scanIn(attendee.ticket_number,attendee.email, checked_in ? true : false); onClick();}}>{checked_in ? "Reset" : "Check in"}</button>
</div>
{/* {data ? <pre className="text-xs">Ticket: {JSON.stringify(data,null,2)}</pre> : <div className="text-white text-xl">Loading</div>} */}
</div>
</div>)
} else {
return <div>Nothing</div>
return <LoadingDialog onClick={onClick} />
}

}

const LoadingDialog = ({onClick}) => {
return <div className="fixed top-0 left-0 w-full h-full bg-black/80 p-2 flex justify-center items-center" onClick={onClick}>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-75" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path className="opacity-100" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
}

export default ScanSuccessDialog

2 changes: 1 addition & 1 deletion components/admin/statBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type StatLine = {
unit?: string;
};
export default function StatBlock({stats}: {stats: StatLine[]}) {
const [hidden,setHidden] = useState(false);
const [hidden,setHidden] = useState(true);

const toggelButton = <div className="w-full flex absolute -top-12 right-2 justify-end"><button className="border border-gray-600 text-gray-400 hover:text-gray-50 hover:border-gray-100 rounded-md px-3 pt-1 mt-3 text-xs" onClick={()=>{setHidden(!hidden)}}>{ hidden ? "Show" : "Hide"} Stats</button></div>
const statBlock = (<div className="mx-auto max-w-7xl bg-richblack-500 rounded-lg">
Expand Down
Loading

0 comments on commit 01b8ed4

Please sign in to comment.