Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import 'package:anystep/core/firebase/firebase.dart';
import 'package:anystep/env/env.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:anystep/core/config/router/url_strategy.dart';
import 'package:posthog_flutter/posthog_flutter.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

void bootstrap(FutureOr<Widget> Function() builder) async {
setPathUrlStrategy();

// Ensure that Flutter is initialized
WidgetsFlutterBinding.ensureInitialized();

Expand Down
4 changes: 4 additions & 0 deletions lib/core/config/router/url_strategy.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import 'url_strategy_stub.dart'
if (dart.library.html) 'url_strategy_web.dart';

void setPathUrlStrategy() => setUrlStrategy();
1 change: 1 addition & 0 deletions lib/core/config/router/url_strategy_stub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
void setUrlStrategy() {}
5 changes: 5 additions & 0 deletions lib/core/config/router/url_strategy_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:flutter_web_plugins/flutter_web_plugins.dart';

void setUrlStrategy() {
usePathUrlStrategy();
}
2 changes: 1 addition & 1 deletion lib/core/config/theme/theme_mode.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions lib/core/features/auth/data/auth_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,21 @@ class AuthRepository {
await _supabase.signInWithPassword(email: email, password: password);
return null;
} on AuthApiException catch (e) {
if (e.code == "email_not_confirmed") {
Log.w('Email not confirmed for user: $email');
return 'Please confirm your email address and try again. An email has been sent to you with instructions.';
switch (e.code) {
case "email_not_confirmed":
{
Log.w('Email not confirmed for user: $email');
return 'Please confirm your email address and try again. An email has been sent to you with instructions.';
}
case "over_email_send_rate_limit":
{
Log.w('Signup email rate limit exceeded for user: $email');
return 'Too many signup attempts. Please wait and try again later.';
}
default:
Log.e('Signup failed for user: $email', e);
return 'Signup failed. Please try again.';
}
return 'Signup failed. Please try again.';
} catch (e, st) {
Log.e('Signup failed', e, st);
return 'An error occurred';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:anystep/core/common/constants/spacing.dart';
import 'package:anystep/core/common/widgets/any_step_app_bar.dart';
import 'package:anystep/core/common/widgets/any_step_scaffold.dart';
import 'package:anystep/core/common/widgets/max_width_container.dart';
import 'package:anystep/core/features/auth/presentation/login/login_screen.dart';
Expand All @@ -16,19 +15,44 @@ class EmailConfirmedScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
final params = GoRouterState.of(context).uri.queryParameters;
final errorDescription = params['error_description'];
final errorCode = params['error_code'];
final hasError =
(params['error']?.isNotEmpty ?? false) ||
(errorCode?.isNotEmpty ?? false) ||
(errorDescription?.isNotEmpty ?? false);
final errorMessage = errorDescription ?? errorCode ?? params['error'];
return AnyStepScaffold(
appBar: AnyStepAppBar(title: Text(loc.emailConfirmedTitle)),
body: MaxWidthContainer(
child: Padding(
padding: const EdgeInsets.all(AnyStepSpacing.md16),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(loc.emailConfirmedTitle, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: AnyStepSpacing.sm8),
Text(loc.emailConfirmedMessage, style: Theme.of(context).textTheme.bodyMedium),
const SizedBox(height: AnyStepSpacing.md16),
if (hasError) ...[
Icon(Icons.error_outline, size: 64, color: Theme.of(context).colorScheme.error),
const SizedBox(height: AnyStepSpacing.md16),
Text(
errorMessage ?? loc.failedToLoad,
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(color: Theme.of(context).colorScheme.error),
textAlign: TextAlign.center,
),
] else ...[
Icon(
Icons.check_circle_outline,
size: 64,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: AnyStepSpacing.md16),
Text(loc.emailConfirmedTitle, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: AnyStepSpacing.sm8),
Text(loc.emailConfirmedMessage, style: Theme.of(context).textTheme.bodyMedium),
],
const SizedBox(height: AnyStepSpacing.md24),
ElevatedButton(
onPressed: () => context.go(LoginScreen.path),
child: Text(loc.login),
Expand Down
13 changes: 12 additions & 1 deletion lib/core/features/auth/presentation/sign_up/sign_up_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import 'package:anystep/l10n/generated/app_localizations.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:go_router/go_router.dart';

import 'sign_up_screen_controller.dart';
import 'package:anystep/core/features/auth/presentation/confirm_email_screen.dart';

class SignUpScreen extends ConsumerStatefulWidget {
const SignUpScreen({super.key});
Expand All @@ -28,6 +30,12 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
final state = ref.watch(signUpScreenControllerProvider);
final loc = AppLocalizations.of(context);

ref.listen(signUpScreenControllerProvider, (previous, next) {
if (next.needsEmailConfirmation) {
context.go(ConfirmEmailScreen.path);
}
});

return AnyStepScaffold(
appBar: AnyStepAppBar(title: Text(loc.backToLogin)),
body: MaxWidthContainer(
Expand Down Expand Up @@ -77,8 +85,11 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
labelText: loc.password,
obscureText: true,
validator: FormBuilderValidators.password(
minLowercaseCount: 0,
minUppercaseCount: 0,
minNumberCount: 0,
minSpecialCharCount: 0,
errorText: '',
errorText: loc.passwordMinLength,
),
),
AnyStepTextField(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,18 @@ class SignUpScreenController extends _$SignUpScreenController {
);
if (result == null) {
await ref.read(eventNotificationsControllerProvider.notifier).enableOnSignup();
state = state.copyWith(isLoading: false, success: true);
state = state.copyWith(isLoading: false, success: true, needsEmailConfirmation: false);
Log.d('Sign up successful, user created');
} else if (result.contains('confirm your email address')) {
state = state.copyWith(
isLoading: false,
success: true,
needsEmailConfirmation: true,
error: null,
);
} else {
state = state.copyWith(isLoading: false, error: result);
state = state.copyWith(isLoading: false, error: result, needsEmailConfirmation: false);
Log.d('Sign up failed: $result');
}
} catch (e, st) {
Log.e("Error signing up", e, st);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ abstract class SignUpScreenState with _$SignUpScreenState {
const factory SignUpScreenState({
@Default(false) bool isLoading,
@Default(false) bool success,
@Default(false) bool needsEmailConfirmation,
String? error,
}) = _SignUpScreenState;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion lib/core/features/profile/data/current_user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ Stream<UserModel?> currentUserStream(Ref ref) async* {
}

try {
if (authState.value == null) throw Exception('No user logged in');
if (authState.isLoading) {
final resolved = await ref.watch(authStateStreamProvider.future);
if (resolved == null) throw Exception('No user logged in');
} else if (authState.value == null) {
throw Exception('No user logged in');
}

final user = await ref
.read(userRepositoryProvider)
Expand Down
2 changes: 1 addition & 1 deletion lib/core/features/profile/data/current_user.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading