an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm
1import { createFileRoute } from "@tanstack/react-router"; 2import { useAtom } from "jotai"; 3 4import { Header } from "~/components/Header"; 5import Login from "~/components/Login"; 6import { 7 constellationURLAtom, 8 defaultconstellationURL, 9 defaultImgCDN, 10 defaultslingshotURL, 11 defaultVideoCDN, 12 imgCDNAtom, 13 slingshotURLAtom, 14 videoCDNAtom, 15} from "~/utils/atoms"; 16 17export const Route = createFileRoute("/settings")({ 18 component: Settings, 19}); 20 21export function Settings() { 22 return ( 23 <> 24 <Header 25 title="Settings" 26 backButtonCallback={() => { 27 if (window.history.length > 1) { 28 window.history.back(); 29 } else { 30 window.location.assign("/"); 31 } 32 }} 33 /> 34 <div className="lg:hidden"><Login /></div> 35 <div className="h-4" /> 36 <TextInputSetting 37 atom={constellationURLAtom} 38 title={"Constellation"} 39 description={ 40 "Customize the Constellation instance to be used by Red Dwarf" 41 } 42 init={defaultconstellationURL} 43 /> 44 <TextInputSetting 45 atom={slingshotURLAtom} 46 title={"Slingshot"} 47 description={"Customize the Slingshot instance to be used by Red Dwarf"} 48 init={defaultslingshotURL} 49 /> 50 <TextInputSetting 51 atom={imgCDNAtom} 52 title={"Image CDN"} 53 description={ 54 "Customize the Constellation instance to be used by Red Dwarf" 55 } 56 init={defaultImgCDN} 57 /> 58 <TextInputSetting 59 atom={videoCDNAtom} 60 title={"Video CDN"} 61 description={"Customize the Slingshot instance to be used by Red Dwarf"} 62 init={defaultVideoCDN} 63 /> 64 <p className="text-gray-500 dark:text-gray-400 py-4 px-6 text-sm">please restart/refresh the app if changes arent applying correctly</p> 65 </> 66 ); 67} 68 69export function TextInputSetting({ 70 atom, 71 title, 72 description, 73 init, 74}: { 75 atom: typeof constellationURLAtom; 76 title?: string; 77 description?: string; 78 init?: string; 79}) { 80 const [value, setValue] = useAtom(atom); 81 return ( 82 <div className="flex flex-col gap-2 px-4 py-2"> 83 {/* <div> 84 {title && ( 85 <h3 className="text-sm font-medium text-gray-900 dark:text-gray-100"> 86 {title} 87 </h3> 88 )} 89 {description && ( 90 <p className="text-sm text-gray-500 dark:text-gray-400"> 91 {description} 92 </p> 93 )} 94 </div> */} 95 96 <div className="flex flex-row gap-2 items-center"> 97 <div className="m3input-field m3input-label m3input-border size-md flex-1"> 98 <input type="text" placeholder=" " value={value} onChange={(e) => setValue(e.target.value)}/> 99 <label>{title}</label> 100 </div> 101 {/* <input 102 type="text" 103 value={value} 104 onChange={(e) => setValue(e.target.value)} 105 className="flex-1 px-3 py-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 106 text-gray-900 dark:text-gray-100 placeholder:text-gray-500 dark:placeholder:text-gray-400 107 focus:outline-none focus:ring-2 focus:ring-gray-400 dark:focus:ring-gray-600" 108 placeholder="Enter value..." 109 /> */} 110 <button 111 onClick={() => setValue(init ?? "")} 112 className="px-6 py-2 h-12 rounded-full bg-gray-100 dark:bg-gray-800 113 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition" 114 > 115 Reset 116 </button> 117 </div> 118 </div> 119 ); 120}