From 77c7bddd9464189c417d1d619219f6e85fd489f2 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Thu, 5 Feb 2026 20:28:55 +0530 Subject: [PATCH 01/51] fix; sidebar --- frontend/components/common/sidebar.templ | 258 +++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 frontend/components/common/sidebar.templ diff --git a/frontend/components/common/sidebar.templ b/frontend/components/common/sidebar.templ new file mode 100644 index 0000000000..de0a66c8d9 --- /dev/null +++ b/frontend/components/common/sidebar.templ @@ -0,0 +1,258 @@ +package common + +templ Sidebar() { + + + +} + From eb1df94ff19ed6dcc8f7eb903152c34fdfd4b230 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Thu, 5 Feb 2026 21:47:23 +0530 Subject: [PATCH 02/51] create new layoting --- frontend/.air.toml | 44 ++ .../components/common/banner/ad_banner.templ | 12 +- frontend/components/common/header.templ | 10 +- frontend/components/common/search_bar.templ | 12 +- frontend/components/common/sidebar.templ | 226 +++++++---- .../components/common/theme_switcher.templ | 222 ++++++++++ frontend/components/layouts/base_layout.templ | 29 +- frontend/components/pages/index.templ | 380 ++++++------------ .../components/common/SidebarProfile.tsx | 360 +++++++++++++++++ frontend/frontend/components/pages/Pro.tsx | 22 +- .../components/pages/pro/Bookmarks.tsx | 270 ++++++------- .../frontend/components/pages/pro/Pro.tsx | 2 +- .../frontend/components/search/SearchPage.tsx | 2 +- .../toolComponents/ToolContainer.tsx | 2 +- frontend/frontend/components/ui/card.tsx | 2 +- frontend/frontend/index.ts | 3 + 16 files changed, 1081 insertions(+), 517 deletions(-) create mode 100644 frontend/.air.toml create mode 100644 frontend/frontend/components/common/SidebarProfile.tsx diff --git a/frontend/.air.toml b/frontend/.air.toml new file mode 100644 index 0000000000..32a47c140c --- /dev/null +++ b/frontend/.air.toml @@ -0,0 +1,44 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ./cmd/server" + delay = 2000 + exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules", ".git", "bin", "public", "db", "scripts", "astro_freedevtools", "frontend", "search-index", "man-pages-stuff", "docs", "md", "nginx", "fdtdb-cli"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/frontend/components/common/banner/ad_banner.templ b/frontend/components/common/banner/ad_banner.templ index e00fd4bcad..cc434ad8b9 100644 --- a/frontend/components/common/banner/ad_banner.templ +++ b/frontend/components/common/banner/ad_banner.templ @@ -154,10 +154,10 @@ templ AdBanner() { window.addEventListener('pro-status-changed', checkAndHideLiveReviewBanner); })(); -
+
-
+ +
+ @SidebarThemeSwitcher()
+ + + } diff --git a/frontend/components/common/theme_switcher.templ b/frontend/components/common/theme_switcher.templ index 5c50cbd650..6390f59d08 100644 --- a/frontend/components/common/theme_switcher.templ +++ b/frontend/components/common/theme_switcher.templ @@ -114,3 +114,225 @@ templ ThemeSwitcher() { })(); } + +templ SidebarThemeSwitcher() { + + + +} diff --git a/frontend/components/layouts/base_layout.templ b/frontend/components/layouts/base_layout.templ index 040d4ed0e7..d085599948 100644 --- a/frontend/components/layouts/base_layout.templ +++ b/frontend/components/layouts/base_layout.templ @@ -279,21 +279,15 @@ templ BaseLayout(props BaseLayoutProps) { - - - Skip to main content - - - Skip to search - -
- if props.ShowHeader { - @common.Header() - } - -
-
- + if props.ShowHeader { + @common.Header() + } +
+ @common.Sidebar() +
+
+
+
if !props.HideBanner { @banner.AdBanner() @@ -393,9 +387,10 @@ templ BaseLayout(props BaseLayoutProps) { } }); -
+
- @common.Footer() + @common.Footer() +
diff --git a/frontend/components/pages/index.templ b/frontend/components/pages/index.templ index 8e1841f8bd..4e100241b5 100644 --- a/frontend/components/pages/index.templ +++ b/frontend/components/pages/index.templ @@ -3,280 +3,138 @@ package pages import ( "fdt-templ/components/banner" "fdt-templ/components/layouts" - "fdt-templ/internal/config" banner_db "fdt-templ/internal/db/banner" ) templ Index(bannerData *banner_db.Banner) { @layouts.BaseLayout(layouts.BaseLayoutProps{ - Title: "Free DevTools - Essential Developer Utilities Online | No Registration Required", - Description: "Essential developer utilities including JSON tools, password generator, Dockerfile linter, and more. No registration required, just free tools for developers.", + Title: "Free DevTools: Search Engine for Developer Resources", + Description: "Essential Developer Utilities Online. Search Fast. Bookmark 350k+ Resources.", Canonical: "https://hexmos.com/freedevtools/", ShowHeader: true, }) { -
-

- Free DevTools - Essential Developer Utilities Online | No Registration Required -

-
- +
+ +
@banner.Banner(bannerData, "https://hexmos.com/freedevtools/")
-
- - -
-
đŸ› ī¸
-

- Tools -

-

- Essential developer utilities including JSON tools, password generator, Dockerfile linter, and more. -

-
-
- - - -
-
📚
-

- TLDR -

-

- Comprehensive documentation for command-line tools across different platforms including Linux, macOS, Windows, Android, and more. -

-
-
- - - -
-
📝
-

- Cheatsheets -

-

- Concise, easy-to-scan reference pages that summarize commands, syntax, and key concepts for faster learning and recall. -

-
-
- - - -
-
🚀
-

- Emojis -

-

- Browse thousands of emojis with categories, meanings, images, shortcodes and more. -

-
-
- - - -
-
🎨
-

- Free SVG Icons -

-

- Download 50000+ of free SVG icons instantly. Edit colors, add backgrounds, and customize vector graphics for your projects. No signup, no registration, just download high quality -

-
-
- - - -
-
đŸ–ŧī¸
-

- Free PNG Icons -

-

- Download 50000+ of free PNG icons instantly. Edit colors, add backgrounds, and customize vector graphics for your projects. No signup, no registration, just download high quality -

-
-
- - - if config.GetAdsEnabled() && config.GetEnabledAdTypes("index")["google"] { - -
- - - -
-
- - - -
-
- - - -
- } - - - -
-
đŸ•šī¸
-

- MCP Directory -

-

- Browse 15000+ Model Context Protocol repositories for AI agents. Find MCP servers, tools, and clients by category with detailed documentation and usage examples. -

-
-
- - - - -
-
📖
-

- Man Pages -

-

- Browse comprehensive UNIX/Linux manual pages with detailed documentation. Find system commands, library functions, file formats, and system administration guides organized by category. -

-
-
- - - -
-
âš™ī¸
-

- Installerpedia -

-

- A curated collection of installation guides and commands for software, packages, and tools, making setup fast, easy, and reliable. -

-
-
+
+

+ Search Engine for Developer Resources +

+ +

+ Essential Developer Utilities Online +

+ +

+ Search Fast. Bookmark 350k+ Resources +

+ + +
+ @banner.Banner(bannerData, "https://hexmos.com/freedevtools/") +
+ + + + 🎉 + The new Free DevTools Pro for supporters is now live! + + + +
+ + +
+ + + + + +
-
+
+
+ } - } diff --git a/frontend/frontend/components/common/SidebarProfile.tsx b/frontend/frontend/components/common/SidebarProfile.tsx new file mode 100644 index 0000000000..0aea34154b --- /dev/null +++ b/frontend/frontend/components/common/SidebarProfile.tsx @@ -0,0 +1,360 @@ +import { useSignOutDialog } from '@/hooks/useSignOutDialog'; +import { getLicences } from '@/lib/api'; +import React, { useEffect, useState } from 'react'; + +// Get pro status from cookie +function getProStatusFromCookie(): boolean { + if (typeof window === 'undefined') return false; + const cookies = document.cookie.split('; '); + for (const cookie of cookies) { + const [name, value] = cookie.split('='); + if (name.trim() === 'hexmos-one-fdt-p-status' && value === 'true') { + return true; + } + } + return false; +} + +// Get JWT from localStorage +function getJWT(): string | null { + if (typeof window === 'undefined') return null; + return localStorage.getItem('hexmos-one'); +} + +// Extract user ID from JWT payload +function extractUserIdFromJWT(jwt: string): string | null { + try { + const parts = jwt.split('.'); + if (parts.length !== 3) return null; + + const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/'))); + return payload.uId || payload.parseUserId || payload.userId || payload.sub || null; + } catch (e) { + console.error('Failed to extract user ID from JWT:', e); + return null; + } +} + +// User info interface +interface UserInfo { + firstName?: string; + lastName?: string; + email?: string; +} + +// Get user info from localStorage +function getUserInfo(): UserInfo | null { + if (typeof window === 'undefined') return null; + const stored = localStorage.getItem('hexmos-user-info'); + if (!stored) return null; + try { + return JSON.parse(stored) as UserInfo; + } catch { + return null; + } +} + +// Format user display name +function formatUserName(info: UserInfo | null): string { + if (!info) return ''; + + if (info.firstName && info.lastName) { + return `${info.firstName} ${info.lastName}`; + } else if (info.firstName) { + return info.firstName; + } else if (info.email) { + return info.email; + } + + return ''; +} + +// Set JWT in localStorage and cookies +function setJWT(jwt: string): void { + if (typeof window === 'undefined') return; + localStorage.setItem('hexmos-one', jwt); + + // Extract user ID and set hexmos-one-id cookie + const userId = extractUserIdFromJWT(jwt); + + // Set cookies for SSR compatibility and cross-domain access + const isSecure = window.location.protocol === 'https:'; + const isProduction = window.location.hostname.includes('hexmos.com'); + const domain = isProduction ? '.hexmos.com' : 'localhost'; + const sameSite = isProduction ? 'None' : 'Lax'; + + // Set hexmos-one cookie + const hexmosCookieOptions = `path=/; SameSite=${sameSite}${isSecure ? '; Secure' : ''}${domain ? `; domain=${domain}` : ''}`; + document.cookie = `hexmos-one=${jwt}; ${hexmosCookieOptions}`; + + // Set hexmos-one-id cookie + if (userId) { + const pIdCookieOptions = `path=/; SameSite=${sameSite}${isSecure ? '; Secure' : ''}${domain ? `; domain=${domain}` : ''}`; + document.cookie = `hexmos-one-id=${userId}; ${pIdCookieOptions}`; + } + + window.dispatchEvent(new Event('jwt-changed')); +} + +// Handle signin callback - parse ?data= parameter +function handleSigninCallback(): string | null { + if (typeof window === 'undefined') return null; + + const urlParams = new URLSearchParams(window.location.search); + const dataParam = urlParams.get('data'); + + if (dataParam) { + try { + const decoded = decodeURIComponent(dataParam); + const parsed = JSON.parse(decoded); + const jwt = parsed?.result?.jwt; + const userData = parsed?.result?.data; + + if (jwt) { + setJWT(jwt); + + // Extract and store user info + if (userData) { + const userInfo: UserInfo = {}; + if (userData.first_name) userInfo.firstName = userData.first_name; + if (userData.last_name) userInfo.lastName = userData.last_name; + if (userData.email) userInfo.email = userData.email; + + if (userInfo.firstName || userInfo.lastName || userInfo.email) { + localStorage.setItem('hexmos-user-info', JSON.stringify(userInfo)); + } + } + + const cleanUrl = window.location.pathname; + window.history.replaceState({}, '', cleanUrl); + return jwt; + } + } catch (e) { + console.error('Failed to parse signin callback data:', e); + } + } + return null; +} + +function handleAutoLogin(): void { + if (typeof window === 'undefined') return; + const cookies = document.cookie.split('; '); + for (const cookie of cookies) { + const [name, value] = cookie.split('='); + if (name.trim() === 'hexmos-one') { + const cookieJWT = decodeURIComponent(value); + if (cookieJWT) { + setJWT(cookieJWT); + } + } + } +} + +function getInitials(userName: string): string { + if (!userName) return ''; + const parts = userName.trim().split(' '); + if (parts.length >= 2) { + return (parts[0][0] + parts[1][0]).toUpperCase(); + } + return userName.substring(0, 2).toUpperCase(); +} + +const SidebarProfile: React.FC = () => { + const [hasJWT, setHasJWT] = useState(false); + const [isPro, setIsPro] = useState(false); + const [userName, setUserName] = useState(''); + const { handleSignOut, SignOutDialog } = useSignOutDialog(); + + useEffect(() => { + // Load user info on mount + const userInfo = getUserInfo(); + setUserName(formatUserName(userInfo)); + + // Check for signin callback first + const jwt = handleSigninCallback(); + if (jwt) { + setHasJWT(true); + // Update user name after signin callback + const updatedUserInfo = getUserInfo(); + setUserName(formatUserName(updatedUserInfo)); + + // Fetch licence status after signin to set cookie and update pro status + getLicences().then((result) => { + if (result.success) { + // getLicences() automatically sets the cookie + // Check the cookie for pro status + const proStatus = getProStatusFromCookie(); + setIsPro(proStatus); + } else { + setIsPro(false); + } + }).catch((error) => { + console.error('[SidebarProfile] Error fetching licences after signin:', error); + setIsPro(false); + }); + return; + } + + // Handle auto-login from cookies + const autoLoginJWT = getJWT(); + if (!autoLoginJWT) { + handleAutoLogin(); + } + + // Check if already signed in + const existingJWT = getJWT(); + setHasJWT(!!existingJWT); + + // Load user info if already signed in + if (existingJWT) { + const userInfo = getUserInfo(); + setUserName(formatUserName(userInfo)); + } + + if (existingJWT) { + // Check cookie first - this is the source of truth, no API call needed + const proStatusFromCookie = getProStatusFromCookie(); + setIsPro(proStatusFromCookie); + + // Only fetch from API if cookie is missing + if (!proStatusFromCookie) { + getLicences().then((result) => { + if (result.success) { + // getLicences sets the cookie, so check it again + const proStatus = getProStatusFromCookie(); + setIsPro(proStatus); + } else { + setIsPro(false); + } + }).catch((error) => { + console.error('[SidebarProfile] Error fetching licences:', error); + setIsPro(false); + }); + } + } else { + // No JWT, definitely not pro + setIsPro(false); + } + }, []); + + useEffect(() => { + const handleJWTChange = async () => { + const jwt = getJWT(); + setHasJWT(!!jwt); + + // Update user name when JWT changes + const userInfo = getUserInfo(); + setUserName(formatUserName(userInfo)); + + if (jwt) { + // When JWT changes (e.g., after sign-in), check cookie first + const proStatusFromCookie = getProStatusFromCookie(); + setIsPro(proStatusFromCookie); + + // Only fetch from API if cookie is missing + if (!proStatusFromCookie) { + try { + const result = await getLicences(); + if (result.success) { + // getLicences sets the cookie, so check it again + const proStatus = getProStatusFromCookie(); + setIsPro(proStatus); + } else { + setIsPro(false); + } + } catch (error) { + console.error('[SidebarProfile] Error fetching licences on JWT change:', error); + setIsPro(false); + } + } + } else { + // No JWT, definitely not pro + setIsPro(false); + } + }; + + const handleLicenceChange = () => { + // Check cookie when licence changes + const proStatus = getProStatusFromCookie(); + setIsPro(proStatus); + }; + + // Listen for custom events + window.addEventListener('jwt-changed', handleJWTChange); + window.addEventListener('active-licence-changed', handleLicenceChange); + window.addEventListener('storage', handleJWTChange); + + return () => { + window.removeEventListener('jwt-changed', handleJWTChange); + window.removeEventListener('active-licence-changed', handleLicenceChange); + window.removeEventListener('storage', handleJWTChange); + }; + }, []); + + const handleSignIn = () => { + const currentUrl = window.location.href; + const signinUrl = `https://hexmos.com/signin?app=livereview&appRedirectURI=${encodeURIComponent(currentUrl)}`; + window.location.href = signinUrl; + }; + + return ( +
+ {hasJWT ? ( + <> +
+
+
+ {getInitials(userName)} +
+
+
+
+ {userName || 'User'} +
+
+ {isPro ? 'PRO' : 'FREE'} +
+
+
+ + + ) : ( + + )} + +
+ ); +}; + +export default SidebarProfile; + diff --git a/frontend/frontend/components/pages/Pro.tsx b/frontend/frontend/components/pages/Pro.tsx index b7afdd1bc0..57fb9824b2 100644 --- a/frontend/frontend/components/pages/Pro.tsx +++ b/frontend/frontend/components/pages/Pro.tsx @@ -7,16 +7,16 @@ function extractCategoryFromURL(urlStr: string): string { try { const url = new URL(urlStr); const path = url.pathname; - + // Remove leading /freedevtools/ if present const cleanPath = path.replace(/^\/freedevtools\//, ''); - + // Extract first segment as category const segments = cleanPath.split('/').filter(s => s); if (segments.length === 0) return 'page'; - + const category = segments[0]; - + // Normalize category names const categoryMap: Record = { 'emojis': 'emoji', @@ -28,7 +28,7 @@ function extractCategoryFromURL(urlStr: string): string { 'man-pages': 'Man Pages', 'installerpedia': 'Installerpedia', }; - + return categoryMap[category] || category.charAt(0).toUpperCase() + category.slice(1); } catch { return 'page'; @@ -40,16 +40,16 @@ function extractItemName(urlStr: string): string { try { const url = new URL(urlStr); const path = url.pathname; - + // Remove leading /freedevtools/ if present const cleanPath = path.replace(/^\/freedevtools\//, ''); - + // Extract last segment as item name const segments = cleanPath.split('/').filter(s => s); if (segments.length === 0) return 'this page'; - + const itemName = segments[segments.length - 1]; - + // Remove file extension if present return itemName.replace(/\.(svg|png|md|txt)$/i, '') || 'this page'; } catch { @@ -66,7 +66,7 @@ const Pro: React.FC = () => { const urlParams = new URLSearchParams(window.location.search); const feature = urlParams.get('feature'); const source = urlParams.get('source'); - + if (feature === 'bookmark' && source) { const decodedSource = decodeURIComponent(source); const category = extractCategoryFromURL(decodedSource); @@ -77,7 +77,7 @@ const Pro: React.FC = () => { return ( <> -
+
{bookmarkInfo && (
diff --git a/frontend/frontend/components/pages/pro/Bookmarks.tsx b/frontend/frontend/components/pages/pro/Bookmarks.tsx index 0b937dee91..e297fd0e23 100644 --- a/frontend/frontend/components/pages/pro/Bookmarks.tsx +++ b/frontend/frontend/components/pages/pro/Bookmarks.tsx @@ -23,7 +23,7 @@ const Bookmarks: React.FC = () => { } return; } - + loadBookmarks(); }, []); @@ -50,13 +50,13 @@ const Bookmarks: React.FC = () => { try { setLoading(true); const result = await getAllBookmarks(); - + // Check if redirect is needed (non-pro user) if (result.requiresPro && result.redirect) { // Redirect will be handled by getAllBookmarks, but we can also check here return; } - + if (result.success) { setBookmarks(result.bookmarks); } @@ -92,7 +92,7 @@ const Bookmarks: React.FC = () => { return sortedCategories.map((category) => ({ category, - bookmarks: grouped[category].sort((a, b) => + bookmarks: grouped[category].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime() ), })); @@ -102,7 +102,7 @@ const Bookmarks: React.FC = () => { // Find the bookmark to get its name const bookmark = bookmarks.find((b) => b.url === url); const itemName = bookmark ? getPageTitleFromUrl(bookmark.url) : 'bookmark'; - + const result = await toggleBookmark(url); if (result.success) { // Show undo notification @@ -112,7 +112,7 @@ const Bookmarks: React.FC = () => { itemName, countdown: 5, }); - + // Reload bookmarks loadBookmarks(); } @@ -120,7 +120,7 @@ const Bookmarks: React.FC = () => { const handleUndo = async () => { if (!undoInfo) return; - + const result = await toggleBookmark(undoInfo.url); if (result.success) { setUndoInfo(null); @@ -174,18 +174,18 @@ const Bookmarks: React.FC = () => { try { const urlObj = new URL(url); let path = urlObj.pathname; - + // Remove trailing slash path = path.replace(/\/$/, ''); - + // For PNG icons, replace png_icons with svg_icons if (category === 'png_icons') { path = path.replace('/png_icons/', '/svg_icons/'); } - + // Add .svg extension path = path + '.svg'; - + // Reconstruct URL return `${urlObj.origin}${path}`; } catch { @@ -195,7 +195,7 @@ const Bookmarks: React.FC = () => { if (loading) { return ( -
+
@@ -234,137 +234,137 @@ const Bookmarks: React.FC = () => {
)} -
+
-

- My Bookmarks -

-

- {bookmarks.length} {bookmarks.length === 1 ? 'bookmark' : 'bookmarks'} saved -

-
- - {/* Search Bar */} -
- setSearchQuery(e.target.value)} - className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" - /> -
- - {bookmarks.length === 0 ? ( -
- -

- No bookmarks yet -

-

- Start bookmarking pages to see them here -

-
- ) : groupedBookmarks.length === 0 ? ( -
+

+ My Bookmarks +

- No bookmarks match your search query + {bookmarks.length} {bookmarks.length === 1 ? 'bookmark' : 'bookmarks'} saved

- ) : ( -
- {groupedBookmarks.map(({ category, bookmarks: categoryBookmarks }) => ( -
-

- {getCategoryDisplayName(category)} - - ({categoryBookmarks.length}) - -

-
- {categoryBookmarks.map((bookmark) => { - const iconPreviewUrl = getIconPreviewUrl(bookmark.url, bookmark.category); - - return ( - -
- {iconPreviewUrl && ( -
- Icon preview { - // Hide image on error - (e.target as HTMLImageElement).style.display = 'none'; - }} - /> + + {/* Search Bar */} +
+ setSearchQuery(e.target.value)} + className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ + {bookmarks.length === 0 ? ( +
+ +

+ No bookmarks yet +

+

+ Start bookmarking pages to see them here +

+
+ ) : groupedBookmarks.length === 0 ? ( +
+

+ No bookmarks match your search query +

+
+ ) : ( +
+ {groupedBookmarks.map(({ category, bookmarks: categoryBookmarks }) => ( +
+

+ {getCategoryDisplayName(category)} + + ({categoryBookmarks.length}) + +

+
+ {categoryBookmarks.map((bookmark) => { + const iconPreviewUrl = getIconPreviewUrl(bookmark.url, bookmark.category); + + return ( + +
+ {iconPreviewUrl && ( +
+ Icon preview { + // Hide image on error + (e.target as HTMLImageElement).style.display = 'none'; + }} + /> +
+ )} +
+ + {getPageTitleFromUrl(bookmark.url)} + +

+ {bookmark.url} +

+

+ Bookmarked on {new Date(bookmark.created_at).toLocaleDateString()} +

- )} -
- - {getPageTitleFromUrl(bookmark.url)} - -

- {bookmark.url} -

-

- Bookmarked on {new Date(bookmark.created_at).toLocaleDateString()} -

-
- -
- ); - })} + + + + ); + })} +
-
- ))} -
- )} + ))} +
+ )}
); diff --git a/frontend/frontend/components/pages/pro/Pro.tsx b/frontend/frontend/components/pages/pro/Pro.tsx index 6b82e69d01..39ba6f3bf0 100644 --- a/frontend/frontend/components/pages/pro/Pro.tsx +++ b/frontend/frontend/components/pages/pro/Pro.tsx @@ -160,7 +160,7 @@ const Pro: React.FC = () => { return ( <> -
+
{bookmarkInfo && (
diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index a5f2d4565a..e8d39d19b3 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -646,7 +646,7 @@ const SearchPage: React.FC = () => { }; return ( -
+
{/* SearchInfoHeader */}
diff --git a/frontend/frontend/components/toolComponents/ToolContainer.tsx b/frontend/frontend/components/toolComponents/ToolContainer.tsx index 90efafecea..4cf42a701d 100644 --- a/frontend/frontend/components/toolComponents/ToolContainer.tsx +++ b/frontend/frontend/components/toolComponents/ToolContainer.tsx @@ -5,7 +5,7 @@ interface ToolContainerProps { } const ToolContainer: React.FC = ({ children }) => { - return
{children}
; + return
{children}
; }; export default ToolContainer; diff --git a/frontend/frontend/components/ui/card.tsx b/frontend/frontend/components/ui/card.tsx index 2c5152d70a..69ab01e829 100644 --- a/frontend/frontend/components/ui/card.tsx +++ b/frontend/frontend/components/ui/card.tsx @@ -72,7 +72,7 @@ const CardFooter = React.forwardRef< >(({ className, ...props }, ref) => (
)); diff --git a/frontend/frontend/index.ts b/frontend/frontend/index.ts index 620a9d44b8..90d4f0cbf7 100644 --- a/frontend/frontend/index.ts +++ b/frontend/frontend/index.ts @@ -128,6 +128,9 @@ const toolLoaders: Record void> = { "bookmarkIcon": (e) => { renderDynamic(e, () => import('./components/common/BookmarkIcon')); }, + "sidebarProfile": (e) => { + renderDynamic(e, () => import('./components/common/SidebarProfile')); + }, }; // Expose render functions globally From 69568e89549f27b9ac2b3029dbc90c0b96f66409 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Fri, 6 Feb 2026 19:24:40 +0530 Subject: [PATCH 03/51] 30 items --- frontend/internal/controllers/png_icons/handlers.go | 2 +- frontend/internal/controllers/svg_icons/handlers.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/internal/controllers/png_icons/handlers.go b/frontend/internal/controllers/png_icons/handlers.go index 93d600ce4e..8b8937aeb5 100644 --- a/frontend/internal/controllers/png_icons/handlers.go +++ b/frontend/internal/controllers/png_icons/handlers.go @@ -180,7 +180,7 @@ func HandleCategory(w http.ResponseWriter, r *http.Request, db *png_icons_db.DB, if page < 1 { page = 1 } - limit := 10 // Show 10 icons per page + limit := 30 // Show 30 icons per page offset := (page - 1) * limit icons, err := db.GetIconsByCluster(cluster.SourceFolder, &categoryName, limit, offset) diff --git a/frontend/internal/controllers/svg_icons/handlers.go b/frontend/internal/controllers/svg_icons/handlers.go index 5f65259a89..ba723b44ce 100644 --- a/frontend/internal/controllers/svg_icons/handlers.go +++ b/frontend/internal/controllers/svg_icons/handlers.go @@ -189,7 +189,7 @@ func HandleCategory(w http.ResponseWriter, r *http.Request, db *svg_icons_db.DB, if page < 1 { page = 1 } - limit := 10 // Show 10 icons per page + limit := 30 // Show 30 icons per page offset := (page - 1) * limit icons, err := db.GetIconsByCluster(cluster.SourceFolder, &categoryName, limit, offset) From 1f14949346d0830d953d9a0165bc8046a416267c Mon Sep 17 00:00:00 2001 From: lovestaco Date: Fri, 6 Feb 2026 20:02:43 +0530 Subject: [PATCH 04/51] fix: pagination and layout issues --- .../layouts/tool_detail_layout.templ | 2 +- .../pages/cheatsheets/category.templ | 2 +- .../pages/cheatsheets/cheatsheet.templ | 2 +- .../components/pages/cheatsheets/index.templ | 2 +- .../pages/emojis/apple/category.templ | 2 +- .../components/pages/emojis/apple/emoji.templ | 2 +- .../components/pages/emojis/apple/index.templ | 2 +- .../components/pages/emojis/category.templ | 2 +- frontend/components/pages/emojis/credits.templ | 2 +- .../pages/emojis/discord/category.templ | 2 +- .../pages/emojis/discord/emoji.templ | 2 +- .../pages/emojis/discord/index.templ | 2 +- frontend/components/pages/emojis/emoji.templ | 2 +- frontend/components/pages/emojis/index.templ | 2 +- .../pages/installerpedia/category.templ | 2 +- .../pages/installerpedia/index.templ | 2 +- .../components/pages/installerpedia/page.templ | 4 ++-- .../components/pages/man_pages/category.templ | 2 +- .../components/pages/man_pages/credits.templ | 2 +- .../components/pages/man_pages/index.templ | 2 +- frontend/components/pages/man_pages/page.templ | 2 +- .../pages/man_pages/subcategory.templ | 2 +- frontend/components/pages/mcp/category.templ | 2 +- frontend/components/pages/mcp/credits.templ | 2 +- frontend/components/pages/mcp/index.templ | 2 +- frontend/components/pages/mcp/repo.templ | 2 +- .../components/pages/png_icons/category.templ | 2 +- .../components/pages/png_icons/credits.templ | 2 +- frontend/components/pages/png_icons/icon.templ | 2 +- .../components/pages/png_icons/index.templ | 2 +- .../components/pages/svg_icons/category.templ | 2 +- .../components/pages/svg_icons/credits.templ | 2 +- frontend/components/pages/svg_icons/icon.templ | 2 +- .../components/pages/svg_icons/index.templ | 2 +- .../pages/t/anthropic_token_counter.templ | 2 +- .../components/pages/t/base64_encoder.templ | 2 +- .../components/pages/t/character_count.templ | 2 +- .../components/pages/t/chmod_calculator.templ | 2 +- frontend/components/pages/t/cron_tester.templ | 2 +- .../pages/t/css_inliner_for_email.templ | 2 +- .../pages/t/css_units_converter.templ | 2 +- frontend/components/pages/t/csv_to_json.templ | 2 +- .../components/pages/t/curl_to_js_fetch.templ | 2 +- .../pages/t/date_time_converter.templ | 2 +- .../pages/t/deepseek_token_counter.templ | 2 +- frontend/components/pages/t/diff_checker.templ | 2 +- .../components/pages/t/dockerfile_linter.templ | 2 +- .../pages/t/env_to_netlify_toml.templ | 2 +- frontend/components/pages/t/faker.templ | 2 +- .../components/pages/t/har_file_viewer.templ | 2 +- .../components/pages/t/hash_generator.templ | 2 +- .../components/pages/t/html_to_markdown.templ | 2 +- .../components/pages/t/image_to_base64.templ | 2 +- frontend/components/pages/t/index.templ | 4 ++-- .../pages/t/json_to_csv_converter.templ | 2 +- frontend/components/pages/t/json_to_xml.templ | 2 +- frontend/components/pages/t/json_to_yaml.templ | 2 +- frontend/components/pages/t/json_tool.templ | 2 +- frontend/components/pages/t/jwt_parser.templ | 2 +- .../pages/t/llama_token_counter.templ | 2 +- .../pages/t/lorem_ipsum_generator.templ | 2 +- .../pages/t/mac_address_generator.templ | 2 +- .../pages/t/mac_address_lookup.templ | 2 +- .../pages/t/markdown_to_html_converter.templ | 2 +- .../components/pages/t/og_meta_generator.templ | 2 +- .../pages/t/openai_cost_calculator.templ | 2 +- .../pages/t/openai_token_counter.templ | 2 +- .../pages/t/password_generator.templ | 2 +- .../components/pages/t/qrcode_generator.templ | 2 +- .../pages/t/query_params_to_json.templ | 2 +- frontend/components/pages/t/regex_tester.templ | 2 +- frontend/components/pages/t/rgb_to_hex.templ | 2 +- .../pages/t/rsa_key_pair_generator.templ | 2 +- .../components/pages/t/slugify_string.templ | 2 +- frontend/components/pages/t/sql_minifier.templ | 2 +- .../pages/t/svg_placeholder_generator.templ | 2 +- frontend/components/pages/t/svg_viewer.templ | 2 +- .../components/pages/t/user_agent_parser.templ | 2 +- .../components/pages/t/uuid_generator.templ | 2 +- .../components/pages/t/webp_converter.templ | 2 +- .../components/pages/t/xml_formatter.templ | 2 +- frontend/components/pages/t/xml_to_json.templ | 2 +- frontend/components/pages/t/yaml_to_json.templ | 2 +- frontend/components/pages/t/yaml_to_toml.templ | 2 +- .../components/pages/t/zstd_compress.templ | 2 +- frontend/components/pages/tldr/command.templ | 2 +- frontend/components/pages/tldr/index.templ | 2 +- frontend/components/pages/tldr/platform.templ | 2 +- .../internal/controllers/png_icons/handlers.go | 18 +++++++++++++++++- .../internal/controllers/svg_icons/handlers.go | 18 +++++++++++++++++- frontend/package.json | 1 - 91 files changed, 124 insertions(+), 93 deletions(-) diff --git a/frontend/components/layouts/tool_detail_layout.templ b/frontend/components/layouts/tool_detail_layout.templ index 7f1f3903b5..49b6d44979 100644 --- a/frontend/components/layouts/tool_detail_layout.templ +++ b/frontend/components/layouts/tool_detail_layout.templ @@ -23,7 +23,7 @@ templ ToolDetailLayout(props ToolDetailLayoutProps) { SoftwareVersion: props.Tool.SoftwareVersion, Features: props.Tool.Features, }) { -
+
{ children... }
diff --git a/frontend/components/pages/cheatsheets/category.templ b/frontend/components/pages/cheatsheets/category.templ index 28da28b73c..6d13d560a4 100644 --- a/frontend/components/pages/cheatsheets/category.templ +++ b/frontend/components/pages/cheatsheets/category.templ @@ -22,7 +22,7 @@ type CategoryData struct { templ Category(data CategoryData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@components.Breadcrumb(data.BreadcrumbItems) diff --git a/frontend/components/pages/cheatsheets/cheatsheet.templ b/frontend/components/pages/cheatsheets/cheatsheet.templ index bb3fb57050..abc46f2c75 100644 --- a/frontend/components/pages/cheatsheets/cheatsheet.templ +++ b/frontend/components/pages/cheatsheets/cheatsheet.templ @@ -68,7 +68,7 @@ templ Cheatsheet(data CheatsheetData) { color: #e5e7eb; } -
+
@components.Breadcrumb(data.BreadcrumbItems) diff --git a/frontend/components/pages/cheatsheets/index.templ b/frontend/components/pages/cheatsheets/index.templ index 0bf46926b2..448269cdda 100644 --- a/frontend/components/pages/cheatsheets/index.templ +++ b/frontend/components/pages/cheatsheets/index.templ @@ -37,7 +37,7 @@ type IndexData struct { templ Index(data IndexData) { @layouts.BaseLayout(data.LayoutProps) { -
+
diff --git a/frontend/components/pages/emojis/apple/category.templ b/frontend/components/pages/emojis/apple/category.templ index fa83ee7089..142706df8c 100644 --- a/frontend/components/pages/emojis/apple/category.templ +++ b/frontend/components/pages/emojis/apple/category.templ @@ -68,7 +68,7 @@ templ emojiCard(emoji *emojis_db.EmojiData, latestImage *string) { templ Category(data CategoryData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/emojis/apple/emoji.templ b/frontend/components/pages/emojis/apple/emoji.templ index 08bfdce4e5..2a0a2a7911 100644 --- a/frontend/components/pages/emojis/apple/emoji.templ +++ b/frontend/components/pages/emojis/apple/emoji.templ @@ -49,7 +49,7 @@ func cleanDescription(text string) string { templ Emoji(data EmojiData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/emojis/apple/index.templ b/frontend/components/pages/emojis/apple/index.templ index 9c6fd8c784..598e426a7d 100644 --- a/frontend/components/pages/emojis/apple/index.templ +++ b/frontend/components/pages/emojis/apple/index.templ @@ -28,7 +28,7 @@ func getAppleCategoryIcon(category string, icons map[string]string) string { templ Index(data IndexData) { @layouts.BaseLayout(data.LayoutProps) { -
+
diff --git a/frontend/components/pages/emojis/category.templ b/frontend/components/pages/emojis/category.templ index 1235797d0e..347fd1e18b 100644 --- a/frontend/components/pages/emojis/category.templ +++ b/frontend/components/pages/emojis/category.templ @@ -25,7 +25,7 @@ type CategoryData struct { templ Category(data CategoryData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/emojis/credits.templ b/frontend/components/pages/emojis/credits.templ index 984c231a5d..2a3f34350a 100644 --- a/frontend/components/pages/emojis/credits.templ +++ b/frontend/components/pages/emojis/credits.templ @@ -8,7 +8,7 @@ type CreditsData struct { templ Credits(data CreditsData) { @layouts.BaseLayout(data.LayoutProps) { -
+
diff --git a/frontend/components/pages/emojis/discord/category.templ b/frontend/components/pages/emojis/discord/category.templ index a5483f94ee..48e0efbc4e 100644 --- a/frontend/components/pages/emojis/discord/category.templ +++ b/frontend/components/pages/emojis/discord/category.templ @@ -68,7 +68,7 @@ templ emojiCard(emoji *emojis_db.EmojiData, latestImage *string) { templ Category(data CategoryData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/emojis/discord/emoji.templ b/frontend/components/pages/emojis/discord/emoji.templ index 7390c01dd1..e913305f13 100644 --- a/frontend/components/pages/emojis/discord/emoji.templ +++ b/frontend/components/pages/emojis/discord/emoji.templ @@ -49,7 +49,7 @@ func cleanDescription(text string) string { templ Emoji(data EmojiData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/emojis/discord/index.templ b/frontend/components/pages/emojis/discord/index.templ index 996f044ace..708ebf5e83 100644 --- a/frontend/components/pages/emojis/discord/index.templ +++ b/frontend/components/pages/emojis/discord/index.templ @@ -29,7 +29,7 @@ func getDiscordCategoryIcon(category string, icons map[string]string) string { templ Index(data IndexData) { @layouts.BaseLayout(data.LayoutProps) { -
+
diff --git a/frontend/components/pages/emojis/emoji.templ b/frontend/components/pages/emojis/emoji.templ index 109d45d886..cfcf890f8d 100644 --- a/frontend/components/pages/emojis/emoji.templ +++ b/frontend/components/pages/emojis/emoji.templ @@ -46,7 +46,7 @@ func cleanDescription(text string) string { templ Emoji(data EmojiData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical) diff --git a/frontend/components/pages/emojis/index.templ b/frontend/components/pages/emojis/index.templ index b42e5e86b8..3d402f2ac7 100644 --- a/frontend/components/pages/emojis/index.templ +++ b/frontend/components/pages/emojis/index.templ @@ -20,7 +20,7 @@ type IndexData struct { templ Index(data IndexData) { @layouts.BaseLayout(data.LayoutProps) { -
+
diff --git a/frontend/components/pages/installerpedia/category.templ b/frontend/components/pages/installerpedia/category.templ index d8f0c450dd..5ba53b7ba8 100644 --- a/frontend/components/pages/installerpedia/category.templ +++ b/frontend/components/pages/installerpedia/category.templ @@ -45,7 +45,7 @@ templ Category(data CategoryData) {
diff --git a/frontend/components/pages/installerpedia/page.templ b/frontend/components/pages/installerpedia/page.templ index fedf159a87..bc618fe992 100644 --- a/frontend/components/pages/installerpedia/page.templ +++ b/frontend/components/pages/installerpedia/page.templ @@ -25,7 +25,7 @@ const MAX_KEYWORDS = 10 templ Page(data PageData) { @layouts.BaseLayout(data.LayoutProps) { if data.Repo.IsDeleted { -
+

Page not available

@@ -67,7 +67,7 @@ templ Page(data PageData) { Official docs and installation instructions GitHub.
-
+
diff --git a/frontend/components/pages/man_pages/category.templ b/frontend/components/pages/man_pages/category.templ index 988a95a306..2a9298d161 100644 --- a/frontend/components/pages/man_pages/category.templ +++ b/frontend/components/pages/man_pages/category.templ @@ -26,7 +26,7 @@ type CategoryData struct { templ Category(data CategoryData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/man_pages/credits.templ b/frontend/components/pages/man_pages/credits.templ index 77a2642831..69751c3b47 100644 --- a/frontend/components/pages/man_pages/credits.templ +++ b/frontend/components/pages/man_pages/credits.templ @@ -12,7 +12,7 @@ type CreditsData struct { templ Credits(data CreditsData) { @layouts.BaseLayout(data.LayoutProps) { -
+
diff --git a/frontend/components/pages/man_pages/index.templ b/frontend/components/pages/man_pages/index.templ index 5dd9ee0de5..29c3692881 100644 --- a/frontend/components/pages/man_pages/index.templ +++ b/frontend/components/pages/man_pages/index.templ @@ -19,7 +19,7 @@ type IndexData struct { templ Index(data IndexData) { @layouts.BaseLayout(data.LayoutProps) { -
+
diff --git a/frontend/components/pages/man_pages/page.templ b/frontend/components/pages/man_pages/page.templ index cb0701b6a4..aac4ddb0db 100644 --- a/frontend/components/pages/man_pages/page.templ +++ b/frontend/components/pages/man_pages/page.templ @@ -35,7 +35,7 @@ func getTOCSections(content man_pages_db.ManPageContent) []string { templ Page(data PageData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/man_pages/subcategory.templ b/frontend/components/pages/man_pages/subcategory.templ index 0f516b4157..9a56a682d1 100644 --- a/frontend/components/pages/man_pages/subcategory.templ +++ b/frontend/components/pages/man_pages/subcategory.templ @@ -25,7 +25,7 @@ type SubCategoryData struct { templ SubCategory(data SubCategoryData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/mcp/category.templ b/frontend/components/pages/mcp/category.templ index ab4da0e787..848f7aa2c7 100644 --- a/frontend/components/pages/mcp/category.templ +++ b/frontend/components/pages/mcp/category.templ @@ -10,7 +10,7 @@ import ( templ Category(data CategoryData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical) diff --git a/frontend/components/pages/mcp/credits.templ b/frontend/components/pages/mcp/credits.templ index b05ddd7fa7..422409d8fb 100644 --- a/frontend/components/pages/mcp/credits.templ +++ b/frontend/components/pages/mcp/credits.templ @@ -7,7 +7,7 @@ import ( templ Credits(layoutProps layouts.BaseLayoutProps) { @layouts.BaseLayout(layoutProps) { -
+
@components.Breadcrumb([]components.BreadcrumbItem{ {Label: "Free DevTools", Href: "/freedevtools/"}, diff --git a/frontend/components/pages/mcp/index.templ b/frontend/components/pages/mcp/index.templ index 42c4fc027d..95d00073e0 100644 --- a/frontend/components/pages/mcp/index.templ +++ b/frontend/components/pages/mcp/index.templ @@ -10,7 +10,7 @@ import ( templ Index(data IndexData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical) diff --git a/frontend/components/pages/mcp/repo.templ b/frontend/components/pages/mcp/repo.templ index dac1b90f1a..7eae8a40bf 100644 --- a/frontend/components/pages/mcp/repo.templ +++ b/frontend/components/pages/mcp/repo.templ @@ -19,7 +19,7 @@ templ Repo(data RepoData) { props.Keywords = keywords }} @layouts.BaseLayout(props) { -
+
// Google Ads Banner (right after LiveReview banner)
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical) diff --git a/frontend/components/pages/png_icons/category.templ b/frontend/components/pages/png_icons/category.templ index 451aa6ac48..6ad9fa92ed 100644 --- a/frontend/components/pages/png_icons/category.templ +++ b/frontend/components/pages/png_icons/category.templ @@ -25,7 +25,7 @@ type CategoryData struct { templ Category(data CategoryData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/png_icons/credits.templ b/frontend/components/pages/png_icons/credits.templ index 03dd5ac222..30ceb55123 100644 --- a/frontend/components/pages/png_icons/credits.templ +++ b/frontend/components/pages/png_icons/credits.templ @@ -8,7 +8,7 @@ type CreditsData struct { templ Credits(data CreditsData) { @layouts.BaseLayout(data.LayoutProps) { -
+
diff --git a/frontend/components/pages/png_icons/icon.templ b/frontend/components/pages/png_icons/icon.templ index 6e6e48e704..ee797033bf 100644 --- a/frontend/components/pages/png_icons/icon.templ +++ b/frontend/components/pages/png_icons/icon.templ @@ -23,7 +23,7 @@ type IconData struct { templ Icon(data IconData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/png_icons/index.templ b/frontend/components/pages/png_icons/index.templ index e1b8c89e83..44efd71722 100644 --- a/frontend/components/pages/png_icons/index.templ +++ b/frontend/components/pages/png_icons/index.templ @@ -44,7 +44,7 @@ type PNGIndexData struct { templ Index(data PNGIndexData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/svg_icons/category.templ b/frontend/components/pages/svg_icons/category.templ index a19b33d49e..6843f3db07 100644 --- a/frontend/components/pages/svg_icons/category.templ +++ b/frontend/components/pages/svg_icons/category.templ @@ -25,7 +25,7 @@ type CategoryData struct { templ Category(data CategoryData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/svg_icons/credits.templ b/frontend/components/pages/svg_icons/credits.templ index 6e1a4ea6cc..8133ee7545 100644 --- a/frontend/components/pages/svg_icons/credits.templ +++ b/frontend/components/pages/svg_icons/credits.templ @@ -8,7 +8,7 @@ type CreditsData struct { templ Credits(data CreditsData) { @layouts.BaseLayout(data.LayoutProps) { -
+
diff --git a/frontend/components/pages/svg_icons/icon.templ b/frontend/components/pages/svg_icons/icon.templ index 132121c054..8ae8b89aae 100644 --- a/frontend/components/pages/svg_icons/icon.templ +++ b/frontend/components/pages/svg_icons/icon.templ @@ -23,7 +23,7 @@ type IconData struct { templ Icon(data IconData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/svg_icons/index.templ b/frontend/components/pages/svg_icons/index.templ index c2e5aeb6b8..b23324e2d4 100644 --- a/frontend/components/pages/svg_icons/index.templ +++ b/frontend/components/pages/svg_icons/index.templ @@ -44,7 +44,7 @@ type SVGIndexData struct { templ Index(data SVGIndexData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/components/pages/t/anthropic_token_counter.templ b/frontend/components/pages/t/anthropic_token_counter.templ index dd4273ae8b..c543301d14 100644 --- a/frontend/components/pages/t/anthropic_token_counter.templ +++ b/frontend/components/pages/t/anthropic_token_counter.templ @@ -22,7 +22,7 @@ templ AnthropicTokenCounter() { import '/freedevtools/static/js/index.js'; renderTool('anthropic-token-counter', 'anthropic-token-counter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/base64_encoder.templ b/frontend/components/pages/t/base64_encoder.templ index baf2b4ed8e..b3ef844ef6 100644 --- a/frontend/components/pages/t/base64_encoder.templ +++ b/frontend/components/pages/t/base64_encoder.templ @@ -22,7 +22,7 @@ templ Base64Encoder(toolKey string) { import '/freedevtools/static/js/index.js'; renderTool("base64-encoder", "base64-tool-root"); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/character_count.templ b/frontend/components/pages/t/character_count.templ index d83524773c..e221e3bebb 100644 --- a/frontend/components/pages/t/character_count.templ +++ b/frontend/components/pages/t/character_count.templ @@ -22,7 +22,7 @@ templ CharacterCount() { import '/freedevtools/static/js/index.js'; renderTool('character-count', 'character-count-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/chmod_calculator.templ b/frontend/components/pages/t/chmod_calculator.templ index 8048e31a7b..941acf738a 100644 --- a/frontend/components/pages/t/chmod_calculator.templ +++ b/frontend/components/pages/t/chmod_calculator.templ @@ -22,7 +22,7 @@ templ ChmodCalculator() { import '/freedevtools/static/js/index.js'; renderTool('chmod-calculator', 'chmod-calculator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/cron_tester.templ b/frontend/components/pages/t/cron_tester.templ index 4819d47c71..72da9662bc 100644 --- a/frontend/components/pages/t/cron_tester.templ +++ b/frontend/components/pages/t/cron_tester.templ @@ -22,7 +22,7 @@ templ CronTester() { import '/freedevtools/static/js/index.js'; renderTool('cron-tester', 'cron-tester-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/css_inliner_for_email.templ b/frontend/components/pages/t/css_inliner_for_email.templ index c43eb76841..9ac1d9f875 100644 --- a/frontend/components/pages/t/css_inliner_for_email.templ +++ b/frontend/components/pages/t/css_inliner_for_email.templ @@ -22,7 +22,7 @@ templ CssInlinerForEmail() { import '/freedevtools/static/js/index.js'; renderTool('css-inliner-for-email', 'css-inliner-for-email-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/css_units_converter.templ b/frontend/components/pages/t/css_units_converter.templ index 365eb0a394..dd1bd2daa3 100644 --- a/frontend/components/pages/t/css_units_converter.templ +++ b/frontend/components/pages/t/css_units_converter.templ @@ -22,7 +22,7 @@ templ CssUnitsConverter() { import '/freedevtools/static/js/index.js'; renderTool('css-units-converter', 'css-units-converter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/csv_to_json.templ b/frontend/components/pages/t/csv_to_json.templ index 77156b7df8..3ae5cc519c 100644 --- a/frontend/components/pages/t/csv_to_json.templ +++ b/frontend/components/pages/t/csv_to_json.templ @@ -22,7 +22,7 @@ templ CsvToJson() { import '/freedevtools/static/js/index.js'; renderTool('csv-to-json', 'csv-to-json-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/curl_to_js_fetch.templ b/frontend/components/pages/t/curl_to_js_fetch.templ index f9e585505e..1bfd7c9a5f 100644 --- a/frontend/components/pages/t/curl_to_js_fetch.templ +++ b/frontend/components/pages/t/curl_to_js_fetch.templ @@ -22,7 +22,7 @@ templ CurlToJsFetch() { import '/freedevtools/static/js/index.js'; renderTool('curl-to-js-fetch', 'curl-to-js-fetch-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/date_time_converter.templ b/frontend/components/pages/t/date_time_converter.templ index 9c2c269281..ebcd1598ba 100644 --- a/frontend/components/pages/t/date_time_converter.templ +++ b/frontend/components/pages/t/date_time_converter.templ @@ -22,7 +22,7 @@ templ DateTimeConverter() { import '/freedevtools/static/js/index.js'; renderTool('date-time-converter', 'date-time-converter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/deepseek_token_counter.templ b/frontend/components/pages/t/deepseek_token_counter.templ index 604e1afe46..e12a3a51f0 100644 --- a/frontend/components/pages/t/deepseek_token_counter.templ +++ b/frontend/components/pages/t/deepseek_token_counter.templ @@ -22,7 +22,7 @@ templ DeepseekTokenCounter() { import '/freedevtools/static/js/index.js'; renderTool('deepseek-token-counter', 'deepseek-token-counter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/diff_checker.templ b/frontend/components/pages/t/diff_checker.templ index 882f52394e..628a64855e 100644 --- a/frontend/components/pages/t/diff_checker.templ +++ b/frontend/components/pages/t/diff_checker.templ @@ -22,7 +22,7 @@ templ DiffChecker() { import '/freedevtools/static/js/index.js'; renderTool('diff-checker', 'diff-checker-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/dockerfile_linter.templ b/frontend/components/pages/t/dockerfile_linter.templ index 3cd6efbeb7..af7a51b968 100644 --- a/frontend/components/pages/t/dockerfile_linter.templ +++ b/frontend/components/pages/t/dockerfile_linter.templ @@ -22,7 +22,7 @@ templ DockerfileLinter() { import '/freedevtools/static/js/index.js'; renderTool('dockerfile-linter', 'dockerfile-linter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/env_to_netlify_toml.templ b/frontend/components/pages/t/env_to_netlify_toml.templ index 2ca0de96e1..81c12bc441 100644 --- a/frontend/components/pages/t/env_to_netlify_toml.templ +++ b/frontend/components/pages/t/env_to_netlify_toml.templ @@ -22,7 +22,7 @@ templ EnvToNetlifyToml() { import '/freedevtools/static/js/index.js'; renderTool('env-to-netlify-toml', 'env-to-netlify-toml-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/faker.templ b/frontend/components/pages/t/faker.templ index 607ed7a377..831774c97a 100644 --- a/frontend/components/pages/t/faker.templ +++ b/frontend/components/pages/t/faker.templ @@ -22,7 +22,7 @@ templ Faker() { import '/freedevtools/static/js/index.js'; renderTool('faker', 'faker-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/har_file_viewer.templ b/frontend/components/pages/t/har_file_viewer.templ index 43f3f30c03..d29b96e675 100644 --- a/frontend/components/pages/t/har_file_viewer.templ +++ b/frontend/components/pages/t/har_file_viewer.templ @@ -22,7 +22,7 @@ templ HarFileViewer() { import '/freedevtools/static/js/index.js'; renderTool('har-file-viewer', 'har-file-viewer-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/hash_generator.templ b/frontend/components/pages/t/hash_generator.templ index de3f768fc4..0e7a7d3a0f 100644 --- a/frontend/components/pages/t/hash_generator.templ +++ b/frontend/components/pages/t/hash_generator.templ @@ -22,7 +22,7 @@ templ HashGenerator() { import '/freedevtools/static/js/index.js'; renderTool('hash-generator', 'hash-generator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/html_to_markdown.templ b/frontend/components/pages/t/html_to_markdown.templ index 75cffffad7..930204824f 100644 --- a/frontend/components/pages/t/html_to_markdown.templ +++ b/frontend/components/pages/t/html_to_markdown.templ @@ -22,7 +22,7 @@ templ HtmlToMarkdown() { import '/freedevtools/static/js/index.js'; renderTool('html-to-markdown', 'html-to-markdown-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/image_to_base64.templ b/frontend/components/pages/t/image_to_base64.templ index 0298dbcc34..a2dc253750 100644 --- a/frontend/components/pages/t/image_to_base64.templ +++ b/frontend/components/pages/t/image_to_base64.templ @@ -22,7 +22,7 @@ templ ImageToBase64() { import '/freedevtools/static/js/index.js'; renderTool('image-to-base64', 'image-to-base64-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/index.templ b/frontend/components/pages/t/index.templ index 707e4928ad..da1574e697 100644 --- a/frontend/components/pages/t/index.templ +++ b/frontend/components/pages/t/index.templ @@ -28,7 +28,7 @@ templ Index() { "Productivity boost", }, }) { -
+

Free Tools - 50+ Dev Tools Online | No Registration Required

@@ -40,7 +40,7 @@ templ Index() { for _, tool := range tools.GetAllUniqueTools() {

{ tool.Name } diff --git a/frontend/components/pages/t/json_to_csv_converter.templ b/frontend/components/pages/t/json_to_csv_converter.templ index f29fe81262..34875a1809 100644 --- a/frontend/components/pages/t/json_to_csv_converter.templ +++ b/frontend/components/pages/t/json_to_csv_converter.templ @@ -22,7 +22,7 @@ templ JsonToCsvConverter() { import '/freedevtools/static/js/index.js'; renderTool('json-to-csv-converter', 'json-to-csv-converter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/json_to_xml.templ b/frontend/components/pages/t/json_to_xml.templ index 89dd6402cd..b6934ee75c 100644 --- a/frontend/components/pages/t/json_to_xml.templ +++ b/frontend/components/pages/t/json_to_xml.templ @@ -22,7 +22,7 @@ templ JsonToXml() { import '/freedevtools/static/js/index.js'; renderTool('json-to-xml', 'json-to-xml-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/json_to_yaml.templ b/frontend/components/pages/t/json_to_yaml.templ index ee7cf27595..ce26846531 100644 --- a/frontend/components/pages/t/json_to_yaml.templ +++ b/frontend/components/pages/t/json_to_yaml.templ @@ -22,7 +22,7 @@ templ JsonToYaml() { import '/freedevtools/static/js/index.js'; renderTool('json-to-yaml', 'json-to-yaml-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/json_tool.templ b/frontend/components/pages/t/json_tool.templ index 55247c227a..7e17439e35 100644 --- a/frontend/components/pages/t/json_tool.templ +++ b/frontend/components/pages/t/json_tool.templ @@ -22,7 +22,7 @@ templ JsonPrettifier(toolKey string) { import '/freedevtools/static/js/index.js'; renderTool("json-prettifier", "json-tool-root"); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/jwt_parser.templ b/frontend/components/pages/t/jwt_parser.templ index 11c7864011..19cec0eb92 100644 --- a/frontend/components/pages/t/jwt_parser.templ +++ b/frontend/components/pages/t/jwt_parser.templ @@ -22,7 +22,7 @@ templ JwtParser() { import '/freedevtools/static/js/index.js'; renderTool('jwt-parser', 'jwt-parser-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/llama_token_counter.templ b/frontend/components/pages/t/llama_token_counter.templ index aef1d2d260..a542ba5dcc 100644 --- a/frontend/components/pages/t/llama_token_counter.templ +++ b/frontend/components/pages/t/llama_token_counter.templ @@ -22,7 +22,7 @@ templ LlamaTokenCounter() { import '/freedevtools/static/js/index.js'; renderTool('llama-token-counter', 'llama-token-counter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/lorem_ipsum_generator.templ b/frontend/components/pages/t/lorem_ipsum_generator.templ index 7a7637e1e3..4c82883291 100644 --- a/frontend/components/pages/t/lorem_ipsum_generator.templ +++ b/frontend/components/pages/t/lorem_ipsum_generator.templ @@ -22,7 +22,7 @@ templ LoremIpsumGenerator() { import '/freedevtools/static/js/index.js'; renderTool('lorem-ipsum-generator', 'lorem-ipsum-generator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/mac_address_generator.templ b/frontend/components/pages/t/mac_address_generator.templ index 99f9c723c0..2cbc3b5041 100644 --- a/frontend/components/pages/t/mac_address_generator.templ +++ b/frontend/components/pages/t/mac_address_generator.templ @@ -22,7 +22,7 @@ templ MacAddressGenerator() { import '/freedevtools/static/js/index.js'; renderTool('mac-address-generator', 'mac-address-generator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/mac_address_lookup.templ b/frontend/components/pages/t/mac_address_lookup.templ index e9630ec3e6..86265775b4 100644 --- a/frontend/components/pages/t/mac_address_lookup.templ +++ b/frontend/components/pages/t/mac_address_lookup.templ @@ -22,7 +22,7 @@ templ MacAddressLookup() { import '/freedevtools/static/js/index.js'; renderTool('mac-address-lookup', 'mac-address-lookup-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/markdown_to_html_converter.templ b/frontend/components/pages/t/markdown_to_html_converter.templ index 01b72bcf49..aaf0d6d251 100644 --- a/frontend/components/pages/t/markdown_to_html_converter.templ +++ b/frontend/components/pages/t/markdown_to_html_converter.templ @@ -22,7 +22,7 @@ templ MarkdownToHtmlConverter() { import '/freedevtools/static/js/index.js'; renderTool('markdown-to-html-converter', 'markdown-to-html-converter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/og_meta_generator.templ b/frontend/components/pages/t/og_meta_generator.templ index a45f6b1078..fe280df8b4 100644 --- a/frontend/components/pages/t/og_meta_generator.templ +++ b/frontend/components/pages/t/og_meta_generator.templ @@ -22,7 +22,7 @@ templ OgMetaGenerator() { import '/freedevtools/static/js/index.js'; renderTool('og-meta-generator', 'og-meta-generator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/openai_cost_calculator.templ b/frontend/components/pages/t/openai_cost_calculator.templ index 88cbc54507..599c14cd23 100644 --- a/frontend/components/pages/t/openai_cost_calculator.templ +++ b/frontend/components/pages/t/openai_cost_calculator.templ @@ -22,7 +22,7 @@ templ OpenaiCostCalculator() { import '/freedevtools/static/js/index.js'; renderTool('openai-cost-calculator', 'openai-cost-calculator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/openai_token_counter.templ b/frontend/components/pages/t/openai_token_counter.templ index 3b5de3c401..d8a4174e06 100644 --- a/frontend/components/pages/t/openai_token_counter.templ +++ b/frontend/components/pages/t/openai_token_counter.templ @@ -22,7 +22,7 @@ templ OpenAiTokenCounter() { import '/freedevtools/static/js/index.js'; renderTool('openai-token-counter', 'openai-token-counter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/password_generator.templ b/frontend/components/pages/t/password_generator.templ index 651c6a34bb..1e0bf5071d 100644 --- a/frontend/components/pages/t/password_generator.templ +++ b/frontend/components/pages/t/password_generator.templ @@ -22,7 +22,7 @@ templ PasswordGenerator() { import '/freedevtools/static/js/index.js'; renderTool('password-generator', 'password-generator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/qrcode_generator.templ b/frontend/components/pages/t/qrcode_generator.templ index 07b9a2ac1e..8808d8b7d7 100644 --- a/frontend/components/pages/t/qrcode_generator.templ +++ b/frontend/components/pages/t/qrcode_generator.templ @@ -22,7 +22,7 @@ templ QrcodeGenerator() { import '/freedevtools/static/js/index.js'; renderTool('qrcode-generator', 'qrcode-generator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/query_params_to_json.templ b/frontend/components/pages/t/query_params_to_json.templ index 97a36b9507..40f0fce417 100644 --- a/frontend/components/pages/t/query_params_to_json.templ +++ b/frontend/components/pages/t/query_params_to_json.templ @@ -22,7 +22,7 @@ templ QueryParamsToJson() { import '/freedevtools/static/js/index.js'; renderTool('query-params-to-json', 'query-params-to-json-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/regex_tester.templ b/frontend/components/pages/t/regex_tester.templ index a1ff31ddab..ffb1a52679 100644 --- a/frontend/components/pages/t/regex_tester.templ +++ b/frontend/components/pages/t/regex_tester.templ @@ -22,7 +22,7 @@ templ RegexTester() { import '/freedevtools/static/js/index.js'; renderTool('regex-tester', 'regex-tester-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/rgb_to_hex.templ b/frontend/components/pages/t/rgb_to_hex.templ index dab18b1163..77870cb7d5 100644 --- a/frontend/components/pages/t/rgb_to_hex.templ +++ b/frontend/components/pages/t/rgb_to_hex.templ @@ -22,7 +22,7 @@ templ RgbToHex() { import '/freedevtools/static/js/index.js'; renderTool('rgb-to-hex', 'rgb-to-hex-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/rsa_key_pair_generator.templ b/frontend/components/pages/t/rsa_key_pair_generator.templ index 13ebb8888a..fa9e9e505e 100644 --- a/frontend/components/pages/t/rsa_key_pair_generator.templ +++ b/frontend/components/pages/t/rsa_key_pair_generator.templ @@ -22,7 +22,7 @@ templ RsaKeyPairGenerator() { import '/freedevtools/static/js/index.js'; renderTool('rsa-key-pair-generator', 'rsa-key-pair-generator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/slugify_string.templ b/frontend/components/pages/t/slugify_string.templ index 358d0aec30..7d9d6304d8 100644 --- a/frontend/components/pages/t/slugify_string.templ +++ b/frontend/components/pages/t/slugify_string.templ @@ -22,7 +22,7 @@ templ SlugifyString() { import '/freedevtools/static/js/index.js'; renderTool('slugify-string', 'slugify-string-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/sql_minifier.templ b/frontend/components/pages/t/sql_minifier.templ index 70ce5bdad5..34d65e8549 100644 --- a/frontend/components/pages/t/sql_minifier.templ +++ b/frontend/components/pages/t/sql_minifier.templ @@ -22,7 +22,7 @@ templ SqlMinifier() { import '/freedevtools/static/js/index.js'; renderTool('sql-minifier', 'sql-minifier-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/svg_placeholder_generator.templ b/frontend/components/pages/t/svg_placeholder_generator.templ index 716318cd53..bd0e27fca3 100644 --- a/frontend/components/pages/t/svg_placeholder_generator.templ +++ b/frontend/components/pages/t/svg_placeholder_generator.templ @@ -22,7 +22,7 @@ templ SvgPlaceholderGenerator() { import '/freedevtools/static/js/index.js'; renderTool('svg-placeholder-generator', 'svg-placeholder-generator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/svg_viewer.templ b/frontend/components/pages/t/svg_viewer.templ index de780fa05b..ea67c71cf8 100644 --- a/frontend/components/pages/t/svg_viewer.templ +++ b/frontend/components/pages/t/svg_viewer.templ @@ -22,7 +22,7 @@ templ SvgViewer() { import '/freedevtools/static/js/index.js'; renderTool('svg-viewer', 'svg-viewer-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/user_agent_parser.templ b/frontend/components/pages/t/user_agent_parser.templ index d33b107b57..8021308d83 100644 --- a/frontend/components/pages/t/user_agent_parser.templ +++ b/frontend/components/pages/t/user_agent_parser.templ @@ -22,7 +22,7 @@ templ UserAgentParser() { import '/freedevtools/static/js/index.js'; renderTool('user-agent-parser', 'user-agent-parser-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/uuid_generator.templ b/frontend/components/pages/t/uuid_generator.templ index 14637ede96..4e9c09d163 100644 --- a/frontend/components/pages/t/uuid_generator.templ +++ b/frontend/components/pages/t/uuid_generator.templ @@ -22,7 +22,7 @@ templ UuidGenerator() { import '/freedevtools/static/js/index.js'; renderTool('uuid-generator', 'uuid-generator-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/webp_converter.templ b/frontend/components/pages/t/webp_converter.templ index cb1ae6b65b..921992e860 100644 --- a/frontend/components/pages/t/webp_converter.templ +++ b/frontend/components/pages/t/webp_converter.templ @@ -22,7 +22,7 @@ templ WebpConverter() { import '/freedevtools/static/js/index.js'; renderTool('webp-converter', 'webp-converter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/xml_formatter.templ b/frontend/components/pages/t/xml_formatter.templ index 17b2379609..aba7fe2269 100644 --- a/frontend/components/pages/t/xml_formatter.templ +++ b/frontend/components/pages/t/xml_formatter.templ @@ -22,7 +22,7 @@ templ XmlFormatter() { import '/freedevtools/static/js/index.js'; renderTool('xml-formatter', 'xml-formatter-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/xml_to_json.templ b/frontend/components/pages/t/xml_to_json.templ index 89787a01f2..7ccaff2e4d 100644 --- a/frontend/components/pages/t/xml_to_json.templ +++ b/frontend/components/pages/t/xml_to_json.templ @@ -22,7 +22,7 @@ templ XmlToJson() { import '/freedevtools/static/js/index.js'; renderTool('xml-to-json', 'xml-to-json-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/yaml_to_json.templ b/frontend/components/pages/t/yaml_to_json.templ index f2b8a570f2..e43fe80127 100644 --- a/frontend/components/pages/t/yaml_to_json.templ +++ b/frontend/components/pages/t/yaml_to_json.templ @@ -22,7 +22,7 @@ templ YamlToJson() { import '/freedevtools/static/js/index.js'; renderTool('yaml-to-json', 'yaml-to-json-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/yaml_to_toml.templ b/frontend/components/pages/t/yaml_to_toml.templ index d8e86400ab..66d523c850 100644 --- a/frontend/components/pages/t/yaml_to_toml.templ +++ b/frontend/components/pages/t/yaml_to_toml.templ @@ -22,7 +22,7 @@ templ YamlToToml() { import '/freedevtools/static/js/index.js'; renderTool('yaml-to-toml', 'yaml-to-toml-root'); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/t/zstd_compress.templ b/frontend/components/pages/t/zstd_compress.templ index 02e99da4f3..6272c95fc6 100644 --- a/frontend/components/pages/t/zstd_compress.templ +++ b/frontend/components/pages/t/zstd_compress.templ @@ -22,7 +22,7 @@ templ ZstdCompress(toolKey string) { import '/freedevtools/static/js/index.js'; renderTool("zstd-compress", "zstd-tool-root"); -
+
@common.SeeAlso()
} diff --git a/frontend/components/pages/tldr/command.templ b/frontend/components/pages/tldr/command.templ index 3cc96eadb9..628f784651 100644 --- a/frontend/components/pages/tldr/command.templ +++ b/frontend/components/pages/tldr/command.templ @@ -87,7 +87,7 @@ templ Command(data TLDRCommandData) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical) diff --git a/frontend/components/pages/tldr/index.templ b/frontend/components/pages/tldr/index.templ index 7311dad3a5..1c6962b36d 100644 --- a/frontend/components/pages/tldr/index.templ +++ b/frontend/components/pages/tldr/index.templ @@ -21,7 +21,7 @@ type TLDRIndexData struct { templ Index(data TLDRIndexData) { @layouts.BaseLayout(data.LayoutProps) { -
+
diff --git a/frontend/components/pages/tldr/platform.templ b/frontend/components/pages/tldr/platform.templ index f513e83342..7865712137 100644 --- a/frontend/components/pages/tldr/platform.templ +++ b/frontend/components/pages/tldr/platform.templ @@ -23,7 +23,7 @@ type TLDRPlatformData struct { templ Platform(data TLDRPlatformData) { @layouts.BaseLayout(data.LayoutProps) { -
+
@banner.Banner(data.TextBanner, data.LayoutProps.Canonical)
diff --git a/frontend/internal/controllers/png_icons/handlers.go b/frontend/internal/controllers/png_icons/handlers.go index 8b8937aeb5..69ecab74ce 100644 --- a/frontend/internal/controllers/png_icons/handlers.go +++ b/frontend/internal/controllers/png_icons/handlers.go @@ -89,7 +89,9 @@ func HandleIndex(w http.ResponseWriter, r *http.Request, db *png_icons_db.DB, pa totalPages := (totalCategories + itemsPerPage - 1) / itemsPerPage if page > totalPages && totalPages > 0 { if page > 1 { - http.NotFound(w, r) + // Redirect invalid pagination pages to home + basePath := config.GetBasePath() + http.Redirect(w, r, basePath+"/png_icons/", http.StatusMovedPermanently) return } } @@ -103,6 +105,13 @@ func HandleIndex(w http.ResponseWriter, r *http.Request, db *png_icons_db.DB, pa // Fallback or error? } + // If home page (page 1) has no items, redirect to store + if page == 1 && (totalCategories == 0 || len(categories) == 0) { + basePath := config.GetBasePath() + http.Redirect(w, r, basePath+"/png_icons/store/", http.StatusMovedPermanently) + return + } + basePath := config.GetBasePath() breadcrumbItems := []components.BreadcrumbItem{ {Label: "Free DevTools", Href: basePath + "/"}, @@ -200,6 +209,13 @@ func HandleCategory(w http.ResponseWriter, r *http.Request, db *png_icons_db.DB, totalPages := (cluster.Count + limit - 1) / limit + // Redirect invalid pagination pages to category page + if page > totalPages && totalPages > 0 { + basePath := config.GetBasePath() + http.Redirect(w, r, fmt.Sprintf("%s/png_icons/%s/", basePath, category), http.StatusMovedPermanently) + return + } + title := cluster.Title if title == "" { title = fmt.Sprintf("%s PNG Icons - Free Download & Edit | Online Free DevTools by Hexmos", category) diff --git a/frontend/internal/controllers/svg_icons/handlers.go b/frontend/internal/controllers/svg_icons/handlers.go index ba723b44ce..37c1f2b737 100644 --- a/frontend/internal/controllers/svg_icons/handlers.go +++ b/frontend/internal/controllers/svg_icons/handlers.go @@ -98,7 +98,9 @@ func HandleIndex(w http.ResponseWriter, r *http.Request, db *svg_icons_db.DB, pa totalPages := (totalCategories + itemsPerPage - 1) / itemsPerPage if page > totalPages && totalPages > 0 { // Added totalPages > 0 check for safety if page > 1 { // Allow page 1 even if empty? - http.NotFound(w, r) + // Redirect invalid pagination pages to home + basePath := config.GetBasePath() + http.Redirect(w, r, basePath+"/svg_icons/", http.StatusMovedPermanently) return } } @@ -113,6 +115,13 @@ func HandleIndex(w http.ResponseWriter, r *http.Request, db *svg_icons_db.DB, pa // Should be OK if success. } + // If home page (page 1) has no items, redirect to store + if page == 1 && (totalCategories == 0 || len(categories) == 0) { + basePath := config.GetBasePath() + http.Redirect(w, r, basePath+"/svg_icons/store/", http.StatusMovedPermanently) + return + } + basePath := config.GetBasePath() breadcrumbItems := []components.BreadcrumbItem{ {Label: "Free DevTools", Href: basePath + "/"}, @@ -207,6 +216,13 @@ func HandleCategory(w http.ResponseWriter, r *http.Request, db *svg_icons_db.DB, totalPages := (cluster.Count + limit - 1) / limit + // Redirect invalid pagination pages to category page + if page > totalPages && totalPages > 0 { + basePath := config.GetBasePath() + http.Redirect(w, r, fmt.Sprintf("%s/svg_icons/%s/", basePath, category), http.StatusMovedPermanently) + return + } + // Fix title/description logic title := cluster.Title if title == "" { diff --git a/frontend/package.json b/frontend/package.json index 9a60580ae3..0df1d2e8c9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,7 +23,6 @@ "vite-tsconfig-paths": "^6.0.1" }, "dependencies": { - "@builder.io/partytown": "^0.10.3", "@huggingface/transformers": "^3.8.1", "@oneidentity/zstd-js": "^1.0.3", "@radix-ui/react-checkbox": "^1.3.3", From 781adca68ad2925a829f6f89476879b8063a740b Mon Sep 17 00:00:00 2001 From: lovestaco Date: Fri, 6 Feb 2026 21:41:28 +0530 Subject: [PATCH 05/51] fix; search and other pages spacing issues --- frontend/cmd/server/routes.go | 6 + frontend/components/common/footer.templ | 56 +++--- frontend/components/common/search_bar.templ | 28 ++- frontend/components/common/sidebar.templ | 181 ++++++++++++++++-- frontend/components/layouts/base_layout.templ | 17 +- frontend/components/pages/index.templ | 40 ++-- .../pages/static/vs_code_extension.templ | 73 +++++++ frontend/frontend/components/pages/Pro.tsx | 2 +- .../frontend/components/pages/pro/Pro.tsx | 38 ++-- .../components/pages/pro/PurchaseHistory.tsx | 28 +-- .../components/pages/pro/ShowPlans.tsx | 110 ++++++----- .../frontend/components/search/SearchPage.tsx | 115 +++++++++-- 12 files changed, 522 insertions(+), 172 deletions(-) create mode 100644 frontend/components/pages/static/vs_code_extension.templ diff --git a/frontend/cmd/server/routes.go b/frontend/cmd/server/routes.go index 4af72159a2..8309abb5c6 100644 --- a/frontend/cmd/server/routes.go +++ b/frontend/cmd/server/routes.go @@ -192,6 +192,12 @@ func setupRoutes(mux *http.ServeMux, svgIconsDB *svg_icons.DB, manPagesDB *man_p handler.ServeHTTP(w, r) }) + // VS Code Extension page + mux.HandleFunc(basePath+"/vs-code-extension/", func(w http.ResponseWriter, r *http.Request) { + handler := templ.Handler(static_pages.VSCodeExtension()) + handler.ServeHTTP(w, r) + }) + // Pro page mux.HandleFunc(basePath+"/pro/", func(w http.ResponseWriter, r *http.Request) { handler := templ.Handler(pro_pages.Pro()) diff --git a/frontend/components/common/footer.templ b/frontend/components/common/footer.templ index e273ccf4fb..daa8ae1d45 100644 --- a/frontend/components/common/footer.templ +++ b/frontend/components/common/footer.templ @@ -2,14 +2,14 @@ package common templ Footer() {
+ +
+ +
+
+ + +
+
+ + +
-
+ } + + + +
+
đŸ•šī¸
+

+ MCP Directory +

+

+ Browse 15000+ Model Context Protocol repositories for AI agents. Find MCP servers, tools, and clients by category with detailed documentation and usage examples. +

+
+
+ + + +
+
📖
+

+ Man Pages +

+

+ Browse comprehensive UNIX/Linux manual pages with detailed documentation. Find system commands, library functions, file formats, and system administration guides organized by category. +

+
+
+ + + +
+
âš™ī¸
+

+ Installerpedia +

+

+ A curated collection of installation guides and commands for software, packages, and tools, making setup fast, easy, and reliable. +

+
+
+
+ +
+ } } - From b574320cc0a5901e112a586fbba2cefcfddfc5d2 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 11:55:38 +0530 Subject: [PATCH 09/51] fix:height --- frontend/components/pages/index.templ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/pages/index.templ b/frontend/components/pages/index.templ index 2637121b26..3c28e2dd45 100644 --- a/frontend/components/pages/index.templ +++ b/frontend/components/pages/index.templ @@ -16,7 +16,7 @@ templ Index(bannerData *banner_db.Banner) { HideBanner: true, }) { -
+
Date: Sat, 7 Feb 2026 14:11:52 +0530 Subject: [PATCH 10/51] fix: homepage --- frontend/components/pages/index.templ | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/frontend/components/pages/index.templ b/frontend/components/pages/index.templ index 3c28e2dd45..a56f84812d 100644 --- a/frontend/components/pages/index.templ +++ b/frontend/components/pages/index.templ @@ -17,17 +17,14 @@ templ Index(bannerData *banner_db.Banner) { }) {
- -
+ +
Free DevTools Logo -

- Free DevTools -

@@ -321,6 +318,11 @@ templ Index(bannerData *banner_db.Banner) { const indexSearch = document.getElementById('index-search'); if (!indexSearch) return; + // Auto-focus search input on page load + setTimeout(() => { + indexSearch.focus(); + }, 100); + // Update keyboard shortcut hint based on platform const kbdModifier = document.querySelector('.kbd-modifier'); if (kbdModifier) { From c1b22a6e109c81c95450a261e450d5cdf8117b12 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 15:11:54 +0530 Subject: [PATCH 11/51] fix: search with number --- .../frontend/components/search/SearchPage.tsx | 113 +++++++++++++++++- frontend/tailwind.config.js | 7 ++ 2 files changed, 114 insertions(+), 6 deletions(-) diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index 0c46659ca7..649d51ee79 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -1,14 +1,17 @@ import React, { useCallback, useEffect, useState } from 'react'; // Icons are tree-shakeable, so only imported icons are bundled +import toast from '@/components/ToastProvider'; import { MEILI_SEARCH_API_KEY } from '@/config'; import { getProStatusFromCookie } from '@/lib/api'; import { Cross2Icon, DownloadIcon, + ExclamationTriangleIcon, FileIcon, FileTextIcon, GearIcon, ImageIcon, + MagnifyingGlassIcon, ModulzLogoIcon, ReaderIcon, RocketIcon @@ -131,6 +134,35 @@ function updateUrlHash(searchQuery: string): void { } } +// LocalStorage utilities for search tracking +const SEARCH_COUNT_KEY = 'freedevtools-search-count'; +const MAX_SEARCHES = 20; + +function getSearchCount(): number { + if (typeof window === 'undefined') return 0; + try { + const count = localStorage.getItem(SEARCH_COUNT_KEY); + return count ? parseInt(count, 10) : 0; + } catch { + return 0; + } +} + +function incrementSearchCount(): void { + if (typeof window === 'undefined') return; + try { + const currentCount = getSearchCount(); + localStorage.setItem(SEARCH_COUNT_KEY, (currentCount + 1).toString()); + } catch { + // Ignore localStorage errors + } +} + +function getSearchesLeft(): number { + const count = getSearchCount(); + return Math.max(0, MAX_SEARCHES - count); +} + // API async function searchUtilities( query: string, @@ -398,7 +430,13 @@ const SearchPage: React.FC = () => { [key: string]: number; }>({}); const [isPro, setIsPro] = useState(false); - const [searchesLeft] = useState(20); + const [searchesLeft, setSearchesLeft] = useState(() => getSearchesLeft()); + + // Track last counted search to avoid duplicate increments + const lastCountedSearchRef = React.useRef<{ + query: string; + categories: string[]; + } | null>(null); const getEffectiveCategories = useCallback(() => { if (activeCategory === 'all') return []; @@ -406,10 +444,13 @@ const SearchPage: React.FC = () => { return [activeCategory]; }, [activeCategory, selectedCategories]); - // Check pro status on mount + // Check pro status on mount and initialize searches left useEffect(() => { const proStatus = getProStatusFromCookie(); setIsPro(proStatus); + if (!proStatus) { + setSearchesLeft(getSearchesLeft()); + } }, []); useEffect(() => { @@ -424,14 +465,28 @@ const SearchPage: React.FC = () => { return; } + // Check if search limit is reached for non-pro users + if (!isPro && searchesLeft <= 0) { + toast.warning('Search Limit Reached - Upgrade to Pro for unlimited searches'); + return; + } + const timeoutId = setTimeout(async () => { setLoading(true); setCurrentPage(1); setAvailableCategories({}); + + const effectiveCategories = getEffectiveCategories(); + + // Check if this is a new search (different from last counted) + const isNewSearch = !lastCountedSearchRef.current || + lastCountedSearchRef.current.query !== query.trim() || + JSON.stringify(lastCountedSearchRef.current.categories.sort()) !== JSON.stringify(effectiveCategories.sort()); + try { const searchResponse = await searchUtilities( query, - getEffectiveCategories(), + effectiveCategories, 1 ); console.log('Search results:', searchResponse); @@ -454,6 +509,17 @@ const SearchPage: React.FC = () => { processingTime: searchResponse.processingTimeMs || 0, facetTotal: facetTotal, }); + + // Increment search count for non-pro users only for new searches + if (!isPro && query.trim() && isNewSearch) { + incrementSearchCount(); + setSearchesLeft(getSearchesLeft()); + // Track this search as counted + lastCountedSearchRef.current = { + query: query.trim(), + categories: effectiveCategories, + }; + } } catch (error) { console.error('Search error:', error); setResults([]); @@ -462,10 +528,10 @@ const SearchPage: React.FC = () => { } finally { setLoading(false); } - }, 300); + }, 500); // Increased debounce delay to reduce rapid searches return () => clearTimeout(timeoutId); - }, [query, activeCategory, selectedCategories, getEffectiveCategories]); + }, [query, activeCategory, selectedCategories, getEffectiveCategories, isPro, searchesLeft]); useEffect(() => { setCurrentPage(1); @@ -781,8 +847,43 @@ const SearchPage: React.FC = () => {
{/* SearchInfoHeader */} -
+

{getTitle()}

+ {/* Search Limit Indicator */} + {!isPro && searchesLeft >= 0 && ( +
+
+
+ {searchesLeft === 0 ? ( + + ) : ( + + )} + + {searchesLeft} searches left + +
+
+
+
+
+
+ )}
{/* CategoryFilter - Google-style tabs */} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index a88139ed41..366376f6a7 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -70,6 +70,13 @@ module.exports = { DEFAULT: '#06b6d4', light: '#22d3ee', }, + 'fdt-yellow': { + DEFAULT: '#d4cb24', + light: '#fef9c3', + }, + 'fdt-red': { + DEFAULT: '#b81c1d', + }, }, borderRadius: { lg: 'var(--radius)', From 8c64da369917ffed65d5ab1df152d585670ffde7 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 15:21:29 +0530 Subject: [PATCH 12/51] fix: color --- frontend/frontend/components/search/SearchPage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index 649d51ee79..8fde244493 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -853,8 +853,8 @@ const SearchPage: React.FC = () => { {!isPro && searchesLeft >= 0 && (
{searchesLeft === 0 ? ( @@ -862,7 +862,7 @@ const SearchPage: React.FC = () => { ) : ( )} - + {searchesLeft} searches left
@@ -870,7 +870,7 @@ const SearchPage: React.FC = () => { className="w-full rounded-full overflow-hidden mb-1" style={{ height: '8px', - backgroundColor: '#fff' + backgroundColor: '#F2F2DC' }} >
Date: Sat, 7 Feb 2026 15:22:51 +0530 Subject: [PATCH 13/51] fix: spacing --- frontend/frontend/components/search/SearchPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index 8fde244493..9381da283a 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -851,7 +851,7 @@ const SearchPage: React.FC = () => {

{getTitle()}

{/* Search Limit Indicator */} {!isPro && searchesLeft >= 0 && ( -
+
Date: Sat, 7 Feb 2026 18:48:38 +0530 Subject: [PATCH 14/51] fix: search --- .../frontend/components/search/SearchPage.tsx | 477 +++++++++--------- 1 file changed, 244 insertions(+), 233 deletions(-) diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index 9381da283a..46c4001c8f 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -136,7 +136,7 @@ function updateUrlHash(searchQuery: string): void { // LocalStorage utilities for search tracking const SEARCH_COUNT_KEY = 'freedevtools-search-count'; -const MAX_SEARCHES = 20; +const MAX_SEARCHES = 99; function getSearchCount(): number { if (typeof window === 'undefined') return 0; @@ -758,103 +758,102 @@ const SearchPage: React.FC = () => { }; return ( -
- {/* Search Bar */} -
-
- { - const newQuery = e.target.value; - setQuery(newQuery); - // Update searchState first - if (window.searchState) { - window.searchState.setQuery(newQuery); - } - // Then update hash - this ensures searchState is updated before hashchange event - if (newQuery.trim()) { - window.location.hash = `search?q=${encodeURIComponent(newQuery)}`; - } else { - // Keep search page open with empty query - // Set hash to search?q= to keep search page visible - if (window.location.hash !== '#search?q=') { - window.location.hash = 'search?q='; - } - } - }} - onKeyDown={(e) => { - if (e.key === 'Escape' && query.trim()) { - clearResults(); - } - }} - style={{ - height: '3rem', - fontSize: '1rem', - paddingLeft: '1rem', - borderWidth: '1px', - boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', - }} - /> - {/* Clear button (X icon) - appears on hover */} - {query.trim() && ( - +
+
+
+ { + const newQuery = e.target.value; + setQuery(newQuery); // Update searchState first if (window.searchState) { - window.searchState.setQuery(emptyQuery); - } - // Set hash to search?q= to keep search page visible - if (window.location.hash !== '#search?q=') { - window.location.hash = 'search?q='; + window.searchState.setQuery(newQuery); } - // Focus back on input after clearing - setTimeout(() => { - if (searchInputRef.current) { - searchInputRef.current.focus(); + // Then update hash - this ensures searchState is updated before hashchange event + if (newQuery.trim()) { + window.location.hash = `search?q=${encodeURIComponent(newQuery)}`; + } else { + // Keep search page open with empty query + // Set hash to search?q= to keep search page visible + if (window.location.hash !== '#search?q=') { + window.location.hash = 'search?q='; } - }, 0); + } }} - className="absolute right-2 top-1/2 transform -translate-y-1/2 p-1.5 rounded-full hover:bg-gray-200 dark:hover:bg-slate-700 cursor-pointer z-20 flex items-center justify-center" - style={{ pointerEvents: 'auto' }} - aria-label="Clear search" - > - - - )} -
- -
- -
- {/* SearchInfoHeader */} -
-

{getTitle()}

+ onKeyDown={(e) => { + if (e.key === 'Escape' && query.trim()) { + clearResults(); + } + }} + style={{ + height: '3rem', + fontSize: '1rem', + paddingLeft: '1rem', + borderWidth: '1px', + borderColor: '#d4cb24', + boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', + }} + /> + {/* Clear button (X icon) - appears on hover */} + {query.trim() && ( + + )} +
{/* Search Limit Indicator */} {!isPro && searchesLeft >= 0 && ( -
+
{searchesLeft === 0 ? ( @@ -884,163 +883,175 @@ const SearchPage: React.FC = () => {
)} +
- {/* CategoryFilter - Google-style tabs */} -
- +
- {categories - .filter((cat) => cat.key !== 'all') - .map((category) => { - const isActive = - activeCategory === category.key || - selectedCategories.includes(category.key); - const searchCategoryKey = getCategoryKeyForSearch(category.key); - const count = - availableCategories[searchCategoryKey] || - (activeCategory === 'all' - ? availableCategories[searchCategoryKey] - : undefined); - - return ( - + + {categories + .filter((cat) => cat.key !== 'all') + .map((category) => { + const isActive = + activeCategory === category.key || + selectedCategories.includes(category.key); + const searchCategoryKey = getCategoryKeyForSearch(category.key); + const count = + availableCategories[searchCategoryKey] || + (activeCategory === 'all' + ? availableCategories[searchCategoryKey] + : undefined); + + return ( + - ); - })} -
-
- - {/* LoadingState */} - {loading && ( -
-
-

Searching...

-
- )} - - {/* EmptyState */} - {!loading && results.length === 0 && query.trim() && ( -
-

- No results found for "{query}" -

+ + ); + })} +
- )} - {!loading && results.length > 0 && filteredResults.length === 0 && ( -
-

- No results found in category {activeCategory} -

- -
- )} - - {/* ResultsGrid */} - {!loading && filteredResults.length > 0 && ( - <> -
- {filteredResults.map((result, index) => ( - - ))} -
+ {/* LoadingState */} + { + loading && ( +
+
+

Searching...

+
+ ) + } - {/* LoadMoreSection */} - {currentPage < totalPages && ( -
- {searchInfo && ( -

- Showing {allResults.length} of{' '} - {searchInfo.totalHits.toLocaleString()}{' '} - {activeCategory === 'all' - ? 'items' - : getCategoryDisplayName(activeCategory)}{' '} - (Page {Math.ceil(allResults.length / 100)} of {totalPages}) -

- )} + {/* EmptyState */} + { + !loading && results.length === 0 && query.trim() && ( +
+

+ No results found for "{query}" +

+
+ ) + } + { + !loading && results.length > 0 && filteredResults.length === 0 && ( +
+

+ No results found in category {activeCategory} +

- )} - - )} + ) + } + + {/* ResultsGrid */} + { + !loading && filteredResults.length > 0 && ( + <> +
+ {filteredResults.map((result, index) => ( + + ))} +
+ + {/* LoadMoreSection */} + {currentPage < totalPages && ( +
+ {searchInfo && ( +

+ Showing {allResults.length} of{' '} + {searchInfo.totalHits.toLocaleString()}{' '} + {activeCategory === 'all' + ? 'items' + : getCategoryDisplayName(activeCategory)}{' '} + (Page {Math.ceil(allResults.length / 100)} of {totalPages}) +

+ )} + + +
+ )} + + ) + } +
); }; From b3abd4d21763bda70cc39f44b29cac71e764d228 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 19:39:43 +0530 Subject: [PATCH 15/51] fix: opacity --- frontend/components/layouts/base_layout.templ | 6 ++++-- frontend/frontend/components/search/SearchPage.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/components/layouts/base_layout.templ b/frontend/components/layouts/base_layout.templ index 2303dd6cf7..6f5bf92f96 100644 --- a/frontend/components/layouts/base_layout.templ +++ b/frontend/components/layouts/base_layout.templ @@ -360,7 +360,8 @@ templ BaseLayout(props BaseLayoutProps) { // Show search page if there's a query OR if hash is #search?q= (empty search state) if (searchQuery.trim() || (isSearchHash && window.location.hash === '#search?q=')) { searchContainer.style.display = 'block'; - slotContainer.style.display = 'none'; + slotContainer.style.opacity = '0.3'; + slotContainer.style.pointerEvents = 'none'; // Only mount if not already mounted if (!window.searchMounted) { @@ -374,7 +375,8 @@ templ BaseLayout(props BaseLayoutProps) { } } else { searchContainer.style.display = 'none'; - slotContainer.style.display = 'block'; + slotContainer.style.opacity = '1'; + slotContainer.style.pointerEvents = 'auto'; } } diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index 46c4001c8f..09cf467b86 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -759,7 +759,7 @@ const SearchPage: React.FC = () => { return (
-
+
{/* Search Bar */}

Search engine for developer resources

From be4d306aafce7a66063fed7104157d4034c2ce24 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 19:56:00 +0530 Subject: [PATCH 16/51] fix: sizing glitch when active search is happening --- frontend/frontend/components/search/SearchPage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index 09cf467b86..820a947084 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -902,10 +902,10 @@ const SearchPage: React.FC = () => { }} > All - + {activeCategory === 'all' && Object.keys(availableCategories).length > 0 && ( - + {formatCount(getAllCount())} )} @@ -946,9 +946,9 @@ const SearchPage: React.FC = () => { {getCategoryIcon(category.key)} */} {category.label} - + {count !== undefined && ( - + {formatCount(count)} )} From 6b1f1407c83bcbde137c03575d1e379bed263230 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 20:12:52 +0530 Subject: [PATCH 17/51] fix: search and index with focus color, grouping --- frontend/components/common/search_bar.templ | 8 +++--- frontend/components/pages/index.templ | 28 +++++++++++++++++++ .../components/search/SearchBar.astro | 10 +++---- .../frontend/components/search/SearchPage.tsx | 11 ++++---- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/frontend/components/common/search_bar.templ b/frontend/components/common/search_bar.templ index 083beebc97..c2221c91ff 100644 --- a/frontend/components/common/search_bar.templ +++ b/frontend/components/common/search_bar.templ @@ -33,7 +33,7 @@ templ SearchBar() { @@ -44,7 +44,7 @@ import { MagnifyingGlassIcon, Cross2Icon } from '@radix-ui/react-icons'; @@ -219,15 +219,15 @@ import { MagnifyingGlassIcon, Cross2Icon } from '@radix-ui/react-icons'; } } - // Handle focus - make border blue + // Handle focus - make border yellow (fdt-yellow) function handleFocus(input: HTMLInputElement) { input.classList.remove('border-gray-300', 'dark:border-gray-600'); - input.classList.add('border-blue-500', 'dark:border-blue-500'); + input.classList.add('border-fdt-yellow', 'dark:border-fdt-yellow'); } // Handle blur - restore default border function handleBlur(input: HTMLInputElement) { - input.classList.remove('border-blue-500', 'dark:border-blue-500'); + input.classList.remove('border-fdt-yellow', 'dark:border-fdt-yellow'); input.classList.add('border-gray-300', 'dark:border-gray-600'); } diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index 820a947084..705f12a289 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -615,12 +615,9 @@ const SearchPage: React.FC = () => { } // Close search page - remove hash entirely + // Use window.location.hash = '' to trigger hashchange event if (window.location.hash.startsWith('#search')) { - history.pushState( - '', - document.title, - window.location.pathname + window.location.search - ); + window.location.hash = ''; } }, [setQuery]); @@ -720,7 +717,9 @@ const SearchPage: React.FC = () => { }, [setQuery]); // Don't render if no query and not on search page - if (!query.trim() && !window.location.hash.startsWith('#search')) { + // Also don't render if hash is empty (search was closed) + const hash = window.location.hash; + if ((!query.trim() && !hash.startsWith('#search')) || hash === '') { return null; } From 4acaffad6278d1dd35c5f4f654786dfd8383edb6 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 20:34:50 +0530 Subject: [PATCH 18/51] fix: ctrl k in other pages --- .../rules/border-hover-stylings-for-ui.mdc | 10 ++++++++++ frontend/components/common/header.templ | 14 ++++++++++++++ frontend/components/common/sidebar.templ | 18 ++++++++++-------- .../components/common/theme_switcher.templ | 4 ++-- frontend/components/pages/index.templ | 6 +++--- frontend/tailwind.config.js | 5 ++++- 6 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 frontend/.cursor/rules/border-hover-stylings-for-ui.mdc diff --git a/frontend/.cursor/rules/border-hover-stylings-for-ui.mdc b/frontend/.cursor/rules/border-hover-stylings-for-ui.mdc new file mode 100644 index 0000000000..4f2655e475 --- /dev/null +++ b/frontend/.cursor/rules/border-hover-stylings-for-ui.mdc @@ -0,0 +1,10 @@ +--- +alwaysApply: true +--- +Always use `border-fdt-yellow-dark` for light mode and `border-fdt-yellow` for dark mode when applying hover or focus border colors. + +**Colors:** +- Light mode: `border-fdt-yellow-dark` (#b6b000) +- Dark mode: `border-fdt-yellow` (#d4cb24) + +Apply this consistently across all search inputs, buttons, and interactive elements. diff --git a/frontend/components/common/header.templ b/frontend/components/common/header.templ index 271a15ba1a..92a3a80cf9 100644 --- a/frontend/components/common/header.templ +++ b/frontend/components/common/header.templ @@ -277,6 +277,20 @@ templ Header() { const handleKeyboardShortcut = (e) => { if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); + // Check if we're on the search page - if so, don't interfere + const isSearchPage = window.location.hash.startsWith('#search'); + if (isSearchPage) { + return; // Let the search page handle it + } + + // Priority: sidebar search first, then other search inputs + const sidebarSearch = document.querySelector('#sidebar #search'); + if (sidebarSearch) { + sidebarSearch.focus(); + return; + } + + // Fallback to any search input with the placeholder const searchInputs = document.querySelectorAll('input[type="text"]'); searchInputs.forEach((input) => { if (input.placeholder === 'Search 350k+ resources') { diff --git a/frontend/components/common/sidebar.templ b/frontend/components/common/sidebar.templ index f1322af0dd..fe9f79a5ec 100644 --- a/frontend/components/common/sidebar.templ +++ b/frontend/components/common/sidebar.templ @@ -167,21 +167,21 @@ templ Sidebar() { diff --git a/frontend/components/pages/index.templ b/frontend/components/pages/index.templ index 21c6683153..359627ff05 100644 --- a/frontend/components/pages/index.templ +++ b/frontend/components/pages/index.templ @@ -32,7 +32,7 @@ templ Index(bannerData *banner_db.Banner) { Date: Sat, 7 Feb 2026 20:42:59 +0530 Subject: [PATCH 19/51] fix: crumbs --- frontend/components/pages/svg_icons/icon.templ | 2 +- frontend/components/pages/svg_icons/index.templ | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/pages/svg_icons/icon.templ b/frontend/components/pages/svg_icons/icon.templ index 8ae8b89aae..f1f4603685 100644 --- a/frontend/components/pages/svg_icons/icon.templ +++ b/frontend/components/pages/svg_icons/icon.templ @@ -29,7 +29,7 @@ templ Icon(data IconData) {
-
+
@components.Breadcrumb(data.BreadcrumbItems)
diff --git a/frontend/components/pages/svg_icons/index.templ b/frontend/components/pages/svg_icons/index.templ index b23324e2d4..7d858614cc 100644 --- a/frontend/components/pages/svg_icons/index.templ +++ b/frontend/components/pages/svg_icons/index.templ @@ -50,7 +50,7 @@ templ Index(data SVGIndexData) {
-
+
@components.Breadcrumb(data.BreadcrumbItems)
From e89b1a3b1463186288d6c4883280a6733f3695de Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 20:50:56 +0530 Subject: [PATCH 20/51] feat: basic pro banner --- frontend/components/layouts/base_layout.templ | 47 ++++- .../frontend/components/common/ProBanner.tsx | 161 ++++++++++++++++++ .../frontend/components/search/SearchPage.tsx | 6 +- frontend/frontend/index.ts | 1 + 4 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 frontend/frontend/components/common/ProBanner.tsx diff --git a/frontend/components/layouts/base_layout.templ b/frontend/components/layouts/base_layout.templ index 6f5bf92f96..26e851ad58 100644 --- a/frontend/components/layouts/base_layout.templ +++ b/frontend/components/layouts/base_layout.templ @@ -313,6 +313,7 @@ templ BaseLayout(props BaseLayoutProps) {
+
if !props.HideBanner { @banner.AdBanner() @@ -380,10 +381,54 @@ templ BaseLayout(props BaseLayoutProps) { } } + let proBannerMounted = false; + + async function loadProBannerModule() { + if (proBannerMounted) return Promise.resolve(); + try { + await loadSearchModule(); // Reuse the same module loader + if (window.renderTool && !proBannerMounted) { + const container = document.getElementById('pro-banner-container'); + if (container) { + window.renderTool('pro-banner', 'pro-banner-container'); + proBannerMounted = true; + } + } + } catch (error) { + console.error('Failed to load ProBanner module:', error); + } + } + + function toggleProBanner() { + const proBannerContainer = document.getElementById('pro-banner-container'); + if (!proBannerContainer) return; + + const urlParams = new URLSearchParams(window.location.search); + const buyParam = urlParams.get('buy'); + const hash = window.location.hash; + const shouldShow = buyParam === 'pro' || hash === '#pro-banner'; + + if (shouldShow) { + proBannerContainer.style.display = 'block'; + if (!proBannerMounted) { + loadProBannerModule(); + } + } else { + proBannerContainer.style.display = 'none'; + } + } + document.addEventListener('DOMContentLoaded', function () { toggleSearchView(); + toggleProBanner(); window.addEventListener('searchQueryChanged', toggleSearchView); - window.addEventListener('hashchange', toggleSearchView); + window.addEventListener('hashchange', function() { + toggleSearchView(); + toggleProBanner(); + }); + window.addEventListener('popstate', function() { + toggleProBanner(); + }); // Check for initial hash param for search if (window.location.hash.startsWith('#search')) { diff --git a/frontend/frontend/components/common/ProBanner.tsx b/frontend/frontend/components/common/ProBanner.tsx new file mode 100644 index 0000000000..dd57da5509 --- /dev/null +++ b/frontend/frontend/components/common/ProBanner.tsx @@ -0,0 +1,161 @@ +import React, { useEffect, useState } from 'react'; +import { X, Sparkles, Zap, Bookmark, Download, Headphones, Rocket, Shield } from 'lucide-react'; + +const PURCHASE_URL = 'https://purchase.hexmos.com/freedevtools/subscription'; + +const ProBanner: React.FC = () => { + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + // Trigger animation on mount + setTimeout(() => setIsVisible(true), 10); + }, []); + + const benefits = [ + { + icon: Sparkles, + title: 'Includes everything from Free Trial', + description: 'All free features plus premium enhancements', + }, + { + icon: Shield, + title: 'No ads', + description: 'Zero distractions, faster pages', + }, + { + icon: Zap, + title: 'Unlimited search', + description: 'No rate limits or throttling', + }, + { + icon: Bookmark, + title: 'Unlimited bookmarks', + description: 'Save without limits', + }, + { + icon: Download, + title: 'Unlimited downloads', + description: 'No daily caps', + }, + { + icon: Headphones, + title: 'Priority support & feature requests', + description: 'Get help when you need it', + }, + { + icon: Rocket, + title: 'Early access to new tools and features', + description: 'Be the first to try new capabilities', + }, + ]; + + const handleBuyNow = () => { + window.location.href = PURCHASE_URL; + }; + + const handleClose = () => { + setIsVisible(false); + setTimeout(() => { + // Remove query params and hash + const url = new URL(window.location.href); + url.searchParams.delete('buy'); + url.hash = ''; + window.history.replaceState({}, '', url.toString()); + + // Hide the container + const container = document.getElementById('pro-banner-container'); + if (container) { + container.style.display = 'none'; + } + }, 200); + }; + + return ( +
+
e.stopPropagation()} + > + {/* Header */} +
+ +
+
+ +

Upgrade to Free DevTools Pro

+
+

+ Unlock unlimited access and premium features +

+
+
+ + {/* Content */} +
+ {/* Benefits Grid */} +
+ {benefits.map((benefit, index) => { + const IconComponent = benefit.icon; + return ( +
+
+
+ +
+
+
+

+ {benefit.title} +

+

+ {benefit.description} +

+
+
+ ); + })} +
+ + {/* CTA Buttons */} +
+ + +
+
+
+
+ ); +}; + +export default ProBanner; + diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index 705f12a289..ac8be6bb3c 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -846,13 +846,17 @@ const SearchPage: React.FC = () => { {!isPro && searchesLeft >= 0 && (
{ + // Trigger ProBanner popup + window.location.hash = '#pro-banner'; + }} >
{searchesLeft === 0 ? ( diff --git a/frontend/frontend/index.ts b/frontend/frontend/index.ts index 90d4f0cbf7..b6d84f88a5 100644 --- a/frontend/frontend/index.ts +++ b/frontend/frontend/index.ts @@ -131,6 +131,7 @@ const toolLoaders: Record void> = { "sidebarProfile": (e) => { renderDynamic(e, () => import('./components/common/SidebarProfile')); }, + "pro-banner": (e) => renderDynamic(e, () => import('./components/common/ProBanner')), }; // Expose render functions globally From 52a1bb3df5da12b1ac34f5466ff946368b9be800 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 21:17:07 +0530 Subject: [PATCH 21/51] fix: homepage spacing priority --- frontend/components/common/header.templ | 2 +- frontend/components/common/search_bar.templ | 2 +- frontend/components/pages/index.templ | 115 ++++++++++++++++-- .../components/search/SearchBar.astro | 4 +- .../frontend/components/search/SearchPage.tsx | 4 +- 5 files changed, 111 insertions(+), 16 deletions(-) diff --git a/frontend/components/common/header.templ b/frontend/components/common/header.templ index 92a3a80cf9..30c90165fc 100644 --- a/frontend/components/common/header.templ +++ b/frontend/components/common/header.templ @@ -293,7 +293,7 @@ templ Header() { // Fallback to any search input with the placeholder const searchInputs = document.querySelectorAll('input[type="text"]'); searchInputs.forEach((input) => { - if (input.placeholder === 'Search 350k+ resources') { + if (input.placeholder === 'Search icon, emoji, tool, cheatsheet or Github repository') { input.focus(); } }); diff --git a/frontend/components/common/search_bar.templ b/frontend/components/common/search_bar.templ index c2221c91ff..a151665c6e 100644 --- a/frontend/components/common/search_bar.templ +++ b/frontend/components/common/search_bar.templ @@ -34,7 +34,7 @@ templ SearchBar() { type="text" id="search-mobile" class="flex-1 bg-white dark:bg-gray-800 placeholder:text-gray-500 dark:placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-0 focus-visible:border-fdt-yellow border-gray-300 dark:border-gray-600 rounded-md" - placeholder="Search 350k+ resources" + placeholder="Search icon, emoji, tool, cheatsheet or Github repository" aria-label="Search for developer tools and resources" style="height: 3rem; font-size: 1rem; padding-left: 1rem; padding-right: 1rem; border-width: 1px;" /> diff --git a/frontend/components/pages/index.templ b/frontend/components/pages/index.templ index 359627ff05..14228923a3 100644 --- a/frontend/components/pages/index.templ +++ b/frontend/components/pages/index.templ @@ -28,13 +28,13 @@ templ Index(bannerData *banner_db.Banner) {
-
+
@@ -51,13 +51,100 @@ templ Index(bannerData *banner_db.Banner) {
- - + + // + + +
+ + +
@@ -405,6 +492,14 @@ templ Index(bannerData *banner_db.Banner) { }); } + // Handle explore pro benefits button + const exploreProBtn = document.getElementById('explore-pro-benefits-btn'); + if (exploreProBtn) { + exploreProBtn.addEventListener('click', function() { + window.location.hash = '#pro-banner'; + }); + } + // Show/hide layouts based on URL parameter function toggleLayouts() { const urlParams = new URLSearchParams(window.location.search); diff --git a/frontend/frontend/components/search/SearchBar.astro b/frontend/frontend/components/search/SearchBar.astro index 645c8fbd9d..6addff9c77 100644 --- a/frontend/frontend/components/search/SearchBar.astro +++ b/frontend/frontend/components/search/SearchBar.astro @@ -34,7 +34,7 @@ import { MagnifyingGlassIcon, Cross2Icon } from '@radix-ui/react-icons'; type="text" id="search-mobile" class="flex-1 h-12 text-base bg-white dark:bg-gray-800 placeholder:text-gray-500 dark:placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-fdt-yellow border-gray-300 dark:border-gray-600 rounded-md border px-4" - placeholder="Search 350k+ resources" + placeholder="Search icon, emoji, tool, cheatsheet or Github repository" aria-label="Search for developer tools and resources" />
@@ -45,7 +45,7 @@ import { MagnifyingGlassIcon, Cross2Icon } from '@radix-ui/react-icons'; type="text" id="search" class="w-full pr-10 h-10 text-sm md:text-base bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm placeholder:text-gray-500 dark:placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fdt-yellow border-gray-300 dark:border-gray-600 hover:bg-white/70 dark:hover:bg-gray-800/70 rounded-md border px-3 py-2" - placeholder="Search 350k+ resources" + placeholder="Search icon, emoji, tool, cheatsheet or Github repository" aria-label="Search for developer tools and resources" /> diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index ac8be6bb3c..3d154b747a 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -776,8 +776,8 @@ const SearchPage: React.FC = () => { type="text" id="search-page-input" className="w-full bg-white dark:bg-slate-800 placeholder:text-gray-500 dark:placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-0 hover:bg-white dark:hover:bg-slate-800 rounded-lg pr-10" - placeholder="Search 350k+ resources" - aria-label="Search 350k+ resources" + placeholder="Search icon, emoji, tool, cheatsheet or Github repository" + aria-label="Search icon, emoji, tool, cheatsheet or Github repository" value={query} onChange={(e) => { const newQuery = e.target.value; From 156de14573821dc19be779113a37b0bdd8644cab Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 21:48:54 +0530 Subject: [PATCH 22/51] fix: basic pro banner --- .../frontend/components/common/ProBanner.tsx | 207 ++++++++++++------ 1 file changed, 134 insertions(+), 73 deletions(-) diff --git a/frontend/frontend/components/common/ProBanner.tsx b/frontend/frontend/components/common/ProBanner.tsx index dd57da5509..ee560d79b7 100644 --- a/frontend/frontend/components/common/ProBanner.tsx +++ b/frontend/frontend/components/common/ProBanner.tsx @@ -1,5 +1,5 @@ +import { Bookmark, Check, Clock, Download, Flame, Headphones, Rocket, Shield, X, Zap } from 'lucide-react'; import React, { useEffect, useState } from 'react'; -import { X, Sparkles, Zap, Bookmark, Download, Headphones, Rocket, Shield } from 'lucide-react'; const PURCHASE_URL = 'https://purchase.hexmos.com/freedevtools/subscription'; @@ -12,11 +12,6 @@ const ProBanner: React.FC = () => { }, []); const benefits = [ - { - icon: Sparkles, - title: 'Includes everything from Free Trial', - description: 'All free features plus premium enhancements', - }, { icon: Shield, title: 'No ads', @@ -61,7 +56,7 @@ const ProBanner: React.FC = () => { url.searchParams.delete('buy'); url.hash = ''; window.history.replaceState({}, '', url.toString()); - + // Hide the container const container = document.getElementById('pro-banner-container'); if (container) { @@ -72,84 +67,150 @@ const ProBanner: React.FC = () => { return (
e.stopPropagation()} > - {/* Header */} -
- -
-
- -

Upgrade to Free DevTools Pro

-
-

- Unlock unlimited access and premium features -

-
-
+ {/* Close Button */} + {/* Content */} -
- {/* Benefits Grid */} -
- {benefits.map((benefit, index) => { - const IconComponent = benefit.icon; - return ( +
+
+ {/* Left Column - Promotional Content */} +
+ {/* Limited Time Offer Badge */} +
+ + LIMITED TIME OFFER +
+ + {/* Main Offer Text */} +

+ Power up FreeDevTools — zero ads, zero limits, all features +

+ + {/* Pricing */} +
+
+ $149 + $89 + lifetime +
+ +
+ + {/* Urgency Bar */} +
+
+
+ + Limited Offer +
+ 43/1000 left +
-
-
- -
-
-
-

- {benefit.title} -

-

- {benefit.description} -

-
+
- ); - })} -
+
+ + {/* CTA Button */} + - {/* CTA Buttons */} -
- - + {/* Guarantees */} +
+
+ + Instant Access +
+
+ + Lifetime Updates +
+
+ + Money Back +
+
+
+ + {/* Right Column - Benefits */} +
+
+ {benefits.map((benefit, index) => { + const IconComponent = benefit.icon; + return ( +
+
+
+ +
+
+
+

+ {benefit.title} +

+

+ {benefit.description} +

+
+
+ ); + })} +
+
From 01e985402eca5285613bc486c8930b4941022944 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 21:52:35 +0530 Subject: [PATCH 23/51] fix: pro banner --- .../frontend/components/common/ProBanner.tsx | 56 +++++++------------ 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/frontend/frontend/components/common/ProBanner.tsx b/frontend/frontend/components/common/ProBanner.tsx index ee560d79b7..d1227f0841 100644 --- a/frontend/frontend/components/common/ProBanner.tsx +++ b/frontend/frontend/components/common/ProBanner.tsx @@ -93,49 +93,49 @@ const ProBanner: React.FC = () => {
{/* Limited Time Offer Badge */}
- - LIMITED TIME OFFER + + LIMITED TIME OFFER
{/* Main Offer Text */} -

+

Power up FreeDevTools — zero ads, zero limits, all features

{/* Pricing */} -
-
- $149 - $89 - lifetime +
+
+ $149 + $89 + lifetime
{/* Urgency Bar */}
-
-
- - Limited Offer +
+
+ + Limited Offer
- 43/1000 left + 43/1000 left
{ {/* CTA Button */} - {/* Guarantees */} -
-
- - Instant Access -
-
- - Lifetime Updates -
-
- - Money Back -
-
+
{/* Right Column - Benefits */}
-
+
{benefits.map((benefit, index) => { const IconComponent = benefit.icon; return ( From fc4f17afa28ddc82dee8b2bb1e534b03614b46c9 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sat, 7 Feb 2026 21:54:41 +0530 Subject: [PATCH 24/51] fix: spacing --- frontend/frontend/components/common/ProBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/frontend/components/common/ProBanner.tsx b/frontend/frontend/components/common/ProBanner.tsx index d1227f0841..dbdb07f75e 100644 --- a/frontend/frontend/components/common/ProBanner.tsx +++ b/frontend/frontend/components/common/ProBanner.tsx @@ -109,7 +109,7 @@ const ProBanner: React.FC = () => {

{/* Pricing */} -
+
$149 $89 From fe9ca7c89a014b34a07e28b36d7f2ca4bf7aed5f Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sun, 8 Feb 2026 15:05:45 +0530 Subject: [PATCH 25/51] fix: search close esc, counter color --- .../frontend/components/search/SearchPage.tsx | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index 3d154b747a..838ecaf6f8 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -431,6 +431,7 @@ const SearchPage: React.FC = () => { }>({}); const [isPro, setIsPro] = useState(false); const [searchesLeft, setSearchesLeft] = useState(() => getSearchesLeft()); + const [isDarkMode, setIsDarkMode] = useState(false); // Track last counted search to avoid duplicate increments const lastCountedSearchRef = React.useRef<{ @@ -438,6 +439,22 @@ const SearchPage: React.FC = () => { categories: string[]; } | null>(null); + // Detect dark mode + useEffect(() => { + const checkDarkMode = () => { + setIsDarkMode(document.documentElement.classList.contains('dark')); + }; + + checkDarkMode(); + const observer = new MutationObserver(checkDarkMode); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class'] + }); + + return () => observer.disconnect(); + }, []); + const getEffectiveCategories = useCallback(() => { if (activeCategory === 'all') return []; if (activeCategory === 'multi') return selectedCategories; @@ -756,14 +773,35 @@ const SearchPage: React.FC = () => { return `Found ${searchInfo.totalHits.toLocaleString()} ${getCategoryDisplayName(activeCategory)} for "${query}"`; }; + const handleBackdropClick = () => { + // Close search page by clearing the hash + window.location.hash = ''; + }; + + const closeSearchPage = () => { + window.location.hash = ''; + setQuery(''); + if (window.searchState) { + window.searchState.setQuery(''); + } + }; + return ( -
-
+
+
e.stopPropagation()} + > {/* Search Bar */}

Search engine for developer resources

- - {/* Content */} -
-
- {/* Left Column - Promotional Content */} -
- {/* Limited Time Offer Badge */} -
- - LIMITED TIME OFFER -
+ {/* First Row - Limited Offer and Close Button */} +
+ {/* Left Column - Limited Time Offer Badge */} +
+
+ + LIMITED TIME OFFER +
+
- {/* Main Offer Text */} -

- Power up FreeDevTools — zero ads, zero limits, all features -

+ {/* Right Column - Close Button */} +
+ +
+
- {/* Pricing */} -
-
- $149 - $89 - lifetime -
+ {/* Second Row - Two Columns */} +
+ {/* Left Column - Promotional Content */} +
+ {/* Main Offer Text - More prominent with extra space, vertically centered */} +
+

+ Power up FreeDevTools — zero ads, zero limits, all features +

+
+ $139 + $29 + lifetime
+
+
- {/* Urgency Bar */} -
-
-
- - Limited Offer -
- 43/1000 left -
+ + {/* Bottom Section - Pricing, Urgency, CTA */} +
+ {/* Urgency Bar */}
+
+
+ + Limited Offer +
+ 43/1000 left +
+ > +
+
-
- - {/* CTA Button */} - - + {/* CTA Button */} + +
+
- {/* Right Column - Benefits */} -
-
- {benefits.map((benefit, index) => { - const IconComponent = benefit.icon; - return ( -
-
-
- -
-
-
-

- {benefit.title} -

-

- {benefit.description} -

+ {/* Right Column - Benefits */} +
+
+ {benefits.map((benefit, index) => { + const IconComponent = benefit.icon; + return ( +
+
+
+
- ); - })} -
+
+

+ {benefit.title} +

+

+ {benefit.description} +

+
+
+ ); + })}
diff --git a/frontend/frontend/components/search/SearchPage.tsx b/frontend/frontend/components/search/SearchPage.tsx index 838ecaf6f8..d01e44e694 100644 --- a/frontend/frontend/components/search/SearchPage.tsx +++ b/frontend/frontend/components/search/SearchPage.tsx @@ -802,9 +802,10 @@ const SearchPage: React.FC = () => {

Search engine for developer resources

From eb59292a57683bccc9cdfc281d9f34275764827d Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sun, 8 Feb 2026 20:46:21 +0530 Subject: [PATCH 30/51] fix: download count for vscode --- frontend/cmd/server/routes.go | 66 ++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/frontend/cmd/server/routes.go b/frontend/cmd/server/routes.go index 8309abb5c6..69c87c66a0 100644 --- a/frontend/cmd/server/routes.go +++ b/frontend/cmd/server/routes.go @@ -1,6 +1,8 @@ package main import ( + "bytes" + "encoding/json" "log" "net/http" "net/http/pprof" @@ -9,6 +11,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "fdt-templ/components/pages" cheatsheets_pages "fdt-templ/components/pages/cheatsheets" @@ -105,6 +108,66 @@ func matchCategoryPagination(path string) (category string, page int, ok bool) { return "", 0, false } +// fetchVSXExtensionInstallCount fetches the install count from Open VSX API +func fetchVSXExtensionInstallCount() int { + client := &http.Client{ + Timeout: 5 * time.Second, + } + + requestBody := map[string]interface{}{ + "filters": []map[string]interface{}{ + { + "criteria": []map[string]interface{}{ + { + "filterType": 7, + "value": "hexmos.freedevtools", + }, + }, + }, + }, + "flags": 914, + } + + jsonData, err := json.Marshal(requestBody) + if err != nil { + log.Printf("Error marshaling VSX API request: %v", err) + return 0 + } + + resp, err := client.Post("https://open-vsx.org/vscode/gallery/extensionquery", "application/json", bytes.NewBuffer(jsonData)) + if err != nil { + log.Printf("Error fetching VSX extension data: %v", err) + return 0 + } + defer resp.Body.Close() + + var result struct { + Results []struct { + Extensions []struct { + Statistics []struct { + StatisticName string `json:"statisticName"` + Value float64 `json:"value"` + } `json:"statistics"` + } `json:"extensions"` + } `json:"results"` + } + + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + log.Printf("Error decoding VSX API response: %v", err) + return 0 + } + + if len(result.Results) > 0 && len(result.Results[0].Extensions) > 0 { + for _, stat := range result.Results[0].Extensions[0].Statistics { + if stat.StatisticName == "install" { + return int(stat.Value) + } + } + } + + return 0 +} + // Cheatsheets route matching functions are now in cheatsheets_routes.go func setupRoutes(mux *http.ServeMux, svgIconsDB *svg_icons.DB, manPagesDB *man_pages.DB, emojisDB *emojis.DB, mcpDB *mcp.DB, pngIconsDB *png_icons.DB, cheatsheetsDB *cheatsheets.DB, tldrDB *tldr.DB, installerpediaDB *installerpedia.DB, toolsConfig *tools.Config, fdtPgDB *bookmarks.DB) { @@ -194,7 +257,8 @@ func setupRoutes(mux *http.ServeMux, svgIconsDB *svg_icons.DB, manPagesDB *man_p // VS Code Extension page mux.HandleFunc(basePath+"/vs-code-extension/", func(w http.ResponseWriter, r *http.Request) { - handler := templ.Handler(static_pages.VSCodeExtension()) + installCount := fetchVSXExtensionInstallCount() + handler := templ.Handler(static_pages.VSCodeExtension(installCount)) handler.ServeHTTP(w, r) }) From 7ef3ebd02effa36e41bed3e1debc4db84fffe96e Mon Sep 17 00:00:00 2001 From: lovestaco Date: Sun, 8 Feb 2026 21:10:06 +0530 Subject: [PATCH 31/51] fix: container padding --- .../rules/border-hover-stylings-for-ui.mdc | 4 ++-- frontend/components/breadcrumb.templ | 2 +- .../components/layouts/tool_detail_layout.templ | 2 +- .../components/pages/cheatsheets/category.templ | 11 +---------- .../components/pages/cheatsheets/cheatsheet.templ | 2 +- frontend/components/pages/cheatsheets/index.templ | 2 +- .../components/pages/emojis/apple/category.templ | 6 ++---- .../components/pages/emojis/apple/index.templ | 6 +----- frontend/components/pages/emojis/category.templ | 6 +----- frontend/components/pages/emojis/credits.templ | 8 +------- .../pages/emojis/discord/category.templ | 4 +--- .../components/pages/emojis/discord/index.templ | 6 ++---- frontend/components/pages/emojis/emoji.templ | 6 +----- frontend/components/pages/emojis/index.templ | 6 +----- .../pages/installerpedia/category.templ | 15 +-------------- .../components/pages/installerpedia/index.templ | 5 ----- .../components/pages/installerpedia/page.templ | 11 +---------- .../components/pages/man_pages/category.templ | 6 +----- frontend/components/pages/man_pages/credits.templ | 6 +----- frontend/components/pages/man_pages/index.templ | 2 +- frontend/components/pages/man_pages/page.templ | 6 +----- .../components/pages/man_pages/subcategory.templ | 6 +----- frontend/components/pages/mcp/category.templ | 11 +---------- frontend/components/pages/mcp/index.templ | 4 +--- frontend/components/pages/mcp/repo.templ | 11 +---------- .../components/pages/png_icons/category.templ | 6 +----- frontend/components/pages/png_icons/credits.templ | 9 +-------- frontend/components/pages/png_icons/icon.templ | 13 +------------ frontend/components/pages/png_icons/index.templ | 6 +----- .../components/pages/svg_icons/category.templ | 11 +---------- frontend/components/pages/svg_icons/credits.templ | 9 ++------- frontend/components/pages/svg_icons/icon.templ | 13 +------------ frontend/components/pages/svg_icons/index.templ | 6 +----- frontend/components/pages/t/index.templ | 7 ++++++- frontend/components/pages/tldr/command.templ | 9 +-------- frontend/components/pages/tldr/index.templ | 6 +----- frontend/components/pages/tldr/platform.templ | 6 +----- 37 files changed, 45 insertions(+), 210 deletions(-) diff --git a/frontend/.cursor/rules/border-hover-stylings-for-ui.mdc b/frontend/.cursor/rules/border-hover-stylings-for-ui.mdc index 4f2655e475..25254ce8fd 100644 --- a/frontend/.cursor/rules/border-hover-stylings-for-ui.mdc +++ b/frontend/.cursor/rules/border-hover-stylings-for-ui.mdc @@ -1,10 +1,10 @@ --- alwaysApply: true --- -Always use `border-fdt-yellow-dark` for light mode and `border-fdt-yellow` for dark mode when applying hover or focus border colors. +Always use `border-fdt-yellow-dark` for light mode and `border-yellow-700` for dark mode when applying hover or focus border colors. **Colors:** - Light mode: `border-fdt-yellow-dark` (#b6b000) -- Dark mode: `border-fdt-yellow` (#d4cb24) +- Dark mode: `dark:border-yellow-700` () Apply this consistently across all search inputs, buttons, and interactive elements. diff --git a/frontend/components/breadcrumb.templ b/frontend/components/breadcrumb.templ index a300a3ee9e..31265732a3 100644 --- a/frontend/components/breadcrumb.templ +++ b/frontend/components/breadcrumb.templ @@ -6,7 +6,7 @@ type BreadcrumbItem struct { } templ Breadcrumb(items []BreadcrumbItem) { -
+ ); }; From 94bb98aa7a375f2bd59ff6c4b7cc12f9d74fe744 Mon Sep 17 00:00:00 2001 From: lovestaco Date: Mon, 9 Feb 2026 19:01:47 +0530 Subject: [PATCH 45/51] fix: add close icon in phone mode for sidbear --- frontend/components/common/sidebar.templ | 47 +++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/frontend/components/common/sidebar.templ b/frontend/components/common/sidebar.templ index 945f8fe158..6350caf23f 100644 --- a/frontend/components/common/sidebar.templ +++ b/frontend/components/common/sidebar.templ @@ -5,7 +5,7 @@ templ Sidebar() {