import { clsx } from "clsx"; import { bpid, formatCentsToUSD, truncate, stripPriceFromTitle, } from "@/lib/utils"; import moment from "moment"; import { useAppDispatch, useAppSelector } from "@/hooks"; import { useState, useEffect } from "react"; import { useGetMeQuery } from "@/models/authAPI"; import { CashoutMethodOut, CashoutMethodsResponse, WalletApi, CashoutRequestResponse, CashoutRequestSummary, } from "@/api_fsb"; import { WalletApi as JBApi, CashoutRequest } from "@/api_jb"; import { List, IdCard, PackageCheck } from "lucide-react"; import { addCashoutMethods, selectAllCashoutMethods, } from "@/models/cashoutMethodsSlice"; import { setCmSelected, setCmShowHistory } from "@/models/appSlice"; import { createColumnHelper, getCoreRowModel, getPaginationRowModel, useReactTable, getFilteredRowModel, getSortedRowModel, flexRender, RowSelectionState, FilterFn, } from "@tanstack/react-table"; import { useSelector } from "react-redux"; import type { Row } from "@tanstack/react-table"; import { useGetUserCashoutsQuery } from "@/models/cashoutRequests"; const RedemptionHeader = () => { const userCashoutMethods = useSelector(selectAllCashoutMethods) || []; return ( <>

{userCashoutMethods.length} {userCashoutMethods.length != null ? "+" : ""} Redemption Methods

); }; interface CashoutMethodListItemDescriptionProps { description?: string; } function CashoutMethodListItemDescription({ description, }: CashoutMethodListItemDescriptionProps) { return (
); } interface CashoutMethodListItemProps { row: Row; } interface CashoutMethodItemAmountProps { cm: CashoutMethodOut; } const CashoutMethodItemAmount = ({ cm }: CashoutMethodItemAmountProps) => { const cardRange = cm.min_value !== cm.max_value; if (cardRange) { return ( <>
Min Amount:
{formatCentsToUSD(cm.min_value_usd || 0)}
Max Amount:
{formatCentsToUSD(cm.max_value_usd || 0)}
); } else { return ( <>
Amount:
{formatCentsToUSD(cm.min_value_usd || 0)}
); } }; function CashoutMethodListItem({ row }: CashoutMethodListItemProps) { const [showDescription, setShowDescription] = useState(false); return (

{stripPriceFromTitle(row.original.name)}

{/* animated glow blob behind the image */}
{row.original.name}
{/* Not every CashoutMethod has a Description */} {row.original.description && (

setShowDescription(!showDescription)} > Show Description

{showDescription && ( )}
)}
); } interface CashoutAmountInputProps { reqAmount: number; // cents setRequestAmount: (cents: number) => void; maxCents: number; // max withdrawable, in cents } const RedemptionDetailAmountEnter = ({ reqAmount, setRequestAmount, maxCents, }: CashoutAmountInputProps) => { // Local text state so users can type freely (e.g. "12.", "0.5") // without fighting a reformatted value every keystroke. const [displayValue, setDisplayValue] = useState( reqAmount ? (reqAmount / 100).toString() : "", ); const overLimit = reqAmount > maxCents; const handleChange = (e: React.ChangeEvent) => { const raw = e.target.value; // Allow only digits and a single decimal point with up to 2 decimals if (!/^\d*\.?\d{0,2}$/.test(raw)) return; setDisplayValue(raw); const dollars = parseFloat(raw); setRequestAmount(isNaN(dollars) ? 0 : Math.round(dollars * 100)); }; return (
$
{!overLimit && (
Max: ${(maxCents / 100).toFixed(2)}
)} {overLimit && (
Amount exceeds your available balance of $ {(maxCents / 100).toFixed(2)}.
)}
); }; const RedemptionDetails = () => { // res = session.post(JB_BASE_URL + 'wallet/cashout/request/', json={ // 'amount': 10_00, // 'cashout_method_id': paypal_cm['id'], // }) // res.raise_for_status() // print(res.json()) const selected = useAppSelector((state) => state.app.cmSelected); if (!selected) return null; const dispatch = useAppDispatch(); const [reqAmount, setRequestAmount] = useState(selected.min_value); const [errorMsg, setErrorMesg] = useState(""); const { data: user } = useGetMeQuery(); if (!user?.product_user_id) return; const submitCashOut = () => { const ccr = { cashout_method_id: selected.id, amount: reqAmount, } as CashoutRequest; // const config = new Configuration({ basePath: "http://localhost:5174" }); new JBApi() .requestCashoutWalletCashoutRequestPost(ccr) .then(() => { // TODO: will `res.data.cashout_method` & `res.data.confirmation_link` // TODO: always be available in this response? dispatch(setCmSelected(undefined)); dispatch(setCmShowHistory(true)); }) .catch((err) => { console.log("ERR", err); const response = err?.response?.data as | CashoutRequestResponse | undefined; setErrorMesg(response?.msg ?? err.message); }); }; return ( <>

{selected.type === "PAYPAL" ? `${selected.name} (${selected.ext_id})` : selected.name}

{errorMsg && (

{errorMsg}

)}

You will recieve an email with confirmation deatils when requesting the Cashout Method

{/* animated glow blob behind the image */}
{selected.name}
); }; const RedemptionBrowser = () => { // Don't show the browser if a CashoutMethod is already selected const selected = useAppSelector((state) => state.app.cmSelected); if (selected) return; const dispatch = useAppDispatch(); const userCashoutMethods = useSelector(selectAllCashoutMethods); const [rowSelection, setRowSelection] = useState({}); const [globalFilter, setGlobalFilter] = useState("CASH"); const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }); const { data: user } = useGetMeQuery(); useEffect(() => { if (!user?.product_user_id) return; new WalletApi() .getCashoutMethodsProductIdCashoutMethodsGet(bpid, user.product_user_id) .then((res) => { const response = res.data as CashoutMethodsResponse; dispatch(addCashoutMethods(response.cashout_methods)); }); }, [bpid, user?.product_user_id]); const columnHelper = createColumnHelper(); const columns = [ columnHelper.accessor("name", { header: () => "Name", }), columnHelper.accessor("image_url", { header: () => "Image", }), columnHelper.accessor("description", { header: () => "Desc", }), ]; useEffect(() => { const selectedRows = table .getSelectedRowModel() .rows.map((r) => r.original); const cm = selectedRows[0] as CashoutMethodOut; dispatch(setCmSelected(cm)); }, [rowSelection]); const categoryFilter: FilterFn = ( row, _columnId, filterValue, ) => row.original.category === filterValue; const table = useReactTable({ data: userCashoutMethods || [], columns: columns, getCoreRowModel: getCoreRowModel(), getRowId: (row) => row.id, enableRowSelection: true, enableMultiRowSelection: false, state: { pagination, rowSelection, globalFilter, }, onPaginationChange: setPagination, onRowSelectionChange: setRowSelection, globalFilterFn: categoryFilter, onGlobalFilterChange: setGlobalFilter, getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), }); return (
setGlobalFilter("CASH")} > Cash
setGlobalFilter("GIFT_CARD")} > Gift Card
{table.getRowModel().rows.map((row) => ( ))}
{/* Page info */}
Page {table.getState().pagination.pageIndex + 1} of{" "} {table.getPageCount()}
{/* Navigation buttons */}
{/* Page size selector */}
); }; const RedemptionModalSteps = () => { const dispatch = useAppDispatch(); const selected = useAppSelector((state) => state.app.cmSelected); const showHistory = useAppSelector((state) => state.app.cmShowHistory); return (
  1. { dispatch(setCmSelected(undefined)); dispatch(setCmShowHistory(false)); }} >
  2. { dispatch(setCmSelected(undefined)); dispatch(setCmShowHistory(true)); }} >
); }; const RedemptionHistory = () => { const { data: user } = useGetMeQuery(); if (!user?.product_user_id) return; const { data: cashout_requests } = useGetUserCashoutsQuery( { bpid, productUserId: user.product_user_id }, { skip: !user.product_user_id }, ); // --- Table work now --- // const [rowSelection, setRowSelection] = useState({}); const [globalFilter, setGlobalFilter] = useState(""); // const [pagination, setPagination] = useState({ // pageIndex: 0, // pageSize: 10, // }); const columnHelper = createColumnHelper(); const columns = [ columnHelper.accessor("created", { header: () => "Created", cell: (info) => moment(info.getValue()).format("M/D/YY"), size: 60, meta: { align: "left", }, }), columnHelper.accessor("description", { header: () => "Description", cell: (info) => truncate(info.getValue(), 12), size: 100, meta: { align: "left", }, }), columnHelper.accessor("amount", { header: () => "Amount", cell: (info) => formatCentsToUSD(info.getValue()), size: 80, meta: { align: "center", }, }), columnHelper.accessor("status", { header: () => "Status", cell: (props) => { const desc = props.getValue(); switch (desc) { case "PENDING": return "Pending 🤞"; case "APPROVED": return "Approved 👍"; case "REJECTED": return "Rejected 🚫"; case "CANCELLED": return "Cancelled 🚫"; case "FAILED": return "Failed ❌"; case "COMPLETE": return "Complete ✅"; default: return desc; } }, size: 80, meta: { align: "left", }, }), ]; let table = useReactTable({ data: cashout_requests || [], columns: columns, getCoreRowModel: getCoreRowModel(), getRowId: (row) => row.id, // enableRowSelection: true, // enableMultiRowSelection: false, state: { // pagination, // rowSelection, globalFilter, }, // onPaginationChange: setPagination, // onRowSelectionChange: setRowSelection, onGlobalFilterChange: setGlobalFilter, getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), }); const STATUSES = ["PENDING", "APPROVED", "REJECTED", "COMPLETE"]; return (
{STATUSES.map((status) => { const isActive = globalFilter === status; return (
setGlobalFilter(status)} > {status}
); })}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { const align = header.column.columnDef.meta?.align || "left"; const alignClass = align === "center" ? "text-center" : align === "right" ? "text-right" : "text-left"; return ( ); })} ))} {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => { const align = cell.column.columnDef.meta?.align || "left"; const alignClass = align === "center" ? "text-center" : align === "right" ? "text-right" : "text-left"; return ( ); })} ))}
{flexRender( header.column.columnDef.header, header.getContext(), )}
{flexRender( cell.column.columnDef.cell, cell.getContext(), )}
{/* Page info */}
Page {table.getState().pagination.pageIndex + 1} of{" "} {table.getPageCount()}
{/* Navigation buttons */}
{/* Page size selector */}
); }; const RedemptionModalBody = () => { // Show (1) Selection, (2) Amount Application, (3) Confirmation const selected = useAppSelector((state) => state.app.cmSelected); const showHistory = useAppSelector((state) => state.app.cmShowHistory); if (showHistory) { return ( <> ); } if (selected) { return ( <> ); } else { return ( <> ); } }; const Redemption = () => { const [showRedemption, setShowRedemption] = useState(true); const { data: user } = useGetMeQuery(); if (!user?.product_user_id) return null; return (
setShowRedemption(!showRedemption)} >

(Click to {showRedemption ? "hide" : "show"})

{showRedemption && }
); }; export default Redemption;