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"]; }