introduced uiText.ts
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useRef, useState, type SyntheticEvent } from 'react'
|
import { useEffect, useRef, useState, type SyntheticEvent } from 'react'
|
||||||
import { login } from '../../api/connections'
|
import { login } from '../../api/connections'
|
||||||
import type { LoginRequest, ServiceType } from '../../types/connection'
|
import type { LoginRequest, ServiceType } from '../../types/connection'
|
||||||
|
import { uiText } from '../../text/uiText'
|
||||||
import '../ui/Modal.scss'
|
import '../ui/Modal.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -44,7 +45,7 @@ export function ConnectModal({ serviceType, label, onClose, onSuccess }: Readonl
|
|||||||
await login(req)
|
await login(req)
|
||||||
onSuccess()
|
onSuccess()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Login failed')
|
setError(err instanceof Error ? err.message : uiText.connections.connectModal.loginFailed)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -53,31 +54,31 @@ export function ConnectModal({ serviceType, label, onClose, onSuccess }: Readonl
|
|||||||
return (
|
return (
|
||||||
<dialog className="modal" ref={dialogRef}>
|
<dialog className="modal" ref={dialogRef}>
|
||||||
<div className="modal__header">
|
<div className="modal__header">
|
||||||
<h2 className="modal__title" id="modal-title">Connect to {label}</h2>
|
<h2 className="modal__title" id="modal-title">{uiText.connections.connectModal.title(label)}</h2>
|
||||||
<button className="modal__close" onClick={onClose} aria-label="Close">×</button>
|
<button className="modal__close" onClick={onClose} aria-label={uiText.modal.closeAriaLabel}>{uiText.modal.closeSymbol}</button>
|
||||||
</div>
|
</div>
|
||||||
<form className="modal__form" onSubmit={handleSubmit}>
|
<form className="modal__form" onSubmit={handleSubmit}>
|
||||||
<div className="modal__field">
|
<div className="modal__field">
|
||||||
<label className="modal__label" htmlFor="appUrl">App URL</label>
|
<label className="modal__label" htmlFor="appUrl">{uiText.connections.connectModal.appUrlLabel}</label>
|
||||||
<input id="appUrl" ref={firstInputRef} className="modal__input" type="url"
|
<input id="appUrl" ref={firstInputRef} className="modal__input" type="url"
|
||||||
placeholder="https://homebox.example.com"
|
placeholder={uiText.connections.connectModal.appUrlPlaceholder}
|
||||||
value={appUrl} onChange={e => setAppUrl(e.target.value)} required />
|
value={appUrl} onChange={e => setAppUrl(e.target.value)} required />
|
||||||
</div>
|
</div>
|
||||||
<div className="modal__field">
|
<div className="modal__field">
|
||||||
<label className="modal__label" htmlFor="username">Username</label>
|
<label className="modal__label" htmlFor="username">{uiText.connections.connectModal.usernameLabel}</label>
|
||||||
<input id="username" className="modal__input" type="text"
|
<input id="username" className="modal__input" type="text"
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
value={username} onChange={e => setUsername(e.target.value)} required />
|
value={username} onChange={e => setUsername(e.target.value)} required />
|
||||||
</div>
|
</div>
|
||||||
<div className="modal__field">
|
<div className="modal__field">
|
||||||
<label className="modal__label" htmlFor="password">Password</label>
|
<label className="modal__label" htmlFor="password">{uiText.connections.connectModal.passwordLabel}</label>
|
||||||
<input id="password" className="modal__input" type="password"
|
<input id="password" className="modal__input" type="password"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
value={password} onChange={e => setPassword(e.target.value)} required />
|
value={password} onChange={e => setPassword(e.target.value)} required />
|
||||||
</div>
|
</div>
|
||||||
{error && <p className="modal__error">{error}</p>}
|
{error && <p className="modal__error">{error}</p>}
|
||||||
<button className="modal__submit" type="submit" disabled={loading}>
|
<button className="modal__submit" type="submit" disabled={loading}>
|
||||||
{loading ? 'Connecting…' : 'Connect'}
|
{loading ? uiText.connections.connectModal.connectingButton : uiText.connections.connectModal.connectButton}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ServiceCard } from "./ServiceCard"
|
|||||||
import { ConnectModal } from "./ConnectModal"
|
import { ConnectModal } from "./ConnectModal"
|
||||||
import "./Dashboard.scss"
|
import "./Dashboard.scss"
|
||||||
import { SearchModal } from "../search/SearchModal"
|
import { SearchModal } from "../search/SearchModal"
|
||||||
|
import { uiText } from "../../text/uiText"
|
||||||
|
|
||||||
const SERVICES = [
|
const SERVICES = [
|
||||||
{ serviceType: ServiceType.HOMEBOX, label: 'Homebox', icon: '📦' },
|
{ serviceType: ServiceType.HOMEBOX, label: 'Homebox', icon: '📦' },
|
||||||
@@ -32,10 +33,10 @@ export function Dashboard() {
|
|||||||
return (
|
return (
|
||||||
<div className="dashboard">
|
<div className="dashboard">
|
||||||
<div className="dashboard__header">
|
<div className="dashboard__header">
|
||||||
<h1 className="dashboard__title">Vaessl Dashboard</h1>
|
<h1 className="dashboard__title">{uiText.dashboard.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="dashboard__section-label">Services</p>
|
<p className="dashboard__section-label">{uiText.dashboard.servicesLabel}</p>
|
||||||
<div className="dashboard__cards">
|
<div className="dashboard__cards">
|
||||||
{SERVICES.map(({ serviceType, label, icon }) => (
|
{SERVICES.map(({ serviceType, label, icon }) => (
|
||||||
<ServiceCard
|
<ServiceCard
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { ConnectionStatus, ServiceType } from '../../types/connection'
|
import type { ConnectionStatus, ServiceType } from '../../types/connection'
|
||||||
import { ActionButton } from '../ui/ActionButton'
|
import { ActionButton } from '../ui/ActionButton'
|
||||||
|
import { uiText } from '../../text/uiText'
|
||||||
import './ServiceCard.scss'
|
import './ServiceCard.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -29,20 +30,20 @@ export function ServiceCard({ serviceType: _serviceType, label, icon, status, on
|
|||||||
<p className="service-card__name">{label}</p>
|
<p className="service-card__name">{label}</p>
|
||||||
<p className="service-card__meta">
|
<p className="service-card__meta">
|
||||||
<span className={`service-card__badge service-card__badge--${connected ? 'connected' : 'disconnected'}`}>
|
<span className={`service-card__badge service-card__badge--${connected ? 'connected' : 'disconnected'}`}>
|
||||||
{connected ? 'Connected' : 'Not connected'}
|
{connected ? uiText.connections.serviceCard.connected : uiText.connections.serviceCard.notConnected}
|
||||||
</span>
|
</span>
|
||||||
{connected && status?.username && <span>{status.username}</span>}
|
{connected && status?.username && <span>{status.username}</span>}
|
||||||
{connected && status?.expiresAt && (
|
{connected && status?.expiresAt && (
|
||||||
<span>· expires {formatExpiry(status.expiresAt)}</span>
|
<span>{uiText.connections.serviceCard.expiresPrefix(formatExpiry(status.expiresAt) ?? '')}</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="service-card__actions">
|
<div className="service-card__actions">
|
||||||
<ActionButton variant={connected ? 'disconnect' : 'connect'} onClick={connected ? onDisconnect : onConnect}>
|
<ActionButton variant={connected ? 'disconnect' : 'connect'} onClick={connected ? onDisconnect : onConnect}>
|
||||||
{connected ? 'Disconnect' : 'Connect'}
|
{connected ? uiText.connections.serviceCard.disconnectButton : uiText.connections.serviceCard.connectButton}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
{connected && (<ActionButton variant='search' onClick={onSearch}>Search</ActionButton>)}
|
{connected && (<ActionButton variant='search' onClick={onSearch}>{uiText.connections.serviceCard.searchButton}</ActionButton>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import '../ui/Modal.scss'
|
|||||||
import { type PagedSearchResponse, type ServiceItem, type SearchRequest, type SyncRequest } from '../../types/search'
|
import { type PagedSearchResponse, type ServiceItem, type SearchRequest, type SyncRequest } from '../../types/search'
|
||||||
import { syncVectorData, search } from '../../api/searches'
|
import { syncVectorData, search } from '../../api/searches'
|
||||||
import type { ServiceType } from '../../types/connection'
|
import type { ServiceType } from '../../types/connection'
|
||||||
|
import { uiText } from '../../text/uiText'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
serviceType: ServiceType
|
serviceType: ServiceType
|
||||||
@@ -48,7 +49,7 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R
|
|||||||
const res = await search(req)
|
const res = await search(req)
|
||||||
setResults(res)
|
setResults(res)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setSearchError(err instanceof Error ? err.message : 'Search failed')
|
setSearchError(err instanceof Error ? err.message : uiText.search.searchFailed)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -61,7 +62,7 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R
|
|||||||
const req: SyncRequest = { appUrl, serviceType, username}
|
const req: SyncRequest = { appUrl, serviceType, username}
|
||||||
await syncVectorData(req)
|
await syncVectorData(req)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setSyncError(err instanceof Error ? err.message: 'Refreshing data failed')
|
setSyncError(err instanceof Error ? err.message: uiText.search.syncFailed)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -70,8 +71,8 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R
|
|||||||
return (
|
return (
|
||||||
<dialog className='modal' ref={dialogRef}>
|
<dialog className='modal' ref={dialogRef}>
|
||||||
<div className='modal__header'>
|
<div className='modal__header'>
|
||||||
<h2 className='modal__title' id='modal-title'>Search in {label}</h2>
|
<h2 className='modal__title' id='modal-title'>{uiText.search.title(label)}</h2>
|
||||||
<button className='modal__close' onClick={onClose} aria-label='Close'>×</button>
|
<button className='modal__close' onClick={onClose} aria-label={uiText.modal.closeAriaLabel}>{uiText.modal.closeSymbol}</button>
|
||||||
</div>
|
</div>
|
||||||
<form className='modal__form' onSubmit={handleSubmit}>
|
<form className='modal__form' onSubmit={handleSubmit}>
|
||||||
<div className='modal__field'>
|
<div className='modal__field'>
|
||||||
@@ -81,8 +82,8 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R
|
|||||||
{searchError && <p className='modal__error'>{searchError}</p>}
|
{searchError && <p className='modal__error'>{searchError}</p>}
|
||||||
{syncError && <p className='modal__error'>{syncError}</p>}
|
{syncError && <p className='modal__error'>{syncError}</p>}
|
||||||
<div className='modal__actions'>
|
<div className='modal__actions'>
|
||||||
<button className='modal__other' title={`Syncs your ${serviceType} database for vectorization`} type='button' disabled={loading} onClick={handleSync}>Refresh Data</button>
|
<button className='modal__other' title={uiText.search.syncTooltip(label)} type='button' disabled={loading} onClick={handleSync}>{uiText.search.refreshDataButton}</button>
|
||||||
<button className='modal__submit' type='submit' disabled={loading}>Search</button>
|
<button className='modal__submit' type='submit' disabled={loading}>{uiText.search.searchButton}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{results && (
|
{results && (
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
export const uiText = {
|
||||||
|
modal: {
|
||||||
|
closeAriaLabel: "Close",
|
||||||
|
closeSymbol: "×",
|
||||||
|
},
|
||||||
|
dashboard: {
|
||||||
|
title: "Vaessl Dashboard",
|
||||||
|
servicesLabel: "Services",
|
||||||
|
},
|
||||||
|
connections: {
|
||||||
|
connectModal: {
|
||||||
|
title: (label: string) => `Connect to ${label}`,
|
||||||
|
appUrlLabel: "App URL",
|
||||||
|
appUrlPlaceholder: "https://homebox.example.com",
|
||||||
|
usernameLabel: "Username",
|
||||||
|
passwordLabel: "Password",
|
||||||
|
loginFailed: "Login failed",
|
||||||
|
connectButton: "Connect",
|
||||||
|
connectingButton: "Connecting…",
|
||||||
|
},
|
||||||
|
serviceCard: {
|
||||||
|
connected: "Connected",
|
||||||
|
notConnected: "Not connected",
|
||||||
|
expiresPrefix: (date: string) => `· expires ${date}`,
|
||||||
|
disconnectButton: "Disconnect",
|
||||||
|
connectButton: "Connect",
|
||||||
|
searchButton: "Search",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
title: (label: string) => `Search in ${label}`,
|
||||||
|
searchFailed: "Search failed",
|
||||||
|
syncFailed: "Refreshing data failed",
|
||||||
|
syncTooltip: (label: string) =>
|
||||||
|
`Syncs your ${label} database for vectorization`,
|
||||||
|
refreshDataButton: "Refresh Data",
|
||||||
|
searchButton: "Search",
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
Reference in New Issue
Block a user