Skip to content

Commit bfd7824

Browse files
committed
improvements
1 parent 5c455ba commit bfd7824

File tree

27 files changed

+320
-33
lines changed

27 files changed

+320
-33
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react'
2+
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
3+
import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css'
4+
import { type Movie } from '#app/movies-data.ts'
5+
6+
export function MovieTrailer({ movie }: { movie: Movie }) {
7+
const [showTrailer, setShowTrailer] = React.useState(false)
8+
9+
return (
10+
<div className="mb-6">
11+
<button
12+
onClick={() => setShowTrailer(!showTrailer)}
13+
className="rr-button mb-4"
14+
>
15+
{showTrailer ? 'Hide Trailer' : 'Watch Trailer'}
16+
</button>
17+
{showTrailer && (
18+
<div className="overflow-hidden rounded-lg">
19+
<LiteYouTubeEmbed
20+
id={movie.youtubeId}
21+
title={movie.title}
22+
params="autoplay=1&mute=0&rel=0&modestbranding=1"
23+
alwaysLoadIframe={true}
24+
/>
25+
</div>
26+
)}
27+
</div>
28+
)
29+
}

exercises/01.start/01.problem.vite-plugin/app/movies-data.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,20 @@ export async function getMovie(id: number) {
8585
}
8686
return movie
8787
}
88+
89+
export async function setIsFavorite({
90+
movieId,
91+
isFavorite,
92+
}: {
93+
movieId: number
94+
isFavorite: boolean
95+
}) {
96+
// Simulate API call delay
97+
await new Promise((resolve) => setTimeout(resolve, 50))
98+
99+
// Update the movie's favorite status
100+
const movie = movies.find((m) => m.id === movieId)
101+
if (movie) {
102+
movie.isFavorite = isFavorite
103+
}
104+
}

exercises/01.start/01.problem.vite-plugin/app/root.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import {
33
Links,
44
Meta,
55
Outlet,
6+
Scripts,
67
ScrollRestoration,
78
} from 'react-router'
89

9-
import type { Route } from './+types/root'
10+
import { type Route } from './+types/root'
1011
import './app.css'
1112

1213
export const links: Route.LinksFunction = () => [
@@ -34,6 +35,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
3435
<body>
3536
{children}
3637
<ScrollRestoration />
38+
<Scripts />
3739
</body>
3840
</html>
3941
)

exercises/01.start/01.problem.vite-plugin/app/routes/movie-details.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
import { href, Link } from 'react-router'
2-
import { getMovie } from '#app/movies-data.ts'
1+
import { Form, href, Link } from 'react-router'
2+
import { MovieTrailer } from '#app/movie-trailer.tsx'
3+
import { getMovie, setIsFavorite } from '#app/movies-data.ts'
34
import { type Route } from './+types/movie-details'
45

56
export async function loader({ params }: Route.LoaderArgs) {
67
const movie = await getMovie(Number(params.movieId))
78
return { movie }
89
}
910

11+
export async function action({ request }: Route.ActionArgs) {
12+
// Simulate API call delay
13+
await new Promise((resolve) => setTimeout(resolve, 50))
14+
const formData = await request.formData()
15+
16+
const movieId = Number(formData.get('id'))
17+
const isFavorite = formData.get('isFavorite') === 'true'
18+
// Update the movie's favorite status
19+
await setIsFavorite({ movieId, isFavorite })
20+
return { success: true }
21+
}
22+
1023
export default function MovieDetailsPage({ loaderData }: Route.ComponentProps) {
1124
const { movie } = loaderData
1225

@@ -39,13 +52,23 @@ export default function MovieDetailsPage({ loaderData }: Route.ComponentProps) {
3952
<div className="mb-4 flex items-center gap-4">
4053
<span className="rr-text text-lg">{movie.year}</span>
4154
<span className="rr-badge">Rating: {movie.rating}/10</span>
42-
<span
43-
className={`rr-badge ${movie.isFavorite ? 'rr-badge-red' : ''}`}
44-
>
45-
{movie.isFavorite ? 'Favorite' : 'Not Favorite'}
46-
</span>
55+
<Form method="post" preventScrollReset>
56+
<input type="hidden" name="id" value={movie.id} />
57+
<input
58+
type="hidden"
59+
name="isFavorite"
60+
value={String(!movie.isFavorite)}
61+
/>
62+
<button
63+
type="submit"
64+
className={`rr-badge ${movie.isFavorite ? 'rr-badge-red' : ''}`}
65+
>
66+
{movie.isFavorite ? 'Favorite' : 'Not Favorite'}
67+
</button>
68+
</Form>
4769
</div>
4870
<p className="rr-text mb-6">{movie.description}</p>
71+
<MovieTrailer movie={movie} />
4972
</div>
5073
</div>
5174
</div>

exercises/01.start/01.problem.vite-plugin/react-router.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Config } from '@react-router/dev/config'
1+
import { type Config } from '@react-router/dev/config'
22

33
export default {
44
// Config options...
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react'
2+
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
3+
import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css'
4+
import { type Movie } from '#app/movies-data.ts'
5+
6+
export function MovieTrailer({ movie }: { movie: Movie }) {
7+
const [showTrailer, setShowTrailer] = React.useState(false)
8+
9+
return (
10+
<div className="mb-6">
11+
<button
12+
onClick={() => setShowTrailer(!showTrailer)}
13+
className="rr-button mb-4"
14+
>
15+
{showTrailer ? 'Hide Trailer' : 'Watch Trailer'}
16+
</button>
17+
{showTrailer && (
18+
<div className="overflow-hidden rounded-lg">
19+
<LiteYouTubeEmbed
20+
id={movie.youtubeId}
21+
title={movie.title}
22+
params="autoplay=1&mute=0&rel=0&modestbranding=1"
23+
alwaysLoadIframe={true}
24+
/>
25+
</div>
26+
)}
27+
</div>
28+
)
29+
}

exercises/01.start/01.solution.vite-plugin/app/movies-data.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,20 @@ export async function getMovie(id: number) {
8585
}
8686
return movie
8787
}
88+
89+
export async function setIsFavorite({
90+
movieId,
91+
isFavorite,
92+
}: {
93+
movieId: number
94+
isFavorite: boolean
95+
}) {
96+
// Simulate API call delay
97+
await new Promise((resolve) => setTimeout(resolve, 50))
98+
99+
// Update the movie's favorite status
100+
const movie = movies.find((m) => m.id === movieId)
101+
if (movie) {
102+
movie.isFavorite = isFavorite
103+
}
104+
}

exercises/01.start/01.solution.vite-plugin/app/root.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import {
33
Links,
44
Meta,
55
Outlet,
6+
Scripts,
67
ScrollRestoration,
78
} from 'react-router'
89

9-
import type { Route } from './+types/root'
10+
import { type Route } from './+types/root'
1011
import './app.css'
1112

1213
export const links: Route.LinksFunction = () => [
@@ -34,6 +35,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
3435
<body>
3536
{children}
3637
<ScrollRestoration />
38+
<Scripts />
3739
</body>
3840
</html>
3941
)

exercises/01.start/01.solution.vite-plugin/app/routes/movie-details.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
import { href, Link } from 'react-router'
2-
import { getMovie } from '#app/movies-data.ts'
1+
import { Form, href, Link } from 'react-router'
2+
import { MovieTrailer } from '#app/movie-trailer.tsx'
3+
import { getMovie, setIsFavorite } from '#app/movies-data.ts'
34
import { type Route } from './+types/movie-details'
45

56
export async function loader({ params }: Route.LoaderArgs) {
67
const movie = await getMovie(Number(params.movieId))
78
return { movie }
89
}
910

11+
export async function action({ request }: Route.ActionArgs) {
12+
// Simulate API call delay
13+
await new Promise((resolve) => setTimeout(resolve, 50))
14+
const formData = await request.formData()
15+
16+
const movieId = Number(formData.get('id'))
17+
const isFavorite = formData.get('isFavorite') === 'true'
18+
// Update the movie's favorite status
19+
await setIsFavorite({ movieId, isFavorite })
20+
return { success: true }
21+
}
22+
1023
export default function MovieDetailsPage({ loaderData }: Route.ComponentProps) {
1124
const { movie } = loaderData
1225

@@ -39,13 +52,23 @@ export default function MovieDetailsPage({ loaderData }: Route.ComponentProps) {
3952
<div className="mb-4 flex items-center gap-4">
4053
<span className="rr-text text-lg">{movie.year}</span>
4154
<span className="rr-badge">Rating: {movie.rating}/10</span>
42-
<span
43-
className={`rr-badge ${movie.isFavorite ? 'rr-badge-red' : ''}`}
44-
>
45-
{movie.isFavorite ? 'Favorite' : 'Not Favorite'}
46-
</span>
55+
<Form method="post" preventScrollReset>
56+
<input type="hidden" name="id" value={movie.id} />
57+
<input
58+
type="hidden"
59+
name="isFavorite"
60+
value={String(!movie.isFavorite)}
61+
/>
62+
<button
63+
type="submit"
64+
className={`rr-badge ${movie.isFavorite ? 'rr-badge-red' : ''}`}
65+
>
66+
{movie.isFavorite ? 'Favorite' : 'Not Favorite'}
67+
</button>
68+
</Form>
4769
</div>
4870
<p className="rr-text mb-6">{movie.description}</p>
71+
<MovieTrailer movie={movie} />
4972
</div>
5073
</div>
5174
</div>

exercises/01.start/01.solution.vite-plugin/react-router.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Config } from '@react-router/dev/config'
1+
import { type Config } from '@react-router/dev/config'
22

33
export default {
44
// Config options...

0 commit comments

Comments
 (0)