Merge pull request #365 from Oloodi/312-feature-integrate-google-maps-places-autocomplete-for-hub-address-validation

Continuation of the mobile apps developement
This commit is contained in:
Achintha Isuru
2026-02-03 23:05:35 -05:00
committed by GitHub
14 changed files with 354 additions and 125 deletions

View File

@@ -14,3 +14,5 @@
- line 125 remove redundant location values. - line 125 remove redundant location values.
- Need to clarify the difference b/w `case dc.ApplicationStatus.ACCEPTED` and `case dc.ApplicationStatus.CONFIRMED`. - Need to clarify the difference b/w `case dc.ApplicationStatus.ACCEPTED` and `case dc.ApplicationStatus.CONFIRMED`.
- Update the dataconnect docs. - Update the dataconnect docs.
- Track `lat` and `lng` in the staff preferred work locations (for now we are only storing the name).
- Remove "Up Next (x)" counter from orders list in client app as it is confusing, becase the tab already has a badge showing the number of the upcoming orders.

View File

@@ -95,10 +95,10 @@ class ClientHubsPage extends StatelessWidget {
).add( ).add(
ClientHubsIdentifyDialogToggled(hub: hub), ClientHubsIdentifyDialogToggled(hub: hub),
), ),
onDeletePressed: () => onDeletePressed: () => _confirmDeleteHub(
BlocProvider.of<ClientHubsBloc>( context,
context, hub,
).add(ClientHubsDeleteRequested(hub.id)), ),
), ),
), ),
], ],
@@ -221,4 +221,51 @@ class ClientHubsPage extends StatelessWidget {
), ),
); );
} }
Future<void> _confirmDeleteHub(BuildContext context, Hub hub) async {
final String hubName = hub.name.isEmpty ? 'this hub' : hub.name;
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: const Text('Confirm Hub Deletion'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Are you sure you want to delete "$hubName"?'),
const SizedBox(height: UiConstants.space2),
const Text('This action cannot be undone.'),
const SizedBox(height: UiConstants.space2),
Text(
'Note that if there are any shifts/orders assigned to this hub we shouldn\'t be able to delete the hub.',
style: UiTypography.footnote1r.copyWith(
color: UiColors.textSecondary,
),
),
],
),
actions: <Widget>[
TextButton(
onPressed: () => Modular.to.pop(),
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
BlocProvider.of<ClientHubsBloc>(
context,
).add(ClientHubsDeleteRequested(hub.id));
Modular.to.pop();
},
style: TextButton.styleFrom(
foregroundColor: UiColors.destructive,
),
child: const Text('Delete'),
),
],
);
},
);
}
} }

View File

@@ -83,7 +83,7 @@ class SettingsActions extends StatelessWidget {
// Cancel button // Cancel button
UiButton.secondary( UiButton.secondary(
text: t.common.cancel, text: t.common.cancel,
onPressed: () => Navigator.of(dialogContext).pop(), onPressed: () => Modular.to.pop(),
), ),
], ],
), ),

View File

@@ -31,9 +31,11 @@ extension HomeNavigator on IModularNavigator {
/// Optionally provide a [tab] query param (e.g. `find`). /// Optionally provide a [tab] query param (e.g. `find`).
void pushShifts({String? tab}) { void pushShifts({String? tab}) {
if (tab == null) { if (tab == null) {
pushNamed('/worker-main/shifts'); navigate('/worker-main/shifts');
} else { } else {
pushNamed('/worker-main/shifts?tab=$tab'); navigate('/worker-main/shifts', arguments: <String, dynamic>{
'initialTab': tab,
});
} }
} }

View File

@@ -132,8 +132,7 @@ class WorkerHomePage extends StatelessWidget {
EmptyStateWidget( EmptyStateWidget(
message: emptyI18n.no_shifts_today, message: emptyI18n.no_shifts_today,
actionLink: emptyI18n.find_shifts_cta, actionLink: emptyI18n.find_shifts_cta,
onAction: () => onAction: () => Modular.to.pushShifts(tab: 'find'),
Modular.to.pushShifts(tab: 'find'),
) )
else else
Column( Column(

View File

@@ -31,6 +31,7 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
on<LoadShiftsEvent>(_onLoadShifts); on<LoadShiftsEvent>(_onLoadShifts);
on<LoadHistoryShiftsEvent>(_onLoadHistoryShifts); on<LoadHistoryShiftsEvent>(_onLoadHistoryShifts);
on<LoadAvailableShiftsEvent>(_onLoadAvailableShifts); on<LoadAvailableShiftsEvent>(_onLoadAvailableShifts);
on<LoadFindFirstEvent>(_onLoadFindFirst);
on<LoadShiftsForRangeEvent>(_onLoadShiftsForRange); on<LoadShiftsForRangeEvent>(_onLoadShiftsForRange);
on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts); on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts);
} }
@@ -62,6 +63,7 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
availableLoaded: false, availableLoaded: false,
historyLoading: false, historyLoading: false,
historyLoaded: false, historyLoaded: false,
myShiftsLoaded: true,
searchQuery: '', searchQuery: '',
jobType: 'all', jobType: 'all',
)); ));
@@ -82,6 +84,7 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
try { try {
final historyResult = await getHistoryShifts(); final historyResult = await getHistoryShifts();
emit(currentState.copyWith( emit(currentState.copyWith(
myShiftsLoaded: true,
historyShifts: historyResult, historyShifts: historyResult,
historyLoading: false, historyLoading: false,
historyLoaded: true, historyLoaded: true,
@@ -113,6 +116,67 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
} }
} }
Future<void> _onLoadFindFirst(
LoadFindFirstEvent event,
Emitter<ShiftsState> emit,
) async {
if (state is! ShiftsLoaded) {
emit(const ShiftsLoaded(
myShifts: [],
pendingShifts: [],
cancelledShifts: [],
availableShifts: [],
historyShifts: [],
availableLoading: false,
availableLoaded: false,
historyLoading: false,
historyLoaded: false,
myShiftsLoaded: false,
searchQuery: '',
jobType: 'all',
));
}
final currentState =
state is ShiftsLoaded ? state as ShiftsLoaded : null;
if (currentState != null && currentState.availableLoaded) return;
if (currentState != null) {
emit(currentState.copyWith(availableLoading: true));
}
try {
final availableResult =
await getAvailableShifts(const GetAvailableShiftsArguments());
final loadedState = state is ShiftsLoaded
? state as ShiftsLoaded
: const ShiftsLoaded(
myShifts: [],
pendingShifts: [],
cancelledShifts: [],
availableShifts: [],
historyShifts: [],
availableLoading: true,
availableLoaded: false,
historyLoading: false,
historyLoaded: false,
myShiftsLoaded: false,
searchQuery: '',
jobType: 'all',
);
emit(loadedState.copyWith(
availableShifts: _filterPastShifts(availableResult),
availableLoading: false,
availableLoaded: true,
));
} catch (_) {
if (state is ShiftsLoaded) {
final current = state as ShiftsLoaded;
emit(current.copyWith(availableLoading: false));
}
}
}
Future<void> _onLoadShiftsForRange( Future<void> _onLoadShiftsForRange(
LoadShiftsForRangeEvent event, LoadShiftsForRangeEvent event,
Emitter<ShiftsState> emit, Emitter<ShiftsState> emit,
@@ -124,7 +188,10 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
if (state is ShiftsLoaded) { if (state is ShiftsLoaded) {
final currentState = state as ShiftsLoaded; final currentState = state as ShiftsLoaded;
emit(currentState.copyWith(myShifts: myShiftsResult)); emit(currentState.copyWith(
myShifts: myShiftsResult,
myShiftsLoaded: true,
));
return; return;
} }
@@ -138,6 +205,7 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
availableLoaded: false, availableLoaded: false,
historyLoading: false, historyLoading: false,
historyLoaded: false, historyLoaded: false,
myShiftsLoaded: true,
searchQuery: '', searchQuery: '',
jobType: 'all', jobType: 'all',
)); ));

View File

@@ -14,6 +14,8 @@ class LoadHistoryShiftsEvent extends ShiftsEvent {}
class LoadAvailableShiftsEvent extends ShiftsEvent {} class LoadAvailableShiftsEvent extends ShiftsEvent {}
class LoadFindFirstEvent extends ShiftsEvent {}
class LoadShiftsForRangeEvent extends ShiftsEvent { class LoadShiftsForRangeEvent extends ShiftsEvent {
final DateTime start; final DateTime start;
final DateTime end; final DateTime end;

View File

@@ -22,6 +22,7 @@ class ShiftsLoaded extends ShiftsState {
final bool availableLoaded; final bool availableLoaded;
final bool historyLoading; final bool historyLoading;
final bool historyLoaded; final bool historyLoaded;
final bool myShiftsLoaded;
final String searchQuery; final String searchQuery;
final String jobType; final String jobType;
@@ -35,6 +36,7 @@ class ShiftsLoaded extends ShiftsState {
required this.availableLoaded, required this.availableLoaded,
required this.historyLoading, required this.historyLoading,
required this.historyLoaded, required this.historyLoaded,
required this.myShiftsLoaded,
required this.searchQuery, required this.searchQuery,
required this.jobType, required this.jobType,
}); });
@@ -49,6 +51,7 @@ class ShiftsLoaded extends ShiftsState {
bool? availableLoaded, bool? availableLoaded,
bool? historyLoading, bool? historyLoading,
bool? historyLoaded, bool? historyLoaded,
bool? myShiftsLoaded,
String? searchQuery, String? searchQuery,
String? jobType, String? jobType,
}) { }) {
@@ -62,6 +65,7 @@ class ShiftsLoaded extends ShiftsState {
availableLoaded: availableLoaded ?? this.availableLoaded, availableLoaded: availableLoaded ?? this.availableLoaded,
historyLoading: historyLoading ?? this.historyLoading, historyLoading: historyLoading ?? this.historyLoading,
historyLoaded: historyLoaded ?? this.historyLoaded, historyLoaded: historyLoaded ?? this.historyLoaded,
myShiftsLoaded: myShiftsLoaded ?? this.myShiftsLoaded,
searchQuery: searchQuery ?? this.searchQuery, searchQuery: searchQuery ?? this.searchQuery,
jobType: jobType ?? this.jobType, jobType: jobType ?? this.jobType,
); );
@@ -78,6 +82,7 @@ class ShiftsLoaded extends ShiftsState {
availableLoaded, availableLoaded,
historyLoading, historyLoading,
historyLoaded, historyLoaded,
myShiftsLoaded,
searchQuery, searchQuery,
jobType, jobType,
]; ];

View File

@@ -21,6 +21,7 @@ class ShiftsPage extends StatefulWidget {
class _ShiftsPageState extends State<ShiftsPage> { class _ShiftsPageState extends State<ShiftsPage> {
late String _activeTab; late String _activeTab;
DateTime? _selectedDate; DateTime? _selectedDate;
bool _prioritizeFind = false;
final ShiftsBloc _bloc = Modular.get<ShiftsBloc>(); final ShiftsBloc _bloc = Modular.get<ShiftsBloc>();
@override @override
@@ -28,12 +29,22 @@ class _ShiftsPageState extends State<ShiftsPage> {
super.initState(); super.initState();
_activeTab = widget.initialTab ?? 'myshifts'; _activeTab = widget.initialTab ?? 'myshifts';
_selectedDate = widget.selectedDate; _selectedDate = widget.selectedDate;
_bloc.add(LoadShiftsEvent()); print('ShiftsPage init: initialTab=$_activeTab');
_prioritizeFind = widget.initialTab == 'find';
if (_prioritizeFind) {
_bloc.add(LoadFindFirstEvent());
} else {
_bloc.add(LoadShiftsEvent());
}
if (_activeTab == 'history') { if (_activeTab == 'history') {
print('ShiftsPage init: loading history tab');
_bloc.add(LoadHistoryShiftsEvent()); _bloc.add(LoadHistoryShiftsEvent());
} }
if (_activeTab == 'find') { if (_activeTab == 'find') {
_bloc.add(LoadAvailableShiftsEvent()); print('ShiftsPage init: entering find tab (not loaded yet)');
if (!_prioritizeFind) {
_bloc.add(LoadAvailableShiftsEvent());
}
} }
} }
@@ -43,6 +54,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
if (widget.initialTab != null && widget.initialTab != _activeTab) { if (widget.initialTab != null && widget.initialTab != _activeTab) {
setState(() { setState(() {
_activeTab = widget.initialTab!; _activeTab = widget.initialTab!;
_prioritizeFind = widget.initialTab == 'find';
}); });
} }
if (widget.selectedDate != null && widget.selectedDate != _selectedDate) { if (widget.selectedDate != null && widget.selectedDate != _selectedDate) {
@@ -86,6 +98,10 @@ class _ShiftsPageState extends State<ShiftsPage> {
final bool historyLoaded = (state is ShiftsLoaded) final bool historyLoaded = (state is ShiftsLoaded)
? state.historyLoaded ? state.historyLoaded
: false; : false;
final bool myShiftsLoaded = (state is ShiftsLoaded)
? state.myShiftsLoaded
: false;
final bool blockTabsForFind = _prioritizeFind && !availableLoaded;
// Note: "filteredJobs" logic moved to FindShiftsTab // Note: "filteredJobs" logic moved to FindShiftsTab
// Note: Calendar logic moved to MyShiftsTab // Note: Calendar logic moved to MyShiftsTab
@@ -124,7 +140,8 @@ class _ShiftsPageState extends State<ShiftsPage> {
"My Shifts", "My Shifts",
UiIcons.calendar, UiIcons.calendar,
myShifts.length, myShifts.length,
enabled: true, showCount: myShiftsLoaded,
enabled: !blockTabsForFind,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
_buildTab( _buildTab(
@@ -143,7 +160,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
UiIcons.clock, UiIcons.clock,
historyShifts.length, historyShifts.length,
showCount: historyLoaded, showCount: historyLoaded,
enabled: baseLoaded, enabled: !blockTabsForFind && baseLoaded,
), ),
], ],
), ),

View File

@@ -22,6 +22,12 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
String _searchQuery = ''; String _searchQuery = '';
String _jobType = 'all'; String _jobType = 'all';
@override
void initState() {
super.initState();
print('FindShiftsTab init: tab entered, data pending');
}
Widget _buildFilterTab(String id, String label) { Widget _buildFilterTab(String id, String label) {
final isSelected = _jobType == id; final isSelected = _jobType == id;
return GestureDetector( return GestureDetector(

View File

@@ -5,7 +5,7 @@ mutation seedAll @transaction {
data: { data: {
id: "dvpWnaBjT6UksS5lo04hfMTyq1q1" id: "dvpWnaBjT6UksS5lo04hfMTyq1q1"
email: "legendary@krowd.com" email: "legendary@krowd.com"
fullName: "Krow" fullName: "Krow Payements"
role: USER role: USER
userRole: "BUSINESS" userRole: "BUSINESS"
} }
@@ -26,7 +26,7 @@ mutation seedAll @transaction {
id: "ef69e942-d6e5-48e5-a8bc-69d3faa63b2f" id: "ef69e942-d6e5-48e5-a8bc-69d3faa63b2f"
businessName: "Krow" businessName: "Krow"
userId: "dvpWnaBjT6UksS5lo04hfMTyq1q1" userId: "dvpWnaBjT6UksS5lo04hfMTyq1q1"
contactName: "Krow Ops" contactName: "Krow Payements"
email: "legendary@krowd.com" email: "legendary@krowd.com"
phone: "+1-818-555-0148" phone: "+1-818-555-0148"
address: "5000 San Jose Street, Granada Hills, CA, USA" address: "5000 San Jose Street, Granada Hills, CA, USA"

63
demos/m3/m3-notes.md Normal file
View File

@@ -0,0 +1,63 @@
# KROW M3 Demo — Test Feedback
**Date:** February 3, 2026
---
## Demo 1: Register Business & Show Empty States (Client App)
- **Flickering company name:** Every time I navigate to the home screen, I see "your company" for a moment before it changes to the real name.
- Creating a One-Time Order shows "No Vendors Available" — this is expected, OK.
**Suggestions: Achintha:**
- We need to have a shimmer loading state while fetching data, to avoid flickering and empty states.
---
## Demo 2: Register Staff & Show Empty States (Staff App)
**Onboarding — Add preferred work locations:**
- Suggestion: Use Google Maps to suggest only city names. Currently users can type anything, which will cause misspellings and inconsistent data. Important for the max distance feature.
**Home page:**
- Same flickering issue — shows "Krower" briefly before displaying the real name.
**Profile page:**
- Phone number should be read-only, or require re-verification if changed.
- Emergency contact: "Save & Continue" works, but shouldn't we navigate to the profile page after? Other flows do this.
- Tax Documents: Would be great to add a file uploader where our AI could identify documents and prefill fields.
- Bank Account: Need to plan real bank verification (KYC)? Ensure the account is real and belongs to the user. Also, I can list banks but I don't see how to change/switch bank.
**Home (empty state):**
- Clicking "Find shifts →" does nothing. But "Find Shifts" with the search icon works.
**My Availability:**
- Working. Some latency, but OK for now.
---
## Demo 5: Client Creates a New Hub
- Hub editing feature seems missing — we'll need this for NFC configuration later.
- No confirmation before deleting a hub.
---
## Demo 6: Client Creates New Order
- "Up Next (x)" counter is confusing. I created 2 orders but it shows "Up Next (1)". Sometimes shows 0 when navigating, then back to 1.
---
## Demo 8: Staff Logs In with Existing Account
- If you accidentally click "Sign Up" with an existing phone number, you get stuck:
1. OTP screen shows error: "This user already has a staff profile. Please log in"
2. Clicking back → login → same OTP error loop
3. Only fix: kill and restart the app
---
## Demo 10: Staff Browses Available Shifts
- **Blocker:** I don't see the shift I created as the Client.

View File

@@ -1,6 +1,6 @@
# KROW Workforce Platform — Feature Demo Plan for Milestone 3 # KROW Workforce Platform — Feature Demo Plan for Milestone 3
**Version:** Milestone 3 (v3.0) **Version:** Milestone 3 (v3.0)
**Date:** February 3, 2026 **Date:** February 3, 2026
**Audience:** Business Stakeholders, Customer Engineers, Sales Teams **Audience:** Business Stakeholders, Customer Engineers, Sales Teams
**Duration:** 25-30 minutes **Duration:** 25-30 minutes
@@ -15,7 +15,7 @@ This demo showcases the progress of the milestone 3.
- **For Businesses (Client App):** One-time shift creation, worker management, real-time coverage tracking - **For Businesses (Client App):** One-time shift creation, worker management, real-time coverage tracking
- **For Workers (Staff App):** Easy access to available shifts, clock-in and profile management - **For Workers (Staff App):** Easy access to available shifts, clock-in and profile management
- **Complete Workflow:** From shift posting and worker check-in and completion/ - **Complete Workflow:** From shift posting and worker check-in and completion
### Estimated Demo Duration ### Estimated Demo Duration
@@ -33,72 +33,78 @@ This demo showcases the progress of the milestone 3.
- Client Name: "Krow" - Client Name: "Krow"
**Staff Account (Worker):** **Staff Account (Worker):**
- Phone: `+1 (555) 765-4321` - Phone: `+15557654321`
- OTP Code: `123456` (demo mode) - OTP Code: `123456` (demo mode)
- Name: "Mariana Torres" - Name: "Mariana Torres"
### Prerequisites ### Prerequisites
1. ✅ Both apps installed on demo devices (or simulators) 1. ✅ Both apps installed on demo devices (or simulators)
2. ✅ Network connection stable 2. ✅ Network connection stable
3. ✅ Seed data is ready to be populated 3. ✅ Seed data is ready to be populated (the database should be empty at start)
- the database should be empty.
### Pre-Demo Data Seeding ### Make Commands Reference
Tracked in : | Command | Purpose |
- https://github.com/Oloodi/krow-workforce/issues/345 |---------|---------|
| `make dataconnect-clean` | Clean the database before seeding |
| `make dataconnect-seed` | Populate the database with seed data for demo |
- At the start the database should be empty. ### Recent Fixes Applied
- Commands to use: - ✅ Fixed 2 bugs on TaxForm: marital status and Citizenship Status now properly saved
- `make dataconnect-clean` - ✅ Fixed update screen after create or update TaxForm
- To clean the database before seeding. - ✅ Created seed data script
- `make dataconnect-seed` - ✅ Created make commands to create and delete information in DataConnect
- To populate the database with seed data for demo.
--- ---
## 3⃣ Demo Flows ## 3⃣ Demo Flows
**Note:**
To start the demo you should clean the database running the next command:
- make dataconnect-clean
### Demo 0: Show Empty Database ### Demo 0: Show Empty Database
**Purpose:** Demonstrate the starting point before any data exists **Purpose:** Demonstrate the starting point before any data exists
**Action:** Show the empty database in Firebase console
**Steps:**
1. Run `make dataconnect-clean` to ensure database is empty
2. Show the empty database in Firebase console
--- ---
### Demo 1: Register Business & Show Empty States (Client App) ### Demo 1: Register Business & Show Empty States (Client App)
**Purpose:** Show the client onboarding experience and empty states **Purpose:** Show the client onboarding experience and empty states
**Steps:** **Steps:**
1. Open Client App → Tap "Register" 1. Open Client App → Tap "Create Account"
2. Enter business email, and password. 2. Enter business email, and password
3. Navigate to home page 3. Navigate to home page
4. **Point out:** Empty dashboard, no orders, no workers, clean slate 4. **Point out:** Empty dashboard, no orders, no workers, clean slate
--- ---
### Demo 2: Register Staff & Show Empty States (Staff App) ### Demo 2: Register Staff & Show Empty States (Staff App)
**Purpose:** Show the worker onboarding experience and empty states **Purpose:** Show the worker onboarding experience and empty states
**Steps:** **Steps:**
1. Open Staff App → Tap "Register" 1. Open Staff App → Tap "Sign Up"
2. Enter phone number and verify with OTP code 2. Enter phone number and verify with OTP code
3. Navigate to home page 3. Follow the onboarding process
4. **Point out:** Empty shifts list, no available work yet 4. Navigate to home page
5. **Point out:** Empty shifts list, no available work yet
--- ---
> **🔄 PAUSE HERE:** Populate the database with seed data (run seeding script) ### 🔄 PAUSE: Populate Database
- Potulate database with the next comand : Run the seeding command:
- make dataconnect-seed ```bash
make dataconnect-seed
```
--- ---
### Demo 3: Client Logs In with Existing Account ### Demo 3: Client Logs In with Existing Account
**Purpose:** Show the sign-in experience for returning users **Purpose:** Show the sign-in experience for returning users
**Screen:** Get Started → Sign In
**Screen:** Get Started → Sign In
**Steps:** **Steps:**
1. Restart Client App 1. Restart Client App
2. Tap "Sign In" button 2. Tap "Sign In" button
@@ -110,7 +116,8 @@ To start the demo you should clean the database running the next command:
--- ---
### Demo 4: Client Views Populated Dashboard ### Demo 4: Client Views Populated Dashboard
**Purpose:** Show how the client app displays active operations **Purpose:** Show how the client app displays active operations
**Steps:** **Steps:**
1. After signing in, observe the home screen 1. After signing in, observe the home screen
2. Navigate through populated sections: 2. Navigate through populated sections:
@@ -127,7 +134,8 @@ To start the demo you should clean the database running the next command:
--- ---
### Demo 5: Client Creates a New Hub ### Demo 5: Client Creates a New Hub
**Screen:** Hubs Tab → "Add Hub" button **Screen:** Hubs Tab → "Add Hub" button
**Steps:** **Steps:**
1. Navigate to Hubs tab in bottom navigation 1. Navigate to Hubs tab in bottom navigation
2. Tap the "+" or "Add Hub" button 2. Tap the "+" or "Add Hub" button
@@ -139,29 +147,30 @@ To start the demo you should clean the database running the next command:
--- ---
> **EXPLAIN**: The main demo flow which is the order creation and acceptance flow. ### 📋 Main Demo Flow Explanation
> ```
>Client Posts Shift [O1] ```
> ↓ Client Posts Shift [O1]
>*Vendor Accepts the Shift (This Part is missing for now)/ Vendor is selected by client* [O2]
> ↓ *Vendor Accepts the Shift (Missing for now) / Vendor is selected by client* [O2]
>Worker Searches for a Shift [O3]
> ↓ Worker Searches for a Shift [O3]
>Worker Applies [O4]
> ↓ Worker Applies [O4]
>Confirmation (This Part is missing for now, for now is confirmed)*[O5]
> ↓ Confirmation (Missing for now, auto-confirmed)* [O5]
>Worker Checks In [O6]
> ↓ Worker Checks In [O6]
>Shift Completed [O7]
>``` Shift Completed [O7]
```
--- ---
### Demo 6: Client Creates New Order - [O1] ### Demo 6: Client Creates New Order - [O1]
**Purpose:** Walk through the shift creation process **Purpose:** Walk through the shift creation process
**Screen:** Orders Tab → "Post" button
**Action:** Create a new shift for upcoming event **Screen:** Orders Tab → "Post" button
**What to Fill:** **What to Fill:**
- Order name: "Spring Gala 2026" - Order name: "Spring Gala 2026"
@@ -172,9 +181,9 @@ To start the demo you should clean the database running the next command:
--- ---
### Demo 7: Client Views Order Details ### Demo 7: Client Views Order Details
**Purpose:** Show detailed shift information and worker assignments (this second part is missing for now) **Purpose:** Show detailed shift information and worker assignments (second part is missing for now)
**Screen:** Orders Tab → Tap on any order card
**Action:** Expand order to see full details **Screen:** Orders Tab → Tap on any order card
**What to Notice:** **What to Notice:**
- Event name and location - Event name and location
@@ -186,18 +195,20 @@ To start the demo you should clean the database running the next command:
--- ---
### Demo 8: Staff Logs In with Existing Account ### Demo 8: Staff Logs In with Existing Account
**Purpose:** Show the worker sign-in experience **Purpose:** Show the worker sign-in experience
**Screen:** Get Started → Sign In with Phone
**Screen:** Get Started → Sign In with Phone
**Steps:** **Steps:**
1. Restart the staff app. 1. Restart the staff app
2. Enter phone number: `5551234567` 2. Enter phone number: `5557654321`
3. Tap "Send Code" 3. Tap "Send Code"
4. Enter OTP: `123456` 4. Enter OTP: `123456`
--- ---
### Demo 9: Staff Views Home Dashboard ### Demo 9: Staff Views Home Dashboard
**Purpose:** Show worker's personalized dashboard **Purpose:** Show worker's personalized dashboard
**What to Notice:** **What to Notice:**
- Today's Shifts section (confirmed shifts for today) - Today's Shifts section (confirmed shifts for today)
@@ -206,9 +217,9 @@ To start the demo you should clean the database running the next command:
--- ---
### Demo 10: Staff Browses Available Shifts - [O3] ### Demo 10: Staff Browses Available Shifts - [O3]
**Purpose:** Show how workers discover and view available work **Purpose:** Show how workers discover and view available work
**Screen:** Shifts → "Find Work"
**Action:** Browse the list of available shifts **Screen:** Shifts → "Find Work"
**What to Notice:** **What to Notice:**
- List of shifts matching worker skills - List of shifts matching worker skills
@@ -219,20 +230,22 @@ To start the demo you should clean the database running the next command:
--- ---
### Demo 11: Staff Applies for Shift - [O4] ### Demo 11: Staff Applies for Shift - [O4]
**Purpose:** Show the application process from worker side **Purpose:** Show the application process from worker side
**Screen:** Shift Details → "Apply Now" button
**Screen:** Shift Details → "Book" Shift button
**Steps:** **Steps:**
1. Tap on an available shift to view details 1. Tap on an available shift to view details
2. Review business name, location, pay, requirements 2. Review business name, location, pay, requirements
3. Tap "Book Shift" 3. Tap "Book" Shift button
4. See instant confirmation 4. See confirmation
--- ---
### Demo 12: Staff Views Confirmed Shifts - [O5] ### Demo 12: Staff Views Confirmed Shifts - [O5]
**Purpose:** Show worker's shift management interface **Purpose:** Show worker's shift management interface
**Screen:** Shifts Tab → "My Shifts"
**Action:** Review calendar view of confirmed shifts **Screen:** Shifts Tab → "My Shifts"
**What to Notice:** **What to Notice:**
- Week-by-week calendar navigation - Week-by-week calendar navigation
@@ -242,22 +255,20 @@ To start the demo you should clean the database running the next command:
--- ---
### Demo 13: Client Monitors Coverage Dashboard - [O5] ### Demo 13: Client Monitors Coverage Dashboard - [O5]
**Purpose:** Show real-time worker tracking capabilities **Purpose:** Show real-time worker tracking capabilities
**Screen:** Client App → Coverage Tab
**Action:** Navigate to Coverage, select today's date **Screen:** Client App → Coverage Tab
**What to Notice:** **What to Notice:**
- Live worker status (Checked In, En Route, Late, Not Arrived) - Live worker status (Checked In, En Route, Late, Not Arrived)
- Color-coded status badges (green, yellow, red) - Color-coded status badges (green, yellow, red)
- Worker contact information - Worker information
- Real-time updates as workers check in
--- ---
### Demo 14: Staff Checks In to Shift (Day of Event) [O6] ### Demo 14: Staff Clock-In to Shift (Day of Event) - [O6]
**Purpose:** Demonstrate the check-in process **Purpose:** Demonstrate the clock-in process
**Screen:** Home or My Shifts → Shift Card → "Check In" button **Screen:** Clockin page → "Clock In" slider
**Action:** Simulate checking in to an active shift
**What to Notice:** **What to Notice:**
- Timestamp automatically recorded - Timestamp automatically recorded
@@ -265,33 +276,36 @@ To start the demo you should clean the database running the next command:
--- ---
### Demo 15: Client Sees Check-In Update - [O6] ### Demo 15: Client Sees Clock-In Update - [O6]
**Purpose:** Show cross-app interaction and real-time updates **Purpose:** Show cross-app interaction and real-time updates
**Screen:** Client App → Coverage Tab
**Screen:** Client App → Coverage Tab
**Action:** Press the update button on the top right to refresh worker statuses **Action:** Press the update button on the top right to refresh worker statuses
**What to Notice:** **What to Notice:**
- Status update - Status update
- Green "Checked In" badge appears - User status changes to "Checked In"
- Check-in time displayed - Check-in time displayed
--- ---
### Demo 16: Staff Checks Out of Shift - - (this is under fixing)[O7] ### Demo 16: Staff Clocks-Out of Shift - [O7]
**Purpose:** Demonstrate the check-out process and shift completion **Purpose:** Demonstrate the clocks-out process and shift completion
**Screen:** Home or My Shifts → Shift Card → "Check Out" button **Screen:** Clockin page -> Clock-out slider
**What to Notice:** **What to Notice:**
- Check-out timestamp automatically recorded - Clock-out timestamp automatically recorded
- Status changes to "Completed" - Status changes to "Completed"
- Total hours worked calculated automatically - Total hours worked calculated automatically
- Shift moves from active to history
--- ---
### Demo 17: Client Views Completed Shift in Coverage - [O7] ### Demo 17: Client Views Completed Shift in Coverage - [O7]
**Purpose:** Show how completed shifts appear in the client app **Purpose:** Show how completed shifts appear in the client app
**Screen:** Client App → Coverage Tab
**Screen:** Client App → Coverage Tab
**Action:** Press the refresh button to update worker statuses **Action:** Press the refresh button to update worker statuses
**What to Notice:** **What to Notice:**
@@ -304,27 +318,31 @@ To start the demo you should clean the database running the next command:
--- ---
### Demo 18: Staff Profile Management ### Demo 18: Staff Profile Management
**Purpose:** Demonstrate worker profile features and compliance management **Purpose:** Demonstrate worker profile features and compliance management
**Screen:** Staff App → Profile Tab
**Screen:** Staff App → Profile Tab
**Steps:** **Steps:**
1. Navigate to Profile tab in bottom navigation 1. Navigate to Profile tab in bottom navigation
2. Review profile sections: 2. Review profile sections:
- **Profile Info:** Name, photo, contact details, date of birth - **Profile Info:**
- **Statistics:** Total shifts worked, average rating, reliability score - **Emergency Contact:** Name, relationship, phone number
- **Bank Account:** Linked payment account for direct deposit - **Bank Account:** Linked payment account for direct deposit
- **Certificates:** Food Handler, ServSafe, Background Check status - **Tax Forms:** W-9, I-9 compliance documents *(bugs fixed: marital status and Citizenship Status now work properly)*
- **Documents:** ID verification, work authorization
- **Tax Forms:** W-9, I-9 compliance documents
- **Time Card:** Historical shift records with hours and earnings - **Time Card:** Historical shift records with hours and earnings
--- ---
## Things we need to handover to the customer ## 4⃣ Customer Handover Checklist
- Android apps of the client and staff. ### Deliverables
- Demo accounts credentials:
- Client Account: - [ ] Android apps (Client and Staff)
- Email: `legendary@krowd.com` - [ ] Demo account credentials (see below)
- Password: `Demo2026!`
- Staff Account: ### Demo Accounts
- Phone: `+15557654321`
- OTP Code: `123456` (demo mode) | Account | Credentials |
|---------|-------------|
| **Client** | Email: `legendary@krowd.com` / Password: `Demo2026!` |
| **Staff** | Phone: `+15557654321` / OTP: `123456` (demo mode) |

View File

@@ -2,13 +2,13 @@
.PHONY: launchpad-dev deploy-launchpad-hosting .PHONY: launchpad-dev deploy-launchpad-hosting
launchpad-dev: launchpad-dev: sync-prototypes
@echo "--> Starting local Launchpad server using Firebase Hosting emulator..." @echo "--> Starting local Launchpad server using Firebase Hosting emulator..."
@echo " - Generating secure email hashes..." @echo " - Generating secure email hashes..."
@node scripts/generate-allowed-hashes.js @node scripts/generate-allowed-hashes.js
@firebase serve --only hosting:launchpad --project=$(FIREBASE_ALIAS) @firebase serve --only hosting:launchpad --project=$(FIREBASE_ALIAS)
deploy-launchpad-hosting: deploy-launchpad-hosting: sync-prototypes
@echo "--> Deploying Internal Launchpad to Firebase Hosting..." @echo "--> Deploying Internal Launchpad to Firebase Hosting..."
@echo " - Generating secure email hashes..." @echo " - Generating secure email hashes..."
@node scripts/generate-allowed-hashes.js @node scripts/generate-allowed-hashes.js