feat: create provider callback handler

This commit is contained in:
2026-01-09 08:23:14 +07:00
parent 34b4ec6232
commit b2c21c5f01
6 changed files with 150 additions and 16 deletions

View File

@ -1,20 +1,7 @@
import React from "react"; import AuthCallbackIndex from "@/features/authCallback";
const page = async ({ const page = async () => {
params, return <AuthCallbackIndex />;
searchParams,
}: {
params: { name: string };
searchParams: { [key: string]: string | string[] | undefined };
}) => {
const resolvedParams = await params;
const resolvedSearchParams = await searchParams;
return (
<div>
<h1>Loading....</h1>
</div>
);
}; };
export default page; export default page;

View File

@ -16,6 +16,7 @@
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"shadcn": "^3.6.3", "shadcn": "^3.6.3",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
}, },
@ -1276,6 +1277,8 @@
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],

View File

@ -0,0 +1,40 @@
"use client";
import { Spinner } from "@/shared/libs/shadcn/ui/spinner";
import { submitProviderCallback } from "@/shared/widgets/signin/actions/submitProviderCallback";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
const AuthCallbackIndex = () => {
const { name } = useParams();
const queries = useSearchParams().toString();
const router = useRouter();
const [textDescription, setTextDescription] = useState(
"We are processing your authentication."
);
useEffect(() => {
(async () => {
const response = await submitProviderCallback(name as string, queries);
if (response.success) {
setTextDescription("Authentication successful! Redirecting...");
router.push("/");
} else {
setTextDescription("Authentication failed. Please try again.");
}
})();
}, []);
return (
<div className="w-full flex flex-col items-center gap-2 pt-8">
<Spinner className="size-6" />
<div className="text-center">
<h1 className="text-neutral-200 text-base">Please wait...</h1>
<p className="font-normal text-neutral-400 text-sm">
{textDescription}
</p>
</div>
</div>
);
};
export default AuthCallbackIndex;

View File

@ -20,6 +20,7 @@
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"shadcn": "^3.6.3", "shadcn": "^3.6.3",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"tw-animate-css": "^1.4.0" "tw-animate-css": "^1.4.0"
}, },

View File

@ -0,0 +1,49 @@
"use client"
import { useTheme } from "next-themes"
import { Toaster as Sonner, type ToasterProps } from "sonner"
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
icons={{
success: (
<CircleCheckIcon className="size-4" />
),
info: (
<InfoIcon className="size-4" />
),
warning: (
<TriangleAlertIcon className="size-4" />
),
error: (
<OctagonXIcon className="size-4" />
),
loading: (
<Loader2Icon className="size-4 animate-spin" />
),
}}
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
"--border-radius": "var(--radius)",
} as React.CSSProperties
}
toastOptions={{
classNames: {
toast: "cn-toast",
},
}}
{...props}
/>
)
}
export { Toaster }

View File

@ -0,0 +1,54 @@
"use server";
import { backendFetch, BackendResponse } from "@/shared/helper/backendFetch";
import { cookies } from "next/headers";
export const submitProviderCallback = async (
providerName: string,
queries?: unknown
): Promise<
BackendResponse<{
authToken: string;
}>
> => {
try {
const envKey = providerName.toUpperCase() + "_CALLBACK_URL";
const authClientCallbackUrl = (await backendFetch(
"auth/providers/" + providerName + "/callback"
)) as BackendResponse<{
callback_url: string;
}>;
if (!authClientCallbackUrl.success)
throw new Error("Failed to get auth client callback URL");
const responseProvision = (await backendFetch(
`${authClientCallbackUrl.data?.callback_url!}?callbackURI=${
process.env.APP_URL
}${process.env[envKey]}&${queries}`
)) as BackendResponse<{
authToken: string;
}>;
if (!responseProvision.success)
throw new Error("Failed to submit provider callback");
(await cookies()).set({
name: "auth_token",
value: responseProvision.data?.authToken!,
httpOnly: true,
path: "/",
secure: process.env.NODE_ENV === "production",
maxAge: Number(process.env.SESSION_EXPIRE),
});
return responseProvision;
} catch (error) {
return {
success: false,
message: "Error submitting provider callback",
error: error,
};
}
};