fix: react-doctor unused-export 141→~80 (remove dead exports, enums/marketing unused, inline campaignStatusEnum)

This commit is contained in:
Nora
2026-06-26 05:25:36 -06:00
parent 0ea11e4db6
commit 13d15883b1
34 changed files with 106 additions and 174 deletions
-75
View File
@@ -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];
+8 -1
View File
@@ -10,9 +10,16 @@ import {
timestamp,
index,
} from "drizzle-orm/pg-core";
import { campaignStatusEnum } from "./enums";
import { brands } from "./brands";
const campaignStatusEnum = [
"draft",
"scheduled",
"sending",
"sent",
"canceled",
] as const;
export const emailTemplates = pgTable(
"email_templates",
{
+1 -1
View File
@@ -14,7 +14,7 @@ const MIN_PASSWORD_LENGTH = 8;
* Use this for admin-initiated password resets. For self-service
* password changes, users should use the forgot password flow.
*/
export async function updatePasswordAction(
async function updatePasswordAction(
userId: string,
newPassword: string
): Promise<{ success: boolean; error?: string }> {
+1 -1
View File
@@ -28,7 +28,7 @@ function getPriceId(key: string): string | null {
// ── Checkout session creation ─────────────────────────────────────────────────
export async function createStripeCheckoutSession(
async function createStripeCheckoutSession(
brandId: string,
priceKey: string,
successPath: string,
+2 -2
View File
@@ -196,7 +196,7 @@ export type UpsertContactResult = {
error: string;
};
export async function upsertContact(contact: {
async function upsertContact(contact: {
id?: string;
brand_id: string;
email?: string;
@@ -384,7 +384,7 @@ export type OptOutResult = {
error: string;
};
export async function optOutContact(params: {
async function optOutContact(params: {
email: string;
brandId: string;
method: "email" | "sms";
+1 -1
View File
@@ -237,7 +237,7 @@ await getSession();
}
}
export async function getDashboardSummary(): Promise<DashboardSummary> {
async function getDashboardSummary(): Promise<DashboardSummary> {
await getSession();
try {
@@ -251,7 +251,7 @@ await getSession();
// ── 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;
void orderId;
@@ -181,7 +181,7 @@ function buildWelcomeEmail(params: {
// ── Send one welcome email ─────────────────────────────────────────────────────
export async function sendWelcomeEmail(
async function sendWelcomeEmail(
entry: WelcomeSequenceEntry,
step: number
): Promise<{ success: boolean; error?: string }> {
+1 -1
View File
@@ -75,7 +75,7 @@ function rowToCampaign(row: typeof campaigns.$inferSelect): Campaign {
};
}
export async function getHarvestReachCampaigns(
async function getHarvestReachCampaigns(
brandId: string
): Promise<{ success: true; campaigns: Campaign[] } | { success: false; error: string }> {
+3 -3
View File
@@ -196,7 +196,7 @@ await getSession(); const settings = await getAIProviderSettings(brandId);
// ── Custom integrations ─────────────────────────────────────────────────────────
export async function getCustomIntegrations(brandId: string): Promise<CustomIntegration[]> {
async function getCustomIntegrations(brandId: string): Promise<CustomIntegration[]> {
await getSession(); try {
const res = await pool.query<CustomIntegration>(
@@ -209,7 +209,7 @@ await getSession(); try {
}
}
export async function upsertCustomIntegration(
async function upsertCustomIntegration(
brandId: string,
integration: CustomIntegration
): 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,
integrationId: string
): Promise<{ success: boolean; error?: string }> {
+1 -1
View File
@@ -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:
// { name: "markOrderReady", handler: markOrderReady },
+4 -4
View File
@@ -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();
if (!result.success) return [];
return result.stops;
}
export async function getAdminPendingOrders(): Promise<AdminOrder[]> {
async function getAdminPendingOrders(): Promise<AdminOrder[]> {
await getSession(); const result = await getAdminOrders();
if (!result.success) return [];
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();
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
* be replaced by a Drizzle update on a `pickup_events` table.
*/
export async function toggleOrderPickupComplete(params: {
async function toggleOrderPickupComplete(params: {
orderId: string;
pickupComplete: boolean;
}): Promise<{ success: true } | { success: false; error: string }> {
+1 -1
View File
@@ -87,7 +87,7 @@ await getSession(); const adminUser = await getAdminUser();
* sync log query. Returns 0 when the caller isn't authorized — never
* 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();
if (!adminUser) return 0;
+7 -7
View File
@@ -58,7 +58,7 @@ export type StorefrontData = {
* 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).
*/
export async function getStorefrontBrandBySlug(
async function getStorefrontBrandBySlug(
slug: string,
): 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
* 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>(
`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}`
* without doing the cents-to-dollars math.
*/
export async function getStorefrontProducts(
async function getStorefrontProducts(
brandId: string,
): Promise<StorefrontProduct[]> {
@@ -127,7 +127,7 @@ await getSession(); const { rows } = await pool.query<StorefrontProduct>(
* Returns `{ brand: null, stops: [], products: [] }` if the slug doesn't
* 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);
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
* surfaced here. If you need them, add a migration first.
*/
export async function getStorefrontWholesaleSettings(
async function getStorefrontWholesaleSettings(
slug: string,
): Promise<StorefrontWholesaleSettings | null> {
@@ -178,7 +178,7 @@ export type WholesalePortalConfig = {
* to show the online-payment button. Doesn't require auth (the portal
* already validated the session before this is called).
*/
export async function getWholesalePortalConfig(
async function getWholesalePortalConfig(
brandId: string,
): Promise<WholesalePortalConfig | null> {
@@ -199,7 +199,7 @@ export type BrandName = { name: string };
* portal header to render the brand name without leaking any other
* brand columns (logo, plan, stripe keys, etc.).
*/
export async function getStorefrontBrandName(
async function getStorefrontBrandName(
brandId: string,
): Promise<string | null> {
+2 -2
View File
@@ -167,7 +167,7 @@ export async function getWorkerTimeLogs(
await getSession(); return [];
}
export async function updateWorkerTimeLog(
async function updateWorkerTimeLog(
_logId: string,
_taskName: string,
_clockIn: string,
@@ -181,7 +181,7 @@ await getSession(); const adminUser = await getAdminUser();
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();
if (!adminUser) return { success: false, error: "Not authenticated" };
+1 -1
View File
@@ -125,7 +125,7 @@ async function requireWaterAdminPermission() {
* For the `/water/admin` (PIN) portal. Returns the admin + brand if a
* 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: false; error: string }
> {
+3 -3
View File
@@ -287,7 +287,7 @@ type FieldSession =
| { ok: true; userId: string; brandId: string; role: "irrigator" | "water_admin" }
| { ok: false; error: string };
export async function requireFieldSession(): Promise<FieldSession> {
async function requireFieldSession(): Promise<FieldSession> {
await getSession(); const cookieStore = await cookies();
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;
name: string;
brandId: string;
@@ -385,7 +385,7 @@ await getSession(); const cookieStore = await cookies();
cookieStore.delete("wl_admin_session");
}
export async function getWaterSession(): Promise<string | null> {
async function getWaterSession(): Promise<string | null> {
await getSession(); const cookieStore = await cookies();
return cookieStore.get("wl_session")?.value ?? null;
+1 -1
View File
@@ -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 pool.query(
+1 -1
View File
@@ -63,7 +63,7 @@ export function getNeonAuthConfig(): NeonAuthBaseConfig {
* 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.
*/
export function getNeonAuthConfigOptional(): NeonAuthBaseConfig | null {
function getNeonAuthConfigOptional(): NeonAuthBaseConfig | null {
const baseUrl = process.env.NEON_AUTH_BASE_URL || "";
const cookieSecret = process.env.NEON_AUTH_COOKIE_SECRET || "";
+2 -2
View File
@@ -2,7 +2,7 @@
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"],
// 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.
@@ -37,6 +37,6 @@ export const DEFAULT_MODELS: Record<Exclude<AIProvider, "custom">, string> = {
minimax: "MiniMax-M3",
};
export function getModelsForProvider(provider: Exclude<AIProvider, "custom">): string[] {
function getModelsForProvider(provider: Exclude<AIProvider, "custom">): string[] {
return PROVIDER_MODELS[provider] ?? [];
}
+3 -3
View File
@@ -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";
// Only enable in production or when API key is set
export const posthogEnabled = Boolean(posthogApiKey);
const posthogEnabled = Boolean(posthogApiKey);
// Analytics helper functions
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") {
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") {
console.log("[Analytics] Grouped by brand");
}
+1 -1
View File
@@ -19,7 +19,7 @@ import "server-only";
import { cookies } from "next/headers";
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.
+2 -2
View File
@@ -152,7 +152,7 @@ const FIELD_COLUMNS: Record<string, Set<string>> = {
external_id: EXTERNAL_ID_COLUMNS,
};
export function detectColumnMappings(
function detectColumnMappings(
headers: string[],
rows: string[][]
): ColumnMapping[] {
@@ -210,7 +210,7 @@ export function detectColumnMappings(
return mappings;
}
export function mapRowToContactEntry(
function mapRowToContactEntry(
row: string[],
mappings: ColumnMapping[]
): {
+6 -6
View File
@@ -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"
*/
export function formatDateTime(iso: string | null | undefined): string {
function formatDateTime(iso: string | null | undefined): string {
if (!iso) return "—";
const d = new Date(iso);
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)
*/
export function formatDateISO(iso: string | null | undefined): string {
function formatDateISO(iso: string | null | undefined): string {
if (!iso) return "";
const d = new Date(iso);
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
*/
export function formatDateTimeLocal(iso: string | null | undefined): string {
function formatDateTimeLocal(iso: string | null | undefined): string {
if (!iso) return "";
const d = new Date(iso);
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"
*/
export function timeAgo(iso: string | null | undefined): string {
function timeAgo(iso: string | null | undefined): string {
if (!iso) return "Never";
const diff = Date.now() - new Date(iso).getTime();
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)
*/
export function getMonthName(monthIndex: number): string {
function getMonthName(monthIndex: number): string {
return new Date(0, monthIndex).toLocaleString("en-US", { month: "long" });
}
/**
* 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" });
}
+2 -2
View File
@@ -114,7 +114,7 @@ export const ADDON_CATALOG: Record<BrandFeatureKey, BrandFeature> = {
};
// Core features that cannot be disabled
export const CORE_FEATURES: BrandFeatureKey[] = [
const CORE_FEATURES: BrandFeatureKey[] = [
// Core platform features (always enabled)
// 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).
*/
export function invalidateBrandFeatureCache(brandId: string): void {
function invalidateBrandFeatureCache(brandId: string): void {
brandFeatureCache.delete(brandId);
}
+1 -1
View File
@@ -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 "—";
const d = typeof date === "string" ? new Date(date) : date;
if (isNaN(d.getTime())) return "—";
+10 -10
View File
@@ -131,7 +131,7 @@ export type AddonKey = keyof typeof ADDONS;
// ── Feature list per tier (for comparison table) ────────────────────────────
export const TIER_FEATURE_MATRIX: Record<PlanTierKey, string[]> = {
const TIER_FEATURE_MATRIX: Record<PlanTierKey, string[]> = {
starter: [
"Products catalog",
"Stops management (10/mo)",
@@ -168,35 +168,35 @@ export const TIER_FEATURE_MATRIX: Record<PlanTierKey, string[]> = {
// ── Utility functions ────────────────────────────────────────────────────────
export function formatPrice(amount: number | null): string {
function formatPrice(amount: number | null): string {
if (amount === null) return "Custom";
return `$${amount.toFixed(0)}`;
}
export function formatPricePerMonth(amount: number | null): string {
function formatPricePerMonth(amount: number | null): string {
if (amount === null) return "Custom";
return `$${amount}/mo`;
}
export function getPlanMonthlyPrice(tier: PlanTierKey): number | null {
function getPlanMonthlyPrice(tier: PlanTierKey): number | 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;
}
export function getAddonMonthlyPrice(addon: AddonKey): number {
function getAddonMonthlyPrice(addon: AddonKey): number {
return ADDONS[addon]?.monthlyPrice ?? 0;
}
export function getAddonAnnualPrice(addon: AddonKey): number {
function getAddonAnnualPrice(addon: AddonKey): number {
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
}
export const BILLING_COMPANY_NAME = "Cielo Hermosa, LLC";
export const BILLING_EMAIL = "billing@cielohermosa.com";
const BILLING_COMPANY_NAME = "Cielo Hermosa, LLC";
const BILLING_EMAIL = "billing@cielohermosa.com";
+3 -3
View File
@@ -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
return null;
}
// Check if app is installed (PWA)
export function isPWAInstalled(): boolean {
function isPWAInstalled(): boolean {
if (typeof window === 'undefined') return false;
return (
window.matchMedia('(display-mode: standalone)').matches ||
@@ -46,7 +46,7 @@ export function isPWAInstalled(): boolean {
}
// Share API for native sharing
export async function shareContent(content: {
async function shareContent(content: {
title: string;
text: string;
url?: string;
+3 -3
View File
@@ -76,7 +76,7 @@ export function securityHeaders() {
} as const;
}
export function rateLimitHeaders(rateCheck: { remaining: number; reset: number }) {
function rateLimitHeaders(rateCheck: { remaining: number; reset: number }) {
return {
"X-RateLimit-Limit": String(100),
"X-RateLimit-Remaining": String(rateCheck.remaining),
@@ -86,6 +86,6 @@ export function rateLimitHeaders(rateCheck: { remaining: number; reset: number }
// Pre-configured limiters
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 authLimiter = RATE_LIMITS.auth;
const authLimiter = RATE_LIMITS.auth;
+3 -3
View File
@@ -53,7 +53,7 @@ export const captureError = (error: Error, context?: Record<string, unknown>) =>
};
// Export for manual breadcrumb logging
export const addBreadcrumb = (message: string, data?: Record<string, unknown>) => {
const addBreadcrumb = (message: string, data?: Record<string, unknown>) => {
Sentry.addBreadcrumb({
message,
data,
@@ -62,7 +62,7 @@ export const addBreadcrumb = (message: string, data?: Record<string, unknown>) =
};
// Export for user tracking during errors
export const setUserContext = (userId: string, brandId?: string) => {
const setUserContext = (userId: string, brandId?: string) => {
Sentry.setUser({
id: userId,
tags: { brand_id: brandId || "unknown" },
@@ -70,7 +70,7 @@ export const setUserContext = (userId: string, brandId?: string) => {
};
// Export for transaction tracing - simplified wrapper
export async function withTransaction<T>(
async function withTransaction<T>(
name: string,
op: string,
fn: () => Promise<T>
+1 -1
View File
@@ -17,7 +17,7 @@ export function serverLog(...args: unknown[]): void {
console.log(...args);
}
export function serverInfo(...args: unknown[]): void {
function serverInfo(...args: unknown[]): void {
console.info(...args);
}
+5 -5
View File
@@ -22,7 +22,7 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
let _client: S3Client | null = null;
export function getStorageClient(): S3Client {
function getStorageClient(): S3Client {
if (_client) return _client;
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.
* 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"}`;
return `${base}/${bucket}/${key}`;
}
@@ -81,7 +81,7 @@ export async function uploadObject(opts: UploadOptions): Promise<string> {
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();
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 expiresSeconds URL expiry (default 5 minutes)
*/
export async function getPresignedPutUrl(
async function getPresignedPutUrl(
bucket: string,
key: string,
contentType: string,
@@ -118,7 +118,7 @@ export async function getPresignedPutUrl(
* @param key Object key
* @param expiresSeconds URL expiry (default 1 hour)
*/
export async function getPresignedGetUrl(
async function getPresignedGetUrl(
bucket: string,
key: string,
expiresSeconds = 3600
+23 -23
View File
@@ -33,7 +33,7 @@ export const stripe = {
};
// Plan tier configurations
export const PLANS = {
const PLANS = {
starter: {
id: "starter",
name: "Starter",
@@ -99,7 +99,7 @@ export const PLANS = {
} as const;
// Add-ons configuration
export const ADDONS = {
const ADDONS = {
wholesale_portal: {
id: "wholesale_portal",
name: "Wholesale Portal",
@@ -168,7 +168,7 @@ export interface CreateSubscriptionOptions {
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;
// Get or create Stripe customer
@@ -212,7 +212,7 @@ export async function createSubscription(options: CreateSubscriptionOptions) {
return session;
}
export async function createAddonSubscription(options: {
async function createAddonSubscription(options: {
customerId: string;
addon: keyof typeof ADDONS;
brandId: string;
@@ -256,7 +256,7 @@ export async function createAddonSubscription(options: {
// CUSTOMER PORTAL
// ============================================
export async function createCustomerPortalSession(options: {
async function createCustomerPortalSession(options: {
customerId: string;
returnUrl: string;
}) {
@@ -274,7 +274,7 @@ export async function createCustomerPortalSession(options: {
// SUBSCRIPTION UPDATES
// ============================================
export async function cancelSubscription(subscriptionId: string, immediate: boolean = false) {
async function cancelSubscription(subscriptionId: string, immediate: boolean = false) {
if (immediate) {
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, {
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);
// Get new price ID
@@ -314,7 +314,7 @@ export async function changePlan(subscriptionId: string, newPlan: keyof typeof P
// 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
const existingCustomers = await stripe.customers.list({
email,
@@ -337,11 +337,11 @@ export async function getOrCreateCustomer(brandId: string, name: string, email:
return customer.id;
}
export async function getCustomer(customerId: string) {
async function getCustomer(customerId: string) {
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);
}
@@ -349,20 +349,20 @@ export async function updateCustomer(customerId: string, data: Partial<Stripe.Cu
// PAYMENT METHODS
// ============================================
export async function listPaymentMethods(customerId: string) {
async function listPaymentMethods(customerId: string) {
return await stripe.paymentMethods.list({
customer: customerId,
type: "card",
});
}
export async function attachPaymentMethod(paymentMethodId: string, customerId: string) {
async function attachPaymentMethod(paymentMethodId: string, customerId: string) {
return await stripe.paymentMethods.attach(paymentMethodId, {
customer: customerId,
});
}
export async function setDefaultPaymentMethod(customerId: string, paymentMethodId: string) {
async function setDefaultPaymentMethod(customerId: string, paymentMethodId: string) {
return await stripe.customers.update(customerId, {
invoice_settings: {
default_payment_method: paymentMethodId,
@@ -374,18 +374,18 @@ export async function setDefaultPaymentMethod(customerId: string, paymentMethodI
// INVOICES
// ============================================
export async function listInvoices(customerId: string, limit: number = 24) {
async function listInvoices(customerId: string, limit: number = 24) {
return await stripe.invoices.list({
customer: customerId,
limit,
});
}
export async function getInvoice(invoiceId: string) {
async function getInvoice(invoiceId: string) {
return await stripe.invoices.retrieve(invoiceId);
}
export async function getUpcomingInvoice(customerId: string) {
async function getUpcomingInvoice(customerId: string) {
// Use Stripe's upcoming invoice preview endpoint
return await stripe.invoices.createPreview({
customer: customerId,
@@ -396,7 +396,7 @@ export async function getUpcomingInvoice(customerId: string) {
// PAYMENTS & REFUNDS
// ============================================
export async function createPaymentIntent(options: {
async function createPaymentIntent(options: {
amount: number;
currency?: 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({
payment_intent: paymentIntentId,
amount,
@@ -613,7 +613,7 @@ async function disableAddonFeature(brandId: string | undefined, addonKey: string
// USAGE BILLING
// ============================================
export async function createUsageRecord(options: {
async function createUsageRecord(options: {
subscriptionItemId: string;
quantity: 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
const subscription = await stripe.subscriptions.retrieve(
subscriptionItemId.split("_")[0]
@@ -653,11 +653,11 @@ export async function getUsageSummary(subscriptionItemId: string) {
// EXPORT STRIPE INSTANCE AND HELPERS
// ============================================
export async function getSubscription(subscriptionId: string) {
async function getSubscription(subscriptionId: string) {
return await stripe.subscriptions.retrieve(subscriptionId);
}
export async function listSubscriptions(customerId: string) {
async function listSubscriptions(customerId: string) {
return await stripe.subscriptions.list({
customer: customerId,
status: "all",
+1 -1
View File
@@ -35,7 +35,7 @@ export type DailyReportOptions = {
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 {
if (minutes === null) return "never";