Working on airport drawer
This commit is contained in:
@@ -45,12 +45,14 @@ async fn register(user: web::Json<RegisterRequest>, req: HttpRequest) -> HttpRes
|
||||
|
||||
// Send confirmation email
|
||||
if let Some(email) = email {
|
||||
if !email.is_empty() {
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = send_confirm_email(&email, &ip_address).await {
|
||||
log::error!("Failed to send confirmation email: {}", err);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Created().json(user_response)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
force=0
|
||||
push=0
|
||||
push_all=0
|
||||
|
||||
API_VERSION=$(sed -n 's/^version *= *"\([^"]*\)".*/\1/p' "$(pwd)"/api/Cargo.toml)
|
||||
UI_VERSION=$(sed -n 's/.*"version": *"\([^"]*\)".*/\1/p' "$(pwd)"/ui/package.json)
|
||||
@@ -17,6 +18,13 @@ for arg in "$@"; do
|
||||
push=1
|
||||
shift
|
||||
;;
|
||||
-a|--push-all)
|
||||
push_all=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -69,3 +77,14 @@ if echo "$changed_files" | grep -q "^api/"; then
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Push all tags
|
||||
if [ $push_all -eq 1 ]; then
|
||||
if [ $force -eq 1 ]; then
|
||||
echo "Force-pushing ALL tags to remote"
|
||||
git push -f origin --tags
|
||||
else
|
||||
echo "Pushing ALL tags to remote"
|
||||
git push origin --tags
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
Accordion,
|
||||
Badge,
|
||||
Box,
|
||||
Box, Button,
|
||||
Divider,
|
||||
Drawer,
|
||||
Group,
|
||||
Group, Stack,
|
||||
Tabs,
|
||||
TabsList,
|
||||
Text,
|
||||
@@ -15,12 +15,13 @@ import { Airport, AirportCategory } from '@lib/airport.types.ts';
|
||||
import { getMarkerColor, Metar } from '@lib/metar.types.ts';
|
||||
import { CSSProperties, forwardRef, ReactNode, useEffect, useState } from 'react';
|
||||
import { useMediaQuery } from '@mantine/hooks';
|
||||
import { IconViewfinder } from '@tabler/icons-react';
|
||||
import { IconStar, IconStarFilled, IconViewfinder } from '@tabler/icons-react';
|
||||
import { RunwayTable } from '@components/AirportDrawer/RunwayTable.tsx';
|
||||
import { CommunicationTable } from '@components/AirportDrawer/CommunicationTable.tsx';
|
||||
import { useMap } from 'react-leaflet';
|
||||
import type { Map as LeafletMap } from 'leaflet';
|
||||
import { getMetars } from '@lib/metar.ts';
|
||||
import { useUserContext } from '@components/context/UserContext.tsx';
|
||||
|
||||
export function AirportDrawer({
|
||||
airport,
|
||||
@@ -29,6 +30,9 @@ export function AirportDrawer({
|
||||
airport: Airport | null;
|
||||
setAirport: (airport: Airport | null) => void;
|
||||
}) {
|
||||
const { user, favorites, toggleFavorite } = useUserContext();
|
||||
const isAdmin = user?.role === 'ADMIN';
|
||||
const isFavorite = airport ? favorites.includes(airport.icao) : false;
|
||||
const [metar, setMetar] = useState<Metar | undefined>(undefined);
|
||||
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||
const map = useMap();
|
||||
@@ -73,6 +77,15 @@ export function AirportDrawer({
|
||||
>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<UnstyledButton
|
||||
onClick={() => toggleFavorite(airport.icao)}
|
||||
aria-label={isFavorite ? 'Unfavorite airport' : 'Favorite airport'}
|
||||
style={{ padding: 4 }}
|
||||
>
|
||||
{isFavorite
|
||||
? <IconStarFilled size={24} color="#faca15" />
|
||||
: <IconStar size={24} />}
|
||||
</UnstyledButton>
|
||||
<Drawer.Title>
|
||||
<Text size={'xl'}>{airport.name}</Text>
|
||||
</Drawer.Title>
|
||||
@@ -104,6 +117,7 @@ export function AirportDrawer({
|
||||
<TabsList grow>
|
||||
<Tabs.Tab value={'info'}>Info</Tabs.Tab>
|
||||
<Tabs.Tab value={'weather'}>Weather</Tabs.Tab>
|
||||
{ user && <Tabs.Tab value={'manage'}>Manage</Tabs.Tab> }
|
||||
</TabsList>
|
||||
<Tabs.Panel value={'info'}>
|
||||
<AirportInfo map={map} airport={airport} />
|
||||
@@ -111,6 +125,23 @@ export function AirportDrawer({
|
||||
<Tabs.Panel value={'weather'}>
|
||||
<WeatherInfo metar={airport.latest_metar} />
|
||||
</Tabs.Panel>
|
||||
{user && (
|
||||
<Tabs.Panel value={'manage'}>
|
||||
{isAdmin ? (
|
||||
<Stack mt="md">
|
||||
<Button onClick={() => {}}>Update METAR</Button>
|
||||
<Button onClick={() => {}}>Edit Airport</Button>
|
||||
<Button color="red" onClick={() => {}}>
|
||||
Delete Airport
|
||||
</Button>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack mt="md">
|
||||
<Button onClick={() => {}}>Request Edit</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</Tabs.Panel>
|
||||
)}
|
||||
</Tabs>
|
||||
</Box>
|
||||
</Drawer.Body>
|
||||
|
||||
@@ -5,12 +5,16 @@ interface UserContextType {
|
||||
user?: User;
|
||||
setUser: (user: User | undefined) => void;
|
||||
loading: boolean;
|
||||
favorites: string[];
|
||||
toggleFavorite: (icao: string) => void;
|
||||
}
|
||||
|
||||
export const UserContext = createContext<UserContextType>({
|
||||
user: undefined,
|
||||
setUser: () => {},
|
||||
loading: true
|
||||
loading: true,
|
||||
favorites: [],
|
||||
toggleFavorite: () => {},
|
||||
});
|
||||
|
||||
export function useUserContext(): UserContextType {
|
||||
|
||||
@@ -9,8 +9,21 @@ const sessionExpirationName = 'session_expiration';
|
||||
|
||||
export function UserProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | undefined>(undefined);
|
||||
const [favorites, setFavorites] = useState<string[]>(() => {
|
||||
return JSON.parse(localStorage.getItem('favorites') || '[]')
|
||||
})
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const toggleFavorite = (icao: string) => {
|
||||
setFavorites((prev) => {
|
||||
const next = prev.includes(icao)
|
||||
? prev.filter((i) => i !== icao)
|
||||
: [...prev, icao]
|
||||
localStorage.setItem('favorites', JSON.stringify(next))
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const sessionExpiration = Cookies.get(sessionExpirationName);
|
||||
|
||||
@@ -36,7 +49,7 @@ export function UserProvider({ children }: { children: ReactNode }) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={{ user, setUser, loading }}>
|
||||
<UserContext.Provider value={{ user, setUser, loading, favorites, toggleFavorite }}>
|
||||
{loading ? (
|
||||
<Center style={{ height: '100vh' }}>
|
||||
<Loader size='xl' />
|
||||
|
||||
Reference in New Issue
Block a user