From cb1b56f5a0eddbf77446f415f2beda57c8305f85 Mon Sep 17 00:00:00 2001
From: polwex <polwex@sortug.com>
Date: Sun, 23 Nov 2025 01:12:53 +0700
Subject: wut

---
 packages/tweetdeck/src/components/Sidebar.tsx | 153 ++++++++++++++++++++++++++
 1 file changed, 153 insertions(+)
 create mode 100644 packages/tweetdeck/src/components/Sidebar.tsx

(limited to 'packages/tweetdeck/src/components/Sidebar.tsx')

diff --git a/packages/tweetdeck/src/components/Sidebar.tsx b/packages/tweetdeck/src/components/Sidebar.tsx
new file mode 100644
index 0000000..3d9b85d
--- /dev/null
+++ b/packages/tweetdeck/src/components/Sidebar.tsx
@@ -0,0 +1,153 @@
+import { useEffect, useState } from "react";
+import type { DeckAccount } from "../types/app";
+import { twitterClient } from "@/lib/client/twitterClient";
+
+export interface NewAccountInput {
+  cookie: string;
+}
+
+interface SidebarProps {
+  accounts: DeckAccount[];
+  activeAccountId?: string;
+  onActivate: (id: string) => void;
+  onAddAccount: (payload: NewAccountInput) => void;
+  onRemoveAccount: (id: string) => void;
+  onAddColumn: () => void;
+}
+
+export function Sidebar({
+  accounts,
+  activeAccountId,
+  onActivate,
+  onAddAccount,
+  onRemoveAccount,
+  onAddColumn,
+}: SidebarProps) {
+  const [isAdding, setIsAdding] = useState(!accounts.length);
+  const [cookie, setCookie] = useState("");
+  const [showCookie, setShowCookie] = useState(false);
+
+  const handleSubmit = (event: React.FormEvent) => {
+    event.preventDefault();
+    if (!cookie.trim()) return;
+    onAddAccount({ cookie: decodeURIComponent(cookie.trim()) });
+    setCookie("");
+    setIsAdding(false);
+  };
+
+  return (
+    <aside className="sidebar">
+      <div>
+        <div className="brand">
+          <div className="brand-glow" />
+          <div>
+            <p className="eyebrow">Project Starling</p>
+            <h1>Open TweetDeck</h1>
+            <p className="tagline">
+              Multi-account Twitter cockpit powered by Bun.
+            </p>
+          </div>
+        </div>
+
+        <section className="sidebar-section">
+          <header>
+            <p className="eyebrow">Accounts</p>
+            <button className="ghost" onClick={() => setIsAdding((v) => !v)}>
+              {isAdding ? "Close" : "Add"}
+            </button>
+          </header>
+          {!accounts.length && !isAdding && (
+            <p className="muted">
+              Add a Twitter session cookie to start streaming timelines. You can
+              rename the account later once data loads.
+            </p>
+          )}
+          {accounts.map((account) => (
+            <div
+              role="button"
+              tabIndex={0}
+              key={account.id}
+              className={`account-chip ${account.id === activeAccountId ? "active" : ""}`}
+              onClick={() => onActivate(account.id)}
+              onKeyDown={(event) => {
+                if (event.key === "Enter" || event.key === " ") {
+                  event.preventDefault();
+                  onActivate(account.id);
+                }
+              }}
+            >
+              <span
+                className="chip-accent"
+                style={{ background: account.accent }}
+              />
+              <span>
+                <strong>{account.label}</strong>
+                {account.handle ? <small>@{account.handle}</small> : null}
+              </span>
+              <span className="chip-actions">
+                <button
+                  type="button"
+                  className="ghost"
+                  onClick={(event) => {
+                    event.stopPropagation();
+                    onRemoveAccount(account.id);
+                  }}
+                  aria-label={`Remove ${account.label}`}
+                >
+                  ×
+                </button>
+              </span>
+            </div>
+          ))}
+          {isAdding && (
+            <form className="account-form" onSubmit={handleSubmit}>
+              <label>
+                Twitter session cookie
+                <textarea
+                  className={!showCookie ? "masked" : undefined}
+                  placeholder="Paste the entire Cookie header"
+                  value={cookie}
+                  onChange={(e) => setCookie(e.target.value)}
+                  rows={4}
+                />
+              </label>
+              <label className="checkbox">
+                <input
+                  type="checkbox"
+                  checked={showCookie}
+                  onChange={(e) => setShowCookie(e.target.checked)}
+                />
+                Reveal cookie contents
+              </label>
+              <small className="muted">
+                Cookie stays in your browser via localStorage. It is only sent
+                to your Bun server when fetching timelines.
+              </small>
+              <button
+                className="primary"
+                type="submit"
+                disabled={!cookie.trim()}
+              >
+                Save account
+              </button>
+            </form>
+          )}
+        </section>
+      </div>
+
+      <div className="sidebar-footer">
+        <button
+          className="primary wide"
+          onClick={onAddColumn}
+          disabled={!accounts.length}
+        >
+          + Add column
+        </button>
+        <p className="muted tiny">
+          Need a cookie? Open x.com, inspect network requests and copy the
+          request `Cookie` header. Keep it secret.
+        </p>
+      </div>
+    </aside>
+  );
+}
-- 
cgit v1.2.3

