Sync frontend and backend

This commit is contained in:
Jon Staab
2026-05-22 11:03:55 -07:00
parent b4af2f3866
commit 384ddbd439
7 changed files with 16 additions and 16 deletions
+1 -1
View File
@@ -178,7 +178,7 @@ Handlers take `State<Arc<Api>>`, an optional `AuthedPubkey`, then path/query/bod
- Admin or matching tenant - Admin or matching tenant
- Looks up the tenant, then lists invoices from Stripe by `stripe_customer_id` - Looks up the tenant, then lists invoices from Stripe by `stripe_customer_id`
- `data` is a list of `StripeInvoice` objects: `{ id, customer, status, amount_due, currency }` - `data` is a list of `StripeInvoice` objects: `{ id, customer, status, amount_due, currency, period_start, period_end }`
## `get_invoice` — `GET /invoices/:id` ## `get_invoice` — `GET /invoices/:id`
+1 -1
View File
@@ -95,4 +95,4 @@ Verifies the `Stripe-Signature` header against `env.stripe_webhook_secret` and p
- `StripeSubscription { id, status, items: Vec<StripeSubscriptionItem> }` (`items` flattened from Stripe's `{ data: [...] }` list) - `StripeSubscription { id, status, items: Vec<StripeSubscriptionItem> }` (`items` flattened from Stripe's `{ data: [...] }` list)
- `StripeSubscriptionItem { id, price: StripePrice, quantity }` (`quantity` defaults to 1 when absent) - `StripeSubscriptionItem { id, price: StripePrice, quantity }` (`quantity` defaults to 1 when absent)
- `StripePrice { id }` - `StripePrice { id }`
- `StripeInvoice { id, customer, status, amount_due, currency }` (the subset of invoice fields the API surfaces; `Serialize` + `Clone`) - `StripeInvoice { id, customer, status, amount_due, currency, period_start, period_end }` (the subset of invoice fields the API surfaces; `Serialize` + `Clone`)
+2
View File
@@ -59,6 +59,8 @@ pub struct StripeInvoice {
pub status: String, pub status: String,
pub amount_due: i64, pub amount_due: i64,
pub currency: String, pub currency: String,
pub period_start: i64,
pub period_end: i64,
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
+2 -2
View File
@@ -97,7 +97,7 @@ export type UpdateRelayInput = {
export type Tenant = { export type Tenant = {
pubkey: string pubkey: string
nwc_url: string nwc_is_set: boolean
created_at: number created_at: number
stripe_customer_id: string stripe_customer_id: string
stripe_subscription_id: string | null stripe_subscription_id: string | null
@@ -107,10 +107,10 @@ export type Tenant = {
export type Invoice = { export type Invoice = {
id: string id: string
customer: string
status: string status: string
amount_due: number amount_due: number
currency: string currency: string
hosted_invoice_url: string
period_start: number period_start: number
period_end: number period_end: number
} }
+1 -1
View File
@@ -135,7 +135,7 @@ export const reactivateRelayById = (id: string) => reactivateRelay(id)
export async function tenantNeedsPaymentSetup(): Promise<boolean> { export async function tenantNeedsPaymentSetup(): Promise<boolean> {
const tenant = await getTenant(account()!.pubkey) const tenant = await getTenant(account()!.pubkey)
return !tenant.nwc_url && !tenant.stripe_subscription_id return !tenant.nwc_is_set && !tenant.stripe_subscription_id
} }
export async function getLatestOpenInvoice(): Promise<Invoice | null> { export async function getLatestOpenInvoice(): Promise<Invoice | null> {
+8 -10
View File
@@ -1,4 +1,4 @@
import { createEffect, createMemo, createResource, createSignal, For, Show } from "solid-js" import { createMemo, createResource, createSignal, For, Show } from "solid-js"
import PageContainer from "@/components/PageContainer" import PageContainer from "@/components/PageContainer"
import LoadingState from "@/components/LoadingState" import LoadingState from "@/components/LoadingState"
import PaymentDialog from "@/components/PaymentDialog" import PaymentDialog from "@/components/PaymentDialog"
@@ -17,15 +17,9 @@ export default function Account() {
const [portalLoading, setPortalLoading] = createSignal(false) const [portalLoading, setPortalLoading] = createSignal(false)
const invoicesLoading = useMinLoading(() => invoices.loading) const invoicesLoading = useMinLoading(() => invoices.loading)
const hasBillingChanges = createMemo(() => { // The backend never returns the stored nwc_url (it's private), so the input is
const current = tenant()?.nwc_url?.trim() ?? "" // write-only: we can only act on a newly entered URL, not prefill the saved one.
const next = nwcUrl().trim() const hasBillingChanges = createMemo(() => nwcUrl().trim().length > 0)
return current !== next
})
createEffect(() => {
setNwcUrl(tenant()?.nwc_url ?? "")
})
async function saveBilling() { async function saveBilling() {
setError("") setError("")
@@ -33,6 +27,7 @@ export default function Account() {
try { try {
const next = nwcUrl().trim() const next = nwcUrl().trim()
await updateActiveTenant({ nwc_url: next }) await updateActiveTenant({ nwc_url: next })
setNwcUrl("")
await refetchTenant() await refetchTenant()
} catch (e) { } catch (e) {
setError(e instanceof Error ? e.message : "Failed to update billing") setError(e instanceof Error ? e.message : "Failed to update billing")
@@ -111,6 +106,9 @@ export default function Account() {
<p class="text-sm text-gray-600 mb-4"> <p class="text-sm text-gray-600 mb-4">
Enable automatic payments by providing your Nostr Wallet Connect URL. Enable automatic payments by providing your Nostr Wallet Connect URL.
</p> </p>
<Show when={tenant()?.nwc_is_set}>
<p class="text-sm text-green-700 mb-4">A wallet is connected. Enter a new URL to replace it.</p>
</Show>
<div class="flex gap-2"> <div class="flex gap-2">
<input <input
type="text" type="text"
+1 -1
View File
@@ -59,7 +59,7 @@ export default function RelayDetail() {
if (!isPaidRelay()) return false if (!isPaidRelay()) return false
const t = tenant() const t = tenant()
if (!t) return false if (!t) return false
return !t.nwc_url return !t.nwc_is_set
}) })
return ( return (