From e19febc186629a9a06810a1d22792322c0fd9cbd Mon Sep 17 00:00:00 2001 From: shivam kumar Date: Fri, 31 Oct 2025 00:40:03 +0530 Subject: [PATCH 1/5] feat: add mobile responsiveness and enhanced search with categories --- package-lock.json | 42 ++++---- src/Meme.jsx | 204 +++++++++++++++++++++++++++++++++----- src/components/Home.jsx | 128 ++++++++++++++++++++++-- src/components/Navbar.jsx | 49 ++++++++- 4 files changed, 363 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65cf981c..674ffe66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -162,6 +162,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -553,6 +554,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -599,6 +601,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1636,8 +1639,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.3", @@ -1716,6 +1718,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.31.tgz", "integrity": "sha512-c2UnPv548q+5DFh03y8lEDeMfDwBn9G3dRwfkrxQMo/dOtRHUUO57k6pHvBIfH/VF4Nh+98mZ5aaSe+2echD5g==", "dev": true, + "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1727,6 +1730,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.14.tgz", "integrity": "sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==", "dev": true, + "peer": true, "dependencies": { "@types/react": "*" } @@ -1838,6 +1842,7 @@ "integrity": "sha512-HURRrgGVzz2GQ2Imurp55FA+majHXgCXMzcwtojUZeRsAXyHNgEvxGRJf4QQY4kJeVakiugusGYeUqBgZ/xylg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "4.0.3", "fflate": "^0.8.2", @@ -1873,6 +1878,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1930,7 +1936,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -2217,6 +2222,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001541", "electron-to-chromium": "^1.4.535", @@ -2536,8 +2542,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.4.563", @@ -2731,6 +2736,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3960,6 +3966,7 @@ "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@asamuzakjp/dom-selector": "^6.7.2", "cssstyle": "^5.3.1", @@ -4135,7 +4142,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -4572,6 +4578,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4716,7 +4723,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -4731,8 +4737,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/prop-types": { "version": "15.8.1", @@ -4779,6 +4784,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4790,6 +4796,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -5510,6 +5517,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5756,6 +5764,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==", "dev": true, + "peer": true, "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -5812,6 +5821,7 @@ "integrity": "sha512-IUSop8jgaT7w0g1yOM/35qVtKjr/8Va4PrjzH1OUb0YH4c3OXB2lCZDkMAB6glA8T5w8S164oJGsbcmAecr4sA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.3", "@vitest/mocker": "4.0.3", @@ -6345,24 +6355,13 @@ } } }, - "node_modules/vitest/node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, "node_modules/vitest/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6418,6 +6417,7 @@ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", diff --git a/src/Meme.jsx b/src/Meme.jsx index 69a94e9d..5f305924 100644 --- a/src/Meme.jsx +++ b/src/Meme.jsx @@ -1,5 +1,5 @@ /* eslint-disable react/prop-types */ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useToast } from "./contexts/ToastContext"; import { shareToTwitter, @@ -22,6 +22,32 @@ const Meme = ({ meme, setMeme }) => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); const [showError, setShowError] = useState(false); + + // Keyboard shortcuts + useEffect(() => { + const handleKeyPress = (e) => { + if (e.ctrlKey || e.metaKey) { + switch (e.key) { + case 's': + e.preventDefault(); + downloadMeme(meme.url, "meme"); + break; + case 'Enter': + e.preventDefault(); + if (!isLoading) { + document.querySelector('form').requestSubmit(); + } + break; + } + } + if (e.key === 'Escape') { + setMeme(null); + } + }; + + window.addEventListener('keydown', handleKeyPress); + return () => window.removeEventListener('keydown', handleKeyPress); + }, [meme.url, isLoading, setMeme]); const saveMemeToHistory = (memeData) => { const savedMemes = JSON.parse(localStorage.getItem('memeHistory') || '[]'); @@ -98,31 +124,82 @@ const Meme = ({ meme, setMeme }) => {
{/* Left Section - Image */} -
- meme +
+
+ meme + {/* Meme Info Overlay */} +
+ {meme.width}x{meme.height}px +
+
+ + {/* Meme Stats */} +
+

{meme.name}

+
+ 📝 {meme.box_count} text boxes + 📈 {meme.width}x{meme.height} +
+
{/* Right Section - Caption Inputs */}
-

Add Your Captions

-
+
+

Add Your Captions

+
+

📝 Fill in the text boxes below

+

⌨️ Shortcuts: Ctrl+S (Save), Ctrl+Enter (Generate), Esc (Back)

+
+
+
{[...Array(meme.box_count)].map((_, index) => ( - { - const newBox = form.boxes; - newBox[index] = { text: e.target.value }; - setForm({ ...form, boxes: newBox }); - }} - /> +
+ { + const newBox = form.boxes; + newBox[index] = { text: e.target.value }; + setForm({ ...form, boxes: newBox }); + }} + /> + {/* Quick Text Options */} +
+ + +
+
))}
@@ -158,11 +235,11 @@ const Meme = ({ meme, setMeme }) => { ) : showError ? ( <> - ❌ Error + ❌ Failed - Try Again ) : showSuccessNote ? ( <> - ✅ Success! + ✅ Meme Created! ) : ( <> @@ -174,10 +251,52 @@ const Meme = ({ meme, setMeme }) => { type="button" className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg transition-colors font-medium" onClick={() => downloadMeme(meme.url, "meme")} + title="Save meme (Ctrl+S)" > 💾 Save + {/* Quick Actions */} + + + + {/* Success Note */} {showSuccessNote && (
@@ -187,8 +306,13 @@ const Meme = ({ meme, setMeme }) => { {/* Error Message */} {showError && ( -
- ❌ {error} +
+ ⚠️ +
+

Generation Failed

+

{error}

+

Check your connection and try again

+
)}
@@ -235,6 +359,36 @@ const Meme = ({ meme, setMeme }) => { > 📋 Copy Link + + {/* Additional Share Options */} + + +
)} diff --git a/src/components/Home.jsx b/src/components/Home.jsx index c4aa2fe5..6cf2444d 100644 --- a/src/components/Home.jsx +++ b/src/components/Home.jsx @@ -12,23 +12,52 @@ const Home = () => { const [meme, setMeme] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [currentPage, setCurrentPage] = useState(1); + const [selectedCategory, setSelectedCategory] = useState('all'); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); const [itemsPerPage] = useState(18); // Fixed 18 items per page useEffect(() => { + setIsLoading(true); fetch("https://api.imgflip.com/get_memes") - .then((res) => res.json()) + .then((res) => { + if (!res.ok) throw new Error('Failed to load memes'); + return res.json(); + }) .then((data) => { setTemp(data.data.memes); + setIsLoading(false); + }) + .catch((err) => { + setError(err.message); + setIsLoading(false); }); }, []); - // Function to filter memes based on the search query - const filteredMemes = temp.filter((meme) => - meme.name.toLowerCase().includes(searchQuery.toLowerCase()) - ); + // Enhanced filtering with categories + const filteredMemes = temp.filter((meme) => { + const matchesSearch = meme.name.toLowerCase().includes(searchQuery.toLowerCase()); + + if (selectedCategory === 'all') return matchesSearch; + + const categoryKeywords = { + reaction: ['surprised', 'pikachu', 'yelling', 'woman', 'cat'], + funny: ['spongebob', 'mocking', 'fine', 'dog'], + choice: ['drake', 'buttons', 'two buttons', 'exit'], + success: ['cheers', 'dicaprio', 'handshake'], + programming: ['debugging', 'brain', 'expanding'] + }; + + const keywords = categoryKeywords[selectedCategory] || []; + const matchesCategory = keywords.some(keyword => + meme.name.toLowerCase().includes(keyword) + ); + + return matchesSearch && matchesCategory; + }); // Calculate the index of the last and first item on the current page const indexOfLastItem = currentPage * itemsPerPage; @@ -94,11 +123,91 @@ const Home = () => { setSearchQuery={setSearchQuery} /> -
+
{meme === null ? ( <> - -
+ {/* Categories */} +
+
+ {[ + { id: 'all', name: 'All', icon: '🎭' }, + { id: 'reaction', name: 'Reaction', icon: '😱' }, + { id: 'funny', name: 'Funny', icon: '😂' }, + { id: 'choice', name: 'Choice', icon: '🤔' }, + { id: 'success', name: 'Success', icon: '🎉' }, + { id: 'programming', name: 'Code', icon: '💻' } + ].map(category => ( + + ))} +
+

+ {filteredMemes.length} memes found + {selectedCategory !== 'all' && ` in ${selectedCategory}`} +

+
+ + {/* Error Message */} + {error && ( +
+
+ ⚠️ +
+

{error}

+ +
+
+
+ )} + + {/* Loading State */} + {isLoading ? ( +
+ {[...Array(18)].map((_, i) => ( +
+
+
+
+
+ ))} +
+ ) : filteredMemes.length === 0 ? ( +
+
😅
+

No memes found

+

+ {searchQuery ? `No results for "${searchQuery}"` : 'No memes in this category'} +

+ +
+ ) : ( + + )} + {/* Pagination - Only show if has results */} + {!isLoading && filteredMemes.length > 0 && ( +
-
+
+ )} ) : ( <> diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index a6892b91..077abd06 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -155,18 +155,28 @@ const Navbar = ({ setMeme, searchQuery, setSearchQuery }) => {
- + {searchQuery ? ( + + ) : ( + + )}
)} {
+ {/* Mobile Search Bar */} + {isMobileSearchOpen && isHomePage && ( +
+
+ + {searchQuery ? ( + + ) : ( + + )} +
+
+ )} + {/* Mobile Dropdown Menu */} {isMobileMenuOpen && (
Date: Fri, 31 Oct 2025 00:49:55 +0530 Subject: [PATCH 2/5] Added history --- src/App.jsx | 2 + src/Meme.jsx | 10 ++- src/components/MemeHistory.jsx | 139 +++++++++++++++++++++++++++++++++ src/components/Navbar.jsx | 2 + 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/components/MemeHistory.jsx diff --git a/src/App.jsx b/src/App.jsx index 3fd27dc8..3b87fade 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,6 +7,7 @@ import Home from "./components/Home"; import "./style.css"; import About from "./components/About"; import History from "./components/History"; +import MemeHistory from "./components/MemeHistory"; import Dynamicmeme from "./components/Dynamicmeme"; import NewMeme from "./components/NewMeme"; @@ -17,6 +18,7 @@ const App = () => { } /> } /> } /> + } /> } /> } /> diff --git a/src/Meme.jsx b/src/Meme.jsx index 5f305924..aa73489f 100644 --- a/src/Meme.jsx +++ b/src/Meme.jsx @@ -55,10 +55,18 @@ const Meme = ({ meme, setMeme }) => { id: Date.now(), url: memeData.url, template_name: meme.name || 'Unknown Template', + template_id: meme.id, texts: form.boxes.map(box => box.text || ''), - created_at: new Date().toISOString() + created_at: new Date().toISOString(), + thumbnail: meme.url }; + + // Keep only last 50 memes to avoid storage issues savedMemes.unshift(newMeme); + if (savedMemes.length > 50) { + savedMemes.splice(50); + } + localStorage.setItem('memeHistory', JSON.stringify(savedMemes)); }; diff --git a/src/components/MemeHistory.jsx b/src/components/MemeHistory.jsx new file mode 100644 index 00000000..93efa172 --- /dev/null +++ b/src/components/MemeHistory.jsx @@ -0,0 +1,139 @@ +import React, { useState, useEffect } from 'react'; +import { downloadMeme } from '../utils/socialShare'; + +const MemeHistory = () => { + const [history, setHistory] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const saved = JSON.parse(localStorage.getItem('memeHistory') || '[]'); + setHistory(saved); + setLoading(false); + }, []); + + const clearHistory = () => { + if (window.confirm('Are you sure you want to clear all history?')) { + localStorage.removeItem('memeHistory'); + setHistory([]); + } + }; + + const deleteMeme = (id) => { + const updated = history.filter(meme => meme.id !== id); + localStorage.setItem('memeHistory', JSON.stringify(updated)); + setHistory(updated); + }; + + if (loading) { + return ( +
+
+
+
+ {[...Array(8)].map((_, i) => ( +
+ ))} +
+
+
+ ); + } + + return ( +
+
+
+

Your Meme History

+ {history.length > 0 && ( + + )} +
+ + {history.length === 0 ? ( +
+
📝
+

No Memes Yet

+

+ Your generated memes will appear here +

+
+ Create Your First Meme + +
+ ) : ( + <> +

+ {history.length} meme{history.length !== 1 ? 's' : ''} in your history +

+ +
+ {history.map((meme) => ( +
+
+ Generated meme +
+ + {/* Action buttons */} +
+ + +
+
+ +
+

+ {meme.template_name} +

+

+ {new Date(meme.created_at).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + })} +

+ + {/* Show meme texts */} + {meme.texts && meme.texts.length > 0 && ( +
+ {meme.texts.slice(0, 2).map((text, i) => ( +

"{text}"

+ ))} +
+ )} +
+
+ ))} +
+ + )} +
+
+ ); +}; + +export default MemeHistory; \ No newline at end of file diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 077abd06..cb2230fd 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -116,6 +116,7 @@ const Navbar = ({ setMeme, searchQuery, setSearchQuery }) => { {[ { path: "/", label: "Home" }, { path: "/dynamic", label: "Dynamic" }, + { path: "/meme-history", label: "My Memes" }, { path: "/about", label: "About" }, { path: "/history", label: "History" } ].map(({ path, label }) => ( @@ -299,6 +300,7 @@ const Navbar = ({ setMeme, searchQuery, setSearchQuery }) => { {[ { path: "/", label: "Home" }, { path: "/dynamic", label: "Dynamic" }, + { path: "/meme-history", label: "My Memes" }, { path: "/about", label: "About" }, { path: "/history", label: "History" }, ].map(({ path, label }) => ( From f012b95c65a110005c206eceeb49554a0cb87a37 Mon Sep 17 00:00:00 2001 From: shivam kumar Date: Fri, 31 Oct 2025 00:54:36 +0530 Subject: [PATCH 3/5] added favourit section --- src/App.jsx | 8 +++- src/Temp.jsx | 24 ++++++++-- src/components/Favorites.jsx | 90 ++++++++++++++++++++++++++++++++++++ src/components/Navbar.jsx | 10 ++-- src/hooks/useFavorites.js | 40 ++++++++++++++++ 5 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 src/components/Favorites.jsx create mode 100644 src/hooks/useFavorites.js diff --git a/src/App.jsx b/src/App.jsx index 3b87fade..3b90b282 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,6 @@ /* eslint-disable no-unused-vars */ // App.jsx -import React from "react"; +import React, { useState } from "react"; import { Route, Routes } from "react-router-dom"; import { ThemeProvider } from "./ThemeContext"; import Home from "./components/Home"; @@ -8,17 +8,21 @@ import "./style.css"; import About from "./components/About"; import History from "./components/History"; import MemeHistory from "./components/MemeHistory"; +import Favorites from "./components/Favorites"; import Dynamicmeme from "./components/Dynamicmeme"; import NewMeme from "./components/NewMeme"; const App = () => { + const [meme, setMeme] = useState(null); + return ( - } /> + } /> } /> } /> } /> + } /> } /> } /> diff --git a/src/Temp.jsx b/src/Temp.jsx index 6b82b1fe..7d5bbeeb 100644 --- a/src/Temp.jsx +++ b/src/Temp.jsx @@ -2,11 +2,13 @@ import React, { useRef, useLayoutEffect } from "react"; import gsap from "gsap"; import memesMeta from "./memesMeta"; +import { useFavorites } from "./hooks/useFavorites"; const Temp = ({ temp, setMeme }) => { const row1 = useRef(null); const row2 = useRef(null); const row3 = useRef(null); + const { toggleFavorite, isFavorite } = useFavorites(); useLayoutEffect(() => { const ctx1 = gsap.context(() => { @@ -45,21 +47,33 @@ const Temp = ({ temp, setMeme }) => { const renderTemplate = (temps) => (
setMeme(temps)} - // 1. Add aria-label to the clickable container aria-label={`Select meme template: ${temps.name}`} - role="button" // Indicates it is an interactive element + role="button" >
+ {/* Heart button */} + + {/* Caption overlay */}
{memesMeta[temps.name]?.captions?.join(", ")} diff --git a/src/components/Favorites.jsx b/src/components/Favorites.jsx new file mode 100644 index 00000000..2f84bd01 --- /dev/null +++ b/src/components/Favorites.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { useFavorites } from '../hooks/useFavorites'; + +const Favorites = ({ setMeme }) => { + const { favorites, toggleFavorite } = useFavorites(); + + if (favorites.length === 0) { + return ( +
+
+

Your Favorite Templates

+ +
+
❤️
+

No Favorites Yet

+

+ Heart your favorite meme templates to see them here +

+ + Browse Templates + +
+
+
+ ); + } + + return ( +
+
+

Your Favorite Templates

+ +

+ {favorites.length} favorite template{favorites.length !== 1 ? 's' : ''} +

+ +
+ {favorites.map((template) => ( +
setMeme(template)} + > +
+ {template.name} + +
+ + {/* Heart button */} + + + {/* Play icon on hover */} +
+
+ 🎭 +
+
+
+ +
+

{template.name}

+
+ 📝 {template.box_count} texts +
+
+
+ ))} +
+
+
+ ); +}; + +export default Favorites; \ No newline at end of file diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index cb2230fd..4977fb61 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -115,10 +115,10 @@ const Navbar = ({ setMeme, searchQuery, setSearchQuery }) => {
{[ { path: "/", label: "Home" }, - { path: "/dynamic", label: "Dynamic" }, + { path: "/favorites", label: "Favorites" }, { path: "/meme-history", label: "My Memes" }, - { path: "/about", label: "About" }, - { path: "/history", label: "History" } + { path: "/dynamic", label: "Dynamic" }, + { path: "/about", label: "About" } ].map(({ path, label }) => ( { > {[ { path: "/", label: "Home" }, - { path: "/dynamic", label: "Dynamic" }, + { path: "/favorites", label: "Favorites" }, { path: "/meme-history", label: "My Memes" }, + { path: "/dynamic", label: "Dynamic" }, { path: "/about", label: "About" }, - { path: "/history", label: "History" }, ].map(({ path, label }) => ( { + const [favorites, setFavorites] = useState([]); + + useEffect(() => { + const saved = JSON.parse(localStorage.getItem('favoriteMemes') || '[]'); + setFavorites(saved); + }, []); + + const toggleFavorite = (meme) => { + const saved = JSON.parse(localStorage.getItem('favoriteMemes') || '[]'); + const exists = saved.find(fav => fav.id === meme.id); + + let updated; + if (exists) { + updated = saved.filter(fav => fav.id !== meme.id); + } else { + updated = [...saved, { + id: meme.id, + name: meme.name, + url: meme.url, + box_count: meme.box_count, + width: meme.width, + height: meme.height, + added_at: new Date().toISOString() + }]; + } + + localStorage.setItem('favoriteMemes', JSON.stringify(updated)); + setFavorites(updated); + return !exists; + }; + + const isFavorite = (memeId) => { + return favorites.some(fav => fav.id === memeId); + }; + + return { favorites, toggleFavorite, isFavorite }; +}; \ No newline at end of file From b6728039ec142e0b2e3d85318f41664b42141ad3 Mon Sep 17 00:00:00 2001 From: shivam kumar Date: Fri, 31 Oct 2025 00:59:08 +0530 Subject: [PATCH 4/5] added a dark/light improved theme --- src/App.jsx | 7 +++++-- src/components/Home.jsx | 4 +++- src/components/Navbar.jsx | 9 ++------- src/components/ThemeToggle.jsx | 28 ++++++++++++++++++++++++++ src/hooks/useTheme.js | 29 +++++++++++++++++++++++++++ src/theme.css | 36 ++++++++++++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 src/components/ThemeToggle.jsx create mode 100644 src/hooks/useTheme.js create mode 100644 src/theme.css diff --git a/src/App.jsx b/src/App.jsx index 3b90b282..0614b64f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,6 +5,7 @@ import { Route, Routes } from "react-router-dom"; import { ThemeProvider } from "./ThemeContext"; import Home from "./components/Home"; import "./style.css"; +import "./theme.css"; import About from "./components/About"; import History from "./components/History"; import MemeHistory from "./components/MemeHistory"; @@ -17,7 +18,8 @@ const App = () => { return ( - +
+ } /> } /> } /> @@ -27,7 +29,8 @@ const App = () => { } /> {/* Define other routes here */} - + +
); }; diff --git a/src/components/Home.jsx b/src/components/Home.jsx index 6cf2444d..754b0138 100644 --- a/src/components/Home.jsx +++ b/src/components/Home.jsx @@ -6,6 +6,7 @@ import Meme from "../Meme"; import Footer from "./Footer"; import "../style.css"; import "../index.css"; +import { useTheme } from "../hooks/useTheme"; const Home = () => { const [temp, setTemp] = useState([]); @@ -15,6 +16,7 @@ const Home = () => { const [selectedCategory, setSelectedCategory] = useState('all'); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); + const { isDark } = useTheme(); const [itemsPerPage] = useState(18); // Fixed 18 items per page @@ -116,7 +118,7 @@ const Home = () => { }; return ( -
+
{ @@ -193,13 +194,7 @@ const Navbar = ({ setMeme, searchQuery, setSearchQuery }) => { - + + ); +}; + +export default ThemeToggle; \ No newline at end of file diff --git a/src/hooks/useTheme.js b/src/hooks/useTheme.js new file mode 100644 index 00000000..8df3362d --- /dev/null +++ b/src/hooks/useTheme.js @@ -0,0 +1,29 @@ +import { useState, useEffect } from 'react'; + +export const useTheme = () => { + const [isDark, setIsDark] = useState(true); + + useEffect(() => { + const saved = localStorage.getItem('theme'); + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + if (saved) { + setIsDark(saved === 'dark'); + } else { + setIsDark(prefersDark); + } + }, []); + + useEffect(() => { + document.documentElement.classList.toggle('light', !isDark); + document.documentElement.classList.toggle('dark', isDark); + }, [isDark]); + + const toggleTheme = () => { + const newTheme = !isDark; + setIsDark(newTheme); + localStorage.setItem('theme', newTheme ? 'dark' : 'light'); + }; + + return { isDark, toggleTheme }; +}; \ No newline at end of file diff --git a/src/theme.css b/src/theme.css new file mode 100644 index 00000000..8c217854 --- /dev/null +++ b/src/theme.css @@ -0,0 +1,36 @@ +/* Theme Variables */ +:root { + --bg-primary: #111827; + --bg-secondary: #1f2937; + --bg-tertiary: #374151; + --text-primary: #ffffff; + --text-secondary: #d1d5db; + --text-muted: #9ca3af; + --border-color: #4b5563; + --accent-color: #ec4899; +} + +.light { + --bg-primary: #ffffff; + --bg-secondary: #f9fafb; + --bg-tertiary: #f3f4f6; + --text-primary: #111827; + --text-secondary: #374151; + --text-muted: #6b7280; + --border-color: #d1d5db; + --accent-color: #ec4899; +} + +/* Apply theme variables */ +.theme-bg-primary { background-color: var(--bg-primary); } +.theme-bg-secondary { background-color: var(--bg-secondary); } +.theme-bg-tertiary { background-color: var(--bg-tertiary); } +.theme-text-primary { color: var(--text-primary); } +.theme-text-secondary { color: var(--text-secondary); } +.theme-text-muted { color: var(--text-muted); } +.theme-border { border-color: var(--border-color); } + +/* Smooth transitions */ +* { + transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; +} \ No newline at end of file From 8abed1fff208b281f0787a0d165b1e3daaffd3a0 Mon Sep 17 00:00:00 2001 From: shivam kumar Date: Fri, 31 Oct 2025 01:10:49 +0530 Subject: [PATCH 5/5] toggle improvede part --- src/components/Home.jsx | 54 ++++++--- src/components/Navbar.jsx | 225 +++++++++++--------------------------- src/theme.css | 12 +- 3 files changed, 110 insertions(+), 181 deletions(-) diff --git a/src/components/Home.jsx b/src/components/Home.jsx index 754b0138..6a3e16d7 100644 --- a/src/components/Home.jsx +++ b/src/components/Home.jsx @@ -6,7 +6,7 @@ import Meme from "../Meme"; import Footer from "./Footer"; import "../style.css"; import "../index.css"; -import { useTheme } from "../hooks/useTheme"; + const Home = () => { const [temp, setTemp] = useState([]); @@ -16,7 +16,16 @@ const Home = () => { const [selectedCategory, setSelectedCategory] = useState('all'); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - const { isDark } = useTheme(); + const [isDark, setIsDark] = useState(() => { + const saved = localStorage.getItem('theme'); + return saved ? saved === 'dark' : true; + }); + + const toggleTheme = () => { + const newTheme = !isDark; + setIsDark(newTheme); + localStorage.setItem('theme', newTheme ? 'dark' : 'light'); + }; const [itemsPerPage] = useState(18); // Fixed 18 items per page @@ -118,11 +127,14 @@ const Home = () => { }; return ( -
+
+
@@ -145,14 +157,16 @@ const Home = () => { className={`flex items-center gap-1 px-3 py-1 rounded-full text-sm transition-all ${ selectedCategory === category.id ? 'bg-pink-600 text-white' - : 'bg-gray-700 text-gray-300 hover:bg-gray-600' + : isDark + ? 'bg-gray-700 text-gray-300 hover:bg-gray-600' + : 'bg-gray-200 text-gray-700 hover:bg-gray-300' }`} > {category.icon} {category.name} ))}
-

+

{filteredMemes.length} memes found {selectedCategory !== 'all' && ` in ${selectedCategory}`}

@@ -181,17 +195,17 @@ const Home = () => {
{[...Array(18)].map((_, i) => (
-
-
-
+
+
+
))}
) : filteredMemes.length === 0 ? (
😅
-

No memes found

-

+

No memes found

+

{searchQuery ? `No results for "${searchQuery}"` : 'No memes in this category'}

) : ( - + )}
)} + - - + +
{/* Mobile menu button */}
- - - {isHomePage && ( )}
- {/* Mobile Search Bar */} + + {/* Mobile Search */} {isMobileSearchOpen && isHomePage && ( -
-
- - {searchQuery ? ( - - ) : ( - - )} -
+
+
)} - {/* Mobile Dropdown Menu */} - {isMobileMenuOpen && ( -
+ {/* Mobile Menu */} + {isMobileMenuOpen && ( +
{[ { path: "/", label: "Home" }, { path: "/favorites", label: "Favorites" }, { path: "/meme-history", label: "My Memes" }, { path: "/dynamic", label: "Dynamic" }, - { path: "/about", label: "About" }, + { path: "/about", label: "About" } ].map(({ path, label }) => ( handleNavigation(path)} - className={`block px-3 py-2 rounded-md text-base font-medium transition-colors duration-200 ${ + className={`block px-3 py-2 rounded-md text-base font-medium ${ location.pathname === path - ? isDarkTheme - ? "bg-gray-800 text-blue-400" - : "bg-blue-100 text-blue-600" - : isDarkTheme - ? "text-gray-300 hover:bg-gray-800 hover:text-white" - : "text-gray-700 hover:bg-gray-100 hover:text-gray-900" + ? isDark ? "bg-gray-800 text-blue-400" : "bg-blue-100 text-blue-600" + : isDark ? "text-gray-300 hover:bg-gray-800" : "text-gray-700 hover:bg-gray-100" }`} > {label} ))} - -
+ +
diff --git a/src/theme.css b/src/theme.css index 8c217854..184f8bac 100644 --- a/src/theme.css +++ b/src/theme.css @@ -12,12 +12,12 @@ .light { --bg-primary: #ffffff; - --bg-secondary: #f9fafb; - --bg-tertiary: #f3f4f6; - --text-primary: #111827; - --text-secondary: #374151; - --text-muted: #6b7280; - --border-color: #d1d5db; + --bg-secondary: #f8fafc; + --bg-tertiary: #e2e8f0; + --text-primary: #1a202c; + --text-secondary: #2d3748; + --text-muted: #4a5568; + --border-color: #cbd5e0; --accent-color: #ec4899; }