diff --git a/package-lock.json b/package-lock.json index f66b02e..116f429 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "memeaigen", "dependencies": { "@headlessui/react": "^2.2.0", "@inertiajs/react": "^2.0.0", @@ -41,7 +40,7 @@ "tailwindcss-animate": "^1.0.7", "typescript": "^5.7.2", "vite": "^6.0", - "zustand": "^5.0.3" + "zustand": "^5.0.5" }, "devDependencies": { "@eslint/js": "^9.19.0", @@ -7809,9 +7808,9 @@ } }, "node_modules/zustand": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", - "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz", + "integrity": "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==", "license": "MIT", "engines": { "node": ">=12.20.0" diff --git a/package.json b/package.json index 50413d9..7a56f16 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "tailwindcss-animate": "^1.0.7", "typescript": "^5.7.2", "vite": "^6.0", - "zustand": "^5.0.3" + "zustand": "^5.0.5" }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "4.9.5", diff --git a/resources/js/modules/editor/editor.tsx b/resources/js/modules/editor/editor.tsx index 01d2fa5..659838c 100644 --- a/resources/js/modules/editor/editor.tsx +++ b/resources/js/modules/editor/editor.tsx @@ -1,152 +1,201 @@ -import { useState, useEffect, useLayoutEffect } from "react" +import { useEffect, useLayoutEffect, useState } from 'react'; -import EditSidebar from "./partials/edit-sidebar" -import EditorCanvas from "./partials/editor-canvas" -import EditorHeader from "./partials/editor-header" -import EditorControls from "./partials/editor-controls" -import { calculateOptimalMaxWidth, calculateResponsiveWidth } from "./utils/layout-constants" +import useLocalSettingsStore from '@/stores/localSettingsStore'; +import { Volume2Icon, VolumeOffIcon } from 'lucide-react'; +import EditNavSidebar from './partials/edit-nav-sidebar'; +import EditSidebar from './partials/edit-sidebar'; +import EditorCanvas from './partials/editor-canvas'; +import EditorControls from './partials/editor-controls'; +import EditorHeader from './partials/editor-header'; +import { calculateOptimalMaxWidth, calculateResponsiveWidth } from './utils/layout-constants'; // Hook to detect if viewport is below minimum width const useViewportDetection = (minWidth = 320) => { - const [isBelowMinWidth, setIsBelowMinWidth] = useState(false); + const [isBelowMinWidth, setIsBelowMinWidth] = useState(false); - useLayoutEffect(() => { - setIsBelowMinWidth(window.innerWidth < minWidth); - }, [minWidth]); + useLayoutEffect(() => { + setIsBelowMinWidth(window.innerWidth < minWidth); + }, [minWidth]); - useEffect(() => { - const checkViewport = () => { - setIsBelowMinWidth(window.innerWidth < minWidth); - }; + useEffect(() => { + const checkViewport = () => { + setIsBelowMinWidth(window.innerWidth < minWidth); + }; - checkViewport(); - window.addEventListener('resize', checkViewport); - window.addEventListener('orientationchange', checkViewport); + checkViewport(); + window.addEventListener('resize', checkViewport); + window.addEventListener('orientationchange', checkViewport); - let resizeObserver; - if (window.ResizeObserver) { - resizeObserver = new ResizeObserver(checkViewport); - resizeObserver.observe(document.body); - } + let resizeObserver; + if (window.ResizeObserver) { + resizeObserver = new ResizeObserver(checkViewport); + resizeObserver.observe(document.body); + } - return () => { - window.removeEventListener('resize', checkViewport); - window.removeEventListener('orientationchange', checkViewport); - if (resizeObserver) resizeObserver.disconnect(); - }; - }, [minWidth]); + return () => { + window.removeEventListener('resize', checkViewport); + window.removeEventListener('orientationchange', checkViewport); + if (resizeObserver) resizeObserver.disconnect(); + }; + }, [minWidth]); - return isBelowMinWidth; + return isBelowMinWidth; }; // Hook for responsive dimensions const useResponsiveDimensions = () => { - const [dimensions, setDimensions] = useState(() => ({ - maxWidth: calculateOptimalMaxWidth(), - responsiveWidth: calculateResponsiveWidth() - })); + const [dimensions, setDimensions] = useState(() => ({ + maxWidth: calculateOptimalMaxWidth(), + responsiveWidth: calculateResponsiveWidth(), + })); - useLayoutEffect(() => { - const newMaxWidth = calculateOptimalMaxWidth(); - const newResponsiveWidth = calculateResponsiveWidth(); - setDimensions({ - maxWidth: newMaxWidth, - responsiveWidth: newResponsiveWidth - }); - }, []); + useLayoutEffect(() => { + const newMaxWidth = calculateOptimalMaxWidth(); + const newResponsiveWidth = calculateResponsiveWidth(); + setDimensions({ + maxWidth: newMaxWidth, + responsiveWidth: newResponsiveWidth, + }); + }, []); - useEffect(() => { - const handleResize = () => { - const newMaxWidth = calculateOptimalMaxWidth(); - const newResponsiveWidth = calculateResponsiveWidth(); - setDimensions({ - maxWidth: newMaxWidth, - responsiveWidth: newResponsiveWidth - }); - }; + useEffect(() => { + const handleResize = () => { + const newMaxWidth = calculateOptimalMaxWidth(); + const newResponsiveWidth = calculateResponsiveWidth(); + setDimensions({ + maxWidth: newMaxWidth, + responsiveWidth: newResponsiveWidth, + }); + }; - window.addEventListener('resize', handleResize); - window.addEventListener('orientationchange', handleResize); + window.addEventListener('resize', handleResize); + window.addEventListener('orientationchange', handleResize); - let resizeObserver; - if (window.ResizeObserver) { - resizeObserver = new ResizeObserver(handleResize); - resizeObserver.observe(document.body); - } + let resizeObserver; + if (window.ResizeObserver) { + resizeObserver = new ResizeObserver(handleResize); + resizeObserver.observe(document.body); + } - let mutationObserver; - if (window.MutationObserver) { - mutationObserver = new MutationObserver(() => { - setTimeout(handleResize, 50); - }); - mutationObserver.observe(document.documentElement, { - attributes: true, - attributeFilter: ['style'] - }); - } + let mutationObserver; + if (window.MutationObserver) { + mutationObserver = new MutationObserver(() => { + setTimeout(handleResize, 50); + }); + mutationObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ['style'], + }); + } - return () => { - window.removeEventListener('resize', handleResize); - window.removeEventListener('orientationchange', handleResize); - if (resizeObserver) resizeObserver.disconnect(); - if (mutationObserver) mutationObserver.disconnect(); - }; - }, []); + return () => { + window.removeEventListener('resize', handleResize); + window.removeEventListener('orientationchange', handleResize); + if (resizeObserver) resizeObserver.disconnect(); + if (mutationObserver) mutationObserver.disconnect(); + }; + }, []); - return dimensions; + return dimensions; }; const Editor = () => { - const [isEditSidebarOpen, setIsEditSidebarOpen] = useState(false) - const { maxWidth, responsiveWidth } = useResponsiveDimensions(); - const isBelowMinWidth = useViewportDetection(320); + const { getSetting } = useLocalSettingsStore(); - const handleEditClick = () => { - setIsEditSidebarOpen(!isEditSidebarOpen) - } + const [isEditNavSidebarOpen, setIsEditNavSidebarOpen] = useState(false); + const [isEditSidebarOpen, setIsEditSidebarOpen] = useState(false); + const [isMuted, setIsMuted] = useState(true); // Video starts muted by default + const { maxWidth, responsiveWidth } = useResponsiveDimensions(); + const isBelowMinWidth = useViewportDetection(320); - const handleCloseSidebar = () => { - setIsEditSidebarOpen(false) - } + const handleEditNavClick = () => { + setIsEditNavSidebarOpen(!isEditNavSidebarOpen); + }; - return ( -
- + const handleEditNavClose = () => { + setIsEditNavSidebarOpen(false); + }; - + const handleEditClick = () => { + setIsEditSidebarOpen(!isEditSidebarOpen); + }; - {isBelowMinWidth ? ( -
-
+ const handleEditClose = () => { + setIsEditSidebarOpen(false); + }; -
-
-
-
- ) : ( - <> - - - - )} -
- ) -} + // Toggle mute functionality + const handleToggleMute = () => { + setIsMuted(!isMuted); + }; + + return ( +
+ + + + + + {isBelowMinWidth ? ( +
+
+
+
+
+
+ +
+
{getSetting('genAlphaSlang') ? 'SHEESH' : 'YIKES'} 🥔
+

+ {getSetting('genAlphaSlang') + ? 'Not gonna lie, using on a potato screen is giving L vibes. Desktop hits different - that experience is straight fire, bet!' + : 'You seem to be using a potato-sized screen. Please continue with desktop for a more refined experience!'} +

+
+ Note: You can turn {getSetting('genAlphaSlang') ? 'off' : 'on'} gen alpha slang in Settings. +
+
+
+ +
+ +
+
+
+
+ ) : ( + <> + + + + )} +
+ ); +}; export default Editor; diff --git a/resources/js/modules/editor/partials/edit-nav-sidebar.tsx b/resources/js/modules/editor/partials/edit-nav-sidebar.tsx new file mode 100644 index 0000000..9b710fa --- /dev/null +++ b/resources/js/modules/editor/partials/edit-nav-sidebar.tsx @@ -0,0 +1,59 @@ +import { Button } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'; +import useLocalSettingsStore from '@/stores/localSettingsStore'; +import { SettingsIcon } from 'lucide-react'; + +interface EditNavSidebarProps { + isOpen: boolean; + onClose: () => void; +} + +export default function EditNavSidebar({ isOpen, onClose }: EditNavSidebarProps) { + const { getSetting, setSetting } = useLocalSettingsStore(); + + return ( + !open && onClose()}> + + + +
MEMEAIGEN
+
+
+ +
+ + + + + + + Settings + Change your settings here. + + +
+ setSetting('genAlphaSlang', !getSetting('genAlphaSlang'))} + /> + +
+ + +
+
+
+
+
+ ); +} diff --git a/resources/js/modules/editor/partials/edit-sidebar.tsx b/resources/js/modules/editor/partials/edit-sidebar.tsx index 1b9a885..454a7a4 100644 --- a/resources/js/modules/editor/partials/edit-sidebar.tsx +++ b/resources/js/modules/editor/partials/edit-sidebar.tsx @@ -1,138 +1,133 @@ -import { Edit3, Plus, Coins } from "lucide-react" -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet" +import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'; +import { Coins, Edit3, Plus } from 'lucide-react'; interface EditSidebarProps { - isOpen: boolean - onClose: () => void + isOpen: boolean; + onClose: () => void; } export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) { - return ( - !open && onClose()}> - - - - - Edit Media - - + return ( + !open && onClose()}> + + + + + Edit Media + + -
- {/* Background and Meme Selection */} -
-
-
- Gaming background -
- Background -
-
-
- Meme character -
- Meme -
-
+
+ {/* Background and Meme Selection */} +
+
+
+ Gaming background +
+ Background +
+
+
+ Meme character +
+ Meme +
+
- {/* AI Background Search */} -
-

Search for backgrounds using AI

+ {/* AI Background Search */} +
+

Search for backgrounds using AI

-
-
- -
-

Generate a background with AI

-
- 1 -
- +
+
+ +
+

Generate a background with AI

+
+ 1 +
+ +
+
+
+
+ + {/* Meme Templates Grid */} +
+
+ Creepy face meme +
+
+ Confused person meme +
???
+
???
+
+
+ Woody meme +
+
+ Doge meme +
+
+ Stock market meme +
+ 📈 28% +
+
+
+ Room meme +
+
-
-
-
- - {/* Meme Templates Grid */} -
-
- Creepy face meme -
-
- Confused person meme -
???
-
???
-
-
- Woody meme -
-
- Doge meme -
-
- Stock market meme -
- 📈 28% -
-
-
- Room meme -
-
-
- - - ) + + + ); } diff --git a/resources/js/modules/editor/partials/editor-header.tsx b/resources/js/modules/editor/partials/editor-header.tsx index 4776478..9e5d3fa 100644 --- a/resources/js/modules/editor/partials/editor-header.tsx +++ b/resources/js/modules/editor/partials/editor-header.tsx @@ -1,26 +1,53 @@ -import { Button } from "@/components/ui/button" -import { cn } from "@/lib/utils" -import CoinIcon from "@/reusables/coin-icon" -import { Menu, Coins } from "lucide-react" +import { Button } from '@/components/ui/button'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { cn } from '@/lib/utils'; +import CoinIcon from '@/reusables/coin-icon'; +import useLocalSettingsStore from '@/stores/localSettingsStore'; +import { Menu } from 'lucide-react'; +import { useState } from 'react'; -const EditorHeader = ( - {className = ''} -) => { - return ( -
- +const EditorHeader = ({ className = '', onNavClick = () => {}, isNavActive = false }) => { + const { getSetting } = useLocalSettingsStore(); -

MEMEAIGEN

+ const [openCoinDialog, setOpenCoinDialog] = useState(false); - + return ( +
+ -
- ) -} +

MEMEAIGEN

+ + + + setOpenCoinDialog(open)}> + + + {getSetting('genAlphaSlang') ? 'Bruh' : 'Chill'} + + {getSetting('genAlphaSlang') + ? "No cap, soon you'll be able to get AI cooking memes that absolutely slay! But lowkey fam, we gotta focus on making these core features bussin' first." + : "Soon you'll be able to prompt AI to generate memes! Let us focus on nailing the core features first."} + + + +
+ + Note: You can turn {getSetting('genAlphaSlang') ? 'off' : 'on'} gen alpha slang in Settings. + + +
+
+
+
+
+ ); +}; export default EditorHeader; diff --git a/resources/js/pages/home/home.tsx b/resources/js/pages/home/home.tsx index aef2a1d..6340da3 100644 --- a/resources/js/pages/home/home.tsx +++ b/resources/js/pages/home/home.tsx @@ -1,11 +1,11 @@ -import Editor from "@/modules/editor/editor"; +import Editor from '@/modules/editor/editor'; const Home = () => { - return ( -
- -
- ); + return ( +
+ +
+ ); }; export default Home; diff --git a/resources/js/reusables/coin-icon.tsx b/resources/js/reusables/coin-icon.tsx index 534bfa7..d4652b8 100644 --- a/resources/js/reusables/coin-icon.tsx +++ b/resources/js/reusables/coin-icon.tsx @@ -1,27 +1,42 @@ import React from 'react'; interface CoinIconProps { - className?: string; + className?: string; } const CoinIcon: React.FC = ({ className }) => { - return ( - - - - - - - - - - - - - - - - ); + return ( + + + + + + + + + + + + + + + + ); }; export default CoinIcon; diff --git a/resources/js/stores/localSettingsStore.ts b/resources/js/stores/localSettingsStore.ts new file mode 100644 index 0000000..9bfbf7b --- /dev/null +++ b/resources/js/stores/localSettingsStore.ts @@ -0,0 +1,80 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +// Immutable default settings +const defaultSettings = { + genAlphaSlang: true, + // Add more settings here +}; + +const useLocalSettingsStore = create( + persist( + (set, get) => ({ + settings: { ...defaultSettings }, // clone to avoid shared reference + + // Get a setting by key + getSetting: (key) => { + const currentSettings = get().settings; + return key in currentSettings ? currentSettings[key] : defaultSettings[key] ?? null; + }, + + // Set or update a specific setting + setSetting: (key, value) => { + console.log(`Updating setting ${key} to`, value); // <-- Debug log + set((state) => ({ + settings: { + ...state.settings, + [key]: value, + }, + })); + }, + + + // Reset all settings to default + resetSettings: () => { + set({ settings: { ...defaultSettings } }); // create new object reference + }, + + // Reset a specific setting to its default + resetSetting: (key) => { + set((state) => ({ + settings: { + ...state.settings, + [key]: defaultSettings[key], + }, + })); + }, + + // Merge default settings with current ones (useful on load) + initializeSettings: () => { + set((state) => ({ + settings: { + ...defaultSettings, + ...state.settings, + }, + })); + }, + }), + { + name: 'local-settings-storage', + // Ensure only the 'settings' key is stored + partialize: (state) => ({ settings: state.settings }), + // Explicit localStorage usage (for compatibility) + storage: { + getItem: (name) => { + const stored = localStorage.getItem(name); + return stored ? JSON.parse(stored) : null; + }, + setItem: (name, value) => { + localStorage.setItem(name, JSON.stringify(value)); + }, + removeItem: (name) => { + localStorage.removeItem(name); + }, + } + + } + ) +); + +export default useLocalSettingsStore;