mirror of
https://gitlab.com/MisterBiggs/hello-remix.git
synced 2025-06-15 13:06:40 +00:00
I got bored and just started reading but fun tut
This commit is contained in:
parent
f70bd9680d
commit
cc898b4b08
75
app/root.tsx
75
app/root.tsx
@ -1,12 +1,40 @@
|
|||||||
|
import type { LinksFunction } from "@remix-run/node";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
|
Link,
|
||||||
Links,
|
Links,
|
||||||
Meta,
|
Meta,
|
||||||
|
NavLink,
|
||||||
|
Outlet,
|
||||||
Scripts,
|
Scripts,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
|
useLoaderData,
|
||||||
|
useNavigation,
|
||||||
} from "@remix-run/react";
|
} from "@remix-run/react";
|
||||||
|
|
||||||
|
import { redirect } from "@remix-run/node";
|
||||||
|
|
||||||
|
import appStylesHref from "./app.css?url"
|
||||||
|
import { getContacts, createEmptyContact } from "./data";
|
||||||
|
|
||||||
|
export const links: LinksFunction = () => [
|
||||||
|
{ rel: "stylesheet", href: appStylesHref },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const action = async () => {
|
||||||
|
const contact = await createEmptyContact();
|
||||||
|
return redirect(`/contacts/${contact.id}/edit`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loader = async () => {
|
||||||
|
const contacts = await getContacts();
|
||||||
|
return { contacts };
|
||||||
|
};
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const { contacts } = useLoaderData<typeof loader>();
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -34,16 +62,47 @@ export default function App() {
|
|||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
{contacts.length ? (
|
||||||
<li>
|
<ul>
|
||||||
<a href={`/contacts/1`}>Your Name</a>
|
{contacts.map((contact) => (
|
||||||
</li>
|
<li key={contact.id}> <NavLink
|
||||||
<li>
|
className={({ isActive, isPending }) =>
|
||||||
<a href={`/contacts/2`}>Your Friend</a>
|
isActive
|
||||||
</li>
|
? "active"
|
||||||
</ul>
|
: isPending
|
||||||
|
? "pending"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
to={`contacts/${contact.id}`}
|
||||||
|
>
|
||||||
|
<Link to={`contacts/${contact.id}`}>
|
||||||
|
{contact.first || contact.last ? (
|
||||||
|
<>
|
||||||
|
{contact.first} {contact.last}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<i>No Name</i>
|
||||||
|
)}{" "}
|
||||||
|
{contact.favorite ? (
|
||||||
|
<span>★</span>
|
||||||
|
) : null}
|
||||||
|
</Link>
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
<i>No contacts</i>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={
|
||||||
|
navigation.state === "loading" ? "loading" : ""
|
||||||
|
} id="detail">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
|
102
app/routes/contacts.$contactId.tsx
Normal file
102
app/routes/contacts.$contactId.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { Form, useLoaderData } from "@remix-run/react";
|
||||||
|
import type { FunctionComponent } from "react";
|
||||||
|
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||||
|
|
||||||
|
import type { ContactRecord } from "../data";
|
||||||
|
import { getContact } from "../data";
|
||||||
|
import invariant from "tiny-invariant";
|
||||||
|
|
||||||
|
export const loader = async ({
|
||||||
|
params,
|
||||||
|
}: LoaderFunctionArgs) => {
|
||||||
|
invariant(params.contactId, "Missing contactId param");
|
||||||
|
const contact = await getContact(params.contactId);
|
||||||
|
if (!contact) {
|
||||||
|
throw new Response("Not Found", {status: 404});
|
||||||
|
}
|
||||||
|
return { contact };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Contact() {
|
||||||
|
const { contact } = useLoaderData<typeof loader>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="contact">
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
alt={`${contact.first} ${contact.last} avatar`}
|
||||||
|
key={contact.avatar}
|
||||||
|
src={contact.avatar}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>
|
||||||
|
{contact.first || contact.last ? (
|
||||||
|
<>
|
||||||
|
{contact.first} {contact.last}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<i>No Name</i>
|
||||||
|
)}{" "}
|
||||||
|
<Favorite contact={contact} />
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{contact.twitter ? (
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href={`https://twitter.com/${contact.twitter}`}
|
||||||
|
>
|
||||||
|
{contact.twitter}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{contact.notes ? <p>{contact.notes}</p> : null}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Form action="edit">
|
||||||
|
<button type="submit">Edit</button>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<Form
|
||||||
|
action="destroy"
|
||||||
|
method="post"
|
||||||
|
onSubmit={(event) => {
|
||||||
|
const response = confirm(
|
||||||
|
"Please confirm you want to delete this record."
|
||||||
|
);
|
||||||
|
if (!response) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button type="submit">Delete</button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Favorite: FunctionComponent<{
|
||||||
|
contact: Pick<ContactRecord, "favorite">;
|
||||||
|
}> = ({ contact }) => {
|
||||||
|
const favorite = contact.favorite;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form method="post">
|
||||||
|
<button
|
||||||
|
aria-label={
|
||||||
|
favorite
|
||||||
|
? "Remove from favorites"
|
||||||
|
: "Add to favorites"
|
||||||
|
}
|
||||||
|
name="favorite"
|
||||||
|
value={favorite ? "false" : "true"}
|
||||||
|
>
|
||||||
|
{favorite ? "★" : "☆"}
|
||||||
|
</button>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
13
app/routes/contacts.$contactId_.destroy.tsx
Normal file
13
app/routes/contacts.$contactId_.destroy.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { ActionFunctionArgs } from "@remix-run/node";
|
||||||
|
import { redirect } from "@remix-run/node";
|
||||||
|
import invariant from "tiny-invariant";
|
||||||
|
|
||||||
|
import { deleteContact } from "../data";
|
||||||
|
|
||||||
|
export const action = async ({
|
||||||
|
params,
|
||||||
|
}: ActionFunctionArgs) => {
|
||||||
|
invariant(params.contactId, "Missing contactId param");
|
||||||
|
await deleteContact(params.contactId);
|
||||||
|
return redirect("/");
|
||||||
|
};
|
85
app/routes/contacts.$contactId_.edit.tsx
Normal file
85
app/routes/contacts.$contactId_.edit.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
|
||||||
|
import { Form, useLoaderData } from "@remix-run/react";
|
||||||
|
import invariant from "tiny-invariant";
|
||||||
|
|
||||||
|
import { getContact, updateContact } from "../data";
|
||||||
|
import { redirect } from "@remix-run/node";
|
||||||
|
|
||||||
|
export const loader = async ({
|
||||||
|
params,
|
||||||
|
}: LoaderFunctionArgs) => {
|
||||||
|
invariant(params.contactId, "Missing contactId param");
|
||||||
|
const contact = await getContact(params.contactId);
|
||||||
|
if (!contact) {
|
||||||
|
throw new Response("Not Found", { status: 404 });
|
||||||
|
}
|
||||||
|
return { contact };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const action = async ({
|
||||||
|
params,
|
||||||
|
request,
|
||||||
|
}: ActionFunctionArgs) => {
|
||||||
|
invariant(params.contactId, "Missing contactId param");
|
||||||
|
const formData = await request.formData();
|
||||||
|
const updates = Object.fromEntries(formData);
|
||||||
|
await updateContact(params.contactId, updates);
|
||||||
|
return redirect(`/contacts/${params.contactId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function EditContact() {
|
||||||
|
const { contact } = useLoaderData<typeof loader>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form key={contact.id} id="contact-form" method="post">
|
||||||
|
<p>
|
||||||
|
<span>Name</span>
|
||||||
|
<input
|
||||||
|
aria-label="First name"
|
||||||
|
defaultValue={contact.first}
|
||||||
|
name="first"
|
||||||
|
placeholder="First"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
aria-label="Last name"
|
||||||
|
defaultValue={contact.last}
|
||||||
|
name="last"
|
||||||
|
placeholder="Last"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<label>
|
||||||
|
<span>Twitter</span>
|
||||||
|
<input
|
||||||
|
defaultValue={contact.twitter}
|
||||||
|
name="twitter"
|
||||||
|
placeholder="@jack"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Avatar URL</span>
|
||||||
|
<input
|
||||||
|
aria-label="Avatar URL"
|
||||||
|
defaultValue={contact.avatar}
|
||||||
|
name="avatar"
|
||||||
|
placeholder="https://example.com/avatar.jpg"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Notes</span>
|
||||||
|
<textarea
|
||||||
|
defaultValue={contact.notes}
|
||||||
|
name="notes"
|
||||||
|
rows={6}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<p>
|
||||||
|
<button type="submit">Save</button>
|
||||||
|
<button type="button">Cancel</button>
|
||||||
|
</p>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
1
package-lock.json
generated
1
package-lock.json
generated
@ -4,6 +4,7 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "hello_remix",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/node": "^2.16.8",
|
"@remix-run/node": "^2.16.8",
|
||||||
"@remix-run/react": "^2.16.8",
|
"@remix-run/react": "^2.16.8",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user