Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/

# ZED run files
**/*.run
Expand Down
42 changes: 42 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.idea

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
19 changes: 19 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## Getting Started

First, create .env file and specify following:
```
NEXT_PUBLIC_API_BASE_URL=
NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN=
```
Where API_BASE_URL is url to the server and MAPBOX_ACCESS_TOKEN is API token that you can get via creating free account here:
https://www.mapbox.com/

Now, install required dependencies:
```bash
npm install
```

Then, run the development server:
```bash
npm run dev
```
14 changes: 14 additions & 0 deletions frontend/api/inspect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { LngLatLike } from "mapbox-gl";

const inspect = async (boundingBox: LngLatLike[]) => {
await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/inspect`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(boundingBox),
});
};

export { inspect };
36 changes: 36 additions & 0 deletions frontend/api/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export enum PlannerEnum {
RRT_STAR = "RRT_STAR",
HYBRID_A_STAR = "HYBRID_A_STAR",
PARKING = "PARKING",
LEAVE_PARKING = "LEAVE_PARKING",
IDLE = "IDLE",
SUMMON_DRIVING = "SUMMON_DRIVING",
PARALLEL_PARKING = "PARALLEL_PARKING",
}

interface StatusResponse {
status: PlannerEnum;
}

const getCarStatus = async (): Promise<PlannerEnum | null> => {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/status`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
},
);

if (!res.ok) {
console.error("Failed to fetch status:", res.statusText);
return null;
}

const body: StatusResponse = await res.json();
return body.status;
};

export { getCarStatus };
15 changes: 15 additions & 0 deletions frontend/api/summon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const summon = async (lng: number, lat: number) => {
return fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/summon`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
lon: lng,
lat,
}),
});
};

export { summon };
Binary file added frontend/app/favicon.ico
Binary file not shown.
80 changes: 80 additions & 0 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
font-family: Arial, Helvetica, sans-serif;
}

@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

#map-container {
height: 100%;
width: 100%;
background-color: lightgrey;
border-top-right-radius: 20px;
border-top-left-radius: 20px;
}
57 changes: 57 additions & 0 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import './globals.css';
import { ThemeProvider } from '@/components/theme-provider';
import { Toaster } from '@/components/ui/sonner';
import { MobileNav } from '@/components/mobile-nav';

const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
});

const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
});

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};

// main layout
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className="dark" suppressHydrationWarning>
<body
className={`${geistMono.className} ${geistSans.className} bg-neutral-950 text-neutral-100`}
>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem={false}
>
<div className="pb-16">{children}</div>
<MobileNav />
<Toaster
theme="dark"
position="top-right"
toastOptions={{
className: 'bg-neutral-800 text-neutral-100 border-neutral-700',
style: {
background: '#262626',
color: '#f5f5f5',
border: '1px solid #404040',
},
}}
/>
</ThemeProvider>
</body>
</html>
);
}
13 changes: 13 additions & 0 deletions frontend/app/map/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AppHeader } from '@/components/app-header';
import { MapView } from '@/components/map-view';

export default function MapPage() {
return (
<main className="flex min-h-screen flex-col items-center bg-neutral-950 text-neutral-100">
<AppHeader title="Location" />
<div className="w-full h-[calc(100vh-8rem)]">
<MapView />
</div>
</main>
);
}
25 changes: 25 additions & 0 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';

import { CarModel } from '@/components/car-model';
import { CarInfo } from '@/components/car-info';
import { SummonButton } from '@/components/summon-button';
import { AppHeader } from '@/components/app-header';
import { OwnerInfo } from '@/components/owner-info';

export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center bg-neutral-950 text-neutral-100">
<AppHeader title="My Car" />

<div className="w-full h-[40vh] md:h-[50vh] relative">
<CarModel />
</div>

<div className="w-full px-4 pb-6 space-y-4">
<OwnerInfo />
<SummonButton />
<CarInfo />
</div>
</main>
);
}
13 changes: 13 additions & 0 deletions frontend/app/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AppHeader } from '@/components/app-header';
import { SettingsForm } from '@/components/settings-form';

export default function SettingsPage() {
return (
<main className="flex min-h-screen flex-col items-center bg-neutral-950 text-neutral-100">
<AppHeader title="Settings" />
<div className="w-full p-4">
<SettingsForm />
</div>
</main>
);
}
21 changes: 21 additions & 0 deletions frontend/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
27 changes: 27 additions & 0 deletions frontend/components/app-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client';

import { Info } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { motion } from 'framer-motion';

interface AppHeaderProps {
title: string;
}

export function AppHeader({ title }: AppHeaderProps) {
return (
<motion.header
className="w-full px-4 py-4 flex justify-between items-center border-b border-neutral-800"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<h1 className="text-xl font-medium">{title}</h1>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" className="rounded-full">
<Info className="h-5 w-5" />
</Button>
</div>
</motion.header>
);
}
Loading
Loading