diff --git a/packages/web/src/components/InstallationForm/index.jsx b/packages/web/src/components/InstallationForm/index.jsx
new file mode 100644
index 00000000..04e30f3e
--- /dev/null
+++ b/packages/web/src/components/InstallationForm/index.jsx
@@ -0,0 +1,208 @@
+import * as React from 'react';
+import { Link as RouterLink } from 'react-router-dom';
+import Paper from '@mui/material/Paper';
+import Typography from '@mui/material/Typography';
+import { Alert } from '@mui/material';
+import LoadingButton from '@mui/lab/LoadingButton';
+import * as yup from 'yup';
+import { yupResolver } from '@hookform/resolvers/yup';
+import { enqueueSnackbar } from 'notistack';
+import { useQueryClient } from '@tanstack/react-query';
+import Link from '@mui/material/Link';
+
+import useFormatMessage from 'hooks/useFormatMessage';
+import useInstallation from 'hooks/useInstallation';
+import * as URLS from 'config/urls';
+import Form from 'components/Form';
+import TextField from 'components/TextField';
+
+const validationSchema = yup.object().shape({
+ fullName: yup.string().trim().required('installationForm.mandatoryInput'),
+ email: yup
+ .string()
+ .trim()
+ .email('installationForm.validateEmail')
+ .required('installationForm.mandatoryInput'),
+ password: yup.string().required('installationForm.mandatoryInput'),
+ confirmPassword: yup
+ .string()
+ .required('installationForm.mandatoryInput')
+ .oneOf([yup.ref('password')], 'installationForm.passwordsMustMatch'),
+});
+
+const initialValues = {
+ fullName: '',
+ email: '',
+ password: '',
+ confirmPassword: '',
+};
+
+function InstallationForm() {
+ const formatMessage = useFormatMessage();
+ const install = useInstallation();
+ const queryClient = useQueryClient();
+
+ const handleOnRedirect = () => {
+ queryClient.invalidateQueries({
+ queryKey: ['automatisch', 'config'],
+ });
+ };
+
+ const handleSubmit = async (values) => {
+ const { fullName, email, password } = values;
+ try {
+ await install.mutateAsync({
+ fullName,
+ email,
+ password,
+ });
+ } catch (error) {
+ enqueueSnackbar(
+ error?.message || formatMessage('installationForm.error'),
+ {
+ variant: 'error',
+ },
+ );
+ }
+ };
+
+ return (
+
+ theme.palette.text.disabled,
+ pb: 2,
+ mb: 2,
+ }}
+ gutterBottom
+ >
+ {formatMessage('installationForm.title')}
+
+
+ );
+}
+
+export default InstallationForm;
diff --git a/packages/web/src/config/urls.js b/packages/web/src/config/urls.js
index f0180199..865edf82 100644
--- a/packages/web/src/config/urls.js
+++ b/packages/web/src/config/urls.js
@@ -8,6 +8,7 @@ export const SIGNUP = '/sign-up';
export const ACCEPT_INVITATON = '/accept-invitation';
export const FORGOT_PASSWORD = '/forgot-password';
export const RESET_PASSWORD = '/reset-password';
+export const INSTALLATION = '/installation';
export const APPS = '/apps';
export const NEW_APP_CONNECTION = '/apps/new';
export const APP = (appKey) => `/app/${appKey}`;
diff --git a/packages/web/src/helpers/translationValues.jsx b/packages/web/src/helpers/translationValues.jsx
index f5c9855c..1c608acd 100644
--- a/packages/web/src/helpers/translationValues.jsx
+++ b/packages/web/src/helpers/translationValues.jsx
@@ -1,10 +1,12 @@
import { Link as RouterLink } from 'react-router-dom';
import Link from '@mui/material/Link';
+
export const generateInternalLink = (link) => (str) => (
{str}
);
+
export const generateExternalLink = (link) => (str) => (
{str}
diff --git a/packages/web/src/hooks/useInstallation.js b/packages/web/src/hooks/useInstallation.js
new file mode 100644
index 00000000..959f2b69
--- /dev/null
+++ b/packages/web/src/hooks/useInstallation.js
@@ -0,0 +1,15 @@
+import { useMutation } from '@tanstack/react-query';
+
+import api from 'helpers/api';
+
+export default function useInstallation() {
+ const mutation = useMutation({
+ mutationFn: async (payload) => {
+ const { data } = await api.post('/v1/installation/users', payload);
+
+ return data;
+ },
+ });
+
+ return mutation;
+}
diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json
index 26d0c233..c1f45d5b 100644
--- a/packages/web/src/locales/en.json
+++ b/packages/web/src/locales/en.json
@@ -124,6 +124,17 @@
"webhookUrlInfo.description": "You'll need to configure your application with this webhook URL.",
"webhookUrlInfo.helperText": "We've generated a custom webhook URL for you to send requests to. Learn more about webhooks.",
"webhookUrlInfo.copy": "Copy",
+ "installationForm.title": "Installation",
+ "installationForm.fullNameFieldLabel": "Full name",
+ "installationForm.emailFieldLabel": "Email",
+ "installationForm.passwordFieldLabel": "Password",
+ "installationForm.confirmPasswordFieldLabel": "Confirm password",
+ "installationForm.submit": "Create admin",
+ "installationForm.validateEmail": "Email must be valid.",
+ "installationForm.passwordsMustMatch": "Passwords must match.",
+ "installationForm.mandatoryInput": "{inputName} is required.",
+ "installationForm.success": "The admin account has been created, and thus, the installation has been completed. You can now log in here.",
+ "installationForm.error": "Something went wrong. Please try again.",
"signupForm.title": "Sign up",
"signupForm.fullNameFieldLabel": "Full name",
"signupForm.emailFieldLabel": "Email",
diff --git a/packages/web/src/pages/Installation/index.jsx b/packages/web/src/pages/Installation/index.jsx
new file mode 100644
index 00000000..73d68338
--- /dev/null
+++ b/packages/web/src/pages/Installation/index.jsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import Stack from '@mui/material/Stack';
+
+import Container from 'components/Container';
+import InstallationForm from 'components/InstallationForm';
+
+export default function Installation() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/web/src/routes.jsx b/packages/web/src/routes.jsx
index 3ed2dab0..065dfeda 100644
--- a/packages/web/src/routes.jsx
+++ b/packages/web/src/routes.jsx
@@ -1,4 +1,10 @@
-import { Route, Routes as ReactRouterRoutes, Navigate } from 'react-router-dom';
+import { useEffect } from 'react';
+import {
+ Route,
+ Routes as ReactRouterRoutes,
+ Navigate,
+ useNavigate,
+} from 'react-router-dom';
import Layout from 'components/Layout';
import NoResultFound from 'components/NotFound';
@@ -23,12 +29,22 @@ import adminSettingsRoutes from './adminSettingsRoutes';
import Notifications from 'pages/Notifications';
import useAutomatischConfig from 'hooks/useAutomatischConfig';
import useAuthentication from 'hooks/useAuthentication';
+import Installation from 'pages/Installation';
function Routes() {
const { data: configData } = useAutomatischConfig();
const { isAuthenticated } = useAuthentication();
const config = configData?.data;
+ const installed = configData?.data?.['installation.completed'] === true;
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (!installed) {
+ navigate(URLS.INSTALLATION, { replace: true });
+ }
+ }, []);
+
return (
+ {!installed && (
+
+
+
+ }
+ />
+ )}
+
{!config?.disableNotificationsPage && (