From 65dee0a0f6e17fbd5658c9bb051e833ff9e2fe51 Mon Sep 17 00:00:00 2001 From: sergio222-dev Date: Tue, 30 Jul 2024 21:24:34 -0300 Subject: [PATCH] * added the ability to set the deck's splash art * changed the design of decks list --- src/components/button/Button.tsx | 9 +- src/components/button/ButtonIcon.tsx | 16 ++ src/components/button/index.ts | 1 + src/components/deckCard/DeckCard.tsx | 44 ++++ src/components/deckCard/index.ts | 1 + src/features/appbar/Appbar.tsx | 1 - src/features/createDeck/Create.tsx | 1 + .../createDeck/components/CardDeck.tsx | 24 +- .../createDeck/components/CardDeckControl.tsx | 39 ++-- src/features/deckList/DeckList.tsx | 30 +-- src/features/myDecks/MyDecks.tsx | 30 +-- src/global.css | 4 + src/hooks/useDeckQuantity.ts | 22 ++ src/models/Deck.ts | 2 + src/providers/repositories/DeckRepository.ts | 46 ++-- src/root.tsx | 1 + src/routes/styles.css | 214 ------------------ src/stores/deckCreationContext.ts | 4 +- src/stores/models/DeckCrationModels.ts | 2 +- 19 files changed, 170 insertions(+), 321 deletions(-) create mode 100644 src/components/button/ButtonIcon.tsx create mode 100644 src/components/deckCard/DeckCard.tsx create mode 100644 src/components/deckCard/index.ts create mode 100644 src/hooks/useDeckQuantity.ts diff --git a/src/components/button/Button.tsx b/src/components/button/Button.tsx index 2fccaa0..6a283aa 100644 --- a/src/components/button/Button.tsx +++ b/src/components/button/Button.tsx @@ -1,10 +1,11 @@ -import { component$, Slot } from "@builder.io/qwik"; +import type { ButtonHTMLAttributes } from "@builder.io/qwik"; +import { component$, Slot } from "@builder.io/qwik"; -interface ButtonProps { +interface ButtonProps extends ButtonHTMLAttributes { } -export const Button = component$(() => { +export const Button = component$(({ class: className, ...props}) => { return ( - + ) }); diff --git a/src/components/button/ButtonIcon.tsx b/src/components/button/ButtonIcon.tsx new file mode 100644 index 0000000..1b9b4f0 --- /dev/null +++ b/src/components/button/ButtonIcon.tsx @@ -0,0 +1,16 @@ +import type { ButtonHTMLAttributes } from "@builder.io/qwik"; +import { Slot } from "@builder.io/qwik"; +import { component$ } from "@builder.io/qwik"; +import { Button } from "~/components/button/Button"; + +interface ButtonIconProps extends ButtonHTMLAttributes { + +} + +export const ButtonIcon = component$(({ class: className, ...props }) => { + return ( + + ) +}); diff --git a/src/components/button/index.ts b/src/components/button/index.ts index 8b166a8..da1f65f 100644 --- a/src/components/button/index.ts +++ b/src/components/button/index.ts @@ -1 +1,2 @@ export * from './Button'; +export * from './ButtonIcon'; diff --git a/src/components/deckCard/DeckCard.tsx b/src/components/deckCard/DeckCard.tsx new file mode 100644 index 0000000..f00591f --- /dev/null +++ b/src/components/deckCard/DeckCard.tsx @@ -0,0 +1,44 @@ +import { component$ } from "@builder.io/qwik"; +import { Link } from "@builder.io/qwik-city"; +import { createClientBrowser } from "~/lib/supabase-qwik"; + +interface DeckCardProps { + id: number; + splashArt?: string; + name: string; + path?: string; +} + +export const DeckCard = component$(({ id, splashArt, name, path = '/decks/preview' }) => { + const supabase = createClientBrowser(); + + return ( + +
+
+ {name} +
+ +
+ + ); +}); diff --git a/src/components/deckCard/index.ts b/src/components/deckCard/index.ts new file mode 100644 index 0000000..958935f --- /dev/null +++ b/src/components/deckCard/index.ts @@ -0,0 +1 @@ +export * from './DeckCard'; diff --git a/src/features/appbar/Appbar.tsx b/src/features/appbar/Appbar.tsx index e8ca5a6..0f131d4 100644 --- a/src/features/appbar/Appbar.tsx +++ b/src/features/appbar/Appbar.tsx @@ -6,7 +6,6 @@ export const Appbar = component$(() => { const location = useLocation(); const user = useContext(UserContext); - console.log('rendered'); return ( <> diff --git a/src/features/createDeck/Create.tsx b/src/features/createDeck/Create.tsx index e23bac9..494aea3 100644 --- a/src/features/createDeck/Create.tsx +++ b/src/features/createDeck/Create.tsx @@ -26,6 +26,7 @@ export const Create = component$(() => { return (
+ diff --git a/src/features/createDeck/components/CardDeck.tsx b/src/features/createDeck/components/CardDeck.tsx index 22be4b0..25b7279 100644 --- a/src/features/createDeck/components/CardDeck.tsx +++ b/src/features/createDeck/components/CardDeck.tsx @@ -1,11 +1,12 @@ -import { component$, useComputed$, useContext, useSignal, useStylesScoped$ } from '@builder.io/qwik'; +import { component$, useContext, useSignal, useStylesScoped$ } from '@builder.io/qwik'; import { PopoverCard -} from "~/components/popoverCard/PopoverCard"; -import type { Card } from '~/models/Card'; +} from "~/components/popoverCard/PopoverCard"; +import { useDeckQuantity } from "~/hooks/useDeckQuantity"; +import type { Card } from '~/models/Card'; import { DeckCreationContext -} from '~/stores/deckCreationContext'; +} from '~/stores/deckCreationContext'; interface CardDeckProps { card: Card; @@ -19,19 +20,9 @@ export const CardDeck = component$(({ card }) => { const showPreview = useSignal(false); const previewRef = useSignal(); - const deckQuantity = useComputed$(() => { - const masterDeckQuantity = Object.entries(deckData.masterDeck) - .map(([, c]) => c) - .find(c => c.id === card.id)?.quantity ?? 0; - const treasureDeckQuantity = Object.entries(deckData.treasureDeck) - .map(([, c]) => c) - .find(c => c.id === card.id)?.quantity ?? 0; - return masterDeckQuantity + treasureDeckQuantity; - }); + const [deckQuantity] = useDeckQuantity(deckData, card.id); - const sideQuantity = useComputed$(() => { - return Object.entries(deckData.sideDeck).map(([, c]) => c).find(c => c.id === card.id)?.quantity ?? 0; - }); + const [sideQuantity] = useDeckQuantity(deckData, card.id, true); useStylesScoped$(` .card-text-shadow { @@ -39,7 +30,6 @@ export const CardDeck = component$(({ card }) => { } `); - return (
(({ card, orienta const deckData = d.deckData; - const deckQuantity = useComputed$(() => { - const masterDeckQuantity = Object.entries(deckData.masterDeck) - .map(([, c]) => c) - .find(c => c.id === card.id)?.quantity ?? 0; - const treasureDeckQuantity = Object.entries(deckData.treasureDeck) - .map(([, c]) => c) - .find(c => c.id === card.id)?.quantity ?? 0; - const sideDeckQuantity = Object.entries(deckData.sideDeck) - .map(([, c]) => c) - .find(c => c.id === card.id)?.quantity ?? 0; - - return isSide ? sideDeckQuantity : masterDeckQuantity + treasureDeckQuantity; - }); + const [deckQuantity] = useDeckQuantity(deckData, card.id, isSide); return (
- +
(({ card, orienta {deckQuantity.value}
-
- +
+ d.setSplashArt(card.image, card.id)}> + +
{row.name}) - }, - { - id: 'description', - label: 'Description' - }, - { - id: 'likes', - label: 'Likes' - } -]; - export const DeckList = component$(() => { const decks = useListPublicDeckLoader(); return ( -
- +
+ {decks.value.decks.map(d => { + return ( + + ); + })}
) }); diff --git a/src/features/myDecks/MyDecks.tsx b/src/features/myDecks/MyDecks.tsx index 21ac4dc..f6a3763 100644 --- a/src/features/myDecks/MyDecks.tsx +++ b/src/features/myDecks/MyDecks.tsx @@ -1,31 +1,17 @@ -import { $, component$ } from "@builder.io/qwik"; -import { Link } from "@builder.io/qwik-city"; -import type { ColumnDefinition } from "~/components/dataGrid/DataGrid"; -import { DataGrid } from "~/components/dataGrid/DataGrid"; +import { component$ } from "@builder.io/qwik"; +import { DeckCard } from "~/components/deckCard"; import { useListMyDeckLoader } from "~/providers/loaders/decks"; -const columns: ColumnDefinition[] = [ - { - id: 'name', - label: 'Name', - cell: $((row) => {row.name}) - }, - { - id: 'description', - label: 'Description' - }, - { - id: 'likes', - label: 'Likes' - } -]; - export const MyDecks = component$(() => { const decks = useListMyDeckLoader(); return ( -
- +
+ {decks.value.decks.map(d => { + return ( + + ); + })}
) }); diff --git a/src/global.css b/src/global.css index b4312a8..044f7a1 100644 --- a/src/global.css +++ b/src/global.css @@ -6,6 +6,10 @@ @tailwind components; @tailwind utilities; +* { + box-sizing: border-box; +} + .theme-dark { --qwik-primary: rgb(33 37 41); --qwik-secondary: #E7AE32; diff --git a/src/hooks/useDeckQuantity.ts b/src/hooks/useDeckQuantity.ts new file mode 100644 index 0000000..a36ab5a --- /dev/null +++ b/src/hooks/useDeckQuantity.ts @@ -0,0 +1,22 @@ +import { useComputed$ } from "@builder.io/qwik"; +import type { DeckState } from "~/models/Deck"; + +export const useDeckQuantity = (deck: DeckState, cardId: number, isSide = false) => { + + const deckQuantity = useComputed$(() => { + const masterDeckQuantity = Object.entries(deck.masterDeck) + .map(([, c]) => c) + .find(c => c.id === cardId)?.quantity ?? 0; + const treasureDeckQuantity = Object.entries(deck.treasureDeck) + .map(([, c]) => c) + .find(c => c.id === cardId)?.quantity ?? 0; + const sideDeckQuantity = Object.entries(deck.sideDeck) + .map(([, c]) => c) + .find(c => c.id === cardId)?.quantity ?? 0; + + return isSide ? sideDeckQuantity : masterDeckQuantity + treasureDeckQuantity; + }); + + return [deckQuantity]; + +} diff --git a/src/models/Deck.ts b/src/models/Deck.ts index 5fb9389..598035f 100644 --- a/src/models/Deck.ts +++ b/src/models/Deck.ts @@ -10,6 +10,7 @@ export interface DeckItem { name: string; description: string | null; likes: number; + splashArt?: string; } export interface DeckState { @@ -22,4 +23,5 @@ export interface DeckState { isPrivate: boolean; likes: number; splashArt?: string; + splashArtId?: number; } diff --git a/src/providers/repositories/DeckRepository.ts b/src/providers/repositories/DeckRepository.ts index e80bb8a..d0f0eb8 100644 --- a/src/providers/repositories/DeckRepository.ts +++ b/src/providers/repositories/DeckRepository.ts @@ -65,6 +65,7 @@ export class DeckRepository { owner: auth.user.id, is_public: data.isPrivate, // TODO: change the column name to is_private likes: 0, + ...data.splashArtId ? { deck_face: data.splashArtId } : { }, ...data.id ? { id: data.id } : { created_at: (new Date()).toISOString() } }) .select('id'); // get the id inserted @@ -93,9 +94,10 @@ export class DeckRepository { // get the deck by id and check if the user is the owner const { data, error } = await supabase .from('decks') - .select() + .select(`*, cards ( image )`) .eq('id', deckId) - .eq('owner', ownerId); + .eq('owner', ownerId) + .single(); if (error) { Logger.error(error, `${DeckRepository.name} ${this.getDeck.name}`); @@ -103,7 +105,7 @@ export class DeckRepository { } // get the collections - const [deck, errorConvertion] = await on(this.convertDataToDeck(data[0])); + const [deck, errorConvertion] = await on(this.convertDataToDeck(data)); if (errorConvertion) { Logger.error(errorConvertion, `${DeckRepository.name} ${this.getDeck.name}`); @@ -111,7 +113,11 @@ export class DeckRepository { } - return deck; + return { + ...deck, + splashArt: deck.splashArt ?? undefined, + splashArtId: data.deck_face ?? undefined + }; } public async getPublicDeck(deckId: string): Promise { @@ -119,7 +125,7 @@ export class DeckRepository { const { data, error } = await supabase .from('decks') - .select() + .select(`*, cards ( image )`) .eq('id', deckId) .eq('is_public', true); @@ -135,7 +141,10 @@ export class DeckRepository { throw new Error('Error converting deck', { cause: errorConversion }); } - return deck; + return { + ...deck, + splashArt: deck.splashArt ?? undefined + }; } public async listPublicDecks(): Promise { @@ -143,7 +152,7 @@ export class DeckRepository { const { data, error } = await supabase .from('decks') - .select('id, name, description, likes') + .select('id, name, description, likes, cards ( image )') .eq('is_public', true) if (error) { @@ -151,18 +160,21 @@ export class DeckRepository { return []; } - return data.map(d => ({ - id: d.id, - name: d.name, - description: d.description, - likes: d.likes, - })); + return data.map(d => { + return { + id: d.id, + name: d.name, + description: d.description, + likes: d.likes, + splashArt: d.cards?.image ?? undefined + }; + }); } public async listUserDecks(): Promise { - const { data: user , error: errorAuth } = await this.supabase.auth.getUser(); + const { data: user, error: errorAuth } = await this.supabase.auth.getUser(); if (errorAuth) { Logger.error(errorAuth, `${DeckRepository.name} ${this.listUserDecks.name}`); @@ -173,7 +185,7 @@ export class DeckRepository { const { data, error } = await supabase .from('decks') - .select() + .select(`*, cards ( image )`) .eq('owner', user.user!.id); if (error) { @@ -186,6 +198,7 @@ export class DeckRepository { name: d.name, description: d.description, likes: d.likes, + splashArt: d.cards?.image ?? undefined, })); } @@ -273,7 +286,8 @@ export class DeckRepository { masterDeck, sideDeck, treasureDeck, - splashArt: deckFace + splashArt: deckFace, + splashArtId: data.deck_face ?? undefined }; } diff --git a/src/root.tsx b/src/root.tsx index c6b0fdb..7e167e8 100644 --- a/src/root.tsx +++ b/src/root.tsx @@ -19,6 +19,7 @@ export default component$(() => { return ( + LDB diff --git a/src/routes/styles.css b/src/routes/styles.css index c7f308e..e69de29 100644 --- a/src/routes/styles.css +++ b/src/routes/styles.css @@ -1,214 +0,0 @@ -/* THIS FILE IS JUST FOR EXAMPLES, DELETE IT IF YOU DON'T NEED IT */ - -/* SHELL ---------------------------------------- */ -html { - font-family: - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - "Helvetica Neue", - Arial, - "Noto Sans", - sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol", - "Noto Color Emoji"; -} - -body { - background: var(--qwik-dark-background); - color: var(--qwik-dark-text); - overflow-x: hidden; -} - -/* HEADINGS ------------------------------------- */ -h1, -h2, -h3 { - color: white; - margin: 0; -} - -h1 { - font-size: 3.2rem; - text-align: center; -} -h1 .highlight, -h3 .highlight { - color: var(--qwik-light-blue); -} - -h2 { - font-weight: 400; - font-size: 2.4rem; -} -h2 .highlight { - font-weight: 700; -} - -h3 { - font-size: 2rem; -} - -@media screen and (min-width: 768px) { - h1 { - font-size: 5rem; - } - h2 { - font-size: 3.4rem; - } - h3 { - font-size: 3rem; - } -} - -/* TAGS ----------------------------------------- */ -a { - text-decoration: none; - color: var(--qwik-light-blue); -} - -code { - background: rgba(230, 230, 230, 0.3); - border-radius: 4px; - padding: 2px 6px; -} - -ul { - margin: 0; - padding-left: 20px; -} - -/* CONTAINER ------------------------------------ */ -.container { - margin: 0 auto; - padding: 30px 40px; -} -.container.container-purple { - background: var(--qwik-light-purple); -} -.container.container-dark { - background: var(--qwik-dark-background); - color: var(--qwik-dark-text); -} -.container.container-center { - text-align: center; -} -.container.container-flex { - /* does nothing on mobile */ -} -.container.container-spacing-xl { - padding: 50px 40px; -} - -@media screen and (min-width: 768px) { - .container { - padding: 60px 80px; - } - .container.container-spacing-xl { - padding: 100px 60px; - } - .container.container-flex { - display: flex; - justify-content: center; - gap: 60px; - } -} - -/* BUTTONS -------------------------------------- */ -a.button, -button { - background: var(--qwik-light-blue); - border: none; - border-radius: 8px; - color: white; - cursor: pointer; - font-size: 0.8rem; - padding: 15px 20px; - text-align: center; -} - -a.button.button-dark, -button.button-dark { - background: var(--qwik-dirty-black); -} - -a.button.button-small, -button.button-small { - padding: 15px 25px; -} - -@media screen and (min-width: 768px) { - a.button, - button { - font-size: 1rem; - padding: 23px 35px; - } -} - -/* DESIGN --------------------------------------- */ -.ellipsis { - position: absolute; - top: 100px; - left: -100px; - width: 400px; - height: 400px; - background: radial-gradient( - 57.58% 57.58% at 48.79% 42.42%, - rgba(24, 180, 244, 0.5) 0%, - rgba(46, 55, 114, 0) 63.22% - ); - transform: rotate(5deg); - opacity: 0.5; - z-index: -1; -} -.ellipsis.ellipsis-purple { - top: 1350px; - left: -100px; - background: radial-gradient( - 50% 50% at 50% 50%, - rgba(172, 127, 244, 0.5) 0%, - rgba(21, 25, 52, 0) 100% - ); - transform: rotate(-5deg); -} - -@media screen and (min-width: 768px) { - .ellipsis { - top: -100px; - left: 350px; - width: 1400px; - height: 800px; - } - .ellipsis.ellipsis-purple { - top: 1300px; - left: -200px; - } -} - -/* used icon pack: https://www.svgrepo.com/collection/phosphor-thin-icons */ -.icon:before { - width: 18px; - height: 18px; - content: ""; - display: inline-block; - margin-right: 20px; - position: relative; - top: 2px; -} - -.icon-cli:before { - background-image: url("data:image/svg+xml,%3Csvg fill='%23ffffff' width='20px' height='20px' viewBox='0 0 256 256' id='Flat' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M122.499 124.87646a4.00053 4.00053 0 0 1 0 6.24708l-40 32a4.0002 4.0002 0 0 1-4.998-6.24708L113.59668 128 77.501 99.12354a4.0002 4.0002 0 0 1 4.998-6.24708ZM175.99414 156h-40a4 4 0 0 0 0 8h40a4 4 0 1 0 0-8ZM228 56.48535v143.0293A12.49909 12.49909 0 0 1 215.51465 212H40.48535A12.49909 12.49909 0 0 1 28 199.51465V56.48535A12.49909 12.49909 0 0 1 40.48535 44h175.0293A12.49909 12.49909 0 0 1 228 56.48535Zm-8 0A4.49023 4.49023 0 0 0 215.51465 52H40.48535A4.49023 4.49023 0 0 0 36 56.48535v143.0293A4.49023 4.49023 0 0 0 40.48535 204h175.0293A4.49023 4.49023 0 0 0 220 199.51465Z'/%3E%3C/svg%3E"); -} - -.icon-apps:before { - background-image: url("data:image/svg+xml,%3Csvg fill='%23ffffff' width='20px' height='20px' viewBox='0 0 256 256' id='Flat' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M216 44.00586H40a12.01375 12.01375 0 0 0-12 12v144a12.01375 12.01375 0 0 0 12 12H216a12.01375 12.01375 0 0 0 12-12v-144A12.01375 12.01375 0 0 0 216 44.00586Zm4 156a4.00458 4.00458 0 0 1-4 4H40a4.00458 4.00458 0 0 1-4-4v-144a4.00458 4.00458 0 0 1 4-4H216a4.00458 4.00458 0 0 1 4 4Zm-144-116a8 8 0 1 1-8-8A7.99977 7.99977 0 0 1 76 84.00586Zm40 0a8 8 0 1 1-8-8A7.99977 7.99977 0 0 1 116 84.00586Z'/%3E%3C/svg%3E"); -} - -.icon-community:before { - background-image: url("data:image/svg+xml,%3Csvg fill='%23ffffff' width='20px' height='20px' viewBox='0 0 256 256' id='Flat' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M246.40381 143.19434a4.00061 4.00061 0 0 1-5.60108-.7959A55.57857 55.57857 0 0 0 196 120a4 4 0 0 1 0-8 28 28 0 1 0-27.50732-33.26074 4.00013 4.00013 0 0 1-7.85987-1.49219 36.00191 36.00191 0 1 1 54.06494 37.50513 63.58068 63.58068 0 0 1 32.50147 22.84155A3.99993 3.99993 0 0 1 246.40381 143.19434Zm-57.24268 71.05273a3.9998 3.9998 0 1 1-7.1914 3.50391 60.02582 60.02582 0 0 0-107.93946 0 3.9998 3.9998 0 1 1-7.1914-3.50391 67.56008 67.56008 0 0 1 40.90625-35.20581 44 44 0 1 1 40.50976 0A67.56139 67.56139 0 0 1 189.16113 214.24707ZM128 176a36 36 0 1 0-36-36A36.04061 36.04061 0 0 0 128 176ZM60 112A28 28 0 1 1 87.50732 78.73828a3.99989 3.99989 0 1 0 7.85938-1.49219A36.00177 36.00177 0 1 0 41.30225 114.7522 63.5829 63.5829 0 0 0 8.79883 137.5957a4 4 0 1 0 6.39648 4.80469A55.58072 55.58072 0 0 1 60 120a4 4 0 0 0 0-8Z'/%3E%3C/svg%3E"); -} diff --git a/src/stores/deckCreationContext.ts b/src/stores/deckCreationContext.ts index 5760078..4a7b2a4 100644 --- a/src/stores/deckCreationContext.ts +++ b/src/stores/deckCreationContext.ts @@ -12,7 +12,6 @@ const initialDeckData: DeckState = { masterDeck: {}, sideDeck: {}, treasureDeck: {}, - splashArt: '', } export const useDeckCreationStore = (deckData?: DeckState) => { @@ -63,8 +62,9 @@ export const useDeckCreationStore = (deckData?: DeckState) => { } } }), - setSplashArt: $(async function (this, splashArt) { + setSplashArt: $(async function (this, splashArt, cardId) { this.deckData.splashArt = splashArt; + this.deckData.splashArtId = cardId; }), removeCard: $(async function (this, card, side = false) { // just remove one copy diff --git a/src/stores/models/DeckCrationModels.ts b/src/stores/models/DeckCrationModels.ts index b00802c..3f72045 100644 --- a/src/stores/models/DeckCrationModels.ts +++ b/src/stores/models/DeckCrationModels.ts @@ -5,7 +5,7 @@ import type { DeckState } from "~/models/Deck"; export interface DeckCreationContextState { deckData: DeckState; addCard: QRL<(this: DeckCreationContextState, card: Card, side?: boolean) => Promise>; - setSplashArt: QRL<(this: DeckCreationContextState, splashArt: string) => Promise>; + setSplashArt: QRL<(this: DeckCreationContextState, splashArt: string, cardId: number) => Promise>; removeCard: QRL<(this: DeckCreationContextState, card: Card, side?: boolean) => Promise>; createDeck: QRL<(this: DeckCreationContextState) => Promise>; }