diff --git a/src/App.tsx b/src/App.tsx
index ce2aa83..ccd76fa 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -33,6 +33,8 @@ import Register from "./pages/register";
import ForgotPassword from "./pages/forgot-password";
import ResetPassword from "./pages/reset-password";
import AcceptInvite from "./pages/accept-invite";
+import AuthErrorPage from "./pages/auth/error";
+import NotFoundPage from "./pages/not-found";
import InviteCreate from "./pages/invites/create";
import InvitesList from "./pages/invites/list";
import JoinClass from "./pages/join-class";
@@ -144,6 +146,8 @@ function App() {
} />
+ } />
+ } />
diff --git a/src/pages/auth/error.tsx b/src/pages/auth/error.tsx
new file mode 100644
index 0000000..2c436a1
--- /dev/null
+++ b/src/pages/auth/error.tsx
@@ -0,0 +1,124 @@
+import { useMemo } from "react";
+import { useSearchParams } from "react-router";
+import { useLink } from "@refinedev/core";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { Separator } from "@/components/ui/separator";
+
+const ERROR_MESSAGES: Record = {
+ account_not_linked: {
+ title: "Account not linked",
+ description:
+ "This Google or GitHub account uses an email that already has a password login. Sign in with email/password first, or use the same method you used to register.",
+ },
+ unable_to_link_account: {
+ title: "Could not link account",
+ description:
+ "We couldn't connect that social account to your user. Try again, or sign in with email and password.",
+ },
+ oauth_provider_not_found: {
+ title: "Sign-in provider unavailable",
+ description:
+ "That login provider isn't configured. Try another method or contact support.",
+ },
+ invalid_code: {
+ title: "Sign-in expired",
+ description:
+ "The login link expired or was already used. Please try signing in again.",
+ },
+ state_mismatch: {
+ title: "Sign-in interrupted",
+ description:
+ "Something went wrong during the login redirect. Close extra tabs and try again.",
+ },
+ access_denied: {
+ title: "Access denied",
+ description:
+ "You cancelled the sign-in request or didn't grant permission. Try again if that was a mistake.",
+ },
+ email_not_verified: {
+ title: "Email not verified",
+ description:
+ "Your social account email must be verified before you can sign in this way.",
+ },
+};
+
+function resolveError(code: string | null, rawMessage: string | null) {
+ if (code && Object.prototype.hasOwnProperty.call(ERROR_MESSAGES, code)) {
+ return { code, ...ERROR_MESSAGES[code] };
+ }
+
+ if (rawMessage) {
+ return {
+ code: code ?? "unknown",
+ title: "Sign-in failed",
+ description: rawMessage,
+ };
+ }
+
+ return {
+ code: code ?? "unknown",
+ title: "Something went wrong",
+ description:
+ "We couldn't complete sign-in. Please try again or return to the login page.",
+ };
+}
+
+export default function AuthErrorPage() {
+ const [params] = useSearchParams();
+ const Link = useLink();
+
+ const error = useMemo(() => {
+ const code =
+ params.get("error") ??
+ params.get("errorCode") ??
+ params.get("code");
+ const message =
+ params.get("error_description") ??
+ params.get("message") ??
+ params.get("errorDescription");
+ return resolveError(code, message);
+ }, [params]);
+
+ return (
+
+
+
+
+ Error
+
+ {error.title}
+
+ {error.description}
+
+
+
+
+
+
+ {error.code !== "unknown" && (
+
+ CODE: {error.code}
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/not-found.tsx b/src/pages/not-found.tsx
new file mode 100644
index 0000000..763e1df
--- /dev/null
+++ b/src/pages/not-found.tsx
@@ -0,0 +1,39 @@
+import { useLink } from "@refinedev/core";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+
+export default function NotFoundPage() {
+ const Link = useLink();
+
+ return (
+
+
+
+
+ 404
+
+ Page not found
+
+ That URL doesn't exist in this app. Check the link or go back home.
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/providers/auth.ts b/src/providers/auth.ts
index 68b52db..886556b 100644
--- a/src/providers/auth.ts
+++ b/src/providers/auth.ts
@@ -2,12 +2,17 @@ import type { AuthProvider } from "@refinedev/core";
import { authClient } from "@/lib/auth-client";
import type { Identity } from "@/types";
+/** Absolute frontend URL for OAuth / email redirects (not the API host). */
+const frontendOrigin =
+ typeof window !== "undefined" ? window.location.origin : "";
+
export const authProvider: AuthProvider = {
login: async ({ email, password, providerName }) => {
if (providerName) {
const { error } = await authClient.signIn.social({
provider: providerName,
- callbackURL: "/",
+ callbackURL: `${frontendOrigin}/`,
+ errorCallbackURL: `${frontendOrigin}/auth/error`,
});
if (error) {
@@ -36,7 +41,8 @@ export const authProvider: AuthProvider = {
if (providerName) {
const { error } = await authClient.signIn.social({
provider: providerName,
- callbackURL: "/",
+ callbackURL: `${frontendOrigin}/`,
+ errorCallbackURL: `${frontendOrigin}/auth/error`,
});
if (error) {
@@ -107,7 +113,7 @@ export const authProvider: AuthProvider = {
forgotPassword: async ({ email }) => {
const { error } = await authClient.requestPasswordReset({
email,
- redirectTo: "/reset-password",
+ redirectTo: `${frontendOrigin}/reset-password`,
});
if (error) {