summaryrefslogtreecommitdiff
path: root/packages/tweetdeck/src/components/Sidebar.tsx
blob: 3d9b85dfd18cfa89828c98a99535027d721f3866 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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>
  );
}