소스 검색

Use modal dialog for WiFi network connection

Tapping a network now opens a dialog with password field and connect
button, instead of inline expansion that pushed content off-screen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tuanchris 4 달 전
부모
커밋
56cd5e836c
1개의 변경된 파일113개의 추가작업 그리고 98개의 파일을 삭제
  1. 113 98
      frontend/src/pages/WiFiSetupPage.tsx

+ 113 - 98
frontend/src/pages/WiFiSetupPage.tsx

@@ -8,6 +8,13 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/com
 import { Alert, AlertDescription } from '@/components/ui/alert'
 import { Badge } from '@/components/ui/badge'
 import { Separator } from '@/components/ui/separator'
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogHeader,
+  DialogTitle,
+} from '@/components/ui/dialog'
 
 interface WiFiNetwork {
   ssid: string
@@ -30,7 +37,6 @@ interface SavedConnection {
 }
 
 function SignalIcon({ signal }: { signal: number }) {
-  // Map signal percentage to a descriptive icon
   const bars = signal >= 75 ? 'signal_wifi_4_bar' :
                signal >= 50 ? 'network_wifi_3_bar' :
                signal >= 25 ? 'network_wifi_2_bar' :
@@ -43,7 +49,7 @@ export function WiFiSetupPage() {
   const [status, setStatus] = useState<WiFiStatus | null>(null)
   const [networks, setNetworks] = useState<WiFiNetwork[]>([])
   const [savedConnections, setSavedConnections] = useState<SavedConnection[]>([])
-  const [selectedNetwork, setSelectedNetwork] = useState<string | null>(null)
+  const [selectedNetwork, setSelectedNetwork] = useState<WiFiNetwork | null>(null)
   const [password, setPassword] = useState('')
   const [showPassword, setShowPassword] = useState(false)
   const [isScanning, setIsScanning] = useState(false)
@@ -86,16 +92,17 @@ export function WiFiSetupPage() {
     fetchSaved()
   }, [fetchStatus, scanNetworks, fetchSaved])
 
+  const needsPassword = selectedNetwork &&
+    selectedNetwork.security !== 'Open' &&
+    !selectedNetwork.saved
+
   const handleConnect = async () => {
     if (!selectedNetwork) return
 
-    const network = networks.find(n => n.ssid === selectedNetwork)
-    const needsPassword = network && network.security !== 'Open' && !network.saved
-
     setIsConnecting(true)
     try {
       const result = await apiClient.post<{ success: boolean; message: string }>('/api/wifi/connect', {
-        ssid: selectedNetwork,
+        ssid: selectedNetwork.ssid,
         password: needsPassword ? password : '',
       })
 
@@ -122,9 +129,21 @@ export function WiFiSetupPage() {
     }
   }
 
-  const isHotspotMode = status?.mode === 'hotspot'
+  const openConnectDialog = (network: WiFiNetwork) => {
+    setSelectedNetwork(network)
+    setPassword('')
+    setShowPassword(false)
+  }
+
+  const closeDialog = () => {
+    if (!isConnecting) {
+      setSelectedNetwork(null)
+      setPassword('')
+      setShowPassword(false)
+    }
+  }
 
-  const selectedNetworkData = networks.find(n => n.ssid === selectedNetwork)
+  const isHotspotMode = status?.mode === 'hotspot'
 
   if (isRebooting) {
     return (
@@ -135,7 +154,7 @@ export function WiFiSetupPage() {
               <span className="material-icons text-5xl text-blue-500 animate-spin">refresh</span>
               <h2 className="text-xl font-semibold">Rebooting...</h2>
               <p className="text-muted-foreground">
-                The system is rebooting to connect to <strong>{selectedNetwork}</strong>.
+                The system is rebooting to connect to <strong>{selectedNetwork?.ssid}</strong>.
               </p>
               <p className="text-sm text-muted-foreground">
                 Once connected, access Dune Weaver on your home network at:
@@ -244,96 +263,28 @@ export function WiFiSetupPage() {
               No networks found. Try scanning again.
             </p>
           )}
-          {networks.map((network) => {
-            const isSelected = selectedNetwork === network.ssid
-            const showForm = isSelected && selectedNetworkData
-            const showPasswordField = showForm &&
-              selectedNetworkData.security !== 'Open' &&
-              !selectedNetworkData.saved
-
-            return (
-              <div key={network.ssid}>
-                <button
-                  onClick={() => {
-                    setSelectedNetwork(isSelected ? null : network.ssid)
-                    setPassword('')
-                    setShowPassword(false)
-                  }}
-                  className={`w-full flex items-center gap-3 p-3 rounded-lg text-left transition-colors
-                    ${isSelected
-                      ? 'bg-primary/10 ring-1 ring-primary'
-                      : 'hover:bg-muted/50'
-                    }
-                    ${network.active ? 'bg-green-50 dark:bg-green-950/20' : ''}`}
-                >
-                  <SignalIcon signal={network.signal} />
-                  <div className="flex-1 min-w-0">
-                    <p className="font-medium truncate">{network.ssid}</p>
-                    <p className="text-xs text-muted-foreground">
-                      {network.security}
-                      {network.saved && ' · Saved'}
-                      {network.active && ' · Connected'}
-                    </p>
-                  </div>
-                  <span className="text-xs text-muted-foreground">{network.signal}%</span>
-                  {network.security !== 'Open' && (
-                    <span className="material-icons text-sm text-muted-foreground">lock</span>
-                  )}
-                </button>
-
-                {/* Inline connection form — appears directly below selected network */}
-                {showForm && (
-                  <div className="mx-3 mb-2 mt-1 p-3 rounded-lg bg-muted/50 space-y-3">
-                    {showPasswordField && (
-                      <div className="space-y-2">
-                        <Label htmlFor="wifi-password">Password</Label>
-                        <div className="relative">
-                          <Input
-                            id="wifi-password"
-                            type={showPassword ? 'text' : 'password'}
-                            value={password}
-                            onChange={(e) => setPassword(e.target.value)}
-                            placeholder="Enter WiFi password"
-                            autoFocus
-                            onKeyDown={(e) => {
-                              if (e.key === 'Enter' && password) handleConnect()
-                            }}
-                          />
-                          <button
-                            type="button"
-                            onClick={() => setShowPassword(!showPassword)}
-                            className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
-                          >
-                            <span className="material-icons text-sm">
-                              {showPassword ? 'visibility_off' : 'visibility'}
-                            </span>
-                          </button>
-                        </div>
-                      </div>
-                    )}
-
-                    <Button
-                      className="w-full"
-                      onClick={handleConnect}
-                      disabled={isConnecting || (showPasswordField && !password)}
-                    >
-                      {isConnecting ? (
-                        <>
-                          <span className="material-icons text-sm animate-spin mr-2">refresh</span>
-                          Connecting...
-                        </>
-                      ) : (
-                        <>
-                          <span className="material-icons text-sm mr-2">wifi</span>
-                          Connect{isHotspotMode ? ' & Reboot' : ''}
-                        </>
-                      )}
-                    </Button>
-                  </div>
-                )}
+          {networks.map((network) => (
+            <button
+              key={network.ssid}
+              onClick={() => openConnectDialog(network)}
+              className={`w-full flex items-center gap-3 p-3 rounded-lg text-left transition-colors hover:bg-muted/50
+                ${network.active ? 'bg-green-50 dark:bg-green-950/20' : ''}`}
+            >
+              <SignalIcon signal={network.signal} />
+              <div className="flex-1 min-w-0">
+                <p className="font-medium truncate">{network.ssid}</p>
+                <p className="text-xs text-muted-foreground">
+                  {network.security}
+                  {network.saved && ' · Saved'}
+                  {network.active && ' · Connected'}
+                </p>
               </div>
-            )
-          })}
+              <span className="text-xs text-muted-foreground">{network.signal}%</span>
+              {network.security !== 'Open' && (
+                <span className="material-icons text-sm text-muted-foreground">lock</span>
+              )}
+            </button>
+          ))}
         </CardContent>
       </Card>
 
@@ -383,6 +334,70 @@ export function WiFiSetupPage() {
           </div>
         </>
       )}
+
+      {/* Connect Dialog */}
+      <Dialog open={selectedNetwork !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>
+        <DialogContent className="sm:max-w-md">
+          <DialogHeader>
+            <DialogTitle className="flex items-center gap-2">
+              {selectedNetwork && <SignalIcon signal={selectedNetwork.signal} />}
+              {selectedNetwork?.ssid}
+            </DialogTitle>
+            <DialogDescription>
+              {selectedNetwork?.security !== 'Open' ? 'Secured network' : 'Open network'}
+              {selectedNetwork?.saved && ' · Saved'}
+            </DialogDescription>
+          </DialogHeader>
+
+          <div className="space-y-4">
+            {needsPassword && (
+              <div className="space-y-2">
+                <Label htmlFor="wifi-password">Password</Label>
+                <div className="relative">
+                  <Input
+                    id="wifi-password"
+                    type={showPassword ? 'text' : 'password'}
+                    value={password}
+                    onChange={(e) => setPassword(e.target.value)}
+                    placeholder="Enter WiFi password"
+                    autoFocus
+                    onKeyDown={(e) => {
+                      if (e.key === 'Enter' && password) handleConnect()
+                    }}
+                  />
+                  <button
+                    type="button"
+                    onClick={() => setShowPassword(!showPassword)}
+                    className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
+                  >
+                    <span className="material-icons text-sm">
+                      {showPassword ? 'visibility_off' : 'visibility'}
+                    </span>
+                  </button>
+                </div>
+              </div>
+            )}
+
+            <Button
+              className="w-full"
+              onClick={handleConnect}
+              disabled={isConnecting || (!!needsPassword && !password)}
+            >
+              {isConnecting ? (
+                <>
+                  <span className="material-icons text-sm animate-spin mr-2">refresh</span>
+                  Connecting...
+                </>
+              ) : (
+                <>
+                  <span className="material-icons text-sm mr-2">wifi</span>
+                  Connect{isHotspotMode ? ' & Reboot' : ''}
+                </>
+              )}
+            </Button>
+          </div>
+        </DialogContent>
+      </Dialog>
     </div>
   )
 }