diff --git a/app/(app)/alpha/additional-details/_actions.ts b/app/(app)/onboarding/_actions.ts
similarity index 89%
rename from app/(app)/alpha/additional-details/_actions.ts
rename to app/(app)/onboarding/_actions.ts
index ed4eaad0..dc28a02e 100644
--- a/app/(app)/alpha/additional-details/_actions.ts
+++ b/app/(app)/onboarding/_actions.ts
@@ -80,8 +80,16 @@ export async function slideThreeSubmitAction(dataInput: TypeSlideThreeSchema) {
}
try {
- const { professionalOrStudent, course, jobTitle, levelOfStudy, workplace } =
- slideThreeSchema.parse(dataInput);
+ const {
+ professionalOrStudent,
+ course,
+ jobTitle,
+ levelOfStudy,
+ workplace,
+ yearsOfExperience,
+ } = slideThreeSchema.parse(dataInput);
+
+ const onboardingComplete = new Date().toISOString();
await db
.update(user)
@@ -91,6 +99,8 @@ export async function slideThreeSubmitAction(dataInput: TypeSlideThreeSchema) {
jobTitle,
levelOfStudy,
workplace,
+ yearsOfExperience,
+ onboardingComplete,
})
.where(eq(user.id, session.user.id));
diff --git a/app/(app)/alpha/additional-details/_client.tsx b/app/(app)/onboarding/_client.tsx
similarity index 92%
rename from app/(app)/alpha/additional-details/_client.tsx
rename to app/(app)/onboarding/_client.tsx
index 5e67dac8..f7b3bc60 100644
--- a/app/(app)/alpha/additional-details/_client.tsx
+++ b/app/(app)/onboarding/_client.tsx
@@ -21,7 +21,7 @@ import {
locationOptions,
levelOfStudyOptions,
monthsOptions,
-} from "@/app/(app)/alpha/additional-details/selectOptions";
+} from "@/app/(app)/onboarding/selectOptions";
import {
slideOneSubmitAction,
slideThreeSubmitAction,
@@ -39,6 +39,7 @@ import { Select } from "@/components/ui-components/select";
import { Button } from "@/components/ui-components/button";
import { Heading, Subheading } from "@/components/ui-components/heading";
import { Divider } from "@/components/ui-components/divider";
+import { experienceRangeEnum } from "@/server/db/schema";
type UserDetails = {
username: string;
@@ -51,6 +52,7 @@ type UserDetails = {
levelOfStudy: string;
jobTitle: string;
workplace: string;
+ yearsOfExperience: "0-1" | "1-3" | "3-5" | "5-8" | "12+" | undefined;
};
export default function AdditionalSignUpDetails({
@@ -357,7 +359,7 @@ function SlideTwo({ details }: { details: UserDetails }) {
id="day"
aria-label="day"
value={day ? day : ""}
- disabled={!month || undefined}
+ disabled={month === undefined || year === undefined}
required
onChange={(e) => setDay(Number(e.target.value))}
>
@@ -399,8 +401,14 @@ function SlideTwo({ details }: { details: UserDetails }) {
function SlideThree({ details }: { details: UserDetails }) {
const router = useRouter();
- const { professionalOrStudent, workplace, jobTitle, course, levelOfStudy } =
- details;
+ const {
+ professionalOrStudent,
+ workplace,
+ jobTitle,
+ course,
+ levelOfStudy,
+ yearsOfExperience,
+ } = details;
const {
register,
@@ -417,6 +425,13 @@ function SlideThree({ details }: { details: UserDetails }) {
jobTitle,
course,
levelOfStudy,
+ yearsOfExperience: yearsOfExperience as
+ | "0-1"
+ | "1-3"
+ | "3-5"
+ | "5-8"
+ | "12+"
+ | undefined,
},
});
@@ -427,7 +442,7 @@ function SlideThree({ details }: { details: UserDetails }) {
const professionalOrStudent = getValues("professionalOrStudent");
if (isError && professionalOrStudent === "Working professional") {
- isError = await trigger(["workplace", "jobTitle"]);
+ isError = await trigger(["workplace", "jobTitle", "yearsOfExperience"]);
}
if (isError && professionalOrStudent === "Current student") {
@@ -512,6 +527,29 @@ function SlideThree({ details }: { details: UserDetails }) {
)}
+
+
+
+
+ {errors.yearsOfExperience && (
+
+ {errors.yearsOfExperience.message}
+
+ )}
+
>
)}
diff --git a/app/(app)/alpha/additional-details/page.tsx b/app/(app)/onboarding/page.tsx
similarity index 92%
rename from app/(app)/alpha/additional-details/page.tsx
rename to app/(app)/onboarding/page.tsx
index dfe5d1be..8fa78dcc 100644
--- a/app/(app)/alpha/additional-details/page.tsx
+++ b/app/(app)/onboarding/page.tsx
@@ -22,6 +22,7 @@ export default async function Page() {
levelOfStudy: true,
jobTitle: true,
workplace: true,
+ yearsOfExperience: true,
},
where: (user, { eq }) => eq(user.id, userId),
});
@@ -37,6 +38,7 @@ export default async function Page() {
levelOfStudy: details?.levelOfStudy || "",
jobTitle: details?.jobTitle || "",
workplace: details?.workplace || "",
+ yearsOfExperience: details?.yearsOfExperience || undefined,
};
return ;
diff --git a/app/(app)/alpha/additional-details/selectOptions.ts b/app/(app)/onboarding/selectOptions.ts
similarity index 100%
rename from app/(app)/alpha/additional-details/selectOptions.ts
rename to app/(app)/onboarding/selectOptions.ts
diff --git a/context/AuthProvider.tsx b/context/AuthProvider.tsx
index 3407172c..e3b219f4 100644
--- a/context/AuthProvider.tsx
+++ b/context/AuthProvider.tsx
@@ -1,10 +1,29 @@
"use client";
-import { SessionProvider } from "next-auth/react";
+import { SessionProvider, useSession } from "next-auth/react";
+import { useRouter } from "next/navigation";
+import { useEffect } from "react";
export default function AuthProvider({
children,
}: {
children: React.ReactNode;
}) {
- return {children};
+ return (
+
+ {children}
+
+ );
+}
+
+function OnboardingCheck({ children }: { children: React.ReactNode }) {
+ const { data: session, status } = useSession();
+ const router = useRouter();
+
+ useEffect(() => {
+ if (status === "authenticated" && !session?.user?.isOnboardingComplete) {
+ router.push("/onboarding");
+ }
+ }, [status, session, router]);
+
+ return <>{children}>;
}
diff --git a/drizzle/0011_years-experience-user-table.sql b/drizzle/0011_years-experience-user-table.sql
new file mode 100644
index 00000000..ec0ba366
--- /dev/null
+++ b/drizzle/0011_years-experience-user-table.sql
@@ -0,0 +1,9 @@
+DO $$ BEGIN
+ CREATE TYPE "public"."experience_range" AS ENUM('0-1', '1-3', '3-5', '5-8', '12+');
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+ALTER TABLE "session" ADD PRIMARY KEY ("sessionToken");--> statement-breakpoint
+ALTER TABLE "user" ADD COLUMN "yearsOfExperience" "experience_range";--> statement-breakpoint
+ALTER TABLE "user" ADD COLUMN "onboardingComplete" timestamp with time zone;
\ No newline at end of file
diff --git a/drizzle/meta/0011_snapshot.json b/drizzle/meta/0011_snapshot.json
new file mode 100644
index 00000000..d3e10496
--- /dev/null
+++ b/drizzle/meta/0011_snapshot.json
@@ -0,0 +1,1420 @@
+{
+ "id": "a4753261-9286-478c-8499-2fa6451ee876",
+ "prevId": "6449498f-2807-4e06-904c-1bbaaa37855a",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "name": "account_provider_providerAccountId_pk",
+ "columns": ["provider", "providerAccountId"]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "public.BannedUsers": {
+ "name": "BannedUsers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bannedById": {
+ "name": "bannedById",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "BannedUsers_userId_key": {
+ "name": "BannedUsers_userId_key",
+ "columns": [
+ {
+ "expression": "userId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "BannedUsers_userId_user_id_fk": {
+ "name": "BannedUsers_userId_user_id_fk",
+ "tableFrom": "BannedUsers",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "BannedUsers_bannedById_user_id_fk": {
+ "name": "BannedUsers_bannedById_user_id_fk",
+ "tableFrom": "BannedUsers",
+ "tableTo": "user",
+ "columnsFrom": ["bannedById"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "BannedUsers_id_unique": {
+ "name": "BannedUsers_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["id"]
+ }
+ }
+ },
+ "public.Bookmark": {
+ "name": "Bookmark",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "postId": {
+ "name": "postId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "Bookmark_userId_postId_key": {
+ "name": "Bookmark_userId_postId_key",
+ "columns": [
+ {
+ "expression": "postId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "userId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "Bookmark_postId_Post_id_fk": {
+ "name": "Bookmark_postId_Post_id_fk",
+ "tableFrom": "Bookmark",
+ "tableTo": "Post",
+ "columnsFrom": ["postId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Bookmark_userId_user_id_fk": {
+ "name": "Bookmark_userId_user_id_fk",
+ "tableFrom": "Bookmark",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "Bookmark_id_unique": {
+ "name": "Bookmark_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["id"]
+ }
+ }
+ },
+ "public.Comment": {
+ "name": "Comment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "postId": {
+ "name": "postId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parentId": {
+ "name": "parentId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "Comment_postId_index": {
+ "name": "Comment_postId_index",
+ "columns": [
+ {
+ "expression": "postId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "Comment_postId_Post_id_fk": {
+ "name": "Comment_postId_Post_id_fk",
+ "tableFrom": "Comment",
+ "tableTo": "Post",
+ "columnsFrom": ["postId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Comment_userId_user_id_fk": {
+ "name": "Comment_userId_user_id_fk",
+ "tableFrom": "Comment",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Comment_parentId_fkey": {
+ "name": "Comment_parentId_fkey",
+ "tableFrom": "Comment",
+ "tableTo": "Comment",
+ "columnsFrom": ["parentId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "Comment_id_unique": {
+ "name": "Comment_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["id"]
+ }
+ }
+ },
+ "public.EmailChangeHistory": {
+ "name": "EmailChangeHistory",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "oldEmail": {
+ "name": "oldEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "newEmail": {
+ "name": "newEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "changedAt": {
+ "name": "changedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "ipAddress": {
+ "name": "ipAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "userAgent": {
+ "name": "userAgent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "EmailChangeHistory_userId_user_id_fk": {
+ "name": "EmailChangeHistory_userId_user_id_fk",
+ "tableFrom": "EmailChangeHistory",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.EmailChangeRequest": {
+ "name": "EmailChangeRequest",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "newEmail": {
+ "name": "newEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expiresAt": {
+ "name": "expiresAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "EmailChangeRequest_userId_user_id_fk": {
+ "name": "EmailChangeRequest_userId_user_id_fk",
+ "tableFrom": "EmailChangeRequest",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "EmailChangeRequest_token_unique": {
+ "name": "EmailChangeRequest_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ }
+ },
+ "public.Flagged": {
+ "name": "Flagged",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "notifierId": {
+ "name": "notifierId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postId": {
+ "name": "postId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "commentId": {
+ "name": "commentId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Flagged_userId_user_id_fk": {
+ "name": "Flagged_userId_user_id_fk",
+ "tableFrom": "Flagged",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Flagged_notifierId_user_id_fk": {
+ "name": "Flagged_notifierId_user_id_fk",
+ "tableFrom": "Flagged",
+ "tableTo": "user",
+ "columnsFrom": ["notifierId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Flagged_postId_Post_id_fk": {
+ "name": "Flagged_postId_Post_id_fk",
+ "tableFrom": "Flagged",
+ "tableTo": "Post",
+ "columnsFrom": ["postId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Flagged_commentId_Comment_id_fk": {
+ "name": "Flagged_commentId_Comment_id_fk",
+ "tableFrom": "Flagged",
+ "tableTo": "Comment",
+ "columnsFrom": ["commentId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "Flagged_id_unique": {
+ "name": "Flagged_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["id"]
+ }
+ }
+ },
+ "public.Like": {
+ "name": "Like",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "postId": {
+ "name": "postId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "commentId": {
+ "name": "commentId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "Like_userId_commentId_key": {
+ "name": "Like_userId_commentId_key",
+ "columns": [
+ {
+ "expression": "userId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "commentId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "Like_userId_postId_key": {
+ "name": "Like_userId_postId_key",
+ "columns": [
+ {
+ "expression": "userId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "postId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "Like_userId_user_id_fk": {
+ "name": "Like_userId_user_id_fk",
+ "tableFrom": "Like",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Like_postId_Post_id_fk": {
+ "name": "Like_postId_Post_id_fk",
+ "tableFrom": "Like",
+ "tableTo": "Post",
+ "columnsFrom": ["postId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Like_commentId_Comment_id_fk": {
+ "name": "Like_commentId_Comment_id_fk",
+ "tableFrom": "Like",
+ "tableTo": "Comment",
+ "columnsFrom": ["commentId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "Like_id_unique": {
+ "name": "Like_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["id"]
+ }
+ }
+ },
+ "public.Notification": {
+ "name": "Notification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "type": {
+ "name": "type",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "postId": {
+ "name": "postId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "commentId": {
+ "name": "commentId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notifierId": {
+ "name": "notifierId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "Notification_userId_index": {
+ "name": "Notification_userId_index",
+ "columns": [
+ {
+ "expression": "userId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "Notification_userId_user_id_fk": {
+ "name": "Notification_userId_user_id_fk",
+ "tableFrom": "Notification",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Notification_postId_Post_id_fk": {
+ "name": "Notification_postId_Post_id_fk",
+ "tableFrom": "Notification",
+ "tableTo": "Post",
+ "columnsFrom": ["postId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Notification_commentId_Comment_id_fk": {
+ "name": "Notification_commentId_Comment_id_fk",
+ "tableFrom": "Notification",
+ "tableTo": "Comment",
+ "columnsFrom": ["commentId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Notification_notifierId_user_id_fk": {
+ "name": "Notification_notifierId_user_id_fk",
+ "tableFrom": "Notification",
+ "tableTo": "user",
+ "columnsFrom": ["notifierId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "Notification_id_unique": {
+ "name": "Notification_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["id"]
+ }
+ }
+ },
+ "public.Post": {
+ "name": "Post",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "canonicalUrl": {
+ "name": "canonicalUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coverImage": {
+ "name": "coverImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "approved": {
+ "name": "approved",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "excerpt": {
+ "name": "excerpt",
+ "type": "varchar(156)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "readTimeMins": {
+ "name": "readTimeMins",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published": {
+ "name": "published",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "showComments": {
+ "name": "showComments",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "likes": {
+ "name": "likes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "Post_id_key": {
+ "name": "Post_id_key",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "Post_slug_key": {
+ "name": "Post_slug_key",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "Post_slug_index": {
+ "name": "Post_slug_index",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "Post_userId_index": {
+ "name": "Post_userId_index",
+ "columns": [
+ {
+ "expression": "userId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "Post_userId_user_id_fk": {
+ "name": "Post_userId_user_id_fk",
+ "tableFrom": "Post",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "Post_id_unique": {
+ "name": "Post_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["id"]
+ }
+ }
+ },
+ "public.PostTag": {
+ "name": "PostTag",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "postId": {
+ "name": "postId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "PostTag_tagId_postId_key": {
+ "name": "PostTag_tagId_postId_key",
+ "columns": [
+ {
+ "expression": "tagId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "postId",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "PostTag_tagId_Tag_id_fk": {
+ "name": "PostTag_tagId_Tag_id_fk",
+ "tableFrom": "PostTag",
+ "tableTo": "Tag",
+ "columnsFrom": ["tagId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "PostTag_postId_Post_id_fk": {
+ "name": "PostTag_postId_Post_id_fk",
+ "tableFrom": "PostTag",
+ "tableTo": "Post",
+ "columnsFrom": ["postId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.Tag": {
+ "name": "Tag",
+ "schema": "",
+ "columns": {
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "Tag_title_key": {
+ "name": "Tag_title_key",
+ "columns": [
+ {
+ "expression": "title",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "Tag_id_unique": {
+ "name": "Tag_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["id"]
+ }
+ }
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "varchar(40)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'/images/person.png'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "CURRENT_TIMESTAMP"
+ },
+ "bio": {
+ "name": "bio",
+ "type": "varchar(200)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "location": {
+ "name": "location",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "websiteUrl": {
+ "name": "websiteUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "emailNotifications": {
+ "name": "emailNotifications",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "newsletter": {
+ "name": "newsletter",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "gender": {
+ "name": "gender",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dateOfBirth": {
+ "name": "dateOfBirth",
+ "type": "timestamp(3) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "professionalOrStudent": {
+ "name": "professionalOrStudent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workplace": {
+ "name": "workplace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "jobTitle": {
+ "name": "jobTitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "levelOfStudy": {
+ "name": "levelOfStudy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "course": {
+ "name": "course",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "Role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'USER'"
+ },
+ "yearsOfExperience": {
+ "name": "yearsOfExperience",
+ "type": "experience_range",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "onboardingComplete": {
+ "name": "onboardingComplete",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "User_username_key": {
+ "name": "User_username_key",
+ "columns": [
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "User_email_key": {
+ "name": "User_email_key",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "User_username_id_idx": {
+ "name": "User_username_id_idx",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "User_username_index": {
+ "name": "User_username_index",
+ "columns": [
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {
+ "public.experience_range": {
+ "name": "experience_range",
+ "schema": "public",
+ "values": ["0-1", "1-3", "3-5", "5-8", "12+"]
+ },
+ "public.Role": {
+ "name": "Role",
+ "schema": "public",
+ "values": ["MODERATOR", "ADMIN", "USER"]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index bee95933..aaceccaf 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -78,6 +78,13 @@
"when": 1728680482423,
"tag": "0010_email-tokens-and-indexes",
"breakpoints": true
+ },
+ {
+ "idx": 11,
+ "version": "7",
+ "when": 1731937114063,
+ "tag": "0011_years-experience-user-table",
+ "breakpoints": true
}
]
}
diff --git a/e2e/setup.ts b/e2e/setup.ts
index 36382a62..80717375 100644
--- a/e2e/setup.ts
+++ b/e2e/setup.ts
@@ -121,6 +121,7 @@ export const setup = async () => {
location: "Ireland",
bio: "Hi I am an robot",
websiteUrl: "codu.co",
+ onboardingComplete: new Date().toISOString(),
};
const [createdUser] = await db.insert(user).values(userData).returning();
return createdUser;
diff --git a/schema/additionalUserDetails.ts b/schema/additionalUserDetails.ts
index 60dc6940..cdf43318 100644
--- a/schema/additionalUserDetails.ts
+++ b/schema/additionalUserDetails.ts
@@ -1,5 +1,8 @@
+import { experienceRangeEnum } from "@/server/db/schema";
import z from "zod";
+export const experienceRangeValues = experienceRangeEnum.enumValues;
+
export const slideOneSchema = z.object({
name: z
.string()
@@ -30,44 +33,50 @@ export const slideThreeSchema = z
jobTitle: z.string().max(30, "Max length is 30 characters."),
levelOfStudy: z.string(),
course: z.string().max(30, "Max name length is 30 characters."),
+ yearsOfExperience: z
+ .enum(experienceRangeValues, {
+ errorMap: () => ({ message: "Please select a valid experience range" }),
+ })
+ .optional(),
})
.superRefine((val, ctx) => {
- if (
- val.professionalOrStudent === "Current student" &&
- val.levelOfStudy === ""
- ) {
- ctx.addIssue({
- path: ["levelOfStudy"],
- code: "custom",
- message: "required",
- });
- }
- if (val.professionalOrStudent === "Current student" && val.course === "") {
- ctx.addIssue({
- path: ["course"],
- code: "custom",
- message: "required",
- });
- }
- if (
- val.professionalOrStudent === "Working professional" &&
- val.workplace === ""
- ) {
- ctx.addIssue({
- path: ["workplace"],
- code: "custom",
- message: "required",
- });
- }
- if (
- val.professionalOrStudent === "Working professional" &&
- val.jobTitle === ""
- ) {
- ctx.addIssue({
- path: ["jobTitle"],
- code: "custom",
- message: "required",
- });
+ if (val.professionalOrStudent === "Current student") {
+ if (val.levelOfStudy === "") {
+ ctx.addIssue({
+ path: ["levelOfStudy"],
+ code: "custom",
+ message: "required",
+ });
+ }
+ if (val.course === "") {
+ ctx.addIssue({
+ path: ["course"],
+ code: "custom",
+ message: "required",
+ });
+ }
+ } else if (val.professionalOrStudent === "Working professional") {
+ if (val.workplace === "") {
+ ctx.addIssue({
+ path: ["workplace"],
+ code: "custom",
+ message: "required",
+ });
+ }
+ if (val.jobTitle === "") {
+ ctx.addIssue({
+ path: ["jobTitle"],
+ code: "custom",
+ message: "required",
+ });
+ }
+ if (val.yearsOfExperience === undefined) {
+ ctx.addIssue({
+ path: ["yearsOfExperience"],
+ code: "custom",
+ message: "required",
+ });
+ }
}
});
diff --git a/server/auth.ts b/server/auth.ts
index 9f5864bf..2f6ed96a 100644
--- a/server/auth.ts
+++ b/server/auth.ts
@@ -62,8 +62,15 @@ export const authOptions: NextAuthOptions = {
callbacks: {
async session({ session, user }) {
if (session.user) {
+ const userStatus = await db.query.user.findFirst({
+ where: (users, { eq }) => eq(users.id, user.id),
+ columns: { onboardingComplete: true },
+ });
+
session.user.id = user.id;
session.user.role = user.role;
+ // onboardingComplete is an iso date string - if it exists then it is complete
+ session.user.isOnboardingComplete = !!userStatus?.onboardingComplete;
}
return session;
},
diff --git a/server/db/schema.ts b/server/db/schema.ts
index ce7a53e6..9daa93e9 100644
--- a/server/db/schema.ts
+++ b/server/db/schema.ts
@@ -170,6 +170,14 @@ export const postRelations = relations(post, ({ one, many }) => ({
tags: many(post_tag),
}));
+export const experienceRangeEnum = pgEnum("experience_range", [
+ "0-1",
+ "1-3",
+ "3-5",
+ "5-8",
+ "12+",
+]);
+
export const user = pgTable(
"user",
{
@@ -217,6 +225,11 @@ export const user = pgTable(
levelOfStudy: text("levelOfStudy"),
course: text("course"),
role: role("role").default("USER").notNull(),
+ yearsOfExperience: experienceRangeEnum("yearsOfExperience"),
+ onboardingComplete: timestamp("onboardingComplete", {
+ mode: "string",
+ withTimezone: true,
+ }),
},
(table) => {
return {
diff --git a/types/next-auth.d.ts b/types/next-auth.d.ts
index 7d038d25..f5284411 100644
--- a/types/next-auth.d.ts
+++ b/types/next-auth.d.ts
@@ -11,6 +11,7 @@ declare module "next-auth" {
role: Role;
username: string;
id: string;
+ isOnboardingComplete: boolean;
} & DefaultSession["user"];
}