summaryrefslogtreecommitdiff
path: root/apps/web/src/stores/stickers-store.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-08-15 02:12:16 +0200
committerMaze Winther <mazewinther@gmail.com>2025-08-15 02:12:16 +0200
commit7f91c9ca00cd51bea1cd1e97eeb3c58b7e7a1bae (patch)
tree25c6cddf67e3227d99c2c28f71496851962f1f45 /apps/web/src/stores/stickers-store.ts
parent5e8a489a147c148b6495a6a25fb4dd9725e6fca6 (diff)
parentc3f3345d7b1ac820b411a5adbca3172378642953 (diff)
downloadopencut-7f91c9ca00cd51bea1cd1e97eeb3c58b7e7a1bae.tar.gz
Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging
Diffstat (limited to 'apps/web/src/stores/stickers-store.ts')
-rw-r--r--apps/web/src/stores/stickers-store.ts178
1 files changed, 178 insertions, 0 deletions
diff --git a/apps/web/src/stores/stickers-store.ts b/apps/web/src/stores/stickers-store.ts
new file mode 100644
index 00000000..da8b926b
--- /dev/null
+++ b/apps/web/src/stores/stickers-store.ts
@@ -0,0 +1,178 @@
+import { create } from "zustand";
+import {
+ getCollections,
+ getCollection,
+ searchIcons,
+ downloadSvgAsText,
+ svgToFile,
+ type IconSet,
+ type CollectionInfo,
+ type IconSearchResult,
+} from "@/lib/iconify-api";
+
+export type StickerCategory = "all" | "general" | "brands" | "emoji";
+
+interface StickersStore {
+ searchQuery: string;
+ selectedCategory: StickerCategory;
+ selectedCollection: string | null;
+ viewMode: "search" | "browse" | "collection";
+
+ collections: Record<string, IconSet>;
+ currentCollection: CollectionInfo | null;
+ searchResults: IconSearchResult | null;
+ recentStickers: string[];
+ isLoadingCollections: boolean;
+ isLoadingCollection: boolean;
+ isSearching: boolean;
+ isDownloading: boolean;
+
+ setSearchQuery: (query: string) => void;
+ setSelectedCategory: (category: StickerCategory) => void;
+ setSelectedCollection: (collection: string | null) => void;
+ setViewMode: (mode: "search" | "browse" | "collection") => void;
+
+ loadCollections: () => Promise<void>;
+ loadCollection: (prefix: string) => Promise<void>;
+ searchStickers: (query: string) => Promise<void>;
+ downloadSticker: (iconName: string) => Promise<File | null>;
+
+ addToRecentStickers: (iconName: string) => void;
+ clearRecentStickers: () => void;
+}
+
+const MAX_RECENT_STICKERS = 50;
+
+export const useStickersStore = create<StickersStore>((set, get) => ({
+ searchQuery: "",
+ selectedCategory: "all",
+ selectedCollection: null,
+ viewMode: "browse",
+
+ collections: {},
+ currentCollection: null,
+ searchResults: null,
+ recentStickers: [],
+
+ isLoadingCollections: false,
+ isLoadingCollection: false,
+ isSearching: false,
+ isDownloading: false,
+
+ setSearchQuery: (query) => set({ searchQuery: query }),
+
+ setSelectedCategory: (category) =>
+ set({
+ selectedCategory: category,
+ viewMode: "browse",
+ selectedCollection: null,
+ currentCollection: null,
+ }),
+
+ setSelectedCollection: (collection) => {
+ set({
+ selectedCollection: collection,
+ viewMode: collection ? "collection" : "browse",
+ currentCollection: null,
+ });
+
+ if (collection) {
+ get().loadCollection(collection);
+ }
+ },
+
+ setViewMode: (mode) => set({ viewMode: mode }),
+
+ loadCollections: async () => {
+ set({ isLoadingCollections: true });
+ try {
+ const collections = await getCollections();
+ set({ collections });
+ } catch (error) {
+ console.error("Failed to load collections:", error);
+ } finally {
+ set({ isLoadingCollections: false });
+ }
+ },
+
+ loadCollection: async (prefix: string) => {
+ set({ isLoadingCollection: true });
+ try {
+ const collection = await getCollection(prefix);
+ set({ currentCollection: collection });
+ } catch (error) {
+ console.error(`Failed to load collection ${prefix}:`, error);
+ set({ currentCollection: null });
+ } finally {
+ set({ isLoadingCollection: false });
+ }
+ },
+
+ searchStickers: async (query: string) => {
+ if (!query.trim()) {
+ set({ searchResults: null, viewMode: "browse" });
+ return;
+ }
+
+ const { selectedCategory } = get();
+
+ set({ isSearching: true, viewMode: "search" });
+ try {
+ let category: string | undefined;
+
+ if (selectedCategory !== "all") {
+ if (selectedCategory === "general") {
+ category = "General";
+ } else if (selectedCategory === "brands") {
+ category = "Brands / Social";
+ } else if (selectedCategory === "emoji") {
+ category = "Emoji";
+ }
+ }
+
+ const results = await searchIcons(query, 100, undefined, category);
+ set({ searchResults: results });
+ } catch (error) {
+ console.error("Search failed:", error);
+ set({ searchResults: null });
+ } finally {
+ set({ isSearching: false });
+ }
+ },
+
+ downloadSticker: async (iconName: string) => {
+ set({ isDownloading: true });
+ try {
+ const svgText = await downloadSvgAsText(iconName, {
+ width: 200,
+ height: 200,
+ });
+
+ const fileName = `${iconName.replace(":", "-")}.svg`;
+ const file = svgToFile(svgText, fileName);
+
+ get().addToRecentStickers(iconName);
+
+ return file;
+ } catch (error) {
+ console.error(`Failed to download sticker ${iconName}:`, error);
+ return null;
+ } finally {
+ set({ isDownloading: false });
+ }
+ },
+
+ addToRecentStickers: (iconName: string) => {
+ set((state) => {
+ const recent = [
+ iconName,
+ ...state.recentStickers.filter((s) => s !== iconName),
+ ];
+ return {
+ recentStickers: recent.slice(0, MAX_RECENT_STICKERS),
+ };
+ });
+ },
+
+ clearRecentStickers: () => set({ recentStickers: [] }),
+}));