mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 19:14:03 +00:00
feat(miniapp): add household general settings
This commit is contained in:
@@ -90,6 +90,7 @@ type SessionState =
|
||||
mode: 'live' | 'demo'
|
||||
member: {
|
||||
id: string
|
||||
householdName: string
|
||||
displayName: string
|
||||
status: 'active' | 'away' | 'left'
|
||||
isAdmin: boolean
|
||||
@@ -348,6 +349,7 @@ function App() {
|
||||
const [testingRolePreview, setTestingRolePreview] = createSignal<TestingRolePreview | null>(null)
|
||||
const [addingPayment, setAddingPayment] = createSignal(false)
|
||||
const [billingForm, setBillingForm] = createSignal({
|
||||
householdName: '',
|
||||
settlementCurrency: 'GEL' as 'USD' | 'GEL',
|
||||
paymentBalanceAdjustmentPolicy: 'utilities' as 'utilities' | 'rent' | 'separate',
|
||||
rentAmountMajor: '',
|
||||
@@ -762,6 +764,7 @@ function App() {
|
||||
''
|
||||
}))
|
||||
setBillingForm({
|
||||
householdName: payload.householdName,
|
||||
settlementCurrency: payload.settings.settlementCurrency,
|
||||
paymentBalanceAdjustmentPolicy: payload.settings.paymentBalanceAdjustmentPolicy,
|
||||
rentAmountMajor: payload.settings.rentAmountMinor
|
||||
@@ -883,6 +886,7 @@ function App() {
|
||||
)
|
||||
)
|
||||
setBillingForm({
|
||||
householdName: demoAdminSettings.householdName,
|
||||
settlementCurrency: demoAdminSettings.settings.settlementCurrency,
|
||||
paymentBalanceAdjustmentPolicy: demoAdminSettings.settings.paymentBalanceAdjustmentPolicy,
|
||||
rentAmountMajor: demoAdminSettings.settings.rentAmountMinor
|
||||
@@ -1200,7 +1204,7 @@ function App() {
|
||||
setSavingBillingSettings(true)
|
||||
|
||||
try {
|
||||
const { settings, assistantConfig } = await updateMiniAppBillingSettings(
|
||||
const { householdName, settings, assistantConfig } = await updateMiniAppBillingSettings(
|
||||
initData,
|
||||
billingForm()
|
||||
)
|
||||
@@ -1208,11 +1212,27 @@ function App() {
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
householdName,
|
||||
settings,
|
||||
assistantConfig
|
||||
}
|
||||
: current
|
||||
)
|
||||
setBillingForm((current) => ({
|
||||
...current,
|
||||
householdName
|
||||
}))
|
||||
setSession((current) =>
|
||||
current.status === 'ready'
|
||||
? {
|
||||
...current,
|
||||
member: {
|
||||
...current.member,
|
||||
householdName
|
||||
}
|
||||
}
|
||||
: current
|
||||
)
|
||||
setCycleForm((current) => ({
|
||||
...current,
|
||||
rentCurrency: settings.rentCurrency,
|
||||
@@ -1992,6 +2012,8 @@ function App() {
|
||||
locale={locale()}
|
||||
readyIsAdmin={effectiveIsAdmin()}
|
||||
householdDefaultLocale={readySession()?.member.householdDefaultLocale ?? 'en'}
|
||||
householdName={readySession()?.member.householdName ?? billingForm().householdName}
|
||||
profileDisplayName={readySession()?.member.displayName ?? displayNameDraft()}
|
||||
dashboard={dashboard()}
|
||||
adminSettings={adminSettings()}
|
||||
cycleState={cycleState()}
|
||||
@@ -2030,6 +2052,7 @@ function App() {
|
||||
resolvedMemberAbsencePolicy(memberId, status)
|
||||
}
|
||||
onChangeHouseholdLocale={handleHouseholdLocaleChange}
|
||||
onOpenProfileEditor={() => setProfileEditorOpen(true)}
|
||||
onOpenCycleModal={() => setCycleRentOpen(true)}
|
||||
onCloseCycleModal={() => setCycleRentOpen(false)}
|
||||
onSaveCycleRent={handleSaveCycleRent}
|
||||
@@ -2061,6 +2084,12 @@ function App() {
|
||||
settlementCurrency: value
|
||||
}))
|
||||
}
|
||||
onBillingHouseholdNameChange={(value) =>
|
||||
setBillingForm((current) => ({
|
||||
...current,
|
||||
householdName: value
|
||||
}))
|
||||
}
|
||||
onBillingAdjustmentPolicyChange={(value) =>
|
||||
setBillingForm((current) => ({
|
||||
...current,
|
||||
@@ -2290,7 +2319,11 @@ function App() {
|
||||
|
||||
<TopBar
|
||||
subtitle={copy().appSubtitle}
|
||||
title={copy().appTitle}
|
||||
title={
|
||||
readySession()?.member.householdName ??
|
||||
onboardingSession()?.householdName ??
|
||||
copy().appTitle
|
||||
}
|
||||
languageLabel={copy().language}
|
||||
locale={locale()}
|
||||
saving={savingMemberLocale()}
|
||||
@@ -2391,15 +2424,6 @@ function App() {
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={readySession()?.mode === 'live'}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
class="app-context-row__action"
|
||||
onClick={() => setProfileEditorOpen(true)}
|
||||
>
|
||||
{copy().manageProfileAction}
|
||||
</Button>
|
||||
</Show>
|
||||
</section>
|
||||
|
||||
<section class="content-stack">{panel()}</section>
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
export const demoMember: NonNullable<MiniAppSession['member']> = {
|
||||
id: 'demo-member',
|
||||
householdId: 'demo-household',
|
||||
householdName: 'Kojori House',
|
||||
displayName: 'Stas',
|
||||
status: 'active',
|
||||
isAdmin: true,
|
||||
@@ -191,6 +192,7 @@ export const demoPendingMembers: readonly MiniAppPendingMember[] = [
|
||||
]
|
||||
|
||||
export const demoAdminSettings: MiniAppAdminSettingsPayload = {
|
||||
householdName: 'Kojori House',
|
||||
settings: {
|
||||
householdId: 'demo-household',
|
||||
settlementCurrency: 'GEL',
|
||||
|
||||
@@ -30,6 +30,10 @@ export const dictionary = {
|
||||
reload: 'Retry',
|
||||
language: 'Language',
|
||||
householdLanguage: 'Household language',
|
||||
generalSettingsBody:
|
||||
'Household identity, default language, and personal profile controls live here.',
|
||||
householdNameLabel: 'Household name',
|
||||
householdNameHint: 'This appears in the mini app, join flow, and bot responses.',
|
||||
savingLanguage: 'Saving…',
|
||||
onLabel: 'On',
|
||||
offLabel: 'Off',
|
||||
@@ -37,6 +41,7 @@ export const dictionary = {
|
||||
balances: 'Balances',
|
||||
ledger: 'Ledger',
|
||||
house: 'House',
|
||||
houseSectionGeneral: 'General',
|
||||
houseSectionBilling: 'Billing',
|
||||
houseSectionUtilities: 'Utilities',
|
||||
houseSectionMembers: 'Members',
|
||||
@@ -292,6 +297,9 @@ export const dictionary = {
|
||||
reload: 'Повторить',
|
||||
language: 'Язык',
|
||||
householdLanguage: 'Язык дома',
|
||||
generalSettingsBody: 'Здесь живут имя дома, язык по умолчанию и доступ к личному профилю.',
|
||||
householdNameLabel: 'Название дома',
|
||||
householdNameHint: 'Показывается в mini app, при вступлении и в ответах бота.',
|
||||
savingLanguage: 'Сохраняем…',
|
||||
onLabel: 'Вкл',
|
||||
offLabel: 'Выкл',
|
||||
@@ -299,6 +307,7 @@ export const dictionary = {
|
||||
balances: 'Баланс',
|
||||
ledger: 'Леджер',
|
||||
house: 'Дом',
|
||||
houseSectionGeneral: 'Общее',
|
||||
houseSectionBilling: 'Биллинг',
|
||||
houseSectionUtilities: 'Коммуналка',
|
||||
houseSectionMembers: 'Участники',
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface MiniAppSession {
|
||||
member?: {
|
||||
id: string
|
||||
householdId: string
|
||||
householdName: string
|
||||
displayName: string
|
||||
status: 'active' | 'away' | 'left'
|
||||
isAdmin: boolean
|
||||
@@ -138,6 +139,7 @@ export interface MiniAppDashboard {
|
||||
}
|
||||
|
||||
export interface MiniAppAdminSettingsPayload {
|
||||
householdName: string
|
||||
settings: MiniAppBillingSettings
|
||||
assistantConfig: MiniAppAssistantConfig
|
||||
topics: readonly MiniAppTopicBinding[]
|
||||
@@ -386,6 +388,7 @@ export async function fetchMiniAppAdminSettings(
|
||||
const payload = (await response.json()) as {
|
||||
ok: boolean
|
||||
authorized?: boolean
|
||||
householdName?: string
|
||||
settings?: MiniAppBillingSettings
|
||||
assistantConfig?: MiniAppAssistantConfig
|
||||
topics?: MiniAppTopicBinding[]
|
||||
@@ -398,6 +401,7 @@ export async function fetchMiniAppAdminSettings(
|
||||
if (
|
||||
!response.ok ||
|
||||
!payload.authorized ||
|
||||
!payload.householdName ||
|
||||
!payload.settings ||
|
||||
!payload.assistantConfig ||
|
||||
!payload.topics ||
|
||||
@@ -409,6 +413,7 @@ export async function fetchMiniAppAdminSettings(
|
||||
}
|
||||
|
||||
return {
|
||||
householdName: payload.householdName,
|
||||
settings: payload.settings,
|
||||
assistantConfig: payload.assistantConfig,
|
||||
topics: payload.topics,
|
||||
@@ -423,6 +428,7 @@ export async function updateMiniAppBillingSettings(
|
||||
input: {
|
||||
settlementCurrency?: 'USD' | 'GEL'
|
||||
paymentBalanceAdjustmentPolicy?: 'utilities' | 'rent' | 'separate'
|
||||
householdName?: string
|
||||
rentAmountMajor?: string
|
||||
rentCurrency: 'USD' | 'GEL'
|
||||
rentDueDay: number
|
||||
@@ -434,6 +440,7 @@ export async function updateMiniAppBillingSettings(
|
||||
assistantTone?: string
|
||||
}
|
||||
): Promise<{
|
||||
householdName: string
|
||||
settings: MiniAppBillingSettings
|
||||
assistantConfig: MiniAppAssistantConfig
|
||||
}> {
|
||||
@@ -451,16 +458,24 @@ export async function updateMiniAppBillingSettings(
|
||||
const payload = (await response.json()) as {
|
||||
ok: boolean
|
||||
authorized?: boolean
|
||||
householdName?: string
|
||||
settings?: MiniAppBillingSettings
|
||||
assistantConfig?: MiniAppAssistantConfig
|
||||
error?: string
|
||||
}
|
||||
|
||||
if (!response.ok || !payload.authorized || !payload.settings || !payload.assistantConfig) {
|
||||
if (
|
||||
!response.ok ||
|
||||
!payload.authorized ||
|
||||
!payload.householdName ||
|
||||
!payload.settings ||
|
||||
!payload.assistantConfig
|
||||
) {
|
||||
throw new Error(payload.error ?? 'Failed to update billing settings')
|
||||
}
|
||||
|
||||
return {
|
||||
householdName: payload.householdName,
|
||||
settings: payload.settings,
|
||||
assistantConfig: payload.assistantConfig
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ type UtilityBillDraft = {
|
||||
}
|
||||
|
||||
type BillingForm = {
|
||||
householdName: string
|
||||
settlementCurrency: 'USD' | 'GEL'
|
||||
paymentBalanceAdjustmentPolicy: 'utilities' | 'rent' | 'separate'
|
||||
rentAmountMajor: string
|
||||
@@ -55,6 +56,8 @@ type Props = {
|
||||
locale: 'en' | 'ru'
|
||||
readyIsAdmin: boolean
|
||||
householdDefaultLocale: 'en' | 'ru'
|
||||
householdName: string
|
||||
profileDisplayName: string
|
||||
dashboard: MiniAppDashboard | null
|
||||
adminSettings: MiniAppAdminSettingsPayload | null
|
||||
cycleState: MiniAppAdminCycleState | null
|
||||
@@ -101,6 +104,7 @@ type Props = {
|
||||
effectiveFromPeriod: string | null
|
||||
}
|
||||
onChangeHouseholdLocale: (locale: 'en' | 'ru') => Promise<void>
|
||||
onOpenProfileEditor: () => void
|
||||
onOpenCycleModal: () => void
|
||||
onCloseCycleModal: () => void
|
||||
onSaveCycleRent: () => Promise<void>
|
||||
@@ -111,6 +115,7 @@ type Props = {
|
||||
onOpenBillingSettingsModal: () => void
|
||||
onCloseBillingSettingsModal: () => void
|
||||
onSaveBillingSettings: () => Promise<void>
|
||||
onBillingHouseholdNameChange: (value: string) => void
|
||||
onBillingSettlementCurrencyChange: (value: 'USD' | 'GEL') => void
|
||||
onBillingAdjustmentPolicyChange: (value: 'utilities' | 'rent' | 'separate') => void
|
||||
onBillingRentAmountChange: (value: string) => void
|
||||
@@ -213,15 +218,83 @@ export function HouseScreen(props: Props) {
|
||||
<strong>{props.copy.residentHouseTitle ?? ''}</strong>
|
||||
</header>
|
||||
<p>{props.copy.residentHouseBody ?? ''}</p>
|
||||
<div class="panel-toolbar">
|
||||
<Button variant="secondary" onClick={props.onOpenProfileEditor}>
|
||||
<PencilIcon />
|
||||
{props.copy.manageProfileAction ?? ''}
|
||||
</Button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class="admin-layout">
|
||||
<HouseSection
|
||||
title={props.copy.houseSectionGeneral ?? ''}
|
||||
body={props.copy.generalSettingsBody}
|
||||
defaultOpen
|
||||
>
|
||||
<section class="admin-section">
|
||||
<div class="admin-grid">
|
||||
<article class="balance-item">
|
||||
<header>
|
||||
<strong>{props.copy.householdNameLabel ?? ''}</strong>
|
||||
<span>{props.householdName}</span>
|
||||
</header>
|
||||
<p>{props.copy.householdNameHint ?? ''}</p>
|
||||
<div class="panel-toolbar">
|
||||
<Button variant="secondary" onClick={props.onOpenBillingSettingsModal}>
|
||||
<SettingsIcon />
|
||||
{props.copy.manageSettingsAction ?? ''}
|
||||
</Button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="balance-item">
|
||||
<header>
|
||||
<strong>{props.copy.householdLanguage ?? ''}</strong>
|
||||
<span>{props.householdDefaultLocale.toUpperCase()}</span>
|
||||
</header>
|
||||
<div class="locale-switch__buttons locale-switch__buttons--inline">
|
||||
<button
|
||||
classList={{ 'is-active': props.householdDefaultLocale === 'en' }}
|
||||
type="button"
|
||||
disabled={props.savingHouseholdLocale}
|
||||
onClick={() => void props.onChangeHouseholdLocale('en')}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
<button
|
||||
classList={{ 'is-active': props.householdDefaultLocale === 'ru' }}
|
||||
type="button"
|
||||
disabled={props.savingHouseholdLocale}
|
||||
onClick={() => void props.onChangeHouseholdLocale('ru')}
|
||||
>
|
||||
RU
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="balance-item">
|
||||
<header>
|
||||
<strong>{props.copy.manageProfileAction ?? ''}</strong>
|
||||
<span>{props.profileDisplayName}</span>
|
||||
</header>
|
||||
<p>{props.copy.profileEditorBody ?? ''}</p>
|
||||
<div class="panel-toolbar">
|
||||
<Button variant="secondary" onClick={props.onOpenProfileEditor}>
|
||||
<PencilIcon />
|
||||
{props.copy.manageProfileAction ?? ''}
|
||||
</Button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</HouseSection>
|
||||
|
||||
<HouseSection
|
||||
title={props.copy.houseSectionBilling ?? ''}
|
||||
body={props.copy.billingSettingsEditorBody}
|
||||
defaultOpen
|
||||
>
|
||||
<section class="admin-section">
|
||||
<div class="admin-grid">
|
||||
@@ -290,31 +363,6 @@ export function HouseScreen(props: Props) {
|
||||
</Button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="balance-item">
|
||||
<header>
|
||||
<strong>{props.copy.householdLanguage ?? ''}</strong>
|
||||
<span>{props.householdDefaultLocale.toUpperCase()}</span>
|
||||
</header>
|
||||
<div class="locale-switch__buttons locale-switch__buttons--inline">
|
||||
<button
|
||||
classList={{ 'is-active': props.householdDefaultLocale === 'en' }}
|
||||
type="button"
|
||||
disabled={props.savingHouseholdLocale}
|
||||
onClick={() => void props.onChangeHouseholdLocale('en')}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
<button
|
||||
classList={{ 'is-active': props.householdDefaultLocale === 'ru' }}
|
||||
type="button"
|
||||
disabled={props.savingHouseholdLocale}
|
||||
onClick={() => void props.onChangeHouseholdLocale('ru')}
|
||||
>
|
||||
RU
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<Modal
|
||||
open={props.cycleRentOpen}
|
||||
@@ -414,6 +462,18 @@ export function HouseScreen(props: Props) {
|
||||
}
|
||||
>
|
||||
<div class="editor-grid">
|
||||
<Field
|
||||
label={props.copy.householdNameLabel ?? ''}
|
||||
hint={props.copy.householdNameHint ?? ''}
|
||||
wide
|
||||
>
|
||||
<input
|
||||
value={props.billingForm.householdName}
|
||||
onInput={(event) =>
|
||||
props.onBillingHouseholdNameChange(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field label={props.copy.settlementCurrency ?? ''}>
|
||||
<select
|
||||
value={props.billingForm.settlementCurrency}
|
||||
|
||||
Reference in New Issue
Block a user