Frontend first draft

This commit is contained in:
Arseni
2025-11-13 15:06:15 +03:00
parent e47f343afb
commit ddb54ddfdc
504 changed files with 25498 additions and 1 deletions

View File

@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
class ErrorMessage extends StatelessWidget {
final String error;
const ErrorMessage({super.key, required this.error});
@override
Widget build(BuildContext context) => Text(
error,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.error,
),
);
}

View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:pin_code_fields/pin_code_fields.dart';
class TwoFactorCodeInput extends StatelessWidget {
final void Function(String) onCompleted;
const TwoFactorCodeInput({super.key, required this.onCompleted});
@override
Widget build(BuildContext context) => Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 300),
child: PinCodeTextField(
length: 6,
appContext: context,
autoFocus: true,
keyboardType: TextInputType.number,
animationType: AnimationType.fade,
pinTheme: PinTheme(
shape: PinCodeFieldShape.box,
borderRadius: BorderRadius.circular(4),
fieldHeight: 48,
fieldWidth: 40,
inactiveColor: Theme.of(context).dividerColor,
activeColor: Theme.of(context).colorScheme.primary,
selectedColor: Theme.of(context).colorScheme.primary,
),
onCompleted: onCompleted,
onChanged: (_) {},
),
),
);
}

View File

@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:pweb/pages/2fa/error_message.dart';
import 'package:pweb/pages/2fa/input.dart';
import 'package:pweb/pages/2fa/prompt.dart';
import 'package:pweb/pages/2fa/resend.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'package:pweb/providers/two_factor.dart';
class TwoFactorCodePage extends StatelessWidget {
final VoidCallback onVerificationSuccess;
const TwoFactorCodePage({
super.key,
required this.onVerificationSuccess,
});
@override
Widget build(BuildContext context) {
return Consumer<TwoFactorProvider>(
builder: (context, provider, child) {
if (provider.verificationSuccess) {
WidgetsBinding.instance.addPostFrameCallback((_) {
onVerificationSuccess();
});
}
return Scaffold(
appBar: AppBar(title: const Text('')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const TwoFactorPromptText(),
const SizedBox(height: 32),
TwoFactorCodeInput(
onCompleted: (code) => provider.submitCode(code),
),
const SizedBox(height: 24),
if (provider.isSubmitting)
const Center(child: CircularProgressIndicator())
else
const ResendCodeButton(),
if (provider.hasError) ...[
const SizedBox(height: 12),
ErrorMessage(error: AppLocalizations.of(context)!.twoFactorError),
],
],
),
),
);
},
);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class TwoFactorPromptText extends StatelessWidget {
const TwoFactorPromptText({super.key});
@override
Widget build(BuildContext context) => Text(
AppLocalizations.of(context)!.twoFactorPrompt,
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
);
}

View File

@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class ResendCodeButton extends StatelessWidget {
const ResendCodeButton({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final localizations = AppLocalizations.of(context)!;
return TextButton(
onPressed: () {
// TODO: Add resend logic
},
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: const Size(0, 0),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
alignment: Alignment.centerLeft,
foregroundColor: theme.colorScheme.primary,
textStyle: theme.textTheme.bodyMedium?.copyWith(
decoration: TextDecoration.underline,
),
),
child: Text(localizations.twoFactorResend),
);
}
}