diff options
Diffstat (limited to 'packages/tweetdeck/src/components/Sidebar.tsx')
| -rw-r--r-- | packages/tweetdeck/src/components/Sidebar.tsx | 153 |
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> + ); +} |
