diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json
index 833e4b472217f31f0ea703edfce427de539e978a..fc1eda768f0f416362340d3e7127db622eca2bed 100644
--- a/frontend/public/locales/en/common.json
+++ b/frontend/public/locales/en/common.json
@@ -16,7 +16,6 @@
     "roleStartDate": "From",
     "roleEndDate": "To",
     "comment": "Comment",
-    "contact": "Contact person",
     "searchable": "Available in search?",
     "email": "E-mail",
     "fullName": "Full name",
@@ -44,6 +43,7 @@
     "bodyText": "Here you can add a new role to the same guest"
   },
   "register": {
+    "noResults": "No guest matching your search found.",
     "registerHeading": "Register new guest",
     "registerText": "Please search for e-mail or phone number before registering a new guest to prevent duplicates.",
     "registerButtonText": "Register new guest"
@@ -62,20 +62,25 @@
   "role": "Guest role",
   "period": "Period",
   "ou": "Organisation",
+  "department": "Department",
   "choice": "Choices",
   "registerText": "Register new guest",
-  "waitingGuests": "Guests waiting for your confirmation",
-  "waitingGuestsDescription": "See the table below for guests ",
+  "waitingGuests": "Guests waiting for confirmation",
+  "waitingGuestsDescription": "Confirm your guests that <1>registered manually</1> here. Guests with FEIDE-user do not need confirmation.",
   "noWaitingGuests": "No waiting guests",
-  "activeGuests": "Your active guests",
-  "activeGuestsDescription": "Make changes to your existing guests",
+  "activeGuests": "Confirmed guests",
+  "activeGuestsDescription": "Make changes to guest roles.",
   "noActiveGuests": "No active guests",
   "sentInvitations": "Sent invitations",
   "sentInvitationsDescription": "Invitations awaiting response from guest.",
   "noInvitations": "No invitations",
   "status": "Status",
-  "active": "Active",
-  "expired": "Expired",
+  "statusText": {
+    "active": "Active",
+    "expired": "Expired",
+    "waitingForGuest": "Waiting for guest",
+    "waitingForSponsor": "Needs confirmation"
+  },
   "details": "Details",
   "nationalIdNumber": "National ID number",
   "validation": {
@@ -146,11 +151,21 @@
     "cancelInvitationDescription": "Do you want to cancel the invitation?"
   },
   "error": {
+    "error": "Error",
     "invitationCreationFailedHeader": "Failed to create invite",
     "errorStatusCode": "Status code: {{statusCode}} (<3>{{statusText}}</3>)",
     "genericServerErrorBody": "Message:<1>{{errorBodyText}}</1>",
     "contactHelp": "Contact help through the link in the footer if the problem persists.",
     "errorLoadOusRoleTypeHeading": "Error loading form data",
-    "errorLoadOusRoleType": "Could not load organizational units and/or role type from server"
+    "errorLoadOusRoleType": "Could not load organizational units and/or role type from server",
+    "unknown": "An unknown error has occurred. If the problem persists, contact support.",
+    "invitationDataFetchFailed": "Failed to fetch invitation data",
+    "guestRegistrationFailed": "Failed to register your data",
+    "codes": {
+      "invalid_invite": "Invalid invite",
+      "invite_expired": "Invite has expired",
+      "cannot_update_fields": "Failed to update data",
+      "update_national_id_not_allowed": "Not allowed to update verified national ID"
+    }
   }
 }
diff --git a/frontend/public/locales/en/invite.json b/frontend/public/locales/en/invite.json
index 70693d275ab5a2c20d9513f1854d1fa4e0643844..ac094ff19553d9360f9af1885bc5a40152839691 100644
--- a/frontend/public/locales/en/invite.json
+++ b/frontend/public/locales/en/invite.json
@@ -1,6 +1,10 @@
 {
-  "description": "Please choose how you want to log in to complete your registration. The recommended way is to log in with either Feide or ID-porten. If that is not possible you can manually fill out the registration form with your passport number.",
-  "header": "Guest Registration",
+  "description": "Please choose how you want to log in to complete your registration. The recommended way is to log in with either Feide or ID-porten. If that is not possible you can manually fill out the registration form with your passport number or Norwegian national ID number.",
+  "header": "Guest registration",
   "login": "Log in with FEIDE",
-  "manual": "Registrate manually"
+  "manual": "Register manually",
+  "errors": {
+    "invalidToken": "The invitation link you followed is invalid.",
+    "expiredToken": "The invitation link you followed has expired. Contact your host to get a new link."
+  }
 }
diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json
index 3eaa4d75f3581666a46171ee3e89d5f0d74c483e..79d565aa28768ffe6a6e95fec7709a679504f0cb 100644
--- a/frontend/public/locales/nb/common.json
+++ b/frontend/public/locales/nb/common.json
@@ -16,7 +16,6 @@
     "roleStartDate": "Fra",
     "roleEndDate": "Til",
     "comment": "Kommentar",
-    "contact": "Kontaktperson",
     "searchable": "Synlig i søk?",
     "email": "E-post",
     "fullName": "Fullt navn",
@@ -44,6 +43,7 @@
     "bodyText": "Her kan du legge til en ny rolle på samme gjest"
   },
   "register": {
+    "noResults": "Finner ingen gjestekontoer som matcher søket ditt.",
     "registerHeading": "Registrer ny gjest",
     "registerText": "Søk etter e-post eller mobilnummer før du registrerer ny gjest for å unngå dobbeltoppføringer.",
     "registerButtonText": "Registrer ny gjest"
@@ -62,20 +62,25 @@
   "role": "Gjesterolle",
   "period": "Periode",
   "ou": "Organisasjon",
+  "department": "Avdeling",
   "choice": "Valg",
   "registerText": "Registrer ny gjest",
-  "waitingGuests": "Dine gjester som venter på godkjenning",
-  "waitingGuestsDescription": "Under er en oversikt over kontoer som venter på godkjenning",
+  "waitingGuests": "Gjester som venter på godkjenning",
+  "waitingGuestsDescription": "Her godkjenner du gjester som har <1>registrert seg manuelt</1>. Gjester som har FEIDE-bruker trenger ikke godkjenning.",
   "noWaitingGuests": "Ingen gjester til godkjenning",
-  "activeGuests": "Dine aktive gjester",
-  "activeGuestsDescription": "Her kan du endre på eksisterende gjester",
+  "activeGuests": "Godkjente gjester",
+  "activeGuestsDescription": "Her kan du endre på gjesteroller",
   "noActiveGuests": "Ingen aktive gjester",
   "sentInvitations": "Sendte invitasjoner",
   "sentInvitationsDescription": "Invitasjoner som venter på at gjesten skal ferdigstille registreringen.",
   "noInvitations": "Ingen invitasjoner",
   "status": "Status",
-  "active": "Aktiv",
-  "expired": "Utgått",
+  "statusText": {
+    "active": "Aktiv",
+    "expired": "Utgått",
+    "waitingForGuest": "Venter på gjest",
+    "waitingForSponsor": "Trenger godkjenning"
+  },
   "details": "Detaljer",
   "nationalIdNumber": "Fødselsnummer/D-nummer",
   "validation": {
@@ -146,11 +151,21 @@
     "cancelInvitationDescription": "Vil du kansellere invitasjonen?"
   },
   "error": {
+    "error": "Feil",
     "invitationCreationFailedHeader": "Kunne ikke opprette invitasjon",
     "errorStatusCode": "Statuskode: {{statusCode}} (<3>{{statusText}}</3>)",
     "genericServerErrorBody": "Melding:<1>{{errorBodyText}}</1>",
     "contactHelp": "Kontakt hjelp via link i footer om problemet vedvarer.",
+    "unknown": "En ukjent feil har oppstått. Om problemet vedvarer, kontakt brukerstøtte.",
     "errorLoadOusRoleTypeHeading": "Feil under lasting av skjemadata",
-    "errorLoadOusRoleType": "Kunne ikke laste organisasjons og/eller rolletype data fra server"
+    "errorLoadOusRoleType": "Kunne ikke laste organisasjons og/eller rolletype data fra server",
+    "invitationDataFetchFailed": "Klarte ikke å hente invitasjonsdata",
+    "guestRegistrationFailed": "Klarte ikke å registrere dataene dine",
+    "codes": {
+      "invalid_invite": "Ugyldig invitasjon",
+      "invite_expired": "Invitasjonen har utløpt",
+      "cannot_update_fields": "Klarte ikke å oppdatere dataene",
+      "update_national_id_not_allowed": "Ikke tillatt å endre verifisert fødselsnummer/D-nummer"
+    }
   }
 }
diff --git a/frontend/public/locales/nb/invite.json b/frontend/public/locales/nb/invite.json
index 6f6b73f0d316f226596aeaa894184030db619f91..8ad98cd8c87f22369f8d57a1f9fb94a1eb7cb5cd 100644
--- a/frontend/public/locales/nb/invite.json
+++ b/frontend/public/locales/nb/invite.json
@@ -2,5 +2,9 @@
   "description": "Vennligst velg hvordan du vil logge inn for å fullføre registreringen. Den anbefalte metoden er å logge inn gjennom Feide eller ID-porten. Dersom det ikke er mulig kan du fylle ut registreringskjemaet manuelt med passnummer",
   "header": "Gjestetjenesten",
   "login": "Logg inn med FEIDE",
-  "manual": "Registrer manuelt"
+  "manual": "Registrer manuelt",
+  "errors": {
+    "invalidToken": "Denne invitasjonslenka er ugyldig.",
+    "expiredToken": "Denne invitasjonslenka er utløpt. Kontakt verten din for å få tilsendt en ny."
+  }
 }
diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json
index 001606dfbcae78e09beab00cf77813aaec07c7ac..c49aa0b3406db333e16c365a6cb2d5bb9651ceb0 100644
--- a/frontend/public/locales/nn/common.json
+++ b/frontend/public/locales/nn/common.json
@@ -17,7 +17,6 @@
     "roleStartDate": "Frå",
     "roleEndDate": "Til",
     "comment": "Kommentar",
-    "contact": "Kontaktperson",
     "searchable": "Synleg i søk?",
     "email": "E-post",
     "fullName": "Fullt namn",
@@ -45,6 +44,7 @@
     "bodyText": "Her kan du legge til en ny rolle på samme gjest"
   },
   "register": {
+    "noResults": "Finner ingen gjestekontoer som matcher søket ditt.",
     "registerHeading": "Registrer ny gjest",
     "registerText": "Søk etter e-post eller mobilnummer før du registrerer ny gjest for å unngå dobbeltoppføringer.",
     "registerButtonText": "Registrer ny gjest"
@@ -63,20 +63,25 @@
   "role": "Gjesterolle",
   "period": "Periode",
   "ou": "Organisasjon",
+  "department": "Avdeling",
   "choice": "Valg",
   "registerText": "Registrer ny gjest",
-  "waitingGuests": "Dine gjester som venter på godkjenning",
-  "waitingGuestsDescription": "Under er en oversikt over kontoer som venter på godkjenning",
+  "waitingGuests": "Gjester som venter på godkjenning",
+  "waitingGuestsDescription": "Her godkjenner du gjester som har <1>registrert seg manuelt</1>. Gjester som har FEIDE-bruker trenger ikke godkjenning.",
   "noWaitingGuests": "Ingen gjester til godkjenning",
-  "activeGuests": "Dine aktive gjester",
-  "activeGuestsDescription": "Her kan du endre på eksisterende gjester",
+  "activeGuests": "Godkjente gjester",
+  "activeGuestsDescription": "Her kan du endre på gjesteroller.",
   "noActiveGuests": "Ingen aktive gjester",
   "sentInvitations": "Sendte invitasjonar",
   "sentInvitationsDescription": "Invitasjonar som venter på at gjesten skal ferdigstille registreringa.",
   "noInvitations": "Ingen invitasjonar",
   "status": "Status",
-  "active": "Aktiv",
-  "expired": "Utgått",
+  "statusText": {
+    "active": "Aktiv",
+    "expired": "Utgått",
+    "waitingForGuest": "Venter på gjest",
+    "waitingForSponsor": "Trenger godkjenning"
+  },
   "details": "Detaljer",
   "nationalIdNumber": "Fødselsnummer/D-nummer",
   "validation": {
@@ -147,11 +152,21 @@
     "cancelInvitationDescription": "Vil du kansellere invitasjonen?"
   },
   "error": {
+    "error": "Feil",
     "invitationCreationFailedHeader": "Kunne ikkje opprette invitasjon",
     "errorStatusCode": "Statuskode: {{statusCode}} (<3>{{statusText}}</3>)",
     "genericServerErrorBody": "Melding:<1>{{errorBodyText}}</1>",
     "contactHelp": "Kontakt hjelp via link i footer om problemet vedvarer.",
+    "unknown": "Ein uventa feil oppstod. Om problemet varer ved, kontakt brukarstøtte.",,
     "errorLoadOusRoleTypeHeading": "Feil under lasting av skjemadata",
-    "errorLoadOusRoleType": "Kunne ikkje laste organisasjons og/eller rolletype data frå server"
+    "errorLoadOusRoleType": "Kunne ikkje laste organisasjons og/eller rolletype data frå server",
+    "invitationDataFetchFailed": "Klarte ikkje å hente invitasjonsdata",
+    "guestRegistrationFailed": "Klarte ikkje å registrere dataene dine",
+    "codes": {
+      "invalid_invite": "Ugyldig invitasjon",
+      "invite_expired": "Invitasjonen har utløpe",
+      "cannot_update_fields": "Klarte ikkje å oppdatere dataene",
+      "update_national_id_not_allowed": "Ikkje tillete å endre verifisert fødselsnummer/D-nummer"
+    }
   }
 }
diff --git a/frontend/public/locales/nn/invite.json b/frontend/public/locales/nn/invite.json
index abb2afaf0747f645944c94525cc49b28015e87bf..155b826bcf3b540b0a5bfb81ddfeb0d7c878bc6f 100644
--- a/frontend/public/locales/nn/invite.json
+++ b/frontend/public/locales/nn/invite.json
@@ -2,5 +2,9 @@
   "description": "Ver venleg og vel korleis du vil logge inn for å fullføre registreringa. Den anbefalte metoden er å logge inn gjennom Feide eller ID-porten. Dersom det ikkje er mogeleg kan du fylle ut registreringskjemaet manuelt med passnummer",
   "header": "Gjestetjenesten",
   "login": "Logg inn med FEIDE",
-  "manual": "Registrer manuelt"
+  "manual": "Registrer manuelt",
+  "errors": {
+    "invalidToken": "Denne invitasjonslenka er ugyldig.",
+    "expiredToken": "Denne invitasjonslenka har utløpe. Kontakt verten din for å få tilsendt ei ny."
+  }
 }
diff --git a/frontend/src/components/debug/index.tsx b/frontend/src/components/debug/index.tsx
index aca2b8bcbe8b9f8fdccb4a23572584ab6131a899..e2bc7339b61b9f995cc5534f927a35990c956853 100644
--- a/frontend/src/components/debug/index.tsx
+++ b/frontend/src/components/debug/index.tsx
@@ -4,16 +4,7 @@ import { useTranslation } from 'react-i18next'
 import CheckIcon from '@mui/icons-material/Check'
 import ClearIcon from '@mui/icons-material/Clear'
 
-import {
-  Box,
-  Button,
-  Table,
-  TableBody,
-  TableRow,
-  TableCell,
-  Stack,
-  Divider,
-} from '@mui/material'
+import { Box, Table, TableBody, TableRow, TableCell } from '@mui/material'
 
 import { appInst, appTimezone, appVersion } from 'appConfig'
 import { Link } from 'react-router-dom'
@@ -26,8 +17,6 @@ export const Debug = () => {
   const [apiHealth, setApiHealth] = useState('not yet')
   const [didContactApi, setDidContactApi] = useState(false)
   const { i18n } = useTranslation(['common'])
-  const [csrf, setCsrf] = useState<String | null>(null)
-  const [username, setUsername] = useState(undefined)
   const { user } = useUserContext()
 
   if (!didContactApi) {
@@ -47,87 +36,6 @@ export const Debug = () => {
       })
   }
 
-  const getCSRF = () => {
-    fetch('/api/ui/v1/csrf/', {
-      credentials: 'same-origin',
-    })
-      .then((res) => {
-        const csrfToken = res.headers.get('X-CSRFToken')
-        setCsrf(csrfToken)
-        console.log(csrfToken)
-      })
-      .catch((err) => {
-        console.log(err)
-      })
-  }
-
-  const getSession = () => {
-    fetch('/api/ui/v1/session/?format=json', {
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      credentials: 'same-origin',
-    })
-      .then((res) => res.json())
-      .then((data) => {
-        console.log(data)
-        getCSRF()
-      })
-      .catch((err) => {
-        console.log(err)
-      })
-  }
-
-  const testMail = () => {
-    fetch('/api/ui/v1/testmail/', {
-      credentials: 'same-origin',
-    })
-      .then((data) => {
-        console.log(data)
-      })
-      .catch((err) => {
-        console.log(err)
-      })
-  }
-
-  const whoami = () => {
-    fetch('/api/ui/v1/whoami/', {
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      credentials: 'same-origin',
-    })
-      .then((res) => res.json())
-      .then((data) => {
-        setUsername(data.username)
-        console.log(`You are logged in as: ${data.username}`)
-      })
-      .catch((err) => {
-        console.log(err)
-      })
-  }
-
-  function isResponseOk(response: any) {
-    if (response.status >= 200 && response.status <= 299) {
-      return response.json()
-    }
-    throw Error(response.statusText)
-  }
-
-  const logout = () => {
-    fetch('/api/ui/v1/logout/', {
-      credentials: 'same-origin',
-    })
-      .then(isResponseOk)
-      .then((data) => {
-        console.log(data)
-        getCSRF()
-      })
-      .catch((err) => {
-        console.log(err)
-      })
-  }
-
   const d = [
     ['NODE_ENV', process.env.NODE_ENV],
     ['Version', appVersion],
@@ -137,8 +45,6 @@ export const Debug = () => {
     ['Institution', appInst],
     ['API reachable?', apiHealth === 'yes' ? <Yes /> : apiHealth],
     ['Authenticated?', user.auth ? <Yes /> : <No />],
-    ['Username', username],
-    ['CSRF', csrf],
   ]
   return (
     <Box>
@@ -163,25 +69,6 @@ export const Debug = () => {
         </ul>
       </p>
       <h3>Debug</h3>
-
-      <Stack
-        direction="row"
-        spacing={1}
-        divider={<Divider orientation="vertical" />}
-      >
-        <Button type="button" onClick={() => getSession()}>
-          AM I AUTHENTICATED?
-        </Button>
-        <Button type="button" onClick={() => whoami()}>
-          WHO AM I?
-        </Button>
-        <Button type="button" onClick={() => logout()}>
-          LOGOUT
-        </Button>
-        <Button type="button" onClick={() => testMail()}>
-          SEND TEST MAIL
-        </Button>
-      </Stack>
       <Box sx={{ maxWidth: '30rem' }}>
         <Table>
           <TableBody>
diff --git a/frontend/src/contexts/featureContext.ts b/frontend/src/contexts/featureContext.ts
index 0fd76a4e800c9e5483994834b9ad0a06cb9717f9..238f2bc3a99393edc4e6d7f4a113d4caba9bc6ab 100644
--- a/frontend/src/contexts/featureContext.ts
+++ b/frontend/src/contexts/featureContext.ts
@@ -5,11 +5,14 @@ export interface IFeatureContext {
   displayContactAtUnit: boolean
   // Controls whether the optional field is shown in the register new guest wizard
   displayComment: boolean
+  // Should the contact at unit field be shown for the guest when he registers his information?
+  displayContactAtUnitGuestInput: boolean
 }
 
 export const FeatureContext = createContext<IFeatureContext>({
   displayContactAtUnit: true,
   displayComment: true,
+  displayContactAtUnitGuestInput: true,
 })
 
 export const useFeatureContext = () => useContext(FeatureContext)
diff --git a/frontend/src/hooks/useRoleTypes/index.tsx b/frontend/src/hooks/useRoleTypes/index.tsx
index 2c8eec3d5019156c0debcbae4d7ee1af7d825fa0..a18671307489b2767ef28ba86cac8e42e3f1bca7 100644
--- a/frontend/src/hooks/useRoleTypes/index.tsx
+++ b/frontend/src/hooks/useRoleTypes/index.tsx
@@ -12,7 +12,7 @@ function useRoleTypes(): RoleTypeData[] {
   const [roleTypes, setRoleTypes] = useState<RoleTypeData[]>([])
 
   async function fetchRoleTypes() {
-    fetch(`/api/ui/v1/roletypes`)
+    fetch(`/api/ui/v1/roletypes/`)
       .then((data) => data.text())
       .then((result) => {
         // The response is a JSON-array
diff --git a/frontend/src/providers/featureProvider.tsx b/frontend/src/providers/featureProvider.tsx
index 962e46aa14d866dd640a5ded31bce38c039753c0..6f5e724225ed67e2ced996aa9c7865d7f9db33a6 100644
--- a/frontend/src/providers/featureProvider.tsx
+++ b/frontend/src/providers/featureProvider.tsx
@@ -13,12 +13,20 @@ function FeatureProvider(props: FeatureProviderProps) {
   let features: IFeatureContext
   switch (appInst) {
     case 'uib':
-      features = { displayContactAtUnit: false, displayComment: false }
+      features = {
+        displayContactAtUnit: false,
+        displayComment: false,
+        displayContactAtUnitGuestInput: false,
+      }
       break
 
     case 'uio':
     default:
-      features = { displayContactAtUnit: true, displayComment: true }
+      features = {
+        displayContactAtUnit: true,
+        displayComment: true,
+        displayContactAtUnitGuestInput: true,
+      }
       break
   }
 
diff --git a/frontend/src/routes/components/sponsorGuestButtons.tsx b/frontend/src/routes/components/sponsorGuestButtons.tsx
index 021703014090bae566c63ea0be96f2298712f2fd..b713ff98ce6fb280fc0cb257286d58557ab69d38 100644
--- a/frontend/src/routes/components/sponsorGuestButtons.tsx
+++ b/frontend/src/routes/components/sponsorGuestButtons.tsx
@@ -1,5 +1,5 @@
 import PersonIcon from '@mui/icons-material/Person'
-import { Box, IconButton, Theme } from '@mui/material'
+import { Box, IconButton, styled, Theme } from '@mui/material'
 import PersonAddIcon from '@mui/icons-material/PersonAdd'
 import React from 'react'
 import { useTranslation } from 'react-i18next'
@@ -10,6 +10,13 @@ interface SponsorGuestButtonsProps {
   registerNewGuestActive?: boolean
 }
 
+const StyledIconButton = styled(IconButton)({
+  display: 'flex',
+  flexDirection: 'column',
+  alignItems: 'center',
+  fontSize: '22px',
+})
+
 export default function SponsorGuestButtons(props: SponsorGuestButtonsProps) {
   const { yourGuestsActive, registerNewGuestActive } = props
   const { t } = useTranslation(['common'])
@@ -30,22 +37,20 @@ export default function SponsorGuestButtons(props: SponsorGuestButtonsProps) {
         flexDirection: 'row',
         justifyContent: 'space-evenly',
         marginBottom: '2rem',
+        fontSize: '22px',
       }}
     >
-      <IconButton
+      <StyledIconButton
         onClick={goToOverview}
         sx={{
-          display: 'flex',
-          flexDirection: 'column',
-          alignItems: 'center',
-          typography: 'caption',
+          color: () => (yourGuestsActive ? 'primary.main' : ''),
           textDecorationLine: () => (yourGuestsActive ? 'underline' : ''),
         }}
       >
         <PersonIcon
-          fontSize="large"
           sx={{
-            borderRadius: '2rem',
+            fontSize: '80px',
+            borderRadius: '4rem',
             borderStyle: 'solid',
             borderColor: (theme: Theme) =>
               yourGuestsActive
@@ -59,22 +64,19 @@ export default function SponsorGuestButtons(props: SponsorGuestButtonsProps) {
           }}
         />
         {t('yourGuests')}
-      </IconButton>
+      </StyledIconButton>
 
-      <IconButton
+      <StyledIconButton
         onClick={goToRegister}
         sx={{
-          display: 'flex',
-          flexDirection: 'column',
-          alignItems: 'center',
-          typography: 'caption',
+          color: () => (registerNewGuestActive ? 'primary.main' : ''),
           textDecorationLine: () => (registerNewGuestActive ? 'underline' : ''),
         }}
       >
         <PersonAddIcon
-          fontSize="large"
           sx={{
-            borderRadius: '2rem',
+            fontSize: '80px',
+            borderRadius: '4rem',
             borderStyle: 'solid',
             borderColor: (theme: Theme) =>
               registerNewGuestActive
@@ -88,7 +90,7 @@ export default function SponsorGuestButtons(props: SponsorGuestButtonsProps) {
           }}
         />
         {t('registerNewGuest')}
-      </IconButton>
+      </StyledIconButton>
     </Box>
   )
 }
diff --git a/frontend/src/routes/guest/register/guestDataForm.ts b/frontend/src/routes/guest/register/guestDataForm.ts
index c1d35dcd19de659ccc018a0166797cf18119ffa4..5c86b6000fccd05c06dd091b5198ef23c3a99c6a 100644
--- a/frontend/src/routes/guest/register/guestDataForm.ts
+++ b/frontend/src/routes/guest/register/guestDataForm.ts
@@ -14,7 +14,7 @@ export type GuestInviteInformation = {
   role_name_nb: string
   role_start: string
   role_end: string
-  comment?: string
+  contact_person_unit?: string
 
   feide_id?: string
   email?: string
diff --git a/frontend/src/routes/guest/register/index.test.tsx b/frontend/src/routes/guest/register/index.test.tsx
index b8a9c0df406d959b8375c6acdceb89c2eb3b3a9a..0ac01aee3ccd557ed5bfce7a30b38b62ffb1ccde 100644
--- a/frontend/src/routes/guest/register/index.test.tsx
+++ b/frontend/src/routes/guest/register/index.test.tsx
@@ -25,7 +25,6 @@ const testData = {
     start: '2021-08-10',
     end: '2021-08-16',
     contact_person_unit: 'Test contact person',
-    comments: 'Test comment',
   },
   meta: {
     session_type: 'invite',
@@ -66,5 +65,7 @@ test('Field showing values correctly', async () => {
   await screen.findByDisplayValue(
     `${testData.role.start} - ${testData.role.end}`
   )
-  await screen.findByDisplayValue(testData.role.comments)
+
+  // For the default setup the contact person at unit field should be showing
+  await screen.findByDisplayValue(testData.role.contact_person_unit)
 })
diff --git a/frontend/src/routes/guest/register/index.tsx b/frontend/src/routes/guest/register/index.tsx
index c595bac7361717600f2670694d96eec2852f3782..3b8bd5b022442fcd09f8d6927bd371aa94f9d97f 100644
--- a/frontend/src/routes/guest/register/index.tsx
+++ b/frontend/src/routes/guest/register/index.tsx
@@ -21,12 +21,9 @@ import AuthenticationMethod from './authenticationMethod'
 import GuestRegisterStep from './steps/register'
 import GuestConsentStep from './steps/consent'
 import GuestSuccessStep from './steps/success'
-
-enum SubmitState {
-  NotSubmitted,
-  Submitted,
-  SubmittedError,
-}
+import ServerErrorReport, {
+  ErrorReportProps,
+} from '../../../components/errorReport'
 
 enum Step {
   RegisterStep,
@@ -56,7 +53,7 @@ type InvitationData = {
     role_name_en: string
     start: string
     end: string
-    comments: string
+    contact_person_unit: string
   }
   meta: {
     session_type: string
@@ -72,12 +69,6 @@ export default function GuestRegister() {
 
   const guestRegisterRef = useRef<GuestRegisterCallableMethods>(null)
   const guestConsentRef = useRef<GuestRegisterCallableMethods>(null)
-
-  // TODO On submit successful the user should be directed to some page telling
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  const [submitState, setSubmitState] = useState<SubmitState>(
-    SubmitState.NotSubmitted
-  )
   const [activeStep, setActiveStep] = useState(0)
 
   const [initialGuestData, setInitialGuestData] =
@@ -87,12 +78,39 @@ export default function GuestRegister() {
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   const [guestConsentData, setGuestConsentData] =
     useState<GuestConsentData | null>(null)
+  const [fetchInvitationDataError, setFetchInvitationDataError] =
+    useState<ErrorReportProps | null>(null)
+  const [submitGuestDataError, setSubmitGuestDataError] =
+    useState<ErrorReportProps | null>(null)
 
   const fetchInvitationData = async () => {
     const response = await fetch('/api/ui/v1/invited/', fetchJsonOpts())
     if (!response.ok) {
+      try {
+        // Expect that the error will contain some JSON-data in the body
+        const errorData = await response.json()
+        setFetchInvitationDataError({
+          errorHeading: t('error.invitationDataFetchFailed'),
+          statusCode: response.status,
+          statusText: response.statusText,
+          errorBodyText: t(`error.codes.${errorData?.code}`),
+        })
+      } catch (e: unknown) {
+        console.error(e)
+
+        // Probably some unknown data in the body, create an error message
+        // using the rest of the information in the response
+        setFetchInvitationDataError({
+          errorHeading: t('error.invitationDataFetchFailed'),
+          statusCode: response.status,
+          statusText: response.statusText,
+          errorBodyText: undefined,
+        })
+      }
+
       return
     }
+    setFetchInvitationDataError(null)
     const data: InvitationData = await response.json()
 
     const authenticationMethod =
@@ -149,7 +167,7 @@ export default function GuestRegister() {
       role_name_nb: data.role.role_name_nb ?? '',
       role_start: data.role.start ?? '',
       role_end: data.role.end ?? '',
-      comment: data.role.comments ?? '',
+      contact_person_unit: data.role.contact_person_unit ?? '',
 
       authentication_method: authenticationMethod,
     })
@@ -192,6 +210,8 @@ export default function GuestRegister() {
   const handleBack = () => {
     if (activeStep === Step.ConsentStep) {
       setActiveStep(Step.RegisterStep)
+      // Clear error if any
+      setSubmitGuestDataError(null)
     }
   }
 
@@ -257,15 +277,43 @@ export default function GuestRegister() {
     fetch('/api/ui/v1/invited/', submitJsonOpts('POST', payload))
       .then((response) => {
         if (response.ok) {
-          setSubmitState(SubmitState.Submitted)
           setActiveStep(Step.SuccessStep)
+          setSubmitGuestDataError(null)
         } else {
-          setSubmitState(SubmitState.SubmittedError)
-          console.error(`Server responded with status: ${response.status}`)
+          // Expect that the error will contain JSON-data in the body
+          // and that there will be a code field there
+          response
+            .json()
+            .then((errorData) => {
+              setSubmitGuestDataError({
+                errorHeading: t('error.guestRegistrationFailed'),
+                statusCode: response.status,
+                statusText: response.statusText,
+                errorBodyText: t(`error.codes.${errorData?.code}`),
+              })
+            })
+            .catch((error) => {
+              console.error(error)
+
+              // Probably some unknown data in the body, create an error message
+              // using the rest of the information in the response
+              setSubmitGuestDataError({
+                errorHeading: t('error.guestRegistrationFailed'),
+                statusCode: response.status,
+                statusText: response.statusText,
+                errorBodyText: undefined,
+              })
+            })
         }
       })
       .catch((error) => {
-        setSubmitState(SubmitState.SubmittedError)
+        // Something went wrong before/during the backend was called
+        setSubmitGuestDataError({
+          errorHeading: t('error.guestRegistrationFailed'),
+          statusCode: undefined,
+          statusText: undefined,
+          errorBodyText: undefined,
+        })
         console.error(error)
       })
   }
@@ -372,6 +420,24 @@ export default function GuestRegister() {
           </Button>
         )}
       </Box>
+
+      {fetchInvitationDataError !== null && (
+        <ServerErrorReport
+          errorHeading={fetchInvitationDataError?.errorHeading}
+          statusCode={fetchInvitationDataError?.statusCode}
+          statusText={fetchInvitationDataError?.statusText}
+          errorBodyText={fetchInvitationDataError?.errorBodyText}
+        />
+      )}
+
+      {submitGuestDataError !== null && (
+        <ServerErrorReport
+          errorHeading={submitGuestDataError?.errorHeading}
+          statusCode={submitGuestDataError?.statusCode}
+          statusText={submitGuestDataError?.statusText}
+          errorBodyText={submitGuestDataError?.errorBodyText}
+        />
+      )}
     </Page>
   )
 }
diff --git a/frontend/src/routes/guest/register/steps/register.tsx b/frontend/src/routes/guest/register/steps/register.tsx
index 9cc70c712169fe2e812b4b3caaef7856ede06bfb..3b4fbb35a67232f2947783647d2a59c6f2b8225f 100644
--- a/frontend/src/routes/guest/register/steps/register.tsx
+++ b/frontend/src/routes/guest/register/steps/register.tsx
@@ -11,6 +11,7 @@ import { SubmitHandler, Controller, useForm } from 'react-hook-form'
 import React, {
   forwardRef,
   Ref,
+  useContext,
   useEffect,
   useImperativeHandle,
   useState,
@@ -30,9 +31,11 @@ import { GuestInviteInformation } from '../guestDataForm'
 import { GuestRegisterData } from '../enteredGuestData'
 import { GuestRegisterCallableMethods } from '../guestRegisterCallableMethods'
 import AuthenticationMethod from '../authenticationMethod'
+import { FeatureContext } from '../../../../contexts'
 
 interface GuestRegisterProperties {
   nextHandler(registerData: GuestRegisterData): void
+
   initialGuestData: GuestInviteInformation
   registerData: GuestRegisterData | null
 }
@@ -57,6 +60,7 @@ const GuestRegisterStep = forwardRef(
       string | undefined
     >(undefined)
     const [idErrorState, setIdErrorState] = useState<string>('')
+    const { displayContactAtUnitGuestInput } = useContext(FeatureContext)
 
     console.log('register step registerData', registerData)
 
@@ -528,14 +532,14 @@ const GuestRegisterStep = forwardRef(
                 disabled
               />
 
-              <TextField
-                id="comment"
-                label={t('input.comment')}
-                multiline
-                rows={5}
-                value={initialGuestData.comment}
-                disabled
-              />
+              {displayContactAtUnitGuestInput && (
+                <TextField
+                  id="contactPersonUnit"
+                  label={t('input.contactPersonUnit')}
+                  value={initialGuestData.contact_person_unit}
+                  disabled
+                />
+              )}
             </Stack>
           </form>
         </Box>
diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx
index e273442a88500c9057310d7a663a4b068b2be86d..1276166a6855b29848e62951470931c8caae4798 100644
--- a/frontend/src/routes/index.tsx
+++ b/frontend/src/routes/index.tsx
@@ -5,24 +5,24 @@ import { styled } from '@mui/system'
 import { CssBaseline } from '@mui/material'
 import fetchIntercept from 'fetch-intercept'
 
+import { registerLocale } from 'i18n-iso-countries'
+import i18n_iso_countries_en from 'i18n-iso-countries/langs/en.json'
+import i18n_iso_countries_nb from 'i18n-iso-countries/langs/nb.json'
+import i18n_iso_countries_nn from 'i18n-iso-countries/langs/nn.json'
+
 import { useUserContext } from 'contexts'
 import { getCookie, deleteCookie } from 'utils'
 
+import GuestRegister from 'routes/guest/register'
 import Sponsor from 'routes/sponsor'
 import Register from 'routes/sponsor/register'
 import FrontPage from 'routes/frontpage'
 import Invite from 'routes/invite'
-import InviteLink from 'routes/invitelink'
-import LogoutInviteSession from 'routes/invitelink/logout'
+import LogoutInviteSession from 'routes/invite/logout'
 import Footer from 'routes/components/footer'
 import Header from 'routes/components/header'
 import NotFound from 'routes/components/notFound'
 import ProtectedRoute from 'components/protectedRoute'
-import { registerLocale } from 'i18n-iso-countries'
-import i18n_iso_countries_en from 'i18n-iso-countries/langs/en.json'
-import i18n_iso_countries_nb from 'i18n-iso-countries/langs/nb.json'
-import i18n_iso_countries_nn from 'i18n-iso-countries/langs/nn.json'
-import GuestRegister from './guest/register'
 
 const AppWrapper = styled('div')({
   display: 'flex',
@@ -77,7 +77,6 @@ export default function App() {
           <ProtectedRoute path="/register">
             <Register />
           </ProtectedRoute>
-          <Route path="/invitelink/" component={InviteLink} />
           <Route path="/invite/logout" component={LogoutInviteSession} />
           <Route path="/invite/" component={Invite} />
           <Route path="/guestregister" component={GuestRegister} />
diff --git a/frontend/src/routes/invite/index.tsx b/frontend/src/routes/invite/index.tsx
index ba43e2d7c2a31266e2f209d6a838d44099b22f48..14acbc64312086b838447bf554ba76f48e48329e 100644
--- a/frontend/src/routes/invite/index.tsx
+++ b/frontend/src/routes/invite/index.tsx
@@ -1,25 +1,27 @@
+import { useEffect, useState } from 'react'
 import { useTranslation } from 'react-i18next'
 import Page from 'components/page'
+import Loading from 'components/loading'
 
 import { styled } from '@mui/material/styles'
 
+import { useUserContext } from 'contexts'
 import { HrefButton } from 'components/button'
 import { HrefLineButton } from 'components/button/linebutton'
+import { submitJsonOpts } from 'utils'
 
 const FlexDiv = styled('div')(() => ({
   display: 'flex',
   gap: '0.5rem',
 }))
 
-function Invite() {
+function ChooseRegistrationMethod() {
   const { t } = useTranslation(['invite'])
 
   return (
     <Page>
       <h1>{t('header')}</h1>
-      <p>
-        {t('description')}
-      </p>
+      <p>{t('description')}</p>
       <FlexDiv>
         <HrefButton to="/oidc/authenticate/">{t('login')}</HrefButton>
         <HrefLineButton to="/guestregister/">{t('manual')}</HrefLineButton>
@@ -28,4 +30,125 @@ function Invite() {
   )
 }
 
+interface ShowFeedbackProps {
+  title: string
+  description: string
+}
+
+function ShowFeedback(props: ShowFeedbackProps) {
+  const { title, description } = props
+
+  return (
+    <Page>
+      <h1>{title}</h1>
+      <p>{description}</p>
+    </Page>
+  )
+}
+
+function Invite() {
+  const { t } = useTranslation(['invite'])
+  const { user, fetchUserInfo } = useUserContext()
+  const [inviteToken, setInviteToken] = useState('')
+  const [tokenChecked, setTokenChecked] = useState(false)
+  const [isCheckingToken, setIsCheckingToken] = useState(false)
+  const [tokenOk, setTokenOk] = useState(false)
+  const [checkError, setCheckError] = useState('')
+
+  async function checkToken(token: string) {
+    try {
+      const response = await fetch(
+        '/api/ui/v1/invitecheck/',
+        submitJsonOpts('POST', { invite_token: token })
+      )
+      if (response.status === 200) {
+        setTokenOk(true)
+        return
+      }
+      const data = await response.json()
+
+      if ('code' in data) {
+        setCheckError(data.code)
+      } else {
+        setCheckError('unknown')
+      }
+    } catch (error) {
+      console.error(error)
+      setCheckError('unknown')
+    } finally {
+      setTokenChecked(true)
+      setIsCheckingToken(false)
+    }
+  }
+
+  console.log({ inviteToken, isCheckingToken, tokenChecked, tokenOk, user })
+
+  useEffect(() => {
+    setIsCheckingToken(true)
+    // This may seem unecessary, but race conditions have been
+    // observed where the userinfo endpoint is called too fast
+    // and no invite_id is found in the server-side session
+    setTimeout(fetchUserInfo, 100)
+  }, [setTokenOk])
+
+  if (user.auth) {
+    return <ChooseRegistrationMethod />
+  }
+
+  if (isCheckingToken || (tokenOk && !user.auth)) {
+    return <Loading />
+  }
+
+  if (inviteToken !== '' && !tokenChecked) {
+    checkToken(inviteToken)
+    return <Loading />
+  }
+
+  if (checkError !== '') {
+    if (
+      checkError === 'missing_invite_token' ||
+      checkError === 'invalid_invite_token'
+    ) {
+      // Missing or invalid token
+      return (
+        <ShowFeedback
+          title={t('common:error.error')}
+          description={t('invite:errors.invalidToken')}
+        />
+      )
+    }
+    if (checkError === 'expired_invite_token') {
+      // Expired token
+      return (
+        <ShowFeedback
+          title={t('common:error.error')}
+          description={t('invite:errors.expiredToken')}
+        />
+      )
+    }
+    // Unknown error
+    return (
+      <ShowFeedback
+        title={t('common:error.error')}
+        description={t('common:error.unknown')}
+      />
+    )
+  }
+
+  const providedToken = window.location.hash.slice(1).trim()
+
+  if (!inviteToken && providedToken) {
+    setInviteToken(providedToken)
+    return <Loading />
+  }
+
+  // We'll end up here if no token was provided in the URL
+  return (
+    <ShowFeedback
+      title={t('common:error.error')}
+      description={t('invite:errors.invalidToken')}
+    />
+  )
+}
+
 export default Invite
diff --git a/frontend/src/routes/invitelink/logout.tsx b/frontend/src/routes/invite/logout.tsx
similarity index 100%
rename from frontend/src/routes/invitelink/logout.tsx
rename to frontend/src/routes/invite/logout.tsx
diff --git a/frontend/src/routes/invitelink/index.tsx b/frontend/src/routes/invitelink/index.tsx
deleted file mode 100644
index fd8d63f056582d59de1a1774688a6d13506e8ec2..0000000000000000000000000000000000000000
--- a/frontend/src/routes/invitelink/index.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { useEffect } from 'react'
-import { Redirect } from 'react-router-dom'
-import { submitJsonOpts, setCookie } from 'utils'
-
-function InviteLink() {
-  // Fetch backend endpoint to preserve invite_id in backend session then redirect
-  // to generic invite page with info about feide login or manual with passport.
-
-  const id = window.location.hash.slice(1)
-
-  useEffect(() => {
-    fetch('/api/ui/v1/invitecheck/', submitJsonOpts('POST', { uuid: id }))
-  }, [])
-  setCookie('redirect', '/guestregister')
-  return <Redirect to="/invite" />
-}
-
-export default InviteLink
diff --git a/frontend/src/routes/sponsor/frontpage/index.tsx b/frontend/src/routes/sponsor/frontpage/index.tsx
index 1353b459c703c3edaffcbc172fbc62aa97c01028..678c03598fb145e5cdb921f43f6f8f30186a23e0 100644
--- a/frontend/src/routes/sponsor/frontpage/index.tsx
+++ b/frontend/src/routes/sponsor/frontpage/index.tsx
@@ -12,13 +12,15 @@ import {
   AccordionDetails,
   Button,
 } from '@mui/material'
-import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
+import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive'
+import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'
+import { styled } from '@mui/system'
 
 import Page from 'components/page'
-import { useTranslation } from 'react-i18next'
+import { useTranslation, Trans } from 'react-i18next'
 import { Link } from 'react-router-dom'
 import { Guest, Role } from 'interfaces'
-import { isBefore, format } from 'date-fns'
+import { isBefore } from 'date-fns'
 
 import SponsorGuestButtons from '../../components/sponsorGuestButtons'
 
@@ -29,40 +31,118 @@ interface GuestProps {
 interface PersonLineProps {
   person: Guest
   role: Role
-  showStatusColumn?: boolean
 }
 
-const PersonLine = ({ person, role, showStatusColumn }: PersonLineProps) => {
+interface StatusProps {
+  status: string
+}
+
+interface GuestTableProps {
+  guests: Guest[]
+  emptyText: string
+}
+
+interface FrontPageProps {
+  guests: Guest[]
+}
+
+const StyledTableRow = styled(TableRow)(({ theme }) => ({
+  borderTop: '0',
+  borderLeft: '0',
+  borderRight: '0',
+  borderBottom: '2px solid',
+  borderColor: theme.palette.primary.main,
+  borderRadius: '0',
+}))
+
+const StyledAccordion = styled(Accordion)({
+  borderStyle: 'none',
+  boxShadow: 'none',
+})
+
+const StyledAccordionSummary = styled(AccordionSummary)({
+  borderStyle: 'solid',
+  borderColor: 'black',
+  borderWidth: '2px',
+  borderRadius: '1%',
+})
+
+const StyledTableHeadCell = styled(TableCell)({
+  fontWeight: 'bold',
+})
+
+const StyledTableHead = styled(TableHead)(({ theme }) => ({
+  borderTop: '0',
+  borderLeft: '0',
+  borderRight: '0',
+  borderBottom: '3px solid',
+  borderColor: theme.palette.secondary.main,
+  borderRadius: '0',
+}))
+
+const Status = ({ status }: StatusProps) => {
+  const { t } = useTranslation('common')
+  switch (status) {
+    case 'active':
+      return (
+        <TableCell sx={{ color: 'success.main' }} align="left">
+          {t('statusText.active')}
+        </TableCell>
+      )
+    case 'expired':
+      return (
+        <TableCell sx={{ color: 'error.main' }} align="left">
+          {t('statusText.expired')}
+        </TableCell>
+      )
+    case 'waitingForGuest':
+      return (
+        <TableCell sx={{ color: 'blue' }} align="left">
+          {t('statusText.waitingForGuest')}
+        </TableCell>
+      )
+    case 'waitingForSponsor':
+      return (
+        <TableCell sx={{ color: 'blue' }} align="left">
+          {t('statusText.waitingForSponsor')}
+        </TableCell>
+      )
+    default:
+      return (
+        <TableCell sx={{ color: 'error.main' }} align="left">
+          {t('statusText.waiting')}
+        </TableCell>
+      )
+  }
+}
+
+const PersonLine = ({ person, role }: PersonLineProps) => {
   const [t, i18n] = useTranslation(['common'])
   const today = new Date()
   today.setHours(0, 0, 0, 0)
+  let status = ''
+
+  if (!person.registered) {
+    status = 'waitingForGuest'
+  } else if (person.registered && !person.verified) {
+    status = 'waitingForSponsor'
+  } else if (person.registered && person.verified) {
+    if (!isBefore(role.end_date, today)) {
+      status = 'active'
+    } else {
+      status = 'expired'
+    }
+  }
+
   return (
-    <TableRow
-      key={`${person.first} ${person.last}`}
-      sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
-    >
+    <StyledTableRow key={`${person.first} ${person.last}`}>
       <TableCell component="th" scope="row">
         {`${person.first} ${person.last}`}
       </TableCell>
       <TableCell align="left">
         {i18n.language === 'en' ? role.name_en : role.name_nb}
       </TableCell>
-
-      {showStatusColumn &&
-        (!isBefore(role.end_date, today) ? (
-          <TableCell sx={{ color: 'green' }} align="left">
-            {t('common:active')}
-          </TableCell>
-        ) : (
-          <TableCell sx={{ color: 'red' }} align="left">
-            {t('common:expired')}
-          </TableCell>
-        ))}
-
-      <TableCell align="left">
-        {role.start_date ? format(role.start_date, 'yyyy-MM-dd') : null} -{' '}
-        {format(role.end_date, 'yyyy-MM-dd')}
-      </TableCell>
+      <Status status={status} />
       <TableCell align="left">
         {i18n.language === 'en' ? role.ou_en : role.ou_nb}
       </TableCell>
@@ -76,15 +156,47 @@ const PersonLine = ({ person, role, showStatusColumn }: PersonLineProps) => {
           {t('common:details')}
         </Button>
       </TableCell>
-    </TableRow>
+    </StyledTableRow>
   )
 }
 
-PersonLine.defaultProps = {
-  showStatusColumn: false,
+const GuestTable = ({ guests, emptyText }: GuestTableProps) => {
+  const { t } = useTranslation('common')
+
+  return (
+    <TableContainer
+      component={Paper}
+      sx={{ boxShadow: 'none', borderRadius: '0px' }}
+    >
+      <Table sx={{ minWidth: 650 }} aria-label="simple table">
+        <StyledTableHead>
+          <TableRow>
+            <StyledTableHeadCell>{t('common:name')}</StyledTableHeadCell>
+            <StyledTableHeadCell>{t('common:role')}</StyledTableHeadCell>
+            <StyledTableHeadCell>{t('common:status')}</StyledTableHeadCell>
+            <StyledTableHeadCell>{t('common:department')}</StyledTableHeadCell>
+            <StyledTableHeadCell />
+          </TableRow>
+        </StyledTableHead>
+        <TableBody>
+          {guests.length > 0 ? (
+            guests.map((person) =>
+              person.roles.map((role) => (
+                <PersonLine role={role} person={person} />
+              ))
+            )
+          ) : (
+            <TableRow>
+              <TableCell> {emptyText}</TableCell>
+            </TableRow>
+          )}
+        </TableBody>
+      </Table>
+    </TableContainer>
+  )
 }
 
-const WaitingForGuestRegistration = ({ persons }: GuestProps) => {
+const InvitedGuests = ({ persons }: GuestProps) => {
   const [activeExpanded, setActiveExpanded] = useState(false)
 
   // Show guests that have not responded to the invite yet
@@ -95,53 +207,20 @@ const WaitingForGuestRegistration = ({ persons }: GuestProps) => {
   }
   const [t] = useTranslation(['common'])
   return (
-    <Accordion
+    <StyledAccordion
       expanded={activeExpanded}
       onChange={() => {
         setActiveExpanded(!activeExpanded)
       }}
     >
-      <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+      <StyledAccordionSummary expandIcon={<ArrowUpwardIcon color="primary" />}>
         <h2>{t('common:sentInvitations')}</h2>
-      </AccordionSummary>
+      </StyledAccordionSummary>
       <AccordionDetails>
         <p>{t('common:sentInvitationsDescription')}</p>
-        <TableContainer component={Paper}>
-          <Table sx={{ minWidth: 650 }} aria-label="simple table">
-            <TableHead sx={{ backgroundColor: 'secondary.light' }}>
-              <TableRow>
-                <TableCell>{t('common:name')}</TableCell>
-                <TableCell align="left">{t('common:role')}</TableCell>
-                <TableCell align="left">{t('common:period')}</TableCell>
-                <TableCell align="left">{t('common:ou')}</TableCell>
-                <TableCell align="left">{t('common:choice')}</TableCell>
-              </TableRow>
-            </TableHead>
-            <TableBody>
-              {guests.length > 0 ? (
-                guests.map((person) =>
-                  person.roles ? (
-                    person.roles.map((role) => (
-                      <PersonLine role={role} person={person} />
-                    ))
-                  ) : (
-                    <></>
-                  )
-                )
-              ) : (
-                <></>
-              )}
-
-              <TableRow>
-                <TableCell>
-                  {guests.length > 0 ? '' : t('common:noActiveGuests')}
-                </TableCell>
-              </TableRow>
-            </TableBody>
-          </Table>
-        </TableContainer>
+        <GuestTable guests={guests} emptyText={t('common:noInvitations')} />
       </AccordionDetails>
-    </Accordion>
+    </StyledAccordion>
   )
 }
 
@@ -155,60 +234,20 @@ const ActiveGuests = ({ persons }: GuestProps) => {
   }
   const [t] = useTranslation(['common'])
   return (
-    <Accordion
+    <StyledAccordion
       expanded={activeExpanded}
       onChange={() => {
         setActiveExpanded(!activeExpanded)
       }}
     >
-      <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+      <StyledAccordionSummary expandIcon={<ArrowUpwardIcon color="primary" />}>
         <h2>{t('common:activeGuests')}</h2>
-      </AccordionSummary>
+      </StyledAccordionSummary>
       <AccordionDetails>
         <p>{t('common:activeGuestsDescription')}</p>
-        <TableContainer component={Paper}>
-          <Table sx={{ minWidth: 650 }} aria-label="simple table">
-            <TableHead sx={{ backgroundColor: 'secondary.light' }}>
-              <TableRow>
-                <TableCell>{t('common:name')}</TableCell>
-                <TableCell align="left">{t('common:role')}</TableCell>
-
-                <TableCell align="left">{t('common:status')}</TableCell>
-
-                <TableCell align="left">{t('common:period')}</TableCell>
-                <TableCell align="left">{t('common:ou')}</TableCell>
-                <TableCell align="left">{t('common:choice')}</TableCell>
-              </TableRow>
-            </TableHead>
-            <TableBody>
-              {guests.length > 0 ? (
-                guests.map((person) =>
-                  person.roles ? (
-                    person.roles.map((role) => (
-                      <PersonLine
-                        role={role}
-                        person={person}
-                        showStatusColumn
-                      />
-                    ))
-                  ) : (
-                    <></>
-                  )
-                )
-              ) : (
-                <></>
-              )}
-
-              <TableRow>
-                <TableCell>
-                  {guests.length > 0 ? '' : t('common:noActiveGuests')}
-                </TableCell>
-              </TableRow>
-            </TableBody>
-          </Table>
-        </TableContainer>
+        <GuestTable guests={guests} emptyText={t('common:noActiveGuests')} />
       </AccordionDetails>
-    </Accordion>
+    </StyledAccordion>
   )
 }
 
@@ -223,67 +262,43 @@ const WaitingGuests = ({ persons }: GuestProps) => {
   const [t] = useTranslation(['common'])
 
   return (
-    <Accordion
+    <StyledAccordion
       expanded={waitingExpanded}
       onChange={() => {
         setWaitingExpanded(!waitingExpanded)
       }}
-      sx={{ border: 'none' }}
     >
-      <AccordionSummary expandIcon={<ExpandMoreIcon />}>
-        <h2>{t('common:waitingGuests')}</h2>
-      </AccordionSummary>
+      <StyledAccordionSummary expandIcon={<ArrowUpwardIcon color="primary" />}>
+        <h2>
+          {t('common:waitingGuests')}{' '}
+          {guests.length > 0 && (
+            <NotificationsActiveIcon
+              sx={{ color: 'error.main', fontSize: '26px' }}
+            />
+          )}
+        </h2>
+      </StyledAccordionSummary>
       <AccordionDetails>
-        <p>{t('common:waitingGuestsDescription')}</p>
-
-        <TableContainer component={Paper}>
-          <Table sx={{ minWidth: 650 }} aria-label="simple table">
-            <TableHead sx={{ backgroundColor: 'secondary.light' }}>
-              <TableRow>
-                <TableCell>{t('common:name')}</TableCell>
-                <TableCell align="left">{t('common:role')}</TableCell>
-                <TableCell align="left">{t('common:period')}</TableCell>
-                <TableCell align="left">{t('common:ou')}</TableCell>
-                <TableCell align="left">{t('common:choice')}</TableCell>
-              </TableRow>
-            </TableHead>
-            <TableBody>
-              {guests.length > 0 ? (
-                guests.map((person) =>
-                  person.roles ? (
-                    person.roles.map((role) => (
-                      <PersonLine role={role} person={person} />
-                    ))
-                  ) : (
-                    <></>
-                  )
-                )
-              ) : (
-                <></>
-              )}
-              <TableRow>
-                <TableCell>
-                  {guests.length > 0 ? '' : t('common:noWaitingGuests')}
-                </TableCell>
-              </TableRow>
-            </TableBody>
-          </Table>
-        </TableContainer>
+        <p>
+          <Trans i18nKey="common:waitingGuestsDescription">
+            Her godkjenner du gjester som har <b>registrert seg manuelt</b>.
+            Gjester som har FEIDE-bruker trenger ikke godkjenning.
+          </Trans>
+        </p>
+        <GuestTable guests={guests} emptyText={t('common:noWaitingGuests')} />
       </AccordionDetails>
-    </Accordion>
+    </StyledAccordion>
   )
 }
 
-interface FrontPageProps {
-  guests: Guest[]
-}
-
 function FrontPage({ guests }: FrontPageProps) {
   return (
     <Page>
       <SponsorGuestButtons yourGuestsActive />
-      <WaitingForGuestRegistration persons={guests} />
+      <InvitedGuests persons={guests} />
+      <br />
       <WaitingGuests persons={guests} />
+      <br />
       <ActiveGuests persons={guests} />
     </Page>
   )
diff --git a/frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx b/frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx
index 66112b9c0b155e2022323a20b0e131b2a7f5bc16..82c67530aa227461b129c9dcdbe67c30e43aa555 100644
--- a/frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx
+++ b/frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx
@@ -138,10 +138,8 @@ export default function GuestRoleInfo({ guest }: GuestRoleInfoProps) {
                     render={({ field: { onChange, value } }) => (
                       <DatePicker
                         mask="____-__-__"
-                        disabled={roleInfo.start_date <= today}
                         label={t('input.roleStartDate')}
                         value={value}
-                        minDate={today}
                         maxDate={todayPlusMaxDays}
                         inputFormat="yyyy-MM-dd"
                         onChange={onChange}
@@ -159,8 +157,6 @@ export default function GuestRoleInfo({ guest }: GuestRoleInfoProps) {
                       <DatePicker
                         mask="____-__-__"
                         label={t('input.roleEndDate')}
-                        disabled={roleInfo.end_date < today}
-                        minDate={today}
                         maxDate={todayPlusMaxDays}
                         value={value}
                         inputFormat="yyyy-MM-dd"
diff --git a/frontend/src/routes/sponsor/guest/newGuestRole/index.tsx b/frontend/src/routes/sponsor/guest/newGuestRole/index.tsx
index f438dca6830fb68c0a7c7e9d5f803918a4d7ee66..3fc2155c5d4161d56c1b49a937d1207299185ef9 100644
--- a/frontend/src/routes/sponsor/guest/newGuestRole/index.tsx
+++ b/frontend/src/routes/sponsor/guest/newGuestRole/index.tsx
@@ -11,6 +11,7 @@ import {
   TextField,
   SelectChangeEvent,
   FormControlLabel,
+  Box,
 } from '@mui/material'
 import Page from 'components/page'
 import { format } from 'date-fns'
@@ -117,13 +118,14 @@ function NewGuestRole({ guest, reloadGuestInfo }: NewGuestRoleProps) {
   const [roleTypeChoice, setRoleTypeChoice] = useState<string>('')
   const [t, i18n] = useTranslation('common')
   const today = new Date()
+  const [maxDate, setMaxDate] = useState(today)
 
-  const todayPlusMaxDays = () => {
-    if (roleTypeChoice) {
-      const role = roleTypes.filter(
-        (rt) => rt.id.toString() === roleTypeChoice.toString()
-      )[0]
-      return addDays(role.max_days)(today)
+  const todayPlusMaxDays = (roleTypeId?: number) => {
+    if (roleTypeId) {
+      const role = roleTypes.find((rt) => rt.id === roleTypeId)
+      if (role !== undefined) {
+        return addDays(role.max_days)(today)
+      }
     }
     return addDays(0)(today)
   }
@@ -138,6 +140,7 @@ function NewGuestRole({ guest, reloadGuestInfo }: NewGuestRoleProps) {
   const handleRoleTypeChange = (event: SelectChangeEvent) => {
     setValue('type', event.target.value)
     setRoleTypeChoice(event.target.value)
+    setMaxDate(todayPlusMaxDays(Number(event.target.value)))
   }
   const handleOuChange = (event: SelectChangeEvent) => {
     if (event.target.value) {
@@ -193,18 +196,21 @@ function NewGuestRole({ guest, reloadGuestInfo }: NewGuestRoleProps) {
               label={t('common:ou')}
               onChange={handleOuChange}
             >
-              {ous.length > 0 ? (
+              {ous.length > 0 &&
                 ous
                   .sort(i18n.language === 'en' ? enSort : nbSort)
-                  .map((ou) => ouToItem(ou))
-              ) : (
-                <></>
-              )}
+                  .map((ou) => ouToItem(ou))}
             </Select>
           </FormControl>
           <Controller
             name="start_date"
             control={control}
+            rules={{
+              required: true,
+              validate: () =>
+                Number(getValues('start_date')) <=
+                Number(getValues('end_date')),
+            }}
             defaultValue={today}
             render={({ field }) => (
               <DatePicker
@@ -212,8 +218,7 @@ function NewGuestRole({ guest, reloadGuestInfo }: NewGuestRoleProps) {
                 label={t('input.roleStartDate')}
                 disabled={!roleTypeChoice}
                 value={field.value}
-                minDate={today}
-                maxDate={todayPlusMaxDays()}
+                maxDate={maxDate}
                 inputFormat="yyyy-MM-dd"
                 onChange={(value) => {
                   field.onChange(value)
@@ -222,9 +227,26 @@ function NewGuestRole({ guest, reloadGuestInfo }: NewGuestRoleProps) {
               />
             )}
           />
+
+          {errors.start_date && errors.start_date.type === 'required' && (
+            <Box sx={{ typography: 'caption', color: 'error.main' }}>
+              {t('validation.startDateMustBeSet')}
+            </Box>
+          )}
+          {errors.start_date && errors.start_date.type === 'validate' && (
+            <Box sx={{ typography: 'caption', color: 'error.main' }}>
+              {t('validation.startDateMustBeBeforeEndDate')}
+            </Box>
+          )}
           <Controller
             name="end_date"
             control={control}
+            rules={{
+              required: true,
+              validate: () =>
+                Number(getValues('start_date')) <=
+                Number(getValues('end_date')),
+            }}
             defaultValue={today}
             render={({ field }) => (
               <DatePicker
@@ -232,8 +254,7 @@ function NewGuestRole({ guest, reloadGuestInfo }: NewGuestRoleProps) {
                 label={t('input.roleEndDate')}
                 disabled={!roleTypeChoice}
                 value={field.value}
-                minDate={today}
-                maxDate={todayPlusMaxDays()}
+                maxDate={maxDate}
                 inputFormat="yyyy-MM-dd"
                 onChange={(value) => {
                   field.onChange(value)
@@ -242,10 +263,9 @@ function NewGuestRole({ guest, reloadGuestInfo }: NewGuestRoleProps) {
               />
             )}
           />
-
           <TextField
             id="contact"
-            label={t('input.contact')}
+            label={t('input.contactPersonUnit')}
             multiline
             rows={5}
             {...register('contact_person_unit')}
diff --git a/frontend/src/routes/sponsor/register/frontPage.tsx b/frontend/src/routes/sponsor/register/frontPage.tsx
index 3797e4d08059e5d1a2d9c64edf29a895272903b0..5ad6ac4ffc4398a8bff0971ce52480c86347b282 100644
--- a/frontend/src/routes/sponsor/register/frontPage.tsx
+++ b/frontend/src/routes/sponsor/register/frontPage.tsx
@@ -6,6 +6,8 @@ import SearchIcon from '@mui/icons-material/Search'
 import { useTranslation } from 'react-i18next'
 import { debounce } from 'lodash'
 import React, { useState } from 'react'
+import { Box, styled } from '@mui/system'
+import { fetchJsonOpts } from 'utils'
 
 type Guest = {
   first: string
@@ -14,21 +16,33 @@ type Guest = {
   value: string
 }
 
+const StyledSpan = styled('span')({
+  color: 'red',
+})
+
 function FrontPage() {
   const [t] = useTranslation('common')
   const [guests, setGuests] = useState<Guest[]>([])
+  const [searchHasInput, setSearchHasInput] = useState(false)
 
-  const getGuests = async (event: React.ChangeEvent<HTMLInputElement>) => {
+  const getGuests = (event: React.ChangeEvent<HTMLInputElement>) => {
     if (event.target.value) {
-      console.log('searching')
-      const response = await fetch(
-        `/api/ui/v1/person/search/${event.target.value}?format=json`
-      )
-      const repjson = await response.json()
-      console.log(repjson)
-      if (response.ok) {
-        setGuests(repjson)
-      }
+      setSearchHasInput(true)
+      fetch(`/api/ui/v1/person/search/${event.target.value}`, fetchJsonOpts())
+        .then((response) => {
+          if (response.ok) {
+            return response.json()
+          }
+          setSearchHasInput(false)
+          return []
+        })
+        .then((responseJson) => {
+          setGuests(responseJson)
+        })
+        .catch(() => {})
+    } else {
+      setSearchHasInput(false)
+      setGuests([])
     }
   }
   return (
@@ -36,36 +50,53 @@ function FrontPage() {
       <SponsorGuestButtons registerNewGuestActive />
       <h2>{t('register.registerHeading')}</h2>
       <p>{t('register.registerText')}</p>
-      <TextField
-        InputProps={{
-          endAdornment: (
-            <InputAdornment position="end">
-              <SearchIcon />
-            </InputAdornment>
-          ),
+      <Box
+        sx={{
+          borderStyle: () => (guests.length > 0 ? 'solid' : 'none'),
+          borderRadius: '5px',
+          borderColor: 'secondary.main',
+          padding: '7px 12px',
         }}
-        fullWidth
-        placeholder="Mobile phone, e-mail"
-        onChange={debounce(getGuests, 600)}
-      />
-      {guests ? (
-        guests.map((guest) => {
-          const guestTo = `/sponsor/guest/${guest.pid}`
-          return (
-            <MenuItem
-              key={`${guest.pid}-${guest.value}`}
-              component={Link}
-              to={guestTo}
-            >
-              {guest.first} {guest.last}
-              <br />
-              {guest.value}
-            </MenuItem>
-          )
-        })
-      ) : (
-        <></>
-      )}
+      >
+        <TextField
+          variant="standard"
+          error={searchHasInput && guests.length === 0}
+          InputProps={{
+            endAdornment: (
+              <InputAdornment position="end">
+                <SearchIcon />
+              </InputAdornment>
+            ),
+          }}
+          fullWidth
+          placeholder="Mobile phone, e-mail"
+          onChange={debounce(getGuests, 600)}
+        />
+        {guests.length === 0 && searchHasInput ? (
+          <StyledSpan>{t('register.noResults')}</StyledSpan>
+        ) : (
+          guests.map((guest) => {
+            const guestTo = `/sponsor/guest/${guest.pid}`
+            return (
+              <MenuItem
+                sx={{
+                  '&:hover': {
+                    fontWeight: 'bold',
+                  },
+                }}
+                key={`${guest.pid}-${guest.value}`}
+                component={Link}
+                to={guestTo}
+              >
+                {guest.first} {guest.last}
+                <br />
+                {guest.value}
+              </MenuItem>
+            )
+          })
+        )}
+      </Box>
+      <br />
       <Button
         variant="contained"
         color="secondary"
diff --git a/frontend/src/routes/sponsor/register/stepPersonForm.tsx b/frontend/src/routes/sponsor/register/stepPersonForm.tsx
index e796fa3361a2fffa1e6843e4237c7e84ea017bf0..1bccad5a56e43c6847053c8e47c097bfc0a5787a 100644
--- a/frontend/src/routes/sponsor/register/stepPersonForm.tsx
+++ b/frontend/src/routes/sponsor/register/stepPersonForm.tsx
@@ -19,6 +19,7 @@ import React, {
   useImperativeHandle,
   useState,
 } from 'react'
+import { addDays } from 'date-fns/fp'
 import { useTranslation } from 'react-i18next'
 import { RegisterFormData } from './formData'
 import { PersonFormMethods } from './personFormMethods'
@@ -44,9 +45,10 @@ const StepPersonForm = forwardRef(
     const [selectedRoleType, setSelectedRoleType] = useState(
       formData.role_type ? formData.role_type : ''
     )
+    const today = new Date()
+    const [todayPlusMaxDays, setTodayPlusMaxDays] = useState(today)
     const roleTypes = useRoleTypes()
     const { displayContactAtUnit, displayComment } = useContext(FeatureContext)
-    const today: Date = new Date()
 
     const roleTypeSort = () => (a: RoleTypeData, b: RoleTypeData) => {
       if (i18n.language === 'en') {
@@ -90,6 +92,14 @@ const StepPersonForm = forwardRef(
     const handleRoleTypeChange = (event: any) => {
       setValue('role_type', event.target.value)
       setSelectedRoleType(event.target.value)
+      const selectedRoleTypeInfo = roleTypes.find(
+        (rt) => rt.id === event.target.value
+      )
+      if (selectedRoleTypeInfo === undefined) {
+        setTodayPlusMaxDays(today)
+      } else {
+        setTodayPlusMaxDays(addDays(selectedRoleTypeInfo.max_days)(today))
+      }
     }
 
     function doSubmit() {
@@ -210,6 +220,7 @@ const StepPersonForm = forwardRef(
                   <DatePicker
                     mask="____-__-__"
                     label={t('input.roleStartDate')}
+                    maxDate={todayPlusMaxDays}
                     value={field.value}
                     inputFormat="yyyy-MM-dd"
                     onChange={(value) => {
@@ -232,6 +243,7 @@ const StepPersonForm = forwardRef(
                   <DatePicker
                     mask="____-__-__"
                     label={t('input.roleEndDate')}
+                    maxDate={todayPlusMaxDays}
                     value={field.value}
                     inputFormat="yyyy-MM-dd"
                     onChange={(value) => {
diff --git a/greg/admin.py b/greg/admin.py
index c850afc5309afa8dbd95e8acedeafa01a265b6e4..2d9252c444828ffde1b216a6882189c88b3dd62a 100644
--- a/greg/admin.py
+++ b/greg/admin.py
@@ -135,12 +135,20 @@ class OrganizationalUnitInline(admin.TabularInline):
 
 
 class SponsorAdmin(VersionAdmin):
-    list_display = ("id", "feide_id")
+    list_display = ("id", "feide_id", "first_name", "last_name")
     inlines = (OrganizationalUnitInline,)
     readonly_fields = ("id", "created", "updated")
 
 
 class SponsorOrganizationalUnitAdmin(VersionAdmin):
+    list_display = (
+        "id",
+        "sponsor",
+        "organizational_unit",
+        "hierarchical_access",
+        "source",
+        "automatic",
+    )
     readonly_fields = ("id", "created", "updated")
 
 
diff --git a/greg/api/serializers/consent.py b/greg/api/serializers/consent.py
index 2d1eadf6dad2599d1e188199d956d73060bb753b..ef3dff43184eba72e70b78b56b7319407891528d 100644
--- a/greg/api/serializers/consent.py
+++ b/greg/api/serializers/consent.py
@@ -6,7 +6,9 @@ from greg.models import Consent
 
 class ConsentSerializerBrief(serializers.ModelSerializer):
     type = ConsentTypeSerializerBrief(read_only=True)
+    choice = serializers.CharField(read_only=True, source="choice.value")
 
     class Meta:
         model = Consent
-        fields = ["type", "consent_given_at"]
+        queryset = Consent.objects.all().select_related("choice")
+        fields = ["type", "consent_given_at", "choice"]
diff --git a/greg/api/serializers/consent_type.py b/greg/api/serializers/consent_type.py
index 407777f6026e7ae0602988889657b2e764d2b71c..fefe1f19caca36bb086422bd2c2cb77371d05781 100644
--- a/greg/api/serializers/consent_type.py
+++ b/greg/api/serializers/consent_type.py
@@ -33,8 +33,4 @@ class ConsentTypeSerializer(ModelSerializer):
 class ConsentTypeSerializerBrief(ModelSerializer):
     class Meta:
         model = ConsentType
-        fields = [
-            "identifier",
-            "valid_from",
-            "user_allowed_to_change",
-        ]
+        fields = ["identifier", "mandatory"]
diff --git a/greg/management/commands/populate_test_data.py b/greg/management/commands/populate_test_data.py
index 72f6d1275f7e67c81b33f72f33afb4114371821f..f978003624786d70ed7c9daf69864f8d5965ef81 100644
--- a/greg/management/commands/populate_test_data.py
+++ b/greg/management/commands/populate_test_data.py
@@ -22,11 +22,10 @@ one of them has denied the other one.
 """
 
 import datetime
-from django.core.management.base import CommandError
 
 from django.db import connection
 from django.conf import settings
-from django.core.management.base import BaseCommand
+from django.core.management.base import BaseCommand, CommandError
 from django.utils import timezone
 
 from greg.models import (
@@ -327,15 +326,27 @@ class DatabasePopulation:
             invitation=invitation,
             expire=timezone.now() - datetime.timedelta(days=32),
         )
+        mandatory_consent_type = ConsentType.objects.get(
+            identifier=CONSENT_IDENT_MANDATORY
+        )
         Consent.objects.create(
             person=adam,
-            type=ConsentType.objects.get(identifier=CONSENT_IDENT_MANDATORY),
+            type=mandatory_consent_type,
             consent_given_at=datetime.date.today() - datetime.timedelta(days=10),
+            choice=ConsentChoice.objects.get(
+                consent_type=mandatory_consent_type, value="yes"
+            ),
+        )
+        optional_consent_type = ConsentType.objects.get(
+            identifier=CONSENT_IDENT_OPTIONAL
         )
         Consent.objects.create(
             person=adam,
-            type=ConsentType.objects.get(identifier=CONSENT_IDENT_OPTIONAL),
+            type=optional_consent_type,
             consent_given_at=datetime.date.today() - datetime.timedelta(days=10),
+            choice=ConsentChoice.objects.get(
+                consent_type=optional_consent_type, value="no"
+            ),
         )
 
     def _add_expired_person(self):
@@ -383,10 +394,16 @@ class DatabasePopulation:
             invitation=invitation,
             expire=timezone.now() - datetime.timedelta(days=204),
         )
+        mandatory_consent_type = ConsentType.objects.get(
+            identifier=CONSENT_IDENT_MANDATORY
+        )
         Consent.objects.create(
             person=esther,
-            type=ConsentType.objects.get(identifier=CONSENT_IDENT_MANDATORY),
-            consent_given_at=datetime.date.today() - datetime.timedelta(days=206),
+            type=mandatory_consent_type,
+            consent_given_at=datetime.date.today() - datetime.timedelta(days=10),
+            choice=ConsentChoice.objects.get(
+                consent_type=mandatory_consent_type, value="yes"
+            ),
         )
 
     def populate_database(self):
diff --git a/greg/migrations/0019_add_ou_parent_relatedname.py b/greg/migrations/0019_add_ou_parent_relatedname.py
new file mode 100644
index 0000000000000000000000000000000000000000..87eae55db0dd2b6ccdb98a68ca2b3a164b296072
--- /dev/null
+++ b/greg/migrations/0019_add_ou_parent_relatedname.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.2.9 on 2021-12-03 08:01
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("greg", "0018_alter_identity_type"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="organizationalunit",
+            name="parent",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.PROTECT,
+                related_name="children",
+                to="greg.organizationalunit",
+            ),
+        ),
+    ]
diff --git a/greg/models.py b/greg/models.py
index 6d04f570ad5bdef058f1373b291b67491a4ccf3b..a3eb03a1a66ff1fb8e9b6bb9e3f7ff4fcbb9ceab 100644
--- a/greg/models.py
+++ b/greg/models.py
@@ -54,7 +54,7 @@ class Person(BaseModel):
     objects = PersonManager()
 
     def __str__(self):
-        return "{} {} ({})".format(self.first_name, self.last_name, self.pk)
+        return f"{self.first_name} {self.last_name} ({self.pk})"
 
     def __repr__(self):
         return "{}(id={!r}, first_name={!r}, last_name={!r})".format(
@@ -197,7 +197,7 @@ class RoleType(BaseModel):
     max_days = models.IntegerField(default=365)
 
     def __str__(self):
-        return "{} ({})".format(str(self.name_en or self.name_nb), self.identifier)
+        return f"{str(self.name_en or self.name_nb)} ({self.identifier})"
 
     def __repr__(self):
         return "{}(pk={!r}, identifier={!r}, name_nb={!r}, name_en={!r})".format(
@@ -243,6 +243,12 @@ class Role(BaseModel):
             self.__class__.__name__, self.pk, self.person, self.type
         )
 
+    def __str__(self) -> str:
+        return (
+            f"{self.__class__.__name__}(id={self.pk}, "
+            f"role={self.person}/{self.type}@{self.orgunit.name_nb})"
+        )
+
 
 class Notification(BaseModel):
     """A change notification that should be delivered to a message queue."""
@@ -253,7 +259,7 @@ class Notification(BaseModel):
     issued_at = models.IntegerField()
     meta = models.JSONField(null=True, blank=True)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return "{}(id={!r}, identifier={!r}, object_type={!r}, operation={!r}, issued_at={!r}, meta={!r})".format(
             self.__class__.__name__,
             self.pk,
@@ -295,7 +301,7 @@ class Identity(BaseModel):
     )
     verified_at = models.DateTimeField(null=True, blank=True)
 
-    def __str__(self):
+    def __str__(self) -> str:
         return "{}(id={!r}, type={!r}, value={!r})".format(
             self.__class__.__name__,
             self.pk,
@@ -303,7 +309,7 @@ class Identity(BaseModel):
             self.value,
         )
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return "{}(id={!r}, person_id={!r}, type={!r}, source={!r}, value={!r}, verified_by={!r}, verified_at={!r})".format(
             self.__class__.__name__,
             self.pk,
@@ -335,10 +341,10 @@ class ConsentType(BaseModel):
     user_allowed_to_change = models.BooleanField()
     mandatory = models.BooleanField(default=False)
 
-    def __str__(self):
-        return "{} ({})".format(str(self.name_en or self.name_nb), self.identifier)
+    def __str__(self) -> str:
+        return f"{str(self.name_en or self.name_nb)} ({self.identifier})"
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return "{}(id={!r}, identifier={!r}, name_en={!r}, valid_from={!r}, user_allowed_to_change={!r})".format(
             self.__class__.__name__,
             self.pk,
@@ -370,10 +376,13 @@ class ConsentChoice(BaseModel):
             ),
         )
 
-    def __str__(self):
-        return "{} ({})".format(str(self.text_en or self.text_nb), self.value)
+    def __str__(self) -> str:
+        return (
+            f"{self.__class__.__name__}(id={self.pk}, value={self.value}, "
+            f"type={self.consent_type}))"
+        )
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return "{}(id={!r}, consent_type={!r} value={!r}, text_en={!r}, text_nb={!r}, text_nn={!r})".format(
             self.__class__.__name__,
             self.pk,
@@ -411,7 +420,14 @@ class Consent(BaseModel):
             )
         ]
 
-    def __repr__(self):
+    def __str__(self) -> str:
+        return (
+            f"{self.__class__.__name__}(id={self.pk}, "
+            f"person={self.person}, type={self.type}, "
+            f"consent_given_at={self.consent_given_at})"
+        )
+
+    def __repr__(self) -> str:
         return "{}(id={!r}, person={!r}, type={!r}, consent_given_at={!r})".format(
             self.__class__.__name__,
             self.pk,
@@ -436,7 +452,13 @@ class OuIdentifier(BaseModel):
             models.UniqueConstraint(name="unique_identifier", fields=["name", "value"])
         ]
 
-    def __repr__(self):
+    def __str__(self) -> str:
+        return (
+            f"{self.__class__.__name__}(id={self.pk}, "
+            f"name={self.name}, value={self.value})"
+        )
+
+    def __repr__(self) -> str:
         return "{}(id={!r}, name={!r}, value={!r})".format(
             self.__class__.__name__, self.pk, self.name, self.value
         )
@@ -449,17 +471,46 @@ class OrganizationalUnit(BaseModel):
 
     name_nb = models.CharField(max_length=256)
     name_en = models.CharField(max_length=256)
-    parent = models.ForeignKey("self", on_delete=models.PROTECT, null=True, blank=True)
+    parent = models.ForeignKey(
+        "self",
+        on_delete=models.PROTECT,
+        null=True,
+        blank=True,
+        related_name="children",
+    )
     active = models.BooleanField(default=True)
     deleted = models.BooleanField(default=False)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return "{}(id={!r}, name_en={!r}, parent={!r})".format(
             self.__class__.__name__, self.pk, self.name_en, self.parent
         )
 
-    def __str__(self):
-        return "{}".format(str(self.name_en or self.name_nb))
+    def __str__(self) -> str:
+        return f"{self.name_en or self.name_nb}"
+
+    def fetch_tree(self):
+        """
+        Convenience method for fetching a set of the entire tree below
+        this unit including itself.
+        """
+        units = set()
+        self.get_children(units)
+        return units
+
+    def get_children(self, units: set["OrganizationalUnit"]):
+        """
+        Fetch the entire tree of units with this unit at the top.
+
+        Expects an empty set for bookkeeping to prevent an infinite loop.
+        """
+        # shortcut to prevent infinite loop in case of a loop in the tree
+        if self.id in [i.id for i in units]:
+            return
+        # Recursion loop for fetching children of children of ...
+        units.add(self)
+        for child in self.children.all():
+            child.get_children(units)
 
 
 class Sponsor(BaseModel):
@@ -478,10 +529,10 @@ class Sponsor(BaseModel):
         related_name="sponsor_unit",
     )
 
-    def __str__(self):
-        return "{} ({} {})".format(self.feide_id, self.first_name, self.last_name)
+    def __str__(self) -> str:
+        return f"{self.feide_id} ({self.first_name} {self.last_name})"
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return "{}(id={!r}, feide_id={!r}, first_name={!r}, last_name={!r})".format(
             self.__class__.__name__,
             self.pk,
@@ -495,6 +546,24 @@ class Sponsor(BaseModel):
             models.UniqueConstraint(name="unique_feide_id", fields=["feide_id"])
         ]
 
+    def get_allowed_units(self) -> set[OrganizationalUnit]:
+        """
+        Fetch every unit the sponsor has access to.
+
+        This includes both through direct access and hierarchical.
+        """
+        connections = self.link_sponsor.all()
+        ha_units = [i.organizational_unit for i in connections if i.hierarchical_access]
+
+        units: set[OrganizationalUnit] = set()  # Collector set
+        # Add units accessible through hierarchical access
+        for unit in ha_units:
+            units.update(unit.fetch_tree())
+
+        # Add units accessible through direct access
+        units = units.union({i.organizational_unit for i in connections})
+        return units
+
 
 class SponsorOrganizationalUnit(BaseModel):
     """
@@ -519,7 +588,13 @@ class SponsorOrganizationalUnit(BaseModel):
             )
         ]
 
-    def __repr__(self):
+    def __str__(self) -> str:
+        return (
+            f"{self.__class__.__name__}(id={self.pk}, sponsor={self.sponsor}, "
+            f"org_unit={self.organizational_unit})"
+        )
+
+    def __repr__(self) -> str:
         return "{}(id={!r}, sponsor={!r}, organizational_unit={!r}, hierarchical_access={!r})".format(
             self.__class__.__name__,
             self.pk,
@@ -543,6 +618,12 @@ class InvitationLink(BaseModel):
     )
     expire = models.DateTimeField(blank=False, null=False)
 
+    def __str__(self) -> str:
+        return (
+            f"{self.__class__.__name__}(id={self.pk}, invitation={self.invitation}, "
+            f"uuid={self.uuid}, expire={self.expire})"
+        )
+
 
 class Invitation(BaseModel):
     """
@@ -552,3 +633,6 @@ class Invitation(BaseModel):
     """
 
     role = models.ForeignKey("Role", null=False, blank=False, on_delete=models.CASCADE)
+
+    def __str__(self) -> str:
+        return f"{self.__class__.__name__}(id={self.pk}, role={self.role})"
diff --git a/greg/signals.py b/greg/signals.py
index 5d427897b25e54fcd26edf53092444796c3c1331..de65ab3e8c62b15e960b358201c094eacdaccd04 100644
--- a/greg/signals.py
+++ b/greg/signals.py
@@ -134,9 +134,11 @@ def save_notification_callback(sender, instance, created, *args, **kwargs):
         return
     # Queue future notifications on start and end date for roles
     if isinstance(instance, Role) and hasattr(instance, "_changed_fields"):
+        today = datetime.date.today()
         if (
             "start_date" in instance._changed_fields  # pylint: disable=protected-access
             and instance.start_date
+            and instance.start_date != today
         ):
             Schedule.objects.create(
                 func="greg.signals._queue_role_start_notification",
@@ -144,7 +146,10 @@ def save_notification_callback(sender, instance, created, *args, **kwargs):
                 next_run=date_to_datetime_midnight(instance.start_date),
                 schedule_type=Schedule.ONCE,
             )
-        if "end_date" in instance._changed_fields:  # pylint: disable=protected-access
+        if (
+            "end_date" in instance._changed_fields  # pylint: disable=protected-access
+            and instance.end_date != today
+        ):
             Schedule.objects.create(
                 func="greg.signals._queue_role_end_notification",
                 args=f"{instance.id},True",
diff --git a/greg/tests/api/test_person.py b/greg/tests/api/test_person.py
index 541c916a67bd10da248e3f2625724baa0205cec0..9be933358048289b9f7ee98d3856c417be9ec669 100644
--- a/greg/tests/api/test_person.py
+++ b/greg/tests/api/test_person.py
@@ -8,6 +8,7 @@ from rest_framework.status import HTTP_200_OK
 from rest_framework.test import APIClient
 
 from greg.models import (
+    Consent,
     Identity,
     Sponsor,
     RoleType,
@@ -60,13 +61,25 @@ def role_data_guest(
 
 
 @pytest.mark.django_db
-def test_get_person(client, person_foo):
+def test_get_person(client, person_foo, consent_type_foo):
+    Consent.objects.create(
+        person=person_foo,
+        type=consent_type_foo,
+        choice=consent_type_foo.choices.get(value="yes"),
+    )
     resp = client.get(reverse("v1:person-detail", kwargs={"id": person_foo.id}))
     assert resp.status_code == HTTP_200_OK
     data = resp.json()
     assert data.get("id") == person_foo.id
     assert data.get("first_name") == person_foo.first_name
     assert data.get("last_name") == person_foo.last_name
+    assert data.get("consents") == [
+        {
+            "consent_given_at": None,
+            "type": {"identifier": "foo", "mandatory": False},
+            "choice": "yes",
+        }
+    ]
 
 
 @pytest.mark.django_db
diff --git a/greg/tests/conftest.py b/greg/tests/conftest.py
index 8315054552d2a98a70dd77e34af6991e5b80d5e8..7b6acedc3680b78217467923cae30bed61fa769a 100644
--- a/greg/tests/conftest.py
+++ b/greg/tests/conftest.py
@@ -238,3 +238,32 @@ def notification():
         meta="foometa",
     )
     return Notification.objects.get(id=1)
+
+
+@pytest.fixture
+def looped_units():
+    # the loop
+    a = OrganizationalUnit.objects.create()
+    b = OrganizationalUnit.objects.create(parent=a)
+    c = OrganizationalUnit.objects.create(parent=b)
+    d = OrganizationalUnit.objects.create(parent=c)
+    e = OrganizationalUnit.objects.create(parent=d)
+    a.parent = e
+    a.save()
+    return a, b, c, d, e
+
+
+@pytest.fixture
+def loop_sponsor(looped_units, unit_foo) -> Sponsor:
+    """A sponsor that is connected to a loop, and a single unit."""
+    loop_unit = looped_units[2]
+    sp = Sponsor.objects.create()
+    # Connection to the loop
+    SponsorOrganizationalUnit.objects.create(
+        sponsor=sp, organizational_unit=loop_unit, hierarchical_access=True
+    )
+    # Stand alone connection
+    SponsorOrganizationalUnit.objects.create(
+        sponsor=sp, organizational_unit=unit_foo, hierarchical_access=False
+    )
+    return sp
diff --git a/greg/tests/models/test_organizational_unit.py b/greg/tests/models/test_organizational_unit.py
index ce2043065ff5b367bbb1975976d7173130b55385..dfbcfa3ebe9621ca45113bd1c6eec4e41a486e55 100644
--- a/greg/tests/models/test_organizational_unit.py
+++ b/greg/tests/models/test_organizational_unit.py
@@ -18,3 +18,10 @@ def test_org_repr(unit_foo):
 @pytest.mark.django_db
 def test_org_str(unit_foo):
     assert str(unit_foo) == "foo_unit"
+
+
+@pytest.mark.django_db
+def test_fetch_tree_loop(looped_units):
+    a = looped_units[0]
+    units = a.fetch_tree()
+    assert [x.id for x in units] == [i.id for i in looped_units]
diff --git a/greg/tests/models/test_sponsor.py b/greg/tests/models/test_sponsor.py
index 581111194aca42b87195e0cab985ff17aec618ef..322ada7d7f559a7a319d53de5194317989360980 100644
--- a/greg/tests/models/test_sponsor.py
+++ b/greg/tests/models/test_sponsor.py
@@ -63,3 +63,10 @@ def test_sponsor_repr(sponsor_guy):
 @pytest.mark.django_db
 def test_sponsor_str(sponsor_guy):
     assert str(sponsor_guy) == "guy@example.org (Sponsor Guy)"
+
+
+@pytest.mark.django_db
+def test_get_allowed_loop(loop_sponsor, looped_units, unit_foo):
+    units = loop_sponsor.get_allowed_units()
+    expected = [i.id for i in looped_units] + [unit_foo.id]
+    assert [x.id for x in units] == expected
diff --git a/gregui/admin.py b/gregui/admin.py
index 7c3f771755952b78e409029ce4c17be9bd6412eb..7b80a29b329de04566c2edcd312c97918686c126 100644
--- a/gregui/admin.py
+++ b/gregui/admin.py
@@ -5,7 +5,7 @@ from gregui.models import EmailTemplate, GregUserProfile
 
 
 class GregUserProfileAdmin(VersionAdmin):
-    pass
+    list_display = ["id", "userid_feide", "person", "sponsor"]
 
 
 class EmailTemplateAdmin(VersionAdmin):
diff --git a/gregui/api/serializers/role.py b/gregui/api/serializers/role.py
index 0b867f092a1ca0f6eea1d9a1f71cbd9aa703a175..b092f8b8102a9079fcb9cdd8cc494823f0cc03b1 100644
--- a/gregui/api/serializers/role.py
+++ b/gregui/api/serializers/role.py
@@ -28,38 +28,13 @@ class RoleSerializerUi(serializers.ModelSerializer):
             )
         ]
 
-    def validate_start_date(self, start_date):
-        """Enfore rules for start_date.
-
-        Must be present, can be blank, before today not allowed.
-        """
-        if not start_date:
-            return start_date
-        today = datetime.date.today()
-        # New start dates cannot be in the past
-        if start_date < today:
-            raise serializers.ValidationError("Start date cannot be in the past")
-
-        return start_date
-
-    def validate_end_date(self, end_date):
-        """Ensure rules for end_date are followed"""
-        today = datetime.date.today()
-        if end_date < today:
-            raise serializers.ValidationError("End date cannot be in the past")
-        if self.instance and self.instance.end_date < today:
-            raise serializers.ValidationError("Role has ended, cannot change end date")
-        return end_date
-
     def validate_orgunit(self, unit):
         """Enforce rules related to orgunit"""
-        sponsor = self.context["sponsor"]
-        units = sponsor.units.all()
-        # Restrict to a sponsor's own units
-        if not units or unit not in units:
-            raise ValidationError(
-                "A sponsor can only make changes to roles at units they are sponsors for."
-            )
+        # A sponsor can only add roles to units they are sponsors at, or children of
+        # that unit if they have hierarchical access.
+        allowed_units = self.context["sponsor"].get_allowed_units()
+        if unit not in allowed_units:
+            raise ValidationError("You can only edit roles connected to your units.")
         return unit
 
     def validate(self, attrs):
@@ -87,10 +62,10 @@ class RoleSerializerUi(serializers.ModelSerializer):
                 raise serializers.ValidationError(
                     "End date cannot be before start date."
                 )
-        # If we are updating an existing roles, we must be the sponsor of the role
-        sponsor = self.context["sponsor"]
-        if self.instance and self.instance.sponsor != sponsor:
-            raise ValidationError("You can only edit your own roles.")
+        # A sponsor can not modify roles connected to units they do not have access to
+        allowed_units = self.context["sponsor"].get_allowed_units()
+        if self.instance and self.instance.orgunit not in allowed_units:
+            raise ValidationError("You can only edit roles connected to your units.")
 
         return attrs
 
diff --git a/gregui/api/urls.py b/gregui/api/urls.py
index 3b991355256b6eb6fb093284013e50408fa90aca..c2fb05dab808f6cd78d1c03c15685a977084efed 100644
--- a/gregui/api/urls.py
+++ b/gregui/api/urls.py
@@ -14,6 +14,7 @@ from gregui.api.views.role import RoleInfoViewSet
 from gregui.api.views.consent import ConsentTypeViewSet
 from gregui.api.views.roletypes import RoleTypeViewSet
 from gregui.api.views.unit import UnitsViewSet
+from gregui.api.views.userinfo import UserInfoView
 
 router = DefaultRouter(trailing_slash=False)
 router.register(r"role", RoleInfoViewSet, basename="role")
@@ -40,4 +41,5 @@ urlpatterns += [
         PersonSearchViewSet.as_view({"get": "list"}),
         name="person-search",
     ),
+    path("userinfo/", UserInfoView.as_view(), name="userinfo"),
 ]
diff --git a/gregui/api/views/invitation.py b/gregui/api/views/invitation.py
index a979d534b95357fe0a02a5e5ee7efb2275dda1a7..54cc778e9dad6c7b00f396dec78eb16f7d0403bd 100644
--- a/gregui/api/views/invitation.py
+++ b/gregui/api/views/invitation.py
@@ -118,15 +118,33 @@ class CheckInvitationView(APIView):
 
         Uses post to prevent invite id from showing up in various logs.
         """
-        invite_id = request.data.get("uuid")
+        invite_id = request.data.get("invite_token")
         if not invite_id:
-            return Response(status=status.HTTP_403_FORBIDDEN)
+            return Response(
+                status=status.HTTP_400_BAD_REQUEST,
+                data={
+                    "code": "missing_invite_token",
+                    "message": "An invite token is required",
+                },
+            )
         try:
             invite_link = InvitationLink.objects.get(uuid=invite_id)
         except (InvitationLink.DoesNotExist, exceptions.ValidationError):
-            return Response(status=status.HTTP_403_FORBIDDEN)
+            return Response(
+                status=status.HTTP_403_FORBIDDEN,
+                data={
+                    "code": "invalid_invite_token",
+                    "message": "Invite token is invalid",
+                },
+            )
         if invite_link.expire <= timezone.now():
-            return Response(status=status.HTTP_403_FORBIDDEN)
+            return Response(
+                status=status.HTTP_403_FORBIDDEN,
+                data={
+                    "code": "expired_invite_token",
+                    "message": "Invite token has expired",
+                },
+            )
         request.session["invite_id"] = invite_id
         return Response(status=status.HTTP_200_OK)
 
@@ -178,9 +196,15 @@ class InvitedGuestView(GenericAPIView):
         try:
             invite_link = InvitationLink.objects.get(uuid=invite_id)
         except (InvitationLink.DoesNotExist, exceptions.ValidationError):
-            return Response(status=status.HTTP_403_FORBIDDEN)
+            return Response(
+                status=status.HTTP_403_FORBIDDEN,
+                data={"code": "invalid_invite", "message": "Invalid invite"},
+            )
         if invite_link.expire <= timezone.now():
-            return Response(status=status.HTTP_403_FORBIDDEN)
+            return Response(
+                status=status.HTTP_403_FORBIDDEN,
+                data={"code": "invite_expired", "message": "Invite expired"},
+            )
 
         # if invite_id:
         invite_link = InvitationLink.objects.get(uuid=invite_id)
@@ -217,7 +241,7 @@ class InvitedGuestView(GenericAPIView):
                 "role_name_en": role.type.name_en,
                 "start": role.start_date,
                 "end": role.end_date,
-                "comments": role.comments,
+                "contact_person_unit": role.contact_person_unit,
             },
             "meta": {"session_type": session_type},
         }
@@ -257,12 +281,21 @@ class InvitedGuestView(GenericAPIView):
         if illegal_fields:
             return Response(
                 status=status.HTTP_400_BAD_REQUEST,
-                data={"error": {"cannot_update_fields": illegal_fields}},
+                data={
+                    "code": "cannot_update_fields",
+                    "message": f"cannot_update_fields: {illegal_fields}",
+                },
             )
 
         if self._verified_fnr_already_exists(person) and fnr:
             # The user should not be allowed to change a verified fnr
-            return Response(status=status.HTTP_400_BAD_REQUEST)
+            return Response(
+                status=status.HTTP_400_BAD_REQUEST,
+                data={
+                    "code": "update_national_id_not_allowed",
+                    "message": "Not allowed to update verified national ID",
+                },
+            )
 
         with transaction.atomic():
             # Note this only serializes data for the person, it does not look at other sections
diff --git a/gregui/api/views/person.py b/gregui/api/views/person.py
index fec45bb3b1467dccb7dd6dcae4b5e01705a9c2da..b7173c76af9e087d5b31047b6f7df285148dfedc 100644
--- a/gregui/api/views/person.py
+++ b/gregui/api/views/person.py
@@ -57,7 +57,10 @@ class PersonSearchViewSet(mixins.ListModelMixin, GenericViewSet):
 
 class GuestInfoViewSet(mixins.ListModelMixin, GenericViewSet):
     """
-    Fetch all the sponsor's guests.
+    Fetch all guests at the sponsor's units.
+
+    If the Sponsor's connection to the Unit is marked with hierarchical access, guests
+    at child units of the Unit are included.
 
     Lists all persons connected to the roles the logged in sponsor is connected to.
     """
@@ -67,5 +70,17 @@ class GuestInfoViewSet(mixins.ListModelMixin, GenericViewSet):
     serializer_class = SpecialPersonSerializer
 
     def get_queryset(self):
+        """
+        Fetch Persons connected to the sponsor some way.
+
+        Any person with a role connected to the same unit as the sponsor, or a unit
+        that the sponsor is connected to through hierarchical access.
+        """
+
         user = GregUserProfile.objects.get(user=self.request.user)
-        return Person.objects.filter(roles__sponsor=user.sponsor).distinct()
+        units = user.sponsor.get_allowed_units()
+        return (
+            Person.objects.filter(roles__orgunit__in=list(units))
+            .distinct()
+            .order_by("id")
+        )
diff --git a/gregui/api/views/userinfo.py b/gregui/api/views/userinfo.py
index bba967c519e0c611d7018bd651a47c35bd984d96..f376fb0ad360d08257e4c52472ce80f06b19bd2d 100644
--- a/gregui/api/views/userinfo.py
+++ b/gregui/api/views/userinfo.py
@@ -21,8 +21,8 @@ class UserInfoView(APIView):
     Quick draft, we might want to expand this later.
     """
 
-    authentication_classes: Sequence[Type[BaseAuthentication]] = [SessionAuthentication]
-    permission_classes: Sequence[Type[BasePermission]] = [AllowAny]
+    authentication_classes = [SessionAuthentication]
+    permission_classes = [AllowAny]
 
     def get(self, request, format=None):
         """
@@ -32,6 +32,7 @@ class UserInfoView(APIView):
         invitation id. Pure django users, and anonymous users are denied access.
         """
         user = request.user
+
         invite_id = request.session.get("invite_id")
 
         auth_type = "invite"
@@ -40,6 +41,8 @@ class UserInfoView(APIView):
 
         person = None
         sponsor = None
+        user_profile = None
+
         content = {
             "feide_id": None,
             "sponsor_id": None,
@@ -48,34 +51,57 @@ class UserInfoView(APIView):
             "auth_type": auth_type,
         }
 
-        # Fetch sponsor and/or person object from profile of authenticated user
         if user.is_authenticated:
             try:
-                user_profile = GregUserProfile.objects.get(user=user)
-                sponsor = user_profile.sponsor
-                person = user_profile.person
-                content.update(
-                    {
-                        "feide_id": user_profile.userid_feide,
-                    }
-                )
+                user_profile = GregUserProfile.objects.select_related(
+                    "person", "sponsor"
+                ).get(user=user)
             except GregUserProfile.DoesNotExist:
-                return Response(status=HTTP_403_FORBIDDEN)
+                return Response(
+                    status=HTTP_403_FORBIDDEN,
+                    data={
+                        "code": "no_user_profile",
+                        "message": "Authenticated, but missing user profile",
+                    },
+                )
 
+        # Fetch sponsor and/or person object from profile of authenticated user
+        if user_profile:
+            sponsor = user_profile.sponsor
+            person = user_profile.person
+            content["feide_id"] = user_profile.userid_feide
         # Or fetch person info for invited guest
         elif invite_id:
-            link = InvitationLink.objects.get(uuid=invite_id)
+            link = InvitationLink.objects.select_related(
+                "invitation__role__person",
+            ).get(uuid=invite_id)
             person = link.invitation.role.person
-
         # Otherwise, deny access
         else:
             return Response(status=HTTP_403_FORBIDDEN)
 
-        # Add sponsor fields if sponsor object present
         if sponsor:
-            content.update({"sponsor_id": user_profile.sponsor.id})
-        # Add person fields if person object present
+            content["sponsor_id"] = sponsor.id
+
         if person:
+            person_roles = [
+                {
+                    "id": role.id,
+                    "ou_nb": role.orgunit.name_nb,
+                    "ou_en": role.orgunit.name_en,
+                    "name_nb": role.type.name_nb,
+                    "name_en": role.type.name_en,
+                    "start_date": role.start_date,
+                    "end_date": role.end_date,
+                    "sponsor": {
+                        "first_name": role.sponsor.first_name,
+                        "last_name": role.sponsor.last_name,
+                    },
+                }
+                for role in person.roles.all().select_related(
+                    "orgunit", "type", "sponsor"
+                )
+            ]
             content.update(
                 {
                     "person_id": person.id,
@@ -86,22 +112,7 @@ class UserInfoView(APIView):
                     and person.private_mobile.value,
                     "fnr": person.fnr and "".join((person.fnr.value[:-5], "*****")),
                     "passport": person.passport and person.passport.value,
-                    "roles": [
-                        {
-                            "id": role.id,
-                            "ou_nb": role.orgunit.name_nb,
-                            "ou_en": role.orgunit.name_en,
-                            "name_nb": role.type.name_nb,
-                            "name_en": role.type.name_en,
-                            "start_date": role.start_date,
-                            "end_date": role.end_date,
-                            "sponsor": {
-                                "first_name": role.sponsor.first_name,
-                                "last_name": role.sponsor.last_name,
-                            },
-                        }
-                        for role in person.roles.all()
-                    ],
+                    "roles": person_roles,
                 }
             )
         return Response(content)
diff --git a/gregui/tests/api/serializers/test_role.py b/gregui/tests/api/serializers/test_role.py
index b36c2b7c4e152b7a82851a2612cd2806f0e276ba..1a43a85692d0fe0ff0db8afad96625c004dc6173 100644
--- a/gregui/tests/api/serializers/test_role.py
+++ b/gregui/tests/api/serializers/test_role.py
@@ -25,8 +25,8 @@ def test_minimum_ok(role, sponsor_foo):
 
 
 @pytest.mark.django_db
-def test_start_date_past_fail(role, sponsor_foo):
-    """Should fail because of start_date in the past"""
+def test_start_date_past_ok(role, sponsor_foo):
+    """Should work even though start_date in the past"""
     ser = RoleSerializerUi(
         data={
             "person": role.person.id,
@@ -37,41 +37,29 @@ def test_start_date_past_fail(role, sponsor_foo):
         },
         context={"sponsor": sponsor_foo},
     )
-    with pytest.raises(
-        ValidationError,
-        match=re.escape(
-            "{'start_date': [ErrorDetail(string='Start date cannot be in the past', code='invalid')]}"
-        ),
-    ):
-        ser.is_valid(raise_exception=True)
+    assert ser.is_valid(raise_exception=True)
 
 
 @pytest.mark.django_db
-def test_end_date_past_fail(role, sponsor_foo):
-    """Should fail because of end_date in the past"""
+def test_end_date_past_ok(role, sponsor_foo):
+    """Should work even though end_date in the past"""
     ser = RoleSerializerUi(
         data={
             "person": role.person.id,
             "orgunit": role.orgunit.id,
             "type": role.type.id,
-            "start_date": datetime.date.today(),
+            "start_date": (timezone.now() - datetime.timedelta(days=12)).date(),
             "end_date": (timezone.now() - datetime.timedelta(days=10)).date(),
         },
         context={"sponsor": sponsor_foo},
     )
-    with pytest.raises(
-        ValidationError,
-        match=re.escape(
-            "{'end_date': [ErrorDetail(string='End date cannot be in the past', code='invalid')]}"
-        ),
-    ):
-        ser.is_valid(raise_exception=True)
+    assert ser.is_valid(raise_exception=True)
 
 
 @pytest.mark.django_db
-def test_end_date_expired_role_fail(role, sponsor_foo):
-    """New end date fail because role has ended"""
-    # Expire the role to ensure failure
+def test_end_date_expired_role_ok(role, sponsor_foo):
+    """Editing an expired role is allowed"""
+    # Expire the role
     role.end_date = datetime.date.today() - datetime.timedelta(days=10)
     role.save()
     # Try to change it
@@ -86,20 +74,13 @@ def test_end_date_expired_role_fail(role, sponsor_foo):
         },
         context={"sponsor": sponsor_foo},
     )
-    # Verify that a validation error is raised
-    with pytest.raises(
-        ValidationError,
-        match=re.escape(
-            "{'end_date': [ErrorDetail(string='Role has ended, cannot change end date', code='invalid')]}"
-        ),
-    ):
-        ser.is_valid(raise_exception=True)
+    assert ser.is_valid(raise_exception=True)
 
 
 @pytest.mark.django_db
 def test_wrong_sponsor(role, sponsor_foo, sponsor_bar):
-    """Touching another sponsor's roles does not work"""
-    # Try to touch sponsor_foo's guest role as sponsor_bar
+    """Touching roles connected to another unit does not work"""
+    # Try to touch a unit at a unit we are not sponsor for
     ser = RoleSerializerUi(
         instance=role,
         data={
@@ -115,7 +96,7 @@ def test_wrong_sponsor(role, sponsor_foo, sponsor_bar):
     with pytest.raises(
         ValidationError,
         match=re.escape(
-            "{'non_field_errors': [ErrorDetail(string='You can only edit your own roles.', code='invalid')]}"
+            "{'orgunit': [ErrorDetail(string='You can only edit roles connected to your units.', code='invalid')]}"
         ),
     ):
         ser.is_valid(raise_exception=True)
diff --git a/gregui/tests/api/views/test_invitation.py b/gregui/tests/api/views/test_invitation.py
index 8a10b3727b196398e143cf0f25ad6e4165afe818..3fe44484be40671ca3ea00f56dcc4b65eff84c37 100644
--- a/gregui/tests/api/views/test_invitation.py
+++ b/gregui/tests/api/views/test_invitation.py
@@ -12,15 +12,21 @@ from greg.models import Consent, InvitationLink, Person, Identity
 @pytest.mark.django_db
 def test_post_invite(client):
     """Forbid access with bad invitation link uuid"""
-    response = client.post(reverse("gregui-v1:invite-verify"), data={"uuid": "baduuid"})
+    response = client.post(
+        reverse("gregui-v1:invite-verify"), data={"invite_token": "baduuid"}
+    )
     assert response.status_code == status.HTTP_403_FORBIDDEN
+    assert response.json() == {
+        "code": "invalid_invite_token",
+        "message": "Invite token is invalid",
+    }
 
 
 @pytest.mark.django_db
 def test_post_invite_ok(client, invitation_link):
     """Access okay with valid invitation link"""
     response = client.post(
-        reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid}
+        reverse("gregui-v1:invite-verify"), data={"invite_token": invitation_link.uuid}
     )
     assert response.status_code == status.HTTP_200_OK
 
@@ -29,23 +35,34 @@ def test_post_invite_ok(client, invitation_link):
 def test_post_invite_expired(client, invitation_link_expired):
     """Forbid access with expired invite link"""
     response = client.post(
-        reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link_expired.uuid}
+        reverse("gregui-v1:invite-verify"),
+        data={"invite_token": invitation_link_expired.uuid},
     )
     assert response.status_code == status.HTTP_403_FORBIDDEN
+    assert response.json() == {
+        "code": "expired_invite_token",
+        "message": "Invite token has expired",
+    }
 
 
 @pytest.mark.django_db
 def test_post_missing_invite_id(client):
     """Forbid access if no id provided."""
     response = client.post(reverse("gregui-v1:invite-verify"))
-    assert response.status_code == status.HTTP_403_FORBIDDEN
+    assert response.status_code == status.HTTP_400_BAD_REQUEST
+    assert response.json() == {
+        "code": "missing_invite_token",
+        "message": "An invite token is required",
+    }
 
 
 @pytest.mark.django_db
 def test_get_invited_info_no_session(client, invitation_link):
     """Forbid access if invite expired after session was created."""
     # get a session
-    client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
+    client.post(
+        reverse("gregui-v1:invite-verify"), data={"invite_token": invitation_link.uuid}
+    )
     # expire the invitation link
     invitation_link.expire = timezone.now() - datetime.timedelta(days=1)
     invitation_link.save()
@@ -60,7 +77,9 @@ def test_get_invited_info_session_okay(
 ):
     person, invitation_link = invited_person
     # get a session
-    client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
+    client.post(
+        reverse("gregui-v1:invite-verify"), data={"invite_token": invitation_link.uuid}
+    )
     # Get info
     response = client.get(reverse("gregui-v1:invited-info"))
     assert response.status_code == status.HTTP_200_OK
@@ -82,7 +101,7 @@ def test_get_invited_info_session_okay(
     assert data.get("role") == dict(
         start=None,
         end="2050-10-15",
-        comments="",
+        contact_person_unit="",
         ou_name_en=unit_foo.name_en,
         ou_name_nb=unit_foo.name_nb,
         role_name_en=role_type_foo.name_en,
@@ -95,7 +114,9 @@ def test_get_invited_info_expired_link(
     client, invitation_link, invitation_expired_date
 ):
     # Get a session while link is valid
-    client.get(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
+    client.get(
+        reverse("gregui-v1:invite-verify"), data={"invite_token": invitation_link.uuid}
+    )
     # Set expire link to expire long ago
     invlink = InvitationLink.objects.get(uuid=invitation_link.uuid)
     invlink.expire = invitation_expired_date
@@ -110,7 +131,9 @@ def test_get_invited_info_expired_link(
 def test_invited_guest_can_post_information(client: APIClient, invited_person):
     person, invitation_link = invited_person
     # get a session
-    client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
+    client.post(
+        reverse("gregui-v1:invite-verify"), data={"invite_token": invitation_link.uuid}
+    )
 
     person = invitation_link.invitation.role.person
     assert person.private_mobile is None
@@ -138,7 +161,9 @@ def test_post_invited_info_expired_session(
     client, invitation_link, invitation_expired_date
 ):
     # get a session
-    client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
+    client.post(
+        reverse("gregui-v1:invite-verify"), data={"invite_token": invitation_link.uuid}
+    )
     # Set expire link to expire long ago
     invlink = InvitationLink.objects.get(uuid=invitation_link.uuid)
     invlink.expire = invitation_expired_date
@@ -152,7 +177,9 @@ def test_post_invited_info_expired_session(
 @pytest.mark.django_db
 def test_post_invited_info_deleted_inv_link(client, invitation_link):
     # get a session
-    client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
+    client.post(
+        reverse("gregui-v1:invite-verify"), data={"invite_token": invitation_link.uuid}
+    )
     # Delete link
     invlink = InvitationLink.objects.get(uuid=invitation_link.uuid)
     invlink.delete()
@@ -284,7 +311,9 @@ def test_name_update_not_allowed_if_feide_identity_is_present(
     )
     invitation = create_invitation(role)
     invitation_link = create_invitation_link(invitation, invitation_valid_date)
-    client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
+    client.post(
+        reverse("gregui-v1:invite-verify"), data={"invite_token": invitation_link.uuid}
+    )
 
     person = invitation_link.invitation.role.person
     data = {
@@ -330,7 +359,9 @@ def test_name_update_allowed_if_feide_identity_is_not_present(
     invitation_link = create_invitation_link(
         create_invitation(role), invitation_valid_date
     )
-    client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
+    client.post(
+        reverse("gregui-v1:invite-verify"), data={"invite_token": invitation_link.uuid}
+    )
 
     person = invitation_link.invitation.role.person
     data = {
diff --git a/gregui/tests/api/views/test_role.py b/gregui/tests/api/views/test_role.py
index 3b740f9ec366e315b1dbc661a0d73e80a3119758..26916673c704f1bf259848428f84954c48241d01 100644
--- a/gregui/tests/api/views/test_role.py
+++ b/gregui/tests/api/views/test_role.py
@@ -70,7 +70,7 @@ def test_role_sponsor_post_fail(client, log_in, user_sponsor, role):
     )
     assert (
         resp.content
-        == b'{"orgunit":["A sponsor can only make changes to roles at units they are sponsors for."]}'
+        == b'{"orgunit":["You can only edit roles connected to your units."]}'
     )
     assert resp.status_code == status.HTTP_400_BAD_REQUEST
 
@@ -99,24 +99,25 @@ def test_role_sponsor_patch_ok(
 
 
 @pytest.mark.django_db
-def test_role_sponsor_patch_fail(client, log_in, user_sponsor, role):
+def test_role_sponsor_patch_fail(client, log_in, user_sponsor_bar, role):
     """Should fail since we are not a sponsor at this unit."""
 
     # Unit the sponsor is not connected to so that we fail
-    ou = OrganizationalUnit.objects.create(name_nb="foo", name_en="foo_en")
+    OrganizationalUnit.objects.create(name_nb="foo", name_en="foo_en")
 
-    log_in(user_sponsor)
+    log_in(user_sponsor_bar)
+    new_date = (timezone.now() + datetime.timedelta(days=10)).date()
     resp = client.patch(
         reverse("gregui-v1:role-detail", kwargs={"pk": role.id}),
         data={
-            "orgunit": ou.id,
+            "end_date": new_date.strftime("%Y-%m-%d"),
         },
     )
+    assert resp.status_code == status.HTTP_400_BAD_REQUEST
     assert (
         resp.content
-        == b'{"orgunit":["A sponsor can only make changes to roles at units they are sponsors for."]}'
+        == b'{"non_field_errors":["You can only edit roles connected to your units."]}'
     )
-    assert resp.status_code == status.HTTP_400_BAD_REQUEST
 
 
 @pytest.mark.django_db
diff --git a/gregui/tests/api/views/test_userinfo.py b/gregui/tests/api/views/test_userinfo.py
index cf1f331d48c6ff3ca1fe2d2ec47f4db756ac7c68..df7d3cb44888f333ac0939a4509c0ec88583fb0d 100644
--- a/gregui/tests/api/views/test_userinfo.py
+++ b/gregui/tests/api/views/test_userinfo.py
@@ -6,7 +6,7 @@ from rest_framework import status
 @pytest.mark.django_db
 def test_userinfo_anon_get(client):
     """Anonymous people should be forbidden."""
-    response = client.get(reverse("api-userinfo"))
+    response = client.get(reverse("gregui-v1:userinfo"))
     assert response.status_code == status.HTTP_403_FORBIDDEN
 
 
@@ -16,7 +16,7 @@ def test_userinfo_invited_get(client, invitation_link):
     session = client.session
     session["invite_id"] = str(invitation_link.uuid)
     session.save()
-    response = client.get(reverse("api-userinfo"))
+    response = client.get(reverse("gregui-v1:userinfo"))
     assert response.status_code == status.HTTP_200_OK
     assert response.json() == {
         "auth_type": "invite",
@@ -48,7 +48,7 @@ def test_userinfo_invited_get(client, invitation_link):
 def test_userinfo_user_no_profile(client, log_in, user_no_profile):
     """Pure django users should be forbidden"""
     log_in(user_no_profile)
-    response = client.get(reverse("api-userinfo"))
+    response = client.get(reverse("gregui-v1:userinfo"))
     assert response.status_code == status.HTTP_403_FORBIDDEN
 
 
@@ -57,7 +57,7 @@ def test_userinfo_sponsor_get(client, log_in, user_sponsor):
     """Sponsors should get info about themselves"""
     log_in(user_sponsor)
 
-    response = client.get(reverse("api-userinfo"))
+    response = client.get(reverse("gregui-v1:userinfo"))
     assert response.status_code == status.HTTP_200_OK
     assert response.json() == {
         "auth_type": "oidc",
@@ -72,7 +72,7 @@ def test_userinfo_sponsor_get(client, log_in, user_sponsor):
 def test_userinfo_guest_get(client, log_in, user_person):
     """Logged in guests should get info about themself"""
     log_in(user_person)
-    response = client.get(reverse("api-userinfo"))
+    response = client.get(reverse("gregui-v1:userinfo"))
     assert response.status_code == status.HTTP_200_OK
     assert response.json() == {
         "auth_type": "oidc",
diff --git a/gregui/tests/conftest.py b/gregui/tests/conftest.py
index 8386752d53ad86292066851b8324c80d8b2e4a30..5fd153b0242d8c213d06b5c91e1a01596ab81c73 100644
--- a/gregui/tests/conftest.py
+++ b/gregui/tests/conftest.py
@@ -74,6 +74,12 @@ def unit_foo() -> OrganizationalUnit:
     return OrganizationalUnit.objects.get(id=ou.id)
 
 
+@pytest.fixture
+def unit_bar() -> OrganizationalUnit:
+    ou = OrganizationalUnit.objects.create(name_en="Bar EN", name_nb="Bar NB")
+    return OrganizationalUnit.objects.get(id=ou.id)
+
+
 @pytest.fixture
 def role_type_foo() -> RoleType:
     rt = RoleType.objects.create(
@@ -118,9 +124,9 @@ def sponsor_foo(
 
 
 @pytest.fixture
-def sponsor_bar(unit_foo: OrganizationalUnit, create_sponsor) -> Sponsor:
+def sponsor_bar(unit_bar: OrganizationalUnit, create_sponsor) -> Sponsor:
     return create_sponsor(
-        feide_id="bar@example.com", first_name="Bar", last_name="Baz", unit=unit_foo
+        feide_id="bar@example.com", first_name="Bar", last_name="Baz", unit=unit_bar
     )
 
 
@@ -161,7 +167,7 @@ def user_sponsor(sponsor_foo: Sponsor, create_user) -> User:
 
     # Create a user and link him to a sponsor
     user = create_user(
-        username="test_sponsor",
+        username="test_sponsor_foo",
         email="test@example.org",
         first_name="Test",
         last_name="Sponsor",
@@ -172,6 +178,23 @@ def user_sponsor(sponsor_foo: Sponsor, create_user) -> User:
     return user_model.objects.get(id=user.id)
 
 
+@pytest.fixture
+def user_sponsor_bar(sponsor_bar: Sponsor, create_user) -> User:
+    user_model = get_user_model()
+
+    # Create a user and link him to a sponsor
+    user = create_user(
+        username="test_sponsor_bar",
+        email="test2@example.org",
+        first_name="Sponsor",
+        last_name="Bar",
+    )
+    GregUserProfile.objects.create(user=user, sponsor=sponsor_bar)
+
+    # This user is a sponsor for unit_foo
+    return user_model.objects.get(id=user.id)
+
+
 @pytest.fixture
 def user_person(person_foo: Sponsor, create_user) -> User:
     user_model = get_user_model()
diff --git a/gregui/urls.py b/gregui/urls.py
index 4292a73e4146722848d2b9f7d476f037bd99e87a..a8ff0fbd7bbb2b6dcd7449a5a48e36285ab51308 100644
--- a/gregui/urls.py
+++ b/gregui/urls.py
@@ -5,18 +5,9 @@ from django.urls import path
 from django.urls.resolvers import URLResolver
 
 from gregui.api import urls as api_urls
-from gregui.api.views.userinfo import UserInfoView
-from . import views
 
 urlpatterns: List[URLResolver] = [
     path(
         "api/ui/v1/", include((api_urls.urlpatterns, "gregui"), namespace="gregui-v1")
     ),
-    path("api/ui/v1/csrf/", views.get_csrf, name="api-csrf"),
-    path("api/ui/v1/logout/", views.logout_view, name="api-logout"),
-    path("api/ui/v1/login/", views.login_view, name="api-login"),
-    path("api/ui/v1/session/", views.SessionView.as_view(), name="api-session"),
-    path("api/ui/v1/testmail/", views.send_test_email, name="api-testmail"),
-    path("api/ui/v1/whoami/", views.WhoAmIView.as_view(), name="api-whoami"),
-    path("api/ui/v1/userinfo/", UserInfoView.as_view(), name="api-userinfo"),  # type: ignore
 ]
diff --git a/gregui/views.py b/gregui/views.py
deleted file mode 100644
index a60d3b760e25993ea4005b78fad4d6850fc472f9..0000000000000000000000000000000000000000
--- a/gregui/views.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from django.contrib.auth import logout
-from django.http import JsonResponse
-from django.middleware.csrf import get_token
-from django.shortcuts import redirect
-from rest_framework.authentication import SessionAuthentication, BasicAuthentication
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.views import APIView
-
-from gregui import mailutils
-
-
-def get_csrf(request):
-    response = JsonResponse({"detail": "CSRF cookie set"})
-    response["X-CSRFToken"] = get_token(request)
-    return response
-
-
-def logout_view(request):
-    if not request.user.is_authenticated:
-        return JsonResponse({"detail": "You're not logged in."}, status=400)
-
-    logout(request)
-    return JsonResponse({"detail": "Successfully logged out."})
-
-
-def login_view(request):
-    """
-    View for pointing login links to
-
-    Sesame will take the query string automatically and use it to create a session for
-    the user, so all this needs to do is redirect the user wherever they're supposed to
-    go after successfully logging in.
-    """
-    # TODO: redirect to whatever path the frontend ends up living at (prob '/')
-    return redirect("/api/ui/v1/whoami/")
-
-
-def send_test_email(request):
-    mailutils.send_registration_mail("test@example.no", "Foo Bar")
-    return JsonResponse({"detail": "Created task to send test mail."})
-
-
-class SessionView(APIView):
-    authentication_classes = [SessionAuthentication, BasicAuthentication]
-    permission_classes = [IsAuthenticated]
-
-    @staticmethod
-    # pylint: disable=W0622
-    def get(request, format=None):
-        return JsonResponse({"isAuthenticated": True})
-
-
-class WhoAmIView(APIView):
-    authentication_classes = [SessionAuthentication, BasicAuthentication]
-    permission_classes = [IsAuthenticated]
-
-    @staticmethod
-    # pylint: disable=W0622
-    def get(request, format=None):
-        return JsonResponse({"username": request.user.username})