ソースを参照

feat: add two-option captive portal page (connect wifi or control table)

Users connecting to the hotspot now see a choice: set up WiFi or jump
straight to controlling the table via the hotspot connection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tuanchris 4 ヶ月 前
コミット
14ba2e2d72

+ 2 - 0
frontend/src/App.tsx

@@ -6,6 +6,7 @@ import { TableControlPage } from '@/pages/TableControlPage'
 import { LEDPage } from '@/pages/LEDPage'
 import { SettingsPage } from '@/pages/SettingsPage'
 import { WiFiSetupPage } from '@/pages/WiFiSetupPage'
+import { CaptivePortalPage } from '@/pages/CaptivePortalPage'
 import { SetupPage } from '@/pages/SetupPage'
 import { Toaster } from '@/components/ui/sonner'
 import { TableProvider } from '@/contexts/TableContext'
@@ -21,6 +22,7 @@ function App() {
           <Route path="led" element={<LEDPage />} />
           <Route path="settings" element={<SettingsPage />} />
           <Route path="wifi-setup" element={<WiFiSetupPage />} />
+          <Route path="captive" element={<CaptivePortalPage />} />
           <Route path="setup" element={<SetupPage />} />
         </Route>
       </Routes>

+ 8 - 3
frontend/src/components/layout/Layout.tsx

@@ -48,10 +48,15 @@ export function Layout() {
     window.scrollTo(0, 0)
   }, [location.pathname])
 
-  // Captive portal: redirect straight to WiFi setup
+  // Captive portal: redirect to captive landing page (unless user dismissed it or is on wifi-setup)
   useEffect(() => {
-    if (isCaptivePortal && location.pathname !== '/wifi-setup') {
-      navigate('/wifi-setup', { replace: true })
+    if (
+      isCaptivePortal &&
+      location.pathname !== '/wifi-setup' &&
+      location.pathname !== '/captive' &&
+      !sessionStorage.getItem('captive-dismissed')
+    ) {
+      navigate('/captive', { replace: true })
     }
   }, [location.pathname, navigate])
 

+ 36 - 0
frontend/src/pages/CaptivePortalPage.tsx

@@ -0,0 +1,36 @@
+import { useNavigate } from 'react-router-dom'
+
+export function CaptivePortalPage() {
+  const navigate = useNavigate()
+
+  const handleControlTable = () => {
+    // Mark that user chose to use the app via hotspot, so Layout stops redirecting back here
+    sessionStorage.setItem('captive-dismissed', '1')
+    navigate('/')
+  }
+
+  return (
+    <div className="flex flex-col items-center justify-center min-h-[60vh] px-4">
+      <div className="w-full max-w-sm text-center space-y-6">
+        <div>
+          <h1 className="text-2xl font-semibold">Welcome to Dune Weaver</h1>
+          <p className="text-muted-foreground mt-2">What would you like to do?</p>
+        </div>
+        <div className="flex flex-col gap-3">
+          <button
+            onClick={() => navigate('/wifi-setup')}
+            className="w-full rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
+          >
+            Connect to WiFi
+          </button>
+          <button
+            onClick={handleControlTable}
+            className="w-full rounded-lg border border-border bg-muted px-6 py-3 font-medium hover:bg-accent transition-colors"
+          >
+            Control Table
+          </button>
+        </div>
+      </div>
+    </div>
+  )
+}

+ 13 - 6
modules/wifi/router.py

@@ -110,7 +110,7 @@ CAPTIVE_PORTAL_HTML = """<!DOCTYPE html>
 <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1">
-    <title>Dune Weaver - WiFi Setup</title>
+    <title>Dune Weaver</title>
     <style>
         * { margin: 0; padding: 0; box-sizing: border-box; }
         body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
@@ -121,16 +121,23 @@ CAPTIVE_PORTAL_HTML = """<!DOCTYPE html>
                 box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
         h1 { font-size: 1.5rem; margin-bottom: 0.5rem; font-weight: 600; }
         p { color: #64748b; margin-bottom: 1.5rem; font-size: 0.9rem; }
-        a { display: inline-block; background: #2563eb; color: white; padding: 0.75rem 2rem;
-            border-radius: 8px; text-decoration: none; font-weight: 500; }
-        a:hover { background: #1d4ed8; }
+        .buttons { display: flex; flex-direction: column; gap: 0.75rem; }
+        a { display: block; padding: 0.75rem 2rem; border-radius: 8px;
+            text-decoration: none; font-weight: 500; text-align: center; }
+        .btn-primary { background: #2563eb; color: white; }
+        .btn-primary:hover { background: #1d4ed8; }
+        .btn-secondary { background: #f1f5f9; color: #0f172a; border: 1px solid #e2e8f0; }
+        .btn-secondary:hover { background: #e2e8f0; }
     </style>
 </head>
 <body>
     <div class="card">
         <h1>Welcome to Dune Weaver</h1>
-        <p>Connect to your home WiFi to get started.</p>
-        <a href="/wifi-setup">Set Up WiFi</a>
+        <p>What would you like to do?</p>
+        <div class="buttons">
+            <a href="/wifi-setup" class="btn-primary">Connect to WiFi</a>
+            <a href="/" class="btn-secondary">Control Table</a>
+        </div>
     </div>
 </body>
 </html>"""

ファイルの差分が大きいため隠しています
+ 0 - 0
static/dist/assets/index-Dphkqeue.js


+ 1 - 1
static/dist/index.html

@@ -58,7 +58,7 @@
           .catch(function() {});
       })();
     </script>
-    <script type="module" crossorigin src="/assets/index-bDuzJAeR.js"></script>
+    <script type="module" crossorigin src="/assets/index-Dphkqeue.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-iGL2FzQP.css">
   <script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
   <body>

ファイルの差分が大きいため隠しています
+ 0 - 0
static/dist/sw.js


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません