mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
refactor(credits): simplify credit system — remove productivity credits, guild pools, complex gift types
The credit system was overengineered for the local-first architecture: - Productivity micro-credits (task/event/contact creation at 0.02 credits) made no sense since these operations happen locally in IndexedDB with zero server cost and were never enforced - Guild pool system (6 DB tables, spending limits, membership checks) had no active users - Gift system had 5 types (simple/personalized/split/first_come/riddle) when 2 suffice Now credits are only charged for operations that actually cost money: AI API calls and premium features (sync, exports). This makes the value proposition clear to users. Changes: - Remove 8 productivity operations + CreditCategory.PRODUCTIVITY from @mana/credits - Delete guild pool service, routes, schema (3 files); remove guild refs from 8 backend files - Simplify gifts to simple + personalized only; remove bcrypt/riddle/portions logic - Update all frontend pages (credits dashboard, gift create/redeem, public gift page) - Update shared-hono consumeCredits() to remove creditSource parameter - Update mana-credits CLAUDE.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
29ad31c4ed
commit
e068335dd4
32 changed files with 143 additions and 922 deletions
|
|
@ -23,7 +23,6 @@ export {
|
|||
getOperationsByCategory,
|
||||
calculateBulkCost,
|
||||
isFreeOperation,
|
||||
isMicroCreditOperation,
|
||||
isAiOperation,
|
||||
formatCreditCost,
|
||||
getPricingTable,
|
||||
|
|
|
|||
|
|
@ -58,28 +58,6 @@ export enum CreditOperationType {
|
|||
AI_SUGGESTIONS = 'ai_suggestions',
|
||||
AI_ENRICHMENT = 'ai_enrichment',
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Productivity Operations (Micro Credits: 0.01-0.10)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Todo
|
||||
TASK_CREATE = 'task_create',
|
||||
PROJECT_CREATE = 'project_create',
|
||||
|
||||
// Calendar
|
||||
EVENT_CREATE = 'event_create',
|
||||
CALENDAR_CREATE = 'calendar_create',
|
||||
|
||||
// Contacts
|
||||
CONTACT_CREATE = 'contact_create',
|
||||
|
||||
// Zitare
|
||||
COLLECTION_CREATE = 'collection_create',
|
||||
|
||||
// Presi
|
||||
PRESENTATION_CREATE = 'presentation_create',
|
||||
SLIDE_CREATE = 'slide_create',
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Premium Features (Standard Credits: 0.5-5)
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
@ -134,20 +112,6 @@ export const CREDIT_COSTS: Record<CreditOperationType, number> = {
|
|||
[CreditOperationType.AI_SUGGESTIONS]: 2,
|
||||
[CreditOperationType.AI_ENRICHMENT]: 2,
|
||||
|
||||
// Productivity Operations (Micro Credits)
|
||||
[CreditOperationType.TASK_CREATE]: 0.02,
|
||||
[CreditOperationType.PROJECT_CREATE]: 0.1,
|
||||
|
||||
[CreditOperationType.EVENT_CREATE]: 0.02,
|
||||
[CreditOperationType.CALENDAR_CREATE]: 0.1,
|
||||
|
||||
[CreditOperationType.CONTACT_CREATE]: 0.02,
|
||||
|
||||
[CreditOperationType.COLLECTION_CREATE]: 0.1,
|
||||
|
||||
[CreditOperationType.PRESENTATION_CREATE]: 0.5,
|
||||
[CreditOperationType.SLIDE_CREATE]: 0.02,
|
||||
|
||||
// Premium Features
|
||||
[CreditOperationType.CALDAV_SYNC]: 0.5,
|
||||
[CreditOperationType.GOOGLE_SYNC]: 0.5,
|
||||
|
|
@ -328,64 +292,6 @@ export const OPERATION_METADATA: Record<CreditOperationType, OperationMetadata>
|
|||
app: 'contacts',
|
||||
},
|
||||
|
||||
// Productivity - Todo
|
||||
[CreditOperationType.TASK_CREATE]: {
|
||||
name: 'Create Task',
|
||||
description: 'Create a new task',
|
||||
category: CreditCategory.PRODUCTIVITY,
|
||||
app: 'todo',
|
||||
},
|
||||
[CreditOperationType.PROJECT_CREATE]: {
|
||||
name: 'Create Project',
|
||||
description: 'Create a new project',
|
||||
category: CreditCategory.PRODUCTIVITY,
|
||||
app: 'todo',
|
||||
},
|
||||
|
||||
// Productivity - Calendar
|
||||
[CreditOperationType.EVENT_CREATE]: {
|
||||
name: 'Create Event',
|
||||
description: 'Create a calendar event',
|
||||
category: CreditCategory.PRODUCTIVITY,
|
||||
app: 'calendar',
|
||||
},
|
||||
[CreditOperationType.CALENDAR_CREATE]: {
|
||||
name: 'Create Calendar',
|
||||
description: 'Create a new calendar',
|
||||
category: CreditCategory.PRODUCTIVITY,
|
||||
app: 'calendar',
|
||||
},
|
||||
|
||||
// Productivity - Contacts
|
||||
[CreditOperationType.CONTACT_CREATE]: {
|
||||
name: 'Create Contact',
|
||||
description: 'Create a new contact',
|
||||
category: CreditCategory.PRODUCTIVITY,
|
||||
app: 'contacts',
|
||||
},
|
||||
|
||||
// Productivity - Zitare
|
||||
[CreditOperationType.COLLECTION_CREATE]: {
|
||||
name: 'Create Collection',
|
||||
description: 'Create a quote collection',
|
||||
category: CreditCategory.PRODUCTIVITY,
|
||||
app: 'zitare',
|
||||
},
|
||||
|
||||
// Productivity - Presi
|
||||
[CreditOperationType.PRESENTATION_CREATE]: {
|
||||
name: 'Create Presentation',
|
||||
description: 'Create a new presentation',
|
||||
category: CreditCategory.PRODUCTIVITY,
|
||||
app: 'presi',
|
||||
},
|
||||
[CreditOperationType.SLIDE_CREATE]: {
|
||||
name: 'Create Slide',
|
||||
description: 'Add a slide to a presentation',
|
||||
category: CreditCategory.PRODUCTIVITY,
|
||||
app: 'presi',
|
||||
},
|
||||
|
||||
// Premium - Sync
|
||||
[CreditOperationType.CALDAV_SYNC]: {
|
||||
name: 'CalDAV Sync',
|
||||
|
|
@ -502,16 +408,6 @@ export function isFreeOperation(operation: CreditOperationType): boolean {
|
|||
return CREDIT_COSTS[operation] === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an operation is a micro-credit operation (< 0.5 credits).
|
||||
* @param operation The operation type
|
||||
* @returns True if micro-credit operation
|
||||
*/
|
||||
export function isMicroCreditOperation(operation: CreditOperationType): boolean {
|
||||
const cost = CREDIT_COSTS[operation];
|
||||
return cost > 0 && cost < 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an operation is an AI operation.
|
||||
* @param operation The operation type
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
getPricingTable,
|
||||
CreditCategory,
|
||||
type CreditOperationType,
|
||||
} from './operations';
|
||||
import { getPricingTable, CreditCategory, type CreditOperationType } from './operations';
|
||||
|
||||
interface Props {
|
||||
/** The app to show pricing for (e.g., 'todo', 'chat', 'calendar') */
|
||||
|
|
@ -19,7 +15,6 @@
|
|||
costLabel?: string;
|
||||
freeLabel?: string;
|
||||
aiLabel?: string;
|
||||
productivityLabel?: string;
|
||||
premiumLabel?: string;
|
||||
creditsLabel?: string;
|
||||
}
|
||||
|
|
@ -33,7 +28,6 @@
|
|||
costLabel = 'Cost',
|
||||
freeLabel = 'Free',
|
||||
aiLabel = 'AI Features',
|
||||
productivityLabel = 'Create',
|
||||
premiumLabel = 'Premium',
|
||||
creditsLabel = 'Credits',
|
||||
}: Props = $props();
|
||||
|
|
@ -62,8 +56,6 @@
|
|||
switch (category) {
|
||||
case CreditCategory.AI:
|
||||
return aiLabel;
|
||||
case CreditCategory.PRODUCTIVITY:
|
||||
return productivityLabel;
|
||||
case CreditCategory.PREMIUM:
|
||||
return premiumLabel;
|
||||
default:
|
||||
|
|
@ -75,8 +67,6 @@
|
|||
switch (category) {
|
||||
case CreditCategory.AI:
|
||||
return 'M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z';
|
||||
case CreditCategory.PRODUCTIVITY:
|
||||
return 'M12 4.5v15m7.5-7.5h-15';
|
||||
case CreditCategory.PREMIUM:
|
||||
return 'M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z';
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prepare": "tsc",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
|
|
|
|||
|
|
@ -85,8 +85,7 @@ export async function consumeCredits(
|
|||
operation: string,
|
||||
amount: number,
|
||||
description: string,
|
||||
metadata?: Record<string, unknown>,
|
||||
creditSource?: { type: 'guild'; guildId: string }
|
||||
metadata?: Record<string, unknown>
|
||||
): Promise<boolean> {
|
||||
const result = await callCredits('/api/v1/internal/credits/use', {
|
||||
method: 'POST',
|
||||
|
|
@ -96,7 +95,6 @@ export async function consumeCredits(
|
|||
appId: APP_ID(),
|
||||
description,
|
||||
metadata: { operation, ...metadata },
|
||||
...(creditSource && { creditSource }),
|
||||
}),
|
||||
});
|
||||
return !!result;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ export type DragType =
|
|||
| 'note'
|
||||
| 'transaction'
|
||||
| 'place'
|
||||
| 'dream';
|
||||
| 'dream'
|
||||
| 'journal-entry';
|
||||
|
||||
export interface DragPayload<T = Record<string, unknown>> {
|
||||
type: DragType;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue