refactor: extract Button to component, reformat all files with Prettier
This commit is contained in:
		
							parent
							
								
									1080d8a0cd
								
							
						
					
					
						commit
						bfdaafeb0a
					
				
					 15 changed files with 504 additions and 335 deletions
				
			
		
							
								
								
									
										1
									
								
								frontend/.prettierignore
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								frontend/.prettierignore
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | .next | ||||||
							
								
								
									
										65
									
								
								frontend/components/Button.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								frontend/components/Button.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | import { MouseEventHandler, ReactNode } from "react"; | ||||||
|  | 
 | ||||||
|  | export enum ButtonStyle { | ||||||
|  |   primary, | ||||||
|  |   success, | ||||||
|  |   danger, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface Props { | ||||||
|  |   onClick?: MouseEventHandler<HTMLButtonElement>; | ||||||
|  |   style?: ButtonStyle; | ||||||
|  |   bold?: boolean; | ||||||
|  |   children?: ReactNode; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default function Button(props: Props) { | ||||||
|  |   if (props.style === undefined) { | ||||||
|  |     return PrimaryButton(props); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   switch (props.style) { | ||||||
|  |     case ButtonStyle.primary: | ||||||
|  |       return PrimaryButton(props); | ||||||
|  |     case ButtonStyle.success: | ||||||
|  |       return SuccessButton(props); | ||||||
|  |     case ButtonStyle.danger: | ||||||
|  |       return DangerButton(props); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function PrimaryButton(props: Props) { | ||||||
|  |   return ( | ||||||
|  |     <button | ||||||
|  |       type="button" | ||||||
|  |       onClick={props.onClick} | ||||||
|  |       className="bg-blue-500 dark:bg-blue-500 hover:bg-blue-700 hover:dark:bg-blue-800 p-2 rounded-md text-white" | ||||||
|  |     > | ||||||
|  |       <span className={props.bold ? "font-bold" : ""}>{props.children}</span> | ||||||
|  |     </button> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function SuccessButton(props: Props) { | ||||||
|  |   return ( | ||||||
|  |     <button | ||||||
|  |       type="button" | ||||||
|  |       onClick={props.onClick} | ||||||
|  |       className="bg-green-600 dark:bg-green-700 hover:bg-green-700 hover:dark:bg-green-800 p-2 rounded-md text-white" | ||||||
|  |     > | ||||||
|  |       <span className={props.bold ? "font-bold" : ""}>{props.children}</span> | ||||||
|  |     </button> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function DangerButton(props: Props) { | ||||||
|  |   return ( | ||||||
|  |     <button | ||||||
|  |       type="button" | ||||||
|  |       onClick={props.onClick} | ||||||
|  |       className="bg-red-600 dark:bg-red-700 hover:bg-red-700 hover:dark:bg-red-800 p-2 rounded-md text-white" | ||||||
|  |     > | ||||||
|  |       <span className={props.bold ? "font-bold" : ""}>{props.children}</span> | ||||||
|  |     </button> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | @ -1,128 +1,129 @@ | ||||||
| import { | import { | ||||||
|     EmojiLaughing, |   EmojiLaughing, | ||||||
|     HandThumbsDown, |   HandThumbsDown, | ||||||
|     HandThumbsUp, |   HandThumbsUp, | ||||||
|     Heart, |   Heart, | ||||||
|     People, |   People, | ||||||
|     Trash3, |   Trash3, | ||||||
| } from "react-bootstrap-icons"; | } from "react-bootstrap-icons"; | ||||||
| 
 | 
 | ||||||
| import Card from "./Card"; | import Card from "./Card"; | ||||||
| import TextInput from "./TextInput"; | import TextInput from "./TextInput"; | ||||||
|  | import Button, { ButtonStyle } from "./Button"; | ||||||
| 
 | 
 | ||||||
| export interface EditField { | export interface EditField { | ||||||
|     id: number; |   id: number; | ||||||
|     name: string; |   name: string; | ||||||
|     pronouns: Record<string, PronounChoice>; |   pronouns: Record<string, PronounChoice>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export enum PronounChoice { | export enum PronounChoice { | ||||||
|     favourite, |   favourite, | ||||||
|     okay, |   okay, | ||||||
|     jokingly, |   jokingly, | ||||||
|     friendsOnly, |   friendsOnly, | ||||||
|     avoid, |   avoid, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type EditableCardProps = { | type EditableCardProps = { | ||||||
|     field: EditField; |   field: EditField; | ||||||
|     onChangeName: React.ChangeEventHandler<HTMLInputElement>; |   onChangeName: React.ChangeEventHandler<HTMLInputElement>; | ||||||
|     onChangeFavourite( |   onChangeFavourite( | ||||||
|         e: React.MouseEvent<HTMLButtonElement>, |     e: React.MouseEvent<HTMLButtonElement>, | ||||||
|         entry: string |     entry: string | ||||||
|     ): void; |   ): void; | ||||||
|     onChangeOkay(e: React.MouseEvent<HTMLButtonElement>, entry: string): void; |   onChangeOkay(e: React.MouseEvent<HTMLButtonElement>, entry: string): void; | ||||||
|     onChangeJokingly(e: React.MouseEvent<HTMLButtonElement>, entry: string): void; |   onChangeJokingly(e: React.MouseEvent<HTMLButtonElement>, entry: string): void; | ||||||
|     onChangeFriends(e: React.MouseEvent<HTMLButtonElement>, entry: string): void; |   onChangeFriends(e: React.MouseEvent<HTMLButtonElement>, entry: string): void; | ||||||
|     onChangeAvoid(e: React.MouseEvent<HTMLButtonElement>, entry: string): void; |   onChangeAvoid(e: React.MouseEvent<HTMLButtonElement>, entry: string): void; | ||||||
|     onClickDelete: React.MouseEventHandler<HTMLButtonElement>; |   onClickDelete: React.MouseEventHandler<HTMLButtonElement>; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function EditableCard(props: EditableCardProps) { | export function EditableCard(props: EditableCardProps) { | ||||||
|     const footer = ( |   const footer = ( | ||||||
|         <div className="flex justify-between"> |     <div className="flex justify-between"> | ||||||
|             <TextInput value={props.field.name} onChange={props.onChangeName} /> |       <TextInput value={props.field.name} onChange={props.onChangeName} /> | ||||||
|             <button |       <Button style={ButtonStyle.danger} onClick={props.onClickDelete}> | ||||||
|                 type="button" |         <Trash3 aria-hidden className="inline" /> Delete | ||||||
|                 onClick={props.onClickDelete} |       </Button> | ||||||
|                 className="bg-red-600 dark:bg-red-700 hover:bg-red-700 hover:dark:bg-red-800 p-2 rounded-md" |     </div> | ||||||
|             > |   ); | ||||||
|                 <Trash3 aria-hidden className="inline" />{" "} |  | ||||||
|                 <span className="font-bold">Delete</span> |  | ||||||
|             </button> |  | ||||||
|         </div> |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     return ( |   return ( | ||||||
|         <Card title={props.field.name} draggable footer={footer}> |     <Card title={props.field.name} draggable footer={footer}> | ||||||
|             <ul> |       <ul> | ||||||
|                 {Object.keys(props.field.pronouns).map((pronoun, index) => { |         {Object.keys(props.field.pronouns).map((pronoun, index) => { | ||||||
|                     const choice = props.field.pronouns[pronoun]; |           const choice = props.field.pronouns[pronoun]; | ||||||
|                     return ( |           return ( | ||||||
|                         <li className="flex justify-between my-1" key={index}> |             <li className="flex justify-between my-1" key={index}> | ||||||
|                             <div>{pronoun}</div> |               <div>{pronoun}</div> | ||||||
|                             <div className="rounded-md"> |               <div className="rounded-md"> | ||||||
|                                 <button |                 <button | ||||||
|                                     type="button" |                   type="button" | ||||||
|                                     onClick={(e) => props.onChangeFavourite(e, pronoun)} |                   onClick={(e) => props.onChangeFavourite(e, pronoun)} | ||||||
|                                     className={`${choice == PronounChoice.favourite |                   className={`${ | ||||||
|                                         ? "bg-slate-500" |                     choice == PronounChoice.favourite | ||||||
|                                         : "bg-slate-600" |                       ? "bg-slate-500" | ||||||
|                                         } hover:bg-slate-400 p-2`}
 |                       : "bg-slate-600" | ||||||
|                                 > |                   } hover:bg-slate-400 p-2`}
 | ||||||
|                                     <Heart /> |                 > | ||||||
|                                 </button> |                   <Heart /> | ||||||
|                                 <button |                 </button> | ||||||
|                                     type="button" |                 <button | ||||||
|                                     onClick={(e) => props.onChangeOkay(e, pronoun)} |                   type="button" | ||||||
|                                     className={`${choice == PronounChoice.okay |                   onClick={(e) => props.onChangeOkay(e, pronoun)} | ||||||
|                                         ? "bg-slate-500" |                   className={`${ | ||||||
|                                         : "bg-slate-600" |                     choice == PronounChoice.okay | ||||||
|                                         } hover:bg-slate-400 p-2`}
 |                       ? "bg-slate-500" | ||||||
|                                 > |                       : "bg-slate-600" | ||||||
|                                     <HandThumbsUp /> |                   } hover:bg-slate-400 p-2`}
 | ||||||
|                                 </button> |                 > | ||||||
|                                 <button |                   <HandThumbsUp /> | ||||||
|                                     type="button" |                 </button> | ||||||
|                                     onClick={(e) => props.onChangeJokingly(e, pronoun)} |                 <button | ||||||
|                                     className={`${choice == PronounChoice.jokingly |                   type="button" | ||||||
|                                         ? "bg-slate-500" |                   onClick={(e) => props.onChangeJokingly(e, pronoun)} | ||||||
|                                         : "bg-slate-600" |                   className={`${ | ||||||
|                                         } hover:bg-slate-400 p-2`}
 |                     choice == PronounChoice.jokingly | ||||||
|                                 > |                       ? "bg-slate-500" | ||||||
|                                     <EmojiLaughing /> |                       : "bg-slate-600" | ||||||
|                                 </button> |                   } hover:bg-slate-400 p-2`}
 | ||||||
|                                 <button |                 > | ||||||
|                                     type="button" |                   <EmojiLaughing /> | ||||||
|                                     onClick={(e) => props.onChangeFriends(e, pronoun)} |                 </button> | ||||||
|                                     className={`${choice == PronounChoice.friendsOnly |                 <button | ||||||
|                                         ? "bg-slate-500" |                   type="button" | ||||||
|                                         : "bg-slate-600" |                   onClick={(e) => props.onChangeFriends(e, pronoun)} | ||||||
|                                         } hover:bg-slate-400 p-2`}
 |                   className={`${ | ||||||
|                                 > |                     choice == PronounChoice.friendsOnly | ||||||
|                                     <People /> |                       ? "bg-slate-500" | ||||||
|                                 </button> |                       : "bg-slate-600" | ||||||
|                                 <button |                   } hover:bg-slate-400 p-2`}
 | ||||||
|                                     type="button" |                 > | ||||||
|                                     onClick={(e) => props.onChangeAvoid(e, pronoun)} |                   <People /> | ||||||
|                                     className={`${choice == PronounChoice.avoid |                 </button> | ||||||
|                                         ? "bg-slate-500" |                 <button | ||||||
|                                         : "bg-slate-600" |                   type="button" | ||||||
|                                         } hover:bg-slate-400 p-2`}
 |                   onClick={(e) => props.onChangeAvoid(e, pronoun)} | ||||||
|                                 > |                   className={`${ | ||||||
|                                     <HandThumbsDown /> |                     choice == PronounChoice.avoid | ||||||
|                                 </button> |                       ? "bg-slate-500" | ||||||
|                                 <button |                       : "bg-slate-600" | ||||||
|                                     type="button" |                   } hover:bg-slate-400 p-2`}
 | ||||||
|                                     className="bg-red-600 dark:bg-red-700 hover:bg-red-700 hover:dark:bg-red-800 p-2" |                 > | ||||||
|                                 > |                   <HandThumbsDown /> | ||||||
|                                     <Trash3 /> |                 </button> | ||||||
|                                 </button> |                 <button | ||||||
|                             </div> |                   type="button" | ||||||
|                         </li> |                   className="bg-red-600 dark:bg-red-700 hover:bg-red-700 hover:dark:bg-red-800 p-2" | ||||||
|                     ); |                 > | ||||||
|                 })} |                   <Trash3 /> | ||||||
|             </ul> |                 </button> | ||||||
|         </Card> |               </div> | ||||||
|     ); |             </li> | ||||||
|  |           ); | ||||||
|  |         })} | ||||||
|  |       </ul> | ||||||
|  |     </Card> | ||||||
|  |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,19 +1,24 @@ | ||||||
| import { ChangeEventHandler } from "react"; | import { ChangeEventHandler } from "react"; | ||||||
| 
 | 
 | ||||||
| export type Props = { | export type Props = { | ||||||
|     defaultValue?: string; |   contrastBackground?: boolean; | ||||||
|     value?: string; |   defaultValue?: string; | ||||||
|     onChange?: ChangeEventHandler<HTMLInputElement>; |   value?: string; | ||||||
|  |   onChange?: ChangeEventHandler<HTMLInputElement>; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default function TextInput(props: Props) { | export default function TextInput(props: Props) { | ||||||
|     return ( |   const bg = props.contrastBackground | ||||||
|         <input |     ? "bg-slate-50 dark:bg-slate-700" | ||||||
|             type="text" |     : "bg-white dark:bg-slate-800"; | ||||||
|             className="p-1 lg:p-2 rounded-md bg-white border-slate-300 text-black dark:bg-slate-800 dark:border-slate-900 dark:text-white" | 
 | ||||||
|             defaultValue={props.defaultValue} |   return ( | ||||||
|             value={props.value} |     <input | ||||||
|             onChange={props.onChange} |       type="text" | ||||||
|         /> |       className={`p-1 lg:p-2 rounded-md ${bg} border-slate-300 text-black dark:border-slate-900 dark:text-white`} | ||||||
|     ); |       defaultValue={props.defaultValue} | ||||||
|  |       value={props.value} | ||||||
|  |       onChange={props.onChange} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -69,13 +69,28 @@ export enum WordStatus { | ||||||
| export enum ErrorCode { | export enum ErrorCode { | ||||||
|   BadRequest = 400, |   BadRequest = 400, | ||||||
|   Forbidden = 403, |   Forbidden = 403, | ||||||
|  |   NotFound = 404, | ||||||
|  |   MethodNotAllowed = 405, | ||||||
|  |   TooManyRequests = 429, | ||||||
|   InternalServerError = 500, |   InternalServerError = 500, | ||||||
| 
 | 
 | ||||||
|   InvalidState = 1001, |   InvalidState = 1001, | ||||||
|   InvalidOAuthCode = 1002, |   InvalidOAuthCode = 1002, | ||||||
|   InvalidToken = 1003, |   InvalidToken = 1003, | ||||||
|  |   InviteRequired = 1004, | ||||||
|  |   InvalidTicket = 1005, | ||||||
|  |   InvalidUsername = 1006, | ||||||
|  |   UsernameTaken = 1007, | ||||||
|  |   InvitesDisabled = 1008, | ||||||
|  |   InviteLimitReached = 1009, | ||||||
|  |   InviteAlreadyUsed = 1010, | ||||||
| 
 | 
 | ||||||
|   UserNotFound = 2001, |   UserNotFound = 2001, | ||||||
|  | 
 | ||||||
|  |   MemberNotFound = 3001, | ||||||
|  |   MemberLimitReached = 3002, | ||||||
|  | 
 | ||||||
|  |   RequestTooBig = 4001, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface SignupRequest { | export interface SignupRequest { | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ | ||||||
|     "eslint": "8.19.0", |     "eslint": "8.19.0", | ||||||
|     "eslint-config-next": "12.2.2", |     "eslint-config-next": "12.2.2", | ||||||
|     "postcss": "^8.4.14", |     "postcss": "^8.4.14", | ||||||
|  |     "prettier": "2.7.1", | ||||||
|     "tailwindcss": "^3.1.6", |     "tailwindcss": "^3.1.6", | ||||||
|     "typescript": "4.7.4" |     "typescript": "4.7.4" | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -16,10 +16,10 @@ | ||||||
|  *  - https://reactjs.org/docs/error-boundaries.html
 |  *  - https://reactjs.org/docs/error-boundaries.html
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import * as Sentry from '@sentry/nextjs'; | import * as Sentry from "@sentry/nextjs"; | ||||||
| import NextErrorComponent from 'next/error'; | import NextErrorComponent from "next/error"; | ||||||
| 
 | 
 | ||||||
| const CustomErrorComponent = props => { | const CustomErrorComponent = (props) => { | ||||||
|   // If you're using a Nextjs version prior to 12.2.1, uncomment this to
 |   // If you're using a Nextjs version prior to 12.2.1, uncomment this to
 | ||||||
|   // compensate for https://github.com/vercel/next.js/issues/8592
 |   // compensate for https://github.com/vercel/next.js/issues/8592
 | ||||||
|   // Sentry.captureUnderscoreErrorException(props);
 |   // Sentry.captureUnderscoreErrorException(props);
 | ||||||
|  | @ -27,7 +27,7 @@ const CustomErrorComponent = props => { | ||||||
|   return <NextErrorComponent statusCode={props.statusCode} />; |   return <NextErrorComponent statusCode={props.statusCode} />; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| CustomErrorComponent.getInitialProps = async contextData => { | CustomErrorComponent.getInitialProps = async (contextData) => { | ||||||
|   // In case this is running in a serverless function, await this in order to give Sentry
 |   // In case this is running in a serverless function, await this in order to give Sentry
 | ||||||
|   // time to send the error before the lambda exits
 |   // time to send the error before the lambda exits
 | ||||||
|   await Sentry.captureUnderscoreErrorException(contextData); |   await Sentry.captureUnderscoreErrorException(contextData); | ||||||
|  |  | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| export default function EditMember() { | export default function EditMember() { | ||||||
|     return <>Editing a member!</>; |   return <>Editing a member!</>; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ import { useEffect } from "react"; | ||||||
| import Loading from "../../../components/Loading"; | import Loading from "../../../components/Loading"; | ||||||
| 
 | 
 | ||||||
| export default function Redirect() { | export default function Redirect() { | ||||||
|     const router = useRouter(); |   const router = useRouter(); | ||||||
|     useEffect(() => { |   useEffect(() => { | ||||||
|         router.push("/") |     router.push("/"); | ||||||
|     }, []) |   }, []); | ||||||
| 
 | 
 | ||||||
|     return <Loading />; |   return <Loading />; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,156 +9,162 @@ import cloneDeep from "lodash/cloneDeep"; | ||||||
| import { ReactSortable } from "react-sortablejs"; | import { ReactSortable } from "react-sortablejs"; | ||||||
| import Card from "../../components/Card"; | import Card from "../../components/Card"; | ||||||
| 
 | 
 | ||||||
| import { EditableCard, EditField, PronounChoice } from "../../components/Editable"; | import { | ||||||
|  |   EditableCard, | ||||||
|  |   EditField, | ||||||
|  |   PronounChoice, | ||||||
|  | } from "../../components/Editable"; | ||||||
| 
 | 
 | ||||||
| export default function Index() { | export default function Index() { | ||||||
|     const [user, setUser] = useRecoilState(userState); |   const [user, setUser] = useRecoilState(userState); | ||||||
|     const router = useRouter(); |   const router = useRouter(); | ||||||
| 
 |  | ||||||
|     useEffect(() => { |  | ||||||
|         if (!user) { |  | ||||||
|             router.push("/"); |  | ||||||
|         } |  | ||||||
|     }, [user]) |  | ||||||
| 
 | 
 | ||||||
|  |   useEffect(() => { | ||||||
|     if (!user) { |     if (!user) { | ||||||
|         return <Loading />; |       router.push("/"); | ||||||
|     } |     } | ||||||
|  |   }, [user]); | ||||||
| 
 | 
 | ||||||
|     const [state, setState] = useState(cloneDeep(user)); |   if (!user) { | ||||||
|  |     return <Loading />; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|     const originalOrder = state.fields ? state.fields.map((f, i) => { |   const [state, setState] = useState(cloneDeep(user)); | ||||||
|  | 
 | ||||||
|  |   const originalOrder = state.fields | ||||||
|  |     ? state.fields.map((f, i) => { | ||||||
|         const field: EditField = { |         const field: EditField = { | ||||||
|             id: i, |           id: i, | ||||||
|             name: f.name, |           name: f.name, | ||||||
|             pronouns: {}, |           pronouns: {}, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         f.favourite?.forEach((val) => { |         f.favourite?.forEach((val) => { | ||||||
|             field.pronouns[val] = PronounChoice.favourite; |           field.pronouns[val] = PronounChoice.favourite; | ||||||
|         }); |         }); | ||||||
|         f.okay?.forEach((val) => { |         f.okay?.forEach((val) => { | ||||||
|             field.pronouns[val] = PronounChoice.okay; |           field.pronouns[val] = PronounChoice.okay; | ||||||
|         }); |         }); | ||||||
|         f.jokingly?.forEach((val) => { |         f.jokingly?.forEach((val) => { | ||||||
|             field.pronouns[val] = PronounChoice.jokingly; |           field.pronouns[val] = PronounChoice.jokingly; | ||||||
|         }); |         }); | ||||||
|         f.friends_only?.forEach((val) => { |         f.friends_only?.forEach((val) => { | ||||||
|             field.pronouns[val] = PronounChoice.friendsOnly; |           field.pronouns[val] = PronounChoice.friendsOnly; | ||||||
|         }); |         }); | ||||||
|         f.avoid?.forEach((val) => { |         f.avoid?.forEach((val) => { | ||||||
|             field.pronouns[val] = PronounChoice.avoid; |           field.pronouns[val] = PronounChoice.avoid; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         return field; |         return field; | ||||||
|     }) : []; |       }) | ||||||
|  |     : []; | ||||||
| 
 | 
 | ||||||
|     const [fields, setFields] = useState(cloneDeep(originalOrder)); |   const [fields, setFields] = useState(cloneDeep(originalOrder)); | ||||||
|     const fieldsUpdated = !fieldsEqual(fields, originalOrder); |   const fieldsUpdated = !fieldsEqual(fields, originalOrder); | ||||||
| 
 | 
 | ||||||
|     return ( |   return ( | ||||||
|         <div className="container mx-auto"> |     <div className="container mx-auto"> | ||||||
|             <div>{`fieldsUpdated: ${fieldsUpdated}`}</div> |       <div>{`fieldsUpdated: ${fieldsUpdated}`}</div> | ||||||
|             {/* @ts-ignore: This component isn't updated to have a "children" prop yet, but it accepts it just fine. */} |       {/* @ts-ignore: This component isn't updated to have a "children" prop yet, but it accepts it just fine. */} | ||||||
|             <ReactSortable |       <ReactSortable | ||||||
|                 handle=".handle" |         handle=".handle" | ||||||
|                 list={fields} |         list={fields} | ||||||
|                 setList={setFields} |         setList={setFields} | ||||||
|                 className="grid grid-cols-1 xl:grid-cols-2 gap-4 py-2" |         className="grid grid-cols-1 xl:grid-cols-2 gap-4 py-2" | ||||||
|             > |       > | ||||||
|                 {fields.map((field, i) => ( |         {fields.map((field, i) => ( | ||||||
|                     <EditableCard |           <EditableCard | ||||||
|                         key={i} |             key={i} | ||||||
|                         field={field} |             field={field} | ||||||
|                         onChangeName={(e) => { |             onChangeName={(e) => { | ||||||
|                             field.name = e.target.value; |               field.name = e.target.value; | ||||||
|                             setFields([...fields]); |               setFields([...fields]); | ||||||
|                         }} |             }} | ||||||
|                         onChangeFavourite={(e, entry: string) => { |             onChangeFavourite={(e, entry: string) => { | ||||||
|                             field.pronouns[entry] = PronounChoice.favourite; |               field.pronouns[entry] = PronounChoice.favourite; | ||||||
|                             setFields([...fields]); |               setFields([...fields]); | ||||||
|                         }} |             }} | ||||||
|                         onChangeOkay={(e, entry: string) => { |             onChangeOkay={(e, entry: string) => { | ||||||
|                             field.pronouns[entry] = PronounChoice.okay; |               field.pronouns[entry] = PronounChoice.okay; | ||||||
|                             setFields([...fields]); |               setFields([...fields]); | ||||||
|                         }} |             }} | ||||||
|                         onChangeJokingly={(e, entry: string) => { |             onChangeJokingly={(e, entry: string) => { | ||||||
|                             field.pronouns[entry] = PronounChoice.jokingly; |               field.pronouns[entry] = PronounChoice.jokingly; | ||||||
|                             setFields([...fields]); |               setFields([...fields]); | ||||||
|                         }} |             }} | ||||||
|                         onChangeFriends={(e, entry: string) => { |             onChangeFriends={(e, entry: string) => { | ||||||
|                             field.pronouns[entry] = PronounChoice.friendsOnly; |               field.pronouns[entry] = PronounChoice.friendsOnly; | ||||||
|                             setFields([...fields]); |               setFields([...fields]); | ||||||
|                         }} |             }} | ||||||
|                         onChangeAvoid={(e, entry: string) => { |             onChangeAvoid={(e, entry: string) => { | ||||||
|                             field.pronouns[entry] = PronounChoice.avoid; |               field.pronouns[entry] = PronounChoice.avoid; | ||||||
|                             setFields([...fields]); |               setFields([...fields]); | ||||||
|                         }} |             }} | ||||||
|                         onClickDelete={(_) => { |             onClickDelete={(_) => { | ||||||
|                             const newFields = [...fields]; |               const newFields = [...fields]; | ||||||
|                             newFields.splice(i, 1); |               newFields.splice(i, 1); | ||||||
|                             setFields(newFields); |               setFields(newFields); | ||||||
|                         }} |             }} | ||||||
|                     /> |           /> | ||||||
|                 ))} |         ))} | ||||||
|             </ReactSortable> |       </ReactSortable> | ||||||
|         </div> |     </div> | ||||||
|     ); |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function fieldsEqual(arr1: EditField[], arr2: EditField[]) { | function fieldsEqual(arr1: EditField[], arr2: EditField[]) { | ||||||
|     if (arr1?.length !== arr2?.length) return false; |   if (arr1?.length !== arr2?.length) return false; | ||||||
| 
 | 
 | ||||||
|     if (!arr1.every((_, i) => arr1[i].id === arr2[i].id)) return false; |   if (!arr1.every((_, i) => arr1[i].id === arr2[i].id)) return false; | ||||||
| 
 | 
 | ||||||
|     return arr1.every((_, i) => |   return arr1.every((_, i) => | ||||||
|         Object.keys(arr1[i].pronouns).every( |     Object.keys(arr1[i].pronouns).every( | ||||||
|             (val) => arr1[i].pronouns[val] === arr2[i].pronouns[val] |       (val) => arr1[i].pronouns[val] === arr2[i].pronouns[val] | ||||||
|         ) |     ) | ||||||
|     ); |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function updateUser(args: { | async function updateUser(args: { | ||||||
|     displayName: string; |   displayName: string; | ||||||
|     bio: string; |   bio: string; | ||||||
|     fields: EditField[]; |   fields: EditField[]; | ||||||
| }) { | }) { | ||||||
|     const newFields = args.fields.map((editField) => { |   const newFields = args.fields.map((editField) => { | ||||||
|         const field: Field = { |     const field: Field = { | ||||||
|             name: editField.name, |       name: editField.name, | ||||||
|             favourite: [], |       favourite: [], | ||||||
|             okay: [], |       okay: [], | ||||||
|             jokingly: [], |       jokingly: [], | ||||||
|             friends_only: [], |       friends_only: [], | ||||||
|             avoid: [], |       avoid: [], | ||||||
|         }; |     }; | ||||||
| 
 | 
 | ||||||
|         Object.keys(editField).forEach((pronoun) => { |     Object.keys(editField).forEach((pronoun) => { | ||||||
|             switch (editField.pronouns[pronoun]) { |       switch (editField.pronouns[pronoun]) { | ||||||
|                 case PronounChoice.favourite: |         case PronounChoice.favourite: | ||||||
|                     field.favourite?.push(pronoun); |           field.favourite?.push(pronoun); | ||||||
|                     break; |           break; | ||||||
|                 case PronounChoice.okay: |         case PronounChoice.okay: | ||||||
|                     field.okay?.push(pronoun); |           field.okay?.push(pronoun); | ||||||
|                     break; |           break; | ||||||
|                 case PronounChoice.jokingly: |         case PronounChoice.jokingly: | ||||||
|                     field.jokingly?.push(pronoun); |           field.jokingly?.push(pronoun); | ||||||
|                     break; |           break; | ||||||
|                 case PronounChoice.friendsOnly: |         case PronounChoice.friendsOnly: | ||||||
|                     field.friends_only?.push(pronoun); |           field.friends_only?.push(pronoun); | ||||||
|                     break; |           break; | ||||||
|                 case PronounChoice.avoid: |         case PronounChoice.avoid: | ||||||
|                     field.avoid?.push(pronoun); |           field.avoid?.push(pronoun); | ||||||
|                     break; |           break; | ||||||
|             } |       } | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         return field; |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     return await fetchAPI<MeUser>("/users/@me", "PATCH", { |     return field; | ||||||
|         display_name: args.displayName, |   }); | ||||||
|         bio: args.bio, | 
 | ||||||
|         fields: newFields, |   return await fetchAPI<MeUser>("/users/@me", "PATCH", { | ||||||
|     }); |     display_name: args.displayName, | ||||||
|  |     bio: args.bio, | ||||||
|  |     fields: newFields, | ||||||
|  |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,6 +5,9 @@ import fetchAPI from "../../lib/fetch"; | ||||||
| import { userState } from "../../lib/state"; | import { userState } from "../../lib/state"; | ||||||
| import { APIError, MeUser, SignupResponse } from "../../lib/types"; | import { APIError, MeUser, SignupResponse } from "../../lib/types"; | ||||||
| import TextInput from "../../components/TextInput"; | import TextInput from "../../components/TextInput"; | ||||||
|  | import Loading from "../../components/Loading"; | ||||||
|  | import { stat } from "fs"; | ||||||
|  | import Button, { ButtonStyle } from "../../components/Button"; | ||||||
| 
 | 
 | ||||||
| interface CallbackResponse { | interface CallbackResponse { | ||||||
|   has_account: boolean; |   has_account: boolean; | ||||||
|  | @ -41,41 +44,47 @@ export default function Discord() { | ||||||
|     error: null, |     error: null, | ||||||
|     requireInvite: false, |     requireInvite: false, | ||||||
|   }); |   }); | ||||||
|   const [formData, setFormData] = useState<{ username: string, invite: string }>({ username: "", invite: "" }); |   const [formData, setFormData] = useState<{ | ||||||
|  |     username: string; | ||||||
|  |     invite: string; | ||||||
|  |   }>({ username: "", invite: "" }); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (!router.query.code || !router.query.state) { return; } |     if (!router.query.code || !router.query.state) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (state.ticket || state.token) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     fetchAPI<CallbackResponse>( |     fetchAPI<CallbackResponse>("/auth/discord/callback", "POST", { | ||||||
|       "/auth/discord/callback", |       callback_domain: window.location.origin, | ||||||
|       "POST", |       code: router.query.code, | ||||||
|       { |       state: router.query.state, | ||||||
|         callback_domain: window.location.origin, |  | ||||||
|         code: router.query.code, |  | ||||||
|         state: router.query.state, |  | ||||||
|       } |  | ||||||
|     ).then(resp => { |  | ||||||
|       setState({ |  | ||||||
|         hasAccount: resp.has_account, |  | ||||||
|         isLoading: false, |  | ||||||
|         token: resp.token || null, |  | ||||||
|         user: resp.user || null, |  | ||||||
|         discord: resp.discord || null, |  | ||||||
|         ticket: resp.ticket || null, |  | ||||||
|         requireInvite: resp.require_invite, |  | ||||||
|       }) |  | ||||||
|     }).catch(e => { |  | ||||||
|       setState({ |  | ||||||
|         hasAccount: false, |  | ||||||
|         isLoading: false, |  | ||||||
|         error: e, |  | ||||||
|         token: null, |  | ||||||
|         user: null, |  | ||||||
|         discord: null, |  | ||||||
|         ticket: null, |  | ||||||
|         requireInvite: false, |  | ||||||
|       }); |  | ||||||
|     }) |     }) | ||||||
|  |       .then((resp) => { | ||||||
|  |         setState({ | ||||||
|  |           hasAccount: resp.has_account, | ||||||
|  |           isLoading: false, | ||||||
|  |           token: resp.token || null, | ||||||
|  |           user: resp.user || null, | ||||||
|  |           discord: resp.discord || null, | ||||||
|  |           ticket: resp.ticket || null, | ||||||
|  |           requireInvite: resp.require_invite, | ||||||
|  |         }); | ||||||
|  |       }) | ||||||
|  |       .catch((e) => { | ||||||
|  |         setState({ | ||||||
|  |           hasAccount: false, | ||||||
|  |           isLoading: false, | ||||||
|  |           error: e, | ||||||
|  |           token: null, | ||||||
|  |           user: null, | ||||||
|  |           discord: null, | ||||||
|  |           ticket: null, | ||||||
|  |           requireInvite: false, | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
| 
 | 
 | ||||||
|     // we got a token + user, save it and return to the home page
 |     // we got a token + user, save it and return to the home page
 | ||||||
|     if (state.token) { |     if (state.token) { | ||||||
|  | @ -86,14 +95,29 @@ export default function Discord() { | ||||||
|     } |     } | ||||||
|   }, [state.token, state.user, setState, router]); |   }, [state.token, state.user, setState, router]); | ||||||
| 
 | 
 | ||||||
|  |   if (!state.ticket && !state.error) { | ||||||
|  |     return <Loading />; | ||||||
|  |   } else if (state.error) { | ||||||
|  |     return ( | ||||||
|  |       <div className="bg-red-600 dark:bg-red-700 p-2 rounded-md"> | ||||||
|  |         <p>Error: {state.error.message ?? state.error}</p> | ||||||
|  |         <p>Try again?</p> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   // user needs to create an account
 |   // user needs to create an account
 | ||||||
|   const signup = async () => { |   const signup = async () => { | ||||||
|     try { |     try { | ||||||
|       const resp = await fetchAPI<SignupResponse>("/auth/discord/signup", "POST", { |       const resp = await fetchAPI<SignupResponse>( | ||||||
|         ticket: state.ticket, |         "/auth/discord/signup", | ||||||
|         username: formData.username, |         "POST", | ||||||
|         invite_code: formData.invite, |         { | ||||||
|       }); |           ticket: state.ticket, | ||||||
|  |           username: formData.username, | ||||||
|  |           invite_code: formData.invite, | ||||||
|  |         } | ||||||
|  |       ); | ||||||
| 
 | 
 | ||||||
|       setUser(resp.user); |       setUser(resp.user); | ||||||
|       localStorage.setItem("pronouns-token", resp.token); |       localStorage.setItem("pronouns-token", resp.token); | ||||||
|  | @ -104,33 +128,46 @@ export default function Discord() { | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   return <> |   return ( | ||||||
|     <h1 className="font-bold text-lg">Get started</h1> |     <> | ||||||
|     <p>You{"'"}ve logged in with Discord as <strong className="font-bold">{state.discord}</strong>.</p> |       <h1 className="font-bold text-lg">Get started</h1> | ||||||
|  |       <p> | ||||||
|  |         You{"'"}ve logged in with Discord as{" "} | ||||||
|  |         <strong className="font-bold">{state.discord}</strong>. | ||||||
|  |       </p> | ||||||
| 
 | 
 | ||||||
|     {state.error && ( |       {state.error && ( | ||||||
|       <div className="bg-red-600 dark:bg-red-700 p-2 rounded-md"> |         <div className="bg-red-600 dark:bg-red-700 p-2 rounded-md"> | ||||||
|         <p>Error: {state.error.message ?? state.error}</p> |           <p>Error: {state.error.message ?? state.error}</p> | ||||||
|         <p>Try again?</p> |           <p>Try again?</p> | ||||||
|       </div> |         </div> | ||||||
|     )} |       )} | ||||||
| 
 | 
 | ||||||
|     <label> |  | ||||||
|       <span className="font-bold">Username</span> |  | ||||||
|       <TextInput value={formData.username} onChange={(e) => setFormData({ ...formData, username: e.target.value })} /> |  | ||||||
|     </label> |  | ||||||
|     {state.requireInvite && ( |  | ||||||
|       <label> |       <label> | ||||||
|         <span className="font-bold">Invite code</span> |         <span className="font-bold">Username</span> | ||||||
|         <TextInput value={formData.invite} onChange={(e) => setFormData({ ...formData, invite: e.target.value })} /> |         <TextInput | ||||||
|  |           contrastBackground | ||||||
|  |           value={formData.username} | ||||||
|  |           onChange={(e) => | ||||||
|  |             setFormData({ ...formData, username: e.target.value }) | ||||||
|  |           } | ||||||
|  |         /> | ||||||
|       </label> |       </label> | ||||||
|     )} |       {state.requireInvite && ( | ||||||
|     <button |         <label> | ||||||
|       type="button" |           <span className="font-bold">Invite code</span> | ||||||
|       onClick={() => signup()} |           <TextInput | ||||||
|       className="bg-green-600 dark:bg-green-700 hover:bg-green-700 hover:dark:bg-green-800 p-2 rounded-md" |             contrastBackground | ||||||
|     > |             value={formData.invite} | ||||||
|       <span className="font-bold">Create account</span> |             onChange={(e) => | ||||||
|     </button> |               setFormData({ ...formData, invite: e.target.value }) | ||||||
|   </>; |             } | ||||||
|  |           /> | ||||||
|  |         </label> | ||||||
|  |       )} | ||||||
|  |       <Button style={ButtonStyle.success} onClick={() => signup()}> | ||||||
|  |         Create account | ||||||
|  |       </Button> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,7 +11,13 @@ import { useRecoilValue } from "recoil"; | ||||||
| import Link from "next/link"; | import Link from "next/link"; | ||||||
| import FallbackImage from "../../../components/FallbackImage"; | import FallbackImage from "../../../components/FallbackImage"; | ||||||
| import { ReactNode } from "react"; | import { ReactNode } from "react"; | ||||||
| import { EmojiLaughing, HandThumbsDown, HandThumbsUp, HeartFill, People } from "react-bootstrap-icons"; | import { | ||||||
|  |   EmojiLaughing, | ||||||
|  |   HandThumbsDown, | ||||||
|  |   HandThumbsUp, | ||||||
|  |   HeartFill, | ||||||
|  |   People, | ||||||
|  | } from "react-bootstrap-icons"; | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   user: User; |   user: User; | ||||||
|  | @ -54,10 +60,11 @@ export default function Index({ user }: Props) { | ||||||
|               <h1 className="text-2xl font-bold">{user.display_name}</h1> |               <h1 className="text-2xl font-bold">{user.display_name}</h1> | ||||||
|             )} |             )} | ||||||
|             <h3 |             <h3 | ||||||
|               className={`${user.display_name |               className={`${ | ||||||
|                 ? "text-xl italic text-slate-600 dark:text-slate-400" |                 user.display_name | ||||||
|                 : "text-2xl font-bold" |                   ? "text-xl italic text-slate-600 dark:text-slate-400" | ||||||
|                 }`}
 |                   : "text-2xl font-bold" | ||||||
|  |               }`}
 | ||||||
|             > |             > | ||||||
|               @{user.username} |               @{user.username} | ||||||
|             </h3> |             </h3> | ||||||
|  | @ -82,12 +89,20 @@ export default function Index({ user }: Props) { | ||||||
|             )} |             )} | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         {user.names?.length > 0 && <div className="border-b border-slate-200 dark:border-slate-700"> |         {user.names?.length > 0 && ( | ||||||
|           {user.names.map((name, index) => <NameEntry name={name} key={index} />)} |           <div className="border-b border-slate-200 dark:border-slate-700"> | ||||||
|         </div>} |             {user.names.map((name, index) => ( | ||||||
|         {user.pronouns?.length > 0 && <div className="border-b border-slate-200 dark:border-slate-700"> |               <NameEntry name={name} key={index} /> | ||||||
|           {user.pronouns.map((pronoun, index) => <PronounEntry pronoun={pronoun} key={index} />)} |             ))} | ||||||
|         </div>} |           </div> | ||||||
|  |         )} | ||||||
|  |         {user.pronouns?.length > 0 && ( | ||||||
|  |           <div className="border-b border-slate-200 dark:border-slate-700"> | ||||||
|  |             {user.pronouns.map((pronoun, index) => ( | ||||||
|  |               <PronounEntry pronoun={pronoun} key={index} /> | ||||||
|  |             ))} | ||||||
|  |           </div> | ||||||
|  |         )} | ||||||
|         <div className="grid grid-cols-1 md:grid-cols-3 gap-4 py-2"> |         <div className="grid grid-cols-1 md:grid-cols-3 gap-4 py-2"> | ||||||
|           {user.fields?.map((field, index) => ( |           {user.fields?.map((field, index) => ( | ||||||
|             <FieldCard key={index} field={field}></FieldCard> |             <FieldCard key={index} field={field}></FieldCard> | ||||||
|  | @ -112,30 +127,44 @@ const entryIcon = (status: WordStatus) => { | ||||||
|       icon = <EmojiLaughing className="inline" />; |       icon = <EmojiLaughing className="inline" />; | ||||||
|       break; |       break; | ||||||
|     case WordStatus.FriendsOnly: |     case WordStatus.FriendsOnly: | ||||||
|       icon = <People className="inline" /> |       icon = <People className="inline" />; | ||||||
|       break; |       break; | ||||||
|     case WordStatus.Avoid: |     case WordStatus.Avoid: | ||||||
|       icon = <HandThumbsDown className="inline" /> |       icon = <HandThumbsDown className="inline" />; | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return icon; |   return icon; | ||||||
| } | }; | ||||||
| 
 | 
 | ||||||
| function NameEntry(props: { name: Name }) { | function NameEntry(props: { name: Name }) { | ||||||
|   const { name } = props; |   const { name } = props; | ||||||
| 
 | 
 | ||||||
|   return <p className={`text-lg ${name.status === WordStatus.Favourite && "font-bold"}`}> |   return ( | ||||||
|     {entryIcon(name.status)} {name.name} |     <p | ||||||
|   </p> |       className={`text-lg ${ | ||||||
|  |         name.status === WordStatus.Favourite && "font-bold" | ||||||
|  |       }`}
 | ||||||
|  |     > | ||||||
|  |       {entryIcon(name.status)} {name.name} | ||||||
|  |     </p> | ||||||
|  |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function PronounEntry(props: { pronoun: Pronoun }) { | function PronounEntry(props: { pronoun: Pronoun }) { | ||||||
|   const { pronoun } = props; |   const { pronoun } = props; | ||||||
| 
 | 
 | ||||||
|   return <p className={`text-lg ${pronoun.status === WordStatus.Favourite && "font-bold"}`}> |   return ( | ||||||
|     {entryIcon(pronoun.status)} {pronoun.display_text ?? pronoun.pronouns.split("/").slice(0, 2).join("/")} |     <p | ||||||
|   </p> |       className={`text-lg ${ | ||||||
|  |         pronoun.status === WordStatus.Favourite && "font-bold" | ||||||
|  |       }`}
 | ||||||
|  |     > | ||||||
|  |       {entryIcon(pronoun.status)}{" "} | ||||||
|  |       {pronoun.display_text ?? | ||||||
|  |         pronoun.pronouns.split("/").slice(0, 2).join("/")} | ||||||
|  |     </p> | ||||||
|  |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const getServerSideProps: GetServerSideProps = async (context) => { | export const getServerSideProps: GetServerSideProps = async (context) => { | ||||||
|  |  | ||||||
|  | @ -2,12 +2,14 @@ | ||||||
| // The config you add here will be used whenever a page is visited.
 | // The config you add here will be used whenever a page is visited.
 | ||||||
| // https://docs.sentry.io/platforms/javascript/guides/nextjs/
 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
 | ||||||
| 
 | 
 | ||||||
| import * as Sentry from '@sentry/nextjs'; | import * as Sentry from "@sentry/nextjs"; | ||||||
| 
 | 
 | ||||||
| const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; | const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; | ||||||
| 
 | 
 | ||||||
| Sentry.init({ | Sentry.init({ | ||||||
|   dsn: SENTRY_DSN || 'https://91af8c15c9cf4153aa260b7f57457d8f@o575775.ingest.sentry.io/6390139', |   dsn: | ||||||
|  |     SENTRY_DSN || | ||||||
|  |     "https://91af8c15c9cf4153aa260b7f57457d8f@o575775.ingest.sentry.io/6390139", | ||||||
|   // Adjust this value in production, or use tracesSampler for greater control
 |   // Adjust this value in production, or use tracesSampler for greater control
 | ||||||
|   tracesSampleRate: 1.0, |   tracesSampleRate: 1.0, | ||||||
|   // ...
 |   // ...
 | ||||||
|  |  | ||||||
|  | @ -2,12 +2,14 @@ | ||||||
| // The config you add here will be used whenever the server handles a request.
 | // The config you add here will be used whenever the server handles a request.
 | ||||||
| // https://docs.sentry.io/platforms/javascript/guides/nextjs/
 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
 | ||||||
| 
 | 
 | ||||||
| import * as Sentry from '@sentry/nextjs'; | import * as Sentry from "@sentry/nextjs"; | ||||||
| 
 | 
 | ||||||
| const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; | const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; | ||||||
| 
 | 
 | ||||||
| Sentry.init({ | Sentry.init({ | ||||||
|   dsn: SENTRY_DSN || 'https://91af8c15c9cf4153aa260b7f57457d8f@o575775.ingest.sentry.io/6390139', |   dsn: | ||||||
|  |     SENTRY_DSN || | ||||||
|  |     "https://91af8c15c9cf4153aa260b7f57457d8f@o575775.ingest.sentry.io/6390139", | ||||||
|   // Adjust this value in production, or use tracesSampler for greater control
 |   // Adjust this value in production, or use tracesSampler for greater control
 | ||||||
|   tracesSampleRate: 1.0, |   tracesSampleRate: 1.0, | ||||||
|   // ...
 |   // ...
 | ||||||
|  |  | ||||||
|  | @ -2379,6 +2379,11 @@ prelude-ls@^1.2.1: | ||||||
|   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" |   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" | ||||||
|   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== |   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== | ||||||
| 
 | 
 | ||||||
|  | prettier@2.7.1: | ||||||
|  |   version "2.7.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" | ||||||
|  |   integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== | ||||||
|  | 
 | ||||||
| process-nextick-args@~2.0.0: | process-nextick-args@~2.0.0: | ||||||
|   version "2.0.1" |   version "2.0.1" | ||||||
|   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" |   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue