the statusphere demo reworked into a vite/react app in a monorepo

dark mode!

+1 -1
packages/client/src/App.tsx
··· 7 7 8 8 function App() { 9 9 return ( 10 - <div className="bg-gray-50 min-h-screen"> 10 + <div className="min-h-screen"> 11 11 <div className="max-w-4xl mx-auto p-4 w-full"> 12 12 <AuthProvider> 13 13 <Routes>
+5 -5
packages/client/src/components/Header.tsx
··· 14 14 } 15 15 16 16 return ( 17 - <header className="mb-8 border-b border-gray-200 pb-4"> 17 + <header className="mb-8 border-b border-gray-200 dark:border-gray-700 pb-4"> 18 18 <div className="flex justify-between items-center"> 19 19 <h1 className="m-0 text-2xl font-bold"> 20 20 <Link 21 21 to="/" 22 - className="no-underline text-inherit hover:text-blue-600 transition-colors" 22 + className="no-underline text-inherit hover:text-blue-600 dark:hover:text-blue-400 transition-colors" 23 23 > 24 24 Statusphere 25 25 </Link> ··· 27 27 <nav> 28 28 {user ? ( 29 29 <div className="flex gap-4 items-center"> 30 - <span className="text-gray-700"> 30 + <span className="text-gray-700 dark:text-gray-300"> 31 31 {user.profile?.displayName || 32 32 user.profile?.handle || 33 33 user.did.substring(0, 15)} 34 34 </span> 35 35 <button 36 36 onClick={handleLogout} 37 - className="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md transition-colors" 37 + className="px-4 py-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-md transition-colors" 38 38 > 39 39 Logout 40 40 </button> 41 41 </div> 42 42 ) : ( 43 43 <Link to="/login"> 44 - <button className="px-4 py-2 bg-blue-500 text-white hover:bg-blue-600 rounded-md transition-colors"> 44 + <button className="px-4 py-2 bg-blue-500 text-white hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 rounded-md transition-colors"> 45 45 Login 46 46 </button> 47 47 </Link>
+7 -5
packages/client/src/components/StatusForm.tsx
··· 132 132 } 133 133 134 134 return ( 135 - <div className="bg-white rounded-lg p-4 mb-6 shadow-sm"> 135 + <div className="bg-white dark:bg-gray-800 rounded-lg p-4 mb-6 shadow-sm"> 136 136 <h2 className="text-xl font-semibold mb-4">How are you feeling?</h2> 137 137 {(error || mutation.error) && ( 138 - <div className="text-red-500 mb-4 p-2 bg-red-50 rounded-md"> 138 + <div className="text-red-500 mb-4 p-2 bg-red-50 dark:bg-red-950 dark:bg-opacity-30 rounded-md"> 139 139 {error || 140 140 (mutation.error instanceof Error 141 141 ? mutation.error.message ··· 159 159 flex items-center justify-center 160 160 transition-all duration-200 161 161 ${isSelected ? 'opacity-60' : 'opacity-100'} 162 - ${!isSelected ? 'hover:bg-gray-100 hover:scale-110' : ''} 163 - ${isCurrentStatus ? 'bg-blue-50 ring-1 ring-blue-200' : ''} 162 + ${!isSelected ? 'hover:bg-gray-100 dark:hover:bg-gray-700 hover:scale-110' : ''} 163 + ${isCurrentStatus 164 + ? 'bg-blue-50 ring-1 ring-blue-200 dark:bg-blue-900 dark:bg-opacity-30 dark:ring-blue-700' 165 + : ''} 164 166 active:scale-95 165 - focus:outline-none focus:ring-2 focus:ring-blue-300 167 + focus:outline-none focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-500 166 168 `} 167 169 title={isCurrentStatus ? 'Your current status' : undefined} 168 170 >
+6 -6
packages/client/src/components/StatusList.tsx
··· 18 18 19 19 if (isLoading && !data) { 20 20 return ( 21 - <div className="py-4 text-center text-gray-500">Loading statuses...</div> 21 + <div className="py-4 text-center text-gray-500 dark:text-gray-400">Loading statuses...</div> 22 22 ) 23 23 } 24 24 ··· 32 32 33 33 if (statuses.length === 0) { 34 34 return ( 35 - <div className="py-4 text-center text-gray-500">No statuses yet.</div> 35 + <div className="py-4 text-center text-gray-500 dark:text-gray-400">No statuses yet.</div> 36 36 ) 37 37 } 38 38 ··· 59 59 return ( 60 60 <div className="px-4"> 61 61 <div className="relative"> 62 - <div className="absolute left-[20.5px] top-[22.5px] bottom-[22.5px] w-0.5 bg-gray-200"></div> 62 + <div className="absolute left-[20.5px] top-[22.5px] bottom-[22.5px] w-0.5 bg-gray-200 dark:bg-gray-700"></div> 63 63 {statuses.map((status) => { 64 64 const handle = 65 65 status.profile.handle || status.profile.did.substring(0, 15) + '...' ··· 71 71 key={status.uri} 72 72 className="relative flex items-center gap-5 py-4" 73 73 > 74 - <div className="relative z-10 rounded-full bg-white border border-gray-200 h-[45px] w-[45px] flex items-center justify-center shadow-sm"> 74 + <div className="relative z-10 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 h-[45px] w-[45px] flex items-center justify-center shadow-sm"> 75 75 <div className="text-2xl">{status.status}</div> 76 76 </div> 77 77 <div className="flex-1"> 78 - <div className="text-gray-600 text-base"> 79 - <span className="font-medium text-gray-700 hover:underline"> 78 + <div className="text-gray-600 dark:text-gray-300 text-base"> 79 + <span className="font-medium text-gray-700 dark:text-gray-200 hover:underline"> 80 80 @{handle} 81 81 </span>{' '} 82 82 {isToday ? (
+7
packages/client/src/index.css
··· 9 9 .status-message-fade { 10 10 animation: fadeOut 2s forwards; 11 11 } 12 + 13 + /* Base styling */ 14 + @layer base { 15 + body { 16 + @apply bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100; 17 + } 18 + }
+5 -5
packages/client/src/pages/HomePage.tsx
··· 10 10 return ( 11 11 <div className="flex justify-center items-center py-16"> 12 12 <div className="text-center p-6"> 13 - <h2 className="text-2xl font-semibold mb-2 text-gray-800"> 13 + <h2 className="text-2xl font-semibold mb-2 text-gray-800 dark:text-gray-200"> 14 14 Loading Statusphere... 15 15 </h2> 16 - <p className="text-gray-600">Setting up your experience</p> 16 + <p className="text-gray-600 dark:text-gray-400">Setting up your experience</p> 17 17 </div> 18 18 </div> 19 19 ) ··· 23 23 return ( 24 24 <div className="flex justify-center items-center py-16"> 25 25 <div className="text-center p-6 max-w-md"> 26 - <h2 className="text-2xl font-semibold mb-2 text-gray-800">Error</h2> 26 + <h2 className="text-2xl font-semibold mb-2 text-gray-800 dark:text-gray-200">Error</h2> 27 27 <p className="text-red-500 mb-4">{error}</p> 28 28 <a 29 29 href="/login" 30 - className="inline-block px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors" 30 + className="inline-block px-4 py-2 bg-blue-500 dark:bg-blue-600 text-white rounded-md hover:bg-blue-600 dark:hover:bg-blue-700 transition-colors" 31 31 > 32 32 Try logging in again 33 33 </a> ··· 43 43 {user && <StatusForm />} 44 44 45 45 <div> 46 - <h2 className="text-xl font-semibold mb-4 text-gray-800"> 46 + <h2 className="text-xl font-semibold mb-4 text-gray-800 dark:text-gray-200"> 47 47 Recent Statuses 48 48 </h2> 49 49 <StatusList />
+6 -6
packages/client/src/pages/LoginPage.tsx
··· 31 31 <div className="flex flex-col gap-8"> 32 32 <Header /> 33 33 34 - <div className="bg-white rounded-lg p-6 shadow-sm max-w-md mx-auto w-full"> 34 + <div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm max-w-md mx-auto w-full"> 35 35 <h2 className="text-xl font-semibold mb-4">Login with your handle</h2> 36 36 37 37 {error && ( 38 - <div className="text-red-500 mb-4 p-2 bg-red-50 rounded-md"> 38 + <div className="text-red-500 mb-4 p-2 bg-red-50 dark:bg-red-950 dark:bg-opacity-30 rounded-md"> 39 39 {error} 40 40 </div> 41 41 )} 42 42 43 43 <form onSubmit={handleSubmit}> 44 44 <div className="mb-4"> 45 - <label htmlFor="handle" className="block mb-2 text-gray-700"> 45 + <label htmlFor="handle" className="block mb-2 text-gray-700 dark:text-gray-300"> 46 46 Enter your Bluesky handle: 47 47 </label> 48 48 <input ··· 52 52 onChange={(e) => setHandle(e.target.value)} 53 53 placeholder="example.bsky.social" 54 54 disabled={loading} 55 - className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300" 55 + className="w-full p-3 border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-500" 56 56 /> 57 57 </div> 58 58 59 59 <button 60 60 type="submit" 61 61 disabled={loading} 62 - className={`w-full px-4 py-2 rounded-md bg-blue-500 text-white font-medium hover:bg-blue-600 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-300 ${ 62 + className={`w-full px-4 py-2 rounded-md bg-blue-500 dark:bg-blue-600 text-white font-medium hover:bg-blue-600 dark:hover:bg-blue-700 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-500 ${ 63 63 loading ? 'opacity-70 cursor-not-allowed' : '' 64 64 }`} 65 65 > ··· 70 70 <div className="mt-4 text-center"> 71 71 <Link 72 72 to="/" 73 - className="text-blue-500 hover:text-blue-700 transition-colors" 73 + className="text-blue-500 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 transition-colors" 74 74 > 75 75 Cancel 76 76 </Link>
+5 -5
packages/client/src/pages/OAuthCallbackPage.tsx
··· 66 66 67 67 return ( 68 68 <div className="flex items-center justify-center py-16"> 69 - <div className="bg-white rounded-lg shadow-sm p-8 max-w-md w-full text-center"> 69 + <div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-8 max-w-md w-full text-center"> 70 70 {error ? ( 71 71 <div> 72 72 <h2 className="text-2xl font-bold text-red-500 mb-4"> ··· 75 75 <p className="text-red-500 mb-6">{error}</p> 76 76 <button 77 77 onClick={() => navigate('/login')} 78 - className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors" 78 + className="px-4 py-2 bg-blue-500 dark:bg-blue-600 text-white rounded-md hover:bg-blue-600 dark:hover:bg-blue-700 transition-colors" 79 79 > 80 80 Try Again 81 81 </button> 82 82 </div> 83 83 ) : ( 84 84 <div> 85 - <h2 className="text-2xl font-bold text-gray-800 mb-4"> 85 + <h2 className="text-2xl font-bold text-gray-800 dark:text-gray-200 mb-4"> 86 86 Authentication in Progress 87 87 </h2> 88 88 <div className="flex justify-center mb-4"> 89 - <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div> 89 + <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 dark:border-blue-400"></div> 90 90 </div> 91 - <p className="text-gray-600">{message}</p> 91 + <p className="text-gray-600 dark:text-gray-400">{message}</p> 92 92 </div> 93 93 )} 94 94 </div>