Skip to content

Commit

Permalink
feat: Add quest sorting (#140)
Browse files Browse the repository at this point in the history
  • Loading branch information
evadecker authored Oct 20, 2024
1 parent 103e0d9 commit b8f60bc
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 136 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-ducks-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"namesake": minor
---

Update sidebar nav and add quest sorting
1 change: 1 addition & 0 deletions convex/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const { auth, signIn, signOut, store } = convexAuth({
emailVerified: args.profile.emailVerified ?? false,
role: "user",
theme: "system",
sortQuestsBy: "newest",
});
},

Expand Down
229 changes: 118 additions & 111 deletions convex/constants.ts
Original file line number Diff line number Diff line change
@@ -1,152 +1,153 @@
import {
type RemixiconComponentType,
RiAccountCircleLine,
RiAccountCircleFill,
RiAppleFill,
RiAtLine,
RiAuctionLine,
RiAwardLine,
RiBankCardLine,
RiBankLine,
RiBasketballLine,
RiAuctionFill,
RiAwardFill,
RiBankCardFill,
RiBankFill,
RiBasketballFill,
RiBlueskyFill,
RiBuildingLine,
RiCakeLine,
RiBuildingFill,
RiCakeFill,
RiCalendarLine,
RiCapsuleLine,
RiCarLine,
RiChat1Line,
RiCapsuleFill,
RiCarFill,
RiChat1Fill,
RiCheckboxLine,
RiChromeFill,
RiCodeLine,
RiCommunityLine,
RiContactsBookLine,
RiCodeFill,
RiCommunityFill,
RiContactsBookFill,
RiDiscordFill,
RiDropboxFill,
RiDropdownList,
RiFacebookCircleFill,
RiFileLine,
RiFlowerLine,
RiFolder2Line,
RiFileFill,
RiFlowerFill,
RiFolder2Fill,
RiGithubFill,
RiGlobalLine,
RiGlobalFill,
RiGoogleFill,
RiGovernmentLine,
RiGraduationCapLine,
RiHandHeartLine,
RiGovernmentFill,
RiGraduationCapFill,
RiHandHeartFill,
RiHashtag,
RiHeartLine,
RiHomeLine,
RiHospitalLine,
RiIdCardLine,
RiImageLine,
RiHeartFill,
RiHomeFill,
RiHospitalFill,
RiIdCardFill,
RiImageFill,
RiInputField,
RiInstagramFill,
RiKeyLine,
RiLightbulbLine,
RiKeyFill,
RiLightbulbFill,
RiLinkedinFill,
RiLock2Line,
RiMailLine,
RiLock2Fill,
RiMailFill,
RiMediumFill,
RiMentalHealthLine,
RiMusic2Line,
RiNewsLine,
RiMentalHealthFill,
RiMusic2Fill,
RiNewsFill,
RiParagraph,
RiPatreonFill,
RiPhoneFill,
RiPhoneLine,
RiPinterestFill,
RiPlantLine,
RiPlantFill,
RiPlaystationFill,
RiPoliceBadgeLine,
RiPoliceBadgeFill,
RiRedditFill,
RiRestaurantLine,
RiRobot2Line,
RiScales3Line,
RiSchoolLine,
RiSettings3Line,
RiShapesLine,
RiShieldUserLine,
RiSignpostLine,
RiRestaurantFill,
RiRobot2Fill,
RiScales3Fill,
RiSchoolFill,
RiSettings3Fill,
RiShapesFill,
RiShieldUserFill,
RiSignpostFill,
RiSlackFill,
RiSmartphoneLine,
RiSmartphoneFill,
RiSnapchatFill,
RiSofaLine,
RiSofaFill,
RiSoundcloudFill,
RiSparklingLine,
RiSparklingFill,
RiSpotifyFill,
RiStore2Line,
RiStore2Fill,
RiSwitchFill,
RiSwordLine,
RiTeamLine,
RiTentLine,
RiSwordFill,
RiTeamFill,
RiTentFill,
RiTiktokFill,
RiToothLine,
RiTrophyLine,
RiTv2Line,
RiToothFill,
RiTrophyFill,
RiTv2Fill,
RiTwitterXFill,
RiUserLine,
RiWalletLine,
RiUserFill,
RiWalletFill,
RiWhatsappFill,
RiWindowsFill,
RiYoutubeFill,
} from "@remixicon/react";

export const ICONS: Record<string, RemixiconComponentType> = {
account: RiAccountCircleLine,
bank: RiBankLine,
basketball: RiBasketballLine,
building: RiBuildingLine,
cake: RiCakeLine,
car: RiCarLine,
certificate: RiAwardLine,
chat: RiChat1Line,
code: RiCodeLine,
college: RiGraduationCapLine,
community: RiCommunityLine,
contacts: RiContactsBookLine,
creditCard: RiBankCardLine,
file: RiFileLine,
flower: RiFlowerLine,
folder: RiFolder2Line,
food: RiRestaurantLine,
gavel: RiAuctionLine,
giving: RiHandHeartLine,
global: RiGlobalLine,
government: RiGovernmentLine,
heart: RiHeartLine,
home: RiHomeLine,
hospital: RiHospitalLine,
id: RiIdCardLine,
image: RiImageLine,
key: RiKeyLine,
lightbulb: RiLightbulbLine,
lock: RiLock2Line,
mail: RiMailLine,
mentalHealth: RiMentalHealthLine,
music: RiMusic2Line,
news: RiNewsLine,
phone: RiPhoneLine,
pill: RiCapsuleLine,
plant: RiPlantLine,
police: RiPoliceBadgeLine,
robot: RiRobot2Line,
scale: RiScales3Line,
school: RiSchoolLine,
settings: RiSettings3Line,
shapes: RiShapesLine,
signpost: RiSignpostLine,
smartphone: RiSmartphoneLine,
socialSecurity: RiShieldUserLine,
sofa: RiSofaLine,
sparkles: RiSparklingLine,
store: RiStore2Line,
sword: RiSwordLine,
team: RiTeamLine,
tent: RiTentLine,
tooth: RiToothLine,
trophy: RiTrophyLine,
tv: RiTv2Line,
user: RiUserLine,
wallet: RiWalletLine,
account: RiAccountCircleFill,
bank: RiBankFill,
basketball: RiBasketballFill,
building: RiBuildingFill,
cake: RiCakeFill,
car: RiCarFill,
certificate: RiAwardFill,
chat: RiChat1Fill,
code: RiCodeFill,
college: RiGraduationCapFill,
community: RiCommunityFill,
contacts: RiContactsBookFill,
creditCard: RiBankCardFill,
file: RiFileFill,
flower: RiFlowerFill,
folder: RiFolder2Fill,
food: RiRestaurantFill,
gavel: RiAuctionFill,
giving: RiHandHeartFill,
global: RiGlobalFill,
government: RiGovernmentFill,
heart: RiHeartFill,
home: RiHomeFill,
hospital: RiHospitalFill,
id: RiIdCardFill,
image: RiImageFill,
key: RiKeyFill,
lightbulb: RiLightbulbFill,
lock: RiLock2Fill,
mail: RiMailFill,
mentalHealth: RiMentalHealthFill,
music: RiMusic2Fill,
news: RiNewsFill,
phone: RiPhoneFill,
pill: RiCapsuleFill,
plant: RiPlantFill,
police: RiPoliceBadgeFill,
robot: RiRobot2Fill,
scale: RiScales3Fill,
school: RiSchoolFill,
settings: RiSettings3Fill,
shapes: RiShapesFill,
signpost: RiSignpostFill,
smartphone: RiSmartphoneFill,
socialSecurity: RiShieldUserFill,
sofa: RiSofaFill,
sparkles: RiSparklingFill,
store: RiStore2Fill,
sword: RiSwordFill,
team: RiTeamFill,
tent: RiTentFill,
tooth: RiToothFill,
trophy: RiTrophyFill,
tv: RiTv2Fill,
user: RiUserFill,
wallet: RiWalletFill,

// Logos
apple: RiAppleFill,
Expand Down Expand Up @@ -286,3 +287,9 @@ export const ROLES = {
admin: "Admin",
} as const;
export type Role = keyof typeof ROLES;

export const SORT_QUESTS_BY = {
newest: "Newest",
oldest: "Oldest",
} as const;
export type SortQuestsBy = keyof typeof SORT_QUESTS_BY;
10 changes: 9 additions & 1 deletion convex/schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { authTables } from "@convex-dev/auth/server";
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
import { field, icon, jurisdiction, role, theme } from "./validators";
import {
field,
icon,
jurisdiction,
role,
sortQuestsBy,
theme,
} from "./validators";

/**
* Represents a collection of steps and forms for a user to complete.
Expand Down Expand Up @@ -92,6 +99,7 @@ const users = defineTable({
jurisdiction: v.optional(jurisdiction),
isMinor: v.optional(v.boolean()),
theme: theme,
sortQuestsBy: v.optional(sortQuestsBy),
}).index("email", ["email"]);

/**
Expand Down
1 change: 1 addition & 0 deletions convex/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const seed = internalMutation(async (ctx) => {
role: "admin",
emailVerified: faker.datatype.boolean(),
theme: faker.helpers.arrayElement(["system", "light", "dark"]),
sortQuestsBy: "newest",
});
console.log(`Created user ${firstName} ${lastName}`);

Expand Down
11 changes: 8 additions & 3 deletions convex/userQuests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ export const getQuestsForCurrentUser = userQuery({
.withIndex("userId", (q) => q.eq("userId", ctx.userId))
.collect();

const quests = Promise.all(
userQuests.map((quest) => ctx.db.get(quest.questId)),
const quests = await Promise.all(
userQuests.map(async (userQuest) => {
const quest = await ctx.db.get(userQuest.questId);
return quest && quest.deletionTime === undefined
? { ...quest, completionTime: userQuest.completionTime }
: null;
}),
);

return (await quests).filter((quest) => quest?.deletionTime === undefined);
return quests.filter((quest) => quest !== null);
},
});

Expand Down
9 changes: 8 additions & 1 deletion convex/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { v } from "convex/values";
import { query } from "./_generated/server";
import type { Role } from "./constants";
import { userMutation, userQuery } from "./helpers";
import { theme } from "./validators";
import { sortQuestsBy, theme } from "./validators";

// TODO: Add `returns` value validation
// https://docs.convex.dev/functions/validation
Expand Down Expand Up @@ -64,6 +64,13 @@ export const setUserTheme = userMutation({
},
});

export const setSortQuestsBy = userMutation({
args: { sortQuestsBy: sortQuestsBy },
handler: async (ctx, args) => {
await ctx.db.patch(ctx.userId, { sortQuestsBy: args.sortQuestsBy });
},
});

// TODO: This throws an error when deleting own account
// Implement RLS check for whether this is the user's own account
// or a different account being deleted by an admin
Expand Down
13 changes: 12 additions & 1 deletion convex/validators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { v } from "convex/values";
import { FIELDS, ICONS, JURISDICTIONS, ROLES, THEMES } from "./constants";
import {
FIELDS,
ICONS,
JURISDICTIONS,
ROLES,
SORT_QUESTS_BY,
THEMES,
} from "./constants";

export const jurisdiction = v.union(
...Object.keys(JURISDICTIONS).map((jurisdiction) => v.literal(jurisdiction)),
Expand All @@ -20,3 +27,7 @@ export const icon = v.union(
export const field = v.union(
...Object.keys(FIELDS).map((field) => v.literal(field)),
);

export const sortQuestsBy = v.union(
...Object.keys(SORT_QUESTS_BY).map((sortQuestsBy) => v.literal(sortQuestsBy)),
);
4 changes: 2 additions & 2 deletions src/components/GridList/GridList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ export function GridList<T extends object>({

const itemStyles = tv({
extend: focusRing,
base: "relative text-gray-normal flex items-center gap-3 cursor-pointer select-none py-2 px-3 text-sm border-y border-gray-dim first:border-t-0 last:border-b-0 first:rounded-t-md last:rounded-b-md -mb-px last:mb-0 -outline-offset-2",
base: "relative text-gray-normal flex items-center gap-3 cursor-pointer select-none py-2 px-3 text-sm first:rounded-t-md last:rounded-b-md -mb-px last:mb-0 -outline-offset-2",
variants: {
isSelected: {
false: "",
true: "bg-purple-subtle border-y border-purple-dim z-20",
true: "bg-purple-3 dark:bg-purple-dark-3 z-20",
},
isDisabled: {
true: "opacity-50 cursor-default forced-colors:text-[GrayText] z-10",
Expand Down
Loading

0 comments on commit b8f60bc

Please sign in to comment.