Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion examples/todo-app/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Todo app using React Router SSR and AWS Amplify

A Todo application using React Router and AWS Amplify.
A Todo application using React Router Framework and AWS Amplify.

## About this Todo App

This Todo app is a simple Todo application built with React Router and AWS Amplify Auth and Data. It uses server-side rendering (SSR) to improve performance and SEO.
4 changes: 2 additions & 2 deletions examples/todo-app/amplify/data/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ const schema = a.schema({
content: a.string(),
isDone: a.boolean()
})
.authorization(allow => [allow.publicApiKey()])
.authorization(allow => [allow.owner()])
});

export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'apiKey',
defaultAuthorizationMode: 'userPool',
}
});

22 changes: 20 additions & 2 deletions examples/todo-app/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
Outlet,
Scripts,
ScrollRestoration,
useNavigate,
} from "react-router";
import { useEffect } from "react";
import { Hub } from "aws-amplify/utils";
import { Authenticator, ThemeProvider } from "@aws-amplify/ui-react";
import "@aws-amplify/ui-react/styles.css";

import type { Route } from "./+types/root";
import "./app.css";
Expand Down Expand Up @@ -38,7 +43,11 @@ export function Layout({ children }: { children: React.ReactNode }) {
<Links />
</head>
<body>
{children}
<Authenticator.Provider>
<ThemeProvider>
{children}
</ThemeProvider>
</Authenticator.Provider>
<ScrollRestoration />
<Scripts />
</body>
Expand All @@ -47,7 +56,16 @@ export function Layout({ children }: { children: React.ReactNode }) {
}

export default function App() {
return <Outlet />
const navigate = useNavigate();
useEffect(() => {
Hub.listen("auth", (data) => {
const { payload } = data;
if (payload.event === "signedIn") {
navigate("/");
}
});
}, [navigate]);
return <Outlet />;
}

export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
Expand Down
13 changes: 8 additions & 5 deletions examples/todo-app/app/routes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { type RouteConfig, index, route } from "@react-router/dev/routes";
import { type RouteConfig, index, route, layout } from "@react-router/dev/routes";

export default [
index("./routes/index.tsx"),
route("new", "./routes/new.tsx"),
route(":todoId", "./routes/$todoId/index.tsx"),
route(":todoId/edit", "./routes/$todoId/edit.tsx"),
route("login", "./routes/auth/login.tsx"), // Update login route path
layout("./routes/protected/layout.tsx", [ // Wrap protected routes
index("./routes/index.tsx"),
route("new", "./routes/new.tsx"),
route(":todoId", "./routes/$todoId/index.tsx"),
route(":todoId/edit", "./routes/$todoId/edit.tsx"),
]),
] satisfies RouteConfig;
35 changes: 35 additions & 0 deletions examples/todo-app/app/routes/auth/login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Authenticator } from "@aws-amplify/ui-react";
import "@aws-amplify/ui-react/styles.css";
import { getCurrentUser } from "aws-amplify/auth";
import { redirect } from "react-router";
import type { Route } from "./+types/login";

export const meta: Route.MetaFunction = () => {
return [
{ title: "Login - Todo App" },
{ name: "description", content: "Login to Todo App" },
];
}

export const clientLoader = async ({ request }: Route.ClientLoaderArgs) => {
try {
const user = await getCurrentUser();
if (user) {
return redirect("/");
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// ignore error - user not logged in
}
return {};
}

export default function Login() {
return (
<div className="flex min-h-screen items-center justify-center p-4">
<div className="w-full max-w-sm">
<Authenticator />
</div>
</div>
);
}
24 changes: 19 additions & 5 deletions examples/todo-app/app/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Route } from "./+types/index";
import { TodoList } from "~/components/TodoList";
import { runWithAmplifyServerContext } from "~/lib/amplifyServerUtils";
import { data, Link } from "react-router";
import { data, Link, useNavigate } from "react-router";
import { client } from "~/lib/amplify-ssr-client";
import { signOut } from "aws-amplify/auth";

export function meta() {
return [
Expand Down Expand Up @@ -30,18 +31,31 @@ export async function loader({ request }: Route.LoaderArgs) {

export default function Home({ loaderData }: Route.ComponentProps) {
const { todos } = loaderData;
const navigate = useNavigate();
return (
<div className="flex flex-col gap-y-3 my-4 mx-2">
<h1 className="text-2xl font-bold">Todo List</h1>
<TodoList items={todos} />
<Link to="/new">
<div className="flex justify-between">
<Link to="/new">
<button
type="button"
className="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded"
>
Add Todo
</button>
</Link>
<button
type="button"
className="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded"
className="bg-transparent hover:bg-red-500 text-red-700 font-semibold hover:text-white py-2 px-4 border border-red-500 hover:border-transparent rounded"
onClick={async () => {
await signOut();
await navigate("/login");
}}
>
Add Todo
SignOut
</button>
</Link>
</div>
</div>
);
}
38 changes: 38 additions & 0 deletions examples/todo-app/app/routes/protected/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { fetchUserAttributes } from "aws-amplify/auth/server";
import { data, Outlet, redirect } from "react-router";
import type { Route } from "./+types/layout";
import { runWithAmplifyServerContext } from "../../lib/amplifyServerUtils";

export async function loader({ request }: Route.LoaderArgs) {
const responseHeaders = new Headers();
return await runWithAmplifyServerContext({
serverContext: { request, responseHeaders },
operation: async (contextSpec) => {
try {
const user = await fetchUserAttributes(contextSpec);
if (!user) {
return redirect("/login", {
// Redirect to /login
headers: responseHeaders,
});
}
return data(
{}, // Return empty data
{
headers: responseHeaders,
},
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error: unknown) {
return redirect("/login", {
// Redirect to /login
headers: responseHeaders,
});
}
},
});
}

export default function Layout({ loaderData }: Route.ComponentProps) {
return <Outlet />;
}
1 change: 1 addition & 0 deletions examples/todo-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"typecheck": "react-router typegen && tsc"
},
"dependencies": {
"@aws-amplify/ui-react": "^6.11.0",
"@react-router/express": "^7.2.0",
"@react-router/node": "^7.4.0",
"@react-router/serve": "^7.4.0",
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
"lint-staged": "^15.4.3",
"vitest": "^3.1.1"
},
"pnpm": {
"overrides": {
"@types/node": "^20.0.0"
}
},
"lint-staged": {
"*.ts": "pnpm lint"
}
Expand Down
Loading