From 4610c71b458a9f04bf39d3ec2b7366f23677fbf0 Mon Sep 17 00:00:00 2001 From: tdgao Date: Mon, 8 Jun 2026 19:04:51 -0700 Subject: [PATCH 01/13] feat: layout creator payouts page files --- .../distribute-earnings/AdjustmentsCard.vue | 70 +++++++ .../DistributeBreakdownCard.vue | 124 +++++++++++++ .../distribute-earnings/VerifyPayoutModal.vue | 174 ++++++++++++++++++ .../distribute-earnings/index.vue | 146 +++++++++++++++ .../distribute-month-card/index.vue | 84 +++++++++ .../payout-initiated-card/index.vue | 71 +++++++ .../creator-payouts/payouts-table/index.vue | 172 +++++++++++++++++ .../components/ui/creator-payouts/routes.md | 90 +++++++++ .../components/ui/creator-payouts/utils.ts | 109 +++++++++++ .../admin/creator-payouts/distribute.vue | 109 +++++++++++ .../src/pages/admin/creator-payouts/index.vue | 135 ++++++++++++++ packages/api-client/src/modules/index.ts | 2 + .../api-client/src/modules/labrinth/index.ts | 1 + .../src/modules/labrinth/payouts/internal.ts | 65 +++++++ .../api-client/src/modules/labrinth/types.ts | 55 ++++++ 15 files changed, 1407 insertions(+) create mode 100644 apps/frontend/src/components/ui/creator-payouts/distribute-earnings/AdjustmentsCard.vue create mode 100644 apps/frontend/src/components/ui/creator-payouts/distribute-earnings/DistributeBreakdownCard.vue create mode 100644 apps/frontend/src/components/ui/creator-payouts/distribute-earnings/VerifyPayoutModal.vue create mode 100644 apps/frontend/src/components/ui/creator-payouts/distribute-earnings/index.vue create mode 100644 apps/frontend/src/components/ui/creator-payouts/distribute-month-card/index.vue create mode 100644 apps/frontend/src/components/ui/creator-payouts/payout-initiated-card/index.vue create mode 100644 apps/frontend/src/components/ui/creator-payouts/payouts-table/index.vue create mode 100644 apps/frontend/src/components/ui/creator-payouts/routes.md create mode 100644 apps/frontend/src/components/ui/creator-payouts/utils.ts create mode 100644 apps/frontend/src/pages/admin/creator-payouts/distribute.vue create mode 100644 apps/frontend/src/pages/admin/creator-payouts/index.vue create mode 100644 packages/api-client/src/modules/labrinth/payouts/internal.ts diff --git a/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/AdjustmentsCard.vue b/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/AdjustmentsCard.vue new file mode 100644 index 0000000000..7c571c1dc7 --- /dev/null +++ b/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/AdjustmentsCard.vue @@ -0,0 +1,70 @@ + + + diff --git a/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/DistributeBreakdownCard.vue b/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/DistributeBreakdownCard.vue new file mode 100644 index 0000000000..46623ac13f --- /dev/null +++ b/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/DistributeBreakdownCard.vue @@ -0,0 +1,124 @@ + + + diff --git a/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/VerifyPayoutModal.vue b/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/VerifyPayoutModal.vue new file mode 100644 index 0000000000..b865e4f666 --- /dev/null +++ b/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/VerifyPayoutModal.vue @@ -0,0 +1,174 @@ + + + diff --git a/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/index.vue b/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/index.vue new file mode 100644 index 0000000000..341173a55f --- /dev/null +++ b/apps/frontend/src/components/ui/creator-payouts/distribute-earnings/index.vue @@ -0,0 +1,146 @@ + + + diff --git a/apps/frontend/src/components/ui/creator-payouts/distribute-month-card/index.vue b/apps/frontend/src/components/ui/creator-payouts/distribute-month-card/index.vue new file mode 100644 index 0000000000..66b8a371a0 --- /dev/null +++ b/apps/frontend/src/components/ui/creator-payouts/distribute-month-card/index.vue @@ -0,0 +1,84 @@ + + + diff --git a/apps/frontend/src/components/ui/creator-payouts/payout-initiated-card/index.vue b/apps/frontend/src/components/ui/creator-payouts/payout-initiated-card/index.vue new file mode 100644 index 0000000000..c69f39dc56 --- /dev/null +++ b/apps/frontend/src/components/ui/creator-payouts/payout-initiated-card/index.vue @@ -0,0 +1,71 @@ + + + diff --git a/apps/frontend/src/components/ui/creator-payouts/payouts-table/index.vue b/apps/frontend/src/components/ui/creator-payouts/payouts-table/index.vue new file mode 100644 index 0000000000..b181b6d9b0 --- /dev/null +++ b/apps/frontend/src/components/ui/creator-payouts/payouts-table/index.vue @@ -0,0 +1,172 @@ + + + diff --git a/apps/frontend/src/components/ui/creator-payouts/routes.md b/apps/frontend/src/components/ui/creator-payouts/routes.md new file mode 100644 index 0000000000..dd4d286d67 --- /dev/null +++ b/apps/frontend/src/components/ui/creator-payouts/routes.md @@ -0,0 +1,90 @@ +## **Roughly the routes needed** + +- New type `YearMonth` which is represented as eg `2026-05` + - So it’s an ISO 8601 year and month, but not day +- GET /\_internal/payouts/history + + ```json + // response + Array<{ + payouts_date: string // YearMonth + days: Array<{ + estimated_revenue_usd: number | null + }> + status: open | pending | review | paid + fees_deducted_usd: number + variance_adjustment_usd: number + net_estimated_revenue_usd: number + creator_net_estimated_revenue_usd: number + modrinth_net_estimated_revenue_usd: number + + // if status is paid + actual_revenue_usd: number + total_external_adjustment_usd: number + + net_actual_revenue_usd: number + creator_net_actual_revenue_usd: number + modrinth_net_actual_revenue_usd: number + + // admin authed fields + started_at: string | null // DateTime + started_by: string | null // user id + detailed_external_adjustments: Array<{ + description: string + amount_usd: number + }> | null + }> + ``` + +- POST /\_internal/payouts/distribution/start + - Only allowed to run on payout dates in review - not pending + + ```json + // body + { + payouts_date: string // YearMonth + totp_code: string // make sure the backend checks that this user has 2FA set up + amount_received: number + adjustments: Array<{ + description: string + amount: number + }> + } + + // response + // (same as GET /_internal/payouts/distribution) + ``` + + - We’re going to need to handle processing time + cancel time. Better to not be just in frontend, so maybe some new routes? + - Only one distribution at a time + - POST /\_internal/payouts/distribution/cancel + - GET /\_internal/payouts/distribution + - gets distribution that’s currently awaiting to go out, and details on it + + ```jsx + // response + { + payouts_date: string // YearMonth + amount_received: number + adjustments: Array<{ + description: string + amount: number + }> + started_at: string // DateTime + started_by: string // user ID + distributes_at: string // DateTime + } + ``` + +- Audit logs notes + - Payout distribution runs will store who started it and when + - We don’t store an explicit audit log of, this user attempted to start a run / this user cancelled a run / etc. +- Backend notes + - When we make `payouts_values` rows, we set `available_at` to null + - When we return when a payout value is estimated to be available, backend computes it on the fly + - Take the `created` time, set to end of month, add net 70/75/etc, return that + - When we do a distribution run, take all the payout values with dates in that year and month, and set their `available_at` to now + - On the payouts routes (routes which fetch from `payouts_values`), we will leave the `available_at` fields as-is, but add a new `estimated_available_at` which is computed on the fly + - Frontend will use the following logic for copy: + - `available_at` is present → this is exactly when it will be (or in our case, was made to be) available. this is not an estimate (also, `estimated_available_at` will not be computed) + - else, `estimated_available_at` is present → say that this actually is an estimate diff --git a/apps/frontend/src/components/ui/creator-payouts/utils.ts b/apps/frontend/src/components/ui/creator-payouts/utils.ts new file mode 100644 index 0000000000..dd2a93cef5 --- /dev/null +++ b/apps/frontend/src/components/ui/creator-payouts/utils.ts @@ -0,0 +1,109 @@ +import type { Labrinth } from '@modrinth/api-client' + +export const CREATOR_PAYOUT_SHARE = 0.75 +export const MODRINTH_PAYOUT_SHARE = 0.25 + +export type PayoutHistoryItem = Labrinth.Payouts.Internal.HistoryItem +export type DistributionAdjustment = Labrinth.Payouts.Internal.DistributionAdjustment +export type DistributionRun = Labrinth.Payouts.Internal.DistributionRun + +export function isYearMonth(value: unknown): value is Labrinth.Payouts.Internal.YearMonth { + return typeof value === 'string' && /^\d{4}-\d{2}$/.test(value) +} + +export function formatMonthYear(yearMonth: string): string { + const date = getYearMonthDate(yearMonth) + return new Intl.DateTimeFormat(undefined, { + month: 'long', + year: 'numeric', + }).format(date) +} + +export function formatShortDate(date: Date): string { + return new Intl.DateTimeFormat(undefined, { + weekday: 'short', + month: 'short', + day: 'numeric', + }).format(date) +} + +export function formatCurrency(amount: number | null | undefined, options?: { cents?: boolean }) { + if (amount === null || amount === undefined || Number.isNaN(amount)) { + return '-' + } + + return new Intl.NumberFormat(undefined, { + style: 'currency', + currency: 'USD', + minimumFractionDigits: options?.cents ? 2 : 0, + maximumFractionDigits: options?.cents ? 2 : 0, + }).format(amount) +} + +export function formatSignedCurrency(amount: number | null | undefined): string { + if (amount === null || amount === undefined || Number.isNaN(amount)) { + return '-' + } + + const formatted = formatCurrency(Math.abs(amount)) + return amount < 0 ? `-${formatted}` : formatted +} + +export function getReviewDueDate(yearMonth: string): Date { + return addDays(getLastDayOfMonth(yearMonth), 75) +} + +export function getPendingAvailableDate(yearMonth: string): Date { + return addDays(getLastDayOfMonth(yearMonth), 60) +} + +export function getDaysRemaining(date: Date): number { + const today = new Date() + today.setHours(0, 0, 0, 0) + const target = new Date(date) + target.setHours(0, 0, 0, 0) + return Math.ceil((target.getTime() - today.getTime()) / 86_400_000) +} + +export function getNetActualRevenue( + amountReceived: number, + adjustments: DistributionAdjustment[], +): number { + return roundCurrency(amountReceived + getTotalAdjustments(adjustments)) +} + +export function getTotalAdjustments(adjustments: DistributionAdjustment[]): number { + return roundCurrency(adjustments.reduce((total, adjustment) => total + adjustment.amount, 0)) +} + +export function getCreatorShare(amount: number): number { + return roundCurrency(amount * CREATOR_PAYOUT_SHARE) +} + +export function getModrinthShare(amount: number): number { + return roundCurrency(amount * MODRINTH_PAYOUT_SHARE) +} + +export function getDistributionCreatorAmount(distribution: DistributionRun): number { + return getCreatorShare(getNetActualRevenue(distribution.amount_received, distribution.adjustments)) +} + +export function roundCurrency(amount: number): number { + return Math.round(amount * 100) / 100 +} + +export function getYearMonthDate(yearMonth: string): Date { + const [year, month] = yearMonth.split('-').map(Number) + return new Date(year, month - 1, 1, 12) +} + +function getLastDayOfMonth(yearMonth: string): Date { + const [year, month] = yearMonth.split('-').map(Number) + return new Date(year, month, 0, 12) +} + +function addDays(date: Date, days: number): Date { + const nextDate = new Date(date) + nextDate.setDate(nextDate.getDate() + days) + return nextDate +} diff --git a/apps/frontend/src/pages/admin/creator-payouts/distribute.vue b/apps/frontend/src/pages/admin/creator-payouts/distribute.vue new file mode 100644 index 0000000000..2b6051d727 --- /dev/null +++ b/apps/frontend/src/pages/admin/creator-payouts/distribute.vue @@ -0,0 +1,109 @@ + + + diff --git a/apps/frontend/src/pages/admin/creator-payouts/index.vue b/apps/frontend/src/pages/admin/creator-payouts/index.vue new file mode 100644 index 0000000000..ef84e1f835 --- /dev/null +++ b/apps/frontend/src/pages/admin/creator-payouts/index.vue @@ -0,0 +1,135 @@ + + + diff --git a/packages/api-client/src/modules/index.ts b/packages/api-client/src/modules/index.ts index 7e857f12c3..024ca02b7c 100644 --- a/packages/api-client/src/modules/index.ts +++ b/packages/api-client/src/modules/index.ts @@ -35,6 +35,7 @@ import { LabrinthOAuthInternalModule } from './labrinth/oauth/internal' import { LabrinthOrganizationsV3Module } from './labrinth/organizations/v3' import { LabrinthPatsV2Module } from './labrinth/pats/v2' import { LabrinthPayoutV3Module } from './labrinth/payout/v3' +import { LabrinthPayoutsInternalModule } from './labrinth/payouts/internal' import { LabrinthPayoutsV3Module } from './labrinth/payouts/v3' import { LabrinthProjectsV2Module } from './labrinth/projects/v2' import { LabrinthProjectsV3Module } from './labrinth/projects/v3' @@ -104,6 +105,7 @@ export const MODULE_REGISTRY = { labrinth_pats_v2: LabrinthPatsV2Module, labrinth_limits_v3: LabrinthLimitsV3Module, labrinth_payout_v3: LabrinthPayoutV3Module, + labrinth_payouts_internal: LabrinthPayoutsInternalModule, labrinth_payouts_v3: LabrinthPayoutsV3Module, labrinth_projects_v2: LabrinthProjectsV2Module, labrinth_projects_v3: LabrinthProjectsV3Module, diff --git a/packages/api-client/src/modules/labrinth/index.ts b/packages/api-client/src/modules/labrinth/index.ts index 4bc8e150e1..8b860c02d8 100644 --- a/packages/api-client/src/modules/labrinth/index.ts +++ b/packages/api-client/src/modules/labrinth/index.ts @@ -13,6 +13,7 @@ export * from './oauth/internal' export * from './organizations/v3' export * from './pats/v2' export * from './payout/v3' +export * from './payouts/internal' export * from './payouts/v3' export * from './projects/v2' export * from './projects/v3' diff --git a/packages/api-client/src/modules/labrinth/payouts/internal.ts b/packages/api-client/src/modules/labrinth/payouts/internal.ts new file mode 100644 index 0000000000..aaf6ba161b --- /dev/null +++ b/packages/api-client/src/modules/labrinth/payouts/internal.ts @@ -0,0 +1,65 @@ +import { AbstractModule } from '../../../core/abstract-module' +import type { Labrinth } from '../types' + +export class LabrinthPayoutsInternalModule extends AbstractModule { + public getModuleID(): string { + return 'labrinth_payouts_internal' + } + + /** + * Get creator payout history. + * GET /_internal/payouts/history + */ + public async getHistory(): Promise { + return this.client.request('/payouts/history', { + api: 'labrinth', + version: 'internal', + method: 'GET', + }) + } + + /** + * Get the active payout distribution run. + * GET /_internal/payouts/distribution + */ + public async getDistribution(): Promise { + return this.client.request( + '/payouts/distribution', + { + api: 'labrinth', + version: 'internal', + method: 'GET', + }, + ) + } + + /** + * Start a payout distribution run. + * POST /_internal/payouts/distribution/start + */ + public async startDistribution( + data: Labrinth.Payouts.Internal.StartDistributionRequest, + ): Promise { + return this.client.request( + '/payouts/distribution/start', + { + api: 'labrinth', + version: 'internal', + method: 'POST', + body: data, + }, + ) + } + + /** + * Cancel the active payout distribution run. + * POST /_internal/payouts/distribution/cancel + */ + public async cancelDistribution(): Promise { + return this.client.request('/payouts/distribution/cancel', { + api: 'labrinth', + version: 'internal', + method: 'POST', + }) + } +} diff --git a/packages/api-client/src/modules/labrinth/types.ts b/packages/api-client/src/modules/labrinth/types.ts index 91a2702091..7bc530c292 100644 --- a/packages/api-client/src/modules/labrinth/types.ts +++ b/packages/api-client/src/modules/labrinth/types.ts @@ -1690,6 +1690,61 @@ export namespace Labrinth { } export namespace Payouts { + export namespace Internal { + export type YearMonth = string + + export type PayoutStatus = 'open' | 'pending' | 'review' | 'paid' + + export type RevenueDay = { + estimated_revenue_usd: number | null + } + + export type DetailedExternalAdjustment = { + description: string + amount_usd: number + } + + export type HistoryItem = { + payouts_date: YearMonth + days: RevenueDay[] + status: PayoutStatus + fees_deducted_usd: number + variance_adjustment_usd: number + net_estimated_revenue_usd: number + creator_net_estimated_revenue_usd: number + modrinth_net_estimated_revenue_usd: number + actual_revenue_usd?: number + total_external_adjustment_usd?: number + net_actual_revenue_usd?: number + creator_net_actual_revenue_usd?: number + modrinth_net_actual_revenue_usd?: number + started_at: string | null + started_by: string | null + detailed_external_adjustments: DetailedExternalAdjustment[] | null + } + + export type DistributionAdjustment = { + description: string + amount: number + } + + export type StartDistributionRequest = { + payouts_date: YearMonth + totp_code: string + amount_received: number + adjustments: DistributionAdjustment[] + } + + export type DistributionRun = { + payouts_date: YearMonth + amount_received: number + adjustments: DistributionAdjustment[] + started_at: string + started_by: string + distributes_at: string + } + } + export namespace v3 { export type RevenueData = { time: number From 7297070f5c2db406ffec4dfdb55ae307743488e5 Mon Sep 17 00:00:00 2001 From: tdgao Date: Mon, 8 Jun 2026 19:57:51 -0700 Subject: [PATCH 02/13] feat: mock route responses --- .../src/modules/labrinth/payouts/internal.ts | 294 ++++++++++++++++-- 1 file changed, 267 insertions(+), 27 deletions(-) diff --git a/packages/api-client/src/modules/labrinth/payouts/internal.ts b/packages/api-client/src/modules/labrinth/payouts/internal.ts index aaf6ba161b..b463572b5e 100644 --- a/packages/api-client/src/modules/labrinth/payouts/internal.ts +++ b/packages/api-client/src/modules/labrinth/payouts/internal.ts @@ -1,6 +1,207 @@ import { AbstractModule } from '../../../core/abstract-module' import type { Labrinth } from '../types' +const mockHistory: Labrinth.Payouts.Internal.HistoryItem[] = [ + { + payouts_date: '2025-04', + days: [{ estimated_revenue_usd: 24_150 }], + status: 'open', + fees_deducted_usd: 1_440, + variance_adjustment_usd: -2_650, + net_estimated_revenue_usd: 20_060, + creator_net_estimated_revenue_usd: 15_045, + modrinth_net_estimated_revenue_usd: 5_015, + started_at: null, + started_by: null, + detailed_external_adjustments: null, + }, + { + payouts_date: '2025-03', + days: [{ estimated_revenue_usd: 48_200 }], + status: 'pending', + fees_deducted_usd: 1_440, + variance_adjustment_usd: -2_650, + net_estimated_revenue_usd: 44_110, + creator_net_estimated_revenue_usd: 33_083, + modrinth_net_estimated_revenue_usd: 11_028, + started_at: null, + started_by: null, + detailed_external_adjustments: null, + }, + { + payouts_date: '2025-02', + days: [{ estimated_revenue_usd: 45_500 }], + status: 'pending', + fees_deducted_usd: 1_312, + variance_adjustment_usd: -2_500, + net_estimated_revenue_usd: 41_688, + creator_net_estimated_revenue_usd: 31_266, + modrinth_net_estimated_revenue_usd: 10_422, + started_at: null, + started_by: null, + detailed_external_adjustments: null, + }, + { + payouts_date: '2025-01', + days: [{ estimated_revenue_usd: 42_000 }], + status: 'review', + fees_deducted_usd: 1_200, + variance_adjustment_usd: -2_100, + net_estimated_revenue_usd: 38_700, + creator_net_estimated_revenue_usd: 29_025, + modrinth_net_estimated_revenue_usd: 9_675, + started_at: null, + started_by: null, + detailed_external_adjustments: null, + }, + { + payouts_date: '2024-12', + days: [{ estimated_revenue_usd: 51_000 }], + status: 'paid', + fees_deducted_usd: 1_520, + variance_adjustment_usd: -2_800, + net_estimated_revenue_usd: 46_680, + creator_net_estimated_revenue_usd: 35_010, + modrinth_net_estimated_revenue_usd: 11_670, + actual_revenue_usd: 48_800, + total_external_adjustment_usd: 0, + net_actual_revenue_usd: 48_800, + creator_net_actual_revenue_usd: 36_600, + modrinth_net_actual_revenue_usd: 12_200, + started_at: '2025-03-16T21:10:00.000Z', + started_by: 'mock-admin-user', + detailed_external_adjustments: [], + }, + { + payouts_date: '2024-11', + days: [{ estimated_revenue_usd: 49_500 }], + status: 'paid', + fees_deducted_usd: 1_472, + variance_adjustment_usd: -2_700, + net_estimated_revenue_usd: 45_328, + creator_net_estimated_revenue_usd: 33_996, + modrinth_net_estimated_revenue_usd: 11_332, + actual_revenue_usd: 47_500, + total_external_adjustment_usd: 0, + net_actual_revenue_usd: 47_500, + creator_net_actual_revenue_usd: 35_625, + modrinth_net_actual_revenue_usd: 11_875, + started_at: '2025-02-14T19:45:00.000Z', + started_by: 'mock-admin-user', + detailed_external_adjustments: [], + }, + { + payouts_date: '2024-10', + days: [{ estimated_revenue_usd: 46_000 }], + status: 'paid', + fees_deducted_usd: 1_360, + variance_adjustment_usd: -2_500, + net_estimated_revenue_usd: 42_140, + creator_net_estimated_revenue_usd: 31_605, + modrinth_net_estimated_revenue_usd: 10_535, + actual_revenue_usd: 44_000, + total_external_adjustment_usd: 0, + net_actual_revenue_usd: 44_000, + creator_net_actual_revenue_usd: 33_000, + modrinth_net_actual_revenue_usd: 11_000, + started_at: '2025-01-15T20:25:00.000Z', + started_by: 'mock-admin-user', + detailed_external_adjustments: [], + }, + { + payouts_date: '2024-09', + days: [{ estimated_revenue_usd: 44_000 }], + status: 'paid', + fees_deducted_usd: 1_280, + variance_adjustment_usd: -2_400, + net_estimated_revenue_usd: 40_320, + creator_net_estimated_revenue_usd: 30_240, + modrinth_net_estimated_revenue_usd: 10_080, + actual_revenue_usd: 42_000, + total_external_adjustment_usd: 0, + net_actual_revenue_usd: 42_000, + creator_net_actual_revenue_usd: 31_500, + modrinth_net_actual_revenue_usd: 10_500, + started_at: '2024-12-14T18:20:00.000Z', + started_by: 'mock-admin-user', + detailed_external_adjustments: [], + }, + { + payouts_date: '2024-08', + days: [{ estimated_revenue_usd: 43_500 }], + status: 'paid', + fees_deducted_usd: 1_264, + variance_adjustment_usd: -2_350, + net_estimated_revenue_usd: 39_886, + creator_net_estimated_revenue_usd: 29_915, + modrinth_net_estimated_revenue_usd: 9_972, + actual_revenue_usd: 39_800, + total_external_adjustment_usd: 0, + net_actual_revenue_usd: 39_800, + creator_net_actual_revenue_usd: 29_850, + modrinth_net_actual_revenue_usd: 9_950, + started_at: '2024-11-15T17:30:00.000Z', + started_by: 'mock-admin-user', + detailed_external_adjustments: [], + }, + { + payouts_date: '2024-07', + days: [{ estimated_revenue_usd: 41_000 }], + status: 'paid', + fees_deducted_usd: 1_200, + variance_adjustment_usd: -2_200, + net_estimated_revenue_usd: 37_600, + creator_net_estimated_revenue_usd: 28_200, + modrinth_net_estimated_revenue_usd: 9_400, + actual_revenue_usd: 39_200, + total_external_adjustment_usd: 0, + net_actual_revenue_usd: 39_200, + creator_net_actual_revenue_usd: 29_400, + modrinth_net_actual_revenue_usd: 9_800, + started_at: '2024-10-14T17:30:00.000Z', + started_by: 'mock-admin-user', + detailed_external_adjustments: [], + }, + { + payouts_date: '2024-06', + days: [{ estimated_revenue_usd: 39_500 }], + status: 'paid', + fees_deducted_usd: 1_152, + variance_adjustment_usd: -2_100, + net_estimated_revenue_usd: 36_248, + creator_net_estimated_revenue_usd: 27_186, + modrinth_net_estimated_revenue_usd: 9_062, + actual_revenue_usd: 37_800, + total_external_adjustment_usd: 0, + net_actual_revenue_usd: 37_800, + creator_net_actual_revenue_usd: 28_350, + modrinth_net_actual_revenue_usd: 9_450, + started_at: '2024-09-14T17:30:00.000Z', + started_by: 'mock-admin-user', + detailed_external_adjustments: [], + }, + { + payouts_date: '2024-05', + days: [{ estimated_revenue_usd: 38_000 }], + status: 'paid', + fees_deducted_usd: 1_120, + variance_adjustment_usd: -2_000, + net_estimated_revenue_usd: 34_880, + creator_net_estimated_revenue_usd: 26_160, + modrinth_net_estimated_revenue_usd: 8_720, + actual_revenue_usd: 36_200, + total_external_adjustment_usd: 0, + net_actual_revenue_usd: 36_200, + creator_net_actual_revenue_usd: 27_150, + modrinth_net_actual_revenue_usd: 9_050, + started_at: '2024-08-15T17:30:00.000Z', + started_by: 'mock-admin-user', + detailed_external_adjustments: [], + }, +] + +let mockDistribution: Labrinth.Payouts.Internal.DistributionRun | null = null + export class LabrinthPayoutsInternalModule extends AbstractModule { public getModuleID(): string { return 'labrinth_payouts_internal' @@ -11,11 +212,13 @@ export class LabrinthPayoutsInternalModule extends AbstractModule { * GET /_internal/payouts/history */ public async getHistory(): Promise { - return this.client.request('/payouts/history', { - api: 'labrinth', - version: 'internal', - method: 'GET', - }) + return getMockHistory() + + // return this.client.request('/payouts/history', { + // api: 'labrinth', + // version: 'internal', + // method: 'GET', + // }) } /** @@ -23,14 +226,16 @@ export class LabrinthPayoutsInternalModule extends AbstractModule { * GET /_internal/payouts/distribution */ public async getDistribution(): Promise { - return this.client.request( - '/payouts/distribution', - { - api: 'labrinth', - version: 'internal', - method: 'GET', - }, - ) + return mockDistribution + + // return this.client.request( + // '/payouts/distribution', + // { + // api: 'labrinth', + // version: 'internal', + // method: 'GET', + // }, + // ) } /** @@ -40,15 +245,27 @@ export class LabrinthPayoutsInternalModule extends AbstractModule { public async startDistribution( data: Labrinth.Payouts.Internal.StartDistributionRequest, ): Promise { - return this.client.request( - '/payouts/distribution/start', - { - api: 'labrinth', - version: 'internal', - method: 'POST', - body: data, - }, - ) + const startedAt = new Date() + mockDistribution = { + payouts_date: data.payouts_date, + amount_received: data.amount_received, + adjustments: data.adjustments, + started_at: startedAt.toISOString(), + started_by: 'mock-admin-user', + distributes_at: new Date(startedAt.getTime() + 2 * 60 * 1000).toISOString(), + } + + return mockDistribution + + // return this.client.request( + // '/payouts/distribution/start', + // { + // api: 'labrinth', + // version: 'internal', + // method: 'POST', + // body: data, + // }, + // ) } /** @@ -56,10 +273,33 @@ export class LabrinthPayoutsInternalModule extends AbstractModule { * POST /_internal/payouts/distribution/cancel */ public async cancelDistribution(): Promise { - return this.client.request('/payouts/distribution/cancel', { - api: 'labrinth', - version: 'internal', - method: 'POST', - }) + mockDistribution = null + + // return this.client.request('/payouts/distribution/cancel', { + // api: 'labrinth', + // version: 'internal', + // method: 'POST', + // }) } } + +function getMockHistory(): Labrinth.Payouts.Internal.HistoryItem[] { + if (!mockDistribution) { + return mockHistory + } + + const activeDistribution = mockDistribution + return mockHistory.map((payout) => + payout.payouts_date === activeDistribution.payouts_date + ? { + ...payout, + started_at: activeDistribution.started_at, + started_by: activeDistribution.started_by, + detailed_external_adjustments: activeDistribution.adjustments.map((adjustment) => ({ + description: adjustment.description, + amount_usd: adjustment.amount, + })), + } + : payout, + ) +} From c047d2d348a11ca04fc55073a9b226aaf7c90119 Mon Sep 17 00:00:00 2001 From: tdgao Date: Mon, 8 Jun 2026 20:01:39 -0700 Subject: [PATCH 03/13] fix: styles --- .../ui/creator-payouts/distribute-month-card/index.vue | 6 +++--- .../ui/creator-payouts/payouts-table/index.vue | 10 ++++++---- .../frontend/src/pages/admin/creator-payouts/index.vue | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/frontend/src/components/ui/creator-payouts/distribute-month-card/index.vue b/apps/frontend/src/components/ui/creator-payouts/distribute-month-card/index.vue index 66b8a371a0..aa6d73664a 100644 --- a/apps/frontend/src/components/ui/creator-payouts/distribute-month-card/index.vue +++ b/apps/frontend/src/components/ui/creator-payouts/distribute-month-card/index.vue @@ -1,13 +1,13 @@