feat: wired the registeration journey with the api, success modal, reactive data

This commit is contained in:
MTG2000
2022-09-08 14:53:08 +03:00
parent 97d31c3360
commit a0f09aaa33
35 changed files with 878 additions and 118 deletions

View File

@@ -1,12 +1,12 @@
import React, { PropsWithChildren } from 'react'
interface Props {
className?: string
}
export default function InfoCard(props: PropsWithChildren<Props>) {
return (
<div className="bg-gray-50 p-16 rounded-12 border border-gray-200 mt-24">
<div className={`bg-gray-50 p-16 rounded-12 border border-gray-200 ${props.className}`}>
<p className="text-body5 text-gray-600">
{props.children}
</p>

View File

@@ -17,7 +17,11 @@ ReactModal.setAppElement('#root');
export default function Modal({ onClose, children, ...props }: Props) {
const dispatch = useAppDispatch()
const dispatch = useAppDispatch();
const onAfterClose = () => {
dispatch(removeClosedModal(props.id))
}
return <ReactModal
isOpen={props.isOpen}
@@ -25,7 +29,7 @@ export default function Modal({ onClose, children, ...props }: Props) {
overlayClassName='fixed w-full inset-0 overflow-x-hidden z-[2020] no-scrollbar'
className=' '
closeTimeoutMS={1000}
onAfterClose={() => dispatch(removeClosedModal(props.id))}
onAfterClose={onAfterClose}
contentElement={(_props, children) => <div {..._props} className={`${_props.className} w-screen min-h-screen relative flex flex-col justify-center items-center md:py-64 md:px-16 inset-0`}>
<div
onClick={onClose}

View File

@@ -71,10 +71,9 @@ export default function ModalsContainer() {
<div className="z-[2020]">
{openModals.map((modal, idx) => {
const Child = ALL_MODALS[modal.Modal];
return (
<Modal
key={idx}
key={modal.id}
isOpen={modal.isOpen}
onClose={onClose}
direction={direction}

View File

@@ -21,7 +21,7 @@ const fetchLnurlAuth = async () => {
return data;
}
const useLnurlQuery = () => {
export const useLnurlQuery = () => {
const [loading, setLoading] = useState(true)
const [error, setError] = useState<any>(null);
const [data, setData] = useState<{ lnurl: string, session_token: string }>({ lnurl: '', session_token: '' })

View File

@@ -64,7 +64,7 @@ export default function LinkedAccountsCard({ value, onChange }: Props) {
<Button color='none' size='sm' className='mt-16 text-gray-600 hover:bg-gray-50' onClick={connectNewWallet}>
+ Add another wallet
</Button>}
<InfoCard>
<InfoCard className='mt-24'>
<span className="font-bold">💡 Note:</span> if you link a wallet that was used to create another account previously, you won't be able to login to that account until you remove it from here.
</InfoCard>
</Card>

View File

@@ -46,7 +46,7 @@ export default function UpdateSkillsCard(props: Props) {
</li>)}
</ul>}
<InfoCard>
<InfoCard className='mt-24'>
<span className="font-bold"> Can't find a specific skill?</span> You can suggest it to be added <a href="https://github.com/peakshift/makers.bolt.fun/discussions/143" target='_blank' rel="noreferrer" className='font-bold underline'>here</a>
</InfoCard>
</Card>

View File

@@ -1,5 +1,6 @@
import Card from 'src/Components/Card/Card'
import { User } from 'src/graphql';
import { Link } from 'react-router-dom'
@@ -26,15 +27,22 @@ export default function TournamentsCard({ tournaments, isOwner }: Props) {
<ul className=' flex flex-wrap gap-x-8 gap-y-20'>
{
tournaments.map((tournament) => {
const isLive = ((new Date() < new Date(tournament.end_date)) && (new Date() > new Date(tournament.start_date)));
return <li key={tournament.id} className="flex gap-16 items-center">
<img src={tournament.thumbnail_image} className='w-48 border-2 border-gray-100 aspect-square rounded-16 object-cover' alt="" />
<div>
<p className="text-gray-900 font-medium">{tournament.title}</p>
<p className={`${isLive ? "text-green-500" : "text-warning-500"} text-body5 font-medium`}>&#8226; {isLive ? "Live" : "Completed"}</p>
</div>
const status = getDateStatus(tournament.start_date, tournament.end_date)
return <li key={tournament.id}>
<Link to={'/tournaments/' + tournament.id} className="flex gap-16 items-center">
<img src={tournament.thumbnail_image} className='w-48 border-2 border-gray-100 aspect-square rounded-16 object-cover' alt="" />
<div>
<p className="text-gray-900 font-medium">{tournament.title}</p>
<p className={`
text-body5 font-medium
${status === 'live' && 'text-green-500'}
${status === 'upcoming' && 'text-violet-500'}
${status === 'finished' && 'text-warning-500'}
`}>&#8226; {status === 'live' && "Running"}
{status === 'upcoming' && "Upcoming"}
{status === 'finished' && "Completed"} </p>
</div>
</Link>
</li>
})}
</ul>
@@ -42,3 +50,15 @@ export default function TournamentsCard({ tournaments, isOwner }: Props) {
</Card>
)
}
function getDateStatus(start: string, end: string) {
const start_date = new Date(start);
const now_date = new Date();
const end_date = new Date(end);
if (now_date < start_date) return 'upcoming'
if (now_date >= start_date && now_date <= end_date) return 'live'
return 'finished'
}

View File

@@ -15,12 +15,6 @@ export default function LinkingAccountModal({ onClose, direction, maker, ...prop
const links = [
{
hasValue: maker.email,
text: maker.email,
icon: FiMail,
url: maker.email && `mailto:${maker.email}`
},
{
hasValue: maker.twitter,
text: maker.twitter,

View File

@@ -11,7 +11,9 @@ interface Props {
export default function MakersPage({ data: { id } }: Props) {
const query = useMeTournamentQuery();
const query = useMeTournamentQuery({
variables: { inTournamentId: id }
});
return (
<div className='pb-42'>
@@ -19,7 +21,7 @@ export default function MakersPage({ data: { id } }: Props) {
{query.loading ?
<MakerCardSkeleton />
:
query.data?.me ?
query.data?.me?.in_tournament ?
<MakerCard isMe maker={query.data.me as User} />
: null
}

View File

@@ -1,13 +1,3 @@
query MeTournament {
me {
id
name
avatar
jobTitle
...UserRolesSkills
}
}
query GetAllRoles {
getAllMakersRoles {
id

View File

@@ -17,9 +17,10 @@ interface Props {
| 'faqs'
>
avatars: string[]
isRegistered: boolean;
}
export default function OverviewPage({ data, avatars }: Props) {
export default function OverviewPage({ data, avatars, isRegistered }: Props) {
return (
<Card onlyMd className='flex flex-col gap-42'>
<div className="grid grid-cols-1 md:grid-cols-3 gap-24 items-start">
@@ -30,7 +31,7 @@ export default function OverviewPage({ data, avatars }: Props) {
>
</div>
</div>
<RegisterCard makers_count={data.makers_count} start_date={data.start_date} avatars={avatars} />
<RegisterCard makers_count={data.makers_count} start_date={data.start_date} avatars={avatars} isRegistered={isRegistered} />
</div>
<PrizesSection prizes={data.prizes} />
<JudgesSection judges={data.judges} />

View File

@@ -1,30 +1,57 @@
import React from 'react'
import { FaUsers } from 'react-icons/fa'
import { useParams } from 'react-router-dom'
import Button from 'src/Components/Button/Button'
import Card from 'src/Components/Card/Card'
import Avatar from 'src/features/Profiles/Components/Avatar/Avatar'
import { openModal } from 'src/redux/features/modals.slice'
import { useCountdown } from 'src/utils/hooks'
import { useAppDispatch, useAppSelector } from "src/utils/hooks";
interface Props {
start_date: string;
makers_count: number
avatars: string[]
isRegistered: boolean;
}
export default function RegisterCard({ makers_count, start_date, avatars }: Props) {
export default function RegisterCard({ makers_count, start_date, avatars, isRegistered }: Props) {
const counter = useCountdown(start_date)
const { id: tournamentId } = useParams()
const isLoggedIn = useAppSelector(state => !!state.user.me)
const dispatch = useAppDispatch()
const onRegister = () => {
if (!tournamentId) return;
if (isLoggedIn)
dispatch(openModal({
Modal: "RegisterTournamet_ConfrimAccount",
props: {
tournamentId: Number(tournamentId)
}
}))
else
dispatch(openModal({
Modal: "RegisterTournamet_Login",
props: {
tournamentId: Number(tournamentId)
}
}))
}
return (
<Card onlyMd className='flex flex-col gap-24'>
<div>
<p className="text-body5 text-gray-600">
<div className="flex">
{avatars.map((img, idx) => <div className='w-[16px] h-32 relative'><Avatar key={idx} src={img} width={32} className='absolute top-0 left-0 min-w-[32px] !border-white' /></div>)}
<span className='self-center ml-24 font-medium '>+ {makers_count} makers</span>
</div>
<p className="text-body5 text-gray-600 flex">
{avatars.map((img, idx) => <div className='w-[16px] h-32 relative'><Avatar key={idx} src={img} width={32} className='absolute top-0 left-0 min-w-[32px] !border-white' /></div>)}
<span className='self-center ml-24 font-medium '>+ {makers_count} makers</span>
</p>
<Button color='primary' fullWidth className='mt-16'>Register</Button>
<Button color='primary' disabled={isRegistered} fullWidth className='mt-16' onClick={onRegister}>{isRegistered ? "Registered!" : "Register Now"}</Button>
</div>
<div>
{counter.isExpired ?

View File

@@ -0,0 +1,68 @@
import { motion } from 'framer-motion'
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
import { IoClose } from 'react-icons/io5';
import Avatar from 'src/features/Profiles/Components/Avatar/Avatar';
import { useAppDispatch, useAppSelector } from "src/utils/hooks";
import Button from 'src/Components/Button/Button';
import { Direction, replaceModal } from 'src/redux/features/modals.slice';
interface Props extends ModalCard {
tournamentId: number
}
export default function ConfirmAccount({ onClose, direction, tournamentId, ...props }: Props) {
const me = useAppSelector(state => state.user.me)
const dispatch = useAppDispatch();
if (!me)
return null;
const onCancel = () => onClose?.();
const onContinue = () => {
dispatch(replaceModal({
Modal: "RegisterTournamet_RegistrationDetails",
direction: Direction.NEXT,
props: {
tournamentId
}
}))
}
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[442px] rounded-xl relative"
>
<div className="p-24">
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
<h2 className='text-h5 font-bold text-center'>Register for tournament</h2>
</div>
<hr className="bg-gray-200" />
<div className='flex flex-col justify-center gap-16 items-center text-center p-24'>
<Avatar src={me.avatar} width={80} />
<div className="flex flex-col gap-4">
<p className="text-body3 text-gray-900">{me.name}</p>
<p className="text-body4 text-gray-600">{me.jobTitle}</p>
</div>
<p className="text-body4 text-gray-600">You are currently signed in using this profile. Would you like to continue with your Tournament registration?</p>
<div className="grid grid-cols-2 gap-16 w-full">
<Button color='gray' onClick={onCancel}>Cancel</Button>
<Button color='primary' onClick={onContinue}>Continue</Button>
</div>
</div>
</motion.div>
)
}

View File

@@ -0,0 +1,3 @@
import { lazyModal } from 'src/utils/helperFunctions';
export const { LazyComponent: ConfirmAccount } = lazyModal(() => import('./ConfirmAccount'))

View File

@@ -0,0 +1,173 @@
import { motion } from 'framer-motion'
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
import { FiCopy } from "react-icons/fi";
import { IoClose, IoRocketOutline } from 'react-icons/io5';
import { useMeTournamentQuery } from 'src/graphql';
import Button from 'src/Components/Button/Button';
import { QRCodeSVG } from 'qrcode.react';
import { Grid } from 'react-loader-spinner';
import { useCallback, useEffect, useState } from 'react';
import { CONSTS } from 'src/utils';
import useCopyToClipboard from 'src/utils/hooks/useCopyToClipboard';
import { useLnurlQuery } from 'src/features/Auth/pages/LoginPage/LoginPage';
import { useAppDispatch } from 'src/utils/hooks';
import { Direction, replaceModal } from 'src/redux/features/modals.slice';
interface Props extends ModalCard {
tournamentId: number
}
export default function LinkingAccountModal({ onClose, direction, tournamentId, ...props }: Props) {
const [copied, setCopied] = useState(false);
const { loadingLnurl, data: { lnurl, session_token }, error } = useLnurlQuery();
const clipboard = useCopyToClipboard()
const dispatch = useAppDispatch();
useEffect(() => {
setCopied(false);
}, [lnurl])
const meQuery = useMeTournamentQuery({
variables: {
inTournamentId: tournamentId
},
onCompleted: (data) => {
if (data.me) {
const already_registerd = data.me.in_tournament;
if (already_registerd)
onClose?.();
else dispatch(replaceModal({
Modal: "RegisterTournamet_RegistrationDetails",
direction: Direction.NEXT,
props: { tournamentId }
}))
}
}
});
const copyToClipboard = () => {
setCopied(true);
clipboard(lnurl);
}
const refetch = meQuery.refetch;
const startPolling = useCallback(
() => {
const interval = setInterval(() => {
fetch(CONSTS.apiEndpoint + '/is-logged-in', {
credentials: 'include',
headers: {
session_token
}
}).then(data => data.json())
.then(data => {
if (data.logged_in) {
clearInterval(interval)
refetch();
}
})
}, 2000);
return interval;
}
, [refetch, session_token],
)
useEffect(() => {
let interval: NodeJS.Timer;
if (lnurl)
interval = startPolling();
return () => {
clearInterval(interval)
}
}, [lnurl, startPolling])
let content = <></>
if (error)
content = <div className="flex flex-col gap-24 items-center">
<p className="text-body3 text-red-500 font-bold">Something wrong happened...</p>
<a href='/login' className="text body4 text-gray-500 hover:underline">Please try again</a>
</div>
else if (loadingLnurl)
content = <div className="flex flex-col gap-24 py-48 items-center">
<Grid color="var(--primary)" width="150" />
<p className="text-body3 font-bold">Fetching Lnurl-Auth link</p>
</div>
else
content = <div className="flex flex-col justify-center gap-24 items-center text-center" >
<a href={`lightning:${lnurl}`} >
<QRCodeSVG
width={280}
height={280}
value={lnurl}
bgColor='transparent'
imageSettings={{
src: '/assets/images/nut_3d.png',
width: 16,
height: 16,
excavate: true,
}}
/>
</a>
<p className="text-gray-600 text-body4 text-left">
To register for this tournament, you need a maker profile. Luckily, this is very easy!
<br />
<br />
To sign in or create an account, just scan this QR, or click to connect using any lightning wallet like <a href="https://getalby.com" className='underline' target='_blank' rel="noreferrer">Alby</a> or <a href="https://breez.technology/" className='underline' target='_blank' rel="noreferrer">Breez</a>.
</p>
<div className="w-full grid grid-cols-2 gap-16">
<a href={`lightning:${lnurl}`}
className='block text-body4 text-center text-white bg-primary-500 hover:bg-primary-600 rounded-10 px-16 py-12 active:scale-90 transition-transform'
>Click to connect <IoRocketOutline /></a>
<Button
color='gray'
onClick={copyToClipboard}
>{copied ? "Copied" : "Copy"} <FiCopy /></Button>
<a href={`https://wiki.ion.radar.tech/tutorials/wallets`} target='_blank' rel="noreferrer"
className='col-span-2 block text-body4 text-center text-gray-900 border border-gray-200 rounded-10 px-16 py-12 active:scale-90 transition-transform'
>What is a lightning wallet?</a>
</div>
</div>;
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[442px] rounded-xl relative"
>
<div className="p-24">
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
<h2 className='text-h5 font-bold text-center'>Connect your maker profile</h2>
</div>
<hr className="bg-gray-200" />
<div className=' p-24'>
{content}
</div>
</motion.div>
)
}

View File

@@ -0,0 +1,3 @@
import { lazyModal } from 'src/utils/helperFunctions';
export const { LazyComponent: LoginModal } = lazyModal(() => import('./LoginModal'))

View File

@@ -0,0 +1,181 @@
import { motion } from 'framer-motion'
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
import { IoClose } from 'react-icons/io5';
import { useAppDispatch, useAppSelector } from "src/utils/hooks";
import Button from 'src/Components/Button/Button';
import { Direction, replaceModal } from 'src/redux/features/modals.slice';
import BasicSelectInput from 'src/Components/Inputs/Selects/BasicSelectInput/BasicSelectInput';
import { GetTournamentByIdDocument, TournamentMakerHackingStatusEnum, useRegisterInTournamentMutation } from 'src/graphql';
import InfoCard from 'src/Components/InfoCard/InfoCard';
import * as yup from "yup";
import { yupResolver } from '@hookform/resolvers/yup';
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import { NotificationsService } from "src/services/notifications.service";
interface Props extends ModalCard {
tournamentId: number
}
const hackingStatusOptions = [
{
label: "Hacking han solo 👻",
value: TournamentMakerHackingStatusEnum.Solo
}, {
label: "Open to connect 👋",
value: TournamentMakerHackingStatusEnum.OpenToConnect
},
]
interface IFormInputs {
email: string;
agreement: boolean;
hacking_status: typeof hackingStatusOptions[number];
}
const schema: yup.SchemaOf<IFormInputs> = yup.object({
email: yup.string().required().email(),
hacking_status: yup.object().shape({
label: yup.string().required(),
value: yup.string().required()
}).required(),
agreement: yup.boolean().required().isTrue("You won't be able to follow the updates/events of the tournament if you don't allow this"),
}).required();
export default function RegistrationDetails({ onClose, direction, ...props }: Props) {
const me = useAppSelector(state => state.user.me)
const dispatch = useAppDispatch();
const [mutate, mutationStatus] = useRegisterInTournamentMutation()
const { handleSubmit, control, register, formState: { errors }, } = useForm<IFormInputs>({
mode: "onChange",
resolver: yupResolver(schema),
defaultValues: {
email: "",
hacking_status: hackingStatusOptions[0]
}
});
if (!me)
return null;
const onCancel = () => onClose?.();
const onSubmit: SubmitHandler<IFormInputs> = data => {
mutate({
variables: {
data: {
email: data.email,
hacking_status: data.hacking_status.value,
},
tournamentId: Number(props.tournamentId)
},
onCompleted: (data) => {
if (data.registerInTournament?.in_tournament) {
dispatch(replaceModal({
Modal: "RegisterTournamet_RegistrationSuccess",
direction: Direction.NEXT
}))
}
},
refetchQueries: [{
query: GetTournamentByIdDocument,
variables: {
id: props.tournamentId
}
}]
})
.catch(() => {
NotificationsService.error("A network error happned...")
mutationStatus.reset()
})
}
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[442px] rounded-xl relative "
>
<div className="p-24">
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
<h2 className='text-h5 font-bold text-center'>Register for tournament</h2>
</div>
<hr className="bg-gray-200" />
<form onSubmit={handleSubmit(onSubmit)} className='flex flex-col gap-24 p-24'>
<p className="text-body4 text-gray-600">Please provide us with some additional details below.</p>
<div className='flex flex-col gap-8'>
<label className="text-body5 text-gray-600 font-medium">Hacking status</label>
<Controller
name="hacking_status"
control={control}
render={({ field: { value, onChange } }) => <BasicSelectInput
isMulti={false}
labelField='label'
valueField='value'
placeholder='Your hacking status'
value={value}
onChange={onChange}
options={hackingStatusOptions}
/>}
/>
<InfoCard >
<span className="font-bold">👋 Details:</span> other makers will be able to see your hacker card and send you Team Up requests.
</InfoCard>
</div>
<div className='flex flex-col gap-8'>
<label className="text-body5 text-gray-600 font-medium">Email address*</label>
<div className="input-wrapper relative">
<input
type='text'
className="input-text"
placeholder="johndoe@gmail.com"
{...register("email")}
/>
</div>
{errors.email && <p className="input-error">
{errors.email.message}
</p>}
<div className="mt-12 flex gap-12">
<input
className='input-checkbox self-center cursor-pointer'
type="checkbox"
{...register('agreement', {})} />
<label className="text-body5 text-gray-600" >
Send me news and updates about the tournament.
<br />
No spam!
</label>
</div>
{errors.agreement && <p className="input-error">
{errors.agreement.message}
</p>}
</div>
<div className="grid grid-cols-2 gap-16">
<Button color='gray' onClick={onCancel}>Cancel</Button>
<Button type='submit' color='primary'>Continue</Button>
</div>
</form>
</motion.div>
)
}

View File

@@ -0,0 +1,3 @@
import { lazyModal } from 'src/utils/helperFunctions';
export const { LazyComponent: RegistrationDetails } = lazyModal(() => import('./RegistrationDetails'))

View File

@@ -0,0 +1,9 @@
mutation RegisterInTournament(
$tournamentId: Int!
$data: RegisterInTournamentInput
) {
registerInTournament(tournament_id: $tournamentId, data: $data) {
id
in_tournament(id: $tournamentId)
}
}

View File

@@ -0,0 +1,72 @@
import { motion } from 'framer-motion'
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
import { IoClose } from 'react-icons/io5';
import Avatar from 'src/features/Profiles/Components/Avatar/Avatar';
import { useAppSelector } from "src/utils/hooks";
import Button from 'src/Components/Button/Button';
import Confetti from "react-confetti";
import { Portal } from 'src/Components/Portal/Portal';
interface Props extends ModalCard {
}
export default function RegistrationSuccess({ onClose, direction, ...props }: Props) {
const me = useAppSelector(state => state.user.me)
if (!me)
throw new Error("User not defined");
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[442px] rounded-xl relative"
>
<div className="p-24">
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
<h2 className='text-h5 font-bold text-center'>Registration succeeded!! </h2>
</div>
<hr className="bg-gray-200" />
<div className='flex flex-col justify-center gap-16 items-center text-center p-24'>
<Avatar src={me.avatar} width={80} />
<div className="flex flex-col gap-4">
<p className="text-body3 text-gray-900 font-medium">{me.name}</p>
<p className="text-body4 text-gray-600">{me.jobTitle}</p>
</div>
<p className="text-body4 text-gray-600">Nice work! Youve successfully registered for the tournament. You can get started with some of the options below!</p>
<div className="flex w-full gap-8 items-center">
<div className={`shrink-0 flex flex-col justify-center items-center bg-gray-50 rounded-8 w-48 h-48`}>👾</div>
<div className="self-center px-16 text-left">
<p className="text-body4 text-gray-900 font-medium">Complete your maker profile</p>
<p className="text-body5 text-gray-400">Add details to your maker profile so you stand out.</p>
</div>
</div>
<div className="flex w-full gap-8 items-center">
<div className={`shrink-0 flex flex-col justify-center items-center bg-gray-50 rounded-8 w-48 h-48`}>🤝</div>
<div className="self-center px-16 text-left">
<p className="text-body4 text-gray-900 font-medium">Find makers to team up with</p>
<p className="text-body5 text-gray-400">Recruit or find makers to team up with.</p>
</div>
</div>
<div className="flex flex-col gap-16 w-full mt-24">
<Button fullWidth color='primary'>👾 Complete maker profile</Button>
<Button fullWidth color='gray'>🤝 Team up with other makers</Button>
</div>
</div>
</motion.div>
)
}

View File

@@ -0,0 +1,3 @@
import { lazyModal } from 'src/utils/helperFunctions';
export const { LazyComponent: RegistrationSuccess } = lazyModal(() => import('./RegistrationSuccess'))

View File

@@ -0,0 +1,11 @@
import { LoginModal } from './LoginModal'
import { ConfirmAccount } from './ConfirmAccount'
import { RegistrationDetails } from './RegistrationDetails'
import { RegistrationSuccess } from './RegistrationSuccess'
export const RegistrationModals = {
LoginModal,
RegistrationDetails,
ConfirmAccount,
RegistrationSuccess,
}

View File

@@ -41,7 +41,7 @@ export default function Navigation({ data }: Props) {
path: "resources",
isDisabled: true,
},
], [data.events_count])
], [data.events_count, data.makers_count, data.projects_count])
return (
<div className="w-full bg-white py-16 border-b border-gray-200 sticky-top-element z-10">

View File

@@ -1,30 +1,34 @@
import Header from './Header/Header'
import { Navigate, Route, Routes } from 'react-router-dom'
import { Navigate, Route, Routes, useParams } from 'react-router-dom'
import OverviewPage from '../OverviewPage/OverviewPage'
import { Helmet } from 'react-helmet'
import Navigation from './Navigation/Navigation'
import EventsPage from '../EventsPage/EventsPage'
import MakersPage from '../MakersPage/MakersPage'
import ProjectsPage from '../ProjectsPage/ProjectsPage'
import { useGetTournamentByIdQuery } from 'src/graphql'
import { useGetTournamentByIdQuery, GetTournamentByIdQuery } from 'src/graphql'
import LoadingPage from 'src/Components/LoadingPage/LoadingPage'
import NotFoundPage from 'src/features/Shared/pages/NotFoundPage/NotFoundPage'
export type MeTournament = GetTournamentByIdQuery['me']
export default function TournamentDetailsPage() {
const query = useGetTournamentByIdQuery({
variables: {
id: 12,
},
const { id } = useParams()
const tournaemntQuery = useGetTournamentByIdQuery({
variables: {
id: Number(id)!,
},
skip: !id
})
if (query.loading)
if (tournaemntQuery.loading)
return <LoadingPage />
if (!query.data?.getTournamentById)
if (!tournaemntQuery.data?.getTournamentById)
return <NotFoundPage />
return (
@@ -32,18 +36,18 @@ export default function TournamentDetailsPage() {
"--maxPageWidth": "910px"
} as any}>
<Helmet>
<title>{query.data.getTournamentById.title} Tournament</title>
<title>{tournaemntQuery.data.getTournamentById.title} Tournament</title>
</Helmet>
<Header data={query.data.getTournamentById} />
<Navigation data={query.data.getTournamentById} />
<Header data={tournaemntQuery.data.getTournamentById} />
<Navigation data={tournaemntQuery.data.getTournamentById} />
<div className="content-container !mt-24">
<Routes >
<Route index element={<Navigate to='overview' />} />
<Route path='overview' element={<OverviewPage data={query.data.getTournamentById} avatars={query.data.getMakersInTournament.makers.map(m => m.avatar)} />} />
<Route path='events' element={<EventsPage data={query.data.getTournamentById} />} />
<Route path='makers' element={<MakersPage data={query.data.getTournamentById} />} />
<Route path='projects' element={<ProjectsPage data={query.data.getTournamentById} />} />
<Route path='overview' element={<OverviewPage data={tournaemntQuery.data.getTournamentById} avatars={tournaemntQuery.data.getMakersInTournament.makers.map(m => m.avatar)} isRegistered={!!tournaemntQuery.data.me?.in_tournament} />} />
<Route path='events' element={<EventsPage data={tournaemntQuery.data.getTournamentById} />} />
<Route path='makers' element={<MakersPage data={tournaemntQuery.data.getTournamentById} />} />
<Route path='projects' element={<ProjectsPage data={tournaemntQuery.data.getTournamentById} />} />
</Routes>
</div>
</div>

View File

@@ -0,0 +1,12 @@
query MeTournament($inTournamentId: Int!) {
me {
id
name
avatar
jobTitle
in_tournament(id: $inTournamentId)
...UserRolesSkills
}
}

View File

@@ -48,4 +48,15 @@ query GetTournamentById($id: Int!) {
avatar
}
}
me {
id
name
avatar
jobTitle
in_tournament(id: $id)
...UserRolesSkills
}
}

View File

@@ -41,6 +41,7 @@ export type BaseUser = {
email: Maybe<Scalars['String']>;
github: Maybe<Scalars['String']>;
id: Scalars['Int'];
in_tournament: Scalars['Boolean'];
jobTitle: Maybe<Scalars['String']>;
join_date: Scalars['Date'];
lightning_address: Maybe<Scalars['String']>;
@@ -57,6 +58,11 @@ export type BaseUser = {
website: Maybe<Scalars['String']>;
};
export type BaseUserIn_TournamentArgs = {
id: Scalars['Int'];
};
export type Bounty = PostBase & {
__typename?: 'Bounty';
applicants_count: Scalars['Int'];
@@ -173,6 +179,7 @@ export type Mutation = {
createStory: Maybe<Story>;
deleteStory: Maybe<Story>;
donate: Donation;
registerInTournament: Maybe<User>;
updateProfileDetails: Maybe<MyProfile>;
updateProfileRoles: Maybe<MyProfile>;
updateUserPreferences: MyProfile;
@@ -207,6 +214,12 @@ export type MutationDonateArgs = {
};
export type MutationRegisterInTournamentArgs = {
data: InputMaybe<RegisterInTournamentInput>;
tournament_id: Scalars['Int'];
};
export type MutationUpdateProfileDetailsArgs = {
data: InputMaybe<ProfileDetailsInput>;
};
@@ -235,6 +248,7 @@ export type MyProfile = BaseUser & {
email: Maybe<Scalars['String']>;
github: Maybe<Scalars['String']>;
id: Scalars['Int'];
in_tournament: Scalars['Boolean'];
jobTitle: Maybe<Scalars['String']>;
join_date: Scalars['Date'];
lightning_address: Maybe<Scalars['String']>;
@@ -254,6 +268,11 @@ export type MyProfile = BaseUser & {
website: Maybe<Scalars['String']>;
};
export type MyProfileIn_TournamentArgs = {
id: Scalars['Int'];
};
export enum Post_Type {
Bounty = 'Bounty',
Question = 'Question',
@@ -469,6 +488,11 @@ export type Question = PostBase & {
votes_count: Scalars['Int'];
};
export type RegisterInTournamentInput = {
email: Scalars['String'];
hacking_status: TournamentMakerHackingStatusEnum;
};
export enum RoleLevelEnum {
Advanced = 'Advanced',
Beginner = 'Beginner',
@@ -567,6 +591,11 @@ export type TournamentJudge = {
name: Scalars['String'];
};
export enum TournamentMakerHackingStatusEnum {
OpenToConnect = 'OpenToConnect',
Solo = 'Solo'
}
export type TournamentMakersResponse = {
__typename?: 'TournamentMakersResponse';
hasNext: Maybe<Scalars['Boolean']>;
@@ -595,6 +624,7 @@ export type User = BaseUser & {
email: Maybe<Scalars['String']>;
github: Maybe<Scalars['String']>;
id: Scalars['Int'];
in_tournament: Scalars['Boolean'];
jobTitle: Maybe<Scalars['String']>;
join_date: Scalars['Date'];
lightning_address: Maybe<Scalars['String']>;
@@ -611,6 +641,11 @@ export type User = BaseUser & {
website: Maybe<Scalars['String']>;
};
export type UserIn_TournamentArgs = {
id: Scalars['Int'];
};
export type UserKeyInputType = {
key: Scalars['String'];
name: Scalars['String'];
@@ -826,11 +861,6 @@ export type ProjectDetailsQueryVariables = Exact<{
export type ProjectDetailsQuery = { __typename?: 'Query', getProject: { __typename?: 'Project', id: number, title: string, description: string, cover_image: string, thumbnail_image: string, screenshots: Array<string>, website: string, lightning_address: string | null, lnurl_callback_url: string | null, votes_count: number, category: { __typename?: 'Category', id: number, title: string }, awards: Array<{ __typename?: 'Award', title: string, image: string, url: string, id: number }>, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } };
export type MeTournamentQueryVariables = Exact<{ [key: string]: never; }>;
export type MeTournamentQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', id: number, name: string, avatar: string, jobTitle: string | null, skills: Array<{ __typename?: 'MakerSkill', id: number, title: string }>, roles: Array<{ __typename?: 'MakerRole', id: number, title: string, icon: string, level: RoleLevelEnum }> } | null };
export type GetAllRolesQueryVariables = Exact<{ [key: string]: never; }>;
@@ -858,12 +888,27 @@ export type GetProjectsInTournamentQueryVariables = Exact<{
export type GetProjectsInTournamentQuery = { __typename?: 'Query', getProjectsInTournament: { __typename?: 'TournamentProjectsResponse', hasNext: boolean | null, hasPrev: boolean | null, projects: Array<{ __typename?: 'Project', id: number, title: string, description: string, thumbnail_image: string, category: { __typename?: 'Category', id: number, title: string, icon: string | null }, recruit_roles: Array<{ __typename?: 'MakerRole', id: number, title: string, icon: string, level: RoleLevelEnum }> }> } };
export type RegisterInTournamentMutationVariables = Exact<{
tournamentId: Scalars['Int'];
data: InputMaybe<RegisterInTournamentInput>;
}>;
export type RegisterInTournamentMutation = { __typename?: 'Mutation', registerInTournament: { __typename?: 'User', id: number, in_tournament: boolean } | null };
export type MeTournamentQueryVariables = Exact<{
inTournamentId: Scalars['Int'];
}>;
export type MeTournamentQuery = { __typename?: 'Query', me: { __typename?: 'MyProfile', id: number, name: string, avatar: string, jobTitle: string | null, in_tournament: boolean, skills: Array<{ __typename?: 'MakerSkill', id: number, title: string }>, roles: Array<{ __typename?: 'MakerRole', id: number, title: string, icon: string, level: RoleLevelEnum }> } | null };
export type GetTournamentByIdQueryVariables = Exact<{
id: Scalars['Int'];
}>;
export type GetTournamentByIdQuery = { __typename?: 'Query', getTournamentById: { __typename?: 'Tournament', id: number, title: string, description: string, thumbnail_image: string, cover_image: string, start_date: any, end_date: any, location: string, website: string, events_count: number, makers_count: number, projects_count: number, prizes: Array<{ __typename?: 'TournamentPrize', title: string, amount: string, image: string }>, judges: Array<{ __typename?: 'TournamentJudge', name: string, company: string, avatar: string }>, events: Array<{ __typename?: 'TournamentEvent', id: number, title: string, image: string, description: string, starts_at: any, ends_at: any, location: string, website: string, type: TournamentEventTypeEnum, links: Array<string> }>, faqs: Array<{ __typename?: 'TournamentFAQ', question: string, answer: string }> }, getMakersInTournament: { __typename?: 'TournamentMakersResponse', makers: Array<{ __typename?: 'User', id: number, avatar: string }> } };
export type GetTournamentByIdQuery = { __typename?: 'Query', getTournamentById: { __typename?: 'Tournament', id: number, title: string, description: string, thumbnail_image: string, cover_image: string, start_date: any, end_date: any, location: string, website: string, events_count: number, makers_count: number, projects_count: number, prizes: Array<{ __typename?: 'TournamentPrize', title: string, amount: string, image: string }>, judges: Array<{ __typename?: 'TournamentJudge', name: string, company: string, avatar: string }>, events: Array<{ __typename?: 'TournamentEvent', id: number, title: string, image: string, description: string, starts_at: any, ends_at: any, location: string, website: string, type: TournamentEventTypeEnum, links: Array<string> }>, faqs: Array<{ __typename?: 'TournamentFAQ', question: string, answer: string }> }, getMakersInTournament: { __typename?: 'TournamentMakersResponse', makers: Array<{ __typename?: 'User', id: number, avatar: string }> }, me: { __typename?: 'MyProfile', id: number, name: string, avatar: string, jobTitle: string | null, in_tournament: boolean, skills: Array<{ __typename?: 'MakerSkill', id: number, title: string }>, roles: Array<{ __typename?: 'MakerRole', id: number, title: string, icon: string, level: RoleLevelEnum }> } | null };
export type VoteMutationVariables = Exact<{
itemType: Vote_Item_Type;
@@ -2188,44 +2233,6 @@ export function useProjectDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt
export type ProjectDetailsQueryHookResult = ReturnType<typeof useProjectDetailsQuery>;
export type ProjectDetailsLazyQueryHookResult = ReturnType<typeof useProjectDetailsLazyQuery>;
export type ProjectDetailsQueryResult = Apollo.QueryResult<ProjectDetailsQuery, ProjectDetailsQueryVariables>;
export const MeTournamentDocument = gql`
query MeTournament {
me {
id
name
avatar
jobTitle
...UserRolesSkills
}
}
${UserRolesSkillsFragmentDoc}`;
/**
* __useMeTournamentQuery__
*
* To run a query within a React component, call `useMeTournamentQuery` and pass it any options that fit your needs.
* When your component renders, `useMeTournamentQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useMeTournamentQuery({
* variables: {
* },
* });
*/
export function useMeTournamentQuery(baseOptions?: Apollo.QueryHookOptions<MeTournamentQuery, MeTournamentQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<MeTournamentQuery, MeTournamentQueryVariables>(MeTournamentDocument, options);
}
export function useMeTournamentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MeTournamentQuery, MeTournamentQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<MeTournamentQuery, MeTournamentQueryVariables>(MeTournamentDocument, options);
}
export type MeTournamentQueryHookResult = ReturnType<typeof useMeTournamentQuery>;
export type MeTournamentLazyQueryHookResult = ReturnType<typeof useMeTournamentLazyQuery>;
export type MeTournamentQueryResult = Apollo.QueryResult<MeTournamentQuery, MeTournamentQueryVariables>;
export const GetAllRolesDocument = gql`
query GetAllRoles {
getAllMakersRoles {
@@ -2390,6 +2397,81 @@ export function useGetProjectsInTournamentLazyQuery(baseOptions?: Apollo.LazyQue
export type GetProjectsInTournamentQueryHookResult = ReturnType<typeof useGetProjectsInTournamentQuery>;
export type GetProjectsInTournamentLazyQueryHookResult = ReturnType<typeof useGetProjectsInTournamentLazyQuery>;
export type GetProjectsInTournamentQueryResult = Apollo.QueryResult<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>;
export const RegisterInTournamentDocument = gql`
mutation RegisterInTournament($tournamentId: Int!, $data: RegisterInTournamentInput) {
registerInTournament(tournament_id: $tournamentId, data: $data) {
id
in_tournament(id: $tournamentId)
}
}
`;
export type RegisterInTournamentMutationFn = Apollo.MutationFunction<RegisterInTournamentMutation, RegisterInTournamentMutationVariables>;
/**
* __useRegisterInTournamentMutation__
*
* To run a mutation, you first call `useRegisterInTournamentMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useRegisterInTournamentMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [registerInTournamentMutation, { data, loading, error }] = useRegisterInTournamentMutation({
* variables: {
* tournamentId: // value for 'tournamentId'
* data: // value for 'data'
* },
* });
*/
export function useRegisterInTournamentMutation(baseOptions?: Apollo.MutationHookOptions<RegisterInTournamentMutation, RegisterInTournamentMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<RegisterInTournamentMutation, RegisterInTournamentMutationVariables>(RegisterInTournamentDocument, options);
}
export type RegisterInTournamentMutationHookResult = ReturnType<typeof useRegisterInTournamentMutation>;
export type RegisterInTournamentMutationResult = Apollo.MutationResult<RegisterInTournamentMutation>;
export type RegisterInTournamentMutationOptions = Apollo.BaseMutationOptions<RegisterInTournamentMutation, RegisterInTournamentMutationVariables>;
export const MeTournamentDocument = gql`
query MeTournament($inTournamentId: Int!) {
me {
id
name
avatar
jobTitle
in_tournament(id: $inTournamentId)
...UserRolesSkills
}
}
${UserRolesSkillsFragmentDoc}`;
/**
* __useMeTournamentQuery__
*
* To run a query within a React component, call `useMeTournamentQuery` and pass it any options that fit your needs.
* When your component renders, `useMeTournamentQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useMeTournamentQuery({
* variables: {
* inTournamentId: // value for 'inTournamentId'
* },
* });
*/
export function useMeTournamentQuery(baseOptions: Apollo.QueryHookOptions<MeTournamentQuery, MeTournamentQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<MeTournamentQuery, MeTournamentQueryVariables>(MeTournamentDocument, options);
}
export function useMeTournamentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MeTournamentQuery, MeTournamentQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<MeTournamentQuery, MeTournamentQueryVariables>(MeTournamentDocument, options);
}
export type MeTournamentQueryHookResult = ReturnType<typeof useMeTournamentQuery>;
export type MeTournamentLazyQueryHookResult = ReturnType<typeof useMeTournamentLazyQuery>;
export type MeTournamentQueryResult = Apollo.QueryResult<MeTournamentQuery, MeTournamentQueryVariables>;
export const GetTournamentByIdDocument = gql`
query GetTournamentById($id: Int!) {
getTournamentById(id: $id) {
@@ -2438,8 +2520,16 @@ export const GetTournamentByIdDocument = gql`
avatar
}
}
me {
id
name
avatar
jobTitle
in_tournament(id: $id)
...UserRolesSkills
}
}
`;
${UserRolesSkillsFragmentDoc}`;
/**
* __useGetTournamentByIdQuery__

View File

@@ -157,6 +157,7 @@ export const users: (User & MyProfile)[] = [{
twitter: "mtg",
website: "https://mtg-dev.tech",
stories: posts.stories,
in_tournament: true,
nostr_prv_key: "123123124asdfsadfsa8d7fsadfasdf",
nostr_pub_key: "123124123123dfsadfsa8d7f11sadfasdf",
walletsKeys: [
@@ -191,6 +192,7 @@ export const users: (User & MyProfile)[] = [{
twitter: "john-doe",
website: "https://mtg-dev.tech",
stories: posts.stories,
in_tournament: true,
nostr_prv_key: "123123124asdfsadfsa8d7fsadfasdf",
nostr_pub_key: "123124123123dfsadfsa8d7f11sadfasdf",
@@ -226,6 +228,7 @@ export const users: (User & MyProfile)[] = [{
twitter: "john-doe",
website: "https://mtg-dev.tech",
stories: posts.stories,
in_tournament: true,
nostr_prv_key: "123123124asdfsadfsa8d7fsadfasdf",
nostr_pub_key: "123124123123dfsadfsa8d7f11sadfasdf",
walletsKeys: [
@@ -260,6 +263,7 @@ export const users: (User & MyProfile)[] = [{
twitter: "john-doe",
website: "https://mtg-dev.tech",
stories: posts.stories,
in_tournament: true,
nostr_prv_key: "123123124asdfsadfsa8d7fsadfasdf",
nostr_pub_key: "123124123123dfsadfsa8d7f11sadfasdf",
walletsKeys: [

View File

@@ -283,7 +283,8 @@ export const handlers = [
return res(
ctx.data({
getTournamentById: getTournamentById(12),
getMakersInTournament: getMakersInTournament({ roleId: null, search: null, skip: null, take: 4, tournamentId: 12 })
getMakersInTournament: getMakersInTournament({ roleId: null, search: null, skip: null, take: 4, tournamentId: 12 }),
me: { ...me() }
})
)
}),

View File

@@ -15,6 +15,7 @@ import { ComponentProps } from "react";
import { generateId } from "src/utils/helperFunctions";
import { NoWeblnModal } from "src/Components/Modals/NoWeblnModal";
import { ConnectToMakerModal } from "src/features/Tournaments/pages/MakersPage/ConnectToMakerModal";
import { RegistrationModals } from "src/features/Tournaments/pages/OverviewPage/RegisterationModals";
@@ -44,6 +45,10 @@ export const ALL_MODALS = {
// Tournaments
EventModal,
ConnectToMakerModal,
RegisterTournamet_Login: RegistrationModals.LoginModal,
RegisterTournamet_ConfrimAccount: RegistrationModals.ConfirmAccount,
RegisterTournamet_RegistrationDetails: RegistrationModals.RegistrationDetails,
RegisterTournamet_RegistrationSuccess: RegistrationModals.RegistrationSuccess,
// Misc
ConfirmModal,

View File

@@ -44,6 +44,6 @@
}
.modal-card {
@apply rounded-[40px] bg-gray-50 overflow-hidden w-full shadow-2xl z-10;
@apply rounded-[40px] bg-white overflow-hidden w-full shadow-2xl z-10;
}
}