an appview-less Bluesky client using Constellation and PDS Queries
reddwarf.app
frontend
spa
bluesky
reddwarf
microcosm
1/// <reference types="vite/client" />
2
3// dont forget to run this
4// npx @tanstack/router-cli generate
5
6import type { QueryClient } from "@tanstack/react-query";
7import {
8 createRootRouteWithContext,
9 Link,
10 Outlet,
11 Scripts,
12 useLocation,
13 useNavigate,
14} from "@tanstack/react-router";
15import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
16import { type SVGProps,useState } from "react";
17import * as React from "react";
18
19import { DefaultCatchBoundary } from "~/components/DefaultCatchBoundary";
20import Login from "~/components/Login";
21import { NotFound } from "~/components/NotFound";
22import { UnifiedAuthProvider, useAuth } from "~/providers/UnifiedAuthProvider";
23import { seo } from "~/utils/seo";
24
25export const Route = createRootRouteWithContext<{
26 queryClient: QueryClient;
27}>()({
28 head: () => ({
29 meta: [
30 {
31 charSet: "utf-8",
32 },
33 {
34 name: "viewport",
35 content: "width=device-width, initial-scale=1",
36 },
37 ...seo({
38 title: "Red Dwarf",
39 description: `Distributed Bluesky Client`,
40 }),
41 ],
42 links: [
43 {
44 rel: "apple-touch-icon",
45 sizes: "180x180",
46 href: "/apple-touch-icon.png",
47 },
48 {
49 rel: "icon",
50 type: "image/png",
51 sizes: "32x32",
52 href: "/redstar.png?whatwg",
53 },
54 {
55 rel: "icon",
56 type: "image/png",
57 sizes: "16x16",
58 href: "/redstar.png?whatwg",
59 },
60 { rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
61 { rel: "icon", href: "/favicon.ico" },
62 ],
63 }),
64 errorComponent: import.meta.env.DEV
65 ? undefined
66 : (props) => (
67 <RootDocument>
68 <DefaultCatchBoundary {...props} />
69 </RootDocument>
70 ),
71 notFoundComponent: () => <NotFound />,
72 component: RootComponent,
73});
74
75function RootComponent() {
76 return (
77 <UnifiedAuthProvider>
78 <RootDocument>
79 <Outlet />
80 </RootDocument>
81 </UnifiedAuthProvider>
82 );
83}
84
85function RootDocument({ children }: { children: React.ReactNode }) {
86 const location = useLocation();
87 const navigate = useNavigate();
88 const { agent } = useAuth();
89 const authed = !!agent?.did;
90 const isHome = location.pathname === "/";
91 const isNotifications = location.pathname.startsWith("/notifications");
92 const isProfile = agent && ((location.pathname === (`/profile/${agent?.did}`)) || (location.pathname === (`/profile/${encodeURIComponent(agent?.did??"")}`)));
93
94 const [postOpen, setPostOpen] = useState(false);
95 const [postText, setPostText] = useState("");
96 const [posting, setPosting] = useState(false);
97 const [postSuccess, setPostSuccess] = useState(false);
98 const [postError, setPostError] = useState<string | null>(null);
99
100 async function handlePost() {
101 if (!agent) return;
102 setPosting(true);
103 setPostError(null);
104 try {
105 await agent.com.atproto.repo.createRecord({
106 collection: "app.bsky.feed.post",
107 repo: agent.assertDid,
108 record: {
109 $type: "app.bsky.feed.post",
110 text: postText,
111 createdAt: new Date().toISOString(),
112 },
113 });
114 setPostSuccess(true);
115 setPostText("");
116 setTimeout(() => {
117 setPostSuccess(false);
118 setPostOpen(false);
119 }, 1500);
120 } catch (e: any) {
121 setPostError(e?.message || "Failed to post");
122 } finally {
123 setPosting(false);
124 }
125 }
126
127 return (
128 <>
129 {postOpen && (
130 <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
131 <div className="bg-white dark:bg-gray-900 rounded-lg shadow-lg p-6 w-full max-w-md relative">
132 <button
133 className="absolute top-2 right-2 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
134 onClick={() => !posting && setPostOpen(false)}
135 disabled={posting}
136 aria-label="Close"
137 >
138 ×
139 </button>
140 <h2 className="text-lg font-bold mb-2">Create Post</h2>
141 {postSuccess ? (
142 <div className="flex flex-col items-center justify-center py-8">
143 <span className="text-green-500 text-4xl mb-2">✓</span>
144 <span className="text-green-600">Posted!</span>
145 </div>
146 ) : (
147 <>
148 <textarea
149 className="w-full border rounded p-2 mb-2 dark:bg-gray-800 dark:border-gray-700"
150 rows={4}
151 placeholder="What's on your mind?"
152 value={postText}
153 onChange={(e) => setPostText(e.target.value)}
154 disabled={posting}
155 autoFocus
156 />
157 {postError && (
158 <div className="text-red-500 text-sm mb-2">{postError}</div>
159 )}
160 <button
161 className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50"
162 onClick={handlePost}
163 disabled={posting || !postText.trim()}
164 >
165 {posting ? "Posting..." : "Post"}
166 </button>
167 </>
168 )}
169 </div>
170 </div>
171 )}
172
173 <div className="min-h-screen flex justify-center bg-gray-50 dark:bg-gray-950">
174 <nav className="hidden lg:flex h-screen w-[250px] flex-col gap-2 p-4 dark:border-gray-800 sticky top-0 self-start">
175 <div className="flex items-center gap-3 mb-4">
176 <img src="/redstar.png" alt="Red Dwarf Logo" className="w-8 h-8" />
177 <span className="font-extrabold text-2xl tracking-tight text-gray-900 dark:text-gray-100">
178 Red Dwarf{" "}
179 {/* <span className="text-gray-500 dark:text-gray-400 text-sm">
180 lite
181 </span> */}
182 </span>
183 </div>
184 <Link
185 to="/"
186 className={
187 `py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ` +
188 (isHome ? "font-bold" : "")
189 }
190 >
191 {isHome ? (
192 <TablerHomeFilled width={28} height={28} />
193 ) : (
194 <TablerHome width={28} height={28} />
195 )}
196 <span>Home</span>
197 </Link>
198 <Link
199 to="/notifications"
200 className={
201 `py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ` +
202 (isNotifications ? "font-bold" : "")
203 }
204 >
205 {isNotifications ? (
206 <TablerBellFilled width={28} height={28} />
207 ) : (
208 <TablerBell width={28} height={28} />
209 )}
210 <span>Notifications</span>
211 </Link>
212 <Link
213 to="/feeds"
214 className={`py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ${
215 location.pathname.startsWith("/feeds") ? "font-bold" : ""
216 }`}
217 >
218 {location.pathname.startsWith("/feeds") ? (
219 <TablerHashtagFilled width={28} height={28} />
220 ) : (
221 <TablerHashtag width={28} height={28} />
222 )}
223 <span>Feeds</span>
224 </Link>
225
226 <Link
227 to="/search"
228 className={`py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ${
229 location.pathname.startsWith("/search") ? "font-bold" : ""
230 }`}
231 >
232 {location.pathname.startsWith("/search") ? (
233 <TablerSearchFilled width={28} height={28} />
234 ) : (
235 <TablerSearch width={28} height={28} />
236 )}
237 <span>Search</span>
238 </Link>
239 <button
240 className={`py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 w-full text-left ${
241 isProfile ? "bg-gray-100 dark:bg-gray-900 font-bold" : ""
242 }`}
243 onClick={() => {
244 if (authed && agent && agent.assertDid) {
245 //window.location.href = `/profile/${agent.assertDid}`;
246 navigate({
247 to: "/profile/$did",
248 params: { did: agent.assertDid },
249 })
250 }
251 }}
252 type="button"
253 >
254 <TablerUserCircle width={28} height={28} />
255 <span>Profile</span>
256 </button>
257 <Link
258 to="/settings"
259 className={`py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ${
260 location.pathname.startsWith("/settings") ? "font-bold" : ""
261 }`}
262 >
263 {location.pathname.startsWith("/settings") ? (
264 <IonSettingsSharp width={28} height={28} />
265 ) : (
266 <IonSettings width={28} height={28} />
267 )}
268 <span>Settings</span>
269 </Link>
270 <button
271 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"
272 onClick={() => setPostOpen(true)}
273 type="button"
274 >
275 <TablerEdit
276 width={24}
277 height={24}
278 className="text-gray-600 dark:text-gray-400"
279 />
280 <span>Post</span>
281 </button>
282 <div className="flex-1"></div>
283 <a
284 href="https://tangled.sh/@whey.party/red-dwarf"
285 target="_blank"
286 rel="noopener noreferrer"
287 className="mt-1 text-xs text-gray-400 dark:text-gray-500 text-center hover:underline"
288 >
289 git repo
290 </a>
291 <a
292 href="https://whey.party/"
293 target="_blank"
294 rel="noopener noreferrer"
295 className="mt-1 text-xs text-gray-400 dark:text-gray-500 text-center hover:underline"
296 >
297 made by @whey.party
298 </a>
299 <div className="mt-2 text-xs text-gray-400 dark:text-gray-500 text-center">
300 powered by{" "}
301 <a
302 href="https://microcosm.blue"
303 target="_blank"
304 rel="noopener noreferrer"
305 className="underline hover:text-blue-500"
306 >
307 microcosm.blue
308 </a>
309 </div>
310 </nav>
311
312 <button
313 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"
314 style={{ boxShadow: "0 4px 24px 0 rgba(0,0,0,0.12)" }}
315 onClick={() => setPostOpen(true)}
316 type="button"
317 aria-label="Create Post"
318 >
319 <TablerEdit
320 width={24}
321 height={24}
322 className="text-gray-600 dark:text-gray-400"
323 />
324 </button>
325
326 <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">
327 <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">
328 <div className="flex items-center gap-2">
329 <img
330 src="/redstar.png"
331 alt="Red Dwarf Logo"
332 className="w-6 h-6"
333 />
334 <span className="font-bold text-lg text-gray-900 dark:text-gray-100">
335 Red Dwarf{" "}
336 {/* <span className="text-gray-500 dark:text-gray-400 text-sm">
337 lite
338 </span> */}
339 </span>
340 </div>
341 <div className="flex items-center gap-2">
342 <Login compact={true} />
343 </div>
344 </div>
345
346 {children}
347 </main>
348
349 <aside className="hidden lg:flex h-screen w-[250px] sticky top-0 self-start flex-col">
350 <Login />
351
352 <div className="flex-1"></div>
353 <p className="text-xs text-gray-400 dark:text-gray-500 text-justify mx-4 mb-4">
354 Red Dwarf is a bluesky client that uses Constellation and direct PDS
355 queries. Skylite would be a self-hosted bluesky "instance". Stay
356 tuned for the release of Skylite.
357 </p>
358 </aside>
359 </div>
360
361 <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">
362 <div className="flex justify-around items-center py-2">
363 <Link
364 to="/"
365 className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
366 isHome
367 ? "text-gray-900 dark:text-gray-100"
368 : "text-gray-600 dark:text-gray-400"
369 }`}
370 >
371 {isHome ? (
372 <TablerHomeFilled width={24} height={24} />
373 ) : (
374 <TablerHome width={24} height={24} />
375 )}
376 <span className="text-xs mt-1">Home</span>
377 </Link>
378 <Link
379 to="/search"
380 className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
381 location.pathname.startsWith("/search")
382 ? "text-gray-900 dark:text-gray-100"
383 : "text-gray-600 dark:text-gray-400"
384 }`}
385 >
386 {location.pathname.startsWith("/search") ? (
387 <TablerSearchFilled width={24} height={24} />
388 ) : (
389 <TablerSearch width={24} height={24} />
390 )}
391 <span className="text-xs mt-1">Search</span>
392 </Link>
393 <Link
394 to="/notifications"
395 className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
396 isNotifications
397 ? "text-gray-900 dark:text-gray-100"
398 : "text-gray-600 dark:text-gray-400"
399 }`}
400 >
401 {isNotifications ? (
402 <TablerBellFilled width={24} height={24} />
403 ) : (
404 <TablerBell width={24} height={24} />
405 )}
406 <span className="text-xs mt-1">Notifications</span>
407 </Link>
408 <button
409 className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
410 isProfile
411 ? "text-gray-900 dark:text-gray-100"
412 : "text-gray-600 dark:text-gray-400"
413 }`}
414 onClick={() => {
415 if (authed && agent && agent.assertDid) {
416 //window.location.href = `/profile/${agent.assertDid}`;
417 navigate({
418 to: "/profile/$did",
419 params: { did: agent.assertDid },
420 })
421 }
422 }}
423 type="button"
424 >
425 <TablerUserCircle width={24} height={24} />
426 <span className="text-xs mt-1">Profile</span>
427 </button>
428 <Link
429 to="/settings"
430 className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${
431 location.pathname.startsWith("/settings")
432 ? "text-gray-900 dark:text-gray-100"
433 : "text-gray-600 dark:text-gray-400"
434 }`}
435 >
436 {location.pathname.startsWith("/settings") ? (
437 <IonSettingsSharp width={24} height={24} />
438 ) : (
439 <IonSettings width={24} height={24} />
440 )}
441 <span className="text-xs mt-1">Settings</span>
442 </Link>
443 </div>
444 </nav>
445
446 <TanStackRouterDevtools position="bottom-right" />
447 <Scripts />
448 </>
449 );
450}
451export function TablerHashtag(props: SVGProps<SVGSVGElement>) {
452 return (
453 <svg
454 xmlns="http://www.w3.org/2000/svg"
455 width={24}
456 height={24}
457 viewBox="0 0 24 24"
458 {...props}
459 >
460 <path
461 fill="none"
462 stroke="currentColor"
463 strokeLinecap="round"
464 strokeLinejoin="round"
465 strokeWidth={2}
466 d="M5 9h14M5 15h14M11 4L7 20M17 4l-4 16"
467 ></path>
468 </svg>
469 );
470}
471
472export function TablerHashtagFilled(props: SVGProps<SVGSVGElement>) {
473 return (
474 <svg
475 xmlns="http://www.w3.org/2000/svg"
476 width={24}
477 height={24}
478 viewBox="0 0 24 24"
479 {...props}
480 >
481 <path
482 fill="none"
483 stroke="currentColor"
484 strokeLinecap="round"
485 strokeLinejoin="round"
486 strokeWidth={3}
487 d="M5 9h14M5 15h14M11 4L7 20M17 4l-4 16"
488 ></path>
489 </svg>
490 );
491}
492export function TablerEdit(props: SVGProps<SVGSVGElement>) {
493 return (
494 <svg
495 xmlns="http://www.w3.org/2000/svg"
496 width={24}
497 height={24}
498 viewBox="0 0 24 24"
499 className="text-white"
500 {...props}
501 >
502 <g
503 fill="none"
504 stroke="currentColor"
505 strokeLinecap="round"
506 strokeLinejoin="round"
507 strokeWidth={2}
508 >
509 <path d="M16.475 5.408a2.36 2.36 0 1 1 3.34 3.34L7.5 21H3v-4.5z"></path>
510 </g>
511 </svg>
512 );
513}
514export function TablerHome(props: SVGProps<SVGSVGElement>) {
515 return (
516 <svg
517 xmlns="http://www.w3.org/2000/svg"
518 width={24}
519 height={24}
520 viewBox="0 0 24 24"
521 className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
522 {...props}
523 >
524 <g
525 stroke="currentColor"
526 strokeLinecap="round"
527 strokeLinejoin="round"
528 strokeWidth={2}
529 fill="none"
530 >
531 <path d="M5 12H3l9-9l9 9h-2M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7"></path>
532 <path d="M9 21v-6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v6"></path>
533 </g>
534 </svg>
535 );
536}
537export function TablerHomeFilled(props: SVGProps<SVGSVGElement>) {
538 return (
539 <svg
540 xmlns="http://www.w3.org/2000/svg"
541 width={24}
542 height={24}
543 viewBox="0 0 24 24"
544 className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
545 {...props}
546 >
547 <path
548 fill="currentColor"
549 d="m12.707 2.293l9 9c.63.63.184 1.707-.707 1.707h-1v6a3 3 0 0 1-3 3h-1v-7a3 3 0 0 0-2.824-2.995L13 12h-2a3 3 0 0 0-3 3v7H7a3 3 0 0 1-3-3v-6H3c-.89 0-1.337-1.077-.707-1.707l9-9a1 1 0 0 1 1.414 0M13 14a1 1 0 0 1 1 1v7h-4v-7a1 1 0 0 1 .883-.993L11 14z"
550 ></path>
551 </svg>
552 );
553}
554
555export function TablerBell(props: SVGProps<SVGSVGElement>) {
556 return (
557 <svg
558 xmlns="http://www.w3.org/2000/svg"
559 width={24}
560 height={24}
561 viewBox="0 0 24 24"
562 {...props}
563 >
564 <path
565 className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
566 stroke="currentColor"
567 strokeLinecap="round"
568 strokeLinejoin="round"
569 strokeWidth={2}
570 d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3H4a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6M9 17v1a3 3 0 0 0 6 0v-1"
571 ></path>
572 </svg>
573 );
574}
575export function TablerBellFilled(props: SVGProps<SVGSVGElement>) {
576 return (
577 <svg
578 xmlns="http://www.w3.org/2000/svg"
579 width={24}
580 height={24}
581 viewBox="0 0 24 24"
582 className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
583 {...props}
584 >
585 <path
586 fill="currentColor"
587 stroke="currentColor"
588 d="M14.235 19c.865 0 1.322 1.024.745 1.668A4 4 0 0 1 12 22a4 4 0 0 1-2.98-1.332c-.552-.616-.158-1.579.634-1.661l.11-.006zM12 2c1.358 0 2.506.903 2.875 2.141l.046.171l.008.043a8.01 8.01 0 0 1 4.024 6.069l.028.287L19 11v2.931l.021.136a3 3 0 0 0 1.143 1.847l.167.117l.162.099c.86.487.56 1.766-.377 1.864L20 18H4c-1.028 0-1.387-1.364-.493-1.87a3 3 0 0 0 1.472-2.063L5 13.924l.001-2.97A8 8 0 0 1 8.822 4.5l.248-.146l.01-.043a3 3 0 0 1 2.562-2.29l.182-.017z"
589 ></path>
590 </svg>
591 );
592}
593
594export function TablerUserCircle(props: SVGProps<SVGSVGElement>) {
595 return (
596 <svg
597 xmlns="http://www.w3.org/2000/svg"
598 width={24}
599 height={24}
600 viewBox="0 0 24 24"
601 className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
602 {...props}
603 >
604 <g
605 fill="none"
606 stroke="currentColor"
607 strokeLinecap="round"
608 strokeLinejoin="round"
609 strokeWidth={2}
610 >
611 <path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0"></path>
612 <path d="M9 10a3 3 0 1 0 6 0a3 3 0 1 0-6 0m-2.832 8.849A4 4 0 0 1 10 16h4a4 4 0 0 1 3.834 2.855"></path>
613 </g>
614 </svg>
615 );
616}
617
618export function TablerSearch(props: SVGProps<SVGSVGElement>) {
619 return (
620 <svg
621 xmlns="http://www.w3.org/2000/svg"
622 width={24}
623 height={24}
624 viewBox="0 0 24 24"
625 //className="text-gray-400 dark:text-gray-500"
626 {...props}
627 >
628 <g
629 fill="none"
630 stroke="currentColor"
631 strokeLinecap="round"
632 strokeLinejoin="round"
633 strokeWidth={2}
634 >
635 <path d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0-14 0"></path>
636 <path d="m21 21l-6-6"></path>
637 </g>
638 </svg>
639 );
640}
641export function TablerSearchFilled(props: SVGProps<SVGSVGElement>) {
642 return (
643 <svg
644 xmlns="http://www.w3.org/2000/svg"
645 width={24}
646 height={24}
647 viewBox="0 0 24 24"
648 //className="text-gray-400 dark:text-gray-500"
649 {...props}
650 >
651 <g
652 fill="none"
653 stroke="currentColor"
654 strokeLinecap="round"
655 strokeLinejoin="round"
656 strokeWidth={3}
657 >
658 <path d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0-14 0"></path>
659 <path d="m21 21l-6-6"></path>
660 </g>
661 </svg>
662 );
663}
664
665export function IonSettings(props: SVGProps<SVGSVGElement>) {
666 return (
667 <svg
668 xmlns="http://www.w3.org/2000/svg"
669 width={24}
670 height={24}
671 viewBox="0 0 512 512"
672 {...props}
673 >
674 <path
675 fill="none"
676 stroke="currentColor"
677 strokeLinecap="round"
678 strokeLinejoin="round"
679 strokeWidth={32}
680 d="M262.29 192.31a64 64 0 1 0 57.4 57.4a64.13 64.13 0 0 0-57.4-57.4M416.39 256a154 154 0 0 1-1.53 20.79l45.21 35.46a10.81 10.81 0 0 1 2.45 13.75l-42.77 74a10.81 10.81 0 0 1-13.14 4.59l-44.9-18.08a16.11 16.11 0 0 0-15.17 1.75A164.5 164.5 0 0 1 325 400.8a15.94 15.94 0 0 0-8.82 12.14l-6.73 47.89a11.08 11.08 0 0 1-10.68 9.17h-85.54a11.11 11.11 0 0 1-10.69-8.87l-6.72-47.82a16.07 16.07 0 0 0-9-12.22a155 155 0 0 1-21.46-12.57a16 16 0 0 0-15.11-1.71l-44.89 18.07a10.81 10.81 0 0 1-13.14-4.58l-42.77-74a10.8 10.8 0 0 1 2.45-13.75l38.21-30a16.05 16.05 0 0 0 6-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16 16 0 0 0-6.07-13.94l-38.19-30A10.81 10.81 0 0 1 49.48 186l42.77-74a10.81 10.81 0 0 1 13.14-4.59l44.9 18.08a16.11 16.11 0 0 0 15.17-1.75A164.5 164.5 0 0 1 187 111.2a15.94 15.94 0 0 0 8.82-12.14l6.73-47.89A11.08 11.08 0 0 1 213.23 42h85.54a11.11 11.11 0 0 1 10.69 8.87l6.72 47.82a16.07 16.07 0 0 0 9 12.22a155 155 0 0 1 21.46 12.57a16 16 0 0 0 15.11 1.71l44.89-18.07a10.81 10.81 0 0 1 13.14 4.58l42.77 74a10.8 10.8 0 0 1-2.45 13.75l-38.21 30a16.05 16.05 0 0 0-6.05 14.08c.33 4.14.55 8.3.55 12.47"
681 ></path>
682 </svg>
683 );
684}
685export function IonSettingsSharp(props: SVGProps<SVGSVGElement>) {
686 return (
687 <svg
688 xmlns="http://www.w3.org/2000/svg"
689 width={24}
690 height={24}
691 viewBox="0 0 512 512"
692 {...props}
693 >
694 <path
695 fill="currentColor"
696 d="M256 176a80 80 0 1 0 80 80a80.24 80.24 0 0 0-80-80m172.72 80a165.5 165.5 0 0 1-1.64 22.34l48.69 38.12a11.59 11.59 0 0 1 2.63 14.78l-46.06 79.52a11.64 11.64 0 0 1-14.14 4.93l-57.25-23a176.6 176.6 0 0 1-38.82 22.67l-8.56 60.78a11.93 11.93 0 0 1-11.51 9.86h-92.12a12 12 0 0 1-11.51-9.53l-8.56-60.78A169.3 169.3 0 0 1 151.05 393L93.8 416a11.64 11.64 0 0 1-14.14-4.92L33.6 331.57a11.59 11.59 0 0 1 2.63-14.78l48.69-38.12A175 175 0 0 1 83.28 256a165.5 165.5 0 0 1 1.64-22.34l-48.69-38.12a11.59 11.59 0 0 1-2.63-14.78l46.06-79.52a11.64 11.64 0 0 1 14.14-4.93l57.25 23a176.6 176.6 0 0 1 38.82-22.67l8.56-60.78A11.93 11.93 0 0 1 209.94 26h92.12a12 12 0 0 1 11.51 9.53l8.56 60.78A169.3 169.3 0 0 1 361 119l57.2-23a11.64 11.64 0 0 1 14.14 4.92l46.06 79.52a11.59 11.59 0 0 1-2.63 14.78l-48.69 38.12a175 175 0 0 1 1.64 22.66"
697 ></path>
698 </svg>
699 );
700}