Skip to content

Commit

Permalink
added import button
Browse files Browse the repository at this point in the history
add app context for handling loading
  • Loading branch information
sergio222-dev committed Aug 15, 2024
1 parent 2ddb00b commit e4c3201
Show file tree
Hide file tree
Showing 14 changed files with 340 additions and 72 deletions.
19 changes: 13 additions & 6 deletions src/components/icons/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,48 @@ import type { JSXOutput, SVGProps } from "@builder.io/qwik";
import { component$ } from "@builder.io/qwik";

const ICONS: Record<IconName, (props: SVGProps<SVGElement>) => JSXOutput> = {
'art': (props: SVGProps<SVGElement>) => (
'art': (props: SVGProps<SVGElement>) => (
<svg {...props} xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<path
d="M0 64C0 28.7 28.7 0 64 0L352 0c35.3 0 64 28.7 64 64l0 64c0 35.3-28.7 64-64 64L64 192c-35.3 0-64-28.7-64-64L0 64zM160 352c0-17.7 14.3-32 32-32l0-16c0-44.2 35.8-80 80-80l144 0c17.7 0 32-14.3 32-32l0-32 0-90.5c37.3 13.2 64 48.7 64 90.5l0 32c0 53-43 96-96 96l-144 0c-8.8 0-16 7.2-16 16l0 16c17.7 0 32 14.3 32 32l0 128c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32-14.3-32-32l0-128z"/>
</svg>),
'close': (props: SVGProps<SVGElement>) => (
'close': (props: SVGProps<SVGElement>) => (
<svg {...props} xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512">
<path
d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/>
</svg>
),
'menu': (props: SVGProps<SVGElement>) => (
'menu': (props: SVGProps<SVGElement>) => (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path
d="M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"/>
</svg>
),
'heart': (props: SVGProps<SVGElement>) => (
'heart': (props: SVGProps<SVGElement>) => (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path
d="M225.8 468.2l-2.5-2.3L48.1 303.2C17.4 274.7 0 234.7 0 192.8l0-3.3c0-70.4 50-130.8 119.2-144C158.6 37.9 198.9 47 231 69.6c9 6.4 17.4 13.8 25 22.3c4.2-4.8 8.7-9.2 13.5-13.3c3.7-3.2 7.5-6.2 11.5-9c0 0 0 0 0 0C313.1 47 353.4 37.9 392.8 45.4C462 58.6 512 119.1 512 189.5l0 3.3c0 41.9-17.4 81.9-48.1 110.4L288.7 465.9l-2.5 2.3c-8.2 7.6-19 11.9-30.2 11.9s-22-4.2-30.2-11.9zM239.1 145c-.4-.3-.7-.7-1-1.1l-17.8-20-.1-.1s0 0 0 0c-23.1-25.9-58-37.7-92-31.2C81.6 101.5 48 142.1 48 189.5l0 3.3c0 28.5 11.9 55.8 32.8 75.2L256 430.7 431.2 268c20.9-19.4 32.8-46.7 32.8-75.2l0-3.3c0-47.3-33.6-88-80.1-96.9c-34-6.5-69 5.4-92 31.2c0 0 0 0-.1 .1s0 0-.1 .1l-17.8 20c-.3 .4-.7 .7-1 1.1c-4.5 4.5-10.6 7-16.9 7s-12.4-2.5-16.9-7z"/>
</svg>
),
'copy': (props: SVGProps<SVGElement>) => (
'copy': (props: SVGProps<SVGElement>) => (
<svg {...props} xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512">
<path
d="M208 0L332.1 0c12.7 0 24.9 5.1 33.9 14.1l67.9 67.9c9 9 14.1 21.2 14.1 33.9L448 336c0 26.5-21.5 48-48 48l-192 0c-26.5 0-48-21.5-48-48l0-288c0-26.5 21.5-48 48-48zM48 128l80 0 0 64-64 0 0 256 192 0 0-32 64 0 0 48c0 26.5-21.5 48-48 48L48 512c-26.5 0-48-21.5-48-48L0 176c0-26.5 21.5-48 48-48z"/>
</svg>
),
'import': (props: SVGProps<SVGElement>) => (
<svg {...props} xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<path
d="M128 64c0-35.3 28.7-64 64-64L352 0l0 128c0 17.7 14.3 32 32 32l128 0 0 288c0 35.3-28.7 64-64 64l-256 0c-35.3 0-64-28.7-64-64l0-112 174.1 0-39 39c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l80-80c9.4-9.4 9.4-24.6 0-33.9l-80-80c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l39 39L128 288l0-224zm0 224l0 48L24 336c-13.3 0-24-10.7-24-24s10.7-24 24-24l104 0zM512 128l-128 0L384 0 512 128z"/>
</svg>
),
}

type IconName = 'art' | 'close' | 'menu' | 'heart' | 'copy';
type IconName = 'art' | 'close' | 'menu' | 'heart' | 'copy' | 'import';

interface IconProps extends SVGProps<SVGElement> {
name: IconName;
Expand Down
25 changes: 17 additions & 8 deletions src/features/appbar/Appbar.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { component$, useContext, useSignal } from '@builder.io/qwik';
import { Link, useLocation } from '@builder.io/qwik-city';
import { component$, useContext, useSignal, useTask$ } from '@builder.io/qwik';
import { Link, useLocation } from '@builder.io/qwik-city';
import { Icon } from "~/components/icons/Icon";
import { UserContext } from "~/routes/layout";
import { AppContext } from "~/stores/appContext";

export const Appbar = component$(() => {
const location = useLocation();
const isMenuOpen = useSignal(false);

const user = useContext(UserContext);
const app = useContext(AppContext);

useTask$(async ({ track }) => {
track(() => location.isNavigating);

app.isLoading = location.isNavigating;
});

return (
<>
<header class="w-full p-4 bg-primary text-white">
<div class="flex justify-between h-16 items-center">
<div class="flex items-center">
<Link onClick$={() => isMenuOpen.value = false} rel="noopener noreferrer" href="/" aria-label="Back to homepage" class="flex items-center p-2">
<Link onClick$={() => isMenuOpen.value = false} rel="noopener noreferrer" href="/"
aria-label="Back to homepage" class="flex items-center p-2">
<h2 class="font-bold text-2xl">LDB</h2>
</Link>
</div>
Expand All @@ -24,22 +33,22 @@ export const Appbar = component$(() => {
<div class="hidden sm:flex">
<ul class="items-stretch space-x-3 flex">
<li class="flex">
<Link rel="noopener noreferrer" href="/cards"
<Link rel="noopener noreferrer" href="/cards"
class="flex items-center px-4 ">Cards</Link>
</li>
<li class="flex">
<Link rel="noopener noreferrer" href="/decks"
<Link rel="noopener noreferrer" href="/decks"
class="flex items-center px-4 ">Decks</Link>
</li>
{user.value && (<li class="flex">
<Link rel="noopener noreferrer" href="/mydecks"
<Link rel="noopener noreferrer" href="/mydecks"
class="flex items-center px-4 ">My decks</Link>
</li>
)}
</ul>
</div>
</div>
<div hidden={!isMenuOpen.value} class="container mx-auto sm:hidden" >
<div hidden={!isMenuOpen.value} class="container mx-auto sm:hidden">
<ul class="items-stretch space-y-3">
<li class="flex">
<Link onClick$={() => isMenuOpen.value = false} rel="noopener noreferrer" href="/cards"
Expand All @@ -57,7 +66,7 @@ export const Appbar = component$(() => {
</ul>
</div>
</header>
<div class={`fixed top-0 left-0 h-1 w-full ${location.isNavigating ? ` bg-secondary animate-pulse` : ''}`}></div>
<div class={`fixed top-0 left-0 h-1 w-full ${app.isLoading ? ` bg-secondary animate-pulse` : ''}`}></div>
</>
);
});
50 changes: 25 additions & 25 deletions src/features/createDeck/components/CreateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ import { Button } from "~/components/button";
import { Icon } from "~/components/icons/Icon";
import { Switch } from '~/components/switch/Switch';
import { Text } from '~/components/text';
import { DeckImporter } from "~/features/importer/DeckImporter";
import { AppContext } from "~/stores/appContext";
import { DeckCreationContext } from '~/stores/deckCreationContext';
import { parseToText } from "~/utils/parser";

export const CreateForm = component$(() => {
const buttonDisabled = useSignal(false);
const isImportOpen = useSignal(false);
const location = useLocation();
const navigation = useNavigate();

const deckStore = useContext(DeckCreationContext);
const app = useContext(AppContext);

const handleCloseImportDialog = $(() => {
isImportOpen.value = false;
})

const handleChange = $<(v: boolean) => void>((v) => deckStore.deckData.isPrivate = v);

Expand All @@ -28,9 +35,6 @@ export const CreateForm = component$(() => {
<div class="p-2">
<div class="flex justify-between items-center flex-wrap py-2">
<Text value={deckStore.deckData.name} placeholder="Deck Name" onInput$={handleNameChange}/>
{/*<MenuTw>*/}
{/* <MenuTwItem label="sergio" value="sergio"/>*/}
{/*</MenuTw>*/}
<div>
<Switch id="public" name="sergio" value={deckStore.deckData.isPrivate} onChange={handleChange}/>
is public?
Expand All @@ -42,37 +46,33 @@ export const CreateForm = component$(() => {
</div>
<div class="flex items-center gap-2">
<Button
class="bg-primary ring-2 ring-pink-800 p-4 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={buttonDisabled.value}
class="bg-primary p-4 disabled:opacity-50 disabled:cursor-not-allowed active:ring-2 ring-red-600"
disabled={app.isLoading}
onClick$={$(async () => {
buttonDisabled.value = true;
const result = await deckStore.createDeck();
buttonDisabled.value = false;
app.isLoading = true;
let result = 0;

try {
result = await deckStore.createDeck();
} finally {
app.isLoading = false;
}

if (location.params['id'] === '' && result > 0) {
void navigation(`/decks/create/${result}`);
}
})}
>Create Deck
>Create/Update Deck
</Button>
<Button class="active:ring-2 ring-red-600" onClick$={() => navigator.clipboard.writeText(parseToText(deckStore.deckData))}>
<Button class="active:ring-2 ring-red-600"
onClick$={() => navigator.clipboard.writeText(parseToText(deckStore.deckData))}>
<Icon name="copy" width={24} height={24} class="fill-primary"/>
</Button>
<Button class="active:ring-2 ring-red-600" onClick$={() => isImportOpen.value = true}>
<Icon name="import" width={24} height={24} class="fill-primary"/>
</Button>
</div>
<button
class="hidden hover:bg-pink-800 bg-white opacity-50 hover:opacity-100 hover:text-white ring-2 ring-pink-800 fixed bottom-[1%] right-[1%] p-4 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={buttonDisabled.value}
onClick$={$(async () => {
buttonDisabled.value = true;
const result = await deckStore.createDeck();
buttonDisabled.value = false;

if (location.params['id'] === '' && result > 0) {
void navigation(`/decks/create/${result}`);
}
})}
>CREATE/UPDATE
</button>
<DeckImporter isOpen={isImportOpen.value} onClose={handleCloseImportDialog}/>
</div>
)
});
11 changes: 0 additions & 11 deletions src/features/createDeck/server/fetchCardData.ts

This file was deleted.

60 changes: 60 additions & 0 deletions src/features/importer/DeckImporter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { $, component$, useContext, useSignal, useTask$ } from "@builder.io/qwik";
import { Button } from "~/components/button";
import { AppContext } from "~/stores/appContext";
import { DeckCreationContext } from "~/stores/deckCreationContext";

interface DeckImporterProps {
isOpen: boolean;
onClose: () => void;
}

export const DeckImporter = component$<DeckImporterProps>(({ isOpen, onClose }) => {
const dialogRef = useSignal<HTMLDialogElement>();
const textAreaRef = useSignal<HTMLTextAreaElement>();

// context
const c = useContext(DeckCreationContext);
const app = useContext(AppContext);

// HANDLERS
const handleImport = $(async () => {
await c.cleanDeck();

try {
const text = textAreaRef.value?.value;
if (!text) {
onClose();
return;
}

app.isLoading = true;
await c.importDeck(text);
} catch (e) {
console.error(e);
} finally {
app.isLoading = false;
onClose();
}
})

// EFFECTS
useTask$(({ track }) => {
track(() => isOpen);
if (!isOpen) {
dialogRef.value?.close();
} else {
dialogRef.value?.showModal();
}
})

return (
<dialog ref={dialogRef} class="p-4 container max-w-xl">
<textarea disabled={app.isLoading} ref={textAreaRef} class="w-full h-full p-4" rows={10} placeholder="Paste your deck here..."/>
<div class="flex items-center justify-end gap-2">
<Button disabled={app.isLoading} onClick$={() => onClose()}>Cancel</Button>
<Button disabled={app.isLoading} onClick$={() => handleImport()}>Import</Button>
</div>
</dialog>
);

})
14 changes: 14 additions & 0 deletions src/features/importer/server/fetchDeckImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { RequestEventBase, server$ } from "@builder.io/qwik-city";
import { ImportDeckRequest } from "~/models/application/ImportCardItem";
import { DeckState } from "~/models/Deck";
import { DeckRepository } from "~/providers/repositories/DeckRepository";

type FetchDeckImport = (this: RequestEventBase, deckImportRequest: ImportDeckRequest) => Promise<DeckState>

export const fetchDeckImport = server$<FetchDeckImport>(async function (deckImportRequest) {
const deckRepo = new DeckRepository(this);

const deck = await deckRepo.getDeckByImport(deckImportRequest);

return deck;
})
10 changes: 10 additions & 0 deletions src/models/application/ImportCardItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface ImportCardItem {
name: string;
quantity: number;
}

export interface ImportDeckRequest {
realm: ImportCardItem[];
treasure: ImportCardItem[];
side: ImportCardItem[];
}
Loading

0 comments on commit e4c3201

Please sign in to comment.