Skip to content

Commit c5ea5fd

Browse files
joebeerninkJoe Beerninkclaude
authored
Add mobile dependent management features for web parity (#3354)
Three features to close the gap between web and mobile for dependent management: 1. PRIVO consent button on My Dependents page - Shows "Consent" button for 13+ dependents needing parental consent - Consent status badges: Needs Consent, Consent Pending, Ready to Invite - Opens PRIVO consent flow in system browser - Invite button now requires consent approval first 2. Dependent waiver signing flow (new DependentWaiverPage) - Parents can sign waivers on behalf of their dependents - Supports single and multi-waiver flows - Waiver text display, agreement checkbox, typed legal name - Navigable from dashboard pending waiver alerts 3. Pending waiver alerts on dashboard - Red alert card at top of dashboard when child needs waivers signed - Shows dependent name, event name, waiver count - Sign button navigates directly to DependentWaiverPage Co-authored-by: Joe Beernink <jobee@microsoft.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6ad55db commit c5ea5fd

12 files changed

Lines changed: 561 additions & 22 deletions

TrashMobMobile/AppShell.xaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,6 @@ public AppShell()
4949
Routing.RegisterRoute(nameof(ChildSignupPage), typeof(ChildSignupPage));
5050
Routing.RegisterRoute(nameof(VerifyIdentityPage), typeof(VerifyIdentityPage));
5151
Routing.RegisterRoute(nameof(SyncStatusPage), typeof(SyncStatusPage));
52+
Routing.RegisterRoute(nameof(DependentWaiverPage), typeof(DependentWaiverPage));
5253
}
5354
}

TrashMobMobile/MauiProgram.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public static MauiApp CreateMauiApp()
105105
builder.Services.AddTransient<ChildSignupPage>();
106106
builder.Services.AddTransient<VerifyIdentityPage>();
107107
builder.Services.AddTransient<SyncStatusPage>();
108+
builder.Services.AddTransient<DependentWaiverPage>();
108109

109110
// ViewModels
110111
builder.Services.AddTransient<CreatePickupLocationViewModel>();
@@ -150,6 +151,7 @@ public static MauiApp CreateMauiApp()
150151
builder.Services.AddTransient<ChildSignupViewModel>();
151152
builder.Services.AddTransient<VerifyIdentityViewModel>();
152153
builder.Services.AddTransient<SyncStatusViewModel>();
154+
builder.Services.AddTransient<DependentWaiverViewModel>();
153155

154156
#if USETEST
155157
builder.Logging.AddDebug();
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
3+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
5+
xmlns:vm="clr-namespace:TrashMobMobile.ViewModels"
6+
x:DataType="vm:DependentWaiverViewModel"
7+
x:Class="TrashMobMobile.Pages.DependentWaiverPage"
8+
Title="Sign Waivers">
9+
<ScrollView>
10+
<VerticalStackLayout Style="{StaticResource OuterVerticalStack}" Spacing="16">
11+
12+
<ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}" />
13+
14+
<!-- List state: show waivers to sign -->
15+
<VerticalStackLayout Spacing="12" IsVisible="{Binding IsListVisible}">
16+
17+
<Label FontFamily="LexendSemibold" FontSize="Large"
18+
HorizontalTextAlignment="Center">
19+
<Label.FormattedText>
20+
<FormattedString>
21+
<Span Text="Waivers for " />
22+
<Span Text="{Binding DependentName}" FontAttributes="Bold" />
23+
</FormattedString>
24+
</Label.FormattedText>
25+
</Label>
26+
27+
<Label Text="{Binding EventName}"
28+
FontSize="Body"
29+
HorizontalTextAlignment="Center"
30+
TextColor="{AppThemeBinding Light={StaticResource MutedForegroundLight}, Dark={StaticResource MutedForegroundDark}}" />
31+
32+
<Label Text="{Binding ProgressText}"
33+
FontSize="Caption"
34+
HorizontalTextAlignment="Center"
35+
TextColor="{AppThemeBinding Light={StaticResource MutedForegroundLight}, Dark={StaticResource MutedForegroundDark}}" />
36+
37+
<CollectionView ItemsSource="{Binding Waivers}" SelectionMode="None">
38+
<CollectionView.ItemTemplate>
39+
<DataTemplate x:DataType="vm:DependentWaiverItem">
40+
<Border Padding="16" Margin="0,0,0,8"
41+
BackgroundColor="{AppThemeBinding Light={StaticResource CardLight}, Dark={StaticResource CardDark}}"
42+
StrokeShape="RoundRectangle 12"
43+
Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
44+
StrokeThickness="1">
45+
<Grid ColumnDefinitions="*, Auto">
46+
<VerticalStackLayout Grid.Column="0" Spacing="4">
47+
<Label Text="{Binding Name}"
48+
FontFamily="LexendSemibold" FontSize="14" />
49+
<Label Text="{Binding StatusText}" FontSize="12"
50+
TextColor="{AppThemeBinding Light={StaticResource MutedForegroundLight}, Dark={StaticResource MutedForegroundDark}}" />
51+
</VerticalStackLayout>
52+
<Button Grid.Column="1" Text="Sign"
53+
Style="{StaticResource PrimaryLargeButton}"
54+
HeightRequest="36" Padding="12,0" FontSize="Small"
55+
IsVisible="{Binding IsSigned, Converter={StaticResource InvertedBoolConverter}}"
56+
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:DependentWaiverViewModel}}, Path=SelectWaiverCommand}"
57+
CommandParameter="{Binding .}" />
58+
<Label Grid.Column="1" Text="" FontSize="24" TextColor="#16a34a"
59+
VerticalOptions="Center" HorizontalOptions="Center"
60+
IsVisible="{Binding IsSigned}" />
61+
</Grid>
62+
</Border>
63+
</DataTemplate>
64+
</CollectionView.ItemTemplate>
65+
</CollectionView>
66+
67+
<Button Text="Done" MaximumWidthRequest="200"
68+
Command="{Binding DoneCommand}"
69+
IsVisible="{Binding AllWaiversSigned}" />
70+
</VerticalStackLayout>
71+
72+
<!-- Signing state: show waiver text and sign form -->
73+
<VerticalStackLayout Spacing="12" IsVisible="{Binding IsSigningWaiver}">
74+
75+
<Label Text="{Binding WaiverName}"
76+
FontFamily="LexendSemibold" FontSize="Large"
77+
HorizontalTextAlignment="Center" />
78+
79+
<Border Padding="12"
80+
BackgroundColor="{AppThemeBinding Light={StaticResource CardLight}, Dark={StaticResource CardDark}}"
81+
StrokeShape="RoundRectangle 8"
82+
Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
83+
StrokeThickness="1">
84+
<Label Text="{Binding WaiverText}" TextType="Html" FontSize="Small" />
85+
</Border>
86+
87+
<HorizontalStackLayout Spacing="8" Margin="0,8,0,0">
88+
<CheckBox IsChecked="{Binding HasAgreed}" />
89+
<Label Text="I have read and agree to the waiver above."
90+
VerticalOptions="Center" FontSize="Small" />
91+
</HorizontalStackLayout>
92+
93+
<VerticalStackLayout Spacing="4">
94+
<Label Text="Type your full legal name to sign:" FontSize="Small"
95+
FontFamily="LexendSemibold" />
96+
<Entry Text="{Binding TypedLegalName}" Placeholder="Full Legal Name" />
97+
</VerticalStackLayout>
98+
99+
<HorizontalStackLayout HorizontalOptions="Center" Spacing="12">
100+
<Button Text="Back"
101+
MaximumWidthRequest="140"
102+
BackgroundColor="Transparent"
103+
TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}"
104+
BorderColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}"
105+
BorderWidth="1"
106+
Command="{Binding BackToListCommand}" />
107+
108+
<Button Text="Sign Waiver"
109+
MaximumWidthRequest="200"
110+
Command="{Binding SignWaiverCommand}" />
111+
</HorizontalStackLayout>
112+
</VerticalStackLayout>
113+
114+
</VerticalStackLayout>
115+
</ScrollView>
116+
</ContentPage>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace TrashMobMobile.Pages;
2+
3+
using TrashMobMobile.ViewModels;
4+
5+
[QueryProperty(nameof(DependentId), nameof(DependentId))]
6+
[QueryProperty(nameof(DependentName), nameof(DependentName))]
7+
[QueryProperty(nameof(EventName), nameof(EventName))]
8+
[QueryProperty(nameof(WaiverVersionIds), nameof(WaiverVersionIds))]
9+
public partial class DependentWaiverPage : ContentPage
10+
{
11+
private readonly DependentWaiverViewModel viewModel;
12+
13+
public DependentWaiverPage(DependentWaiverViewModel viewModel)
14+
{
15+
InitializeComponent();
16+
this.viewModel = viewModel;
17+
this.viewModel.Navigation = Navigation;
18+
BindingContext = this.viewModel;
19+
}
20+
21+
public string DependentId { get; set; } = string.Empty;
22+
23+
public string DependentName { get; set; } = string.Empty;
24+
25+
public string EventName { get; set; } = string.Empty;
26+
27+
public string WaiverVersionIds { get; set; } = string.Empty;
28+
29+
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
30+
{
31+
base.OnNavigatedTo(args);
32+
await viewModel.Init(
33+
new Guid(DependentId),
34+
Uri.UnescapeDataString(DependentName),
35+
Uri.UnescapeDataString(EventName),
36+
WaiverVersionIds);
37+
}
38+
}

TrashMobMobile/Pages/MyDashboardPage.xaml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,64 @@
88
xmlns:controls="clr-namespace:TrashMobMobile.Controls"
99
xmlns:tabs="http://sharpnado.com"
1010
xmlns:views="clr-namespace:TrashMobMobile.Views.MyDashboard"
11+
xmlns:poco="clr-namespace:TrashMob.Models.Poco.V2;assembly=TrashMob.Models"
1112
Title="My Dashboard">
1213

1314
<ScrollView>
1415
<Grid>
1516
<VerticalStackLayout Style="{StaticResource OuterVerticalStack}">
17+
18+
<!-- Pending Waiver Alerts -->
19+
<Border Padding="12" Margin="5,5"
20+
BackgroundColor="{AppThemeBinding Light=#fef2f2, Dark=#450a0a}"
21+
StrokeShape="RoundRectangle 10"
22+
Stroke="{AppThemeBinding Light=#fca5a5, Dark=#dc2626}"
23+
StrokeThickness="1"
24+
IsVisible="{Binding HasPendingWaiverAlerts}">
25+
<VerticalStackLayout Spacing="8">
26+
<Label Text="Waiver Signatures Required"
27+
FontFamily="LexendSemibold" FontSize="14"
28+
TextColor="{AppThemeBinding Light=#991b1b, Dark=#fca5a5}" />
29+
<Label Text="Your child has registered for an event that requires your signature on a waiver."
30+
FontSize="13"
31+
TextColor="{AppThemeBinding Light=#991b1b, Dark=#fecaca}" />
32+
<CollectionView ItemsSource="{Binding PendingWaiverAlerts}" SelectionMode="None">
33+
<CollectionView.ItemTemplate>
34+
<DataTemplate x:DataType="poco:PendingDependentWaiverDto">
35+
<Border Padding="10" Margin="0,4"
36+
BackgroundColor="{AppThemeBinding Light=#ffffff, Dark=#1c1917}"
37+
StrokeShape="RoundRectangle 8"
38+
Stroke="{AppThemeBinding Light=#fca5a5, Dark=#7f1d1d}"
39+
StrokeThickness="1">
40+
<Grid ColumnDefinitions="*, Auto" RowDefinitions="Auto, Auto">
41+
<Label Grid.Row="0" Grid.Column="0"
42+
FontFamily="LexendSemibold" FontSize="13">
43+
<Label.FormattedText>
44+
<FormattedString>
45+
<Span Text="{Binding DependentFirstName}" />
46+
<Span Text="" />
47+
<Span Text="{Binding EventName}" />
48+
</FormattedString>
49+
</Label.FormattedText>
50+
</Label>
51+
<Label Grid.Row="1" Grid.Column="0" FontSize="12"
52+
TextColor="{AppThemeBinding Light={StaticResource MutedForegroundLight}, Dark={StaticResource MutedForegroundDark}}"
53+
Text="{Binding RequiredWaivers.Count, StringFormat='{0} waiver(s) to sign'}" />
54+
<Button Grid.Row="0" Grid.RowSpan="2" Grid.Column="1"
55+
Text="Sign"
56+
Style="{StaticResource PrimaryLargeButton}"
57+
HeightRequest="36" Padding="12,0" FontSize="Small"
58+
VerticalOptions="Center"
59+
Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:MyDashboardViewModel}}, Path=SignDependentWaiversCommand}"
60+
CommandParameter="{Binding .}" />
61+
</Grid>
62+
</Border>
63+
</DataTemplate>
64+
</CollectionView.ItemTemplate>
65+
</CollectionView>
66+
</VerticalStackLayout>
67+
</Border>
68+
1669
<Border Style="{x:StaticResource RoundBorder}" Margin="5,5">
1770
<Grid RowDefinitions="Auto, Auto, Auto, Auto" ColumnDefinitions="*, *, *, *">
1871
<Label Text="Here's what you have done so far!" Grid.Row="0" Grid.Column="0"

TrashMobMobile/Pages/MyDependentsPage.xaml

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,15 @@
107107
<DataTrigger TargetType="Border" Binding="{Binding HasActiveInvitation}" Value="True">
108108
<Setter Property="BackgroundColor" Value="#dbeafe" />
109109
</DataTrigger>
110-
<DataTrigger TargetType="Border" Binding="{Binding IsEligibleForInvite}" Value="True">
110+
<DataTrigger TargetType="Border" Binding="{Binding IsConsentPending}" Value="True">
111+
<Setter Property="BackgroundColor" Value="#fef3c7" />
112+
</DataTrigger>
113+
<DataTrigger TargetType="Border" Binding="{Binding NeedsConsent}" Value="True">
111114
<Setter Property="BackgroundColor" Value="#fef3c7" />
112115
</DataTrigger>
116+
<DataTrigger TargetType="Border" Binding="{Binding IsEligibleForInvite}" Value="True">
117+
<Setter Property="BackgroundColor" Value="#dbeafe" />
118+
</DataTrigger>
113119
</Style.Triggers>
114120
</Style>
115121
</Border.Style>
@@ -125,9 +131,15 @@
125131
<DataTrigger TargetType="Label" Binding="{Binding HasActiveInvitation}" Value="True">
126132
<Setter Property="TextColor" Value="#1e40af" />
127133
</DataTrigger>
128-
<DataTrigger TargetType="Label" Binding="{Binding IsEligibleForInvite}" Value="True">
134+
<DataTrigger TargetType="Label" Binding="{Binding IsConsentPending}" Value="True">
135+
<Setter Property="TextColor" Value="#92400e" />
136+
</DataTrigger>
137+
<DataTrigger TargetType="Label" Binding="{Binding NeedsConsent}" Value="True">
129138
<Setter Property="TextColor" Value="#92400e" />
130139
</DataTrigger>
140+
<DataTrigger TargetType="Label" Binding="{Binding IsEligibleForInvite}" Value="True">
141+
<Setter Property="TextColor" Value="#1e40af" />
142+
</DataTrigger>
131143
</Style.Triggers>
132144
</Style>
133145
</Label.Style>
@@ -137,7 +149,17 @@
137149

138150
<!-- Action buttons -->
139151
<HorizontalStackLayout Spacing="8" HorizontalOptions="End">
140-
<!-- Invite button (eligible dependents) -->
152+
<!-- Consent button (needs PRIVO consent) -->
153+
<Button Text="Consent"
154+
Style="{StaticResource PrimaryLargeButton}"
155+
HeightRequest="36"
156+
Padding="12,0"
157+
FontSize="Small"
158+
IsVisible="{Binding NeedsConsent}"
159+
Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:MyDependentsViewModel}}, Path=StartConsentCommand}"
160+
CommandParameter="{Binding .}" />
161+
162+
<!-- Invite button (consent approved, ready to invite) -->
141163
<Button Text="Invite"
142164
Style="{StaticResource PrimaryLargeButton}"
143165
HeightRequest="36"

0 commit comments

Comments
 (0)