-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.notes.index.tsx
139 lines (129 loc) · 4.54 KB
/
app.notes.index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import type { ActionArgs, LoaderArgs, MetaFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import {
Form,
Link,
useFetcher,
useLoaderData,
useTransition,
} from "@remix-run/react";
import { useEffect } from "react";
import toast from "react-hot-toast";
import { notFound } from "remix-utils";
import { requireAuth } from "~/auth.server";
import { deleteNote, searchNotes } from "~/models/note.server";
import { requireHttpPost } from "~/utils";
export const meta: MetaFunction = () => {
return {
title: "Notes Home",
};
};
export async function loader({ request }: LoaderArgs) {
const { userId } = await requireAuth(request);
const url = new URL(request.url);
const query = url.searchParams.get("q") || "";
const notes = await searchNotes(userId, query);
if (!notes || notes.length === 0)
return json({ notes: [], query, status: "noResults" });
return json({ notes, query, status: "resultsFound" });
}
export async function action({ request }: ActionArgs) {
requireHttpPost(request);
const { userId } = await requireAuth(request);
const noteId = (await request.formData()).get("noteId");
if (!noteId) throw notFound({ noteId });
try {
await deleteNote(noteId.toString(), userId);
return json({ ok: true });
} catch (error) {
return json({ error: (error as Error).message });
}
}
export default function NotesHome() {
const { notes, query, status } = useLoaderData<typeof loader>();
const fetcher = useFetcher();
const transition = useTransition();
useEffect(() => {
if (fetcher.state !== "idle" || fetcher.type !== "done") return;
if (fetcher.data.ok) {
toast.success("Note successfully deleted");
} else {
toast.error(`Error deleting note: ${fetcher.data.error}`);
}
}, [fetcher.state, fetcher.type, fetcher.data]);
return (
<main className="container mx-auto space-y-4">
<h1 className="text-lg font-medium">My Notes</h1>
<div className="flex w-full flex-col items-center justify-between gap-3 sm:flex-row">
<Form method="get" className="w-full">
<div className="form-control w-full sm:max-w-sm">
<label className="label">
<span className="label-text">Search Note</span>
</label>
<div className="flex">
<input
defaultValue={query}
type="search"
name="q"
autoComplete="off"
className="input input-bordered flex-1"
placeholder="Search for a note..."
/>
<button className="btn ml-3">Search</button>
</div>
</div>
</Form>
<Link to="/app/notes/new" className="btn btn-primary w-full sm:w-fit">
New Note
</Link>
</div>
{transition.state === "submitting" && (
<div className="grid w-full grid-cols-1 gap-5 sm:grid-cols-4">
<div className="h-96 w-96 animate-pulse" />
</div>
)}
{transition.state !== "submitting" && status === "noResults" && (
<div className="flex items-center justify-center">
<p>No results found</p>
</div>
)}
{transition.state !== "submitting" && status === "resultsFound" && (
<div className="grid w-full grid-cols-1 gap-5 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{notes.map((note) => (
<div
key={note.id}
className="sm:w-92 card card-compact w-full border border-base-300 bg-base-300 shadow-md"
>
<figure>
<img
src="https://placeimg.com/400/225/arch"
alt="midday in soho"
className="w-full"
/>
</figure>
<div className="card-body">
<h2 className="card-title">{note.name}</h2>
{note.description && (
<p className="card-body truncate">{note.description}</p>
)}
<div className="card-actions flex-row-reverse">
<Link to={`/app/note/${note.id}`} className="btn btn-sm">
View
</Link>
<button
onClick={() => {
fetcher.submit({ noteId: note.id }, { method: "post" });
}}
className="btn btn-secondary btn-sm"
>
Remove
</button>
</div>
</div>
</div>
))}
</div>
)}
</main>
);
}