Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified apps/web/public/icon-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/web/public/icon-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions apps/web/public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
"description": "A collection of developer tools for everyday use.",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"background_color": "#0a0b12",
"icons": [
{
"src": "/icon-192x192.png",
Expand Down
11 changes: 11 additions & 0 deletions apps/web/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1845,3 +1845,14 @@ html { overflow-x: hidden; }
/* dashboard editor caret */
.mdt-dash-caret { background: var(--mdt-cyan); box-shadow: 0 0 8px var(--mdt-cyan); border-radius: 1px; animation: mdt-caret-blink 1.1s steps(2) infinite; }
@keyframes mdt-caret-blink { 50% { opacity: 0; } }

/* ============================================
iOS INPUT AUTO-ZOOM PREVENTION
============================================ */
@media (max-width: 640px) {
input,
textarea,
select {
font-size: 16px !important;
}
}
2 changes: 1 addition & 1 deletion apps/web/src/app/help/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default function HelpPage() {
const tTools = useTranslations('Dashboard.tools')

return (
<div className="flex-1 space-y-6 p-6 pb-24 md:pb-8 max-w-5xl mx-auto w-full pt-16 md:pt-8 bg-background/50">
<div className="flex-1 space-y-6 p-6 pb-24 md:pb-8 max-w-5xl mx-auto w-full pt-[calc(theme(spacing.16)+env(safe-area-inset-top))] md:pt-[calc(theme(spacing.8)+env(safe-area-inset-top))] bg-background/50">
<div className="space-y-1">
<h1 className="text-3xl font-bold tracking-tight flex items-center gap-2">
<BookOpen className="h-8 w-8 opacity-80 shrink-0" aria-hidden />
Expand Down
21 changes: 17 additions & 4 deletions apps/web/src/app/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import {
AtSign,
Github,
ChevronDown,
ChevronRight,
FileDown,
} from 'lucide-react'
import { useActiveOrg, useActiveWorkspace } from '@/store/workspace-store'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import Link from 'next/link'
import { Input } from '@/components/ui/input'
Expand Down Expand Up @@ -57,6 +59,8 @@ import {
export default function ProfilePage() {
const t = useTranslations('SettingsPage')
const { user } = useAuth()
const activeOrg = useActiveOrg()
const activeWorkspace = useActiveWorkspace()
const [mounted, setMounted] = useState(false)
const [username, setUsername] = useState<string | null>(null)
const [editUsernameVal, setEditUsernameVal] = useState('')
Expand Down Expand Up @@ -253,10 +257,19 @@ export default function ProfilePage() {
{/* ── Page header ── */}
<div className="flex items-center justify-between">
<div className="space-y-1.5">
<p className="inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
<span className="h-1.5 w-1.5 rounded-full bg-gradient-to-br from-primary to-violet-500" />
Developer identity
</p>
{activeOrg && activeWorkspace ? (
<p className="inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
<Briefcase className="h-3 w-3 text-primary" />
<span className="truncate max-w-[120px] sm:max-w-none">{activeOrg.name}</span>
<ChevronRight className="h-3 w-3 opacity-60" />
<span className="truncate max-w-[160px] sm:max-w-none text-foreground/80">{activeWorkspace.name}</span>
</p>
) : (
<p className="inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
<span className="h-1.5 w-1.5 rounded-full bg-gradient-to-br from-primary to-violet-500" />
Developer identity
</p>
)}
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight">Your Profile</h2>
<p className="text-muted-foreground text-sm">Manage your identity and developer portfolio.</p>
</div>
Expand Down
21 changes: 17 additions & 4 deletions apps/web/src/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { useRouter } from 'next/navigation'
import { COLOR_THEME_OPTIONS, type ColorTheme, useColorTheme } from '@/hooks/use-color-theme'
import { getToolMessageKey } from '@/lib/tool-i18n'
import { PasskeySection } from '@/components/settings/passkey-section'
import { useActiveOrg, useActiveWorkspace } from '@/store/workspace-store'
import { Briefcase, ChevronRight } from 'lucide-react'
const colorDisplay: Record<ColorTheme, { swatchClass: string; name: string }> = {
cyan: { swatchClass: 'bg-cyan-500', name: 'Teal' },
blue: { swatchClass: 'bg-blue-500', name: 'Blue' },
Expand All @@ -36,6 +38,8 @@ export default function SettingsPage() {
const { theme, setTheme } = useTheme()
const { colorTheme, setColorTheme } = useColorTheme()
const { isToolEnabled, toggleTool } = useToolVisibility()
const activeOrg = useActiveOrg()
const activeWorkspace = useActiveWorkspace()
const [mounted, setMounted] = useState(false)

useEffect(() => {
Expand All @@ -54,10 +58,19 @@ export default function SettingsPage() {
return (
<div className="flex-1 space-y-8 p-6 md:p-8 max-w-5xl mx-auto w-full pt-20 lg:pt-8 bg-background/50">
<div className="space-y-1.5">
<p className="inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
<span className="h-1.5 w-1.5 rounded-full bg-gradient-to-br from-primary to-violet-500" />
Workspace
</p>
{activeOrg && activeWorkspace ? (
<p className="inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
<Briefcase className="h-3 w-3 text-primary" />
<span className="truncate max-w-[120px] sm:max-w-none">{activeOrg.name}</span>
<ChevronRight className="h-3 w-3 opacity-60" />
<span className="truncate max-w-[160px] sm:max-w-none text-foreground/80">{activeWorkspace.name}</span>
</p>
) : (
<p className="inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
<span className="h-1.5 w-1.5 rounded-full bg-gradient-to-br from-primary to-violet-500" />
Workspace
</p>
)}
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight">{t('title')}</h2>
<p className="text-muted-foreground">{t('subtitle')}</p>
</div>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function Header({ showThemeToggle = true }: HeaderProps) {
return (
<header
className={cn(
"mdt-nav fixed inset-x-0 top-0 z-50 w-full",
"mdt-nav fixed inset-x-0 top-0 z-50 w-full pt-[env(safe-area-inset-top)]",
scrolled
? "glass-nav border-b border-white/5 shadow-[0_8px_30px_-12px_rgba(0,0,0,0.7)]"
: "border-b border-transparent bg-transparent"
Expand Down Expand Up @@ -126,7 +126,7 @@ export function Header({ showThemeToggle = true }: HeaderProps) {
<Button
variant="ghost"
size="icon"
className="md:hidden h-10 w-10"
className="md:hidden h-11 w-11"
onClick={() => setIsMenuOpen(!isMenuOpen)}
aria-expanded={isMenuOpen}
aria-label={isMenuOpen ? "Close menu" : "Open menu"}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function Logo({ size = 32, className = '', showText = true }: LogoProps)
/>
</div>
{showText && (
<div className="relative h-8 w-32 flex items-center">
<div className="relative h-8 w-32 hidden sm:flex items-center">
<div className="relative h-8 w-24">
<Image
src="/logo-text-light.png"
Expand Down
65 changes: 65 additions & 0 deletions apps/web/src/components/mobile-desktop-hint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use client'

import { useEffect, useState } from 'react'
import { usePathname } from 'next/navigation'
import { Monitor, X } from 'lucide-react'

const DESKTOP_RECOMMENDED_SLUGS = new Set([
'api-client',
'sql-client',
'database-explorer',
'redis-commander',
'encryption-playground',
's3-drive',
'csv-excel-json',
'environment-manager',
'notes',
'to-do',
'bookmarks',
'break-room',
'api-keys',
'image-compressor',
'image-to-base64',
'svg-optimizer',
])

function slugFromPathname(pathname: string): string | null {
const match = pathname.match(/^\/app\/([^/]+)/)
return match ? match[1] : null
}

export function MobileDesktopHint() {
const pathname = usePathname()
const slug = slugFromPathname(pathname)
const applies = slug !== null && DESKTOP_RECOMMENDED_SLUGS.has(slug)
const storageKey = applies ? `mdt:desktop-hint-dismissed:${slug}` : ''

const [dismissed, setDismissed] = useState(true)

useEffect(() => {
if (!applies) return
setDismissed(sessionStorage.getItem(storageKey) === '1')
}, [applies, storageKey])

if (!applies || dismissed) return null

return (
<div className="md:hidden flex items-start gap-3 border-b border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-900 dark:text-amber-200">
<Monitor className="mt-0.5 h-4 w-4 shrink-0" aria-hidden />
<p className="flex-1 leading-relaxed">
This tool works best on a larger screen. You can keep going on mobile, but expect a tight fit.
</p>
<button
type="button"
aria-label="Dismiss"
onClick={() => {
sessionStorage.setItem(storageKey, '1')
setDismissed(true)
}}
className="-mr-1 -mt-1 inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-amber-500/20"
>
<X className="h-4 w-4" />
</button>
</div>
)
}
2 changes: 1 addition & 1 deletion apps/web/src/components/mobile-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function MobileNav() {
return (
<nav
className={cn(
"fixed bottom-6 left-1/2 -translate-x-1/2 z-50",
"fixed bottom-[max(1.5rem,calc(env(safe-area-inset-bottom)+0.5rem))] left-1/2 -translate-x-1/2 z-50",
"flex items-center justify-around gap-1",
"h-[64px] px-2 w-[calc(100%-2rem)] max-w-sm",
"border border-border/50 rounded-full",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/notifications-bell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function NotificationsBell() {
<DropdownMenuTrigger asChild>
<button
type="button"
className="relative hidden md:inline-flex h-9 w-9 items-center justify-center rounded-lg border border-border/60 bg-muted/40 text-muted-foreground transition-colors hover:border-primary/40 hover:bg-muted/60 hover:text-foreground"
className="relative inline-flex h-9 w-9 items-center justify-center rounded-lg border border-border/60 bg-muted/40 text-muted-foreground transition-colors hover:border-primary/40 hover:bg-muted/60 hover:text-foreground"
aria-label={count > 0 ? `${count} notification${count === 1 ? "" : "s"}` : "Notifications"}
>
<Bell className="h-4 w-4" />
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/pricing-tiers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ export function PricingTiers() {
onClick={() => setBilling(option)}
className={
billing === option
? 'mdt-btn-grad rounded-full px-5 py-2 text-sm font-medium capitalize'
: 'rounded-full px-5 py-2 text-sm font-medium capitalize text-muted-foreground transition-colors hover:text-foreground'
? 'mdt-btn-grad inline-flex min-h-[44px] items-center rounded-full px-5 py-2 text-sm font-medium capitalize sm:min-h-0'
: 'inline-flex min-h-[44px] items-center rounded-full px-5 py-2 text-sm font-medium capitalize text-muted-foreground transition-colors hover:text-foreground sm:min-h-0'
}
>
{option}
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/components/sidebar/client-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { useTabStore } from '@/store/tab-store';
import { isTabRoute } from '@/lib/route-config';
import { getTabComponent, isRegisteredTab } from '@/lib/tab-registry';
import { MigrationBanner } from '@/components/migration-banner';
import { MobileDesktopHint } from '@/components/mobile-desktop-hint';
import { WorkspaceSwitcher } from '@/components/workspace-switcher';
import { NotificationsBell } from '@/components/notifications-bell';

// Renders all open tool tabs simultaneously. The active tab is visible;
// inactive tabs use display:none to stay mounted (preserving their state).
Expand Down Expand Up @@ -85,12 +88,19 @@ function Layout({ children }: { children: React.ReactNode }) {
}`}
>
<div className="shrink-0 z-20 bg-background">
<div className="md:hidden flex items-center justify-between gap-2 px-3 pt-[max(0.5rem,env(safe-area-inset-top))] pb-2 border-b border-border/40">
<div className="min-w-0 flex-1">
<WorkspaceSwitcher />
</div>
<NotificationsBell />
</div>
{inTabMode ? (
<TabBar onNewTab={openCommandPalette} />
) : (
<NavBar />
)}
<MigrationBanner />
<MobileDesktopHint />
</div>

{inTabMode ? (
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/workspace-switcher-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function WorkspaceSwitcherDropdown() {
<div className="flex items-center gap-1.5">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="hidden md:inline-flex items-center gap-1.5 h-9 rounded-lg border border-border/60 bg-muted/40 px-2.5 text-sm font-medium text-foreground/90 hover:border-primary/40 cursor-pointer">
<button className="inline-flex items-center gap-1.5 h-9 rounded-lg border border-border/60 bg-muted/40 px-2.5 text-sm font-medium text-foreground/90 hover:border-primary/40 cursor-pointer">
<Briefcase className="h-4 w-4 text-muted-foreground" />
<span className="truncate max-w-[140px]">{ws.name}</span>
<ChevronsUpDown className="h-3.5 w-3.5 text-muted-foreground" />
Expand Down