diff --git a/app/layout.tsx b/app/layout.tsx
index 04624d8..5a2abfd 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -44,6 +44,9 @@ export default function RootLayout({
Interactive
+
+ Star Wars
+
Error
diff --git a/app/star-wars/layout.tsx b/app/star-wars/layout.tsx
new file mode 100644
index 0000000..5e8efdc
--- /dev/null
+++ b/app/star-wars/layout.tsx
@@ -0,0 +1,15 @@
+export default function Layout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+ Star Wars
+
+
+ MtFBWY!
+
+
+ {children}
+
+ );
+}
diff --git a/app/star-wars/loading.tsx b/app/star-wars/loading.tsx
new file mode 100644
index 0000000..eb55c4d
--- /dev/null
+++ b/app/star-wars/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ Loading
+
+ );
+}
diff --git a/app/star-wars/not-found.tsx b/app/star-wars/not-found.tsx
new file mode 100644
index 0000000..5c8afb6
--- /dev/null
+++ b/app/star-wars/not-found.tsx
@@ -0,0 +1,21 @@
+export const metadata = {
+ title: `Star War Not Found`,
+};
+
+export default function NotFound() {
+ return (
+ <>
+
+
+ Star War Not Found
+
+
+
+ You have failed me for the last time!
+
+
+
+
+ >
+ );
+}
diff --git a/app/star-wars/people/[id]/loading.tsx b/app/star-wars/people/[id]/loading.tsx
new file mode 100644
index 0000000..3eb45bb
--- /dev/null
+++ b/app/star-wars/people/[id]/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ Loading
+
+ );
+}
diff --git a/app/star-wars/people/[id]/page.tsx b/app/star-wars/people/[id]/page.tsx
new file mode 100644
index 0000000..2c2059e
--- /dev/null
+++ b/app/star-wars/people/[id]/page.tsx
@@ -0,0 +1,46 @@
+import { type Person } from '../../types';
+import { Metadata } from 'next';
+import { notFound } from 'next/navigation';
+import sleep from '@/util/sleep';
+
+type Props = {
+ params: { id: string };
+};
+
+async function getPerson(id: string): Promise {
+ await sleep();
+ const res = await fetch(`https://swapi.dev/api/people/${id}`);
+
+ const json = await res.json();
+ return json;
+}
+
+export async function generateMetadata({ params }: Props): Promise {
+ const person = await getPerson(params.id);
+
+ return {
+ title: person.name,
+ };
+}
+
+export default async function Page({ params }: Props) {
+ const person = await getPerson(params.id);
+
+ if (!person.name) {
+ notFound();
+ }
+
+ return (
+
+
{person.name}
+
+ - Height
+ - {person.height}
+ - Mass
+ - {person.mass}
+ - Hair Color
+ - {person.hair_color}
+
+
+ );
+}
diff --git a/app/star-wars/people/page.tsx b/app/star-wars/people/page.tsx
new file mode 100644
index 0000000..e532322
--- /dev/null
+++ b/app/star-wars/people/page.tsx
@@ -0,0 +1,37 @@
+import Link from 'next/link';
+import sleep from '@/util/sleep';
+import { type Person } from '../types';
+
+async function getPeople(): Promise {
+ await sleep();
+ const res = await fetch('https://swapi.dev/api/people/');
+ const json = await res.json();
+ return json.results;
+}
+
+export const metadata = {
+ title: 'Star Wars People',
+};
+
+export default async function Page() {
+ const people = await getPeople();
+
+ return (
+
+
Star Wars People
+
+ {people.map((person) => (
+ -
+
+ {person.name}
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/star-wars/types.tsx b/app/star-wars/types.tsx
new file mode 100644
index 0000000..8fa55e5
--- /dev/null
+++ b/app/star-wars/types.tsx
@@ -0,0 +1,18 @@
+export type Person = {
+ name: string;
+ height: string;
+ mass: string;
+ hair_color: string;
+ skin_color: string;
+ eye_color: string;
+ birth_year: string;
+ gender: string;
+ homeworld: string;
+ films: string[];
+ species: string[];
+ vehicles: string[];
+ starships: string[];
+ created: string;
+ edited: string;
+ url: string;
+};
diff --git a/util/sleep.tsx b/util/sleep.tsx
new file mode 100644
index 0000000..c9c34b0
--- /dev/null
+++ b/util/sleep.tsx
@@ -0,0 +1,5 @@
+export default function sleep(ms = 2000) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, ms);
+ });
+}