Skip to content

Commit f559a5d

Browse files
authored
Implement export (#21)
* Add `mobile-web-app-capable` metatag. * Add export data page. * Add form * Add option to change name of exported file * Add a gap. * Implement export with/without notes.
1 parent 658e31f commit f559a5d

File tree

5 files changed

+106
-0
lines changed

5 files changed

+106
-0
lines changed

app/(homescreens)/settings/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export default function Page() {
77
return (
88
<PageTemplate header={<PrimaryHeader title="Settings" />}>
99
<div className="flex flex-col gap-2 px-2 py-3 w-full">
10+
<Link href={Routes.ExportData}>
11+
<button className="btn btn-primary w-full">Export data</button>
12+
</Link>
1013
<Link href={Routes.About}>
1114
<button className="btn btn-primary w-full">About</button>
1215
</Link>

app/export-data/export-data-page.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use client';
2+
import useAppState from '@/app/lib/app-state/app-state';
3+
import { TextField } from '@/app/ui/text-field';
4+
import { omit } from 'lodash';
5+
import { useForm } from 'react-hook-form';
6+
7+
enum ExportFormNames {
8+
WithNotes = 'withNotes',
9+
FileName = 'fileName',
10+
}
11+
12+
type ExportForm = {
13+
[ExportFormNames.WithNotes]: boolean;
14+
[ExportFormNames.FileName]: string;
15+
};
16+
17+
const DEFAULT_FILE_NAME = 'my-loyalty-cards';
18+
19+
export function ExportDataPage() {
20+
const {
21+
register,
22+
handleSubmit,
23+
formState: { errors },
24+
} = useForm<ExportForm>({
25+
defaultValues: {
26+
[ExportFormNames.WithNotes]: false,
27+
[ExportFormNames.FileName]: DEFAULT_FILE_NAME,
28+
},
29+
});
30+
const [state] = useAppState();
31+
const processExport = ({ fileName, withNotes }: ExportForm) => {
32+
console.log(state);
33+
34+
const cardsToExport = state.cards.map(c =>
35+
omit(c, withNotes ? ['id', 'favorite'] : ['id', 'favorite', 'note'])
36+
);
37+
38+
const jsonString = JSON.stringify(cardsToExport, null, 2);
39+
const blob = new Blob([jsonString], { type: 'application/json' });
40+
const a = document.createElement('a');
41+
a.href = URL.createObjectURL(blob);
42+
a.download = `${fileName ? fileName : DEFAULT_FILE_NAME}.json`;
43+
a.click();
44+
45+
URL.revokeObjectURL(a.href);
46+
};
47+
return (
48+
<form
49+
className="px-4 py-6 w-full h-full"
50+
onSubmit={handleSubmit(processExport)}
51+
>
52+
<div className="px-4 py-6 bg-base-300">
53+
<TextField
54+
label="File name"
55+
name={ExportFormNames.FileName}
56+
register={register}
57+
placeholder="Name of file with exported data"
58+
errors={errors}
59+
className="pb-4"
60+
/>
61+
62+
<label className="form-control">
63+
<div className="label">
64+
<span className="label-text">Export with notes</span>
65+
</div>
66+
<input
67+
type="checkbox"
68+
className="toggle"
69+
{...register(ExportFormNames.WithNotes)}
70+
/>
71+
<span className="text-sm pt-2 px-1 text-base-content/50">
72+
Please be aware that your notes may contain sensitive information
73+
such as passwords or PINs. If you choose to include notes in your
74+
export, ensure that the exported data is stored securely and handled
75+
with caution. If you prefer to keep this information private, we
76+
recommend exporting without notes.
77+
</span>
78+
</label>
79+
</div>
80+
<footer className="btm-nav btm-nav-md text-base-content px-4">
81+
<button className="btn btn-primary w-full" type="submit">
82+
Export data
83+
</button>
84+
</footer>
85+
</form>
86+
);
87+
}

app/export-data/page.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { ExportDataPage } from '@/app/export-data/export-data-page';
2+
import { Routes } from '@/app/lib/shared';
3+
import { PageTemplate } from '@/app/ui/page-template';
4+
import { SecondaryHeader } from '@/app/ui/secondary-header';
5+
6+
export default function Page() {
7+
return (
8+
<PageTemplate
9+
header={<SecondaryHeader title="Export data" href={Routes.Settings} />}
10+
>
11+
<ExportDataPage />
12+
</PageTemplate>
13+
);
14+
}

app/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export default function RootLayout({
5050
sizes="180x180"
5151
href="/apple-touch-icon.png"
5252
/>
53+
<meta name="mobile-web-app-capable" content="yes" />
5354
</head>
5455
<body
5556
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-zinc-50 h-full max-h-screen`}

app/lib/shared.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export enum Routes {
2929
Card = '/card',
3030
About = '/about',
3131
AboutAuthor = '/about-author',
32+
ExportData = '/export-data',
3233
}
3334

3435
export const ICON_COLOR = 'oklch(0.4912 0.3096 275.75)';

0 commit comments

Comments
 (0)