Fixed docker deploments, moved files to _folders in dispatch app
This commit is contained in:
@@ -10,3 +10,4 @@ Dockerfile
|
|||||||
.eslint.config.msj
|
.eslint.config.msj
|
||||||
.README.md
|
.README.md
|
||||||
.env.example
|
.env.example
|
||||||
|
.env
|
||||||
96
.env.prod
96
.env.prod
@@ -1,37 +1,73 @@
|
|||||||
# Allgemein / NextAuth
|
# ───────────────────────────────────────────────
|
||||||
NEXTAUTH_URL=http://localhost:3000
|
# 🔐 Authentifizierung & Cookies
|
||||||
NEXTAUTH_COOKIE_PREFIX=HUB
|
# ───────────────────────────────────────────────
|
||||||
NEXTAUTH_SECRET=var
|
AUTH_DISPATCH_SECRET=dispatch
|
||||||
NEXTAUTH_HUB_SECRET=var-hub-secret
|
AUTH_HUB_SECRET=var
|
||||||
|
|
||||||
# Datenbank
|
AUTH_DISPATCH_COOKIE_PREFIX=DISPATCH
|
||||||
|
AUTH_HUB_COOKIE_PREFIX=HUB
|
||||||
|
|
||||||
|
AUTH_DISPATCH_URL=http://localhost:3001
|
||||||
|
AUTH_HUB_URL=http://localhost:3000
|
||||||
|
NEXT_PUBLIC_DISPATCH_SERVICE_ID=1
|
||||||
|
|
||||||
|
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
# 🌐 Öffentliche URLs
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
NEXT_PUBLIC_HUB_URL=http://localhost:3000
|
||||||
|
NEXT_PUBLIC_HUB_SERVER_URL=http://localhost:3003
|
||||||
|
NEXT_PUBLIC_DISPATCH_URL=http://localhost:3001
|
||||||
|
NEXT_PUBLIC_DISPATCH_SERVER_URL=http://localhost:3002
|
||||||
|
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
# 🗄️ Datenbank
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
DATABASE_URL=postgresql://persistant-data:persistant-data-pw@postgres:5432/var
|
DATABASE_URL=postgresql://persistant-data:persistant-data-pw@postgres:5432/var
|
||||||
|
|
||||||
# Discord
|
# ───────────────────────────────────────────────
|
||||||
|
# 📡 LiveKit Konfiguration
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
NEXT_PUBLIC_LIVEKIT_URL=ws://localhost:7880
|
||||||
|
LIVEKIT_API_KEY=APIAnsGdtdYp2Ho
|
||||||
|
LIVEKIT_API_SECRET=tdPjVsYUx8ddC7K9NvdmVAeLRF9GeADD6Fedm1x63fWC
|
||||||
|
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
# 🚦 Dispatch Server (Backend)
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
DISPATCH_SERVER_PORT=3000
|
||||||
|
DISPATCH_APP_TOKEN=dispatch
|
||||||
|
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
# 🧠 HUB Server (Backend)
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
HUB_SERVER_PORT=3000
|
||||||
|
HUB_URL=
|
||||||
|
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
# 📚 Moodle
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
MOODLE_URL=http://localhost:8081
|
||||||
|
MOODLE_API_TOKEN=ac346f0324647b68488d13fd52a9bbe8
|
||||||
|
MOODLE_USER_PASSWORD=var-api-user-P1
|
||||||
|
NEXT_PUBLIC_MOODLE_URL=http://localhost:8081
|
||||||
|
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
# 📧 E-Mail Einstellungen (nur HUB Server)
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
MAIL_SERVER=asmtp.mail.hostpoint.ch
|
||||||
|
MAIL_PORT=465
|
||||||
|
MAIL_USER=noreply@virtualairrescue.com
|
||||||
|
MAIL_PASSWORD=b7316PB8aDPCC%-&
|
||||||
|
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
|
# 🕹️ Discord OAuth (optional)
|
||||||
|
# ───────────────────────────────────────────────
|
||||||
DISCORD_OAUTH_CLIENT_ID=
|
DISCORD_OAUTH_CLIENT_ID=
|
||||||
DISCORD_OAUTH_SECRET=
|
DISCORD_OAUTH_SECRET=
|
||||||
DISCORD_BOT_TOKEN=
|
DISCORD_BOT_TOKEN=
|
||||||
|
DISCORD_REDIRECT_URL=
|
||||||
NEXT_PUBLIC_DISCORD_URL=
|
NEXT_PUBLIC_DISCORD_URL=
|
||||||
DISCORD_REDIRECT=
|
|
||||||
|
|
||||||
# Moodle
|
|
||||||
MOODLE_PW=var-api-user-P1
|
|
||||||
MOODLE_TOKEN=ac346f0324647b68488d13fd52a9bbe8
|
|
||||||
NEXT_PUBLIC_MOODLE_URL=http://localhost:8081
|
|
||||||
|
|
||||||
# Hub Server
|
|
||||||
NEXT_PUBLIC_HUB_SERVER_URL=http://localhost:3003
|
|
||||||
|
|
||||||
# Dispatch Server
|
|
||||||
NEXT_PUBLIC_DISPATCH_SERVER_URL=http://localhost:3001
|
|
||||||
|
|
||||||
# Livekit
|
|
||||||
NEXT_PUBLIC_LIVEKIT_URL=http://localhost:7880
|
|
||||||
LIVEKIT_API_KEY=
|
|
||||||
LIVEKIT_API_SECRET=
|
|
||||||
|
|
||||||
# HUB Server
|
|
||||||
HUB_API_PORT=3000
|
|
||||||
|
|
||||||
# Redis (Beispiel)
|
|
||||||
REDIS_URL=redis://localhost:6379
|
|
||||||
102
README.md
102
README.md
@@ -1,103 +1,9 @@
|
|||||||
# Turborepo starter
|
# Turborepo Monorepo für LST V2
|
||||||
|
|
||||||
This is an official starter Turborepo.
|
## Docker Dev
|
||||||
|
|
||||||
## Using this example
|
Um lokal Docker-Images zu bauen, gib die `.env`-Datei mit folgendem Befehl an `docker compose` weiter:
|
||||||
|
|
||||||
Run the following command:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npx create-turbo@latest
|
docker compose --env-file .env.prod -f 'docker-compose.prod.yml' up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
## What's inside?
|
|
||||||
|
|
||||||
This Turborepo includes the following packages/apps:
|
|
||||||
|
|
||||||
### Apps and Packages
|
|
||||||
|
|
||||||
- `dispatch`: a dispatching platform for web based unit/mission dispatching
|
|
||||||
- `docs`: a [Next.js](https://nextjs.org/) app
|
|
||||||
- `@repo/ui`: a stub React component library shared by both `web` and `docs` applications
|
|
||||||
- `@repo/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
|
|
||||||
- `@repo/typescript-config`: `tsconfig.json`s used throughout the monorepo
|
|
||||||
|
|
||||||
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
|
|
||||||
|
|
||||||
### Utilities
|
|
||||||
|
|
||||||
This Turborepo has some additional tools already setup for you:
|
|
||||||
|
|
||||||
- [TypeScript](https://www.typescriptlang.org/) for static type checking
|
|
||||||
- [ESLint](https://eslint.org/) for code linting
|
|
||||||
- [Prettier](https://prettier.io) for code formatting
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
To build all apps and packages, run the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
cd my-turborepo
|
|
||||||
pnpm build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Develop
|
|
||||||
|
|
||||||
To develop all apps and packages, run the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
cd my-turborepo
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Remote Caching
|
|
||||||
|
|
||||||
> [!TIP]
|
|
||||||
> Vercel Remote Cache is free for all plans. Get started today at [vercel.com](https://vercel.com/signup?/signup?utm_source=remote-cache-sdk&utm_campaign=free_remote_cache).
|
|
||||||
|
|
||||||
Turborepo can use a technique known as [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines.
|
|
||||||
|
|
||||||
By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup?utm_source=turborepo-examples), then enter the following commands:
|
|
||||||
|
|
||||||
```
|
|
||||||
cd my-turborepo
|
|
||||||
npx turbo login
|
|
||||||
```
|
|
||||||
|
|
||||||
This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview).
|
|
||||||
|
|
||||||
Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo:
|
|
||||||
|
|
||||||
```
|
|
||||||
npx turbo link
|
|
||||||
```
|
|
||||||
|
|
||||||
## Useful Links
|
|
||||||
|
|
||||||
Learn more about the power of Turborepo:
|
|
||||||
|
|
||||||
- [Tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks)
|
|
||||||
- [Caching](https://turbo.build/repo/docs/core-concepts/caching)
|
|
||||||
- [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching)
|
|
||||||
- [Filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering)
|
|
||||||
- [Configuration Options](https://turbo.build/repo/docs/reference/configuration)
|
|
||||||
- [CLI Usage](https://turbo.build/repo/docs/reference/command-line-reference)
|
|
||||||
|
|
||||||
## Execution policy
|
|
||||||
|
|
||||||
MachinePolicy Undefined
|
|
||||||
UserPolicy Undefined
|
|
||||||
Process Undefined
|
|
||||||
CurrentUser RemoteSigned
|
|
||||||
LocalMachine RemoteSigned
|
|
||||||
|
|
||||||
## Moodle:
|
|
||||||
|
|
||||||
1. Im docker volume gehe in lib -> Classes -> OAuth2 -> Endpoint.php
|
|
||||||
2. überspringe die https enforcement rule am Ende der Datei (true in if abfrage)
|
|
||||||
3. Moodle Admin -> General -> HTTP Security -> Curl einschränkungen löschen
|
|
||||||
4. http://localhost:8081/admin/category.php?category=authsettings -> Guest login button -> Hide
|
|
||||||
5. http://localhost:8081/admin/settings.php?section=sitepolicies -> emailchangeconfirmation -> False
|
|
||||||
6. Beim anlegen des Auth-Services Require Email verification deaktivieren
|
|
||||||
7. Beim erstellen der Role für API user: permission moodle/site:viewuseridentity geben
|
|
||||||
8. API user zu moodle kursen hinzufügen, um andere Nutzer hinzuzufügen
|
|
||||||
|
|||||||
@@ -47,6 +47,6 @@ app.use(cookieParser());
|
|||||||
app.use(authMiddleware as any);
|
app.use(authMiddleware as any);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
server.listen(process.env.PORT, () => {
|
server.listen(process.env.DISPATCH_SERVER_PORT, () => {
|
||||||
console.log(`Server running on port ${process.env.PORT}`);
|
console.log(`Server running on port ${process.env.DISPATCH_SERVER_PORT}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { createClient, RedisClientType } from "redis";
|
import { createClient, RedisClientType } from "redis";
|
||||||
|
|
||||||
export const pubClient: RedisClientType = createClient();
|
export const pubClient: RedisClientType = createClient({
|
||||||
|
url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
|
||||||
|
});
|
||||||
export const subClient: RedisClientType = pubClient.duplicate();
|
export const subClient: RedisClientType = pubClient.duplicate();
|
||||||
|
|
||||||
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
|
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon --signal SIGINT",
|
"dev": "nodemon --signal SIGINT",
|
||||||
"start": "node index.js",
|
"start": "tsx index.ts --transpile-only",
|
||||||
"build": "tsc"
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"redis": "^4.7.0",
|
"redis": "^4.7.0",
|
||||||
"socket.io": "^4.8.1"
|
"socket.io": "^4.8.1",
|
||||||
|
"tsx": "^4.19.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ Dockerfile
|
|||||||
.eslint.config.msj
|
.eslint.config.msj
|
||||||
.README.md
|
.README.md
|
||||||
.env.example
|
.env.example
|
||||||
|
.env
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
NEXTAUTH_SECRET=dispatch
|
AUTH_DISPATCH_SECRET=dispatch
|
||||||
NEXTAUTH_COOKIE_PREFIX=DISPATCH
|
AUTH_DISPATCH_COOKIE_PREFIX=DISPATCH
|
||||||
NEXT_PUBLIC_DISPATCH_SERVER_URL=http://localhost:3002
|
NEXT_PUBLIC_DISPATCH_SERVER_URL=http://localhost:3002
|
||||||
NEXTAUTH_URL=http://localhost:3001
|
NEXTAUTH_URL=http://localhost:3001
|
||||||
NEXT_PUBLIC_HUB_URL=http://localhost:3000
|
NEXT_PUBLIC_HUB_URL=http://localhost:3000
|
||||||
NEXT_PUBLIC_PUBLIC_URL=http://localhost:3001
|
NEXT_PUBLIC_DISPATCH_URL=http://localhost:3001
|
||||||
NEXT_PUBLIC_SERVICE_ID=1
|
NEXT_PUBLIC_DISPATCH_SERVICE_ID=1
|
||||||
DATABASE_URL=postgresql://persistant-data:persistant-data-pw@localhost:5432/var
|
DATABASE_URL=postgresql://persistant-data:persistant-data-pw@localhost:5432/var
|
||||||
NEXT_PUBLIC_LIVEKIT_URL=ws://localhost:7880
|
NEXT_PUBLIC_LIVEKIT_URL=ws://localhost:7880
|
||||||
LIVEKIT_API_KEY=APIAnsGdtdYp2Ho
|
LIVEKIT_API_KEY=APIAnsGdtdYp2Ho
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
FROM node:22-alpine AS base
|
FROM node:22-alpine AS base
|
||||||
|
|
||||||
|
ARG NEXT_PUBLIC_DISPATCH_URL
|
||||||
|
ARG NEXT_PUBLIC_DISPATCH_SERVER_URL
|
||||||
|
ARG NEXT_PUBLIC_HUB_URL
|
||||||
|
ARG NEXT_PUBLIC_DISPATCH_SERVICE_ID
|
||||||
|
ARG NEXT_PUBLIC_LIVEKIT_URL
|
||||||
|
|
||||||
|
ENV NEXT_PUBLIC_DISPATCH_SERVER_URL=$NEXT_PUBLIC_DISPATCH_SERVER_URL
|
||||||
|
ENV NEXT_PUBLIC_DISPATCH_URL=$NEXT_PUBLIC_DISPATCH_URL
|
||||||
|
ENV NEXT_PUBLIC_HUB_URL=$NEXT_PUBLIC_HUB_URL
|
||||||
|
ENV NEXT_PUBLIC_DISPATCH_SERVICE_ID=$NEXT_PUBLIC_DISPATCH_SERVICE_ID
|
||||||
|
ENV NEXT_PUBLIC_LIVEKIT_URL=$NEXT_PUBLIC_LIVEKIT_URL
|
||||||
|
|
||||||
ENV PNPM_HOME="/usr/local/pnpm"
|
ENV PNPM_HOME="/usr/local/pnpm"
|
||||||
ENV PATH="${PNPM_HOME}:${PATH}"
|
ENV PATH="${PNPM_HOME}:${PATH}"
|
||||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||||
@@ -12,6 +24,10 @@ RUN apk add --no-cache libc6-compat
|
|||||||
|
|
||||||
WORKDIR /usr/app
|
WORKDIR /usr/app
|
||||||
|
|
||||||
|
RUN echo "NEXT_PUBLIC_HUB_URL is: $NEXT_PUBLIC_HUB_URL"
|
||||||
|
RUN echo "NEXT_PUBLIC_DISPATCH_SERVICE_ID is: $NEXT_PUBLIC_DISPATCH_SERVICE_ID"
|
||||||
|
RUN echo "NEXT_PUBLIC_DISPATCH_SERVER_URL is: $NEXT_PUBLIC_DISPATCH_SERVER_URL"
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN turbo prune dispatch --docker
|
RUN turbo prune dispatch --docker
|
||||||
|
|||||||
@@ -49,16 +49,10 @@ export const Login = () => {
|
|||||||
|
|
||||||
<div className="form-control mt-6">
|
<div className="form-control mt-6">
|
||||||
<a
|
<a
|
||||||
href={`${process.env.NEXT_PUBLIC_HUB_URL}/oauth?service=${encodeURIComponent(process.env.NEXT_PUBLIC_SERVICE_ID || "")}&redirect_uri=${encodeURIComponent(`${process.env.NEXT_PUBLIC_PUBLIC_URL}/login`)}`}
|
href={`${process.env.NEXT_PUBLIC_HUB_URL}/oauth?service=${encodeURIComponent(process.env.NEXT_PUBLIC_DISPATCH_SERVICE_ID || "")}&redirect_uri=${encodeURIComponent(`${process.env.NEXT_PUBLIC_DISPATCH_URL}/login`)}`}
|
||||||
>
|
>
|
||||||
<button
|
<button className="btn btn-primary" name="loginBtn" disabled={isLoading}>
|
||||||
className="btn btn-primary"
|
{isLoading && <span className="loading loading-spinner loading-sm"></span>}
|
||||||
name="loginBtn"
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
{isLoading && (
|
|
||||||
<span className="loading loading-spinner loading-sm"></span>
|
|
||||||
)}
|
|
||||||
Login{isLoading && "..."}
|
Login{isLoading && "..."}
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
ZapOff,
|
ZapOff,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAudioStore } from "_store/audioStore";
|
import { useAudioStore } from "_store/audioStore";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import { ConnectionQuality } from "livekit-client";
|
import { ConnectionQuality } from "livekit-client";
|
||||||
import { ROOMS } from "_data/livekitRooms";
|
import { ROOMS } from "_data/livekitRooms";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
type MicrophoneLevelProps = {
|
type MicrophoneLevelProps = {
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import {
|
import { FieldValues, Path, RegisterOptions, UseFormReturn } from "react-hook-form";
|
||||||
FieldValues,
|
import SelectTemplate, { Props as SelectTemplateProps, StylesConfig } from "react-select";
|
||||||
Path,
|
import { cn } from "_helpers/cn";
|
||||||
RegisterOptions,
|
|
||||||
UseFormReturn,
|
|
||||||
} from "react-hook-form";
|
|
||||||
import SelectTemplate, {
|
|
||||||
Props as SelectTemplateProps,
|
|
||||||
StylesConfig,
|
|
||||||
} from "react-select";
|
|
||||||
import { cn } from "helpers/cn";
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { CSSProperties } from "react";
|
import { CSSProperties } from "react";
|
||||||
|
|
||||||
interface SelectProps<T extends FieldValues>
|
interface SelectProps<T extends FieldValues> extends Omit<SelectTemplateProps, "form"> {
|
||||||
extends Omit<SelectTemplateProps, "form"> {
|
|
||||||
label?: any;
|
label?: any;
|
||||||
name: Path<T>;
|
name: Path<T>;
|
||||||
form: UseFormReturn<T> | any;
|
form: UseFormReturn<T> | any;
|
||||||
@@ -69,9 +60,7 @@ const SelectCom = <T extends FieldValues>({
|
|||||||
}: SelectProps<T>) => {
|
}: SelectProps<T>) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<span className="label-text text-lg flex items-center gap-2">
|
<span className="label-text text-lg flex items-center gap-2">{label}</span>
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
<SelectTemplate
|
<SelectTemplate
|
||||||
onChange={(newValue: any) => {
|
onChange={(newValue: any) => {
|
||||||
if (Array.isArray(newValue)) {
|
if (Array.isArray(newValue)) {
|
||||||
@@ -88,12 +77,8 @@ const SelectCom = <T extends FieldValues>({
|
|||||||
}}
|
}}
|
||||||
value={
|
value={
|
||||||
(inputProps as any)?.isMulti
|
(inputProps as any)?.isMulti
|
||||||
? (inputProps as any).options?.filter((o: any) =>
|
? (inputProps as any).options?.filter((o: any) => form.watch(name)?.includes(o.value))
|
||||||
form.watch(name)?.includes(o.value),
|
: (inputProps as any).options?.find((o: any) => o.value === form.watch(name))
|
||||||
)
|
|
||||||
: (inputProps as any).options?.find(
|
|
||||||
(o: any) => o.value === form.watch(name),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
styles={customStyles as any}
|
styles={customStyles as any}
|
||||||
className={cn("w-full placeholder:text-neutral-600", className)}
|
className={cn("w-full placeholder:text-neutral-600", className)}
|
||||||
@@ -101,17 +86,13 @@ const SelectCom = <T extends FieldValues>({
|
|||||||
{...inputProps}
|
{...inputProps}
|
||||||
/>
|
/>
|
||||||
{form.formState.errors[name]?.message && (
|
{form.formState.errors[name]?.message && (
|
||||||
<p className="text-error">
|
<p className="text-error">{form.formState.errors[name].message as string}</p>
|
||||||
{form.formState.errors[name].message as string}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SelectWrapper = <T extends FieldValues>(props: SelectProps<T>) => (
|
const SelectWrapper = <T extends FieldValues>(props: SelectProps<T>) => <SelectCom {...props} />;
|
||||||
<SelectCom {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Select = dynamic(() => Promise.resolve(SelectWrapper), {
|
export const Select = dynamic(() => Promise.resolve(SelectWrapper), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { GearIcon } from "@radix-ui/react-icons";
|
|||||||
import { SettingsIcon, Volume2 } from "lucide-react";
|
import { SettingsIcon, Volume2 } from "lucide-react";
|
||||||
import MicVolumeBar from "_components/MicVolumeIndication";
|
import MicVolumeBar from "_components/MicVolumeIndication";
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { editUserAPI, getUserAPI } from "querys/user";
|
import { editUserAPI, getUserAPI } from "_querys/user";
|
||||||
import { Prisma } from "@repo/db";
|
import { Prisma } from "@repo/db";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useAudioStore } from "_store/audioStore";
|
import { useAudioStore } from "_store/audioStore";
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import {
|
import { RefAttributes, useCallback, useEffect, useImperativeHandle } from "react";
|
||||||
RefAttributes,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useImperativeHandle,
|
|
||||||
} from "react";
|
|
||||||
import { createContext, Ref, useContext, useState } from "react";
|
import { createContext, Ref, useContext, useState } from "react";
|
||||||
import { Popup, PopupProps, useMap } from "react-leaflet";
|
import { Popup, PopupProps, useMap } from "react-leaflet";
|
||||||
import { Popup as LPopup } from "leaflet";
|
import { Popup as LPopup } from "leaflet";
|
||||||
@@ -113,9 +108,9 @@ export const SmartPopup = (
|
|||||||
const [showContent, setShowContent] = useState(false);
|
const [showContent, setShowContent] = useState(false);
|
||||||
const { smartPopupRef, id, className, wrapperClassName, options } = props;
|
const { smartPopupRef, id, className, wrapperClassName, options } = props;
|
||||||
|
|
||||||
const [anchor, setAnchor] = useState<
|
const [anchor, setAnchor] = useState<"topleft" | "topright" | "bottomleft" | "bottomright">(
|
||||||
"topleft" | "topright" | "bottomleft" | "bottomright"
|
"topleft",
|
||||||
>("topleft");
|
);
|
||||||
|
|
||||||
const handleConflict = useCallback(() => {
|
const handleConflict = useCallback(() => {
|
||||||
const newAnchor = calculateAnchor(id, "popup", options);
|
const newAnchor = calculateAnchor(id, "popup", options);
|
||||||
@@ -160,9 +155,7 @@ export const SmartPopup = (
|
|||||||
anchor.includes("top") && "-translate-y-1/2",
|
anchor.includes("top") && "-translate-y-1/2",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<PopupContext.Provider value={{ anchor: anchor }}>
|
<PopupContext.Provider value={{ anchor: anchor }}>{props.children}</PopupContext.Provider>
|
||||||
{props.children}
|
|
||||||
</PopupContext.Provider>
|
|
||||||
</div>
|
</div>
|
||||||
</Popup>
|
</Popup>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
|
|
||||||
export const BaseNotification = ({
|
export const BaseNotification = ({
|
||||||
children,
|
children,
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { ChatBubbleIcon, PaperPlaneIcon } from "@radix-ui/react-icons";
|
|||||||
import { useLeftMenuStore } from "_store/leftMenuStore";
|
import { useLeftMenuStore } from "_store/leftMenuStore";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import { asPublicUser } from "@repo/db";
|
import { asPublicUser } from "@repo/db";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getConnectedUserAPI } from "querys/connected-user";
|
import { getConnectedUserAPI } from "_querys/connected-user";
|
||||||
|
|
||||||
export const Chat = () => {
|
export const Chat = () => {
|
||||||
const {
|
const {
|
||||||
@@ -88,8 +88,7 @@ export const Chat = () => {
|
|||||||
|
|
||||||
{[
|
{[
|
||||||
...(connectedUser?.filter(
|
...(connectedUser?.filter(
|
||||||
(user, idx, arr) =>
|
(user, idx, arr) => arr.findIndex((u) => u.userId === user.userId) === idx,
|
||||||
arr.findIndex((u) => u.userId === user.userId) === idx,
|
|
||||||
) || []),
|
) || []),
|
||||||
].map((user) => (
|
].map((user) => (
|
||||||
<option key={user.userId} value={user.userId}>
|
<option key={user.userId} value={user.userId}>
|
||||||
@@ -100,9 +99,7 @@ export const Chat = () => {
|
|||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-soft btn-primary join-item"
|
className="btn btn-sm btn-soft btn-primary join-item"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const user = connectedUser?.find(
|
const user = connectedUser?.find((user) => user.userId === addTabValue);
|
||||||
(user) => user.userId === addTabValue,
|
|
||||||
);
|
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
addChat(addTabValue, asPublicUser(user.publicUser).fullName);
|
addChat(addTabValue, asPublicUser(user.publicUser).fullName);
|
||||||
setSelectedChat(addTabValue);
|
setSelectedChat(addTabValue);
|
||||||
@@ -135,28 +132,21 @@ export const Chat = () => {
|
|||||||
/>
|
/>
|
||||||
<div className="tab-content bg-base-100 border-base-300 p-6">
|
<div className="tab-content bg-base-100 border-base-300 p-6">
|
||||||
{chat.messages.map((chatMessage) => {
|
{chat.messages.map((chatMessage) => {
|
||||||
const isSender =
|
const isSender = chatMessage.senderId === session.data?.user.id;
|
||||||
chatMessage.senderId === session.data?.user.id;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={chatMessage.id}
|
key={chatMessage.id}
|
||||||
className={`chat ${isSender ? "chat-end" : "chat-start"}`}
|
className={`chat ${isSender ? "chat-end" : "chat-start"}`}
|
||||||
>
|
>
|
||||||
<p className="chat-footer opacity-50">
|
<p className="chat-footer opacity-50">
|
||||||
{new Date(
|
{new Date(chatMessage.timestamp).toLocaleTimeString()}
|
||||||
chatMessage.timestamp,
|
|
||||||
).toLocaleTimeString()}
|
|
||||||
</p>
|
</p>
|
||||||
<div className="chat-bubble">
|
<div className="chat-bubble">{chatMessage.text}</div>
|
||||||
{chatMessage.text}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!chat.messages.length && (
|
{!chat.messages.length && (
|
||||||
<p className="text-xs opacity-50">
|
<p className="text-xs opacity-50">Noch keine Nachrichten</p>
|
||||||
Noch keine Nachrichten
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@@ -2,17 +2,16 @@
|
|||||||
import { ExclamationTriangleIcon, PaperPlaneIcon } from "@radix-ui/react-icons";
|
import { ExclamationTriangleIcon, PaperPlaneIcon } from "@radix-ui/react-icons";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { useLeftMenuStore } from "_store/leftMenuStore";
|
import { useLeftMenuStore } from "_store/leftMenuStore";
|
||||||
import { asPublicUser } from "@repo/db";
|
import { asPublicUser } from "@repo/db";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getConnectedUserAPI } from "querys/connected-user";
|
import { getConnectedUserAPI } from "_querys/connected-user";
|
||||||
import { sendReportAPI } from "querys/report";
|
import { sendReportAPI } from "_querys/report";
|
||||||
|
|
||||||
export const Report = () => {
|
export const Report = () => {
|
||||||
const { setChatOpen, setReportTabOpen, reportTabOpen, setOwnId } =
|
const { setChatOpen, setReportTabOpen, reportTabOpen, setOwnId } = useLeftMenuStore();
|
||||||
useLeftMenuStore();
|
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const [selectedPlayer, setSelectedPlayer] = useState<string>("default");
|
const [selectedPlayer, setSelectedPlayer] = useState<string>("default");
|
||||||
@@ -34,12 +33,7 @@ export const Report = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={cn("dropdown dropdown-right", reportTabOpen && "dropdown-open")}>
|
||||||
className={cn(
|
|
||||||
"dropdown dropdown-right",
|
|
||||||
reportTabOpen && "dropdown-open",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="indicator">
|
<div className="indicator">
|
||||||
<button
|
<button
|
||||||
className="btn btn-soft btn-sm btn-error"
|
className="btn btn-soft btn-sm btn-error"
|
||||||
@@ -78,8 +72,7 @@ export const Report = () => {
|
|||||||
)}
|
)}
|
||||||
{[
|
{[
|
||||||
...(connectedUser?.filter(
|
...(connectedUser?.filter(
|
||||||
(user, idx, arr) =>
|
(user, idx, arr) => arr.findIndex((u) => u.userId === user.userId) === idx,
|
||||||
arr.findIndex((u) => u.userId === user.userId) === idx,
|
|
||||||
) || []),
|
) || []),
|
||||||
].map((user) => (
|
].map((user) => (
|
||||||
<option key={user.userId} value={user.userId}>
|
<option key={user.userId} value={user.userId}>
|
||||||
|
|||||||
@@ -1,26 +1,10 @@
|
|||||||
import { Marker, useMap } from "react-leaflet";
|
import { Marker, useMap } from "react-leaflet";
|
||||||
import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet";
|
import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet";
|
||||||
import { useMapStore } from "_store/mapStore";
|
import { useMapStore } from "_store/mapStore";
|
||||||
import {
|
import { Fragment, useCallback, useEffect, useRef, useState, useMemo } from "react";
|
||||||
Fragment,
|
import { cn } from "_helpers/cn";
|
||||||
useCallback,
|
import { ChevronsRightLeft, House, MessageSquareText, Minimize2 } from "lucide-react";
|
||||||
useEffect,
|
import { SmartPopup, calculateAnchor, useSmartPopup } from "_components/SmartPopup";
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
useMemo,
|
|
||||||
} from "react";
|
|
||||||
import { cn } from "helpers/cn";
|
|
||||||
import {
|
|
||||||
ChevronsRightLeft,
|
|
||||||
House,
|
|
||||||
MessageSquareText,
|
|
||||||
Minimize2,
|
|
||||||
} from "lucide-react";
|
|
||||||
import {
|
|
||||||
SmartPopup,
|
|
||||||
calculateAnchor,
|
|
||||||
useSmartPopup,
|
|
||||||
} from "_components/SmartPopup";
|
|
||||||
import FMSStatusHistory, {
|
import FMSStatusHistory, {
|
||||||
FMSStatusSelector,
|
FMSStatusSelector,
|
||||||
MissionTab,
|
MissionTab,
|
||||||
@@ -29,9 +13,9 @@ import FMSStatusHistory, {
|
|||||||
} from "./_components/AircraftMarkerTabs";
|
} from "./_components/AircraftMarkerTabs";
|
||||||
import { ConnectedAircraft, Station } from "@repo/db";
|
import { ConnectedAircraft, Station } from "@repo/db";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getConnectedAircraftsAPI } from "querys/aircrafts";
|
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||||
import { getMissionsAPI } from "querys/missions";
|
import { getMissionsAPI } from "_querys/missions";
|
||||||
import { checkSimulatorConnected } from "helpers/simulatorConnected";
|
import { checkSimulatorConnected } from "_helpers/simulatorConnected";
|
||||||
|
|
||||||
export const FMS_STATUS_COLORS: { [key: string]: string } = {
|
export const FMS_STATUS_COLORS: { [key: string]: string } = {
|
||||||
"0": "rgb(140,10,10)",
|
"0": "rgb(140,10,10)",
|
||||||
@@ -86,9 +70,7 @@ const AircraftPopupContent = ({
|
|||||||
aircraft: ConnectedAircraft & { Station: Station };
|
aircraft: ConnectedAircraft & { Station: Station };
|
||||||
}) => {
|
}) => {
|
||||||
const setAircraftTab = useMapStore((state) => state.setAircraftTab);
|
const setAircraftTab = useMapStore((state) => state.setAircraftTab);
|
||||||
const currentTab = useMapStore(
|
const currentTab = useMapStore((state) => state.aircraftTabs[aircraft.id] || "home");
|
||||||
(state) => state.aircraftTabs[aircraft.id] || "home",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Memoize the tab change handler to avoid unnecessary re-renders
|
// Memoize the tab change handler to avoid unnecessary re-renders
|
||||||
const handleTabChange = useCallback(
|
const handleTabChange = useCallback(
|
||||||
@@ -126,9 +108,7 @@ const AircraftPopupContent = ({
|
|||||||
<MissionTab mission={mission} />
|
<MissionTab mission={mission} />
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center min-h-full">
|
<div className="flex flex-col items-center justify-center min-h-full">
|
||||||
<span className="text-gray-500 my-10 font-semibold">
|
<span className="text-gray-500 my-10 font-semibold">Kein aktiver Einsatz</span>
|
||||||
Kein aktiver Einsatz
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case "chat":
|
case "chat":
|
||||||
@@ -138,9 +118,7 @@ const AircraftPopupContent = ({
|
|||||||
}
|
}
|
||||||
}, [currentTab, aircraft, mission]);
|
}, [currentTab, aircraft, mission]);
|
||||||
|
|
||||||
const setOpenAircraftMarker = useMapStore(
|
const setOpenAircraftMarker = useMapStore((state) => state.setOpenAircraftMarker);
|
||||||
(state) => state.setOpenAircraftMarker,
|
|
||||||
);
|
|
||||||
const { anchor } = useSmartPopup();
|
const { anchor } = useSmartPopup();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -278,19 +256,13 @@ const AircraftPopupContent = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AircraftMarker = ({
|
const AircraftMarker = ({ aircraft }: { aircraft: ConnectedAircraft & { Station: Station } }) => {
|
||||||
aircraft,
|
|
||||||
}: {
|
|
||||||
aircraft: ConnectedAircraft & { Station: Station };
|
|
||||||
}) => {
|
|
||||||
const [hideMarker, setHideMarker] = useState(false);
|
const [hideMarker, setHideMarker] = useState(false);
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const markerRef = useRef<LMarker>(null);
|
const markerRef = useRef<LMarker>(null);
|
||||||
const popupRef = useRef<LPopup>(null);
|
const popupRef = useRef<LPopup>(null);
|
||||||
|
|
||||||
const { openAircraftMarker, setOpenAircraftMarker } = useMapStore(
|
const { openAircraftMarker, setOpenAircraftMarker } = useMapStore((store) => store);
|
||||||
(store) => store,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
@@ -319,9 +291,9 @@ const AircraftMarker = ({
|
|||||||
};
|
};
|
||||||
}, [aircraft.id, openAircraftMarker, setOpenAircraftMarker]);
|
}, [aircraft.id, openAircraftMarker, setOpenAircraftMarker]);
|
||||||
|
|
||||||
const [anchor, setAnchor] = useState<
|
const [anchor, setAnchor] = useState<"topleft" | "topright" | "bottomleft" | "bottomright">(
|
||||||
"topleft" | "topright" | "bottomleft" | "bottomright"
|
"topleft",
|
||||||
>("topleft");
|
);
|
||||||
|
|
||||||
const handleConflict = useCallback(() => {
|
const handleConflict = useCallback(() => {
|
||||||
const newAnchor = calculateAnchor(`aircraft-${aircraft.id}`, "marker");
|
const newAnchor = calculateAnchor(`aircraft-${aircraft.id}`, "marker");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
|||||||
import { useMapStore } from "_store/mapStore";
|
import { useMapStore } from "_store/mapStore";
|
||||||
import { usePannelStore } from "_store/pannelStore";
|
import { usePannelStore } from "_store/pannelStore";
|
||||||
import { MapPin, MapPinned, Radius, Ruler, Search, RulerDimensionLine, Scan } from "lucide-react";
|
import { MapPin, MapPinned, Radius, Ruler, Search, RulerDimensionLine, Scan } from "lucide-react";
|
||||||
import { getOsmAddress } from "querys/osm";
|
import { getOsmAddress } from "_querys/osm";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { Popup, useMap } from "react-leaflet";
|
import { Popup, useMap } from "react-leaflet";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { DivIcon, Marker as LMarker, Popup as LPopup } from "leaflet";
|
|||||||
import { useMapStore } from "_store/mapStore";
|
import { useMapStore } from "_store/mapStore";
|
||||||
import { usePannelStore } from "_store/pannelStore";
|
import { usePannelStore } from "_store/pannelStore";
|
||||||
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import { ClipboardList, Cross, House, Minimize2, SmartphoneNfc, PencilLine } from "lucide-react";
|
import { ClipboardList, Cross, House, Minimize2, SmartphoneNfc, PencilLine } from "lucide-react";
|
||||||
import { calculateAnchor, SmartPopup, useSmartPopup } from "_components/SmartPopup";
|
import { calculateAnchor, SmartPopup, useSmartPopup } from "_components/SmartPopup";
|
||||||
import { Mission, MissionState } from "@repo/db";
|
import { Mission, MissionState } from "@repo/db";
|
||||||
@@ -13,10 +13,10 @@ import Einsatzdetails, {
|
|||||||
Rettungsmittel,
|
Rettungsmittel,
|
||||||
} from "./_components/MissionMarkerTabs";
|
} from "./_components/MissionMarkerTabs";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getMissionsAPI } from "querys/missions";
|
import { getMissionsAPI } from "_querys/missions";
|
||||||
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
||||||
import { HPGValidationRequired } from "helpers/hpgValidationRequired";
|
import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
|
||||||
import { getConnectedAircraftsAPI } from "querys/aircrafts";
|
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||||
|
|
||||||
export const MISSION_STATUS_COLORS: Record<MissionState | "attention", string> = {
|
export const MISSION_STATUS_COLORS: Record<MissionState | "attention", string> = {
|
||||||
draft: "#0092b8",
|
draft: "#0092b8",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Fragment, useEffect, useRef } from "react";
|
|||||||
import { Marker, Polygon, Popup } from "react-leaflet";
|
import { Marker, Polygon, Popup } from "react-leaflet";
|
||||||
import L from "leaflet";
|
import L from "leaflet";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getMissionsAPI } from "querys/missions";
|
import { getMissionsAPI } from "_querys/missions";
|
||||||
import { OSMWay } from "@repo/db";
|
import { OSMWay } from "@repo/db";
|
||||||
import { usePannelStore } from "_store/pannelStore";
|
import { usePannelStore } from "_store/pannelStore";
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import {
|
|||||||
} from "@repo/db";
|
} from "@repo/db";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { editConnectedAircraftAPI } from "querys/aircrafts";
|
import { editConnectedAircraftAPI } from "_querys/aircrafts";
|
||||||
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import { PersonIcon } from "@radix-ui/react-icons";
|
import { PersonIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import {
|
||||||
Ban,
|
Ban,
|
||||||
@@ -37,7 +37,7 @@ import {
|
|||||||
TextSearch,
|
TextSearch,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { editMissionAPI, sendSdsMessageAPI } from "querys/missions";
|
import { editMissionAPI, sendSdsMessageAPI } from "_querys/missions";
|
||||||
|
|
||||||
const FMSStatusHistory = ({
|
const FMSStatusHistory = ({
|
||||||
aircraft,
|
aircraft,
|
||||||
@@ -48,25 +48,18 @@ const FMSStatusHistory = ({
|
|||||||
}) => {
|
}) => {
|
||||||
console.log("FMSStatusHistory", mission?.missionLog);
|
console.log("FMSStatusHistory", mission?.missionLog);
|
||||||
const log = ((mission?.missionLog as unknown as MissionLog[]) || [])
|
const log = ((mission?.missionLog as unknown as MissionLog[]) || [])
|
||||||
.filter(
|
.filter((entry) => entry.type === "station-log" && entry.data.stationId === aircraft.Station.id)
|
||||||
(entry) =>
|
|
||||||
entry.type === "station-log" &&
|
|
||||||
entry.data.stationId === aircraft.Station.id,
|
|
||||||
)
|
|
||||||
.reverse()
|
.reverse()
|
||||||
.splice(0, 6) as MissionStationLog[];
|
.splice(0, 6) as MissionStationLog[];
|
||||||
|
|
||||||
const aircraftUser =
|
const aircraftUser =
|
||||||
typeof aircraft.publicUser === "string"
|
typeof aircraft.publicUser === "string" ? JSON.parse(aircraft.publicUser) : aircraft.publicUser;
|
||||||
? JSON.parse(aircraft.publicUser)
|
|
||||||
: aircraft.publicUser;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<ul className="text-base-content font-semibold">
|
<ul className="text-base-content font-semibold">
|
||||||
<li className="flex items-center gap-2 mb-1">
|
<li className="flex items-center gap-2 mb-1">
|
||||||
<PersonIcon className="w-5 h-5" /> {aircraftUser.fullName} (
|
<PersonIcon className="w-5 h-5" /> {aircraftUser.fullName} ({aircraftUser.publicId})
|
||||||
{aircraftUser.publicId})
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className="divider mt-0 mb-0" />
|
<div className="divider mt-0 mb-0" />
|
||||||
@@ -99,8 +92,7 @@ const FMSStatusSelector = ({
|
|||||||
}: {
|
}: {
|
||||||
aircraft: ConnectedAircraft & { Station: Station };
|
aircraft: ConnectedAircraft & { Station: Station };
|
||||||
}) => {
|
}) => {
|
||||||
const dispatcherConnected =
|
const dispatcherConnected = useDispatchConnectionStore((s) => s.status) === "connected";
|
||||||
useDispatchConnectionStore((s) => s.status) === "connected";
|
|
||||||
const [hoveredStatus, setHoveredStatus] = useState<string | null>(null);
|
const [hoveredStatus, setHoveredStatus] = useState<string | null>(null);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const changeAircraftMutation = useMutation({
|
const changeAircraftMutation = useMutation({
|
||||||
@@ -299,13 +291,7 @@ const SDSTab = ({
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const sendSdsMutation = useMutation({
|
const sendSdsMutation = useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({ id, message }: { id: number; message: MissionSdsLog }) => {
|
||||||
id,
|
|
||||||
message,
|
|
||||||
}: {
|
|
||||||
id: number;
|
|
||||||
message: MissionSdsLog;
|
|
||||||
}) => {
|
|
||||||
await sendSdsMessageAPI(id, message);
|
await sendSdsMessageAPI(id, message);
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["missions"],
|
queryKey: ["missions"],
|
||||||
@@ -318,9 +304,7 @@ const SDSTab = ({
|
|||||||
?.slice()
|
?.slice()
|
||||||
.reverse()
|
.reverse()
|
||||||
.filter(
|
.filter(
|
||||||
(entry) =>
|
(entry) => entry.type === "sds-log" && entry.data.stationId === aircraft.Station.id,
|
||||||
entry.type === "sds-log" &&
|
|
||||||
entry.data.stationId === aircraft.Station.id,
|
|
||||||
) || [];
|
) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
|||||||
import { useMapStore } from "_store/mapStore";
|
import { useMapStore } from "_store/mapStore";
|
||||||
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_components/map/AircraftMarker";
|
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_components/map/AircraftMarker";
|
||||||
import { MISSION_STATUS_COLORS, MISSION_STATUS_TEXT_COLORS } from "_components/map/MissionMarkers";
|
import { MISSION_STATUS_COLORS, MISSION_STATUS_TEXT_COLORS } from "_components/map/MissionMarkers";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import { checkSimulatorConnected } from "helpers/simulatorConnected";
|
import { checkSimulatorConnected } from "_helpers/simulatorConnected";
|
||||||
import { getConnectedAircraftsAPI } from "querys/aircrafts";
|
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||||
import { getMissionsAPI } from "querys/missions";
|
import { getMissionsAPI } from "_querys/missions";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useMap } from "react-leaflet";
|
import { useMap } from "react-leaflet";
|
||||||
import { HPGValidationRequired } from "helpers/hpgValidationRequired";
|
import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
|
||||||
|
|
||||||
const PopupContent = ({
|
const PopupContent = ({
|
||||||
aircrafts,
|
aircrafts,
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ import {
|
|||||||
import { usePannelStore } from "_store/pannelStore";
|
import { usePannelStore } from "_store/pannelStore";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { deleteMissionAPI, editMissionAPI, sendMissionAPI } from "querys/missions";
|
import { deleteMissionAPI, editMissionAPI, sendMissionAPI } from "_querys/missions";
|
||||||
import { getConnectedAircraftsAPI } from "querys/aircrafts";
|
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||||
import { getStationsAPI } from "querys/stations";
|
import { getStationsAPI } from "_querys/stations";
|
||||||
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
||||||
import { HPGValidationRequired } from "helpers/hpgValidationRequired";
|
import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
|
||||||
import { getOsmAddress } from "querys/osm";
|
import { getOsmAddress } from "_querys/osm";
|
||||||
import { hpgStateToFMSStatus } from "helpers/hpgStateToFmsStatus";
|
import { hpgStateToFMSStatus } from "_helpers/hpgStateToFmsStatus";
|
||||||
|
|
||||||
const Einsatzdetails = ({
|
const Einsatzdetails = ({
|
||||||
mission,
|
mission,
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { ConnectedAircraft, Prisma, Station } from "@repo/db";
|
import { ConnectedAircraft, Prisma, Station } from "@repo/db";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { serverApi } from "helpers/axios";
|
import { serverApi } from "_helpers/axios";
|
||||||
|
|
||||||
export const getConnectedAircraftsAPI = async () => {
|
export const getConnectedAircraftsAPI = async () => {
|
||||||
const res =
|
const res = await axios.get<(ConnectedAircraft & { Station: Station })[]>("/api/aircrafts"); // return only connected aircrafts
|
||||||
await axios.get<(ConnectedAircraft & { Station: Station })[]>(
|
|
||||||
"/api/aircrafts",
|
|
||||||
); // return only connected aircrafts
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error("Failed to fetch stations");
|
throw new Error("Failed to fetch stations");
|
||||||
}
|
}
|
||||||
@@ -17,9 +14,6 @@ export const editConnectedAircraftAPI = async (
|
|||||||
id: number,
|
id: number,
|
||||||
mission: Prisma.ConnectedAircraftUpdateInput,
|
mission: Prisma.ConnectedAircraftUpdateInput,
|
||||||
) => {
|
) => {
|
||||||
const respone = await serverApi.patch<ConnectedAircraft>(
|
const respone = await serverApi.patch<ConnectedAircraft>(`/aircrafts/${id}`, mission);
|
||||||
`/aircrafts/${id}`,
|
|
||||||
mission,
|
|
||||||
);
|
|
||||||
return respone.data;
|
return respone.data;
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Mission, MissionSdsLog, Prisma } from "@repo/db";
|
import { Mission, MissionSdsLog, Prisma } from "@repo/db";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { serverApi } from "helpers/axios";
|
import { serverApi } from "_helpers/axios";
|
||||||
|
|
||||||
export const getMissionsAPI = async (filter?: Prisma.MissionWhereInput) => {
|
export const getMissionsAPI = async (filter?: Prisma.MissionWhereInput) => {
|
||||||
const res = await axios.get<Mission[]>("/api/missions", {
|
const res = await axios.get<Mission[]>("/api/missions", {
|
||||||
@@ -1,17 +1,11 @@
|
|||||||
import { Prisma, Report } from "@repo/db";
|
import { Prisma, Report } from "@repo/db";
|
||||||
import { serverApi } from "helpers/axios";
|
import { serverApi } from "_helpers/axios";
|
||||||
|
|
||||||
export const sendReportAPI = async (
|
export const sendReportAPI = async (
|
||||||
report:
|
report:
|
||||||
| (Prisma.Without<
|
| (Prisma.Without<Prisma.ReportCreateInput, Prisma.ReportUncheckedCreateInput> &
|
||||||
Prisma.ReportCreateInput,
|
|
||||||
Prisma.ReportUncheckedCreateInput
|
|
||||||
> &
|
|
||||||
Prisma.ReportUncheckedCreateInput)
|
Prisma.ReportUncheckedCreateInput)
|
||||||
| (Prisma.Without<
|
| (Prisma.Without<Prisma.ReportUncheckedCreateInput, Prisma.ReportCreateInput> &
|
||||||
Prisma.ReportUncheckedCreateInput,
|
|
||||||
Prisma.ReportCreateInput
|
|
||||||
> &
|
|
||||||
Prisma.ReportCreateInput),
|
Prisma.ReportCreateInput),
|
||||||
) => {
|
) => {
|
||||||
const repsonse = await serverApi.put("/report", report);
|
const repsonse = await serverApi.put("/report", report);
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { PublicUser } from "@repo/db";
|
import { PublicUser } from "@repo/db";
|
||||||
import { dispatchSocket } from "dispatch/socket";
|
import { dispatchSocket } from "dispatch/socket";
|
||||||
import { serverApi } from "helpers/axios";
|
import { serverApi } from "_helpers/axios";
|
||||||
import {
|
import {
|
||||||
handleActiveSpeakerChange,
|
handleActiveSpeakerChange,
|
||||||
handleDisconnect,
|
handleDisconnect,
|
||||||
handleLocalTrackUnpublished,
|
handleLocalTrackUnpublished,
|
||||||
handleTrackSubscribed,
|
handleTrackSubscribed,
|
||||||
handleTrackUnsubscribed,
|
handleTrackUnsubscribed,
|
||||||
} from "helpers/liveKitEventHandler";
|
} from "_helpers/liveKitEventHandler";
|
||||||
import { ConnectionQuality, Room, RoomEvent } from "livekit-client";
|
import { ConnectionQuality, Room, RoomEvent } from "livekit-client";
|
||||||
import { pilotSocket } from "pilot/socket";
|
import { pilotSocket } from "pilot/socket";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import {
|
import { AuthOptions, getServerSession as getNextAuthServerSession } from "next-auth";
|
||||||
AuthOptions,
|
|
||||||
getServerSession as getNextAuthServerSession,
|
|
||||||
} from "next-auth";
|
|
||||||
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
||||||
import Credentials from "next-auth/providers/credentials";
|
import Credentials from "next-auth/providers/credentials";
|
||||||
import { prisma, PrismaClient } from "@repo/db";
|
import { prisma, PrismaClient } from "@repo/db";
|
||||||
@@ -36,10 +33,10 @@ export const options: AuthOptions = {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
secret: process.env.AUTH_DISPATCH_SECRET,
|
||||||
cookies: {
|
cookies: {
|
||||||
sessionToken: {
|
sessionToken: {
|
||||||
name: `${process.env.NEXTAUTH_COOKIE_PREFIX}-next-auth.session-token`, // Ändere den Namen für App 1
|
name: `${process.env.AUTH_DISPATCH_COOKIE_PREFIX}-next-auth.session-token`, // Ändere den Namen für App 1
|
||||||
options: {
|
options: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: process.env.NODE_ENV === "production",
|
secure: process.env.NODE_ENV === "production",
|
||||||
@@ -47,7 +44,7 @@ export const options: AuthOptions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
csrfToken: {
|
csrfToken: {
|
||||||
name: `${process.env.NEXTAUTH_COOKIE_PREFIX}-next-auth.csrf-token`,
|
name: `${process.env.AUTH_DISPATCH_COOKIE_PREFIX}-next-auth.csrf-token`,
|
||||||
options: {
|
options: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: process.env.NODE_ENV === "production",
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ import {
|
|||||||
editMissionAPI,
|
editMissionAPI,
|
||||||
sendMissionAPI,
|
sendMissionAPI,
|
||||||
startHpgValidation,
|
startHpgValidation,
|
||||||
} from "querys/missions";
|
} from "_querys/missions";
|
||||||
import { getKeywordsAPI } from "querys/keywords";
|
import { getKeywordsAPI } from "_querys/keywords";
|
||||||
import { getStationsAPI } from "querys/stations";
|
import { getStationsAPI } from "_querys/stations";
|
||||||
import { useMapStore } from "_store/mapStore";
|
import { useMapStore } from "_store/mapStore";
|
||||||
import { getConnectedAircraftsAPI } from "querys/aircrafts";
|
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||||
import { HPGValidationRequired } from "helpers/hpgValidationRequired";
|
import { HPGValidationRequired } from "_helpers/hpgValidationRequired";
|
||||||
import { selectRandomHPGMissionSzenery } from "helpers/selectRandomHPGMission";
|
import { selectRandomHPGMissionSzenery } from "_helpers/selectRandomHPGMission";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
export const MissionForm = () => {
|
export const MissionForm = () => {
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { usePannelStore } from "_store/pannelStore";
|
import { usePannelStore } from "_store/pannelStore";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import { MissionForm } from "./MissionForm";
|
import { MissionForm } from "./MissionForm";
|
||||||
import { Rss, Trash2Icon } from "lucide-react";
|
import { Rss, Trash2Icon } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getMissionsAPI } from "querys/missions";
|
import { getMissionsAPI } from "_querys/missions";
|
||||||
|
|
||||||
export const Pannel = () => {
|
export const Pannel = () => {
|
||||||
const { setOpen, setMissionFormValues } = usePannelStore();
|
const { setOpen, setMissionFormValues } = usePannelStore();
|
||||||
const { isEditingMission, setEditingMission, missionFormValues } =
|
const { isEditingMission, setEditingMission, missionFormValues } = usePannelStore();
|
||||||
usePannelStore();
|
|
||||||
const missions = useQuery({
|
const missions = useQuery({
|
||||||
queryKey: ["missions"],
|
queryKey: ["missions"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
@@ -20,9 +19,7 @@ export const Pannel = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEditingMission && missionFormValues) {
|
if (isEditingMission && missionFormValues) {
|
||||||
const mission = missions.data?.find(
|
const mission = missions.data?.find((mission) => mission.id === missionFormValues.id);
|
||||||
(mission) => mission.id === missionFormValues.id,
|
|
||||||
);
|
|
||||||
if (!mission) {
|
if (!mission) {
|
||||||
setEditingMission(false, null);
|
setEditingMission(false, null);
|
||||||
setMissionFormValues({});
|
setMissionFormValues({});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Pannel } from "dispatch/_components/pannel/Pannel";
|
import { Pannel } from "dispatch/_components/pannel/Pannel";
|
||||||
import { usePannelStore } from "_store/pannelStore";
|
import { usePannelStore } from "_store/pannelStore";
|
||||||
import { cn } from "helpers/cn";
|
import { cn } from "_helpers/cn";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { Chat } from "../_components/left/Chat";
|
import { Chat } from "../_components/left/Chat";
|
||||||
import { Report } from "../_components/left/Report";
|
import { Report } from "../_components/left/Report";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { io } from "socket.io-client";
|
import { io, Socket } from "socket.io-client";
|
||||||
|
|
||||||
import type { Socket } from "socket.io-client";
|
console.log("ENV:", process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL);
|
||||||
|
|
||||||
export const dispatchSocket: Socket = io(process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, {
|
export const dispatchSocket: Socket = io(process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, {
|
||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
|
|||||||
@@ -2,21 +2,18 @@ import { ConnectedAircraft } from "@repo/db";
|
|||||||
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||||
import { useMrtStore } from "_store/pilot/MrtStore";
|
import { useMrtStore } from "_store/pilot/MrtStore";
|
||||||
import { pilotSocket } from "pilot/socket";
|
import { pilotSocket } from "pilot/socket";
|
||||||
import { editConnectedAircraftAPI } from "querys/aircrafts";
|
import { editConnectedAircraftAPI } from "_querys/aircrafts";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export const useButtons = () => {
|
export const useButtons = () => {
|
||||||
const station = usePilotConnectionStore((state) => state.selectedStation);
|
const station = usePilotConnectionStore((state) => state.selectedStation);
|
||||||
const connectedAircraft = usePilotConnectionStore(
|
const connectedAircraft = usePilotConnectionStore((state) => state.connectedAircraft);
|
||||||
(state) => state.connectedAircraft,
|
|
||||||
);
|
|
||||||
const connectionStatus = usePilotConnectionStore((state) => state.status);
|
const connectionStatus = usePilotConnectionStore((state) => state.status);
|
||||||
|
|
||||||
const { page, setPage } = useMrtStore((state) => state);
|
const { page, setPage } = useMrtStore((state) => state);
|
||||||
|
|
||||||
const handleButton =
|
const handleButton =
|
||||||
(button: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0") =>
|
(button: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0") => () => {
|
||||||
() => {
|
|
||||||
if (connectionStatus !== "connected") return;
|
if (connectionStatus !== "connected") return;
|
||||||
if (!station) return;
|
if (!station) return;
|
||||||
if (!connectedAircraft?.id) return;
|
if (!connectedAircraft?.id) return;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useSession } from "next-auth/react";
|
|||||||
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getStationsAPI } from "querys/stations";
|
import { getStationsAPI } from "_querys/stations";
|
||||||
|
|
||||||
export const ConnectionBtn = () => {
|
export const ConnectionBtn = () => {
|
||||||
const modalRef = useRef<HTMLDialogElement>(null);
|
const modalRef = useRef<HTMLDialogElement>(null);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { io, Socket } from "socket.io-client";
|
import { io, Socket } from "socket.io-client";
|
||||||
|
|
||||||
export const pilotSocket: Socket = io(process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, {
|
export const pilotSocket: Socket = io(process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
NEXTAUTH_URL=http://localhost:3000
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
NEXTAUTH_COOKIE_PREFIX=HUB
|
AUTH_HUB_COOKIE_PREFIX=HUB
|
||||||
NEXTAUTH_SECRET=var
|
AUTH_HUB_SECRET=var
|
||||||
DATABASE_URL=postgresql://persistant-data:persistant-data-pw@localhost:5432/var
|
DATABASE_URL=postgresql://persistant-data:persistant-data-pw@localhost:5432/var
|
||||||
DISCORD_OAUTH_CLIENT_ID=
|
DISCORD_OAUTH_CLIENT_ID=
|
||||||
DISCORD_OAUTH_SECRET=
|
DISCORD_OAUTH_SECRET=
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
FROM node:22-alpine AS base
|
FROM node:22-alpine AS base
|
||||||
|
|
||||||
ARG DATABASE_URL
|
|
||||||
ENV DATABASE_URL=${DATABASE_URL}
|
|
||||||
|
|
||||||
ENV PNPM_HOME="/usr/local/pnpm"
|
ENV PNPM_HOME="/usr/local/pnpm"
|
||||||
ENV PATH="${PNPM_HOME}:${PATH}"
|
ENV PATH="${PNPM_HOME}:${PATH}"
|
||||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo "NEXT_PUBLIC_DISPATCH_SERVER_URL=${NEXT_PUBLIC_DISPATCH_SERVER_URL}"
|
||||||
RUN pnpm add -g turbo@^2.5
|
RUN pnpm add -g turbo@^2.5
|
||||||
|
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { signIn, useSession } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect, useSearchParams } from "next/navigation";
|
import { redirect, useSearchParams } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -72,12 +72,7 @@ export const Login = () => {
|
|||||||
<path d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z" />
|
<path d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z" />
|
||||||
<path d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z" />
|
<path d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<input
|
<input type="text" className="grow" {...form.register("email")} placeholder="Email" />
|
||||||
type="text"
|
|
||||||
className="grow"
|
|
||||||
{...form.register("email")}
|
|
||||||
placeholder="Email"
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
<p className="text-error">
|
<p className="text-error">
|
||||||
{typeof form.formState.errors.email?.message === "string"
|
{typeof form.formState.errors.email?.message === "string"
|
||||||
@@ -111,11 +106,7 @@ export const Login = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
<div className="card-actions mt-6">
|
<div className="card-actions mt-6">
|
||||||
<Button
|
<Button disabled={isLoading} isLoading={isLoading} className="btn btn-primary btn-block">
|
||||||
disabled={isLoading}
|
|
||||||
isLoading={isLoading}
|
|
||||||
className="btn btn-primary btn-block"
|
|
||||||
>
|
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -70,21 +70,14 @@ export const VerticalNav = () => {
|
|||||||
export const HorizontalNav = () => (
|
export const HorizontalNav = () => (
|
||||||
<div className="navbar bg-base-200 shadow-md rounded-lg mb-4">
|
<div className="navbar bg-base-200 shadow-md rounded-lg mb-4">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<a className="btn btn-ghost normal-case text-xl">
|
<a className="btn btn-ghost normal-case text-xl">Virtual Air Rescue - HUB</a>
|
||||||
Virtual Air Rescue - HUB
|
|
||||||
</a>
|
|
||||||
<WarningAlert />
|
<WarningAlert />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center ml-auto">
|
<div className="flex items-center ml-auto">
|
||||||
<ul className="flex space-x-2 px-1">
|
<ul className="flex space-x-2 px-1">
|
||||||
<li>
|
<li>
|
||||||
<Link
|
<Link href={process.env.NEXT_PUBLIC_DISPATCH_URL || "#!"} rel="noopener noreferrer">
|
||||||
href={process.env.NEXT_PUBLIC_DISPATCH_URL || "#!"}
|
<button className="btn btn-sm btn-outline btn-primary">Zur Leitstelle</button>
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<button className="btn btn-sm btn-outline btn-primary">
|
|
||||||
Zur Leitstelle
|
|
||||||
</button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ export const options: AuthOptions = {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
secret: process.env.AUTH_HUB_SECRET,
|
||||||
session: {
|
session: {
|
||||||
strategy: "jwt",
|
strategy: "jwt",
|
||||||
maxAge: 30 * 24 * 60 * 60,
|
maxAge: 30 * 24 * 60 * 60,
|
||||||
},
|
},
|
||||||
cookies: {
|
cookies: {
|
||||||
sessionToken: {
|
sessionToken: {
|
||||||
name: `${process.env.NEXTAUTH_COOKIE_PREFIX}-next-auth.session-token`, // Ändere den Namen für App 1
|
name: `${process.env.AUTH_HUB_COOKIE_PREFIX}-next-auth.session-token`, // Ändere den Namen für App 1
|
||||||
options: {
|
options: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: process.env.NODE_ENV === "production",
|
secure: process.env.NODE_ENV === "production",
|
||||||
@@ -42,7 +42,7 @@ export const options: AuthOptions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
csrfToken: {
|
csrfToken: {
|
||||||
name: `${process.env.NEXTAUTH_COOKIE_PREFIX}-next-auth.csrf-token`,
|
name: `${process.env.AUTH_HUB_COOKIE_PREFIX}-next-auth.csrf-token`,
|
||||||
options: {
|
options: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: process.env.NODE_ENV === "production",
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const POST = async (req: NextRequest) => {
|
|||||||
{
|
{
|
||||||
...accessRequest.user,
|
...accessRequest.user,
|
||||||
},
|
},
|
||||||
process.env.NEXTAUTH_SECRET as string,
|
process.env.AUTH_HUB_SECRET as string,
|
||||||
{
|
{
|
||||||
expiresIn: "30d",
|
expiresIn: "30d",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const GET = async (req: NextRequest) => {
|
|||||||
if (!authHeader || !token) {
|
if (!authHeader || !token) {
|
||||||
return NextResponse.json({ error: "Not logged in" }, { status: 401 });
|
return NextResponse.json({ error: "Not logged in" }, { status: 401 });
|
||||||
}
|
}
|
||||||
const decoded = await verify(token, process.env.NEXTAUTH_SECRET as string);
|
const decoded = await verify(token, process.env.AUTH_HUB_SECRET as string);
|
||||||
|
|
||||||
if (typeof decoded === "string")
|
if (typeof decoded === "string")
|
||||||
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
|
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
|
||||||
@@ -22,8 +22,7 @@ export const GET = async (req: NextRequest) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user)
|
if (!user) return NextResponse.json({ error: "User not found" }, { status: 404 });
|
||||||
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const moodleUser = await getMoodleUserById(user.id);
|
const moodleUser = await getMoodleUserById(user.id);
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
@@ -49,10 +48,7 @@ export const GET = async (req: NextRequest) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
participatingEvents.forEach(async (p) => {
|
participatingEvents.forEach(async (p) => {
|
||||||
await inscribeToMoodleCourse(
|
await inscribeToMoodleCourse(p.Event.finisherMoodleCourseId!, moodleUser?.id);
|
||||||
p.Event.finisherMoodleCourseId!,
|
|
||||||
moodleUser?.id,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const geistMono = Geist_Mono({
|
|||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export default async ({
|
const RootLayout = async ({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -23,12 +23,14 @@ export default async ({
|
|||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<NextAuthSessionProvider session={session}>
|
<NextAuthSessionProvider session={session}>
|
||||||
<body
|
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</body>
|
</body>
|
||||||
</NextAuthSessionProvider>
|
</NextAuthSessionProvider>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RootLayout.displayName = "RootLayout";
|
||||||
|
|
||||||
|
export default RootLayout;
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./apps/dispatch/Dockerfile
|
dockerfile: ./apps/dispatch/Dockerfile
|
||||||
|
args:
|
||||||
|
- NEXT_PUBLIC_DISPATCH_URL=$NEXT_PUBLIC_DISPATCH_URL
|
||||||
|
- NEXT_PUBLIC_HUB_URL=$NEXT_PUBLIC_HUB_URL
|
||||||
|
- NEXT_PUBLIC_DISPATCH_SERVICE_ID=1
|
||||||
|
- NEXT_PUBLIC_LIVEKIT_URL=$NEXT_PUBLIC_LIVEKIT_URL
|
||||||
|
- NEXT_PUBLIC_DISPATCH_SERVER_URL=$NEXT_PUBLIC_DISPATCH_SERVER_URL
|
||||||
container_name: dispatch
|
container_name: dispatch
|
||||||
ports:
|
ports:
|
||||||
- "3001:3000"
|
- "3001:3000"
|
||||||
@@ -41,14 +47,17 @@ services:
|
|||||||
dockerfile: ./apps/dispatch-server/Dockerfile
|
dockerfile: ./apps/dispatch-server/Dockerfile
|
||||||
container_name: dispatch-server
|
container_name: dispatch-server
|
||||||
ports:
|
ports:
|
||||||
- "3003:3000"
|
- "3002:3000"
|
||||||
env_file:
|
env_file:
|
||||||
- .env.prod
|
- .env.prod
|
||||||
networks:
|
networks:
|
||||||
- postgres_network
|
- postgres_network
|
||||||
|
- redis_network
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:13
|
image: postgres:13
|
||||||
container_name: postgres
|
container_name: postgres
|
||||||
@@ -84,6 +93,10 @@ services:
|
|||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
volumes:
|
volumes:
|
||||||
- "redis_data:/data"
|
- "redis_data:/data"
|
||||||
|
networks:
|
||||||
|
- redis_network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
||||||
|
|
||||||
moodle_database:
|
moodle_database:
|
||||||
container_name: moodle_database
|
container_name: moodle_database
|
||||||
@@ -141,6 +154,8 @@ networks:
|
|||||||
|
|
||||||
postgres_network:
|
postgres_network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
redis_network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
"EMAIL_FROM",
|
"EMAIL_FROM",
|
||||||
"SECRET",
|
"SECRET",
|
||||||
"DATABASE_URL",
|
"DATABASE_URL",
|
||||||
"NEXTAUTH_SECRET",
|
"AUTH_DISPATCH_SECRET",
|
||||||
"LIVEKIT_API_KEY",
|
"LIVEKIT_API_KEY",
|
||||||
"LIVEKIT_API_SECRET",
|
"LIVEKIT_API_SECRET",
|
||||||
"NEXTAUTH_HUB_SECRET",
|
"NEXTAUTH_HUB_SECRET",
|
||||||
"NEXTAUTH_COOKIE_PREFIX"
|
"AUTH_DISPATCH_COOKIE_PREFIX"
|
||||||
],
|
],
|
||||||
"ui": "tui",
|
"ui": "tui",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
|
|||||||
Reference in New Issue
Block a user