{[...Array(16).keys()].map(function (cost) {
- const cards_color_type_cost =
- cards_color_type.filter(
- (card) => card.cmc === cost
- );
+ const cards_color_type_cost = cards_color_type.filter(
+ (card) =>
+ (Number.isInteger(card.cmc) ? card.cmc : card.scryfall_card.cmc) ===
+ cost
+ );
return (
{cards_color_type_cost.length > 0 && (
- {customSort(cards_color_type_cost, [
- 'name'
- ]).map(function (card, index) {
- return (
-
-
-
- setSelectedCard(card)
+ {customSort(cards_color_type_cost, ['scryfall_card.name']).map(
+ function (card, index) {
+ return (
+
+
- {index + 1}) {card.name}
- {!card.mtgo_id && (
-
-
-
- )}
-
-
-
- );
- })}
+ setSelectedCard(card)}
+ style={{ cursor: 'pointer' }}
+ variant="body1"
+ >
+ {index + 1}){' '}
+ {card.name ? card.name : card.scryfall_card.name}
+ {!card.scryfall_card.mtgo_id && (
+
+
+
+ )}
+
+
+
+ );
+ }
+ )}
)}
@@ -244,8 +247,9 @@ export default function CubeDisplay({ setSelectedCard }) {
(
{
- displayedCards.filter((card) => card.color_identity.length > 1)
- .length
+ displayedCards.filter(
+ (card) => (card.color_identity ?? card.scryfall_card.color_identity).length > 1
+ ).length
}
)
@@ -254,20 +258,17 @@ export default function CubeDisplay({ setSelectedCard }) {
{multiColors.map(function (color) {
const cards_color = displayedCards.filter(
- (card) => card.color_identity.toString() === color.color_identity
+ (card) =>
+ (card.color_identity ?? card.scryfall_card.color_identity).toString() ===
+ color.color_identity
);
return (
{cards_color.length > 0 && (
-
+
{color.svg &&
React.cloneElement(color.svg, {
style: { height: 24, marginRight: 8, width: 24 }
@@ -276,7 +277,9 @@ export default function CubeDisplay({ setSelectedCard }) {
{generalCardTypes.map(function (type) {
const cards_color_type = cards_color.filter(
- (card) => type.name === generalCardType(card.type_line)
+ (card) =>
+ generalCardType(card.type_line ?? card.scryfall_card.type_line) ===
+ type.name
);
return (
cards_color_type.length > 0 && (
@@ -293,44 +296,51 @@ export default function CubeDisplay({ setSelectedCard }) {
})}
{type.name}
- {customSort(cards_color_type, ['cmc']).map(
- function (card, index) {
- return (
-
-
+
+ setSelectedCard(card)}
+ style={{
+ cursor: 'pointer',
+ userSelect: 'none'
+ }}
+ variant="body1"
>
-
- setSelectedCard(card)
- }
- style={{
- cursor: 'pointer',
- userSelect: 'none'
- }}
- variant="body1"
- >
- {index + 1}) {card.name}
- {!card.mtgo_id && (
-
-
-
- )}
-
-
-
- );
- }
- )}
+ {index + 1}) {card.name ?? card.scryfall_card.name}
+ {!card.scryfall_card.mtgo_id && (
+
+
+
+ )}
+
+
+
+ );
+ })}
)
);
diff --git a/src/components/Cube Page/CubeFilterInput.jsx b/src/components/Cube Page/CubeFilterInput.jsx
new file mode 100644
index 0000000..0928ab3
--- /dev/null
+++ b/src/components/Cube Page/CubeFilterInput.jsx
@@ -0,0 +1,47 @@
+import React, { useContext, useRef, useState } from 'react';
+import MUIInputAdornment from '@mui/material/InputAdornment';
+import MUITextField from '@mui/material/TextField';
+
+import { CubeContext } from '../../contexts/cube-context';
+
+export default function CubeFilterInput() {
+ const {
+ activeComponentState,
+ displayState: { filter },
+ setDisplayState
+ } = useContext(CubeContext);
+ const timer = useRef();
+ const [cubeFilterInputState, setCubeFilterInputState] = useState(filter);
+
+ return (
+
+
+ Matches:{' '}
+ {activeComponentState.displayedCards.length}
+
+
+ )
+ }}
+ label="Filter by keywords, name or type"
+ margin="normal"
+ onChange={(event) => {
+ event.persist();
+ clearTimeout(timer.current);
+ setCubeFilterInputState(event.target.value);
+ timer.current = setTimeout(() => {
+ setDisplayState((prevState) => ({
+ ...prevState,
+ filter: event.target.value
+ }));
+ }, 100);
+ }}
+ type="text"
+ value={cubeFilterInputState}
+ />
+ );
+}
diff --git a/src/components/Cube Page/CubeNameInput.jsx b/src/components/Cube Page/CubeNameInput.jsx
new file mode 100644
index 0000000..4c01b54
--- /dev/null
+++ b/src/components/Cube Page/CubeNameInput.jsx
@@ -0,0 +1,44 @@
+import React, { useContext, useEffect, useRef, useState } from 'react';
+import MUITextField from '@mui/material/TextField';
+
+import editCube from '../../graphql/mutations/cube/edit-cube';
+import { CubeContext } from '../../contexts/cube-context';
+import { ErrorContext } from '../../contexts/Error';
+
+export default function CubeNameInput() {
+ const {
+ cubeState: { _id: cubeID, name: cubeName }
+ } = useContext(CubeContext);
+ const { setErrorMessages } = useContext(ErrorContext);
+ const cubeNameInputRef = useRef();
+ const [cubeNameInputState, setCubeNameInputState] = useState(cubeName);
+
+ useEffect(() => {
+ setCubeNameInputState(cubeName);
+ }, [cubeName]);
+
+ return (
+ {
+ try {
+ const data = await editCube({
+ headers: { CubeID: cubeID },
+ queryString: `{\n_id\nname\n}`,
+ variables: { name: cubeNameInputState }
+ });
+ setCubeNameInputState(data.data.editCube.name);
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ setCubeNameInputState(cubeName);
+ }
+ }
+ }}
+ label="Cube Name"
+ onChange={(event) => setCubeNameInputState(event.target.value)}
+ ref={cubeNameInputRef}
+ type="text"
+ value={cubeNameInputState}
+ />
+ );
+}
diff --git a/src/components/Cube Page/CubePublishedCheckbox.jsx b/src/components/Cube Page/CubePublishedCheckbox.jsx
new file mode 100644
index 0000000..9d500aa
--- /dev/null
+++ b/src/components/Cube Page/CubePublishedCheckbox.jsx
@@ -0,0 +1,59 @@
+import React, { useContext, useEffect, useState } from 'react';
+import MUICheckbox from '@mui/material/Checkbox';
+import MUIFormControlLabel from '@mui/material/FormControlLabel';
+import MUIHelpOutlineIcon from '@mui/icons-material/HelpOutline';
+import MUITooltip from '@mui/material/Tooltip';
+
+import editCube from '../../graphql/mutations/cube/edit-cube';
+import { CubeContext } from '../../contexts/cube-context';
+import { ErrorContext } from '../../contexts/Error';
+
+export default function CubePublishedCheckbox() {
+ const {
+ cubeState: { _id: cubeID, published }
+ } = useContext(CubeContext);
+ const { setErrorMessages } = useContext(ErrorContext);
+ const [publishedCheckedState, setPublishedCheckedState] = useState(published);
+
+ useEffect(() => {
+ setPublishedCheckedState(published);
+ }, [published]);
+
+ useEffect(() => {
+ (async function () {
+ try {
+ await editCube({
+ headers: { CubeID: cubeID },
+ queryString: `{\n_id\npublished\n}`,
+ variables: { published: publishedCheckedState }
+ });
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ setPublishedCheckedState(published);
+ }
+ })();
+ }, [publishedCheckedState]);
+
+ return (
+
+ setPublishedCheckedState((prevState) => !prevState)}
+ />
+ }
+ label="Published"
+ style={{ marginRight: 8 }}
+ />
+
+
+
+
+ );
+}
diff --git a/src/components/Cube Page/EditCardModal.jsx b/src/components/Cube Page/EditCardModal.jsx
index cc6295f..524436e 100644
--- a/src/components/Cube Page/EditCardModal.jsx
+++ b/src/components/Cube Page/EditCardModal.jsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useContext, useState } from 'react';
import MUIButton from '@mui/material/Button';
import MUICancelOutlinedIcon from '@mui/icons-material/CancelOutlined';
import MUIDialog from '@mui/material/Dialog';
@@ -8,8 +8,10 @@ import MUIDialogTitle from '@mui/material/DialogTitle';
import MUIGrid from '@mui/material/Grid';
import MUIPublishedWithChangesOutlinedIcon from '@mui/icons-material/PublishedWithChangesOutlined';
import MUITextField from '@mui/material/TextField';
+import { useParams } from 'react-router-dom';
-import ChangePrintMenu from './ChangePrintMenu';
+import editCard from '../../graphql/mutations/cube/edit-card';
+// import ChangePrintMenu from './ChangePrintMenu';
import ColorCheckboxes from './ColorCheckboxes';
import MoveDeleteMenu from './MoveDeleteMenu';
import { CubeContext } from '../../contexts/cube-context';
@@ -17,69 +19,61 @@ import { CubeContext } from '../../contexts/cube-context';
export default function EditCardModal({ card, clear, editable }) {
const {
activeComponentState: { _id: activeComponentID },
- deleteCard,
- editCard
- } = React.useContext(CubeContext);
- const [destination, setDestination] = React.useState(activeComponentID);
- const [mutableCardDetails, setMutableCardDetails] = React.useState({
- cmc: card.cmc,
- color_identity: card.color_identity,
+ deleteCard
+ } = useContext(CubeContext);
+ const { cubeID } = useParams();
+ const [destination, setDestination] = useState(activeComponentID);
+ const [mutableCardDetails, setMutableCardDetails] = useState({
+ cmc: Number.isInteger(card.cmc) ? card.cmc : card.scryfall_card.cmc,
+ color_identity: card.color_identity ? card.color_identity : card.scryfall_card.color_identity,
notes: card.notes,
- scryfall_id: card.scryfall_id,
- type_line: card.type_line
+ scryfall_id: card.scryfall_card._id,
+ type_line: card.type_line ? card.type_line : card.scryfall_card.type_line
});
- const submitForm = React.useCallback(
- async (event) => {
- event.preventDefault();
+ async function submitForm(event) {
+ event.preventDefault();
- if (
- JSON.stringify(mutableCardDetails) !==
- JSON.stringify({
- cmc: card.cmc,
- color_identity: card.color_identity,
- notes: card.notes,
- scryfall_id: card.scryfall_id,
- type_line: card.type_line
- })
- ) {
- await editCard(
- `cardID: "${card._id}",\ncmc: ${
- mutableCardDetails.cmc
- },\ncolor_identity: [${mutableCardDetails.color_identity.map(
- (ci) => '"' + ci + '"'
- )}],\nnotes: "${mutableCardDetails.notes}",\nscryfall_id: "${
- mutableCardDetails.scryfall_id
- }",\ntype_line: "${mutableCardDetails.type_line}"`
- );
- }
+ if (
+ JSON.stringify(mutableCardDetails) !==
+ JSON.stringify({
+ cmc: Number.isInteger(card.cmc) ? card.cmc : card.scryfall_card.cmc,
+ color_identity: card.color_identity
+ ? card.color_identity
+ : card.scryfall_card.color_identity,
+ notes: card.notes,
+ scryfall_id: card.scryfall_card._id,
+ type_line: card.type_line ? card.type_line : card.scryfall_card.type_line
+ })
+ ) {
+ editCard({
+ headers: { CubeID: cubeID },
+ variables: {
+ cardID: card._id,
+ componentID: activeComponentID,
+ ...mutableCardDetails
+ }
+ });
+ }
- if (activeComponentID !== destination) {
- deleteCard(card._id, destination);
- }
+ if (activeComponentID !== destination) {
+ deleteCard(card._id, destination);
+ }
- clear();
- },
- [
- activeComponentID,
- card,
- clear,
- deleteCard,
- destination,
- editCard,
- mutableCardDetails
- ]
- );
+ clear();
+ }
return (
0}>
{Object.keys(card).length > 0 && (
)}
- {isLoggedIn && (
- }
- >
- Clone Deck
-
- )}
-
-
- setSampleHand(randomSampleWOReplacement(mainboard, 7))
- }
- startIcon={ }
- >
+ {isLoggedIn && }
+ }>
Sample Hand
diff --git a/src/components/Deck Page/MoveToOption.jsx b/src/components/Deck Page/MoveToOption.jsx
new file mode 100644
index 0000000..a7c26da
--- /dev/null
+++ b/src/components/Deck Page/MoveToOption.jsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import MUIMenuItem from '@mui/material/MenuItem';
+
+export default function MoveToOption({
+ cardCountState,
+ component,
+ handleChangeNumberOfCopies,
+ option,
+ scryfall_card,
+ setAnchorEl
+}) {
+ return (
+
{
+ handleChangeNumberOfCopies({
+ ...cardCountState[scryfall_card._id],
+ [component.field_name]: option.multiple
+ ? 0
+ : cardCountState[scryfall_card._id][component.field_name] - 1,
+ [option.field_name]:
+ cardCountState[scryfall_card._id][option.field_name] +
+ (option.multiple ? cardCountState[scryfall_card._id][component.field_name] : 1),
+ scryfall_id: scryfall_card._id
+ });
+ setAnchorEl(null);
+ }}
+ >
+ {`Move ${option.multiple ? 'All' : 1} to ${option.display_name}`}
+
+ );
+}
diff --git a/src/components/Deck Page/TypeMapItem.jsx b/src/components/Deck Page/TypeMapItem.jsx
new file mode 100644
index 0000000..1cddd01
--- /dev/null
+++ b/src/components/Deck Page/TypeMapItem.jsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import MUITypography from '@mui/material/Typography';
+
+import CardMapItem from './CardMapItem';
+import specificCardType from '../../functions/specific-card-type';
+
+export default function TypeMapItem({
+ cardCountState,
+ component,
+ componentCards,
+ generalCardType,
+ setCardCountState
+}) {
+ const cardsOfType = componentCards.filter(
+ (card) => specificCardType(card.scryfall_card.type_line) === generalCardType
+ );
+
+ return (
+ cardsOfType.length > 0 && (
+
+
+ {`${generalCardType} (${cardsOfType.reduce(
+ (previousValue, currentValue) =>
+ previousValue +
+ (cardCountState[currentValue.scryfall_card._id]
+ ? cardCountState[currentValue.scryfall_card._id][component.field_name]
+ : 0),
+ 0
+ )})`}
+
+ {cardsOfType.map(({ scryfall_card }) => (
+
+ ))}
+
+ )
+ );
+}
diff --git a/src/components/Main Navigation/AuthenticateForm.jsx b/src/components/Main Navigation/AuthenticateForm.jsx
index 898ee1f..920e720 100644
--- a/src/components/Main Navigation/AuthenticateForm.jsx
+++ b/src/components/Main Navigation/AuthenticateForm.jsx
@@ -1,4 +1,5 @@
import React, { useContext, useState } from 'react';
+import Cookies from 'js-cookie';
import MUIButton from '@mui/material/Button';
import MUIDialog from '@mui/material/Dialog';
import MUIDialogActions from '@mui/material/DialogActions';
@@ -13,8 +14,13 @@ import MUIVisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutli
import MUIVisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import { makeStyles } from '@mui/styles';
+import login from '../../graphql/mutations/account/login';
+import register from '../../graphql/mutations/account/register';
+import requestPasswordReset from '../../graphql/mutations/account/request-password-reset';
+import tokenQuery from '../../constants/token-query';
import LoadingSpinner from '../miscellaneous/LoadingSpinner';
import { AuthenticationContext } from '../../contexts/Authentication';
+import { ErrorContext } from '../../contexts/Error';
const useStyles = makeStyles({
activeTab: {
@@ -31,9 +37,9 @@ const useStyles = makeStyles({
});
export default function AuthenticateForm({ open, toggleOpen }) {
- const { loading, login, register, requestPasswordReset } = useContext(
- AuthenticationContext
- );
+ const { abortControllerRef, loading, setLoading, setUserInfo } =
+ useContext(AuthenticationContext);
+ const { setErrorMessages } = useContext(ErrorContext);
const classes = useStyles();
const [selectedTab, setSelectedTab] = useState(0);
const [emailInput, setEmailInput] = useState('');
@@ -41,19 +47,107 @@ export default function AuthenticateForm({ open, toggleOpen }) {
const [passwordInput, setPasswordInput] = useState('');
const [passwordVisible, setPasswordVisible] = useState(false);
- function submitForm(event) {
+ async function submitForm(event) {
event.preventDefault();
if (selectedTab === 0) {
- login(emailInput, passwordInput);
+ try {
+ setLoading(true);
+ const {
+ data: {
+ login: {
+ _id,
+ admin,
+ avatar,
+ buds,
+ conversations,
+ measurement_system,
+ name,
+ radius,
+ token
+ }
+ }
+ } = await login({
+ queryString: tokenQuery,
+ signal: abortControllerRef.current.signal,
+ variables: { email: emailInput, password: passwordInput }
+ });
+ setUserInfo({
+ admin,
+ avatar,
+ buds,
+ conversations,
+ measurement_system,
+ radius,
+ userID: _id,
+ userName: name
+ });
+ Cookies.set('authentication_token', token);
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ } finally {
+ setLoading(false);
+ }
}
if (selectedTab === 1) {
- requestPasswordReset(emailInput);
+ try {
+ setLoading(true);
+ await requestPasswordReset({
+ signal: abortControllerRef.current.signal,
+ variables: { email: emailInput }
+ });
+ setErrorMessages((prevState) => {
+ return [
+ ...prevState,
+ 'A link to reset your password has been sent to the provided email address. Please allow a few minutes and check both your inbox and your spam folder.'
+ ];
+ });
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ } finally {
+ setLoading(false);
+ }
}
if (selectedTab === 2) {
- register(emailInput, nameInput, passwordInput);
+ try {
+ setLoading(true);
+ const {
+ data: {
+ register: {
+ _id,
+ admin,
+ avatar,
+ buds,
+ conversations,
+ measurement_system,
+ name,
+ radius,
+ token
+ }
+ }
+ } = await register({
+ queryString: tokenQuery,
+ signal: abortControllerRef.current.signal,
+ variables: { email: emailInput, name: nameInput, password: passwordInput }
+ });
+ setUserInfo({
+ admin,
+ avatar,
+ buds,
+ conversations,
+ measurement_system,
+ radius,
+ userID: _id,
+ userName: name
+ });
+ Cookies.set('authentication_token', token);
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ } finally {
+ setLoading(false);
+ }
}
toggleOpen();
@@ -121,9 +215,7 @@ export default function AuthenticateForm({ open, toggleOpen }) {
- setPasswordVisible((prevState) => !prevState)
- }
+ onClick={() => setPasswordVisible((prevState) => !prevState)}
>
{passwordVisible ? (
@@ -202,9 +294,7 @@ export default function AuthenticateForm({ open, toggleOpen }) {
- setPasswordVisible((prevState) => !prevState)
- }
+ onClick={() => setPasswordVisible((prevState) => !prevState)}
>
{passwordVisible ? (
diff --git a/src/components/Main Navigation/ChatDialog.jsx b/src/components/Main Navigation/ChatDialog.jsx
new file mode 100644
index 0000000..7fe7b0a
--- /dev/null
+++ b/src/components/Main Navigation/ChatDialog.jsx
@@ -0,0 +1,184 @@
+import React, { useContext, useRef, useState } from 'react';
+import MUIAddCommentOutlinedIcon from '@mui/icons-material/AddCommentOutlined';
+import MUIButton from '@mui/material/Button';
+import MUIDialog from '@mui/material/Dialog';
+import MUIDialogActions from '@mui/material/DialogActions';
+import MUIDialogContent from '@mui/material/DialogContent';
+import MUIDialogTitle from '@mui/material/DialogTitle';
+import MUIPaper from '@mui/material/Paper';
+import MUITextField from '@mui/material/TextField';
+import MUITypography from '@mui/material/Typography';
+import { makeStyles } from '@mui/styles';
+
+import Avatar from '../miscellaneous/Avatar';
+import ParticipantsInput from './ParticipantsInput';
+import createConversationMessage from '../../graphql/mutations/conversation/create-conversation-message';
+import { AuthenticationContext } from '../../contexts/Authentication';
+import { ErrorContext } from '../../contexts/Error';
+import { primaryColor, secondaryColor } from '../../theme';
+
+const useStyles = makeStyles({
+ messageDialog: {
+ display: 'flex',
+ flexDirection: 'column',
+ maxHeight: '90vh'
+ },
+ messageDialogActions: {
+ alignItems: 'stretch',
+ columnGap: 8,
+ flexDirection: 'row'
+ },
+ messageDialogContent: {
+ flexGrow: 1,
+ marginRight: 16,
+ overflowY: 'auto',
+ display: 'flex',
+ flexDirection: 'column-reverse'
+ },
+ messageLI: {
+ display: 'flex',
+ margin: '4px 0'
+ }
+});
+
+export default function ChatDialog({
+ close,
+ conversation,
+ open,
+ setNewConversationParticipants,
+ setSelectedConversationID
+}) {
+ if (!conversation) return null;
+
+ const { abortControllerRef, userID } = useContext(AuthenticationContext);
+ const { setErrorMessages } = useContext(ErrorContext);
+ const newMessageRef = useRef();
+ const [newMessageText, setNewMessageText] = useState('');
+ const { messageDialog, messageDialogActions, messageDialogContent, messageLI } = useStyles();
+
+ const { _id, messages, participants } = conversation;
+ participants.sort((a, b) => {
+ if (a._id === userID) return -1;
+ if (b._id === userID) return 1;
+ if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
+ return 1;
+ });
+
+ return (
+
+
+
+
+
+
+ {messages.map((message) => (
+
+
+
+ {message.body.split('\n').map((subString, index) => (
+
+ {subString}
+
+ ))}
+
+ {new Date(parseInt(message.createdAt)).toLocaleString()}
+
+
+
+ ))}
+
+
+
+ {
+ if (event.target.value !== '\n') {
+ setNewMessageText(event.target.value);
+ }
+ }}
+ onKeyDown={async (event) => {
+ event.persist();
+ try {
+ if (!event.shiftKey && event.key === 'Enter' && newMessageText.length > 0) {
+ const response = await createConversationMessage({
+ headers: _id ? { ConversationID: _id } : undefined,
+ queryString: `{\n_id\n}`,
+ signal: abortControllerRef.current.signal,
+ variables: {
+ body: newMessageText,
+ participants: participants.map((participant) => participant._id)
+ }
+ });
+
+ if (_id) {
+ setNewMessageText('');
+ newMessageRef.current.focus();
+ } else {
+ // setNewConversationParticipants([]);
+ setSelectedConversationID(response.data.createConversationMessage._id);
+ }
+ }
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ }
+ }}
+ rows={2}
+ type="text"
+ value={newMessageText}
+ />
+ {
+ try {
+ if (newMessageText.length > 0) {
+ const response = await createConversationMessage({
+ headers: _id ? { ConversationID: _id } : undefined,
+ queryString: `{\n_id\n}`,
+ signal: abortControllerRef.current.signal,
+ variables: {
+ body: newMessageText,
+ participants: participants.map((participant) => participant._id)
+ }
+ });
+
+ if (_id) {
+ setNewMessageText('');
+ newMessageRef.current.focus();
+ } else {
+ // setNewConversationParticipants([]);
+ setSelectedConversationID(response.data.createConversationMessage._id);
+ }
+ }
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ }
+ }}
+ startIcon={ }
+ >
+ Send
+
+
+
+ );
+}
diff --git a/src/components/Main Navigation/ChatDrawer.jsx b/src/components/Main Navigation/ChatDrawer.jsx
new file mode 100644
index 0000000..a182226
--- /dev/null
+++ b/src/components/Main Navigation/ChatDrawer.jsx
@@ -0,0 +1,215 @@
+import React, { useContext, useEffect, useState } from 'react';
+import MUIAddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
+import MUIAvatarGroup from '@mui/material/AvatarGroup';
+import MUICard from '@mui/material/Card';
+import MUICardContent from '@mui/material/CardContent';
+import MUICardHeader from '@mui/material/CardHeader';
+import MUIDrawer from '@mui/material/Drawer';
+import MUITypography from '@mui/material/Typography';
+import { makeStyles } from '@mui/styles';
+
+import Avatar from '../miscellaneous/Avatar';
+import ChatDialog from './ChatDialog';
+import ParticipantsInput from './ParticipantsInput';
+import { AuthenticationContext } from '../../contexts/Authentication';
+
+const useStyles = makeStyles({
+ columnFlex: {
+ display: 'flex',
+ flexDirection: 'column',
+ rowGap: 8
+ },
+ conversationCard: {
+ backgroundColor: 'transparent',
+ border: '2px solid white',
+ cursor: 'pointer',
+ margin: 0
+ }
+});
+
+function ConversationMapItem({ conversation, setSelectedConversationID }) {
+ const { userID } = useContext(AuthenticationContext);
+ const lastMessage = conversation.messages[conversation.messages.length - 1];
+ const { conversationCard } = useStyles();
+
+ conversation.participants.sort((a, b) => {
+ if (a._id === userID) return -1;
+ if (b._id === userID) return 1;
+ if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
+ return 1;
+ });
+
+ return (
+ setSelectedConversationID(conversation._id)}
+ >
+
+ {conversation.participants
+ .filter((participant) => participant._id !== userID)
+ .map((participant) => (
+
+ ))}
+
+ }
+ style={{ backgroundColor: 'transparent' }}
+ title={
+
+ {conversation.participants
+ .filter((participant) => participant._id !== userID)
+ .map((participant) => participant.name)
+ .join(', ')}
+
+ }
+ />
+
+
+ {lastMessage.body.substring(0, 100).concat(lastMessage.body.length > 100 ? '...' : '')}
+
+
+ {`— ${lastMessage.author.name}, ${new Date(
+ parseInt(lastMessage.createdAt)
+ ).toLocaleString()}`}
+
+
+
+ );
+}
+
+export default function ChatDrawer({ chatDrawerOpen, setChatDrawerOpen }) {
+ const { avatar, conversations, userID, userName } = useContext(AuthenticationContext);
+ const [existingConversationParticipants, setExistingConversationParticipants] = useState([]);
+ const [newConversationParticipants, setNewConversationParticipants] = useState([]);
+ const [selectedConversationID, setSelectedConversationID] = useState();
+ const { columnFlex, conversationCard } = useStyles();
+
+ const filteredConversations = conversations.filter((conversation) =>
+ existingConversationParticipants.every((ecp) =>
+ conversation.participants.some((cp) => ecp._id === cp._id)
+ )
+ );
+
+ useEffect(() => {
+ const existingConversation = conversations.find(
+ (conversation) =>
+ conversation.participants.length === newConversationParticipants.length &&
+ conversation.participants.every((cp) =>
+ newConversationParticipants.some((ncp) => ncp._id === cp._id)
+ )
+ );
+
+ if (existingConversation) {
+ setSelectedConversationID(existingConversation._id);
+ } else {
+ setSelectedConversationID(null);
+ }
+ }, [newConversationParticipants]);
+
+ return (
+
+ {
+ setNewConversationParticipants([]);
+ setSelectedConversationID(null);
+ }}
+ conversation={(() => {
+ if (selectedConversationID) {
+ return conversations.find(
+ (conversation) => conversation._id === selectedConversationID
+ );
+ }
+ if (newConversationParticipants.length > 0) {
+ return { messages: [], participants: newConversationParticipants };
+ }
+ return null;
+ })()}
+ open={!!selectedConversationID || newConversationParticipants.length > 0}
+ setNewConversationParticipants={setNewConversationParticipants}
+ setSelectedConversationID={setSelectedConversationID}
+ />
+
+ setChatDrawerOpen(false)}
+ open={chatDrawerOpen}
+ >
+
+
+
+
+ {!filteredConversations.find(
+ (fc) => fc.participants.length - 1 === existingConversationParticipants.length
+ ) && (
+
{
+ setExistingConversationParticipants([]);
+ setNewConversationParticipants([
+ ...existingConversationParticipants,
+ { _id: userID, avatar, name: userName }
+ ]);
+ }}
+ >
+
+ {existingConversationParticipants.map((participant) => (
+
+ ))}
+
+ }
+ style={{ backgroundColor: 'transparent' }}
+ title={
+
+ {existingConversationParticipants
+ .map((participant) => participant.name)
+ .join(', ')}
+
+ }
+ />
+
+
+
+ New Conversation
+
+
+
+ )}
+ {filteredConversations
+ .sort((a, b) => {
+ if (
+ parseInt(a.messages[a.messages.length - 1].createdAt) >
+ parseInt(b.messages[b.messages.length - 1].createdAt)
+ ) {
+ return -1;
+ }
+ return 1;
+ })
+ .map((conversation) => (
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/src/components/Main Navigation/Navigation.jsx b/src/components/Main Navigation/Navigation.jsx
index 791397a..29157b2 100644
--- a/src/components/Main Navigation/Navigation.jsx
+++ b/src/components/Main Navigation/Navigation.jsx
@@ -1,13 +1,14 @@
-import React, { useCallback, useContext, useEffect, useState } from 'react';
+import React, { useContext, useState } from 'react';
+import Cookies from 'js-cookie';
import MUIAccountCircleIcon from '@mui/icons-material/AccountCircle';
import MUIAppBar from '@mui/material/AppBar';
-import MUIButton from '@mui/material/Button';
-import MUIDownloadIcon from '@mui/icons-material/Download';
-import MUIDrawer from '@mui/material/Drawer';
+import MUIBadge from '@mui/material/Badge';
+import MUIChatOutlinedIcon from '@mui/icons-material/ChatOutlined';
import MUIIconButton from '@mui/material/IconButton';
import MUIToolbar from '@mui/material/Toolbar';
import MUITooltip from '@mui/material/Tooltip';
import MUITypography from '@mui/material/Typography';
+import MUILogoutOutlinedIcon from '@mui/icons-material/LogoutOutlined';
import MUIMenuIcon from '@mui/icons-material/Menu';
import useMediaQuery from '@mui/material/useMediaQuery';
import { Link } from 'react-router-dom';
@@ -15,22 +16,33 @@ import { makeStyles } from '@mui/styles';
import AuthenticateForm from './AuthenticateForm';
import Avatar from '../miscellaneous/Avatar';
-import NavigationLinks from './NavigationLinks';
+import ChatDrawer from './ChatDrawer';
+import NavigationDrawer from './NavigationDrawer';
import SiteSearchBar from './SiteSearchBar';
+import logoutSingleDevice from '../../graphql/mutations/account/logout-single-device';
import theme from '../../theme';
import { AuthenticationContext } from '../../contexts/Authentication';
-import { PermissionsContext } from '../../contexts/Permissions';
+import { ErrorContext } from '../../contexts/Error';
const useStyles = makeStyles({
appBar: {
background: `linear-gradient(to right, ${theme.palette.primary.main}, calc(2/3 * 100%), ${theme.palette.secondary.main})`
},
- drawer: {
- '& .MuiPaper-root': {
- background: `linear-gradient(to bottom, ${theme.palette.primary.main}, calc(2/3 * 100%), ${theme.palette.secondary.main})`,
- margin: 0
+ badge: {
+ '& > .MuiBadge-badge': {
+ border: '2px solid white',
+ borderRadius: '100%',
+ color: 'white',
+ cursor: 'pointer',
+ height: 38,
+ padding: 8,
+ width: 38
}
},
+ badgeIcon: {
+ height: 26,
+ width: 26
+ },
leftContainer: {
alignItems: 'center',
display: 'flex',
@@ -58,41 +70,29 @@ const useStyles = makeStyles({
});
export default function Navigation() {
- const { isLoggedIn, avatar, userID, userName } = useContext(
- AuthenticationContext
- );
- const { deferredPrompt, setDeferredPrompt } = useContext(PermissionsContext);
- const searchBarLocation = useMediaQuery(theme.breakpoints.up('md'))
- ? 'top'
- : 'side';
- const [authenticateFormDisplayed, setAuthenticateFormDisplayed] =
- useState(false);
- const [drawerOpen, setDrawerOpen] = useState(false);
+ const { abortControllerRef, avatar, isLoggedIn, setLoading, setUserInfo, userID, userName } =
+ useContext(AuthenticationContext);
+ const { setErrorMessages } = useContext(ErrorContext);
+ const searchBarLocation = useMediaQuery(theme.breakpoints.up('md')) ? 'top' : 'side';
+ const [authenticateFormDisplayed, setAuthenticateFormDisplayed] = useState(false);
+ const [chatDrawerOpen, setChatDrawerOpen] = useState(false);
+ const [navigationDrawerOpen, setNavigationDrawerOpen] = useState(false);
const classes = useStyles();
- function toggleDrawer(event) {
- if (
- event.type === 'keydown' &&
- (event.key === 'Tab' || event.key === 'Shift')
- ) {
- return;
- }
- setDrawerOpen((prevState) => !prevState);
- }
-
return (
setAuthenticateFormDisplayed(false)}
/>
+
setDrawerOpen(true)}
+ onClick={() => setNavigationDrawerOpen(true)}
/>
Cube Level Midnight
@@ -100,12 +100,75 @@ export default function Navigation() {
{searchBarLocation === 'top' && (
-
+
)}
{isLoggedIn ? (
-
-
-
+
+
}
+ className={classes.badge}
+ color="primary"
+ onClick={(event) => {
+ if (event.target.closest('span').classList.contains('MuiBadge-colorPrimary')) {
+ setChatDrawerOpen(true);
+ }
+ }}
+ overlap="circular"
+ >
+
}
+ className={classes.badge}
+ color="secondary"
+ onClick={async (event) => {
+ event.persist();
+ if (
+ event.target.closest('span').classList.contains('MuiBadge-colorSecondary')
+ ) {
+ try {
+ setLoading(true);
+ await logoutSingleDevice({ signal: abortControllerRef.current.signal });
+ Cookies.remove('authentication_token');
+ setUserInfo({
+ admin: false,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
+ measurement_system: 'imperial',
+ radius: 10,
+ userID: null,
+ userName: null
+ });
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ } finally {
+ setLoading(false);
+ }
+ }
+ }}
+ overlap="circular"
+ >
+
+
+
+
+
+
) : (
- setDrawerOpen(false)}
- open={drawerOpen}
- >
- {searchBarLocation === 'side' && (
-
- )}
-
- {deferredPrompt && (
- {
- deferredPrompt.prompt();
- await deferredPrompt.userChoice;
- setDeferredPrompt(null);
- setDrawerOpen(false);
- }}
- startIcon={ }
- >
- Install the App!
-
- )}
-
+
+
+
+
);
diff --git a/src/components/Main Navigation/NavigationDrawer.jsx b/src/components/Main Navigation/NavigationDrawer.jsx
new file mode 100644
index 0000000..a3c9eac
--- /dev/null
+++ b/src/components/Main Navigation/NavigationDrawer.jsx
@@ -0,0 +1,50 @@
+import React, { useContext } from 'react';
+import MUIButton from '@mui/material/Button';
+import MUIDownloadIcon from '@mui/icons-material/Download';
+import MUIDrawer from '@mui/material/Drawer';
+import useMediaQuery from '@mui/material/useMediaQuery';
+
+import NavigationLinks from './NavigationLinks';
+import SiteSearchBar from './SiteSearchBar';
+import theme from '../../theme';
+import { PermissionsContext } from '../../contexts/Permissions';
+
+export default function NavigationDrawer({ navigationDrawerOpen, setNavigationDrawerOpen }) {
+ const { deferredPrompt, setDeferredPrompt } = useContext(PermissionsContext);
+ const searchBarLocation = useMediaQuery(theme.breakpoints.up('md')) ? 'top' : 'side';
+
+ function toggleDrawer(event) {
+ if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
+ return;
+ }
+ setNavigationDrawerOpen((prevState) => !prevState);
+ }
+
+ return (
+ setNavigationDrawerOpen(false)}
+ open={navigationDrawerOpen}
+ >
+ {searchBarLocation === 'side' && (
+
+ )}
+
+ {deferredPrompt && (
+ {
+ deferredPrompt.prompt();
+ await deferredPrompt.userChoice;
+ setDeferredPrompt(null);
+ setNavigationDrawerOpen(false);
+ }}
+ startIcon={ }
+ >
+ Install the App!
+
+ )}
+
+ );
+}
diff --git a/src/components/Main Navigation/NavigationLinks.jsx b/src/components/Main Navigation/NavigationLinks.jsx
index f4c83da..d159951 100644
--- a/src/components/Main Navigation/NavigationLinks.jsx
+++ b/src/components/Main Navigation/NavigationLinks.jsx
@@ -1,19 +1,15 @@
-import React, { useContext } from 'react';
+import React from 'react';
import MUIAllInclusiveIcon from '@mui/icons-material/AllInclusive';
import MUIArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined';
-// import MUIChatOutlinedIcon from '@mui/icons-material/ChatOutlined';
import MUIHelpOutlineIcon from '@mui/icons-material/HelpOutline';
import MUIHomeOutlinedIcon from '@mui/icons-material/HomeOutlined';
import MUIList from '@mui/material/List';
import MUIListItem from '@mui/material/ListItem';
import MUIListItemIcon from '@mui/material/ListItemIcon';
import MUIListItemText from '@mui/material/ListItemText';
-import MUILogoutOutlinedIcon from '@mui/icons-material/LogoutOutlined';
import { makeStyles } from '@mui/styles';
import { useNavigate } from 'react-router-dom';
-import { AuthenticationContext } from '../../contexts/Authentication';
-
const useStyles = makeStyles({
item: {
color: '#fff',
@@ -28,7 +24,6 @@ const useStyles = makeStyles({
});
export default function NavigationLinks({ toggleDrawer }) {
- const { isLoggedIn, logout } = useContext(AuthenticationContext);
const classes = useStyles();
const navigate = useNavigate();
@@ -55,26 +50,12 @@ export default function NavigationLinks({ toggleDrawer }) {
}
];
- if (isLoggedIn) {
- options.push({
- icon: ,
- name: 'Logout',
- onClick: logout
- });
- }
-
return (
-
+
{options.map(function (option) {
return (
-
- {option.icon}
-
+ {option.icon}
);
diff --git a/src/components/Main Navigation/ParticipantsInput.jsx b/src/components/Main Navigation/ParticipantsInput.jsx
new file mode 100644
index 0000000..c0a3cc2
--- /dev/null
+++ b/src/components/Main Navigation/ParticipantsInput.jsx
@@ -0,0 +1,127 @@
+import React, { useContext, useState } from 'react';
+import MUIAvatar from '@mui/material/Avatar';
+import MUIAutocomplete from '@mui/material/Autocomplete';
+import MUICheckbox from '@mui/material/Checkbox';
+import MUIChip from '@mui/material/Chip';
+import MUIFormControlLabel from '@mui/material/FormControlLabel';
+import MUISearchIcon from '@mui/icons-material/Search';
+import MUITextField from '@mui/material/TextField';
+import { alpha } from '@mui/material/styles';
+import { makeStyles } from '@mui/styles';
+
+import theme from '../../theme';
+import { AuthenticationContext } from '../../contexts/Authentication';
+
+const useStyles = makeStyles({
+ textfield: {
+ margin: 8,
+ // minWidth: 300,
+ width: 'calc(100% - 16px)',
+ '& input[type=text]': {
+ color: '#ffffff'
+ }
+ },
+ autocomplete: {
+ backgroundColor: alpha(theme.palette.common.white, 0.15),
+ borderRadius: theme.shape.borderRadius,
+ // color: '#fff',
+ // position: 'relative',
+ '&:hover': {
+ backgroundColor: alpha(theme.palette.common.white, 0.25)
+ }
+ }
+});
+
+export default function ParticipantsInput({ faded, participants, setParticipants }) {
+ const { buds, userID } = useContext(AuthenticationContext);
+ const [filterText, setFilterText] = useState('');
+ const { autocomplete, textfield } = useStyles();
+
+ return (
+ option.name}
+ id="bud-include-input"
+ inputValue={filterText}
+ isOptionEqualToValue={(option, value) => option._id === value._id}
+ multiple
+ onChange={function (event, value) {
+ setParticipants(value);
+ }}
+ onInputChange={(event, newInputValue) => {
+ setFilterText(newInputValue);
+ }}
+ options={buds.sort((a, b) => {
+ if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
+ return 1;
+ })}
+ renderInput={(params) => (
+ setFilterText(event.target.value)}
+ InputProps={{
+ ...params.InputProps,
+ startAdornment: (
+
+
+ {params.InputProps.startAdornment}
+
+ )
+ }}
+ />
+ )}
+ renderOption={(props, option, { selected }) => (
+
+ }
+ label={
+
+ }
+ />
+ {option.name}
+
+ )}
+ renderTags={(tagValue, getTagProps) =>
+ tagValue.map((option, index) => (
+
+ }
+ color="primary"
+ disabled={option._id === userID}
+ onDelete={
+ option._id === userID
+ ? undefined
+ : () => {
+ setParticipants((prevState) => prevState.filter((p) => p._id !== option._id));
+ }
+ }
+ />
+ ))
+ }
+ value={participants}
+ />
+ );
+}
diff --git a/src/components/Main Navigation/SiteSearchBar.jsx b/src/components/Main Navigation/SiteSearchBar.jsx
index 81a5da7..d52d503 100644
--- a/src/components/Main Navigation/SiteSearchBar.jsx
+++ b/src/components/Main Navigation/SiteSearchBar.jsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useContext, useRef, useState } from 'react';
+import React, { useCallback, useRef, useState } from 'react';
import MUIAutocomplete from '@mui/material/Autocomplete';
import MUICircularProgress from '@mui/material/CircularProgress';
import MUISearchIcon from '@mui/icons-material/Search';
@@ -11,7 +11,6 @@ import { useNavigate } from 'react-router';
import theme from '../../theme';
import useRequest from '../../hooks/request-hook';
import Avatar from '../miscellaneous/Avatar';
-import { CardCacheContext } from '../../contexts/CardCache';
const useStyles = makeStyles({
input: {
@@ -41,51 +40,14 @@ const useStyles = makeStyles({
}
});
-export default function SiteSearchBar({ color, setDrawerOpen }) {
+export default function SiteSearchBar({ color, setNavigationDrawerOpen }) {
const searchInput = useRef();
- const { addCardsToCache, scryfallCardDataCache } =
- useContext(CardCacheContext);
const { loading, sendRequest } = useRequest();
const [searchResults, setSearchResults] = useState([]);
const [timer, setTimer] = useState();
const classes = useStyles();
const navigate = useNavigate();
- const updateSearchResults = useCallback(
- async function (data) {
- const cardSet = new Set();
-
- for (const result of data) {
- if (
- (result.__typename === 'CubeType' ||
- result.__typename === 'DeckType') &&
- result.image
- ) {
- cardSet.add(result.image);
- }
- }
-
- await addCardsToCache([...cardSet]);
-
- for (const result of data) {
- if (
- (result.__typename === 'CubeType' ||
- result.__typename === 'DeckType') &&
- result.image
- ) {
- result.image = {
- alt: scryfallCardDataCache.current[result.image].name,
- scryfall_id: result.image,
- src: scryfallCardDataCache.current[result.image].art_crop
- };
- }
- }
-
- setSearchResults(data);
- },
- [addCardsToCache]
- );
-
const searchSite = useCallback(
(event) => {
event.persist();
@@ -95,10 +57,11 @@ export default function SiteSearchBar({ color, setDrawerOpen }) {
setSearchResults([]);
} else {
await sendRequest({
- callback: updateSearchResults,
+ callback: setSearchResults,
load: true,
operation: 'searchSite',
get body() {
+ // https://www.rakeshjesadiya.com/graphql-fields-conflict-return-conflicting-types-use-different-aliases-on-the-fields/
return {
query: `
query {
@@ -108,37 +71,97 @@ export default function SiteSearchBar({ color, setDrawerOpen }) {
__typename
}
... on AccountType {
- avatar
+ avatar {
+ card_faces {
+ image_uris {
+ art_crop
+ }
+ }
+ image_uris {
+ art_crop
+ }
+ }
name
}
... on BlogPostType {
- image
+ stringImage: image
title
subtitle
}
... on CubeType {
creator {
_id
- avatar
+ avatar {
+ card_faces {
+ image_uris {
+ art_crop
+ }
+ }
+ image_uris {
+ art_crop
+ }
+ }
+ name
+ }
+ cardImage: image {
+ _id
+ image_uris {
+ art_crop
+ }
name
+ card_faces {
+ image_uris {
+ art_crop
+ }
+ name
+ }
}
- image
name
}
... on DeckType {
creator {
_id
- avatar
+ avatar {
+ card_faces {
+ image_uris {
+ art_crop
+ }
+ }
+ image_uris {
+ art_crop
+ }
+ }
name
}
- image
+ cardImage: image {
+ _id
+ image_uris {
+ art_crop
+ }
+ name
+ card_faces {
+ image_uris {
+ art_crop
+ }
+ name
+ }
+ }
name
}
... on EventType {
createdAt
host {
_id
- avatar
+ avatar {
+ card_faces {
+ image_uris {
+ art_crop
+ }
+ }
+ image_uris {
+ art_crop
+ }
+ }
name
}
name
@@ -191,7 +214,7 @@ export default function SiteSearchBar({ color, setDrawerOpen }) {
.getElementsByClassName('MuiAutocomplete-clearIndicator')[0]
.click();
setSearchResults([]);
- setDrawerOpen(false);
+ setNavigationDrawerOpen(false);
}, 0);
}
}}
@@ -224,7 +247,7 @@ export default function SiteSearchBar({ color, setDrawerOpen }) {
{option.__typename === 'AccountType' && (
-
+
User
@@ -240,7 +263,8 @@ export default function SiteSearchBar({ color, setDrawerOpen }) {
@@ -248,9 +272,7 @@ export default function SiteSearchBar({ color, setDrawerOpen }) {
Blog Post
-
- {option.title}
-
+ {option.title}
@@ -259,14 +281,33 @@ export default function SiteSearchBar({ color, setDrawerOpen }) {
{option.__typename === 'CubeType' && (
- {option.image && (
-
- )}
+ {
+ /* option.image */ option.cardImage && (
+
+ )
+ }
Cube
@@ -280,10 +321,17 @@ export default function SiteSearchBar({ color, setDrawerOpen }) {
{option.__typename === 'DeckType' && (
- {option.image && (
+ {option.cardImage && (
diff --git a/src/components/Match Page/Intermission.jsx b/src/components/Match Page/Intermission.jsx
index dd5b5de..74b163c 100644
--- a/src/components/Match Page/Intermission.jsx
+++ b/src/components/Match Page/Intermission.jsx
@@ -3,16 +3,14 @@ import MUIButton from '@mui/material/Button';
import MUITypography from '@mui/material/Typography';
import ConfirmationDialog from '../miscellaneous/ConfirmationDialog';
-import DeckDisplay from '../miscellaneous/DeckDisplay';
+// import DeckDisplay from '../miscellaneous/DeckDisplay';
import { AuthenticationContext } from '../../contexts/Authentication';
import { MatchContext } from '../../contexts/match-context';
export default function Intermission() {
- const [confirmationDialogVisible, setConfirmationDialogVisible] =
- React.useState(false);
+ const [confirmationDialogVisible, setConfirmationDialogVisible] = React.useState(false);
const { userID } = React.useContext(AuthenticationContext);
- const { matchState, ready, toggleMainboardSideboardMatch } =
- React.useContext(MatchContext);
+ const { matchState, ready, toggleMainboardSideboardMatch } = React.useContext(MatchContext);
const player = matchState.players.find((plr) => plr.account._id === userID);
@@ -22,13 +20,10 @@ export default function Intermission() {
confirmHandler={ready}
open={confirmationDialogVisible}
title="Are you readier than SpongeBob SquarePants with a belly full of Krabby Patties?"
- toggleOpen={() =>
- setConfirmationDialogVisible((prevState) => !prevState)
- }
+ toggleOpen={() => setConfirmationDialogVisible((prevState) => !prevState)}
>
- Think of how embarrassing it will be if you get mushroom stamped! Oh,
- the shame!
+ Think of how embarrassing it will be if you get mushroom stamped! Oh, the shame!
@@ -41,11 +36,11 @@ export default function Intermission() {
Ready!
-
+ /> */}
) : (
diff --git a/src/components/Match Page/PlayerInfo.jsx b/src/components/Match Page/PlayerInfo.jsx
index 5b7913d..750f595 100644
--- a/src/components/Match Page/PlayerInfo.jsx
+++ b/src/components/Match Page/PlayerInfo.jsx
@@ -47,12 +47,8 @@ export default function PlayerInfo({ player, position, setClickedPlayer }) {
const classes = useStyles();
const [dragging, setDragging] = React.useState(false);
const { userID } = React.useContext(AuthenticationContext);
- const {
- adjustEnergyCounters,
- adjustLifeTotal,
- adjustPoisonCounters,
- setNumberInputDialogInfo
- } = React.useContext(MatchContext);
+ const { adjustEnergyCounters, adjustLifeTotal, adjustPoisonCounters, setNumberInputDialogInfo } =
+ React.useContext(MatchContext);
React.useEffect(() => {
function energyBadgeClickListner() {
@@ -196,10 +192,9 @@ export default function PlayerInfo({ player, position, setClickedPlayer }) {
showZero
>
diff --git a/src/components/miscellaneous/AutoScrollMessages.jsx b/src/components/miscellaneous/AutoScrollMessages.jsx
index fd24189..a5a24a2 100644
--- a/src/components/miscellaneous/AutoScrollMessages.jsx
+++ b/src/components/miscellaneous/AutoScrollMessages.jsx
@@ -42,11 +42,7 @@ const useStyles = makeStyles({
}
});
-export default function AutoScrollMessages({
- messages,
- submitFunction,
- title
-}) {
+export default function AutoScrollMessages({ messages, submitFunction, title }) {
const { isLoggedIn, userID } = useContext(AuthenticationContext);
const newMessageRef = useRef();
const [newMessageText, setNewMessageText] = useState('');
@@ -61,9 +57,7 @@ export default function AutoScrollMessages({
return (
- {title}}
- />
+ {title}} />
{messages
@@ -73,21 +67,14 @@ export default function AutoScrollMessages({
className={messageLI}
key={message._id}
style={{
- flexDirection:
- message.author._id === userID ? 'row-reverse' : 'row'
+ flexDirection: message.author._id === userID ? 'row-reverse' : 'row'
}}
>
-
+
{
- if (
- !event.shiftKey &&
- event.key === 'Enter' &&
- newMessageText.length > 0
- ) {
+ if (!event.shiftKey && event.key === 'Enter' && newMessageText.length > 0) {
submitFunction(newMessageText);
setNewMessageText('');
newMessageRef.current.focus();
diff --git a/src/components/miscellaneous/Avatar.jsx b/src/components/miscellaneous/Avatar.jsx
index 78bd7a4..fc9fe11 100644
--- a/src/components/miscellaneous/Avatar.jsx
+++ b/src/components/miscellaneous/Avatar.jsx
@@ -22,15 +22,18 @@ const useStyles = makeStyles({
}
});
-export default function LargeAvatar({ alt, size, ...rest }) {
+export default function LargeAvatar({ profile, size, ...rest }) {
const classes = useStyles();
return (
-
+
diff --git a/src/components/miscellaneous/DeckDisplay.jsx b/src/components/miscellaneous/DeckDisplay.jsx
deleted file mode 100644
index ac594eb..0000000
--- a/src/components/miscellaneous/DeckDisplay.jsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import React from 'react';
-import MUICard from '@mui/material/Card';
-import MUICardContent from '@mui/material/CardContent';
-import MUICardHeader from '@mui/material/CardHeader';
-import MUIGrid from '@mui/material/Grid';
-import MUITypography from '@mui/material/Typography';
-
-import customSort from '../../functions/custom-sort';
-import specificCardType from '../../functions/specific-card-type';
-import PlaysetDisplay from './PlaysetDisplay';
-
-export default function DeckDisplay({
- add = () => {
- // default: don't do anything
- },
- authorizedID,
- deck,
- remove = () => {
- // default: don't do anything
- },
- toggle = () => {
- // default: don't do anything
- }
-}) {
- return (
-
- {['Mainboard', 'Sideboard'].map((component) => (
-
-
-
- {component} ({deck[component.toLowerCase()].length})
-
- }
- />
-
- {[
- 'Land',
- 'Creature',
- 'Planeswalker',
- 'Artifact',
- 'Enchantment',
- 'Instant',
- 'Sorcery'
- ].map(function (type) {
- const group = customSort(deck[component.toLocaleLowerCase()], [
- 'cmc',
- 'name',
- 'set'
- ]).filter((card) => specificCardType(card.type_line) === type);
- const condensedGroup = [];
-
- for (const card of group) {
- const existingCopies = condensedGroup.find(
- (abstraction) =>
- abstraction.card.scryfall_id === card.scryfall_id
- );
- if (existingCopies) {
- existingCopies.copies.push(card._id);
- } else {
- condensedGroup.push({
- card: {
- back_image: card.back_image,
- cmc: card.cmc,
- collector_number: card.collector_number,
- color_identity: card.color_identity,
- image: card.image,
- keywords: card.keywords,
- mana_cost: card.mana_cost,
- mtgo_id: card.mtgo_id,
- name: card.name,
- oracle_id: card.oracle_id,
- scryfall_id: card.scryfall_id,
- set: card.set,
- set_name: card.set_name,
- tcgplayer_id: card.tcgplayer_id,
- type_line: card.type_line
- },
- copies: [card._id]
- });
- }
- }
-
- return (
- group.length > 0 && (
-
- {`${type} (${group.length})`}
- {condensedGroup.map((playset) => (
-
- ))}
-
- )
- );
- })}
-
-
-
- ))}
-
- );
-}
diff --git a/src/components/miscellaneous/HoverPreview.jsx b/src/components/miscellaneous/HoverPreview.jsx
index 6026746..c467509 100644
--- a/src/components/miscellaneous/HoverPreview.jsx
+++ b/src/components/miscellaneous/HoverPreview.jsx
@@ -10,7 +10,7 @@ const useStyles = makeStyles({
hoverPreviewImage: {
borderRadius: 8,
display: 'inline',
- height: 264
+ height: 350
}
});
@@ -47,12 +47,7 @@ export default function HoverPreview({ back_image, children, image }) {
right = undefined;
} else {
left = undefined;
- right = `${
- windowWidth -
- event.pageX -
- hpcWidth +
- (hpcWidth * event.pageX) / windowWidth
- }px`;
+ right = `${windowWidth - event.pageX - hpcWidth + (hpcWidth * event.pageX) / windowWidth}px`;
}
if (event.screenY < windowHeight / 2) {
@@ -103,17 +98,9 @@ export default function HoverPreview({ back_image, children, image }) {
top: preview.top
}}
>
-
+
{back_image && (
-
+
)}
,
document.getElementById('hover-preview')
diff --git a/src/components/miscellaneous/PlaysetDisplay.jsx b/src/components/miscellaneous/PlaysetDisplay.jsx
deleted file mode 100644
index d835d11..0000000
--- a/src/components/miscellaneous/PlaysetDisplay.jsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import React from 'react';
-import MUIIconButton from '@mui/material/IconButton';
-import MUISwapHorizIcon from '@mui/icons-material/SwapHoriz';
-import MUISwapVertIcon from '@mui/icons-material/SwapVert';
-import MUITextField from '@mui/material/TextField';
-import MUITooltip from '@mui/material/Tooltip';
-import MUITypography from '@mui/material/Typography';
-import { makeStyles } from '@mui/styles';
-import useMediaQuery from '@mui/material/useMediaQuery';
-import { useParams } from 'react-router';
-
-import theme, { backgroundColor } from '../../theme';
-import HoverPreview from '../miscellaneous/HoverPreview';
-import ManaCostSVGs from '../miscellaneous/ManaCostSVGs';
-import { AuthenticationContext } from '../../contexts/Authentication';
-import addBasics from '../../graphql/mutations/event/add-basics';
-import removeBasics from '../../graphql/mutations/event/remove-basics';
-import toggleMainboardSideboardEvent from '../../graphql/mutations/event/toggle-mainboard-sideboard-event';
-
-const useStyles = makeStyles({
- iconButton: {
- background: theme.palette.secondary.main,
- color: backgroundColor,
- marginLeft: 8,
- marginRight: 8,
- '&:hover': {
- background: theme.palette.secondary.dark
- }
- }
-});
-
-export default function PlaysetDisplay({
- add,
- authorizedID,
- component,
- playset: { card, copies },
- remove,
- toggle
-}) {
- const { eventID, deckID, matchID } = useParams();
- const { userID } = React.useContext(AuthenticationContext);
- const classes = useStyles();
- const [updatedCount, setUpdatedCount] = React.useState(copies.length);
-
- React.useEffect(() => {
- setUpdatedCount(copies.length);
- }, [copies.length]);
-
- const isMatch = !!useParams().matchID;
- const isEvent = !!useParams().eventID;
-
- function handleChangeNumberOfCopies() {
- if (copies.length < updatedCount) {
- if (eventID) {
- addBasics({
- headers: { EventID: eventID },
- variables: {
- component,
- name: card.name,
- numberOfCopies: updatedCount - copies.length,
- scryfall_id: card.scryfall_id
- }
- });
- } else {
- // TODO don't pass add as props for deck
- add(card, component, updatedCount - copies.length);
- }
- } else if (copies.length > updatedCount) {
- if (eventID) {
- removeBasics({
- headers: { EventID: eventID },
- variables: {
- cardIDs: copies.slice(updatedCount),
- component
- }
- });
- }
- // TODO don't pass remove as props for deck
- remove(copies.slice(updatedCount), component);
- } else {
- // don't do anything; no changes
- }
- }
-
- return (
-
-
setUpdatedCount(event.target.value)}
- style={{
- marginLeft: 16,
- marginTop: 4,
- width: 64
- }}
- type="number"
- value={updatedCount}
- />
-
-
- {
- if (eventID) {
- toggleMainboardSideboardEvent({
- headers: { EventID: eventID },
- cardID: copies[0]
- });
- } else {
- // TODO don't pass toggle as prop
- toggle(copies[0]);
- }
- }}
- size="small"
- style={{ alignSelf: 'center' }}
- >
- {useMediaQuery(theme.breakpoints.up('md')) ? (
-
- ) : (
-
- )}
-
-
-
-
- {card.name}
-
-
- {card.set.toUpperCase()}
-
-
-
-
-
- );
-}
diff --git a/src/components/miscellaneous/ScryfallCardLink.jsx b/src/components/miscellaneous/ScryfallCardLink.jsx
new file mode 100644
index 0000000..990ee94
--- /dev/null
+++ b/src/components/miscellaneous/ScryfallCardLink.jsx
@@ -0,0 +1,13 @@
+import React from 'react';
+
+import HoverPreview from './HoverPreview';
+
+const ScryfallCardLink = ({ card }) => (
+
+
+ {card.name}
+
+
+);
+
+export default ScryfallCardLink;
diff --git a/src/components/miscellaneous/ScryfallRequest.jsx b/src/components/miscellaneous/ScryfallRequest.jsx
index ba523a5..cdab76d 100644
--- a/src/components/miscellaneous/ScryfallRequest.jsx
+++ b/src/components/miscellaneous/ScryfallRequest.jsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useRef, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import MUIAutocomplete from '@mui/material/Autocomplete';
import MUIButton from '@mui/material/Button';
import MUICircularProgress from '@mui/material/CircularProgress';
@@ -9,141 +9,108 @@ import MUIMenu from '@mui/material/Menu';
import MUIMenuItem from '@mui/material/MenuItem';
import MUITextField from '@mui/material/TextField';
-import useRequest from '../../hooks/request-hook';
+import searchCard from '../../graphql/queries/card/search-card';
+import searchPrintings from '../../graphql/queries/card/search-printings';
import HoverPreview from './HoverPreview';
export default function ScryfallRequest({ buttonText, labelText, onSubmit }) {
+ const abortControllerRef = useRef(new AbortController());
const cardSearchInput = useRef();
- const { loading, sendRequest } = useRequest();
+ const timer = useRef();
const [anchorEl, setAnchorEl] = useState(null);
const [availablePrintings, setAvailablePrintings] = useState([]);
- const [timer, setTimer] = useState();
const [cardSearchResults, setCardSearchResults] = useState([]);
- const [chosenCard, setChosenCard] = useState(null);
+ const [chosenCardOracleID, setChosenCardOracleID] = useState(null);
+ const [chosenPrinting, setChosenPrinting] = useState(null);
+ const [loading, setLoading] = useState(false);
- const scryfallCardSearch = useCallback(
- (event) => {
- event.persist();
- setTimer(
- setTimeout(async function () {
- if (event.target.value.length < 2) {
- setCardSearchResults([]);
- setChosenCard(null);
+ const scryfallCardSearch = (event) => {
+ event.persist();
+ if ('current' in timer) {
+ clearTimeout(timer.current);
+ }
+ timer.current = setTimeout(async function () {
+ try {
+ if (event.target.value.length < 2) {
+ setCardSearchResults([]);
+ setChosenCardOracleID(null);
+ setChosenPrinting(null);
+ } else {
+ setLoading(true);
+ const cards = await searchCard({
+ queryString: `{
+ name
+ oracle_id
+ }`,
+ signal: abortControllerRef.current.signal,
+ variables: { search: event.target.value }
+ });
+ if (cards && cards.data && cards.data.searchCard) {
+ setCardSearchResults(cards.data.searchCard);
} else {
- await sendRequest({
- callback: (data) => {
- if (data.data) {
- setCardSearchResults(
- data.data.map((match) => ({
- name: match.name,
- oracle_id: match.oracle_id
- }))
- );
- } else {
- setCardSearchResults([]);
- }
- },
- method: 'GET',
- url: `https://api.scryfall.com/cards/search?q=${event.target.value}`
- });
+ setCardSearchResults([]);
}
- }, 250)
- );
- },
- [sendRequest]
- );
+ }
+ } catch (error) {
+ setCardSearchResults([]);
+ setChosenCardOracleID(null);
+ setChosenPrinting(null);
+ } finally {
+ setLoading(false);
+ }
+ }, 250);
+ };
- const scryfallPrintSearch = useCallback(
- async function (oracleID) {
- await sendRequest({
- callback: async (data) => {
- const printings = await Promise.all(
- data.data.map(async function (print) {
- let art_crop, back_image, image;
- switch (print.layout) {
- case 'adventure':
- // this mechanic debuted in Throne of Eldrain. all adventure cards are either (instants or sorceries) and creatures. it seems to have been popular, so it may appear again
- art_crop = print.image_uris.art_crop;
- image = print.image_uris.large;
- break;
- case 'flip':
- // flip was only in Kamigawa block (plus an "Un" card and a couple of reprints), which was before planeswalkers existed. unlikely they ever bring this layout back, and if they do, no idea how they would fit a planeswalker onto one side. all flip cards are creatures on one end and either a creature or an enchantment on the other
- art_crop = print.image_uris.art_crop;
- image = print.image_uris.large;
- break;
- case 'leveler':
- // all level up cards have been creatures. this is a mechanic that has so far only appeared in Rise of the Eldrazi and a single card in Modern Horizons. i don't expect the mechanic to return, but the printing of Hexdrinker in MH1 suggests it may
- art_crop = print.image_uris.art_crop;
- image = print.image_uris.large;
- break;
- case 'meld':
- // meld only appeared in Eldritch Moon and probably won't ever come back. no planeswalkers; only creatures and a single land
- art_crop = print.image_uris.art_crop;
- const meldResultPart = print.all_parts.find(
- (part) => part.component === 'meld_result'
- );
- await sendRequest({
- callback: (data) => {
- back_image = data.image_uris.large;
- image = print.image_uris.large;
- },
- method: 'GET',
- url: meldResultPart.uri
- });
- break;
- case 'modal_dfc':
- art_crop = print.card_faces[0].image_uris.art_crop;
- back_image = print.card_faces[1].image_uris.large;
- image = print.card_faces[0].image_uris.large;
- break;
- case 'saga':
- // saga's have no other faces; they simply have their own layout type becuase of the fact that the art is on the right side of the card rather than the top of the card. all sagas printed so far (through Kaldheim) have only 3 or 4 chapters
- art_crop = print.image_uris.art_crop;
- image = print.image_uris.large;
- break;
- case 'split':
- // split cards are always instants and/or sorceries
- art_crop = print.image_uris.art_crop;
- image = print.image_uris.large;
- break;
- case 'transform':
- art_crop = print.card_faces[0].image_uris.art_crop;
- back_image = print.card_faces[1].image_uris.large;
- image = print.card_faces[0].image_uris.large;
- break;
- default:
- art_crop = print.image_uris.art_crop;
- image = print.image_uris.large;
+ useEffect(() => {
+ (async function () {
+ try {
+ if (chosenCardOracleID) {
+ const printings = await searchPrintings({
+ queryString: `{
+ _id
+ card_faces {
+ image_uris {
+ art_crop
+ large
+ }
+ }
+ collector_number
+ image_uris {
+ art_crop
+ large
}
- return {
- art_crop,
- back_image,
- collector_number: print.collector_number,
- image,
- name: print.name,
- oracle_id: print.oracle_id,
- scryfall_id: print.id,
- set_name: print.set_name
- };
- })
- );
+ name
+ oracle_id
+ set_name
+ }`,
+ signal: abortControllerRef.current.signal,
+ variables: { oracle_id: chosenCardOracleID }
+ });
+ if (printings && printings.data && printings.data.searchPrintings) {
+ setAvailablePrintings(printings.data.searchPrintings);
+ if (printings.data.searchPrintings.length > 0) {
+ setChosenPrinting(printings[0]);
+ }
+ }
+ }
+ } catch (error) {
+ setChosenPrinting(null);
+ } finally {
+ }
+ })();
+ }, [chosenCardOracleID]);
- setChosenCard(printings[0]);
- setAvailablePrintings(printings);
- },
- method: 'GET',
- url: `https://api.scryfall.com/cards/search?order=released&q=oracleid%3A${oracleID}&unique=prints`
- });
- },
- [sendRequest]
- );
+ useEffect(() => {
+ return () => abortControllerRef.current.abort();
+ }, []);
function submitForm() {
setAnchorEl(null);
setAvailablePrintings([]);
setCardSearchResults([]);
- onSubmit(chosenCard);
- setChosenCard(null);
+ onSubmit(chosenPrinting);
+ setChosenCardOracleID(null);
+ setChosenPrinting(null);
cardSearchInput.current.parentElement
.getElementsByClassName('MuiAutocomplete-clearIndicator')[0]
.click();
@@ -172,7 +139,7 @@ export default function ScryfallRequest({ buttonText, labelText, onSubmit }) {
loading={loading}
onChange={function (event, value, reason) {
if (reason === 'selectOption') {
- scryfallPrintSearch(value.oracle_id);
+ setChosenCardOracleID(value.oracle_id);
}
}}
options={cardSearchResults}
@@ -181,23 +148,23 @@ export default function ScryfallRequest({ buttonText, labelText, onSubmit }) {
{...params}
inputRef={cardSearchInput}
label={labelText}
- onKeyUp={(event) => {
- clearTimeout(timer);
- scryfallCardSearch(event);
- }}
+ onChange={scryfallCardSearch}
InputProps={{
...params.InputProps,
endAdornment: (
- {loading && (
-
- )}
+ {loading && }
{params.InputProps.endAdornment}
)
}}
/>
)}
+ renderOption={(props, option) => (
+
+ {option.name}
+
+ )}
style={{
marginBottom: 8
}}
@@ -221,8 +188,7 @@ export default function ScryfallRequest({ buttonText, labelText, onSubmit }) {
@@ -237,18 +203,18 @@ export default function ScryfallRequest({ buttonText, labelText, onSubmit }) {
role: 'listbox'
}}
>
- {availablePrintings.map((option, index) => (
+ {availablePrintings.map((option) => (
{
- setChosenCard({ ...availablePrintings[index] });
+ setChosenPrinting(option);
setAnchorEl(null);
}}
- selected={option.scryfall_id === chosenCard.scryfall_id}
+ selected={option._id === chosenPrinting?._id}
>
{`${option.set_name} - ${option.collector_number}`}
diff --git a/src/components/miscellaneous/VideoAvatar.jsx b/src/components/miscellaneous/VideoAvatar.jsx
index f250343..e9d72ff 100644
--- a/src/components/miscellaneous/VideoAvatar.jsx
+++ b/src/components/miscellaneous/VideoAvatar.jsx
@@ -30,12 +30,7 @@ const useStyles = makeStyles({
}
});
-export default function VideoAvatar({
- account,
- context,
- rtcConnectionIndex,
- size
-}) {
+export default function VideoAvatar({ account, context, rtcConnectionIndex, size }) {
const { userID } = useContext(AuthenticationContext);
const { setErrorMessages } = useContext(ErrorContext);
const { peerConnectionsRef } = useContext(context);
@@ -49,12 +44,8 @@ export default function VideoAvatar({
const audioBadge = useRef();
const videoBadge = useRef();
const classes = useStyles();
- const audioTracks = !!mediaStreamRef.current
- ? mediaStreamRef.current.getAudioTracks()
- : [];
- const videoTracks = !!mediaStreamRef.current
- ? mediaStreamRef.current.getVideoTracks()
- : [];
+ const audioTracks = !!mediaStreamRef.current ? mediaStreamRef.current.getAudioTracks() : [];
+ const videoTracks = !!mediaStreamRef.current ? mediaStreamRef.current.getVideoTracks() : [];
const pc = peerConnectionsRef.current[rtcConnectionIndex];
async function toggleAudio() {
@@ -65,15 +56,9 @@ export default function VideoAvatar({
const microphoneStream = await navigator.mediaDevices.getUserMedia({
audio: true
});
- for (
- let index = 0;
- index < peerConnectionsRef.current.length;
- index++
- ) {
+ for (let index = 0; index < peerConnectionsRef.current.length; index++) {
if (peerConnectionsRef.current[index]) {
- audioSendersRef.current[index] = peerConnectionsRef.current[
- index
- ].addTrack(
+ audioSendersRef.current[index] = peerConnectionsRef.current[index].addTrack(
microphoneStream.getAudioTracks()[0]
// mediaStreamRef.current
);
@@ -84,15 +69,9 @@ export default function VideoAvatar({
audioTracks[index].stop();
mediaStreamRef.current.removeTrack(audioTracks[index]);
}
- for (
- let index = 0;
- index < peerConnectionsRef.current.length;
- index++
- ) {
+ for (let index = 0; index < peerConnectionsRef.current.length; index++) {
if (peerConnectionsRef.current[index]) {
- peerConnectionsRef.current[index].removeTrack(
- audioSendersRef.current[index]
- );
+ peerConnectionsRef.current[index].removeTrack(audioSendersRef.current[index]);
audioSendersRef.current[index] = null;
}
}
@@ -105,15 +84,9 @@ export default function VideoAvatar({
audio: true
});
// mediaStreamRef.current = microphoneStream;
- for (
- let index = 0;
- index < peerConnectionsRef.current.length;
- index++
- ) {
+ for (let index = 0; index < peerConnectionsRef.current.length; index++) {
if (peerConnectionsRef.current[index]) {
- audioSendersRef.current[index] = peerConnectionsRef.current[
- index
- ].addTrack(
+ audioSendersRef.current[index] = peerConnectionsRef.current[index].addTrack(
microphoneStream.getAudioTracks()[0]
// mediaStreamRef.current
);
@@ -137,15 +110,9 @@ export default function VideoAvatar({
const cameraStream = await navigator.mediaDevices.getUserMedia({
video: true
});
- for (
- let index = 0;
- index < peerConnectionsRef.current.length;
- index++
- ) {
+ for (let index = 0; index < peerConnectionsRef.current.length; index++) {
if (peerConnectionsRef.current[index]) {
- videoSendersRef.current[index] = peerConnectionsRef.current[
- index
- ].addTrack(
+ videoSendersRef.current[index] = peerConnectionsRef.current[index].addTrack(
cameraStream.getVideoTracks()[0],
mediaStreamRef.current
);
@@ -156,15 +123,9 @@ export default function VideoAvatar({
videoTracks[index].stop();
mediaStreamRef.current.removeTrack(videoTracks[index]);
}
- for (
- let index = 0;
- index < peerConnectionsRef.current.length;
- index++
- ) {
+ for (let index = 0; index < peerConnectionsRef.current.length; index++) {
if (peerConnectionsRef.current[index]) {
- peerConnectionsRef.current[index].removeTrack(
- videoSendersRef.current[index]
- );
+ peerConnectionsRef.current[index].removeTrack(videoSendersRef.current[index]);
videoSendersRef.current[index] = null;
}
}
@@ -177,15 +138,9 @@ export default function VideoAvatar({
video: true
});
mediaStreamRef.current = cameraStream;
- for (
- let index = 0;
- index < peerConnectionsRef.current.length;
- index++
- ) {
+ for (let index = 0; index < peerConnectionsRef.current.length; index++) {
if (peerConnectionsRef.current[index]) {
- videoSendersRef.current[index] = peerConnectionsRef.current[
- index
- ].addTrack(
+ videoSendersRef.current[index] = peerConnectionsRef.current[index].addTrack(
mediaStreamRef.current.getVideoTracks()[0],
mediaStreamRef.current
);
@@ -235,8 +190,7 @@ export default function VideoAvatar({
return (
) : (
{
- if (
- event.target
- .closest('span')
- .classList.contains('MuiBadge-colorPrimary')
- ) {
+ if (event.target.closest('span').classList.contains('MuiBadge-colorPrimary')) {
toggleVideo();
}
}}
@@ -370,11 +318,7 @@ export default function VideoAvatar({
color="secondary"
onClick={async (event) => {
event.persist();
- if (
- event.target
- .closest('span')
- .classList.contains('MuiBadge-colorSecondary')
- ) {
+ if (event.target.closest('span').classList.contains('MuiBadge-colorSecondary')) {
toggleAudio();
}
}}
@@ -398,8 +342,7 @@ export default function VideoAvatar({
) : (
{
- // don't return anything
- },
- logout: () => {
- // don't return anything
- },
+ measurement_system: 'imperial',
peerConnection: null,
- register: () => {
- // don't return anything
- },
- requestPasswordReset: () => {
+ setLoading: () => {
// don't return anything
},
setLocalStream: () => {
@@ -47,18 +30,29 @@ export const AuthenticationContext = createContext({
setUserInfo: () => {
// don't return anything
},
- submitPasswordReset: () => {
- // don't return anything
- }
+ radius: 10,
+ userID: null,
+ userName: null
});
export function AuthenticationProvider({ children }) {
const { setErrorMessages } = useContext(ErrorContext);
- const { loading, sendRequest } = useRequest();
+ const abortControllerRef = useRef(new AbortController());
+ const [loading, setLoading] = useState(false);
const [localStream, setLocalStream] = useState(null);
const [remoteStreams, setRemoteStreams] = useState([]);
const [userInfo, setUserInfo] = useState({
- ...unauthenticatedUserInfo
+ admin: false,
+ avatar: {
+ card_faces: [],
+ image_uris: {}
+ },
+ buds: [],
+ conversations: [],
+ measurement_system: 'imperial',
+ radius: 10,
+ userID: null,
+ userName: null
});
const servers = useRef({
iceServers: [
@@ -69,259 +63,82 @@ export function AuthenticationProvider({ children }) {
iceCandidatePoolSize: 10
});
const peerConnection = useRef(new RTCPeerConnection(servers.current));
- const authenticationQuery = `
- _id
- admin
- avatar
- name
- settings {
- measurement_system
- radius
- }
- token
- `;
-
- const storeUserInfo = useCallback(function ({
- _id,
- admin,
- avatar,
- name,
- settings,
- token
- }) {
- // store in running application
- setUserInfo({
- admin,
- avatar,
- settings,
- userID: _id,
- userName: name
- });
- // store in browser
- Cookies.set('authentication_token', token);
- },
- []);
-
- const authenticate = useCallback(
- async function () {
- await sendRequest({
- callback: storeUserInfo,
- load: true,
- operation: 'authenticate',
- get body() {
- return {
- query: `
- query {
- ${this.operation} {
- ${authenticationQuery}
- }
- }
- `
- };
- }
- });
- },
- [sendRequest]
- );
-
- const login = useCallback(
- async function (email, password) {
- await sendRequest({
- callback: storeUserInfo,
- load: true,
- operation: 'login',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- email: "${email}",
- password: "${password}"
- ) {
- ${authenticationQuery}
- }
+ useEffect(() => {
+ (async () => {
+ try {
+ if (Cookies.get('authentication_token')) {
+ setLoading(true);
+ const {
+ data: {
+ authenticate: {
+ _id,
+ admin,
+ avatar,
+ buds,
+ conversations,
+ measurement_system,
+ name,
+ radius
}
- `
- };
- }
- });
- },
- [sendRequest]
- );
-
- const logout = useCallback(
- async function () {
- // unsubscribe from push notifications if subscribed
- let subscription;
-
- if ('Notification' in window && 'serviceWorker' in navigator) {
- const swreg = await navigator.serviceWorker.ready;
- subscription = await swreg.pushManager.getSubscription();
- if (subscription) {
- try {
- await subscription.unsubscribe();
- } catch (error) {
- setErrorMessages((prevState) => [...prevState, error.message]);
- }
- }
- }
-
- // if the logged in user had a push subscription, remove it and the token from the server
- await sendRequest({
- operation: 'logoutSingleDevice',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}${
- subscription
- ? `(
- endpoint: "${subscription.endpoint}"
- )`
- : ''
}
- }
- `
- };
- }
- });
-
- // clear from browser and running application
- setUserInfo({
- ...unauthenticatedUserInfo
- });
- Cookies.remove('authentication_token');
- },
- [sendRequest]
- );
-
- const register = useCallback(
- async function (email, name, password) {
- const avatar = {
- prints_search_uri: null,
- printings: []
- };
-
- await sendRequest({
- callback: (data) => {
- avatar.prints_search_uri = data.prints_search_uri;
- },
- load: true,
- method: 'GET',
- url: 'https://api.scryfall.com/cards/random'
- });
-
- await sendRequest({
- callback: (data) => {
- avatar.printings = data.data;
- },
- load: true,
- method: 'GET',
- url: avatar.prints_search_uri
- });
-
- const randomIndex = Math.floor(Math.random() * avatar.printings.length);
-
- await sendRequest({
- callback: storeUserInfo,
- load: true,
- operation: 'register',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- avatar: "${avatar.printings[randomIndex].image_uris.art_crop}",
- email: "${email}",
- name: "${name}",
- password: "${password}"
- ) {
- ${authenticationQuery}
- }
- }
- `
- };
- }
- });
- },
- [sendRequest]
- );
-
- const requestPasswordReset = useCallback(
- async function (email) {
- await sendRequest({
- callback: () => {
- setErrorMessages((prevState) => {
- return [
- ...prevState,
- 'A link to reset your password has been sent. Please check your email inbox and your spam folder.'
- ];
+ } = await authenticate({
+ queryString: authenticateQuery,
+ signal: abortControllerRef.current.signal
+ });
+ setUserInfo({
+ admin,
+ avatar,
+ buds,
+ conversations,
+ measurement_system,
+ radius,
+ userID: _id,
+ userName: name
});
- },
- load: true,
- operation: 'requestPasswordReset',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(email: "${email}")
- }
- `
- };
}
- });
- },
- [sendRequest]
- );
+ } catch (error) {
+ Cookies.remove('authentication_token');
+ } finally {
+ setLoading(false);
+ }
+ })();
+ }, []);
- const submitPasswordReset = useCallback(
- async function (email, password, reset_token) {
- await sendRequest({
- callback: storeUserInfo,
- load: true,
- operation: 'submitPasswordReset',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- email: "${email}"
- password: "${password}"
- reset_token: "${reset_token}"
- ) {
- ${authenticationQuery}
- }
- }
- `
- };
- }
- });
+ useSubscribe({
+ cleanup: () => {
+ abortControllerRef.current.abort();
+ abortControllerRef.current = new AbortController();
},
- [sendRequest]
- );
-
- useEffect(() => {
- if (Cookies.get('authentication_token')) {
- authenticate();
+ condition: !!Cookies.get('authentication_token'),
+ queryString: authenticateQuery,
+ subscriptionType: 'subscribeAccount',
+ update: (data) => {
+ setUserInfo({
+ admin: data.admin,
+ avatar: data.avatar,
+ buds: data.buds,
+ conversations: data.conversations,
+ measurement_system: data.measurement_system,
+ radius: data.radius,
+ userID: data._id,
+ userName: data.name
+ });
}
- }, []);
+ });
return (
{children}
diff --git a/src/contexts/Permissions.jsx b/src/contexts/Permissions.jsx
index 090e319..b94117c 100644
--- a/src/contexts/Permissions.jsx
+++ b/src/contexts/Permissions.jsx
@@ -1,10 +1,4 @@
-import React, {
- createContext,
- useContext,
- useEffect,
- useRef,
- useState
-} from 'react';
+import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
import urlBase64ToUint8Array from '../functions/url-base64-to-uint8-array';
import useRequest from '../hooks/request-hook';
@@ -71,8 +65,7 @@ export function PermissionsProvider({ children }) {
const [microphoneSupported, setMicrophoneSupported] = useState(false);
const [notificationsEnabled, setNotificationsEnabled] = useState(false);
const [notificationsPermission, setNotificationsPermission] = useState();
- const notificationsSupported =
- 'Notification' in window && 'serviceWorker' in navigator;
+ const notificationsSupported = 'Notification' in window && 'serviceWorker' in navigator;
async function turnOnNotificationsAndSubscribeToPushMessaging() {
if (isLoggedIn) {
@@ -84,19 +77,14 @@ export function PermissionsProvider({ children }) {
setNotificationsEnabled(true);
const swreg = await navigator.serviceWorker.ready;
- const existingSubscription =
- await swreg.pushManager.getSubscription();
+ const existingSubscription = await swreg.pushManager.getSubscription();
if (!existingSubscription) {
const newSubscription = await swreg.pushManager.subscribe({
userVisibleOnly: true,
- applicationServerKey: urlBase64ToUint8Array(
- process.env.REACT_APP_VAPID_PUBLIC_KEY
- ),
+ applicationServerKey: urlBase64ToUint8Array(process.env.REACT_APP_VAPID_PUBLIC_KEY),
userID
});
- const parsedNewSubscription = JSON.parse(
- JSON.stringify(newSubscription)
- );
+ const parsedNewSubscription = JSON.parse(JSON.stringify(newSubscription));
sendRequest({
operation: 'subscribeToPush',
get body() {
@@ -330,7 +318,10 @@ export function PermissionsProvider({ children }) {
// when the app closes, the user logs in/out or the geolocationPermission changes, clear the watch
useEffect(() => {
- return clearAndDeleteLocation;
+ return () => {
+ // opposite of what you might expect due to a closure i suspect... maybe need to use useCallback?
+ if (!isLoggedIn) clearAndDeleteLocation();
+ };
}, [isLoggedIn, geolocationPermission]);
useEffect(() => {
@@ -340,17 +331,23 @@ export function PermissionsProvider({ children }) {
const N = await navigator.permissions.query({ name: 'notifications' });
setNotificationsPermission(N.state);
- const devices =
- navigator.mediaDevices &&
- (await navigator.mediaDevices.enumerateDevices());
+ const devices = navigator.mediaDevices && (await navigator.mediaDevices.enumerateDevices());
- if (devices.some((device) => device.kind === 'videoinput')) {
+ if (
+ devices.some((device) => device.kind === 'videoinput') &&
+ navigator.permissions &&
+ 'camera' in navigator.permissions
+ ) {
setCameraSupported(true);
const C = await navigator.permissions.query({ name: 'camera' });
setCameraPermission(C.state);
}
- if (devices.some((device) => device.kind === 'audioinput')) {
+ if (
+ devices.some((device) => device.kind === 'audioinput') &&
+ navigator.permissions &&
+ 'microphone' in navigator.permissions
+ ) {
setMicrophoneSupported(true);
const M = await navigator.permissions.query({ name: 'microphone' });
setMicrophonePermission(M.state);
diff --git a/src/contexts/account-context.jsx b/src/contexts/account-context.jsx
index 26774a8..bc8a417 100644
--- a/src/contexts/account-context.jsx
+++ b/src/contexts/account-context.jsx
@@ -1,25 +1,24 @@
-import React, {
- createContext,
- useCallback,
- useContext,
- useEffect,
- useState
-} from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
+import React, { createContext, /* useCallback, */ useContext, useRef, useState } from 'react';
+import { useLocation, useNavigate, useParams } from 'react-router-dom';
+import accountQuery from '../constants/account-query';
import fetchAccountByID from '../graphql/queries/account/fetch-account-by-ID';
-import useRequest from '../hooks/request-hook';
+// import useRequest from '../hooks/request-hook';
+import useSubscribe from '../hooks/subscribe-hook';
import Account from '../pages/Account';
import LoadingSpinner from '../components/miscellaneous/LoadingSpinner';
-import { AuthenticationContext } from './Authentication';
-import { CardCacheContext } from './CardCache';
import { ErrorContext } from './Error';
export const AccountContext = createContext({
- loading: false,
+ abortControllerRef: { current: new AbortController() },
accountState: {
_id: '',
- avatar: '',
+ avatar: {
+ card_faces: null,
+ image_uris: {
+ art_crop: '...'
+ }
+ },
buds: [],
cubes: [],
decks: [],
@@ -35,23 +34,26 @@ export const AccountContext = createContext({
sent_bud_requests: [],
total_events: 0
},
- setAccountState: () => null,
- createMatch: () => null,
+ setAccountState: () => null
+ // createMatch: () => null
// deleteEvent: () => null,
- // deleteMatch: () => null,
- editAccount: () => null
+ // deleteMatch: () => null
});
export default function ContextualizedAccountPage() {
- const { setUserInfo, userID } = useContext(AuthenticationContext);
- const { addCardsToCache, scryfallCardDataCache } =
- useContext(CardCacheContext);
const { setErrorMessages } = useContext(ErrorContext);
- const navigate = useNavigate();
+ const location = useLocation();
+ // const navigate = useNavigate();
const { accountID } = useParams();
+ const abortControllerRef = useRef(new AbortController());
const [accountState, setAccountState] = useState({
_id: accountID,
- avatar: '',
+ avatar: {
+ card_faces: null,
+ image_uris: {
+ art_crop: '...'
+ }
+ },
buds: [],
cubes: [],
decks: [],
@@ -68,272 +70,77 @@ export default function ContextualizedAccountPage() {
total_events: 0
});
const [loading, setLoading] = useState(false);
- const accountQuery = `
- _id
- avatar
- buds {
- _id
- avatar
- buds {
- _id
- avatar
- name
- }
- decks {
- _id
- format
- name
- }
- name
- }
- cubes {
- _id
- description
- image
- mainboard {
- _id
- }
- modules {
- _id
- cards {
- _id
- }
- name
- }
- name
- rotations {
- _id
- cards {
- _id
- }
- name
- size
- }
- sideboard {
- _id
- }
- }
- decks {
- _id
- description
- format
- image
- name
- }
- email
- events {
- _id
- createdAt
- cube {
- _id
- image
- name
- }
- host {
- _id
- avatar
- name
- }
- name
- players {
- account {
- _id
- avatar
- name
- }
- }
- }
- location {
- coordinates
- }
- matches {
- _id
- createdAt
- cube {
- _id
- name
- }
- decks {
- _id
- format
- name
- }
- event {
- _id
- name
- }
- players {
- account {
- _id
- avatar
- name
- }
- }
- }
- name
- nearby_users {
- _id
- avatar
- name
- }
- received_bud_requests {
- _id
- avatar
- name
- }
- sent_bud_requests {
- _id
- avatar
- name
- }
- settings {
- measurement_system
- radius
- }
- total_events
- `;
- const { sendRequest } = useRequest();
-
- const updateAccountState = useCallback(
- async function (data) {
- const cardSet = new Set();
-
- for (const cube of data.cubes) {
- if (cube.image) cardSet.add(cube.image);
- }
-
- for (const deck of data.decks) {
- if (deck.image) cardSet.add(deck.image);
- }
-
- for (const event of data.events) {
- if (event.cube.image) cardSet.add(event.cube.image);
- }
-
- await addCardsToCache([...cardSet]);
-
- for (const cube of data.cubes) {
- if (cube.image) {
- cube.image = {
- alt: scryfallCardDataCache.current[cube.image].name,
- scryfall_id: cube.image,
- src: scryfallCardDataCache.current[cube.image].art_crop
- };
- }
- }
-
- for (const deck of data.decks) {
- if (deck.image) {
- deck.image = {
- alt: scryfallCardDataCache.current[deck.image].name,
- scryfall_id: deck.image,
- src: scryfallCardDataCache.current[deck.image].art_crop
- };
- }
- }
-
- for (const event of data.events) {
- if (event.cube.image) {
- event.cube.image = {
- alt: scryfallCardDataCache.current[event.cube.image].name,
- scryfall_id: event.cube.image,
- src: scryfallCardDataCache.current[event.cube.image].art_crop
- };
- }
- }
-
- setAccountState(data);
+ const { accountData } = location.state || {};
+ // const { sendRequest } = useRequest();
+
+ // const createMatch = useCallback(
+ // async function (event, deckIDs, eventID, playerIDs) {
+ // event.preventDefault();
+
+ // await sendRequest({
+ // callback: (data) => {
+ // navigate(`/match/${data._id}`);
+ // },
+ // load: true,
+ // operation: 'createMatch',
+ // get body() {
+ // return {
+ // query: `
+ // mutation {
+ // ${this.operation}(
+ // deckIDs: [${deckIDs.map((dckID) => '"' + dckID + '"')}],
+ // ${eventID ? 'eventID: "' + eventID + '",\n' : ''}
+ // playerIDs: [${playerIDs.map((plrID) => '"' + plrID + '"')}]
+ // ) {
+ // _id
+ // }
+ // }
+ // `
+ // };
+ // }
+ // });
+ // },
+ // [navigate, sendRequest]
+ // );
+
+ useSubscribe({
+ cleanup: () => {
+ abortControllerRef.current.abort();
+ abortControllerRef.current = new AbortController();
},
- [addCardsToCache]
- );
-
- const createMatch = useCallback(
- async function (event, deckIDs, eventID, playerIDs) {
- event.preventDefault();
-
- await sendRequest({
- callback: (data) => {
- navigate(`/match/${data._id}`);
- },
- load: true,
- operation: 'createMatch',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- deckIDs: [${deckIDs.map((dckID) => '"' + dckID + '"')}],
- ${eventID ? 'eventID: "' + eventID + '",\n' : ''}
- playerIDs: [${playerIDs.map((plrID) => '"' + plrID + '"')}]
- ) {
- _id
- }
- }
- `
- };
- }
- });
- },
- [navigate, sendRequest]
- );
-
- const editAccount = useCallback(
- async function (changes) {
- await sendRequest({
- callback: (data) => {
- setAccountState(data);
- if (!changes.toString().includes('return_other')) {
- setUserInfo((prevState) => ({
- ...prevState,
- avatar: data.avatar,
- settings: data.settings,
- userName: data.name
- }));
- }
- },
- operation: 'editAccount',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- ${changes}
- ) {
- ${accountQuery}
- }
- }
- `
- };
+ connectionInfo: { accountID },
+ dependencies: [accountID],
+ queryString: accountQuery,
+ setup: async () => {
+ if (accountData) {
+ setAccountState(accountData);
+ } else {
+ try {
+ setLoading(true);
+ const response = await fetchAccountByID({
+ headers: { AccountID: accountID },
+ queryString: accountQuery,
+ signal: abortControllerRef.current.signal
+ });
+ setAccountState(response.data.fetchAccountByID);
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ } finally {
+ setLoading(false);
}
- });
- },
- [accountQuery, sendRequest]
- );
-
- useEffect(() => {
- (async function () {
- try {
- setLoading(true);
- const data = await fetchAccountByID({
- headers: { AccountID: accountID },
- queryString: `{\n${accountQuery}\n}`
- });
- await updateAccountState(data.data.fetchAccountByID);
- } catch (error) {
- setErrorMessages((prevState) => [...prevState, error.message]);
- } finally {
- setLoading(false);
}
- })();
- }, [accountID, userID]);
+ },
+ subscriptionType: 'subscribeAccount',
+ update: setAccountState
+ });
return (
{loading ? : }
diff --git a/src/contexts/blog-post-context.jsx b/src/contexts/blog-post-context.jsx
index 5032cca..07b60b3 100644
--- a/src/contexts/blog-post-context.jsx
+++ b/src/contexts/blog-post-context.jsx
@@ -1,24 +1,25 @@
-import React, {
- createContext,
- useCallback,
- useContext,
- useEffect,
- useState
-} from 'react';
+import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
-import useRequest from '../hooks/request-hook';
+import blogPostQuery from '../constants/blog-post-query';
+import fetchBlogPostByID from '../graphql/queries/blog/fetch-blog-post-by-ID';
import useSubscribe from '../hooks/subscribe-hook';
import BlogPost from '../pages/BlogPost';
+import LoadingSpinner from '../components/miscellaneous/LoadingSpinner';
import { AuthenticationContext } from './Authentication';
+import { ErrorContext } from './Error';
export const BlogPostContext = createContext({
+ abortControllerRef: { current: new AbortController() },
loading: false,
blogPostState: {
_id: '',
author: {
_id: '',
- avatar: '',
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: ''
},
body: '',
@@ -30,18 +31,22 @@ export const BlogPostContext = createContext({
createdAt: 0,
updatedAt: 0
},
- createComment: () => null,
setBlogPostState: () => null
});
export default function ContextualizedBlogPostPage() {
- const { blogPostID } = useParams();
const { avatar, userID, userName } = useContext(AuthenticationContext);
+ const { setErrorMessages } = useContext(ErrorContext);
+ const { blogPostID } = useParams();
+ const abortControllerRef = useRef(new AbortController());
const [blogPostState, setBlogPostState] = useState({
_id: null,
author: {
_id: '',
- avatar: '',
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: '...'
},
body: '',
@@ -53,83 +58,32 @@ export default function ContextualizedBlogPostPage() {
createdAt: null,
updatedAt: null
});
- const blogPostQuery = `
- _id
- author {
- _id
- avatar
- name
- }
- body
- comments {
- _id
- author {
- _id
- avatar
- name
- }
- body
- createdAt
- updatedAt
- }
- image
- published
- subtitle
- title
- createdAt
- updatedAt
- `;
- const { loading, sendRequest } = useRequest();
-
- const createComment = useCallback(
- async function (newComment) {
- await sendRequest({
- headers: { BlogPostID: blogPostID },
- operation: 'createComment',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(body: "${newComment}") {
- _id
- }
- }
- `
- };
- }
- });
- },
- [sendRequest]
- );
-
- const fetchBlogPostByID = useCallback(
- async function () {
- await sendRequest({
- callback: setBlogPostState,
- headers: { BlogPostID: blogPostID },
- load: true,
- operation: 'fetchBlogPostByID',
- get body() {
- return {
- query: `
- query {
- ${this.operation} {
- ${blogPostQuery}
- }
- }
- `
- };
- }
- });
- },
- [blogPostQuery, blogPostID, sendRequest]
- );
+ const [loading, setLoading] = useState(false);
if (blogPostID !== 'new-post') {
useSubscribe({
+ cleanup: () => {
+ abortControllerRef.current.abort();
+ abortControllerRef.current = new AbortController();
+ },
connectionInfo: { blogPostID },
+ dependencies: [blogPostID],
queryString: blogPostQuery,
- setup: fetchBlogPostByID,
+ setup: async () => {
+ try {
+ setLoading(true);
+ const response = await fetchBlogPostByID({
+ headers: { BlogPostID: blogPostID },
+ queryString: blogPostQuery,
+ signal: abortControllerRef.current.signal
+ });
+ setBlogPostState(response.data.fetchBlogPostByID);
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ } finally {
+ setLoading(false);
+ }
+ },
subscriptionType: 'subscribeBlogPost',
update: setBlogPostState
});
@@ -154,13 +108,12 @@ export default function ContextualizedBlogPostPage() {
return (
-
+ {loading ? : }
);
}
diff --git a/src/contexts/cube-context.jsx b/src/contexts/cube-context.jsx
index 14b19e0..0e0ce59 100644
--- a/src/contexts/cube-context.jsx
+++ b/src/contexts/cube-context.jsx
@@ -1,20 +1,16 @@
-import React, {
- createContext,
- useCallback,
- useContext,
- useEffect,
- useState
-} from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
+import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
+import { useLocation, useParams } from 'react-router-dom';
-import usePopulate from '../hooks/populate-hook';
+import cubeQuery from '../constants/cube-query';
+import fetchCubeByID from '../graphql/queries/cube/fetch-cube-by-ID';
import useRequest from '../hooks/request-hook';
import useSubscribe from '../hooks/subscribe-hook';
import Cube from '../pages/Cube';
import LoadingSpinner from '../components/miscellaneous/LoadingSpinner';
-import { CardCacheContext } from './CardCache';
+import { ErrorContext } from './Error';
export const CubeContext = createContext({
+ abortControllerRef: { current: new AbortController() },
activeComponentState: {
_id: 'mainbaord',
displayedCards: [],
@@ -26,14 +22,27 @@ export const CubeContext = createContext({
_id: null,
creator: {
_id: null,
- avatar: null,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: null
},
description: null,
image: {
- alt: undefined,
- scryfall_id: undefined,
- src: undefined
+ _id: '',
+ image_uris: {
+ art_crop: ''
+ },
+ name: '',
+ card_faces: [
+ {
+ image_uris: {
+ art_crop: ''
+ },
+ name: ''
+ }
+ ]
},
mainboard: [],
modules: [],
@@ -47,21 +56,20 @@ export const CubeContext = createContext({
filter: ''
},
setDisplayState: () => null,
- // addCardToCube: () => null,
- cloneCube: () => null,
createModule: () => null,
createRotation: () => null,
deleteCard: () => null,
deleteModule: () => null,
deleteRotation: () => null,
- editCard: () => null,
- editCube: () => null,
- editModule: () => null,
editRotation: () => null
});
export default function ContextualizedCubePage() {
- const navigate = useNavigate();
+ const { setErrorMessages } = useContext(ErrorContext);
+ const location = useLocation();
+ const { cubeID } = useParams();
+ const abortControllerRef = useRef(new AbortController());
+ const { sendRequest } = useRequest();
const [activeComponentState, setActiveComponentState] = useState({
_id: 'mainboard',
displayedCards: [],
@@ -69,21 +77,31 @@ export default function ContextualizedCubePage() {
name: 'Mainboard',
size: null
});
- const { cubeID } = useParams();
- const { addCardsToCache, scryfallCardDataCache } =
- useContext(CardCacheContext);
const [cubeState, setCubeState] = useState({
_id: cubeID,
creator: {
_id: null,
- avatar: null,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: '...'
},
description: '',
image: {
- alt: undefined,
- scryfall_id: undefined,
- src: undefined
+ _id: '',
+ image_uris: {
+ art_crop: ''
+ },
+ name: '',
+ card_faces: [
+ {
+ image_uris: {
+ art_crop: ''
+ },
+ name: ''
+ }
+ ]
},
mainboard: [],
modules: [],
@@ -96,49 +114,8 @@ export default function ContextualizedCubePage() {
activeComponentID: 'mainboard',
filter: ''
});
- const cardQuery = `
- _id
- cmc
- color_identity
- notes
- scryfall_id
- type_line
- `;
- const cubeQuery = `
- _id
- creator {
- _id
- avatar
- name
- }
- description
- image
- mainboard {
- ${cardQuery}
- }
- modules {
- _id
- cards {
- ${cardQuery}
- }
- name
- }
- name
- published
- rotations {
- _id
- cards {
- ${cardQuery}
- }
- name
- size
- }
- sideboard {
- ${cardQuery}
- }
- `;
- const { populateCachedScryfallData } = usePopulate();
- const { loading, sendRequest } = useRequest();
+ const [loading, setLoading] = useState(false);
+ const { cubeData } = location.state || {};
const filterCards = useCallback(
(cards, text) =>
@@ -146,12 +123,9 @@ export default function ContextualizedCubePage() {
const wordArray = text.split(' ');
return wordArray.every(
(word) =>
- card.keywords
- .join(' ')
- .toLowerCase()
- .includes(word.toLowerCase()) ||
- card.name.toLowerCase().includes(word.toLowerCase()) ||
- card.type_line.toLowerCase().includes(word.toLowerCase())
+ card.scryfall_card.keywords.join(' ').toLowerCase().includes(word.toLowerCase()) ||
+ card.scryfall_card.name.toLowerCase().includes(word.toLowerCase()) ||
+ card.scryfall_card.type_line.toLowerCase().includes(word.toLowerCase())
);
}),
[]
@@ -161,18 +135,13 @@ export default function ContextualizedCubePage() {
const state = { _id: displayState.activeComponentID };
if (state._id === 'sideboard') {
- state.displayedCards = filterCards(
- cubeState.sideboard,
- displayState.filter
- );
+ state.displayedCards = filterCards(cubeState.sideboard, displayState.filter);
state.name = 'Sideboard';
} else if (cubeState.modules.find((module) => module._id === state._id)) {
const module = cubeState.modules.find((mdl) => mdl._id === state._id);
state.displayedCards = filterCards(module.cards, displayState.filter);
state.name = module.name;
- } else if (
- cubeState.rotations.find((rotation) => rotation._id === state._id)
- ) {
+ } else if (cubeState.rotations.find((rotation) => rotation._id === state._id)) {
const rotation = cubeState.rotations.find((rtn) => rtn._id === state._id);
state.displayedCards = filterCards(rotation.cards, displayState.filter);
state.maxSize = rotation.cards.length;
@@ -180,119 +149,13 @@ export default function ContextualizedCubePage() {
state.size = rotation.size;
} else {
state._id = 'mainboard';
- state.displayedCards = filterCards(
- cubeState.mainboard,
- displayState.filter
- );
+ state.displayedCards = filterCards(cubeState.mainboard, displayState.filter);
state.name = 'Mainboard';
}
setActiveComponentState(state);
}, [cubeState, displayState, filterCards]);
- const updateCubeState = useCallback(
- async function (data) {
- const cardSet = new Set();
-
- if (data.image) cardSet.add(data.image);
-
- for (const card of data.mainboard) {
- cardSet.add(card.scryfall_id);
- }
-
- for (const card of data.sideboard) {
- cardSet.add(card.scryfall_id);
- }
-
- for (const module of data.modules) {
- for (const card of module.cards) {
- cardSet.add(card.scryfall_id);
- }
- }
-
- for (const rotation of data.rotations) {
- for (const card of rotation.cards) {
- cardSet.add(card.scryfall_id);
- }
- }
-
- await addCardsToCache([...cardSet]);
-
- if (data.image) {
- data.image = {
- alt: scryfallCardDataCache.current[data.image].name,
- scryfall_id: data.image,
- src: scryfallCardDataCache.current[data.image].art_crop
- };
- }
-
- data.mainboard.forEach(populateCachedScryfallData);
-
- data.sideboard.forEach(populateCachedScryfallData);
-
- for (const module of data.modules) {
- module.cards.forEach(populateCachedScryfallData);
- }
-
- for (const rotation of data.rotations) {
- rotation.cards.forEach(populateCachedScryfallData);
- }
-
- setCubeState(data);
- },
- [addCardsToCache, populateCachedScryfallData]
- );
-
- // const addCardToCube = useCallback(
- // async function ({ name, scryfall_id }) {
- // await sendRequest({
- // headers: { CubeID: cubeState._id },
- // operation: 'addCardToCube',
- // get body() {
- // return {
- // query: `
- // mutation {
- // ${this.operation}(
- // componentID: "${activeComponentState._id}",
- // name: "${name}",
- // scryfall_id: "${scryfall_id}"
- // ) {
- // _id
- // }
- // }
- // `
- // };
- // }
- // });
- // },
- // [activeComponentState._id, cubeState._id, sendRequest]
- // );
-
- const cloneCube = useCallback(
- async function () {
- await sendRequest({
- callback: (data) => {
- navigate(`/cube/${data._id}`);
- },
- headers: { CubeID: cubeState._id },
- load: true,
- operation: 'cloneCube',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation} {
- ${cubeQuery}
- }
- }
- `
- };
- }
- });
- },
- [cubeQuery, cubeState._id, navigate, sendRequest]
- );
-
const createModule = useCallback(
async function (name, toggleOpen) {
await sendRequest({
@@ -367,9 +230,7 @@ export default function ContextualizedCubePage() {
mutation {
${this.operation}(
cardID: "${cardID}",
- ${
- destinationID ? 'destinationID: "' + destinationID + '",' : ''
- }
+ ${destinationID ? 'destinationID: "' + destinationID + '",' : ''}
originID: "${activeComponentState._id}"
)
}
@@ -431,80 +292,6 @@ export default function ContextualizedCubePage() {
[activeComponentState._id, cubeState._id, sendRequest]
);
- const editCard = useCallback(
- async function (changes) {
- await sendRequest({
- headers: { CubeID: cubeState._id },
- operation: 'editCard',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- componentID: "${activeComponentState._id}",
- ${changes}
- ) {
- _id
- }
- }
- `
- };
- }
- });
- },
- [activeComponentState._id, cubeState._id, sendRequest]
- );
-
- const editCube = useCallback(
- async function (description, image, name, published) {
- await sendRequest({
- headers: { CubeID: cubeState._id },
- operation: 'editCube',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- description: "${description}",
- ${image ? `image: "${image}",` : ''}
- name: "${name}",
- published: ${published}
- ) {
- _id
- }
- }
- `
- };
- }
- });
- },
- [cubeState._id, sendRequest]
- );
-
- const editModule = useCallback(
- async function (name) {
- await sendRequest({
- headers: { CubeID: cubeState._id },
- operation: 'editModule',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- moduleID: "${activeComponentState._id}",
- name: "${name}"
- ) {
- _id
- }
- }
- `
- };
- }
- });
- },
- [activeComponentState._id, cubeState._id, sendRequest]
- );
-
const editRotation = useCallback(
async function (name, size) {
await sendRequest({
@@ -530,54 +317,50 @@ export default function ContextualizedCubePage() {
[activeComponentState._id, cubeState._id, sendRequest]
);
- const fetchCubeByID = useCallback(
- async function () {
- await sendRequest({
- callback: updateCubeState,
- headers: { CubeID: cubeState._id },
- load: true,
- operation: 'fetchCubeByID',
- get body() {
- return {
- query: `
- query {
- ${this.operation} {
- ${cubeQuery}
- }
- }
- `
- };
- }
- });
- },
- [cubeQuery, cubeState._id, sendRequest, updateCubeState]
- );
-
useSubscribe({
+ cleanup: () => {
+ abortControllerRef.current.abort();
+ abortControllerRef.current = new AbortController();
+ },
connectionInfo: { cubeID },
+ dependencies: [cubeID],
queryString: cubeQuery,
- setup: fetchCubeByID,
+ setup: async () => {
+ if (cubeData) {
+ setCubeState(cubeData);
+ } else {
+ try {
+ setLoading(true);
+ const response = await fetchCubeByID({
+ headers: { CubeID: cubeID },
+ queryString: cubeQuery,
+ signal: abortControllerRef.current.signal
+ });
+ setCubeState(response.data.fetchCubeByID);
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ } finally {
+ setLoading(false);
+ }
+ }
+ },
subscriptionType: 'subscribeCube',
- update: updateCubeState
+ update: setCubeState
});
return (
diff --git a/src/contexts/deck-context.jsx b/src/contexts/deck-context.jsx
index aec4d91..ddc60db 100644
--- a/src/contexts/deck-context.jsx
+++ b/src/contexts/deck-context.jsx
@@ -1,290 +1,146 @@
-import React, { createContext, useCallback, useContext, useState } from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
-
-import usePopulate from '../hooks/populate-hook';
-import useRequest from '../hooks/request-hook';
+import React, {
+ createContext,
+ useContext,
+ /* useEffect, */
+ useRef,
+ useState
+} from 'react';
+import { useLocation, useParams } from 'react-router-dom';
+
+import deckQuery from '../constants/deck-query';
+import fetchDeckByID from '../graphql/queries/deck/fetch-deck-by-ID';
import useSubscribe from '../hooks/subscribe-hook';
+// import validateDeck from '../functions/validate-deck';
import Deck from '../pages/Deck';
-import { CardCacheContext } from './CardCache';
+import LoadingSpinner from '../components/miscellaneous/LoadingSpinner';
+import { ErrorContext } from './Error';
export const DeckContext = createContext({
- loading: false,
+ abortControllerRef: { current: new AbortController() },
deckState: {
_id: '',
+ cards: [],
creator: {
_id: '',
- avatar: '',
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: ''
},
description: '',
format: '',
image: {
- alt: undefined,
- scryfall_id: undefined,
- src: undefined
+ _id: '',
+ image_uris: {
+ art_crop: ''
+ },
+ name: '',
+ card_faces: [
+ {
+ image_uris: {
+ art_crop: ''
+ },
+ name: ''
+ }
+ ]
},
- mainboard: [],
name: '',
- published: false,
- sideboard: []
- },
- addCardsToDeck: () => null,
- cloneDeck: () => null,
- editDeck: () => null,
- removeCardsFromDeck: () => null,
- toggleMainboardSideboardDeck: () => null
+ published: false
+ }
+ // warnings: []
});
export default function ContextualizedDeckPage() {
- const navigate = useNavigate();
+ const { setErrorMessages } = useContext(ErrorContext);
+ const location = useLocation();
const { deckID } = useParams();
- const { addCardsToCache, scryfallCardDataCache } =
- useContext(CardCacheContext);
+ const abortControllerRef = useRef(new AbortController());
+ const [loading, setLoading] = useState(false);
const [deckState, setDeckState] = useState({
_id: deckID,
+ cards: [],
creator: {
_id: '',
- avatar: '',
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: '...'
},
description: '',
format: '',
image: {
- alt: undefined,
- scryfall_id: undefined,
- src: undefined
- },
- mainboard: [],
- name: '',
- published: false,
- sideboard: []
- });
- const cardQuery = `
- _id
- scryfall_id
- `;
- const deckQuery = `
- _id
- creator {
- _id
- avatar
- name
- }
- description
- format
- image
- mainboard {
- ${cardQuery}
- }
- name
- published
- sideboard {
- ${cardQuery}
- }
- `;
- const { loading, sendRequest } = useRequest();
- const { populateCachedScryfallData } = usePopulate();
-
- const updateDeckState = useCallback(
- async function (data) {
- const cardSet = new Set();
-
- if (data.image) cardSet.add(data.image);
-
- for (const card of data.mainboard) {
- cardSet.add(card.scryfall_id);
- }
-
- for (const card of data.sideboard) {
- cardSet.add(card.scryfall_id);
- }
-
- await addCardsToCache([...cardSet]);
-
- if (data.image) {
- data.image = {
- alt: scryfallCardDataCache.current[data.image].name,
- scryfall_id: data.image,
- src: scryfallCardDataCache.current[data.image].art_crop
- };
- }
-
- data.mainboard.forEach(populateCachedScryfallData);
-
- data.sideboard.forEach(populateCachedScryfallData);
-
- setDeckState(data);
- },
- [addCardsToCache, populateCachedScryfallData]
- );
-
- const addCardsToDeck = useCallback(
- async function ({ name, scryfall_id }, component, numberOfCopies) {
- await sendRequest({
- headers: { DeckID: deckState._id },
- operation: 'addCardsToDeck',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- component: ${component},
- name: "${name}",
- numberOfCopies: ${numberOfCopies},
- scryfall_id: "${scryfall_id}"
- ) {
- _id
- }
- }
- `
- };
- }
- });
- },
- [deckState._id, sendRequest]
- );
-
- const cloneDeck = useCallback(
- async function () {
- await sendRequest({
- callback: (data) => {
- navigate(`/deck/${data._id}`);
+ _id: '',
+ image_uris: {
+ image_uris: {
+ art_crop: ''
},
- headers: { DeckID: deckState._id },
- load: true,
- operation: 'cloneDeck',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation} {
- ${deckQuery}
- }
- }
- `
- };
- }
- });
- },
- [deckQuery, deckState._id, navigate, sendRequest]
- );
-
- const editDeck = useCallback(
- async function ({ description, format, image, name, published }) {
- await sendRequest({
- headers: { DeckID: deckState._id },
- operation: 'editDeck',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- description: "${description}",
- format: ${format},
- ${image ? `image: "${image}",` : ''}
- published: ${published},
- name: "${name}"
- ) {
- _id
- }
- }
- `
- };
- }
- });
- },
- [deckState._id, sendRequest]
- );
-
- const fetchDeckByID = useCallback(
- async function () {
- await sendRequest({
- callback: updateDeckState,
- headers: { DeckID: deckState._id },
- load: true,
- operation: 'fetchDeckByID',
- get body() {
- return {
- query: `
- query {
- ${this.operation} {
- ${deckQuery}
- }
- }
- `
- };
- }
- });
- },
- [deckQuery, deckState._id, sendRequest, updateDeckState]
- );
-
- const removeCardsFromDeck = useCallback(
- async function (cardIDs, component) {
- await sendRequest({
- headers: { DeckID: deckState._id },
- operation: 'removeCardsFromDeck',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(
- cardIDs: [${cardIDs.map((cardID) => '"' + cardID + '"')}],
- component: ${component}
- ) {
- _id
- }
- }
- `
- };
+ name: ''
+ },
+ name: '',
+ card_faces: [
+ {
+ image_uris: {
+ art_crop: ''
+ },
+ name: ''
}
- });
+ ]
},
- [deckState._id, sendRequest]
- );
+ name: '',
+ published: false
+ });
+ // const [warnings, setWarnings] = useState([]);
- const toggleMainboardSideboardDeck = useCallback(
- async function (cardID) {
- await sendRequest({
- headers: { DeckID: deckState._id },
- operation: 'toggleMainboardSideboardDeck',
- get body() {
- return {
- query: `
- mutation {
- ${this.operation}(cardID: "${cardID}") {
- _id
- }
- }
- `
- };
- }
- });
- },
- [deckState._id, sendRequest]
- );
+ const { deckData } = location.state || {};
useSubscribe({
+ cleanup: () => {
+ abortControllerRef.current.abort();
+ abortControllerRef.current = new AbortController();
+ },
connectionInfo: { deckID },
+ dependencies: [deckID],
queryString: deckQuery,
- setup: fetchDeckByID,
+ setup: async () => {
+ if (deckData) {
+ setDeckState(deckData);
+ } else {
+ try {
+ setLoading(true);
+ const response = await fetchDeckByID({
+ headers: { DeckID: deckID },
+ queryString: deckQuery,
+ signal: abortControllerRef.current.signal
+ });
+ setDeckState(response.data.fetchDeckByID);
+ } catch (error) {
+ setErrorMessages((prevState) => [...prevState, error.message]);
+ } finally {
+ setLoading(false);
+ }
+ }
+ },
+ fetchDeckByID,
subscriptionType: 'subscribeDeck',
- update: updateDeckState
+ update: setDeckState
});
+ // useEffect(() => {
+ // const { format, mainboard, sideboard } = deckState;
+ // validateDeck({ format, mainboard, sideboard }, setWarnings);
+ // }, [deckState.format, deckState.mainboard.length, deckState.sideboard.length]);
+
return (
-
+ {loading ? : }
);
}
diff --git a/src/contexts/event-context.jsx b/src/contexts/event-context.jsx
index 7669ea8..524aef7 100644
--- a/src/contexts/event-context.jsx
+++ b/src/contexts/event-context.jsx
@@ -21,7 +21,10 @@ export const EventContext = createContext({
finished: false,
host: {
_id: null,
- avatar: null,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: '...'
},
name: null,
@@ -29,7 +32,10 @@ export const EventContext = createContext({
{
account: {
_id: null,
- avatar: null,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: '...'
},
answers: [],
@@ -45,7 +51,10 @@ export const EventContext = createContext({
me: {
account: {
_id: null,
- avatar: null,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: '...'
},
answers: [],
@@ -72,7 +81,10 @@ export default function ContextualizedEventPage() {
finished: false,
host: {
_id: null,
- avatar: null,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: '...'
},
name: null,
@@ -90,7 +102,16 @@ export default function ContextualizedEventPage() {
_id
author {
_id
- avatar
+ avatar {
+ card_faces {
+ image_uris {
+ art_crop
+ }
+ }
+ image_uris {
+ art_crop
+ }
+ }
name
}
body
@@ -104,7 +125,16 @@ export default function ContextualizedEventPage() {
players {
account {
_id
- avatar
+ avatar {
+ card_faces {
+ image_uris {
+ art_crop
+ }
+ }
+ image_uris {
+ art_crop
+ }
+ }
name
}
current_pack {
@@ -165,10 +195,7 @@ export default function ContextualizedEventPage() {
const playerIndex = eventState.players.findIndex(
(player) => player.account._id === data.remote_account._id
);
- if (
- Number.isInteger(playerIndex) &&
- !!peerConnectionsRef.current[playerIndex]
- ) {
+ if (Number.isInteger(playerIndex) && !!peerConnectionsRef.current[playerIndex]) {
switch (data.__typename) {
case 'ICECandidate':
const { candidate, sdpMLineIndex, sdpMid, usernameFragment } = data;
@@ -183,15 +210,9 @@ export default function ContextualizedEventPage() {
const { sdp, type } = data;
switch (type) {
case 'offer':
- await peerConnectionsRef.current[
- playerIndex
- ].setRemoteDescription({ type, sdp });
- const answer = await peerConnectionsRef.current[
- playerIndex
- ].createAnswer();
- await peerConnectionsRef.current[playerIndex].setLocalDescription(
- answer
- );
+ await peerConnectionsRef.current[playerIndex].setRemoteDescription({ type, sdp });
+ const answer = await peerConnectionsRef.current[playerIndex].createAnswer();
+ await peerConnectionsRef.current[playerIndex].setLocalDescription(answer);
sendRTCSessionDescription({
variables: {
accountIDs: [eventState.players[playerIndex].account._id],
@@ -279,9 +300,10 @@ export default function ContextualizedEventPage() {
useSubscribe({
cleanup: () => {
abortControllerRef.current.abort();
+ abortControllerRef.current = new AbortController();
},
connectionInfo: { eventID },
- dependencies: [eventID, userID],
+ dependencies: [eventID],
queryString: eventQuery,
setup: async () => {
try {
@@ -339,9 +361,7 @@ export default function ContextualizedEventPage() {
peerConnectionsRef.current = [];
for (let index = 0; index < eventState.players.length; index++) {
if (eventState.players[index].account._id !== userID) {
- const newPeerConnection = new RTCPeerConnection(
- RTCPeerConnectionConfig
- );
+ const newPeerConnection = new RTCPeerConnection(RTCPeerConnectionConfig);
newPeerConnection.onicecandidate = onIceCandidate;
newPeerConnection.onnegotiationneeded = onNegotiationNeeded;
diff --git a/src/contexts/match-context.jsx b/src/contexts/match-context.jsx
index b383de4..3d36d1d 100644
--- a/src/contexts/match-context.jsx
+++ b/src/contexts/match-context.jsx
@@ -11,7 +11,10 @@ export const MatchContext = createContext({
bottomPlayerState: {
account: {
_id: null,
- avatar: null,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: null
},
battlefield: [],
@@ -34,7 +37,10 @@ export const MatchContext = createContext({
{
account: {
_id: null,
- avatar: null,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: null
},
battlefield: [],
@@ -52,7 +58,10 @@ export const MatchContext = createContext({
{
account: {
_id: null,
- avatar: null,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: null
},
battlefield: [],
@@ -80,7 +89,10 @@ export const MatchContext = createContext({
topPlayerState: {
account: {
_id: null,
- avatar: null,
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: null
},
battlefield: [],
@@ -136,7 +148,10 @@ export default function ContextualizedMatchPage() {
{
account: {
_id: 'A',
- avatar: '',
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: '...'
},
battlefield: [],
@@ -154,7 +169,10 @@ export default function ContextualizedMatchPage() {
{
account: {
_id: 'B',
- avatar: '',
+ avatar: {
+ card_faces: [],
+ image_uris: null
+ },
name: '...'
},
battlefield: [],
@@ -179,24 +197,38 @@ export default function ContextualizedMatchPage() {
title: null,
updateFunction: null
});
- const [bottomPlayerState, setBottomPlayerState] = React.useState(
- matchState.players[0]
- );
- const [topPlayerState, setTopPlayerState] = React.useState(
- matchState.players[1]
- );
+ const [bottomPlayerState, setBottomPlayerState] = React.useState(matchState.players[0]);
+ const [topPlayerState, setTopPlayerState] = React.useState(matchState.players[1]);
const matchQuery = `
_id
game_winners {
_id
- avatar
+ avatar {
+ card_faces {
+ image_uris {
+ art_crop
+ }
+ }
+ image_uris {
+ art_crop
+ }
+ }
name
}
log
players {
account {
_id
- avatar
+ avatar {
+ card_faces {
+ image_uris {
+ art_crop
+ }
+ }
+ image_uris {
+ art_crop
+ }
+ }
name
}
battlefield {
@@ -397,20 +429,14 @@ export default function ContextualizedMatchPage() {
React.useEffect(() => {
// this allows a more smooth drag and drop experience
- const me = matchState.players.find(
- (player) => player.account._id === userID
- );
+ const me = matchState.players.find((player) => player.account._id === userID);
if (me) {
- if (JSON.stringify(bottomPlayerState) !== JSON.stringify(me))
- setBottomPlayerState(me);
+ if (JSON.stringify(bottomPlayerState) !== JSON.stringify(me)) setBottomPlayerState(me);
- const opponent = matchState.players.find(
- (player) => player.account._id !== userID
- );
+ const opponent = matchState.players.find((player) => player.account._id !== userID);
- if (JSON.stringify(topPlayerState) !== JSON.stringify(opponent))
- setTopPlayerState(opponent);
+ if (JSON.stringify(topPlayerState) !== JSON.stringify(opponent)) setTopPlayerState(opponent);
}
if (!me) {
@@ -924,14 +950,7 @@ export default function ContextualizedMatchPage() {
// TODO: Improve
const transferCard = React.useCallback(
- async function (
- cardID,
- destinationZone,
- originZone,
- reveal,
- shuffle,
- index
- ) {
+ async function (cardID, destinationZone, originZone, reveal, shuffle, index) {
await sendRequest({
headers: { MatchID: matchState._id },
operation: 'transferCard',
@@ -1033,7 +1052,12 @@ export default function ContextualizedMatchPage() {
);
useSubscribe({
+ // cleanup: () => {
+ // abortControllerRef.current.abort();
+ // abortControllerRef.current = new AbortController();
+ // },
connectionInfo: { matchID },
+ dependencies: [matchID],
queryString: matchQuery,
setup: fetchMatchByID,
subscriptionType: 'subscribeMatch',
diff --git a/src/forms/CreateEventForm.jsx b/src/forms/CreateEventForm.jsx
index 524879f..4a9b4cd 100644
--- a/src/forms/CreateEventForm.jsx
+++ b/src/forms/CreateEventForm.jsx
@@ -100,10 +100,7 @@ export default function CreateEventForm({ buds, cubes, open, toggleOpen }) {
onClick={(event) => setCubeAnchorEl(event.currentTarget)}
style={{ padding: 0 }}
>
-
+
)}
-
- {cube.name}
-
+ {cube.name}
))}
-
+
Event Type
- }
- label="Draft"
- />
+ } label="Draft" />
- }
- label="Sealed"
- />
+ } label="Sealed" />
@@ -174,11 +157,7 @@ export default function CreateEventForm({ buds, cubes, open, toggleOpen }) {
-
+
Buds
@@ -204,18 +183,13 @@ export default function CreateEventForm({ buds, cubes, open, toggleOpen }) {
prevState.filter((plr) => plr !== bud._id)
);
} else {
- setOtherPlayers((prevState) => [
- ...prevState,
- bud._id
- ]);
+ setOtherPlayers((prevState) => [...prevState, bud._id]);
}
}}
value={bud._id}
/>
}
- label={
-
- }
+ label={ }
/>
))}
@@ -225,11 +199,7 @@ export default function CreateEventForm({ buds, cubes, open, toggleOpen }) {
-
+
Modules
@@ -247,10 +217,7 @@ export default function CreateEventForm({ buds, cubes, open, toggleOpen }) {
prevState.filter((mdl) => mdl !== module._id)
);
} else {
- setIncludedModules((prevState) => [
- ...prevState,
- module._id
- ]);
+ setIncludedModules((prevState) => [...prevState, module._id]);
}
}}
value={module._id}
@@ -315,9 +282,7 @@ export default function CreateEventForm({ buds, cubes, open, toggleOpen }) {
disabled={posting}
startIcon={(() => {
if (posting) {
- return (
-
- );
+ return ;
}
if (success) {
return ;
@@ -328,11 +293,7 @@ export default function CreateEventForm({ buds, cubes, open, toggleOpen }) {
>
Create
- }
- >
+ }>
Cancel
diff --git a/src/forms/CreateMatchForm.jsx b/src/forms/CreateMatchForm.jsx
index 80bed9a..9efad36 100644
--- a/src/forms/CreateMatchForm.jsx
+++ b/src/forms/CreateMatchForm.jsx
@@ -75,9 +75,7 @@ export default function CreateMatchForm({ open, toggleOpen }) {
])
}
>
-
+
- }
- label="Decks"
- value={false}
- />
- }
- label="Event"
- value={true}
- />
+ } label="Decks" value={false} />
+ } label="Event" value={true} />
@@ -119,10 +109,7 @@ export default function CreateMatchForm({ open, toggleOpen }) {
button
onClick={(event) => setAnchorEl(event.currentTarget)}
>
-
+
-
- {dck.name}
-
-
- {dck.format}
-
+ {dck.name}
+ {dck.format}
}
value={dck._id}
@@ -196,14 +179,8 @@ export default function CreateMatchForm({ open, toggleOpen }) {
key={plr.account._id}
label={
-
-
- {plr.account.name}
-
+
+ {plr.account.name}
}
value={plr.account._id}
@@ -217,14 +194,8 @@ export default function CreateMatchForm({ open, toggleOpen }) {
key={bud._id}
label={
-
-
- {bud.name}
-
+
+ {bud.name}
}
value={bud._id}
@@ -241,9 +212,7 @@ export default function CreateMatchForm({ open, toggleOpen }) {
component="fieldset"
required={true}
>
-
- Your Opponent's Deck
-
+ Your Opponent's Deck
setOpponentDeckID(event.target.value)}
value={opponentDeckID}
@@ -257,12 +226,8 @@ export default function CreateMatchForm({ open, toggleOpen }) {
key={dck._id}
label={
-
- {dck.name}
-
-
- {dck.format}
-
+ {dck.name}
+ {dck.format}
}
value={dck._id}
@@ -274,11 +239,7 @@ export default function CreateMatchForm({ open, toggleOpen }) {
Whoop that Ass!
- }
- >
+ }>
Cancel
diff --git a/src/forms/DeleteCubeForm.jsx b/src/forms/DeleteCubeForm.jsx
index 4b93b90..ecbb7c7 100644
--- a/src/forms/DeleteCubeForm.jsx
+++ b/src/forms/DeleteCubeForm.jsx
@@ -12,48 +12,36 @@ import MUITypography from '@mui/material/Typography';
import { useNavigate, useParams } from 'react-router-dom';
import deleteCube from '../graphql/mutations/cube/delete-cube';
-import { AccountContext } from '../contexts/account-context';
import { AuthenticationContext } from '../contexts/Authentication';
import { ErrorContext } from '../contexts/Error';
export default function DeleteCubeForm({ cubeToDelete, setCubeToDelete }) {
- const { setAccountState } = useContext(AccountContext);
const { userID } = useContext(AuthenticationContext);
const { setErrorMessages } = useContext(ErrorContext);
const navigate = useNavigate();
- const { accountID, cubeID } = useParams();
+ const { cubeID } = useParams();
const [deleting, setDeleting] = useState(false);
const [success, setSuccess] = useState(false);
return (
- setCubeToDelete({ _id: null, name: null })}
- >
+ setCubeToDelete({ _id: null, name: null })}>