dev #1

Merged
rishabh merged 7 commits from dev into main 2025-01-02 12:46:37 +00:00
12 changed files with 303 additions and 136 deletions
Showing only changes of commit 9408ac16b2 - Show all commits

BIN
public/215697_small.mp4 Normal file

Binary file not shown.

View file

@ -14,10 +14,17 @@ body {
} }
.header img { .header img {
width: 200px; width: 150px;
height: 200px; height: auto;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: auto; margin: auto;
animation: fadeIn 2s ease-in-out; flex-wrap: wrap;
} }
.character-container { .character-container {
@ -43,19 +50,14 @@ body {
box-shadow: 0 5px 15px rgba(255, 204, 0, 0.5); box-shadow: 0 5px 15px rgba(255, 204, 0, 0.5);
} }
.character-image {
width: 100%;
height: auto;
border-radius: 10px;
}
.character-details p { .character-details p {
margin: 10px 0; margin: 10px 0;
color: #ffcc00; color: #fff;
} }
.character-details p strong {
.changePage { color: #ffcc00;
margin-top: 20px; text-transform: capitalize;
padding-right: 5px;
} }
.button { .button {
@ -66,7 +68,7 @@ body {
font-size: 16px; font-size: 16px;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
margin: 0 10px; margin: 30px 10px;
transition: background-color 0.3s; transition: background-color 0.3s;
} }
@ -74,33 +76,6 @@ body {
background-color: #ffb103; background-color: #ffb103;
} }
.loading {
font-size: 24px;
color: #ffcc00;
animation: pulse 1.5s infinite;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0%,
100% {
opacity: 0.8;
}
50% {
opacity: 1;
}
}
@media (max-width: 768px) { @media (max-width: 768px) {
.character-card { .character-card {
width: 100%; width: 100%;
@ -111,3 +86,113 @@ body {
font-size: 14px; font-size: 14px;
} }
} }
/* Search Functionality */
.search-container {
position: relative;
margin: 50px auto;
width: 300px;
}
.search-input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
color: #000;
}
.suggestions {
list-style: none;
margin: 0;
padding: 0;
background: #333;
border: 1px solid #ccc;
border-top: none;
height: auto;
overflow-y: auto;
position: absolute;
width: 100%;
z-index: 10;
}
.suggestion-item {
padding: 10px;
cursor: pointer;
}
.suggestion-item:hover {
background-color: #222;
}
@media (max-width: 768px) {
.header-container {
flex-direction: column;
align-items: center;
}
.search-container {
margin-top: 20px;
width: 100%;
max-width: none;
}
.header img {
width: 120px;
}
}
/* SKELETON */
.skeleton-character-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
list-style: none;
padding: 0;
justify-content: center;
}
.skeleton-character-card {
display: flex;
justify-content: center;
background: #fff;
border: 1px solid #ccc;
border-radius: 8px;
padding: 20px;
width: 250px;
height: 225px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.skeleton-character-details p {
margin: 10px 0;
}
.skeleton-line {
text-align: center;
background: linear-gradient(to right, #666 8%, #999 18%, #666 33%);
background-size: 1000px 100%;
animation: shimmer 3s infinite;
border-radius: 4px;
width: 120px;
height: 16px;
margin: 15px 0;
}
@keyframes shimmer {
0% {
background-position: -1000px;
}
100% {
background-position: 1000px;
}
}
@media (max-width: 768px) {
.character-card {
width: 100%;
max-width: 300px;
}
}

View file

@ -1,33 +1,19 @@
import { RouterProvider, createBrowserRouter } from "react-router-dom"; import { RouterProvider, createBrowserRouter } from "react-router-dom";
import "./App.css"; import "./App.css";
import Home from "./pages/Home"; import Home from "./pages/Home";
import About from "./pages/About";
import Contact from "./pages/Contact";
import Layout from "./components/layout/Layout";
import Error from "./components/Error"; import Error from "./components/Error";
import CharacterDetails from "./components/CharacterDetails"; import CharacterDetails from "./components/characterDetail/CharacterDetails";
function App() { function App() {
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
path: "/",
element: <Layout />,
errorElement: <Error />, errorElement: <Error />,
children: [ children: [
{
path: "/about",
element: <About />,
},
{
path: "/contact",
element: <Contact />,
},
{ {
path: "/", path: "/",
element: <Home />, element: <Home />,
}, },
{ {
path: "/characterDetails/:id", path: "/characterDetails/:id",
element: <CharacterDetails />, element: <CharacterDetails />,
}, },
], ],

View file

@ -1,4 +1,4 @@
import React from "react"; import React, { useEffect, useState } from "react";
import "../App.css"; import "../App.css";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@ -6,17 +6,26 @@ const CharacterCard = (props) => {
return ( return (
<ul className="character-container"> <ul className="character-container">
{props.data.map((character, index) => { {props.data.map((character, index) => {
// Extract ID from character's URL
const id = character.url.split("/").filter(Boolean).pop(); const id = character.url.split("/").filter(Boolean).pop();
return ( return (
<li className="character-card" key={index}> <li className="character-card" key={index}>
<Link to={`/characterDetails/${id}`}> <Link to={`/characterDetails/${id}`}>
<div className="character-details"> <div className="character-details">
<p>Name: {character.name}</p> <p>
<p>Gender: {character.gender}</p> <strong> Name </strong> {character.name}
<p>Height: {character.height} cm</p> </p>
<p>Hair Color: {character.hair_color}</p> <p>
<p>Birth Year: {character.birth_year}</p> <strong> Gender </strong> {character.gender}
</p>
<p>
<strong> Height </strong> {character.height} cm
</p>
<p>
<strong> Hair Color </strong> {character.hair_color}
</p>
<p>
<strong> Birth Year </strong> {character.birth_year}
</p>
</div> </div>
</Link> </Link>
</li> </li>

View file

@ -1,11 +0,0 @@
import React from 'react'
const Footer = () => {
return (
<div>
footer
</div>
)
}
export default Footer

View file

@ -1,7 +0,0 @@
import React from "react";
const Navbar = () => {
return <div>navbar</div>;
};
export default Navbar;

View file

@ -0,0 +1,47 @@
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
.character-detail-page {
background-color: #000;
font-family: "Poppins", sans-serif;
color: #ffffff;
padding: 20px;
border-radius: 10px;
max-width: 700px;
margin: 40px auto;
box-shadow: 0px 5px 10px rgba(255, 255, 255, 0.3);
border: 2px solid #ffd700;
}
.character-detail-page p {
margin: 12px 0;
font-size: 18px;
}
.character-detail-page p strong {
color: #ffd700;
text-transform: uppercase;
font-weight: bold;
padding-right: 10px;
}
.character-detail-page:hover {
box-shadow: 0 5px 15px 2px rgba(255, 204, 0, 0.9);
}
.character-detail-page p:first-child {
margin-top: 0;
}
.character-detail-page p:last-child {
margin-bottom: 0;
}
.character-detail-page h2 {
font-size: 3rem;
font-family: "Poppins", serif;
text-align: center;
color: #ffd700;
font-weight: bold;
text-transform: capitalize;
}

View file

@ -1,6 +1,8 @@
import axios from "axios"; import axios from "axios";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useParams } from "react-router"; import { useParams } from "react-router";
import Film from "../Film";
import "../characterDetail/CharacterDetails.css";
const CharacterDetails = () => { const CharacterDetails = () => {
const { id } = useParams(); const { id } = useParams();
console.log(id); console.log(id);
@ -30,29 +32,32 @@ const CharacterDetails = () => {
return ( return (
<div className="character-detail-page"> <div className="character-detail-page">
<p>{character.name}</p> <h2>Character Info </h2>
<p> <p>
<strong>Gender: </strong> <strong> Name </strong> {character.name}
</p>
<p>
<strong>Gender </strong>
{character.gender} {character.gender}
</p> </p>
<p> <p>
<strong>Home World: </strong> <strong>Height </strong> {character.height} cm
{character.homeworld}
</p> </p>
<p> <p>
<strong>Species: </strong> {character.species} <strong>Hair Color </strong> {character.hair_color}
</p> </p>
<p> <p>
<strong>Films: </strong> {character.films} <strong>Skin Color </strong> {character.skin_color}
</p> </p>
<p> <p>
<strong>Height: </strong> {character.height} cm <strong>Eye Color </strong> {character.eye_color}
</p> </p>
<p> <p>
<strong>Hair Color: </strong> {character.hair_color} <strong>Birth Year </strong> {character.birth_year}
</p> </p>
<p> <p>
<strong>Birth Year: </strong> {character.birth_year} <strong> Films </strong> <Film />
</p> </p>
</div> </div>
); );

View file

@ -1,15 +0,0 @@
import React from 'react'
import { Outlet } from 'react-router'
import Navbar from '../Navbar'
import Footer from '../Footer'
const Layout = () => {
return (
<>
<Navbar />
<Outlet />
<Footer />
</>
)
}
export default Layout;

View file

@ -1,11 +0,0 @@
import React from 'react'
const About = () => {
return (
<div>
about page
</div>
)
}
export default About

View file

@ -1,11 +0,0 @@
import React from 'react'
const Contact = () => {
return (
<div>
contact page
</div>
)
}
export default Contact

View file

@ -5,7 +5,10 @@ import CharacterCard from "../components/CharacterCard";
const Home = () => { const Home = () => {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [data, setData] = useState([]); const [data, setData] = useState([]);
const [loading, setLoading] = useState(false); const [filteredData, setFilteredData] = useState([]);
const [searchQuery, setSearchQuery] = useState("");
const [loading, setLoading] = useState(true);
const [suggestions, setSuggestions] = useState([]);
const increment = () => { const increment = () => {
setPage(page + 1); setPage(page + 1);
@ -16,21 +19,108 @@ const Home = () => {
}; };
useEffect(() => { useEffect(() => {
setLoading(true);
axios.get(`https://swapi.py4e.com/api/people/?page=${page}`).then((res) => { axios.get(`https://swapi.py4e.com/api/people/?page=${page}`).then((res) => {
setData(res.data.results); setData(res.data.results);
setLoading(false); setFilteredData(res.data.results);
}); });
}, [page]); }, [page]);
useEffect(() => {
const timer = setTimeout(() => {
setLoading(false);
}, 1500);
return () => clearTimeout(timer);
}, []);
const handleSearch = (e) => {
const query = e.target.value.toLowerCase();
setSearchQuery(query);
if (query === "") {
setFilteredData(data);
setSuggestions([]);
return;
}
// FILTERING DATA TO LOWERCASE
const filtered = data.filter((character) =>
character.name.toLowerCase().includes(query)
);
setFilteredData(filtered);
// AUTOCOMPLETE SUGGESTION
const autoSuggestions = data
.map((character) => character.name)
.filter((name) => name.toLowerCase().includes(query))
.slice(0, 5);
setSuggestions(autoSuggestions);
};
// HANDLING SUGGESTION ONCLICK EVENT
const handleSuggestionClick = (suggestion) => {
setSearchQuery(suggestion);
setFilteredData(
data.filter(
(character) => character.name.toLowerCase() === suggestion.toLowerCase()
)
);
setSuggestions([]);
};
return ( return (
<div className="main"> <div className="main">
<div className="header"> <div className="header-container">
<img src="/images/logo.png" alt="Logo" /> <div className="header">
<img src="/images/logo.png" alt="Logo" />
</div>
<div className="search-container">
<input
type="text"
placeholder="Search by name..."
value={searchQuery}
onChange={handleSearch}
className="search-input"
/>
{suggestions.length > 0 && (
<ul className="suggestions">
{suggestions.map((suggestion, index) => (
<li
key={index}
className="suggestion-item"
onClick={() => handleSuggestionClick(suggestion)}
>
{suggestion}
</li>
))}
</ul>
)}
</div>
</div> </div>
{loading ? <p className="loading">Loading...</p> : <CharacterCard data={data} />} {loading ? (
{!loading && <div className="showPage">Page: {page}</div>} <ul className="skeleton-character-container">
{[...(data || Array(5))].map((_, index) => (
<li className="skeleton-character-card skeleton" key={index}>
<div className="skeleton-character-details">
<div className="skeleton-line skeleton-title"></div>
<div className="skeleton-line"></div>
<div className="skeleton-line"></div>
<div className="skeleton-line"></div>
<div className="skeleton-line"></div>
</div>
</li>
))}
</ul>
) : (
<CharacterCard data={filteredData} />
)}
<div className="changePage"> <div className="changePage">
<button className="button" disabled={page === 1} onClick={decrement}> <button className="button" disabled={page === 1} onClick={decrement}>