invites page
This commit is contained in:
		
							parent
							
								
									4935c7dc1c
								
							
						
					
					
						commit
						7107d329ba
					
				| @ -28,10 +28,22 @@ export default { | |||||||
|       return await models.invite.create({ |       return await models.invite.create({ | ||||||
|         data: { gift, limit, userId: me.id } |         data: { gift, limit, userId: me.id } | ||||||
|       }) |       }) | ||||||
|  |     }, | ||||||
|  |     revokeInvite: async (parent, { id }, { me, models }) => { | ||||||
|  |       if (!me) { | ||||||
|  |         throw new AuthenticationError('you must be logged in') | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return await models.invite.update({ | ||||||
|  |         where: { id }, | ||||||
|  |         data: { revoked: true } | ||||||
|  |       }) | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   Invite: { |   Invite: { | ||||||
|     invitees: async (invite, args, { models }) => [] |     invitees: async (invite, args, { me, models }) => { | ||||||
|  |       return await models.user.findMany({ where: { inviteId: invite.id } }) | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ export default gql` | |||||||
| 
 | 
 | ||||||
|   extend type Mutation { |   extend type Mutation { | ||||||
|     createInvite(gift: Int!, limit: Int): Invite |     createInvite(gift: Int!, limit: Int): Invite | ||||||
|  |     revokeInvite(id: ID!): Invite | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   type Invite { |   type Invite { | ||||||
|  | |||||||
| @ -3,15 +3,17 @@ import ArrowRight from '../svgs/arrow-right-s-fill.svg' | |||||||
| import ArrowDown from '../svgs/arrow-down-s-fill.svg' | import ArrowDown from '../svgs/arrow-down-s-fill.svg' | ||||||
| import { useEffect, useState } from 'react' | import { useEffect, useState } from 'react' | ||||||
| 
 | 
 | ||||||
| export default function AccordianItem ({ header, body, headerColor = 'grey' }) { | export default function AccordianItem ({ header, body, headerColor = 'grey', show }) { | ||||||
|   const [open, setOpen] = useState(false) |   const [open, setOpen] = useState(show) | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setOpen(false) |     setOpen(show) | ||||||
|   }, []) |   }, []) | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Accordion> |     <Accordion | ||||||
|  |       defaultActiveKey={show ? '0' : undefined} | ||||||
|  |     > | ||||||
|       <Accordion.Toggle |       <Accordion.Toggle | ||||||
|         as={props => <div {...props} />} |         as={props => <div {...props} />} | ||||||
|         eventKey='0' |         eventKey='0' | ||||||
|  | |||||||
| @ -62,6 +62,10 @@ export default function Header () { | |||||||
|               <Link href='/wallet' passHref> |               <Link href='/wallet' passHref> | ||||||
|                 <NavDropdown.Item>wallet</NavDropdown.Item> |                 <NavDropdown.Item>wallet</NavDropdown.Item> | ||||||
|               </Link> |               </Link> | ||||||
|  |               <NavDropdown.Divider /> | ||||||
|  |               <Link href='/invites' passHref> | ||||||
|  |                 <NavDropdown.Item>invites</NavDropdown.Item> | ||||||
|  |               </Link> | ||||||
|               <div> |               <div> | ||||||
|                 <NavDropdown.Divider /> |                 <NavDropdown.Divider /> | ||||||
|                 <RefreshableLink href='/recent' passHref> |                 <RefreshableLink href='/recent' passHref> | ||||||
|  | |||||||
| @ -1,9 +1,11 @@ | |||||||
| import Layout from '../../components/layout' | import Layout from '../../components/layout' | ||||||
| import * as Yup from 'yup' | import * as Yup from 'yup' | ||||||
| import { Form, Input, SubmitButton } from '../../components/form' | import { CopyInput, Form, Input, SubmitButton } from '../../components/form' | ||||||
| import { InputGroup } from 'react-bootstrap' | import { InputGroup } from 'react-bootstrap' | ||||||
| import { gql, useMutation, useQuery } from '@apollo/client' | import { gql, useMutation, useQuery } from '@apollo/client' | ||||||
| import { INVITE_FIELDS } from '../../fragments/invites' | import { INVITE_FIELDS } from '../../fragments/invites' | ||||||
|  | import AccordianItem from '../../components/accordian-item' | ||||||
|  | import styles from '../../styles/invites.module.css' | ||||||
| 
 | 
 | ||||||
| export const InviteSchema = Yup.object({ | export const InviteSchema = Yup.object({ | ||||||
|   gift: Yup.number().typeError('must be a number') |   gift: Yup.number().typeError('must be a number') | ||||||
| @ -67,11 +69,74 @@ function InviteForm () { | |||||||
|         name='limit' |         name='limit' | ||||||
|       /> |       /> | ||||||
| 
 | 
 | ||||||
|       <SubmitButton variant='secondary' className='mt-2'>create</SubmitButton> |       <SubmitButton variant='secondary'>create</SubmitButton> | ||||||
|     </Form> |     </Form> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function Invite ({ invite, active }) { | ||||||
|  |   const [revokeInvite] = useMutation( | ||||||
|  |     gql` | ||||||
|  |       ${INVITE_FIELDS} | ||||||
|  |       mutation revokeInvite($id: ID!) { | ||||||
|  |         revokeInvite(id: $id) { | ||||||
|  |           ...InviteFields | ||||||
|  |         } | ||||||
|  |       }` | ||||||
|  |   ) | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <div | ||||||
|  |       className={styles.invite} | ||||||
|  |     > | ||||||
|  |       <CopyInput | ||||||
|  |         groupClassName='mb-1' | ||||||
|  |         size='sm' type='text' | ||||||
|  |         placeholder={`https://stacker.news/invites/${invite.id}`} readOnly | ||||||
|  |       /> | ||||||
|  |       <div className={styles.other}> | ||||||
|  |         <span>{invite.invitees.length} joined{invite.limit ? ` of ${invite.limit}` : ''}</span> | ||||||
|  |         {active | ||||||
|  |           ? ( | ||||||
|  |             <> | ||||||
|  |               <span> \ </span> | ||||||
|  |               <span | ||||||
|  |                 className={styles.revoke} | ||||||
|  |                 onClick={() => revokeInvite({ variables: { id: invite.id } })} | ||||||
|  |               >revoke | ||||||
|  |               </span> | ||||||
|  |             </>) | ||||||
|  | 
 | ||||||
|  |           : invite.revoked && ( | ||||||
|  |             <> | ||||||
|  |               <span> \ </span> | ||||||
|  |               <span | ||||||
|  |                 className='text-danger' | ||||||
|  |               >revoked | ||||||
|  |               </span> | ||||||
|  |             </>)} | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function InviteList ({ name, invites }) { | ||||||
|  |   return ( | ||||||
|  |     <div className='mt-4'> | ||||||
|  |       <AccordianItem | ||||||
|  |         show | ||||||
|  |         headerColor='#212529' | ||||||
|  |         header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>{name}</div>} body={ | ||||||
|  |           <div className={styles.invites}>{invites.map(invite => { | ||||||
|  |             return <Invite invite={invite} key={invite.id} active={name === 'active'} /> | ||||||
|  |           })} | ||||||
|  |           </div> | ||||||
|  |       } | ||||||
|  |       /> | ||||||
|  |     </div> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export default function Invites () { | export default function Invites () { | ||||||
|   const { data } = useQuery( |   const { data } = useQuery( | ||||||
|     gql` |     gql` | ||||||
| @ -82,12 +147,20 @@ export default function Invites () { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     `)
 |     `)
 | ||||||
|  | 
 | ||||||
|  |   const [active, inactive] = data && data.invites | ||||||
|  |     ? data.invites.reduce((result, invite) => { | ||||||
|  |         result[invite.revoked || (invite.limit && invite.invitees.length >= invite.limit) ? 1 : 0].push(invite) | ||||||
|  |         return result | ||||||
|  |       }, | ||||||
|  |       [[], []]) | ||||||
|  |     : [[], []] | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Layout> |     <Layout> | ||||||
|       <InviteForm /> |       <InviteForm /> | ||||||
|       {data && data.invites && data.invites.map(invite => { |       {active.length > 0 && <InviteList name='active' invites={active} />} | ||||||
|         return <div key={invite.id}>{invite.id}</div> |       {inactive.length > 0 && <InviteList name='inactive' invites={inactive} />} | ||||||
|       })} |  | ||||||
|     </Layout> |     </Layout> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										27
									
								
								styles/invites.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								styles/invites.module.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | .invites { | ||||||
|  |     background-color: rgba(0, 0, 0, 0.06); | ||||||
|  |     border-radius: .4rem; | ||||||
|  |     padding: .75rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .invites { | ||||||
|  |     margin-bottom: 0 !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .other { | ||||||
|  |     font-weight: 500; | ||||||
|  |     font-size: 70%; | ||||||
|  |     color: grey; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .revoke { | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .invite { | ||||||
|  |     margin-bottom: .5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .invite:last-child { | ||||||
|  |     margin-bottom: 0; | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user