forked from
rocksky.app/rocksky
A decentralized music tracking and discovery platform built on AT Protocol 馃幍
1import styled from "@emotion/styled";
2import { useSearch } from "@tanstack/react-router";
3import { Button } from "baseui/button";
4import { Input } from "baseui/input";
5import { PLACEMENT, ToasterContainer } from "baseui/toast";
6import { LabelMedium } from "baseui/typography";
7import { useAtomValue } from "jotai";
8import { useEffect, useState } from "react";
9import { profileAtom } from "../atoms/profile";
10import ScrobblesAreaChart from "../components/ScrobblesAreaChart";
11import StickyPlayer from "../components/StickyPlayer";
12import { API_URL } from "../consts";
13import useProfile from "../hooks/useProfile";
14import CloudDrive from "./CloudDrive";
15import ExternalLinks from "./ExternalLinks";
16import Navbar from "./Navbar";
17import Search from "./Search";
18import SpotifyLogin from "./SpotifyLogin";
19
20const Container = styled.div`
21 display: flex;
22 justify-content: center;
23 height: 100vh;
24 overflow-y: auto;
25 flex-direction: row;
26`;
27
28const Flex = styled.div`
29 display: flex;
30 width: 770px;
31 margin-top: 50px;
32 flex-direction: column;
33 margin-bottom: 200px;
34`;
35
36const RightPane = styled.div`
37 @media (max-width: 1152px) {
38 display: none;
39 }
40`;
41
42const Link = styled.a`
43 text-decoration: none;
44 cursor: pointer;
45 display: block;
46 font-size: 13px;
47
48 &:hover {
49 text-decoration: underline;
50 }
51`;
52
53export type MainProps = {
54 children: React.ReactNode;
55 withRightPane?: boolean;
56};
57
58function Main(props: MainProps) {
59 const { children } = props;
60 const withRightPane = props.withRightPane ?? true;
61 const [handle, setHandle] = useState("");
62 const jwt = localStorage.getItem("token");
63 const profile = useAtomValue(profileAtom);
64 const [token, setToken] = useState<string | null>(null);
65 const { did, cli } = useSearch({ strict: false });
66
67 useEffect(() => {
68 if (did && did !== "null") {
69 localStorage.setItem("did", did);
70
71 const fetchToken = async () => {
72 try {
73 const response = await fetch(`${API_URL}/token`, {
74 method: "GET",
75 headers: {
76 "session-did": did,
77 },
78 });
79 const data = await response.json();
80 localStorage.setItem("token", data.token);
81 setToken(data.token);
82
83 if (cli) {
84 await fetch("http://localhost:6996/token", {
85 method: "POST",
86 headers: {
87 "Content-Type": "application/json",
88 },
89 body: JSON.stringify({ token: data.token }),
90 });
91 }
92
93 if (!jwt && data.token) {
94 window.location.href = "/";
95 }
96 } catch (e) {
97 console.error(e);
98 }
99 };
100 fetchToken();
101 }
102 // eslint-disable-next-line react-hooks/exhaustive-deps
103 }, []);
104
105 useProfile(token || localStorage.getItem("token"));
106
107 const onLogin = async () => {
108 if (!handle.trim()) {
109 return;
110 }
111
112 if (API_URL.includes("localhost")) {
113 window.location.href = `${API_URL}/login?handle=${handle}`;
114 return;
115 }
116
117 window.location.href = `https://rocksky.pages.dev/loading?handle=${handle}`;
118 };
119
120 return (
121 <Container className="bg-[var(--color-background)] text-[var(--color-text)]">
122 <ToasterContainer
123 placement={PLACEMENT.top}
124 overrides={{
125 ToastBody: {
126 style: {
127 zIndex: 2,
128 boxShadow: "none",
129 },
130 },
131 }}
132 />
133 <Flex style={{ width: withRightPane ? "770px" : "1090px" }}>
134 <Navbar />
135 <div
136 style={{
137 position: "relative",
138 }}
139 >
140 {children}
141 </div>
142 </Flex>
143 {withRightPane && (
144 <RightPane className="relative w-[300px]">
145 <div className="fixed top-[100px] w-[300px] bg-white p-[20px]">
146 <div className="mb-[30px]">
147 <Search />
148 </div>
149 {jwt && profile && !profile.spotifyConnected && <SpotifyLogin />}
150 {jwt && profile && <CloudDrive />}
151 {!jwt && (
152 <div className="mt-[40px]">
153 <div className="mb-[20px]">
154 <div className="mb-[15px]">
155 <LabelMedium className="!text-[var(--color-text)]">
156 Bluesky handle
157 </LabelMedium>
158 </div>
159 <Input
160 name="handle"
161 startEnhancer={
162 <div className="text-[var(--color-text-muted)] bg-[var(--color-input-background)]">
163 @
164 </div>
165 }
166 placeholder="<username>.bsky.social"
167 value={handle}
168 onChange={(e) => setHandle(e.target.value)}
169 overrides={{
170 Root: {
171 style: {
172 backgroundColor: "var(--color-input-background)",
173 borderColor: "var(--color-input-background)",
174 },
175 },
176 StartEnhancer: {
177 style: {
178 backgroundColor: "var(--color-input-background)",
179 },
180 },
181 InputContainer: {
182 style: {
183 backgroundColor: "var(--color-input-background)",
184 },
185 },
186 Input: {
187 style: {
188 color: "var(--color-text)",
189 caretColor: "var(--color-text)",
190 },
191 },
192 }}
193 />
194 </div>
195 <Button
196 onClick={onLogin}
197 overrides={{
198 BaseButton: {
199 style: {
200 width: "100%",
201 backgroundColor: "var(--color-primary)",
202 ":hover": {
203 backgroundColor: "var(--color-primary)",
204 },
205 ":focus": {
206 backgroundColor: "var(--color-primary)",
207 },
208 },
209 },
210 }}
211 >
212 Sign In
213 </Button>
214 <LabelMedium className="text-center mt-[20px] !text-[var(--color-text-muted)]">
215 Don't have an account?
216 </LabelMedium>
217 <div className="text-center text-[var(--color-text-muted)] ">
218 <a
219 href="https://bsky.app"
220 className="no-underline cursor-pointer !text-[var(--color-primary)]"
221 target="_blank"
222 >
223 Sign up for Bluesky
224 </a>{" "}
225 to create one now!
226 </div>
227 </div>
228 )}
229
230 <div className="mt-[40px]">
231 <ScrobblesAreaChart />
232 </div>
233 <ExternalLinks />
234 <div className="inline-flex mt-[40px]">
235 <Link
236 href="https://docs.rocksky.app/introduction-918639m0"
237 target="_blank"
238 className="mr-[10px] text-[var(--color-primary)]"
239 >
240 About
241 </Link>
242 <Link
243 href="https://docs.rocksky.app/faq-918661m0"
244 target="_blank"
245 className="mr-[10px] text-[var(--color-primary)]"
246 >
247 FAQ
248 </Link>
249 <Link
250 href="https://doc.rocksky.app/"
251 target="_blank"
252 className="mr-[10px] text-[var(--color-primary)]"
253 >
254 API Docs
255 </Link>
256
257 <Link
258 href="https://discord.gg/EVcBy2fVa3"
259 target="_blank"
260 className="text-[var(--color-primary)]"
261 >
262 Discord
263 </Link>
264 </div>
265 </div>
266 </RightPane>
267 )}
268 <StickyPlayer />
269 </Container>
270 );
271}
272
273export default Main;