Skip to content

Commit

Permalink
rename subscription bulk to recovery
Browse files Browse the repository at this point in the history
  • Loading branch information
vorant94 committed Jun 24, 2024
1 parent 6340959 commit 8b926f0
Show file tree
Hide file tree
Showing 14 changed files with 259 additions and 201 deletions.
17 changes: 11 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import {
NavLinksProvider,
type NavLink,
} from '@/router/hooks/use-nav-links.tsx';
import { route } from '@/router/types/route.ts';
import { SubscriptionUpsertProvider } from '@/subscriptions/hooks/use-subscription-upsert.tsx';
import { SubscriptionsProvider } from '@/subscriptions/hooks/use-subscriptions.tsx';
import { DefaultLayoutProvider } from '@/ui/hooks/use-default-layout.tsx';
import { faChartSimple, faCreditCard } from '@fortawesome/free-solid-svg-icons';
import {
faChartSimple,
faClockRotateLeft,
faCreditCard,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { memo } from 'react';
import { Outlet } from 'react-router-dom';
Expand All @@ -29,20 +34,20 @@ export const App = memo(() => {
const topNavLinks = [
{
label: 'Dashboard',
path: '/dashboard',
path: `/${route.dashboard}`,
icon: <FontAwesomeIcon icon={faChartSimple} />,
},
{
label: 'Subscriptions',
path: '/subscriptions',
path: `/${route.subscriptions}`,
icon: <FontAwesomeIcon icon={faCreditCard} />,
},
] as const satisfies Array<NavLink>;

const bottomNavLinks = [
{
label: 'Bulk Actions',
path: '/subscriptions-bulk',
icon: <FontAwesomeIcon icon={faChartSimple} />,
label: 'Recovery',
path: `/${route.recovery}`,
icon: <FontAwesomeIcon icon={faClockRotateLeft} />,
},
] as const satisfies Array<NavLink>;
4 changes: 3 additions & 1 deletion src/db/globals/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import type { SubscriptionModel } from '@/subscriptions/models/subscription.mode
import type { TagModel } from '@/tags/models/tag.model.ts';
import Dexie, { type EntityTable, type Table } from 'dexie';

export const dbVersion = 3;

export const db = new Dexie('subs-savvy') as Dexie & {
subscriptions: EntityTable<Omit<SubscriptionModel, 'tags'>, 'id'>;
tags: EntityTable<TagModel, 'id'>;
subscriptionsTags: Table<SubscriptionTagModel, [number, number]>;
};

db.version(3).stores({
db.version(dbVersion).stores({
subscriptions: '++id',
tags: '++id',
subscriptionsTags: '[subscriptionId+tagId],subscriptionId,tagId',
Expand Down
41 changes: 31 additions & 10 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { recoveryRoute } from '@/recovery/types/recovery-route.ts';
import { route } from '@/router/types/route.ts';
import { MantineProvider } from '@mantine/core';
import { StrictMode } from 'react';
Expand Down Expand Up @@ -28,36 +29,56 @@ const router = createBrowserRouter([
Component: App,
children: [
{
path: route.root,
path: '/',
element: (
<Navigate
to="/dashboard"
to={`/${route.dashboard}`}
replace
/>
),
},
{
path: route.dashboard,
path: `/${route.dashboard}`,
lazy: () =>
import(`@/dashboard/pages/dashboard.page.tsx`).then((m) => ({
Component: m.DashboardPage,
})),
},
{
path: route.subscriptions,
path: `/${route.subscriptions}`,
lazy: () =>
import(`@/subscriptions/pages/subscriptions.page.tsx`).then((m) => ({
Component: m.SubscriptionsPage,
})),
},
{
path: route.subscriptionsBulk,
path: `/${route.recovery}`,
lazy: () =>
import(`@/subscriptions/pages/subscriptions-bulk.page.tsx`).then(
(m) => ({
Component: m.SubscriptionsBulkPage,
}),
),
import(`@/recovery/pages/recovery.page.tsx`).then((m) => ({
Component: m.RecoveryPage,
})),
children: [
{
path: `/${route.recovery}`,
element: (
<Navigate to={`/${route.recovery}/${recoveryRoute.import}`} />
),
},
{
path: `/${route.recovery}/${recoveryRoute.import}`,
lazy: () =>
import(`@/recovery/pages/recovery-import.page.tsx`).then((m) => ({
Component: m.RecoveryImportPage,
})),
},
{
path: `/${route.recovery}/${recoveryRoute.export}`,
lazy: () =>
import(`@/recovery/pages/recovery-export.page.tsx`).then((m) => ({
Component: m.RecoveryExportPage,
})),
},
],
},
],
},
Expand Down
14 changes: 14 additions & 0 deletions src/recovery/models/recovery.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { subscriptionSchema } from '@/subscriptions/models/subscription.model.ts';
import { z } from 'zod';

// TODO add support for exporting with tags as well
export const recoverySchema = z.object({
dbVersion: z.number(),
subscriptions: z.array(
subscriptionSchema.omit({
id: true,
tags: true,
}),
),
});
export type RecoveryModel = z.infer<typeof recoverySchema>;
65 changes: 65 additions & 0 deletions src/recovery/pages/recovery-export.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { dbVersion } from '@/db/globals/db.ts';
import { SubscriptionsSelectTable } from '@/subscriptions/components/subscriptions-select-table.tsx';
import { findSubscriptions } from '@/subscriptions/models/subscription.table.ts';
import { cn } from '@/ui/utils/cn.ts';
import { Button, Switch } from '@mantine/core';
import { useLiveQuery } from 'dexie-react-hooks';
import { memo, useCallback, useState } from 'react';
import { recoverySchema } from '../models/recovery.model.ts';

export const RecoveryExportPage = memo(() => {
const subscriptions = useLiveQuery(() => findSubscriptions(), [], []);
const [selectedIds, setSelectedIds] = useState<number[]>([]);

const [isPrettify, setIsPrettify] = useState(true);
const toggleIsPrettify = useCallback(() => {
setIsPrettify(!isPrettify);
}, [isPrettify]);

const exportSubscriptions = useCallback(() => {
const subscriptionsToExport = subscriptions.filter((subscription) =>
selectedIds.includes(subscription.id),
);
const subscriptionsExport = recoverySchema.parse({
dbVersion,
subscriptions: subscriptionsToExport,
});
const jsonToExport = isPrettify
? JSON.stringify(subscriptionsExport, null, 2)
: JSON.stringify(subscriptionsExport);
const blobToExport = new Blob([jsonToExport], { type: 'application/json' });

const exportLink = document.createElement('a');
exportLink.href = URL.createObjectURL(blobToExport);
exportLink.download = 'subscriptions.json';
document.body.appendChild(exportLink);
exportLink.click();
document.body.removeChild(exportLink);
}, [isPrettify, selectedIds, subscriptions]);

return (
<div className={cn(`flex flex-col gap-4`)}>
<SubscriptionsSelectTable
subscriptions={subscriptions}
selectedIds={selectedIds}
setSelectedIds={setSelectedIds}
/>

<div className={cn(`flex items-center`)}>
<Switch
checked={isPrettify}
onChange={toggleIsPrettify}
label="Prettify"
/>

<div className={cn('flex-1')} />

<Button
disabled={selectedIds.length === 0}
onClick={exportSubscriptions}>
export
</Button>
</div>
</div>
);
});
6 changes: 6 additions & 0 deletions src/recovery/pages/recovery-import.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { cn } from '@/ui/utils/cn.ts';
import { memo } from 'react';

export const RecoveryImportPage = memo(() => {
return <div className={cn(`flex flex-col gap-4`)}>Work is in progress</div>;
});
53 changes: 53 additions & 0 deletions src/recovery/pages/recovery.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { route } from '@/router/types/route.ts';
import {
DefaultLayout,
DefaultLayoutHeader,
} from '@/ui/layouts/default.layout.tsx';
import { faDownload, faUpload } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Card, Tabs } from '@mantine/core';
import { memo, useCallback, useMemo } from 'react';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import { recoveryRoute } from '../types/recovery-route.ts';

export const RecoveryPage = memo(() => {
const { pathname } = useLocation();
const activeTab = useMemo(() => {
return pathname.split('/').at(-1);
}, [pathname]);

const navigate = useNavigate();
const navigateToTab = useCallback(
(tab: string | null) => navigate(`/${route.recovery}/${tab}`),
[navigate],
);

return (
<DefaultLayout header={<DefaultLayoutHeader />}>
<Card
shadow="xs"
padding="xs"
radius="md"
withBorder>
<Tabs
value={activeTab}
onChange={navigateToTab}>
<Tabs.List>
<Tabs.Tab
value={recoveryRoute.import}
leftSection={<FontAwesomeIcon icon={faUpload} />}>
Import
</Tabs.Tab>
<Tabs.Tab
value={recoveryRoute.export}
leftSection={<FontAwesomeIcon icon={faDownload} />}>
Export
</Tabs.Tab>
</Tabs.List>
</Tabs>

<Outlet />
</Card>
</DefaultLayout>
);
});
6 changes: 6 additions & 0 deletions src/recovery/types/recovery-route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const recoveryRoute = {
import: 'import',
export: 'export',
} as const;

export type RecoveryRoute = (typeof recoveryRoute)[keyof typeof recoveryRoute];
3 changes: 1 addition & 2 deletions src/router/hooks/use-nav-links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
type PropsWithChildren,
type ReactNode,
} from 'react';
import type { Route } from '../types/route.ts';

export function useNavLinks(): UseNavLinks {
return useContext(navLinksContext);
Expand All @@ -18,7 +17,7 @@ export interface UseNavLinks {

export interface NavLink {
label: string;
path: Route | string;
path: string;
icon: ReactNode;
}

Expand Down
7 changes: 3 additions & 4 deletions src/router/types/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
export const route = {
root: '/',
dashboard: '/dashboard',
subscriptions: '/subscriptions',
subscriptionsBulk: '/subscriptions-bulk',
dashboard: 'dashboard',
subscriptions: 'subscriptions',
recovery: 'recovery',
} as const;

export type Route = (typeof route)[keyof typeof route];
Loading

0 comments on commit 8b926f0

Please sign in to comment.