summaryrefslogtreecommitdiff
path: root/packages/tweetdeck/src/components/Sidebar.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/tweetdeck/src/components/Sidebar.tsx')
-rw-r--r--packages/tweetdeck/src/components/Sidebar.tsx153
1 files changed, 153 insertions, 0 deletions
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>
+ );
+}