1
0
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:
Anson Biggs 2025-06-01 00:47:54 -06:00
parent f70bd9680d
commit cc898b4b08
5 changed files with 268 additions and 8 deletions

View File

@ -1,12 +1,40 @@
import type { LinksFunction } from "@remix-run/node";
import {
Form,
Link,
Links,
Meta,
NavLink,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
useNavigation,
} 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() {
const { contacts } = useLoaderData<typeof loader>();
const navigation = useNavigation();
return (
<html lang="en">
<head>
@ -34,16 +62,47 @@ export default function App() {
</Form>
</div>
<nav>
{contacts.length ? (
<ul>
<li>
<a href={`/contacts/1`}>Your Name</a>
</li>
<li>
<a href={`/contacts/2`}>Your Friend</a>
{contacts.map((contact) => (
<li key={contact.id}> <NavLink
className={({ isActive, isPending }) =>
isActive
? "active"
: 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>
</div>
<div className={
navigation.state === "loading" ? "loading" : ""
} id="detail">
<Outlet />
</div>
<ScrollRestoration />
<Scripts />

View 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>
);
};

View 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("/");
};

View 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
View File

@ -4,6 +4,7 @@
"requires": true,
"packages": {
"": {
"name": "hello_remix",
"dependencies": {
"@remix-run/node": "^2.16.8",
"@remix-run/react": "^2.16.8",