Skip to content

Commit

Permalink
card de dados do local; fix social nome
Browse files Browse the repository at this point in the history
  • Loading branch information
ruanosena committed Oct 25, 2024
1 parent 972b8c6 commit 388fa9c
Show file tree
Hide file tree
Showing 15 changed files with 452 additions and 32 deletions.
18 changes: 13 additions & 5 deletions app/[localSlugOrEnderecoId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import prisma from "@/lib/prisma";
import { Endereco, isEndereco, isLocal, Local } from "@/types";
import { Endereco, GeoCookieValue, isEndereco, isLocal, Local } from "@/types";
import { notFound } from "next/navigation";
import { transformEndereco, transformLocal } from "@/lib/utils";
import MapPlacesEndereco from "../components/MapPlaces/MapPlacesEndereco";
import MapPlacesLocal from "../components/MapPlaces/MapPlacesLocal";
import { Fragment } from "react";
import { cookies } from "next/headers";

async function queryLocal(slug: string) {
const result = await prisma.local.findUnique({
Expand Down Expand Up @@ -39,14 +43,18 @@ export default async function LocalPage({
}: {
params: { localSlugOrEnderecoId: string };
}) {
const geoCookie = cookies().get("geo");
let geo: GeoCookieValue | undefined;
if (geoCookie) geo = JSON.parse(decodeURIComponent(geoCookie.value));

const data = await getPlace(localSlugOrEnderecoId);

if (!data) return notFound();

return (
<div>
page {data?.id}
<code>{JSON.stringify(data, null, 2)}</code>
</div>
<Fragment>
{isEndereco(data) && <MapPlacesEndereco data={data} {...(geo?.lat && geo.lng && { location: geo })} />}
{isLocal(data) && <MapPlacesLocal data={data} {...(geo?.lat && geo.lng && { location: geo })} />}
</Fragment>
);
}
89 changes: 89 additions & 0 deletions app/components/MapPlaces/LocalPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { cn } from "@/lib/utils";
import * as React from "react";

import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Local } from "@/types";
import { REDE_SOCIAL_NOME } from "@/lib/constants";

import { SocialIcon } from "react-social-icons";

interface Props extends React.HTMLAttributes<HTMLDivElement> {
data: Local;
}

function LocalPanel({ data, className, ...props }: Props) {
const [open, setOpen] = React.useState(true);

return open ? (
<Card className={cn("m-6 max-w-72 text-sm sm:max-w-80", className)} {...props}>
<CardHeader className="py-3">
<CardTitle className="text-base">{data.nome}</CardTitle>
{data.endereco.enderecoFormatado && <CardDescription>{data.endereco.enderecoFormatado}</CardDescription>}
</CardHeader>
<CardContent
className={cn("max-h-[calc(100vh_-_12rem)] cursor-auto overflow-y-scroll px-6 py-0 text-sm", className)}
>
<dl className="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 sm:gap-y-16 lg:gap-x-8">
<div className="border-t border-gray-200 pt-4">
<dt className="font-medium text-gray-900">Nomes populares</dt>
{data.apelidos.map(({ id, apelido }) => (
<dd key={`apelido-${id}`} className="mt-2 text-base">
{apelido}
</dd>
))}
</div>

<div className="border-t border-gray-200 pt-4">
<dt className="font-medium text-gray-900">Redes sociais</dt>
<dd className="mt-2 flex flex-wrap gap-2">
{data.redesSociais.map(({ id, link, nome }) => (
<SocialIcon
key={id}
style={{ width: "2.85rem", height: "2.85rem" }}
url={link}
title={REDE_SOCIAL_NOME[nome]}
/>
))}
</dd>
</div>

<div className="border-t border-gray-200 pt-4">
<dt className="font-medium text-gray-900">Telefone</dt>
<dd className="mt-2 text-base">(11) 95194-9746</dd>
</div>

<div className="border-t border-gray-200 pt-4">
<dt className="font-medium text-gray-900">Site</dt>
<dd className="mt-1 truncate text-base">
<a href={"https://ruanosena.github.io"}>{new URL("https://ruanosena.github.io").hostname}</a>
</dd>
</div>
</dl>
</CardContent>
<CardFooter className="py-3">
<div className="mt-2 flex justify-between">
<a
href="https://codesandbox.io/s/github/visgl/react-google-maps/tree/main/examples/advanced-marker-interaction"
target="_new"
>
Try on CodeSandbox ↗
</a>

<a
href="https://github.com/visgl/react-google-maps/tree/main/examples/advanced-marker-interaction"
target="_new"
>
View Code ↗
</a>
</div>
</CardFooter>
</Card>
) : (
<Button variant="outline" onClick={() => setOpen((prevState) => !prevState)}>
Informações
</Button>
);
}

export default React.memo(LocalPanel);
54 changes: 54 additions & 0 deletions app/components/MapPlaces/MapPlacesEndereco copy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use client";
import { useGeo } from "@/contexts/GeoContext";
import { useDirections } from "@/hooks/useDirections";
import { getEnderecoBounds } from "@/lib/utils";
import { Endereco, isLocal, Local } from "@/types";
import { AdvancedMarker, InfoWindow, Map, Pin, useMap } from "@vis.gl/react-google-maps";
import React, { useEffect, useRef, useState } from "react";

interface Props {
data: Endereco | Local;
location?: google.maps.LatLngLiteral;
}

export default function MapPlaces({ location: locationProps, data }: Props) {
const { location, isDefaultLocation, promptGeolocation } = useGeo();
const { leg } = useDirections(data, isDefaultLocation ? locationProps : location);

const map = useMap();

const [open, setOpen] = useState(false);

const defaultBounds = useRef<google.maps.LatLngBoundsLiteral | undefined>(
getEnderecoBounds(isLocal(data) ? data.endereco : data),
).current;
const { mapsGetBoundingBox, geometryAvailable } = useGeo();

useEffect(() => {
if (!map || !geometryAvailable) return;
// fit local coordinate bounds if no endereco bounds was found
if (!defaultBounds) map.fitBounds(mapsGetBoundingBox(data.lat, data.lng, 250));
}, [map, geometryAvailable, mapsGetBoundingBox, data]);

return (
<Map
className="max-h-screen"
mapId={process.env.NEXT_PUBLIC_MAP_ID}
defaultZoom={12}
defaultBounds={defaultBounds}
gestureHandling={"greedy"}
clickableIcons={false}
disableDefaultUI={false}
>
<AdvancedMarker position={data} onClick={() => setOpen(true)}>
<Pin background={"cyan"} borderColor={"teal"} glyphColor={"darkcyan"} />
</AdvancedMarker>

{open && (
<InfoWindow position={data} onCloseClick={() => setOpen(false)}>
<p>{leg?.distance?.text}</p>
</InfoWindow>
)}
</Map>
);
}
52 changes: 52 additions & 0 deletions app/components/MapPlaces/MapPlacesEndereco.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use client";
import { useGeo } from "@/contexts/GeoContext";
import { useDirections } from "@/hooks/useDirections";
import { getEnderecoBounds } from "@/lib/utils";
import { Endereco } from "@/types";
import { AdvancedMarker, InfoWindow, Map, Pin, useMap } from "@vis.gl/react-google-maps";
import React, { useEffect, useRef, useState } from "react";

interface Props {
data: Endereco;
location?: google.maps.LatLngLiteral;
}

export default function MapPlacesEndereco({ location: locationProps, data }: Props) {
const { location, isDefaultLocation, promptGeolocation } = useGeo();
const { leg } = useDirections(data, isDefaultLocation ? locationProps : location);

const map = useMap();

const [open, setOpen] = useState(false);

const defaultBounds = useRef<google.maps.LatLngBoundsLiteral | undefined>(getEnderecoBounds(data)).current;
const { mapsGetBoundingBox, geometryAvailable } = useGeo();

useEffect(() => {
if (!map || !geometryAvailable) return;

if (!defaultBounds) map.fitBounds(mapsGetBoundingBox(data.lat, data.lng, 250));
}, [map, geometryAvailable, mapsGetBoundingBox, data]);

return (
<Map
className="max-h-screen"
mapId={process.env.NEXT_PUBLIC_MAP_ID}
defaultZoom={12}
defaultBounds={defaultBounds}
gestureHandling={"greedy"}
clickableIcons={false}
disableDefaultUI={false}
>
<AdvancedMarker position={data} onClick={() => setOpen(true)}>
<Pin background={"cyan"} borderColor={"teal"} glyphColor={"darkcyan"} />
</AdvancedMarker>

{open && (
<InfoWindow position={data} onCloseClick={() => setOpen(false)}>
<p>{leg?.distance?.text}</p>
</InfoWindow>
)}
</Map>
);
}
64 changes: 64 additions & 0 deletions app/components/MapPlaces/MapPlacesLocal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";
import { useGeo } from "@/contexts/GeoContext";
import { useDirections } from "@/hooks/useDirections";
import { getEnderecoBounds } from "@/lib/utils";
import { Local } from "@/types";
import { AdvancedMarker, ControlPosition, InfoWindow, Map, Pin, useMap } from "@vis.gl/react-google-maps";
import React, { Fragment, useEffect, useRef, useState } from "react";
import PanelLocal from "./LocalPanel";
import dynamic from "next/dynamic";
const DynamicMapControl = dynamic(() => import("@vis.gl/react-google-maps").then((m) => m.MapControl), { ssr: false });

interface Props {
data: Local;
location?: google.maps.LatLngLiteral;
}

export default function MapPlacesLocal({ data, location: locationProps }: Props) {
const { location, isDefaultLocation, promptGeolocation } = useGeo();
const { leg } = useDirections(data, isDefaultLocation ? locationProps : location);

const map = useMap();

const [open, setOpen] = useState(false);

const defaultBounds = useRef<google.maps.LatLngBoundsLiteral | undefined>(getEnderecoBounds(data.endereco)).current;
const { mapsGetBoundingBox, geometryAvailable } = useGeo();

useEffect(() => {
if (!map || !geometryAvailable) return;

if (!defaultBounds) map.fitBounds(mapsGetBoundingBox(data.lat, data.lng, 250));
}, [map, geometryAvailable, mapsGetBoundingBox, data]);

useEffect(() => {
promptGeolocation();
}, []);

return (
<Fragment>
<Map
className="h-screen"
mapId={process.env.NEXT_PUBLIC_MAP_ID}
defaultZoom={12}
defaultBounds={defaultBounds}
gestureHandling={"greedy"}
clickableIcons={false}
disableDefaultUI={true}
>
<AdvancedMarker position={data} onClick={() => setOpen(true)}>
<Pin background={"cyan"} borderColor={"teal"} glyphColor={"darkcyan"} />
</AdvancedMarker>

{open && (
<InfoWindow position={data} onCloseClick={() => setOpen(false)}>
<p>{leg?.distance?.text}</p>
</InfoWindow>
)}
</Map>
<DynamicMapControl position={ControlPosition.TOP_RIGHT}>
<PanelLocal data={data} />
</DynamicMapControl>
</Fragment>
);
}
21 changes: 10 additions & 11 deletions app/local/add/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
import { MapPlaceMark } from "@/app/components/MapPlaceMark";
import { useCallback, useEffect, useReducer, useRef, useState } from "react";
import { XMarkIcon } from "@heroicons/react/20/solid";
import { MAX_APELIDOS, SOCIAL_LINKS_PLACEHOLDERS } from "@/lib/constants";
import { RedeSocialNome } from "@/types";
import { MAX_APELIDOS, REDE_SOCIAL_NOME, REDE_SOCIAL_PLACEHOLDER } from "@/lib/constants";
import { createLocal } from "@/lib/actions";
import { useMarker } from "@/contexts/MarkerContext";
import { initialState, reducer, REDUCER_ACTION_TYPE } from "@/lib/slices/socialSlice";
Expand All @@ -26,8 +25,8 @@ export default function AddLocal() {
const [socialState, socialDispatch] = useReducer(reducer, initialState);

const handleSocialRemove = useCallback(
(value: RedeSocialNome) => {
const usedIndex = socialState.used.findIndex(([_, uValue]) => uValue === value);
(key: keyof typeof REDE_SOCIAL_NOME) => {
const usedIndex = socialState.used.findIndex(([usedKey]) => usedKey === key);
if (usedIndex > -1) socialDispatch({ type: REDUCER_ACTION_TYPE.REMOVE, payload: usedIndex });
},
[socialState.used],
Expand Down Expand Up @@ -354,28 +353,28 @@ export default function AddLocal() {
<h2 className="font-semibold leading-7 text-gray-900">Social</h2>
<p className="mt-1 leading-6 text-gray-600">Conecte as redes sociais</p>
<div className="mt-6 space-y-6">
{socialState.used.map(([key, value], index) => (
{socialState.used.map(([key, value]) => (
<div key={value} className="flex flex-col gap-x-2 sm:flex-row">
<input
type="text"
defaultValue={key}
defaultValue={value}
className="block max-w-36 rounded-md border-0 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 invalid:ring-red-500 read-only:text-gray-500 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-lg sm:leading-6 lg:max-w-48"
readOnly
/>
<input name="socialNome" type="hidden" value={value} />
<input name="socialNome" type="hidden" value={key} />

<div className="flex flex-1 gap-x-2">
<input
name="socialLink"
type="url"
autoComplete="url"
placeholder={SOCIAL_LINKS_PLACEHOLDERS[value]}
placeholder={REDE_SOCIAL_PLACEHOLDER[key]}
className="inline-block w-full rounded-md border-0 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-lg sm:leading-6"
/>
<button
type="button"
className="rounded-md px-3.5 py-2.5 text-center text-sm font-semibold outline-none ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600"
onClick={() => handleSocialRemove(value)}
onClick={() => handleSocialRemove(key)}
>
<XMarkIcon className="size-5" />
</button>
Expand All @@ -392,8 +391,8 @@ export default function AddLocal() {
-rede social-
</option>
{socialState.available.map(([aKey, aValue]) => (
<option key={`option-${aValue}`} value={aValue}>
{aKey}
<option key={`option-${aKey}`} value={aValue}>
{aValue}
</option>
))}
</select>
Expand Down
2 changes: 1 addition & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default async function Home() {
<Fragment>
<Search
// avoids cookie without coords
{...(geo?.lat && geo.lng && { location: { lat: geo.lat, lng: geo.lng } })}
{...(geo?.lat && geo.lng && { location: geo })}
/>

<section className="flex min-h-screen items-center bg-background">
Expand Down
Loading

0 comments on commit 388fa9c

Please sign in to comment.