Show badge only in local test environment
This commit is contained in:
@@ -49,6 +49,9 @@ LINE 推播目標支援分成兩組:
|
||||
- `LINE_TARGET_ID_PROD`: 正式環境用對話
|
||||
- `LINE_TARGET_MODE`: `local` 或 `prod`
|
||||
|
||||
當系統偵測目前為 `local` 模式時,頁面右上角會顯示「測試環境」標籤。
|
||||
正式環境 `prod` 不會顯示這個標籤。
|
||||
|
||||
## 資料庫欄位
|
||||
|
||||
- `time`: 目標日期,格式為 `YYYYMMDD`
|
||||
|
||||
22
src/App.css
22
src/App.css
@@ -29,10 +29,27 @@
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
position: relative;
|
||||
padding: 36px;
|
||||
background: linear-gradient(145deg, rgba(255, 247, 234, 0.92), rgba(232, 244, 235, 0.84));
|
||||
}
|
||||
|
||||
.env-badge {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 34px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(186, 61, 47, 0.12);
|
||||
color: #8d2d22;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 12px;
|
||||
color: #1d6c46;
|
||||
@@ -371,6 +388,11 @@
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.env-badge {
|
||||
position: static;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.team-line {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
27
src/App.tsx
27
src/App.tsx
@@ -26,6 +26,10 @@ type LoadMatchResultsResponse = {
|
||||
battlecombination: string | null
|
||||
}
|
||||
|
||||
type HealthResponse = {
|
||||
lineTargetMode?: string
|
||||
}
|
||||
|
||||
const STORAGE_KEYS = {
|
||||
areaA: 'badminton-match-hub::area-a',
|
||||
areaB: 'badminton-match-hub::area-b',
|
||||
@@ -49,6 +53,7 @@ function App() {
|
||||
const [error, setError] = useState('')
|
||||
const [actionState, setActionState] = useState<ActionState>('idle')
|
||||
const [actionMessage, setActionMessage] = useState('')
|
||||
const [showTestBadge, setShowTestBadge] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
window.localStorage.setItem(STORAGE_KEYS.areaA, areaAInput)
|
||||
@@ -58,6 +63,13 @@ function App() {
|
||||
window.localStorage.setItem(STORAGE_KEYS.areaB, areaBInput)
|
||||
}, [areaBInput])
|
||||
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
const isLocalMode = await loadEnvironmentMode()
|
||||
setShowTestBadge(isLocalMode)
|
||||
})()
|
||||
}, [])
|
||||
|
||||
const parsedAreaA = parseRoster(areaAInput)
|
||||
const parsedAreaB = parseRoster(areaBInput)
|
||||
const teamCount = Math.max(parsedAreaA.length, parsedAreaB.length)
|
||||
@@ -196,6 +208,7 @@ function App() {
|
||||
<main className="app-shell">
|
||||
<section className="hero-card">
|
||||
<div className="hero-copy">
|
||||
{showTestBadge ? <div className="env-badge">測試環境</div> : null}
|
||||
<p className="eyebrow">羽毛球隊伍配對</p>
|
||||
<h1>羽毛球分組配對器</h1>
|
||||
<p className="hero-text">
|
||||
@@ -477,6 +490,20 @@ async function pushMatchResultsToLine(time: string, rounds: RoundResult[]) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadEnvironmentMode() {
|
||||
try {
|
||||
const response = await fetch('/api/health')
|
||||
if (!response.ok) {
|
||||
return false
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as HealthResponse
|
||||
return payload.lineTargetMode === 'local'
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function convertDbRecordToAppState(record: LoadMatchResultsResponse) {
|
||||
const personnel = JSON.parse(record.personnel) as [number, string][]
|
||||
const battlecombination = JSON.parse(record.battlecombination ?? '{}') as Record<
|
||||
|
||||
Reference in New Issue
Block a user