Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Rework storage and display of core quest content #336

Merged
merged 6 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading