Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…b/main/examples/server/

This allows us to test using the local build.
  • Loading branch information
sebmarkbage committed Dec 12, 2024
1 parent 68dba2d commit de0fe68
Show file tree
Hide file tree
Showing 16 changed files with 2,812 additions and 0 deletions.
5 changes: 5 additions & 0 deletions fixtures/flight-parcel/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.parcel-cache
.DS_Store
node_modules
dist
todos.json
4 changes: 4 additions & 0 deletions fixtures/flight-parcel/.parcelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "@parcel/config-default",
"runtimes": ["...", "@parcel/runtime-rsc"]
}
48 changes: 48 additions & 0 deletions fixtures/flight-parcel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "flight-parcel",
"private": true,
"workspaces": [
"examples/*"
],
"server": "dist/server.js",
"targets": {
"server": {
"source": "src/server.tsx",
"context": "react-server",
"outputFormat": "commonjs",
"includeNodeModules": {
"express": false
}
}
},
"scripts": {
"prewatch": "cp -r ../../build/oss-experimental/* ./node_modules/",
"prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/",
"watch": "parcel watch",
"build": "parcel build",
"start": "node dist/server.js"
},
"@parcel/resolver-default": {
"packageExports": true
},
"dependencies": {
"@parcel/config-default": "2.0.0-dev.1789",
"@parcel/runtime-rsc": "2.13.3-dev.3412",
"@types/parcel-env": "^0.0.6",
"@types/express": "*",
"@types/node": "^22.10.1",
"@types/react": "^19",
"@types/react-dom": "^19",
"express": "^4.18.2",
"parcel": "2.0.0-dev.1787",
"process": "^0.11.10",
"react": "^19",
"react-dom": "^19",
"react-server-dom-parcel": "^0.0.1",
"rsc-html-stream": "^0.0.4",
"ws": "^8.8.1"
},
"@parcel/bundler-default": {
"minBundleSize": 0
}
}
21 changes: 21 additions & 0 deletions fixtures/flight-parcel/src/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use client';

import {ReactNode, useRef} from 'react';

export function Dialog({
trigger,
children,
}: {
trigger: ReactNode;
children: ReactNode;
}) {
let ref = useRef<HTMLDialogElement | null>(null);
return (
<>
<button onClick={() => ref.current?.showModal()}>{trigger}</button>
<dialog ref={ref} onSubmit={() => ref.current?.close()}>
{children}
</dialog>
</>
);
}
18 changes: 18 additions & 0 deletions fixtures/flight-parcel/src/TodoCreate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {createTodo} from './actions';

export function TodoCreate() {
return (
<form action={createTodo}>
<label>
Title: <input name="title" />
</label>
<label>
Description: <textarea name="description" />
</label>
<label>
Due date: <input type="date" name="dueDate" />
</label>
<button>Add todo</button>
</form>
);
}
25 changes: 25 additions & 0 deletions fixtures/flight-parcel/src/TodoDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {getTodo, updateTodo} from './actions';

export async function TodoDetail({id}: {id: number}) {
let todo = await getTodo(id);
if (!todo) {
return <p>Todo not found</p>;
}

return (
<form className="todo" action={updateTodo.bind(null, todo.id)}>
<label>
Title: <input name="title" defaultValue={todo.title} />
</label>
<label>
Description:{' '}
<textarea name="description" defaultValue={todo.description} />
</label>
<label>
Due date:{' '}
<input type="date" name="dueDate" defaultValue={todo.dueDate} />
</label>
<button type="submit">Update todo</button>
</form>
);
}
37 changes: 37 additions & 0 deletions fixtures/flight-parcel/src/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client';

import {startTransition, useOptimistic} from 'react';
import {deleteTodo, setTodoComplete, type Todo as ITodo} from './actions';

export function TodoItem({
todo,
isSelected,
}: {
todo: ITodo;
isSelected: boolean;
}) {
let [isOptimisticComplete, setOptimisticComplete] = useOptimistic(
todo.isComplete,
);

return (
<li data-selected={isSelected || undefined}>
<input
type="checkbox"
checked={isOptimisticComplete}
onChange={e => {
startTransition(async () => {
setOptimisticComplete(e.target.checked);
await setTodoComplete(todo.id, e.target.checked);
});
}}
/>
<a
href={`/todos/${todo.id}`}
aria-current={isSelected ? 'page' : undefined}>
{todo.title}
</a>
<button onClick={() => deleteTodo(todo.id)}>x</button>
</li>
);
}
13 changes: 13 additions & 0 deletions fixtures/flight-parcel/src/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {TodoItem} from './TodoItem';
import {getTodos} from './actions';

export async function TodoList({id}: {id: number | undefined}) {
let todos = await getTodos();
return (
<ul className="todo-list">
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} isSelected={todo.id === id} />
))}
</ul>
);
}
63 changes: 63 additions & 0 deletions fixtures/flight-parcel/src/Todos.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
body {
font-family: system-ui;
color-scheme: light dark;
}

form {
display: grid;
grid-template-columns: auto 1fr;
flex-direction: column;
max-width: 400px;
gap: 8px;
}

label {
display: contents;
}

main {
display: flex;
gap: 32px;
}

.todo-column {
width: 250px;
}

header {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 250px;
padding: 8px;
padding-right: 40px;
box-sizing: border-box;
}

.todo-list {
max-width: 250px;
padding: 0;
list-style: none;
padding-right: 32px;
border-right: 1px solid gray;

li {
display: flex;
gap: 8px;
padding: 8px;
border-radius: 8px;
accent-color: light-dark(black, white);

a {
color: inherit;
text-decoration: none;
width: 100%;
}

&[data-selected] {
background-color: light-dark(#222, #ddd);
color: light-dark(#ddd, #222);
accent-color: light-dark(white, black);
}
}
}
35 changes: 35 additions & 0 deletions fixtures/flight-parcel/src/Todos.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use server-entry';

import './client';
import './Todos.css';
import {Resources} from '@parcel/runtime-rsc';
import {Dialog} from './Dialog';
import {TodoDetail} from './TodoDetail';
import {TodoCreate} from './TodoCreate';
import {TodoList} from './TodoList';

export async function Todos({id}: {id?: number}) {
return (
<html style={{colorScheme: 'dark light'}}>
<head>
<title>Todos</title>
<Resources />
</head>
<body>
<header>
<h1>Todos</h1>
<Dialog trigger="+">
<h2>Add todo</h2>
<TodoCreate />
</Dialog>
</header>
<main>
<div className="todo-column">
<TodoList id={id} />
</div>
{id != null ? <TodoDetail key={id} id={id} /> : <p>Select a todo</p>}
</main>
</body>
</html>
);
}
75 changes: 75 additions & 0 deletions fixtures/flight-parcel/src/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'use server';

import fs from 'fs/promises';

export interface Todo {
id: number;
title: string;
description: string;
dueDate: string;
isComplete: boolean;
}

export async function getTodos(): Promise<Todo[]> {
try {
let contents = await fs.readFile('todos.json', 'utf8');
return JSON.parse(contents);
} catch {
await fs.writeFile('todos.json', '[]');
return [];
}
}

export async function getTodo(id: number): Promise<Todo | undefined> {
let todos = await getTodos();
return todos.find(todo => todo.id === id);
}

export async function createTodo(formData: FormData) {
let todos = await getTodos();
let title = formData.get('title');
let description = formData.get('description');
let dueDate = formData.get('dueDate');
let id = todos.length > 0 ? Math.max(...todos.map(todo => todo.id)) + 1 : 0;
todos.push({
id,
title: typeof title === 'string' ? title : '',
description: typeof description === 'string' ? description : '',
dueDate: typeof dueDate === 'string' ? dueDate : new Date().toISOString(),
isComplete: false,
});
await fs.writeFile('todos.json', JSON.stringify(todos));
}

export async function updateTodo(id: number, formData: FormData) {
let todos = await getTodos();
let title = formData.get('title');
let description = formData.get('description');
let dueDate = formData.get('dueDate');
let todo = todos.find(todo => todo.id === id);
if (todo) {
todo.title = typeof title === 'string' ? title : '';
todo.description = typeof description === 'string' ? description : '';
todo.dueDate =
typeof dueDate === 'string' ? dueDate : new Date().toISOString();
await fs.writeFile('todos.json', JSON.stringify(todos));
}
}

export async function setTodoComplete(id: number, isComplete: boolean) {
let todos = await getTodos();
let todo = todos.find(todo => todo.id === id);
if (todo) {
todo.isComplete = isComplete;
await fs.writeFile('todos.json', JSON.stringify(todos));
}
}

export async function deleteTodo(id: number) {
let todos = await getTodos();
let index = todos.findIndex(todo => todo.id === id);
if (index >= 0) {
todos.splice(index, 1);
await fs.writeFile('todos.json', JSON.stringify(todos));
}
}
Loading

0 comments on commit de0fe68

Please sign in to comment.