Files
zeekay bc4ddf332b chore: rebase the tree onto upstream master fa1a9a60
Mechanically re-derived: bin/debrand runs over a fresh import of
posthog/posthog@fa1a9a60d4 (2026-08-06).
The pipeline (.hanzo/workflows), the Dockerfiles and bin/docker*
entrypoints, compose files, LLM.md and the squashed initial migration
are restored from the shipped tree, same as the previous import.

The prune's import-closure guard kept 21 products the new core now
imports; dropping them is a refactor now, recorded rather than forced.
2026-08-06 01:25:36 -07:00

150 lines
6.4 KiB
TypeScript

import { useActions, useValues } from 'kea'
import { router } from 'kea-router'
import { Button } from '@hanzo/elements'
import { NotFound } from 'lib/components/NotFound'
import { Switch } from 'lib/elements/Switch'
import { deleteWithUndo } from 'lib/utils/deleteWithUndo'
import { getCurrentTeamId } from 'lib/utils/getAppContext'
import { SceneExport } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'
import { SceneContent } from '~/layout/scenes/components/SceneContent'
import { SceneTitleSection } from '~/layout/scenes/components/SceneTitleSection'
import type { SubscriptionApi } from 'products/subscriptions/frontend/generated/api.schemas'
import { ResourceTypeEnumApi } from 'products/subscriptions/frontend/generated/api.schemas'
import { SubscriptionDeliveryHistory } from './components/SubscriptionDeliveryHistory'
import { isSubscriptionEnabled, subscriptionEditHref, subscriptionName } from './components/SubscriptionsTable'
import { SubscriptionSummary } from './components/SubscriptionSummary'
import { subscriptionSceneLogic } from './subscriptionSceneLogic'
import { subscriptionsSceneLogic } from './subscriptionsSceneLogic'
function SubscriptionDetailActions({ sub }: { sub: SubscriptionApi }): JSX.Element {
const { push } = useActions(router)
const { setEnabled, deliverSubscription } = useActions(subscriptionSceneLogic)
const { subscriptionLoading, deliveringSubscriptionId } = useValues(subscriptionSceneLogic)
const editHref = subscriptionEditHref(sub)
const enabled = isSubscriptionEnabled(sub)
const isDelivering = deliveringSubscriptionId === sub.id
const deleteSubscription = (): void => {
const name = subscriptionName(sub)
void deleteWithUndo({
endpoint: `projects/${String(getCurrentTeamId())}/subscriptions`,
object: {
id: sub.id,
name,
},
callback: () => {
subscriptionsSceneLogic.findMounted()?.actions.deleteSubscriptionSuccess()
push(urls.subscriptions())
},
})
}
return (
<div className="flex flex-wrap items-center gap-2">
<Switch
checked={enabled}
onChange={(newEnabled) => setEnabled({ enabled: newEnabled })}
loading={subscriptionLoading}
label={enabled ? 'Enabled' : 'Disabled'}
data-attr="subscription-toggle-enabled"
/>
{editHref ? (
<Button type="secondary" onClick={() => push(editHref)}>
Edit subscription
</Button>
) : null}
<Button
type="primary"
onClick={() => deliverSubscription(sub.id)}
loading={isDelivering}
disabledReason={isDelivering ? 'Sending test delivery…' : null}
data-attr="subscription-detail-header-test-delivery"
>
Test delivery
</Button>
<Button
type="secondary"
status="danger"
data-attr="subscription-detail-delete"
onClick={deleteSubscription}
>
Delete
</Button>
</div>
)
}
export function SubscriptionScene(): JSX.Element {
const {
subscription,
subscriptionLoading,
deliveriesPage,
deliveriesPageLoading,
deliveringSubscriptionId,
deliveryStatusFilter,
deliveryFeedback,
recentlyThankedDeliveries,
} = useValues(subscriptionSceneLogic)
const { loadDeliveriesPage, deliverSubscription, setDeliveryStatusFilter, submitDeliveryFeedback } =
useActions(subscriptionSceneLogic)
const showNotFound = !subscriptionLoading && !subscription
return (
<SceneContent>
{showNotFound ? (
<NotFound object="subscription" />
) : (
<div className="py-8 flex-1 min-h-0 flex flex-col gap-6 max-w-full">
<SceneTitleSection
name={subscription ? subscriptionName(subscription) : 'Subscription'}
description={null}
resourceType={{ type: 'inbox' }}
isLoading={subscriptionLoading}
actions={subscription ? <SubscriptionDetailActions sub={subscription} /> : undefined}
/>
{subscription ? (
// Mute the body when the subscription is disabled — the switch in the
// header is the explicit signal; this is the at-a-glance reinforcement.
<div className={isSubscriptionEnabled(subscription) ? '' : 'opacity-60'}>
<SubscriptionSummary sub={subscription} />
</div>
) : null}
<SubscriptionDeliveryHistory
deliveriesPage={deliveriesPage}
deliveriesPageLoading={deliveriesPageLoading}
loadDeliveriesPage={loadDeliveriesPage}
deliveryStatusFilter={deliveryStatusFilter}
onDeliveryStatusFilterChange={setDeliveryStatusFilter}
// Empty-state CTA intentionally coexists with the header Test delivery button:
// it's the discoverable first-run nudge when a subscription has no deliveries yet.
onTestDelivery={subscription ? () => deliverSubscription(subscription.id) : undefined}
testDeliveryLoading={Boolean(subscription && deliveringSubscriptionId === subscription.id)}
onDeliveryFeedback={
subscription?.resource_type === ResourceTypeEnumApi.AiPrompt
? (deliveryId, feedback) => submitDeliveryFeedback(deliveryId, feedback, 'in_app')
: undefined
}
deliveryFeedback={deliveryFeedback}
recentlyThankedDeliveries={recentlyThankedDeliveries}
/>
</div>
)}
</SceneContent>
)
}
export const scene: SceneExport = {
component: SubscriptionScene,
logic: subscriptionSceneLogic,
paramsToProps: ({ params: { subscriptionId } }): { id: string } => ({
id: String(subscriptionId ?? ''),
}),
}