Skip to content

Commit

Permalink
add icons, simplify upsert form, update deps
Browse files Browse the repository at this point in the history
  • Loading branch information
vorant94 committed Jun 12, 2024
1 parent d8f38f0 commit 18ecf3b
Show file tree
Hide file tree
Showing 14 changed files with 895 additions and 369 deletions.
6 changes: 3 additions & 3 deletions e2e/subscriptions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import type { InsertSubscriptionModel } from '../src/subscriptions/models/subscr
test('should create subscription', async ({ page }) => {
const formValue = {
name: 'Webstorm',
description: 'JetBrains',
icon: 'webstorm',
description: 'JavaScript/TypeScript IDE',
icon: 'jetbrains',
price: '10',
startedAt: '2024-03-01',
endedAt: '2024-08-01',
Expand All @@ -24,7 +24,7 @@ test('should create subscription', async ({ page }) => {

await page.getByLabel('name').fill(formValue.name);
await page.getByLabel('description').fill(formValue.description);
await page.getByLabel('icon').fill(formValue.icon);
await page.getByLabel('icon').selectOption(formValue.icon);
await page.getByLabel('price').fill(formValue.price);
await page.getByLabel('started at').fill(formValue.startedAt);
await page.getByLabel('ended at').fill(formValue.endedAt);
Expand Down
1,005 changes: 731 additions & 274 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"react-router-dom": "^6.23.1",
"react-use": "^17.5.0",
"recharts": "^2.10.2",
"simple-icons": "^12.2.0",
"tailwind-merge": "^2.3.0",
"zod": "^3.23.8"
},
Expand All @@ -45,7 +46,7 @@
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.11.0",
"@typescript-eslint/parser": "^7.11.0",
"@vitejs/plugin-react": "^4.2.1",
"@vitejs/plugin-react-swc": "^3.7.0",
"autoprefixer": "^10.4.19",
"cssnano": "^7.0.1",
"dotenv": "^16.4.5",
Expand All @@ -66,6 +67,7 @@
"typescript": "^5.2.2",
"vite": "^5.2.0",
"vite-bundle-visualizer": "^1.2.1",
"vite-plugin-svgr": "^4.2.0",
"vitest": "^1.6.0"
},
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion src/db/globals/db.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SubscriptionModel } from '@/subscriptions/models/subscription.model.ts';
import type { SubscriptionModel } from '@/subscriptions/models/subscription.model.tsx';
import Dexie, { type EntityTable } from 'dexie';

export const db = new Dexie('subs-savvy') as Dexie & {
Expand Down
3 changes: 3 additions & 0 deletions src/form/utils/preprocess-nullable-value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function preprocessNullableValue<T = unknown>(value: T): T | null {
return value === '' ? null : value;
}
2 changes: 1 addition & 1 deletion src/subscriptions/components/subscription-graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { differenceInCalendarYears } from 'date-fns';
import { useLiveQuery } from 'dexie-react-hooks';
import { memo, useMemo } from 'react';
import { Bar, BarChart, ResponsiveContainer, XAxis } from 'recharts';
import type { SubscriptionModel } from '../models/subscription.model.ts';
import type { SubscriptionModel } from '../models/subscription.model.tsx';
import { findSubscriptions } from '../models/subscription.table.ts';

export const SubscriptionGraph = memo(() => {
Expand Down
20 changes: 16 additions & 4 deletions src/subscriptions/components/subscription-list-item.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { cn } from '@/ui/utils/cn.ts';
import { Avatar, Card, CardBody, Heading, Text } from '@chakra-ui/react';
import { memo, useCallback, useContext } from 'react';
import type { SubscriptionModel } from '../models/subscription.model.ts';
import {
subscriptionIconToIconElement,
type SubscriptionModel,
} from '../models/subscription.model.tsx';
import { SubscriptionUpsertStateContext } from './subscription-upsert.tsx';

export const SubscriptionListItem = memo(
Expand All @@ -21,17 +24,26 @@ export const SubscriptionListItem = memo(
onClick={openSubscriptionUpdate}>
<CardBody>
<div className={cn(`flex items-center gap-2`)}>
<Avatar size="sm" />
<Avatar
bg="transparent"
size="sm"
icon={subscriptionIconToIconElement[subscription.icon]}
/>

<div className={cn('flex-1')}>
<div className={cn('flex-1 overflow-hidden')}>
<Heading
size="xs"
className={cn(`truncate`)}
textTransform="uppercase">
{subscription.name}
</Heading>

{subscription.description ? (
<Text fontSize="sm">{subscription.description}</Text>
<Text
className={cn(`truncate`)}
fontSize="sm">
{subscription.description}
</Text>
) : null}
</div>

Expand Down
110 changes: 62 additions & 48 deletions src/subscriptions/components/subscription-upsert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
FormControl,
FormLabel,
Input,
Select,
Textarea,
} from '@chakra-ui/react';
import { clsx } from 'clsx';
Expand All @@ -23,11 +24,13 @@ import {
import { useForm, type SubmitHandler } from 'react-hook-form';
import { usePrevious } from 'react-use';
import {
subscriptionIconToLabel,
subscriptionIcons,
type InsertSubscriptionModel,
type SubscriptionModel,
type UpdateSubscriptionModel,
type UpsertSubscriptionModel,
} from '../models/subscription.model.ts';
} from '../models/subscription.model.tsx';
import {
deleteSubscription,
insertSubscription,
Expand Down Expand Up @@ -94,68 +97,79 @@ export const SubscriptionUpsert = memo(() => {
/>

<FormControl>
<FormLabel htmlFor="name">Name</FormLabel>
<Input
{...register('name')}
id="name"
placeholder="name"
type="text"
autoComplete="off"
/>
<FormLabel>
Name
<Input
{...register('name')}
placeholder="Name"
type="text"
autoComplete="off"
/>
</FormLabel>
</FormControl>

<FormControl>
<FormLabel htmlFor="description">Description</FormLabel>
<Textarea
{...register('description')}
id="description"
placeholder="description"
autoComplete="off"
/>
<FormLabel>
Description
<Textarea
{...register('description')}
placeholder="Description"
autoComplete="off"
/>
</FormLabel>
</FormControl>

<FormControl>
<FormLabel htmlFor="icon">Icon</FormLabel>
<Input
{...register('icon')}
id="icon"
placeholder="icon"
type="text"
autoComplete="off"
/>
<FormLabel>
Icon
<Select
{...register('icon')}
placeholder="Icon">
{subscriptionIcons.map((icon) => (
<option
key={icon}
value={icon}>
{subscriptionIconToLabel[icon]}
</option>
))}
</Select>
</FormLabel>
</FormControl>

<FormControl>
<FormLabel htmlFor="price">Price</FormLabel>
<Input
{...register('price')}
id="price"
placeholder="price"
type="number"
autoComplete="off"
/>
<FormLabel>
Price
<Input
{...register('price')}
placeholder="Price"
type="number"
autoComplete="off"
/>
</FormLabel>
</FormControl>

<FormControl>
<FormLabel htmlFor="startedAt">Started At</FormLabel>
<Input
{...register('startedAt')}
id="startedAt"
placeholder="startedAt"
type="date"
autoComplete="off"
/>
<FormLabel>
Started At
<Input
{...register('startedAt')}
placeholder="Started At"
type="date"
autoComplete="off"
/>
</FormLabel>
</FormControl>

<FormControl>
<FormLabel htmlFor="endedAt">Ended At</FormLabel>
<Input
{...register('endedAt')}
id="endedAt"
placeholder="endedAt"
type="date"
autoComplete="off"
/>
<FormLabel>
Ended At
<Input
{...register('endedAt')}
placeholder="Ended At"
type="date"
autoComplete="off"
/>
</FormLabel>
</FormControl>

<div className={cn('flex gap-2')}>
Expand Down
6 changes: 3 additions & 3 deletions src/subscriptions/models/subscription.mock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { setMonth } from 'date-fns';
import type { SubscriptionModel } from './subscription.model.ts';
import type { SubscriptionModel } from './subscription.model.tsx';

export const minimalSubscription = {
id: 1,
Expand All @@ -12,7 +12,7 @@ export const subscriptionWithDescription = {
id: 2,
name: 'WebStorm',
price: 5.0,
icon: 'intellij',
icon: 'jetbrains',
description: 'Jetbrains',
} as const satisfies SubscriptionModel;

Expand All @@ -28,7 +28,7 @@ export const subscriptionWithEndedAt = {
id: 4,
name: 'YouTube',
price: 5.0,
icon: 'google',
icon: 'youtube',
endedAt: setMonth(new Date(), 9),
} as const satisfies SubscriptionModel;

Expand Down
30 changes: 0 additions & 30 deletions src/subscriptions/models/subscription.model.ts

This file was deleted.

63 changes: 63 additions & 0 deletions src/subscriptions/models/subscription.model.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { preprocessNullableValue } from '@/form/utils/preprocess-nullable-value.ts';
import { cn } from '@/ui/utils/cn.ts';
import type { ReactElement } from 'react';
import GitHub from 'simple-icons/icons/github.svg?react';
import JetBrains from 'simple-icons/icons/jetbrains.svg?react';
import Netflix from 'simple-icons/icons/netflix.svg?react';
import Telegram from 'simple-icons/icons/telegram.svg?react';
import YouTube from 'simple-icons/icons/youtube.svg?react';
import { z } from 'zod';

export const subscriptionIcons = [
'telegram',
'netflix',
'jetbrains',
'github',
'youtube',
] as const;
export type SubscriptionIcon = (typeof subscriptionIcons)[number];

export const subscriptionSchema = z.object({
// Coercion is needed because <input/> with type="number" still returns string as a value
id: z.coerce.number(),
name: z.string().min(1),
price: z.coerce.number(),
icon: z.enum(subscriptionIcons),
description: z.string().nullable().optional(),
// Preprocessing is needed because <input/> with type="date" returns empty string as a value and it is not a valid date
startedAt: z.preprocess(
preprocessNullableValue,
z.coerce.date().nullable().optional(),
),
endedAt: z.preprocess(
preprocessNullableValue,
z.coerce.date().nullable().optional(),
),
});
export type SubscriptionModel = z.infer<typeof subscriptionSchema>;

export const insertSubscriptionSchema = subscriptionSchema.omit({ id: true });
export type InsertSubscriptionModel = z.infer<typeof insertSubscriptionSchema>;

export const updateSubscriptionSchema = subscriptionSchema;
export type UpdateSubscriptionModel = z.infer<typeof updateSubscriptionSchema>;

export type UpsertSubscriptionModel =
| InsertSubscriptionModel
| UpdateSubscriptionModel;

export const subscriptionIconToIconElement = {
telegram: <Telegram className={cn(`fill-[#26A5E4]`)} />,
netflix: <Netflix className={cn(`fill-[#E50914]`)} />,
jetbrains: <JetBrains className={cn(`fill-[#000000]`)} />,
github: <GitHub className={cn(`fill-[#181717]`)} />,
youtube: <YouTube className={cn(`fill-[#FF0000]`)} />,
} as const satisfies Record<SubscriptionIcon, ReactElement>;

export const subscriptionIconToLabel = {
telegram: 'Telegram',
netflix: 'Netflix',
jetbrains: 'JetBrains',
github: 'GitHub',
youtube: 'YouTube',
} as const satisfies Record<SubscriptionIcon, string>;
2 changes: 1 addition & 1 deletion src/subscriptions/models/subscription.table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
type InsertSubscriptionModel,
type SubscriptionModel,
type UpdateSubscriptionModel,
} from './subscription.model.ts';
} from './subscription.model.tsx';

export async function findSubscriptions(): Promise<Array<SubscriptionModel>> {
const raws = await db.subscriptions.toArray();
Expand Down
6 changes: 5 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@
"allowImportingTsExtensions": true,

/* Misc */
"types": ["vite/client", "@total-typescript/ts-reset"],
"types": [
"vite/client",
"vite-plugin-svgr/client",
"@total-typescript/ts-reset"
],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
Expand Down
Loading

0 comments on commit 18ecf3b

Please sign in to comment.