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}