Skip to content

Commit

Permalink
- Added the ability to import json as a decklink. Added a checkJson.t…
Browse files Browse the repository at this point in the history
…s for validating the deck data.
  • Loading branch information
CheBato committed Mar 2, 2025
1 parent c1cc2b9 commit 740f964
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 7 deletions.
18 changes: 15 additions & 3 deletions src/app/_components/Lobby/_subcomponents/SetUpCard/SetUpCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
DeckValidationFailureReason,
} from '@/app/_validators/DeckValidation/DeckValidationTypes';
import { ErrorModal } from '@/app/_components/_sharedcomponents/Error/ErrorModal';
import { parseInputAsDeckData } from '@/app/_utils/checkJson';

const SetUpCard: React.FC<ISetUpProps> = ({
readyStatus,
Expand All @@ -31,7 +32,7 @@ const SetUpCard: React.FC<ISetUpProps> = ({

// For deck error display
const [deckErrorSummary, setDeckErrorSummary] = useState<string | null>(null);
const [deckErrorDetails, setDeckErrorDetails] = useState<IDeckValidationFailures | undefined>(undefined);
const [deckErrorDetails, setDeckErrorDetails] = useState<IDeckValidationFailures | string | undefined>(undefined);
const [displayError, setDisplayerror] = useState(false);
const [errorModalOpen, setErrorModalOpen] = useState(false);
const [blockError, setBlockError] = useState(false);
Expand All @@ -43,7 +44,18 @@ const SetUpCard: React.FC<ISetUpProps> = ({
const handleOnChangeDeck = async () => {
if (!deckLink || readyStatus) return;
try {
const deckData = deckLink ? await fetchDeckData(deckLink, false) : null;
let deckData;
const parsedInput = parseInputAsDeckData(deckLink);
if(parsedInput.type === 'url') {
deckData = deckLink ? await fetchDeckData(deckLink, false) : null;
}else if(parsedInput.type === 'json') {
deckData = parsedInput.data
}else{
setDeckErrorSummary('Couldn\'t import. Deck is invalid or unsupported deckbuilder');
setDeckErrorDetails('Incorrect deck format or unsupported deckbuilder.');
setErrorModalOpen(true);
return;
}
sendLobbyMessage(['changeDeck', deckData])
}catch (error){
setDisplayerror(true);
Expand Down Expand Up @@ -298,7 +310,7 @@ const SetUpCard: React.FC<ISetUpProps> = ({
</Typography>
</Box>
<StyledTextField
type="url"
type="text"
disabled={readyStatus}
value={deckLink}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
IDeckValidationFailures
} from '@/app/_validators/DeckValidation/DeckValidationTypes';
import { SwuGameFormat, FormatLabels } from '@/app/_constants/constants';
import { parseInputAsDeckData } from '@/app/_utils/checkJson';

const deckOptions: string[] = [
'Order66',
Expand Down Expand Up @@ -65,7 +66,17 @@ const CreateGameForm = () => {
event.preventDefault();
let deckData = null
try {
deckData = deckLink ? await fetchDeckData(deckLink, false) : null;
const parsedInput = parseInputAsDeckData(deckLink);
if(parsedInput.type === 'url') {
deckData = deckLink ? await fetchDeckData(deckLink, false) : null;
}else if(parsedInput.type === 'json') {
deckData = parsedInput.data
}else{
setErrorTitle('Deck Validation Error');
setDeckErrorDetails('Incorrect deck format or unsupported deckbuilder.');
setDeckErrorSummary('Couldn\'t import. Deck is invalid or unsupported deckbuilder');
setErrorModalOpen(true);
}
}catch (error){
setDeckErrorDetails(undefined);
if(error instanceof Error){
Expand Down Expand Up @@ -212,7 +223,7 @@ const CreateGameForm = () => {
</Typography>
</Box>
<StyledTextField
type="url"
type="text"
value={deckLink}
onChange={(e: ChangeEvent<HTMLInputElement>) =>{
setDeckLink(e.target.value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '@/app/_validators/DeckValidation/DeckValidationTypes';
import { ErrorModal } from '@/app/_components/_sharedcomponents/Error/ErrorModal';
import { SwuGameFormat, FormatLabels } from '@/app/_constants/constants';
import { parseInputAsDeckData } from '@/app/_utils/checkJson';

interface ICreateGameFormProps {
format?: string | null;
Expand Down Expand Up @@ -56,7 +57,18 @@ const QuickGameForm: React.FC<ICreateGameFormProps> = () => {
setQueueState(true);
let deckData = null
try {
deckData = deckLink ? await fetchDeckData(deckLink, false) : null;
const parsedInput = parseInputAsDeckData(deckLink);
if(parsedInput.type === 'url') {
deckData = deckLink ? await fetchDeckData(deckLink, false) : null;
}else if(parsedInput.type === 'json') {
deckData = parsedInput.data
}else{
setQueueState(false);
setDeckErrorSummary('Couldn\'t import. Deck is invalid or unsupported deckbuilder');
setDeckErrorDetails('Incorrect deck format or unsupported deckbuilder.');
setErrorModalOpen(true);
return;
}
}catch (error){
setQueueState(false);
setDeckErrorDetails(undefined);
Expand Down Expand Up @@ -208,7 +220,7 @@ const QuickGameForm: React.FC<ICreateGameFormProps> = () => {
</Typography>
</Box>
<StyledTextField
type="url"
type="text"
value={deckLink}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setDeckLink(e.target.value);
Expand Down
103 changes: 103 additions & 0 deletions src/app/_utils/checkJson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// src/app/_utils/deckValidationUtils.ts

/**
* Interface for deck JSON structure
*/
export interface DeckJSON {
metadata: {
name: string;
author?: string;
};
leader: {
id: string;
count: number;
};
base: {
id: string;
count: number;
};
deck: Array<{
id: string;
count: number;
}>;
sideboard?: Array<{
id: string;
count: number;
}>;
deckID?: string;
deckSource?: string;
}

/**
* Validates if a string is a properly formatted deck JSON
* @param jsonString - String to validate as JSON
* @returns Parsed DeckJSON object if valid, null if invalid
*/
export const validateDeckJSON = (jsonString: string): DeckJSON | null => {
try {
// Try to parse as JSON
const deckData = JSON.parse(jsonString);

// Validate the required fields
if (!deckData.metadata || !deckData.leader || !deckData.base || !deckData.deck) {
return null;
}

// Check if metadata contains required fields
if (!deckData.metadata.name) {
return null;
}

// Validate leader and base
if (!deckData.leader.id || !deckData.base.id) {
return null;
}

// Validate deck array
if (!Array.isArray(deckData.deck) || deckData.deck.length === 0) {
return null;
}

// Ensure all deck entries have id and count
for (const card of deckData.deck) {
if (!card.id || card.count === undefined) {
return null;
}
}

// Validate sideboard if present
if (deckData.sideboard && Array.isArray(deckData.sideboard)) {
for (const card of deckData.sideboard) {
if (!card.id || card.count === undefined) {
return null;
}
}
}

// If we got here, the JSON structure is valid
return deckData;
} catch (error) {
// JSON parsing failed
return null;
}
};

/**
* Attempts to parse input as either a deck URL or deck JSON
* @param input - String containing either a URL or JSON
* @returns Object with the input type and the parsed deck if valid
*/
export const parseInputAsDeckData = (input: string): {
type: 'url' | 'json' | 'invalid',
data: DeckJSON | null
} => {
const jsonData = validateDeckJSON(input);
if (jsonData) {
return { type: 'json', data: jsonData };
}

if(input.includes('swustats.net') || input.includes('swudb.com')){
return { type: 'url', data: null };
}
return { type: 'invalid', data:null }
};

0 comments on commit 740f964

Please sign in to comment.