working on cached customer
This commit is contained in:
@@ -14,6 +14,33 @@ import { getCustomerDetails } from "../../customers/cusUtils/getCustomerDetails.
|
||||
import { RELEVANT_STATUSES } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
|
||||
export class CusBatchService {
|
||||
static async getByInternalIds({
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
internalCustomerIds,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
internalCustomerIds: string[];
|
||||
}) {
|
||||
let query = getPaginatedFullCusQuery({
|
||||
orgId: org.id,
|
||||
env,
|
||||
includeInvoices: true,
|
||||
withEntities: true,
|
||||
withTrialsUsed: false,
|
||||
withSubs: true,
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
internalCustomerIds,
|
||||
});
|
||||
let results = await db.execute(query);
|
||||
|
||||
return results as unknown as FullCustomer[];
|
||||
}
|
||||
|
||||
static async getPage({
|
||||
db,
|
||||
ch,
|
||||
@@ -48,17 +75,17 @@ export class CusBatchService {
|
||||
const withEntities = expand.includes(CusExpand.Entities);
|
||||
const withTrialsUsed = expand.includes(CusExpand.TrialsUsed);
|
||||
|
||||
let query = getPaginatedFullCusQuery(
|
||||
org.id,
|
||||
let query = getPaginatedFullCusQuery({
|
||||
orgId: org.id,
|
||||
env,
|
||||
statuses,
|
||||
inStatuses: statuses,
|
||||
includeInvoices,
|
||||
withEntities,
|
||||
withTrialsUsed,
|
||||
true,
|
||||
withSubs: true,
|
||||
limit,
|
||||
offset
|
||||
);
|
||||
offset,
|
||||
});
|
||||
let results = await db.execute(query);
|
||||
let finals = [];
|
||||
for (let result of results) {
|
||||
|
||||
@@ -3,16 +3,16 @@ import { CusProductStatus } from "@autumn/shared";
|
||||
import { sql, SQL } from "drizzle-orm";
|
||||
|
||||
const buildOptimizedCusProductsCTE = (inStatuses?: CusProductStatus[]) => {
|
||||
const withStatusFilter = () => {
|
||||
return inStatuses
|
||||
? sql`AND cp.status = ANY(ARRAY[${sql.join(
|
||||
inStatuses.map((status) => sql`${status}`),
|
||||
sql`, `
|
||||
)}])`
|
||||
: sql``;
|
||||
};
|
||||
const withStatusFilter = () => {
|
||||
return inStatuses
|
||||
? sql`AND cp.status = ANY(ARRAY[${sql.join(
|
||||
inStatuses.map((status) => sql`${status}`),
|
||||
sql`, `
|
||||
)}])`
|
||||
: sql``;
|
||||
};
|
||||
|
||||
return sql`
|
||||
return sql`
|
||||
customer_products_with_prices AS (
|
||||
SELECT
|
||||
cp.*,
|
||||
@@ -80,11 +80,11 @@ const buildOptimizedCusProductsCTE = (inStatuses?: CusProductStatus[]) => {
|
||||
};
|
||||
|
||||
const buildEntitiesCTE = (withEntities: boolean) => {
|
||||
if (!withEntities) {
|
||||
return sql``;
|
||||
}
|
||||
if (!withEntities) {
|
||||
return sql``;
|
||||
}
|
||||
|
||||
return sql`
|
||||
return sql`
|
||||
customer_entities AS (
|
||||
SELECT
|
||||
COALESCE(
|
||||
@@ -99,11 +99,11 @@ const buildEntitiesCTE = (withEntities: boolean) => {
|
||||
};
|
||||
|
||||
const buildEntityCTE = (entityId?: string) => {
|
||||
if (!entityId) {
|
||||
return sql``;
|
||||
}
|
||||
if (!entityId) {
|
||||
return sql``;
|
||||
}
|
||||
|
||||
return sql`
|
||||
return sql`
|
||||
entity_record AS (
|
||||
SELECT * FROM entities e
|
||||
WHERE e.internal_customer_id = (SELECT internal_id FROM customer_record)
|
||||
@@ -116,15 +116,15 @@ const buildEntityCTE = (entityId?: string) => {
|
||||
};
|
||||
|
||||
const buildTrialsUsedCTE = (
|
||||
withTrialsUsed: boolean,
|
||||
orgId: string,
|
||||
env: AppEnv
|
||||
withTrialsUsed: boolean,
|
||||
orgId: string,
|
||||
env: AppEnv
|
||||
) => {
|
||||
if (!withTrialsUsed) {
|
||||
return sql``;
|
||||
}
|
||||
if (!withTrialsUsed) {
|
||||
return sql``;
|
||||
}
|
||||
|
||||
return sql`
|
||||
return sql`
|
||||
customer_trials_used AS (
|
||||
SELECT
|
||||
COALESCE(
|
||||
@@ -147,14 +147,14 @@ const buildTrialsUsedCTE = (
|
||||
};
|
||||
|
||||
const buildSubscriptionsCTE = (
|
||||
withSubs: boolean,
|
||||
inStatuses?: CusProductStatus[]
|
||||
withSubs: boolean,
|
||||
inStatuses?: CusProductStatus[]
|
||||
) => {
|
||||
if (!withSubs) {
|
||||
return sql``;
|
||||
}
|
||||
if (!withSubs) {
|
||||
return sql``;
|
||||
}
|
||||
|
||||
return sql`
|
||||
return sql`
|
||||
customer_subscriptions AS (
|
||||
SELECT
|
||||
COALESCE(
|
||||
@@ -171,14 +171,14 @@ const buildSubscriptionsCTE = (
|
||||
};
|
||||
|
||||
const buildInvoicesCTE = (hasEntityCTE: boolean) => {
|
||||
let entityFilter = hasEntityCTE
|
||||
? sql`AND (
|
||||
let entityFilter = hasEntityCTE
|
||||
? sql`AND (
|
||||
NOT EXISTS (SELECT 1 FROM entity_record)
|
||||
OR i.internal_entity_id = (SELECT internal_id FROM entity_record LIMIT 1)
|
||||
)`
|
||||
: sql``;
|
||||
: sql``;
|
||||
|
||||
return sql`
|
||||
return sql`
|
||||
customer_invoices AS (
|
||||
SELECT
|
||||
COALESCE(
|
||||
@@ -194,20 +194,20 @@ const buildInvoicesCTE = (hasEntityCTE: boolean) => {
|
||||
};
|
||||
|
||||
export const getFullCusQuery = (
|
||||
idOrInternalId: string,
|
||||
orgId: string,
|
||||
env: AppEnv,
|
||||
inStatuses: CusProductStatus[],
|
||||
includeInvoices: boolean,
|
||||
withEntities: boolean,
|
||||
withTrialsUsed: boolean,
|
||||
withSubs: boolean,
|
||||
entityId?: string
|
||||
idOrInternalId: string,
|
||||
orgId: string,
|
||||
env: AppEnv,
|
||||
inStatuses: CusProductStatus[],
|
||||
includeInvoices: boolean,
|
||||
withEntities: boolean,
|
||||
withTrialsUsed: boolean,
|
||||
withSubs: boolean,
|
||||
entityId?: string
|
||||
) => {
|
||||
const sqlChunks: SQL[] = [];
|
||||
const sqlChunks: SQL[] = [];
|
||||
|
||||
// Step 1: Get customer record
|
||||
sqlChunks.push(sql`
|
||||
// Step 1: Get customer record
|
||||
sqlChunks.push(sql`
|
||||
WITH customer_record AS (
|
||||
SELECT * FROM customers c
|
||||
WHERE (
|
||||
@@ -220,44 +220,44 @@ export const getFullCusQuery = (
|
||||
)
|
||||
`);
|
||||
|
||||
// Step 2: Get entities
|
||||
if (withEntities) {
|
||||
sqlChunks.push(sql`, `);
|
||||
sqlChunks.push(buildEntitiesCTE(withEntities));
|
||||
}
|
||||
// Step 2: Get entities
|
||||
if (withEntities) {
|
||||
sqlChunks.push(sql`, `);
|
||||
sqlChunks.push(buildEntitiesCTE(withEntities));
|
||||
}
|
||||
|
||||
// Step 3: Get entity
|
||||
if (entityId) {
|
||||
sqlChunks.push(sql`, `);
|
||||
sqlChunks.push(buildEntityCTE(entityId));
|
||||
}
|
||||
// Step 3: Get entity
|
||||
if (entityId) {
|
||||
sqlChunks.push(sql`, `);
|
||||
sqlChunks.push(buildEntityCTE(entityId));
|
||||
}
|
||||
|
||||
// Add customer products CTE
|
||||
sqlChunks.push(sql`, `);
|
||||
// sqlChunks.push(buildCusProductsCTE(inStatuses));
|
||||
sqlChunks.push(buildOptimizedCusProductsCTE(inStatuses));
|
||||
// Add customer products CTE
|
||||
sqlChunks.push(sql`, `);
|
||||
// sqlChunks.push(buildCusProductsCTE(inStatuses));
|
||||
sqlChunks.push(buildOptimizedCusProductsCTE(inStatuses));
|
||||
|
||||
// Conditionally add trials used CTE
|
||||
if (withTrialsUsed) {
|
||||
sqlChunks.push(sql`, `);
|
||||
sqlChunks.push(buildTrialsUsedCTE(withTrialsUsed, orgId, env));
|
||||
}
|
||||
// Conditionally add trials used CTE
|
||||
if (withTrialsUsed) {
|
||||
sqlChunks.push(sql`, `);
|
||||
sqlChunks.push(buildTrialsUsedCTE(withTrialsUsed, orgId, env));
|
||||
}
|
||||
|
||||
// Conditionally add subscriptions CTE
|
||||
if (withSubs) {
|
||||
sqlChunks.push(sql`, `);
|
||||
sqlChunks.push(buildSubscriptionsCTE(withSubs, inStatuses));
|
||||
}
|
||||
// Conditionally add subscriptions CTE
|
||||
if (withSubs) {
|
||||
sqlChunks.push(sql`, `);
|
||||
sqlChunks.push(buildSubscriptionsCTE(withSubs, inStatuses));
|
||||
}
|
||||
|
||||
// Conditionally add invoices CTE
|
||||
if (includeInvoices) {
|
||||
sqlChunks.push(sql`, `);
|
||||
sqlChunks.push(buildInvoicesCTE(!!entityId));
|
||||
}
|
||||
// Conditionally add invoices CTE
|
||||
if (includeInvoices) {
|
||||
sqlChunks.push(sql`, `);
|
||||
sqlChunks.push(buildInvoicesCTE(!!entityId));
|
||||
}
|
||||
|
||||
// Build final SELECT
|
||||
const selectFieldsChunks: SQL[] = [];
|
||||
selectFieldsChunks.push(sql`
|
||||
// Build final SELECT
|
||||
const selectFieldsChunks: SQL[] = [];
|
||||
selectFieldsChunks.push(sql`
|
||||
cr.*,
|
||||
COALESCE(
|
||||
(SELECT json_agg(cpwp) FROM customer_products_with_prices cpwp),
|
||||
@@ -265,56 +265,68 @@ export const getFullCusQuery = (
|
||||
) AS customer_products
|
||||
`);
|
||||
|
||||
// Add entities to SELECT if withEntities is true
|
||||
if (withEntities) {
|
||||
selectFieldsChunks.push(sql`,
|
||||
// Add entities to SELECT if withEntities is true
|
||||
if (withEntities) {
|
||||
selectFieldsChunks.push(sql`,
|
||||
(SELECT entities FROM customer_entities) AS entities`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add entity to SELECT if entityId is provided
|
||||
if (entityId) {
|
||||
selectFieldsChunks.push(sql`,
|
||||
// Add entity to SELECT if entityId is provided
|
||||
if (entityId) {
|
||||
selectFieldsChunks.push(sql`,
|
||||
(SELECT row_to_json(er) FROM entity_record er LIMIT 1) AS entity`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add trials used to SELECT if withTrialsUsed is true
|
||||
if (withTrialsUsed) {
|
||||
selectFieldsChunks.push(sql`,
|
||||
// Add trials used to SELECT if withTrialsUsed is true
|
||||
if (withTrialsUsed) {
|
||||
selectFieldsChunks.push(sql`,
|
||||
(SELECT trials_used FROM customer_trials_used) AS trials_used`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add subscriptions to SELECT if withSubs is true
|
||||
if (withSubs) {
|
||||
selectFieldsChunks.push(sql`,
|
||||
// Add subscriptions to SELECT if withSubs is true
|
||||
if (withSubs) {
|
||||
selectFieldsChunks.push(sql`,
|
||||
(SELECT subscriptions FROM customer_subscriptions) AS subscriptions`);
|
||||
}
|
||||
}
|
||||
|
||||
if (includeInvoices) {
|
||||
selectFieldsChunks.push(sql`,
|
||||
if (includeInvoices) {
|
||||
selectFieldsChunks.push(sql`,
|
||||
(SELECT invoices FROM customer_invoices) AS invoices`);
|
||||
}
|
||||
}
|
||||
|
||||
sqlChunks.push(sql`
|
||||
sqlChunks.push(sql`
|
||||
SELECT ${sql.join(selectFieldsChunks, sql``)}
|
||||
FROM customer_record cr
|
||||
`);
|
||||
|
||||
return sql.join(sqlChunks, sql``);
|
||||
return sql.join(sqlChunks, sql``);
|
||||
};
|
||||
|
||||
export const getPaginatedFullCusQuery = (
|
||||
orgId: string,
|
||||
env: AppEnv,
|
||||
inStatuses: CusProductStatus[],
|
||||
includeInvoices: boolean,
|
||||
withEntities: boolean,
|
||||
withTrialsUsed: boolean,
|
||||
withSubs: boolean,
|
||||
limit: number = 10,
|
||||
offset: number = 0,
|
||||
entityId?: string
|
||||
) => {
|
||||
|
||||
export const getPaginatedFullCusQuery = ({
|
||||
orgId,
|
||||
env,
|
||||
inStatuses,
|
||||
includeInvoices,
|
||||
withEntities,
|
||||
withTrialsUsed,
|
||||
withSubs,
|
||||
limit = 10,
|
||||
offset = 0,
|
||||
entityId,
|
||||
internalCustomerIds,
|
||||
}: {
|
||||
orgId: string;
|
||||
env: AppEnv;
|
||||
inStatuses?: CusProductStatus[];
|
||||
includeInvoices: boolean;
|
||||
withEntities: boolean;
|
||||
withTrialsUsed: boolean;
|
||||
withSubs: boolean;
|
||||
limit: number;
|
||||
offset: number;
|
||||
entityId?: string;
|
||||
internalCustomerIds?: string[];
|
||||
}) => {
|
||||
const withStatusFilter = () => {
|
||||
return inStatuses?.length
|
||||
? sql`AND cp.status = ANY(ARRAY[${sql.join(
|
||||
@@ -393,7 +405,14 @@ export const getPaginatedFullCusQuery = (
|
||||
LEFT JOIN customer_prices cpr ON cpr.customer_product_id = cp.id
|
||||
LEFT JOIN prices p ON cpr.price_id = p.id
|
||||
LEFT JOIN customer_entitlements ce ON ce.customer_product_id = cp.id
|
||||
WHERE cp.internal_customer_id IN (SELECT internal_id FROM customer_records)
|
||||
WHERE cp.internal_customer_id IN (SELECT internal_id FROM customer_records) ${
|
||||
internalCustomerIds
|
||||
? sql`AND cp.internal_customer_id IN (${sql.join(
|
||||
internalCustomerIds.map((id) => sql`${id}`),
|
||||
sql`, `
|
||||
)})`
|
||||
: sql``
|
||||
}
|
||||
${withStatusFilter()}
|
||||
GROUP BY cp.id, prod.*
|
||||
),
|
||||
@@ -406,7 +425,9 @@ export const getPaginatedFullCusQuery = (
|
||||
GROUP BY cpwp.internal_customer_id
|
||||
)
|
||||
|
||||
${withSubs ? sql`, customer_subscriptions AS (
|
||||
${
|
||||
withSubs
|
||||
? sql`, customer_subscriptions AS (
|
||||
SELECT
|
||||
cpwp.internal_customer_id,
|
||||
COALESCE(
|
||||
@@ -416,9 +437,13 @@ export const getPaginatedFullCusQuery = (
|
||||
FROM customer_products_with_prices cpwp
|
||||
JOIN subscriptions s ON s.stripe_id = ANY(cpwp.subscription_ids)
|
||||
GROUP BY cpwp.internal_customer_id
|
||||
)` : sql``}
|
||||
)`
|
||||
: sql``
|
||||
}
|
||||
|
||||
${withEntities ? sql`, customer_entities AS (
|
||||
${
|
||||
withEntities
|
||||
? sql`, customer_entities AS (
|
||||
SELECT
|
||||
e.internal_customer_id,
|
||||
COALESCE(
|
||||
@@ -428,9 +453,13 @@ export const getPaginatedFullCusQuery = (
|
||||
FROM entities e
|
||||
WHERE e.internal_customer_id IN (SELECT internal_id FROM customer_records)
|
||||
GROUP BY e.internal_customer_id
|
||||
)` : sql``}
|
||||
)`
|
||||
: sql``
|
||||
}
|
||||
|
||||
${includeInvoices ? sql`, customer_invoices AS (
|
||||
${
|
||||
includeInvoices
|
||||
? sql`, customer_invoices AS (
|
||||
SELECT
|
||||
i.internal_customer_id,
|
||||
COALESCE(
|
||||
@@ -440,9 +469,13 @@ export const getPaginatedFullCusQuery = (
|
||||
FROM invoices i
|
||||
WHERE i.internal_customer_id IN (SELECT internal_id FROM customer_records)
|
||||
GROUP BY i.internal_customer_id
|
||||
)` : sql``}
|
||||
)`
|
||||
: sql``
|
||||
}
|
||||
|
||||
${withTrialsUsed ? sql`, customer_trials_used AS (
|
||||
${
|
||||
withTrialsUsed
|
||||
? sql`, customer_trials_used AS (
|
||||
SELECT
|
||||
cp.internal_customer_id,
|
||||
json_agg(json_build_object(
|
||||
@@ -456,7 +489,9 @@ export const getPaginatedFullCusQuery = (
|
||||
WHERE cp.internal_customer_id IN (SELECT internal_id FROM customer_records)
|
||||
AND cp.free_trial_id IS NOT NULL
|
||||
GROUP BY cp.internal_customer_id
|
||||
)` : sql``}
|
||||
)`
|
||||
: sql``
|
||||
}
|
||||
|
||||
SELECT
|
||||
cr.*,
|
||||
@@ -473,4 +508,4 @@ export const getPaginatedFullCusQuery = (
|
||||
${withTrialsUsed ? sql`LEFT JOIN customer_trials_used ctu ON ctu.internal_customer_id = cr.internal_id` : sql``}
|
||||
ORDER BY cr.created_at DESC
|
||||
`;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -23,9 +23,10 @@ import { RewardRedemptionService } from "../rewards/RewardRedemptionService.js";
|
||||
import { CusReadService } from "./CusReadService.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { cusProductToProduct } from "./cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { createOrgResponse } from "../orgs/orgUtils.js";
|
||||
import { createOrgResponse, isStripeConnected } from "../orgs/orgUtils.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { CusSearchService } from "./CusSearchService.js";
|
||||
import { CusBatchService } from "../api/batch/CusBatchService.js";
|
||||
|
||||
export const cusRouter: Router = Router();
|
||||
|
||||
@@ -53,6 +54,172 @@ cusRouter.post("/all/search", (req, res) =>
|
||||
})
|
||||
);
|
||||
|
||||
// Customer page
|
||||
cusRouter.get("/:customer_id", async (req: any, res: any) => {
|
||||
try {
|
||||
const { db, org, features, env } = req;
|
||||
const { customer_id } = req.params;
|
||||
const orgId = req.orgId;
|
||||
|
||||
const fullCus = await CusService.getFull({
|
||||
db,
|
||||
orgId,
|
||||
env,
|
||||
idOrInternalId: customer_id,
|
||||
withEntities: true,
|
||||
expand: [CusExpand.Invoices],
|
||||
inStatuses: [
|
||||
CusProductStatus.Active,
|
||||
CusProductStatus.PastDue,
|
||||
CusProductStatus.Scheduled,
|
||||
CusProductStatus.Expired,
|
||||
],
|
||||
});
|
||||
|
||||
// const [coupons, products, customer] = await Promise.all([
|
||||
// RewardService.list({
|
||||
// db,
|
||||
// orgId: orgId,
|
||||
// env,
|
||||
// }),
|
||||
|
||||
// ProductService.listFull({ db, orgId, env, returnAll: true }),
|
||||
|
||||
// ]);
|
||||
|
||||
// let invoices = customer.invoices;
|
||||
// let entities = customer.entities;
|
||||
// const events = await EventService.getByCustomerId({
|
||||
// db,
|
||||
// internalCustomerId: customer.internal_id,
|
||||
// env,
|
||||
// orgId: orgId,
|
||||
// limit: 10,
|
||||
// });
|
||||
|
||||
// let fullCustomer = customer as any;
|
||||
// let cusProducts = fullCustomer.customer_products;
|
||||
// fullCustomer.products = fullCustomer.customer_products;
|
||||
// fullCustomer.entitlements = cusProducts.flatMap(
|
||||
// (product: FullCusProduct) => product.customer_entitlements
|
||||
// );
|
||||
// fullCustomer.prices = cusProducts.flatMap(
|
||||
// (product: FullCusProduct) => product.customer_prices
|
||||
// );
|
||||
|
||||
// for (const product of fullCustomer.products) {
|
||||
// product.entitlements = product.customer_entitlements.map(
|
||||
// (cusEnt: FullCustomerEntitlement) => {
|
||||
// return cusEnt.entitlement;
|
||||
// }
|
||||
// );
|
||||
// product.prices = product.customer_prices.map(
|
||||
// (cusPrice: FullCustomerPrice) => {
|
||||
// return cusPrice.price;
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
// let discount = null;
|
||||
// if (org.stripe_config && customer.processor?.id) {
|
||||
// try {
|
||||
// const stripeCli = createStripeCli({ org, env });
|
||||
// const stripeCus: any = await stripeCli.customers.retrieve(
|
||||
// customer.processor.id
|
||||
// );
|
||||
|
||||
// if (stripeCus.discount) {
|
||||
// discount = stripeCus.discount;
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.log("error", error);
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (const invoice of invoices || []) {
|
||||
// invoice.product_ids = invoice.product_ids.sort();
|
||||
// invoice.internal_product_ids = invoice.internal_product_ids.sort();
|
||||
// }
|
||||
|
||||
// fullCustomer.entitlements = fullCustomer.entitlements.sort(
|
||||
// (a: any, b: any) => {
|
||||
// const productA = fullCustomer.products.find(
|
||||
// (p: any) => p.id === a.customer_product_id
|
||||
// );
|
||||
// const productB = fullCustomer.products.find(
|
||||
// (p: any) => p.id === b.customer_product_id
|
||||
// );
|
||||
|
||||
// return (
|
||||
// new Date(b.created_at).getTime() - new Date(a.created_at).getTime() ||
|
||||
// b.id.localeCompare(a.id)
|
||||
// );
|
||||
// }
|
||||
// );
|
||||
|
||||
// for (const cusEnt of fullCustomer.entitlements) {
|
||||
// // let entitlement = cusEnt.entitlement;
|
||||
|
||||
// // Show used, limit, etc.
|
||||
// let { balance, unused } = getCusEntMasterBalance({
|
||||
// cusEnt,
|
||||
// entities,
|
||||
// });
|
||||
|
||||
// cusEnt.balance = balance;
|
||||
// cusEnt.unused = unused;
|
||||
// }
|
||||
|
||||
res.status(200).json({
|
||||
customer: fullCus,
|
||||
// products: getLatestProducts(products),
|
||||
// versionCounts: getProductVersionCounts(products),
|
||||
// invoices,
|
||||
// features,
|
||||
// coupons,
|
||||
// events,
|
||||
// discount,
|
||||
// org,
|
||||
// entities,
|
||||
});
|
||||
} catch (error) {
|
||||
handleFrontendReqError({ req, error, res, action: "get customer data" });
|
||||
}
|
||||
});
|
||||
|
||||
// cusRouter.get("/:customer_id/stripe", async (req: any, res: any) => {
|
||||
// try {
|
||||
// const { db, org, features, env } = req;
|
||||
// const { customer_id } = req.params;
|
||||
// let discount = null;
|
||||
|
||||
// const customer = await CusService.get({
|
||||
// db,
|
||||
// orgId: req.orgId,
|
||||
// env,
|
||||
// idOrInternalId: customer_id,
|
||||
// });
|
||||
|
||||
// if (org.stripe_config && customer.processor?.id) {
|
||||
// try {
|
||||
// const stripeCli = createStripeCli({ org, env });
|
||||
// const stripeCus: any = await stripeCli.customers.retrieve(
|
||||
// customer.processor.id
|
||||
// );
|
||||
|
||||
// if (stripeCus.discount) {
|
||||
// discount = stripeCus.discount;
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.log("error", error);
|
||||
// }
|
||||
// }
|
||||
|
||||
// } catch (error) {
|
||||
// handleFrontendReqError({ req, error, res, action: "get customer data" });
|
||||
// }
|
||||
// });
|
||||
|
||||
cusRouter.get("/:customer_id/events", async (req: any, res: any) => {
|
||||
try {
|
||||
const { db, org, features, env } = req;
|
||||
@@ -215,16 +382,18 @@ cusRouter.get("/:customer_id/data", async (req: any, res: any) => {
|
||||
|
||||
cusRouter.get("/:customer_id/referrals", async (req: any, res: any) => {
|
||||
try {
|
||||
const { env, db } = req;
|
||||
const { env, db, org } = req;
|
||||
const { customer_id } = req.params;
|
||||
const orgId = req.orgId;
|
||||
|
||||
console.time("get_customer");
|
||||
let internalCustomer = await CusService.get({
|
||||
db,
|
||||
orgId,
|
||||
env,
|
||||
idOrInternalId: customer_id,
|
||||
});
|
||||
console.timeEnd("get_customer");
|
||||
|
||||
if (!internalCustomer) {
|
||||
throw new RecaseError({
|
||||
@@ -235,7 +404,7 @@ cusRouter.get("/:customer_id/referrals", async (req: any, res: any) => {
|
||||
}
|
||||
|
||||
// Get all redemptions for this customer
|
||||
let [referred, redeemed] = await Promise.all([
|
||||
let [referred, redeemed, stripeCus] = await Promise.all([
|
||||
RewardRedemptionService.getByReferrer({
|
||||
db,
|
||||
internalCustomerId: internalCustomer.internal_id,
|
||||
@@ -248,6 +417,16 @@ cusRouter.get("/:customer_id/referrals", async (req: any, res: any) => {
|
||||
withReferralCode: true,
|
||||
limit: 100,
|
||||
}),
|
||||
async () => {
|
||||
if (isStripeConnected({ org, env }) && internalCustomer.processor?.id) {
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
const stripeCus: any = await stripeCli.customers.retrieve(
|
||||
internalCustomer.processor.id
|
||||
);
|
||||
return stripeCus;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
]);
|
||||
|
||||
let redeemedCustomerIds = redeemed.map(
|
||||
@@ -269,9 +448,12 @@ cusRouter.get("/:customer_id/referrals", async (req: any, res: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
const end = performance.now();
|
||||
|
||||
res.status(200).send({
|
||||
referred,
|
||||
redeemed,
|
||||
stripeCus,
|
||||
});
|
||||
} catch (error) {
|
||||
handleFrontendReqError({
|
||||
@@ -283,6 +465,42 @@ cusRouter.get("/:customer_id/referrals", async (req: any, res: any) => {
|
||||
}
|
||||
});
|
||||
|
||||
cusRouter.post("/all/full_customers", async (req: any, res: any) =>
|
||||
routeHandler({
|
||||
req,
|
||||
res,
|
||||
action: "get customer full customers",
|
||||
handler: async (req, res) => {
|
||||
const { db, org, env } = req;
|
||||
const { search, page_size = 50, page = 1, last_item, filters } = req.body;
|
||||
|
||||
const { data: customers, count } = await CusSearchService.search({
|
||||
db: req.db,
|
||||
orgId: req.orgId,
|
||||
env: req.env,
|
||||
search,
|
||||
filters,
|
||||
lastItem: last_item,
|
||||
pageNumber: page,
|
||||
pageSize: page_size,
|
||||
});
|
||||
|
||||
console.log("First customer", customers?.[0]);
|
||||
|
||||
const fullCustomers = await CusBatchService.getByInternalIds({
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
internalCustomerIds: customers.map(
|
||||
(customer: any) => customer.internal_id
|
||||
),
|
||||
});
|
||||
|
||||
res.status(200).json({ fullCustomers });
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
cusRouter.get(
|
||||
"/:customer_id/product/:product_id",
|
||||
async (req: any, res: any) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import OnboardingView2 from "./views/onboarding2/OnboardingView2";
|
||||
import CustomerView from "./views/customers/customer/CustomerView";
|
||||
import CustomerProductView from "./views/customers/customer/product/CustomerProductView";
|
||||
import CustomersView from "./views/customers/CustomersView";
|
||||
import DevScreen from "./views/developer/DevView";
|
||||
import ProductView from "./views/products/product/ProductView";
|
||||
import ProductsView from "./views/products/ProductsView";
|
||||
@@ -18,6 +17,7 @@ import { AnalyticsView } from "./views/customers/customer/analytics/AnalyticsVie
|
||||
import { TerminalView } from "./views/TerminalView";
|
||||
import { DefaultView } from "./views/DefaultView";
|
||||
import { MainLayout } from "./app/layout";
|
||||
import CustomersPage from "./views/customers/CustomersPage";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
@@ -50,14 +50,8 @@ export default function App() {
|
||||
path="/sandbox/products/:product_id"
|
||||
element={<ProductView env={AppEnv.Sandbox} />}
|
||||
/>
|
||||
<Route
|
||||
path="/customers"
|
||||
element={<CustomersView env={AppEnv.Sandbox} />}
|
||||
/>
|
||||
<Route
|
||||
path="/sandbox/customers"
|
||||
element={<CustomersView env={AppEnv.Sandbox} />}
|
||||
/>
|
||||
<Route path="/customers" element={<CustomersPage />} />
|
||||
<Route path="/sandbox/customers" element={<CustomersPage />} />
|
||||
<Route
|
||||
path="/customers/:customer_id"
|
||||
element={<CustomerView env={AppEnv.Live} />}
|
||||
|
||||
@@ -9,9 +9,9 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchInterval: 0,
|
||||
},
|
||||
// queries: {
|
||||
// refetchInterval: 0,
|
||||
// },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -68,12 +68,12 @@ export const pushPage = ({
|
||||
preserveParams = true,
|
||||
}: {
|
||||
path: string;
|
||||
queryParams: Record<string, string | undefined>;
|
||||
queryParams?: Record<string, string | undefined>;
|
||||
navigate?: any;
|
||||
preserveParams?: boolean;
|
||||
}) => {
|
||||
const curPath = window.location.pathname;
|
||||
const curEnv = getEnvFromPath(curPath);
|
||||
const pathname = window.location.pathname;
|
||||
const curEnv = getEnvFromPath(pathname);
|
||||
|
||||
const curQueryParams = new URLSearchParams(window.location.search);
|
||||
if (!preserveParams) {
|
||||
@@ -95,13 +95,15 @@ export const pushPage = ({
|
||||
if (curQueryParams.toString()) {
|
||||
path = `${path}?${curQueryParams.toString()}`;
|
||||
}
|
||||
if (navigate) {
|
||||
if (curEnv === AppEnv.Sandbox) {
|
||||
navigate(`/sandbox${path}`);
|
||||
} else {
|
||||
navigate(path);
|
||||
}
|
||||
|
||||
if (curEnv === AppEnv.Sandbox) {
|
||||
path = `/sandbox${path}`;
|
||||
}
|
||||
|
||||
if (navigate) {
|
||||
navigate(path);
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import React, { useRef } from "react";
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import { useRef } from "react";
|
||||
import { CustomersContext } from "./CustomersContext";
|
||||
import { CustomersTable } from "./components/CustomersTable";
|
||||
import LoadingScreen from "../general/LoadingScreen";
|
||||
import { CustomersTopBar } from "./components/customers-top-bar/CustomersTopBar";
|
||||
import { useCusSearchQuery } from "./hooks/useCusSearchQuery";
|
||||
import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
|
||||
import { useCustomersQueryStates } from "./hooks/useCustomersQueryStates";
|
||||
import { useSavedViewsQuery } from "./hooks/useSavedViewsQuery";
|
||||
import LoadingScreen from "../general/LoadingScreen";
|
||||
import { useFullCusSearchQuery } from "./hooks/useFullCusSearchQuery";
|
||||
|
||||
function CustomersView({ env }: { env: AppEnv }) {
|
||||
function CustomersPage() {
|
||||
const { customers, totalCount, isLoading, error, refetch } =
|
||||
useCusSearchQuery();
|
||||
|
||||
const { queryStates, setQueryStates } = useCustomersQueryStates();
|
||||
|
||||
const { products, isLoading: productsLoading } = useProductsQuery();
|
||||
|
||||
useSavedViewsQuery();
|
||||
useFullCusSearchQuery();
|
||||
|
||||
// const { data, isLoading, error, mutate } = useAxiosPostSWR({
|
||||
// url: `/v1/customers/all/search`,
|
||||
// env,
|
||||
@@ -217,4 +220,4 @@ function CustomersView({ env }: { env: AppEnv }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomersView;
|
||||
export default CustomersPage;
|
||||
@@ -10,7 +10,7 @@ import SmallSpinner from "@/components/general/SmallSpinner";
|
||||
import { useCusSearchQuery } from "../hooks/useCusSearchQuery";
|
||||
|
||||
export const CustomersPagination = () => {
|
||||
const { isLoading, totalCount, isFetchingUncached } = useCusSearchQuery();
|
||||
const { isLoading, totalCount } = useCusSearchQuery();
|
||||
const { queryStates, setQueryStates } = useCustomersQueryStates();
|
||||
|
||||
const totalPages = Math.ceil((totalCount || 0) / 50);
|
||||
|
||||
@@ -11,12 +11,11 @@ import {
|
||||
import { ListFilter, X } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { FilterStatusSubMenu } from "./FilterStatusSubMenu";
|
||||
import { SavedViews } from "../../filter/SavedViews";
|
||||
import { SavedViews } from "./SavedViews";
|
||||
import { useCustomersQueryStates } from "../../hooks/useCustomersQueryStates";
|
||||
import { useGeneralQuery } from "@/hooks/queries/useGeneralQuery";
|
||||
import { SaveViewPopover } from "./SavedViewPopover";
|
||||
import { ProductsSubMenu } from "../../filter/ProductsSubMenu";
|
||||
import { useSavedViewsQuery } from "../../hooks/useSavedViewsQuery";
|
||||
import { ProductsSubMenu } from "./ProductsSubMenu";
|
||||
|
||||
function CustomersFilterButton() {
|
||||
const { setQueryStates } = useCustomersQueryStates();
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useCustomersQueryStates } from "../hooks/useCustomersQueryStates";
|
||||
import { useCustomersQueryStates } from "../../hooks/useCustomersQueryStates";
|
||||
import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
|
||||
import { getVersionCounts } from "@/utils/productUtils";
|
||||
|
||||
@@ -16,7 +16,7 @@ import { getBackendErr } from "@/utils/genUtils";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { Delete } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useCustomersQueryStates } from "../hooks/useCustomersQueryStates";
|
||||
import { useCustomersQueryStates } from "../../hooks/useCustomersQueryStates";
|
||||
|
||||
interface SavedView {
|
||||
id: string;
|
||||
@@ -1,45 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { useAxiosSWR } from "@/services/useAxiosSwr";
|
||||
import LoadingScreen from "@/views/general/LoadingScreen";
|
||||
import { AppEnv, CusProductStatus } from "@autumn/shared";
|
||||
|
||||
import { CusProductStatus } from "@autumn/shared";
|
||||
import { CustomerContext } from "./CustomerContext";
|
||||
import { Link, useNavigate, useParams, useSearchParams } from "react-router";
|
||||
import { CustomerEntitlementsList } from "./entitlements/CustomerEntitlementsList";
|
||||
import { getRedirectUrl, notNullish } from "@/utils/genUtils";
|
||||
import { CustomerEventsList } from "./CustomerEventsList";
|
||||
import { notNullish } from "@/utils/genUtils";
|
||||
import { useEffect, useState } from "react";
|
||||
import ErrorScreen from "@/views/general/ErrorScreen";
|
||||
import { InvoicesTable } from "./InvoicesTable";
|
||||
|
||||
import { CustomerSidebar } from "./customer-sidebar/CustomerSidebar";
|
||||
import { CustomerBreadcrumbs } from "./customer-breadcrumbs";
|
||||
import { SelectEntity } from "./customer-sidebar/select-entity";
|
||||
import { useParams, useSearchParams } from "react-router";
|
||||
import { useCusQuery } from "./hooks/useCusQuery";
|
||||
import { CustomerSidebar } from "./components/customer-sidebar/CustomerSidebar";
|
||||
import { CustomerPageHeader } from "./components/customer-header/CustomerPageHeader";
|
||||
import { CustomerProductList } from "./customer-product-list/CustomerProductList";
|
||||
import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
|
||||
import { CustomerEntitlementsList } from "./entitlements/CustomerEntitlementsList";
|
||||
import { useCusReferralQuery } from "./hooks/useCusReferralQuery";
|
||||
|
||||
export default function CustomerView({ env }: { env: AppEnv }) {
|
||||
const { customer_id } = useParams();
|
||||
export default function CustomerView() {
|
||||
// const { customer_id } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const entityIdParam = searchParams.get("entity_id");
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
mutate: cusMutate,
|
||||
} = useAxiosSWR({
|
||||
url: `/customers/${customer_id}/data`,
|
||||
env,
|
||||
});
|
||||
const { customer, isLoading: cusLoading, error, refetch } = useCusQuery();
|
||||
|
||||
const { data: referrals } = useAxiosSWR({
|
||||
url: `/customers/${customer_id}/referrals`,
|
||||
env,
|
||||
});
|
||||
const { data: rewardsData } = useAxiosSWR({
|
||||
url: `/products/rewards`,
|
||||
env,
|
||||
});
|
||||
useCusReferralQuery();
|
||||
|
||||
// const {
|
||||
// data,
|
||||
// isLoading,
|
||||
// mutate: cusMutate,
|
||||
// } = useAxiosSWR({
|
||||
// url: `/customers/${customer_id}/data`,
|
||||
// env,
|
||||
// });
|
||||
|
||||
// const { data: referrals } = useAxiosSWR({
|
||||
// url: `/customers/${customer_id}/referrals`,
|
||||
// env,
|
||||
// });
|
||||
// const { data: rewardsData } = useAxiosSWR({
|
||||
// url: `/products/rewards`,
|
||||
// env,
|
||||
// });
|
||||
|
||||
const [setAddCouponOpen] = useState(false);
|
||||
const [entityId, setEntityId] = useState(entityIdParam);
|
||||
@@ -52,24 +52,24 @@ export default function CustomerView({ env }: { env: AppEnv }) {
|
||||
}
|
||||
}, [entityIdParam]);
|
||||
|
||||
if (isLoading) return <LoadingScreen />;
|
||||
if (cusLoading) return <LoadingScreen />;
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<ErrorScreen>
|
||||
<div className="text-t2 text-sm">Customer not found</div>
|
||||
<Link
|
||||
className="text-t3 text-xs hover:underline"
|
||||
to={getRedirectUrl("/customers", env)}
|
||||
>
|
||||
Return
|
||||
</Link>
|
||||
</ErrorScreen>
|
||||
);
|
||||
}
|
||||
// if (!data) {
|
||||
// return (
|
||||
// <ErrorScreen>
|
||||
// <div className="text-t2 text-sm">Customer not found</div>
|
||||
// <Link
|
||||
// className="text-t3 text-xs hover:underline"
|
||||
// to={getRedirectUrl("/customers", env)}
|
||||
// >
|
||||
// Return
|
||||
// </Link>
|
||||
// </ErrorScreen>
|
||||
// );
|
||||
// }
|
||||
|
||||
const { customer, products, invoices, coupons, discount, events, entities } =
|
||||
data;
|
||||
// const { customer, products, invoices, coupons, discount, events, entities } =
|
||||
// data;
|
||||
|
||||
const showEntityView = customer.customer_products.some(
|
||||
(cp: any) =>
|
||||
@@ -80,63 +80,44 @@ export default function CustomerView({ env }: { env: AppEnv }) {
|
||||
return (
|
||||
<CustomerContext.Provider
|
||||
value={{
|
||||
...data,
|
||||
customer,
|
||||
products,
|
||||
invoices,
|
||||
coupons,
|
||||
discount,
|
||||
env,
|
||||
cusMutate,
|
||||
setAddCouponOpen,
|
||||
referrals,
|
||||
// ...data,
|
||||
// customer,
|
||||
// products,
|
||||
// invoices,
|
||||
// coupons,
|
||||
// discount,
|
||||
// env,
|
||||
// cusMutate,
|
||||
// setAddCouponOpen,
|
||||
// referrals,
|
||||
entityId,
|
||||
setEntityId,
|
||||
showEntityView,
|
||||
rewards: rewardsData?.rewards,
|
||||
// rewards: rewardsData?.rewards,
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full overflow-auto h-full ">
|
||||
<div className="flex flex-col gap-4 w-full ">
|
||||
<CustomerBreadcrumbs />
|
||||
<div className="flex w-full justify-between pl-10 pr-7">
|
||||
<div className="flex gap-2 w-full">
|
||||
<h2 className="flex text-lg text-t1 font-medium w-full max-w-md justify-start truncate">
|
||||
{customer.name ? (
|
||||
<span className="truncate">{customer.name}</span>
|
||||
) : customer.id ? (
|
||||
<span className="truncate font-mono">{customer.id}</span>
|
||||
) : (
|
||||
<span className="truncate">{customer.email}</span>
|
||||
)}
|
||||
</h2>
|
||||
</div>
|
||||
{/* <EntityHeader entity={entity} /> */}
|
||||
<SelectEntity entityId={entityId || ""} entities={entities} />
|
||||
</div>
|
||||
<CustomerPageHeader />
|
||||
<div className="flex w-full !pb-[50px]">
|
||||
{/* main content */}
|
||||
<div className="flex flex-col gap-10 w-full text-t2 text-sm">
|
||||
<div className="flex flex-col gap-2">
|
||||
<CustomerProductList customer={customer} products={products} />
|
||||
<CustomerProductList />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<CustomerEntitlementsList />
|
||||
</div>
|
||||
<CustomerEntitlementsList />
|
||||
<div className="flex flex-col gap-2"></div>
|
||||
|
||||
<InvoicesTable />
|
||||
{/* <InvoicesTable />
|
||||
<CustomerEventsList
|
||||
customer={customer}
|
||||
events={events}
|
||||
env={env}
|
||||
/>
|
||||
/> */}
|
||||
</div>
|
||||
{/* customer details */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex max-w-md w-1/3 shrink-1 hidden lg:block lg:min-w-xs sticky top-0">
|
||||
{/* <CustomerDetails /> */}
|
||||
<div className="max-w-md w-1/3 shrink-1 hidden lg:block lg:min-w-xs sticky top-0">
|
||||
<CustomerSidebar />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useEnv } from "@/utils/envUtils";
|
||||
import { CustomerConfig } from "./CustomerConfig";
|
||||
import { CusService } from "@/services/customers/CusService";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useCusQuery } from "./hooks/useCusQuery";
|
||||
|
||||
const UpdateCustomerDialog = ({
|
||||
selectedCustomer,
|
||||
@@ -23,17 +24,18 @@ const UpdateCustomerDialog = ({
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}) => {
|
||||
const { cusMutate } = useCustomerContext();
|
||||
const [couponSelected, setCouponSelected] = useState<Reward | null>(null);
|
||||
const [customer, setCustomer] = useState<CreateCustomer>(selectedCustomer);
|
||||
// const { cusMutate } = useCustomerContext();
|
||||
const { customer: curCustomer, refetch } = useCusQuery();
|
||||
const [customer, setCustomer] = useState<CreateCustomer>(curCustomer);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const env = useEnv();
|
||||
const axiosInstance = useAxiosInstance({ env });
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
setCustomer(selectedCustomer);
|
||||
}, [open]);
|
||||
// useEffect(() => {
|
||||
// setCustomer(selectedCustomer);
|
||||
// }, [open]);
|
||||
|
||||
const handleAddClicked = async () => {
|
||||
try {
|
||||
@@ -51,7 +53,7 @@ const UpdateCustomerDialog = ({
|
||||
|
||||
toast.success(`Successfully updated customer`);
|
||||
setOpen(false);
|
||||
await cusMutate();
|
||||
await refetch();
|
||||
|
||||
if (customer.id != selectedCustomer.id) {
|
||||
navigateTo(`/customers/${customer.id}`, navigate, env);
|
||||
|
||||
@@ -12,20 +12,25 @@ import { getBackendErr } from "@/utils/genUtils";
|
||||
import { toast } from "sonner";
|
||||
import { CusService } from "@/services/customers/CusService";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { getOriginalCouponId } from "@/utils/product/couponUtils";
|
||||
import { WarningBox } from "@/components/general/modal-components/WarningBox";
|
||||
import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
import { useCusReferralQuery } from "../hooks/useCusReferralQuery";
|
||||
|
||||
const AddCouponDialogContent = ({
|
||||
setOpen,
|
||||
}: {
|
||||
setOpen: (open: boolean) => void;
|
||||
}) => {
|
||||
const { cusMutate, customer, coupons, discount } = useCustomerContext();
|
||||
const { stripeCus } = useCusReferralQuery();
|
||||
const { customer, refetch } = useCusQuery();
|
||||
|
||||
const [couponSelected, setCouponSelected] = useState<Reward | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const env = useEnv();
|
||||
const axiosInstance = useAxiosInstance({ env });
|
||||
const axiosInstance = useAxiosInstance();
|
||||
|
||||
const { rewards } = useRewardsQuery();
|
||||
|
||||
const handleAddClicked = async () => {
|
||||
try {
|
||||
@@ -36,7 +41,7 @@ const AddCouponDialogContent = ({
|
||||
coupon_id: couponSelected!.internal_id,
|
||||
});
|
||||
setOpen(false);
|
||||
await cusMutate();
|
||||
await refetch();
|
||||
toast.success("Reward added to customer");
|
||||
} catch (error) {
|
||||
toast.error(getBackendErr(error, "Failed to create coupon"));
|
||||
@@ -45,18 +50,20 @@ const AddCouponDialogContent = ({
|
||||
}
|
||||
};
|
||||
|
||||
const existingDiscount = discount;
|
||||
const existingDiscount = stripeCus?.discount;
|
||||
|
||||
const getExistingCoupon = () => {
|
||||
if (discount) {
|
||||
return coupons.find(
|
||||
(c: Reward) => c.id === getOriginalCouponId(discount.coupon.id),
|
||||
if (existingDiscount) {
|
||||
return rewards.find(
|
||||
(c: Reward) => c.id === getOriginalCouponId(existingDiscount.coupon.id)
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
if (!rewards) return null;
|
||||
|
||||
return (
|
||||
<DialogContent className="min-w-sm max-w-md">
|
||||
<DialogTitle>Add Reward</DialogTitle>
|
||||
@@ -70,7 +77,7 @@ const AddCouponDialogContent = ({
|
||||
<Select
|
||||
value={couponSelected?.internal_id}
|
||||
onValueChange={(value) => {
|
||||
const coupon = coupons.find((c: Reward) => c.internal_id === value);
|
||||
const coupon = rewards.find((c: Reward) => c.internal_id === value);
|
||||
if (coupon) {
|
||||
setCouponSelected(coupon);
|
||||
}
|
||||
@@ -82,8 +89,8 @@ const AddCouponDialogContent = ({
|
||||
<SelectContent>
|
||||
{/* If empty */}
|
||||
|
||||
{coupons && coupons.length > 0 ? (
|
||||
coupons.map((coupon: Reward) => {
|
||||
{rewards && rewards.length > 0 ? (
|
||||
rewards.map((coupon: Reward) => {
|
||||
if (coupon.type == RewardType.FreeProduct) return null;
|
||||
return (
|
||||
<SelectItem
|
||||
|
||||
@@ -4,15 +4,18 @@ import { FullCusProduct } from "@autumn/shared";
|
||||
import { ExternalLinkIcon } from "lucide-react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useCustomerContext } from "../CustomerContext";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
|
||||
export const CusProductEntityItem = ({
|
||||
internalEntityId,
|
||||
}: {
|
||||
internalEntityId?: string | null;
|
||||
}) => {
|
||||
const { entities } = useCustomerContext();
|
||||
// console.log("Cus product", cusProduct);
|
||||
const entity = entities.find((e: any) => e.internal_id === internalEntityId);
|
||||
const { customer } = useCusQuery();
|
||||
|
||||
const entity = customer.entities.find(
|
||||
(e: any) => e.internal_id === internalEntityId
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
return internalEntityId ? (
|
||||
@@ -25,7 +28,6 @@ export const CusProductEntityItem = ({
|
||||
},
|
||||
});
|
||||
}}
|
||||
// icon={<ExternalLinkIcon size={12} />}
|
||||
>
|
||||
<span className="truncate">
|
||||
{entity?.name || entity?.id || "Unknown"}
|
||||
|
||||
@@ -9,15 +9,18 @@ import {
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { navigateTo } from "@/utils/genUtils";
|
||||
import { useNavigate, useLocation } from "react-router";
|
||||
import { useCustomerContext } from "./CustomerContext";
|
||||
import { useCustomerContext } from "../../CustomerContext";
|
||||
import { useCusQuery } from "../../hooks/useCusQuery";
|
||||
|
||||
export const CustomerBreadcrumbs = () => {
|
||||
const env = useEnv();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { customer, entities, entityId, setEntityId } = useCustomerContext();
|
||||
|
||||
const entity = entities.find((e: any) => e.id === entityId);
|
||||
const { customer } = useCusQuery();
|
||||
const { entityId, setEntityId } = useCustomerContext();
|
||||
|
||||
const entity = customer.entities.find((e: any) => e.id === entityId);
|
||||
|
||||
return (
|
||||
<Breadcrumb className="text-t3 pt-6 pl-10 flex justify-start ">
|
||||
@@ -39,7 +42,9 @@ export const CustomerBreadcrumbs = () => {
|
||||
},
|
||||
{
|
||||
key: "Entities",
|
||||
value: (entities || []).map((e: any) => e.id).join(", "),
|
||||
value: (customer.entities || [])
|
||||
.map((e: any) => e.id)
|
||||
.join(", "),
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useCustomerContext } from "../../CustomerContext";
|
||||
import { useCusQuery } from "../../hooks/useCusQuery";
|
||||
import { CustomerBreadcrumbs } from "./CustomerBreadcrumbs";
|
||||
import { SelectEntity } from "./SelectEntity";
|
||||
|
||||
export const CustomerPageHeader = () => {
|
||||
const { customer } = useCusQuery();
|
||||
const { entityId, setEntityId } = useCustomerContext();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<CustomerBreadcrumbs />
|
||||
<div className="flex w-full justify-between pl-10 pr-7">
|
||||
<div className="flex gap-2 w-full">
|
||||
<h2 className="flex text-lg text-t1 font-medium w-full max-w-md justify-start truncate">
|
||||
{customer.name ? (
|
||||
<span className="truncate">{customer.name}</span>
|
||||
) : customer.id ? (
|
||||
<span className="truncate font-mono">{customer.id}</span>
|
||||
) : (
|
||||
<span className="truncate">{customer.email}</span>
|
||||
)}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<SelectEntity entityId={entityId || ""} entities={customer.entities} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -9,37 +9,36 @@ import {
|
||||
import { Entity, Feature, FeatureUsageType } from "@autumn/shared";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
|
||||
import { CreateEntity } from "./create-entity/CreateEntity";
|
||||
import { CreateEntity } from "../customer-sidebar/create-entity/CreateEntity";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useCustomerContext } from "../CustomerContext";
|
||||
import { useCustomerContext } from "../../CustomerContext";
|
||||
import { useCusQuery } from "../../hooks/useCusQuery";
|
||||
import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
|
||||
|
||||
export const SelectEntity = ({
|
||||
entityId,
|
||||
entities,
|
||||
}: {
|
||||
entityId?: string;
|
||||
|
||||
entities: Entity[];
|
||||
}) => {
|
||||
const cusContext = useCustomerContext();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { features } = useFeaturesQuery();
|
||||
|
||||
if (!entities || entities.length === 0) {
|
||||
// Only show create entity flow if there are continuous use features
|
||||
const hasContinuousUseFeatures = cusContext?.features?.some(
|
||||
const hasContinuousUseFeatures = features?.some(
|
||||
(feature: Feature) =>
|
||||
feature.config?.usage_type === FeatureUsageType.Continuous
|
||||
);
|
||||
|
||||
if (!hasContinuousUseFeatures) {
|
||||
return null;
|
||||
}
|
||||
if (!hasContinuousUseFeatures) return null;
|
||||
|
||||
// Create entity flow
|
||||
return (
|
||||
<>
|
||||
<CreateEntity open={open} setOpen={setOpen} />
|
||||
@@ -7,9 +7,10 @@ import { ArrowUpRightFromSquare } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Link } from "react-router";
|
||||
import { useCustomerContext } from "../CustomerContext";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { SidebarLabel } from "@/components/general/sidebar/sidebar-label";
|
||||
import { useCusQuery } from "../../hooks/useCusQuery";
|
||||
|
||||
export const CustomerDetails = ({
|
||||
setIsModalOpen,
|
||||
setModalType,
|
||||
@@ -17,7 +18,7 @@ export const CustomerDetails = ({
|
||||
setIsModalOpen: (isModalOpen: boolean) => void;
|
||||
setModalType: (modalType: string) => void;
|
||||
}) => {
|
||||
const { customer } = useCustomerContext();
|
||||
const { customer } = useCusQuery();
|
||||
const env = useEnv();
|
||||
|
||||
return (
|
||||
@@ -1,15 +1,19 @@
|
||||
import { SideAccordion } from "@/components/general/SideAccordion";
|
||||
import { SidebarLabel } from "@/components/general/sidebar/sidebar-label";
|
||||
|
||||
import { useCustomerContext } from "../CustomerContext";
|
||||
import { Entity, Feature } from "@autumn/shared";
|
||||
|
||||
import { getFeatureName } from "@autumn/shared";
|
||||
|
||||
import CopyButton from "@/components/general/CopyButton";
|
||||
import { useCusQuery } from "../../hooks/useCusQuery";
|
||||
import { useCustomerContext } from "../../CustomerContext";
|
||||
|
||||
export const CustomerEntities = () => {
|
||||
const { entityId, setEntityId, entities, features } = useCustomerContext();
|
||||
const { customer, features } = useCusQuery();
|
||||
const { entityId } = useCustomerContext();
|
||||
|
||||
const entities = customer.entities;
|
||||
|
||||
const entity = entities.find(
|
||||
(entity: Entity) =>
|
||||
@@ -2,14 +2,17 @@ import { useState } from "react";
|
||||
import { Accordion } from "@/components/ui/accordion";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { CustomerRewards } from "./customer-rewards";
|
||||
import { useCustomerContext } from "../CustomerContext";
|
||||
import UpdateCustomerDialog from "../UpdateCustomerDialog";
|
||||
import { CustomerToolbar } from "../CustomerToolbar";
|
||||
import { CustomerDetails } from "./customer-details";
|
||||
import { useCustomerContext } from "../../CustomerContext";
|
||||
import UpdateCustomerDialog from "../../UpdateCustomerDialog";
|
||||
import { CustomerToolbar } from "../../CustomerToolbar";
|
||||
import { CustomerDetails } from "./CustomerDetails";
|
||||
import { CustomerEntities } from "./CustomerEntities";
|
||||
import { useCusQuery } from "../../hooks/useCusQuery";
|
||||
|
||||
export const CustomerSidebar = () => {
|
||||
const { customer, entities } = useCustomerContext();
|
||||
const { customer } = useCusQuery();
|
||||
const entities = customer.entities;
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [modalType, setModalType] = useState("coupon");
|
||||
|
||||
@@ -7,14 +7,15 @@ import {
|
||||
DialogFooter,
|
||||
DialogContent,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useCustomerContext } from "../../CustomerContext";
|
||||
import { useState } from "react";
|
||||
import { EntityConfig } from "./entity-config";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { toast } from "sonner";
|
||||
import { getBackendErr } from "@/utils/genUtils";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { EntityConfig } from "./EntityConfig";
|
||||
import { getBackendErr } from "@/utils/genUtils";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { useCustomerContext } from "../../../CustomerContext";
|
||||
import { useCusQuery } from "../../../hooks/useCusQuery";
|
||||
|
||||
export const CreateEntity = ({
|
||||
open,
|
||||
setOpen,
|
||||
@@ -23,6 +24,7 @@ export const CreateEntity = ({
|
||||
setOpen: (open: boolean) => void;
|
||||
}) => {
|
||||
const cusContext = useCustomerContext();
|
||||
const { customer, refetch } = useCusQuery();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -31,13 +33,11 @@ export const CreateEntity = ({
|
||||
name: "",
|
||||
});
|
||||
|
||||
const env = useEnv();
|
||||
const axiosInstance = useAxiosInstance({ env });
|
||||
const axiosInstance = useAxiosInstance();
|
||||
|
||||
if (!cusContext) {
|
||||
return null;
|
||||
}
|
||||
const { customer, cusMutate } = cusContext;
|
||||
if (!cusContext) return null;
|
||||
|
||||
// const { customer, cusMutate } = cusContext;
|
||||
|
||||
const handleCreateClicked = async () => {
|
||||
setIsLoading(true);
|
||||
@@ -51,10 +51,10 @@ export const CreateEntity = ({
|
||||
name: entity.name || null,
|
||||
feature_id: entity.feature_id,
|
||||
customer_id: customer.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await cusMutate();
|
||||
await refetch();
|
||||
setOpen(false);
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
@@ -7,8 +7,9 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useCustomerContext } from "../../CustomerContext";
|
||||
import { useCustomerContext } from "../../../CustomerContext";
|
||||
import { Feature, FeatureUsageType } from "@autumn/shared";
|
||||
import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
|
||||
|
||||
export const EntityConfig = ({
|
||||
entity,
|
||||
@@ -17,7 +18,8 @@ export const EntityConfig = ({
|
||||
entity: any;
|
||||
setEntity: (entity: any) => void;
|
||||
}) => {
|
||||
const { features } = useCustomerContext();
|
||||
const { features } = useFeaturesQuery();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-2">
|
||||
@@ -15,11 +15,20 @@ import { ArrowUpRightFromSquare } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Link } from "react-router";
|
||||
import AddCouponDialogContent from "../add-coupon/AddCouponDialogContent";
|
||||
import { useCustomerContext } from "../CustomerContext";
|
||||
import { useCusQuery } from "../../hooks/useCusQuery";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { useCusReferralQuery } from "../../hooks/useCusReferralQuery";
|
||||
import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
|
||||
import AddCouponDialogContent from "../../add-coupon/AddCouponDialogContent";
|
||||
|
||||
export const CustomerRewards = () => {
|
||||
const { discount, env } = useCustomerContext();
|
||||
// const { discount, env } = useCustomerContext();
|
||||
|
||||
const env = useEnv();
|
||||
// const { customer, rewards } = useCusQuery();
|
||||
const { referred, redeemed, stripeCus } = useCusReferralQuery();
|
||||
useRewardsQuery();
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const getDiscountText = (discount: any) => {
|
||||
@@ -44,9 +53,6 @@ export const CustomerRewards = () => {
|
||||
}
|
||||
return coupon.name;
|
||||
};
|
||||
const { referrals } = useCustomerContext();
|
||||
|
||||
// if (!referrals) return null;
|
||||
|
||||
return (
|
||||
<div className="flex w-full border-b mt-[2.5px] p-4">
|
||||
@@ -64,15 +70,15 @@ export const CustomerRewards = () => {
|
||||
variant="sidebarItem"
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
>
|
||||
{discount ? (
|
||||
getDiscountText(discount)
|
||||
{stripeCus?.discount ? (
|
||||
getDiscountText(stripeCus?.discount)
|
||||
) : (
|
||||
<span className="text-t3">Add Coupon</span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
{referrals?.referred.length > 0 && (
|
||||
{referred?.length > 0 && (
|
||||
<>
|
||||
<span className="text-t3 text-xs font-medium col-span-2">
|
||||
Referrals
|
||||
@@ -82,7 +88,7 @@ export const CustomerRewards = () => {
|
||||
<div className="col-span-6 justify-end flex">
|
||||
<PopoverTrigger className="">
|
||||
<Button variant="sidebarItem">
|
||||
{referrals.referred.length} referred
|
||||
{referred.length} referred
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</div>
|
||||
@@ -94,11 +100,11 @@ export const CustomerRewards = () => {
|
||||
sideOffset={5}
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
{referrals.referred.map((referral: any) => (
|
||||
{referred.map((referral: any) => (
|
||||
<Link
|
||||
to={getRedirectUrl(
|
||||
`/customers/${referral.customer.id}`,
|
||||
env,
|
||||
env
|
||||
)}
|
||||
className="flex justify-between hover:bg-zinc-100 items-center"
|
||||
key={referral.customer.id}
|
||||
@@ -120,10 +126,7 @@ export const CustomerRewards = () => {
|
||||
<div className="col-span-6 justify-end flex">
|
||||
<PopoverTrigger className="">
|
||||
<Button variant="sidebarItem">
|
||||
{
|
||||
referrals.referred.filter((r: any) => r.triggered)
|
||||
.length
|
||||
}{" "}
|
||||
{referred.filter((r: any) => r.triggered).length}{" "}
|
||||
activated
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@@ -136,13 +139,13 @@ export const CustomerRewards = () => {
|
||||
sideOffset={5}
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
{referrals.referred
|
||||
{referred
|
||||
.filter((r: any) => r.triggered)
|
||||
.map((referral: any) => (
|
||||
<Link
|
||||
to={getRedirectUrl(
|
||||
`/customers/${referral.customer.id}`,
|
||||
env,
|
||||
env
|
||||
)}
|
||||
className="flex justify-between hover:bg-zinc-100 items-center"
|
||||
key={referral.customer.id}
|
||||
@@ -161,7 +164,7 @@ export const CustomerRewards = () => {
|
||||
</Popover>
|
||||
</>
|
||||
)}
|
||||
{referrals?.redeemed.length > 0 && (
|
||||
{redeemed?.length > 0 && (
|
||||
<>
|
||||
<span className="text-t3 text-xs font-medium col-span-2">
|
||||
Referred by
|
||||
@@ -171,13 +174,13 @@ export const CustomerRewards = () => {
|
||||
<Button variant="sidebarItem">
|
||||
<Link
|
||||
to={getRedirectUrl(
|
||||
`/customers/${referrals.redeemed[0].referral_code?.customer.id}`,
|
||||
env,
|
||||
`/customers/${redeemed[0].referral_code?.customer.id}`,
|
||||
env
|
||||
)}
|
||||
className="flex items-center gap-1 truncate w-full"
|
||||
>
|
||||
<span className="truncate">
|
||||
{referrals.redeemed[0].referral_code?.customer.name}
|
||||
{redeemed[0].referral_code?.customer.name}
|
||||
</span>
|
||||
<div className="flex items-center justify-center">
|
||||
<ArrowUpRightFromSquare
|
||||
@@ -195,8 +198,8 @@ export const CustomerRewards = () => {
|
||||
sideOffset={5}
|
||||
>
|
||||
<p>
|
||||
{referrals.redeemed[0].referral_code?.customer.id}{" "}
|
||||
{referrals.redeemed[0].referral_code.code}
|
||||
{redeemed[0].referral_code?.customer.id}{" "}
|
||||
{redeemed[0].referral_code.code}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -4,15 +4,16 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
import { toast } from "sonner";
|
||||
import { useState } from "react";
|
||||
import { CusProductStatus, FullCusProduct } from "@autumn/shared";
|
||||
import { Dialog, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useCustomerContext } from "../CustomerContext";
|
||||
import { getBackendErr, notNullish } from "@/utils/genUtils";
|
||||
import { getBackendErr } from "@/utils/genUtils";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
|
||||
export const CancelProductDialog = ({
|
||||
cusProduct,
|
||||
@@ -23,10 +24,10 @@ export const CancelProductDialog = ({
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}) => {
|
||||
const axiosInstance = useAxiosInstance();
|
||||
const { customer, refetch } = useCusQuery();
|
||||
const [immediateLoading, setImmediateLoading] = useState(false);
|
||||
const [endOfCycleLoading, setEndOfCycleLoading] = useState(false);
|
||||
const axiosInstance = useAxiosInstance();
|
||||
const { cusMutate, customer, entities } = useCustomerContext();
|
||||
|
||||
const handleClicked = async (cancelImmediately?: boolean) => {
|
||||
if (cancelImmediately) {
|
||||
@@ -35,7 +36,7 @@ export const CancelProductDialog = ({
|
||||
setEndOfCycleLoading(true);
|
||||
}
|
||||
|
||||
const entity = entities.find(
|
||||
const entity = customer.entities.find(
|
||||
(e: any) => e.internal_id === cusProduct.internal_entity_id
|
||||
);
|
||||
|
||||
@@ -47,7 +48,7 @@ export const CancelProductDialog = ({
|
||||
cancel_immediately: cancelImmediately,
|
||||
prorate: false,
|
||||
});
|
||||
await cusMutate();
|
||||
await refetch();
|
||||
setOpen(false);
|
||||
toast.success("Product cancelled");
|
||||
} catch (error) {
|
||||
|
||||
@@ -20,11 +20,8 @@ export const CusProductStatusItem = ({
|
||||
const trialing =
|
||||
cusProduct.trial_ends_at && cusProduct.trial_ends_at > Date.now();
|
||||
|
||||
// const canceled = notNullish(cusProduct.canceled_at);
|
||||
const canceled = cusProduct.canceled;
|
||||
// console.log(
|
||||
// `entity ID: ${cusProduct.entity_id}, canceled: ${canceled}, product: ${cusProduct.product_id}`
|
||||
// );
|
||||
|
||||
if (canceled) return "canceled";
|
||||
|
||||
if (trialing) {
|
||||
@@ -64,15 +61,6 @@ export const CusProductStatusItem = ({
|
||||
>
|
||||
{getTitle()}
|
||||
</Badge>
|
||||
{/* {isCanceled && (
|
||||
<Badge variant="status" className="ml-2 bg-gray-500">
|
||||
canceled
|
||||
</Badge>
|
||||
)} */}
|
||||
|
||||
{/* <span className="text-t3">
|
||||
ends {formatUnixToDateTime(cusProduct.trial_ends_at).date}
|
||||
</span> */}
|
||||
|
||||
<CusProductStripeLink cusProduct={cusProduct} />
|
||||
</div>
|
||||
|
||||
@@ -12,13 +12,15 @@ import { useCustomerContext } from "../CustomerContext";
|
||||
import { TransferProductDialog } from "./TransferProductDialog";
|
||||
import { ArrowLeftRight, ArrowRightFromLine, Delete } from "lucide-react";
|
||||
import { CancelProductDialog } from "./CancelProductDialog";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
|
||||
export const CusProductToolbar = ({
|
||||
cusProduct,
|
||||
}: {
|
||||
cusProduct: FullCusProduct;
|
||||
}) => {
|
||||
const { showEntityView, customer } = useCustomerContext();
|
||||
const { customer } = useCusQuery();
|
||||
const { showEntityView } = useCustomerContext();
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [transferOpen, setTransferOpen] = useState(false);
|
||||
const [cancelOpen, setCancelOpen] = useState(false);
|
||||
@@ -65,8 +67,6 @@ export const CusProductToolbar = ({
|
||||
>
|
||||
<p>Cancel</p>
|
||||
<Delete width={14} className="text-t3" />
|
||||
{/* <ArrowRightFromLine width={14} className="text-t3" /> */}
|
||||
{/* <UpdateStatusDropdownBtn cusProduct={cusProduct} /> */}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
|
||||
import { compareStatus, navigateTo, notNullish } from "@/utils/genUtils";
|
||||
import {
|
||||
compareStatus,
|
||||
navigateTo,
|
||||
notNullish,
|
||||
pushPage,
|
||||
} from "@/utils/genUtils";
|
||||
import { CusProduct, CusProductStatus, FullCusProduct } from "@autumn/shared";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useCustomerContext } from "../CustomerContext";
|
||||
@@ -17,24 +22,28 @@ import { CusProductStatusItem } from "../customer-product-list/CusProductStatus"
|
||||
import { CusProductEntityItem } from "../components/CusProductEntityItem";
|
||||
import { CusProductToolbar } from "./CusProductToolbar";
|
||||
import { MultiAttachDialog } from "../product/multi-attach/MultiAttachDialog";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
|
||||
import { getVersionCounts } from "@/utils/productUtils";
|
||||
|
||||
export const CustomerProductList = ({
|
||||
customer,
|
||||
products,
|
||||
}: {
|
||||
customer: any;
|
||||
products: any;
|
||||
}) => {
|
||||
export const CustomerProductList = () => {
|
||||
const navigate = useNavigate();
|
||||
const { env, versionCounts, entities, entityId, showEntityView } =
|
||||
useCustomerContext();
|
||||
const { customer } = useCusQuery();
|
||||
const { entityId, showEntityView } = useCustomerContext();
|
||||
|
||||
const { products } = useProductsQuery();
|
||||
const versionCounts = getVersionCounts(products);
|
||||
|
||||
// const { env, versionCounts, entities, entityId, showEntityView } =
|
||||
// useCustomerContext();
|
||||
|
||||
const [showExpired, setShowExpired] = useState(false);
|
||||
|
||||
const [multiAttachOpen, setMultiAttachOpen] = useState(false);
|
||||
|
||||
const sortedProducts = customer.products
|
||||
.filter((p: CusProduct & { entitlements: any[] }) => {
|
||||
const entities = customer.entities;
|
||||
|
||||
const sortedProducts = customer.customer_products
|
||||
.filter((cp: FullCusProduct) => {
|
||||
if (showExpired) {
|
||||
return true;
|
||||
}
|
||||
@@ -42,17 +51,17 @@ export const CustomerProductList = ({
|
||||
const entity = entities.find((e: any) => e.id === entityId);
|
||||
|
||||
const entityMatches =
|
||||
entity && notNullish(p.internal_entity_id)
|
||||
? p.internal_entity_id === entity.internal_id ||
|
||||
p.entitlements.some(
|
||||
(cusEnt: any) =>
|
||||
cusEnt.entities &&
|
||||
Object.keys(cusEnt.entities).includes(entity.internal_id)
|
||||
entity && notNullish(cp.internal_entity_id)
|
||||
? cp.internal_entity_id === entity.internal_id ||
|
||||
cp.customer_entitlements.some(
|
||||
(ce: any) =>
|
||||
ce.entities &&
|
||||
Object.keys(ce.entities).includes(entity.internal_id)
|
||||
)
|
||||
: true;
|
||||
|
||||
return (
|
||||
p.status !== CusProductStatus.Expired &&
|
||||
cp.status !== CusProductStatus.Expired &&
|
||||
(entityId ? entityMatches : true)
|
||||
);
|
||||
})
|
||||
@@ -93,7 +102,7 @@ export const CustomerProductList = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center grid grid-cols-10 gap-8 justify-between border-y bg-stone-100 pl-10 pr-7 h-10">
|
||||
<div className="items-center grid grid-cols-10 gap-8 justify-between border-y bg-stone-100 pl-10 pr-7 h-10">
|
||||
<h2 className="text-sm text-t2 font-medium col-span-2 flex">
|
||||
Products
|
||||
</h2>
|
||||
@@ -112,11 +121,11 @@ export const CustomerProductList = ({
|
||||
</Button>
|
||||
{/* <CreateEntitlement buttonType={"feature"} /> */}
|
||||
<div className="flex items-center gap-0">
|
||||
<MultiAttachDialog
|
||||
{/* <MultiAttachDialog
|
||||
open={multiAttachOpen}
|
||||
setOpen={setMultiAttachOpen}
|
||||
/>
|
||||
<AddProduct setMultiAttachOpen={setMultiAttachOpen} />
|
||||
<AddProduct setMultiAttachOpen={setMultiAttachOpen} /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -150,15 +159,26 @@ export const CustomerProductList = ({
|
||||
const entity = entities.find(
|
||||
(e: any) => e.internal_id === cusProduct.internal_entity_id
|
||||
);
|
||||
navigateTo(
|
||||
`/customers/${customer.id || customer.internal_id}/${
|
||||
cusProduct.product_id
|
||||
}?id=${cusProduct.id}${
|
||||
entity ? `&entity_id=${entity.id || entity.internal_id}` : ""
|
||||
}`,
|
||||
|
||||
pushPage({
|
||||
path: `/customers/${customer.id || customer.internal_id}/${cusProduct.product_id}`,
|
||||
queryParams: {
|
||||
id: cusProduct.id,
|
||||
entity_id: entity
|
||||
? entity.id || entity.internal_id
|
||||
: undefined,
|
||||
},
|
||||
navigate,
|
||||
env
|
||||
);
|
||||
});
|
||||
|
||||
// navigateTo(
|
||||
// `/customers/${customer.id || customer.internal_id}/${
|
||||
// cusProduct.product_id
|
||||
// }?id=${cusProduct.id}${
|
||||
// entity ? `&entity_id=${entity.id || entity.internal_id}` : ""
|
||||
// }`,
|
||||
// navigate
|
||||
// );
|
||||
}}
|
||||
>
|
||||
<Item className="col-span-3">
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useCustomerContext } from "../CustomerContext";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { toast } from "sonner";
|
||||
import { getBackendErr } from "@/utils/genUtils";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
|
||||
export const TransferProductDialog = ({
|
||||
cusProduct,
|
||||
@@ -30,13 +31,13 @@ export const TransferProductDialog = ({
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}) => {
|
||||
const { entities, cusMutate } = useCustomerContext();
|
||||
const { customer, refetch } = useCusQuery();
|
||||
const axiosInstance = useAxiosInstance();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const filteredEntities = entities.filter(
|
||||
const [selectedEntity, setSelectedEntity] = useState<any>(null);
|
||||
const filteredEntities = customer.entities.filter(
|
||||
(entity: any) => entity.internal_id !== cusProduct.internal_entity_id
|
||||
);
|
||||
const [selectedEntity, setSelectedEntity] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@@ -53,7 +54,7 @@ export const TransferProductDialog = ({
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const fromEntity = entities.find(
|
||||
const fromEntity = customer.entities.find(
|
||||
(e: any) => e.internal_id === cusProduct.internal_entity_id
|
||||
);
|
||||
await axiosInstance.post(
|
||||
@@ -66,7 +67,7 @@ export const TransferProductDialog = ({
|
||||
// customer_product_id: cusProduct.id,
|
||||
}
|
||||
);
|
||||
await cusMutate();
|
||||
await refetch();
|
||||
toast.success("Product transferred successfully");
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
@@ -93,7 +94,9 @@ export const TransferProductDialog = ({
|
||||
<Select
|
||||
value={selectedEntity?.id}
|
||||
onValueChange={(value) => {
|
||||
setSelectedEntity(entities.find((e: any) => e.id === value));
|
||||
setSelectedEntity(
|
||||
customer.entities.find((e: any) => e.id === value)
|
||||
);
|
||||
}}
|
||||
disabled={filteredEntities.length == 0}
|
||||
>
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
AllowanceType,
|
||||
FeatureType,
|
||||
FullCusEntWithFullCusProduct,
|
||||
FullCusProduct,
|
||||
FullCustomerEntitlement,
|
||||
} from "@autumn/shared";
|
||||
|
||||
@@ -22,6 +23,8 @@ import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CusProductEntityItem } from "../components/CusProductEntityItem";
|
||||
import { CusEntBalance } from "./CusEntBalance";
|
||||
import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
|
||||
export const CustomerEntitlementsList = () => {
|
||||
const [featureType, setFeatureType] = useState<FeatureType>(
|
||||
@@ -29,8 +32,8 @@ export const CustomerEntitlementsList = () => {
|
||||
);
|
||||
const [showExpired, setShowExpired] = useState(false);
|
||||
|
||||
const { products, customer, entities, entityId, showEntityView } =
|
||||
useCustomerContext();
|
||||
const { entityId, showEntityView } = useCustomerContext();
|
||||
const { customer, products, features, entities } = useCusQuery();
|
||||
|
||||
const [selectedCusEntitlement, setSelectedCusEntitlement] =
|
||||
useState<FullCustomerEntitlement | null>(null);
|
||||
@@ -67,11 +70,10 @@ export const CustomerEntitlementsList = () => {
|
||||
if (entityId) {
|
||||
entityMatch = false;
|
||||
|
||||
const cusProduct = customer.products.find(
|
||||
const cusProduct = customer.customer_products.find(
|
||||
(p: any) => p.id === cusEnt.customer_product_id
|
||||
);
|
||||
|
||||
// 1. Product match
|
||||
const productAttachedToEntity =
|
||||
cusProduct?.internal_entity_id === entity?.internal_id;
|
||||
|
||||
@@ -154,7 +156,7 @@ export const CustomerEntitlementsList = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center grid grid-cols-10 gap-8 justify-between border-y bg-stone-100 px-10 h-10">
|
||||
<div className="items-center grid grid-cols-10 gap-8 justify-between border-y bg-stone-100 px-10 h-10">
|
||||
<h2 className="text-sm text-t2 font-medium col-span-2 flex whitespace-nowrap">
|
||||
Available Features
|
||||
</h2>
|
||||
@@ -231,7 +233,6 @@ export const CustomerEntitlementsList = () => {
|
||||
|
||||
{filteredEntitlements.map((cusEnt: FullCusEntWithFullCusProduct) => {
|
||||
const entitlement = cusEnt.entitlement;
|
||||
const allowanceType = entitlement.allowance_type;
|
||||
|
||||
return (
|
||||
<Row
|
||||
@@ -265,8 +266,8 @@ export const CustomerEntitlementsList = () => {
|
||||
<div className="flex items-center gap-2 max-w-[150px] truncate text-t3">
|
||||
{/* {getProductName(cusEnt)} */}
|
||||
{cusEnt.customer_product.product.name}
|
||||
{customer.products.find(
|
||||
(p: any) => p.id === cusEnt.customer_product_id
|
||||
{customer.customer_products.find(
|
||||
(cp: FullCusProduct) => cp.id === cusEnt.customer_product_id
|
||||
)?.status === "expired" && (
|
||||
<Badge variant="status" className="bg-black">
|
||||
expired
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { DialogContent } from "@/components/ui/dialog";
|
||||
import { Dialog } from "@/components/ui/dialog";
|
||||
import { FullCustomerEntitlement } from "@autumn/shared";
|
||||
import { FullCusProduct, FullCustomerEntitlement } from "@autumn/shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCustomerContext } from "../CustomerContext";
|
||||
import FieldLabel from "@/components/general/modal-components/FieldLabel";
|
||||
@@ -21,6 +21,7 @@ import { toast } from "sonner";
|
||||
import { getBackendErr, notNullish } from "@/utils/genUtils";
|
||||
import CopyButton from "@/components/general/CopyButton";
|
||||
import { AlertCircle, Info, InfoIcon } from "lucide-react";
|
||||
import { useCusQuery } from "../hooks/useCusQuery";
|
||||
|
||||
function UpdateCusEntitlement({
|
||||
selectedCusEntitlement,
|
||||
@@ -29,13 +30,13 @@ function UpdateCusEntitlement({
|
||||
selectedCusEntitlement: FullCustomerEntitlement | null;
|
||||
setSelectedCusEntitlement: (cusEnt: FullCustomerEntitlement | null) => void;
|
||||
}) {
|
||||
// Get customer product
|
||||
const { customer, env, cusMutate, entityId } = useCustomerContext();
|
||||
const axiosInstance = useAxiosInstance({ env });
|
||||
const { customer, refetch } = useCusQuery();
|
||||
const { entityId } = useCustomerContext();
|
||||
// const { customer, env, cusMutate, entityId } = useCustomerContext();
|
||||
const cusEnt = selectedCusEntitlement;
|
||||
|
||||
const [updateLoading, setUpdateLoading] = useState(false);
|
||||
|
||||
const cusEnt = selectedCusEntitlement;
|
||||
const axiosInstance = useAxiosInstance();
|
||||
|
||||
const [updateFields, setUpdateFields] = useState<any>({
|
||||
balance:
|
||||
@@ -46,8 +47,8 @@ function UpdateCusEntitlement({
|
||||
});
|
||||
|
||||
const getCusProduct = (cusEnt: FullCustomerEntitlement) => {
|
||||
const cusProduct = customer.products.find(
|
||||
(p: any) => p.id === cusEnt.customer_product_id
|
||||
const cusProduct = customer.customer_products.find(
|
||||
(cp: FullCusProduct) => cp.id === cusEnt.customer_product_id
|
||||
);
|
||||
return cusProduct;
|
||||
};
|
||||
@@ -95,7 +96,7 @@ function UpdateCusEntitlement({
|
||||
}
|
||||
);
|
||||
toast.success("Entitlement updated successfully");
|
||||
await cusMutate();
|
||||
await refetch();
|
||||
setSelectedCusEntitlement(null);
|
||||
} catch (error) {
|
||||
toast.error(getBackendErr(error, "Failed to update entitlement"));
|
||||
@@ -106,8 +107,6 @@ function UpdateCusEntitlement({
|
||||
const cusPrice = cusProduct?.customer_prices.find(
|
||||
(cp: any) => cp.price.entitlement_id === cusEnt?.entitlement.id
|
||||
);
|
||||
console.log("Cus price:", cusPrice);
|
||||
console.log("Cus product:", cusProduct);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -177,17 +176,3 @@ function UpdateCusEntitlement({
|
||||
}
|
||||
|
||||
export default UpdateCusEntitlement;
|
||||
|
||||
// const DateInput = ({
|
||||
// value,
|
||||
// onChange,
|
||||
// }: {
|
||||
// value: string;
|
||||
// onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
// }) => {
|
||||
// const [date, setDate] = React.useState<Date>();
|
||||
|
||||
// return (
|
||||
|
||||
// );
|
||||
// };
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { FullCustomer } from "@autumn/shared";
|
||||
|
||||
export const useCachedCustomer = (customerId: string | undefined) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const getCachedCustomer = (): FullCustomer | null => {
|
||||
if (!customerId) return null;
|
||||
|
||||
// Check all cached full customers queries
|
||||
const queryCache = queryClient.getQueryCache();
|
||||
const fullCustomersQueries = queryCache.findAll({
|
||||
queryKey: ["full_customers"],
|
||||
});
|
||||
|
||||
// Sort by most recently updated first to get the freshest data
|
||||
const sortedQueries = fullCustomersQueries.sort((a, b) => {
|
||||
const aTime = a.state.dataUpdatedAt || 0;
|
||||
const bTime = b.state.dataUpdatedAt || 0;
|
||||
return bTime - aTime;
|
||||
});
|
||||
|
||||
for (const query of sortedQueries) {
|
||||
// Only use data that's not stale and has been successfully fetched
|
||||
if (query.state.status === "success" && query.state.data) {
|
||||
const cachedData = query.state.data as
|
||||
| { fullCustomers: FullCustomer[] }
|
||||
| undefined;
|
||||
|
||||
if (cachedData?.fullCustomers) {
|
||||
const cachedCustomer = cachedData.fullCustomers.find(
|
||||
(customer) =>
|
||||
customer.id === customerId || customer.internal_id === customerId
|
||||
);
|
||||
|
||||
if (cachedCustomer) {
|
||||
return cachedCustomer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return { getCachedCustomer };
|
||||
};
|
||||
52
vite/src/views/customers/customer/hooks/useCusQuery.tsx
Normal file
52
vite/src/views/customers/customer/hooks/useCusQuery.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
|
||||
import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
|
||||
import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "react-router";
|
||||
import { useCachedCustomer } from "./useCachedCustomer";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const useCusQuery = () => {
|
||||
const { customer_id } = useParams();
|
||||
const axiosInstance = useAxiosInstance();
|
||||
const { getCachedCustomer } = useCachedCustomer(customer_id);
|
||||
|
||||
const cachedCustomer = useMemo(getCachedCustomer, [getCachedCustomer]);
|
||||
|
||||
const fetcher = async () => {
|
||||
const { data } = await axiosInstance.get(`/customers/${customer_id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading: customerLoading,
|
||||
error,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ["customer", customer_id],
|
||||
queryFn: fetcher,
|
||||
});
|
||||
|
||||
const { products, isLoading: productsLoading } = useProductsQuery();
|
||||
const { features, isLoading: featuresLoading } = useFeaturesQuery();
|
||||
|
||||
const customer = cachedCustomer || data?.customer;
|
||||
const cusWithCacheLoading = cachedCustomer ? false : customerLoading;
|
||||
|
||||
return {
|
||||
customer: customer,
|
||||
entities: customer?.entities,
|
||||
products,
|
||||
features,
|
||||
isLoading: cusWithCacheLoading || productsLoading || featuresLoading,
|
||||
error,
|
||||
refetch,
|
||||
};
|
||||
|
||||
// const { data, isLoading, error } = useQuery({
|
||||
// queryKey: ["customer", customerId],
|
||||
// queryFn: fetcher,
|
||||
// });
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "react-router";
|
||||
|
||||
export const useCusReferralQuery = () => {
|
||||
const { customer_id } = useParams();
|
||||
const axiosInstance = useAxiosInstance();
|
||||
|
||||
const referralFetcher = async () => {
|
||||
console.log("referralFetcher");
|
||||
const { data } = await axiosInstance.get(
|
||||
`/customers/${customer_id}/referrals`
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const {
|
||||
data: cusRewardData,
|
||||
isLoading: cusRewardLoading,
|
||||
error: cusRewardError,
|
||||
refetch: cusRewardRefetch,
|
||||
} = useQuery({
|
||||
queryKey: ["customer_referrals", customer_id],
|
||||
queryFn: referralFetcher,
|
||||
});
|
||||
|
||||
return {
|
||||
stripeCus: cusRewardData?.stripeCus,
|
||||
|
||||
redeemed: cusRewardData?.redeemed,
|
||||
referred: cusRewardData?.referred,
|
||||
cusRewardLoading,
|
||||
cusRewardError,
|
||||
cusRewardRefetch,
|
||||
};
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { useCustomersQueryStates } from "./useCustomersQueryStates";
|
||||
import {
|
||||
CusProductSchema,
|
||||
CustomerSchema,
|
||||
FullCustomer,
|
||||
ProductSchema,
|
||||
} from "@autumn/shared";
|
||||
import { z } from "zod";
|
||||
@@ -56,7 +57,6 @@ export const useCusSearchQuery = () => {
|
||||
queryStates.q,
|
||||
],
|
||||
queryFn: fetcher,
|
||||
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
|
||||
42
vite/src/views/customers/hooks/useFullCusSearchQuery.tsx
Normal file
42
vite/src/views/customers/hooks/useFullCusSearchQuery.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { FullCustomer } from "@autumn/shared";
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import { useCustomersQueryStates } from "./useCustomersQueryStates";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
|
||||
export const useFullCusSearchQuery = () => {
|
||||
const { queryStates } = useCustomersQueryStates();
|
||||
const axiosInstance = useAxiosInstance();
|
||||
|
||||
const { data: fullCustomersData } = useQuery<{
|
||||
fullCustomers: FullCustomer[];
|
||||
}>({
|
||||
queryKey: [
|
||||
"full_customers",
|
||||
queryStates.page,
|
||||
queryStates.status,
|
||||
queryStates.version,
|
||||
queryStates.none,
|
||||
queryStates.q,
|
||||
],
|
||||
queryFn: async () => {
|
||||
console.log("Fetching full customers: ", queryStates.q);
|
||||
const { data } = await axiosInstance.post(
|
||||
`/customers/all/full_customers`,
|
||||
{
|
||||
search: queryStates.q,
|
||||
page_size: 50,
|
||||
page: queryStates.page,
|
||||
filters: {
|
||||
status: queryStates.status,
|
||||
version: queryStates.version,
|
||||
none: queryStates.none,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
console.log("data", data);
|
||||
return data;
|
||||
},
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
};
|
||||
@@ -36,7 +36,7 @@ export const NavButton = ({
|
||||
isGroup?: boolean;
|
||||
}) => {
|
||||
// Get window path
|
||||
env = useEnv();
|
||||
const finalEnv = useEnv();
|
||||
const tab = useTab();
|
||||
const { expanded } = useSidebarContext();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useProductContext } from "./ProductContext";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { ProductItemTable } from "./product-item/ProductItemTable";
|
||||
import { SelectEntity } from "@/views/customers/customer/customer-sidebar/select-entity";
|
||||
import { SelectEntity } from "@/views/customers/customer/components/customer-header/SelectEntity";
|
||||
|
||||
export const ManageProduct = ({
|
||||
hideAdminHover = false,
|
||||
|
||||
Reference in New Issue
Block a user