Skip to content
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
17 changes: 16 additions & 1 deletion frontend/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,23 @@ interface RootRouterContext {
os: OpenSecretContextType;
}

export type RootSearchParams = {
login?: string;
next?: string;
selected_plan?: string;
success?: boolean;
canceled?: boolean;
};

export const Route = createRootRouteWithContext<RootRouterContext>()({
component: Root
component: Root,
validateSearch: (search: Record<string, unknown>): RootSearchParams => ({
login: typeof search.login === "string" ? search.login : undefined,
next: typeof search.next === "string" ? search.next : undefined,
selected_plan: typeof search.selected_plan === "string" ? search.selected_plan : undefined,
success: typeof search.success === "boolean" ? search.success : undefined,
canceled: typeof search.canceled === "boolean" ? search.canceled : undefined
})
});

function Root() {
Expand Down
26 changes: 23 additions & 3 deletions frontend/src/routes/auth.$provider.callback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,25 @@ function OAuthCallback() {
} else {
throw new Error("Unsupported provider");
}
// If successful, redirect to home page after a short delay
setTimeout(() => navigate({ to: "/" }), 2000);

// Check for stored selected_plan
const selectedPlan = sessionStorage.getItem("selected_plan");
// Clear it from storage
sessionStorage.removeItem("selected_plan");

// If successful, redirect after a short delay
setTimeout(() => {
if (selectedPlan) {
// If there was a selected plan, go to pricing
navigate({
to: "/pricing",
search: { selected_plan: selectedPlan }
});
} else {
// Otherwise go home (original behavior)
navigate({ to: "/" });
}
}, 2000);
} catch (error) {
console.error(`${provider} callback error:`, error);
if (error instanceof Error) {
Expand Down Expand Up @@ -108,7 +125,10 @@ function OAuthCallback() {
<CardTitle>{formattedProvider} Authentication Successful</CardTitle>
</CardHeader>
<CardContent>
You have successfully authenticated with {formattedProvider}. Redirecting to home page...
You have successfully authenticated with {formattedProvider}.
{sessionStorage.getItem("selected_plan")
? "Redirecting to complete your plan selection..."
: "Redirecting to home page..."}
</CardContent>
</Card>
);
Expand Down
32 changes: 27 additions & 5 deletions frontend/src/routes/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import { AuthMain } from "@/components/AuthMain";

type LoginSearchParams = {
next?: string;
selected_plan?: string;
};

export const Route = createFileRoute("/login")({
component: LoginPage,
validateSearch: (search: Record<string, unknown>): LoginSearchParams => ({
next: typeof search.next === "string" ? search.next : undefined
next: typeof search.next === "string" ? search.next : undefined,
selected_plan: typeof search.selected_plan === "string" ? search.selected_plan : undefined
})
});

Expand All @@ -25,17 +27,24 @@ type LoginMethod = "email" | "github" | "google" | null;
function LoginPage() {
const navigate = useNavigate();
const os = useOpenSecret();
const { next } = Route.useSearch();
const { next, selected_plan } = Route.useSearch();
const [loginMethod, setLoginMethod] = useState<LoginMethod>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);

// Redirect if already logged in
useEffect(() => {
if (os.auth.user) {
navigate({ to: next || "/" });
if (selected_plan) {
navigate({
to: "/pricing",
search: { selected_plan }
});
} else {
navigate({ to: next || "/" });
}
}
}, [os.auth.user, navigate, next]);
}, [os.auth.user, navigate, next, selected_plan]);

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
Expand All @@ -48,7 +57,14 @@ function LoginPage() {
try {
await os.signIn(email, password);
setTimeout(() => {
navigate({ to: next || "/" });
if (selected_plan) {
navigate({
to: "/pricing",
search: { selected_plan }
});
} else {
navigate({ to: next || "/" });
}
window.scrollTo(0, 0);
}, 100);
} catch (error) {
Expand All @@ -66,6 +82,9 @@ function LoginPage() {
const handleGitHubLogin = async () => {
try {
const { auth_url } = await os.initiateGitHubAuth("");
if (selected_plan) {
sessionStorage.setItem("selected_plan", selected_plan);
}
window.location.href = auth_url;
} catch (error) {
console.error("Failed to initiate GitHub login:", error);
Expand All @@ -76,6 +95,9 @@ function LoginPage() {
const handleGoogleLogin = async () => {
try {
const { auth_url } = await os.initiateGoogleAuth("");
if (selected_plan) {
sessionStorage.setItem("selected_plan", selected_plan);
}
window.location.href = auth_url;
} catch (error) {
console.error("Failed to initiate Google login:", error);
Expand Down
58 changes: 54 additions & 4 deletions frontend/src/routes/pricing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import { useLocalState } from "@/state/useLocalState";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";

type PricingSearchParams = {
selected_plan?: string;
success?: boolean;
canceled?: boolean;
};

function PricingSkeletonCard() {
return (
<div className="flex flex-col border-white/10 bg-black/75 text-white p-4 sm:p-6 md:p-8 border rounded-lg relative overflow-hidden">
Expand Down Expand Up @@ -125,6 +131,7 @@ function PricingPage() {
const os = useOpenSecret();
const { setBillingStatus } = useLocalState();
const isLoggedIn = !!os.auth.user;
const { selected_plan } = Route.useSearch();

// Fetch billing status if user is logged in
const { data: freshBillingStatus, isLoading: isBillingStatusLoading } = useQuery({
Expand Down Expand Up @@ -258,6 +265,18 @@ function PricingPage() {

const handleButtonClick = (product: any) => {
if (!isLoggedIn) {
const targetPlanName = product.name.toLowerCase();
if (!targetPlanName.includes("free")) {
// For paid plans, redirect to signup with the plan selection
navigate({
to: "/signup",
search: {
next: "/pricing",
selected_plan: product.id
}
});
return;
}
navigate({ to: "/signup" });
return;
}
Expand Down Expand Up @@ -323,14 +342,14 @@ function PricingPage() {
await billingService.createZapriteCheckoutSession(
email,
productId,
`${window.location.origin}/pricing?success=true`
`${window.location.origin}/`
);
} else {
await billingService.createCheckoutSession(
email,
productId,
`${window.location.origin}/pricing?success=true`,
`${window.location.origin}/pricing?canceled=true`
`${window.location.origin}/`,
`${window.location.origin}/`
);
}
} catch (err) {
Expand All @@ -341,6 +360,32 @@ function PricingPage() {
}
};

useEffect(() => {
let isSubscribed = true;

// If user is logged in and there's a selected plan, trigger checkout
if (isLoggedIn && selected_plan && !isBillingStatusLoading) {
if (loadingProductId) return; // Prevent multiple triggers
const product = products?.find((p) => p.id === selected_plan);
if (product) {
if (isSubscribed) {
handleButtonClick(product);
}
}
}

return () => {
isSubscribed = false;
};
}, [
isLoggedIn,
selected_plan,
isBillingStatusLoading,
products,
loadingProductId,
handleButtonClick
]);

// Show loading state if we're fetching initial data
if (productsLoading || (isLoggedIn && isBillingStatusLoading)) {
return (
Expand Down Expand Up @@ -582,5 +627,10 @@ function PricingPage() {
}

export const Route = createFileRoute("/pricing")({
component: PricingPage
component: PricingPage,
validateSearch: (search: Record<string, unknown>): PricingSearchParams => ({
selected_plan: typeof search.selected_plan === "string" ? search.selected_plan : undefined,
success: typeof search.success === "boolean" ? search.success : undefined,
canceled: typeof search.canceled === "boolean" ? search.canceled : undefined
})
});
32 changes: 27 additions & 5 deletions frontend/src/routes/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import { AuthMain } from "@/components/AuthMain";

type SignupSearchParams = {
next?: string;
selected_plan?: string;
};

export const Route = createFileRoute("/signup")({
component: SignupPage,
validateSearch: (search: Record<string, unknown>): SignupSearchParams => ({
next: typeof search.next === "string" ? search.next : undefined
next: typeof search.next === "string" ? search.next : undefined,
selected_plan: typeof search.selected_plan === "string" ? search.selected_plan : undefined
})
});

Expand All @@ -25,17 +27,24 @@ type SignUpMethod = "email" | "github" | "google" | null;
function SignupPage() {
const navigate = useNavigate();
const os = useOpenSecret();
const { next } = Route.useSearch();
const { next, selected_plan } = Route.useSearch();
const [signUpMethod, setSignUpMethod] = useState<SignUpMethod>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);

// Redirect if already logged in
useEffect(() => {
if (os.auth.user) {
navigate({ to: next || "/" });
if (selected_plan) {
navigate({
to: "/pricing",
search: { selected_plan }
});
} else {
navigate({ to: next || "/" });
}
}
}, [os.auth.user, navigate, next]);
}, [os.auth.user, navigate, next, selected_plan]);

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
Expand All @@ -48,7 +57,14 @@ function SignupPage() {
try {
await os.signUp(email, password, "", "ANON");
setTimeout(() => {
navigate({ to: next || "/" });
if (selected_plan) {
navigate({
to: "/pricing",
search: { selected_plan }
});
} else {
navigate({ to: next || "/" });
}
window.scrollTo(0, 0);
}, 100);
} catch (error) {
Expand All @@ -66,6 +82,9 @@ function SignupPage() {
const handleGitHubSignup = async () => {
try {
const { auth_url } = await os.initiateGitHubAuth("");
if (selected_plan) {
sessionStorage.setItem("selected_plan", selected_plan);
}
window.location.href = auth_url;
} catch (error) {
console.error("Failed to initiate GitHub signup:", error);
Expand All @@ -76,6 +95,9 @@ function SignupPage() {
const handleGoogleSignup = async () => {
try {
const { auth_url } = await os.initiateGoogleAuth("");
if (selected_plan) {
sessionStorage.setItem("selected_plan", selected_plan);
}
window.location.href = auth_url;
} catch (error) {
console.error("Failed to initiate Google signup:", error);
Expand Down