Improve flow of joining a tablo

This commit is contained in:
Arthur Belleville 2025-07-05 21:32:54 +02:00
parent 4f207099a3
commit aafec28885
No known key found for this signature in database
9 changed files with 99 additions and 27 deletions

View file

@ -45,14 +45,7 @@ export const App = () => {
</Layout>
}
/>
<Route
path="join/:tablo_name"
element={
<Layout>
<JoinPage />
</Layout>
}
/>
<Route
path="devis"
element={
@ -104,6 +97,24 @@ export const App = () => {
}
/>
</Route>
<Route
element={
<ProtectedRoute
fallback="/login"
shouldRedirectToCurrentPage
/>
}
>
<Route
path="join/:tablo_name"
element={
<Layout>
<JoinPage />
</Layout>
}
/>
</Route>
<Route path="login-with-oauth" element={<OAuthSigninPage />} />
<Route path="landing" element={<LandingPage />} />
<Route element={<PublicRoute />}>

View file

@ -14,7 +14,7 @@ describe("LoginWithGoogle", () => {
loginWithGoogle: mockLoginWithGoogle,
});
render(<LoginWithGoogle />);
render(<LoginWithGoogle redirectUrl={null} />);
const button = screen.getByRole("button", {
name: /Continuer avec Google/i,
@ -28,7 +28,7 @@ describe("LoginWithGoogle", () => {
loginWithGoogle: mockLoginWithGoogle,
});
render(<LoginWithGoogle />);
render(<LoginWithGoogle redirectUrl={null} />);
const button = screen.getByRole("button", {
name: /Continuer avec Google/i,

View file

@ -1,8 +1,12 @@
import "./login-with-google.css";
import { useLoginGoogle } from "../../hooks/auth";
export function LoginWithGoogle() {
const { loginWithGoogle } = useLoginGoogle();
export function LoginWithGoogle({
redirectUrl,
}: {
redirectUrl: string | null;
}) {
const { loginWithGoogle } = useLoginGoogle({ redirectUrl });
return (
<button className="gsi-material-button" onClick={() => loginWithGoogle()}>

View file

@ -8,7 +8,15 @@ interface ProtectedRouteProps {
fallback?: string;
}
export const ProtectedRoute = ({ fallback }: ProtectedRouteProps) => {
interface ProtectedRouteProps {
fallback?: string;
shouldRedirectToCurrentPage?: boolean;
}
export const ProtectedRoute = ({
fallback,
shouldRedirectToCurrentPage,
}: ProtectedRouteProps) => {
const { session } = useSession();
const [isLoading, setIsLoading] = useState(true);
@ -38,14 +46,18 @@ export const ProtectedRoute = ({ fallback }: ProtectedRouteProps) => {
status = "should-pass";
}
const redirectUrl = shouldRedirectToCurrentPage
? `${fallback ?? "/login"}?redirect=${encodeURIComponent(
window.location.pathname
)}`
: fallback ?? "/login";
return (
<>
{match(status)
.with("loading", () => <LoadingSpinner />)
.with("should-land-user", () => <Navigate to="/landing" replace />)
.with("should-redirect", () => (
<Navigate to={fallback ?? "/login"} replace />
))
.with("should-redirect", () => <Navigate to={redirectUrl} replace />)
.with("should-pass", () => <Outlet />)
.exhaustive()}
</>

View file

@ -122,7 +122,7 @@ export function useSignUpToStream() {
});
return { signUpToStream };
}
export function useLoginEmail() {
export function useLoginEmail({ redirectUrl }: { redirectUrl: string | null }) {
const navigate = useNavigate();
const [errors, setErrors] = useState<Record<string, string>>({});
const { signUpToStream } = useSignUpToStream();
@ -143,7 +143,11 @@ export function useLoginEmail() {
return response;
},
onSuccess: () => {
navigate("/");
if (redirectUrl) {
navigate(decodeURIComponent(redirectUrl));
} else {
navigate("/");
}
},
onError: (error) => {
match(error.code)
@ -168,13 +172,28 @@ export function useLoginEmail() {
return { mutate, isPending, errors };
}
export function useLoginGoogle() {
export function useLoginGoogle({
redirectUrl,
}: {
redirectUrl: string | null;
}) {
console.log({
redirectTo: redirectUrl
? `${
window.location.origin
}/login-with-oauth?redirect=${encodeURIComponent(redirectUrl)}`
: `${window.location.origin}/login-with-oauth`,
});
const { mutate } = useMutation({
mutationFn: async () => {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${window.location.origin}/login-with-oauth`,
redirectTo: redirectUrl
? `${
window.location.origin
}/login-with-oauth?redirect=${encodeURIComponent(redirectUrl)}`
: `${window.location.origin}/login-with-oauth`,
},
});
if (error) throw error;

View file

@ -1,6 +1,7 @@
import { useParams, useNavigate, useSearchParams } from "react-router-dom";
import { useUser } from "@ui/providers/UserStoreProvider";
import { useJoinTablo } from "@ui/hooks/invite";
import { toast } from "@ui/ui-library/toast/toast-queue";
export const JoinPage = () => {
const { tablo_name } = useParams<{ tablo_name: string }>();
@ -12,6 +13,20 @@ export const JoinPage = () => {
const [searchParams] = useSearchParams();
const token = searchParams.get("token");
if (!tablo_name || !token) {
toast.add(
{
title: "Invitation invalide",
description: "Veuillez vérifier le lien d'invitation",
type: "error",
},
{
timeout: 2000,
}
);
navigate("/");
}
// const handleJoinTablo = async () => {
// if (!user || !tablo_name || !token) return;

View file

@ -5,10 +5,13 @@ import { Label, Input, TextField, FieldError } from "@ui/ui-library/field";
import { useLoginEmail } from "@ui/hooks/auth";
import { Form } from "@ui/ui-library/form";
import { LoginWithGoogle } from "@ui/components/BrandButtons/LoginWithGoogle";
import { Link } from "react-router-dom";
import { Link, useSearchParams } from "react-router-dom";
export function LoginPage() {
const { mutate: login, isPending, errors } = useLoginEmail();
const [searchParams] = useSearchParams();
const redirectUrl = searchParams.get("redirect");
const { mutate: login, isPending, errors } = useLoginEmail({ redirectUrl });
const [formData, setFormData] = useState({
email: "",
password: "",
@ -113,7 +116,7 @@ export function LoginPage() {
</div>
</div>
<LoginWithGoogle />
<LoginWithGoogle redirectUrl={redirectUrl} />
<p className="text-center text-sm text-slate-600 dark:text-slate-400">
Pas encore de compte ?{" "}

View file

@ -1,5 +1,5 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate, useSearchParams } from "react-router-dom";
import { useSession } from "@ui/contexts/SessionContext";
import { useSignUpToStream } from "@ui/hooks/auth";
@ -7,11 +7,17 @@ export const OAuthSigninPage = () => {
const navigate = useNavigate();
const { session } = useSession();
const { signUpToStream } = useSignUpToStream();
const [searchParams] = useSearchParams();
const redirectUrl = searchParams.get("redirect");
useEffect(() => {
const interval = setInterval(() => {
if (session) {
signUpToStream(session.access_token);
navigate("/");
if (redirectUrl) {
navigate(decodeURIComponent(redirectUrl));
} else {
navigate("/");
}
}
}, 100);
return () => clearInterval(interval);

View file

@ -1,6 +1,6 @@
import { Button } from "@ui/ui-library/button";
import { twMerge } from "tailwind-merge";
import { Link, useNavigate } from "react-router-dom";
import { Link, useNavigate, useSearchParams } from "react-router-dom";
import { useState } from "react";
import { Label, Input, TextField, FieldError } from "@ui/ui-library/field";
import { useSignUp } from "@ui/hooks/auth";
@ -10,6 +10,8 @@ import { LoginWithGoogle } from "@ui/components/BrandButtons/LoginWithGoogle";
export function SignUpPage() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const redirectUrl = searchParams.get("redirect");
const { mutate: signUp, isPending } = useSignUp();
const [errors, setErrors] = useState<Record<string, string>>({});
@ -246,7 +248,7 @@ export function SignUpPage() {
</div>
</div>
<LoginWithGoogle />
<LoginWithGoogle redirectUrl={redirectUrl} />
<p className="text-center text-sm text-slate-600 dark:text-slate-400">
Déjà un compte ?{" "}