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 Onboarding Integration #528

Merged
merged 9 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
17 changes: 17 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"@react-oauth/google": "^0.12.1",
"@react-three/drei": "^9.109.2",
"@react-three/fiber": "^8.16.8",
"@stripe/connect-js": "^3.3.16",
"@stripe/react-connect-js": "^3.3.18",
"@stripe/stripe-js": "^1.54.2",
"@types/three": "^0.168.0",
"@uidotdev/usehooks": "^2.4.1",
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,21 @@ import Login from "@/components/pages/Login";
import Logout from "@/components/pages/Logout";
import NotFound from "@/components/pages/NotFound";
import Profile from "@/components/pages/Profile";
import SellerDashboard from "@/components/pages/SellerDashboard";
import Signup from "@/components/pages/Signup";
import { AlertQueue, AlertQueueProvider } from "@/hooks/useAlertQueue";
import { AuthenticationProvider } from "@/hooks/useAuth";

import GDPRBanner from "./components/gdpr/gdprbanner";
import DeleteConnect from "./components/pages/DeleteConnect";
import DownloadsPage from "./components/pages/Download";
import OrderSuccess from "./components/pages/OrderSuccess";
import OrdersPage from "./components/pages/Orders";
import Playground from "./components/pages/Playground";
import PrivacyPolicy from "./components/pages/PrivacyPolicy";
import ResearchPage from "./components/pages/ResearchPage";
import SellerOnboarding from "./components/pages/SellerOnboarding";
import SellerOnboardingContinued from "./components/pages/SellerOnboardingContinued";
import Terminal from "./components/pages/Terminal";
import TermsOfService from "./components/pages/TermsOfService";

Expand Down Expand Up @@ -82,6 +86,22 @@ const App = () => {
<Route path="/tos" element={<TermsOfService />} />
<Route path="/privacy" element={<PrivacyPolicy />} />

<Route
path="/seller-onboarding"
element={<SellerOnboarding />}
/>
<Route
path="/seller-onboarding-continued"
element={<SellerOnboardingContinued />}
/>
<Route
path="/seller-dashboard"
element={<SellerDashboard />}
/>
<Route
path="/delete-connect"
element={<DeleteConnect />}
/>
<Route path="/success" element={<OrderSuccess />} />
<Route path="/orders" element={<OrdersPage />} />

Expand Down
85 changes: 85 additions & 0 deletions frontend/src/components/pages/DeleteConnect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

import { useAlertQueue } from "@/hooks/useAlertQueue";
import { useAuthentication } from "@/hooks/useAuth";

export default function DeleteConnect() {
const navigate = useNavigate();
const auth = useAuthentication();
const { addErrorAlert, addAlert } = useAlertQueue();

useEffect(() => {
if (auth.isLoading) return;

if (!auth.isAuthenticated) {
navigate("/login");
return;
}

// Only allow access in development
if (process.env.NODE_ENV !== "development") {
navigate("/");
return;
}
Winston-Hsiao marked this conversation as resolved.
Show resolved Hide resolved
}, [auth.isLoading, auth.isAuthenticated]);

const handleDeleteTestAccounts = async () => {
try {
const { data, error } = await auth.client.POST(
"/stripe/connect-account/delete-test-accounts",
{},
);

if (error) {
addErrorAlert(error);
return;
}

addAlert(`Successfully deleted ${data.count} test accounts`, "success");
setTimeout(() => {
navigate("/seller-onboarding");
Winston-Hsiao marked this conversation as resolved.
Show resolved Hide resolved
}, 2000);
} catch (error) {
addErrorAlert(`Failed to delete test accounts: ${error}`);
}
};

if (auth.isLoading) {
return (
<div className="container mx-auto px-4 py-8">
<div className="max-w-2xl mx-auto">
<p>Loading...</p>
</div>
</div>
);
}

return (
<div className="container mx-auto px-4 py-8">
<div className="max-w-2xl mx-auto">
<h1 className="text-3xl font-bold mb-6">
Delete Test Connect Accounts
</h1>
Winston-Hsiao marked this conversation as resolved.
Show resolved Hide resolved

<div className="bg-red-50 p-6 rounded-lg mb-6">
<h2 className="text-red-800 font-semibold mb-2">⚠️ Warning</h2>
<p className="text-red-700 mb-4">
This action will delete all test Stripe Connect accounts associated
with this environment. This operation cannot be undone.
</p>
<p className="text-red-700 mb-4">
This functionality is only available in development mode.
</p>
</div>

<button
onClick={handleDeleteTestAccounts}
className="w-full bg-red-600 text-white px-6 py-3 rounded-lg hover:bg-red-700"
>
Delete All Test Connect Accounts
</button>
</div>
</div>
);
}
68 changes: 56 additions & 12 deletions frontend/src/components/pages/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import UpvotedGrid from "@/components/listings/UpvotedGrid";
import { Card, CardContent, CardHeader } from "@/components/ui/Card";
import { Input, TextArea } from "@/components/ui/Input/Input";
import Spinner from "@/components/ui/Spinner";
import { Tooltip } from "@/components/ui/ToolTip";
import { Button } from "@/components/ui/button";
import { paths } from "@/gen/api";
import { useAlertQueue } from "@/hooks/useAlertQueue";
Expand Down Expand Up @@ -187,15 +188,16 @@ export const RenderProfile = (props: RenderProfileProps) => {
<div className="space-y-8 mb-12">
<Card className="w-full max-w-4xl mx-auto">
<CardHeader className="flex flex-col items-center space-y-4">
<div className="flex flex-col items-center space-y-2 mb-2">
<div className="flex flex-col items-center space-y-2">
<h1 className="text-3xl font-bold text-primary-9">
{user.first_name || user.last_name
? `${user.first_name || ""} ${user.last_name || ""}`
: "Anonymous Creator"}
</h1>
<div className="flex gap-2">
<p className="text-sm text-gray-1 bg-gray-10 px-3 py-1 rounded-md">
@{user.username}
<span className="font-semibold mr-0.5 select-none">@</span>
{user.username}
</p>
{user.permissions && (
<p className="text-sm text-primary-9 bg-primary-3 px-3 py-1 rounded-md">
Expand All @@ -211,13 +213,10 @@ export const RenderProfile = (props: RenderProfileProps) => {
</p>
</div>
{!isEditing && canEdit && (
<div className="flex space-x-2">
<div className="flex flex-wrap gap-2 justify-center">
<Button onClick={() => navigate("/keys")} variant="primary">
API Keys
</Button>
<Button onClick={() => navigate("/orders")} variant="default">
Orders
</Button>
<Button onClick={() => setIsEditing(true)} variant="outline">
Edit Profile
</Button>
Expand Down Expand Up @@ -358,11 +357,11 @@ export const RenderProfile = (props: RenderProfileProps) => {
) : (
<div className="space-y-6">
<div>
<h2 className="text-xl font-semibold mb-2">Bio</h2>
<h2 className="text-lg font-medium mb-2">Bio</h2>
{user.bio ? (
<p>{user.bio}</p>
) : (
<p className="text-gray-11">
<p className="text-gray-11 text-sm">
No bio set. Edit your profile to add a bio.
</p>
)}
Expand All @@ -374,9 +373,54 @@ export const RenderProfile = (props: RenderProfileProps) => {

<Card className="w-full max-w-4xl mx-auto">
<CardHeader>
<h2 className="text-2xl font-bold">Listings</h2>
<h2 className="text-2xl font-bold">Store</h2>
</CardHeader>
<CardContent>
<div className="mb-4">
{user.stripe_connect_account_id &&
!user.stripe_connect_onboarding_completed ? (
<p className="text-gray-11 text-sm">
Your Stripe account setup is not complete. Please resolve
outstanding requirements to begin selling robots. It may take
some time for Stripe to process your info between submissions.
</p>
) : user.stripe_connect_onboarding_completed ? (
<p className="text-gray-11 text-sm">
Stripe account setup complete.
</p>
) : null}
</div>
<div className="flex gap-2">
<Button onClick={() => navigate("/orders")} variant="primary">
Orders
</Button>
{!user.stripe_connect_account_id ? (
<Tooltip content="Start seller onboarding" position="bottom">
<Button
onClick={() => navigate("/seller-onboarding")}
variant="outline"
>
Sell Robots
</Button>
</Tooltip>
) : !user.stripe_connect_onboarding_completed ? (
<Tooltip content="Continue seller onboarding" position="bottom">
<Button
onClick={() => navigate("/seller-onboarding-continued")}
variant="outline"
>
Complete Seller Setup
</Button>
</Tooltip>
) : (
<Button
onClick={() => navigate("/seller-dashboard")}
variant="outline"
>
Seller Dashboard
</Button>
)}
</div>
<div className="flex flex-col items-center space-y-4">
<Tabs
defaultValue="own"
Expand All @@ -386,13 +430,13 @@ export const RenderProfile = (props: RenderProfileProps) => {
<TabsList className="flex justify-center space-x-4 mb-4">
<TabsTrigger
value="own"
className="px-3 py-1.5 rounded-md transition-colors duration-300 hover:bg-gray-11 hover:text-gray-1 data-[state=active]:bg-primary-9 data-[state=active]:text-gray-1"
className="text-sm px-3 py-1.5 rounded-md transition-colors duration-300 hover:bg-gray-9 hover:text-gray-1 data-[state=active]:bg-gray-12 data-[state=active]:text-gray-1"
>
Overview
Your Listings
</TabsTrigger>
<TabsTrigger
value="upvoted"
className="px-3 py-1.5 rounded-md transition-colors duration-300 hover:bg-gray-11 hover:text-gray-1 data-[state=active]:bg-primary-9 data-[state=active]:text-gray-1"
className="text-sm px-3 py-1.5 rounded-md transition-colors duration-300 hover:bg-gray-9 hover:text-gray-1 data-[state=active]:bg-gray-12 data-[state=active]:text-gray-1"
>
Upvoted
</TabsTrigger>
Expand Down
65 changes: 65 additions & 0 deletions frontend/src/components/pages/SellerDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

import { useAuthentication } from "@/hooks/useAuth";

export default function SellerDashboard() {
const navigate = useNavigate();
const auth = useAuthentication();

useEffect(() => {
auth.fetchCurrentUser();
}, []);

useEffect(() => {
if (auth.isLoading) return;

if (!auth.isAuthenticated) {
navigate("/login");
return;
}

// Redirect to onboarding if not completed
if (!auth.currentUser?.stripe_connect_onboarding_completed) {
navigate("/seller-onboarding");
return;
}
}, [auth.isLoading, auth.isAuthenticated, auth.currentUser]);

if (auth.isLoading) {
return (
<div className="container mx-auto px-4 py-8">
<div className="max-w-2xl mx-auto">
<p>Loading...</p>
</div>
</div>
);
}

return (
<div className="container mx-auto px-4 py-8">
<div className="max-w-2xl mx-auto">
<h1 className="text-3xl font-bold mb-6">Seller Dashboard</h1>

<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-xl font-semibold mb-4">Account Status</h2>
<p className="text-green-600 mb-4">
✓ Your K-Scale seller account is active and ready to receive
Winston-Hsiao marked this conversation as resolved.
Show resolved Hide resolved
payments.
</p>

<div className="mt-6">
<a
href={`https://dashboard.stripe.com/${auth.currentUser?.stripe_connect_account_id}`}
target="_blank"
rel="noopener noreferrer"
className="bg-primary-9 text-white px-6 py-3 rounded-lg hover:bg-primary-9/80 inline-block"
>
Open Stripe Dashboard
</a>
</div>
</div>
</div>
</div>
);
}
Loading
Loading