feat: Add UiEmptyState widget and integrate it into BankAccountPage and WorkerHomePage for improved empty state handling
This commit is contained in:
@@ -61,12 +61,41 @@ class WorkerHomePage extends StatelessWidget {
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: UiConstants.space4,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
BlocBuilder<HomeCubit, HomeState>(
|
||||
builder: (context, state) {
|
||||
if (state.isProfileComplete) return const SizedBox();
|
||||
return PlaceholderBanner(
|
||||
child: BlocBuilder<HomeCubit, HomeState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.isProfileComplete != current.isProfileComplete,
|
||||
builder: (context, state) {
|
||||
if (!state.isProfileComplete) {
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height -
|
||||
300,
|
||||
child: Column(
|
||||
children: [
|
||||
PlaceholderBanner(
|
||||
title: bannersI18n.complete_profile_title,
|
||||
subtitle: bannersI18n.complete_profile_subtitle,
|
||||
bg: UiColors.primaryInverse,
|
||||
accent: UiColors.primary,
|
||||
onTap: () {
|
||||
Modular.to.toProfile();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space10),
|
||||
Expanded(
|
||||
child: UiEmptyState(
|
||||
icon: UiIcons.users,
|
||||
title: 'Complete Your Profile',
|
||||
description: 'Finish setting up your profile to unlock shifts, view earnings, and start earning today.',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
PlaceholderBanner(
|
||||
title: bannersI18n.complete_profile_title,
|
||||
subtitle: bannersI18n.complete_profile_subtitle,
|
||||
bg: UiColors.primaryInverse,
|
||||
@@ -74,156 +103,156 @@ class WorkerHomePage extends StatelessWidget {
|
||||
onTap: () {
|
||||
Modular.to.toProfile();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Quick Actions
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: QuickActionItem(
|
||||
icon: UiIcons.search,
|
||||
label: quickI18n.find_shifts,
|
||||
onTap: () => Modular.to.toShifts(),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: QuickActionItem(
|
||||
icon: UiIcons.calendar,
|
||||
label: quickI18n.availability,
|
||||
onTap: () => Modular.to.toAvailability(),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: QuickActionItem(
|
||||
icon: UiIcons.dollar,
|
||||
label: quickI18n.earnings,
|
||||
onTap: () => Modular.to.toPayments(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Today's Shifts
|
||||
BlocBuilder<HomeCubit, HomeState>(
|
||||
builder: (context, state) {
|
||||
final shifts = state.todayShifts;
|
||||
return Column(
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Quick Actions
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SectionHeader(
|
||||
title: sectionsI18n.todays_shift,
|
||||
action: shifts.isNotEmpty
|
||||
? sectionsI18n.scheduled_count(
|
||||
count: shifts.length,
|
||||
)
|
||||
: null,
|
||||
Expanded(
|
||||
child: QuickActionItem(
|
||||
icon: UiIcons.search,
|
||||
label: quickI18n.find_shifts,
|
||||
onTap: () => Modular.to.toShifts(),
|
||||
),
|
||||
),
|
||||
if (state.status == HomeStatus.loading)
|
||||
const Center(
|
||||
child: SizedBox(
|
||||
height: UiConstants.space10,
|
||||
width: UiConstants.space10,
|
||||
child: CircularProgressIndicator(
|
||||
color: UiColors.primary,
|
||||
Expanded(
|
||||
child: QuickActionItem(
|
||||
icon: UiIcons.calendar,
|
||||
label: quickI18n.availability,
|
||||
onTap: () => Modular.to.toAvailability(),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: QuickActionItem(
|
||||
icon: UiIcons.dollar,
|
||||
label: quickI18n.earnings,
|
||||
onTap: () => Modular.to.toPayments(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Today's Shifts
|
||||
BlocBuilder<HomeCubit, HomeState>(
|
||||
builder: (context, state) {
|
||||
final shifts = state.todayShifts;
|
||||
return Column(
|
||||
children: [
|
||||
SectionHeader(
|
||||
title: sectionsI18n.todays_shift,
|
||||
action: shifts.isNotEmpty
|
||||
? sectionsI18n.scheduled_count(
|
||||
count: shifts.length,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (state.status == HomeStatus.loading)
|
||||
const Center(
|
||||
child: SizedBox(
|
||||
height: UiConstants.space10,
|
||||
width: UiConstants.space10,
|
||||
child: CircularProgressIndicator(
|
||||
color: UiColors.primary,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (shifts.isEmpty)
|
||||
EmptyStateWidget(
|
||||
message: emptyI18n.no_shifts_today,
|
||||
actionLink: emptyI18n.find_shifts_cta,
|
||||
onAction: () =>
|
||||
Modular.to.toShifts(initialTab: 'find'),
|
||||
)
|
||||
else
|
||||
Column(
|
||||
children: shifts
|
||||
.map(
|
||||
(shift) => ShiftCard(
|
||||
shift: shift,
|
||||
compact: true,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Tomorrow's Shifts
|
||||
BlocBuilder<HomeCubit, HomeState>(
|
||||
builder: (context, state) {
|
||||
final shifts = state.tomorrowShifts;
|
||||
return Column(
|
||||
children: [
|
||||
SectionHeader(title: sectionsI18n.tomorrow),
|
||||
if (shifts.isEmpty)
|
||||
EmptyStateWidget(
|
||||
message: emptyI18n.no_shifts_tomorrow,
|
||||
)
|
||||
else
|
||||
Column(
|
||||
children: shifts
|
||||
.map(
|
||||
(shift) => ShiftCard(
|
||||
shift: shift,
|
||||
compact: true,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Recommended Shifts
|
||||
SectionHeader(title: sectionsI18n.recommended_for_you),
|
||||
BlocBuilder<HomeCubit, HomeState>(
|
||||
builder: (context, state) {
|
||||
if (state.recommendedShifts.isEmpty) {
|
||||
return EmptyStateWidget(
|
||||
message: emptyI18n.no_recommended_shifts,
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
height: 160,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: state.recommendedShifts.length,
|
||||
clipBehavior: Clip.none,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: UiConstants.space3,
|
||||
),
|
||||
child: RecommendedShiftCard(
|
||||
shift: state.recommendedShifts[index],
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (shifts.isEmpty)
|
||||
EmptyStateWidget(
|
||||
message: emptyI18n.no_shifts_today,
|
||||
actionLink: emptyI18n.find_shifts_cta,
|
||||
onAction: () =>
|
||||
Modular.to.toShifts(initialTab: 'find'),
|
||||
)
|
||||
else
|
||||
Column(
|
||||
children: shifts
|
||||
.map(
|
||||
(shift) => ShiftCard(
|
||||
shift: shift,
|
||||
compact: true,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Tomorrow's Shifts
|
||||
BlocBuilder<HomeCubit, HomeState>(
|
||||
builder: (context, state) {
|
||||
final shifts = state.tomorrowShifts;
|
||||
return Column(
|
||||
children: [
|
||||
SectionHeader(title: sectionsI18n.tomorrow),
|
||||
if (shifts.isEmpty)
|
||||
EmptyStateWidget(
|
||||
message: emptyI18n.no_shifts_tomorrow,
|
||||
)
|
||||
else
|
||||
Column(
|
||||
children: shifts
|
||||
.map(
|
||||
(shift) => ShiftCard(
|
||||
shift: shift,
|
||||
compact: true,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Recommended Shifts
|
||||
SectionHeader(title: sectionsI18n.recommended_for_you),
|
||||
BlocBuilder<HomeCubit, HomeState>(
|
||||
builder: (context, state) {
|
||||
if (state.recommendedShifts.isEmpty) {
|
||||
return EmptyStateWidget(
|
||||
message: emptyI18n.no_recommended_shifts,
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
height: 160,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: state.recommendedShifts.length,
|
||||
clipBehavior: Clip.none,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: UiConstants.space3,
|
||||
),
|
||||
child: RecommendedShiftCard(
|
||||
shift: state.recommendedShifts[index],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Benefits
|
||||
BlocBuilder<HomeCubit, HomeState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.benefits != current.benefits,
|
||||
builder: (context, state) {
|
||||
return BenefitsWidget(benefits: state.benefits);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
],
|
||||
// Benefits
|
||||
BlocBuilder<HomeCubit, HomeState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.benefits != current.benefits,
|
||||
builder: (context, state) {
|
||||
return BenefitsWidget(benefits: state.benefits);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -28,10 +28,7 @@ class BankAccountPage extends StatelessWidget {
|
||||
final dynamic strings = t.staff.profile.bank_account_page;
|
||||
|
||||
return Scaffold(
|
||||
appBar: UiAppBar(
|
||||
title: strings.title,
|
||||
showBackButton: true,
|
||||
),
|
||||
appBar: UiAppBar(title: strings.title, showBackButton: true),
|
||||
body: BlocConsumer<BankAccountCubit, BankAccountState>(
|
||||
bloc: cubit,
|
||||
listener: (BuildContext context, BankAccountState state) {
|
||||
@@ -81,34 +78,13 @@ class BankAccountPage extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
SecurityNotice(strings: strings),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
const SizedBox(height: UiConstants.space32),
|
||||
if (state.accounts.isEmpty)
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space10,
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.building,
|
||||
size: 48,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Text(
|
||||
'No accounts yet',
|
||||
style: UiTypography.headline4m,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
'Add your first bank account to get started',
|
||||
style: UiTypography.body2m.textSecondary,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const UiEmptyState(
|
||||
icon: UiIcons.building,
|
||||
title: 'No accounts yet',
|
||||
description:
|
||||
'Add your first bank account to get started',
|
||||
)
|
||||
else ...<Widget>[
|
||||
Text(
|
||||
@@ -119,10 +95,8 @@ class BankAccountPage extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
...state.accounts.map<Widget>(
|
||||
(StaffBankAccount account) => AccountCard(
|
||||
account: account,
|
||||
strings: strings,
|
||||
),
|
||||
(StaffBankAccount account) =>
|
||||
AccountCard(account: account, strings: strings),
|
||||
),
|
||||
],
|
||||
// Add extra padding at bottom
|
||||
|
||||
Reference in New Issue
Block a user