Skip to content

Commit 5325fcf

Browse files
authored
Task 255 add more categories (#258)
* Type safe category icons * Use default category variable instead of magic string * Display the general category in picker * Add missing sw categories and assign unique icons * Resolve type issues between string and categoryItem * Replace any with unknown
1 parent 3260be2 commit 5325fcf

8 files changed

Lines changed: 162 additions & 129 deletions

File tree

Lines changed: 21 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
import { Banknote } from 'lucide-react';
2-
import { useMemo } from 'react';
1+
import { CATEGORIES, type CategoryItem } from '~/lib/category';
32

43
import { Button } from '../ui/button';
5-
import { CategoryIcons } from '../ui/categoryIcons';
4+
import { CategoryIcon } from '../ui/categoryIcons';
65
import { AppDrawer, DrawerClose } from '../ui/drawer';
76

87
export const CategoryPicker: React.FC<{
98
category: string;
109
onCategoryPick: (category: string) => void;
1110
}> = ({ category, onCategoryPick }) => {
12-
const CategoryIcon = useMemo(() => CategoryIcons[category] ?? Banknote, [category]);
13-
1411
return (
1512
<AppDrawer
1613
trigger={
1714
<div className="flex w-[70px] justify-center rounded-lg border py-2">
18-
<CategoryIcon size={20} />
15+
<CategoryIcon category={category} size={20} />
1916
</div>
2017
}
2118
title="Categories"
@@ -27,117 +24,28 @@ export const CategoryPicker: React.FC<{
2724
<div key={categoryName} className="mb-8">
2825
<h3 className="mb-4 text-lg font-semibold">{categoryDetails.name}</h3>
2926
<div className="flex flex-wrap justify-between gap-2">
30-
{categoryDetails.items.map((item) =>
31-
Object.entries(item).map(([key, value]) => {
32-
const Icon = CategoryIcons[key] ?? CategoryIcons[categoryName] ?? Banknote;
33-
return (
34-
<DrawerClose key={key}>
35-
<Button
36-
variant="ghost"
37-
className="flex w-[75px] flex-col gap-1 py-8 text-center"
38-
onClick={() => {
39-
onCategoryPick(key === 'other' ? categoryName : key);
40-
}}
41-
>
42-
<span className="block text-2xl">
43-
<Icon />
44-
</span>
45-
<span className="block text-xs capitalize">{value}</span>
46-
</Button>
47-
</DrawerClose>
48-
);
49-
}),
50-
)}
27+
{Object.entries(categoryDetails.items).map(([key, value]) => (
28+
<DrawerClose key={key}>
29+
<Button
30+
variant="ghost"
31+
className="flex w-[75px] flex-col gap-1 py-8 text-center"
32+
onClick={() => {
33+
onCategoryPick(key === 'other' ? categoryName : key);
34+
}}
35+
>
36+
<span className="block text-2xl">
37+
<CategoryIcon
38+
category={(key === 'other' ? categoryName : key) as CategoryItem}
39+
/>
40+
</span>
41+
<span className="block text-xs capitalize">{value}</span>
42+
</Button>
43+
</DrawerClose>
44+
))}
5145
</div>
5246
</div>
5347
);
5448
})}
5549
</AppDrawer>
5650
);
5751
};
58-
59-
const CATEGORIES = {
60-
entertainment: {
61-
name: 'Entertainment',
62-
items: [
63-
{
64-
games: 'Games',
65-
movies: 'Movies',
66-
music: 'Music',
67-
sports: 'Sports',
68-
other: 'Entertainment',
69-
},
70-
],
71-
},
72-
food: {
73-
name: 'Food & Drinks',
74-
items: [
75-
{
76-
diningOut: 'Dining Out',
77-
groceries: 'Groceries',
78-
liquor: 'Liquor',
79-
other: 'Food & Drinks',
80-
},
81-
],
82-
},
83-
home: {
84-
name: 'Home',
85-
items: [
86-
{
87-
electronics: 'Electronics',
88-
furniture: 'Furniture',
89-
supplies: 'Supplies',
90-
maintenance: 'Maintenance',
91-
mortgage: 'Mortgage',
92-
pets: 'Pets',
93-
rent: 'Rent',
94-
services: 'Services',
95-
other: 'Home',
96-
},
97-
],
98-
},
99-
life: {
100-
name: 'Life',
101-
items: [
102-
{
103-
childcare: 'Childcare',
104-
clothing: 'Clothing',
105-
education: 'Education',
106-
gifts: 'Gifts',
107-
medical: 'Medical',
108-
taxes: 'Taxes',
109-
other: 'Life',
110-
},
111-
],
112-
},
113-
travel: {
114-
name: 'Travel',
115-
items: [
116-
{
117-
bus: 'Bus',
118-
train: 'Train',
119-
car: 'Car',
120-
fuel: 'Fuel',
121-
parking: 'Parking',
122-
plane: 'Plane',
123-
taxi: 'Taxi',
124-
other: 'Travel',
125-
},
126-
],
127-
},
128-
utilities: {
129-
name: 'Utilities',
130-
items: [
131-
{
132-
cleaning: 'Cleaning',
133-
electricity: 'Electricity',
134-
gas: 'Gas',
135-
internet: 'Internet',
136-
trash: 'Trash',
137-
phone: 'Phone',
138-
water: 'Water',
139-
other: 'Utilities',
140-
},
141-
],
142-
},
143-
};

src/components/Expense/ExpensePage.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { type Expense, type ExpenseParticipant, type User } from '@prisma/client';
22
import { format, isSameDay } from 'date-fns';
3-
import { Banknote } from 'lucide-react';
43
import Image from 'next/image';
54
import { type User as NextUser } from 'next-auth';
65
import React from 'react';
@@ -9,7 +8,7 @@ import React from 'react';
98
import { toUIString } from '~/utils/numbers';
109

1110
import { UserAvatar } from '../ui/avatar';
12-
import { CategoryIcons } from '../ui/categoryIcons';
11+
import { CategoryIcon } from '../ui/categoryIcons';
1312
import { AppDrawer } from '../ui/drawer';
1413
import { Separator } from '../ui/separator';
1514

@@ -28,16 +27,14 @@ type ExpenseDetailsProps = {
2827
const ExpenseDetails: React.FC<ExpenseDetailsProps> = ({ user, expense, storagePublicUrl }) => {
2928
const youPaid = expense.paidBy === user.id;
3029

31-
const CategoryIcon = CategoryIcons[expense.category] ?? Banknote;
32-
3330
// const sendNotificationMutation = api.user.sendExpensePushNotification.useMutation();
3431

3532
return (
3633
<div className="">
3734
<div className="mb-4 flex items-start justify-between gap-2 px-6">
3835
<div className="flex items-start gap-4">
3936
<div className="rounded-lg border p-2 text-xl">
40-
<CategoryIcon className="text-gray-400" size={24} />
37+
<CategoryIcon category={expense.category} className="text-gray-400" size={24} />
4138
</div>
4239
<div className="flex flex-col gap-2">
4340
<p className="">{expense.name}</p>

src/components/Friend/GroupSettleup.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ArrowRightIcon } from 'lucide-react';
33
import React, { type ReactNode, useCallback, useState } from 'react';
44
import { toast } from 'sonner';
55

6+
import { DEFAULT_CATEGORY } from '~/lib/category';
67
import { api } from '~/utils/api';
78
import { BigMath, toSafeBigInt } from '~/utils/numbers';
89
import { displayName } from '~/utils/strings';
@@ -51,7 +52,7 @@ export const GroupSettleUp: React.FC<{
5152
},
5253
],
5354
paidBy: sender.id,
54-
category: 'general',
55+
category: DEFAULT_CATEGORY,
5556
},
5657
{
5758
onSuccess: () => {

src/components/Friend/Settleup.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { type User as NextUser } from 'next-auth';
44
import React, { useState } from 'react';
55
import { toast } from 'sonner';
66

7+
import { DEFAULT_CATEGORY } from '~/lib/category';
78
import { api } from '~/utils/api';
89
import { BigMath, toSafeBigInt, toUIString } from '~/utils/numbers';
910

@@ -57,7 +58,7 @@ export const SettleUp: React.FC<{
5758
},
5859
],
5960
paidBy: isCurrentUserPaying ? currentUser.id : friend.id,
60-
category: 'general',
61+
category: DEFAULT_CATEGORY,
6162
},
6263
{
6364
onSuccess: () => {

src/components/ui/categoryIcons.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import {
22
Baby,
33
Backpack,
44
Banknote,
5+
Bike,
56
Bone,
67
Bus,
78
Car,
89
CarTaxiFront,
910
Construction,
11+
DoorOpen,
1012
FerrisWheel,
1113
Flame,
1214
Fuel,
@@ -18,15 +20,20 @@ import {
1820
Hammer,
1921
HandIcon,
2022
Home,
23+
Hotel,
2124
type LucideIcon,
25+
type LucideProps,
2226
Music,
2327
Paintbrush,
28+
Paintbrush2,
2429
ParkingCircle,
2530
Phone,
2631
Pizza,
2732
Plane,
2833
Plug,
2934
Popcorn,
35+
ReceiptText,
36+
Shield,
3037
Shirt,
3138
ShoppingCart,
3239
Sofa,
@@ -41,7 +48,9 @@ import {
4148
Zap,
4249
} from 'lucide-react';
4350

44-
export const CategoryIcons: Record<string, LucideIcon> = {
51+
import { type CategoryItem, DEFAULT_CATEGORY } from '~/lib/category';
52+
53+
export const CategoryIcons: Record<CategoryItem, LucideIcon> = {
4554
games: Gamepad2,
4655
movies: Popcorn,
4756
music: Music,
@@ -54,19 +63,19 @@ export const CategoryIcons: Record<string, LucideIcon> = {
5463
home: Home,
5564
electronics: Plug,
5665
furniture: Sofa,
57-
supplies: Sofa,
66+
supplies: Paintbrush2,
5867
maintenance: Construction,
5968
mortgage: HandIcon,
6069
pets: Bone,
61-
rent: Home,
70+
rent: DoorOpen,
6271
services: Hammer,
6372
life: Sprout,
6473
childcare: Baby,
6574
clothing: Shirt,
6675
education: GraduationCap,
6776
gifts: Gift,
6877
medical: Stethoscope,
69-
taxes: Banknote,
78+
taxes: ReceiptText,
7079
travel: Backpack,
7180
bus: Bus,
7281
train: TrainFront,
@@ -84,13 +93,18 @@ export const CategoryIcons: Record<string, LucideIcon> = {
8493
general: Banknote,
8594
cleaning: Paintbrush,
8695
trash: Trash,
96+
insurance: Shield,
97+
bicycle: Bike,
98+
hotel: Hotel,
8799
};
88100

89-
export const CategoryIcon: React.FC<{ category: string; className?: string }> = ({
90-
category,
101+
export const DEFAULT_CATEGORY_ICON = CategoryIcons[DEFAULT_CATEGORY];
102+
103+
export const CategoryIcon: React.FC<{ category?: string } & LucideProps> = ({
104+
category = DEFAULT_CATEGORY,
91105
...props
92106
}) => {
93-
const Icon = CategoryIcons[category] ?? Banknote;
107+
const Icon = CategoryIcons[category as CategoryItem] ?? DEFAULT_CATEGORY_ICON;
94108

95109
return <Icon {...props} />;
96110
};

0 commit comments

Comments
 (0)