Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stripe Connect Listing Payments #568

Merged
merged 7 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import APIKeys from "@/components/pages/APIKeys";
import About from "@/components/pages/About";
import Account from "@/components/pages/Account";
import Browse from "@/components/pages/Browse";
import Create from "@/components/pages/Create";
import CreateSell from "@/components/pages/CreateSell";
import CreateShare from "@/components/pages/CreateShare";
import DeleteConnect from "@/components/pages/DeleteConnect";
import EmailSignup from "@/components/pages/EmailSignup";
import FileBrowser from "@/components/pages/FileBrowser";
Expand Down Expand Up @@ -90,14 +91,19 @@ const App = () => {
/>

{/* Listings */}
<Route path={"/create"} element={<CreateSell />} />
<Route path={ROUTES.BOTS.path}>
<Route
path={ROUTES.BOTS.$.BROWSE.relativePath}
element={<Browse />}
/>
<Route
path={ROUTES.BOTS.$.CREATE.relativePath}
element={<Create />}
element={<CreateShare />}
/>
<Route
path={ROUTES.BOTS.$.SELL.relativePath}
element={<CreateSell />}
/>
</Route>
<Route path={ROUTES.BOT.path} element={<Listing />} />
Expand All @@ -108,14 +114,14 @@ const App = () => {

{/* Seller */}
<Route path={ROUTES.SELL.path}>
<Route
path={ROUTES.SELL.$.DASHBOARD.relativePath}
element={<SellerDashboard />}
/>
<Route
path={ROUTES.SELL.$.ONBOARDING.relativePath}
element={<SellerOnboarding />}
/>
<Route
path={ROUTES.SELL.$.DASHBOARD.relativePath}
element={<SellerDashboard />}
/>
<Route
path={ROUTES.SELL.$.DELETE.relativePath}
element={<DeleteConnect />}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const Drawer = ({
transition={{
ease: "easeInOut",
}}
className="absolute bottom-0 h-[75vh] w-full overflow-hidden rounded-t-3xl bg-neutral-900"
className="absolute bottom-0 h-[60vh] w-full overflow-hidden rounded-t-3xl bg-neutral-900"
style={{ y }}
drag="y"
dragControls={controls}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/gdpr/gdprbanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const GDPRBanner: React.FC = () => {
Opt out
</button>
<button
className="bg-primary-9 text-white rounded-full px-4 py-2 transition-colors duration-300 hover:bg-primary-8"
className="bg-gray-1 text-gray-12 rounded-full px-4 py-2 transition-colors duration-300 hover:bg-gray-11"
onClick={handleAccept}
>
Accept
Expand Down
69 changes: 69 additions & 0 deletions frontend/src/components/listing/ListingPayment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import CheckoutButton from "@/components/stripe/CheckoutButton";
import { formatPrice } from "@/lib/utils/formatNumber";

interface Props {
listingId: string;
stripeProductId: string;
priceAmount: number;
inventoryType: "finite" | "infinite" | "preorder";
inventoryQuantity?: number;
preorderReleaseDate?: number;
isReservation: boolean;
reservationDepositAmount?: number;
}

const ListingPayment = ({
listingId,
stripeProductId,
priceAmount,
inventoryType,
inventoryQuantity,
preorderReleaseDate,
isReservation,
reservationDepositAmount,
}: Props) => {
return (
<div className="p-4">
<h3 className="text-lg font-semibold mb-2">Purchase Information</h3>

<div className="space-y-2 mb-4">
<div className="flex justify-between">
<span>Price:</span>
<span className="font-semibold">{formatPrice(priceAmount)}</span>
</div>

{isReservation && reservationDepositAmount && (
<div className="flex justify-between text-sm text-gray-2">
<span>Reservation Deposit:</span>
<span>{formatPrice(reservationDepositAmount)}</span>
</div>
)}

{inventoryType === "finite" && inventoryQuantity !== undefined && (
<div className="flex justify-between text-sm text-gray-2">
<span>Available Units:</span>
<span>{inventoryQuantity}</span>
</div>
)}

{inventoryType === "preorder" && preorderReleaseDate && (
<div className="flex justify-between text-sm text-gray-2">
<span>Release Date:</span>
<span>
{new Date(preorderReleaseDate * 1000).toLocaleDateString()}
</span>
</div>
)}
</div>
<div className="flex justify-end">
<CheckoutButton
listingId={listingId}
stripeProductId={stripeProductId}
label="Purchase Now"
/>
</div>
</div>
);
};

export default ListingPayment;
30 changes: 29 additions & 1 deletion frontend/src/components/listing/ListingRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ListingImageGallery from "@/components/listing/ListingImageGallery";
import ListingMetadata from "@/components/listing/ListingMetadata";
import ListingName from "@/components/listing/ListingName";
import ListingOnshape from "@/components/listing/ListingOnshape";
import ListingPayment from "@/components/listing/ListingPayment";
import ListingRegisterRobot from "@/components/listing/ListingRegisterRobot";
import { ListingResponse } from "@/components/listing/types";

Expand All @@ -27,9 +28,17 @@ const ListingRenderer = ({ listing }: { listing: ListingResponse }) => {
user_vote: userVote,
onshape_url: onshapeUrl,
is_featured: isFeatured,
stripe_product_id: stripeProductId,
price_amount: priceAmount,
inventory_type: inventoryType,
inventory_quantity: inventoryQuantity,
preorder_release_date: preorderReleaseDate,
is_reservation: isReservation,
reservation_deposit_amount: reservationDepositAmount,
} = listing;
const [artifacts, setArtifacts] = useState(initialArtifacts);
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const isForSale = priceAmount && stripeProductId && inventoryType;

return (
<div className="max-w-6xl mx-auto p-4 pt-12">
Expand Down Expand Up @@ -61,7 +70,26 @@ const ListingRenderer = ({ listing }: { listing: ListingResponse }) => {
userVote={userVote}
/>

<hr className="border-gray-200 my-4" />
{/* Add payment section if price exists */}
{isForSale && (
<>
<hr className="border-gray-2 my-4" />
<ListingPayment
listingId={listingId}
stripeProductId={stripeProductId}
priceAmount={priceAmount}
inventoryType={
inventoryType as "finite" | "infinite" | "preorder"
}
inventoryQuantity={inventoryQuantity || undefined}
preorderReleaseDate={preorderReleaseDate || undefined}
isReservation={isReservation || false}
reservationDepositAmount={reservationDepositAmount || undefined}
/>
</>
)}

<hr className="border-gray-2 my-4" />

{/* Build this robot */}
<div className="flex items-baseline gap-4">
Expand Down
16 changes: 9 additions & 7 deletions frontend/src/components/listing/UploadContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const UploadContent: FC<UploadContentProps> = ({ images, onChange }) => {
}, [images]);

return (
<div className="p-6 bg-black border border-gray-500 rounded-lg">
<div className="p-6 bg-gray-12 border border-gray-11 rounded-lg">
<ImageUploading
multiple
value={images}
Expand All @@ -65,15 +65,17 @@ const UploadContent: FC<UploadContentProps> = ({ images, onChange }) => {
<div className="upload__image-wrapper">
{/* Dropzone Area */}
<div
className={`border-2 border-dashed p-5 rounded-lg flex flex-col items-center justify-center h-64 transition-colors duration-300 ${
isDragging
? "border-orange-900 bg-orange-300"
: "border-gray-6 bg-gray-900"
}`}
className={`
border-2 border-dashed p-5 rounded-lg flex flex-col items-center justify-center h-64 transition-colors duration-300 hover:cursor-pointer hover:bg-gray-10 hover:border-gray-1
${
isDragging
? "border-gray-1 bg-gray-10"
: "border-gray-5 bg-gray-11"
}`}
onClick={onImageUpload}
{...dragProps}
>
<p className="text-gray-300">
<p className="text-gray-1">
Drag & drop images here, click to select files, or paste an
image from your clipboard
</p>
Expand Down
77 changes: 77 additions & 0 deletions frontend/src/components/modals/CreateListingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useNavigate } from "react-router-dom";

import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useAuthentication } from "@/hooks/useAuth";
import ROUTES from "@/lib/types/routes";
import { Share2, ShoppingBag } from "lucide-react";

interface Props {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
}

export const CreateListingModal = ({ isOpen, onOpenChange }: Props) => {
const navigate = useNavigate();
const auth = useAuthentication();
const canSell = auth.currentUser?.stripe_connect_onboarding_completed;

const handleOptionClick = (path: string) => {
onOpenChange(false);
navigate(path);
};

return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[600px] bg-gray-12 text-gray-1 border border-gray-1 rounded-lg shadow-lg">
<DialogHeader>
<DialogTitle>What would you like to do?</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-4 p-4">
<div
onClick={() =>
handleOptionClick(
`${ROUTES.BOTS.path}/${ROUTES.BOTS.$.CREATE.relativePath}`,
)
}
className="flex flex-col items-center justify-center p-6 rounded-lg border border-gray-7 hover:border-gray-1 hover:bg-gray-11 cursor-pointer transition-all"
>
<Share2 className="w-12 h-12 mb-4" />
<h3 className="text-lg font-semibold">Share a Robot</h3>
<p className="text-sm text-gray-7 text-center mt-2">
Share your Robot with the community
</p>
</div>

<div
onClick={() =>
canSell &&
handleOptionClick(
`${ROUTES.BOTS.path}/${ROUTES.BOTS.$.SELL.relativePath}`,
)
}
className={`flex flex-col items-center justify-center p-6 rounded-lg border ${
canSell
? "border-gray-7 hover:border-gray-1 hover:bg-gray-11 cursor-pointer"
: "border-gray-4 opacity-50 cursor-not-allowed"
} transition-all`}
>
<ShoppingBag className="w-12 h-12 mb-4" />
<h3 className="text-lg font-semibold">Sell a Robot</h3>
<p className="text-sm text-gray-7 text-center mt-2">
{canSell
? "List your Robot for sale"
: "Complete Seller onboarding to sell"}
</p>
</div>
</div>
</DialogContent>
</Dialog>
);
};

export default CreateListingModal;
12 changes: 6 additions & 6 deletions frontend/src/components/orders/OrderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
const getStatusColor = (status: string) => {
if (isRedStatus) return "bg-red-500";
if (activeStatuses.includes(status) || status === "delivered")
return "bg-primary-9";
return "bg-primary";
return "bg-gray-300";
};

const getTextColor = (status: string) => {
if (isRedStatus) return "text-red-600";
if (activeStatuses.includes(status) || status === "delivered")
return "text-primary-9";
return "text-primary";
return "text-gray-600";
};

Expand All @@ -78,7 +78,7 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
};

return (
<div className="bg-white shadow-md rounded-lg p-4 md:p-6 w-full">
<div className="bg-gray-1 shadow-md rounded-lg p-4 md:p-6 w-full">
<h2 className="text-gray-12 font-bold text-2xl mb-1">{product.name}</h2>
<p className="text-gray-11 mb-2 sm:text-lg">
Status:{" "}
Expand Down Expand Up @@ -138,7 +138,7 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
index <= currentStatusIndex
? getStatusColor(status)
: "bg-gray-300"
} text-white`}
} text-gray-12`}
>
{index < currentStatusIndex ? (
<svg
Expand Down Expand Up @@ -199,8 +199,8 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
</p>
)}

<div className="mt-4 text-sm bg-gray-3 p-3 rounded-md">
<h3 className="text-gray-12 font-semibold text-lg">Shipping Address</h3>
<div className="mt-4 text-sm bg-gray-3 p-3 rounded-md text-gray-12">
<h3 className="font-semibold text-lg">Shipping Address</h3>
<p>{order.shipping_name}</p>
<p>{order.shipping_address_line1}</p>
{order.shipping_address_line2 && <p>{order.shipping_address_line2}</p>}
Expand Down
Loading
Loading