diff --git a/bun.lockb b/bun.lockb
index 4d22c2f..f577256 100644
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 0bb922d..64c7379 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,6 @@
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.53.0",
- "react-social-icons": "^6.18.0",
"react-turnstile": "^1.1.3",
"sharp": "^0.33.5",
"sonner": "^1.5.0",
diff --git a/public/favicon.ico b/public/favicon.ico
index 009fca4..ad3987c 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ
diff --git a/public/media/github.png b/public/media/github.png
deleted file mode 100644
index 50b8175..0000000
Binary files a/public/media/github.png and /dev/null differ
diff --git a/public/media/logo.png b/public/media/logo.png
index a39e7e7..5046004 100644
Binary files a/public/media/logo.png and b/public/media/logo.png differ
diff --git a/public/media/platforms/github.svg b/public/media/platforms/github.svg
new file mode 100644
index 0000000..ac671ba
--- /dev/null
+++ b/public/media/platforms/github.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/public/media/platforms/google.svg b/public/media/platforms/google.svg
new file mode 100644
index 0000000..29b84f1
--- /dev/null
+++ b/public/media/platforms/google.svg
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/src/app/(pages)/auth/page.tsx b/src/app/(pages)/auth/page.tsx
index 19c66fe..738f1ce 100644
--- a/src/app/(pages)/auth/page.tsx
+++ b/src/app/(pages)/auth/page.tsx
@@ -6,7 +6,6 @@ import { Separator } from "@/components/ui/separator";
import AuthForm from "@/components/auth/auth-form";
import { motion } from "framer-motion";
import Greeting from "@/components/auth/greeting";
-import Footer from "@/components/auth/footer";
/**
* The page to authenticate with.
@@ -29,7 +28,6 @@ const AuthPage = (): ReactElement => (
-
);
@@ -40,7 +38,7 @@ const AuthPage = (): ReactElement => (
* @return the providers jsx
*/
const OAuthProviders = (): ReactElement => (
-
+
diff --git a/src/app/(pages)/layout.tsx b/src/app/(pages)/layout.tsx
index a4ccb4b..f550700 100644
--- a/src/app/(pages)/layout.tsx
+++ b/src/app/(pages)/layout.tsx
@@ -14,7 +14,10 @@ const inter: NextFont = Inter({ subsets: ["latin"] });
* The metadata for this app.
*/
export const metadata: Metadata = {
- title: "Pulse App",
+ title: {
+ default: "Pulse App",
+ template: "%s • Pulse App",
+ },
description:
"A lightweight service monitoring solution for tracking the availability of whatever service your heart desires!",
openGraph: {
@@ -31,7 +34,7 @@ export const metadata: Metadata = {
},
};
export const viewport: Viewport = {
- themeColor: "#DC2626",
+ themeColor: "#A855F7",
};
/**
@@ -48,7 +51,7 @@ const RootLayout = ({
diff --git a/src/app/provider/user-provider.tsx b/src/app/provider/user-provider.tsx
index c16df69..d6c75f5 100644
--- a/src/app/provider/user-provider.tsx
+++ b/src/app/provider/user-provider.tsx
@@ -74,7 +74,7 @@ const UserProvider = ({ children }: { children: ReactNode }) => {
) {
router.push("/dashboard/onboarding");
}
- }, [cookies, router]);
+ }, [cookies, router, path]);
useEffect(() => {
fetchUser();
diff --git a/src/app/styles/globals.css b/src/app/styles/globals.css
index fe695a0..bea104f 100644
--- a/src/app/styles/globals.css
+++ b/src/app/styles/globals.css
@@ -50,9 +50,9 @@ body {
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
- --primary: 0 0% 98%;
+ --primary: 271 91% 65%;
--primary-foreground: 240 5.9% 10%;
- --secondary: 240 3.7% 15.9%;
+ --secondary: 272 72% 47%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
diff --git a/src/app/types/user/response/user-auth-response.tsx b/src/app/types/user/response/user-auth-response.tsx
new file mode 100644
index 0000000..2820dcc
--- /dev/null
+++ b/src/app/types/user/response/user-auth-response.tsx
@@ -0,0 +1,17 @@
+import { User } from "@/app/types/user/user";
+import { Session } from "@/app/types/user/session";
+
+/**
+ * The response for successfully logging in.
+ */
+export type UserAuthResponse = {
+ /**
+ * The created session for the user.
+ */
+ session: Session;
+
+ /**
+ * The user logging in.
+ */
+ user: User;
+};
diff --git a/src/components/auth/auth-form.tsx b/src/components/auth/auth-form.tsx
index eeeb8a1..66bb445 100644
--- a/src/components/auth/auth-form.tsx
+++ b/src/components/auth/auth-form.tsx
@@ -14,13 +14,16 @@ import {
LockClosedIcon,
} from "@heroicons/react/24/outline";
import { apiRequest } from "@/lib/api";
-import { Session } from "@/app/types/user/session";
import { Cookies, useCookies } from "next-client-cookies";
import { useRouter } from "next/navigation";
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
import { motion } from "framer-motion";
import Turnstile, { useTurnstile } from "react-turnstile";
import { TurnstileObject } from "turnstile-types";
+import { UserAuthResponse } from "@/app/types/user/response/user-auth-response";
+import { hasFlag } from "@/lib/user";
+import { UserFlag } from "@/app/types/user/user-flag";
+import { User } from "@/app/types/user/user";
/**
* Define the form schemas for the various stages.
@@ -28,24 +31,31 @@ import { TurnstileObject } from "turnstile-types";
const buildEmailInput = (allowEmpty: boolean) =>
z
.string()
- .email("Invalid email address")
+ .email("That email address is invalid.")
.refine(
(val) => {
return !allowEmpty || val.length > 0;
},
- { message: "Email is required" }
+ { message: "An email address is required." }
);
const EmailSchema = z.object({
email: buildEmailInput(false),
});
-const RegisterSchema = z.object({
- email: buildEmailInput(true),
- username: z.string(),
- password: z.string(),
- passwordConfirmation: z.string(),
-});
+const RegisterSchema = z
+ .object({
+ email: buildEmailInput(true),
+ username: z
+ .string()
+ .regex(/^[a-z0-9_.]*$/, "That username is invalid."),
+ password: z.string(),
+ passwordConfirmation: z.string(),
+ })
+ .refine((data) => data.password === data.passwordConfirmation, {
+ path: ["passwordConfirmation"],
+ message: "Your passwords do not match.",
+ });
const LoginSchema = z.object({
email: buildEmailInput(true),
@@ -108,7 +118,7 @@ const AuthForm = (): ReactElement => {
setStage(data?.exists ? "login" : "register");
} else {
const registering: boolean = stage === "register";
- const { data, error } = await apiRequest({
+ const { data, error } = await apiRequest({
endpoint: `/auth/${registering ? "register" : "login"}`,
method: "POST",
body: registering
@@ -132,13 +142,18 @@ const AuthForm = (): ReactElement => {
turnstile.reset();
} else {
// Otherwise store the session and redirect to the dashboard
- cookies.set("session", JSON.stringify(data), {
+ cookies.set("session", JSON.stringify(data?.session), {
expires:
- ((data?.expires as number) - Date.now()) / 86_400_000,
+ ((data?.session.expires as number) - Date.now()) /
+ 86_400_000,
secure: true,
sameSite: "lax",
});
- router.push("/dashboard");
+ router.push(
+ hasFlag(data?.user as User, UserFlag.COMPLETED_ONBOARDING)
+ ? "/dashboard"
+ : "/dashboard/onboarding"
+ );
return;
}
}
@@ -238,7 +253,7 @@ const AuthForm = (): ReactElement => {
{/* Submit Form */}