fix: react-doctor unused-export 141→~80 (remove dead exports, enums/marketing unused, inline campaignStatusEnum)
This commit is contained in:
@@ -1,75 +0,0 @@
|
|||||||
/**
|
|
||||||
* Shared enums for the SaaS schema. Mirrored in SQL as TEXT + CHECK.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* import { tenantStatusEnum, type TenantStatus } from "@/db/schema/enums";
|
|
||||||
* import { pgEnum } from "drizzle-orm/pg-core";
|
|
||||||
*
|
|
||||||
* export const tenantStatus = pgEnum("tenant_status", tenantStatusEnum);
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const tenantStatusEnum = [
|
|
||||||
"trial",
|
|
||||||
"active",
|
|
||||||
"past_due",
|
|
||||||
"suspended",
|
|
||||||
"churned",
|
|
||||||
] as const;
|
|
||||||
export type TenantStatus = (typeof tenantStatusEnum)[number];
|
|
||||||
|
|
||||||
export const authProviderEnum = ["dev", "google", "email"] as const;
|
|
||||||
export type AuthProvider = (typeof authProviderEnum)[number];
|
|
||||||
|
|
||||||
export const roleEnum = ["platform_admin", "brand_admin", "store_employee"] as const;
|
|
||||||
export type Role = (typeof roleEnum)[number];
|
|
||||||
|
|
||||||
export const planCodeEnum = ["starter", "farm", "enterprise"] as const;
|
|
||||||
export type PlanCode = (typeof planCodeEnum)[number];
|
|
||||||
|
|
||||||
export const addOnCodeEnum = [
|
|
||||||
"wholesale_portal",
|
|
||||||
"harvest_reach",
|
|
||||||
"ai_tools",
|
|
||||||
"water_log",
|
|
||||||
"square_sync",
|
|
||||||
"sms_campaigns",
|
|
||||||
] as const;
|
|
||||||
export type AddOnCode = (typeof addOnCodeEnum)[number];
|
|
||||||
|
|
||||||
export const subscriptionStatusEnum = [
|
|
||||||
"trialing",
|
|
||||||
"active",
|
|
||||||
"past_due",
|
|
||||||
"canceled",
|
|
||||||
"incomplete",
|
|
||||||
] as const;
|
|
||||||
export type SubscriptionStatus = (typeof subscriptionStatusEnum)[number];
|
|
||||||
|
|
||||||
export const addOnStatusEnum = ["active", "canceled"] as const;
|
|
||||||
export type AddOnStatus = (typeof addOnStatusEnum)[number];
|
|
||||||
|
|
||||||
export const stopStatusEnum = ["active", "paused", "closed"] as const;
|
|
||||||
export type StopStatus = (typeof stopStatusEnum)[number];
|
|
||||||
|
|
||||||
export const orderStatusEnum = [
|
|
||||||
"pending",
|
|
||||||
"confirmed",
|
|
||||||
"fulfilled",
|
|
||||||
"canceled",
|
|
||||||
] as const;
|
|
||||||
export type OrderStatus = (typeof orderStatusEnum)[number];
|
|
||||||
|
|
||||||
export const fulfillmentEnum = ["pickup", "ship", "mixed"] as const;
|
|
||||||
export type Fulfillment = (typeof fulfillmentEnum)[number];
|
|
||||||
|
|
||||||
export const itemFulfillmentEnum = ["pickup", "ship"] as const;
|
|
||||||
export type ItemFulfillment = (typeof itemFulfillmentEnum)[number];
|
|
||||||
|
|
||||||
export const campaignStatusEnum = [
|
|
||||||
"draft",
|
|
||||||
"scheduled",
|
|
||||||
"sending",
|
|
||||||
"sent",
|
|
||||||
"canceled",
|
|
||||||
] as const;
|
|
||||||
export type CampaignStatus = (typeof campaignStatusEnum)[number];
|
|
||||||
@@ -10,9 +10,16 @@ import {
|
|||||||
timestamp,
|
timestamp,
|
||||||
index,
|
index,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { campaignStatusEnum } from "./enums";
|
|
||||||
import { brands } from "./brands";
|
import { brands } from "./brands";
|
||||||
|
|
||||||
|
const campaignStatusEnum = [
|
||||||
|
"draft",
|
||||||
|
"scheduled",
|
||||||
|
"sending",
|
||||||
|
"sent",
|
||||||
|
"canceled",
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const emailTemplates = pgTable(
|
export const emailTemplates = pgTable(
|
||||||
"email_templates",
|
"email_templates",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const MIN_PASSWORD_LENGTH = 8;
|
|||||||
* Use this for admin-initiated password resets. For self-service
|
* Use this for admin-initiated password resets. For self-service
|
||||||
* password changes, users should use the forgot password flow.
|
* password changes, users should use the forgot password flow.
|
||||||
*/
|
*/
|
||||||
export async function updatePasswordAction(
|
async function updatePasswordAction(
|
||||||
userId: string,
|
userId: string,
|
||||||
newPassword: string
|
newPassword: string
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function getPriceId(key: string): string | null {
|
|||||||
|
|
||||||
// ── Checkout session creation ─────────────────────────────────────────────────
|
// ── Checkout session creation ─────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function createStripeCheckoutSession(
|
async function createStripeCheckoutSession(
|
||||||
brandId: string,
|
brandId: string,
|
||||||
priceKey: string,
|
priceKey: string,
|
||||||
successPath: string,
|
successPath: string,
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export type UpsertContactResult = {
|
|||||||
error: string;
|
error: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function upsertContact(contact: {
|
async function upsertContact(contact: {
|
||||||
id?: string;
|
id?: string;
|
||||||
brand_id: string;
|
brand_id: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
@@ -384,7 +384,7 @@ export type OptOutResult = {
|
|||||||
error: string;
|
error: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function optOutContact(params: {
|
async function optOutContact(params: {
|
||||||
email: string;
|
email: string;
|
||||||
brandId: string;
|
brandId: string;
|
||||||
method: "email" | "sms";
|
method: "email" | "sms";
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ await getSession();
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDashboardSummary(): Promise<DashboardSummary> {
|
async function getDashboardSummary(): Promise<DashboardSummary> {
|
||||||
|
|
||||||
await getSession();
|
await getSession();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ await getSession();
|
|||||||
|
|
||||||
// ── Mark cart as recovered when order is placed ────────────────────────────────
|
// ── Mark cart as recovered when order is placed ────────────────────────────────
|
||||||
|
|
||||||
export async function markCartRecovered(cartId: string, orderId: string): Promise<void> {
|
async function markCartRecovered(cartId: string, orderId: string): Promise<void> {
|
||||||
|
|
||||||
await getSession(); void cartId;
|
await getSession(); void cartId;
|
||||||
void orderId;
|
void orderId;
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ function buildWelcomeEmail(params: {
|
|||||||
|
|
||||||
// ── Send one welcome email ─────────────────────────────────────────────────────
|
// ── Send one welcome email ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function sendWelcomeEmail(
|
async function sendWelcomeEmail(
|
||||||
entry: WelcomeSequenceEntry,
|
entry: WelcomeSequenceEntry,
|
||||||
step: number
|
step: number
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ function rowToCampaign(row: typeof campaigns.$inferSelect): Campaign {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getHarvestReachCampaigns(
|
async function getHarvestReachCampaigns(
|
||||||
brandId: string
|
brandId: string
|
||||||
): Promise<{ success: true; campaigns: Campaign[] } | { success: false; error: string }> {
|
): Promise<{ success: true; campaigns: Campaign[] } | { success: false; error: string }> {
|
||||||
|
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ await getSession(); const settings = await getAIProviderSettings(brandId);
|
|||||||
|
|
||||||
// ── Custom integrations ─────────────────────────────────────────────────────────
|
// ── Custom integrations ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function getCustomIntegrations(brandId: string): Promise<CustomIntegration[]> {
|
async function getCustomIntegrations(brandId: string): Promise<CustomIntegration[]> {
|
||||||
|
|
||||||
await getSession(); try {
|
await getSession(); try {
|
||||||
const res = await pool.query<CustomIntegration>(
|
const res = await pool.query<CustomIntegration>(
|
||||||
@@ -209,7 +209,7 @@ await getSession(); try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function upsertCustomIntegration(
|
async function upsertCustomIntegration(
|
||||||
brandId: string,
|
brandId: string,
|
||||||
integration: CustomIntegration
|
integration: CustomIntegration
|
||||||
): Promise<{ success: boolean; integrations?: CustomIntegration[]; error?: string }> {
|
): Promise<{ success: boolean; integrations?: CustomIntegration[]; error?: string }> {
|
||||||
@@ -230,7 +230,7 @@ await getSession(); const adminUser = await getAdminUser();
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCustomIntegration(
|
async function deleteCustomIntegration(
|
||||||
brandId: string,
|
brandId: string,
|
||||||
integrationId: string
|
integrationId: string
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ interface ClientAction {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listClientActions(): Promise<ClientAction[]> {
|
async function listClientActions(): Promise<ClientAction[]> {
|
||||||
|
|
||||||
await getSession(); // Wire real server actions here as they land:
|
await getSession(); // Wire real server actions here as they land:
|
||||||
// { name: "markOrderReady", handler: markOrderReady },
|
// { name: "markOrderReady", handler: markOrderReady },
|
||||||
|
|||||||
@@ -136,21 +136,21 @@ await getSession(); const adminUser = await getAdminUser();
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAdminStops(): Promise<AdminStop[]> {
|
async function getAdminStops(): Promise<AdminStop[]> {
|
||||||
|
|
||||||
await getSession(); const result = await getAdminOrders();
|
await getSession(); const result = await getAdminOrders();
|
||||||
if (!result.success) return [];
|
if (!result.success) return [];
|
||||||
return result.stops;
|
return result.stops;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAdminPendingOrders(): Promise<AdminOrder[]> {
|
async function getAdminPendingOrders(): Promise<AdminOrder[]> {
|
||||||
|
|
||||||
await getSession(); const result = await getAdminOrders();
|
await getSession(); const result = await getAdminOrders();
|
||||||
if (!result.success) return [];
|
if (!result.success) return [];
|
||||||
return result.orders.filter((o) => !o.pickup_complete);
|
return result.orders.filter((o) => !o.pickup_complete);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAdminPickedUpOrders(): Promise<AdminOrder[]> {
|
async function getAdminPickedUpOrders(): Promise<AdminOrder[]> {
|
||||||
|
|
||||||
await getSession(); const result = await getAdminOrders();
|
await getSession(); const result = await getAdminOrders();
|
||||||
if (!result.success) return [];
|
if (!result.success) return [];
|
||||||
@@ -181,7 +181,7 @@ await getSession(); const adminUser = await getAdminUser();
|
|||||||
* When the pickup flow is rebuilt against the SaaS schema, this should
|
* When the pickup flow is rebuilt against the SaaS schema, this should
|
||||||
* be replaced by a Drizzle update on a `pickup_events` table.
|
* be replaced by a Drizzle update on a `pickup_events` table.
|
||||||
*/
|
*/
|
||||||
export async function toggleOrderPickupComplete(params: {
|
async function toggleOrderPickupComplete(params: {
|
||||||
orderId: string;
|
orderId: string;
|
||||||
pickupComplete: boolean;
|
pickupComplete: boolean;
|
||||||
}): Promise<{ success: true } | { success: false; error: string }> {
|
}): Promise<{ success: true } | { success: false; error: string }> {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ await getSession(); const adminUser = await getAdminUser();
|
|||||||
* sync log query. Returns 0 when the caller isn't authorized — never
|
* sync log query. Returns 0 when the caller isn't authorized — never
|
||||||
* throws, so callers can render "0" while the auth check settles.
|
* throws, so callers can render "0" while the auth check settles.
|
||||||
*/
|
*/
|
||||||
export async function getSquareQueueCount(brandId: string): Promise<number> {
|
async function getSquareQueueCount(brandId: string): Promise<number> {
|
||||||
|
|
||||||
await getSession(); const adminUser = await getAdminUser();
|
await getSession(); const adminUser = await getAdminUser();
|
||||||
if (!adminUser) return 0;
|
if (!adminUser) return 0;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export type StorefrontData = {
|
|||||||
* Look up a brand by its public slug. Returns `null` if no brand matches
|
* Look up a brand by its public slug. Returns `null` if no brand matches
|
||||||
* (which means the storefront slug is wrong, or the brand was renamed).
|
* (which means the storefront slug is wrong, or the brand was renamed).
|
||||||
*/
|
*/
|
||||||
export async function getStorefrontBrandBySlug(
|
async function getStorefrontBrandBySlug(
|
||||||
slug: string,
|
slug: string,
|
||||||
): Promise<StorefrontBrand | null> {
|
): Promise<StorefrontBrand | null> {
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ await getSession(); const { rows } = await pool.query<StorefrontBrand>(
|
|||||||
* Public stops for a brand storefront. Filters out draft / private stops
|
* Public stops for a brand storefront. Filters out draft / private stops
|
||||||
* and any stop whose date has already passed.
|
* and any stop whose date has already passed.
|
||||||
*/
|
*/
|
||||||
export async function getStorefrontStops(brandId: string): Promise<StorefrontStop[]> {
|
async function getStorefrontStops(brandId: string): Promise<StorefrontStop[]> {
|
||||||
|
|
||||||
await getSession(); const { rows } = await pool.query<StorefrontStop>(
|
await getSession(); const { rows } = await pool.query<StorefrontStop>(
|
||||||
`SELECT id, brand_id, name, city, state, zip, address, location,
|
`SELECT id, brand_id, name, city, state, zip, address, location,
|
||||||
@@ -101,7 +101,7 @@ await getSession(); const { rows } = await pool.query<StorefrontStop>(
|
|||||||
* float in **dollars** so callers can format directly as `$${price}`
|
* float in **dollars** so callers can format directly as `$${price}`
|
||||||
* without doing the cents-to-dollars math.
|
* without doing the cents-to-dollars math.
|
||||||
*/
|
*/
|
||||||
export async function getStorefrontProducts(
|
async function getStorefrontProducts(
|
||||||
brandId: string,
|
brandId: string,
|
||||||
): Promise<StorefrontProduct[]> {
|
): Promise<StorefrontProduct[]> {
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ await getSession(); const { rows } = await pool.query<StorefrontProduct>(
|
|||||||
* Returns `{ brand: null, stops: [], products: [] }` if the slug doesn't
|
* Returns `{ brand: null, stops: [], products: [] }` if the slug doesn't
|
||||||
* match any brand — pages fall back to default copy in that case.
|
* match any brand — pages fall back to default copy in that case.
|
||||||
*/
|
*/
|
||||||
export async function getStorefrontData(slug: string): Promise<StorefrontData> {
|
async function getStorefrontData(slug: string): Promise<StorefrontData> {
|
||||||
|
|
||||||
await getSession(); const brand = await getStorefrontBrandBySlug(slug);
|
await getSession(); const brand = await getStorefrontBrandBySlug(slug);
|
||||||
if (!brand) return { brand: null, stops: [], products: [] };
|
if (!brand) return { brand: null, stops: [], products: [] };
|
||||||
@@ -151,7 +151,7 @@ export type StorefrontWholesaleSettings = {
|
|||||||
* fields don't exist on `wholesale_settings` yet and shouldn't be
|
* fields don't exist on `wholesale_settings` yet and shouldn't be
|
||||||
* surfaced here. If you need them, add a migration first.
|
* surfaced here. If you need them, add a migration first.
|
||||||
*/
|
*/
|
||||||
export async function getStorefrontWholesaleSettings(
|
async function getStorefrontWholesaleSettings(
|
||||||
slug: string,
|
slug: string,
|
||||||
): Promise<StorefrontWholesaleSettings | null> {
|
): Promise<StorefrontWholesaleSettings | null> {
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ export type WholesalePortalConfig = {
|
|||||||
* to show the online-payment button. Doesn't require auth (the portal
|
* to show the online-payment button. Doesn't require auth (the portal
|
||||||
* already validated the session before this is called).
|
* already validated the session before this is called).
|
||||||
*/
|
*/
|
||||||
export async function getWholesalePortalConfig(
|
async function getWholesalePortalConfig(
|
||||||
brandId: string,
|
brandId: string,
|
||||||
): Promise<WholesalePortalConfig | null> {
|
): Promise<WholesalePortalConfig | null> {
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ export type BrandName = { name: string };
|
|||||||
* portal header to render the brand name without leaking any other
|
* portal header to render the brand name without leaking any other
|
||||||
* brand columns (logo, plan, stripe keys, etc.).
|
* brand columns (logo, plan, stripe keys, etc.).
|
||||||
*/
|
*/
|
||||||
export async function getStorefrontBrandName(
|
async function getStorefrontBrandName(
|
||||||
brandId: string,
|
brandId: string,
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export async function getWorkerTimeLogs(
|
|||||||
await getSession(); return [];
|
await getSession(); return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateWorkerTimeLog(
|
async function updateWorkerTimeLog(
|
||||||
_logId: string,
|
_logId: string,
|
||||||
_taskName: string,
|
_taskName: string,
|
||||||
_clockIn: string,
|
_clockIn: string,
|
||||||
@@ -181,7 +181,7 @@ await getSession(); const adminUser = await getAdminUser();
|
|||||||
return { success: false, error: "Time tracking is not configured" };
|
return { success: false, error: "Time tracking is not configured" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteWorkerTimeLog(_logId: string): Promise<{ success: boolean; error?: string }> {
|
async function deleteWorkerTimeLog(_logId: string): Promise<{ success: boolean; error?: string }> {
|
||||||
|
|
||||||
await getSession(); const adminUser = await getAdminUser();
|
await getSession(); const adminUser = await getAdminUser();
|
||||||
if (!adminUser) return { success: false, error: "Not authenticated" };
|
if (!adminUser) return { success: false, error: "Not authenticated" };
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ async function requireWaterAdminPermission() {
|
|||||||
* For the `/water/admin` (PIN) portal. Returns the admin + brand if a
|
* For the `/water/admin` (PIN) portal. Returns the admin + brand if a
|
||||||
* valid session cookie is present. Falls back to a 401-shaped error.
|
* valid session cookie is present. Falls back to a 401-shaped error.
|
||||||
*/
|
*/
|
||||||
export async function requireWaterAdminSession(): Promise<
|
async function requireWaterAdminSession(): Promise<
|
||||||
| { ok: true; adminUserId: string; brandId: string }
|
| { ok: true; adminUserId: string; brandId: string }
|
||||||
| { ok: false; error: string }
|
| { ok: false; error: string }
|
||||||
> {
|
> {
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ type FieldSession =
|
|||||||
| { ok: true; userId: string; brandId: string; role: "irrigator" | "water_admin" }
|
| { ok: true; userId: string; brandId: string; role: "irrigator" | "water_admin" }
|
||||||
| { ok: false; error: string };
|
| { ok: false; error: string };
|
||||||
|
|
||||||
export async function requireFieldSession(): Promise<FieldSession> {
|
async function requireFieldSession(): Promise<FieldSession> {
|
||||||
|
|
||||||
await getSession(); const cookieStore = await cookies();
|
await getSession(); const cookieStore = await cookies();
|
||||||
const sessionId = cookieStore.get("wl_session")?.value;
|
const sessionId = cookieStore.get("wl_session")?.value;
|
||||||
@@ -322,7 +322,7 @@ await getSession(); const cookieStore = await cookies();
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFieldSessionUser(): Promise<{
|
async function getFieldSessionUser(): Promise<{
|
||||||
userId: string;
|
userId: string;
|
||||||
name: string;
|
name: string;
|
||||||
brandId: string;
|
brandId: string;
|
||||||
@@ -385,7 +385,7 @@ await getSession(); const cookieStore = await cookies();
|
|||||||
cookieStore.delete("wl_admin_session");
|
cookieStore.delete("wl_admin_session");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWaterSession(): Promise<string | null> {
|
async function getWaterSession(): Promise<string | null> {
|
||||||
|
|
||||||
await getSession(); const cookieStore = await cookies();
|
await getSession(); const cookieStore = await cookies();
|
||||||
return cookieStore.get("wl_session")?.value ?? null;
|
return cookieStore.get("wl_session")?.value ?? null;
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ await getSession(); // Generate UUID at the start of checkout — before RPC ca
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function enqueueWholesaleWebhookForOrderCreated(orderId: string, brandId: string, customerId: string, invoiceNumber: string | null, subtotal: number) {
|
async function enqueueWholesaleWebhookForOrderCreated(orderId: string, brandId: string, customerId: string, invoiceNumber: string | null, subtotal: number) {
|
||||||
|
|
||||||
await getSession(); try {
|
await getSession(); try {
|
||||||
await pool.query(
|
await pool.query(
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@ export function getNeonAuthConfig(): NeonAuthBaseConfig {
|
|||||||
* Returns the Neon Auth base config, or null if not configured.
|
* Returns the Neon Auth base config, or null if not configured.
|
||||||
* Use this for middleware that should gracefully degrade if auth is not set up.
|
* Use this for middleware that should gracefully degrade if auth is not set up.
|
||||||
*/
|
*/
|
||||||
export function getNeonAuthConfigOptional(): NeonAuthBaseConfig | null {
|
function getNeonAuthConfigOptional(): NeonAuthBaseConfig | null {
|
||||||
const baseUrl = process.env.NEON_AUTH_BASE_URL || "";
|
const baseUrl = process.env.NEON_AUTH_BASE_URL || "";
|
||||||
const cookieSecret = process.env.NEON_AUTH_COOKIE_SECRET || "";
|
const cookieSecret = process.env.NEON_AUTH_COOKIE_SECRET || "";
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
export type AIProvider = "openai" | "anthropic" | "google" | "xai" | "minimax" | "custom";
|
export type AIProvider = "openai" | "anthropic" | "google" | "xai" | "minimax" | "custom";
|
||||||
|
|
||||||
export const PROVIDER_MODELS: Record<Exclude<AIProvider, "custom">, string[]> = {
|
const PROVIDER_MODELS: Record<Exclude<AIProvider, "custom">, string[]> = {
|
||||||
openai: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo"],
|
openai: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo"],
|
||||||
// Note: claude-3-5-sonnet-* model IDs have been retired by Anthropic.
|
// Note: claude-3-5-sonnet-* model IDs have been retired by Anthropic.
|
||||||
// Current IDs are claude-sonnet-4-5, claude-sonnet-4-*, claude-opus-4-*, etc.
|
// Current IDs are claude-sonnet-4-5, claude-sonnet-4-*, claude-opus-4-*, etc.
|
||||||
@@ -37,6 +37,6 @@ export const DEFAULT_MODELS: Record<Exclude<AIProvider, "custom">, string> = {
|
|||||||
minimax: "MiniMax-M3",
|
minimax: "MiniMax-M3",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getModelsForProvider(provider: Exclude<AIProvider, "custom">): string[] {
|
function getModelsForProvider(provider: Exclude<AIProvider, "custom">): string[] {
|
||||||
return PROVIDER_MODELS[provider] ?? [];
|
return PROVIDER_MODELS[provider] ?? [];
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ const posthogApiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY;
|
|||||||
const posthogHost = process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://app.posthog.com";
|
const posthogHost = process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://app.posthog.com";
|
||||||
|
|
||||||
// Only enable in production or when API key is set
|
// Only enable in production or when API key is set
|
||||||
export const posthogEnabled = Boolean(posthogApiKey);
|
const posthogEnabled = Boolean(posthogApiKey);
|
||||||
|
|
||||||
// Analytics helper functions
|
// Analytics helper functions
|
||||||
export const analytics = {
|
export const analytics = {
|
||||||
@@ -118,13 +118,13 @@ export const analytics = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function identifyUser(_userId: string, _properties?: Record<string, unknown>) {
|
function identifyUser(_userId: string, _properties?: Record<string, unknown>) {
|
||||||
if (posthogEnabled && typeof window !== "undefined") {
|
if (posthogEnabled && typeof window !== "undefined") {
|
||||||
console.log("[Analytics] User identified");
|
console.log("[Analytics] User identified");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function groupByBrand(_brandId: string, _properties?: Record<string, unknown>) {
|
function groupByBrand(_brandId: string, _properties?: Record<string, unknown>) {
|
||||||
if (posthogEnabled && typeof window !== "undefined") {
|
if (posthogEnabled && typeof window !== "undefined") {
|
||||||
console.log("[Analytics] Grouped by brand");
|
console.log("[Analytics] Grouped by brand");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import "server-only";
|
|||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import type { AdminUser } from "./admin-permissions-types";
|
import type { AdminUser } from "./admin-permissions-types";
|
||||||
|
|
||||||
export const ACTIVE_BRAND_COOKIE = "active_brand_id";
|
const ACTIVE_BRAND_COOKIE = "active_brand_id";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the active brand id for the given admin user.
|
* Resolve the active brand id for the given admin user.
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ const FIELD_COLUMNS: Record<string, Set<string>> = {
|
|||||||
external_id: EXTERNAL_ID_COLUMNS,
|
external_id: EXTERNAL_ID_COLUMNS,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function detectColumnMappings(
|
function detectColumnMappings(
|
||||||
headers: string[],
|
headers: string[],
|
||||||
rows: string[][]
|
rows: string[][]
|
||||||
): ColumnMapping[] {
|
): ColumnMapping[] {
|
||||||
@@ -210,7 +210,7 @@ export function detectColumnMappings(
|
|||||||
return mappings;
|
return mappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapRowToContactEntry(
|
function mapRowToContactEntry(
|
||||||
row: string[],
|
row: string[],
|
||||||
mappings: ColumnMapping[]
|
mappings: ColumnMapping[]
|
||||||
): {
|
): {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function formatDate(iso: string | null | undefined): string {
|
|||||||
/**
|
/**
|
||||||
* Format an ISO datetime string as "Jan 5, 2025 at 2:30 PM"
|
* Format an ISO datetime string as "Jan 5, 2025 at 2:30 PM"
|
||||||
*/
|
*/
|
||||||
export function formatDateTime(iso: string | null | undefined): string {
|
function formatDateTime(iso: string | null | undefined): string {
|
||||||
if (!iso) return "—";
|
if (!iso) return "—";
|
||||||
const d = new Date(iso);
|
const d = new Date(iso);
|
||||||
if (isNaN(d.getTime())) return "—";
|
if (isNaN(d.getTime())) return "—";
|
||||||
@@ -26,7 +26,7 @@ export function formatDateTime(iso: string | null | undefined): string {
|
|||||||
/**
|
/**
|
||||||
* Format an ISO date string as YYYY-MM-DD (for form inputs, export filenames)
|
* Format an ISO date string as YYYY-MM-DD (for form inputs, export filenames)
|
||||||
*/
|
*/
|
||||||
export function formatDateISO(iso: string | null | undefined): string {
|
function formatDateISO(iso: string | null | undefined): string {
|
||||||
if (!iso) return "";
|
if (!iso) return "";
|
||||||
const d = new Date(iso);
|
const d = new Date(iso);
|
||||||
if (isNaN(d.getTime())) return "";
|
if (isNaN(d.getTime())) return "";
|
||||||
@@ -36,7 +36,7 @@ export function formatDateISO(iso: string | null | undefined): string {
|
|||||||
/**
|
/**
|
||||||
* Format as YYYY-MM-DDTHH:MM for datetime-local input min values
|
* Format as YYYY-MM-DDTHH:MM for datetime-local input min values
|
||||||
*/
|
*/
|
||||||
export function formatDateTimeLocal(iso: string | null | undefined): string {
|
function formatDateTimeLocal(iso: string | null | undefined): string {
|
||||||
if (!iso) return "";
|
if (!iso) return "";
|
||||||
const d = new Date(iso);
|
const d = new Date(iso);
|
||||||
if (isNaN(d.getTime())) return "";
|
if (isNaN(d.getTime())) return "";
|
||||||
@@ -46,7 +46,7 @@ export function formatDateTimeLocal(iso: string | null | undefined): string {
|
|||||||
/**
|
/**
|
||||||
* Relative time: "5m ago", "2h ago", "3d ago"
|
* Relative time: "5m ago", "2h ago", "3d ago"
|
||||||
*/
|
*/
|
||||||
export function timeAgo(iso: string | null | undefined): string {
|
function timeAgo(iso: string | null | undefined): string {
|
||||||
if (!iso) return "Never";
|
if (!iso) return "Never";
|
||||||
const diff = Date.now() - new Date(iso).getTime();
|
const diff = Date.now() - new Date(iso).getTime();
|
||||||
if (diff < 0) return "Just now";
|
if (diff < 0) return "Just now";
|
||||||
@@ -66,14 +66,14 @@ export function timeAgo(iso: string | null | undefined): string {
|
|||||||
/**
|
/**
|
||||||
* Month name from 0-based index (for selector options)
|
* Month name from 0-based index (for selector options)
|
||||||
*/
|
*/
|
||||||
export function getMonthName(monthIndex: number): string {
|
function getMonthName(monthIndex: number): string {
|
||||||
return new Date(0, monthIndex).toLocaleString("en-US", { month: "long" });
|
return new Date(0, monthIndex).toLocaleString("en-US", { month: "long" });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Short month name: "Jan", "Feb"
|
* Short month name: "Jan", "Feb"
|
||||||
*/
|
*/
|
||||||
export function getMonthNameShort(monthIndex: number): string {
|
function getMonthNameShort(monthIndex: number): string {
|
||||||
return new Date(0, monthIndex).toLocaleString("en-US", { month: "short" });
|
return new Date(0, monthIndex).toLocaleString("en-US", { month: "short" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export const ADDON_CATALOG: Record<BrandFeatureKey, BrandFeature> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Core features that cannot be disabled
|
// Core features that cannot be disabled
|
||||||
export const CORE_FEATURES: BrandFeatureKey[] = [
|
const CORE_FEATURES: BrandFeatureKey[] = [
|
||||||
// Core platform features (always enabled)
|
// Core platform features (always enabled)
|
||||||
// These are not add-ons — the platform doesn't function without them
|
// These are not add-ons — the platform doesn't function without them
|
||||||
];
|
];
|
||||||
@@ -197,6 +197,6 @@ async function fetchBrandFeatures(
|
|||||||
/**
|
/**
|
||||||
* Invalidate cached features for a brand (call after feature toggle).
|
* Invalidate cached features for a brand (call after feature toggle).
|
||||||
*/
|
*/
|
||||||
export function invalidateBrandFeatureCache(brandId: string): void {
|
function invalidateBrandFeatureCache(brandId: string): void {
|
||||||
brandFeatureCache.delete(brandId);
|
brandFeatureCache.delete(brandId);
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ export function formatDateTime(date: string | Date | null | undefined): string {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatTime(date: string | Date | null | undefined): string {
|
function formatTime(date: string | Date | null | undefined): string {
|
||||||
if (!date) return "—";
|
if (!date) return "—";
|
||||||
const d = typeof date === "string" ? new Date(date) : date;
|
const d = typeof date === "string" ? new Date(date) : date;
|
||||||
if (isNaN(d.getTime())) return "—";
|
if (isNaN(d.getTime())) return "—";
|
||||||
|
|||||||
+10
-10
@@ -131,7 +131,7 @@ export type AddonKey = keyof typeof ADDONS;
|
|||||||
|
|
||||||
// ── Feature list per tier (for comparison table) ────────────────────────────
|
// ── Feature list per tier (for comparison table) ────────────────────────────
|
||||||
|
|
||||||
export const TIER_FEATURE_MATRIX: Record<PlanTierKey, string[]> = {
|
const TIER_FEATURE_MATRIX: Record<PlanTierKey, string[]> = {
|
||||||
starter: [
|
starter: [
|
||||||
"Products catalog",
|
"Products catalog",
|
||||||
"Stops management (10/mo)",
|
"Stops management (10/mo)",
|
||||||
@@ -168,35 +168,35 @@ export const TIER_FEATURE_MATRIX: Record<PlanTierKey, string[]> = {
|
|||||||
|
|
||||||
// ── Utility functions ────────────────────────────────────────────────────────
|
// ── Utility functions ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export function formatPrice(amount: number | null): string {
|
function formatPrice(amount: number | null): string {
|
||||||
if (amount === null) return "Custom";
|
if (amount === null) return "Custom";
|
||||||
return `$${amount.toFixed(0)}`;
|
return `$${amount.toFixed(0)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatPricePerMonth(amount: number | null): string {
|
function formatPricePerMonth(amount: number | null): string {
|
||||||
if (amount === null) return "Custom";
|
if (amount === null) return "Custom";
|
||||||
return `$${amount}/mo`;
|
return `$${amount}/mo`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPlanMonthlyPrice(tier: PlanTierKey): number | null {
|
function getPlanMonthlyPrice(tier: PlanTierKey): number | null {
|
||||||
return PLAN_TIERS[tier]?.monthlyPrice ?? null;
|
return PLAN_TIERS[tier]?.monthlyPrice ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPlanAnnualPrice(tier: PlanTierKey): number | null {
|
function getPlanAnnualPrice(tier: PlanTierKey): number | null {
|
||||||
return PLAN_TIERS[tier]?.annualPrice ?? null;
|
return PLAN_TIERS[tier]?.annualPrice ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAddonMonthlyPrice(addon: AddonKey): number {
|
function getAddonMonthlyPrice(addon: AddonKey): number {
|
||||||
return ADDONS[addon]?.monthlyPrice ?? 0;
|
return ADDONS[addon]?.monthlyPrice ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAddonAnnualPrice(addon: AddonKey): number {
|
function getAddonAnnualPrice(addon: AddonKey): number {
|
||||||
return ADDONS[addon]?.annualPrice ?? 0;
|
return ADDONS[addon]?.annualPrice ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateAnnualSavings(monthlyTotal: number): number {
|
function calculateAnnualSavings(monthlyTotal: number): number {
|
||||||
return Math.round(monthlyTotal * 0.25); // 25% annual discount
|
return Math.round(monthlyTotal * 0.25); // 25% annual discount
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BILLING_COMPANY_NAME = "Cielo Hermosa, LLC";
|
const BILLING_COMPANY_NAME = "Cielo Hermosa, LLC";
|
||||||
export const BILLING_EMAIL = "billing@cielohermosa.com";
|
const BILLING_EMAIL = "billing@cielohermosa.com";
|
||||||
+3
-3
@@ -31,13 +31,13 @@ export function registerServiceWorker() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function subscribeToPush(_registration: ServiceWorkerRegistration) {
|
async function subscribeToPush(_registration: ServiceWorkerRegistration) {
|
||||||
// Push subscription disabled for now - requires VAPID key setup
|
// Push subscription disabled for now - requires VAPID key setup
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if app is installed (PWA)
|
// Check if app is installed (PWA)
|
||||||
export function isPWAInstalled(): boolean {
|
function isPWAInstalled(): boolean {
|
||||||
if (typeof window === 'undefined') return false;
|
if (typeof window === 'undefined') return false;
|
||||||
return (
|
return (
|
||||||
window.matchMedia('(display-mode: standalone)').matches ||
|
window.matchMedia('(display-mode: standalone)').matches ||
|
||||||
@@ -46,7 +46,7 @@ export function isPWAInstalled(): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Share API for native sharing
|
// Share API for native sharing
|
||||||
export async function shareContent(content: {
|
async function shareContent(content: {
|
||||||
title: string;
|
title: string;
|
||||||
text: string;
|
text: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export function securityHeaders() {
|
|||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rateLimitHeaders(rateCheck: { remaining: number; reset: number }) {
|
function rateLimitHeaders(rateCheck: { remaining: number; reset: number }) {
|
||||||
return {
|
return {
|
||||||
"X-RateLimit-Limit": String(100),
|
"X-RateLimit-Limit": String(100),
|
||||||
"X-RateLimit-Remaining": String(rateCheck.remaining),
|
"X-RateLimit-Remaining": String(rateCheck.remaining),
|
||||||
@@ -86,6 +86,6 @@ export function rateLimitHeaders(rateCheck: { remaining: number; reset: number }
|
|||||||
|
|
||||||
// Pre-configured limiters
|
// Pre-configured limiters
|
||||||
export const apiLimiter = RATE_LIMITS.api;
|
export const apiLimiter = RATE_LIMITS.api;
|
||||||
export const checkoutLimiter = RATE_LIMITS.checkout;
|
const checkoutLimiter = RATE_LIMITS.checkout;
|
||||||
export const emailLimiter = RATE_LIMITS.email;
|
export const emailLimiter = RATE_LIMITS.email;
|
||||||
export const authLimiter = RATE_LIMITS.auth;
|
const authLimiter = RATE_LIMITS.auth;
|
||||||
+3
-3
@@ -53,7 +53,7 @@ export const captureError = (error: Error, context?: Record<string, unknown>) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Export for manual breadcrumb logging
|
// Export for manual breadcrumb logging
|
||||||
export const addBreadcrumb = (message: string, data?: Record<string, unknown>) => {
|
const addBreadcrumb = (message: string, data?: Record<string, unknown>) => {
|
||||||
Sentry.addBreadcrumb({
|
Sentry.addBreadcrumb({
|
||||||
message,
|
message,
|
||||||
data,
|
data,
|
||||||
@@ -62,7 +62,7 @@ export const addBreadcrumb = (message: string, data?: Record<string, unknown>) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Export for user tracking during errors
|
// Export for user tracking during errors
|
||||||
export const setUserContext = (userId: string, brandId?: string) => {
|
const setUserContext = (userId: string, brandId?: string) => {
|
||||||
Sentry.setUser({
|
Sentry.setUser({
|
||||||
id: userId,
|
id: userId,
|
||||||
tags: { brand_id: brandId || "unknown" },
|
tags: { brand_id: brandId || "unknown" },
|
||||||
@@ -70,7 +70,7 @@ export const setUserContext = (userId: string, brandId?: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Export for transaction tracing - simplified wrapper
|
// Export for transaction tracing - simplified wrapper
|
||||||
export async function withTransaction<T>(
|
async function withTransaction<T>(
|
||||||
name: string,
|
name: string,
|
||||||
op: string,
|
op: string,
|
||||||
fn: () => Promise<T>
|
fn: () => Promise<T>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function serverLog(...args: unknown[]): void {
|
|||||||
console.log(...args);
|
console.log(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serverInfo(...args: unknown[]): void {
|
function serverInfo(...args: unknown[]): void {
|
||||||
console.info(...args);
|
console.info(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -22,7 +22,7 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|||||||
|
|
||||||
let _client: S3Client | null = null;
|
let _client: S3Client | null = null;
|
||||||
|
|
||||||
export function getStorageClient(): S3Client {
|
function getStorageClient(): S3Client {
|
||||||
if (_client) return _client;
|
if (_client) return _client;
|
||||||
|
|
||||||
const endpoint = process.env.MINIO_ENDPOINT ?? "s3.crispygoat.com";
|
const endpoint = process.env.MINIO_ENDPOINT ?? "s3.crispygoat.com";
|
||||||
@@ -48,7 +48,7 @@ export function getStorageClient(): S3Client {
|
|||||||
* Returns the public base URL for the given bucket.
|
* Returns the public base URL for the given bucket.
|
||||||
* Override with MINIO_PUBLIC_URL if your public endpoint differs from the endpoint.
|
* Override with MINIO_PUBLIC_URL if your public endpoint differs from the endpoint.
|
||||||
*/
|
*/
|
||||||
export function getPublicUrl(bucket: string, key: string): string {
|
function getPublicUrl(bucket: string, key: string): string {
|
||||||
const base = process.env.MINIO_PUBLIC_URL ?? `https://${process.env.MINIO_ENDPOINT ?? "s3.crispygoat.com"}`;
|
const base = process.env.MINIO_PUBLIC_URL ?? `https://${process.env.MINIO_ENDPOINT ?? "s3.crispygoat.com"}`;
|
||||||
return `${base}/${bucket}/${key}`;
|
return `${base}/${bucket}/${key}`;
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ export async function uploadObject(opts: UploadOptions): Promise<string> {
|
|||||||
return getPublicUrl(opts.bucket, opts.key);
|
return getPublicUrl(opts.bucket, opts.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteObject(bucket: string, key: string): Promise<void> {
|
async function deleteObject(bucket: string, key: string): Promise<void> {
|
||||||
const client = getStorageClient();
|
const client = getStorageClient();
|
||||||
await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
|
await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ export async function deleteObject(bucket: string, key: string): Promise<void> {
|
|||||||
* @param contentType Expected MIME type
|
* @param contentType Expected MIME type
|
||||||
* @param expiresSeconds URL expiry (default 5 minutes)
|
* @param expiresSeconds URL expiry (default 5 minutes)
|
||||||
*/
|
*/
|
||||||
export async function getPresignedPutUrl(
|
async function getPresignedPutUrl(
|
||||||
bucket: string,
|
bucket: string,
|
||||||
key: string,
|
key: string,
|
||||||
contentType: string,
|
contentType: string,
|
||||||
@@ -118,7 +118,7 @@ export async function getPresignedPutUrl(
|
|||||||
* @param key Object key
|
* @param key Object key
|
||||||
* @param expiresSeconds URL expiry (default 1 hour)
|
* @param expiresSeconds URL expiry (default 1 hour)
|
||||||
*/
|
*/
|
||||||
export async function getPresignedGetUrl(
|
async function getPresignedGetUrl(
|
||||||
bucket: string,
|
bucket: string,
|
||||||
key: string,
|
key: string,
|
||||||
expiresSeconds = 3600
|
expiresSeconds = 3600
|
||||||
|
|||||||
+23
-23
@@ -33,7 +33,7 @@ export const stripe = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Plan tier configurations
|
// Plan tier configurations
|
||||||
export const PLANS = {
|
const PLANS = {
|
||||||
starter: {
|
starter: {
|
||||||
id: "starter",
|
id: "starter",
|
||||||
name: "Starter",
|
name: "Starter",
|
||||||
@@ -99,7 +99,7 @@ export const PLANS = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Add-ons configuration
|
// Add-ons configuration
|
||||||
export const ADDONS = {
|
const ADDONS = {
|
||||||
wholesale_portal: {
|
wholesale_portal: {
|
||||||
id: "wholesale_portal",
|
id: "wholesale_portal",
|
||||||
name: "Wholesale Portal",
|
name: "Wholesale Portal",
|
||||||
@@ -168,7 +168,7 @@ export interface CreateSubscriptionOptions {
|
|||||||
metadata?: Record<string, string>;
|
metadata?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createSubscription(options: CreateSubscriptionOptions) {
|
async function createSubscription(options: CreateSubscriptionOptions) {
|
||||||
const { brandId, brandName, email, plan, interval, successUrl, cancelUrl, metadata } = options;
|
const { brandId, brandName, email, plan, interval, successUrl, cancelUrl, metadata } = options;
|
||||||
|
|
||||||
// Get or create Stripe customer
|
// Get or create Stripe customer
|
||||||
@@ -212,7 +212,7 @@ export async function createSubscription(options: CreateSubscriptionOptions) {
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAddonSubscription(options: {
|
async function createAddonSubscription(options: {
|
||||||
customerId: string;
|
customerId: string;
|
||||||
addon: keyof typeof ADDONS;
|
addon: keyof typeof ADDONS;
|
||||||
brandId: string;
|
brandId: string;
|
||||||
@@ -256,7 +256,7 @@ export async function createAddonSubscription(options: {
|
|||||||
// CUSTOMER PORTAL
|
// CUSTOMER PORTAL
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
export async function createCustomerPortalSession(options: {
|
async function createCustomerPortalSession(options: {
|
||||||
customerId: string;
|
customerId: string;
|
||||||
returnUrl: string;
|
returnUrl: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -274,7 +274,7 @@ export async function createCustomerPortalSession(options: {
|
|||||||
// SUBSCRIPTION UPDATES
|
// SUBSCRIPTION UPDATES
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
export async function cancelSubscription(subscriptionId: string, immediate: boolean = false) {
|
async function cancelSubscription(subscriptionId: string, immediate: boolean = false) {
|
||||||
if (immediate) {
|
if (immediate) {
|
||||||
return await stripe.subscriptions.cancel(subscriptionId);
|
return await stripe.subscriptions.cancel(subscriptionId);
|
||||||
}
|
}
|
||||||
@@ -284,13 +284,13 @@ export async function cancelSubscription(subscriptionId: string, immediate: bool
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function reactivateSubscription(subscriptionId: string) {
|
async function reactivateSubscription(subscriptionId: string) {
|
||||||
return await stripe.subscriptions.update(subscriptionId, {
|
return await stripe.subscriptions.update(subscriptionId, {
|
||||||
cancel_at_period_end: false,
|
cancel_at_period_end: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function changePlan(subscriptionId: string, newPlan: keyof typeof PLANS, interval: "monthly" | "annual") {
|
async function changePlan(subscriptionId: string, newPlan: keyof typeof PLANS, interval: "monthly" | "annual") {
|
||||||
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
|
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
|
||||||
|
|
||||||
// Get new price ID
|
// Get new price ID
|
||||||
@@ -314,7 +314,7 @@ export async function changePlan(subscriptionId: string, newPlan: keyof typeof P
|
|||||||
// CUSTOMER MANAGEMENT
|
// CUSTOMER MANAGEMENT
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
export async function getOrCreateCustomer(brandId: string, name: string, email: string) {
|
async function getOrCreateCustomer(brandId: string, name: string, email: string) {
|
||||||
// Check if customer exists for this brand
|
// Check if customer exists for this brand
|
||||||
const existingCustomers = await stripe.customers.list({
|
const existingCustomers = await stripe.customers.list({
|
||||||
email,
|
email,
|
||||||
@@ -337,11 +337,11 @@ export async function getOrCreateCustomer(brandId: string, name: string, email:
|
|||||||
return customer.id;
|
return customer.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCustomer(customerId: string) {
|
async function getCustomer(customerId: string) {
|
||||||
return await stripe.customers.retrieve(customerId);
|
return await stripe.customers.retrieve(customerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateCustomer(customerId: string, data: Partial<Stripe.CustomerUpdateParams>) {
|
async function updateCustomer(customerId: string, data: Partial<Stripe.CustomerUpdateParams>) {
|
||||||
return await stripe.customers.update(customerId, data);
|
return await stripe.customers.update(customerId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,20 +349,20 @@ export async function updateCustomer(customerId: string, data: Partial<Stripe.Cu
|
|||||||
// PAYMENT METHODS
|
// PAYMENT METHODS
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
export async function listPaymentMethods(customerId: string) {
|
async function listPaymentMethods(customerId: string) {
|
||||||
return await stripe.paymentMethods.list({
|
return await stripe.paymentMethods.list({
|
||||||
customer: customerId,
|
customer: customerId,
|
||||||
type: "card",
|
type: "card",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function attachPaymentMethod(paymentMethodId: string, customerId: string) {
|
async function attachPaymentMethod(paymentMethodId: string, customerId: string) {
|
||||||
return await stripe.paymentMethods.attach(paymentMethodId, {
|
return await stripe.paymentMethods.attach(paymentMethodId, {
|
||||||
customer: customerId,
|
customer: customerId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setDefaultPaymentMethod(customerId: string, paymentMethodId: string) {
|
async function setDefaultPaymentMethod(customerId: string, paymentMethodId: string) {
|
||||||
return await stripe.customers.update(customerId, {
|
return await stripe.customers.update(customerId, {
|
||||||
invoice_settings: {
|
invoice_settings: {
|
||||||
default_payment_method: paymentMethodId,
|
default_payment_method: paymentMethodId,
|
||||||
@@ -374,18 +374,18 @@ export async function setDefaultPaymentMethod(customerId: string, paymentMethodI
|
|||||||
// INVOICES
|
// INVOICES
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
export async function listInvoices(customerId: string, limit: number = 24) {
|
async function listInvoices(customerId: string, limit: number = 24) {
|
||||||
return await stripe.invoices.list({
|
return await stripe.invoices.list({
|
||||||
customer: customerId,
|
customer: customerId,
|
||||||
limit,
|
limit,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getInvoice(invoiceId: string) {
|
async function getInvoice(invoiceId: string) {
|
||||||
return await stripe.invoices.retrieve(invoiceId);
|
return await stripe.invoices.retrieve(invoiceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUpcomingInvoice(customerId: string) {
|
async function getUpcomingInvoice(customerId: string) {
|
||||||
// Use Stripe's upcoming invoice preview endpoint
|
// Use Stripe's upcoming invoice preview endpoint
|
||||||
return await stripe.invoices.createPreview({
|
return await stripe.invoices.createPreview({
|
||||||
customer: customerId,
|
customer: customerId,
|
||||||
@@ -396,7 +396,7 @@ export async function getUpcomingInvoice(customerId: string) {
|
|||||||
// PAYMENTS & REFUNDS
|
// PAYMENTS & REFUNDS
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
export async function createPaymentIntent(options: {
|
async function createPaymentIntent(options: {
|
||||||
amount: number;
|
amount: number;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
customerId?: string;
|
customerId?: string;
|
||||||
@@ -413,7 +413,7 @@ export async function createPaymentIntent(options: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createRefund(paymentIntentId: string, amount?: number, reason?: string) {
|
async function createRefund(paymentIntentId: string, amount?: number, reason?: string) {
|
||||||
return await stripe.refunds.create({
|
return await stripe.refunds.create({
|
||||||
payment_intent: paymentIntentId,
|
payment_intent: paymentIntentId,
|
||||||
amount,
|
amount,
|
||||||
@@ -613,7 +613,7 @@ async function disableAddonFeature(brandId: string | undefined, addonKey: string
|
|||||||
// USAGE BILLING
|
// USAGE BILLING
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
export async function createUsageRecord(options: {
|
async function createUsageRecord(options: {
|
||||||
subscriptionItemId: string;
|
subscriptionItemId: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
@@ -640,7 +640,7 @@ export async function createUsageRecord(options: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsageSummary(subscriptionItemId: string) {
|
async function getUsageSummary(subscriptionItemId: string) {
|
||||||
// List subscription items with usage summaries
|
// List subscription items with usage summaries
|
||||||
const subscription = await stripe.subscriptions.retrieve(
|
const subscription = await stripe.subscriptions.retrieve(
|
||||||
subscriptionItemId.split("_")[0]
|
subscriptionItemId.split("_")[0]
|
||||||
@@ -653,11 +653,11 @@ export async function getUsageSummary(subscriptionItemId: string) {
|
|||||||
// EXPORT STRIPE INSTANCE AND HELPERS
|
// EXPORT STRIPE INSTANCE AND HELPERS
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
export async function getSubscription(subscriptionId: string) {
|
async function getSubscription(subscriptionId: string) {
|
||||||
return await stripe.subscriptions.retrieve(subscriptionId);
|
return await stripe.subscriptions.retrieve(subscriptionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listSubscriptions(customerId: string) {
|
async function listSubscriptions(customerId: string) {
|
||||||
return await stripe.subscriptions.list({
|
return await stripe.subscriptions.list({
|
||||||
customer: customerId,
|
customer: customerId,
|
||||||
status: "all",
|
status: "all",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export type DailyReportOptions = {
|
|||||||
previewMode?: boolean;
|
previewMode?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WATER_LOG_DISPLAY_REFRESH_MS = 30_000;
|
const WATER_LOG_DISPLAY_REFRESH_MS = 30_000;
|
||||||
|
|
||||||
export function getDisplayAgeLabel(minutes: number | null): string {
|
export function getDisplayAgeLabel(minutes: number | null): string {
|
||||||
if (minutes === null) return "never";
|
if (minutes === null) return "never";
|
||||||
|
|||||||
Reference in New Issue
Block a user