Skip to content

Commit

Permalink
feat: Rework storage and display of core quest content (#336)
Browse files Browse the repository at this point in the history
  • Loading branch information
evadecker authored Jan 22, 2025
1 parent 7281014 commit 24464bb
Show file tree
Hide file tree
Showing 50 changed files with 838 additions and 893 deletions.
5 changes: 5 additions & 0 deletions .changeset/strong-coins-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"namesake": minor
---

Display core quests by default and redirect user to appropriate state documentation
1 change: 0 additions & 1 deletion convex/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ describe("auth", () => {

expect(userSettings).toBeDefined();
expect(userSettings?.theme).toBe("system");
expect(userSettings?.groupQuestsBy).toBe("dateAdded");
});
});
});
1 change: 0 additions & 1 deletion convex/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export const createOrUpdateUser = async (ctx: MutationCtx, args: any) => {
ctx.db.insert("userSettings", {
userId,
theme: "system",
groupQuestsBy: "dateAdded",
});
return userId;
});
Expand Down
92 changes: 40 additions & 52 deletions convex/constants.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
Baby,
Calendar,
CalendarClock,
CalendarDays,
CircleArrowRight,
CircleCheckBig,
CircleDashed,
CircleHelp,
Expand All @@ -11,20 +11,22 @@ import {
Clock,
Computer,
Gamepad2,
Gavel,
Globe,
GraduationCap,
HeartPulse,
History,
House,
IdCard,
Landmark,
LaptopMinimal,
LoaderCircle,
type LucideIcon,
Mail,
MapPin,
MessageCircle,
Milestone,
Moon,
Scale,
ShieldCheck,
ShoppingBag,
Sun,
Zap,
Expand Down Expand Up @@ -117,13 +119,6 @@ export const ROLES = {
} as const;
export type Role = keyof typeof ROLES;

export const GROUP_QUESTS_BY = {
dateAdded: "Date added",
category: "Category",
status: "Status",
} as const;
export type GroupQuestsBy = keyof typeof GROUP_QUESTS_BY;

/**
* Generic group details.
* Used for UI display of filter groups.
Expand All @@ -133,12 +128,45 @@ export type GroupDetails = {
icon: LucideIcon;
};

/**
* Core quests.
* Used to display the primary quests.
*/
export type CoreQuest =
| "court-order"
| "state-id"
| "social-security"
| "passport"
| "birth-certificate";

export const CORE_QUESTS: Record<CoreQuest, GroupDetails> = {
"court-order": {
label: "Court Order",
icon: Gavel,
},
"state-id": {
label: "State ID",
icon: IdCard,
},
"social-security": {
label: "Social Security",
icon: ShieldCheck,
},
passport: {
label: "Passport",
icon: Globe,
},
"birth-certificate": {
label: "Birth Certificate",
icon: Baby,
},
};

/**
* Categories.
* Used to filter quests in the quests list.
*/
export type Category =
| "core"
| "entertainment"
| "devices"
| "education"
Expand All @@ -155,10 +183,6 @@ export type Category =
| "other";

export const CATEGORIES: Record<Category, GroupDetails> = {
core: {
label: "Core",
icon: Milestone,
},
entertainment: {
label: "Arts and Entertainment",
icon: Clapperboard,
Expand Down Expand Up @@ -217,43 +241,13 @@ export const CATEGORIES: Record<Category, GroupDetails> = {
},
};

export const CATEGORY_ORDER: Category[] = Object.keys(CATEGORIES) as Category[];

/**
* Date added filters.
* Used to filter quests in the quests list.
*/
export type DateAdded = "lastWeek" | "lastMonth" | "earlier";

export const DATE_ADDED: Record<DateAdded, GroupDetails> = {
lastWeek: {
label: "Last 7 days",
icon: Calendar,
},
lastMonth: {
label: "Last 30 days",
icon: CalendarDays,
},
earlier: {
label: "Earlier",
icon: History,
},
};

export const DATE_ADDED_ORDER: DateAdded[] = Object.keys(
DATE_ADDED,
) as DateAdded[];

/**
* User quest statuses.
* "filed" is only available for core quests.
* "notStarted", "inProgress", and "complete" are available for all quests.
*/
export type Status = "notStarted" | "inProgress" | "filed" | "complete";
export type Status = "notStarted" | "inProgress" | "complete";

interface StatusDetails extends GroupDetails {
variant?: "info" | "warning" | "danger" | "waiting" | "success";
isCoreOnly?: boolean;
}

export const STATUS: Record<Status, StatusDetails> = {
Expand All @@ -266,12 +260,6 @@ export const STATUS: Record<Status, StatusDetails> = {
icon: LoaderCircle,
variant: "warning",
},
filed: {
label: "Filed",
icon: CircleArrowRight,
isCoreOnly: true,
variant: "waiting",
},
complete: {
label: "Done",
icon: CircleCheckBig,
Expand Down
7 changes: 2 additions & 5 deletions convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
import {
category,
groupQuestsBy,
jurisdiction,
role,
status,
Expand Down Expand Up @@ -50,7 +49,7 @@ const faqTopics = defineTable({
const quests = defineTable({
/** The title of the quest. (e.g. "Court Order") */
title: v.string(),
/** The category of the quest. (e.g. "Core", "Social") */
/** The category of the quest. (e.g. "Education", "Social") */
category: v.optional(category),
/** The user who created the quest. */
creationUser: v.id("users"),
Expand Down Expand Up @@ -127,7 +126,7 @@ const users = defineTable({
}).index("email", ["email"]);

/**
* A unique piece of user data that has been enteed through filling a form.
* A unique piece of user data that has been entered through filling a form.
*/
const userFormData = defineTable({
/** The user who owns the data. */
Expand All @@ -148,8 +147,6 @@ const userSettings = defineTable({
userId: v.id("users"),
/** The user's preferred color scheme. (e.g. "system", "light", "dark") */
theme: v.optional(theme),
/** The user's preferred way to group quests. (e.g. "dateAdded", "category") */
groupQuestsBy: v.optional(groupQuestsBy),
}).index("userId", ["userId"]);

/**
Expand Down
20 changes: 1 addition & 19 deletions convex/seed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { faker } from "@faker-js/faker";
import { internalMutation } from "./_generated/server";
import { DEFAULT_TIME_REQUIRED, JURISDICTIONS } from "./constants";

const seed = internalMutation(async (ctx) => {
if (process.env.NODE_ENV === "production") {
Expand All @@ -23,7 +22,7 @@ const seed = internalMutation(async (ctx) => {
try {
const firstName = faker.person.firstName();
const lastName = faker.person.lastName();
const userId = await ctx.db.insert("users", {
await ctx.db.insert("users", {
name: firstName,
email: faker.internet.email({
firstName: firstName,
Expand All @@ -34,23 +33,6 @@ const seed = internalMutation(async (ctx) => {
emailVerified: faker.datatype.boolean(),
});
console.log(`Created user ${firstName} ${lastName}`);

const questTitle = faker.helpers.arrayElement([
"Court Order",
"State ID",
"Birth Certificate",
]);
const questJurisdiction = faker.helpers.arrayElement(
Object.keys(JURISDICTIONS),
);
await ctx.db.insert("quests", {
title: questTitle,
category: "core",
jurisdiction: questJurisdiction,
timeRequired: DEFAULT_TIME_REQUIRED,
creationUser: userId,
});
console.log(`Created quest ${questTitle} (${questJurisdiction})`);
} catch (e) {
throw new Error(`Failed to seed data: ${e}`);
}
Expand Down
60 changes: 6 additions & 54 deletions convex/userQuests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe("userQuests", () => {
const questId = await asUser.run(async (ctx) => {
return await ctx.db.insert("quests", {
title: "Test Quest",
category: "core",
category: "education",
jurisdiction: "MA",
creationUser: userId,
});
Expand Down Expand Up @@ -233,7 +233,7 @@ describe("userQuests", () => {
const questId = await t.run(async (ctx) => {
return ctx.db.insert("quests", {
title: "Test Quest",
category: "core",
category: "education",
jurisdiction: "MA",
creationUser: userId,
});
Expand All @@ -249,54 +249,6 @@ describe("userQuests", () => {
).rejects.toThrow("Invalid status");
});

it("should prevent setting 'filed' status on non-core quests", async () => {
const t = convexTest(schema, modules);

const userId = await t.run(async (ctx) => {
return await ctx.db.insert("users", {
email: "test@example.com",
role: "user",
});
});

const asUser = t.withIdentity({ subject: userId });

const nonCoreQuestId = await t.run(async (ctx) => {
return ctx.db.insert("quests", {
title: "Test Quest",
category: "housing",
jurisdiction: "MA",
creationUser: userId,
});
});

const coreQuestId = await t.run(async (ctx) => {
return ctx.db.insert("quests", {
title: "Test Quest",
category: "core",
jurisdiction: "MA",
creationUser: userId,
});
});

await asUser.mutation(api.userQuests.create, { questId: nonCoreQuestId });
await asUser.mutation(api.userQuests.create, { questId: coreQuestId });

await expect(
asUser.mutation(api.userQuests.setStatus, {
questId: nonCoreQuestId,
status: "filed",
}),
).rejects.toThrow("This status is reserved for core quests only.");

await expect(
asUser.mutation(api.userQuests.setStatus, {
questId: coreQuestId,
status: "filed",
}),
).resolves.toBeNull();
});

it("should add completedAt when status changed to complete", async () => {
const t = convexTest(schema, modules);

Expand Down Expand Up @@ -441,7 +393,7 @@ describe("userQuests", () => {
await t.run(async (ctx) => {
const quest1Id = await ctx.db.insert("quests", {
title: "Test Quest 1",
category: "core",
category: "education",
jurisdiction: "Test Jurisdiction",
creationUser: userId,
});
Expand Down Expand Up @@ -470,9 +422,9 @@ describe("userQuests", () => {
api.userQuests.getByCategory,
{},
);
expect(Object.keys(questsByCategory)).toContain("core");
expect(Object.keys(questsByCategory)).toContain("education");
expect(Object.keys(questsByCategory)).toContain("housing");
expect(questsByCategory.core).toHaveLength(1);
expect(questsByCategory.education).toHaveLength(1);
expect(questsByCategory.housing).toHaveLength(1);
});
});
Expand All @@ -493,7 +445,7 @@ describe("userQuests", () => {
await t.run(async (ctx) => {
const quest1Id = await ctx.db.insert("quests", {
title: "Active Quest",
category: "core",
category: "education",
jurisdiction: "Test Jurisdiction",
creationUser: userId,
});
Expand Down
8 changes: 0 additions & 8 deletions convex/userQuests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,6 @@ export const setStatus = userMutation({
// Throw if status is invalid
if (!STATUS[args.status as Status]) throw new Error("Invalid status");

// Prevent setting "filed" on non-core quests
if (
STATUS[args.status as Status].isCoreOnly === true &&
quest.category !== "core"
)
throw new Error("This status is reserved for core quests only.");

// Prevent setting the existing status
if (userQuest.status === args.status) return;

Expand Down Expand Up @@ -321,7 +314,6 @@ export const getByStatus = userQuery({
const initial: Record<Status, typeof validQuests> = {
notStarted: [],
inProgress: [],
filed: [],
complete: [],
};

Expand Down
Loading

0 comments on commit 24464bb

Please sign in to comment.