-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #47 from nnivxix/feat/seo-meta
Feat/seo meta
- Loading branch information
Showing
44 changed files
with
1,726 additions
and
1,408 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,12 @@ | ||
module.exports = { | ||
root: true, | ||
env: { browser: true, es2020: true }, | ||
extends: [ | ||
'eslint:recommended', | ||
'plugin:@typescript-eslint/recommended', | ||
'plugin:react-hooks/recommended', | ||
], | ||
ignorePatterns: ['dist', '.eslintrc.cjs'], | ||
parser: '@typescript-eslint/parser', | ||
plugins: ['react-refresh'], | ||
rules: { | ||
'react-refresh/only-export-components': [ | ||
'warn', | ||
{ allowConstantExport: true }, | ||
], | ||
}, | ||
} | ||
module.exports = { | ||
root: true, | ||
env: { browser: true, es2020: true }, | ||
extends: [ | ||
"eslint:recommended", | ||
"plugin:@typescript-eslint/recommended", | ||
"plugin:react-hooks/recommended", | ||
], | ||
ignorePatterns: ["dist", ".eslintrc.cjs"], | ||
parser: "@typescript-eslint/parser", | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,67 @@ | ||
"use client" | ||
|
||
import type { Response, MovieTv } from "../../src/types/response"; | ||
import useFetch from "@/hooks/useFetch"; | ||
import type { Response, MovieTv } from "@/types/response"; | ||
import CardItem from "@/components/CardItem"; | ||
// import "../../src/index.css"; | ||
import { cookies } from "next/headers"; | ||
import config from "@/config"; | ||
import { Metadata } from "next"; | ||
|
||
type Status = "idle" | "pending" | "success" | "error"; | ||
|
||
const { apiUrl, token } = config; | ||
|
||
export default function Page() { | ||
const { data: movies, isLoading } = useFetch<Response<MovieTv[]>>("/trending/all/day"); | ||
export const metadata: Metadata = { | ||
title: "Vilm - Discover Movies and Tv Shows ", | ||
description: "Discover movies and tv shows.", | ||
}; | ||
|
||
if (isLoading) { | ||
return "Loading..." | ||
export default async function Page() { | ||
const { data, status } = await getDiscover(); | ||
|
||
if (status === "pending") { | ||
return "Loading..."; | ||
} | ||
return ( | ||
<div> | ||
<div className="grid lg:grid-cols-8 md:grid-cols-4 grid-cols-2 gap-5 mx-auto px-5 mt-5"> | ||
{movies?.results.map((movie: MovieTv) => ( | ||
{data?.results.map((movie: MovieTv) => ( | ||
<CardItem movie={movie} key={movie.id} /> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
async function getDiscover(): Promise<{ | ||
data: Response<MovieTv[]> | null; | ||
status: Status; | ||
error: string | null; | ||
}> { | ||
const apiToken = cookies().get("API_TOKEN")?.value ?? token; | ||
let status: Status = "idle"; | ||
let data: Response<MovieTv[]> | null = null; | ||
let error: string | null = null; | ||
|
||
status = "pending"; | ||
try { | ||
const response = await fetch(`${apiUrl}/trending/all/day`, { | ||
method: "GET", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Accept: "application/json", | ||
Authorization: `Bearer ${apiToken}`, | ||
}, | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error("Failed to fetch the movie data"); | ||
} | ||
|
||
data = await response.json(); | ||
status = "success"; | ||
} catch (err) { | ||
console.error(err); | ||
status = "error"; | ||
error = err instanceof Error ? err.message : "Unknown error"; | ||
} | ||
|
||
return { data, status, error }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,119 +1,33 @@ | ||
"use client" | ||
import { SearchForm } from "@/components/SearchForm"; | ||
import { Suspense } from "react"; | ||
|
||
import CardItem from "@/components/CardItem"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; | ||
import useFetch from "@/hooks/useFetch"; | ||
import useHead from "@/hooks/useHead"; | ||
import type { MovieTv, Response } from "@/types/response"; | ||
import { useSearchParams } from "next/navigation"; | ||
import { useRouter } from "next/navigation"; | ||
import { useEffect, useState, type FormEvent } from "react"; | ||
import { Suspense } from 'react' | ||
|
||
|
||
function SearchForm() { | ||
const searchParams = useSearchParams(); | ||
const router = useRouter(); | ||
|
||
const queryTitle = searchParams?.get("title"); | ||
const queryType = searchParams?.get("type"); | ||
|
||
const [results, setResults] = useState<MovieTv[]>([]); | ||
const [title, setTitle] = useState<string>(queryTitle ?? ""); | ||
const [type, setType] = useState<string>(queryType ?? "movie"); | ||
interface Props { | ||
searchParams: { [key: string]: string | string[] | undefined }; | ||
} | ||
|
||
const { data } = useFetch<Response<MovieTv[]>>( | ||
`/search/${queryType}?query=${queryTitle}` | ||
); | ||
export async function generateMetadata({ searchParams }: Props) { | ||
const title = searchParams.title; | ||
const type = () => { | ||
const paramType = searchParams.type; | ||
|
||
const changeType = (value: string) => { | ||
setType(value); | ||
router.push(`/search?title=${title}&type=${value}`); | ||
window.scrollTo({ | ||
top: 0, | ||
left: 0, | ||
behavior: "smooth", | ||
}); | ||
if (paramType === "tv") return "Tv Shows"; | ||
if (paramType === "movie") return "Movies"; | ||
return ""; | ||
}; | ||
|
||
const handleSearch = async (e: FormEvent) => { | ||
e.preventDefault(); | ||
router.push(`/search?title=${title}&type=${type}`); | ||
if (title) { | ||
return { | ||
title: `Vilm - Search ${type()} for "${title}" `, | ||
}; | ||
} | ||
return { | ||
title: "Vilm - Search", | ||
}; | ||
|
||
useHead({ | ||
title: `Vilm - Search ${type} "${title}"`, | ||
meta: { | ||
description: 'Here you can search movies or tv shows' | ||
} | ||
}); | ||
useEffect(() => { | ||
if (!queryTitle && !queryType) { | ||
router.push("/"); | ||
} | ||
|
||
setType(queryType!); | ||
setTitle(queryTitle!); | ||
setResults(data?.results as MovieTv[]); | ||
}, [data, router, queryTitle, queryType, results]); | ||
return ( | ||
<div> | ||
<form | ||
onSubmit={handleSearch} | ||
className="grid md:grid-cols-4 grid-cols-1 px-4 max-w-4xl mx-auto gap-2 mt-5 text-white mb-20 md:mb-16" | ||
> | ||
<Input | ||
type="text" | ||
value={title} | ||
className="bg-slate-800 text-white md:col-span-3 col-span-1" | ||
onChange={(e) => setTitle(e.target.value)} | ||
/> | ||
<Select value={type} onValueChange={(value) => changeType(value)}> | ||
<SelectTrigger className="w-[180px] col-span-1 bg-slate-800"> | ||
<SelectValue placeholder="Select Media Type " /> | ||
</SelectTrigger> | ||
<SelectContent className="bg-slate-800 text-white"> | ||
<SelectGroup> | ||
<SelectItem className=" hover:bg-slate-600" value="movie"> | ||
Movie | ||
</SelectItem> | ||
<SelectItem className=" hover:bg-slate-600" value="tv"> | ||
Tv | ||
</SelectItem> | ||
</SelectGroup> | ||
</SelectContent> | ||
</Select> | ||
</form> | ||
<div className="grid lg:grid-cols-8 md:grid-cols-4 grid-cols-2 gap-5 mx-auto px-5 mt-5"> | ||
{!!results?.length && | ||
results?.map((movie: MovieTv) => ( | ||
<CardItem media={queryType!} movie={movie} key={movie.id} /> | ||
))} | ||
<div className="col-span-full py-3 mx-auto"> | ||
<p> | ||
didn't found what you search?{" "} | ||
<button | ||
className="underline" | ||
onClick={() => changeType(type === "movie" ? "tv" : "movie")} | ||
> | ||
please change type | ||
</button> | ||
</p> | ||
</div> | ||
{/* Fallback if results is null */} | ||
</div> | ||
</div> | ||
); | ||
|
||
} | ||
|
||
export default function Page() { | ||
export default async function Page() { | ||
return ( | ||
<Suspense> | ||
<SearchForm /> | ||
</Suspense> | ||
) | ||
); | ||
} | ||
|
||
|
Oops, something went wrong.