diff --git a/src/app/DeckPage/[DeckId]/page.tsx b/src/app/DeckPage/[DeckId]/page.tsx
index 523720a..b66e905 100644
--- a/src/app/DeckPage/[DeckId]/page.tsx
+++ b/src/app/DeckPage/[DeckId]/page.tsx
@@ -101,8 +101,19 @@ const DeckDetails: React.FC = () => {
setDeleteDialogOpen(true);
};
- // Handle delete confirmation
+ // handle confirm delete
const handleConfirmDelete = () => {
+ if (deckId) {
+ try {
+ // Remove the deck from localStorage
+ const storageKey = `swu_deck_${deckId}`;
+ localStorage.removeItem(storageKey);
+ console.log(`Deck ${deckId} removed from localStorage`);
+ } catch (error) {
+ console.error('Error deleting deck from localStorage:', error);
+ }
+ }
+
setDeleteDialogOpen(false);
router.push('/DeckPage');
};
@@ -177,14 +188,14 @@ const DeckDetails: React.FC = () => {
justifyContent: 'space-between',
},
sortBy:{
- width: '13rem',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
+ justifyContent: 'space-between',
},
sortText:{
- width: '5rem',
- mb:'0px'
+ mb:'0px',
+ minWidth:'65px'
},
deckContainer:{
width: '100%',
@@ -223,9 +234,8 @@ const DeckDetails: React.FC = () => {
},
editButtons:{
display:'flex',
- width: '15rem',
alignItems: 'center',
- justifyContent: 'space-between',
+ justifyContent: 'end',
},
titleTextContainer:{
ml:'10px',
@@ -240,6 +250,10 @@ const DeckDetails: React.FC = () => {
aspectRatio: '1.4 / 1',
width: '21rem',
},
+ viewDeck:{
+ width:'380px',
+ ml:'40px'
+ }
}
return (
@@ -294,9 +308,8 @@ const DeckDetails: React.FC = () => {
- {/* Stats go here */}
+ {/* Stats go here
- {/* A row for the big win % circle and quick stats */}
@@ -310,13 +323,14 @@ const DeckDetails: React.FC = () => {
- {/* A table for Opposing Leaders, Wins, Losses, etc. */}
+ {/* A table for Opposing Leaders, Wins, Losses, etc.
+ */}
{/* Right side: Sort dropdown & deck cards */}
@@ -338,9 +352,11 @@ const DeckDetails: React.FC = () => {
))}
+
+
+
-
diff --git a/src/app/DeckPage/page.tsx b/src/app/DeckPage/page.tsx
index 1c4da2e..6307a0a 100644
--- a/src/app/DeckPage/page.tsx
+++ b/src/app/DeckPage/page.tsx
@@ -1,6 +1,6 @@
'use client';
import { Box, MenuItem, Typography } from '@mui/material';
-import React, { ChangeEvent, useState } from 'react';
+import React, { ChangeEvent, useState, useEffect } from 'react';
import { useRouter } from 'next/navigation'
import Grid from '@mui/material/Grid2';
import StyledTextField from '@/app/_components/_sharedcomponents/_styledcomponents/StyledTextField';
@@ -8,6 +8,25 @@ import PreferenceButton from '@/app/_components/_sharedcomponents/Preferences/_s
import { IDeckData } from '@/app/_utils/fetchDeckData';
import { s3CardImageURL } from '@/app/_utils/s3Utils';
import AddDeckDialog from '@/app/_components/_sharedcomponents/DeckPage/AddDeckDialog';
+import ConfirmationDialog from '@/app/_components/_sharedcomponents/DeckPage/ConfirmationDialog';
+
+// Define interfaces for deck data
+interface StoredDeck {
+ leader: { id: string };
+ base: { id: string };
+ name: string;
+ favourite: boolean;
+ deckLink: string;
+ deckID: string;
+}
+
+interface DisplayDeck {
+ deckID: string;
+ leader: { id: string, types:string[] };
+ base: { id: string, types:string[] };
+ metadata: { name: string };
+ favourite: boolean;
+}
const sortByOptions: string[] = [
'Recently Played',
@@ -16,18 +35,165 @@ const sortByOptions: string[] = [
const DeckPage: React.FC = () => {
const [sortBy, setSortBy] = useState('');
- const [decks, setDecks] = useState([]);
+ const [decks, setDecks] = useState([]);
const [addDeckDialogOpen, setAddDeckDialogOpen] = useState(false);
+ const [selectedDecks, setSelectedDecks] = useState([]); // Track selected decks
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const router = useRouter();
+ // Load decks from localStorage on component mount
+ useEffect(() => {
+ loadDecksFromStorage();
+ }, []);
+
+ // Function to load decks from localStorage
+ const loadDecksFromStorage = () => {
+ try {
+ const displayDecks: DisplayDeck[] = [];
+
+ // Get all localStorage keys
+ for (let i = 0; i < localStorage.length; i++) {
+ const key = localStorage.key(i);
+ // Check if this is a deck key
+ if (key && key.startsWith('swu_deck_')) {
+ const deckID = key.replace('swu_deck_', '');
+ const deckDataJSON = localStorage.getItem(key);
+
+ if (deckDataJSON) {
+ const deckData = JSON.parse(deckDataJSON) as StoredDeck;
+
+ // Convert to display format
+ displayDecks.push({
+ deckID,
+ leader: { id: deckData.leader.id, types:['leader'] },
+ base: { id: deckData.base.id, types:['base'] },
+ metadata: { name: deckData.name },
+ favourite: deckData.favourite
+ });
+ }
+ }
+ }
+
+ // Sort decks to show favorites first
+ const sortedDecks = [...displayDecks].sort((a, b) => {
+ // If one is favorite and other is not, favorite comes first
+ if (a.favourite && !b.favourite) return -1;
+ if (!a.favourite && b.favourite) return 1;
+
+ // Otherwise maintain original order or sort by name if needed
+ return 0;
+ });
+
+ setDecks(sortedDecks);
+ } catch (error) {
+ console.error('Error loading decks from localStorage:', error);
+ }
+ };
+
// Handle successful deck addition
const handleAddDeckSuccess = (deckData: IDeckData) => {
- setDecks(prevDecks => [...prevDecks, deckData]);
+ const newDeck: DisplayDeck = {
+ deckID: deckData.deckID,
+ leader: { id: deckData.leader.id, types:['leader'] },
+ base: { id: deckData.base.id, types:['base'] },
+ metadata: { name: deckData.metadata?.name || 'Untitled Deck' },
+ favourite: false
+ };
+
+ // Add the new deck and re-sort to maintain favorites first
+ setDecks(prevDecks => {
+ const updatedDecks = [...prevDecks, newDeck];
+ return updatedDecks.sort((a, b) => {
+ if (a.favourite && !b.favourite) return -1;
+ if (!a.favourite && b.favourite) return 1;
+ return 0;
+ });
+ });
};
// Handler to navigate to the deck subpage using the deck's id
const handleViewDeck = (deckId: string) => {
- router.push(`/DeckPage/${deckId}`);
+ // Only navigate if no decks are selected
+ if (selectedDecks.length === 0) {
+ router.push(`/DeckPage/${deckId}`);
+ }
+ };
+
+ // Handle deck selection
+ const toggleDeckSelection = (deckId: string) => {
+ setSelectedDecks(prevSelected => {
+ // Check if the deck is already selected
+ if (prevSelected.includes(deckId)) {
+ // Remove from selection
+ return prevSelected.filter(id => id !== deckId);
+ } else {
+ // Add to selection
+ return [...prevSelected, deckId];
+ }
+ });
+ };
+
+ // Toggle favorite status for a deck
+ const toggleFavorite = (deckId: string, e:React.MouseEvent) => {
+ e.stopPropagation();
+ // Update in state and resort
+ const updatedDecks = decks.map(deck =>
+ deck.deckID === deckId
+ ? { ...deck, favourite: !deck.favourite }
+ : deck
+ );
+
+ // Re-sort to ensure favorites appear first
+ const sortedDecks = [...updatedDecks].sort((a, b) => {
+ if (a.favourite && !b.favourite) return -1;
+ if (!a.favourite && b.favourite) return 1;
+ return 0;
+ });
+
+ setDecks(sortedDecks);
+
+ // Update in localStorage
+ try {
+ const storageKey = `swu_deck_${deckId}`;
+ const deckDataJSON = localStorage.getItem(storageKey);
+
+ if (deckDataJSON) {
+ const deckData = JSON.parse(deckDataJSON) as StoredDeck;
+ deckData.favourite = !deckData.favourite;
+ localStorage.setItem(storageKey, JSON.stringify(deckData));
+ }
+ } catch (error) {
+ console.error('Error updating favorite status:', error);
+ }
+ };
+
+ // Open delete confirmation dialog
+ const openDeleteDialog = () => {
+ if (selectedDecks.length > 0) {
+ setDeleteDialogOpen(true);
+ }
+ };
+
+ // Delete selected decks
+ const handleDeleteSelectedDecks = () => {
+ // Delete each selected deck from localStorage
+ selectedDecks.forEach(deckId => {
+ try {
+ const storageKey = `swu_deck_${deckId}`;
+ localStorage.removeItem(storageKey);
+ } catch (error) {
+ console.error(`Error deleting deck ${deckId}:`, error);
+ }
+ });
+
+ // Update deck list in state
+ setDecks(prevDecks => prevDecks.filter(deck => !selectedDecks.includes(deck.deckID)));
+
+ // Reset selection
+ setSelectedDecks([]);
+
+ // Close dialog
+ setDeleteDialogOpen(false);
};
// ----------------------Styles-----------------------------//
@@ -39,31 +205,35 @@ const DeckPage: React.FC = () => {
justifyContent: 'space-between',
},
sortBy:{
- width:'100px'
+ minWidth:'100px'
},
sortByContainer:{
display:'flex',
flexDirection: 'row',
- width:'300px',
alignItems:'center',
},
- deckContainer:{
- background: '#20344280',
+ dropdown:{
+ maxWidth:'10rem',
+ },
+ deckContainer: (isSelected: boolean) => ({
+ background: isSelected ? '#2F7DB680' : '#20344280',
width: '31rem',
height: '13rem',
borderRadius: '5px',
padding:'5px',
display:'flex',
flexDirection: 'row',
+ border: '2px solid transparent',
'&:hover': {
backgroundColor: '#2F7DB680',
},
cursor: 'pointer',
- },
+ position: 'relative',
+ }),
gridContainer:{
mt: '30px',
overflowY: 'auto',
- maxHeight: '90%',
+ maxHeight: '84%',
},
CardSetContainerStyle:{
display: 'flex',
@@ -109,6 +279,58 @@ const DeckPage: React.FC = () => {
viewDeckButton:{
display:'flex',
marginBottom: '7%',
+ gap: '0.5rem',
+ },
+ favoriteIcon: {
+ position: 'absolute',
+ top: '10px',
+ right: '10px',
+ fontSize: '24px',
+ color: 'gold',
+ cursor: 'pointer',
+ zIndex: 10,
+ },
+ deleteIcon: {
+ position: 'absolute',
+ top: '10px',
+ right: '40px',
+ fontSize: '20px',
+ color: '#FF5555',
+ cursor: 'pointer',
+ zIndex: 10,
+ },
+ noDecksMessage: {
+ color: 'white',
+ width: '100%',
+ textAlign: 'center',
+ marginTop: '2rem',
+ },
+ addNewDeck:{
+ width:'350px',
+ ml:'40px'
+ },
+ selectionInfo: {
+ color: 'white',
+ margin: '0 20px',
+ },
+ // New style for the selection checkmark
+ selectionCheckmark: {
+ position: 'absolute',
+ bottom: '34px',
+ right: '10px',
+ width: '24px',
+ height: '24px',
+ borderRadius: '50%',
+ backgroundColor: '#66E5FF',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ zIndex: 10,
+ },
+ checkmarkSymbol: {
+ color: '#1E2D32',
+ fontWeight: 'bold',
+ fontSize: '16px',
}
};
@@ -119,6 +341,7 @@ const DeckPage: React.FC = () => {
Sort by
) =>
@@ -131,46 +354,95 @@ const DeckPage: React.FC = () => {
))}
+
+ setAddDeckDialogOpen(true)}/>
+
- setAddDeckDialogOpen(true)}/>
+
- {decks.map((deck) => (
-
-
-
-
-
+ {decks.length > 0 ? (
+ decks.map((deck) => {
+ const isSelected = selectedDecks.includes(deck.deckID);
+
+ return (
+ toggleDeckSelection(deck.deckID)}
+ >
+ {/* Favorite Icon */}
+ toggleFavorite(deck.deckID, e)}
+ >
+ {deck.favourite ? '★' : '☆'}
-
-
+ {isSelected && (
+
+ ✓
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ {deck.metadata.name}
+
+
+ {
+ handleViewDeck(deck.deckID);
+ }}
+ />
+
-
-
-
- {deck.metadata.name}
-
-
- handleViewDeck(deck.deckID)}
- />
-
-
-
- ))}
- setAddDeckDialogOpen(false)}
- onSuccess={handleAddDeckSuccess}
- />
+ );
+ })
+ ) : (
+
+ No decks found. Add a deck to get started!
+
+ )}
+
+ {/* Dialogs */}
+ setAddDeckDialogOpen(false)}
+ onSuccess={handleAddDeckSuccess}
+ />
+
+ 1 ? 's' : ''}? This action cannot be undone.`}
+ onCancel={() => setDeleteDialogOpen(false)}
+ onConfirm={handleDeleteSelectedDecks}
+ confirmButtonText="Delete"
+ cancelButtonText="Cancel"
+ />
>
);
};
-export default DeckPage;
+export default DeckPage;
\ No newline at end of file
diff --git a/src/app/_components/_sharedcomponents/ControlHub/ControlHub.tsx b/src/app/_components/_sharedcomponents/ControlHub/ControlHub.tsx
index fb4450f..e28740f 100644
--- a/src/app/_components/_sharedcomponents/ControlHub/ControlHub.tsx
+++ b/src/app/_components/_sharedcomponents/ControlHub/ControlHub.tsx
@@ -122,14 +122,14 @@ const ControlHub: React.FC = ({
Unimplemented
+
+ Decks
+
{user ? (
<>
Profile
-
- Decks
-
Preferences
diff --git a/src/app/_components/_sharedcomponents/CreateGameForm/CreateGameForm.tsx b/src/app/_components/_sharedcomponents/CreateGameForm/CreateGameForm.tsx
index b3287cb..ceb32a0 100644
--- a/src/app/_components/_sharedcomponents/CreateGameForm/CreateGameForm.tsx
+++ b/src/app/_components/_sharedcomponents/CreateGameForm/CreateGameForm.tsx
@@ -1,4 +1,4 @@
-import React, { useState, FormEvent, ChangeEvent, useRef } from 'react';
+import React, { useState, FormEvent, ChangeEvent, useRef, useEffect } from 'react';
import {
Box,
Button,
@@ -28,6 +28,16 @@ const deckOptions: string[] = [
'ThisIsTheWay',
];
+// Interface for saved decks
+interface StoredDeck {
+ leader: { id: string };
+ base: { id: string };
+ name: string;
+ favourite: boolean;
+ deckID: string;
+ deckLink: string;
+}
+
const CreateGameForm = () => {
const pathname = usePathname();
const router = useRouter();
@@ -38,6 +48,7 @@ const CreateGameForm = () => {
const [favouriteDeck, setFavouriteDeck] = useState('');
const [deckLink, setDeckLink] = useState('');
const [saveDeck, setSaveDeck] = useState(false);
+ const [savedDecks, setSavedDecks] = useState([]);
const [errorModalOpen, setErrorModalOpen] = useState(false);
const [errorTitle, setErrorTitle] = useState('Deck Validation Error');
@@ -56,6 +67,46 @@ const CreateGameForm = () => {
const [gameName, setGameName] = useState('');
const [privacy, setPrivacy] = useState('Public');
+ useEffect(() => {
+ loadSavedDecks();
+ }, []);
+
+ // Load saved decks from localStorage
+ const loadSavedDecks = () => {
+ try {
+ const storedDecks: StoredDeck[] = [];
+
+ // Get all localStorage keys
+ for (let i = 0; i < localStorage.length; i++) {
+ const key = localStorage.key(i);
+ // Check if this is a deck key
+ if (key && key.startsWith('swu_deck_')) {
+ const deckID = key.replace('swu_deck_', '');
+ const deckDataJSON = localStorage.getItem(key);
+
+ if (deckDataJSON) {
+ const deckData = JSON.parse(deckDataJSON) as StoredDeck;
+
+ // Add to our list with the ID for reference
+ storedDecks.push({
+ ...deckData,
+ deckID: deckID
+ });
+ }
+ }
+ }
+
+ // Sort to show favorites first
+ const sortedDecks = [...storedDecks].sort((a, b) => {
+ if (a.favourite && !b.favourite) return -1;
+ if (!a.favourite && b.favourite) return 1;
+ return 0;
+ });
+ setSavedDecks(sortedDecks);
+ } catch (error) {
+ console.error('Error loading decks from localStorage:', error);
+ }
+ }
const handleChangeFormat = (format: SwuGameFormat) => {
localStorage.setItem('format', format);
setFormat(format);
@@ -64,11 +115,25 @@ const CreateGameForm = () => {
// Handle Create Game Submission
const handleCreateGameSubmit = async (event: FormEvent) => {
event.preventDefault();
+ let userDeck = '';
+ // check whether the favourite deck was selected or a decklink was used. The decklink always has precedence
+ if(favouriteDeck) {
+ const selectedDeck = savedDecks.find(deck => deck.deckID === favouriteDeck);
+ if (selectedDeck?.deckLink && !deckLink) {
+ userDeck = selectedDeck?.deckLink
+ }else{
+ console.log(deckLink);
+ userDeck = deckLink;
+ }
+ }else{
+ userDeck = deckLink;
+ }
+
let deckData = null
try {
- const parsedInput = parseInputAsDeckData(deckLink);
+ const parsedInput = parseInputAsDeckData(userDeck);
if(parsedInput.type === 'url') {
- deckData = deckLink ? await fetchDeckData(deckLink, false) : null;
+ deckData = userDeck ? await fetchDeckData(userDeck, false) : null;
}else if(parsedInput.type === 'json') {
deckData = parsedInput.data
}else{
@@ -87,7 +152,6 @@ const CreateGameForm = () => {
[DeckValidationFailureReason.DeckSetToPrivate]: true,
});
setErrorModalOpen(true);
- console.log('here')
}else{
setErrorTitle('Deck Validation Error');
setDeckErrorSummary('Couldn\'t import. Deck is invalid.');
@@ -96,6 +160,7 @@ const CreateGameForm = () => {
}
return;
}
+ console.log(deckData);
try {
const payload = {
user: { id: user?.id || localStorage.getItem('anonymousUserId'),
@@ -129,6 +194,23 @@ const CreateGameForm = () => {
}
return;
}
+ if (saveDeck && deckData && deckLink){
+ // save new deck to local storage and only if its a new deck
+ try {
+ const deckToSave = {
+ leader: deckData.leader,
+ base: deckData.base,
+ name: deckData.metadata.name,
+ favourite: false,
+ deckID: deckData.deckID,
+ deckLink: deckLink// Store the original link if we have one
+ };
+ localStorage.setItem(`swu_deck_${deckData.deckID}`, JSON.stringify(deckToSave));
+ } catch (error) {
+ console.error('Error saving deck to favorites:', error);
+ }
+ }
+
setDeckErrorSummary(null);
setDeckErrorDetails(undefined);
setErrorTitle('Deck Validation Error');
@@ -185,7 +267,7 @@ const CreateGameForm = () => {