From 8532bbc8d9dfcf765ed4bfe0402e02c3e45d5a16 Mon Sep 17 00:00:00 2001 From: maffe Date: Thu, 11 Jun 2026 15:37:48 +0200 Subject: [PATCH 1/6] version bump --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 0714d43b..9ab56f98 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,7 +13,7 @@ description: Mobile application for the F-guild # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # -version: 1.27.6122+1 +version: 1.28.6122+1 environment: sdk: '>=3.10.0 <4.0.0' From 0b00e564d1215953910e9268b3c93a3b3f42bf22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Bl=C3=A5man?= Date: Sun, 26 Jul 2026 00:53:59 +0200 Subject: [PATCH 2/6] Changed event signup to only allow specific group types, and some general error checking --- lib/api_client/lib/api_client.dart | 1 + .../lib/src/model/event_create.dart | 52 ++ lib/api_client/lib/src/model/event_read.dart | 52 ++ .../lib/src/model/event_update.dart | 77 ++ .../src/model/user_for_event_signup_read.dart | 190 +++++ lib/api_client/lib/src/serializers.dart | 2 + .../test/user_for_event_signup_read_test.dart | 41 + lib/l10n/app_en.arb | 7 + lib/l10n/app_localizations.dart | 44 +- lib/l10n/app_localizations_en.dart | 25 + lib/l10n/app_localizations_sv.dart | 25 +- lib/l10n/app_sv.arb | 9 +- lib/screens/event/event.dart | 788 ++++++++++-------- 13 files changed, 949 insertions(+), 364 deletions(-) create mode 100644 lib/api_client/lib/src/model/user_for_event_signup_read.dart create mode 100644 lib/api_client/test/user_for_event_signup_read_test.dart diff --git a/lib/api_client/lib/api_client.dart b/lib/api_client/lib/api_client.dart index 12ec5de7..ed8862e2 100644 --- a/lib/api_client/lib/api_client.dart +++ b/lib/api_client/lib/api_client.dart @@ -164,6 +164,7 @@ export 'package:api_client/src/model/user_access_read.dart'; export 'package:api_client/src/model/user_access_update.dart'; export 'package:api_client/src/model/user_create.dart'; export 'package:api_client/src/model/user_event_read.dart'; +export 'package:api_client/src/model/user_for_event_signup_read.dart'; export 'package:api_client/src/model/user_in_event_read.dart'; export 'package:api_client/src/model/user_in_group_read.dart'; export 'package:api_client/src/model/user_post_read.dart'; diff --git a/lib/api_client/lib/src/model/event_create.dart b/lib/api_client/lib/src/model/event_create.dart index 66401c2c..be1f185c 100644 --- a/lib/api_client/lib/src/model/event_create.dart +++ b/lib/api_client/lib/src/model/event_create.dart @@ -31,6 +31,8 @@ part 'event_create.g.dart'; /// * [canSignup] /// * [drinkPackage] /// * [isNollningEvent] +/// * [mentorGroupTypes] +/// * [allowOtherMentors] /// * [alcoholEventType] /// * [dressCode] /// * [price] @@ -95,6 +97,13 @@ abstract class EventCreate implements Built { @BuiltValueField(wireName: r'is_nollning_event') bool get isNollningEvent; + @BuiltValueField(wireName: r'mentor_group_types') + BuiltList get mentorGroupTypes; + // enum mentorGroupTypesEnum { Mentor, Mission, Default, Committee, }; + + @BuiltValueField(wireName: r'allow_other_mentors') + bool get allowOtherMentors; + @BuiltValueField(wireName: r'alcohol_event_type') EventCreateAlcoholEventTypeEnum get alcoholEventType; // enum alcoholEventTypeEnum { Alcohol, Alcohol-Served, None, }; @@ -230,6 +239,16 @@ class _$EventCreateSerializer implements PrimitiveSerializer { object.isNollningEvent, specifiedType: const FullType(bool), ); + yield r'mentor_group_types'; + yield serializers.serialize( + object.mentorGroupTypes, + specifiedType: const FullType(BuiltList, [FullType(EventCreateMentorGroupTypesEnum)]), + ); + yield r'allow_other_mentors'; + yield serializers.serialize( + object.allowOtherMentors, + specifiedType: const FullType(bool), + ); yield r'alcohol_event_type'; yield serializers.serialize( object.alcoholEventType, @@ -411,6 +430,20 @@ class _$EventCreateSerializer implements PrimitiveSerializer { ) as bool; result.isNollningEvent = valueDes; break; + case r'mentor_group_types': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, [FullType(EventCreateMentorGroupTypesEnum)]), + ) as BuiltList; + result.mentorGroupTypes.replace(valueDes); + break; + case r'allow_other_mentors': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType(bool), + ) as bool; + result.allowOtherMentors = valueDes; + break; case r'alcohol_event_type': final valueDes = serializers.deserialize( value, @@ -475,6 +508,25 @@ class _$EventCreateSerializer implements PrimitiveSerializer { } } +class EventCreateMentorGroupTypesEnum extends EnumClass { + + @BuiltValueEnumConst(wireName: r'Mentor') + static const EventCreateMentorGroupTypesEnum mentor = _$eventCreateMentorGroupTypesEnum_mentor; + @BuiltValueEnumConst(wireName: r'Mission') + static const EventCreateMentorGroupTypesEnum mission = _$eventCreateMentorGroupTypesEnum_mission; + @BuiltValueEnumConst(wireName: r'Default') + static const EventCreateMentorGroupTypesEnum default_ = _$eventCreateMentorGroupTypesEnum_default_; + @BuiltValueEnumConst(wireName: r'Committee') + static const EventCreateMentorGroupTypesEnum committee = _$eventCreateMentorGroupTypesEnum_committee; + + static Serializer get serializer => _$eventCreateMentorGroupTypesEnumSerializer; + + const EventCreateMentorGroupTypesEnum._(String name): super(name); + + static BuiltSet get values => _$eventCreateMentorGroupTypesEnumValues; + static EventCreateMentorGroupTypesEnum valueOf(String name) => _$eventCreateMentorGroupTypesEnumValueOf(name); +} + class EventCreateAlcoholEventTypeEnum extends EnumClass { @BuiltValueEnumConst(wireName: r'Alcohol') diff --git a/lib/api_client/lib/src/model/event_read.dart b/lib/api_client/lib/src/model/event_read.dart index ec949cda..675df488 100644 --- a/lib/api_client/lib/src/model/event_read.dart +++ b/lib/api_client/lib/src/model/event_read.dart @@ -35,6 +35,8 @@ part 'event_read.g.dart'; /// * [canSignup] /// * [drinkPackage] /// * [isNollningEvent] +/// * [mentorGroupTypes] +/// * [allowOtherMentors] /// * [alcoholEventType] /// * [dressCode] /// * [price] @@ -107,6 +109,13 @@ abstract class EventRead implements Built { @BuiltValueField(wireName: r'is_nollning_event') bool get isNollningEvent; + @BuiltValueField(wireName: r'mentor_group_types') + BuiltList get mentorGroupTypes; + // enum mentorGroupTypesEnum { Mentor, Mission, Default, Committee, }; + + @BuiltValueField(wireName: r'allow_other_mentors') + bool get allowOtherMentors; + @BuiltValueField(wireName: r'alcohol_event_type') String get alcoholEventType; @@ -256,6 +265,16 @@ class _$EventReadSerializer implements PrimitiveSerializer { object.isNollningEvent, specifiedType: const FullType(bool), ); + yield r'mentor_group_types'; + yield serializers.serialize( + object.mentorGroupTypes, + specifiedType: const FullType(BuiltList, [FullType(EventReadMentorGroupTypesEnum)]), + ); + yield r'allow_other_mentors'; + yield serializers.serialize( + object.allowOtherMentors, + specifiedType: const FullType(bool), + ); yield r'alcohol_event_type'; yield serializers.serialize( object.alcoholEventType, @@ -461,6 +480,20 @@ class _$EventReadSerializer implements PrimitiveSerializer { ) as bool; result.isNollningEvent = valueDes; break; + case r'mentor_group_types': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, [FullType(EventReadMentorGroupTypesEnum)]), + ) as BuiltList; + result.mentorGroupTypes.replace(valueDes); + break; + case r'allow_other_mentors': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType(bool), + ) as bool; + result.allowOtherMentors = valueDes; + break; case r'alcohol_event_type': final valueDes = serializers.deserialize( value, @@ -539,3 +572,22 @@ class _$EventReadSerializer implements PrimitiveSerializer { } } +class EventReadMentorGroupTypesEnum extends EnumClass { + + @BuiltValueEnumConst(wireName: r'Mentor') + static const EventReadMentorGroupTypesEnum mentor = _$eventReadMentorGroupTypesEnum_mentor; + @BuiltValueEnumConst(wireName: r'Mission') + static const EventReadMentorGroupTypesEnum mission = _$eventReadMentorGroupTypesEnum_mission; + @BuiltValueEnumConst(wireName: r'Default') + static const EventReadMentorGroupTypesEnum default_ = _$eventReadMentorGroupTypesEnum_default_; + @BuiltValueEnumConst(wireName: r'Committee') + static const EventReadMentorGroupTypesEnum committee = _$eventReadMentorGroupTypesEnum_committee; + + static Serializer get serializer => _$eventReadMentorGroupTypesEnumSerializer; + + const EventReadMentorGroupTypesEnum._(String name): super(name); + + static BuiltSet get values => _$eventReadMentorGroupTypesEnumValues; + static EventReadMentorGroupTypesEnum valueOf(String name) => _$eventReadMentorGroupTypesEnumValueOf(name); +} + diff --git a/lib/api_client/lib/src/model/event_update.dart b/lib/api_client/lib/src/model/event_update.dart index f2f8f61e..ba852718 100644 --- a/lib/api_client/lib/src/model/event_update.dart +++ b/lib/api_client/lib/src/model/event_update.dart @@ -30,11 +30,14 @@ part 'event_update.g.dart'; /// * [canSignup] /// * [drinkPackage] /// * [isNollningEvent] +/// * [mentorGroupTypes] +/// * [allowOtherMentors] /// * [priorities] /// * [alcoholEventType] /// * [dressCode] /// * [price] /// * [dot] +/// * [lottery] @BuiltValue() abstract class EventUpdate implements Built { @BuiltValueField(wireName: r'council_id') @@ -91,6 +94,13 @@ abstract class EventUpdate implements Built { @BuiltValueField(wireName: r'is_nollning_event') bool? get isNollningEvent; + @BuiltValueField(wireName: r'mentor_group_types') + BuiltList? get mentorGroupTypes; + // enum mentorGroupTypesEnum { Mentor, Mission, Default, Committee, }; + + @BuiltValueField(wireName: r'allow_other_mentors') + bool? get allowOtherMentors; + @BuiltValueField(wireName: r'priorities') BuiltList? get priorities; @@ -108,6 +118,9 @@ abstract class EventUpdate implements Built { EventUpdateDotEnum? get dot; // enum dotEnum { None, Single, Double, }; + @BuiltValueField(wireName: r'lottery') + bool? get lottery; + EventUpdate._(); factory EventUpdate([void updates(EventUpdateBuilder b)]) = _$EventUpdate; @@ -257,6 +270,20 @@ class _$EventUpdateSerializer implements PrimitiveSerializer { specifiedType: const FullType.nullable(bool), ); } + if (object.mentorGroupTypes != null) { + yield r'mentor_group_types'; + yield serializers.serialize( + object.mentorGroupTypes, + specifiedType: const FullType.nullable(BuiltList, [FullType(EventUpdateMentorGroupTypesEnum)]), + ); + } + if (object.allowOtherMentors != null) { + yield r'allow_other_mentors'; + yield serializers.serialize( + object.allowOtherMentors, + specifiedType: const FullType.nullable(bool), + ); + } if (object.priorities != null) { yield r'priorities'; yield serializers.serialize( @@ -292,6 +319,13 @@ class _$EventUpdateSerializer implements PrimitiveSerializer { specifiedType: const FullType.nullable(EventUpdateDotEnum), ); } + if (object.lottery != null) { + yield r'lottery'; + yield serializers.serialize( + object.lottery, + specifiedType: const FullType.nullable(bool), + ); + } } @override @@ -459,6 +493,22 @@ class _$EventUpdateSerializer implements PrimitiveSerializer { if (valueDes == null) continue; result.isNollningEvent = valueDes; break; + case r'mentor_group_types': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType.nullable(BuiltList, [FullType(EventUpdateMentorGroupTypesEnum)]), + ) as BuiltList?; + if (valueDes == null) continue; + result.mentorGroupTypes.replace(valueDes); + break; + case r'allow_other_mentors': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType.nullable(bool), + ) as bool?; + if (valueDes == null) continue; + result.allowOtherMentors = valueDes; + break; case r'priorities': final valueDes = serializers.deserialize( value, @@ -499,6 +549,14 @@ class _$EventUpdateSerializer implements PrimitiveSerializer { if (valueDes == null) continue; result.dot = valueDes; break; + case r'lottery': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType.nullable(bool), + ) as bool?; + if (valueDes == null) continue; + result.lottery = valueDes; + break; default: unhandled.add(key); unhandled.add(value); @@ -528,6 +586,25 @@ class _$EventUpdateSerializer implements PrimitiveSerializer { } } +class EventUpdateMentorGroupTypesEnum extends EnumClass { + + @BuiltValueEnumConst(wireName: r'Mentor') + static const EventUpdateMentorGroupTypesEnum mentor = _$eventUpdateMentorGroupTypesEnum_mentor; + @BuiltValueEnumConst(wireName: r'Mission') + static const EventUpdateMentorGroupTypesEnum mission = _$eventUpdateMentorGroupTypesEnum_mission; + @BuiltValueEnumConst(wireName: r'Default') + static const EventUpdateMentorGroupTypesEnum default_ = _$eventUpdateMentorGroupTypesEnum_default_; + @BuiltValueEnumConst(wireName: r'Committee') + static const EventUpdateMentorGroupTypesEnum committee = _$eventUpdateMentorGroupTypesEnum_committee; + + static Serializer get serializer => _$eventUpdateMentorGroupTypesEnumSerializer; + + const EventUpdateMentorGroupTypesEnum._(String name): super(name); + + static BuiltSet get values => _$eventUpdateMentorGroupTypesEnumValues; + static EventUpdateMentorGroupTypesEnum valueOf(String name) => _$eventUpdateMentorGroupTypesEnumValueOf(name); +} + class EventUpdateAlcoholEventTypeEnum extends EnumClass { @BuiltValueEnumConst(wireName: r'Alcohol') diff --git a/lib/api_client/lib/src/model/user_for_event_signup_read.dart b/lib/api_client/lib/src/model/user_for_event_signup_read.dart new file mode 100644 index 00000000..770a0172 --- /dev/null +++ b/lib/api_client/lib/src/model/user_for_event_signup_read.dart @@ -0,0 +1,190 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// + +// ignore_for_file: unused_element +import 'package:api_client/src/model/group_read.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +part 'user_for_event_signup_read.g.dart'; + +/// UserForEventSignupRead +/// +/// Properties: +/// * [id] +/// * [firstName] +/// * [lastName] +/// * [groups] +/// * [standardFoodPreferences] +/// * [otherFoodPreferences] +@BuiltValue() +abstract class UserForEventSignupRead implements Built { + @BuiltValueField(wireName: r'id') + int get id; + + @BuiltValueField(wireName: r'first_name') + String get firstName; + + @BuiltValueField(wireName: r'last_name') + String get lastName; + + @BuiltValueField(wireName: r'groups') + BuiltList get groups; + + @BuiltValueField(wireName: r'standard_food_preferences') + BuiltList? get standardFoodPreferences; + + @BuiltValueField(wireName: r'other_food_preferences') + String? get otherFoodPreferences; + + UserForEventSignupRead._(); + + factory UserForEventSignupRead([void updates(UserForEventSignupReadBuilder b)]) = _$UserForEventSignupRead; + + @BuiltValueHook(initializeBuilder: true) + static void _defaults(UserForEventSignupReadBuilder b) => b; + + @BuiltValueSerializer(custom: true) + static Serializer get serializer => _$UserForEventSignupReadSerializer(); +} + +class _$UserForEventSignupReadSerializer implements PrimitiveSerializer { + @override + final Iterable types = const [UserForEventSignupRead, _$UserForEventSignupRead]; + + @override + final String wireName = r'UserForEventSignupRead'; + + Iterable _serializeProperties( + Serializers serializers, + UserForEventSignupRead object, { + FullType specifiedType = FullType.unspecified, + }) sync* { + yield r'id'; + yield serializers.serialize( + object.id, + specifiedType: const FullType(int), + ); + yield r'first_name'; + yield serializers.serialize( + object.firstName, + specifiedType: const FullType(String), + ); + yield r'last_name'; + yield serializers.serialize( + object.lastName, + specifiedType: const FullType(String), + ); + yield r'groups'; + yield serializers.serialize( + object.groups, + specifiedType: const FullType(BuiltList, [FullType(GroupRead)]), + ); + yield r'standard_food_preferences'; + yield object.standardFoodPreferences == null ? null : serializers.serialize( + object.standardFoodPreferences, + specifiedType: const FullType.nullable(BuiltList, [FullType(String)]), + ); + yield r'other_food_preferences'; + yield object.otherFoodPreferences == null ? null : serializers.serialize( + object.otherFoodPreferences, + specifiedType: const FullType.nullable(String), + ); + } + + @override + Object serialize( + Serializers serializers, + UserForEventSignupRead object, { + FullType specifiedType = FullType.unspecified, + }) { + return _serializeProperties(serializers, object, specifiedType: specifiedType).toList(); + } + + void _deserializeProperties( + Serializers serializers, + Object serialized, { + FullType specifiedType = FullType.unspecified, + required List serializedList, + required UserForEventSignupReadBuilder result, + required List unhandled, + }) { + for (var i = 0; i < serializedList.length; i += 2) { + final key = serializedList[i] as String; + final value = serializedList[i + 1]; + switch (key) { + case r'id': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType(int), + ) as int; + result.id = valueDes; + break; + case r'first_name': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType(String), + ) as String; + result.firstName = valueDes; + break; + case r'last_name': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType(String), + ) as String; + result.lastName = valueDes; + break; + case r'groups': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, [FullType(GroupRead)]), + ) as BuiltList; + result.groups.replace(valueDes); + break; + case r'standard_food_preferences': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType.nullable(BuiltList, [FullType(String)]), + ) as BuiltList?; + if (valueDes == null) continue; + result.standardFoodPreferences.replace(valueDes); + break; + case r'other_food_preferences': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType.nullable(String), + ) as String?; + if (valueDes == null) continue; + result.otherFoodPreferences = valueDes; + break; + default: + unhandled.add(key); + unhandled.add(value); + break; + } + } + } + + @override + UserForEventSignupRead deserialize( + Serializers serializers, + Object serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = UserForEventSignupReadBuilder(); + final serializedList = (serialized as Iterable).toList(); + final unhandled = []; + _deserializeProperties( + serializers, + serialized, + specifiedType: specifiedType, + serializedList: serializedList, + unhandled: unhandled, + result: result, + ); + return result.build(); + } +} + diff --git a/lib/api_client/lib/src/serializers.dart b/lib/api_client/lib/src/serializers.dart index 6cd633d9..50f426c4 100644 --- a/lib/api_client/lib/src/serializers.dart +++ b/lib/api_client/lib/src/serializers.dart @@ -135,6 +135,7 @@ import 'package:api_client/src/model/user_access_read.dart'; import 'package:api_client/src/model/user_access_update.dart'; import 'package:api_client/src/model/user_create.dart'; import 'package:api_client/src/model/user_event_read.dart'; +import 'package:api_client/src/model/user_for_event_signup_read.dart'; import 'package:api_client/src/model/user_in_event_read.dart'; import 'package:api_client/src/model/user_in_group_read.dart'; import 'package:api_client/src/model/user_post_read.dart'; @@ -266,6 +267,7 @@ part 'serializers.g.dart'; UserAccessUpdate, UserCreate, UserEventRead, + UserForEventSignupRead, UserInEventRead, UserInGroupRead, UserPostRead, diff --git a/lib/api_client/test/user_for_event_signup_read_test.dart b/lib/api_client/test/user_for_event_signup_read_test.dart new file mode 100644 index 00000000..1f2f0a58 --- /dev/null +++ b/lib/api_client/test/user_for_event_signup_read_test.dart @@ -0,0 +1,41 @@ +import 'package:test/test.dart'; +import 'package:api_client/api_client.dart'; + +// tests for UserForEventSignupRead +void main() { + final instance = UserForEventSignupReadBuilder(); + // TODO add properties to the builder and call build() + + group(UserForEventSignupRead, () { + // int id + test('to test the property `id`', () async { + // TODO + }); + + // String firstName + test('to test the property `firstName`', () async { + // TODO + }); + + // String lastName + test('to test the property `lastName`', () async { + // TODO + }); + + // BuiltList groups + test('to test the property `groups`', () async { + // TODO + }); + + // BuiltList standardFoodPreferences + test('to test the property `standardFoodPreferences`', () async { + // TODO + }); + + // String otherFoodPreferences + test('to test the property `otherFoodPreferences`', () async { + // TODO + }); + + }); +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 908c977a..94e8dc31 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -48,6 +48,10 @@ "eventWebMasters": "the web masters", "eventFoodPreferences": "Food preferences:", "eventSendSignup": "Submit", + "eventSignupErrorTitle": "Signup failed", + "eventSignupErrorInvalidGroup": "You cannot sign up with this group!", + "eventSignupErrorDeadlinePassed": "Signup deadline for this event has passed!", + "eventSignupErrorClose": "Stäng", "eventFoodPrefInfo": "You can change these in Settings > Food preferences.", "eventLinkToFoodPrefs": "Shortcut to the settings", "eventDesignup": "Remove sign-up", @@ -73,6 +77,9 @@ "eventAlcoholMayAppear" : "Alcohol may be present", "eventAlcoholFree" : "Alcoholfree", "eventNoAlcohol" : "None", + "eventNollning": "Introduction event", + "eventNollningMentor": "Introduction event (with your mentor group)", + "eventNollningMission": "Introduction event (with your mission group)", "settingsSettings": "Settings", "settingsSave": "Save", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e507b816..22713b8e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -353,9 +353,33 @@ abstract class AppLocalizations { /// No description provided for @eventSendSignup. /// /// In sv, this message translates to: - /// **'Skicka Anmälan'** + /// **'Skicka anmälan'** String get eventSendSignup; + /// No description provided for @eventSignupErrorTitle. + /// + /// In sv, this message translates to: + /// **'Anmälan misslyckades'** + String get eventSignupErrorTitle; + + /// No description provided for @eventSignupErrorInvalidGroup. + /// + /// In sv, this message translates to: + /// **'Du kan inte anmäla dig med den här gruppen!'** + String get eventSignupErrorInvalidGroup; + + /// No description provided for @eventSignupErrorDeadlinePassed. + /// + /// In sv, this message translates to: + /// **'Anmälningsperioden för det här eventet är slut!'** + String get eventSignupErrorDeadlinePassed; + + /// No description provided for @eventSignupErrorClose. + /// + /// In sv, this message translates to: + /// **'Stäng'** + String get eventSignupErrorClose; + /// No description provided for @eventFoodPrefInfo. /// /// In sv, this message translates to: @@ -506,6 +530,24 @@ abstract class AppLocalizations { /// **'Inget'** String get eventNoAlcohol; + /// No description provided for @eventNollning. + /// + /// In sv, this message translates to: + /// **'Nollningsevent'** + String get eventNollning; + + /// No description provided for @eventNollningMentor. + /// + /// In sv, this message translates to: + /// **'Nollningsevent (med faddergruppen)'** + String get eventNollningMentor; + + /// No description provided for @eventNollningMission. + /// + /// In sv, this message translates to: + /// **'Nollningsevent (med uppdragsgruppen)'** + String get eventNollningMission; + /// No description provided for @settingsSettings. /// /// In sv, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 93fe5d13..4d14efc4 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -140,6 +140,20 @@ class AppLocalizationsEn extends AppLocalizations { @override String get eventSendSignup => 'Submit'; + @override + String get eventSignupErrorTitle => 'Signup failed'; + + @override + String get eventSignupErrorInvalidGroup => + 'You cannot sign up with this group!'; + + @override + String get eventSignupErrorDeadlinePassed => + 'Signup deadline for this event has passed!'; + + @override + String get eventSignupErrorClose => 'Stäng'; + @override String get eventFoodPrefInfo => 'You can change these in Settings > Food preferences.'; @@ -218,6 +232,17 @@ class AppLocalizationsEn extends AppLocalizations { @override String get eventNoAlcohol => 'None'; + @override + String get eventNollning => 'Introduction event'; + + @override + String get eventNollningMentor => + 'Introduction event (with your mentor group)'; + + @override + String get eventNollningMission => + 'Introduction event (with your mission group)'; + @override String get settingsSettings => 'Settings'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index dfaa768a..6caecf1d 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -138,7 +138,21 @@ class AppLocalizationsSv extends AppLocalizations { String get eventFoodPreferences => 'Matpreferenser:'; @override - String get eventSendSignup => 'Skicka Anmälan'; + String get eventSendSignup => 'Skicka anmälan'; + + @override + String get eventSignupErrorTitle => 'Anmälan misslyckades'; + + @override + String get eventSignupErrorInvalidGroup => + 'Du kan inte anmäla dig med den här gruppen!'; + + @override + String get eventSignupErrorDeadlinePassed => + 'Anmälningsperioden för det här eventet är slut!'; + + @override + String get eventSignupErrorClose => 'Stäng'; @override String get eventFoodPrefInfo => @@ -217,6 +231,15 @@ class AppLocalizationsSv extends AppLocalizations { @override String get eventNoAlcohol => 'Inget'; + @override + String get eventNollning => 'Nollningsevent'; + + @override + String get eventNollningMentor => 'Nollningsevent (med faddergruppen)'; + + @override + String get eventNollningMission => 'Nollningsevent (med uppdragsgruppen)'; + @override String get settingsSettings => 'Inställningar'; diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index b2ebf01b..0b49d143 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -47,7 +47,11 @@ "eventTechnicalDifficulties": "Vid tekniska problem med anmälan, kontakta ", "eventWebMasters": "spindelmännen", "eventFoodPreferences": "Matpreferenser:", - "eventSendSignup": "Skicka Anmälan", + "eventSendSignup": "Skicka anmälan", + "eventSignupErrorTitle": "Anmälan misslyckades", + "eventSignupErrorInvalidGroup": "Du kan inte anmäla dig med den här gruppen!", + "eventSignupErrorDeadlinePassed": "Anmälningsperioden för det här eventet är slut!", + "eventSignupErrorClose": "Stäng", "eventFoodPrefInfo": "Du kan ändra dessa i Inställningar > Matpreferenser.", "eventLinkToFoodPrefs": "Genväg till inställningarna", "eventDesignup": "Avanmäl", @@ -73,6 +77,9 @@ "eventAlcoholMayAppear" : "Alkohol kan förekomma", "eventAlcoholFree" : "Alkoholfritt", "eventNoAlcohol" : "Inget", + "eventNollning": "Nollningsevent", + "eventNollningMentor": "Nollningsevent (med faddergruppen)", + "eventNollningMission": "Nollningsevent (med uppdragsgruppen)", "settingsSettings": "Inställningar", "settingsSave": "Spara", diff --git a/lib/screens/event/event.dart b/lib/screens/event/event.dart index fde70c6c..1b6f285f 100644 --- a/lib/screens/event/event.dart +++ b/lib/screens/event/event.dart @@ -19,7 +19,7 @@ class EventPage extends StatefulWidget { class _EventPageState extends State { EventRead? event; - AdminUserRead? user; + UserForEventSignupRead? user; String? userType; GroupRead? group; // String? answer; @@ -35,9 +35,10 @@ class _EventPageState extends State { final Map _htmlStyle = { "body": Style(margin: Margins.zero, padding: HtmlPaddings.zero), "p": Style( - padding: HtmlPaddings.zero, - margin: Margins.zero, - lineHeight: LineHeight(1.2)), + padding: HtmlPaddings.zero, + margin: Margins.zero, + lineHeight: LineHeight(1.2), + ), }; static const foodPrefsDisplay = { @@ -60,10 +61,10 @@ class _EventPageState extends State { static const String drinkPackageAlcoholFree = "AlcoholFree"; static const Map - drinkPackageToEnum = { + drinkPackageToEnum = { "None": EventSignupCreateDrinkPackageEnum.none, "Alcohol": EventSignupCreateDrinkPackageEnum.alcohol, - "AlcoholFree": EventSignupCreateDrinkPackageEnum.alcoholFree + "AlcoholFree": EventSignupCreateDrinkPackageEnum.alcoholFree, }; void initState() { @@ -80,8 +81,9 @@ class _EventPageState extends State { final event = eventResponse.data; if (event == null) return; - final userResponse = - await ApiService.apiClient.getUsersApi().usersGetMe(); + final userResponse = await ApiService.apiClient + .getEventSignupApi() + .eventSignupGetMeForEventSignup(eventId: widget.eventId); final user = userResponse.data; if (user == null) return; @@ -102,15 +104,16 @@ class _EventPageState extends State { } } - final prioritesResponse = - await ApiService.apiClient.getUsersApi().usersGetMyPriorities(); + final prioritesResponse = await ApiService.apiClient + .getUsersApi() + .usersGetMyPriorities(); setState(() { this.event = event; this.user = user; this.eventSignup = eventSignup; this.drinkPackageAnswer = drinkPackageAlcohol; - this.priorites = prioritesResponse.data!.toList(); + this.priorites = prioritesResponse.data!.toList().cast(); if (user.groups.isNotEmpty) { this.defaultGroup = user.groups.first; this.group = defaultGroup; @@ -141,24 +144,54 @@ class _EventPageState extends State { Widget alcoholEventRow(EventRead event, BuildContext context) { var t = AppLocalizations.of(context)!; if (event.alcoholEventType == "Alcohol-Served") { - return Row(children: [ - Icon(Icons.wine_bar_rounded), - Text(" " + t.eventAlcoholServed) - ]); + return Row( + children: [ + Icon(Icons.wine_bar_rounded), + Text(" " + t.eventAlcoholServed), + ], + ); } if (event.isNollningEvent && event.alcoholEventType == "Alcohol") { - return Row(children: [ - Icon(Icons.local_drink_rounded), - Text(" " + t.eventAlcoholMayAppear) - ]); + return Row( + children: [ + Icon(Icons.local_drink_rounded), + Text(" " + t.eventAlcoholMayAppear), + ], + ); } if (event.isNollningEvent && event.alcoholEventType == "None") { - return Row(children: [ - Icon(Icons.no_drinks_rounded), - Text(" " + t.eventAlcoholFree) - ]); + return Row( + children: [ + Icon(Icons.no_drinks_rounded), + Text(" " + t.eventAlcoholFree), + ], + ); + } + + return SizedBox.shrink(); + } + + Widget nollningEventRow(EventRead event, BuildContext context) { + var t = AppLocalizations.of(context)!; + + if (event.isNollningEvent) { + final mentor = event.mentorGroupTypes.contains( + EventReadMentorGroupTypesEnum.mentor, + ); + final mission = event.mentorGroupTypes.contains( + EventReadMentorGroupTypesEnum.mission, + ); + + var text = t.eventNollning; + if (mentor && !mission) { + text = t.eventNollningMentor; + } else if (mission && !mentor) { + text = t.eventNollningMission; + } + + return Row(children: [Icon(Icons.star_border), Text(" " + text)]); } return SizedBox.shrink(); @@ -177,17 +210,11 @@ class _EventPageState extends State { locale = "en"; } if (event == null) { - return Scaffold( - appBar: AppBar( - title: Text(t.eventTitle), - ), - ); + return Scaffold(appBar: AppBar(title: Text(t.eventTitle))); } return Scaffold( - appBar: AppBar( - title: Text(t.eventTitle), - ), + appBar: AppBar(title: Text(t.eventTitle)), body: Container( width: double.infinity, child: Padding( @@ -196,99 +223,98 @@ class _EventPageState extends State { onRefresh: () => _onRefresh(), child: ListView( children: [ - Text(locale == "sv" ? event!.titleSv : event!.titleEn, - style: Theme.of(context).textTheme.headlineMedium!.copyWith( - fontSize: 30, - )), - Divider( - color: null, + Text( + locale == "sv" ? event!.titleSv : event!.titleEn, + style: Theme.of( + context, + ).textTheme.headlineMedium!.copyWith(fontSize: 30), ), + Divider(color: null), Row( children: [ - Icon( - Icons.access_time_rounded, - ), + Icon(Icons.access_time_rounded), Text( /* better error checking */ " " + DateFormat("HH:mm").format( - event?.startsAt.toLocal() ?? DateTime.now()) + + event?.startsAt.toLocal() ?? DateTime.now(), + ) + getDots() + " - " + - DateFormat("HH:mm").format( - event?.endsAt.toLocal() ?? DateTime.now()) + + DateFormat( + "HH:mm", + ).format(event?.endsAt.toLocal() ?? DateTime.now()) + ", " + - DateFormat("MMMMd", locale).format( - event?.startsAt.toLocal() ?? DateTime.now()), + DateFormat( + "MMMMd", + locale, + ).format(event?.startsAt.toLocal() ?? DateTime.now()), style: TextStyle( - fontSize: 14, - color: Theme.of(context).textTheme.bodyMedium!.color), + fontSize: 14, + color: Theme.of(context).textTheme.bodyMedium!.color, + ), ), ], ), Row( children: [ - Icon( - Icons.room, - ), + Icon(Icons.room), Text( " " + (event?.location ?? "intigheten"), style: TextStyle( - fontSize: 14, - color: Theme.of(context).textTheme.bodyMedium!.color), + fontSize: 14, + color: Theme.of(context).textTheme.bodyMedium!.color, + ), ), ], ), - Divider( - color: null, - ), + Divider(color: null), Container( - margin: EdgeInsets.fromLTRB(0, 0, 0, 0), - width: double.infinity, - /* should be parsed html */ - child: Markdown( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - data: (locale == "sv" - ? event!.descriptionSv - : event!.descriptionEn) - .replaceAll("
", ""), - onTapLink: (text, href, title) { - if (href != null) { - launchUrl(Uri.parse(href)); - } - }, - styleSheet: - MarkdownStyleSheet.fromTheme(Theme.of(context)) - .copyWith( - p: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(height: 1.2), - ), - )), - Divider( - color: null, + margin: EdgeInsets.fromLTRB(0, 0, 0, 0), + width: double.infinity, + /* should be parsed html */ + child: Markdown( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + data: + (locale == "sv" + ? event!.descriptionSv + : event!.descriptionEn) + .replaceAll("
", ""), + onTapLink: (text, href, title) { + if (href != null) { + launchUrl(Uri.parse(href)); + } + }, + styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context)) + .copyWith( + p: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(height: 1.2), + ), + ), ), + Divider(color: null), Padding( padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row(children: [ - Text(t.eventDressCode + event!.dressCode) - ]), + Row( + children: [Text(t.eventDressCode + event!.dressCode)], + ), Visibility( - visible: event!.price <= 0 ? false : true, - child: Text(t.eventPrice + + visible: event!.price <= 0 ? false : true, + child: Text( + t.eventPrice + (event?.price.toString() ?? "") + - " kr")), + " kr", + ), + ), ], ), ), - Divider( - color: null, - ), + Divider(color: null), Padding( padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), child: Column( @@ -298,7 +324,7 @@ class _EventPageState extends State { child: Row( children: [ Icon(Icons.attach_money_rounded), - Text(t.eventCostsMoney) + Text(t.eventCostsMoney), ], ), ), @@ -306,22 +332,19 @@ class _EventPageState extends State { visible: event!.food, child: Row( children: [ - Icon( - Icons.restaurant_rounded, - ), - Text(t.eventFoodServed) + Icon(Icons.restaurant_rounded), + Text(t.eventFoodServed), ], ), ), + nollningEventRow(event!, context), alcoholEventRow(event!, context), Visibility( visible: event!.canSignup, child: Row( children: [ - Icon( - Icons.event_rounded, - ), - Text(t.eventHasSignup) + Icon(Icons.event_rounded), + Text(t.eventHasSignup), ], ), ), @@ -329,10 +352,8 @@ class _EventPageState extends State { visible: event!.lottery, child: Row( children: [ - Icon( - Icons.casino_outlined, - ), - Text(" " + t.eventHasLottery) + Icon(Icons.casino_outlined), + Text(" " + t.eventHasLottery), ], ), ), @@ -341,9 +362,7 @@ class _EventPageState extends State { ), Visibility( visible: event!.canSignup, - child: Divider( - color: null, - ), + child: Divider(color: null), ), Visibility( visible: (!(event!.council == null)), @@ -351,23 +370,17 @@ class _EventPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - t.eventInCaseOfQuestions, - ), + Text(t.eventInCaseOfQuestions), InkWell( child: new Text( t.localeName == "sv" ? event!.council.nameSv : event!.council.nameEn, - style: TextStyle( - color: Colors.blue[300], - ), + style: TextStyle(color: Colors.blue[300]), ), onTap: () => _goToPostContact(event!.council.nameSv), ), - Divider( - color: null, - ), + Divider(color: null), ], ), ), @@ -388,14 +401,31 @@ class _EventPageState extends State { }); } - final eventSignupCreate = EventSignupCreate((b) => b - ..userId = user!.id - ..priority = userType - ..groupName = group?.name ?? customGroup - ..drinkPackage = drinkPackageToEnum[drinkPackageAnswer]); + final eventSignupCreate = EventSignupCreate( + (b) => b + ..userId = user!.id + ..priority = userType + ..groupName = group?.name ?? customGroup + ..drinkPackage = drinkPackageToEnum[drinkPackageAnswer], + ); - await ApiService.apiClient.getEventSignupApi().eventSignupEventSignupRoute( - eventId: event!.id, eventSignupCreate: eventSignupCreate); + try { + await ApiService.apiClient + .getEventSignupApi() + .eventSignupEventSignupRoute( + eventId: event!.id, + eventSignupCreate: eventSignupCreate, + ); + } catch (e) { + if (e is DioException && + e.response!.statusCode! >= 400 && + e.response!.statusCode! < 500) { + + String? detail = e.response!.data["detail"]; + return showSignupError(detail); + } + throw e; + } update(); } @@ -404,17 +434,55 @@ class _EventPageState extends State { int userId = user?.id ?? -1; int eventId = event?.id ?? -1; - await ApiService.apiClient - .getEventSignupApi() - .eventSignupEventSignoffRoute(eventId: eventId, userId: userId); + await ApiService.apiClient.getEventSignupApi().eventSignupEventSignoffRoute( + eventId: eventId, + userId: userId, + ); update(); } + Future showSignupError(String? detail) async { + var t = AppLocalizations.of(context)!; + + String? detailText; + + switch (detail!.toLowerCase()) { // TODO: this could probably be improved if all response details came as an enum + case "user cannot sign up with this group": + detailText = t.eventSignupErrorInvalidGroup; + break; + case "event signup deadline is passed": + detailText = t.eventSignupErrorDeadlinePassed; + break; + default: + detailText = detail; + break; + } + + return showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(t.eventSignupErrorTitle), + content: detailText != null ? Text(detailText) : SizedBox.shrink(), + actions: [ + TextButton( + child: Text(t.eventSignupErrorClose), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ); + }, + ); + } + //Bör denna vara async som de andra funktionerna? void goToSettings() { Navigator.push( - context, MaterialPageRoute(builder: (context) => SettingsPage())) - .then((_) { + context, + MaterialPageRoute(builder: (context) => SettingsPage()), + ).then((_) { //uppdaterar sidan så man ser sin ändring update(); }); @@ -442,17 +510,18 @@ class _EventPageState extends State { if (event!.priorities.toList().isNotEmpty) ...event!.priorities .where((prio) => priorites!.contains(prio.priority)) - .map((prio) => DropdownMenuItem( - value: prio.priority, - child: Text(t.localeName == "en" + .map( + (prio) => DropdownMenuItem( + value: prio.priority, + child: Text( + t.localeName == "en" ? (prioritiesSvToEn[prio.priority] ?? - prio.priority) - : prio.priority), - )), - DropdownMenuItem( - value: null, - child: Text(t.eventOther), - ) + prio.priority) + : prio.priority, + ), + ), + ), + DropdownMenuItem(value: null, child: Text(t.eventOther)), ], ), ], @@ -492,7 +561,9 @@ class _EventPageState extends State { if (user!.groups.toList().isNotEmpty) ...user!.groups.map(((GroupRead? g) { return DropdownMenuItem( - value: g, child: Text(g!.name)); + value: g, + child: Text(g!.name), + ); })), DropdownMenuItem( value: null, @@ -503,15 +574,16 @@ class _EventPageState extends State { Visibility( visible: displayGroupInput, child: TextField( - onChanged: (String? newValue) { - setState(() { - customGroup = newValue; - }); - }, - decoration: InputDecoration( - border: OutlineInputBorder(), - hintText: t.eventCustomGroupName, - )), + onChanged: (String? newValue) { + setState(() { + customGroup = newValue; + }); + }, + decoration: InputDecoration( + border: OutlineInputBorder(), + hintText: t.eventCustomGroupName, + ), + ), ), ], ), @@ -544,15 +616,11 @@ class _EventPageState extends State { InkWell( child: new Text( t.eventWebMasters, - style: TextStyle( - color: Colors.blue[300], - ), + style: TextStyle(color: Colors.blue[300]), ), onTap: () => _goToPostContact("Spindelman"), ), - Divider( - color: null, - ), + Divider(color: null), ], ), ); @@ -569,9 +637,7 @@ class _EventPageState extends State { Text(" " + t.eventNoSignup), ], ), - Divider( - color: null, - ), + Divider(color: null), Container( margin: EdgeInsets.all(10), child: Column( @@ -581,18 +647,14 @@ class _EventPageState extends State { InkWell( child: new Text( t.eventWebMasters, - style: TextStyle( - color: Colors.blue[300], - ), + style: TextStyle(color: Colors.blue[300]), ), onTap: () => _goToPostContact("Spindelman"), ), - Divider( - color: null, - ), + Divider(color: null), ], ), - ) + ), ], ), ); @@ -607,14 +669,12 @@ class _EventPageState extends State { children: [ Icon( Icons.info_outline_rounded, - color: Colors.red[ - 300], // I don't like it, but this hardcoding kinda just works + color: Colors + .red[300], // I don't like it, but this hardcoding kinda just works ), Text( t.eventNotSignedUp, - style: TextStyle( - color: Colors.red[300], - ), + style: TextStyle(color: Colors.red[300]), ), ], ); @@ -628,21 +688,14 @@ class _EventPageState extends State { children: [ Row( children: [ - Icon( - Icons.cancel, - color: Colors.red[300], - ), + Icon(Icons.cancel, color: Colors.red[300]), Text( t.eventNoSpot, - style: TextStyle( - color: Colors.red[300], - ), + style: TextStyle(color: Colors.red[300]), ), ], ), - Divider( - color: null, - ), + Divider(color: null), ..._signupDetails(groupName, userType), ], ); @@ -652,21 +705,14 @@ class _EventPageState extends State { children: [ Row( children: [ - Icon( - Icons.check_circle, - color: Colors.green[300], - ), + Icon(Icons.check_circle, color: Colors.green[300]), Text( t.eventGotSpot, - style: TextStyle( - color: Colors.green[300], - ), + style: TextStyle(color: Colors.green[300]), ), ], ), - Divider( - color: null, - ), + Divider(color: null), ..._signupDetails(groupName, userType), ], ); @@ -689,9 +735,7 @@ class _EventPageState extends State { ), ], ), - Divider( - color: null, - ), + Divider(color: null), ..._signupDetails(groupName, userType), ], ); @@ -714,28 +758,20 @@ class _EventPageState extends State { t.eventSignUp, style: Theme.of(context).textTheme.headlineMedium, ), - Divider( - color: null, - ), + Divider(color: null), Padding( padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), child: Column( children: [ Row( children: [ - Icon( - Icons.person, - ), - Text( - t.eventNbrSignUps + event!.signupCount.toString(), - ), + Icon(Icons.person), + Text(t.eventNbrSignUps + event!.signupCount.toString()), ], ), Row( children: [ - Icon( - Icons.people, - ), + Icon(Icons.people), Text( t.eventNbrSpots + (event!.maxEventUsers == 0 @@ -746,43 +782,40 @@ class _EventPageState extends State { ), Row( children: [ - Icon( - Icons.event_available_rounded, - ), + Icon(Icons.event_available_rounded), Text( t.eventSignUpOpens + - DateFormat("d/M") - .format(event!.signupStart.toLocal()) + + DateFormat( + "d/M", + ).format(event!.signupStart.toLocal()) + " " + - DateFormat("jm", locale) - .format(event!.signupStart.toLocal()), + DateFormat( + "jm", + locale, + ).format(event!.signupStart.toLocal()), ), ], ), Row( children: [ - Icon( - Icons.event_busy_rounded, - ), + Icon(Icons.event_busy_rounded), Text( t.eventSignUpCloses + DateFormat("d/M").format(event!.signupEnd.toLocal()) + " " + - DateFormat("jm", locale) - .format(event!.signupEnd.toLocal()), + DateFormat( + "jm", + locale, + ).format(event!.signupEnd.toLocal()), ), ], ), ], ), ), - Divider( - color: null, - ), + Divider(color: null), signup, - Divider( - color: null, - ), + Divider(color: null), Container( margin: EdgeInsets.all(10), child: Column( @@ -792,15 +825,11 @@ class _EventPageState extends State { InkWell( child: new Text( t.eventWebMasters, - style: TextStyle( - color: Colors.blue[300], - ), + style: TextStyle(color: Colors.blue[300]), ), onTap: () => _goToPostContact("Spindelman"), ), - Divider( - color: null, - ), + Divider(color: null), ], ), ), @@ -850,80 +879,88 @@ class _EventPageState extends State { DropdownMenuItem( value: drinkPackageNone, child: Text(t.eventNoAlcohol), - ) + ), ], - ) + ), ), ], - ) + ), ); } if (eventSignup == null) { return Container( - padding: EdgeInsets.fromLTRB(0, 5, 0, 5), - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - groupDropdown(), - userTypeDropDown(), - // questionInput(), - drinkPackageInput, - Wrap( - children: [ - Text(t.eventFoodPreferences + " ", - style: TextStyle( - fontWeight: FontWeight.bold, - color: - Theme.of(context).textTheme.bodyMedium!.color)), - ...?foodPreferences[locale] - ?.where((element) => element.isNotEmpty) - .map((foodPreference) => Text(foodPreference + " ")), - Text(" " + (foodCustom ?? "")), - ], - ), - Wrap(children: [ + padding: EdgeInsets.fromLTRB(0, 5, 0, 5), + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + groupDropdown(), + userTypeDropDown(), + // questionInput(), + drinkPackageInput, + Wrap( + children: [ + Text( + t.eventFoodPreferences + " ", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + ), + ...?foodPreferences[locale] + ?.where((element) => element.isNotEmpty) + .map((foodPreference) => Text(foodPreference + " ")), + Text(" " + (foodCustom ?? "")), + ], + ), + Wrap( + children: [ Text( t.eventFoodPrefInfo, style: TextStyle( - fontStyle: FontStyle.italic, - color: Theme.of(context).textTheme.bodyMedium!.color), + fontStyle: FontStyle.italic, + color: Theme.of(context).textTheme.bodyMedium!.color, + ), ), GestureDetector( - child: Text(t.eventLinkToFoodPrefs, - style: TextStyle( - decoration: TextDecoration.underline, - color: Theme.of(context).colorScheme.primary)), + child: Text( + t.eventLinkToFoodPrefs, + style: TextStyle( + decoration: TextDecoration.underline, + color: Theme.of(context).colorScheme.primary, + ), + ), onTap: () => goToSettings(), ), - ]), - SizedBox( - height: 16, - ), - Align( - alignment: Alignment.center, - child: SizedBox( - height: 50, - width: 200, - child: InkWell( - onTap: () => sendSignup(), - child: Card( - color: Theme.of(context).colorScheme.primary, - child: Align( - alignment: Alignment.center, - child: Text( - t.eventSendSignup, - style: TextStyle( - fontSize: 20, - color: Theme.of(context).colorScheme.onPrimary), + ], + ), + SizedBox(height: 16), + Align( + alignment: Alignment.center, + child: SizedBox( + height: 50, + width: 200, + child: InkWell( + onTap: () => sendSignup(), + child: Card( + color: Theme.of(context).colorScheme.primary, + child: Align( + alignment: Alignment.center, + child: Text( + t.eventSendSignup, + style: TextStyle( + fontSize: 20, + color: Theme.of(context).colorScheme.onPrimary, ), ), ), ), ), ), - ], - )); + ), + ], + ), + ); } else { String? groupName = eventSignup!.groupName; String userType = eventSignup!.priority; @@ -931,9 +968,7 @@ class _EventPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ ..._signupDetails(groupName, userType), - SizedBox( - height: 16, - ), + SizedBox(height: 16), Align( alignment: Alignment.center, child: SizedBox( @@ -951,14 +986,15 @@ class _EventPageState extends State { child: Text( t.eventDesignup, style: TextStyle( - fontSize: 20, - color: Theme.of(context).colorScheme.onError), + fontSize: 20, + color: Theme.of(context).colorScheme.onError, + ), ), ), ), ), ), - ) + ), ], ); } @@ -967,26 +1003,27 @@ class _EventPageState extends State { Future _confirmUnenroll(BuildContext context) { var t = AppLocalizations.of(context)!; return showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(t.eventConfirmCancel), - actions: [ - TextButton( - child: Text(t.eventCancel), - onPressed: () { - Navigator.pop(context, false); - }, - ), - TextButton( - child: Text(t.eventConfirmRemoveSignUp), - onPressed: () { - Navigator.pop(context, true); - }, - ), - ], - ); - }); + context: context, + builder: (context) { + return AlertDialog( + title: Text(t.eventConfirmCancel), + actions: [ + TextButton( + child: Text(t.eventCancel), + onPressed: () { + Navigator.pop(context, false); + }, + ), + TextButton( + child: Text(t.eventConfirmRemoveSignUp), + onPressed: () { + Navigator.pop(context, true); + }, + ), + ], + ); + }, + ); } List _signupDetails(String? groupName, String? userType) { @@ -1000,55 +1037,71 @@ class _EventPageState extends State { if (event!.drinkPackage) { switch (eventSignup!.drinkPackage.name) { case "alcohol": // Had to hard code this cuz the openapi generator lowered all enums... TODO maybe change this later... - drinkPackage = - _drinkPackageWidget(t.eventDrinkPackage, " ${t.eventAlcohol}"); + drinkPackage = _drinkPackageWidget( + t.eventDrinkPackage, + " ${t.eventAlcohol}", + ); break; case "alcoholFree": drinkPackage = _drinkPackageWidget( - t.eventDrinkPackage, " ${t.eventAlcoholFree}"); + t.eventDrinkPackage, + " ${t.eventAlcoholFree}", + ); break; case "none": - drinkPackage = - _drinkPackageWidget(t.eventDrinkPackage, " ${t.eventNoAlcohol}"); + drinkPackage = _drinkPackageWidget( + t.eventDrinkPackage, + " ${t.eventNoAlcohol}", + ); break; default: this.drinkPackageAnswer = drinkPackageNone; - drinkPackage = - _drinkPackageWidget(t.eventDrinkPackage, " ${t.eventNoAlcohol}"); + drinkPackage = _drinkPackageWidget( + t.eventDrinkPackage, + " ${t.eventNoAlcohol}", + ); break; } } return [ RichText( - text: TextSpan( - text: t.eventGroup, - style: TextStyle( + text: TextSpan( + text: t.eventGroup, + style: TextStyle( fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.bodyMedium!.color), - children: [ - TextSpan( + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + children: [ + TextSpan( text: " $groupName", style: TextStyle( - fontWeight: FontWeight.normal, - color: Theme.of(context).textTheme.bodyMedium!.color)) - ], - )), + fontWeight: FontWeight.normal, + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + ), + ], + ), + ), RichText( - text: TextSpan( - text: t.eventPriority2, - style: TextStyle( + text: TextSpan( + text: t.eventPriority2, + style: TextStyle( fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.bodyMedium!.color), - children: [ - TextSpan( + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + children: [ + TextSpan( text: t.localeName == "en" ? (prioritiesSvToEn[userType] ?? userType) : userType, style: TextStyle( - fontWeight: FontWeight.normal, - color: Theme.of(context).textTheme.bodyMedium!.color)) - ], - )), + fontWeight: FontWeight.normal, + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + ), + ], + ), + ), // event!.event_signup!.question != "" // ? RichText( // text: TextSpan( @@ -1065,33 +1118,41 @@ class _EventPageState extends State { Wrap( children: [ RichText( - text: TextSpan( - text: t.eventFoodPreferences + " ", - style: TextStyle( + text: TextSpan( + text: t.eventFoodPreferences + " ", + style: TextStyle( fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.bodyMedium!.color), - )), + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + ), + ), ...?foodPreferences[locale] ?.where((element) => element.isNotEmpty) .map((foodPreferences) => Text(foodPreferences + " ")), Text(foodCustom ?? ""), ], ), - Wrap(children: [ - Text( - t.eventFoodPrefInfo, - style: TextStyle( + Wrap( + children: [ + Text( + t.eventFoodPrefInfo, + style: TextStyle( fontStyle: FontStyle.italic, - color: Theme.of(context).textTheme.bodyMedium!.color), - ), - GestureDetector( - child: Text(t.eventLinkToFoodPrefs, + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + ), + GestureDetector( + child: Text( + t.eventLinkToFoodPrefs, style: TextStyle( - decoration: TextDecoration.underline, - color: Theme.of(context).colorScheme.primary)), - onTap: () => goToSettings(), - ), - ]), + decoration: TextDecoration.underline, + color: Theme.of(context).colorScheme.primary, + ), + ), + onTap: () => goToSettings(), + ), + ], + ), ]; } @@ -1100,14 +1161,17 @@ class _EventPageState extends State { text: TextSpan( text: drinkPackageText, style: TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.bodyMedium!.color), + fontWeight: FontWeight.bold, + color: Theme.of(context).textTheme.bodyMedium!.color, + ), children: [ TextSpan( - text: choice, - style: TextStyle( - fontWeight: FontWeight.normal, - color: Theme.of(context).textTheme.bodyMedium!.color)) + text: choice, + style: TextStyle( + fontWeight: FontWeight.normal, + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + ), ], ), ); @@ -1143,8 +1207,10 @@ class _EventPageState extends State { void _goToPostContact(String nameSv) { String finalname = _councilPostMap[nameSv] ?? "Spindelman"; Navigator.push( - context, - MaterialPageRoute( - builder: ((context) => ContactPage(initPostNameSv: finalname)))); + context, + MaterialPageRoute( + builder: ((context) => ContactPage(initPostNameSv: finalname)), + ), + ); } } From 8f75fe5e770db6a9f0d34e04e68272aeecfe23d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Bl=C3=A5man?= Date: Sun, 26 Jul 2026 01:05:50 +0200 Subject: [PATCH 3/6] Fixed some localization errors --- lib/l10n/app_en.arb | 4 ++-- lib/l10n/app_localizations_en.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 94e8dc31..c3e925ef 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -12,7 +12,7 @@ "calendar" : "Calendar", "notifications" : "Notifications", "other" : "Other", - "introduction" : "Nollning", + "introduction" : "Introduction", "loginRequiredField": "Required field", "loginEmail": "Email", @@ -51,7 +51,7 @@ "eventSignupErrorTitle": "Signup failed", "eventSignupErrorInvalidGroup": "You cannot sign up with this group!", "eventSignupErrorDeadlinePassed": "Signup deadline for this event has passed!", - "eventSignupErrorClose": "Stäng", + "eventSignupErrorClose": "Close", "eventFoodPrefInfo": "You can change these in Settings > Food preferences.", "eventLinkToFoodPrefs": "Shortcut to the settings", "eventDesignup": "Remove sign-up", diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 4d14efc4..66324a81 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -39,7 +39,7 @@ class AppLocalizationsEn extends AppLocalizations { String get other => 'Other'; @override - String get introduction => 'Nollning'; + String get introduction => 'Introduction'; @override String get loginRequiredField => 'Required field'; @@ -152,7 +152,7 @@ class AppLocalizationsEn extends AppLocalizations { 'Signup deadline for this event has passed!'; @override - String get eventSignupErrorClose => 'Stäng'; + String get eventSignupErrorClose => 'Close'; @override String get eventFoodPrefInfo => From b32dc4040748b1b1a8914429bfc7002c7f6be531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Bl=C3=A5man?= Date: Wed, 5 Aug 2026 00:12:01 +0200 Subject: [PATCH 4/6] Don't allow custom groups for nollning events --- lib/screens/event/event.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/screens/event/event.dart b/lib/screens/event/event.dart index 1b6f285f..fb0f6d63 100644 --- a/lib/screens/event/event.dart +++ b/lib/screens/event/event.dart @@ -420,7 +420,6 @@ class _EventPageState extends State { if (e is DioException && e.response!.statusCode! >= 400 && e.response!.statusCode! < 500) { - String? detail = e.response!.data["detail"]; return showSignupError(detail); } @@ -446,7 +445,8 @@ class _EventPageState extends State { String? detailText; - switch (detail!.toLowerCase()) { // TODO: this could probably be improved if all response details came as an enum + switch (detail!.toLowerCase()) { + // TODO: this could probably be improved if all response details came as an enum case "user cannot sign up with this group": detailText = t.eventSignupErrorInvalidGroup; break; @@ -565,10 +565,11 @@ class _EventPageState extends State { child: Text(g!.name), ); })), - DropdownMenuItem( - value: null, - child: Text(t.eventOtherDifferent), - ), + if (!event!.isNollningEvent) + DropdownMenuItem( + value: null, + child: Text(t.eventOtherDifferent), + ), ], ), Visibility( From 41651c6ef9a0a00de1835913083876424e18c743 Mon Sep 17 00:00:00 2001 From: georgelgeback Date: Tue, 11 Aug 2026 16:50:05 +0200 Subject: [PATCH 5/6] Build API --- lib/api_client/.openapi-generator/FILES | 2 + lib/api_client/README.md | 2 + lib/api_client/doc/EventCreate.md | 2 + lib/api_client/doc/EventRead.md | 2 + lib/api_client/doc/EventSignupApi.md | 48 +++++++++++ lib/api_client/doc/EventUpdate.md | 3 + lib/api_client/doc/EventsApi.md | 8 +- lib/api_client/doc/UserCreate.md | 1 + lib/api_client/doc/UserForEventSignupRead.md | 20 +++++ lib/api_client/doc/UserUpdate.md | 2 +- .../lib/src/api/event_signup_api.dart | 86 +++++++++++++++++++ lib/api_client/lib/src/api/events_api.dart | 12 ++- .../lib/src/model/event_signup_update.dart | 2 +- lib/api_client/lib/src/model/user_create.dart | 19 ++++ lib/api_client/lib/src/model/user_update.dart | 38 ++++---- 15 files changed, 224 insertions(+), 23 deletions(-) create mode 100644 lib/api_client/doc/UserForEventSignupRead.md diff --git a/lib/api_client/.openapi-generator/FILES b/lib/api_client/.openapi-generator/FILES index eb8f63c3..daa62386 100644 --- a/lib/api_client/.openapi-generator/FILES +++ b/lib/api_client/.openapi-generator/FILES @@ -153,6 +153,7 @@ doc/UserAccessUpdate.md doc/UserCreate.md doc/UserDoorAccessApi.md doc/UserEventRead.md +doc/UserForEventSignupRead.md doc/UserInEventRead.md doc/UserInGroupRead.md doc/UserPostRead.md @@ -323,6 +324,7 @@ lib/src/model/user_access_read.dart lib/src/model/user_access_update.dart lib/src/model/user_create.dart lib/src/model/user_event_read.dart +lib/src/model/user_for_event_signup_read.dart lib/src/model/user_in_event_read.dart lib/src/model/user_in_group_read.dart lib/src/model/user_post_read.dart diff --git a/lib/api_client/README.md b/lib/api_client/README.md index ee688c85..7016d457 100644 --- a/lib/api_client/README.md +++ b/lib/api_client/README.md @@ -149,6 +149,7 @@ Class | Method | HTTP request | Description [*EventSignupApi*](doc/EventSignupApi.md) | [**eventSignupEventSignoffRoute**](doc/EventSignupApi.md#eventsignupeventsignoffroute) | **DELETE** /event-signup/{event_id} | Event Signoff Route [*EventSignupApi*](doc/EventSignupApi.md) | [**eventSignupEventSignupRoute**](doc/EventSignupApi.md#eventsignupeventsignuproute) | **POST** /event-signup/{event_id} | Event Signup Route [*EventSignupApi*](doc/EventSignupApi.md) | [**eventSignupGetMeEventSignup**](doc/EventSignupApi.md#eventsignupgetmeeventsignup) | **GET** /event-signup/me-signup/{event_id} | Get Me Event Signup +[*EventSignupApi*](doc/EventSignupApi.md) | [**eventSignupGetMeForEventSignup**](doc/EventSignupApi.md#eventsignupgetmeforeventsignup) | **GET** /event-signup/me/{event_id} | Get Me For Event Signup [*EventSignupApi*](doc/EventSignupApi.md) | [**eventSignupUpdateEventSignupRoute**](doc/EventSignupApi.md#eventsignupupdateeventsignuproute) | **PATCH** /event-signup/{event_id} | Update Event Signup Route [*EventsApi*](doc/EventsApi.md) | [**eventsAddTagToEvent**](doc/EventsApi.md#eventsaddtagtoevent) | **POST** /events/add-tag | Add Tag To Event [*EventsApi*](doc/EventsApi.md) | [**eventsConfirmEventUsers**](doc/EventsApi.md#eventsconfirmeventusers) | **PATCH** /events/event-confirm-event-users/{event_id} | Confirm Event Users @@ -413,6 +414,7 @@ Class | Method | HTTP request | Description - [UserAccessUpdate](doc/UserAccessUpdate.md) - [UserCreate](doc/UserCreate.md) - [UserEventRead](doc/UserEventRead.md) + - [UserForEventSignupRead](doc/UserForEventSignupRead.md) - [UserInEventRead](doc/UserInEventRead.md) - [UserInGroupRead](doc/UserInGroupRead.md) - [UserPostRead](doc/UserPostRead.md) diff --git a/lib/api_client/doc/EventCreate.md b/lib/api_client/doc/EventCreate.md index 6ec1d91b..95ec0066 100644 --- a/lib/api_client/doc/EventCreate.md +++ b/lib/api_client/doc/EventCreate.md @@ -27,6 +27,8 @@ Name | Type | Description | Notes **canSignup** | **bool** | | **drinkPackage** | **bool** | | **isNollningEvent** | **bool** | | +**mentorGroupTypes** | **BuiltList<String>** | | +**allowOtherMentors** | **bool** | | **alcoholEventType** | **String** | | **dressCode** | **String** | | **price** | **int** | | diff --git a/lib/api_client/doc/EventRead.md b/lib/api_client/doc/EventRead.md index 4bdf5a1d..202c2673 100644 --- a/lib/api_client/doc/EventRead.md +++ b/lib/api_client/doc/EventRead.md @@ -29,6 +29,8 @@ Name | Type | Description | Notes **canSignup** | **bool** | | **drinkPackage** | **bool** | | **isNollningEvent** | **bool** | | +**mentorGroupTypes** | **BuiltList<String>** | | +**allowOtherMentors** | **bool** | | **alcoholEventType** | **String** | | **dressCode** | **String** | | **price** | **int** | | diff --git a/lib/api_client/doc/EventSignupApi.md b/lib/api_client/doc/EventSignupApi.md index cbc87d42..04f58d16 100644 --- a/lib/api_client/doc/EventSignupApi.md +++ b/lib/api_client/doc/EventSignupApi.md @@ -12,6 +12,7 @@ Method | HTTP request | Description [**eventSignupEventSignoffRoute**](EventSignupApi.md#eventsignupeventsignoffroute) | **DELETE** /event-signup/{event_id} | Event Signoff Route [**eventSignupEventSignupRoute**](EventSignupApi.md#eventsignupeventsignuproute) | **POST** /event-signup/{event_id} | Event Signup Route [**eventSignupGetMeEventSignup**](EventSignupApi.md#eventsignupgetmeeventsignup) | **GET** /event-signup/me-signup/{event_id} | Get Me Event Signup +[**eventSignupGetMeForEventSignup**](EventSignupApi.md#eventsignupgetmeforeventsignup) | **GET** /event-signup/me/{event_id} | Get Me For Event Signup [**eventSignupUpdateEventSignupRoute**](EventSignupApi.md#eventsignupupdateeventsignuproute) | **PATCH** /event-signup/{event_id} | Update Event Signup Route @@ -160,6 +161,53 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **eventSignupGetMeForEventSignup** +> UserForEventSignupRead eventSignupGetMeForEventSignup(eventId) + +Get Me For Event Signup + +### Example +```dart +import 'package:api_client/api.dart'; +// TODO Configure OAuth2 access token for authorization: OAuth2PasswordBearer +//defaultApiClient.getAuthentication('OAuth2PasswordBearer').accessToken = 'YOUR_ACCESS_TOKEN'; +// TODO Configure API key authorization: APIKeyCookie +//defaultApiClient.getAuthentication('APIKeyCookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('APIKeyCookie').apiKeyPrefix = 'Bearer'; + +final api = ApiClient().getEventSignupApi(); +final int eventId = 56; // int | + +try { + final response = api.eventSignupGetMeForEventSignup(eventId); + print(response); +} on DioException catch (e) { + print('Exception when calling EventSignupApi->eventSignupGetMeForEventSignup: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **eventId** | **int**| | + +### Return type + +[**UserForEventSignupRead**](UserForEventSignupRead.md) + +### Authorization + +[OAuth2PasswordBearer](../README.md#OAuth2PasswordBearer), [APIKeyCookie](../README.md#APIKeyCookie) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **eventSignupUpdateEventSignupRoute** > EventSignupRead eventSignupUpdateEventSignupRoute(eventId, eventSignupUpdate) diff --git a/lib/api_client/doc/EventUpdate.md b/lib/api_client/doc/EventUpdate.md index eca4e92a..5bf01658 100644 --- a/lib/api_client/doc/EventUpdate.md +++ b/lib/api_client/doc/EventUpdate.md @@ -26,11 +26,14 @@ Name | Type | Description | Notes **canSignup** | **bool** | | [optional] **drinkPackage** | **bool** | | [optional] **isNollningEvent** | **bool** | | [optional] +**mentorGroupTypes** | **BuiltList<String>** | | [optional] +**allowOtherMentors** | **bool** | | [optional] **priorities** | **BuiltList<String>** | | [optional] **alcoholEventType** | **String** | | [optional] **dressCode** | **String** | | [optional] **price** | **int** | | [optional] **dot** | **String** | | [optional] +**lottery** | **bool** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/lib/api_client/doc/EventsApi.md b/lib/api_client/doc/EventsApi.md index ab4e7119..133da28d 100644 --- a/lib/api_client/doc/EventsApi.md +++ b/lib/api_client/doc/EventsApi.md @@ -132,6 +132,12 @@ Confirm Places ### Example ```dart import 'package:api_client/api.dart'; +// TODO Configure OAuth2 access token for authorization: OAuth2PasswordBearer +//defaultApiClient.getAuthentication('OAuth2PasswordBearer').accessToken = 'YOUR_ACCESS_TOKEN'; +// TODO Configure API key authorization: APIKeyCookie +//defaultApiClient.getAuthentication('APIKeyCookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('APIKeyCookie').apiKeyPrefix = 'Bearer'; final api = ApiClient().getEventsApi(); final int eventId = 56; // int | @@ -156,7 +162,7 @@ Name | Type | Description | Notes ### Authorization -No authorization required +[OAuth2PasswordBearer](../README.md#OAuth2PasswordBearer), [APIKeyCookie](../README.md#APIKeyCookie) ### HTTP request headers diff --git a/lib/api_client/doc/UserCreate.md b/lib/api_client/doc/UserCreate.md index 2430f299..f15fd511 100644 --- a/lib/api_client/doc/UserCreate.md +++ b/lib/api_client/doc/UserCreate.md @@ -13,6 +13,7 @@ Name | Type | Description | Notes **isActive** | **bool** | | [optional] **isSuperuser** | **bool** | | [optional] **isVerified** | **bool** | | [optional] +**stilId** | **String** | | [optional] **firstName** | **String** | | **lastName** | **String** | | **telephoneNumber** | **String** | | diff --git a/lib/api_client/doc/UserForEventSignupRead.md b/lib/api_client/doc/UserForEventSignupRead.md new file mode 100644 index 00000000..f98e496b --- /dev/null +++ b/lib/api_client/doc/UserForEventSignupRead.md @@ -0,0 +1,20 @@ +# api_client.model.UserForEventSignupRead + +## Load the model package +```dart +import 'package:api_client/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **int** | | +**firstName** | **String** | | +**lastName** | **String** | | +**groups** | [**BuiltList<GroupRead>**](GroupRead.md) | | +**standardFoodPreferences** | **BuiltList<String>** | | +**otherFoodPreferences** | **String** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/lib/api_client/doc/UserUpdate.md b/lib/api_client/doc/UserUpdate.md index 8092a96c..de13b4e4 100644 --- a/lib/api_client/doc/UserUpdate.md +++ b/lib/api_client/doc/UserUpdate.md @@ -8,12 +8,12 @@ import 'package:api_client/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**stilId** | **String** | | [optional] **firstName** | **String** | | [optional] **lastName** | **String** | | [optional] **startYear** | **int** | | [optional] **program** | **String** | | [optional] **notifications** | **bool** | | [optional] -**stilId** | **String** | | [optional] **standardFoodPreferences** | **BuiltList<String>** | | [optional] **otherFoodPreferences** | **String** | | [optional] **telephoneNumber** | **String** | | [optional] diff --git a/lib/api_client/lib/src/api/event_signup_api.dart b/lib/api_client/lib/src/api/event_signup_api.dart index 3d06ec78..493c5f24 100644 --- a/lib/api_client/lib/src/api/event_signup_api.dart +++ b/lib/api_client/lib/src/api/event_signup_api.dart @@ -13,6 +13,7 @@ import 'package:api_client/src/model/event_signup_create.dart'; import 'package:api_client/src/model/event_signup_read.dart'; import 'package:api_client/src/model/event_signup_update.dart'; import 'package:api_client/src/model/http_validation_error.dart'; +import 'package:api_client/src/model/user_for_event_signup_read.dart'; class EventSignupApi { @@ -306,6 +307,91 @@ class EventSignupApi { ); } + /// Get Me For Event Signup + /// + /// + /// Parameters: + /// * [eventId] + /// * [cancelToken] - A [CancelToken] that can be used to cancel the operation + /// * [headers] - Can be used to add additional headers to the request + /// * [extras] - Can be used to add flags to the request + /// * [validateStatus] - A [ValidateStatus] callback that can be used to determine request success based on the HTTP status of the response + /// * [onSendProgress] - A [ProgressCallback] that can be used to get the send progress + /// * [onReceiveProgress] - A [ProgressCallback] that can be used to get the receive progress + /// + /// Returns a [Future] containing a [Response] with a [UserForEventSignupRead] as data + /// Throws [DioException] if API call or serialization fails + Future> eventSignupGetMeForEventSignup({ + required int eventId, + CancelToken? cancelToken, + Map? headers, + Map? extra, + ValidateStatus? validateStatus, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) async { + final _path = r'/event-signup/me/{event_id}'.replaceAll('{' r'event_id' '}', encodeQueryParameter(_serializers, eventId, const FullType(int)).toString()); + final _options = Options( + method: r'GET', + headers: { + ...?headers, + }, + extra: { + 'secure': >[ + { + 'type': 'oauth2', + 'name': 'OAuth2PasswordBearer', + },{ + 'type': 'apiKey', + 'name': 'APIKeyCookie', + 'keyName': '__Secure-fsek_refresh_token', + 'where': '', + }, + ], + ...?extra, + }, + validateStatus: validateStatus, + ); + + final _response = await _dio.request( + _path, + options: _options, + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + + UserForEventSignupRead? _responseData; + + try { + final rawResponse = _response.data; + _responseData = rawResponse == null ? null : _serializers.deserialize( + rawResponse, + specifiedType: const FullType(UserForEventSignupRead), + ) as UserForEventSignupRead; + + } catch (error, stackTrace) { + throw DioException( + requestOptions: _response.requestOptions, + response: _response, + type: DioExceptionType.unknown, + error: error, + stackTrace: stackTrace, + ); + } + + return Response( + data: _responseData, + headers: _response.headers, + isRedirect: _response.isRedirect, + requestOptions: _response.requestOptions, + redirects: _response.redirects, + statusCode: _response.statusCode, + statusMessage: _response.statusMessage, + extra: _response.extra, + ); + } + /// Update Event Signup Route /// /// diff --git a/lib/api_client/lib/src/api/events_api.dart b/lib/api_client/lib/src/api/events_api.dart index 310398e1..9bc38aad 100644 --- a/lib/api_client/lib/src/api/events_api.dart +++ b/lib/api_client/lib/src/api/events_api.dart @@ -269,7 +269,17 @@ class EventsApi { ...?headers, }, extra: { - 'secure': >[], + 'secure': >[ + { + 'type': 'oauth2', + 'name': 'OAuth2PasswordBearer', + },{ + 'type': 'apiKey', + 'name': 'APIKeyCookie', + 'keyName': '__Secure-fsek_refresh_token', + 'where': '', + }, + ], ...?extra, }, validateStatus: validateStatus, diff --git a/lib/api_client/lib/src/model/event_signup_update.dart b/lib/api_client/lib/src/model/event_signup_update.dart index 511cf97d..2459e474 100644 --- a/lib/api_client/lib/src/model/event_signup_update.dart +++ b/lib/api_client/lib/src/model/event_signup_update.dart @@ -9,7 +9,7 @@ import 'package:built_value/serializer.dart'; part 'event_signup_update.g.dart'; -/// EventSignupUpdate +/// This schema uses partial updates, any field not sent to backend will be ignored and not updated. /// /// Properties: /// * [userId] diff --git a/lib/api_client/lib/src/model/user_create.dart b/lib/api_client/lib/src/model/user_create.dart index 13d22c92..abff142e 100644 --- a/lib/api_client/lib/src/model/user_create.dart +++ b/lib/api_client/lib/src/model/user_create.dart @@ -16,6 +16,7 @@ part 'user_create.g.dart'; /// * [isActive] /// * [isSuperuser] /// * [isVerified] +/// * [stilId] /// * [firstName] /// * [lastName] /// * [telephoneNumber] @@ -37,6 +38,9 @@ abstract class UserCreate implements Built { @BuiltValueField(wireName: r'is_verified') bool? get isVerified; + @BuiltValueField(wireName: r'stil_id') + String? get stilId; + @BuiltValueField(wireName: r'first_name') String get firstName; @@ -103,6 +107,13 @@ class _$UserCreateSerializer implements PrimitiveSerializer { specifiedType: const FullType.nullable(bool), ); } + if (object.stilId != null) { + yield r'stil_id'; + yield serializers.serialize( + object.stilId, + specifiedType: const FullType.nullable(String), + ); + } yield r'first_name'; yield serializers.serialize( object.firstName, @@ -186,6 +197,14 @@ class _$UserCreateSerializer implements PrimitiveSerializer { if (valueDes == null) continue; result.isVerified = valueDes; break; + case r'stil_id': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType.nullable(String), + ) as String?; + if (valueDes == null) continue; + result.stilId = valueDes; + break; case r'first_name': final valueDes = serializers.deserialize( value, diff --git a/lib/api_client/lib/src/model/user_update.dart b/lib/api_client/lib/src/model/user_update.dart index 004f7b2a..b3e7c301 100644 --- a/lib/api_client/lib/src/model/user_update.dart +++ b/lib/api_client/lib/src/model/user_update.dart @@ -12,18 +12,21 @@ part 'user_update.g.dart'; /// UserUpdate /// /// Properties: +/// * [stilId] /// * [firstName] /// * [lastName] /// * [startYear] /// * [program] /// * [notifications] -/// * [stilId] /// * [standardFoodPreferences] /// * [otherFoodPreferences] /// * [telephoneNumber] /// * [mooseGameName] @BuiltValue() abstract class UserUpdate implements Built { + @BuiltValueField(wireName: r'stil_id') + String? get stilId; + @BuiltValueField(wireName: r'first_name') String? get firstName; @@ -40,9 +43,6 @@ abstract class UserUpdate implements Built { @BuiltValueField(wireName: r'notifications') bool? get notifications; - @BuiltValueField(wireName: r'stil_id') - String? get stilId; - @BuiltValueField(wireName: r'standard_food_preferences') BuiltList? get standardFoodPreferences; @@ -78,6 +78,13 @@ class _$UserUpdateSerializer implements PrimitiveSerializer { UserUpdate object, { FullType specifiedType = FullType.unspecified, }) sync* { + if (object.stilId != null) { + yield r'stil_id'; + yield serializers.serialize( + object.stilId, + specifiedType: const FullType.nullable(String), + ); + } if (object.firstName != null) { yield r'first_name'; yield serializers.serialize( @@ -113,13 +120,6 @@ class _$UserUpdateSerializer implements PrimitiveSerializer { specifiedType: const FullType.nullable(bool), ); } - if (object.stilId != null) { - yield r'stil_id'; - yield serializers.serialize( - object.stilId, - specifiedType: const FullType.nullable(String), - ); - } if (object.standardFoodPreferences != null) { yield r'standard_food_preferences'; yield serializers.serialize( @@ -171,6 +171,14 @@ class _$UserUpdateSerializer implements PrimitiveSerializer { final key = serializedList[i] as String; final value = serializedList[i + 1]; switch (key) { + case r'stil_id': + final valueDes = serializers.deserialize( + value, + specifiedType: const FullType.nullable(String), + ) as String?; + if (valueDes == null) continue; + result.stilId = valueDes; + break; case r'first_name': final valueDes = serializers.deserialize( value, @@ -211,14 +219,6 @@ class _$UserUpdateSerializer implements PrimitiveSerializer { if (valueDes == null) continue; result.notifications = valueDes; break; - case r'stil_id': - final valueDes = serializers.deserialize( - value, - specifiedType: const FullType.nullable(String), - ) as String?; - if (valueDes == null) continue; - result.stilId = valueDes; - break; case r'standard_food_preferences': final valueDes = serializers.deserialize( value, From e34eb4ed29b4f2735d29b33226422cfdcf660924 Mon Sep 17 00:00:00 2001 From: georgelgeback Date: Tue, 11 Aug 2026 16:52:04 +0200 Subject: [PATCH 6/6] Fix bugs relating to signup error handling, reject users with no group and no priority, hide group select entirely when nollning event and user has no group --- lib/l10n/app_en.arb | 1 + lib/l10n/app_localizations.dart | 6 ++ lib/l10n/app_localizations_en.dart | 4 ++ lib/l10n/app_localizations_sv.dart | 4 ++ lib/l10n/app_sv.arb | 1 + lib/screens/event/event.dart | 108 +++++++++++++++++------------ 6 files changed, 79 insertions(+), 45 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c3e925ef..9295c433 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -51,6 +51,7 @@ "eventSignupErrorTitle": "Signup failed", "eventSignupErrorInvalidGroup": "You cannot sign up with this group!", "eventSignupErrorDeadlinePassed": "Signup deadline for this event has passed!", + "eventSignupErrorNoGroupNoPriority": "You are not a member of any group and have not selected a priority, so you cannot sign up for this event. You are probably simply not a part of the introduction, or event admins have not added your priority group.", "eventSignupErrorClose": "Close", "eventFoodPrefInfo": "You can change these in Settings > Food preferences.", "eventLinkToFoodPrefs": "Shortcut to the settings", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 22713b8e..3728f9cf 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -374,6 +374,12 @@ abstract class AppLocalizations { /// **'Anmälningsperioden för det här eventet är slut!'** String get eventSignupErrorDeadlinePassed; + /// No description provided for @eventSignupErrorNoGroupNoPriority. + /// + /// In sv, this message translates to: + /// **'Du är inte medlem i någon grupp och har inte valt någon prioritet, så du kan inte anmäla dig till det här eventet. Du är troligtvis inte en del av nollningen, eller så har eventansvariga inte lagt till din prioriterade grupp.'** + String get eventSignupErrorNoGroupNoPriority; + /// No description provided for @eventSignupErrorClose. /// /// In sv, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 66324a81..6ec8a6bb 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -151,6 +151,10 @@ class AppLocalizationsEn extends AppLocalizations { String get eventSignupErrorDeadlinePassed => 'Signup deadline for this event has passed!'; + @override + String get eventSignupErrorNoGroupNoPriority => + 'You are not a member of any group and have not selected a priority, so you cannot sign up for this event. You are probably simply not a part of the introduction, or event admins have not added your priority group.'; + @override String get eventSignupErrorClose => 'Close'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 6caecf1d..1cfe8804 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -151,6 +151,10 @@ class AppLocalizationsSv extends AppLocalizations { String get eventSignupErrorDeadlinePassed => 'Anmälningsperioden för det här eventet är slut!'; + @override + String get eventSignupErrorNoGroupNoPriority => + 'Du är inte medlem i någon grupp och har inte valt någon prioritet, så du kan inte anmäla dig till det här eventet. Du är troligtvis inte en del av nollningen, eller så har eventansvariga inte lagt till din prioriterade grupp.'; + @override String get eventSignupErrorClose => 'Stäng'; diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 0b49d143..1cf080b9 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -51,6 +51,7 @@ "eventSignupErrorTitle": "Anmälan misslyckades", "eventSignupErrorInvalidGroup": "Du kan inte anmäla dig med den här gruppen!", "eventSignupErrorDeadlinePassed": "Anmälningsperioden för det här eventet är slut!", + "eventSignupErrorNoGroupNoPriority": "Du är inte medlem i någon grupp och har inte valt någon prioritet, så du kan inte anmäla dig till det här eventet. Du är troligtvis inte en del av nollningen, eller så har eventansvariga inte lagt till din prioriterade grupp.", "eventSignupErrorClose": "Stäng", "eventFoodPrefInfo": "Du kan ändra dessa i Inställningar > Matpreferenser.", "eventLinkToFoodPrefs": "Genväg till inställningarna", diff --git a/lib/screens/event/event.dart b/lib/screens/event/event.dart index fb0f6d63..463d6791 100644 --- a/lib/screens/event/event.dart +++ b/lib/screens/event/event.dart @@ -417,10 +417,9 @@ class _EventPageState extends State { eventSignupCreate: eventSignupCreate, ); } catch (e) { - if (e is DioException && - e.response!.statusCode! >= 400 && - e.response!.statusCode! < 500) { - String? detail = e.response!.data["detail"]; + final int? statusCode = e is DioException ? e.response?.statusCode : null; + if (statusCode != null && statusCode >= 400 && statusCode < 500) { + String? detail = (e as DioException).response!.data["detail"]; return showSignupError(detail); } throw e; @@ -441,21 +440,32 @@ class _EventPageState extends State { } Future showSignupError(String? detail) async { + if (!mounted) return; var t = AppLocalizations.of(context)!; String? detailText; - switch (detail!.toLowerCase()) { - // TODO: this could probably be improved if all response details came as an enum - case "user cannot sign up with this group": - detailText = t.eventSignupErrorInvalidGroup; - break; - case "event signup deadline is passed": - detailText = t.eventSignupErrorDeadlinePassed; - break; - default: - detailText = detail; - break; + // Reject users with no priority and no group with more helpful error message + final bool hasNoGroup = + (user?.groups.isEmpty ?? true) && + (customGroup == null || customGroup!.isEmpty); + final bool hasNoPriority = userType == null || userType!.isEmpty; + + if (hasNoGroup && hasNoPriority) { + detailText = t.eventSignupErrorNoGroupNoPriority; + } else if (detail != null) { + switch (detail.toLowerCase()) { + // TODO: this could probably be improved if all response details came as an enum + case "user cannot sign up with this group": + detailText = t.eventSignupErrorInvalidGroup; + break; + case "event signup deadline is passed": + detailText = t.eventSignupErrorDeadlinePassed; + break; + default: + detailText = detail; + break; + } } return showDialog( @@ -541,39 +551,47 @@ class _EventPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(t.eventChooseGroup), - DropdownButton( - iconEnabledColor: Theme.of(context).colorScheme.onSurface, - isExpanded: true, - menuMaxHeight: MediaQuery.of(context).size.height / 2, - value: group, - onChanged: (GroupRead? newValue) { - setState(() { - group = newValue; - if (newValue == null) { - displayGroupInput = true; - } else { - displayGroupInput = false; - } - }); - }, - items: [ - if (user!.groups.toList().isNotEmpty) - ...user!.groups.map(((GroupRead? g) { - return DropdownMenuItem( - value: g, - child: Text(g!.name), - ); - })), - if (!event!.isNollningEvent) - DropdownMenuItem( - value: null, - child: Text(t.eventOtherDifferent), + Visibility( + visible: !(event!.isNollningEvent && user!.groups.toList().isEmpty), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t.eventChooseGroup), + DropdownButton( + iconEnabledColor: Theme.of(context).colorScheme.onSurface, + isExpanded: true, + menuMaxHeight: MediaQuery.of(context).size.height / 2, + value: group, + onChanged: (GroupRead? newValue) { + setState(() { + group = newValue; + if (newValue == null) { + displayGroupInput = true; + } else { + displayGroupInput = false; + } + }); + }, + items: [ + if (user!.groups.toList().isNotEmpty) + ...user!.groups.map(((GroupRead? g) { + return DropdownMenuItem( + value: g, + child: Text(g!.name), + ); + })), + if (!event!.isNollningEvent) + DropdownMenuItem( + value: null, + child: Text(t.eventOtherDifferent), + ), + ], ), - ], + ], + ), ), Visibility( - visible: displayGroupInput, + visible: displayGroupInput && !event!.isNollningEvent, child: TextField( onChanged: (String? newValue) { setState(() {