MVP frontend+backend
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Dev-only scraper test harness — /dev/fetch
|
||||
*
|
||||
* ⚠️ This page is mounted ONLY in DEV mode (import.meta.env.DEV).
|
||||
* It is never included in production builds.
|
||||
*
|
||||
* Usage:
|
||||
* 1. Run `npm run login` in the frontend directory.
|
||||
* 2. Log in through the normal app flow first (the scraper endpoints require auth).
|
||||
* 3. Click "Refresh" — the backend resolves your IDs automatically.
|
||||
* 4. Scan the QR with BankID and watch status transitions until data appears.
|
||||
*
|
||||
* This runs the REAL FK scraper against the REAL Försäkringskassan site with YOUR BankID.
|
||||
* Only run it intentionally. SCRAPER_ENABLED=true is set by npm run login:backend.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import * as api from '@/services/api'
|
||||
import { useScraperFetch } from '@/hooks'
|
||||
import { isUnauthorized } from '@/services/errors'
|
||||
|
||||
export function DevFetch() {
|
||||
const [context, setContext] = useState<api.UserContext | null>(null)
|
||||
const [contextError, setContextError] = useState<string | null>(null)
|
||||
const [childName, setChildName] = useState('')
|
||||
const [childBirth, setChildBirth] = useState('')
|
||||
const [creatingChild, setCreatingChild] = useState(false)
|
||||
|
||||
const scraper = useScraperFetch()
|
||||
|
||||
const loadContext = async () => {
|
||||
try {
|
||||
const ctx = await api.getUserContext()
|
||||
setContext(ctx)
|
||||
setContextError(null)
|
||||
} catch (err) {
|
||||
if (isUnauthorized(err)) {
|
||||
setContextError('Not authenticated')
|
||||
} else {
|
||||
setContextError(err instanceof Error ? err.message : 'Failed to load context')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadContext()
|
||||
}, [])
|
||||
|
||||
const handleRefresh = async () => {
|
||||
await scraper.start()
|
||||
}
|
||||
|
||||
const handleCreateChild = async () => {
|
||||
if (!context || !childName || !childBirth) return
|
||||
setCreatingChild(true)
|
||||
try {
|
||||
await api.createChild({
|
||||
workspace_id: context.workspace_id,
|
||||
name: childName,
|
||||
birth_date: childBirth,
|
||||
})
|
||||
await loadContext()
|
||||
setChildName('')
|
||||
setChildBirth('')
|
||||
} catch (err) {
|
||||
setContextError(err instanceof Error ? err.message : 'Failed to create child')
|
||||
} finally {
|
||||
setCreatingChild(false)
|
||||
}
|
||||
}
|
||||
|
||||
const hasChildren = context && context.children.length > 0
|
||||
|
||||
return (
|
||||
<div style={{ fontFamily: 'monospace', maxWidth: 700, margin: '2rem auto', padding: '1rem' }}>
|
||||
<h1 style={{ fontSize: '1.2rem', marginBottom: 4 }}>🔧 FK Scraper Test Harness</h1>
|
||||
<p style={{ color: '#888', fontSize: '0.85rem', marginBottom: '1.5rem' }}>
|
||||
Dev-only. Runs the real FK scraper against Försäkringskassan with your BankID.
|
||||
<br />
|
||||
Log in through the normal app first — these endpoints require an app session.
|
||||
</p>
|
||||
|
||||
{contextError && (
|
||||
<div style={{ background: '#fee', border: '1px solid #f88', padding: '0.75rem', marginBottom: '1rem' }}>
|
||||
<strong>Setup-fel:</strong> {contextError}
|
||||
{scraper.isUnauthorized && (
|
||||
<div style={{ marginTop: '0.5rem' }}>
|
||||
<a href="/auth" style={{ color: '#0066cc', textDecoration: 'underline' }}>
|
||||
Logga in först
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{context && (
|
||||
<div style={{ background: '#f6f8fa', border: '1px solid #ddd', padding: '0.75rem', marginBottom: '1rem', fontSize: '0.8rem' }}>
|
||||
<div style={{ marginBottom: 4 }}>
|
||||
<strong>Workspace:</strong> {context.workspace_id}
|
||||
</div>
|
||||
<div style={{ marginBottom: 4 }}>
|
||||
<strong>PersonalData:</strong> {context.personal_data_uuid}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Barn:</strong>{' '}
|
||||
{context.children.length === 0
|
||||
? <span style={{ color: '#c00' }}>Inga barn registrerade</span>
|
||||
: context.children.map(c => `${c.name} (${c.birth_date})`).join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{context && !hasChildren && (
|
||||
<div style={{ background: '#fffbe6', border: '1px solid #ffe58f', padding: '0.75rem', marginBottom: '1rem' }}>
|
||||
<div style={{ marginBottom: '0.5rem', fontWeight: 'bold', fontSize: '0.9rem' }}>Registrera ett barn för att kunna köra scrapern</div>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<input
|
||||
placeholder="Barnets namn"
|
||||
value={childName}
|
||||
onChange={e => setChildName(e.target.value)}
|
||||
style={{ padding: '0.4rem', fontFamily: 'monospace', fontSize: '0.85rem' }}
|
||||
/>
|
||||
<input
|
||||
type="date"
|
||||
placeholder="Födelsedatum"
|
||||
value={childBirth}
|
||||
onChange={e => setChildBirth(e.target.value)}
|
||||
style={{ padding: '0.4rem', fontFamily: 'monospace', fontSize: '0.85rem' }}
|
||||
/>
|
||||
<button
|
||||
onClick={handleCreateChild}
|
||||
disabled={creatingChild || !childName || !childBirth}
|
||||
style={{ padding: '0.4rem 1rem', cursor: 'pointer' }}
|
||||
>
|
||||
{creatingChild ? 'Skapar…' : 'Skapa barn'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: '1.5rem' }}>
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
disabled={!hasChildren}
|
||||
style={{ padding: '0.5rem 1.5rem', cursor: hasChildren ? 'pointer' : 'not-allowed', fontWeight: 'bold', opacity: hasChildren ? 1 : 0.5 }}
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{scraper.resolvedIds && (
|
||||
<div style={{ color: '#aaa', fontSize: '0.75rem', marginBottom: '1rem', lineHeight: 1.6 }}>
|
||||
<div>workspace_id: {scraper.resolvedIds.workspace_id}</div>
|
||||
<div>personal_data_uuid: {scraper.resolvedIds.personal_data_uuid}</div>
|
||||
<div>child_id: {scraper.resolvedIds.child_id}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{scraper.error && (
|
||||
<div style={{ background: '#fee', border: '1px solid #f88', padding: '0.75rem', marginBottom: '1rem' }}>
|
||||
<strong>Fel:</strong> {scraper.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{scraper.pollData && (
|
||||
<div>
|
||||
<div style={{ marginBottom: '0.75rem' }}>
|
||||
<strong>Status:</strong>{' '}
|
||||
<span style={{ color: scraper.pollData.status === 'done' ? 'green' : scraper.pollData.status === 'error' ? 'red' : 'inherit' }}>
|
||||
{scraper.pollData.status}
|
||||
</span>
|
||||
{scraper.pollData.message && (
|
||||
<span style={{ color: '#888', marginLeft: '0.5rem' }}>— {scraper.pollData.message}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{scraper.pollData.status === 'awaiting_scan' && scraper.pollData.qr_data && (
|
||||
<div style={{ marginBottom: '1rem' }}>
|
||||
<p style={{ margin: '0 0 0.5rem', fontSize: '0.9rem' }}>
|
||||
Öppna BankID-appen och skanna QR-koden (uppdateras automatiskt):
|
||||
</p>
|
||||
<img
|
||||
src={`data:image/png;base64,${scraper.pollData.qr_data}`}
|
||||
alt="BankID QR-kod"
|
||||
style={{ width: 220, height: 220, imageRendering: 'pixelated', border: '1px solid #ccc' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(scraper.pollData.status === 'authenticating' || scraper.pollData.status === 'fetching') && (
|
||||
<p style={{ color: '#555' }}>⏳ {scraper.pollData.status === 'authenticating' ? 'Autentiserar…' : 'Hämtar data från FK…'}</p>
|
||||
)}
|
||||
|
||||
{scraper.pollData.status === 'done' && scraper.pollData.result && (
|
||||
<div>
|
||||
<p style={{ color: 'green', marginBottom: '0.5rem' }}>✅ Klar! Hämtad data:</p>
|
||||
<pre style={{ background: '#f6f8fa', padding: '1rem', overflow: 'auto', fontSize: '0.8rem', border: '1px solid #ddd' }}>
|
||||
{JSON.stringify(scraper.pollData.result, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{scraper.pollData.status === 'cancelled' && (
|
||||
<p style={{ color: '#888' }}>Sessionen avbröts.</p>
|
||||
)}
|
||||
|
||||
{scraper.isRunning && (
|
||||
<button
|
||||
onClick={scraper.cancel}
|
||||
style={{ marginTop: '0.5rem', padding: '0.4rem 0.8rem', cursor: 'pointer', background: '#fee', border: '1px solid #faa' }}
|
||||
>
|
||||
Avbryt
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user