diff --git a/.prettierrc b/.prettierrc index 35606a40..25b3c703 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,6 @@ { "printWidth": 130, - "bracketSameLine": true, + "bracketSameLine": false, "tabWidth": 2, "useTabs": false, "singleQuote": true, diff --git a/components/home-components/HeroSection.jsx b/components/home-components/HeroSection.jsx index 2f5350ac..f990c305 100644 --- a/components/home-components/HeroSection.jsx +++ b/components/home-components/HeroSection.jsx @@ -8,7 +8,7 @@ function HomepageHeroSection() { const title = t('homePage:homePageHero.title'); const subtitle = t('homePage:homePageHero.subtitle'); - useEffect(() => { + useEffect(() => { let isMounted = true; const initializeParticles = async () => { diff --git a/pages/launches/[launchName].tsx b/pages/launches/[launchName].tsx index 96fbae09..ac4d1bc2 100644 --- a/pages/launches/[launchName].tsx +++ b/pages/launches/[launchName].tsx @@ -1,25 +1,44 @@ -import { useEffect, useMemo, useState } from 'react'; -import Link from 'next/link'; +import React, { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/router'; -import { Alert, Box, Button, Card, CardContent, Chip, CircularProgress, Container, Stack, Typography } from '@mui/material'; +import Link from 'next/link'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; -import { formatLaunchDatetime, formatLaunchName, slugifyLaunchName } from '@/src/shared/utils/formatters.utils'; +import { Alert, Box, Button, Card, CardContent, Chip, CircularProgress, Container, Stack, Typography } from '@mui/material'; +import { + formatAltitude, + formatAltitudeInKm, + formatLaunchDatetime, + formatLaunchName, + slugifyLaunchName +} from '@/src/shared/utils/formatters.utils'; import { useGetLaunchContent } from '@/src/core/services/launches/useGetLaunchContent.service'; import { LaunchSummary } from '@/src/shared/types/api/launches-api.types'; import dynamic from 'next/dynamic'; +import LaunchAndLandingCities from '@/src/components/LaunchAndLandingCities/LaunchAndLandingCities'; const SELECTED_LAUNCH_STORAGE_KEY = 'zenith-selected-launch'; +// TODO mover textos para arquivo de tradução +// TODO mover para arquivos de formatters +const formatMissionDuration = (start: string, end: string) => { + const durationInSeconds = Math.max(0, Math.round((new Date(end).getTime() - new Date(start).getTime()) / 1000)); + const hours = Math.floor(durationInSeconds / 3600); + const minutes = Math.floor((durationInSeconds % 3600) / 60); + const seconds = durationInSeconds % 60; + + return `${hours}h ${minutes}min ${seconds}seg`; +}; + export default function LaunchDetailsPage() { const router = useRouter(); const launchName = typeof router.query.launchName === 'string' ? router.query.launchName : ''; const [launch, setLaunch] = useState(null); + const [launchResolved, setLaunchResolved] = useState(false); const { records, isLoadingRecords, recordsError } = useGetLaunchContent(launch?.download_url ?? '', Boolean(launch)); - const Map = useMemo( + const MapTrajectory = useMemo( () => - dynamic(() => import('@/src/components/Map/Map'), { + dynamic(() => import('@/src/components/Map/MapTrajectory'), { loading: () =>

A map is loading

, ssr: false }), @@ -27,10 +46,17 @@ export default function LaunchDetailsPage() { ); useEffect(() => { + if (!router.isReady) { + return; + } + + setLaunchResolved(false); + const storedLaunch = sessionStorage.getItem(SELECTED_LAUNCH_STORAGE_KEY); if (!storedLaunch) { setLaunch(null); + setLaunchResolved(true); return; } @@ -40,110 +66,123 @@ export default function LaunchDetailsPage() { if (storedLaunchName !== launchName) { setLaunch(null); + setLaunchResolved(true); return; } setLaunch(parsedLaunch); } catch { setLaunch(null); + } finally { + setLaunchResolved(true); } - }, [launchName]); + }, [launchName, router.isReady]); - if (!launchName) { - return ( - - - - Lançamento não encontrado. - - - ); - } - - if (!launch) { - return ( - - - - Lançamento não encontrado nesta sessão. Volte para a lista e abra o detalhe novamente. - - - ); - } - - if (isLoadingRecords) { - return ( - - - - - - ); - } - if (recordsError) { - return ( - - {recordsError} - - ); - } - - if (records.length === 0) { - return ( - - - - Lançamento não encontrado. + {!router.isReady || + (!launchResolved && ( + + + + ))} + + {!launchName || + (!launch && ( + + {!launchName + ? 'Lançamento não encontrado.' + : 'Lançamento não encontrado nesta sessão. Volte para a lista e abra o detalhe novamente.'} + + ))} + + {isLoadingRecords && ( + + + + )} + + {recordsError && {recordsError}} + + {records.length === 0 && Lançamento não encontrado.} + + {launch && records.length > 0 && ( + + + + + + + {formatLaunchName(launch.name)} + + + {formatLaunchDatetime(launch.launch_datetime)} + + + + + + + {/* */} + + + + + + + + + + + Trajetória do lançamento + + + + [r.lat, r.lon])} + trajectoryRecords={records} + landingCity={launch.landing_city} + lineColor="#f44336" + lineWeight={4} + mapHeight="100vh" + /> + + + )} - ); - } - - return ( - - - - - - - - - - {formatLaunchName(launch.name)} - - - {formatLaunchDatetime(launch.launch_datetime)} - - - - - - - - - - - - - - - Leituras do lançamento - - - - - - - - + ); } diff --git a/pages/launches/index.tsx b/pages/launches/index.tsx index 49833ac1..f21cd548 100644 --- a/pages/launches/index.tsx +++ b/pages/launches/index.tsx @@ -1,22 +1,11 @@ import { useEffect } from 'react'; import { useRouter } from 'next/router'; import { useAllLaunches } from '@/src/core/services/launches/useGetAllLaunches.service'; -import { - convertAltitudeToKm, - formatLaunchDatetime, - formatLaunchName, - slugifyLaunchName -} from '@/src/shared/utils/formatters.utils'; -import { Alert, Box, Button, Card, CardActions, CardContent, CardHeader } from '@mui/material'; -import { Chip, CircularProgress, Container, Stack, Typography } from '@mui/material'; -import { Timeline, TimelineItem, TimelineSeparator, TimelineConnector } from '@mui/lab'; -import { TimelineContent, TimelineDot, timelineItemClasses } from '@mui/lab'; +import { slugifyLaunchName } from '@/src/shared/utils/formatters.utils'; +import { Alert, Box, CircularProgress, Container, Stack, Typography } from '@mui/material'; import type {} from '@mui/lab/themeAugmentation'; -import PinDropIcon from '@mui/icons-material/PinDrop'; -import HeightIcon from '@mui/icons-material/Height'; -import ChevronRightIcon from '@mui/icons-material/ChevronRight'; -import ShareLocationIcon from '@mui/icons-material/ShareLocation'; import useTranslation from 'next-translate/useTranslation'; +import LaunchSummaryCard from '@/src/components/LaunchCard/LaunchSummaryCard'; const SELECTED_LAUNCH_STORAGE_KEY = 'zenith-selected-launch'; @@ -73,67 +62,11 @@ export default function LaunchesPage() { } }}> {launches.map((launch) => ( - - - {formatLaunchName(launch.name)} - - } - subheader={formatLaunchDatetime(launch.launch_datetime)} - /> - - - - - - - - - - - {/* */} - {/* */} - - {launch.launch_city} - - - - - - - - - {launch.landing_city} - - - - } - label={convertAltitudeToKm(launch.max_altitude)} - color="primary" - variant="filled" - sx={{ alignSelf: 'flex-start', mt: 3 }} - /> - - - - - - - + ))} )} diff --git a/public/images/markersSondehub/antenna-bronze.png b/public/images/markersSondehub/antenna-bronze.png new file mode 100644 index 00000000..e0a82e49 Binary files /dev/null and b/public/images/markersSondehub/antenna-bronze.png differ diff --git a/public/images/markersSondehub/antenna-gold.png b/public/images/markersSondehub/antenna-gold.png new file mode 100644 index 00000000..c309217d Binary files /dev/null and b/public/images/markersSondehub/antenna-gold.png differ diff --git a/public/images/markersSondehub/antenna-silver.png b/public/images/markersSondehub/antenna-silver.png new file mode 100644 index 00000000..c23fc80f Binary files /dev/null and b/public/images/markersSondehub/antenna-silver.png differ diff --git a/public/images/markersSondehub/antenna-white.png b/public/images/markersSondehub/antenna-white.png new file mode 100644 index 00000000..4cafc55c Binary files /dev/null and b/public/images/markersSondehub/antenna-white.png differ diff --git a/public/images/markersSondehub/antenna.png b/public/images/markersSondehub/antenna.png new file mode 100644 index 00000000..707518db Binary files /dev/null and b/public/images/markersSondehub/antenna.png differ diff --git a/public/images/markersSondehub/balloon-pop.png b/public/images/markersSondehub/balloon-pop.png new file mode 100644 index 00000000..eac488b2 Binary files /dev/null and b/public/images/markersSondehub/balloon-pop.png differ diff --git a/public/images/markersSondehub/balloon-xmark.png b/public/images/markersSondehub/balloon-xmark.png new file mode 100644 index 00000000..6e8c2014 Binary files /dev/null and b/public/images/markersSondehub/balloon-xmark.png differ diff --git a/public/images/markersSondehub/balloon.svg b/public/images/markersSondehub/balloon.svg new file mode 100755 index 00000000..89665058 --- /dev/null +++ b/public/images/markersSondehub/balloon.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/markersSondehub/car.svg b/public/images/markersSondehub/car.svg new file mode 100644 index 00000000..c277ce87 --- /dev/null +++ b/public/images/markersSondehub/car.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/markersSondehub/hab_nyan.gif b/public/images/markersSondehub/hab_nyan.gif new file mode 100644 index 00000000..bfb68766 Binary files /dev/null and b/public/images/markersSondehub/hab_nyan.gif differ diff --git a/public/images/markersSondehub/iss.png b/public/images/markersSondehub/iss.png new file mode 100644 index 00000000..a6bb97f5 Binary files /dev/null and b/public/images/markersSondehub/iss.png differ diff --git a/public/images/markersSondehub/marker_hole.png b/public/images/markersSondehub/marker_hole.png new file mode 100644 index 00000000..17481f2e Binary files /dev/null and b/public/images/markersSondehub/marker_hole.png differ diff --git a/public/images/markersSondehub/nyan-afro.gif b/public/images/markersSondehub/nyan-afro.gif new file mode 100644 index 00000000..62cf2d77 Binary files /dev/null and b/public/images/markersSondehub/nyan-afro.gif differ diff --git a/public/images/markersSondehub/nyan-coin.gif b/public/images/markersSondehub/nyan-coin.gif new file mode 100644 index 00000000..7bde0a29 Binary files /dev/null and b/public/images/markersSondehub/nyan-coin.gif differ diff --git a/public/images/markersSondehub/nyan-cool.gif b/public/images/markersSondehub/nyan-cool.gif new file mode 100644 index 00000000..82260e21 Binary files /dev/null and b/public/images/markersSondehub/nyan-cool.gif differ diff --git a/public/images/markersSondehub/nyan-gameboy.gif b/public/images/markersSondehub/nyan-gameboy.gif new file mode 100644 index 00000000..a3389e69 Binary files /dev/null and b/public/images/markersSondehub/nyan-gameboy.gif differ diff --git a/public/images/markersSondehub/nyan-mon.gif b/public/images/markersSondehub/nyan-mon.gif new file mode 100644 index 00000000..afb98064 Binary files /dev/null and b/public/images/markersSondehub/nyan-mon.gif differ diff --git a/public/images/markersSondehub/nyan-mummy.gif b/public/images/markersSondehub/nyan-mummy.gif new file mode 100644 index 00000000..6221b5fb Binary files /dev/null and b/public/images/markersSondehub/nyan-mummy.gif differ diff --git a/public/images/markersSondehub/nyan-pirate.gif b/public/images/markersSondehub/nyan-pirate.gif new file mode 100644 index 00000000..7e47916b Binary files /dev/null and b/public/images/markersSondehub/nyan-pirate.gif differ diff --git a/public/images/markersSondehub/nyan-pumpkin.gif b/public/images/markersSondehub/nyan-pumpkin.gif new file mode 100644 index 00000000..8952601a Binary files /dev/null and b/public/images/markersSondehub/nyan-pumpkin.gif differ diff --git a/public/images/markersSondehub/nyan-tothemax.gif b/public/images/markersSondehub/nyan-tothemax.gif new file mode 100644 index 00000000..e9787491 Binary files /dev/null and b/public/images/markersSondehub/nyan-tothemax.gif differ diff --git a/public/images/markersSondehub/nyan.gif b/public/images/markersSondehub/nyan.gif new file mode 100644 index 00000000..fc230a18 Binary files /dev/null and b/public/images/markersSondehub/nyan.gif differ diff --git a/public/images/markersSondehub/parachute.svg b/public/images/markersSondehub/parachute.svg new file mode 100644 index 00000000..8b10853a --- /dev/null +++ b/public/images/markersSondehub/parachute.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/markersSondehub/payload-not-recovered.png b/public/images/markersSondehub/payload-not-recovered.png new file mode 100644 index 00000000..a75bb17a Binary files /dev/null and b/public/images/markersSondehub/payload-not-recovered.png differ diff --git a/public/images/markersSondehub/payload-recovered.png b/public/images/markersSondehub/payload-recovered.png new file mode 100644 index 00000000..2a6f8eec Binary files /dev/null and b/public/images/markersSondehub/payload-recovered.png differ diff --git a/public/images/markersSondehub/payload-recovery-planned.png b/public/images/markersSondehub/payload-recovery-planned.png new file mode 100644 index 00000000..e3147149 Binary files /dev/null and b/public/images/markersSondehub/payload-recovery-planned.png differ diff --git a/public/images/markersSondehub/payload.svg b/public/images/markersSondehub/payload.svg new file mode 100644 index 00000000..b89d7d61 --- /dev/null +++ b/public/images/markersSondehub/payload.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/markersSondehub/shadow.png b/public/images/markersSondehub/shadow.png new file mode 100644 index 00000000..b9aaf2b1 Binary files /dev/null and b/public/images/markersSondehub/shadow.png differ diff --git a/public/images/markersSondehub/target.svg b/public/images/markersSondehub/target.svg new file mode 100644 index 00000000..e80875e0 --- /dev/null +++ b/public/images/markersSondehub/target.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/LaunchAndLandingCities/LaunchAndLandingCities.tsx b/src/components/LaunchAndLandingCities/LaunchAndLandingCities.tsx new file mode 100644 index 00000000..1eb7cce5 --- /dev/null +++ b/src/components/LaunchAndLandingCities/LaunchAndLandingCities.tsx @@ -0,0 +1,51 @@ +import { Box } from '@mui/material'; +import { + Timeline, + TimelineConnector, + TimelineContent, + TimelineDot, + TimelineItem, + TimelineSeparator, + timelineItemClasses +} from '@mui/lab'; +import type {} from '@mui/lab/themeAugmentation'; +import ShareLocationIcon from '@mui/icons-material/ShareLocation'; +import PinDropIcon from '@mui/icons-material/PinDrop'; +import { LaunchAndLandingCitiesProps } from '@/src/shared/props/components/launch-and-landing.props'; + +export default function LaunchAndLandingCities({ + startLabel, + endLabel, + startIcon = , + endIcon = +}: LaunchAndLandingCitiesProps) { + return ( + + + + + {startIcon} + + + {startLabel} + + + + + {endIcon} + + {endLabel} + + + + ); +} diff --git a/src/components/LaunchCard/LaunchSummaryCard.tsx b/src/components/LaunchCard/LaunchSummaryCard.tsx new file mode 100644 index 00000000..db646300 --- /dev/null +++ b/src/components/LaunchCard/LaunchSummaryCard.tsx @@ -0,0 +1,42 @@ +import { Card, CardActions, CardContent, CardHeader, Button, Chip, Stack, Typography, Box } from '@mui/material'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import HeightIcon from '@mui/icons-material/Height'; +import LaunchAndLandingCities from '@/src/components/LaunchAndLandingCities/LaunchAndLandingCities'; +import { formatAltitudeInKm, formatLaunchDatetime, formatLaunchName } from '@/src/shared/utils/formatters.utils'; +import { LaunchSummaryCardProps } from '@/src/shared/props/components/launch-summary-card.props'; + +export default function LaunchSummaryCard({ launch, onDetailsClick }: LaunchSummaryCardProps) { + return ( + + + {formatLaunchName(launch.name)} + + } + subheader={formatLaunchDatetime(launch.launch_datetime)} + /> + + + + + + } + label={formatAltitudeInKm(launch.max_altitude)} + color="primary" + variant="filled" + sx={{ alignSelf: 'flex-start', mt: 3 }} + /> + + + + + + + + ); +} diff --git a/src/components/Map/Map.styles.ts b/src/components/Map/Map.styles.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/Map/Map.tsx b/src/components/Map/Map.tsx deleted file mode 100644 index 9d8e93bb..00000000 --- a/src/components/Map/Map.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet'; -import 'leaflet/dist/leaflet.css'; -import 'leaflet-defaulticon-compatibility'; -import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css'; -import { MapProps } from '@/src/shared/types/map.types'; -import { useEffect } from 'react'; -import { useAllLaunches } from '@/src/core/services/launches/useGetAllLaunches.service'; - -export default function MyMap(props: MapProps) { - const { position = [0, 0], zoom = 2 } = props; - - return ( -
- - - - - A pretty CSS3 popup.
Easily customizable. -
-
-
-
- ); -} diff --git a/src/components/LaunchCard/LaunchCard.tsx b/src/components/Map/MapTrajectory.styles.ts similarity index 100% rename from src/components/LaunchCard/LaunchCard.tsx rename to src/components/Map/MapTrajectory.styles.ts diff --git a/src/components/Map/MapTrajectory.tsx b/src/components/Map/MapTrajectory.tsx new file mode 100644 index 00000000..f5e8de3c --- /dev/null +++ b/src/components/Map/MapTrajectory.tsx @@ -0,0 +1,157 @@ +import { useEffect } from 'react'; +import L from 'leaflet'; +import { LayersControl, MapContainer, Marker, Polyline, Popup, TileLayer, useMap } from 'react-leaflet'; +import 'leaflet/dist/leaflet.css'; +import 'leaflet-defaulticon-compatibility'; +import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css'; +import { MapProps } from '@/src/shared/types/map.types'; +import { formatAltitudeInKm, formatLaunchDatetime } from '@/src/shared/utils/formatters.utils'; + +const parachutIconUrl = '/images/markersSondehub/parachute.svg'; +const payloadNotRecoveredIconUrl = '/images/markersSondehub/payload-not-recovered.png'; +const startMarkerIconUrl = '/images/markersSondehub/target.svg'; + +const startMarkerIcon = L.icon({ + iconUrl: startMarkerIconUrl, + iconSize: [12, 12], + iconAnchor: [6, 6], + popupAnchor: [1, -34], + shadowSize: [41, 41] +}); + +const endMarkerIcon = L.icon({ + iconUrl: parachutIconUrl, + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + shadowSize: [41, 41] +}); + +const unknownEndMarkerIcon = L.icon({ + iconUrl: payloadNotRecoveredIconUrl, + iconSize: [28, 28], + iconAnchor: [14, 14], + popupAnchor: [1, -16], + shadowSize: [41, 41] +}); + +function FitBoundsToTrajectory({ trajectory }: { trajectory: MapProps['trajectory'] }) { + const map = useMap(); + + useEffect(() => { + if (!trajectory || trajectory.length < 2) { + return; + } + + map.fitBounds(trajectory, { padding: [24, 24] }); + }, [map, trajectory]); + + return null; +} + +function LaunchPointPopup({ + title, + record, + fallbackLabel +}: { + title: string; + record?: MapProps['trajectoryRecords'][number]; + fallbackLabel: string; +}) { + // TODO: move formatters to utils + const formatNumber = (value: number, digits = 2) => value.toFixed(digits); + const formatVelocity = (velocity: number) => `${formatNumber(velocity)} m/s`; + const formatBattery = (battery: number) => `${formatNumber(battery)} V`; + + return ( +
+
{title}
+ {record ? ( +
+
{formatLaunchDatetime(record.datetime)}
+
Callsign: {record.uploader_callsign}
+
Serial: {record.serial}
+
Fabricante: {record.manufacturer}
+
+ Tipo/Subtipo: {record.type} / {record.subtype} +
+
Bateria: {formatBattery(record.batt)}
+ +
Altitude: {formatAltitudeInKm(record.alt)}
+
Vel. horizontal: {formatVelocity(record.vel_h)}
+
Vel. vertical: {formatVelocity(record.vel_v)}
+
+ Lat/Lon: {record.lat.toFixed(5)}, {record.lon.toFixed(5)} +
+
+ ) : ( +
{fallbackLabel}
+ )} +
+ ); +} + +export default function MapTrajectory(props: MapProps) { + const { + position = [0, 0], + zoom = 2, + trajectory = [], + trajectoryRecords = [], + landingCity = '', + lineColor = '#d32f2f', + lineWeight = 2, + mapHeight = '100vh' + } = props; + const hasTrajectory = trajectory.length > 1; + const startPosition = hasTrajectory ? trajectory[0] : position; + const endPosition = hasTrajectory ? trajectory[trajectory.length - 1] : position; + const startRecord = trajectoryRecords[0]; + const endRecord = trajectoryRecords[trajectoryRecords.length - 1]; + const { BaseLayer } = LayersControl; + const isUnknownEndPoint = Boolean( + landingCity && (landingCity.toLowerCase().includes('desconhecido') || landingCity.toLowerCase().includes('unknown')) + ); + const finalMarkerIcon = isUnknownEndPoint ? unknownEndMarkerIcon : endMarkerIcon; + + return ( +
+ + + + + + + + + + + {hasTrajectory && } + + {hasTrajectory ? ( + <> + + + + + + + + + + + + ) : ( + + Ponto do lancamento + + )} + +
+ ); +} diff --git a/src/shared/props/components/launch-and-landing.props.ts b/src/shared/props/components/launch-and-landing.props.ts new file mode 100644 index 00000000..140aab84 --- /dev/null +++ b/src/shared/props/components/launch-and-landing.props.ts @@ -0,0 +1,8 @@ +import { ReactNode } from 'react'; + +export type LaunchAndLandingCitiesProps = { + startLabel: string; + endLabel: string; + startIcon?: ReactNode; + endIcon?: ReactNode; +}; diff --git a/src/shared/props/components/launch-summary-card.props.ts b/src/shared/props/components/launch-summary-card.props.ts new file mode 100644 index 00000000..04c63858 --- /dev/null +++ b/src/shared/props/components/launch-summary-card.props.ts @@ -0,0 +1,6 @@ +import { LaunchSummary } from '../../types/api/launches-api.types'; + +export type LaunchSummaryCardProps = { + launch: LaunchSummary; + onDetailsClick: (launch: LaunchSummary) => void; +}; diff --git a/src/shared/types/map.types.ts b/src/shared/types/map.types.ts index 9046807a..9b97e977 100644 --- a/src/shared/types/map.types.ts +++ b/src/shared/types/map.types.ts @@ -1,4 +1,18 @@ +import { LaunchRecord } from '@/src/shared/types/api/launches-api.types'; + export type MapProps = { - position?: [number, number]; + position?: Position; zoom?: number; + trajectory?: LaunchTrajectory; + trajectoryRecords?: LaunchRecord[]; + landingCity?: string; + lineColor?: string; + lineWeight?: number; + mapHeight?: string; }; + +export type Position = [Latitude, Longitude]; +export type Latitude = number; +export type Longitude = number; + +export type LaunchTrajectory = Position[]; diff --git a/src/shared/utils/formatters.utils.ts b/src/shared/utils/formatters.utils.ts index 1aea5e10..c3353332 100644 --- a/src/shared/utils/formatters.utils.ts +++ b/src/shared/utils/formatters.utils.ts @@ -57,16 +57,10 @@ export const formatLaunchDatetime = (datetime: string): string => { locale: ptBR }); - return `${formattedDate} - ${date.toLocaleTimeString('pt-BR')} UTC`; + return `${formattedDate} - ${date.toLocaleTimeString('pt-BR')}`; }; -/** - * Converts altitude from meters to kilometers for display. - * - * @param altitude - Altitude value in meters. - * @returns The altitude formatted in kilometers with two decimal places. - */ -export const convertAltitudeToKm = (altitude: number): string => { - const altitudeInKm = altitude / 1000; - return `${altitudeInKm.toFixed(2)} km`; -}; +export const formatAltitude = (altitude: number) => + new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(altitude) + ' m'; +export const formatAltitudeInKm = (altitude: number) => + `${new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(altitude / 1000)} km`; diff --git a/styles/HomepageHeroSection.module.css b/styles/HomepageHeroSection.module.css index 530d578c..0e1893e5 100644 --- a/styles/HomepageHeroSection.module.css +++ b/styles/HomepageHeroSection.module.css @@ -1,91 +1,87 @@ .heroContainer { - height: 100vh; - width: 100%; - position: relative; - overflow: hidden; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - box-shadow: inset 0 0 0 1000px rgba(0, 0, 0, 0.2); - object-fit: cover; - cursor: default; -} - -.heroContainerStaticImage { - background: url('../public/images/HomePage/static.webp') center center/cover no-repeat; + height: 100vh; + width: 100%; + position: relative; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + box-shadow: inset 0 0 0 1000px; + object-fit: cover; + cursor: default; } .particlesLayer { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - z-index: 0; + position: absolute; + inset: 0; + width: 100%; + height: 100%; + z-index: 0; } .heroContainer > h1 { - color: #fff; - font-size: 4.6vw; - width: 85%; - font-weight: 250; - margin-bottom: 8px; - text-align: center; - position: relative; - z-index: 1; + color: #fff; + font-size: 4.6vw; + width: 85%; + font-weight: 250; + margin-bottom: 8px; + text-align: center; + position: relative; + z-index: 1; } .heroContainer > hr { - background-color: #fff; - width: 90%; - position: relative; - z-index: 1; + background-color: #fff; + width: 90%; + position: relative; + z-index: 1; } .heroContainer > p { - color: #fff; - font-size: 1.8vw; - font-weight: 280; - width: 70%; - text-align: center; - margin-top: 8px; - position: relative; - z-index: 1; + color: #fff; + font-size: 1.8vw; + font-weight: 280; + width: 70%; + text-align: center; + margin-top: 8px; + position: relative; + z-index: 1; } @media screen and (max-width: 1024px) { - .heroContainer > h1 { - font-size: 45px; - } - .heroContainer > p { - font-size: 21px; - } + .heroContainer > h1 { + font-size: 45px; + } + .heroContainer > p { + font-size: 21px; + } } @media screen and (max-width: 768px) { - .heroContainer > h1 { - font-size: 35px; - } - .heroContainer > p { - font-size: 18px; - } + .heroContainer > h1 { + font-size: 35px; + } + .heroContainer > p { + font-size: 18px; + } } .hrAnimation { - width: 100%; - border: 1px solid white; - animation-name: horizontal-line; - animation-duration: 4s; - animation-fill-mode: forwards; + width: 100%; + border: 1px solid white; + animation-name: horizontal-line; + animation-duration: 4s; + animation-fill-mode: forwards; } @keyframes horizontal-line { - 0% { - width: 5%; - opacity: 0%; - } - 100% { - width: 90%; - opacity: 100%; - } + 0% { + width: 5%; + opacity: 0%; + } + 100% { + width: 90%; + opacity: 100%; + } }