Better error handling during team member invitation
Now tells you if you have already added this member / invite is pending. Behaviour a little bit more predictable during team editting.
This commit is contained in:
		
							parent
							
								
									9f12ccf5c1
								
							
						
					
					
						commit
						5d3e39ea6e
					
				
					 4 changed files with 50 additions and 18 deletions
				
			
		|  | @ -62,7 +62,8 @@ export default function EditTeamModal(props) { | |||
|                     <td className="text-right py-2 px-1"> | ||||
|                       {member.email !== session.user.email && | ||||
|                       <button | ||||
|                         onClick={() => removeMember(member)} | ||||
|                         type="button" | ||||
|                         onClick={(e) => removeMember(member)} | ||||
|                         className="btn-sm text-xs bg-transparent px-3 py-1 rounded ml-2"> | ||||
|                         <UserRemoveIcon className="text-red-400 group-hover:text-gray-500 flex-shrink-0 -mt-1 mr-1 h-4 w-4 inline"/> | ||||
|                       </button> | ||||
|  | @ -76,20 +77,20 @@ export default function EditTeamModal(props) { | |||
|             <div className="mb-4 border border-red-400 rounded p-2 px-4"> | ||||
|               <p className="block text-sm font-medium text-gray-700">Tick the box to disband this team.</p> | ||||
|               <label className="mt-1"> | ||||
|                 <input type="checkbox" name="title" id="title" onChange={(e) => setCheckedDisbandTeam(e.target.checked)} required className="shadow-sm mr-2 focus:ring-blue-500 focus:border-blue-500  sm:text-sm border-gray-300 rounded-md" /> | ||||
|                 <input type="checkbox" onChange={(e) => setCheckedDisbandTeam(e.target.checked)} className="shadow-sm mr-2 focus:ring-blue-500 focus:border-blue-500  sm:text-sm border-gray-300 rounded-md" /> | ||||
|                 Disband this team | ||||
|               </label> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"> | ||||
|             {!checkedDisbandTeam && <button type="submit" className="btn btn-primary"> | ||||
|             {/*!checkedDisbandTeam && <button type="submit" className="btn btn-primary"> | ||||
|               Update | ||||
|             </button>} | ||||
|             </button>*/} | ||||
|             {checkedDisbandTeam && <button onClick={deleteTeam} className="btn bg-red-700 rounded text-white px-2 font-medium text-sm"> | ||||
|               Disband Team | ||||
|             </button>} | ||||
|             <button onClick={props.onExit} type="button" className="btn btn-white mr-2"> | ||||
|               Cancel | ||||
|               Close | ||||
|             </button> | ||||
|           </div> | ||||
|         </form> | ||||
|  |  | |||
|  | @ -1,8 +1,22 @@ | |||
| import {useEffect, useState} from "react"; | ||||
| import {UsersIcon} from "@heroicons/react/outline"; | ||||
| import { UsersIcon } from "@heroicons/react/outline"; | ||||
| import { useState } from "react"; | ||||
| 
 | ||||
| export default function MemberInvitationModal(props) { | ||||
| 
 | ||||
|   const [ errorMessage, setErrorMessage ] = useState(''); | ||||
| 
 | ||||
|   const handleError = async (res) => { | ||||
| 
 | ||||
|     const responseData = await res.json(); | ||||
| 
 | ||||
|     if (res.ok === false) { | ||||
|       setErrorMessage(responseData.message); | ||||
|       throw new Error(responseData.message); | ||||
|     } | ||||
| 
 | ||||
|     return responseData; | ||||
|   }; | ||||
| 
 | ||||
|   const inviteMember = (e) => { | ||||
| 
 | ||||
|     e.preventDefault(); | ||||
|  | @ -19,7 +33,9 @@ export default function MemberInvitationModal(props) { | |||
|       headers: { | ||||
|         'Content-Type': 'application/json' | ||||
|       } | ||||
|     }).then(props.onExit); | ||||
|     }).then(handleError).then(props.onExit).catch( (e) => { | ||||
|       // do nothing.
 | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   return (<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true"> | ||||
|  | @ -60,6 +76,7 @@ export default function MemberInvitationModal(props) { | |||
|               </label> | ||||
|             </div> | ||||
|           </div> | ||||
|           {errorMessage && <p className="text-red-700 text-sm"><span class="font-bold">Error: </span>{errorMessage}</p>} | ||||
|           <div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"> | ||||
|             <button type="submit" className="btn btn-primary"> | ||||
|               Invite | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| import type { NextApiRequest, NextApiResponse } from 'next'; | ||||
| import prisma from '../../lib/prisma'; | ||||
| import {getSession} from "next-auth/client"; | ||||
| import {create} from "domain"; | ||||
| 
 | ||||
| export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||||
| 
 | ||||
|  | @ -13,6 +12,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|   } | ||||
| 
 | ||||
|   if (req.method === "POST") { | ||||
| 
 | ||||
|     // TODO: Prevent creating a team with identical names?
 | ||||
| 
 | ||||
|     const createTeam = await prisma.team.create({ | ||||
|       data: { | ||||
|         name: req.body.name, | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|   }); | ||||
| 
 | ||||
|   if (!team) { | ||||
|     return res.status(404).json({message: "Unable to find team to invite user to."}); | ||||
|     return res.status(404).json({message: "Invalid team"}); | ||||
|   } | ||||
| 
 | ||||
|   const invitee = await prisma.user.findFirst({ | ||||
|  | @ -34,10 +34,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|   }); | ||||
| 
 | ||||
|   if (!invitee) { | ||||
|     return res.status(404).json({message: "Missing user, currently unsupported."}); | ||||
|     return res.status(400).json({ | ||||
|       message: `Invite failed because there is no corresponding user for ${req.body.usernameOrEmail}`}); | ||||
|   } | ||||
| 
 | ||||
|   // create provisional membership
 | ||||
|   try { | ||||
|     const createMembership = await prisma.membership.create({ | ||||
|       data: { | ||||
|         teamId: parseInt(req.query.team), | ||||
|  | @ -45,6 +47,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|         role: req.body.role, | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
|   catch (err) { | ||||
|     if (err.code === "P2002") { // unique constraint violation
 | ||||
|       return res.status(409).json({ | ||||
|         message: 'This user is a member of this team / has a pending invitation.', | ||||
|       }); | ||||
|     } else { | ||||
|       throw err; // rethrow
 | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   // inform user of membership by email
 | ||||
|   if (req.body.sendEmailInvitation) { | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Alex van Andel
						Alex van Andel