Openstatus www.openstatus.dev

fix: invite callback redirect (#1719)

authored by

Maximilian Kaske and committed by
GitHub
c5f89042 252bded3

+35 -8
+3 -3
apps/dashboard/src/app/(dashboard)/invite/client.tsx
··· 59 <SectionHeader> 60 <SectionTitle>Invitation</SectionTitle> 61 <SectionDescription> 62 - You&apos;ve been invited to join the workspace 63 {invitation.workspace.name ? ( 64 - <span className="font-semibold">{` ${invitation.workspace.name}`}</span> 65 ) : ( 66 - "" 67 )} 68 . 69 </SectionDescription>
··· 59 <SectionHeader> 60 <SectionTitle>Invitation</SectionTitle> 61 <SectionDescription> 62 + You&apos;ve been invited to join the workspace{" "} 63 {invitation.workspace.name ? ( 64 + <span className="font-semibold">{invitation.workspace.name}</span> 65 ) : ( 66 + <span className="font-mono">{invitation.workspace.slug}</span> 67 )} 68 . 69 </SectionDescription>
+15 -1
apps/dashboard/src/app/(dashboard)/onboarding/client.tsx
··· 31 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 32 import { ArrowUpRight } from "lucide-react"; 33 import Link from "next/link"; 34 import { useQueryStates } from "nuqs"; 35 import { searchParamsParsers } from "./search-params"; 36 37 const moreActions = [ ··· 86 ]; 87 88 export function Client() { 89 - const [{ step }, setSearchParams] = useQueryStates(searchParamsParsers); 90 const trpc = useTRPC(); 91 const queryClient = useQueryClient(); 92 const { data: workspace, refetch } = useQuery( ··· 123 const createFeedbackMutation = useMutation( 124 trpc.feedback.submit.mutationOptions({}), 125 ); 126 127 return ( 128 <SectionGroup>
··· 31 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 32 import { ArrowUpRight } from "lucide-react"; 33 import Link from "next/link"; 34 + import { useRouter } from "next/navigation"; 35 import { useQueryStates } from "nuqs"; 36 + import { useEffect } from "react"; 37 import { searchParamsParsers } from "./search-params"; 38 39 const moreActions = [ ··· 88 ]; 89 90 export function Client() { 91 + const [{ step, callbackUrl }, setSearchParams] = 92 + useQueryStates(searchParamsParsers); 93 + const router = useRouter(); 94 const trpc = useTRPC(); 95 const queryClient = useQueryClient(); 96 const { data: workspace, refetch } = useQuery( ··· 127 const createFeedbackMutation = useMutation( 128 trpc.feedback.submit.mutationOptions({}), 129 ); 130 + 131 + useEffect(() => { 132 + if (callbackUrl) { 133 + const callbackUrlObj = new URL(callbackUrl); 134 + const redirectTo = callbackUrlObj.searchParams.get("redirectTo"); 135 + if (redirectTo?.startsWith("/invite")) { 136 + router.push(redirectTo); 137 + } 138 + } 139 + }, [callbackUrl, router]); 140 141 return ( 142 <SectionGroup>
+6 -1
apps/dashboard/src/app/(dashboard)/onboarding/search-params.ts
··· 1 - import { createSearchParamsCache, parseAsStringLiteral } from "nuqs/server"; 2 3 const STEPS = ["1", "2", "next"] as const; 4 5 export const searchParamsParsers = { 6 step: parseAsStringLiteral(STEPS).withDefault("1"), 7 }; 8 9 export const searchParamsCache = createSearchParamsCache(searchParamsParsers);
··· 1 + import { 2 + createSearchParamsCache, 3 + parseAsString, 4 + parseAsStringLiteral, 5 + } from "nuqs/server"; 6 7 const STEPS = ["1", "2", "next"] as const; 8 9 export const searchParamsParsers = { 10 step: parseAsStringLiteral(STEPS).withDefault("1"), 11 + callbackUrl: parseAsString, 12 }; 13 14 export const searchParamsCache = createSearchParamsCache(searchParamsParsers);
+8
apps/dashboard/src/proxy.ts
··· 32 return NextResponse.redirect(newURL); 33 } 34 35 const hasWorkspaceSlug = req.cookies.has("workspace-slug"); 36 37 if (req.auth?.user?.id && !hasWorkspaceSlug) {
··· 32 return NextResponse.redirect(newURL); 33 } 34 35 + if (req.auth && url.pathname === "/login") { 36 + const redirectTo = url.searchParams.get("redirectTo"); 37 + if (redirectTo) { 38 + const redirectToUrl = new URL(redirectTo, req.url); 39 + return NextResponse.redirect(redirectToUrl); 40 + } 41 + } 42 + 43 const hasWorkspaceSlug = req.cookies.has("workspace-slug"); 44 45 if (req.auth?.user?.id && !hasWorkspaceSlug) {
+2 -2
packages/api/src/router/invitation.ts
··· 62 63 if (process.env.NODE_ENV === "development") { 64 console.log( 65 - `>>>> Invitation token: http://localhost:3000/app/invite?token=${token} <<<< `, 66 ); 67 } 68 ··· 108 }), 109 110 /** 111 - * REMINDER: we are not using a protected procedure here of the `/app/invite` url 112 * instead of `/app/workspace-slug/invite` as the user is not allowed to it yet. 113 * We validate the auth token in the `acceptInvitation` procedure 114 */
··· 62 63 if (process.env.NODE_ENV === "development") { 64 console.log( 65 + `>>>> Invitation token: http://localhost:3000/invite?token=${token} <<<< `, 66 ); 67 } 68 ··· 108 }), 109 110 /** 111 + * REMINDER: we are not using a protected procedure here of the `/invite` url 112 * instead of `/app/workspace-slug/invite` as the user is not allowed to it yet. 113 * We validate the auth token in the `acceptInvitation` procedure 114 */
+1 -1
packages/emails/emails/team-invitation.tsx
··· 13 import { Layout } from "./_components/layout"; 14 import { styles } from "./_components/styles"; 15 16 - const BASE_URL = "https://openstatus.dev/app/invite"; 17 18 export const TeamInvitationSchema = z.object({ 19 invitedBy: z.string(),
··· 13 import { Layout } from "./_components/layout"; 14 import { styles } from "./_components/styles"; 15 16 + const BASE_URL = "https://app.openstatus.dev/invite"; 17 18 export const TeamInvitationSchema = z.object({ 19 invitedBy: z.string(),