|
1 | | -import { Settings } from "../../types"; |
| 1 | +import { Denomination, Settings } from "../../types"; |
2 | 2 | import React, { useState } from "react"; |
3 | 3 | import { useTranslation } from "react-i18next"; |
4 | 4 | import { noVolume, halfVolume, fullVolume } from "../../hooks/useLocalSettings"; |
5 | 5 | import { useLocalSettingsCtx } from "../../context/LocalSettingsContext"; |
| 6 | +import MoneyDisplay from "../MoneyDisplay"; |
6 | 7 |
|
7 | 8 | export function SeatingCard({ |
8 | 9 | settings, |
@@ -155,22 +156,136 @@ export function SoundsCard({ |
155 | 156 | } |
156 | 157 |
|
157 | 158 |
|
| 159 | +const CURRENCY_OPTIONS = [ |
| 160 | + { label: "$ (USD)", value: "$" }, |
| 161 | + { label: "\u00a3 (GBP)", value: "\u00a3" }, |
| 162 | + { label: "\u20ac (EUR)", value: "\u20ac" }, |
| 163 | + { label: "\u00a5 (JPY)", value: "\u00a5" }, |
| 164 | + { label: "\u20bf (BTC)", value: "\u20bf" }, |
| 165 | + { label: "None", value: "" } |
| 166 | +]; |
| 167 | + |
| 168 | +export function CurrencyCard({ |
| 169 | + settings, |
| 170 | + onSave |
| 171 | +}: { |
| 172 | + settings: Settings | null; |
| 173 | + onSave: (symbol: string, denomination: Denomination) => Promise<void>; |
| 174 | +}) { |
| 175 | + const { t } = useTranslation(); |
| 176 | + const curSymbol = settings?.currency?.symbol ?? "$"; |
| 177 | + const curDenom: Denomination = settings?.currency?.denomination ?? "cents"; |
| 178 | + |
| 179 | + const [draftSymbol, setDraftSymbol] = useState<string>(curSymbol); |
| 180 | + const [draftDenom, setDraftDenom] = useState<Denomination>(curDenom); |
| 181 | + |
| 182 | + React.useEffect(() => { |
| 183 | + setDraftSymbol(curSymbol); |
| 184 | + setDraftDenom(curDenom); |
| 185 | + }, [curSymbol, curDenom]); |
| 186 | + |
| 187 | + const dirty = draftSymbol !== curSymbol || draftDenom !== curDenom; |
| 188 | + |
| 189 | + // Example blind for preview |
| 190 | + const previewCents = 250; |
| 191 | + |
| 192 | + return ( |
| 193 | + <div className="card"> |
| 194 | + <h3>{t("currency.sectionTitle")}</h3> |
| 195 | + <div className="muted">{t("currency.helpText")}</div> |
| 196 | + <hr /> |
| 197 | + |
| 198 | + {settings ? ( |
| 199 | + <div style={{ display: "grid", gap: 12 }}> |
| 200 | + <div className="grid2"> |
| 201 | + <div> |
| 202 | + <label>{t("currency.symbol")}</label> |
| 203 | + <select |
| 204 | + className="input" |
| 205 | + value={draftSymbol} |
| 206 | + onChange={(e) => setDraftSymbol(e.target.value)} |
| 207 | + > |
| 208 | + {CURRENCY_OPTIONS.map((opt) => ( |
| 209 | + <option key={opt.value} value={opt.value}> |
| 210 | + {opt.label} |
| 211 | + </option> |
| 212 | + ))} |
| 213 | + </select> |
| 214 | + </div> |
| 215 | + |
| 216 | + <div> |
| 217 | + <label>{t("currency.denomination")}</label> |
| 218 | + <select |
| 219 | + className="input" |
| 220 | + value={draftDenom} |
| 221 | + onChange={(e) => setDraftDenom(e.target.value as Denomination)} |
| 222 | + > |
| 223 | + <option value="cents">{t("currency.denominationCents")}</option> |
| 224 | + <option value="whole">{t("currency.denominationWhole")}</option> |
| 225 | + </select> |
| 226 | + </div> |
| 227 | + </div> |
| 228 | + |
| 229 | + <div> |
| 230 | + <div className="muted" style={{ marginBottom: 4 }}>{t("currency.preview")}</div> |
| 231 | + <div style={{ padding: "8px 12px", border: "1px solid rgba(255,255,255,0.18)", borderRadius: 8, display: "inline-block" }}> |
| 232 | + <MoneyDisplay |
| 233 | + cents={previewCents} |
| 234 | + size={24} |
| 235 | + currencySymbol={draftSymbol} |
| 236 | + denomination={draftDenom} |
| 237 | + /> |
| 238 | + </div> |
| 239 | + </div> |
| 240 | + |
| 241 | + <div style={{ display: "flex", gap: 8 }}> |
| 242 | + <button |
| 243 | + className="btn primary" |
| 244 | + disabled={!dirty} |
| 245 | + onClick={async () => { |
| 246 | + await onSave(draftSymbol, draftDenom); |
| 247 | + }} |
| 248 | + > |
| 249 | + {t("currency.save")} |
| 250 | + </button> |
| 251 | + <button |
| 252 | + className="btn" |
| 253 | + disabled={!dirty} |
| 254 | + onClick={() => { |
| 255 | + setDraftSymbol(curSymbol); |
| 256 | + setDraftDenom(curDenom); |
| 257 | + }} |
| 258 | + > |
| 259 | + {t("levels.actions.discard")} |
| 260 | + </button> |
| 261 | + </div> |
| 262 | + </div> |
| 263 | + ) : ( |
| 264 | + <div className="muted">{t("common.loading")}</div> |
| 265 | + )} |
| 266 | + </div> |
| 267 | + ); |
| 268 | +} |
| 269 | + |
158 | 270 | export function SettingsTab({ |
159 | 271 | settings, |
160 | 272 | sounds, |
161 | 273 | onSetSound, |
162 | 274 | onPreviewSound, |
163 | | - onSaveSeating |
| 275 | + onSaveSeating, |
| 276 | + onSaveCurrency |
164 | 277 | }: { |
165 | 278 | settings: Settings | null; |
166 | 279 | sounds: string[]; |
167 | 280 | onSetSound: (cue: "transition" | "half" | "thirty" | "five" | "end", file: string | null) => Promise<void>; |
168 | 281 | onPreviewSound: (file: string) => void; |
169 | 282 | onSaveSeating: (minPlayersPerTable: number) => Promise<void>; |
| 283 | + onSaveCurrency: (symbol: string, denomination: Denomination) => Promise<void>; |
170 | 284 | }) { |
171 | 285 | return ( |
172 | 286 | <div className="row" style={{ marginTop: 12, display: "grid", gap: 12 }}> |
173 | 287 | <SoundsCard settings={settings} sounds={sounds} onSetSound={onSetSound} onPreview={onPreviewSound} /> |
| 288 | + <CurrencyCard settings={settings} onSave={onSaveCurrency} /> |
174 | 289 | <SeatingCard settings={settings} onSave={onSaveSeating} /> |
175 | 290 | </div> |
176 | 291 | ); |
|
0 commit comments