feat: Implement notice and file types banners for attire upload and enhance incomplete profile messaging
This commit is contained in:
@@ -1318,7 +1318,7 @@
|
|||||||
},
|
},
|
||||||
"find_shifts": {
|
"find_shifts": {
|
||||||
"incomplete_profile_banner_title": "Your account isn't complete yet.",
|
"incomplete_profile_banner_title": "Your account isn't complete yet.",
|
||||||
"incomplete_profile_banner_message": "You won't be able to apply for shifts until your account is fully set up. Complete your account now to unlock shift applications and start getting matched with opportunities.",
|
"incomplete_profile_banner_message": "Complete your account now to unlock shift applications and start getting matched with opportunities.",
|
||||||
"incomplete_profile_cta": "Complete your account now",
|
"incomplete_profile_cta": "Complete your account now",
|
||||||
"search_hint": "Search jobs, location...",
|
"search_hint": "Search jobs, location...",
|
||||||
"filter_all": "All Jobs",
|
"filter_all": "All Jobs",
|
||||||
|
|||||||
@@ -1313,7 +1313,7 @@
|
|||||||
},
|
},
|
||||||
"find_shifts": {
|
"find_shifts": {
|
||||||
"incomplete_profile_banner_title": "Tu cuenta aún no está completa.",
|
"incomplete_profile_banner_title": "Tu cuenta aún no está completa.",
|
||||||
"incomplete_profile_banner_message": "No podrás solicitar turnos hasta que tu cuenta esté completamente configurada. Completa tu cuenta ahora para desbloquear las solicitudes de turnos y empezar a recibir oportunidades.",
|
"incomplete_profile_banner_message": "Completa tu cuenta ahora para desbloquear las solicitudes de turnos y empezar a recibir oportunidades.",
|
||||||
"incomplete_profile_cta": "Completa tu cuenta ahora",
|
"incomplete_profile_cta": "Completa tu cuenta ahora",
|
||||||
"search_hint": "Buscar trabajos, ubicaci\u00f3n...",
|
"search_hint": "Buscar trabajos, ubicaci\u00f3n...",
|
||||||
"filter_all": "Todos",
|
"filter_all": "Todos",
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ export 'src/widgets/ui_button.dart';
|
|||||||
export 'src/widgets/ui_chip.dart';
|
export 'src/widgets/ui_chip.dart';
|
||||||
export 'src/widgets/ui_loading_page.dart';
|
export 'src/widgets/ui_loading_page.dart';
|
||||||
export 'src/widgets/ui_snackbar.dart';
|
export 'src/widgets/ui_snackbar.dart';
|
||||||
|
export 'src/widgets/ui_notice_banner.dart';
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../ui_constants.dart';
|
||||||
|
|
||||||
|
/// A customizable notice banner widget for displaying informational messages.
|
||||||
|
///
|
||||||
|
/// [UiNoticeBanner] displays a message with an optional icon and supports
|
||||||
|
/// custom styling through title and description text.
|
||||||
|
class UiNoticeBanner extends StatelessWidget {
|
||||||
|
/// Creates a [UiNoticeBanner].
|
||||||
|
const UiNoticeBanner({
|
||||||
|
super.key,
|
||||||
|
this.icon,
|
||||||
|
required this.title,
|
||||||
|
this.description,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.borderRadius,
|
||||||
|
this.padding,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The icon to display on the left side.
|
||||||
|
/// Defaults to null. The icon will be rendered with primary color and 24pt size.
|
||||||
|
final IconData? icon;
|
||||||
|
|
||||||
|
/// The title text to display.
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
/// Optional description text to display below the title.
|
||||||
|
final String? description;
|
||||||
|
|
||||||
|
/// The background color of the banner.
|
||||||
|
/// Defaults to [UiColors.primary] with 8% opacity.
|
||||||
|
final Color? backgroundColor;
|
||||||
|
|
||||||
|
/// The border radius of the banner.
|
||||||
|
/// Defaults to [UiConstants.radiusLg].
|
||||||
|
final BorderRadius? borderRadius;
|
||||||
|
|
||||||
|
/// The padding around the banner content.
|
||||||
|
/// Defaults to [UiConstants.space4] on all sides.
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: padding ?? const EdgeInsets.all(UiConstants.space4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor ?? UiColors.primary.withValues(alpha: 0.08),
|
||||||
|
borderRadius: borderRadius ?? UiConstants.radiusLg,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
if (icon != null) ...<Widget>[
|
||||||
|
Icon(icon, color: UiColors.primary, size: 24),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
],
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: UiTypography.body2m.textPrimary,
|
||||||
|
),
|
||||||
|
if (description != null) ...<Widget>[
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
description!,
|
||||||
|
style: UiTypography.body2r.textSecondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
import 'package:staff_attire/src/presentation/blocs/attire_capture/attire_capture_cubit.dart';
|
import 'package:staff_attire/src/presentation/blocs/attire_capture/attire_capture_cubit.dart';
|
||||||
import 'package:staff_attire/src/presentation/blocs/attire_capture/attire_capture_state.dart';
|
import 'package:staff_attire/src/presentation/blocs/attire_capture/attire_capture_state.dart';
|
||||||
|
|
||||||
|
import '../widgets/attire_capture_page/file_types_banner.dart';
|
||||||
import '../widgets/attire_capture_page/footer_section.dart';
|
import '../widgets/attire_capture_page/footer_section.dart';
|
||||||
import '../widgets/attire_capture_page/image_preview_section.dart';
|
import '../widgets/attire_capture_page/image_preview_section.dart';
|
||||||
import '../widgets/attire_capture_page/info_section.dart';
|
import '../widgets/attire_capture_page/info_section.dart';
|
||||||
@@ -286,7 +287,7 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_FileTypesBanner(
|
FileTypesBanner(
|
||||||
message: t
|
message: t
|
||||||
.staff_profile_attire
|
.staff_profile_attire
|
||||||
.upload_file_types_banner,
|
.upload_file_types_banner,
|
||||||
@@ -335,35 +336,3 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Banner displaying accepted file types and size limit for attire upload.
|
|
||||||
class _FileTypesBanner extends StatelessWidget {
|
|
||||||
const _FileTypesBanner({required this.message});
|
|
||||||
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space4,
|
|
||||||
vertical: UiConstants.space3,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.primary.withAlpha(20),
|
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
const Icon(UiIcons.info, size: 20, color: UiColors.primary),
|
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
Expanded(
|
|
||||||
child: Text(message, style: UiTypography.body2r.textSecondary),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Banner displaying accepted file types and size limit for attire upload.
|
||||||
|
class FileTypesBanner extends StatelessWidget {
|
||||||
|
/// Creates a [FileTypesBanner].
|
||||||
|
const FileTypesBanner({super.key, required this.message});
|
||||||
|
|
||||||
|
/// The message to display in the banner.
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: UiConstants.space3,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.primary.withAlpha(20),
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
const Icon(UiIcons.info, size: 20, color: UiColors.primary),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
Expanded(
|
||||||
|
child: Text(message, style: UiTypography.body2r.textSecondary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,35 +7,10 @@ class AttireInfoCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return UiNoticeBanner(
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
icon: UiIcons.shirt,
|
||||||
decoration: BoxDecoration(
|
title: t.staff_profile_attire.info_card.title,
|
||||||
color: UiColors.primary.withValues(alpha: 0.08),
|
description: t.staff_profile_attire.info_card.description,
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
const Icon(UiIcons.shirt, color: UiColors.primary, size: 24),
|
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
t.staff_profile_attire.info_card.title,
|
|
||||||
style: UiTypography.body2m.textPrimary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
t.staff_profile_attire.info_card.description,
|
|
||||||
style: UiTypography.body2r.textSecondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:krow_core/core.dart';
|
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import 'package:core_localization/core_localization.dart';
|
|
||||||
import '../../blocs/shifts/shifts_bloc.dart';
|
import '../../blocs/shifts/shifts_bloc.dart';
|
||||||
import '../my_shift_card.dart';
|
import '../my_shift_card.dart';
|
||||||
import '../shared/empty_state_view.dart';
|
import '../shared/empty_state_view.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
|
||||||
|
|
||||||
class FindShiftsTab extends StatefulWidget {
|
class FindShiftsTab extends StatefulWidget {
|
||||||
final List<Shift> availableJobs;
|
final List<Shift> availableJobs;
|
||||||
@@ -316,11 +315,21 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
|||||||
children: [
|
children: [
|
||||||
// Incomplete profile banner
|
// Incomplete profile banner
|
||||||
if (!widget.profileComplete) ...[
|
if (!widget.profileComplete) ...[
|
||||||
_IncompleteProfileBanner(
|
Container(
|
||||||
title: context.t.staff_shifts.find_shifts.incomplete_profile_banner_title,
|
padding: const EdgeInsets.all(UiConstants.space4),
|
||||||
message: context.t.staff_shifts.find_shifts.incomplete_profile_banner_message,
|
child: UiNoticeBanner(
|
||||||
ctaText: context.t.staff_shifts.find_shifts.incomplete_profile_cta,
|
icon: UiIcons.sparkles,
|
||||||
onCtaPressed: () => Modular.to.toProfile(),
|
title: context
|
||||||
|
.t
|
||||||
|
.staff_shifts
|
||||||
|
.find_shifts
|
||||||
|
.incomplete_profile_banner_title,
|
||||||
|
description: context
|
||||||
|
.t
|
||||||
|
.staff_shifts
|
||||||
|
.find_shifts
|
||||||
|
.incomplete_profile_banner_message,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
// Search and Filters
|
// Search and Filters
|
||||||
@@ -462,9 +471,7 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
|||||||
? () {
|
? () {
|
||||||
BlocProvider.of<ShiftsBloc>(
|
BlocProvider.of<ShiftsBloc>(
|
||||||
context,
|
context,
|
||||||
).add(
|
).add(AcceptShiftEvent(shift.id));
|
||||||
AcceptShiftEvent(shift.id),
|
|
||||||
);
|
|
||||||
UiSnackbar.show(
|
UiSnackbar.show(
|
||||||
context,
|
context,
|
||||||
message: context
|
message: context
|
||||||
@@ -488,54 +495,3 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Banner shown when the worker's profile is incomplete.
|
|
||||||
class _IncompleteProfileBanner extends StatelessWidget {
|
|
||||||
const _IncompleteProfileBanner({
|
|
||||||
required this.title,
|
|
||||||
required this.message,
|
|
||||||
required this.ctaText,
|
|
||||||
required this.onCtaPressed,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
final String message;
|
|
||||||
final String ctaText;
|
|
||||||
final VoidCallback onCtaPressed;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
margin: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.tagPending,
|
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
|
||||||
border: Border.all(
|
|
||||||
color: UiColors.textWarning.withValues(alpha: 0.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: UiTypography.body2b.textPrimary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space2),
|
|
||||||
Text(
|
|
||||||
message,
|
|
||||||
style: UiTypography.body3r.textSecondary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
UiButton.primary(
|
|
||||||
text: ctaText,
|
|
||||||
onPressed: onCtaPressed,
|
|
||||||
size: UiButtonSize.small,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user