diff --git a/app/components/notification.tsx b/app/components/notification.tsx
new file mode 100644
index 0000000..b119a52
--- /dev/null
+++ b/app/components/notification.tsx
@@ -0,0 +1,33 @@
+import { Alert, Spinner } from "@heroui/react";
+
+export type NotificationType = {
+ type: "success" | "default" | "accent" | "danger" | "warning";
+ loading: boolean;
+ title: string;
+ message: string;
+};
+
+export function Notification({
+ type,
+ loading,
+ title,
+ message,
+}: NotificationType) {
+ return (
+
+
+ {loading ? (
+
+
+
+ ) : (
+
+ )}
+
+ {title}
+ {message}
+
+
+
+ );
+}
diff --git a/app/hooks/auth.tsx b/app/hooks/auth.tsx
index ef542ec..92b8a6d 100644
--- a/app/hooks/auth.tsx
+++ b/app/hooks/auth.tsx
@@ -1,4 +1,3 @@
-import { Alert, Spinner } from "@heroui/react";
import {
useEffect,
useState,
@@ -7,6 +6,7 @@ import {
type PropsWithChildren,
} from "react";
import { useLocation, useSearchParams } from "react-router";
+import { useNotification } from "./notification";
type User = {
id: string;
@@ -33,56 +33,96 @@ export function AuthProvider({ children }: PropsWithChildren) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [searchParams, setSeachParams] = useSearchParams();
- const [popupShowed, setPopupShowed] = useState(false);
- const [error, setError] = useState("");
+ const { notify } = useNotification();
const location = useLocation();
- useEffect(() => {
- if (user !== null && !loading) {
- const timer = setTimeout(() => {
- setPopupShowed(true);
- }, 2000);
- return () => clearTimeout(timer);
- } else if (error !== "") {
- const timer = setTimeout(() => {
- setPopupShowed(true);
- }, 10000);
- return () => clearTimeout(timer);
- }
- else {
- setPopupShowed(false);
- }
- }, [user, loading, error]);
-
useEffect(() => {
const load = async () => {
- try {
if (searchParams.get("openid.ns") !== null) {
setLoading(true);
+ notify(
+ {
+ type: "accent",
+ loading: true,
+ title: "Logging in...",
+ message:
+ "Please wait while we log you in and sync your data. This may take a few moments.",
+ },
+ Infinity,
+ );
let query = searchParams.toString();
setSeachParams({}, { replace: true });
let response = await fetch(
- "http://localhost:8080/login?" + query,
+ `${import.meta.env.VITE_BASE_API_URL}/login?${query}`,
{ credentials: "include" },
- ).then((r) => r.text());
+ )
+ .then((r) => r.text())
+ .catch((e) => {
+ notify(
+ {
+ type: "danger",
+ loading: false,
+ title: "An error occured.",
+ message: `An error occured: ${e}`,
+ },
+ 10000,
+ );
+ });
if (response === "nok") {
- setError("An error occured during the information exchange between Steam and Porning.")
+ notify(
+ {
+ type: "danger",
+ loading: false,
+ title: "An error occured.",
+ message:
+ "An error occured during the information exchange between Steam and Porning.",
+ },
+ 10000,
+ );
}
}
- let data = await fetch("http://localhost:8080/me", {
+ let data = await fetch(`${import.meta.env.VITE_BASE_API_URL}/me`, {
credentials: "include",
- }).then((r) => r.json());
+ })
+ .then((r) => r.json())
+ .catch((e) => {
+ notify(
+ {
+ type: "danger",
+ loading: false,
+ title: "An error occured.",
+ message: `An error occured: ${e}`,
+ },
+ 10000,
+ );
+ });
if (data === null) {
- setError("An error occured while trying to fetch your user profile. Please contact an administrator.")
+ notify(
+ {
+ type: "danger",
+ loading: false,
+ title: "An error occured.",
+ message:
+ "An error occured while trying to fetch your user profile. Please contact an administrator.",
+ },
+ 10000,
+ );
+ } else if (data !== undefined) {
+ setUser(data);
+ notify(
+ {
+ type: "success",
+ loading: false,
+ title: "Login successful!",
+ message: "",
+ },
+ 2000,
+ );
}
- setUser(data);
setLoading(false);
- } catch (_) {
- setError("An error occured while communicating with the server.")
- }
};
load();
@@ -90,65 +130,32 @@ export function AuthProvider({ children }: PropsWithChildren) {
const login = () => {
window.location.replace(
- "https://steamcommunity.com/openid/login?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=http://localhost:5173&openid.return_to=http://localhost:5173" +
- location.pathname,
+ `https://steamcommunity.com/openid/login?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${import.meta.env.VITE_BASE_URL}&openid.return_to=${import.meta.env.VITE_BASE_URL}${location.pathname}`,
);
};
const logout = async () => {
- await fetch("http://localhost:8080/logout", { credentials: "include" });
- setUser(null);
+ await fetch(import.meta.env.VITE_BASE_API_URL + "/logout", {
+ credentials: "include",
+ })
+ .then(() => {
+ setUser(null);
+ })
+ .catch((e) => {
+ notify(
+ {
+ type: "danger",
+ loading: false,
+ title: "An error occured.",
+ message: `An error occured: ${e}`,
+ },
+ 10000,
+ );
+ });
};
return (
- {loading ? (
-
-
-
-
-
-
- Logging in...
-
- Please wait while we log you in and sync your
- data. This may take a few moments.
-
-
-
-
- ) : (
- (!popupShowed &&
- user !== null && (
-
-
-
-
- Login sucessful!
-
-
-
- )) || (!popupShowed && error !== "" && (
-
-
-
-
- An error occured.
- {error}
-
-
-
- ))
- )}
{children}
);
diff --git a/app/hooks/notification.tsx b/app/hooks/notification.tsx
new file mode 100644
index 0000000..080650d
--- /dev/null
+++ b/app/hooks/notification.tsx
@@ -0,0 +1,62 @@
+import {
+ createContext,
+ type PropsWithChildren,
+ useEffect,
+ useState,
+ useContext,
+} from "react";
+import { type NotificationType, Notification } from "~/components/notification";
+
+type NotificationContextType = {
+ notify: (arg0: NotificationType, arg1: number) => void;
+};
+
+const NotificationContext = createContext({
+ notify: (_arg0: NotificationType, _arg1: number) => {},
+});
+
+export function NotificationProvider({ children }: PropsWithChildren) {
+ const [notification, setNotification] = useState(
+ null,
+ );
+ const [notificationTimeout, setNotificationTimeout] = useState(2000);
+ const [popupShowed, setPopupShowed] = useState(false);
+
+ const notify = (newNotification: NotificationType, timeout: number) => {
+ console.log("Changed notification");
+ setNotification(newNotification);
+ setNotificationTimeout(timeout);
+ };
+
+ useEffect(() => {
+ if (!popupShowed && notification !== null) {
+ console.log("setting timeout")
+ const timeout = setTimeout(() => {
+ console.log("timeout done")
+ setPopupShowed(true);
+ setNotification(null);
+ }, notificationTimeout);
+ return () => clearTimeout(timeout);
+ } else {
+ setPopupShowed(false);
+ }
+ }, [notification, notificationTimeout]);
+
+ return (
+
+ {notification !== null && !popupShowed && (
+
+ )}
+ {children}
+
+ );
+}
+
+export function useNotification() {
+ return useContext(NotificationContext);
+}
diff --git a/app/root.tsx b/app/root.tsx
index 9c96f36..4dc0adb 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -1,78 +1,82 @@
import {
- isRouteErrorResponse,
- Links,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
+ isRouteErrorResponse,
+ Links,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
} from "react-router";
import type { Route } from "./+types/root";
import "./app.css";
import { AuthProvider } from "./hooks/auth";
+import { NotificationProvider } from "./hooks/notification";
export const links: Route.LinksFunction = () => [
- { rel: "preconnect", href: "https://fonts.googleapis.com" },
- {
- rel: "preconnect",
- href: "https://fonts.gstatic.com",
- crossOrigin: "anonymous",
- },
- {
- rel: "stylesheet",
- href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
- },
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
+ {
+ rel: "preconnect",
+ href: "https://fonts.gstatic.com",
+ crossOrigin: "anonymous",
+ },
+ {
+ rel: "stylesheet",
+ href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
+ },
];
export function Layout({ children }: { children: React.ReactNode }) {
- return (
-
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
}
export default function App() {
- return ;
+ return ;
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- let message = "Oops!";
- let details = "An unexpected error occurred.";
- let stack: string | undefined;
+ let message = "Oops!";
+ let details = "An unexpected error occurred.";
+ let stack: string | undefined;
- if (isRouteErrorResponse(error)) {
- message = error.status === 404 ? "404" : "Error";
- details =
- error.status === 404
- ? "The requested page could not be found."
- : error.statusText || details;
- } else if (import.meta.env.DEV && error && error instanceof Error) {
- details = error.message;
- stack = error.stack;
- }
+ if (isRouteErrorResponse(error)) {
+ message = error.status === 404 ? "404" : "Error";
+ details =
+ error.status === 404
+ ? "The requested page could not be found."
+ : error.statusText || details;
+ } else if (import.meta.env.DEV && error && error instanceof Error) {
+ details = error.message;
+ stack = error.stack;
+ }
- return (
-
- {message}
- {details}
- {stack && (
-
- {stack}
-
- )}
-
- );
+ return (
+
+ {message}
+ {details}
+ {stack && (
+
+ {stack}
+
+ )}
+
+ );
}