diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index 4d2e9038..f2350072 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -16,6 +16,7 @@ import ArrivalPaymentPage from "../../containers/ArrivalPaymentPageContainer";
import AerodromeStatusPage from '../../containers/AerodromeStatusPageContainer';
import ProfilePage from '../../containers/ProfilePageContainer';
+
const UNPROTECTED_ROUTES = [
'/aerodrome-status'
]
diff --git a/src/components/wizards/ArrivalWizard/Finish/FinishActions.tsx b/src/components/wizards/ArrivalWizard/Finish/FinishActions.tsx
index ae9753de..d2910377 100644
--- a/src/components/wizards/ArrivalWizard/Finish/FinishActions.tsx
+++ b/src/components/wizards/ArrivalWizard/Finish/FinishActions.tsx
@@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import ActionButton from './ActionButton'
import ActionsWrapper from './ActionsWrapper'
+import SaveProfilePrompt from '../../SaveProfilePrompt'
import { useTranslation } from 'react-i18next'
const FinishActions = ({itemKey, createMovementFromMovement, finish}) => {
@@ -10,6 +11,8 @@ const FinishActions = ({itemKey, createMovementFromMovement, finish}) => {
const departureImagePath = require('./ic_flight_takeoff_black_48dp_2x.png');
return (
+ <>
+
{
dataCy="finish-button"
/>
+ >
)
}
diff --git a/src/components/wizards/DepartureWizard/Finish/Finish.tsx b/src/components/wizards/DepartureWizard/Finish/Finish.tsx
index 15c9fb6c..dab3f30f 100644
--- a/src/components/wizards/DepartureWizard/Finish/Finish.tsx
+++ b/src/components/wizards/DepartureWizard/Finish/Finish.tsx
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import ImageButton from '../../../ImageButton';
+import SaveProfilePrompt from '../../SaveProfilePrompt';
import Wrapper from '../../ArrivalWizard/Finish/Wrapper';
import Heading from '../../ArrivalWizard/Finish/Heading';
import { useTranslation } from 'react-i18next';
@@ -11,6 +12,7 @@ const Finish = props => {
return (
{props.isUpdate === true ? t('departure.updated') : t('departure.created')}
+ {!props.isUpdate && }
);
diff --git a/src/components/wizards/SaveProfilePrompt.tsx b/src/components/wizards/SaveProfilePrompt.tsx
new file mode 100644
index 00000000..29ec4513
--- /dev/null
+++ b/src/components/wizards/SaveProfilePrompt.tsx
@@ -0,0 +1,201 @@
+import React, { useState } from 'react';
+import { connect } from 'react-redux';
+import { useTranslation } from 'react-i18next';
+import styled from 'styled-components';
+import MaterialIcon from '../MaterialIcon';
+import { addAircraft, updateAircraft, saveProfile } from '../../modules/profile';
+import { toAircraftsArray, Aircraft } from '../../modules/profile/migration';
+
+const Bar = styled.div`
+ display: inline-flex;
+ align-items: center;
+ gap: 0.6em;
+ padding: 0.6em 1em;
+ margin: 0.5em auto;
+ background-color: #fff8e1;
+ border: 1px solid #ffe082;
+ border-radius: 6px;
+ color: #666;
+ font-size: 0.9em;
+`;
+
+const StarIcon = styled.span`
+ color: #e8a735;
+ display: flex;
+ align-items: center;
+`;
+
+const SaveLink = styled.button`
+ background: none;
+ border: none;
+ padding: 0;
+ font-family: inherit;
+ font-size: inherit;
+ font-weight: bold;
+ cursor: pointer;
+ color: ${props => props.theme.colors.main};
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+`;
+
+const DismissButton = styled.button`
+ background: none;
+ border: none;
+ padding: 0.2em;
+ cursor: pointer;
+ color: #bbb;
+ font-size: 1.1em;
+ display: flex;
+ align-items: center;
+
+ &:hover {
+ color: #666;
+ }
+`;
+
+const SuccessMessage = styled.div`
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4em;
+ padding: 0.6em 1em;
+ margin: 0.5em auto;
+ background-color: #e8f5e9;
+ border: 1px solid #a5d6a7;
+ border-radius: 6px;
+ color: #2e7d32;
+ font-size: 0.9em;
+ font-weight: bold;
+`;
+
+interface SaveProfilePromptProps {
+ wizardValues: Record;
+ profileAircrafts: Aircraft[];
+ profileHasPersonalData: boolean;
+ auth: any;
+ addAircraft: (aircraft: Aircraft) => void;
+ updateAircraft: (index: number, aircraft: Aircraft) => void;
+ saveProfile: (values: Record) => void;
+}
+
+const SaveProfilePrompt: React.FC = ({
+ wizardValues,
+ profileAircrafts,
+ profileHasPersonalData,
+ auth,
+ addAircraft,
+ updateAircraft,
+ saveProfile,
+}) => {
+ const { t } = useTranslation();
+ const [state, setState] = useState<'prompt' | 'confirmed' | 'dismissed'>('prompt');
+
+
+ if (typeof __CONF__ !== 'undefined' && __CONF__.profileEnabled === false) {
+ return null;
+ }
+
+ if (!auth || auth.guest || auth.kiosk || auth.uid === 'ipauth') {
+ return null;
+ }
+
+ if (state === 'dismissed' || !wizardValues?.immatriculation) {
+ return null;
+ }
+
+ if (state === 'confirmed') {
+ return (
+
+
+ {t('common.saved')}
+
+ );
+ }
+
+ const movementAircraft: Aircraft = {
+ immatriculation: wizardValues.immatriculation,
+ aircraftType: wizardValues.aircraftType || null,
+ mtow: wizardValues.mtow ?? null,
+ aircraftCategory: wizardValues.aircraftCategory || null,
+ };
+
+ const existingIndex = profileAircrafts.findIndex(
+ a => a.immatriculation === movementAircraft.immatriculation
+ );
+ const existing = existingIndex >= 0 ? profileAircrafts[existingIndex] : null;
+
+ const aircraftChanged = existing && (
+ existing.aircraftType !== movementAircraft.aircraftType ||
+ existing.mtow !== movementAircraft.mtow ||
+ existing.aircraftCategory !== movementAircraft.aircraftCategory
+ );
+
+ // Determine prompt mode
+ let promptKey: string | null = null;
+ let onConfirm: () => void;
+
+ if (!profileHasPersonalData) {
+ promptKey = 'profile.saveDetailsPrompt';
+ onConfirm = () => {
+ saveProfile({
+ firstname: wizardValues.firstname || null,
+ lastname: wizardValues.lastname || null,
+ email: wizardValues.email || null,
+ phone: wizardValues.phone || null,
+ memberNr: wizardValues.memberNr || null,
+ });
+ if (!existing) {
+ addAircraft(movementAircraft);
+ } else if (aircraftChanged) {
+ updateAircraft(existingIndex, movementAircraft);
+ }
+ setState('confirmed');
+ };
+ } else if (!existing) {
+ promptKey = 'profile.saveAircraftPrompt';
+ onConfirm = () => {
+ addAircraft(movementAircraft);
+ setState('confirmed');
+ };
+ } else if (aircraftChanged) {
+ promptKey = 'profile.updateAircraftPrompt';
+ onConfirm = () => {
+ updateAircraft(existingIndex, movementAircraft);
+ setState('confirmed');
+ };
+ } else {
+ return null;
+ }
+
+ return (
+
+
+ {t(promptKey, { immatriculation: movementAircraft.immatriculation })}
+ {t('common.save')}
+ setState('dismissed')} aria-label="dismiss">
+
+
+
+ );
+};
+
+const mapStateToProps = (state: any) => {
+ const profile = state.profile.profile;
+ const aircrafts = toAircraftsArray(profile?.aircrafts) || [];
+ return {
+ wizardValues: state.ui.wizard.values,
+ profileAircrafts: aircrafts,
+ profileHasPersonalData: !!(profile?.firstname && profile?.lastname),
+ auth: state.auth.data,
+ };
+};
+
+const mapDispatchToProps = {
+ addAircraft,
+ updateAircraft,
+ saveProfile,
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(SaveProfilePrompt);
diff --git a/src/locales/de.json b/src/locales/de.json
index 76c59560..e2f6c57c 100644
--- a/src/locales/de.json
+++ b/src/locales/de.json
@@ -12,6 +12,9 @@
"login": "Anmelden",
"confirm": "Bestätigen",
"finish": "Beenden",
+ "yes": "Ja",
+ "no": "Nein",
+ "saved": "Gespeichert!",
"add": "Hinzufügen",
"download": "Herunterladen",
"status": "Status",
@@ -294,7 +297,10 @@
"save": "Speichern",
"addAircraft": "Flugzeug hinzufügen",
"removeAircraft": "Entfernen",
- "noAircraft": "Noch keine Flugzeuge gespeichert."
+ "noAircraft": "Noch keine Flugzeuge gespeichert.",
+ "saveDetailsPrompt": "Pilotendaten und Flugzeug speichern für das nächste Mal?",
+ "saveAircraftPrompt": "{{immatriculation}} im Profil speichern?",
+ "updateAircraftPrompt": "{{immatriculation}} im Profil aktualisieren?"
},
"lockMovements": {
"heading": "Erfasste Bewegungen sperren",
diff --git a/src/locales/en.json b/src/locales/en.json
index bbf8a0b0..1e2318d0 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -12,6 +12,9 @@
"login": "Log in",
"confirm": "Confirm",
"finish": "Finish",
+ "yes": "Yes",
+ "no": "No",
+ "saved": "Saved!",
"add": "Add",
"download": "Download",
"status": "Status",
@@ -294,7 +297,10 @@
"save": "Save",
"addAircraft": "Add aircraft",
"removeAircraft": "Remove",
- "noAircraft": "No aircraft saved."
+ "noAircraft": "No aircraft saved.",
+ "saveDetailsPrompt": "Save pilot info and aircraft so they're prefilled next time?",
+ "saveAircraftPrompt": "Save {{immatriculation}} to your profile?",
+ "updateAircraftPrompt": "Update {{immatriculation}} in your profile?"
},
"lockMovements": {
"heading": "Lock recorded movements",