From d5984d30e9ff31b07f5665f1fd0606da22584da5 Mon Sep 17 00:00:00 2001 From: Andreas Ellewsen <andreas@ellewsen.no> Date: Tue, 24 May 2022 16:10:09 +0200 Subject: [PATCH] Show invalid email status for invites in frontend Introduces the invalid field on the Identity model. The field is used to indicate that the Identity is in invalid. This is useful when a value is not a working value, even though the value passes validation checks, like unused national id numbers, emails tied to non-existing domains, etc. Resolves: GREG-252 --- frontend/public/locales/en/common.json | 3 ++- frontend/public/locales/nb/common.json | 3 ++- frontend/public/locales/nn/common.json | 3 ++- .../src/routes/sponsor/frontpage/index.tsx | 19 ++++++++++++++++--- .../routes/sponsor/guest/guestInfo/index.tsx | 3 +++ greg/migrations/0023_identity_invalid.py | 18 ++++++++++++++++++ greg/models.py | 16 ++++++++++++++++ gregui/api/serializers/guest.py | 4 ++++ 8 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 greg/migrations/0023_identity_invalid.py diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index 1f686858..50e97ac8 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -82,7 +82,7 @@ "name": "Name", "role": "Guest role", "period": "Period", - "host": "Host", + "host": "Host", "ou": "Organisation", "department": "Department", "choice": "Choices", @@ -103,6 +103,7 @@ "expiring_other": "Expiring in {{count}} days", "expiring_one": "Expiring in {{count}} day", "waitingForGuest": "Waiting for guest", + "invalidEmail": "Invalid e-mail address", "waitingForSponsor": "Needs confirmation", "invitationExpired": "Invitation expired" }, diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json index d145058f..a6849c3e 100644 --- a/frontend/public/locales/nb/common.json +++ b/frontend/public/locales/nb/common.json @@ -82,7 +82,7 @@ "name": "Navn", "role": "Gjesterolle", "period": "Periode", - "host": "Vert", + "host": "Vert", "ou": "Organisasjon", "department": "Avdeling", "choice": "Valg", @@ -103,6 +103,7 @@ "expiring_other": "Utløper om {{count}} dager", "expiring_one": "Utløper om {{count}} dag", "waitingForGuest": "Venter på gjest", + "invalidEmail": "Ugyldig e-postadresse", "waitingForSponsor": "Trenger godkjenning", "invitationExpired": "Invitasjon utløpt" }, diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json index e39c0bd0..aa5b64c7 100644 --- a/frontend/public/locales/nn/common.json +++ b/frontend/public/locales/nn/common.json @@ -82,7 +82,7 @@ "name": "Namn", "role": "Gjesterolle", "period": "Periode", - "host": "Vert", + "host": "Vert", "ou": "Organisasjonsenhet", "department": "Enhet", "choice": "Val", @@ -103,6 +103,7 @@ "expiring_other": "Utløper om {{count}} dagar", "expiring_one": "Utløper om {{count}} dag", "waitingForGuest": "Venter på gjest", + "invalidEmail": "Ugyldig e-postadresse", "waitingForSponsor": "Trenger godkjenning", "invitationExpired": "Invitasjon utløpt" }, diff --git a/frontend/src/routes/sponsor/frontpage/index.tsx b/frontend/src/routes/sponsor/frontpage/index.tsx index d489fc49..c992c5a8 100644 --- a/frontend/src/routes/sponsor/frontpage/index.tsx +++ b/frontend/src/routes/sponsor/frontpage/index.tsx @@ -82,16 +82,17 @@ const StyledTableHead = styled(TableHead)(({ theme }) => ({ borderRadius: '0', })) -const Status = ({ person, role }: StatusProps) => { +const calculateStatus = (person: Guest, role: Role): [string, number] => { const today = new Date() today.setHours(0, 0, 0, 0) - const { t } = useTranslation('common') let status = '' const days = differenceInDays(role.end_date, today) if (!person.registered) { status = 'waitingForGuest' - if (person.invitation_status !== 'active') { + if (person.invitation_status === 'invalidEmail') { + status = 'invalidEmail' + } else if (person.invitation_status !== 'active') { status = 'invitationExpired' } } else if (person.registered && !person.verified) { @@ -105,6 +106,12 @@ const Status = ({ person, role }: StatusProps) => { status = 'active' } } + return [status, days] +} + +const Status = ({ person, role }: StatusProps) => { + const { t } = useTranslation('common') + const [status, days] = calculateStatus(person, role) switch (status) { case 'active': @@ -143,6 +150,12 @@ const Status = ({ person, role }: StatusProps) => { <Trans t={t} i18nKey="statusText.expiring" count={days} /> </TableCell> ) + case 'invalidEmail': + return ( + <TableCell sx={{ color: 'error.main' }} align="left"> + <Trans t={t} i18nKey="statusText.invalidEmail" count={days} /> + </TableCell> + ) default: return ( <TableCell sx={{ color: 'error.main' }} align="left"> diff --git a/frontend/src/routes/sponsor/guest/guestInfo/index.tsx b/frontend/src/routes/sponsor/guest/guestInfo/index.tsx index 43f663e1..1a6fb58d 100644 --- a/frontend/src/routes/sponsor/guest/guestInfo/index.tsx +++ b/frontend/src/routes/sponsor/guest/guestInfo/index.tsx @@ -172,6 +172,9 @@ export default function GuestInfo({ }) } }) + // Reload guests to update status on sponsor frontpage + // in case the email was invalid before update + reloadGuests() } const onSubmit = handleSubmit(submit) diff --git a/greg/migrations/0023_identity_invalid.py b/greg/migrations/0023_identity_invalid.py new file mode 100644 index 00000000..0ffd9e9c --- /dev/null +++ b/greg/migrations/0023_identity_invalid.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.2 on 2022-05-24 08:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('greg', '0022_alter_identity_type'), + ] + + operations = [ + migrations.AddField( + model_name='identity', + name='invalid', + field=models.BooleanField(null=True), + ), + ] diff --git a/greg/models.py b/greg/models.py index 85f208a0..4055837f 100644 --- a/greg/models.py +++ b/greg/models.py @@ -264,6 +264,21 @@ class Notification(BaseModel): class Identity(BaseModel): + """ + Model used for storing identifiers of a person + + A person must have at least one verified PASSPORT_NUMBER or + NORWEGIAN_NATIONAL_ID_NUMBER to be active. + + If for some reason we find that a value is invalid, even if it has + been verified, it can be marked as invalid by setting the invalid + field to True. + + MIGRATION_ID is used to identify persons imported from another + source system. + PRIVATE_EMAIL is used when sending invitation emails. + """ + class IdentityType(models.TextChoices): FEIDE_ID = "feide_id" FEIDE_EMAIL = "feide_email" @@ -294,6 +309,7 @@ class Identity(BaseModel): blank=True, ) verified_at = models.DateTimeField(null=True, blank=True) + invalid = models.BooleanField(null=True) def __str__(self) -> str: return "{}(id={!r}, type={!r}, value={!r})".format( diff --git a/gregui/api/serializers/guest.py b/gregui/api/serializers/guest.py index 4161b2ff..b7474ead 100644 --- a/gregui/api/serializers/guest.py +++ b/gregui/api/serializers/guest.py @@ -40,6 +40,7 @@ def create_identity_or_update( ) else: existing_identity.value = value + existing_identity.invalid = None existing_identity.save() @@ -257,6 +258,9 @@ class GuestSerializer(serializers.ModelSerializer): return obj.feide_id and obj.feide_id.value def get_invitation_status(self, obj): + if obj.private_email and obj.private_email.invalid: + return "invalidEmail" + invitation_links = InvitationLink.objects.filter( invitation__role__person__id=obj.id ) -- GitLab