feat: 🎸 toast to alert of any unmigrated prices
This commit is contained in:
220
shared/index.ts
220
shared/index.ts
@@ -4,153 +4,129 @@ export { schemas };
|
||||
|
||||
// Auth Models
|
||||
export * from "./db/auth-schema.js";
|
||||
export * from "./enums/APIVersion.js";
|
||||
export * from "./enums/AttachErrCode.js";
|
||||
export * from "./enums/ErrCode.js";
|
||||
export * from "./enums/LoggerAction.js";
|
||||
// ENUMS
|
||||
export * from "./enums/SuccessCode.js";
|
||||
export * from "./enums/WebhookEventType.js";
|
||||
// ANALYTICS MODELS
|
||||
export * from "./models/analyticsModels/actionEnums.js";
|
||||
export * from "./models/analyticsModels/actionTable.js";
|
||||
export * from "./models/attachModels/attachBody.js";
|
||||
export * from "./models/attachModels/attachEnums/AttachBranch.js";
|
||||
export * from "./models/attachModels/attachEnums/AttachConfig.js";
|
||||
export * from "./models/attachModels/attachEnums/AttachFunction.js";
|
||||
// Attach Models
|
||||
export * from "./models/attachModels/attachPreviewModels.js";
|
||||
export * from "./models/attachModels/checkoutModels.js";
|
||||
export * from "./models/authModels/membership.js";
|
||||
|
||||
// Gen Models
|
||||
export * from "./models/genModels/genEnums.js";
|
||||
|
||||
// 1. Org Models
|
||||
export * from "./models/orgModels/orgTable.js";
|
||||
export * from "./models/orgModels/orgConfig.js";
|
||||
export * from "./models/orgModels/frontendOrg.js";
|
||||
|
||||
// 2. Feature Models
|
||||
export * from "./models/featureModels/featureTable.js";
|
||||
export * from "./models/chatResultModels/chatResultFeature.js";
|
||||
export * from "./models/chatResultModels/chatResultFeature.js";
|
||||
// 4. Chat Result Models
|
||||
export * from "./models/chatResultModels/chatResultTable.js";
|
||||
export * from "./models/checkModels/checkPreviewModels.js";
|
||||
export * from "./models/cusModels/cusExpand.js";
|
||||
// 8. Customer Models
|
||||
export * from "./models/cusModels/cusModels.js";
|
||||
export * from "./models/cusModels/cusResModels/cusFeatureResponse.js";
|
||||
export * from "./models/cusModels/cusResModels/cusProductResponse.js";
|
||||
export * from "./models/cusModels/cusResModels/cusReferralsResponse.js";
|
||||
// Cus response
|
||||
export * from "./models/cusModels/cusResponseModels.js";
|
||||
export * from "./models/cusModels/cusTable.js";
|
||||
export * from "./models/cusModels/entityModels/entityExpand.js";
|
||||
export * from "./models/cusModels/entityModels/entityModels.js";
|
||||
export * from "./models/cusModels/entityModels/entityResModels.js";
|
||||
export * from "./models/cusModels/entityModels/entityTable.js";
|
||||
export * from "./models/cusModels/fullCusModel.js";
|
||||
export * from "./models/cusModels/invoiceModels/invoiceModels.js";
|
||||
export * from "./models/cusModels/invoiceModels/invoiceResponseModels.js";
|
||||
export * from "./models/cusModels/invoiceModels/invoiceTable.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/cusEntModels.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/cusEntTable.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/cusEntWithProduct.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/replaceableSchema.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/replaceableTable.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/resetCusEnt.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/rolloverModels/rolloverTable.js";
|
||||
export * from "./models/cusProductModels/cusPriceModels/cusPriceModels.js";
|
||||
export * from "./models/cusProductModels/cusPriceModels/cusPriceTable.js";
|
||||
export * from "./models/cusProductModels/cusProductEnums.js";
|
||||
// 7. Cus Product Models
|
||||
export * from "./models/cusProductModels/cusProductModels.js";
|
||||
export * from "./models/cusProductModels/cusProductTable.js";
|
||||
export * from "./models/devModels/apiKeyModels.js";
|
||||
export * from "./models/devModels/apiKeyTable.js";
|
||||
// 5. Others: events, apiKeys
|
||||
export * from "./models/eventModels/eventModels.js";
|
||||
export * from "./models/eventModels/eventTable.js";
|
||||
export * from "./models/featureModels/featureConfig/creditConfig.js";
|
||||
export * from "./models/featureModels/featureConfig/meteredConfig.js";
|
||||
export * from "./models/featureModels/featureEnums.js";
|
||||
export * from "./models/featureModels/featureModels.js";
|
||||
export * from "./models/featureModels/featureResModels.js";
|
||||
export * from "./models/featureModels/featureConfig/meteredConfig.js";
|
||||
export * from "./models/featureModels/featureConfig/creditConfig.js";
|
||||
|
||||
// 2. Feature Models
|
||||
export * from "./models/featureModels/featureTable.js";
|
||||
// Gen Models
|
||||
export * from "./models/genModels/genEnums.js";
|
||||
export * from "./models/migrationModels/migrationErrorTable.js";
|
||||
export * from "./models/migrationModels/migrationJobTable.js";
|
||||
export * from "./models/migrationModels/migrationModels.js";
|
||||
export * from "./models/orgModels/frontendOrg.js";
|
||||
export * from "./models/orgModels/orgConfig.js";
|
||||
// 1. Org Models
|
||||
export * from "./models/orgModels/orgTable.js";
|
||||
export * from "./models/otherModels/metadataModels.js";
|
||||
export * from "./models/otherModels/metadataTable.js";
|
||||
export * from "./models/productModels/entModels/entEnums.js";
|
||||
export * from "./models/productModels/entModels/entModels.js";
|
||||
// 3. Entitlement Models
|
||||
export * from "./models/productModels/entModels/entTable.js";
|
||||
export * from "./models/productModels/entModels/entModels.js";
|
||||
export * from "./models/productModels/entModels/entEnums.js";
|
||||
|
||||
// 4. Free Trial Models
|
||||
export * from "./models/productModels/freeTrialModels/freeTrialEnums.js";
|
||||
export * from "./models/productModels/freeTrialModels/freeTrialModels.js";
|
||||
export * from "./models/productModels/freeTrialModels/freeTrialTable.js";
|
||||
|
||||
// 4. Price Models
|
||||
export * from "./models/productModels/priceModels/priceEnums.js";
|
||||
export * from "./models/productModels/priceModels/priceConfig/fixedPriceConfig.js";
|
||||
export * from "./models/productModels/priceModels/priceConfig/usagePriceConfig.js";
|
||||
export * from "./models/productModels/priceModels/priceTable.js";
|
||||
// 4. Price Models
|
||||
export * from "./models/productModels/priceModels/priceEnums.js";
|
||||
export * from "./models/productModels/priceModels/priceModels.js";
|
||||
|
||||
export * from "./models/productModels/priceModels/priceTable.js";
|
||||
// 5. Product Models
|
||||
export * from "./models/productModels/productEnums.js";
|
||||
export * from "./models/productModels/productTable.js";
|
||||
export * from "./models/productModels/productModels.js";
|
||||
export * from "./models/productModels/productRelations.js";
|
||||
|
||||
// 6. Product V2 Models
|
||||
export * from "./models/productV2Models/productV2Models.js";
|
||||
export * from "./models/productV2Models/productResponseModels.js";
|
||||
export * from "./models/productV2Models/productItemModels/productItemModels.js";
|
||||
export * from "./models/productV2Models/productItemModels/prodItemResponseModels.js";
|
||||
export * from "./models/productModels/productTable.js";
|
||||
export * from "./models/productV2Models/productItemModels/featureItem.js";
|
||||
export * from "./models/productV2Models/productItemModels/featurePriceItem.js";
|
||||
export * from "./models/productV2Models/productItemModels/priceItem.js";
|
||||
export * from "./models/productV2Models/productItemModels/prodItemResponseModels.js";
|
||||
export * from "./models/productV2Models/productItemModels/productItemEnums.js";
|
||||
|
||||
// 7. Cus Product Models
|
||||
export * from "./models/cusProductModels/cusProductModels.js";
|
||||
export * from "./models/cusProductModels/cusProductTable.js";
|
||||
export * from "./models/cusProductModels/cusProductEnums.js";
|
||||
export * from "./models/cusProductModels/cusPriceModels/cusPriceModels.js";
|
||||
export * from "./models/cusProductModels/cusPriceModels/cusPriceTable.js";
|
||||
|
||||
export * from "./models/cusProductModels/cusEntModels/cusEntModels.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/cusEntWithProduct.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/cusEntTable.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/replaceableTable.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/replaceableSchema.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/rolloverModels/rolloverTable.js";
|
||||
export * from "./models/cusProductModels/cusEntModels/resetCusEnt.js";
|
||||
|
||||
// 8. Customer Models
|
||||
export * from "./models/cusModels/cusModels.js";
|
||||
export * from "./models/cusModels/cusTable.js";
|
||||
export * from "./models/cusModels/fullCusModel.js";
|
||||
export * from "./models/cusModels/cusExpand.js";
|
||||
export * from "./models/cusModels/invoiceModels/invoiceResponseModels.js";
|
||||
export * from "./models/cusModels/invoiceModels/invoiceTable.js";
|
||||
// Cus response
|
||||
export * from "./models/cusModels/cusResponseModels.js";
|
||||
export * from "./models/cusModels/cusResModels/cusProductResponse.js";
|
||||
export * from "./models/cusModels/cusResModels/cusFeatureResponse.js";
|
||||
export * from "./models/cusModels/cusResModels/cusReferralsResponse.js";
|
||||
|
||||
export * from "./models/cusModels/entityModels/entityModels.js";
|
||||
export * from "./models/cusModels/entityModels/entityTable.js";
|
||||
export * from "./models/cusModels/entityModels/entityExpand.js";
|
||||
export * from "./models/cusModels/entityModels/entityResModels.js";
|
||||
|
||||
// 4. Chat Result Models
|
||||
export * from "./models/chatResultModels/chatResultTable.js";
|
||||
export * from "./models/chatResultModels/chatResultFeature.js";
|
||||
|
||||
// Reward Models
|
||||
export * from "./models/rewardModels/rewardModels/rewardModels.js";
|
||||
export * from "./models/rewardModels/rewardModels/rewardEnums.js";
|
||||
export * from "./models/rewardModels/rewardModels/rewardTable.js";
|
||||
export * from "./models/rewardModels/rewardModels/rewardResponseModels.js";
|
||||
|
||||
export * from "./models/rewardModels/rewardProgramModels/rewardProgramModels.js";
|
||||
export * from "./models/rewardModels/rewardProgramModels/rewardProgramEnums.js";
|
||||
export * from "./models/rewardModels/rewardProgramModels/rewardProgramTable.js";
|
||||
export * from "./models/productV2Models/productItemModels/productItemModels.js";
|
||||
export * from "./models/productV2Models/productResponseModels.js";
|
||||
// 6. Product V2 Models
|
||||
export * from "./models/productV2Models/productV2Models.js";
|
||||
export * from "./models/rewardModels/referralModels/referralCodeTable.js";
|
||||
export * from "./models/rewardModels/referralModels/referralModels.js";
|
||||
export * from "./models/rewardModels/referralModels/rewardRedemptionTable.js";
|
||||
export * from "./models/rewardModels/referralModels/referralCodeTable.js";
|
||||
|
||||
// 5. Others: events, apiKeys
|
||||
export * from "./models/eventModels/eventModels.js";
|
||||
export * from "./models/eventModels/eventTable.js";
|
||||
|
||||
export * from "./models/devModels/apiKeyModels.js";
|
||||
export * from "./models/devModels/apiKeyTable.js";
|
||||
|
||||
export * from "./models/otherModels/metadataModels.js";
|
||||
export * from "./models/otherModels/metadataTable.js";
|
||||
|
||||
export * from "./models/rewardModels/rewardModels/rewardEnums.js";
|
||||
// Reward Models
|
||||
export * from "./models/rewardModels/rewardModels/rewardModels.js";
|
||||
export * from "./models/rewardModels/rewardModels/rewardResponseModels.js";
|
||||
export * from "./models/rewardModels/rewardModels/rewardTable.js";
|
||||
export * from "./models/rewardModels/rewardProgramModels/rewardProgramEnums.js";
|
||||
export * from "./models/rewardModels/rewardProgramModels/rewardProgramModels.js";
|
||||
export * from "./models/rewardModels/rewardProgramModels/rewardProgramTable.js";
|
||||
export * from "./models/subModels/subModels.js";
|
||||
export * from "./models/subModels/subTable.js";
|
||||
|
||||
export * from "./models/cusModels/invoiceModels/invoiceModels.js";
|
||||
|
||||
export * from "./models/migrationModels/migrationModels.js";
|
||||
export * from "./models/migrationModels/migrationJobTable.js";
|
||||
export * from "./models/migrationModels/migrationErrorTable.js";
|
||||
|
||||
// ANALYTICS MODELS
|
||||
export * from "./models/analyticsModels/actionEnums.js";
|
||||
export * from "./models/analyticsModels/actionTable.js";
|
||||
|
||||
// Attach Models
|
||||
export * from "./models/attachModels/attachPreviewModels.js";
|
||||
export * from "./models/attachModels/attachEnums/AttachBranch.js";
|
||||
export * from "./models/attachModels/attachEnums/AttachFunction.js";
|
||||
export * from "./models/attachModels/attachEnums/AttachConfig.js";
|
||||
export * from "./models/attachModels/checkoutModels.js";
|
||||
export * from "./models/attachModels/attachBody.js";
|
||||
|
||||
// Utils
|
||||
export * from "./utils/displayUtils.js";
|
||||
export * from "./models/checkModels/checkPreviewModels.js";
|
||||
export * from "./models/chatResultModels/chatResultFeature.js";
|
||||
export * from "./utils/productDisplayUtils/getProductItemRes.js";
|
||||
export * from "./utils/productUtils.js";
|
||||
export * from "./utils/productDisplayUtils/sortProductItems.js";
|
||||
export * from "./utils/intervalUtils.js";
|
||||
export * from "./utils/productUtils/priceToInvoiceAmount.js";
|
||||
export * from "./utils/index.js";
|
||||
|
||||
// ENUMS
|
||||
export * from "./enums/SuccessCode.js";
|
||||
export * from "./enums/ErrCode.js";
|
||||
export * from "./enums/LoggerAction.js";
|
||||
export * from "./enums/AttachErrCode.js";
|
||||
export * from "./enums/APIVersion.js";
|
||||
export * from "./enums/WebhookEventType.js";
|
||||
export * from "./utils/intervalUtils.js";
|
||||
export * from "./utils/productDisplayUtils/getProductItemRes.js";
|
||||
export * from "./utils/productDisplayUtils/sortProductItems.js";
|
||||
export * from "./utils/productUtils/priceToInvoiceAmount.js";
|
||||
export * from "./utils/productUtils.js";
|
||||
export * from "./utils/rewardUtils/rewardMigrationUtils.js";
|
||||
@@ -13,6 +13,8 @@ export const UsageTierSchema = z.object({
|
||||
amount: z.number(),
|
||||
});
|
||||
|
||||
export type UsageTier = z.infer<typeof UsageTierSchema>;
|
||||
|
||||
export const UsagePriceConfigSchema = z.object({
|
||||
type: z.string(),
|
||||
bill_when: z.nativeEnum(BillWhen),
|
||||
|
||||
177
shared/utils/rewardUtils/rewardMigrationUtils.ts
Normal file
177
shared/utils/rewardUtils/rewardMigrationUtils.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import type {
|
||||
FixedPriceConfig,
|
||||
Price,
|
||||
Reward,
|
||||
RewardType,
|
||||
UsagePriceConfig,
|
||||
} from "../../index.js";
|
||||
import type { UsageTier } from "../../models/productModels/priceModels/priceConfig/usagePriceConfig.js";
|
||||
import { isFixedPrice, isUsagePrice } from "../productUtils/priceUtils.js";
|
||||
|
||||
// Helper function to check if tier structures match
|
||||
const tiersMatch = (oldTiers: UsageTier[], newTiers: UsageTier[]): boolean => {
|
||||
if (oldTiers.length !== newTiers.length) return false;
|
||||
|
||||
return oldTiers.every((oldTier, index) => {
|
||||
const newTier = newTiers[index];
|
||||
return oldTier.to === newTier.to && oldTier.amount === newTier.amount;
|
||||
});
|
||||
};
|
||||
|
||||
// Match fixed prices by amount
|
||||
const findMatchingFixedPrice = (
|
||||
oldPrice: Price,
|
||||
candidates: Price[],
|
||||
): Price | null => {
|
||||
const oldConfig = oldPrice.config as FixedPriceConfig;
|
||||
|
||||
return (
|
||||
candidates.find((candidate) => {
|
||||
const newConfig = candidate.config as FixedPriceConfig;
|
||||
return newConfig.amount === oldConfig.amount;
|
||||
}) || null
|
||||
);
|
||||
};
|
||||
|
||||
// Match usage prices by feature and billing characteristics
|
||||
const findMatchingUsagePrice = (
|
||||
oldPrice: Price,
|
||||
candidates: Price[],
|
||||
): Price | null => {
|
||||
const oldConfig = oldPrice.config as UsagePriceConfig;
|
||||
|
||||
return (
|
||||
candidates.find((candidate) => {
|
||||
const newConfig = candidate.config as UsagePriceConfig;
|
||||
|
||||
// Match by feature
|
||||
if (newConfig.feature_id !== oldConfig.feature_id) return false;
|
||||
if (newConfig.internal_feature_id !== oldConfig.internal_feature_id)
|
||||
return false;
|
||||
|
||||
// Match by billing behavior
|
||||
if (newConfig.bill_when !== oldConfig.bill_when) return false;
|
||||
if (newConfig.should_prorate !== oldConfig.should_prorate) return false;
|
||||
|
||||
// Optionally match by tier structure
|
||||
if (!tiersMatch(oldConfig.usage_tiers, newConfig.usage_tiers))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}) || null
|
||||
);
|
||||
};
|
||||
|
||||
// Main matching function with type-specific logic
|
||||
const findBestMatch = (oldPrice: Price, newPrices: Price[]): Price | null => {
|
||||
// First, filter by basic characteristics
|
||||
const candidates = newPrices.filter(
|
||||
(newPrice) =>
|
||||
newPrice.config.type === oldPrice.config.type &&
|
||||
newPrice.config.interval === oldPrice.config.interval &&
|
||||
newPrice.config.interval_count === oldPrice.config.interval_count,
|
||||
);
|
||||
|
||||
if (candidates.length === 0) return null;
|
||||
if (candidates.length === 1) return candidates[0];
|
||||
|
||||
// If multiple candidates, use type-specific matching
|
||||
if (isFixedPrice({ price: oldPrice })) {
|
||||
return findMatchingFixedPrice(oldPrice, candidates);
|
||||
} else if (isUsagePrice({ price: oldPrice })) {
|
||||
return findMatchingUsagePrice(oldPrice, candidates);
|
||||
}
|
||||
|
||||
// Fallback to first candidate
|
||||
return candidates[0];
|
||||
};
|
||||
|
||||
export interface RewardMigrationResult {
|
||||
willMigrateCount: number;
|
||||
willNotMigrateCount: number;
|
||||
}
|
||||
|
||||
export interface RewardPriceAnalysisResult {
|
||||
validPriceCount: number;
|
||||
invalidPriceCount: number;
|
||||
}
|
||||
|
||||
export function analyzeRewardMigration({
|
||||
rewards,
|
||||
oldPrices,
|
||||
newPrices,
|
||||
rewardTypesToCheck,
|
||||
}: {
|
||||
rewards: Reward[];
|
||||
oldPrices: Price[];
|
||||
newPrices: Price[];
|
||||
rewardTypesToCheck: RewardType[];
|
||||
}): RewardMigrationResult {
|
||||
let willMigrateCount = 0;
|
||||
let willNotMigrateCount = 0;
|
||||
|
||||
// Filter rewards to only those we care about and that have discount configs with price_ids
|
||||
const relevantRewards = rewards.filter(
|
||||
(reward) =>
|
||||
rewardTypesToCheck.includes(reward.type) &&
|
||||
reward.discount_config?.price_ids &&
|
||||
reward.discount_config.price_ids.length > 0,
|
||||
);
|
||||
|
||||
for (const reward of relevantRewards) {
|
||||
if (!reward.discount_config?.price_ids) continue;
|
||||
|
||||
for (const priceId of reward.discount_config.price_ids) {
|
||||
const oldPrice = oldPrices.find((p) => p.id === priceId);
|
||||
if (!oldPrice) {
|
||||
// Price not in old prices list, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
const matchingNewPrice = findBestMatch(oldPrice, newPrices);
|
||||
|
||||
if (matchingNewPrice) {
|
||||
willMigrateCount++;
|
||||
} else {
|
||||
willNotMigrateCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
willMigrateCount,
|
||||
willNotMigrateCount,
|
||||
};
|
||||
}
|
||||
|
||||
export function analyzeRewardPrices({
|
||||
reward,
|
||||
availablePriceIds,
|
||||
}: {
|
||||
reward: Reward;
|
||||
availablePriceIds: string[];
|
||||
}): RewardPriceAnalysisResult {
|
||||
let validPriceCount = 0;
|
||||
let invalidPriceCount = 0;
|
||||
|
||||
// Skip rewards that apply to all products
|
||||
if (reward.discount_config?.apply_to_all) {
|
||||
return { validPriceCount: 0, invalidPriceCount: 0 };
|
||||
}
|
||||
|
||||
// Check each price ID in the reward
|
||||
if (reward.discount_config?.price_ids) {
|
||||
for (const priceId of reward.discount_config.price_ids) {
|
||||
if (availablePriceIds.includes(priceId)) {
|
||||
validPriceCount++;
|
||||
} else {
|
||||
invalidPriceCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
validPriceCount,
|
||||
invalidPriceCount,
|
||||
};
|
||||
}
|
||||
@@ -1,79 +1,128 @@
|
||||
import React, { useState } from "react";
|
||||
import type { ProductV2, Reward } from "@autumn/shared";
|
||||
import { analyzeRewardPrices } from "@autumn/shared";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { WarningBox } from "@/components/general/modal-components/WarningBox";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { toast } from "sonner";
|
||||
import { Reward } from "@autumn/shared";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { RewardService } from "@/services/products/RewardService";
|
||||
import { getBackendErr } from "@/utils/genUtils";
|
||||
import { WarningBox } from "@/components/general/modal-components/WarningBox";
|
||||
import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
|
||||
import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
|
||||
import { RewardService } from "@/services/products/RewardService";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { getBackendErr } from "@/utils/genUtils";
|
||||
import { RewardConfig } from "./RewardConfig";
|
||||
|
||||
const checkRewardMigration = (
|
||||
reward: Reward,
|
||||
products: ProductV2[],
|
||||
): { willMigrateCount: number; willNotMigrateCount: number } => {
|
||||
// Extract all available price IDs from current products
|
||||
const availablePriceIds: string[] = [];
|
||||
for (const product of products) {
|
||||
if (product.items) {
|
||||
for (const item of product.items) {
|
||||
if (item.price_id) {
|
||||
availablePriceIds.push(item.price_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use the shared utility to analyze the reward
|
||||
const analysis = analyzeRewardPrices({
|
||||
reward,
|
||||
availablePriceIds,
|
||||
});
|
||||
|
||||
return {
|
||||
willMigrateCount: analysis.validPriceCount,
|
||||
willNotMigrateCount: analysis.invalidPriceCount,
|
||||
};
|
||||
};
|
||||
|
||||
function UpdateReward({
|
||||
open,
|
||||
setOpen,
|
||||
selectedReward,
|
||||
setSelectedReward,
|
||||
open,
|
||||
setOpen,
|
||||
selectedReward,
|
||||
setSelectedReward,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
selectedReward: Reward | null;
|
||||
setSelectedReward: (reward: Reward) => void;
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
selectedReward: Reward | null;
|
||||
setSelectedReward: (reward: Reward) => void;
|
||||
}) {
|
||||
const [updateLoading, setUpdateLoading] = useState(false);
|
||||
const { refetch } = useRewardsQuery();
|
||||
const [updateLoading, setUpdateLoading] = useState(false);
|
||||
const { refetch } = useRewardsQuery();
|
||||
const { products } = useProductsQuery();
|
||||
|
||||
const env = useEnv();
|
||||
const axiosInstance = useAxiosInstance({ env });
|
||||
const env = useEnv();
|
||||
const axiosInstance = useAxiosInstance({ env });
|
||||
|
||||
const handleUpdate = async () => {
|
||||
setUpdateLoading(true);
|
||||
try {
|
||||
await RewardService.updateReward({
|
||||
axiosInstance,
|
||||
internalId: selectedReward!.internal_id,
|
||||
data: selectedReward!,
|
||||
});
|
||||
toast.success("Reward updated successfully");
|
||||
await refetch();
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
toast.error(getBackendErr(error, "Failed to update coupon"));
|
||||
}
|
||||
setUpdateLoading(false);
|
||||
};
|
||||
if (!selectedReward) {
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="w-[500px]">
|
||||
<DialogTitle>Update Reward</DialogTitle>
|
||||
<WarningBox>
|
||||
Existing customers with this coupon will not be affected
|
||||
</WarningBox>
|
||||
const handleUpdate = async () => {
|
||||
setUpdateLoading(true);
|
||||
try {
|
||||
// Check migration status and show warning if needed
|
||||
if (products) {
|
||||
const migrationResult = checkRewardMigration(selectedReward, products);
|
||||
if (migrationResult.willNotMigrateCount > 0) {
|
||||
toast.warning(
|
||||
`${migrationResult.willNotMigrateCount} price${migrationResult.willNotMigrateCount === 1 ? "" : "s"} won't be migrated to the latest product version.`,
|
||||
{
|
||||
duration: 5000,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{selectedReward && (
|
||||
<RewardConfig reward={selectedReward} setReward={setSelectedReward} />
|
||||
)}
|
||||
await RewardService.updateReward({
|
||||
axiosInstance,
|
||||
internalId: selectedReward.internal_id,
|
||||
data: selectedReward,
|
||||
});
|
||||
toast.success("Reward updated successfully");
|
||||
await refetch();
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
toast.error(getBackendErr(error, "Failed to update coupon"));
|
||||
}
|
||||
setUpdateLoading(false);
|
||||
};
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
isLoading={updateLoading}
|
||||
onClick={() => handleUpdate()}
|
||||
variant="gradientPrimary"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="w-[500px]">
|
||||
<DialogTitle>Update Reward</DialogTitle>
|
||||
<WarningBox>
|
||||
Existing customers with this coupon will not be affected
|
||||
</WarningBox>
|
||||
|
||||
{selectedReward && (
|
||||
<RewardConfig reward={selectedReward} setReward={setSelectedReward} />
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
isLoading={updateLoading}
|
||||
onClick={() => handleUpdate()}
|
||||
variant="gradientPrimary"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default UpdateReward;
|
||||
|
||||
Reference in New Issue
Block a user