diff --git a/frontend/src/components/auth/AuthBlock.tsx b/frontend/src/components/auth/AuthBlock.tsx index a6dccf45..172d3260 100644 --- a/frontend/src/components/auth/AuthBlock.tsx +++ b/frontend/src/components/auth/AuthBlock.tsx @@ -25,7 +25,10 @@ const AuthBlock: React.FC = ({ title, onClosed, signup }) => { return ( -
+
diff --git a/frontend/src/components/auth/AuthProvider.tsx b/frontend/src/components/auth/AuthProvider.tsx index 8900c0f7..2e30a59a 100644 --- a/frontend/src/components/auth/AuthProvider.tsx +++ b/frontend/src/components/auth/AuthProvider.tsx @@ -144,7 +144,7 @@ const AuthProvider = () => {
- OR + OR
diff --git a/frontend/src/components/auth/RequireAuthentication.tsx b/frontend/src/components/auth/RequireAuthentication.tsx index 0f01ab1d..b3a44a91 100644 --- a/frontend/src/components/auth/RequireAuthentication.tsx +++ b/frontend/src/components/auth/RequireAuthentication.tsx @@ -2,6 +2,7 @@ import React from "react"; import { useNavigate } from "react-router-dom"; import AuthBlock from "@/components/auth/AuthBlock"; +import Container from "@/components/ui/container"; import { useAuthentication } from "@/hooks/useAuth"; interface Props { @@ -24,16 +25,16 @@ const RequireAuthentication = (props: Props) => { return isAuthenticated ? ( <>{children} ) : ( - <> -
-
-
- -
+ +
+

+ You must be logged in to view this page +

+
+
-
- +
); }; diff --git a/frontend/src/components/listing/ListingPayment.tsx b/frontend/src/components/listing/ListingPayment.tsx index b9e7fcb0..fc8d0179 100644 --- a/frontend/src/components/listing/ListingPayment.tsx +++ b/frontend/src/components/listing/ListingPayment.tsx @@ -60,6 +60,8 @@ const ListingPayment = ({ listingId={listingId} stripeProductId={stripeProductId} label="Purchase Now" + inventoryType={inventoryType} + inventoryQuantity={inventoryQuantity} />
diff --git a/frontend/src/components/listing/ListingRenderer.tsx b/frontend/src/components/listing/ListingRenderer.tsx index 7f679b9c..06632362 100644 --- a/frontend/src/components/listing/ListingRenderer.tsx +++ b/frontend/src/components/listing/ListingRenderer.tsx @@ -74,6 +74,14 @@ const ListingRenderer = ({ listing }: { listing: ListingResponse }) => { createdAt={createdAt} /> +
+ + + {/* Add payment section if price exists */} {isForSale && ( <> @@ -93,14 +101,6 @@ const ListingRenderer = ({ listing }: { listing: ListingResponse }) => { )} -
- - -
{/* Build this robot */} diff --git a/frontend/src/components/listings/ListingGridCard.tsx b/frontend/src/components/listings/ListingGridCard.tsx index d7087ccc..312d85b3 100644 --- a/frontend/src/components/listings/ListingGridCard.tsx +++ b/frontend/src/components/listings/ListingGridCard.tsx @@ -1,4 +1,5 @@ import { paths } from "@/gen/api"; +import { formatPrice } from "@/lib/utils/formatNumber"; import { RenderDescription } from "../listing/ListingDescription"; @@ -59,6 +60,20 @@ const ListingGridCard = ({ />
)} + {listing?.price_amount && ( +
+ {formatPrice(listing.price_amount)} + {listing.inventory_type === "finite" && + listing.inventory_quantity !== null && ( + + ({listing.inventory_quantity} available) + + )} + {listing.inventory_type === "preorder" && ( + (Pre-order) + )} +
+ )}
); diff --git a/frontend/src/components/nav/Navbar.tsx b/frontend/src/components/nav/Navbar.tsx index 703e2cd7..5425c7f8 100644 --- a/frontend/src/components/nav/Navbar.tsx +++ b/frontend/src/components/nav/Navbar.tsx @@ -123,7 +123,7 @@ const Navbar = () => { variant="ghost" className="px-3 py-2 text-gray-1" > - Sign In + Log In @@ -150,7 +150,7 @@ const APIKeys = () => { return ( -
+

API Keys

{apiKeys === null ? ( @@ -191,7 +191,7 @@ const APIKeys = () => { -
diff --git a/frontend/src/components/pages/ResearchPage.tsx b/frontend/src/components/pages/ResearchPage.tsx index f9861a57..e3c9af0c 100644 --- a/frontend/src/components/pages/ResearchPage.tsx +++ b/frontend/src/components/pages/ResearchPage.tsx @@ -105,7 +105,7 @@ const MediumCard: React.FC<{ article: ResearchPost }> = ({ article }) => {
{article.author} @@ -144,4 +144,15 @@ const MediumCard: React.FC<{ article: ResearchPost }> = ({ article }) => { ); }; +const getAuthorImage = (author: string): string => { + switch (author.toLowerCase()) { + case "ben bolte": + return "https://miro.medium.com/v2/resize:fill:176:176/1*EuQxKArtHb0orCJcWTPHkA.jpeg"; + case "paweł budzianowski": + return "https://miro.medium.com/v2/resize:fill:40:40/1*REeM2VDUPg7VWMU1UwnsBw.png"; + default: + return "https://miro.medium.com/v2/resize:fill:40:40/1*REeM2VDUPg7VWMU1UwnsBw.png"; + } +}; + export default MediumArticles; diff --git a/frontend/src/components/pages/SellerOnboarding.tsx b/frontend/src/components/pages/SellerOnboarding.tsx index e14f8998..1cbed7d6 100644 --- a/frontend/src/components/pages/SellerOnboarding.tsx +++ b/frontend/src/components/pages/SellerOnboarding.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; +import RequireAuthentication from "@/components/auth/RequireAuthentication"; import Spinner from "@/components/ui/Spinner"; import { Button } from "@/components/ui/button"; import { useAlertQueue } from "@/hooks/useAlertQueue"; @@ -40,7 +41,7 @@ export default function SellerOnboarding() { if (auth.isLoading) { return ( -
+
@@ -48,11 +49,6 @@ export default function SellerOnboarding() { ); } - if (!auth.isAuthenticated) { - navigate(ROUTES.LOGIN.path); - return null; - } - const handleCreateAccount = async () => { try { setAccountCreatePending(true); @@ -81,73 +77,75 @@ export default function SellerOnboarding() { const showStripeConnect = connectedAccountId && stripeConnectInstance; return ( -
-
-

Start Selling on K-Scale

- - {!connectedAccountId && ( -
-

- Set up your K-Scale connected Stripe account to start selling - robots and receiving payments. -

- - -
- )} - - {showStripeConnect && stripeConnectInstance && ( - - { - setOnboardingExited(true); - auth.fetchCurrentUser(); - if (auth.currentUser?.stripe_connect_onboarding_completed) { - navigate(ROUTES.SELL.DASHBOARD.path); - } - }} - /> - - )} - - {(connectedAccountId || accountCreatePending || onboardingExited) && ( -
- {connectedAccountId && ( -
- - Complete the onboarding process to start selling robots. - -
- Connected account ID:{" "} - - {connectedAccountId} - + +
+
+

Start Selling on K-Scale

+ + {!connectedAccountId && ( +
+

+ Set up your K-Scale connected Stripe account to start selling + robots and receiving payments. +

+ + +
+ )} + + {showStripeConnect && stripeConnectInstance && ( + + { + setOnboardingExited(true); + auth.fetchCurrentUser(); + if (auth.currentUser?.stripe_connect_onboarding_completed) { + navigate(ROUTES.SELL.DASHBOARD.path); + } + }} + /> + + )} + + {(connectedAccountId || accountCreatePending || onboardingExited) && ( +
+ {connectedAccountId && ( +
+ + Complete the onboarding process to start selling robots. + +
+ Connected account ID:{" "} + + {connectedAccountId} + +
+ + This usually takes a few steps/submissions. + + + It may take some time for Stripe to process your info + between submissions. Continue through your account page or + refresh this page to check/update your application status. +
- - This usually takes a few steps/submissions. - - - It may take some time for Stripe to process your info between - submissions. Continue through your account page or refresh - this page to check/update your application status. - -
- )} - {accountCreatePending && ( -

Creating a K-Scale Stripe connected account...

- )} - {onboardingExited &&

Account setup completed

} -
- )} + )} + {accountCreatePending && ( +

Creating a K-Scale Stripe connected account...

+ )} + {onboardingExited &&

Account setup completed

} +
+ )} +
-
+ ); } diff --git a/frontend/src/components/stripe/CheckoutButton.tsx b/frontend/src/components/stripe/CheckoutButton.tsx index 9ad2ac24..7ff4cbaf 100644 --- a/frontend/src/components/stripe/CheckoutButton.tsx +++ b/frontend/src/components/stripe/CheckoutButton.tsx @@ -13,11 +13,21 @@ import { loadStripe } from "@stripe/stripe-js"; const stripePromise = loadStripe(STRIPE_PUBLISHABLE_KEY); -const CheckoutButton: React.FC<{ +interface Props { listingId: string; stripeProductId: string; label?: string; -}> = ({ listingId, stripeProductId, label = "Order Now" }) => { + inventoryType?: "finite" | "infinite" | "preorder"; + inventoryQuantity?: number; +} + +const CheckoutButton: React.FC = ({ + listingId, + stripeProductId, + label = "Order Now", + inventoryType, + inventoryQuantity, +}) => { const { addErrorAlert } = useAlertQueue(); const [isLoading, setIsLoading] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false); @@ -25,6 +35,12 @@ const CheckoutButton: React.FC<{ const navigate = useNavigate(); const location = useLocation(); + const isOutOfStock = + inventoryType === "finite" && + (inventoryQuantity === undefined || inventoryQuantity <= 0); + + const buttonLabel = isOutOfStock ? "Out of Stock" : label; + const handleClick = async () => { setIsLoading(true); @@ -96,12 +112,14 @@ const CheckoutButton: React.FC<{ <> diff --git a/frontend/src/components/terminal/TerminalAllRobots.tsx b/frontend/src/components/terminal/TerminalAllRobots.tsx index 6b7e7507..51813c8c 100644 --- a/frontend/src/components/terminal/TerminalAllRobots.tsx +++ b/frontend/src/components/terminal/TerminalAllRobots.tsx @@ -28,13 +28,16 @@ const TerminalAllRobots = ({ robots, onDeleteRobot }: Props) => {
) : (
-

No robots found.

+

+ No robots registered. +

+

Register a robot to get started.

)} diff --git a/frontend/src/components/ui/Header.tsx b/frontend/src/components/ui/Header.tsx index 0b974866..c920bb9d 100644 --- a/frontend/src/components/ui/Header.tsx +++ b/frontend/src/components/ui/Header.tsx @@ -1,16 +1,24 @@ import { FaTimes } from "react-icons/fa"; +import { cn } from "@/lib/utils"; + interface HeaderProps { title: string | React.ReactNode; label?: string; onClosed?: () => void; + className?: string; } -const Header = ({ title, label, onClosed }: HeaderProps) => { +const Header = ({ title, label, onClosed, className }: HeaderProps) => { return ( -
-

{title}

- {label &&

{label}

} +
+

{title}

+ {label &&

{label}

} {onClosed && (