From d250287836280c27bd13d16d457b75bf040b0c5e Mon Sep 17 00:00:00 2001 From: Snorre Skjellestad Kristiansen Date: Thu, 22 Aug 2024 21:32:55 +0200 Subject: [PATCH 01/15] add promo video to form --- .../RecruitmentFormAdminPage.tsx | 18 +++++++++++++++++- frontend/src/dto.ts | 1 + frontend/src/i18n/constants.ts | 1 + frontend/src/i18n/translations.ts | 2 ++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx b/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx index 81071f4c9..511d30f77 100644 --- a/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { useNavigate, useParams, useRouteLoaderData } from 'react-router-dom'; import { toast } from 'react-toastify'; import { DropDownOption } from '~/Components/Dropdown/Dropdown'; -import { SamfForm } from '~/Forms/SamfForm'; +import { SamfError, SamfForm } from '~/Forms/SamfForm'; import { SamfFormField } from '~/Forms/SamfFormField'; import { getOrganizations, postRecruitment, putRecruitment } from '~/api'; import { OrganizationDto, RecruitmentDto } from '~/dto'; @@ -24,8 +24,21 @@ type FormType = { reprioritization_deadline_for_applicant: string; reprioritization_deadline_for_groups: string; organization: number; + promo_video: string; }; +function youtubeLinkValidator(state: FormType): SamfError { + const link = state.promo_video; + var regex = /(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))/ + if (link && !link.match(regex)) { + return "Not valid youtbue link" + } + + return true; +} + + + export function RecruitmentFormAdminPage() { const { t } = useTranslation(); const navigate = useNavigate(); @@ -161,6 +174,9 @@ export function RecruitmentFormAdminPage() { required={true} /> +
+ +
diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 6a463d666..72934f121 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -383,6 +383,7 @@ export type RecruitmentDto = { max_applications?: number; organization: number; seperate_positions?: RecruitmentSeperatePositionDto[]; + promo_video?: string; }; export type RecruitmentSeperatePositionDto = { diff --git a/frontend/src/i18n/constants.ts b/frontend/src/i18n/constants.ts index d1b3f3d5f..ca9678935 100644 --- a/frontend/src/i18n/constants.ts +++ b/frontend/src/i18n/constants.ts @@ -291,6 +291,7 @@ export const KEY = { recrutment_default_application_letter: 'recrutment_default_application_letter', reprioritization_deadline_for_groups: 'reprioritization_deadline_for_groups', max_applications: 'max_applications', + promo_media: 'promo_video', recruitment_norwegian_applicants_only: 'recruitment_norwegian_applicants_only', reprioritization_deadline_for_applicant: 'reprioritization_deadline_for_applicant', recruitment_show_unprocessed_applicants: 'recruitment_show_unprocessed_applicants', diff --git a/frontend/src/i18n/translations.ts b/frontend/src/i18n/translations.ts index 96bf3054d..3f712227d 100644 --- a/frontend/src/i18n/translations.ts +++ b/frontend/src/i18n/translations.ts @@ -280,6 +280,7 @@ export const nb: Record = { [KEY.recrutment_default_application_letter]: 'Standard søknadstekst', [KEY.reprioritization_deadline_for_groups]: 'Flaggefrist', [KEY.max_applications]: 'Maks søknader per bruker', + [KEY.promo_media]: 'Promo video', [KEY.recruitment_norwegian_applicants_only]: 'Kun norsktalende søkere', [KEY.reprioritization_deadline_for_applicant]: 'Omprioriteringsfrist', [KEY.recruitment_show_unprocessed_applicants]: 'Vis ubehandlede søkere', @@ -688,6 +689,7 @@ export const en: Record = { [KEY.recrutment_default_application_letter]: 'Default application letter', [KEY.reprioritization_deadline_for_groups]: 'Group reprioritization deadline', [KEY.max_applications]: 'Max applications per user', + [KEY.promo_media]: "Promo Video", [KEY.reprioritization_deadline_for_applicant]: 'Reprioritization deadline', [KEY.recruitment_show_unprocessed_applicants]: 'Show unprocessed applicants', [KEY.recruitment_show_all_applicants]: 'Show all applicants', From b25220386e4148e2a5173c6048755d8de69f355a Mon Sep 17 00:00:00 2001 From: Snorre Skjellestad Kristiansen Date: Tue, 10 Sep 2024 20:54:47 +0200 Subject: [PATCH 02/15] add backend or something --- .../migrations/0003_recruitment_promo_media.py | 18 ++++++++++++++++++ .../0004_alter_recruitment_promo_media.py | 18 ++++++++++++++++++ backend/samfundet/models/recruitment.py | 9 +++++++++ backend/samfundet/serializers.py | 10 ++++++++++ .../RecruitmentFormAdminPage.tsx | 18 ++++++++++-------- frontend/src/dto.ts | 2 +- 6 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 backend/samfundet/migrations/0003_recruitment_promo_media.py create mode 100644 backend/samfundet/migrations/0004_alter_recruitment_promo_media.py diff --git a/backend/samfundet/migrations/0003_recruitment_promo_media.py b/backend/samfundet/migrations/0003_recruitment_promo_media.py new file mode 100644 index 000000000..dc830f935 --- /dev/null +++ b/backend/samfundet/migrations/0003_recruitment_promo_media.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-08-29 16:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('samfundet', '0002_alter_recruitmentapplication_recruiter_status'), + ] + + operations = [ + migrations.AddField( + model_name='recruitment', + name='promo_media', + field=models.CharField(help_text='Youtube video id', max_length=12, null=True), + ), + ] diff --git a/backend/samfundet/migrations/0004_alter_recruitment_promo_media.py b/backend/samfundet/migrations/0004_alter_recruitment_promo_media.py new file mode 100644 index 000000000..57deb6703 --- /dev/null +++ b/backend/samfundet/migrations/0004_alter_recruitment_promo_media.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-08-29 18:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('samfundet', '0003_recruitment_promo_media'), + ] + + operations = [ + migrations.AlterField( + model_name='recruitment', + name='promo_media', + field=models.CharField(default=None, help_text='Youtube video id', max_length=12, null=True), + ), + ] diff --git a/backend/samfundet/models/recruitment.py b/backend/samfundet/models/recruitment.py index 725b5fc3f..c7ed8e5bf 100644 --- a/backend/samfundet/models/recruitment.py +++ b/backend/samfundet/models/recruitment.py @@ -3,6 +3,7 @@ # from __future__ import annotations +import re import uuid from collections import defaultdict @@ -31,6 +32,7 @@ class Recruitment(CustomBaseModel): organization = models.ForeignKey(null=False, blank=False, to=Organization, on_delete=models.CASCADE, help_text='The organization that is recruiting') max_applications = models.PositiveIntegerField(null=True, blank=True, verbose_name='Max applications per applicant') + promo_media = models.CharField(max_length=11 ,help_text='Youtube video id', null=True, default=None) def is_active(self) -> bool: return self.visible_from < timezone.now() < self.actual_application_deadline @@ -92,6 +94,13 @@ def save(self, *args: tuple, **kwargs: dict) -> None: if not self.statistics: RecruitmentStatistics.objects.create(self) + def process_promo_media(self, promo_media: str| None) -> str | None: + if (promo_media is None): + return None + match = re.search(r'(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))', promo_media) + if (match): + return match.group(3) + raise ValidationError("Invalid youtube url") class RecruitmentPosition(CustomBaseModel): name_nb = models.CharField(max_length=100, help_text='Name of the position') diff --git a/backend/samfundet/serializers.py b/backend/samfundet/serializers.py index 886f48f19..e150cb9d6 100644 --- a/backend/samfundet/serializers.py +++ b/backend/samfundet/serializers.py @@ -1,5 +1,6 @@ from __future__ import annotations +import re import itertools from typing import TYPE_CHECKING from collections import defaultdict @@ -701,11 +702,20 @@ class Meta: class RecruitmentSerializer(CustomBaseSerializer): seperate_positions = RecruitmentSeperatePositionSerializer(many=True, read_only=True) + promo_media = serializers.CharField(max_length=100) class Meta: model = Recruitment fields = '__all__' + def validate_promo_media(self, value: str | None) -> str | None: + if (value is None): + return None + match = re.search(r'(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))', value) + if (match): + return match.group(3) + raise ValidationError("Invalid youtube url") + class RecruitmentPositionSerializer(CustomBaseSerializer): total_applicants = serializers.SerializerMethodField(method_name='get_total_applicants', read_only=True) diff --git a/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx b/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx index 511d30f77..9ffbb0c2a 100644 --- a/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx @@ -24,21 +24,18 @@ type FormType = { reprioritization_deadline_for_applicant: string; reprioritization_deadline_for_groups: string; organization: number; - promo_video: string; + promo_media: string; }; function youtubeLinkValidator(state: FormType): SamfError { - const link = state.promo_video; - var regex = /(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))/ + const link = state.promo_media; + const regex = /(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))/; if (link && !link.match(regex)) { - return "Not valid youtbue link" + return 'Not valid youtbue link'; } - return true; } - - export function RecruitmentFormAdminPage() { const { t } = useTranslation(); const navigate = useNavigate(); @@ -175,7 +172,12 @@ export function RecruitmentFormAdminPage() { />
- +
diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 72934f121..bf6bdb973 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -383,7 +383,7 @@ export type RecruitmentDto = { max_applications?: number; organization: number; seperate_positions?: RecruitmentSeperatePositionDto[]; - promo_video?: string; + promo_media?: string; }; export type RecruitmentSeperatePositionDto = { From 2d7445aeb0a9351c54cbb7398c89a56b6344fe85 Mon Sep 17 00:00:00 2001 From: Snorre Skjellestad Kristiansen Date: Tue, 10 Sep 2024 20:56:11 +0200 Subject: [PATCH 03/15] remove useless method --- backend/samfundet/models/recruitment.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/backend/samfundet/models/recruitment.py b/backend/samfundet/models/recruitment.py index c7ed8e5bf..29113d4bb 100644 --- a/backend/samfundet/models/recruitment.py +++ b/backend/samfundet/models/recruitment.py @@ -3,7 +3,6 @@ # from __future__ import annotations -import re import uuid from collections import defaultdict @@ -94,14 +93,6 @@ def save(self, *args: tuple, **kwargs: dict) -> None: if not self.statistics: RecruitmentStatistics.objects.create(self) - def process_promo_media(self, promo_media: str| None) -> str | None: - if (promo_media is None): - return None - match = re.search(r'(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))', promo_media) - if (match): - return match.group(3) - raise ValidationError("Invalid youtube url") - class RecruitmentPosition(CustomBaseModel): name_nb = models.CharField(max_length=100, help_text='Name of the position') name_en = models.CharField(max_length=100, help_text='Name of the position') From 5f25e7f95fa3631b61e78a85e9a7406857325963 Mon Sep 17 00:00:00 2001 From: Snorre Skjellestad Kristiansen Date: Tue, 10 Sep 2024 21:13:14 +0200 Subject: [PATCH 04/15] add promo_media to frontend display --- .../0005_alter_recruitment_promo_media.py | 18 ++++++++++++ .../OrganizationRecruitmentPage.tsx | 29 +++++++++---------- 2 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 backend/samfundet/migrations/0005_alter_recruitment_promo_media.py diff --git a/backend/samfundet/migrations/0005_alter_recruitment_promo_media.py b/backend/samfundet/migrations/0005_alter_recruitment_promo_media.py new file mode 100644 index 000000000..d44e14c73 --- /dev/null +++ b/backend/samfundet/migrations/0005_alter_recruitment_promo_media.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-09-10 18:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('samfundet', '0004_alter_recruitment_promo_media'), + ] + + operations = [ + migrations.AlterField( + model_name='recruitment', + name='promo_media', + field=models.CharField(default=None, help_text='Youtube video id', max_length=11, null=True), + ), + ] diff --git a/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx b/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx index c833e0728..75928db45 100644 --- a/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx +++ b/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx @@ -1,22 +1,21 @@ -import styles from './OrganizationRecruitmentPage.module.scss'; -import { RecruitmentTabs, GangTypeContainer } from './Components'; -import { Text, Page, Video, Logo, OccupiedFormModal, SamfundetLogoSpinner, ToggleSwitch } from '~/Components'; -import { PersonalRow } from '~/Pages/RecruitmentPage'; -import { OrgNameType, OrgNameTypeValue } from '~/types'; -import { useDesktop } from '~/hooks'; -import { useParams } from 'react-router-dom'; -import { KEY } from '~/i18n/constants'; -import { dbT } from '~/utils'; -import { useTranslation } from 'react-i18next'; +import classNames from 'classnames'; import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import { getOrganization, getRecruitment } from '~/api'; +import { Logo, OccupiedFormModal, Page, SamfundetLogoSpinner, Text, ToggleSwitch, Video } from '~/Components'; import { useOrganizationContext } from '~/context/OrgContextProvider'; import { RecruitmentDto } from '~/dto'; -import { getOrganization, getRecruitment } from '~/api'; -import classNames from 'classnames'; +import { useDesktop } from '~/hooks'; +import { KEY } from '~/i18n/constants'; +import { PersonalRow } from '~/Pages/RecruitmentPage'; +import { OrgNameType, OrgNameTypeValue } from '~/types'; +import { dbT } from '~/utils'; +import { GangTypeContainer, RecruitmentTabs } from './Components'; +import styles from './OrganizationRecruitmentPage.module.scss'; export function OrganizationRecruitmentPage() { const isDesktop = useDesktop(); - const embededId = '-nYQb8_TvQ4'; // TODO: Make this dynamic DO IN ISSUE #1121 for backend. #1274 for frontend const { recruitmentID } = useParams<{ recruitmentID: string }>(); const [viewAllPositions, setViewAllPositions] = useState(true); const { t } = useTranslation(); @@ -76,9 +75,9 @@ export function OrganizationRecruitmentPage() { {dbT(recruitment, 'name')} - {embededId ? ( + {recruitment?.promo_media ? ( <> - + ) : ( <> From 2108f83317bd00cd81073ceec09d0f64ad30fcd4 Mon Sep 17 00:00:00 2001 From: Snorre Skjellestad Kristiansen Date: Tue, 10 Sep 2024 21:39:50 +0200 Subject: [PATCH 05/15] fix migrations --- .../migrations/0003_recruitment_promo_media.py | 18 ------------------ .../0004_alter_recruitment_promo_media.py | 18 ------------------ ...edia.py => 0008_recruitment_promo_media.py} | 6 +++--- 3 files changed, 3 insertions(+), 39 deletions(-) delete mode 100644 backend/samfundet/migrations/0003_recruitment_promo_media.py delete mode 100644 backend/samfundet/migrations/0004_alter_recruitment_promo_media.py rename backend/samfundet/migrations/{0005_alter_recruitment_promo_media.py => 0008_recruitment_promo_media.py} (69%) diff --git a/backend/samfundet/migrations/0003_recruitment_promo_media.py b/backend/samfundet/migrations/0003_recruitment_promo_media.py deleted file mode 100644 index dc830f935..000000000 --- a/backend/samfundet/migrations/0003_recruitment_promo_media.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.0.2 on 2024-08-29 16:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('samfundet', '0002_alter_recruitmentapplication_recruiter_status'), - ] - - operations = [ - migrations.AddField( - model_name='recruitment', - name='promo_media', - field=models.CharField(help_text='Youtube video id', max_length=12, null=True), - ), - ] diff --git a/backend/samfundet/migrations/0004_alter_recruitment_promo_media.py b/backend/samfundet/migrations/0004_alter_recruitment_promo_media.py deleted file mode 100644 index 57deb6703..000000000 --- a/backend/samfundet/migrations/0004_alter_recruitment_promo_media.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.0.2 on 2024-08-29 18:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('samfundet', '0003_recruitment_promo_media'), - ] - - operations = [ - migrations.AlterField( - model_name='recruitment', - name='promo_media', - field=models.CharField(default=None, help_text='Youtube video id', max_length=12, null=True), - ), - ] diff --git a/backend/samfundet/migrations/0005_alter_recruitment_promo_media.py b/backend/samfundet/migrations/0008_recruitment_promo_media.py similarity index 69% rename from backend/samfundet/migrations/0005_alter_recruitment_promo_media.py rename to backend/samfundet/migrations/0008_recruitment_promo_media.py index d44e14c73..6f83efed7 100644 --- a/backend/samfundet/migrations/0005_alter_recruitment_promo_media.py +++ b/backend/samfundet/migrations/0008_recruitment_promo_media.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-09-10 18:58 +# Generated by Django 5.0.2 on 2024-09-10 19:39 from django.db import migrations, models @@ -6,11 +6,11 @@ class Migration(migrations.Migration): dependencies = [ - ('samfundet', '0004_alter_recruitment_promo_media'), + ('samfundet', '0007_recruitmentgangstat'), ] operations = [ - migrations.AlterField( + migrations.AddField( model_name='recruitment', name='promo_media', field=models.CharField(default=None, help_text='Youtube video id', max_length=11, null=True), From 63fc909c5b96df302c4623338075903eede07d6b Mon Sep 17 00:00:00 2001 From: Snorre Skjellestad Kristiansen Date: Thu, 3 Oct 2024 19:37:27 +0200 Subject: [PATCH 06/15] code cleanup --- .../RecruitmentFormAdminPage.tsx | 30 +++++++------------ .../RecruitmentFormAdminPage/utils.ts | 11 +++++++ frontend/src/i18n/constants.ts | 2 +- frontend/src/i18n/translations.ts | 4 +-- 4 files changed, 25 insertions(+), 22 deletions(-) create mode 100644 frontend/src/PagesAdmin/RecruitmentFormAdminPage/utils.ts diff --git a/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx b/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx index 7f14d5478..f9787a2cd 100644 --- a/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentFormAdminPage/RecruitmentFormAdminPage.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { useNavigate, useParams, useRouteLoaderData } from 'react-router-dom'; import { toast } from 'react-toastify'; import { DropDownOption } from '~/Components/Dropdown/Dropdown'; -import { SamfError, SamfForm } from '~/Forms/SamfForm'; +import { SamfForm } from '~/Forms/SamfForm'; import { SamfFormField } from '~/Forms/SamfFormField'; import { getOrganizations, postRecruitment, putRecruitment } from '~/api'; import { OrganizationDto, RecruitmentDto } from '~/dto'; @@ -14,8 +14,9 @@ import { ROUTES } from '~/routes'; import { dbT, lowerCapitalize, utcTimestampToLocal } from '~/utils'; import { AdminPageLayout } from '../AdminPageLayout/AdminPageLayout'; import styles from './RecruitmentFormAdminPage.module.scss'; +import { youtubeLinkValidator } from './utils'; -type FormType = { +export type RecruitmentFormType = { name_nb: string; name_en: string; visible_from: string; @@ -27,15 +28,6 @@ type FormType = { promo_media: string; }; -function youtubeLinkValidator(state: FormType): SamfError { - const link = state.promo_media; - const regex = /(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))/; - if (link && !link.match(regex)) { - return 'Not valid youtbue link'; - } - return true; -} - export function RecruitmentFormAdminPage() { const { t } = useTranslation(); const navigate = useNavigate(); @@ -56,7 +48,7 @@ export function RecruitmentFormAdminPage() { }); }, []); - const initialData: Partial = { + const initialData: Partial = { name_nb: data?.recruitment?.name_nb, name_en: data?.recruitment?.name_en, visible_from: utcTimestampToLocal(data?.recruitment?.visible_from), @@ -77,7 +69,7 @@ export function RecruitmentFormAdminPage() { const submitText = recruitmentId ? t(KEY.common_save) : t(KEY.common_create); - function handleOnSubmit(data: FormType) { + function handleOnSubmit(data: RecruitmentFormType) { const errors = validateForm(data); if (Object.keys(errors).length > 0) { Object.values(errors).forEach((error) => toast.error(error)); @@ -106,8 +98,8 @@ export function RecruitmentFormAdminPage() { } } - function validateForm(data: FormType) { - const errors: Partial = {}; + function validateForm(data: RecruitmentFormType) { + const errors: Partial = {}; const visibleFrom = new Date(data.visible_from); const shownApplicationDeadline = new Date(data.shown_application_deadline); @@ -134,20 +126,20 @@ export function RecruitmentFormAdminPage() { return (
- + onSubmit={handleOnSubmit} initialData={initialData} submitText={submitText} validateOn={'submit'} >
- + field="name_nb" type="text" label={t(KEY.common_name) + ' ' + t(KEY.common_english)} required={true} /> - + field="name_en" type="text" label={t(KEY.common_name) + ' ' + t(KEY.common_norwegian)} @@ -204,7 +196,7 @@ export function RecruitmentFormAdminPage() {
diff --git a/frontend/src/PagesAdmin/RecruitmentFormAdminPage/utils.ts b/frontend/src/PagesAdmin/RecruitmentFormAdminPage/utils.ts new file mode 100644 index 000000000..1483871a4 --- /dev/null +++ b/frontend/src/PagesAdmin/RecruitmentFormAdminPage/utils.ts @@ -0,0 +1,11 @@ +import { SamfError } from '~/Forms/SamfForm'; +import { RecruitmentFormType } from './RecruitmentFormAdminPage'; + +export function youtubeLinkValidator(state: RecruitmentFormType): SamfError { + const link = state.promo_media; + const regex = /(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))/; + if (link && !link.match(regex)) { + return 'Not valid youtbue link'; + } + return true; +} diff --git a/frontend/src/i18n/constants.ts b/frontend/src/i18n/constants.ts index 3fc724d50..542f051d3 100644 --- a/frontend/src/i18n/constants.ts +++ b/frontend/src/i18n/constants.ts @@ -293,7 +293,7 @@ export const KEY = { recrutment_default_application_letter: 'recrutment_default_application_letter', reprioritization_deadline_for_groups: 'reprioritization_deadline_for_groups', max_applications: 'max_applications', - promo_media: 'promo_video', + recruitment_promo_media: 'recruitment_promo_video', recruitment_norwegian_applicants_only: 'recruitment_norwegian_applicants_only', reprioritization_deadline_for_applicant: 'reprioritization_deadline_for_applicant', recruitment_show_unprocessed_applicants: 'recruitment_show_unprocessed_applicants', diff --git a/frontend/src/i18n/translations.ts b/frontend/src/i18n/translations.ts index b1686825a..1c9afcb35 100644 --- a/frontend/src/i18n/translations.ts +++ b/frontend/src/i18n/translations.ts @@ -279,7 +279,7 @@ export const nb: Record = { [KEY.recrutment_default_application_letter]: 'Standard søknadstekst', [KEY.reprioritization_deadline_for_groups]: 'Flaggefrist', [KEY.max_applications]: 'Maks søknader per bruker', - [KEY.promo_media]: 'Promo video', + [KEY.recruitment_promo_media]: 'Promo video', [KEY.recruitment_norwegian_applicants_only]: 'Kun norsktalende søkere', [KEY.reprioritization_deadline_for_applicant]: 'Omprioriteringsfrist', [KEY.recruitment_show_unprocessed_applicants]: 'Vis ubehandlede søkere', @@ -693,7 +693,7 @@ export const en: Record = { [KEY.recrutment_default_application_letter]: 'Default application letter', [KEY.reprioritization_deadline_for_groups]: 'Group reprioritization deadline', [KEY.max_applications]: 'Max applications per user', - [KEY.promo_media]: "Promo Video", + [KEY.recruitment_promo_media]: 'Promo video', [KEY.reprioritization_deadline_for_applicant]: 'Reprioritization deadline', [KEY.recruitment_show_unprocessed_applicants]: 'Show unprocessed applicants', [KEY.recruitment_show_all_applicants]: 'Show all applicants', From 0e6450eb1639268160cec6d5e99a917551c17bcf Mon Sep 17 00:00:00 2001 From: Snorre Skjellestad Kristiansen Date: Thu, 3 Oct 2024 20:02:52 +0200 Subject: [PATCH 07/15] fix migrations --- backend/samfundet/migrations/0008_recruitment_promo_media.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/samfundet/migrations/0008_recruitment_promo_media.py b/backend/samfundet/migrations/0008_recruitment_promo_media.py index 6f83efed7..10d58f0b3 100644 --- a/backend/samfundet/migrations/0008_recruitment_promo_media.py +++ b/backend/samfundet/migrations/0008_recruitment_promo_media.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.2 on 2024-09-10 19:39 +# Generated by Django 5.1.1 on 2024-10-03 17:50 from django.db import migrations, models @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('samfundet', '0007_recruitmentgangstat'), + ('samfundet', '0007_alter_infobox_color_alter_infobox_image_and_more'), ] operations = [ From 2d596784dcbf136c7e922a243b5b69500e447d23 Mon Sep 17 00:00:00 2001 From: Snorre Skjellestad Kristiansen Date: Thu, 3 Oct 2024 21:33:08 +0200 Subject: [PATCH 08/15] Fix poetry and other shit --- backend/samfundet/models/recruitment.py | 2 +- .../OrganizationRecruitmentPage.tsx | 10 +++++----- .../src/PagesAdmin/RecruitmentFormAdminPage/utils.ts | 4 ++-- frontend/src/i18n/constants.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/samfundet/models/recruitment.py b/backend/samfundet/models/recruitment.py index 88410255e..02700139e 100644 --- a/backend/samfundet/models/recruitment.py +++ b/backend/samfundet/models/recruitment.py @@ -31,7 +31,7 @@ class Recruitment(CustomBaseModel): organization = models.ForeignKey(null=False, blank=False, to=Organization, on_delete=models.CASCADE, help_text='The organization that is recruiting') max_applications = models.PositiveIntegerField(null=True, blank=True, verbose_name='Max applications per applicant') - promo_media = models.CharField(max_length=11 ,help_text='Youtube video id', null=True, default=None) + promo_media = models.CharField(max_length=11, help_text='Youtube video id', null=True, default=None) def resolve_org(self, *, return_id: bool = False) -> Organization | int: if return_id: diff --git a/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx b/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx index 717e31e4d..18d4c8ef5 100644 --- a/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx +++ b/frontend/src/Pages/OrganizationRecruitmentPage/OrganizationRecruitmentPage.tsx @@ -2,14 +2,14 @@ import classNames from 'classnames'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; -import { getOrganization, getRecruitment } from '~/api'; import { Logo, OccupiedFormModal, Page, SamfundetLogoSpinner, Text, ToggleSwitch, Video } from '~/Components'; +import { PersonalRow } from '~/Pages/RecruitmentPage'; +import { getOrganization, getRecruitment } from '~/api'; import { useOrganizationContext } from '~/context/OrgContextProvider'; -import { RecruitmentDto } from '~/dto'; +import type { RecruitmentDto } from '~/dto'; import { useDesktop } from '~/hooks'; import { KEY } from '~/i18n/constants'; -import { PersonalRow } from '~/Pages/RecruitmentPage'; -import { OrgNameType, OrgNameTypeValue } from '~/types'; +import { OrgNameType, type OrgNameTypeValue } from '~/types'; import { dbT, getObjectFieldOrNumber } from '~/utils'; import { GangSeparatePositions, GangTypeContainer, RecruitmentTabs } from './Components'; import styles from './OrganizationRecruitmentPage.module.scss'; @@ -77,7 +77,7 @@ export function OrganizationRecruitmentPage() {
{recruitment?.promo_media ? ( <> - +