0ac4beaaa8
- Add requireAuth() to admin-permissions.ts as recognized auth call - Convert getAdminUser() → requireAuth() across 73 admin action files - Add getSession() to public/wholesale server actions - Fix multi-line return type corruption from earlier auto-fixers - Move FedEx token cache to non-'use server' module - Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD, EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS - Update Stripe API version 2026-05-27 → 2026-06-24 - Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient - Fix 51 TypeScript errors (return type corruption, missing imports)
113 lines
3.5 KiB
TypeScript
113 lines
3.5 KiB
TypeScript
"use server";
|
|
|
|
import { getAdminUser } from "@/lib/admin-permissions";
|
|
import { pool } from "@/lib/db";
|
|
import { getSession } from "@/lib/auth";
|
|
|
|
export type StopDetail = {
|
|
id: string;
|
|
city: string;
|
|
state: string;
|
|
date: string;
|
|
time: string;
|
|
location: string;
|
|
slug: string;
|
|
active: boolean;
|
|
brand_id: string;
|
|
address: string | null;
|
|
zip: string | null;
|
|
cutoff_time: string | null;
|
|
brands: { name: string; slug: string } | { name: string; slug: string }[] | null;
|
|
};
|
|
|
|
export type AssignedProduct = {
|
|
id: string;
|
|
product_id: string;
|
|
products: { name: string; type: string; price: number } | null;
|
|
};
|
|
|
|
export type StopDetailsResult =
|
|
| {
|
|
success: true;
|
|
stop: StopDetail;
|
|
allProducts: { id: string; name: string; type: string; price: number }[];
|
|
assignedProducts: AssignedProduct[];
|
|
brands: { id: string; name: string; slug: string }[];
|
|
/** admin_users.user_id of the caller, forwarded to RPCs that authorise via user_id lookup */
|
|
callerUid: string;
|
|
}
|
|
| { success: false; error: string };
|
|
|
|
/**
|
|
* Fetch a single stop with its brand, all candidate products, currently
|
|
* assigned products, and the list of brands (for the brand switcher in the
|
|
* edit form). Mirrors the data the old `/admin/stops/[id]` page server
|
|
* component loaded, so the modal can be a drop-in replacement.
|
|
*/
|
|
export async function getStopDetails(stopId: string): Promise<StopDetailsResult> {
|
|
|
|
await getSession(); const adminUser = await getAdminUser();
|
|
if (!adminUser) return { success: false, error: "Not authenticated" };
|
|
if (!adminUser.can_manage_stops) {
|
|
return { success: false, error: "Not authorized" };
|
|
}
|
|
|
|
// 1. Stop + brand — legacy schema, raw SQL (the Drizzle stops table
|
|
// maps to the new schema which doesn't have city/state/date/etc).
|
|
const stopRes = await pool.query<StopDetail & { brand_name: string; brand_slug: string }>(
|
|
`SELECT s.*, b.name AS brand_name, b.slug AS brand_slug
|
|
FROM stops s
|
|
LEFT JOIN brands b ON b.id = s.brand_id
|
|
WHERE s.id = $1
|
|
LIMIT 1`,
|
|
[stopId],
|
|
);
|
|
const stopRow = stopRes.rows[0];
|
|
if (!stopRow) {
|
|
return { success: false, error: "Stop not found" };
|
|
}
|
|
const stop: StopDetail = {
|
|
...stopRow,
|
|
brands: stopRow.brand_name
|
|
? { name: stopRow.brand_name, slug: stopRow.brand_slug }
|
|
: null,
|
|
};
|
|
|
|
// Brand-scope check for brand_admin
|
|
if (adminUser.brand_id && stop.brand_id !== adminUser.brand_id) {
|
|
return { success: false, error: "Not authorized for this brand" };
|
|
}
|
|
|
|
// 2. Candidate products for this brand
|
|
const { rows: allProducts } = await pool.query<{ id: string; name: string; type: string; price: number }>(
|
|
`SELECT id, name, type, price
|
|
FROM products
|
|
WHERE brand_id = $1 AND active = true`,
|
|
[stop.brand_id],
|
|
);
|
|
|
|
// 3. Assigned products (joined with product info)
|
|
const { rows: productStops } = await pool.query<AssignedProduct>(
|
|
`SELECT ps.id, ps.product_id,
|
|
json_build_object('id', p.id, 'name', p.name, 'type', p.type, 'price', p.price) AS products
|
|
FROM product_stops ps
|
|
LEFT JOIN products p ON p.id = ps.product_id
|
|
WHERE ps.stop_id = $1`,
|
|
[stopId],
|
|
);
|
|
|
|
// 4. Brands for the brand switcher
|
|
const { rows: brands } = await pool.query<{ id: string; name: string; slug: string }>(
|
|
`SELECT id, name, slug FROM brands ORDER BY name`,
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
stop,
|
|
allProducts: allProducts ?? [],
|
|
assignedProducts: (productStops ?? []) as AssignedProduct[],
|
|
brands: brands ?? [],
|
|
callerUid: adminUser.user_id,
|
|
};
|
|
}
|