1
0
mirror of https://github.com/immich-app/immich.git synced 2025-07-16 07:24:40 +02:00

feat(web): improved user onboarding (#18782)

* wip

* added user metadata key

* wip

* restructure onboarding system and add initial locale

* update language card and fix translation updating

* remove prints

* new card formattings

* fix cursed unmount effect

* add OAuth route onboarding

* remove required admin auth for onboarding

* delete the hotwire button

* update open-api files

* delete import

* fix failing oauth onboarding fields

* fix e2e test

* fix web e2e test

* add onboarding to user registration e2e test

* remove todo

this was a holdover during dev and didn't get deleted

* fix server small tests

* use onDestroy to save settings rather than a bind:this

* change to false for isOnboarded

* fix other auth small test

* provide type annotation in user factory metadata field

* remove onboardingCompelted from UserDto

* move translations to onboarding steps array and mark as derived so they update

* break language selector out into its own component as per @danieldietzler suggestion

* remove hello header on card

* fix flixkering on server privacy card

* label/id fixes

* openapi

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Brandon Wees
2025-06-02 16:09:13 -05:00
committed by GitHub
parent e7d7886f44
commit 74438f5bd8
36 changed files with 961 additions and 235 deletions

View File

@ -15,6 +15,7 @@ class LoginResponseDto {
LoginResponseDto({
required this.accessToken,
required this.isAdmin,
required this.isOnboarded,
required this.name,
required this.profileImagePath,
required this.shouldChangePassword,
@ -26,6 +27,8 @@ class LoginResponseDto {
bool isAdmin;
bool isOnboarded;
String name;
String profileImagePath;
@ -40,6 +43,7 @@ class LoginResponseDto {
bool operator ==(Object other) => identical(this, other) || other is LoginResponseDto &&
other.accessToken == accessToken &&
other.isAdmin == isAdmin &&
other.isOnboarded == isOnboarded &&
other.name == name &&
other.profileImagePath == profileImagePath &&
other.shouldChangePassword == shouldChangePassword &&
@ -51,6 +55,7 @@ class LoginResponseDto {
// ignore: unnecessary_parenthesis
(accessToken.hashCode) +
(isAdmin.hashCode) +
(isOnboarded.hashCode) +
(name.hashCode) +
(profileImagePath.hashCode) +
(shouldChangePassword.hashCode) +
@ -58,12 +63,13 @@ class LoginResponseDto {
(userId.hashCode);
@override
String toString() => 'LoginResponseDto[accessToken=$accessToken, isAdmin=$isAdmin, name=$name, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, userEmail=$userEmail, userId=$userId]';
String toString() => 'LoginResponseDto[accessToken=$accessToken, isAdmin=$isAdmin, isOnboarded=$isOnboarded, name=$name, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, userEmail=$userEmail, userId=$userId]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'accessToken'] = this.accessToken;
json[r'isAdmin'] = this.isAdmin;
json[r'isOnboarded'] = this.isOnboarded;
json[r'name'] = this.name;
json[r'profileImagePath'] = this.profileImagePath;
json[r'shouldChangePassword'] = this.shouldChangePassword;
@ -83,6 +89,7 @@ class LoginResponseDto {
return LoginResponseDto(
accessToken: mapValueOfType<String>(json, r'accessToken')!,
isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
isOnboarded: mapValueOfType<bool>(json, r'isOnboarded')!,
name: mapValueOfType<String>(json, r'name')!,
profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
@ -137,6 +144,7 @@ class LoginResponseDto {
static const requiredKeys = <String>{
'accessToken',
'isAdmin',
'isOnboarded',
'name',
'profileImagePath',
'shouldChangePassword',

View File

@ -0,0 +1,99 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class OnboardingDto {
/// Returns a new [OnboardingDto] instance.
OnboardingDto({
required this.isOnboarded,
});
bool isOnboarded;
@override
bool operator ==(Object other) => identical(this, other) || other is OnboardingDto &&
other.isOnboarded == isOnboarded;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(isOnboarded.hashCode);
@override
String toString() => 'OnboardingDto[isOnboarded=$isOnboarded]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'isOnboarded'] = this.isOnboarded;
return json;
}
/// Returns a new [OnboardingDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static OnboardingDto? fromJson(dynamic value) {
upgradeDto(value, "OnboardingDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return OnboardingDto(
isOnboarded: mapValueOfType<bool>(json, r'isOnboarded')!,
);
}
return null;
}
static List<OnboardingDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <OnboardingDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = OnboardingDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, OnboardingDto> mapFromJson(dynamic json) {
final map = <String, OnboardingDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = OnboardingDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of OnboardingDto-objects as value to a dart map
static Map<String, List<OnboardingDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<OnboardingDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = OnboardingDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'isOnboarded',
};
}

View File

@ -0,0 +1,99 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class OnboardingResponseDto {
/// Returns a new [OnboardingResponseDto] instance.
OnboardingResponseDto({
required this.isOnboarded,
});
bool isOnboarded;
@override
bool operator ==(Object other) => identical(this, other) || other is OnboardingResponseDto &&
other.isOnboarded == isOnboarded;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(isOnboarded.hashCode);
@override
String toString() => 'OnboardingResponseDto[isOnboarded=$isOnboarded]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'isOnboarded'] = this.isOnboarded;
return json;
}
/// Returns a new [OnboardingResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static OnboardingResponseDto? fromJson(dynamic value) {
upgradeDto(value, "OnboardingResponseDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return OnboardingResponseDto(
isOnboarded: mapValueOfType<bool>(json, r'isOnboarded')!,
);
}
return null;
}
static List<OnboardingResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <OnboardingResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = OnboardingResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, OnboardingResponseDto> mapFromJson(dynamic json) {
final map = <String, OnboardingResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = OnboardingResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of OnboardingResponseDto-objects as value to a dart map
static Map<String, List<OnboardingResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<OnboardingResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = OnboardingResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'isOnboarded',
};
}