Skip to content

Commit

Permalink
Deck category
Browse files Browse the repository at this point in the history
  • Loading branch information
kubk committed Dec 14, 2023
1 parent ba148da commit 5f13ba3
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 18 deletions.
1 change: 1 addition & 0 deletions functions/add-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const onRequestPost = handleError(async ({ request, env }) => {
envSafe,
input.data.deckId,
user.id,
user.is_admin,
);
if (!canEdit) {
return createForbiddenRequestResponse();
Expand Down
31 changes: 31 additions & 0 deletions functions/db/databaseTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface Database {
Row: {
author_id: number | null
available_in: string | null
category_id: string | null
created_at: string
description: string | null
id: number
Expand All @@ -65,6 +66,7 @@ export interface Database {
Insert: {
author_id?: number | null
available_in?: string | null
category_id?: string | null
created_at?: string
description?: string | null
id?: number
Expand All @@ -77,6 +79,7 @@ export interface Database {
Update: {
author_id?: number | null
available_in?: string | null
category_id?: string | null
created_at?: string
description?: string | null
id?: number
Expand All @@ -92,6 +95,12 @@ export interface Database {
columns: ["author_id"]
referencedRelation: "user"
referencedColumns: ["id"]
},
{
foreignKeyName: "deck_category_id_fkey"
columns: ["category_id"]
referencedRelation: "deck_category"
referencedColumns: ["id"]
}
]
}
Expand Down Expand Up @@ -132,6 +141,27 @@ export interface Database {
}
]
}
deck_category: {
Row: {
created_at: string
id: string
logo: string | null
name: string
}
Insert: {
created_at?: string
id?: string
logo?: string | null
name: string
}
Update: {
created_at?: string
id?: string
logo?: string | null
name?: string
}
Relationships: []
}
notification: {
Row: {
created_at: string
Expand Down Expand Up @@ -248,6 +278,7 @@ export interface Database {
Returns: {
author_id: number | null
available_in: string | null
category_id: string | null
created_at: string
description: string | null
id: number
Expand Down
7 changes: 7 additions & 0 deletions functions/db/deck/decks-with-cards-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ export const deckWithCardsSchema = deckSchema.merge(
z.object({
deck_card: z.array(deckCardSchema),
available_in: z.string().nullable(),
deck_category: z
.object({
name: z.string(),
logo: z.string().nullable(),
})
.nullable()
.optional(),
}),
);

Expand Down
25 changes: 25 additions & 0 deletions functions/db/deck/get-all-categories-db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { EnvType } from "../../env/env-schema.ts";
import { getDatabase } from "../get-database.ts";
import { DatabaseException } from "../database-exception.ts";
import { z } from "zod";

const categorySchema = z.object({
id: z.string(),
name: z.string(),
logo: z.string().nullable(),
});

export const getAllCategoriesDb = async (env: EnvType) => {
const db = getDatabase(env);

const { data: categories, error: categoriesError } = await db
.from("deck_category")
.select("*")
.limit(100);

if (categoriesError) {
throw new DatabaseException(categoriesError);
}

return z.array(categorySchema).parse(categories);
};
7 changes: 5 additions & 2 deletions functions/db/deck/get-catalog-decks-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const getCatalogDecksDb = async (

const { data, error } = await db
.from("deck")
.select("*")
.select("*, deck_category:category_id(name, logo)")
.eq("is_public", true)
.order("id", { ascending: false })
.limit(100);
Expand All @@ -23,6 +23,9 @@ export const getCatalogDecksDb = async (
}

return decksWithCardsSchema.parse(
data.map((item) => ({ ...item, deck_card: [] })),
data.map((deck) => {
// @ts-ignore
return { ...deck, deck_card: [] };
}),
);
};
14 changes: 8 additions & 6 deletions functions/db/deck/get-deck-by-id-and-author-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ export const getDeckByIdAndAuthorId = async (
envSafe: EnvType,
deckId: number,
userId: number,
isAdmin: boolean,
) => {
const db = getDatabase(envSafe);

const canEditDeckResult = await db
.from("deck")
.select()
.eq("author_id", userId)
.eq("id", deckId)
.single();
let query = db.from("deck").select().eq("id", deckId);

if (!isAdmin) {
query = query.eq("author_id", userId);
}

const canEditDeckResult = await query.single();

if (canEditDeckResult.error) {
throw new DatabaseException(canEditDeckResult.error);
Expand Down
1 change: 1 addition & 0 deletions functions/upsert-deck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const onRequestPost = handleError(async ({ request, env }) => {
envSafe,
input.data.id,
user.id,
user.is_admin,
);
if (!databaseDeck) {
return createForbiddenRequestResponse();
Expand Down
1 change: 1 addition & 0 deletions src/screens/deck-catalog/deck-added-label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const DeckAddedLabel = () => {
fontStyle: "normal",
padding: "0 8px",
borderRadius: theme.borderRadius,
backgroundColor: theme.secondaryBgColor,
border: "1px solid " + theme.linkColor,
color: theme.linkColor,
})}
Expand Down
8 changes: 4 additions & 4 deletions src/screens/deck-review/deck-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const DeckPreview = observer(() => {
gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))",
})}
>
{deckListStore.myId && deck.author_id === deckListStore.myId ? (
{deckListStore.canEditDeck(deck) ? (
<ButtonSideAligned
icon={"mdi-plus-circle mdi-24px"}
outline
Expand All @@ -130,17 +130,17 @@ export const DeckPreview = observer(() => {
icon={"mdi-content-duplicate mdi-24px"}
outline
onClick={() => {
showConfirm("Are you sure to copy this deck?").then(() => {
showConfirm("Are you sure to duplicate this deck?").then(() => {
apiDuplicateDeckRequest(deck.id).then(() => {
screenStore.go({ type: "main" });
});
});
}}
>
Copy
Duplicate
</ButtonSideAligned>
)}
{deckListStore.myId && deck.author_id === deckListStore.myId ? (
{deckListStore.canEditDeck(deck) ? (
<ButtonSideAligned
icon={"mdi-pencil-circle mdi-24px"}
outline
Expand Down
4 changes: 1 addition & 3 deletions src/store/deck-form-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ export class DeckFormStore {
assert(screen.type === "deckForm");

if (screen.deckId) {
const deck = deckListStore.myDecks.find(
(myDeck) => myDeck.id === screen.deckId,
);
const deck = deckListStore.searchDeckById(screen.deckId);
assert(deck, "Deck not found in deckListStore");
this.form = createUpdateForm(screen.deckId, deck);
} else {
Expand Down
29 changes: 26 additions & 3 deletions src/store/deck-list-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@ export class DeckListStore {
isDeckCardsLoading = false;

constructor() {
makeAutoObservable(this, {}, { autoBind: true });
makeAutoObservable(
this,
{
canEditDeck: false,
searchDeckById: false,
},
{ autoBind: true },
);
}

loadFirstTime(startParam?: string) {
Expand Down Expand Up @@ -188,6 +195,15 @@ export class DeckListStore {
return this.user?.id;
}

canEditDeck(deck: DeckWithCardsWithReviewType) {
const isAdmin = this.user?.is_admin ?? false;
if (isAdmin) {
return true;
}

return deckListStore.myId && deck.author_id === deckListStore.myId;
}

openDeckFromCatalog(deck: DeckWithCardsDbType, isMine: boolean) {
assert(this.myInfo);
if (isMine) {
Expand All @@ -211,15 +227,22 @@ export class DeckListStore {
);
}

searchDeckById(deckId: number) {
if (!this.myInfo) {
return null;
}
const decksToSearch = this.myInfo.myDecks.concat(this.publicDecks);
return decksToSearch.find((deck) => deck.id === deckId);
}

get selectedDeck(): DeckWithCardsWithReviewType | null {
const screen = screenStore.screen;
assert(screen.type === "deckPublic" || screen.type === "deckMine");
if (!screen.deckId || !this.myInfo) {
return null;
}

const decksToSearch = this.myInfo.myDecks.concat(this.publicDecks);
const deck = decksToSearch.find((deck) => deck.id === screen.deckId);
const deck = this.searchDeckById(screen.deckId);
if (!deck) {
return null;
}
Expand Down
53 changes: 53 additions & 0 deletions src/ui/deck-available-in-flag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from "react";
import { css } from "@emotion/css";
import WebApp from "@twa-dev/sdk";

// Windows doesn't support flag emojis, so we replace them with images
export const replaceFlagEmojiOnWindows = (logo: string) => {
switch (logo) {
case "🇬🇧":
return "gb";
default:
return null;
}
};

const supportsEmojiFlag = WebApp.platform !== "tdesktop";

type Props = { logo: string; categoryName: string };

export const DeckAvailableInFlag = (props: Props) => {
const { logo, categoryName } = props;

if (supportsEmojiFlag) {
return logo;
}

return (
<span
className={css({ marginRight: 6 })}
title={`Deck category is ${categoryName}`}
>
{(() => {
if (supportsEmojiFlag) {
return logo;
}

const replacedFlag = replaceFlagEmojiOnWindows(logo);

if (!replacedFlag) {
return null;
}

return (
<img
src={`https://flagcdn.com/16x12/${replacedFlag}.png`}
width="16"
height="12"
alt={logo}
/>
);
})()}
</span>
);
};
9 changes: 9 additions & 0 deletions src/ui/deck-list-item-with-description.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import { css } from "@emotion/css";
import { theme } from "./theme.tsx";
import LinesEllipsis from "react-lines-ellipsis";
import React from "react";
import { DeckAvailableInFlag } from "./deck-available-in-flag.tsx";

type Props = {
deck: {
id: number;
name: string;
description: string | null;
available_in: string | null;
deck_category?: { name: string; logo: string | null } | null;
};
onClick: () => void;
titleRightSlot?: React.ReactNode;
Expand Down Expand Up @@ -41,6 +44,12 @@ export const DeckListItemWithDescription = observer((props: Props) => {
position: "relative",
})}
>
{deck.deck_category?.logo ? (
<DeckAvailableInFlag
logo={deck.deck_category.logo}
categoryName={deck.deck_category.name}
/>
) : null}
{deck.name}
{titleRightSlot}
</div>
Expand Down

0 comments on commit 5f13ba3

Please sign in to comment.