+12
package-lock.json
+12
package-lock.json
···
32
32
"@iconify-icon/react": "^3.0.1",
33
33
"@iconify-json/material-symbols": "^1.2.42",
34
34
"@iconify-json/mdi": "^1.2.3",
35
+
"@iconify/json": "^2.2.396",
35
36
"@svgr/core": "^8.1.0",
36
37
"@svgr/plugin-jsx": "^8.1.0",
37
38
"@testing-library/dom": "^10.4.0",
···
1681
1682
"license": "Apache-2.0",
1682
1683
"dependencies": {
1683
1684
"@iconify/types": "*"
1685
+
}
1686
+
},
1687
+
"node_modules/@iconify/json": {
1688
+
"version": "2.2.396",
1689
+
"resolved": "https://registry.npmjs.org/@iconify/json/-/json-2.2.396.tgz",
1690
+
"integrity": "sha512-tijg77JFuYIt32S9N8p7La8C0zp9zKZsX6UP8ip5GVB1F6Mp3pZA5Vc5eAquTY50NoDJX58U6z4Qn3d6Wyossg==",
1691
+
"dev": true,
1692
+
"license": "MIT",
1693
+
"dependencies": {
1694
+
"@iconify/types": "*",
1695
+
"pathe": "^2.0.0"
1684
1696
}
1685
1697
},
1686
1698
"node_modules/@iconify/types": {
+1
package.json
+1
package.json
+1
src/auto-imports.d.ts
+1
src/auto-imports.d.ts
···
8
8
declare global {
9
9
const IconMaterialSymbolsAccountCircle: typeof import('~icons/material-symbols/account-circle.jsx').default
10
10
const IconMaterialSymbolsAccountCircleOutline: typeof import('~icons/material-symbols/account-circle-outline.jsx').default
11
+
const IconMaterialSymbolsArrowBack: typeof import('~icons/material-symbols/arrow-back.jsx').default
11
12
const IconMaterialSymbolsHome: typeof import('~icons/material-symbols/home.jsx').default
12
13
const IconMaterialSymbolsHomeOutline: typeof import('~icons/material-symbols/home-outline.jsx').default
13
14
const IconMaterialSymbolsNotifications: typeof import('~icons/material-symbols/notifications.jsx').default
+29
src/components/Header.tsx
+29
src/components/Header.tsx
···
1
+
import { Link, useRouter } from "@tanstack/react-router";
2
+
3
+
export function Header({
4
+
backButtonCallback,
5
+
title
6
+
}: {
7
+
backButtonCallback?: () => void;
8
+
title?: string;
9
+
}) {
10
+
const router = useRouter();
11
+
//const what = router.history.
12
+
return (
13
+
<div className="flex items-center gap-4 px-4 py-3 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700">
14
+
{backButtonCallback ? (<Link
15
+
to=".."
16
+
//className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg"
17
+
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg"
18
+
onClick={(e) => {
19
+
e.preventDefault();
20
+
backButtonCallback();
21
+
}}
22
+
aria-label="Go back"
23
+
>
24
+
<IconMaterialSymbolsArrowBack className="w-6 h-6" />
25
+
</Link>) : (<div className="w-[0px]" />)}
26
+
<span className="text-[21px] font-roboto">{title}</span>
27
+
</div>
28
+
);
29
+
}
+5
-4
src/components/InfiniteCustomFeed.tsx
+5
-4
src/components/InfiniteCustomFeed.tsx
···
1
1
import * as React from "react";
2
+
2
3
//import { useInView } from "react-intersection-observer";
3
4
import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer";
4
5
import { useAuth } from "~/providers/UnifiedAuthProvider";
5
6
import {
6
-
useQueryArbitrary,
7
-
useQueryIdentity,
8
7
useInfiniteQueryFeedSkeleton,
8
+
// useQueryArbitrary,
9
+
// useQueryIdentity,
9
10
} from "~/utils/useQuery";
10
11
11
12
interface InfiniteCustomFeedProps {
···
112
113
<button
113
114
onClick={handleRefresh}
114
115
disabled={isRefetching}
115
-
className="sticky lg:bottom-6 bottom-24 ml-4 w-[42px] h-[42px] z-10 bg-gray-500 hover:bg-gray-600 text-gray-50 p-[9px] rounded-full shadow-lg transition-transform duration-200 ease-in-out hover:scale-110 disabled:bg-gray-400 disabled:cursor-not-allowed"
116
+
className="sticky lg:bottom-4 bottom-22 ml-4 w-[42px] h-[42px] z-10 bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 text-gray-50 p-[9px] rounded-full shadow-lg transition-transform duration-200 ease-in-out hover:scale-110 disabled:bg-gray-400 disabled:cursor-not-allowed"
116
117
aria-label="Refresh feed"
117
118
>
118
-
{isRefetching ? <RefreshIcon className="h-6 w-6 animate-spin" /> : <RefreshIcon className="h-6 w-6" />}
119
+
{isRefetching ? <RefreshIcon className="h-6 w-6 text-gray-600 dark:text-gray-400 animate-spin" /> : <RefreshIcon className="h-6 w-6 text-gray-600 dark:text-gray-400" />}
119
120
</button>
120
121
</>
121
122
);
+143
-57
src/components/Login.tsx
+143
-57
src/components/Login.tsx
···
1
1
// src/components/Login.tsx
2
-
import React, { useEffect, useState, useRef } from "react";
2
+
import { Agent } from "@atproto/api";
3
+
import React, { useEffect, useRef, useState } from "react";
4
+
3
5
import { useAuth } from "~/providers/UnifiedAuthProvider";
4
-
import { Agent } from "@atproto/api";
5
6
6
7
// --- 1. The Main Component (Orchestrator with `compact` prop) ---
7
-
export default function Login({ compact = false }: { compact?: boolean }) {
8
+
export default function Login({
9
+
compact = false,
10
+
popup = false,
11
+
}: {
12
+
compact?: boolean;
13
+
popup?: boolean;
14
+
}) {
8
15
const { status, agent, logout } = useAuth();
9
16
10
17
// Loading state can be styled differently based on the prop
···
33
40
// Large view
34
41
if (!compact) {
35
42
return (
36
-
<div className="p-6 bg-gray-100 dark:bg-gray-900 rounded-xl shadow border border-gray-200 dark:border-gray-800 mt-6 mx-4">
43
+
<div className="p-4 bg-gray-100 dark:bg-gray-900 rounded-xl border-gray-200 dark:border-gray-800 mt-6 mx-4">
37
44
<div className="flex flex-col items-center justify-center text-center">
38
45
<p className="text-lg font-semibold mb-4 text-gray-800 dark:text-gray-100">
39
46
You are logged in!
···
41
48
<ProfileThing agent={agent} large />
42
49
<button
43
50
onClick={logout}
44
-
className="bg-gray-600 mt-4 hover:bg-gray-700 text-white rounded px-6 py-2 font-semibold text-base transition-colors"
51
+
className="bg-gray-600 mt-4 hover:bg-gray-700 text-white rounded-full px-6 py-2 font-semibold text-base transition-colors"
45
52
>
46
53
Log out
47
54
</button>
···
67
74
if (!compact) {
68
75
// Large view renders the form directly in the card
69
76
return (
70
-
<div className="p-6 bg-gray-100 dark:bg-gray-900 rounded-xl shadow border border-gray-200 dark:border-gray-800 mt-6 mx-4">
77
+
<div className="p-4 bg-gray-100 dark:bg-gray-900 rounded-xl border-gray-200 dark:border-gray-800 mt-6 mx-4">
71
78
<UnifiedLoginForm />
72
79
</div>
73
80
);
74
81
}
75
82
76
83
// Compact view renders a button that toggles the form in a dropdown
77
-
return <CompactLoginButton />;
84
+
return <CompactLoginButton popup={popup} />;
78
85
}
79
86
80
87
// --- 2. The Reusable, Self-Contained Login Form Component ---
···
83
90
84
91
return (
85
92
<div>
86
-
<div className="flex border-b border-gray-200 dark:border-gray-700 mb-4">
93
+
<div className="flex bg-gray-300 rounded-full dark:bg-gray-700 mb-4">
87
94
<TabButton
88
95
label="OAuth"
89
96
active={mode === "oauth"}
···
103
110
// --- 3. Helper components for layouts, forms, and UI ---
104
111
105
112
// A new component to contain the logic for the compact dropdown
106
-
const CompactLoginButton = () => {
113
+
const CompactLoginButton = ({popup}:{popup?: boolean}) => {
107
114
const [showForm, setShowForm] = useState(false);
108
115
const formRef = useRef<HTMLDivElement>(null);
109
116
···
125
132
<div className="relative" ref={formRef}>
126
133
<button
127
134
onClick={() => setShowForm(!showForm)}
128
-
className="text-sm bg-gray-600 hover:bg-gray-700 text-white rounded px-3 py-1 font-medium transition-colors"
135
+
className="text-sm bg-gray-600 hover:bg-gray-700 text-white rounded-full px-3 py-1 font-medium transition-colors"
129
136
>
130
137
Log in
131
138
</button>
132
139
{showForm && (
133
-
<div className="absolute top-full right-0 mt-2 w-80 bg-white dark:bg-gray-900 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-4 z-50">
140
+
<div className={`absolute ${popup ? `bottom-[calc(100%)]` :`top-full`} right-0 mt-2 w-80 bg-white dark:bg-gray-900 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-4 z-50`}>
134
141
<UnifiedLoginForm />
135
142
</div>
136
143
)}
···
138
145
);
139
146
};
140
147
141
-
const TabButton = ({ label, active, onClick }: { label: string; active: boolean; onClick: () => void; }) => (
148
+
const TabButton = ({
149
+
label,
150
+
active,
151
+
onClick,
152
+
}: {
153
+
label: string;
154
+
active: boolean;
155
+
onClick: () => void;
156
+
}) => (
142
157
<button
143
158
onClick={onClick}
144
-
className={`px-4 py-2 text-sm font-medium transition-colors ${
159
+
className={`px-4 py-2 text-sm font-medium transition-colors rounded-full flex-1 ${
145
160
active
146
-
? "text-gray-600 dark:text-gray-200 border-b-2 border-gray-500"
147
-
: "text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
161
+
? "text-gray-950 dark:text-gray-200 border-gray-500 bg-gray-400 dark:bg-gray-500"
162
+
: "text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200"
148
163
}`}
149
164
>
150
165
{label}
···
169
184
};
170
185
return (
171
186
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
172
-
<p className="text-xs text-gray-500 dark:text-gray-400">Sign in with AT. Your password is never shared.</p>
173
-
<input type="text" placeholder="handle.bsky.social" value={handle} onChange={(e) => setHandle(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" />
174
-
<button type="submit" className="bg-gray-600 hover:bg-gray-700 text-white rounded px-4 py-2 font-medium text-sm transition-colors">Log in</button>
187
+
<p className="text-xs text-gray-500 dark:text-gray-400">
188
+
Sign in with AT. Your password is never shared.
189
+
</p>
190
+
<input
191
+
type="text"
192
+
placeholder="handle.bsky.social"
193
+
value={handle}
194
+
onChange={(e) => setHandle(e.target.value)}
195
+
className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500"
196
+
/>
197
+
<button
198
+
type="submit"
199
+
className="bg-gray-600 hover:bg-gray-700 text-white rounded-full px-4 py-2 font-medium text-sm transition-colors"
200
+
>
201
+
Log in
202
+
</button>
175
203
</form>
176
204
);
177
205
};
···
201
229
202
230
return (
203
231
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
204
-
<p className="text-xs text-red-500 dark:text-red-400">Warning: Less secure. Use an App Password.</p>
205
-
<input type="text" placeholder="handle.bsky.social" value={user} onChange={(e) => setUser(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" autoComplete="username" />
206
-
<input type="password" placeholder="App Password" value={password} onChange={(e) => setPassword(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" autoComplete="current-password" />
207
-
<input type="text" placeholder="PDS (e.g., bsky.social)" value={serviceURL} onChange={(e) => setServiceURL(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" />
232
+
<p className="text-xs text-red-500 dark:text-red-400">
233
+
Warning: Less secure. Use an App Password.
234
+
</p>
235
+
<input
236
+
type="text"
237
+
placeholder="handle.bsky.social"
238
+
value={user}
239
+
onChange={(e) => setUser(e.target.value)}
240
+
className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500"
241
+
autoComplete="username"
242
+
/>
243
+
<input
244
+
type="password"
245
+
placeholder="App Password"
246
+
value={password}
247
+
onChange={(e) => setPassword(e.target.value)}
248
+
className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500"
249
+
autoComplete="current-password"
250
+
/>
251
+
<input
252
+
type="text"
253
+
placeholder="PDS (e.g., bsky.social)"
254
+
value={serviceURL}
255
+
onChange={(e) => setServiceURL(e.target.value)}
256
+
className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500"
257
+
/>
208
258
{error && <p className="text-xs text-red-500">{error}</p>}
209
-
<button type="submit" className="bg-gray-600 hover:bg-gray-700 text-white rounded px-4 py-2 font-medium text-sm transition-colors">Log in</button>
259
+
<button
260
+
type="submit"
261
+
className="bg-gray-600 hover:bg-gray-700 text-white rounded-full px-4 py-2 font-medium text-sm transition-colors"
262
+
>
263
+
Log in
264
+
</button>
210
265
</form>
211
266
);
212
267
};
213
268
214
269
// --- Profile Component (now supports a `large` prop for styling) ---
215
-
export const ProfileThing = ({ agent, large = false }: { agent: Agent | null; large?: boolean }) => {
216
-
const [profile, setProfile] = useState<any>(null);
270
+
export const ProfileThing = ({
271
+
agent,
272
+
large = false,
273
+
}: {
274
+
agent: Agent | null;
275
+
large?: boolean;
276
+
}) => {
277
+
const [profile, setProfile] = useState<any>(null);
217
278
218
-
useEffect(() => {
219
-
const fetchUser = async () => {
220
-
const did = (agent as any)?.session?.did ?? (agent as any)?.assertDid;
221
-
if (!did) return;
222
-
try {
223
-
const res = await agent!.getProfile({ actor: did });
224
-
setProfile(res.data);
225
-
} catch (e) { console.error("Failed to fetch profile", e); }
226
-
};
227
-
if (agent) fetchUser();
228
-
}, [agent]);
229
-
230
-
if (!profile) {
231
-
return ( // Skeleton loader
232
-
<div className={`flex items-center gap-2.5 animate-pulse ${large ? 'mb-1' : ''}`}>
233
-
<div className={`rounded-full bg-gray-300 dark:bg-gray-700 ${large ? 'w-10 h-10' : 'w-[30px] h-[30px]'}`} />
234
-
<div className="flex flex-col gap-2">
235
-
<div className={`bg-gray-300 dark:bg-gray-700 rounded ${large ? 'h-4 w-28' : 'h-3 w-20'}`} />
236
-
<div className={`bg-gray-300 dark:bg-gray-700 rounded ${large ? 'h-4 w-20' : 'h-3 w-16'}`} />
237
-
</div>
238
-
</div>
239
-
);
279
+
useEffect(() => {
280
+
const fetchUser = async () => {
281
+
const did = (agent as any)?.session?.did ?? (agent as any)?.assertDid;
282
+
if (!did) return;
283
+
try {
284
+
const res = await agent!.getProfile({ actor: did });
285
+
setProfile(res.data);
286
+
} catch (e) {
287
+
console.error("Failed to fetch profile", e);
240
288
}
241
-
242
-
return (
243
-
<div className={`flex flex-row items-center gap-2.5 ${large ? 'mb-1' : ''}`}>
244
-
<img src={profile?.avatar} alt="avatar" className={`object-cover rounded-full ${large ? 'w-10 h-10' : 'w-[30px] h-[30px]'}`} />
245
-
<div className="flex flex-col items-start text-left">
246
-
<div className={`font-medium ${large ? 'text-gray-800 dark:text-gray-100 text-md' : 'text-gray-800 dark:text-gray-100 text-sm'}`}>{profile?.displayName}</div>
247
-
<div className={` ${large ? 'text-gray-500 dark:text-gray-400 text-sm' : 'text-gray-500 dark:text-gray-400 text-xs'}`}>@{profile?.handle}</div>
248
-
</div>
289
+
};
290
+
if (agent) fetchUser();
291
+
}, [agent]);
292
+
293
+
if (!profile) {
294
+
return (
295
+
// Skeleton loader
296
+
<div
297
+
className={`flex items-center gap-2.5 animate-pulse ${large ? "mb-1" : ""}`}
298
+
>
299
+
<div
300
+
className={`rounded-full bg-gray-300 dark:bg-gray-700 ${large ? "w-10 h-10" : "w-[30px] h-[30px]"}`}
301
+
/>
302
+
<div className="flex flex-col gap-2">
303
+
<div
304
+
className={`bg-gray-300 dark:bg-gray-700 rounded ${large ? "h-4 w-28" : "h-3 w-20"}`}
305
+
/>
306
+
<div
307
+
className={`bg-gray-300 dark:bg-gray-700 rounded ${large ? "h-4 w-20" : "h-3 w-16"}`}
308
+
/>
249
309
</div>
250
-
);
251
-
};
310
+
</div>
311
+
);
312
+
}
313
+
314
+
return (
315
+
<div
316
+
className={`flex flex-row items-center gap-2.5 ${large ? "mb-1" : ""}`}
317
+
>
318
+
<img
319
+
src={profile?.avatar}
320
+
alt="avatar"
321
+
className={`object-cover rounded-full ${large ? "w-10 h-10" : "w-[30px] h-[30px]"}`}
322
+
/>
323
+
<div className="flex flex-col items-start text-left">
324
+
<div
325
+
className={`font-medium ${large ? "text-gray-800 dark:text-gray-100 text-md" : "text-gray-800 dark:text-gray-100 text-sm"}`}
326
+
>
327
+
{profile?.displayName}
328
+
</div>
329
+
<div
330
+
className={` ${large ? "text-gray-500 dark:text-gray-400 text-sm" : "text-gray-500 dark:text-gray-400 text-xs"}`}
331
+
>
332
+
@{profile?.handle}
333
+
</div>
334
+
</div>
335
+
</div>
336
+
);
337
+
};
+403
-122
src/routes/__root.tsx
+403
-122
src/routes/__root.tsx
···
5
5
import type { QueryClient } from "@tanstack/react-query";
6
6
import {
7
7
createRootRouteWithContext,
8
-
Link,
8
+
// Link,
9
9
// Outlet,
10
10
Scripts,
11
11
useLocation,
···
176
176
)}
177
177
178
178
<div className="min-h-screen flex justify-center bg-gray-50 dark:bg-gray-950">
179
-
<nav className="hidden lg:flex h-screen w-[250px] flex-col gap-2 p-4 dark:border-gray-800 sticky top-0 self-start">
179
+
<nav className="hidden lg:flex h-screen w-[250px] flex-col gap-0 p-4 dark:border-gray-800 sticky top-0 self-start">
180
180
<div className="flex items-center gap-3 mb-4">
181
181
<img src="/redstar.png" alt="Red Dwarf Logo" className="w-8 h-8" />
182
182
<span className="font-extrabold text-2xl tracking-tight text-gray-900 dark:text-gray-100">
···
186
186
</span> */}
187
187
</span>
188
188
</div>
189
-
<Link
189
+
<MaterialNavItem
190
+
InactiveIcon={
191
+
<IconMaterialSymbolsHomeOutline className="w-6 h-6" />
192
+
}
193
+
ActiveIcon={<IconMaterialSymbolsHome className="w-6 h-6" />}
194
+
active={isHome}
195
+
onClickCallbback={() =>
196
+
navigate({
197
+
to: "/",
198
+
//params: { did: agent.assertDid },
199
+
})
200
+
}
201
+
text="Home"
202
+
/>
203
+
204
+
<MaterialNavItem
205
+
InactiveIcon={
206
+
<IconMaterialSymbolsNotificationsOutline className="w-6 h-6" />
207
+
}
208
+
ActiveIcon={
209
+
<IconMaterialSymbolsNotifications className="w-6 h-6" />
210
+
}
211
+
active={isNotifications}
212
+
onClickCallbback={() =>
213
+
navigate({
214
+
to: "/notifications",
215
+
//params: { did: agent.assertDid },
216
+
})
217
+
}
218
+
text="Notifications"
219
+
/>
220
+
<MaterialNavItem
221
+
InactiveIcon={<IconMaterialSymbolsTag className="w-6 h-6" />}
222
+
ActiveIcon={<IconMaterialSymbolsTag className="w-6 h-6" />}
223
+
active={location.pathname.startsWith("/feeds")}
224
+
onClickCallbback={() =>
225
+
navigate({
226
+
to: "/feeds",
227
+
//params: { did: agent.assertDid },
228
+
})
229
+
}
230
+
text="Feeds"
231
+
/>
232
+
<MaterialNavItem
233
+
InactiveIcon={<IconMaterialSymbolsSearch className="w-6 h-6" />}
234
+
ActiveIcon={<IconMaterialSymbolsSearch className="w-6 h-6" />}
235
+
active={location.pathname.startsWith("/search")}
236
+
onClickCallbback={() =>
237
+
navigate({
238
+
to: "/search",
239
+
//params: { did: agent.assertDid },
240
+
})
241
+
}
242
+
text="Search"
243
+
/>
244
+
<MaterialNavItem
245
+
InactiveIcon={
246
+
<IconMaterialSymbolsAccountCircleOutline className="w-6 h-6" />
247
+
}
248
+
ActiveIcon={
249
+
<IconMaterialSymbolsAccountCircle className="w-6 h-6" />
250
+
}
251
+
active={isProfile ?? false}
252
+
onClickCallbback={() => {
253
+
if (authed && agent && agent.assertDid) {
254
+
//window.location.href = `/profile/${agent.assertDid}`;
255
+
navigate({
256
+
to: "/profile/$did",
257
+
params: { did: agent.assertDid },
258
+
});
259
+
}
260
+
}}
261
+
text="Profile"
262
+
/>
263
+
<MaterialNavItem
264
+
InactiveIcon={
265
+
<IconMaterialSymbolsSettingsOutline className="w-6 h-6" />
266
+
}
267
+
ActiveIcon={<IconMaterialSymbolsSettings className="w-6 h-6" />}
268
+
active={location.pathname.startsWith("/settings")}
269
+
onClickCallbback={() =>
270
+
navigate({
271
+
to: "/settings",
272
+
//params: { did: agent.assertDid },
273
+
})
274
+
}
275
+
text="Settings"
276
+
/>
277
+
<div className="flex flex-row items-center justify-center mt-3">
278
+
<MaterialPillButton
279
+
InactiveIcon={<IconMdiPencilOutline className="w-6 h-6" />}
280
+
ActiveIcon={<IconMdiPencilOutline className="w-6 h-6" />}
281
+
//active={true}
282
+
onClickCallbback={() => setPostOpen(true)}
283
+
text="Post"
284
+
/>
285
+
</div>
286
+
{/* <Link
190
287
to="/"
191
288
className={
192
289
`py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ` +
···
260
357
<IconMaterialSymbolsAccountCircleOutline width={28} height={28} />
261
358
) : (
262
359
<IconMaterialSymbolsAccountCircle width={28} height={28} />
263
-
)
264
-
}
360
+
)}
265
361
<span>Profile</span>
266
362
</button>
267
363
<Link
···
276
372
<IconMaterialSymbolsSettings width={28} height={28} />
277
373
)}
278
374
<span>Settings</span>
279
-
</Link>
280
-
<button
375
+
</Link> */}
376
+
{/* <button
281
377
className="mt-4 w-full flex items-center justify-center gap-3 py-3 px-0 mb-3 bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100 text-xl font-bold rounded-full transition-colors shadow"
282
378
onClick={() => setPostOpen(true)}
283
379
type="button"
···
288
384
className="text-gray-600 dark:text-gray-400"
289
385
/>
290
386
<span>Post</span>
291
-
</button>
387
+
</button> */}
292
388
<div className="flex-1"></div>
293
389
<a
294
390
href="https://tangled.sh/@whey.party/red-dwarf"
···
319
415
</div>
320
416
</nav>
321
417
322
-
<button
323
-
className="lg:hidden fixed bottom-20 right-6 z-50 bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 text-blue-600 dark:text-blue-400 rounded-full shadow-lg w-16 h-16 flex items-center justify-center border-4 border-white dark:border-gray-950 transition-all"
324
-
style={{ boxShadow: "0 4px 24px 0 rgba(0,0,0,0.12)" }}
325
-
onClick={() => setPostOpen(true)}
326
-
type="button"
327
-
aria-label="Create Post"
328
-
>
329
-
<IconMdiPencilOutline
330
-
width={24}
331
-
height={24}
332
-
className="text-gray-600 dark:text-gray-400"
333
-
/>
334
-
</button>
418
+
{agent?.did && (
419
+
<button
420
+
className="lg:hidden fixed bottom-22 right-4 z-50 bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 rounded-2xl w-14 h-14 flex items-center justify-center transition-all"
421
+
style={{ boxShadow: "0 4px 24px 0 rgba(0,0,0,0.12)" }}
422
+
onClick={() => setPostOpen(true)}
423
+
type="button"
424
+
aria-label="Create Post"
425
+
>
426
+
<IconMdiPencilOutline
427
+
width={24}
428
+
height={24}
429
+
className="text-gray-600 dark:text-gray-400"
430
+
/>
431
+
</button>
432
+
)}
335
433
336
434
<main className="w-full max-w-[600px] lg:border-x border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-950 pb-16 lg:pb-0">
337
-
<div className="lg:hidden flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-950">
338
-
<div className="flex items-center gap-2">
339
-
<img
340
-
src="/redstar.png"
341
-
alt="Red Dwarf Logo"
342
-
className="w-6 h-6"
343
-
/>
344
-
<span className="font-bold text-lg text-gray-900 dark:text-gray-100">
345
-
Red Dwarf{" "}
346
-
{/* <span className="text-gray-500 dark:text-gray-400 text-sm">
347
-
lite
348
-
</span> */}
349
-
</span>
350
-
</div>
351
-
<div className="flex items-center gap-2">
352
-
<Login compact={true} />
353
-
</div>
354
-
</div>
355
-
356
435
{children}
357
436
</main>
358
437
···
368
447
</aside>
369
448
</div>
370
449
371
-
<nav className="lg:hidden fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-950 border-t border-gray-200 dark:border-gray-700 z-40">
372
-
<div className="flex justify-around items-center py-2">
373
-
<Link
374
-
to="/"
375
-
className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
376
-
isHome
377
-
? "text-gray-900 dark:text-gray-100"
378
-
: "text-gray-600 dark:text-gray-400"
379
-
}`}
380
-
>
381
-
{!isHome ? (
382
-
<IconMaterialSymbolsHomeOutline width={24} height={24} />
383
-
) : (
384
-
<IconMaterialSymbolsHome width={24} height={24} />
385
-
)}
386
-
<span className="text-xs mt-1">Home</span>
387
-
</Link>
388
-
<Link
389
-
to="/search"
390
-
className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
391
-
location.pathname.startsWith("/search")
392
-
? "text-gray-900 dark:text-gray-100"
393
-
: "text-gray-600 dark:text-gray-400"
394
-
}`}
395
-
>
396
-
{!location.pathname.startsWith("/search") ? (
397
-
<IconMaterialSymbolsSearch width={24} height={24} />
398
-
) : (
399
-
<IconMaterialSymbolsSearch width={24} height={24} />
400
-
)}
401
-
<span className="text-xs mt-1">Search</span>
402
-
</Link>
403
-
<Link
404
-
to="/notifications"
405
-
className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
406
-
isNotifications
407
-
? "text-gray-900 dark:text-gray-100"
408
-
: "text-gray-600 dark:text-gray-400"
409
-
}`}
410
-
>
411
-
{!isNotifications ? (
412
-
<IconMaterialSymbolsNotificationsOutline width={24} height={24} />
413
-
) : (
414
-
<IconMaterialSymbolsNotifications width={24} height={24} />
415
-
)}
416
-
<span className="text-xs mt-1">Notifications</span>
417
-
</Link>
418
-
<button
419
-
className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
420
-
isProfile
421
-
? "text-gray-900 dark:text-gray-100"
422
-
: "text-gray-600 dark:text-gray-400"
423
-
}`}
424
-
onClick={() => {
425
-
if (authed && agent && agent.assertDid) {
426
-
//window.location.href = `/profile/${agent.assertDid}`;
450
+
{agent?.did ? (
451
+
<nav className="lg:hidden fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-950 border-t border-gray-200 dark:border-gray-700 z-40">
452
+
<div className="flex justify-around items-center p-2">
453
+
<MaterialNavItem
454
+
small
455
+
InactiveIcon={
456
+
<IconMaterialSymbolsHomeOutline className="w-6 h-6" />
457
+
}
458
+
ActiveIcon={<IconMaterialSymbolsHome className="w-6 h-6" />}
459
+
active={isHome}
460
+
onClickCallbback={() =>
461
+
navigate({
462
+
to: "/",
463
+
//params: { did: agent.assertDid },
464
+
})
465
+
}
466
+
text="Home"
467
+
/>
468
+
{/* <Link
469
+
to="/"
470
+
className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
471
+
isHome
472
+
? "text-gray-900 dark:text-gray-100"
473
+
: "text-gray-600 dark:text-gray-400"
474
+
}`}
475
+
>
476
+
{!isHome ? (
477
+
<IconMaterialSymbolsHomeOutline width={24} height={24} />
478
+
) : (
479
+
<IconMaterialSymbolsHome width={24} height={24} />
480
+
)}
481
+
<span className="text-xs mt-1">Home</span>
482
+
</Link> */}
483
+
<MaterialNavItem
484
+
small
485
+
InactiveIcon={<IconMaterialSymbolsSearch className="w-6 h-6" />}
486
+
ActiveIcon={<IconMaterialSymbolsSearch className="w-6 h-6" />}
487
+
active={location.pathname.startsWith("/search")}
488
+
onClickCallbback={() =>
489
+
navigate({
490
+
to: "/search",
491
+
//params: { did: agent.assertDid },
492
+
})
493
+
}
494
+
text="Search"
495
+
/>
496
+
{/* <Link
497
+
to="/search"
498
+
className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
499
+
location.pathname.startsWith("/search")
500
+
? "text-gray-900 dark:text-gray-100"
501
+
: "text-gray-600 dark:text-gray-400"
502
+
}`}
503
+
>
504
+
{!location.pathname.startsWith("/search") ? (
505
+
<IconMaterialSymbolsSearch width={24} height={24} />
506
+
) : (
507
+
<IconMaterialSymbolsSearch width={24} height={24} />
508
+
)}
509
+
<span className="text-xs mt-1">Search</span>
510
+
</Link> */}
511
+
<MaterialNavItem
512
+
small
513
+
InactiveIcon={
514
+
<IconMaterialSymbolsNotificationsOutline className="w-6 h-6" />
515
+
}
516
+
ActiveIcon={
517
+
<IconMaterialSymbolsNotifications className="w-6 h-6" />
518
+
}
519
+
active={isNotifications}
520
+
onClickCallbback={() =>
521
+
navigate({
522
+
to: "/notifications",
523
+
//params: { did: agent.assertDid },
524
+
})
525
+
}
526
+
text="Notifications"
527
+
/>
528
+
{/* <Link
529
+
to="/notifications"
530
+
className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
531
+
isNotifications
532
+
? "text-gray-900 dark:text-gray-100"
533
+
: "text-gray-600 dark:text-gray-400"
534
+
}`}
535
+
>
536
+
{!isNotifications ? (
537
+
<IconMaterialSymbolsNotificationsOutline
538
+
width={24}
539
+
height={24}
540
+
/>
541
+
) : (
542
+
<IconMaterialSymbolsNotifications width={24} height={24} />
543
+
)}
544
+
<span className="text-xs mt-1">Notifications</span>
545
+
</Link> */}
546
+
<MaterialNavItem
547
+
small
548
+
InactiveIcon={
549
+
<IconMaterialSymbolsAccountCircleOutline className="w-6 h-6" />
550
+
}
551
+
ActiveIcon={
552
+
<IconMaterialSymbolsAccountCircle className="w-6 h-6" />
553
+
}
554
+
active={isProfile ?? false}
555
+
onClickCallbback={() => {
556
+
if (authed && agent && agent.assertDid) {
557
+
//window.location.href = `/profile/${agent.assertDid}`;
558
+
navigate({
559
+
to: "/profile/$did",
560
+
params: { did: agent.assertDid },
561
+
});
562
+
}
563
+
}}
564
+
text="Profile"
565
+
/>
566
+
{/* <button
567
+
className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
568
+
isProfile
569
+
? "text-gray-900 dark:text-gray-100"
570
+
: "text-gray-600 dark:text-gray-400"
571
+
}`}
572
+
onClick={() => {
573
+
if (authed && agent && agent.assertDid) {
574
+
//window.location.href = `/profile/${agent.assertDid}`;
575
+
navigate({
576
+
to: "/profile/$did",
577
+
params: { did: agent.assertDid },
578
+
});
579
+
}
580
+
}}
581
+
type="button"
582
+
>
583
+
<IconMaterialSymbolsAccountCircleOutline width={24} height={24} />
584
+
<span className="text-xs mt-1">Profile</span>
585
+
</button> */}
586
+
<MaterialNavItem
587
+
small
588
+
InactiveIcon={
589
+
<IconMaterialSymbolsSettingsOutline className="w-6 h-6" />
590
+
}
591
+
ActiveIcon={<IconMaterialSymbolsSettings className="w-6 h-6" />}
592
+
active={location.pathname.startsWith("/settings")}
593
+
onClickCallbback={() =>
427
594
navigate({
428
-
to: "/profile/$did",
429
-
params: { did: agent.assertDid },
430
-
});
595
+
to: "/settings",
596
+
//params: { did: agent.assertDid },
597
+
})
431
598
}
432
-
}}
433
-
type="button"
434
-
>
435
-
<IconMaterialSymbolsAccountCircleOutline width={24} height={24} />
436
-
<span className="text-xs mt-1">Profile</span>
437
-
</button>
438
-
<Link
439
-
to="/settings"
440
-
className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
441
-
location.pathname.startsWith("/settings")
442
-
? "text-gray-900 dark:text-gray-100"
443
-
: "text-gray-600 dark:text-gray-400"
444
-
}`}
445
-
>
446
-
{!location.pathname.startsWith("/settings") ? (
447
-
<IconMaterialSymbolsSettingsOutline width={24} height={24} />
448
-
) : (
449
-
<IconMaterialSymbolsSettings width={24} height={24} />
450
-
)}
451
-
<span className="text-xs mt-1">Settings</span>
452
-
</Link>
599
+
text="Settings"
600
+
/>
601
+
{/* <Link
602
+
to="/settings"
603
+
className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
604
+
location.pathname.startsWith("/settings")
605
+
? "text-gray-900 dark:text-gray-100"
606
+
: "text-gray-600 dark:text-gray-400"
607
+
}`}
608
+
>
609
+
{!location.pathname.startsWith("/settings") ? (
610
+
<IconMaterialSymbolsSettingsOutline width={24} height={24} />
611
+
) : (
612
+
<IconMaterialSymbolsSettings width={24} height={24} />
613
+
)}
614
+
<span className="text-xs mt-1">Settings</span>
615
+
</Link> */}
616
+
</div>
617
+
</nav>
618
+
) : (
619
+
<div className="lg:hidden flex items-center fixed bottom-0 left-0 right-0 justify-between px-4 py-3 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-950 z-10">
620
+
<div className="flex items-center gap-2">
621
+
<img src="/redstar.png" alt="Red Dwarf Logo" className="w-6 h-6" />
622
+
<span className="font-bold text-lg text-gray-900 dark:text-gray-100">
623
+
Red Dwarf{" "}
624
+
{/* <span className="text-gray-500 dark:text-gray-400 text-sm">
625
+
lite
626
+
</span> */}
627
+
</span>
628
+
</div>
629
+
<div className="flex items-center gap-2">
630
+
<Login compact={true} popup={true} />
631
+
</div>
453
632
</div>
454
-
</nav>
633
+
)}
455
634
456
-
<TanStackRouterDevtools position="bottom-right" />
635
+
<TanStackRouterDevtools position="bottom-left" />
457
636
<Scripts />
458
637
</>
459
638
);
460
639
}
640
+
641
+
function MaterialNavItem({
642
+
InactiveIcon,
643
+
ActiveIcon,
644
+
text,
645
+
active,
646
+
onClickCallbback,
647
+
small,
648
+
}: {
649
+
InactiveIcon: React.ReactElement;
650
+
ActiveIcon: React.ReactElement;
651
+
text: string;
652
+
active: boolean;
653
+
onClickCallbback: () => void;
654
+
small?: boolean;
655
+
}) {
656
+
if (small)
657
+
return (
658
+
<button
659
+
className={`flex flex-col items-center rounded-lg transition-colors flex-1 gap-1 ${
660
+
active
661
+
? "text-gray-900 dark:text-gray-100"
662
+
: "text-gray-600 dark:text-gray-400"
663
+
}`}
664
+
onClick={() => {
665
+
onClickCallbback();
666
+
}}
667
+
>
668
+
<div
669
+
className={`px-4 py-1 rounded-full flex items-center justify-center ${active ? " bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 hover:dark:bg-gray-700" : "hover:bg-gray-50 hover:dark:bg-gray-900"}`}
670
+
>
671
+
{active ? ActiveIcon : InactiveIcon}
672
+
</div>
673
+
<span
674
+
className={`text-[12.8px] text-roboto ${active ? "font-medium" : ""}`}
675
+
>
676
+
{text}
677
+
</span>
678
+
</button>
679
+
);
680
+
681
+
return (
682
+
<button
683
+
className={`flex flex-row h-12 min-h-12 max-h-12 px-4 py-0.5 w-full items-center rounded-full transition-colors flex-1 gap-1 ${
684
+
active
685
+
? "text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:bg-gray-700 bg-gray-200 hover:dark:bg-gray-600"
686
+
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 hover:dark:bg-gray-800"
687
+
}`}
688
+
onClick={() => {
689
+
onClickCallbback();
690
+
}}
691
+
>
692
+
<div className={`mr-4 ${active ? " " : " "}`}>
693
+
{active ? ActiveIcon : InactiveIcon}
694
+
</div>
695
+
<span
696
+
className={`text-[16px] text-roboto ${active ? "font-medium" : ""}`}
697
+
>
698
+
{text}
699
+
</span>
700
+
</button>
701
+
);
702
+
}
703
+
704
+
function MaterialPillButton({
705
+
InactiveIcon,
706
+
ActiveIcon,
707
+
text,
708
+
//active,
709
+
onClickCallbback,
710
+
small,
711
+
}: {
712
+
InactiveIcon: React.ReactElement;
713
+
ActiveIcon: React.ReactElement;
714
+
text: string;
715
+
//active: boolean;
716
+
onClickCallbback: () => void;
717
+
small?: boolean;
718
+
}) {
719
+
const active = false;
720
+
return (
721
+
<button
722
+
className={`flex border border-gray-400 dark:border-gray-400 flex-row h-12 min-h-12 max-h-12 px-4 py-0.5 items-center rounded-full transition-colors gap-1 ${
723
+
active
724
+
? "text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:bg-gray-700 bg-gray-200 hover:dark:bg-gray-600"
725
+
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 hover:dark:bg-gray-800"
726
+
}`}
727
+
onClick={() => {
728
+
onClickCallbback();
729
+
}}
730
+
>
731
+
<div className={`mr-2 ${active ? " " : " "}`}>
732
+
{active ? ActiveIcon : InactiveIcon}
733
+
</div>
734
+
<span
735
+
className={`text-[16px] text-roboto ${active ? "font-medium" : ""}`}
736
+
>
737
+
{text}
738
+
</span>
739
+
</button>
740
+
);
741
+
}
+23
-13
src/routes/index.tsx
+23
-13
src/routes/index.tsx
···
3
3
import * as React from "react";
4
4
import { useEffect, useLayoutEffect } from "react";
5
5
6
+
import { Header } from "~/components/Header";
6
7
import { InfiniteCustomFeed } from "~/components/InfiniteCustomFeed";
7
8
import { useAuth } from "~/providers/UnifiedAuthProvider";
8
9
import {
···
353
354
<div
354
355
className={`relative flex flex-col divide-y divide-gray-200 dark:divide-gray-800 ${hidden && "hidden"}`}
355
356
>
356
-
<div className="flex items-center gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700 overflow-x-auto overflow-y-hidden scroll-thin">
357
-
{savedFeeds.length > 0 ? (
358
-
savedFeeds.map((item: any, idx: number) => {
357
+
{savedFeeds.length > 0 ? (
358
+
<div className="flex items-center px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700 overflow-x-auto overflow-y-hidden scroll-thin">
359
+
{savedFeeds.map((item: any, idx: number) => {
359
360
const label = item.value.split("/").pop() || item.value;
360
361
const isActive = selectedFeed === item.value;
361
362
return (
···
363
364
key={item.value || idx}
364
365
className={`px-3 py-1 rounded-full whitespace-nowrap font-medium transition-colors ${
365
366
isActive
366
-
? "bg-gray-500 text-white"
367
-
: item.pinned
368
-
? "bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200"
369
-
: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-200"
367
+
? "text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:bg-gray-700 bg-gray-200 hover:dark:bg-gray-600"
368
+
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 hover:dark:bg-gray-800"
369
+
// ? "bg-gray-500 text-white"
370
+
// : item.pinned
371
+
// ? "bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200"
372
+
// : "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-200"
370
373
}`}
371
374
onClick={() => setSelectedFeed(item.value)}
372
375
title={item.value}
373
376
>
374
377
{label}
375
378
{item.pinned && (
376
-
<span className="ml-1 text-xs text-gray-700 dark:text-gray-200">
379
+
<span
380
+
className={`ml-1 text-xs ${
381
+
isActive
382
+
? "text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:bg-gray-700 bg-gray-200 hover:dark:bg-gray-600"
383
+
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 hover:dark:bg-gray-800"
384
+
}`}
385
+
>
377
386
★
378
387
</span>
379
388
)}
380
389
</button>
381
390
);
382
-
})
383
-
) : (
384
-
<span className="text-xl font-bold ml-2">Home</span>
385
-
)}
386
-
</div>
391
+
})}
392
+
</div>
393
+
) : (
394
+
// <span className="text-xl font-bold ml-2">Home</span>
395
+
<Header title="Home" />
396
+
)}
387
397
{/* {isFeedLoading && <div className="p-4 text-gray-500">Loading...</div>}
388
398
{feedError && <div className="p-4 text-red-500">{feedError.message}</div>}
389
399
{!isFeedLoading && !feedError && feed.length === 0 && (
+14
-3
src/routes/profile.$did/index.tsx
+14
-3
src/routes/profile.$did/index.tsx
···
1
1
import { useQueryClient } from "@tanstack/react-query";
2
-
import { createFileRoute, Link } from "@tanstack/react-router";
2
+
import { createFileRoute } from "@tanstack/react-router";
3
3
import React from "react";
4
4
5
+
import { Header } from "~/components/Header";
5
6
import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer";
6
7
import { useAuth } from "~/providers/UnifiedAuthProvider";
7
8
import { toggleFollow, useGetFollowState } from "~/utils/followState";
···
104
105
105
106
return (
106
107
<>
107
-
<div className="flex gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700">
108
+
<Header
109
+
title={`Profile`}
110
+
backButtonCallback={() => {
111
+
if (window.history.length > 1) {
112
+
window.history.back();
113
+
} else {
114
+
window.location.assign("/");
115
+
}
116
+
}}
117
+
/>
118
+
{/* <div className="flex gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700">
108
119
<Link
109
120
to=".."
110
121
className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg"
···
121
132
←
122
133
</Link>
123
134
<span className="text-xl font-bold ml-2">Profile</span>
124
-
</div>
135
+
</div> */}
125
136
126
137
{/* Profile Header */}
127
138
<div className="w-full max-w-2xl mx-auto overflow-hidden relative bg-gray-100 dark:bg-gray-900">
+14
-31
src/routes/profile.$did/post.$rkey.tsx
+14
-31
src/routes/profile.$did/post.$rkey.tsx
···
2
2
import { createFileRoute } from "@tanstack/react-router";
3
3
import React, { useLayoutEffect } from "react";
4
4
5
+
import { Header } from "~/components/Header";
5
6
import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer";
6
7
//import { usePersistentStore } from '~/providers/PersistentStoreProvider';
7
8
import {
···
296
297
297
298
return (
298
299
<>
299
-
<div className="flex items-center gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700">
300
-
{!nopics ? (
301
-
<button
302
-
//to=".."
303
-
className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg"
304
-
onClick={(e) => {
305
-
e.preventDefault();
306
-
if (window.history.length > 1) {
307
-
window.history.back();
308
-
} else {
309
-
window.location.assign("/");
300
+
<Header
301
+
title={`Post`}
302
+
backButtonCallback={
303
+
nopics
304
+
? nopics
305
+
: () => {
306
+
if (window.history.length > 1) {
307
+
window.history.back();
308
+
} else {
309
+
window.location.assign("/");
310
+
}
310
311
}
311
-
}}
312
-
aria-label="Go back"
313
-
>
314
-
←
315
-
</button>
316
-
) : (
317
-
<button
318
-
//to=".."
319
-
className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg"
320
-
onClick={(e) => {
321
-
e.preventDefault();
322
-
nopics();
323
-
}}
324
-
aria-label="Go back"
325
-
>
326
-
←
327
-
</button>
328
-
)}
329
-
<span className="text-xl font-bold ml-2">Post</span>
330
-
</div>
312
+
}
313
+
/>
331
314
332
315
{parentsLoading && (
333
316
<div className="text-center text-gray-500 dark:text-gray-400 flex flex-row">
+8
src/styles/app.css
+8
src/styles/app.css
···
1
+
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&family=Spectral+SC:wght@500&display=swap');
1
2
@import "tailwindcss";
2
3
3
4
/* @theme {
···
78
79
color: rgb(29, 122, 242);
79
80
word-break: break-all;
80
81
}
82
+
}
83
+
84
+
.font-inter {
85
+
font-family: "Inter", sans-serif;
86
+
}
87
+
.font-roboto {
88
+
font-family: "Roboto", sans-serif;
81
89
}
+2
-1
vite.config.ts
+2
-1
vite.config.ts
···
39
39
IconsResolver({
40
40
prefix: 'Icon',
41
41
extension: 'jsx',
42
+
enabledCollections: ['mdi','material-symbols'],
42
43
}),
43
44
],
44
45
dts: 'src/auto-imports.d.ts',
45
46
}),
46
47
Icons({
47
-
autoInstall: true,
48
+
//autoInstall: true,
48
49
compiler: 'jsx',
49
50
jsx: 'react'
50
51
}),