its for when you want to get like notifications for your reposts
1import { createSignal, onCleanup, For, type Component } from "solid-js";
2
3import type {} from "@atcute/bluesky";
4import type {} from "@atcute/atproto";
5import { AppProps, ConnectionStatus, Notification } from "./types.js";
6import { ActivityItem } from "./ActivityItem.jsx";
7import { connect as connectService } from "./ws.ts";
8
9const Wrapped: Component = () => {
10 const [actorId, setActorId] = createSignal<string>("");
11 const [serviceDomain, setWsUrl] = createSignal<string>("likes.gaze.systems");
12 const [items, setItems] = createSignal<Notification[]>([]);
13 const [connectionStatus, setConnectionStatus] =
14 createSignal<ConnectionStatus>("disconnected");
15 const [error, setError] = createSignal<string | null>(null);
16 const [ws, setWs] = createSignal<WebSocket | null>(null);
17
18 const connect = async () => {
19 // close existing connection if any
20 ws()?.close();
21 setWs(
22 (await connectService({
23 actorId: actorId(),
24 serviceDomain: serviceDomain(),
25 pushNotification: (item) => setItems((prev) => [item, ...prev]),
26 setConnectionStatus,
27 setError,
28 })) ?? null,
29 );
30 };
31
32 const disconnect = (): void => {
33 setConnectionStatus("disconnecting...");
34 ws()?.close();
35 setWs(null);
36 };
37
38 onCleanup(disconnect);
39
40 const props: AppProps = {
41 actorIdSignal: [actorId, setActorId],
42 serviceDomainSignal: [serviceDomain, setWsUrl],
43 items,
44 clearItems: () => {
45 setItems([]);
46 },
47 connectionStatus,
48 error,
49 connect,
50 disconnect,
51 isExtension: false,
52 };
53
54 return <App {...props} />;
55};
56export default Wrapped;
57
58export const App: Component<AppProps> = (props) => {
59 const [actorId, setActorId] = props.actorIdSignal;
60 const [serviceDomain, setWsUrl] = props.serviceDomainSignal;
61 const { disconnect, connect, error, connectionStatus, items, clearItems } =
62 props;
63
64 const isConnected = () => {
65 return (
66 connectionStatus() == "connecting..." || connectionStatus() == "connected"
67 );
68 };
69
70 const inputStyle = `flex-1 ${props.isExtension ? "px-2 py-1" : "px-4 py-2"} border border-gray-300 rounded-none bg-white focus:(outline-none ring-2)`;
71
72 return (
73 <div max-w-4xl mx-auto p-2 bg-gray-50 min-h-screen>
74 {props.isExtension ? (
75 <></>
76 ) : (
77 <h1 border="l-16 blue" font-bold text="3xl gray-800" pl-2 mb-6>
78 monitor bluesky repost likes
79 </h1>
80 )}
81
82 {/* connection */}
83 <div mb-6>
84 <div flex gap-2 mb-2>
85 <input
86 type="text"
87 value={serviceDomain()}
88 onInput={(e) => setWsUrl((e.target as HTMLInputElement).value)}
89 placeholder="enter service host (e.g., likes.gaze.systems)"
90 class={`${inputStyle} focus-ring-purple-500`}
91 disabled={isConnected()}
92 />
93 </div>
94 <div flex gap-2 mb-2>
95 <input
96 type="text"
97 value={actorId()}
98 onInput={(e) => setActorId((e.target as HTMLInputElement).value)}
99 onKeyPress={(e) => {
100 if (!isConnected() && e.key == "Enter") {
101 connect();
102 e.preventDefault();
103 }
104 }}
105 placeholder="enter handle or DID"
106 class={`${inputStyle} focus-ring-blue-500`}
107 disabled={isConnected()}
108 />
109 <button
110 onClick={() => (isConnected() ? disconnect() : connect())}
111 class={`${props.isExtension ? "px-3 py-1" : "px-6 py-2"} rounded-none font-medium transition-colors ${
112 isConnected()
113 ? "bg-red-500 hover:bg-red-600 text-white"
114 : "bg-blue-500 hover:bg-blue-600 text-white"
115 }`}
116 >
117 {isConnected() ? "disconnect" : "connect"}
118 </button>
119 </div>
120
121 {/* Status indicator */}
122 <div flex gap-2 items-center>
123 <div w-fit border border-gray-300 bg-gray-80 px-1 py="0.5">
124 <div
125 inline-block
126 w-3
127 h-3
128 rounded-full
129 class={
130 connectionStatus() === "connected"
131 ? "bg-green-500"
132 : connectionStatus() === "connecting..."
133 ? "bg-yellow-500"
134 : connectionStatus() === "error"
135 ? "bg-red-500"
136 : "bg-gray-400"
137 }
138 />
139 <span
140 ml-2
141 align="10%"
142 text={`${props.isExtension ? "xs" : "sm"} gray-600`}
143 >
144 status: {connectionStatus()}
145 </span>
146 </div>
147 {error() && (
148 <div w-fit border border-gray-300 bg-gray-80 p-1>
149 <div text={`${props.isExtension ? "xs" : "sm"} red-500`}>
150 {error()}
151 </div>
152 </div>
153 )}
154 </div>
155 </div>
156
157 {/* feed */}
158 <div>
159 <div class="flex justify-between items-center mb-4">
160 <h2
161 border="l-8 blue"
162 pl-2
163 text={`${props.isExtension ? "base" : "xl"} gray-700`}
164 font-semibold
165 >
166 activity feed ({items().length})
167 </h2>
168 <button
169 onClick={clearItems}
170 text={`white ${props.isExtension ? "xs" : "sm"}`}
171 class={`${props.isExtension ? "px-2 py-1" : "px-4 py-2"} ml-1 bg-gray-500 hover:bg-gray-600 rounded-none transition-colors disabled:opacity-50`}
172 disabled={items().length === 0}
173 >
174 clear feed
175 </button>
176 </div>
177
178 <div
179 class={`${props.isExtension ? "p-2" : "p-4"} h-[60vh] max-h-[60vh] overflow-y-auto border border-gray-200 rounded-none bg-white`}
180 >
181 {items().length === 0 ? (
182 <div flex items-center w-full h-full>
183 <div mx-auto text="center gray-500">
184 <div text-lg mb-2>
185 👀
186 </div>
187 <div>
188 nothing yet. connect and wait for someone to like a repost of
189 yours!
190 </div>
191 </div>
192 </div>
193 ) : (
194 <For each={items()}>
195 {(item, index) => (
196 <div mb={index() == items().length - 1 ? "0" : "2"}>
197 <ActivityItem data={item} isExtension={props.isExtension} />
198 </div>
199 )}
200 </For>
201 )}
202 </div>
203 </div>
204
205 {props.isExtension ? (
206 <></>
207 ) : (
208 <div border bg-blue-50 border-blue-200 rounded-none pl="1.5" p-1 mt-4>
209 <span text="xs blue-800" align="10%">
210 <span text-pink-400>source</span> <span text-gray>=</span>{" "}
211 <a
212 href="https://tangled.sh/@poor.dog/bsky-repost-likes"
213 text-orange-700
214 hover:text-orange-400
215 >
216 "https://tangled.sh/@poor.dog/bsky-repost-likes"
217 </a>{" "}
218 // made by{" "}
219 <a text-purple-700 hover:text-purple href="https://gaze.systems">
220 dusk
221 </a>
222 </span>
223 </div>
224 )}
225 </div>
226 );
227};