fixed customer search
This commit is contained in:
@@ -30,7 +30,9 @@ import {
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
const CustomerWithProductsSchema = CustomerSchema.extend({
|
||||
customer_products: z.array(CusProductSchema.extend({ product: ProductSchema })),
|
||||
customer_products: z.array(
|
||||
CusProductSchema.extend({ product: ProductSchema })
|
||||
),
|
||||
});
|
||||
type CustomerWithProducts = z.infer<typeof CustomerWithProductsSchema>;
|
||||
|
||||
@@ -42,10 +44,15 @@ export const CustomersTable = ({
|
||||
const { env } = useCustomersContext();
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
|
||||
// console.log("customers", customers);
|
||||
const getCusProductsInfo = (customer: CustomerWithProducts) => {
|
||||
if (
|
||||
!customer.customer_products ||
|
||||
customer.customer_products.length === 0
|
||||
) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
// Filter out expired products first
|
||||
const activeProducts = customer.customer_products.filter(
|
||||
(cusProduct) => cusProduct.status !== CusProductStatus.Expired
|
||||
@@ -103,14 +110,17 @@ export const CustomersTable = ({
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{activeProducts.slice(0, 1).map((cusProduct: any) => (
|
||||
<div key={cusProduct.id}>
|
||||
{activeProducts.slice(0, 1).map((cusProduct: any, index: number) => (
|
||||
<div key={index}>
|
||||
{getProductBadge(cusProduct)}
|
||||
{activeProducts.length > 1 && (
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger>
|
||||
<Badge variant="status" className="ml-1 bg-stone-100 text-primary">
|
||||
<Badge
|
||||
variant="status"
|
||||
className="ml-1 bg-stone-100 text-primary"
|
||||
>
|
||||
+{activeProducts.length - 1}
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
@@ -143,24 +153,16 @@ export const CustomersTable = ({
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{customers.map((customer) => (
|
||||
{customers.map((customer, index) => (
|
||||
<TableRow
|
||||
key={customer.id}
|
||||
key={index}
|
||||
className="cursor-pointer"
|
||||
onClick={() => navigateTo(`/customers/${customer.id}`, router, env)}
|
||||
>
|
||||
<TableCell>
|
||||
{customer.name}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono">
|
||||
{customer.id}{" "}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{customer.email}{" "}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{getCusProductsInfo(customer)}
|
||||
</TableCell>
|
||||
<TableCell>{customer.name}</TableCell>
|
||||
<TableCell className="font-mono">{customer.id} </TableCell>
|
||||
<TableCell>{customer.email} </TableCell>
|
||||
<TableCell>{getCusProductsInfo(customer)}</TableCell>
|
||||
<TableCell className="min-w-20 w-24">
|
||||
{formatUnixToDateTime(customer.created_at).date}
|
||||
<span className="text-t3">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import { useAxiosPostSWR, useAxiosSWR } from "@/services/useAxiosSwr";
|
||||
import { CustomersContext } from "./CustomersContext";
|
||||
@@ -17,18 +17,24 @@ import CreateCustomer from "./CreateCustomer";
|
||||
import { SearchBar } from "./SearchBar";
|
||||
import LoadingScreen from "../general/LoadingScreen";
|
||||
import FilterButton from "./FilterButton";
|
||||
import { debounce } from "lodash";
|
||||
import SmallSpinner from "@/components/general/SmallSpinner";
|
||||
|
||||
function CustomersView({ env }: { env: AppEnv }) {
|
||||
// const [debouncedSearch, setDebouncedSearch] = React.useState("");
|
||||
const pageSize = 50;
|
||||
const [currentPage, setCurrentPage] = React.useState(1);
|
||||
const [searchQuery, setSearchQuery] = React.useState("");
|
||||
const [filters, setFilters] = React.useState<any>({});
|
||||
const [lastItemStack, setLastItemStack] = React.useState<any[]>([]);
|
||||
// url: debouncedSearch
|
||||
// ? `/customers/search?search=${debouncedSearch}&page=${currentPage}`
|
||||
// : `/customers?page=${currentPage}`,
|
||||
// Get products
|
||||
// const [currentPage, setCurrentPage] = React.useState(1);
|
||||
// const [lastItemStack, setLastItemStack] = React.useState<any[]>([]);
|
||||
const [pagination, setPagination] = React.useState<{
|
||||
page: number;
|
||||
lastItemStack: any;
|
||||
}>({
|
||||
page: 1,
|
||||
lastItemStack: [],
|
||||
});
|
||||
const [paginationLoading, setPaginationLoading] = React.useState(false);
|
||||
|
||||
const { data: productsData, isLoading: productsLoading } = useAxiosSWR({
|
||||
url: `/products/data`,
|
||||
@@ -36,34 +42,26 @@ function CustomersView({ env }: { env: AppEnv }) {
|
||||
});
|
||||
|
||||
const { data, isLoading, error, mutate } = useAxiosPostSWR({
|
||||
url: `/customers/search`,
|
||||
url: `/v1/customers/search`,
|
||||
env,
|
||||
data: {
|
||||
page: currentPage,
|
||||
page: pagination.page,
|
||||
page_size: pageSize,
|
||||
search: searchQuery,
|
||||
filters,
|
||||
last_item: lastItemStack[lastItemStack.length - 1],
|
||||
last_item: pagination.lastItemStack[pagination.lastItemStack.length - 1],
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log(pagination.lastItemStack[pagination.lastItemStack.length - 1]);
|
||||
const fetchData = async () => {
|
||||
// If filters changed (not on mount), reset to page 1 and clear lastItem
|
||||
if (Object.keys(filters).length > 0) {
|
||||
setCurrentPage(1);
|
||||
setLastItemStack([]);
|
||||
}
|
||||
setPaginationLoading(true);
|
||||
await mutate();
|
||||
setPaginationLoading(false);
|
||||
};
|
||||
fetchData();
|
||||
}, [currentPage, filters, mutate]);
|
||||
|
||||
// useEffect(() => {
|
||||
// const fetchData = async () => {
|
||||
// await mutate();
|
||||
// };
|
||||
// fetchData();
|
||||
// }, [currentPage, mutate]);
|
||||
}, [pagination, filters, mutate]);
|
||||
|
||||
// useEffect(() => {
|
||||
// const updateFilters = async () => {
|
||||
@@ -76,22 +74,35 @@ function CustomersView({ env }: { env: AppEnv }) {
|
||||
|
||||
const totalPages = Math.ceil((data?.totalCount || 0) / pageSize);
|
||||
|
||||
// return <LoadingScreen />;
|
||||
if (isLoading || productsLoading) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
const handleNextPage = () => {
|
||||
const lastItem = data?.customers[data?.customers.length - 1];
|
||||
const newLastItemStack = [...lastItemStack, lastItem];
|
||||
setLastItemStack(newLastItemStack);
|
||||
setCurrentPage(currentPage + 1);
|
||||
const handleNextPage = async () => {
|
||||
if (pagination.page === totalPages) return;
|
||||
setPagination((prev) => {
|
||||
const lastItem = data?.customers[data?.customers.length - 1];
|
||||
const newItem = {
|
||||
created_at: lastItem.created_at,
|
||||
name: lastItem.name,
|
||||
internal_id: lastItem.internal_id,
|
||||
};
|
||||
|
||||
const newLastItemStack = [...prev.lastItemStack, newItem];
|
||||
return {
|
||||
page: prev.page + 1,
|
||||
lastItemStack: newLastItemStack,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handlePreviousPage = () => {
|
||||
const newLastItemStack = lastItemStack.slice(0, -1);
|
||||
setLastItemStack(newLastItemStack);
|
||||
setCurrentPage(currentPage - 1);
|
||||
const handlePreviousPage = async () => {
|
||||
if (pagination.page === 1) return;
|
||||
const newLastItemStack = pagination.lastItemStack.slice(0, -1);
|
||||
setPagination({
|
||||
page: pagination.page - 1,
|
||||
lastItemStack: newLastItemStack,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -114,7 +125,13 @@ function CustomersView({ env }: { env: AppEnv }) {
|
||||
<SearchBar
|
||||
query={searchQuery}
|
||||
setQuery={setSearchQuery}
|
||||
setCurrentPage={setCurrentPage}
|
||||
setCurrentPage={(page: number) => {
|
||||
setPagination({
|
||||
page: page,
|
||||
lastItemStack: [],
|
||||
});
|
||||
mutate();
|
||||
}}
|
||||
mutate={mutate}
|
||||
/>
|
||||
<FilterButton />
|
||||
@@ -125,27 +142,33 @@ function CustomersView({ env }: { env: AppEnv }) {
|
||||
<span className="font-semibold">{data?.totalCount} </span>
|
||||
{data?.totalCount === 1 ? "Customer" : "Customers"}
|
||||
</p>
|
||||
<Pagination className="w-[100px] h-8 text-xs">
|
||||
<PaginationContent className="w-full flex justify-between ">
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
onClick={handlePreviousPage}
|
||||
isActive={currentPage !== 1}
|
||||
className="text-xs cursor-pointer p-1 h-6"
|
||||
/>
|
||||
</PaginationItem>
|
||||
<PaginationItem className="">
|
||||
{currentPage} / {totalPages}
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
onClick={handleNextPage}
|
||||
isActive={currentPage !== totalPages}
|
||||
className="text-xs cursor-pointer p-1 h-6"
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
{paginationLoading ? (
|
||||
<div className="w-[120px] h-8 flex items-center justify-center">
|
||||
<SmallSpinner />
|
||||
</div>
|
||||
) : (
|
||||
<Pagination className="w-[120px] h-8 text-xs">
|
||||
<PaginationContent className="w-full flex justify-between ">
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
onClick={handlePreviousPage}
|
||||
isActive={pagination.page !== 1}
|
||||
className="text-xs cursor-pointer p-1 h-6"
|
||||
/>
|
||||
</PaginationItem>
|
||||
<PaginationItem className="">
|
||||
{pagination.page} / {totalPages}
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
onClick={handleNextPage}
|
||||
isActive={pagination.page !== totalPages}
|
||||
className="text-xs cursor-pointer p-1 h-6"
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@ export function FeatureConfig({
|
||||
setFeature({
|
||||
...feature,
|
||||
name: fields.name,
|
||||
id: fields.id,
|
||||
id: isUpdate ? feature.id : fields.id,
|
||||
type: featureType,
|
||||
config: meteredConfig,
|
||||
});
|
||||
|
||||
26
server/env.sh
Executable file
26
server/env.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
# Print existing env:
|
||||
if [ -f .env.prod ]; then
|
||||
echo "Current env: local"
|
||||
elif [ -f .env.local ]; then
|
||||
echo "Current env: local"
|
||||
else
|
||||
echo "Current env: none"
|
||||
fi
|
||||
|
||||
# If arg1 is prod:
|
||||
if [ "$1" = "prod" ]; then
|
||||
# If .env and .env.prod exists, then switch
|
||||
if [ -f .env ] && [ -f .env.prod ]; then
|
||||
mv .env .env.local
|
||||
mv .env.prod .env
|
||||
fi
|
||||
fi
|
||||
|
||||
# If arg1 is local:
|
||||
if [ "$1" = "local" ]; then
|
||||
if [ -f .env ] && [ -f .env.local ]; then
|
||||
cp .env .env.prod # Copy current .env to .env.prod first
|
||||
cp .env.local .env # Copy .env.local to .env
|
||||
rm .env.local # Remove the .env.local file
|
||||
fi
|
||||
fi
|
||||
@@ -2,5 +2,13 @@
|
||||
# npx tsx scripts/alex.ts
|
||||
filename=$1
|
||||
npx tsx $filename
|
||||
# # If filename ends with .sh, then run it
|
||||
# if [ "${filename##*.}" = "sh" ]; then
|
||||
# ./$filename
|
||||
# else if [ "${filename##*.}" = "ts" ]; then
|
||||
# npx tsx $filename
|
||||
# else
|
||||
# echo "Invalid file extension"
|
||||
# fi
|
||||
|
||||
# npm run test
|
||||
# # npm run test
|
||||
@@ -107,29 +107,21 @@ export const getCustomerDetails = async ({
|
||||
|
||||
cusRouter.post("/:search", async (req: any, res: any) => {
|
||||
try {
|
||||
const {
|
||||
search,
|
||||
page_size = 100,
|
||||
// page = 1,
|
||||
last_item,
|
||||
first_item,
|
||||
} = req.body;
|
||||
const { search, page_size = 50, page = 1, last_item, filters } = req.body;
|
||||
|
||||
const { data: customers, count } = await CusService.searchCustomers({
|
||||
sb: req.sb,
|
||||
orgId: req.orgId,
|
||||
env: req.env,
|
||||
search,
|
||||
page: null,
|
||||
pageSize: page_size,
|
||||
filters: {},
|
||||
filters,
|
||||
lastItem: last_item,
|
||||
firstItem: first_item,
|
||||
pg: req.pg,
|
||||
pageNumber: page,
|
||||
pageSize: page_size,
|
||||
});
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.json({ customers, totalCount: count, count: customers.length });
|
||||
res.status(200).json({ customers, totalCount: count });
|
||||
} catch (error) {
|
||||
handleRequestError({ error, res, action: "search customers" });
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
CreateCustomerSchema,
|
||||
CusProductSchema,
|
||||
Customer,
|
||||
CustomerSchema,
|
||||
Organization,
|
||||
ProcessorType,
|
||||
ProductSchema,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { CreateCustomer } from "@autumn/shared";
|
||||
@@ -13,10 +15,9 @@ import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js";
|
||||
import { createStripeCustomer } from "@/external/stripe/stripeCusUtils.js";
|
||||
|
||||
import { generateId } from "@/utils/genUtils.js";
|
||||
import Stripe from "stripe";
|
||||
import { z } from "zod";
|
||||
|
||||
export const createNewCustomer = async ({
|
||||
sb,
|
||||
@@ -134,3 +135,22 @@ export const attachDefaultProducts = async ({
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const CusProductResultSchema = CusProductSchema.extend({
|
||||
customer: CustomerSchema,
|
||||
product: ProductSchema,
|
||||
});
|
||||
|
||||
export const flipProductResults = (
|
||||
cusProducts: z.infer<typeof CusProductResultSchema>[]
|
||||
) => {
|
||||
const customers = [];
|
||||
|
||||
for (const cusProduct of cusProducts) {
|
||||
customers.push({
|
||||
...cusProduct.customer,
|
||||
customer_products: [cusProduct],
|
||||
});
|
||||
}
|
||||
return customers;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ErrCode } from "@/errors/errCodes.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { Client } from "pg";
|
||||
import { CusProductService } from "./products/CusProductService.js";
|
||||
import { flipProductResults } from "../api/customers/cusUtils.js";
|
||||
import { format } from "date-fns";
|
||||
|
||||
export class CusService {
|
||||
static async getById({
|
||||
@@ -205,107 +207,215 @@ export class CusService {
|
||||
}
|
||||
|
||||
//search customers
|
||||
static async searchCustomers({
|
||||
|
||||
static addPaginationAndSearch = ({
|
||||
query,
|
||||
search,
|
||||
pageNumber,
|
||||
pageSize,
|
||||
lastItem,
|
||||
customerPrefix = "",
|
||||
}: {
|
||||
query: any;
|
||||
search: string;
|
||||
pageNumber: number | null;
|
||||
pageSize: number;
|
||||
lastItem: any;
|
||||
customerPrefix: string;
|
||||
}) => {
|
||||
if (search && search !== "") {
|
||||
query.or(
|
||||
`"name".ilike.%${search}%, ` +
|
||||
`"email".ilike.%${search}%, ` +
|
||||
`"id".ilike.%${search}%`,
|
||||
customerPrefix && {
|
||||
foreignTable: "customers",
|
||||
referencedTable: "customers",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
console.log("pageNumber", pageNumber);
|
||||
if (pageNumber) {
|
||||
const from = (pageNumber - 1) * pageSize;
|
||||
const to = from + pageSize - 1;
|
||||
query.range(from, to);
|
||||
} else if (lastItem) {
|
||||
console.log("Using last item");
|
||||
query.or(
|
||||
`"created_at".lt.${lastItem.created_at},` +
|
||||
`and("created_at".eq.${lastItem.created_at},"internal_id".gt.${lastItem.internal_id})`,
|
||||
customerPrefix && {
|
||||
foreignTable: "customers",
|
||||
referencedTable: "customers",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
query.order("created_at", {
|
||||
foreignTable: customerPrefix.slice(0, -1),
|
||||
ascending: false,
|
||||
});
|
||||
|
||||
query.order("internal_id", {
|
||||
foreignTable: customerPrefix.slice(0, -1),
|
||||
ascending: true,
|
||||
});
|
||||
query.limit(pageSize);
|
||||
};
|
||||
|
||||
static async searchCustomersByProduct({
|
||||
sb,
|
||||
pg,
|
||||
orgId,
|
||||
env,
|
||||
search,
|
||||
filters,
|
||||
pageSize,
|
||||
lastItem,
|
||||
pageNumber,
|
||||
}: {
|
||||
sb: SupabaseClient;
|
||||
pg: Client;
|
||||
orgId: string;
|
||||
env: AppEnv;
|
||||
search: string;
|
||||
filters: any;
|
||||
pageSize: number;
|
||||
lastItem: any;
|
||||
pageNumber: number;
|
||||
}) {
|
||||
const query = sb
|
||||
.from("customer_products")
|
||||
.select(
|
||||
"*, customer:customers!inner(*), product:products!inner(id, name)",
|
||||
{
|
||||
count: "exact",
|
||||
}
|
||||
)
|
||||
.eq("customer.org_id", orgId)
|
||||
.eq("customer.env", env);
|
||||
|
||||
if (filters.product_id) {
|
||||
query.eq("product.id", filters.product_id);
|
||||
}
|
||||
|
||||
if (filters?.status === "canceled") {
|
||||
console.log("Adding canceled filter");
|
||||
query
|
||||
.eq("status", CusProductStatus.Active)
|
||||
.not("canceled_at", "is", null);
|
||||
} else if (filters?.status === "free_trial") {
|
||||
console.log("Adding free trial filter");
|
||||
query
|
||||
.eq("status", CusProductStatus.Active)
|
||||
.gt("trial_ends_at", Date.now());
|
||||
}
|
||||
|
||||
this.addPaginationAndSearch({
|
||||
query,
|
||||
search,
|
||||
pageNumber,
|
||||
pageSize,
|
||||
lastItem,
|
||||
customerPrefix: "customers.",
|
||||
});
|
||||
|
||||
const { data, count, error } = await query;
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Flip
|
||||
|
||||
const customers = flipProductResults(data);
|
||||
|
||||
return { data: customers, count };
|
||||
}
|
||||
static async searchCustomers({
|
||||
sb,
|
||||
pg,
|
||||
orgId,
|
||||
env,
|
||||
search,
|
||||
page,
|
||||
pageSize = 50,
|
||||
filters,
|
||||
lastItem,
|
||||
firstItem,
|
||||
pageNumber,
|
||||
}: {
|
||||
pg: Client;
|
||||
sb: SupabaseClient;
|
||||
orgId: string;
|
||||
env: AppEnv;
|
||||
search: string;
|
||||
page?: number | null;
|
||||
pageSize?: number;
|
||||
lastItem?: { created_at: string; name: string; internal_id: string } | null;
|
||||
firstItem?: {
|
||||
created_at: string;
|
||||
name: string;
|
||||
internal_id: string;
|
||||
} | null;
|
||||
|
||||
filters: any;
|
||||
pageSize?: number;
|
||||
pageNumber: number;
|
||||
}) {
|
||||
let from, to;
|
||||
if (page) {
|
||||
from = (page - 1) * pageSize;
|
||||
to = from + pageSize - 1;
|
||||
if (filters.product_id || filters.status) {
|
||||
return await this.searchCustomersByProduct({
|
||||
sb,
|
||||
pg,
|
||||
orgId,
|
||||
env,
|
||||
search,
|
||||
filters,
|
||||
pageSize,
|
||||
lastItem,
|
||||
pageNumber,
|
||||
});
|
||||
}
|
||||
|
||||
let select =
|
||||
"*, customer_products:customer_products(*, product:products(*))";
|
||||
|
||||
if (filters.status || filters.product_id) {
|
||||
select = `*, customer_products:customer_products!inner(*, product:products!inner(*))`;
|
||||
select = `*, customer_products:customer_products!inner(*, product:products(*))`;
|
||||
}
|
||||
|
||||
let query = sb
|
||||
.from("customers")
|
||||
.select(select, {
|
||||
// count: "exact",
|
||||
count: "planned",
|
||||
count: "exact",
|
||||
// count: "planned", // use for 1M rows...?
|
||||
})
|
||||
.eq("org_id", orgId)
|
||||
.eq("env", env)
|
||||
.order("created_at", { ascending: false })
|
||||
.order("name", { ascending: true })
|
||||
.order("internal_id", { ascending: true })
|
||||
.limit(pageSize);
|
||||
.eq("env", env);
|
||||
|
||||
if (page) {
|
||||
query.range(from!, to!);
|
||||
} else if (firstItem) {
|
||||
query.or(
|
||||
`created_at.gt.${firstItem.created_at},` +
|
||||
`and(created_at.eq.${firstItem.created_at},name.lt.${firstItem.name}),` +
|
||||
`and(created_at.eq.${firstItem.created_at},name.eq.${firstItem.name},internal_id.lt.${firstItem.internal_id})`
|
||||
);
|
||||
} else if (lastItem) {
|
||||
query.or(
|
||||
`created_at.lt.${lastItem.created_at},` +
|
||||
`and(created_at.eq.${lastItem.created_at},name.gt.${lastItem.name}),` +
|
||||
`and(created_at.eq.${lastItem.created_at},name.eq.${lastItem.name},internal_id.gt.${lastItem.internal_id})`
|
||||
);
|
||||
}
|
||||
this.addPaginationAndSearch({
|
||||
query,
|
||||
search,
|
||||
pageNumber: null,
|
||||
pageSize,
|
||||
lastItem,
|
||||
customerPrefix: "",
|
||||
});
|
||||
// if (filters?.status === "canceled") {
|
||||
// console.log("Adding canceled filter");
|
||||
// query
|
||||
// .not("customer_products.canceled_at", "is", null)
|
||||
// .gt("customer_products.canceled_at", Date.now());
|
||||
// } else if (filters?.status === "free_trial") {
|
||||
// console.log("Adding free trial filter");
|
||||
// query
|
||||
// .eq("customer_products.status", CusProductStatus.Active)
|
||||
// .gt("customer_products.trial_ends_at", Date.now());
|
||||
// }
|
||||
|
||||
if (search && search !== "") {
|
||||
console.log("Adding search filter:", search);
|
||||
query.or(
|
||||
`name.ilike.%${search}%,email.ilike.%${search}%,id.ilike.%${search}%`
|
||||
);
|
||||
}
|
||||
|
||||
if (filters?.status === "canceled") {
|
||||
console.log("Adding canceled filter");
|
||||
query
|
||||
.not("customer_products.canceled_at", "is", null)
|
||||
.gt("customer_products.canceled_at", Date.now());
|
||||
} else if (filters?.status === "free_trial") {
|
||||
console.log("Adding free trial filter");
|
||||
query
|
||||
.eq("customer_products.status", CusProductStatus.Active)
|
||||
.gt("customer_products.trial_ends_at", Date.now());
|
||||
}
|
||||
|
||||
if (filters?.product_id) {
|
||||
query.eq("customer_products.product.id", filters.product_id);
|
||||
}
|
||||
// if (filters?.product_id) {
|
||||
// console.log("Filtering for product:", filters.product_id);
|
||||
// query.eq("customer_products.product.id", filters.product_id);
|
||||
// }
|
||||
|
||||
const { data, count, error } = await query;
|
||||
|
||||
// console.log(data);
|
||||
// return { data: [], count: 0 };
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return { data, count };
|
||||
const totalCount = count && count + pageSize * (pageNumber - 1);
|
||||
return { data, count: totalCount };
|
||||
}
|
||||
|
||||
static async getCustomers(
|
||||
|
||||
@@ -30,6 +30,7 @@ cusRouter.post("/search", async (req: any, res: any) => {
|
||||
try {
|
||||
const { data: customers, count } = await CusService.searchCustomers({
|
||||
sb,
|
||||
pg,
|
||||
orgId: orgId,
|
||||
env,
|
||||
search: cleanedQuery,
|
||||
|
||||
@@ -132,29 +132,29 @@ export class InvoiceService {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log(" ✅ Created invoice from stripe");
|
||||
console.log(" ✅ Created invoice from stripe");
|
||||
|
||||
// // Send monthly_revenue event
|
||||
// try {
|
||||
// if (!stripeInvoice.livemode) {
|
||||
// return;
|
||||
// }
|
||||
// Send monthly_revenue event
|
||||
try {
|
||||
if (!stripeInvoice.livemode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// const autumn = new Autumn();
|
||||
// await autumn.sendEvent({
|
||||
// customerId: org.id,
|
||||
// eventName: "revenue",
|
||||
// properties: {
|
||||
// value: stripeInvoice.total / 100,
|
||||
// },
|
||||
// customer_data: {
|
||||
// name: org.slug,
|
||||
// },
|
||||
// });
|
||||
// console.log(" ✅ Sent revenue event");
|
||||
// } catch (error) {
|
||||
// console.log("Failed to send revenue event", error);
|
||||
// }
|
||||
const autumn = new Autumn();
|
||||
await autumn.sendEvent({
|
||||
customerId: org.id,
|
||||
eventName: "revenue",
|
||||
properties: {
|
||||
value: stripeInvoice.total / 100,
|
||||
},
|
||||
customer_data: {
|
||||
name: org.slug,
|
||||
},
|
||||
});
|
||||
console.log(" ✅ Sent revenue event");
|
||||
} catch (error) {
|
||||
console.log("Failed to send revenue event", error);
|
||||
}
|
||||
}
|
||||
|
||||
static async updateByStripeId({
|
||||
|
||||
2
server/trigger.config.d.ts
vendored
2
server/trigger.config.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
declare const _default: import("@trigger.dev/sdk/v3").TriggerConfig;
|
||||
export default _default;
|
||||
@@ -1,21 +0,0 @@
|
||||
import { defineConfig } from "@trigger.dev/sdk/v3";
|
||||
export default defineConfig({
|
||||
project: "proj_yqrybepbgrhzmnbaccat",
|
||||
runtime: "node",
|
||||
logLevel: "log",
|
||||
// The max compute seconds a task is allowed to run. If the task run exceeds this duration, it will be stopped.
|
||||
// You can override this on an individual task.
|
||||
// See https://trigger.dev/docs/runs/max-duration
|
||||
maxDuration: 3600,
|
||||
retries: {
|
||||
enabledInDev: true,
|
||||
default: {
|
||||
maxAttempts: 3,
|
||||
minTimeoutInMs: 1000,
|
||||
maxTimeoutInMs: 10000,
|
||||
factor: 2,
|
||||
randomize: true,
|
||||
},
|
||||
},
|
||||
dirs: ["./src/trigger"],
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
import { defineConfig } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export default defineConfig({
|
||||
project: "proj_yqrybepbgrhzmnbaccat",
|
||||
runtime: "node",
|
||||
logLevel: "log",
|
||||
// The max compute seconds a task is allowed to run. If the task run exceeds this duration, it will be stopped.
|
||||
// You can override this on an individual task.
|
||||
// See https://trigger.dev/docs/runs/max-duration
|
||||
maxDuration: 3600,
|
||||
retries: {
|
||||
enabledInDev: true,
|
||||
default: {
|
||||
maxAttempts: 3,
|
||||
minTimeoutInMs: 1000,
|
||||
maxTimeoutInMs: 10000,
|
||||
factor: 2,
|
||||
randomize: true,
|
||||
},
|
||||
},
|
||||
dirs: ["./src/trigger"],
|
||||
});
|
||||
Reference in New Issue
Block a user