mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
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.
655 lines
24 KiB
TypeScript
655 lines
24 KiB
TypeScript
import { MakeLogicType, actions, afterMount, connect, kea, listeners, path, reducers, selectors } from 'kea'
|
|
import { loaders } from 'kea-loaders'
|
|
import { router, urlToAction } from 'kea-router'
|
|
|
|
import { Sorting } from '@hanzo/elements'
|
|
|
|
import { FEATURE_FLAGS } from 'lib/constants'
|
|
import { featureFlagLogic, type FeatureFlagsSet } from 'lib/logic/featureFlagLogic'
|
|
import { trackedActionToUrl } from 'lib/logic/scenes/trackedActionToUrl'
|
|
import { getCurrentTeamId } from 'lib/utils/getAppContext'
|
|
import { organizationLogic } from 'scenes/organizationLogic'
|
|
import { sceneConfigurations } from 'scenes/scenes'
|
|
import { Scene } from 'scenes/sceneTypes'
|
|
import { urls } from 'scenes/urls'
|
|
import { userLogic } from 'scenes/userLogic'
|
|
|
|
import { Breadcrumb } from '~/types'
|
|
|
|
import { subscriptionsList, subscriptionsTestDeliveryCreate } from 'products/subscriptions/frontend/generated/api'
|
|
import {
|
|
SubscriptionsListResourceType,
|
|
TargetTypeEnumApi,
|
|
type PaginatedSubscriptionListApi,
|
|
type SubscriptionsListTargetType,
|
|
} from 'products/subscriptions/frontend/generated/api.schemas'
|
|
|
|
import type { OrganizationType, UserType } from '../../../../frontend/src/types'
|
|
import { runSubscriptionTestDelivery } from '../components/Subscriptions/runSubscriptionTestDelivery'
|
|
import { toggleSubscriptionEnabled } from '../components/Subscriptions/toggleSubscriptionEnabled'
|
|
import type { SubscriptionApi } from '../generated/api.schemas'
|
|
|
|
const PAGE_SIZE = 20
|
|
|
|
export enum SubscriptionsTab {
|
|
All = 'all',
|
|
Mine = 'mine',
|
|
Dashboard = 'dashboard',
|
|
Insight = 'insight',
|
|
AI = 'ai_prompt',
|
|
}
|
|
|
|
/** Return type is full `SubscriptionsTab` so Kea typegen does not collapse state to the `All` literal. */
|
|
function getInitialSubscriptionsTab(): SubscriptionsTab {
|
|
return SubscriptionsTab.All
|
|
}
|
|
|
|
/** Query keys owned by the subscriptions list scene (merged into the router; other params are preserved). */
|
|
const SUBSCRIPTIONS_URL_KEYS = ['tab', 'search', 'created_by', 'target_type', 'page'] as const
|
|
|
|
/** Router may coerce numeric-looking query values; keep text fields as strings. */
|
|
function urlSearchParamToString(value: unknown): string {
|
|
return `${value ?? ''}`
|
|
}
|
|
|
|
export interface SubscriptionsQueryFromUrl {
|
|
tab: SubscriptionsTab
|
|
search: string
|
|
createdByUuid: string | null
|
|
targetTypeFilter: SubscriptionsListTargetType | null
|
|
page: number
|
|
}
|
|
|
|
function parseSubscriptionsSearchParams(searchParams: Record<string, unknown>): SubscriptionsQueryFromUrl {
|
|
const rawTab = searchParams['tab']
|
|
const tab: SubscriptionsTab = (Object.values(SubscriptionsTab) as string[]).includes(rawTab as string)
|
|
? (rawTab as SubscriptionsTab)
|
|
: SubscriptionsTab.All
|
|
|
|
const search = urlSearchParamToString(searchParams['search'])
|
|
|
|
let createdByUuid: string | null =
|
|
typeof searchParams['created_by'] === 'string' ? searchParams['created_by'] : null
|
|
if (tab === SubscriptionsTab.Mine) {
|
|
createdByUuid = null
|
|
}
|
|
|
|
const ttRaw = searchParams['target_type']
|
|
const targetTypeFilter: SubscriptionsListTargetType | null =
|
|
ttRaw === TargetTypeEnumApi.Email || ttRaw === TargetTypeEnumApi.Slack ? ttRaw : null
|
|
|
|
let page = 1
|
|
const pageRaw = searchParams['page']
|
|
if (pageRaw !== undefined && pageRaw !== null && `${pageRaw}` !== '') {
|
|
const n = parseInt(String(pageRaw), 10)
|
|
if (Number.isFinite(n) && n >= 1) {
|
|
page = n
|
|
}
|
|
}
|
|
|
|
return { tab, search, createdByUuid, targetTypeFilter, page }
|
|
}
|
|
|
|
function buildSubscriptionUrlParams(values: {
|
|
currentTab: SubscriptionsTab
|
|
search: string
|
|
createdByUuid: string | null
|
|
targetTypeFilter: SubscriptionsListTargetType | null
|
|
page: number
|
|
}): Record<string, string> {
|
|
const out: Record<string, string> = {}
|
|
if (values.currentTab !== SubscriptionsTab.All) {
|
|
out.tab = values.currentTab
|
|
}
|
|
const q = values.search.trim()
|
|
if (q) {
|
|
out.search = q
|
|
}
|
|
if (values.currentTab !== SubscriptionsTab.Mine && values.createdByUuid) {
|
|
out.created_by = values.createdByUuid
|
|
}
|
|
if (values.targetTypeFilter) {
|
|
out.target_type = values.targetTypeFilter
|
|
}
|
|
if (values.page > 1) {
|
|
out.page = String(values.page)
|
|
}
|
|
return out
|
|
}
|
|
|
|
function mergeSubscriptionRouterSearchParams(
|
|
current: Record<string, any>,
|
|
values: {
|
|
currentTab: SubscriptionsTab
|
|
search: string
|
|
createdByUuid: string | null
|
|
targetTypeFilter: SubscriptionsListTargetType | null
|
|
page: number
|
|
}
|
|
): Record<string, any> {
|
|
const next = { ...current }
|
|
for (const k of SUBSCRIPTIONS_URL_KEYS) {
|
|
delete next[k]
|
|
}
|
|
Object.assign(next, buildSubscriptionUrlParams(values))
|
|
return next
|
|
}
|
|
|
|
function subscriptionRouterSearchParamsEqual(
|
|
current: Record<string, any>,
|
|
values: {
|
|
currentTab: SubscriptionsTab
|
|
search: string
|
|
createdByUuid: string | null
|
|
targetTypeFilter: SubscriptionsListTargetType | null
|
|
page: number
|
|
}
|
|
): boolean {
|
|
const desired = buildSubscriptionUrlParams(values)
|
|
for (const k of SUBSCRIPTIONS_URL_KEYS) {
|
|
const cur = current[k] != null && current[k] !== '' ? String(current[k]) : ''
|
|
const des = desired[k] ?? ''
|
|
if (cur !== des) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
function subscriptionsListStateEqual(
|
|
parsed: SubscriptionsQueryFromUrl,
|
|
values: {
|
|
currentTab: SubscriptionsTab
|
|
search: string
|
|
createdByUuid: string | null
|
|
targetTypeFilter: SubscriptionsListTargetType | null
|
|
page: number
|
|
}
|
|
): boolean {
|
|
return (
|
|
parsed.tab === values.currentTab &&
|
|
parsed.search === values.search &&
|
|
(parsed.createdByUuid ?? '') === (values.createdByUuid ?? '') &&
|
|
(parsed.targetTypeFilter ?? null) === (values.targetTypeFilter ?? null) &&
|
|
parsed.page === values.page
|
|
)
|
|
}
|
|
|
|
function omitKey(state: Record<number, true>, id: number): Record<number, true> {
|
|
const next = { ...state }
|
|
delete next[id]
|
|
return next
|
|
}
|
|
|
|
/** Table columnKey → DRF `ordering` field (see SubscriptionViewSet.ordering_fields). */
|
|
function buildSubscriptionsListOrdering(sorting: Sorting | null): string {
|
|
if (!sorting) {
|
|
return '-created_at'
|
|
}
|
|
const fieldByColumnKey: Record<string, string> = {
|
|
next_delivery_date: 'next_delivery_date',
|
|
created_at: 'created_at',
|
|
created_by: 'created_by__email',
|
|
name: 'title',
|
|
}
|
|
const field = fieldByColumnKey[sorting.columnKey]
|
|
if (!field) {
|
|
return '-created_at'
|
|
}
|
|
const dir = sorting.order === -1 ? '-' : ''
|
|
return `${dir}${field}`
|
|
}
|
|
|
|
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
|
|
export interface subscriptionsSceneLogicValues {
|
|
featureFlags: FeatureFlagsSet // featureFlagLogic
|
|
receivedFeatureFlags: boolean // featureFlagLogic
|
|
currentOrganization: OrganizationType | null // organizationLogic
|
|
user: UserType | null // userLogic
|
|
aiSubscriptionsAvailable: boolean
|
|
breadcrumbs: Breadcrumb[]
|
|
createdByUuid: string | null
|
|
currentTab: SubscriptionsTab
|
|
deliveringSubscriptionId: number | null
|
|
page: number
|
|
pagination: {
|
|
controlled: true
|
|
currentPage: number
|
|
entryCount: number
|
|
onBackward: () => void
|
|
onForward: () => void
|
|
pageSize: number
|
|
}
|
|
search: string
|
|
subscriptionModalId: number | 'new' | null
|
|
subscriptions: SubscriptionApi[]
|
|
subscriptionsListAwaitingDebouncedFetch: boolean
|
|
subscriptionsLoading: boolean
|
|
subscriptionsResponse: PaginatedSubscriptionListApi | null
|
|
subscriptionsResponseLoading: boolean
|
|
subscriptionsSorting: Sorting | null
|
|
targetTypeFilter: SubscriptionsListTargetType | null
|
|
togglingEnabledIds: Record<number, true>
|
|
}
|
|
|
|
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
|
|
export interface subscriptionsSceneLogicActions {
|
|
applySubscriptionsQueryFromUrl: (query: SubscriptionsQueryFromUrl) => {
|
|
query: SubscriptionsQueryFromUrl
|
|
}
|
|
deleteSubscriptionSuccess: () => {
|
|
value: true
|
|
}
|
|
deliverSubscription: (id: number) => {
|
|
id: number
|
|
}
|
|
deliverSubscriptionFailure: () => {
|
|
value: true
|
|
}
|
|
deliverSubscriptionSuccess: () => {
|
|
value: true
|
|
}
|
|
loadSubscriptions: () => {
|
|
value: true
|
|
}
|
|
loadSubscriptionsFailure: (
|
|
error: string,
|
|
errorObject?: any
|
|
) => {
|
|
error: string
|
|
errorObject?: any
|
|
}
|
|
loadSubscriptionsSuccess: (
|
|
subscriptionsResponse: PaginatedSubscriptionListApi,
|
|
payload?: {
|
|
value: true
|
|
}
|
|
) => {
|
|
subscriptionsResponse: PaginatedSubscriptionListApi
|
|
payload?: {
|
|
value: true
|
|
}
|
|
}
|
|
setCreatedByFilter: (uuid: string | null) => {
|
|
uuid: string | null
|
|
}
|
|
setCurrentTab: (tab: SubscriptionsTab) => {
|
|
tab: SubscriptionsTab
|
|
}
|
|
setPage: (page: number) => {
|
|
page: number
|
|
}
|
|
setSearch: (search: string) => {
|
|
search: string
|
|
}
|
|
setSubscriptionEnabled: (
|
|
id: number,
|
|
enabled: boolean
|
|
) => {
|
|
enabled: boolean
|
|
id: number
|
|
}
|
|
setSubscriptionEnabledFailure: (id: number) => {
|
|
id: number
|
|
}
|
|
setSubscriptionEnabledSuccess: (id: number) => {
|
|
id: number
|
|
}
|
|
setSubscriptionModalId: (id: number | 'new' | null) => {
|
|
id: number | 'new' | null
|
|
}
|
|
setSubscriptionsSorting: (sorting: Sorting | null) => {
|
|
sorting: Sorting | null
|
|
}
|
|
setTargetTypeFilter: (targetType: SubscriptionsListTargetType | null) => {
|
|
targetType: SubscriptionsListTargetType | null
|
|
}
|
|
}
|
|
|
|
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
|
|
export interface subscriptionsSceneLogicMeta {
|
|
__keaTypeGenInternalSelectorTypes: {
|
|
aiSubscriptionsAvailable: (
|
|
featureFlags: FeatureFlagsSet,
|
|
receivedFeatureFlags: boolean,
|
|
currentOrganization: OrganizationType | null
|
|
) => boolean
|
|
subscriptions: (subscriptionsResponse: PaginatedSubscriptionListApi | null) => SubscriptionApi[]
|
|
subscriptionsLoading: (subscriptionsResponseLoading: boolean) => boolean
|
|
pagination: (
|
|
page: number,
|
|
subscriptionsResponse: PaginatedSubscriptionListApi | null
|
|
) => {
|
|
controlled: true
|
|
currentPage: number
|
|
entryCount: number
|
|
onBackward: () => void
|
|
onForward: () => void
|
|
pageSize: number
|
|
}
|
|
}
|
|
}
|
|
|
|
export type subscriptionsSceneLogicType = MakeLogicType<
|
|
subscriptionsSceneLogicValues,
|
|
subscriptionsSceneLogicActions,
|
|
Record<string, any>,
|
|
subscriptionsSceneLogicMeta
|
|
>
|
|
|
|
export const subscriptionsSceneLogic = kea<subscriptionsSceneLogicType>([
|
|
path(['scenes', 'subscriptions', 'subscriptionsSceneLogic']),
|
|
connect(() => ({
|
|
values: [
|
|
userLogic,
|
|
['user'],
|
|
featureFlagLogic,
|
|
['featureFlags', 'receivedFeatureFlags'],
|
|
organizationLogic,
|
|
['currentOrganization'],
|
|
],
|
|
})),
|
|
actions({
|
|
loadSubscriptions: true,
|
|
setSearch: (search: string) => ({ search }),
|
|
setPage: (page: number) => ({ page }),
|
|
setCreatedByFilter: (uuid: string | null) => ({ uuid }),
|
|
setCurrentTab: (tab: SubscriptionsTab) => ({ tab }),
|
|
setSubscriptionsSorting: (sorting: Sorting | null) => ({ sorting }),
|
|
setTargetTypeFilter: (targetType: SubscriptionsListTargetType | null) => ({ targetType }),
|
|
applySubscriptionsQueryFromUrl: (query: SubscriptionsQueryFromUrl) => ({ query }),
|
|
setSubscriptionModalId: (id: number | 'new' | null) => ({ id }),
|
|
deleteSubscriptionSuccess: true,
|
|
deliverSubscription: (id: number) => ({ id }),
|
|
deliverSubscriptionSuccess: true,
|
|
deliverSubscriptionFailure: true,
|
|
setSubscriptionEnabled: (id: number, enabled: boolean) => ({ id, enabled }),
|
|
setSubscriptionEnabledSuccess: (id: number) => ({ id }),
|
|
setSubscriptionEnabledFailure: (id: number) => ({ id }),
|
|
}),
|
|
reducers({
|
|
search: [
|
|
'',
|
|
{
|
|
setSearch: (_, { search }) => search,
|
|
applySubscriptionsQueryFromUrl: (_, { query }) => query.search,
|
|
},
|
|
],
|
|
createdByUuid: [
|
|
null as string | null,
|
|
{
|
|
setCreatedByFilter: (_, { uuid }) => uuid,
|
|
setCurrentTab: (state, { tab }) => (tab === SubscriptionsTab.Mine ? null : state),
|
|
applySubscriptionsQueryFromUrl: (_, { query }) =>
|
|
query.tab === SubscriptionsTab.Mine ? null : query.createdByUuid,
|
|
},
|
|
],
|
|
currentTab: [
|
|
getInitialSubscriptionsTab(),
|
|
{
|
|
setCurrentTab: (_, { tab }) => tab,
|
|
applySubscriptionsQueryFromUrl: (_, { query }) => query.tab,
|
|
},
|
|
],
|
|
page: [
|
|
1,
|
|
{
|
|
setPage: (_, { page }) => page,
|
|
setSearch: () => 1,
|
|
setCreatedByFilter: () => 1,
|
|
setCurrentTab: () => 1,
|
|
setSubscriptionsSorting: () => 1,
|
|
setTargetTypeFilter: () => 1,
|
|
deleteSubscriptionSuccess: () => 1,
|
|
applySubscriptionsQueryFromUrl: (_, { query }) => query.page,
|
|
},
|
|
],
|
|
subscriptionsSorting: [
|
|
null as Sorting | null,
|
|
{
|
|
setSubscriptionsSorting: (_, { sorting }) => sorting,
|
|
},
|
|
],
|
|
targetTypeFilter: [
|
|
null as SubscriptionsListTargetType | null,
|
|
{
|
|
setTargetTypeFilter: (_, { targetType }) => targetType,
|
|
applySubscriptionsQueryFromUrl: (_, { query }) => query.targetTypeFilter,
|
|
},
|
|
],
|
|
// Drives the create/edit modal that now renders OVER the list (route-driven), so the
|
|
// list stays painted behind it instead of swapping to a separate scene. `null` = closed.
|
|
subscriptionModalId: [
|
|
null as number | 'new' | null,
|
|
{
|
|
setSubscriptionModalId: (_, { id }) => id,
|
|
},
|
|
],
|
|
/**
|
|
* True after `setSearch` until the debounced list request finishes. Avoids treating stale
|
|
* empty `subscriptionsResponse` as "no subscriptions in project" when filters were cleared.
|
|
*/
|
|
subscriptionsListAwaitingDebouncedFetch: [
|
|
false,
|
|
{
|
|
setSearch: () => true,
|
|
loadSubscriptionsSuccess: () => false,
|
|
loadSubscriptionsFailure: () => false,
|
|
},
|
|
],
|
|
deliveringSubscriptionId: [
|
|
null as number | null,
|
|
{
|
|
deliverSubscription: (_, { id }) => id,
|
|
deliverSubscriptionSuccess: () => null,
|
|
deliverSubscriptionFailure: () => null,
|
|
},
|
|
],
|
|
// Tracks the subscriptions whose Pause/Resume PATCH is in flight so each row's
|
|
// switch can show its own busy state. A set (not a single id) so concurrent toggles
|
|
// on different rows don't clobber each other.
|
|
togglingEnabledIds: [
|
|
{} as Record<number, true>,
|
|
{
|
|
setSubscriptionEnabled: (state, { id }) => ({ ...state, [id]: true }),
|
|
setSubscriptionEnabledSuccess: (state, { id }) => omitKey(state, id),
|
|
setSubscriptionEnabledFailure: (state, { id }) => omitKey(state, id),
|
|
},
|
|
],
|
|
}),
|
|
loaders(({ values }) => ({
|
|
subscriptionsResponse: [
|
|
null as PaginatedSubscriptionListApi | null,
|
|
{
|
|
loadSubscriptions: async () => {
|
|
const projectId = String(getCurrentTeamId())
|
|
let resourceType: SubscriptionsListResourceType | undefined
|
|
if (values.currentTab === SubscriptionsTab.Dashboard) {
|
|
resourceType = SubscriptionsListResourceType.Dashboard
|
|
} else if (values.currentTab === SubscriptionsTab.Insight) {
|
|
resourceType = SubscriptionsListResourceType.Insight
|
|
} else if (values.currentTab === SubscriptionsTab.AI) {
|
|
resourceType = SubscriptionsListResourceType.AiPrompt
|
|
}
|
|
const createdBy =
|
|
values.currentTab === SubscriptionsTab.Mine
|
|
? values.user?.uuid
|
|
: values.createdByUuid || undefined
|
|
const ordering = buildSubscriptionsListOrdering(values.subscriptionsSorting)
|
|
return await subscriptionsList(projectId, {
|
|
limit: PAGE_SIZE,
|
|
offset: (values.page - 1) * PAGE_SIZE,
|
|
search: values.search.trim() || undefined,
|
|
ordering,
|
|
created_by: createdBy,
|
|
resource_type: resourceType,
|
|
target_type: values.targetTypeFilter ?? undefined,
|
|
})
|
|
},
|
|
},
|
|
],
|
|
})),
|
|
selectors({
|
|
aiSubscriptionsAvailable: [
|
|
(s) => [s.featureFlags, s.receivedFeatureFlags, s.currentOrganization],
|
|
(
|
|
featureFlags: FeatureFlagsSet,
|
|
receivedFeatureFlags: boolean,
|
|
currentOrganization: OrganizationType | null
|
|
) =>
|
|
receivedFeatureFlags &&
|
|
Boolean(featureFlags[FEATURE_FLAGS.SUBSCRIPTION_AI_PROMPT]) &&
|
|
Boolean(currentOrganization?.is_ai_data_processing_approved),
|
|
],
|
|
subscriptions: [
|
|
(s) => [s.subscriptionsResponse],
|
|
(subscriptionsResponse: PaginatedSubscriptionListApi | null) => subscriptionsResponse?.results ?? [],
|
|
],
|
|
subscriptionsLoading: [
|
|
(s) => [s.subscriptionsResponseLoading],
|
|
(subscriptionsResponseLoading: boolean) => Boolean(subscriptionsResponseLoading),
|
|
],
|
|
breadcrumbs: [
|
|
() => [],
|
|
(): Breadcrumb[] => [
|
|
{
|
|
key: Scene.Subscriptions,
|
|
name: sceneConfigurations[Scene.Subscriptions].name,
|
|
iconType: sceneConfigurations[Scene.Subscriptions].iconType || 'default_icon_type',
|
|
},
|
|
],
|
|
],
|
|
}),
|
|
selectors(({ actions }) => ({
|
|
pagination: [
|
|
(s) => [s.page, s.subscriptionsResponse],
|
|
(page: number, subscriptionsResponse: PaginatedSubscriptionListApi | null) => {
|
|
const count = subscriptionsResponse?.count ?? 0
|
|
// usePagination uses `entryCount || null`; 0 is treated as missing and breaks pageCount / next button.
|
|
// Table shows "No …" when the page is empty, so inflating 0→1 only satisfies the hook.
|
|
const entryCount = Math.max(count, 1)
|
|
return {
|
|
controlled: true,
|
|
pageSize: PAGE_SIZE,
|
|
currentPage: page,
|
|
entryCount,
|
|
onBackward: () => actions.setPage(page - 1),
|
|
onForward: () => actions.setPage(page + 1),
|
|
}
|
|
},
|
|
],
|
|
})),
|
|
listeners(({ actions, values }) => ({
|
|
setSearch: async (_, breakpoint) => {
|
|
await breakpoint(300)
|
|
actions.loadSubscriptions()
|
|
},
|
|
setPage: () => actions.loadSubscriptions(),
|
|
setCreatedByFilter: () => actions.loadSubscriptions(),
|
|
setCurrentTab: ({ tab }) => {
|
|
if (tab === SubscriptionsTab.AI && !values.aiSubscriptionsAvailable) {
|
|
actions.setCurrentTab(SubscriptionsTab.All)
|
|
return
|
|
}
|
|
actions.loadSubscriptions()
|
|
},
|
|
setSubscriptionsSorting: () => actions.loadSubscriptions(),
|
|
setTargetTypeFilter: () => actions.loadSubscriptions(),
|
|
applySubscriptionsQueryFromUrl: () => {
|
|
if (values.currentTab === SubscriptionsTab.AI && !values.aiSubscriptionsAvailable) {
|
|
actions.setCurrentTab(SubscriptionsTab.All)
|
|
return
|
|
}
|
|
actions.loadSubscriptions()
|
|
},
|
|
[featureFlagLogic.actionTypes.setFeatureFlags]: () => {
|
|
if (values.currentTab === SubscriptionsTab.AI && !values.aiSubscriptionsAvailable) {
|
|
actions.setCurrentTab(SubscriptionsTab.All)
|
|
}
|
|
},
|
|
[organizationLogic.actionTypes.loadCurrentOrganizationSuccess]: () => {
|
|
if (values.currentTab === SubscriptionsTab.AI && !values.aiSubscriptionsAvailable) {
|
|
actions.setCurrentTab(SubscriptionsTab.All)
|
|
}
|
|
},
|
|
deleteSubscriptionSuccess: () => actions.loadSubscriptions(),
|
|
deliverSubscription: async ({ id }) => {
|
|
const result = await runSubscriptionTestDelivery(() =>
|
|
subscriptionsTestDeliveryCreate(String(getCurrentTeamId()), id)
|
|
)
|
|
if (result === 'success') {
|
|
actions.deliverSubscriptionSuccess()
|
|
} else {
|
|
actions.deliverSubscriptionFailure()
|
|
}
|
|
},
|
|
setSubscriptionEnabled: async ({ id, enabled }) => {
|
|
const ok = await toggleSubscriptionEnabled(id, enabled)
|
|
if (ok) {
|
|
actions.setSubscriptionEnabledSuccess(id)
|
|
} else {
|
|
actions.setSubscriptionEnabledFailure(id)
|
|
}
|
|
},
|
|
setSubscriptionEnabledSuccess: () => actions.loadSubscriptions(),
|
|
})),
|
|
trackedActionToUrl(({ values }) => {
|
|
const syncUrl = (
|
|
replace: boolean
|
|
): [string, Record<string, any>, Record<string, unknown> | undefined, { replace: boolean }] | undefined => {
|
|
const subscriptionValues = {
|
|
currentTab: values.currentTab,
|
|
search: values.search,
|
|
createdByUuid: values.createdByUuid,
|
|
targetTypeFilter: values.targetTypeFilter,
|
|
page: values.page,
|
|
}
|
|
const pathname = router.values.location.pathname
|
|
if (pathname !== urls.subscriptions()) {
|
|
return
|
|
}
|
|
if (subscriptionRouterSearchParamsEqual(router.values.searchParams, subscriptionValues)) {
|
|
return
|
|
}
|
|
const next = mergeSubscriptionRouterSearchParams(router.values.searchParams, subscriptionValues)
|
|
return [pathname, next, router.values.hashParams, { replace }] as const
|
|
}
|
|
return {
|
|
setSearch: () => syncUrl(true),
|
|
setCreatedByFilter: () => syncUrl(true),
|
|
setTargetTypeFilter: () => syncUrl(true),
|
|
setPage: () => syncUrl(true),
|
|
setCurrentTab: () => syncUrl(false),
|
|
}
|
|
}),
|
|
urlToAction(({ actions, values }) => ({
|
|
[urls.subscriptionNew()]: () => {
|
|
actions.setSubscriptionModalId('new')
|
|
},
|
|
[urls.subscriptionEdit(':subscriptionId')]: ({ subscriptionId }) => {
|
|
const editingId = Number(subscriptionId)
|
|
actions.setSubscriptionModalId(Number.isFinite(editingId) ? editingId : 'new')
|
|
},
|
|
[urls.subscriptions()]: (_, searchParams) => {
|
|
if (values.subscriptionModalId !== null) {
|
|
actions.setSubscriptionModalId(null)
|
|
}
|
|
const parsed = parseSubscriptionsSearchParams(searchParams)
|
|
const listState = {
|
|
currentTab: values.currentTab,
|
|
search: values.search,
|
|
createdByUuid: values.createdByUuid,
|
|
targetTypeFilter: values.targetTypeFilter,
|
|
page: values.page,
|
|
}
|
|
if (subscriptionsListStateEqual(parsed, listState)) {
|
|
return
|
|
}
|
|
actions.applySubscriptionsQueryFromUrl(parsed)
|
|
},
|
|
})),
|
|
afterMount(({ actions }) => {
|
|
actions.loadSubscriptions()
|
|
}),
|
|
])
|