Refactor authentication flow and improve code organization
The update implemented a redirect functionality in the multi-factor authentication flow for a better user experience. It also involved a refactoring of some parts of the code, substituting direct routing paths with path configs for easier future modifications. Import statements were adjusted for better code organization and readability.
This commit is contained in:
@@ -206,7 +206,9 @@ function generateDemoData() {
|
||||
});
|
||||
}
|
||||
|
||||
return [data, data[data.length - 1].value] as [typeof data, string];
|
||||
const lastValue = data[data.length - 1]?.value;
|
||||
|
||||
return [data, lastValue] as [typeof data, string];
|
||||
}
|
||||
|
||||
function Chart(
|
||||
|
||||
@@ -26,7 +26,6 @@ export function TeamAccountCheckoutForm(params: { accountId: string }) {
|
||||
if (checkoutToken) {
|
||||
return (
|
||||
<EmbeddedCheckout
|
||||
load
|
||||
checkoutToken={checkoutToken}
|
||||
provider={billingConfig.provider}
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,7 @@ import Post from '~/(marketing)/blog/_components/post';
|
||||
import appConfig from '~/config/app.config';
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
|
||||
export function generateMetadata({
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string };
|
||||
@@ -17,7 +17,7 @@ export function generateMetadata({
|
||||
const post = allPosts.find((post) => post.slug === params.slug);
|
||||
|
||||
if (!post) {
|
||||
return;
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { title, date, description, image, slug } = post;
|
||||
|
||||
@@ -5,8 +5,8 @@ import type { DocumentationPage } from 'contentlayer/generated';
|
||||
export interface ProcessedDocumentationPage extends DocumentationPage {
|
||||
collapsible: boolean;
|
||||
pathSegments: string[];
|
||||
nextPage: ProcessedDocumentationPage | DocumentationPage | null;
|
||||
previousPage: ProcessedDocumentationPage | DocumentationPage | null;
|
||||
nextPage: ProcessedDocumentationPage | DocumentationPage | undefined;
|
||||
previousPage: ProcessedDocumentationPage | DocumentationPage | undefined;
|
||||
children: DocsTree;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PricingTable } from '@kit/billing/components/pricing-table';
|
||||
import { PricingTable } from '@kit/billing-gateway/components';
|
||||
|
||||
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
||||
import billingConfig from '~/config/billing.config';
|
||||
|
||||
@@ -6,30 +6,37 @@ import { getSupabaseRouteHandlerClient } from '@kit/supabase/route-handler-clien
|
||||
|
||||
import pathsConfig from '~/config/paths.config';
|
||||
|
||||
const defaultNextUrl = pathsConfig.app.home;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const requestUrl = new URL(request.url);
|
||||
const searchParams = requestUrl.searchParams;
|
||||
|
||||
const authCode = searchParams.get('code');
|
||||
const inviteCode = searchParams.get('inviteCode');
|
||||
const error = searchParams.get('error');
|
||||
const nextUrl = searchParams.get('next') ?? pathsConfig.app.home;
|
||||
const nextUrlPathFromParams = searchParams.get('next');
|
||||
const inviteToken = searchParams.get('invite_token');
|
||||
|
||||
let userId: string | undefined = undefined;
|
||||
let nextUrl = nextUrlPathFromParams ?? defaultNextUrl;
|
||||
|
||||
// if we have an invite token, we redirect to the join team page
|
||||
// instead of the default next url. This is because the user is trying
|
||||
// to join a team and we want to make sure they are redirected to the
|
||||
// correct page.
|
||||
if (inviteToken) {
|
||||
nextUrl = `${pathsConfig.app.joinTeam}?invite_token=${inviteToken}`;
|
||||
}
|
||||
|
||||
if (authCode) {
|
||||
const client = getSupabaseRouteHandlerClient();
|
||||
|
||||
try {
|
||||
const { error, data } =
|
||||
await client.auth.exchangeCodeForSession(authCode);
|
||||
const { error } = await client.auth.exchangeCodeForSession(authCode);
|
||||
|
||||
// if we have an error, we redirect to the error page
|
||||
if (error) {
|
||||
return onError({ error: error.message });
|
||||
}
|
||||
|
||||
userId = data.user.id;
|
||||
} catch (error) {
|
||||
Logger.error(
|
||||
{
|
||||
@@ -42,34 +49,6 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return onError({ error: message as string });
|
||||
}
|
||||
|
||||
if (inviteCode && userId) {
|
||||
try {
|
||||
Logger.info(
|
||||
{
|
||||
userId,
|
||||
inviteCode,
|
||||
},
|
||||
`Attempting to accept user invite...`,
|
||||
);
|
||||
|
||||
// if we have an invite code, we accept the invite
|
||||
await acceptInviteFromEmailLink({ inviteCode, userId });
|
||||
} catch (error) {
|
||||
Logger.error(
|
||||
{
|
||||
userId,
|
||||
inviteCode,
|
||||
error,
|
||||
},
|
||||
`An error occurred while accepting user invite`,
|
||||
);
|
||||
|
||||
const message = error instanceof Error ? error.message : error;
|
||||
|
||||
return onError({ error: message as string });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
@@ -79,37 +58,6 @@ export async function GET(request: NextRequest) {
|
||||
return redirect(nextUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name acceptInviteFromEmailLink
|
||||
* @description If we find an invite code, we try to accept the invite
|
||||
* received from the email link method
|
||||
* @param params
|
||||
*/
|
||||
async function acceptInviteFromEmailLink(params: {
|
||||
inviteCode: string;
|
||||
userId: string | undefined;
|
||||
}) {
|
||||
if (!params.userId) {
|
||||
Logger.error(params, `Attempted to accept invite, but no user id provided`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.info(params, `Found invite code. Accepting invite...`);
|
||||
|
||||
await acceptInviteToOrganization(
|
||||
getSupabaseRouteHandlerClient({
|
||||
admin: true,
|
||||
}),
|
||||
{
|
||||
code: params.inviteCode,
|
||||
userId: params.userId,
|
||||
},
|
||||
);
|
||||
|
||||
Logger.info(params, `Invite successfully accepted`);
|
||||
}
|
||||
|
||||
function onError({ error }: { error: string }) {
|
||||
const errorMessage = getAuthErrorMessage(error);
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
|
||||
import { Heading } from '@kit/ui/heading';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
|
||||
@@ -25,36 +22,7 @@ async function JoinTeamAccountPage({ searchParams }: Context) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading level={4}>
|
||||
<Trans
|
||||
i18nKey={'auth:joinOrganizationHeading'}
|
||||
values={{
|
||||
organization: organization.name,
|
||||
}}
|
||||
/>
|
||||
</Heading>
|
||||
|
||||
<div>
|
||||
<p className={'text-center'}>
|
||||
<Trans
|
||||
i18nKey={'auth:joinOrganizationSubHeading'}
|
||||
values={{
|
||||
organization: organization.name,
|
||||
}}
|
||||
components={{ b: <b /> }}
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p className={'text-center'}>
|
||||
<If condition={!data.session}>
|
||||
<Trans i18nKey={'auth:signUpToAcceptInvite'} />
|
||||
</If>
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default withI18n(JoinTeamAccountPage);
|
||||
@@ -66,7 +34,7 @@ async function getInviteDataFromInviteToken(token: string) {
|
||||
|
||||
const { data: invitation, error } = await adminClient
|
||||
.from('invitations')
|
||||
.select('*')
|
||||
.select()
|
||||
.eq('invite_token', token)
|
||||
.single();
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ const authConfig = AuthConfigSchema.parse({
|
||||
magicLink: false,
|
||||
oAuth: ['google'],
|
||||
},
|
||||
});
|
||||
} satisfies z.infer<typeof AuthConfigSchema>);
|
||||
|
||||
export default authConfig;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ const featuresFlagConfig = FeatureFlagsSchema.parse({
|
||||
process.env.NEXT_PUBLIC_ENABLE_ORGANIZATION_BILLING,
|
||||
false,
|
||||
),
|
||||
});
|
||||
} satisfies z.infer<typeof FeatureFlagsSchema>);
|
||||
|
||||
export default featuresFlagConfig;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ const PathsSchema = z.object({
|
||||
accountBilling: z.string().min(1),
|
||||
accountMembers: z.string().min(1),
|
||||
accountBillingReturn: z.string().min(1),
|
||||
joinTeam: z.string().min(1),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -41,7 +42,8 @@ const pathsConfig = PathsSchema.parse({
|
||||
accountBilling: `/home/[account]/billing`,
|
||||
accountMembers: `/home/[account]/members`,
|
||||
accountBillingReturn: `/home/[account]/billing/return`,
|
||||
joinTeam: '/join',
|
||||
},
|
||||
});
|
||||
} satisfies z.infer<typeof PathsSchema>);
|
||||
|
||||
export default pathsConfig;
|
||||
|
||||
@@ -57,11 +57,13 @@ export default withBundleAnalyzer({
|
||||
})(config);
|
||||
|
||||
function getRemotePatterns() {
|
||||
// add here the remote patterns for your images
|
||||
/** @type {import('next').NextConfig['remotePatterns']} */
|
||||
// add here the remote patterns for your images
|
||||
const remotePatterns = [];
|
||||
|
||||
if (SUPABASE_URL) {
|
||||
const hostname = new URL(SUPABASE_URL).hostname;
|
||||
|
||||
remotePatterns.push({
|
||||
protocol: 'https',
|
||||
hostname,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"subscriptionTabSubheading": "Manage your Subscription and Billing",
|
||||
"planCardTitle": "Your Plan",
|
||||
"planCardDescription": "Below are the details of your current plan. You can change your plan or cancel your subscription at any time.",
|
||||
"planRenewal": "Renews every {{interval}} at {{currency}} {{price}}",
|
||||
"planRenewal": "Renews every {{interval}} at {{price}}",
|
||||
"planDetails": "Plan Details",
|
||||
"checkout": "Proceed to Checkout",
|
||||
"trialEndsOn": "Your trial ends on",
|
||||
|
||||
Reference in New Issue
Block a user