Skip to content

Commit

Permalink
Merge pull request #13 from fabriciopgl/feature/1
Browse files Browse the repository at this point in the history
✨ Adiciona página de detalhes e mapa de localização
  • Loading branch information
fabriciopgl authored Nov 28, 2024
2 parents e7e65c4 + 1318c74 commit b39b3cf
Show file tree
Hide file tree
Showing 15 changed files with 436 additions and 143 deletions.
72 changes: 52 additions & 20 deletions app/detalhes/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
"use client";
import Carousel from "@/components/Carousel";
const Map = dynamic(() => import("../../components/Map/index"), { ssr: false });
import { usePlacesContext } from "@/contexts/PlacesContext";
import { Place, PlaceDetails } from "@/domains/Places/types";
import useCoordinates from "@/hooks/useCoordinates";
import dynamic from "next/dynamic";
import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { FaMapPin } from "react-icons/fa";

const PlaceDetailsComponent: React.FC = () => {
const PlaceDetailsPage: React.FC = () => {
const [selectedPlace, setSelectedPlace] = useState<Place>();
const { convertCoordinates } = useCoordinates();
const { places, placeDetails, loading, fetchPlaceDetails } =
usePlacesContext();
const searchParams = useSearchParams();

const placeId = searchParams.get("placeId");

useEffect(() => {
Expand All @@ -29,39 +34,66 @@ const PlaceDetailsComponent: React.FC = () => {

return (
<div className="container mx-auto p-4">
{/* Skeleton do carousel */}
<div className="relative h-[50vh] sm:h-[25vh] md:h-[30vh] lg:h-[40vh] xl:h-[50vh] portrait:h-[15vh] mb-8">
{loading ? (
// Skeleton loader enquanto os dados são carregados
{loading || !placeDetails || !selectedPlace ? (
<div className="w-full h-full bg-gray-200 animate-pulse rounded-lg">
<div className="w-full h-full bg-gray-300 rounded-lg"></div>
</div>
) : (
// Carousel real depois que os dados foram carregados
<Carousel images={placeDetails?.carouselImages || []} />
)}
</div>

{/* Nome do local */}
<h1 className="text-4xl font-bold mb-4">{selectedPlace?.name}</h1>
{/* Skeleton do nome do local */}
{loading || !placeDetails || !selectedPlace ? (
<div className="h-8 w-3/4 bg-gray-200 animate-pulse rounded-lg mb-4"></div>
) : (
<h1 className="text-4xl font-bold mb-4">{selectedPlace?.name}</h1>
)}

<div className="flex items-center space-x-2 mb-4">
<FaMapPin className="text-red-500" />
<span className="text-lg">{placeDetails?.location}</span>
</div>
{/* Skeleton do ícone e localização */}
{loading || !placeDetails || !selectedPlace ? (
<div className="flex items-center space-x-2 mb-4">
<div className="h-8 w-8 bg-gray-200 animate-pulse rounded-full"></div>
<div className="h-6 w-1/2 bg-gray-200 animate-pulse rounded-lg"></div>
</div>
) : (
<div className="flex items-center space-x-2 mb-4">
<FaMapPin className="text-red-500 text-2xl" />
<span className="text-lg font-semibold">
{placeDetails?.location}
</span>
</div>
)}

{/* Descrição do local */}
<p className="text-lg mb-8">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque sit
amet sapien eu ligula blandit consequat. Integer eget dolor in lacus
posuere volutpat.
</p>
{/* Skeleton da descrição */}
{loading || !placeDetails || !selectedPlace ? (
<div className="space-y-2 mb-8">
<div className="h-6 w-full bg-gray-200 animate-pulse rounded-lg"></div>
<div className="h-6 w-5/6 bg-gray-200 animate-pulse rounded-lg"></div>
<div className="h-6 w-4/6 bg-gray-200 animate-pulse rounded-lg"></div>
</div>
) : (
<p className="text-lg mb-8 text-justify">{placeDetails?.description}</p>
)}

{/* Mapa com a localização */}
{selectedPlace?.description && (
<div className="w-full h-[400px] mb-8"></div>
{/* Skeleton do mapa */}
{loading || !placeDetails || !selectedPlace ? (
<div className="w-full h-[400px] bg-gray-200 animate-pulse rounded-lg mb-8"></div>
) : (
selectedPlace?.description && (
<div className="w-full h-[400px] mb-8">
<Map
coordinates={convertCoordinates(
placeDetails?.locationCoordinates || "-29.2544,-51.5312"
)}
/>
</div>
)
)}
</div>
);
};

export default PlaceDetailsComponent;
export default PlaceDetailsPage;
1 change: 1 addition & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Footer from "@/components/Footer";
import Header from "@/components/Header";
import type { Metadata } from "next";
import "leaflet/dist/leaflet.css";
import "../styles/globals.css";
import Providers from "./providers";

Expand Down
4 changes: 2 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { FaArrowLeft, FaArrowRight, FaSearch } from "react-icons/fa";
export default function HomePage() {
const { user, isAnonymous } = useAuth();
const { favorites, fetchFavorites, isLoading } = useFavorites();
const { places, loading } = usePlacesContext();
const { filteredPlaces: places, loading } = usePlacesContext();
const [currentPage, setCurrentPage] = useState(1);
const [filteredPlaces, setFilteredPlaces] = useState<Place[]>(places);
const [searchValue, setSearchValue] = useState("");
Expand Down Expand Up @@ -82,7 +82,7 @@ export default function HomePage() {
</div>
</div>

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5 pt-8">
<div className="grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-5 pt-8">
{loading || isLoading
? Array.from({ length: 9 }).map((_, index) => (
<Card
Expand Down
33 changes: 31 additions & 2 deletions app/sobre/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
// app/routes/about/page.tsx
import { FaGit, FaGitAlt, FaGithub } from "react-icons/fa";

export default function AboutPage() {
return (
<div className="container mx-auto p-4">
<h1 className="text-4xl font-bold mb-4">Sobre o Aplicativo</h1>
<p>
<p className="text-lg text-gray-700 mb-4">
Nosso aplicativo promove o turismo sustentável na Serra Gaúcha,
conectando visitantes a experiências autênticas e empreendimentos
locais.
</p>

<p className="text-lg text-gray-700 mb-6">
Este aplicativo foi desenvolvido como parte da Atividade Extensionista
IV da Uninter, por Fabrício Pagliarini sob o RU 4110920.
</p>

<div className="border-t border-gray-200 pt-6 mt-6">
<div className="flex items-center space-x-2">
<FaGithub className="text-2xl text-gray-800" />
<h2 className="text-2xl font-semibold text-gray-800">
Contribua para o Projeto
</h2>
</div>

<p className="text-lg text-gray-700 mb-2 mt-4">
O código-fonte do projeto está disponível no GitHub. Sinta-se à
vontade para explorar o mesmo!
</p>

<a
href="https://github.com/fabriciopgl/turismo-serra-gaucha"
target="_blank"
rel="noopener noreferrer"
className="text-lg text-blue-600 hover:underline"
>
Acesse o repositório no GitHub
</a>
</div>
</div>
);
}
129 changes: 84 additions & 45 deletions components/Carousel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,103 @@
"use client";
import { useState } from "react";
import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
import { useState, useEffect, useRef } from "react";

const Carousel = ({ images }: { images: string[] }) => {
const [currentIndex, setCurrentIndex] = useState(0);
const [isDragging, setIsDragging] = useState(false);
const [startX, setStartX] = useState(0);
const [translate, setTranslate] = useState(0);
const autoSlideRef = useRef<NodeJS.Timeout | null>(null);

const goToNext = () => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % images.length);
const handleDragStart = (e: React.MouseEvent | React.TouchEvent) => {
e.preventDefault();
setIsDragging(true);
const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
setStartX(clientX);
clearAutoSlide();
};

const goToPrevious = () => {
setCurrentIndex(
(prevIndex) => (prevIndex - 1 + images.length) % images.length
);
const handleDragMove = (e: React.MouseEvent | React.TouchEvent) => {
if (!isDragging) return;
const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
setTranslate(clientX - startX);
};

return (
<div>
{/* Imagens */}
<div className="absolute inset-0 overflow-hidden">
<div
className="flex transition-transform duration-500 ease-in-out"
style={{
transform: `translateX(-${currentIndex * 100}%)`,
}}
>
{images.map((image, index) => (
<div
key={index}
className="w-full h-full flex justify-center items-center flex-shrink-0"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={image}
alt={`Imagem ${index + 1}`}
className="w-full h-full object-contain"
/>
</div>
))}
</div>
</div>
const handleDragEnd = () => {
if (!isDragging) return;
setIsDragging(false);

<button
onClick={goToPrevious}
className="absolute left-4 top-1/2 transform -translate-y-1/2 bg-green-900 text-white p-2 rounded-full shadow-lg z-10"
>
<FaChevronLeft />
</button>
<button
onClick={goToNext}
className="absolute right-4 top-1/2 transform -translate-y-1/2 bg-green-900 text-white p-2 rounded-full shadow-lg z-10"
if (translate > 50 && currentIndex > 0) {
setCurrentIndex((prev) => prev - 1);
} else if (translate < -50 && currentIndex < images.length - 1) {
setCurrentIndex((prev) => prev + 1);
}

setTranslate(0);
restartAutoSlide();
};

useEffect(() => {
restartAutoSlide();
return () => clearAutoSlide();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentIndex]);

const restartAutoSlide = () => {
clearAutoSlide();
autoSlideRef.current = setTimeout(() => {
setCurrentIndex((prev) => (prev + 1) % images.length);
}, 5000);
};

const clearAutoSlide = () => {
if (autoSlideRef.current) {
clearTimeout(autoSlideRef.current);
autoSlideRef.current = null;
}
};

return (
<div
onMouseDown={handleDragStart}
onMouseMove={handleDragMove}
onMouseUp={handleDragEnd}
onMouseLeave={handleDragEnd}
onTouchStart={handleDragStart}
onTouchMove={handleDragMove}
onTouchEnd={handleDragEnd}
className="relative w-full h-full overflow-hidden rounded-lg"
>
<div
className="flex transition-transform duration-700 ease-in-out cursor-pointer"
style={{
transform: `translateX(calc(-${
currentIndex * 100
}% + ${translate}px))`,
}}
>
<FaChevronRight />
</button>
{images.map((image, index) => (
<div
key={index}
className="w-full h-full flex justify-center items-center flex-shrink-0"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={image}
alt={`Image ${index + 1}`}
className="w-full h-full object-contain pointer-events-none"
/>
</div>
))}
</div>

<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex space-x-2">
{images.map((_, index) => (
<div
key={index}
onClick={() => setCurrentIndex(index)}
onClick={() => {
setCurrentIndex(index);
restartAutoSlide();
}}
className={`w-3 h-3 rounded-full bg-white cursor-pointer ${
currentIndex === index ? "bg-opacity-100" : "bg-opacity-50"
}`}
Expand Down
Loading

0 comments on commit b39b3cf

Please sign in to comment.