From 0f6124f0981c6d5e0b2e559ee613ec92f2da1be8 Mon Sep 17 00:00:00 2001 From: Mohammad Junaid Date: Tue, 7 Apr 2026 22:22:46 +0100 Subject: [PATCH] feat(upload): restore tab cycling + inline claude code tab to match npm terminal style --- next-env.d.ts | 2 +- src/components/upload/UploadScreen.tsx | 363 ++++++++++++++++--------- 2 files changed, 232 insertions(+), 133 deletions(-) diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/src/components/upload/UploadScreen.tsx b/src/components/upload/UploadScreen.tsx index 59a5ffe..39f186d 100644 --- a/src/components/upload/UploadScreen.tsx +++ b/src/components/upload/UploadScreen.tsx @@ -160,61 +160,111 @@ function PreviewCards() { } function TabContentNPM({ active }: { active: boolean }) { - const [lines, setLines] = useState(0); - useEffect(() => { - if (!active) { - setLines(0); - return; - } - const timeouts = [ - setTimeout(() => setLines(1), 50), - setTimeout(() => setLines(2), 100), - setTimeout(() => setLines(3), 150), - setTimeout(() => setLines(4), 200), - setTimeout(() => setLines(5), 250), - ]; - return () => timeouts.forEach(clearTimeout); - }, [active]); + const [copied, setCopied] = useState(false); + const copyCmd = () => { + navigator.clipboard.writeText("npx ccrewind"); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }; + + const hourBars = [40, 30, 25, 20, 10, 10, 10, 15, 20, 30, 35, 45, 60, 70, 80, 95, 75, 30, 15, 50, 55, 65, 50, 45]; return ( -
-
-
-
-
-
-
-

- $ npx ccrewind +

+
+
+
+
+
+
+ +
+ + $ npx ccrewind + + +
+ +

⟡ CC REWIND ⟡

+ +
+
+

$361

+

cost

+
+
+

585M

+

tokens

+
+
+

8.4K

+

messages

+
+
+ +

+ ─── ACTIVITY BY HOUR ───

- {lines >= 1 &&

◆ CC REWIND ◆

} - {lines >= 2 &&

Reading ~/.claude...

} - {lines >= 3 && ( -

- Your character: The Quant -

- )} - {lines >= 4 && ( -

- Claude Elo: 847 / 1000 -

- )} - {lines >= 5 &&

→ ccrewind.com for the full story

} -
-
- - npm version - - - Socket Badge - +
+ {hourBars.map((h, i) => ( +
= 70 ? "#ff6b35" : h >= 40 ? "rgba(255,107,53,0.5)" : "rgba(255,107,53,0.2)", + opacity: active ? 1 : 0, + transition: `opacity 0.2s ease ${i * 0.02}s`, + }} + /> + ))} +
+

peak: 3pm (736 messages)

+ +
); @@ -228,59 +278,73 @@ function TabContentClaude() { setTimeout(() => setCopied(false), 1500); }; return ( -
-

Run inside Claude Code

-
-
- - - npx ccrewind --setup - - - # registers slash commands +
+
+
+
+
+
+
+ +
+ + $ npx ccrewind --setup - - -
-
-
- /ccrewind +
-
- /ccrewind-ui + +

+ # Then use inside any Claude Code session ↓ +

+ +

+ ─── SLASH COMMANDS ─── +

+ +
+

+ /ccrewind{" "} + — TUI report in your terminal +

+

+ /ccrewind-ui{" "} + — Open web UI in browser +

+ +

registered to ~/.local/share/ccrewind/

); @@ -295,9 +359,35 @@ export default function UploadScreen({ onDataParsed }: UploadScreenProps) { const [showHiddenHelp, setShowHiddenHelp] = useState(false); const inputRef = useRef(null); - const [activeTab, setActiveTab] = useState<"npm" | "web" | "claude">("web"); - const [everExpandedNpm, setEverExpandedNpm] = useState(false); + const [activeTab, setActiveTab] = useState<"npm" | "web" | "claude">("npm"); const [hasInteracted, setHasInteracted] = useState(false); + const [cycleKey, setCycleKey] = useState(0); + const [cycleActive, setCycleActive] = useState(true); + const TABS = ["npm", "web", "claude"] as const; + const CYCLE_MS = 6000; + + useEffect(() => { + if (!cycleActive) return; + const timer = setInterval(() => { + setActiveTab((prev) => { + const idx = TABS.indexOf(prev); + return TABS[(idx + 1) % TABS.length]; + }); + setCycleKey((k) => k + 1); + }, CYCLE_MS); + return () => clearInterval(timer); + }, [cycleKey, cycleActive]); + + useEffect(() => { + const stop = () => setCycleActive(false); + window.addEventListener("click", stop, { once: true }); + return () => window.removeEventListener("click", stop); + }, []); + + const handleTabClick = (t: "npm" | "web" | "claude") => { + setActiveTab(t); + setCycleActive(false); + }; useEffect(() => { fetch("/api/local-data/status") @@ -438,7 +528,7 @@ export default function UploadScreen({ onDataParsed }: UploadScreenProps) { onClick={handleDemo} className="w-full bg-surface-container-low hover:bg-surface-container border border-on-surface/8 text-on-surface/60 hover:text-on-surface rounded-xl px-4 py-3 font-label text-[11px] font-bold uppercase tracking-wider transition-all duration-200" > - Try demo + Try a demo! @@ -538,7 +628,7 @@ export default function UploadScreen({ onDataParsed }: UploadScreenProps) { return (
-
+
- +
{(["npm", "web", "claude"] as const).map((t, idx) => ( ))}
- {activeTab === "npm" && ( @@ -718,22 +810,29 @@ export default function UploadScreen({ onDataParsed }: UploadScreenProps) { )} - - +
+
- - - - )} + {/* Preview cards — fixed to viewport center, independent of tab height */} + {!isLoading && ( + +
+
+ +
+
+
+ )} + {isDragging && (