flora is a fast and secure runtime that lets you write discord bots for your servers, with a rich TypeScript SDK, without worrying about running infrastructure. [mirror]
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

perf(frontend): remove unncessary effects, memoizes

+74 -83
+8
apps/frontend/src/components/docs-stack/documentation-stack.tsx
··· 41 41 <div 42 42 className='relative w-full flex items-center justify-center' 43 43 style={{ minHeight: 600 }} 44 + role='button' 45 + tabIndex={0} 44 46 onClick={() => setSelectedCardId(null)} 47 + onKeyDown={(event) => { 48 + if (event.key === 'Enter' || event.key === ' ') { 49 + event.preventDefault() 50 + setSelectedCardId(null) 51 + } 52 + }} 45 53 > 46 54 <LazyMotion features={domAnimation}> 47 55 <m.div
+5 -7
apps/frontend/src/components/editor/workbench.tsx
··· 33 33 import getThemeServiceOverride from '@codingame/monaco-vscode-theme-service-override' 34 34 import getWorkbenchServiceOverride from '@codingame/monaco-vscode-workbench-service-override' 35 35 import * as monaco from 'monaco-editor' 36 - import { useEffect, useMemo, useRef, useState } from 'react' 36 + import { useEffect, useRef, useState } from 'react' 37 37 38 38 import floraSdkGlobalTypes from '../../../../../sdk/global-types.d.ts?raw' 39 39 import { getParentFolder, normalizePath } from './editor-utils' ··· 361 361 const [ready, setReady] = useState(false) 362 362 const [error, setError] = useState<string | null>(null) 363 363 364 - const filesMemo = useMemo(() => files, [files]) 365 - 366 364 useEffect(() => { 367 365 const container = containerRef.current 368 - if (!container || Object.keys(filesMemo).length === 0) return 366 + if (!container || Object.keys(files).length === 0) return 369 367 370 368 let changeDisposable: IDisposable | null = null 371 369 let unsubscribe: (() => void) | null = null ··· 373 371 const setup = async () => { 374 372 try { 375 373 if (!sharedController) { 376 - sharedController = await createController(container, filesMemo, entryFile) 374 + sharedController = await createController(container, files, entryFile) 377 375 } 378 376 379 377 changeDisposable = sharedController.provider.onDidChangeFile((changes) => { 380 378 void handleFileChange(sharedController!.provider, changes) 381 379 }) 382 380 383 - void sharedController.syncFiles(filesMemo) 381 + void sharedController.syncFiles(files) 384 382 unsubscribe = sharedController.subscribe(onFilesChange) 385 383 setReady(true) 386 384 setError(null) ··· 397 395 changeDisposable?.dispose() 398 396 if (unsubscribe) unsubscribe() 399 397 } 400 - }, [entryFile, filesMemo, onFilesChange]) 398 + }, [entryFile, files, onFilesChange]) 401 399 402 400 return ( 403 401 <div className='relative h-full min-w-0 flex-1'>
+15 -17
apps/frontend/src/components/features/DeploymentHistory.tsx
··· 16 16 import { cn } from '@/lib/utils' 17 17 import { formatDistanceToNow } from 'date-fns' 18 18 import { ChevronDown, Loader2, RotateCcw } from 'lucide-react' 19 - import { lazy, Suspense, useEffect, useMemo, useState } from 'react' 19 + import { lazy, Suspense, useMemo, useState } from 'react' 20 20 import { match } from 'ts-pattern' 21 21 22 22 const LazyMultiFileDiff = lazy(async () => { ··· 93 93 const [diffOpen, setDiffOpen] = useState(false) 94 94 const historyQuery = useDeploymentHistoryQuery(guildId) 95 95 96 - const selectedSummary = useMemo( 97 - () => historyQuery.data?.find((row) => row.id === selectedRevisionId), 98 - [historyQuery.data, selectedRevisionId] 99 - ) 100 - 101 - useEffect(() => { 102 - if (!historyQuery.data?.length) { 103 - setSelectedRevisionId(null) 104 - return 105 - } 106 - 107 - if (!selectedRevisionId || !historyQuery.data.some((row) => row.id === selectedRevisionId)) { 108 - setSelectedRevisionId(historyQuery.data[0]?.id ?? null) 96 + const resolvedRevisionId = useMemo(() => { 97 + const rows = historyQuery.data 98 + if (!rows?.length) return null 99 + if (selectedRevisionId && rows.some((row) => row.id === selectedRevisionId)) { 100 + return selectedRevisionId 109 101 } 102 + return rows[0]?.id ?? null 110 103 }, [historyQuery.data, selectedRevisionId]) 111 104 112 - const shouldLoadDiffs = diffOpen && !!selectedRevisionId 105 + const selectedSummary = useMemo( 106 + () => historyQuery.data?.find((row) => row.id === resolvedRevisionId), 107 + [historyQuery.data, resolvedRevisionId] 108 + ) 109 + 110 + const shouldLoadDiffs = diffOpen && !!resolvedRevisionId 113 111 114 112 const selectedRevisionQuery = useDeploymentRevisionQuery( 115 113 guildId, 116 - selectedRevisionId, 114 + resolvedRevisionId, 117 115 shouldLoadDiffs 118 116 ) 119 117 ··· 377 375 </TableHeader> 378 376 <TableBody> 379 377 {historyQuery.data?.map((row) => { 380 - const isSelected = row.id === selectedRevisionId 378 + const isSelected = row.id === resolvedRevisionId 381 379 382 380 return ( 383 381 <TableRow
+45 -51
apps/frontend/src/components/features/KvManager.tsx
··· 20 20 import type { components } from '@/lib/openapi-schema' 21 21 import { cn } from '@/lib/utils' 22 22 import { Database, KeyRound, Plus, RefreshCw, Trash2 } from 'lucide-react' 23 - import { useEffect, useMemo, useState } from 'react' 23 + import { useMemo, useState } from 'react' 24 24 25 25 type KvStore = components['schemas']['KvStore'] 26 26 type RawKvKeyInfo = components['schemas']['RawKvKeyInfo'] ··· 65 65 const [selectedStore, setSelectedStore] = useState<string>('') 66 66 const [keyPrefix, setKeyPrefix] = useState('') 67 67 const [keyName, setKeyName] = useState('') 68 - const [keyValue, setKeyValue] = useState('') 68 + const [keyValueDraft, setKeyValueDraft] = useState<string | null>(null) 69 69 const [keyMetadata, setKeyMetadata] = useState('') 70 70 const [keyExpiration, setKeyExpiration] = useState('') 71 71 const [selectedKey, setSelectedKey] = useState<RawKvKeyInfo | null>(null) ··· 73 73 74 74 const storesQuery = useKvStoresQuery(guildId) 75 75 76 - const keysQuery = useKvKeysQuery(guildId, selectedStore, keyPrefix) 76 + const activeStore = useMemo(() => { 77 + const storeList = storesQuery.data ?? [] 78 + if (selectedStore && storeList.some((store) => store.store_name === selectedStore)) { 79 + return selectedStore 80 + } 81 + return storeList[0]?.store_name ?? '' 82 + }, [selectedStore, storesQuery.data]) 77 83 78 - const valueQuery = useKvValueQuery(guildId, selectedStore, selectedKey?.name ?? null) 84 + const keysQuery = useKvKeysQuery(guildId, activeStore, keyPrefix) 85 + 86 + const valueQuery = useKvValueQuery(guildId, activeStore, selectedKey?.name ?? null) 79 87 80 88 const createStoreMutation = useCreateKvStoreMutation({ 81 89 onSuccess: () => { ··· 91 99 onSuccess: () => { 92 100 void storesQuery.refetch() 93 101 setSelectedStore('') 94 - setSelectedKey(null) 102 + resetEditor() 95 103 }, 96 104 onError: (err: any) => { 97 105 setError(err.message || 'Failed to delete store') ··· 103 111 void keysQuery.refetch() 104 112 setSelectedKey(null) 105 113 setKeyName('') 106 - setKeyValue('') 114 + setKeyValueDraft('') 107 115 setKeyMetadata('') 108 116 setKeyExpiration('') 109 117 }, ··· 117 125 void keysQuery.refetch() 118 126 setSelectedKey(null) 119 127 setKeyName('') 120 - setKeyValue('') 128 + setKeyValueDraft('') 121 129 setKeyMetadata('') 122 130 setKeyExpiration('') 123 131 }, ··· 129 137 const stores = storesQuery.data ?? [] 130 138 const keys = keysQuery.data?.keys ?? [] 131 139 132 - useEffect(() => { 133 - if (!storesQuery.data) return 134 - if (storesQuery.data.length === 0) { 135 - setSelectedStore('') 136 - return 137 - } 138 - if (!selectedStore || !storesQuery.data.some((store) => store.store_name === selectedStore)) { 139 - setSelectedStore(storesQuery.data[0]?.store_name ?? '') 140 - } 141 - }, [selectedStore, storesQuery.data]) 140 + const resolvedKeyValue = keyValueDraft ?? valueQuery.data?.value ?? '' 142 141 143 - useEffect(() => { 142 + const resetEditor = () => { 144 143 setSelectedKey(null) 145 144 setKeyName('') 146 - setKeyValue('') 145 + setKeyValueDraft('') 147 146 setKeyMetadata('') 148 147 setKeyExpiration('') 149 - }, [selectedStore]) 150 - 151 - useEffect(() => { 152 - if (!selectedKey?.name) return 153 - if (!valueQuery.data) return 154 - setKeyValue(valueQuery.data.value ?? '') 155 - }, [selectedKey, valueQuery.data]) 148 + } 156 149 157 150 const storeSummary = useMemo(() => { 158 - if (!selectedStore) return 'Select a store to view keys.' 151 + if (!activeStore) return 'Select a store to view keys.' 159 152 if (!keysQuery.data) return 'Loading keys…' 160 153 if (keysQuery.data.listComplete) return `${keys.length} keys loaded` 161 154 return `${keys.length} keys loaded (more available)` 162 - }, [keys.length, keysQuery.data, selectedStore]) 155 + }, [activeStore, keys.length, keysQuery.data]) 163 156 164 157 const handleCreateStore = () => { 165 158 setError(null) ··· 182 175 const handleSelectKey = (keyInfo: RawKvKeyInfo) => { 183 176 setSelectedKey(keyInfo) 184 177 setKeyName(keyInfo.name) 178 + setKeyValueDraft(null) 185 179 setKeyMetadata(formatMetadata(keyInfo.metadata)) 186 180 setKeyExpiration(keyInfo.expiration ? String(keyInfo.expiration) : '') 187 181 setError(null) ··· 189 183 190 184 const handleSaveKey = () => { 191 185 setError(null) 192 - if (!selectedStore) { 186 + if (!activeStore) { 193 187 setError('Select a store') 194 188 return 195 189 } ··· 226 220 params: { 227 221 path: { 228 222 guild_id: guildId, 229 - store_name: selectedStore, 223 + store_name: activeStore, 230 224 key: name 231 225 } 232 226 }, 233 227 body: { 234 - value: keyValue, 228 + value: resolvedKeyValue, 235 229 metadata, 236 230 expiration 237 231 } ··· 240 234 241 235 const handleDeleteKey = (keyInfo: RawKvKeyInfo) => { 242 236 setError(null) 243 - if (!selectedStore) return 237 + if (!activeStore) return 244 238 if (!confirm(`Delete key ${keyInfo.name}?`)) return 245 239 void deleteKeyMutation.mutateAsync({ 246 240 params: { 247 241 path: { 248 242 guild_id: guildId, 249 - store_name: selectedStore, 243 + store_name: activeStore, 250 244 key: keyInfo.name 251 245 } 252 246 } ··· 256 250 const handleClearEditor = () => { 257 251 setSelectedKey(null) 258 252 setKeyName('') 259 - setKeyValue('') 253 + setKeyValueDraft('') 260 254 setKeyMetadata('') 261 255 setKeyExpiration('') 262 256 setError(null) ··· 300 294 key={store.id} 301 295 className={cn( 302 296 'flex items-center justify-between rounded-md border px-3 py-2 text-sm', 303 - selectedStore === store.store_name && 'border-primary/50 bg-primary/5' 297 + activeStore === store.store_name && 'border-primary/50 bg-primary/5' 304 298 )} 305 299 > 306 300 <button ··· 308 302 className='flex-1 text-left font-medium' 309 303 onClick={() => { 310 304 setSelectedStore(store.store_name) 311 - setSelectedKey(null) 305 + resetEditor() 312 306 }} 313 307 > 314 308 {store.store_name} ··· 338 332 variant='outline' 339 333 size='sm' 340 334 onClick={() => keysQuery.refetch()} 341 - disabled={!selectedStore || keysQuery.isFetching} 335 + disabled={!activeStore || keysQuery.isFetching} 342 336 > 343 337 <RefreshCw className='mr-2 h-4 w-4' /> 344 338 Refresh ··· 349 343 placeholder='Filter by prefix' 350 344 value={keyPrefix} 351 345 onChange={(event) => setKeyPrefix(event.target.value)} 352 - disabled={!selectedStore} 346 + disabled={!activeStore} 353 347 /> 354 348 355 - {!selectedStore && ( 349 + {!activeStore && ( 356 350 <EmptyState 357 351 icon={KeyRound} 358 352 title='Select a store' ··· 360 354 /> 361 355 )} 362 356 363 - {selectedStore && keysQuery.isLoading && <Skeleton className='h-16 w-full' />} 357 + {activeStore && keysQuery.isLoading && <Skeleton className='h-16 w-full' />} 364 358 365 - {selectedStore && !keysQuery.isLoading && keys.length === 0 && ( 359 + {activeStore && !keysQuery.isLoading && keys.length === 0 && ( 366 360 <EmptyState 367 361 icon={KeyRound} 368 362 title='No keys yet' ··· 370 364 /> 371 365 )} 372 366 373 - {selectedStore && keys.length > 0 && ( 367 + {activeStore && keys.length > 0 && ( 374 368 <Table> 375 369 <TableHeader> 376 370 <TableRow> ··· 423 417 <CardHeader> 424 418 <CardTitle>Key editor</CardTitle> 425 419 <CardDescription> 426 - {selectedStore 420 + {activeStore 427 421 ? 'Create or update a key in the selected store.' 428 422 : 'Select a store to edit keys.'} 429 423 </CardDescription> ··· 434 428 placeholder='Key name' 435 429 value={keyName} 436 430 onChange={(event) => setKeyName(event.target.value)} 437 - disabled={!selectedStore} 431 + disabled={!activeStore} 438 432 /> 439 433 <Input 440 434 placeholder='Expiration (unix seconds)' 441 435 value={keyExpiration} 442 436 onChange={(event) => setKeyExpiration(event.target.value)} 443 - disabled={!selectedStore} 437 + disabled={!activeStore} 444 438 /> 445 439 </div> 446 440 447 441 <textarea 448 442 className='min-h-[120px] w-full rounded-3xl border border-input bg-input/30 px-3 py-2 text-sm transition-colors outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50' 449 443 placeholder='Value' 450 - value={keyValue} 451 - onChange={(event) => setKeyValue(event.target.value)} 452 - disabled={!selectedStore || valueQuery.isLoading} 444 + value={resolvedKeyValue} 445 + onChange={(event) => setKeyValueDraft(event.target.value)} 446 + disabled={!activeStore || valueQuery.isLoading} 453 447 /> 454 448 455 449 <textarea ··· 457 451 placeholder='Metadata (JSON)' 458 452 value={keyMetadata} 459 453 onChange={(event) => setKeyMetadata(event.target.value)} 460 - disabled={!selectedStore} 454 + disabled={!activeStore} 461 455 /> 462 456 463 457 <div className='flex flex-wrap gap-2'> 464 458 <Button 465 459 onClick={handleSaveKey} 466 - disabled={!selectedStore || setKeyMutation.isPending} 460 + disabled={!activeStore || setKeyMutation.isPending} 467 461 > 468 462 Save key 469 463 </Button> 470 - <Button variant='outline' onClick={handleClearEditor} disabled={!selectedStore}> 464 + <Button variant='outline' onClick={handleClearEditor} disabled={!activeStore}> 471 465 Clear 472 466 </Button> 473 467 </div>
+1
apps/frontend/src/components/sidebar/app-sidebar.tsx
··· 163 163 <NavUser 164 164 user={session} 165 165 onSettingsClick={() => { 166 + setView('guild') 166 167 setLocation('/settings') 167 168 if (isMobile) setOpenMobile(false) 168 169 }}
-8
apps/frontend/src/pages/user-settings-page.tsx
··· 1 1 import { TokenManager } from '@/components/features/TokenManager' 2 2 import { DashboardSidebar } from '@/components/sidebar/app-sidebar' 3 3 import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar' 4 - import { useApp } from '@/contexts/AppContext' 5 4 import { Seo } from '@/lib/seo' 6 - import { useEffect } from 'react' 7 5 8 6 export function UserSettingsPage() { 9 - const { setView } = useApp() 10 - 11 - useEffect(() => { 12 - setView('guild') 13 - }, [setView]) 14 - 15 7 return ( 16 8 <> 17 9 <Seo