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:
giancarlo
2024-03-27 15:07:15 +08:00
parent f0883c19ef
commit 7579ee9a2c
33 changed files with 103 additions and 151 deletions

View File

@@ -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(

View File

@@ -26,7 +26,6 @@ export function TeamAccountCheckoutForm(params: { accountId: string }) {
if (checkoutToken) {
return (
<EmbeddedCheckout
load
checkoutToken={checkoutToken}
provider={billingConfig.provider}
/>

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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';

View File

@@ -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);

View File

@@ -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();

View File

@@ -24,7 +24,7 @@ const authConfig = AuthConfigSchema.parse({
magicLink: false,
oAuth: ['google'],
},
});
} satisfies z.infer<typeof AuthConfigSchema>);
export default authConfig;

View File

@@ -36,7 +36,7 @@ const featuresFlagConfig = FeatureFlagsSchema.parse({
process.env.NEXT_PUBLIC_ENABLE_ORGANIZATION_BILLING,
false,
),
});
} satisfies z.infer<typeof FeatureFlagsSchema>);
export default featuresFlagConfig;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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",