feat: added ask permision for contact and add friend screen#41
feat: added ask permision for contact and add friend screen#41akshat3410 wants to merge 23 commits into
Conversation
Added the full text of the GNU General Public License v3.
Add COPYRIGHT.md
Add GNU General Public License v3 to License.md
Added the app design figma file
…esolved eslint issues.
Improved ziplit website added folder structure, light mode, toggle button, responsiveness, animations, working links and better UX.
- Refactored project into MVC architecture (controllers, views, models, services) - Styled Onboarding page with perfect circular flat-trimmed ClipPath baseline - Applied exact SF Pro font weights (600 Semibold, 400 Regular) - Implemented new high-resolution onboard png assets matching specs perfectly
- Refactored project into MVC architecture (controllers, views, models, services) - Styled Onboarding page with perfect circular flat-trimmed ClipPath baseline - Applied exact SF Pro font weights (600 Semibold, 400 Regular) - Implemented new high-resolution onboard png assets matching specs perfectly
📝 WalkthroughWalkthroughThe PR introduces a comprehensive Next.js website for Zplit alongside updating the Flutter mobile app with onboarding and profile setup flows. It removes generated build artifacts and IDE configurations, adds licensing files, and expands .gitignore coverage for modern development workflows. Changes
Sequence DiagramsequenceDiagram
participant User
participant SplashScreen
participant OnboardingScreen
participant OnboardingController
participant OnboardingService
participant ProfileSetupScreen
participant AddFriendsScreen
User->>SplashScreen: App launched
activate SplashScreen
SplashScreen->>SplashScreen: Wait 2 seconds
SplashScreen->>OnboardingScreen: Navigate via pushReplacement
deactivate SplashScreen
activate OnboardingScreen
OnboardingScreen->>OnboardingController: Create & listen
OnboardingController->>OnboardingService: fetchOnboardingData()
activate OnboardingService
OnboardingService-->>OnboardingController: Return mock pages
deactivate OnboardingService
OnboardingController-->>OnboardingScreen: Notify listeners
User->>OnboardingScreen: Skip / Navigate through pages / Complete
OnboardingScreen->>OnboardingScreen: Update page via PageView
OnboardingScreen->>OnboardingScreen: Show progress dots
User->>OnboardingScreen: Reach last page or press skip
OnboardingScreen->>ProfileSetupScreen: Navigate via pushReplacement
deactivate OnboardingScreen
activate ProfileSetupScreen
User->>ProfileSetupScreen: Select avatar (camera/gallery/file)
ProfileSetupScreen->>ProfileSetupScreen: Request contacts permission
User->>ProfileSetupScreen: Enter name & username
ProfileSetupScreen->>ProfileSetupScreen: Validate form
User->>ProfileSetupScreen: Tap Continue
ProfileSetupScreen->>AddFriendsScreen: Navigate via push
deactivate ProfileSetupScreen
activate AddFriendsScreen
User->>AddFriendsScreen: Search & select contacts
deactivate AddFriendsScreen
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes The PR spans significant scope across two platforms (Next.js website with ~20 files and Flutter app with ~10 files), introduces new flows and state management patterns, and requires attention to configuration setup, component composition logic, permission handling, and image picker integration. While many website components follow similar animation patterns (reducing individual complexity), the overall heterogeneity of changes—spanning web infrastructure, UI components, mobile navigation flows, and platform-specific permissions—demands comprehensive review. Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (13)
License.md (1)
1-595: Exclude this file from prose/style rewrites to preserve legal text integrity.Most reported “style” findings should not be auto-fixed in a license body. Add markdown/prose-lint ignore rules for this file to avoid accidental legal-text mutation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@License.md` around lines 1 - 595, The License.md file contains legal text that must not be auto-modified by prose/style linters or autofixers; update the repository's prose/style tooling configuration (e.g., prose-lint, markdownlint, or your prose rewrite tool's ignore list) to exclude "License.md" from auto-fixes and style checks, and add a comment or rule entry in the relevant config (e.g., prose-lint ignore, .markdownlint.json, or your formatter's ignore) so that prose/style rewrite actions skip this file going forward.Zplit-Website/next.config.ts (1)
5-9: TightenremotePatternspathname to least privilege.The Image component in Footer.tsx uses only
/logo1.pngfrom this domain. Restrict thepathnameto that specific asset instead of allowing all paths on the host.Suggested hardening
images: { remotePatterns: [ { protocol: 'https', hostname: 'aossie.org', + pathname: '/logo1.png', }, ], },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Zplit-Website/next.config.ts` around lines 5 - 9, The remotePatterns entry in next.config.ts is too permissive; restrict its pathname to the exact asset used by the Image in Footer.tsx (the /logo1.png path) to follow least-privilege. Edit the remotePatterns object (same block containing protocol: 'https' and hostname: 'aossie.org') and add a pathname property that exactly matches '/logo1.png' so only that asset is allowed; verify Footer.tsx's Image src matches that path and adjust if necessary.Zplit-Website/app/page.tsx (1)
1-25: Page-level'use client'can be removed, but the bundle benefit is minimal.This file has no hooks, so it could be a Server Component. However, all direct children (Navbar, P2PMethods, FAQ, FinalCTA, etc.) are already marked
'use client'and most use React hooks or event handlers (e.g., Navbar uses useState, useEffect, useRef, and useTheme; P2PMethods and FAQ use useState). In Next.js, child Client Components render as client regardless of the parent's boundary, so removing this directive won't reduce the client bundle—the same components will still be bundled client-side. The change is technically valid but provides negligible practical benefit.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Zplit-Website/app/page.tsx` around lines 1 - 25, The file-level 'use client' directive at the top of app/page.tsx is unnecessary because the Home component does not use hooks; remove the "'use client';" line to convert Home into a Server Component while keeping its client children (Navbar, P2PMethods, FAQ, FinalCTA, etc.) as client components—locate the top of the file where "'use client';" is declared and delete that line, leaving the default export function Home() and its JSX unchanged.Zplit-Website/components/home/Features.tsx (1)
46-135: Consider data-driven card rendering to reduce duplication.The current implementation is correct, but extracting card content into an array and mapping it would make future edits safer and shorter.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Zplit-Website/components/home/Features.tsx` around lines 46 - 135, The component has many repeated motion.div blocks (each using variants={item}) for feature cards; refactor by extracting a features array (objects with icon, title, description, tags, and optional color classes) and replace the repeated JSX with a map over that array to render a single Card render (keep variants={item} on the mapped motion.div), updating Features.tsx to use the new data-driven render and removing duplicate blocks; ensure the array fields match where used (icon, title, description, tags, hover/badge color classes) so existing styles and the item animation remain unchanged.Zplit-Website/components/home/FAQ.tsx (1)
81-88: Add ARIA state wiring for the FAQ toggle.The toggle works, but accessibility is stronger if the button exposes expanded state and controls relationship to the collapsible region.
♿ Suggested patch
- <motion.div + <motion.div + id="faq-more" initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ duration: 0.3, ease: "easeInOut" }} className="overflow-hidden" > @@ <button onClick={() => setShowAll(!showAll)} + aria-expanded={showAll} + aria-controls="faq-more" className="rounded-full border border-zinc-300 bg-white px-6 py-3 font-semibold text-zinc-900 transition-colors hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-white dark:hover:bg-zinc-800" >Also applies to: 118-123
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Zplit-Website/components/home/FAQ.tsx` around lines 81 - 88, The FAQ toggle needs ARIA state and relationships: in the FAQ component locate the toggle button that controls the collapsing motion.div (around the motion.div shown) and add aria-expanded tied to the open state (e.g., aria-expanded={isOpen}) and aria-controls pointing to a unique panel id (e.g., `faq-panel-${index}`); give the collapsible container the matching id, role="region", and aria-labelledby pointing back to the toggle button id (e.g., `faq-button-${index}`), and ensure the button has that id; apply the same aria-expanded/aria-controls/id/role/aria-labelledby wiring for the other FAQ block referenced (lines ~118-123) so each question/panel pair is properly linked and accessible.Zplit-Website/components/shared/Navbar.tsx (1)
104-131: Desktop nav doesn’t apply the same offset-aware scroll behavior as mobile.Desktop links use plain hash navigation, while mobile uses
handleLinkClickwith fixed-header offset. This causes inconsistent section landing positions.Also applies to: 65-73
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Zplit-Website/components/shared/Navbar.tsx` around lines 104 - 131, Desktop nav anchor tags use plain hash navigation while mobile uses handleLinkClick to apply a fixed-header offset, causing inconsistent scroll positions; update the desktop link elements (the <a> anchors for "Features", "Technology", "Gallery", "FAQ") to use the same click handler instead of direct href navigation: attach onClick handlers that call handleLinkClick with the target id (and preventDefault) or call the shared scroll-with-offset utility used by mobile, ensuring the link labels remain accessible and retain href attributes for fallback/SEO if desired so behavior is identical across desktop and mobile.Zplit-Website/components/home/P2PMethods.tsx (1)
27-47: Add proper tab semantics for accessibility.This works visually, but the control/panel relationship is not exposed to assistive technologies. Please add
tablist/tab/tabpanelsemantics and ARIA linkage.Proposed fix
- <div className="mb-12 flex justify-center"> - <div className="inline-flex rounded-full bg-white p-1 shadow-lg dark:bg-zinc-900"> + <div className="mb-12 flex justify-center"> + <div role="tablist" aria-label="Architecture details" className="inline-flex rounded-full bg-white p-1 shadow-lg dark:bg-zinc-900"> <button + role="tab" + id="tab-p2p" + aria-selected={activeTab === 'p2p'} + aria-controls="panel-p2p" onClick={() => setActiveTab('p2p')} @@ <button + role="tab" + id="tab-tech" + aria-selected={activeTab === 'tech'} + aria-controls="panel-tech" onClick={() => setActiveTab('tech')} @@ - <motion.div + <motion.div + role="tabpanel" + id="panel-p2p" + aria-labelledby="tab-p2p" key="p2p" @@ - <motion.div + <motion.div + role="tabpanel" + id="panel-tech" + aria-labelledby="tab-tech" key="tech"Also applies to: 50-190
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Zplit-Website/components/home/P2PMethods.tsx` around lines 27 - 47, The tab buttons and content need proper ARIA/tab semantics: add role="tablist" to the wrapper around the two buttons, give each button role="tab" plus unique ids (e.g., "tab-p2p" and "tab-tech"), set aria-selected based on activeTab and aria-controls pointing to corresponding panel ids (e.g., "panel-p2p"/"panel-tech"), and ensure each content panel under P2PMethods has role="tabpanel", an id matching aria-controls, and is hidden (or aria-hidden) when inactive; also add keyboard handling on the tabs (onKeyDown) to support ArrowLeft/ArrowRight (and Home/End) to move focus and call setActiveTab. Apply the same changes to the panels and tab controls in the remainder of the file (lines ~50-190).lib/main.dart (1)
29-30: Now thatSplashScreenis the entrypoint, consider removing legacy starter-page code from this file.Keeping
MyHomePagescaffold code here is non-blocking but increases noise around app bootstrap ownership.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/main.dart` around lines 29 - 30, You can remove the legacy starter-page code now that SplashScreen is the app entrypoint: delete the MyHomePage widget class and any unused references (constructor calls, routes, and imports) from this file so main() only instantiates SplashScreen; keep any shared helpers or state used elsewhere but ensure there are no remaining references to MyHomePage (search for class MyHomePage, MyHomePageState, and any route names) and run the analyzer to remove any unused imports.lib/views/onboarding/onboarding_screen.dart (1)
221-228: Consider structured logging overdebugPrintfor production.While
debugPrintis fine for development, consider using a logging package (e.g.,logger,logging) for production builds to support log levels and filtering.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/views/onboarding/onboarding_screen.dart` around lines 221 - 228, Replace the debugPrint call inside the _controller.nextPage callback with a structured logger call: import and initialize your chosen logging package (e.g., package:logger or dart:developer/logging) at app startup, then use a log level (e.g., logger.i or log.info) to record the message "Done onboarding — navigating to Profile Setup..." in the nextPage callback where Navigator.pushReplacement to ProfileSetupScreen is invoked; ensure the logger is injected or globally accessible and do not include any sensitive data in the log message.lib/views/profile/add_friends_screen.dart (2)
4-10: Consider extractingContactmodel to a dedicated file.As the app grows, keeping models separate from screens improves maintainability and reusability (e.g.,
lib/models/contact.dart).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/views/profile/add_friends_screen.dart` around lines 4 - 10, Extract the Contact class into its own model file: create a new Contact model file and move the Contact class (fields: name, avatarUrl, isSelected and its constructor) out of add_friends_screen.dart, then update add_friends_screen.dart to import the new model and replace the local definition with the imported Contact; ensure the class signature (final String name, final String avatarUrl, bool isSelected = false) and constructor remain unchanged and update any other files referencing Contact to import the new model file.
273-292: FABonPressedis a no-op stub.The "Add" button does nothing. Consider adding a TODO comment to track this, or disable the button until implemented.
Would you like me to help implement the friend-adding logic or open an issue to track this?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/views/profile/add_friends_screen.dart` around lines 273 - 292, The FloatingActionButton.extended currently has an empty onPressed stub; either implement the friend-add flow or mark it clearly and disable the button until implemented. Replace the no-op onPressed with a call to a new handler (e.g., _onAddPressed) that contains the friend-adding logic, or set onPressed to null and add a TODO comment above the FloatingActionButton.extended noting work remaining (e.g., TODO: implement friend-add flow in _onAddPressed) so reviewers know it’s intentionally unimplemented; ensure the handler name _onAddPressed (or finishFriendSetup) is used consistently in the class.lib/views/profile/profile_setup_screen.dart (2)
62-69: "Choose File" duplicates "Photo Library" functionality.Both options call
ImageSource.gallery. If "Choose File" is intended to be distinct (e.g., for file picker), consider using a different approach likefile_pickerpackage. Otherwise, remove the redundant option.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/views/profile/profile_setup_screen.dart` around lines 62 - 69, The "Choose File" dialog option currently duplicates the "Photo Library" because both call _controller.pickImage(ImageSource.gallery); update the onTap for the _buildDialogOption titled 'Choose File' so it either (A) is removed if redundant, or (B) invokes a file-picker flow instead of ImageSource.gallery — e.g., call a new method on the controller like _controller.pickFile() (which should use a file_picker implementation) and close the dialog via Navigator.of(context).pop() as currently done; ensure you add/implement pickFile() on the controller if choosing option B and adjust any imports accordingly.
175-179: Consider usingImage.memoryfor consistent cross-platform image display.While
NetworkImagewith blob URLs on web does work, usingImage.memorywithXFile.readAsBytes()provides a cleaner, more uniform approach across platforms. This avoids the platform-specific blob URL handling and ensures consistent behavior.🔧 Suggested refactor for consistent cross-platform handling
Option 1: Use Image.memory consistently:
FutureBuilder<Uint8List?>( future: imageFile != null ? imageFile!.readAsBytes() : Future.value(null), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.data != null) { return Image( image: MemoryImage(snapshot.data!), fit: BoxFit.cover, ); } return SizedBox.shrink(); // or placeholder }, )Option 2: Simpler platform-specific approach:
image: imageFile != null ? DecorationImage( image: kIsWeb ? NetworkImage(imageFile.path) : FileImage(File(imageFile.path)), fit: BoxFit.cover, ) : null,The second approach works as-is, but the first provides better consistency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/views/profile/profile_setup_screen.dart` around lines 175 - 179, The current kIsWeb branch uses NetworkImage(blob URL); change to using Image.memory-backed MemoryImage for consistent cross-platform rendering by reading bytes from the XFile and using MemoryImage as the DecorationImage's image provider. Concretely: where DecorationImage is built (reference: DecorationImage and imageFile), replace the kIsWeb NetworkImage path with a MemoryImage created from await imageFile.readAsBytes() (or preload bytes into state) and supply that MemoryImage to the DecorationImage; if asynchronous loading is needed, wrap the decoration/content in a FutureBuilder around imageFile.readAsBytes() to return DecorationImage/placeholder once bytes are available.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@COPYRIGHT.md`:
- Line 5: The reference in COPYRIGHT.md points to LICENSE.md but the PR added a
file named License.md; update the casing so they match on case-sensitive systems
by changing the reference in COPYRIGHT.md from "LICENSE.md" to "License.md" (or
alternatively rename the added file to "LICENSE.md"); ensure all occurrences of
the filename in COPYRIGHT.md and the repository use the same exact casing.
- Around line 1-5: The COPYRIGHT.md contains a conflicting "All rights reserved"
phrase vs the GPL grant in LICENSE.md; remove or replace the "All rights
reserved" line and replace the header with a single consistent copyright/license
statement that references the repository's GPL details (e.g., "Copyright © 2025
The Stable Order. Licensed under the GNU General Public License vX.Y; see
LICENSE.md for full terms"), ensuring the file only references the LICENSE.md
GPL grant and no proprietary-reserved language so the wording is consistent
across the repository.
In `@lib/controllers/onboarding_controller.dart`:
- Around line 21-34: The async _loadData() can call notifyListeners() after the
controller is disposed; add a disposal guard: add a private bool _disposed =
false, set _disposed = true in the controller's dispose() override, and wrap
state updates and notifyListeners() inside checks (if (!_disposed) { ... }) in
_loadData (around assigning _pages, setting _isLoading, and both
notifyListeners() calls) so no notifications run on a disposed ChangeNotifier;
keep using _service.fetchOnboardingData() as before and only update state when
!_disposed.
In `@lib/views/profile/add_friends_screen.dart`:
- Around line 126-128: The code uses contact.name[0].toUpperCase() when
rendering the avatar placeholder which will throw if contact.name is empty;
update the widget in add_friends_screen.dart to guard access by checking
contact.name.isNotEmpty (e.g., use contact.name.isNotEmpty ?
contact.name[0].toUpperCase() : '?' or a fallback character) instead of
unconditionally indexing, and apply the identical bounds-check/fallback fix to
the other occurrence referenced around line 239 so both avatar placeholders
never index into an empty string.
- Around line 217-227: The current ListView.builder in add_friends_screen.dart
uses _contacts.length with an itemBuilder that returns SizedBox.shrink() for
non-matching entries, which causes wrong scroll extent and accessibility gaps;
instead compute a filtered list (e.g., final visibleContacts =
_contacts.where(...).toList() using _searchQuery and contact.name), then use
ListView.builder with itemCount: visibleContacts.length and build each row from
visibleContacts[index] (update any references to contact to use visibleContacts)
so only matching items are rendered.
In `@lib/views/profile/profile_setup_screen.dart`:
- Around line 293-306: The button handler currently calls
Permission.contacts.request() but ignores its return value and always navigates
to AddFriendsScreen; change the onPressed flow in the widget (the branch using
isValid and Permission.contacts.request()) to capture the PermissionStatus
result, only Navigator.push to AddFriendsScreen when the status is granted (or
appropriate allowed state like limited on iOS), and handle
denied/permanentlyDenied by showing user feedback (e.g., a SnackBar or dialog)
and offering to openAppSettings() for permanently denied; keep the existing
context.mounted guard before navigation or showing UI.
In `@pubspec.yaml`:
- Line 39: The project added the dependency permission_handler (pubspec entry
"permission_handler: ^12.0.1") but iOS is missing the required permission key;
open ios/Runner/Info.plist and add the NSContactsUsageDescription key with a
user-facing string explaining why contacts access is needed (use a clear
description like "Permission text describing why contacts access is needed") so
iOS permission requests will succeed and pass review.
In `@Zplit-Website/.gitignore`:
- Line 12: Replace the glob pattern '*.vscode' in .gitignore with a
directory-specific entry (e.g., '.vscode/' or '/.vscode/') so the .vscode
directory is reliably ignored; update the existing '*.vscode' line to the
directory form to ensure Git treats it as a folder ignore rather than a filename
pattern.
In `@Zplit-Website/components/home/FinalCTA.tsx`:
- Around line 23-28: Replace the placeholder alert in the FinalCTA component's
form onSubmit handler with a real async submission to a minimal API endpoint
(e.g., POST /api/waitlist): validate the input (email) client-side, call an
async function (keep the handler name inline in the form onSubmit) that sends
JSON to /api/waitlist, handle success/error non-blockingly by setting local
state (e.g., successMessage / errorMessage) and rendering those messages in the
component, disable the submit button while the request is in flight, and remove
any alert(...) usage; also add basic error handling for network/validation
failures and ensure the API route performs server-side validation and returns
appropriate status codes.
In `@Zplit-Website/components/home/Hero.tsx`:
- Around line 56-75: The hero primary CTA buttons (the two motion.button
elements rendering "Download on iOS" and "Get on Android") are non-functional
because they have no click handlers or links; replace each motion.button with a
clickable element (either motion.a or add an onClick) that navigates to the
correct store URL (App Store and Google Play), include target="_blank"
rel="noopener noreferrer" and an appropriate aria-label for accessibility, and
ensure the className and motion props are preserved so visuals/animations remain
unchanged.
In `@Zplit-Website/components/shared/Footer.tsx`:
- Line 5: In the Footer component (export default function Footer) replace the
hardcoded "© 2025" with a runtime year by computing const year = new
Date().getFullYear() and using {year} in the JSX; do the same for the other
hardcoded occurrences referenced (around lines 65–67) so the displayed copyright
updates automatically each year.
In `@Zplit-Website/components/shared/Navbar.tsx`:
- Around line 160-167: The "Download App" controls in the Navbar component are
purely visual and lack handlers; update the motion.button elements in Navbar
(the desktop Download App button and the corresponding mobile Download App
control around lines referenced) to include an actionable onClick (or wrap with
an <a> link) that opens the appropriate app download URL or store page (use
target="_blank" rel="noopener noreferrer" for external links) and handle any
tracking or analytics call if needed; ensure both the desktop motion.button and
the mobile counterpart use the same onClick/link behavior so the CTA is
functional across viewports.
In `@Zplit-Website/public/.well-known/assetlinks.json`:
- Around line 8-10: The assetlinks.json sha256_cert_fingerprints entry does not
match your actual release signing key; update your release signing configuration
in android/app/build.gradle.kts (replace the temporary debug signing config and
the TODO block with a proper signingConfigs.release using your release keystore,
storePassword, keyAlias, and keyPassword) and then compute the release key's
SHA-256 fingerprint and replace the value in assetlinks.json (ensure
"package_name": "com.example.zplit" remains correct). After adding
signingConfigs.release and wiring it into the release buildType, verify the
fingerprint by running keytool (or your CI signing step) and synchronize that
exact fingerprint into the sha256_cert_fingerprints array so App Links will
work.
---
Nitpick comments:
In `@lib/main.dart`:
- Around line 29-30: You can remove the legacy starter-page code now that
SplashScreen is the app entrypoint: delete the MyHomePage widget class and any
unused references (constructor calls, routes, and imports) from this file so
main() only instantiates SplashScreen; keep any shared helpers or state used
elsewhere but ensure there are no remaining references to MyHomePage (search for
class MyHomePage, MyHomePageState, and any route names) and run the analyzer to
remove any unused imports.
In `@lib/views/onboarding/onboarding_screen.dart`:
- Around line 221-228: Replace the debugPrint call inside the
_controller.nextPage callback with a structured logger call: import and
initialize your chosen logging package (e.g., package:logger or
dart:developer/logging) at app startup, then use a log level (e.g., logger.i or
log.info) to record the message "Done onboarding — navigating to Profile
Setup..." in the nextPage callback where Navigator.pushReplacement to
ProfileSetupScreen is invoked; ensure the logger is injected or globally
accessible and do not include any sensitive data in the log message.
In `@lib/views/profile/add_friends_screen.dart`:
- Around line 4-10: Extract the Contact class into its own model file: create a
new Contact model file and move the Contact class (fields: name, avatarUrl,
isSelected and its constructor) out of add_friends_screen.dart, then update
add_friends_screen.dart to import the new model and replace the local definition
with the imported Contact; ensure the class signature (final String name, final
String avatarUrl, bool isSelected = false) and constructor remain unchanged and
update any other files referencing Contact to import the new model file.
- Around line 273-292: The FloatingActionButton.extended currently has an empty
onPressed stub; either implement the friend-add flow or mark it clearly and
disable the button until implemented. Replace the no-op onPressed with a call to
a new handler (e.g., _onAddPressed) that contains the friend-adding logic, or
set onPressed to null and add a TODO comment above the
FloatingActionButton.extended noting work remaining (e.g., TODO: implement
friend-add flow in _onAddPressed) so reviewers know it’s intentionally
unimplemented; ensure the handler name _onAddPressed (or finishFriendSetup) is
used consistently in the class.
In `@lib/views/profile/profile_setup_screen.dart`:
- Around line 62-69: The "Choose File" dialog option currently duplicates the
"Photo Library" because both call _controller.pickImage(ImageSource.gallery);
update the onTap for the _buildDialogOption titled 'Choose File' so it either
(A) is removed if redundant, or (B) invokes a file-picker flow instead of
ImageSource.gallery — e.g., call a new method on the controller like
_controller.pickFile() (which should use a file_picker implementation) and close
the dialog via Navigator.of(context).pop() as currently done; ensure you
add/implement pickFile() on the controller if choosing option B and adjust any
imports accordingly.
- Around line 175-179: The current kIsWeb branch uses NetworkImage(blob URL);
change to using Image.memory-backed MemoryImage for consistent cross-platform
rendering by reading bytes from the XFile and using MemoryImage as the
DecorationImage's image provider. Concretely: where DecorationImage is built
(reference: DecorationImage and imageFile), replace the kIsWeb NetworkImage path
with a MemoryImage created from await imageFile.readAsBytes() (or preload bytes
into state) and supply that MemoryImage to the DecorationImage; if asynchronous
loading is needed, wrap the decoration/content in a FutureBuilder around
imageFile.readAsBytes() to return DecorationImage/placeholder once bytes are
available.
In `@License.md`:
- Around line 1-595: The License.md file contains legal text that must not be
auto-modified by prose/style linters or autofixers; update the repository's
prose/style tooling configuration (e.g., prose-lint, markdownlint, or your prose
rewrite tool's ignore list) to exclude "License.md" from auto-fixes and style
checks, and add a comment or rule entry in the relevant config (e.g., prose-lint
ignore, .markdownlint.json, or your formatter's ignore) so that prose/style
rewrite actions skip this file going forward.
In `@Zplit-Website/app/page.tsx`:
- Around line 1-25: The file-level 'use client' directive at the top of
app/page.tsx is unnecessary because the Home component does not use hooks;
remove the "'use client';" line to convert Home into a Server Component while
keeping its client children (Navbar, P2PMethods, FAQ, FinalCTA, etc.) as client
components—locate the top of the file where "'use client';" is declared and
delete that line, leaving the default export function Home() and its JSX
unchanged.
In `@Zplit-Website/components/home/FAQ.tsx`:
- Around line 81-88: The FAQ toggle needs ARIA state and relationships: in the
FAQ component locate the toggle button that controls the collapsing motion.div
(around the motion.div shown) and add aria-expanded tied to the open state
(e.g., aria-expanded={isOpen}) and aria-controls pointing to a unique panel id
(e.g., `faq-panel-${index}`); give the collapsible container the matching id,
role="region", and aria-labelledby pointing back to the toggle button id (e.g.,
`faq-button-${index}`), and ensure the button has that id; apply the same
aria-expanded/aria-controls/id/role/aria-labelledby wiring for the other FAQ
block referenced (lines ~118-123) so each question/panel pair is properly linked
and accessible.
In `@Zplit-Website/components/home/Features.tsx`:
- Around line 46-135: The component has many repeated motion.div blocks (each
using variants={item}) for feature cards; refactor by extracting a features
array (objects with icon, title, description, tags, and optional color classes)
and replace the repeated JSX with a map over that array to render a single Card
render (keep variants={item} on the mapped motion.div), updating Features.tsx to
use the new data-driven render and removing duplicate blocks; ensure the array
fields match where used (icon, title, description, tags, hover/badge color
classes) so existing styles and the item animation remain unchanged.
In `@Zplit-Website/components/home/P2PMethods.tsx`:
- Around line 27-47: The tab buttons and content need proper ARIA/tab semantics:
add role="tablist" to the wrapper around the two buttons, give each button
role="tab" plus unique ids (e.g., "tab-p2p" and "tab-tech"), set aria-selected
based on activeTab and aria-controls pointing to corresponding panel ids (e.g.,
"panel-p2p"/"panel-tech"), and ensure each content panel under P2PMethods has
role="tabpanel", an id matching aria-controls, and is hidden (or aria-hidden)
when inactive; also add keyboard handling on the tabs (onKeyDown) to support
ArrowLeft/ArrowRight (and Home/End) to move focus and call setActiveTab. Apply
the same changes to the panels and tab controls in the remainder of the file
(lines ~50-190).
In `@Zplit-Website/components/shared/Navbar.tsx`:
- Around line 104-131: Desktop nav anchor tags use plain hash navigation while
mobile uses handleLinkClick to apply a fixed-header offset, causing inconsistent
scroll positions; update the desktop link elements (the <a> anchors for
"Features", "Technology", "Gallery", "FAQ") to use the same click handler
instead of direct href navigation: attach onClick handlers that call
handleLinkClick with the target id (and preventDefault) or call the shared
scroll-with-offset utility used by mobile, ensuring the link labels remain
accessible and retain href attributes for fallback/SEO if desired so behavior is
identical across desktop and mobile.
In `@Zplit-Website/next.config.ts`:
- Around line 5-9: The remotePatterns entry in next.config.ts is too permissive;
restrict its pathname to the exact asset used by the Image in Footer.tsx (the
/logo1.png path) to follow least-privilege. Edit the remotePatterns object (same
block containing protocol: 'https' and hostname: 'aossie.org') and add a
pathname property that exactly matches '/logo1.png' so only that asset is
allowed; verify Footer.tsx's Image src matches that path and adjust if
necessary.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f038272e-424b-41f7-868e-e6ced0a2bf3b
⛔ Files ignored due to path filters (15)
.DS_Storeis excluded by!**/.DS_StoreZplit-Website/app/favicon.icois excluded by!**/*.icoZplit-Website/package-lock.jsonis excluded by!**/package-lock.jsonZplit-Website/public/file.svgis excluded by!**/*.svgZplit-Website/public/globe.svgis excluded by!**/*.svgZplit-Website/public/next.svgis excluded by!**/*.svgZplit-Website/public/vercel.svgis excluded by!**/*.svgZplit-Website/public/window.svgis excluded by!**/*.svgZplit-Website/stability.svgis excluded by!**/*.svgassets/icons/add_photo.svgis excluded by!**/*.svgassets/images/onboarding1.pngis excluded by!**/*.pngassets/images/onboarding2.pngis excluded by!**/*.pngassets/images/onboarding3.pngis excluded by!**/*.pngassets/images/zplit_logo.pngis excluded by!**/*.pngpubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (49)
.dart_tool/dartpad/web_plugin_registrant.dart.dart_tool/package_config.json.dart_tool/package_graph.json.dart_tool/version.gitignore.idea/libraries/Dart_SDK.xml.idea/libraries/KotlinJavaRuntime.xml.idea/modules.xml.idea/runConfigurations/main_dart.xml.idea/workspace.xmlCOPYRIGHT.mdLicense.mdZplit-Website/.gitignoreZplit-Website/README.mdZplit-Website/app/globals.cssZplit-Website/app/layout.tsxZplit-Website/app/page.tsxZplit-Website/app/providers.tsxZplit-Website/components/home/AppGallery.tsxZplit-Website/components/home/FAQ.tsxZplit-Website/components/home/Features.tsxZplit-Website/components/home/FinalCTA.tsxZplit-Website/components/home/Hero.tsxZplit-Website/components/home/OpenSourceCTA.tsxZplit-Website/components/home/P2PMethods.tsxZplit-Website/components/shared/Footer.tsxZplit-Website/components/shared/Navbar.tsxZplit-Website/eslint.config.mjsZplit-Website/next.config.tsZplit-Website/package.jsonZplit-Website/postcss.config.mjsZplit-Website/public/.well-known/assetlinks.jsonZplit-Website/tsconfig.jsonandroid/app/src/main/AndroidManifest.xmlandroid/zplit_android.imldesign/zplit-design.figlib/controllers/onboarding_controller.dartlib/controllers/profile_setup_controller.dartlib/core/constants/colors.dartlib/main.dartlib/models/onboarding_model.dartlib/services/onboarding_service.dartlib/views/onboarding/onboarding_screen.dartlib/views/profile/add_friends_screen.dartlib/views/profile/profile_setup_screen.dartlib/views/splash/splash_screen.dartpubspec.yamlzplit.imlzplit2.iml
💤 Files with no reviewable changes (12)
- .dart_tool/version
- zplit.iml
- .dart_tool/package_graph.json
- .idea/workspace.xml
- .dart_tool/dartpad/web_plugin_registrant.dart
- .idea/modules.xml
- .idea/runConfigurations/main_dart.xml
- .dart_tool/package_config.json
- zplit2.iml
- .idea/libraries/KotlinJavaRuntime.xml
- android/zplit_android.iml
- .idea/libraries/Dart_SDK.xml
| Copyright © 2025 The Stable Order <br /> | ||
| All rights reserved. | ||
|
|
||
| All works in this repository may be used according to the conditions | ||
| stated in the LICENSE.md file available in this repository. |
There was a problem hiding this comment.
Conflicting license signals create legal ambiguity (Line 2 vs Line 4-5).
All rights reserved conflicts with the GPL grant referenced in this repo. Keep one consistent licensing statement to avoid downstream compliance risk.
Proposed fix
Copyright © 2025 The Stable Order <br />
-All rights reserved.
+Licensed under the GNU General Public License v3.0 (see License.md).
All works in this repository may be used according to the conditions
stated in the LICENSE.md file available in this repository.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Copyright © 2025 The Stable Order <br /> | |
| All rights reserved. | |
| All works in this repository may be used according to the conditions | |
| stated in the LICENSE.md file available in this repository. | |
| Copyright © 2025 The Stable Order <br /> | |
| Licensed under the GNU General Public License v3.0 (see License.md). | |
| All works in this repository may be used according to the conditions | |
| stated in the LICENSE.md file available in this repository. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@COPYRIGHT.md` around lines 1 - 5, The COPYRIGHT.md contains a conflicting
"All rights reserved" phrase vs the GPL grant in LICENSE.md; remove or replace
the "All rights reserved" line and replace the header with a single consistent
copyright/license statement that references the repository's GPL details (e.g.,
"Copyright © 2025 The Stable Order. Licensed under the GNU General Public
License vX.Y; see LICENSE.md for full terms"), ensuring the file only references
the LICENSE.md GPL grant and no proprietary-reserved language so the wording is
consistent across the repository.
| All rights reserved. | ||
|
|
||
| All works in this repository may be used according to the conditions | ||
| stated in the LICENSE.md file available in this repository. |
There was a problem hiding this comment.
Fix filename casing in the license reference (Line 5).
This line points to LICENSE.md, but the file added in this PR is License.md. Update one side so the reference is consistent on case-sensitive environments.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@COPYRIGHT.md` at line 5, The reference in COPYRIGHT.md points to LICENSE.md
but the PR added a file named License.md; update the casing so they match on
case-sensitive systems by changing the reference in COPYRIGHT.md from
"LICENSE.md" to "License.md" (or alternatively rename the added file to
"LICENSE.md"); ensure all occurrences of the filename in COPYRIGHT.md and the
repository use the same exact casing.
| Future<void> _loadData() async { | ||
| _isLoading = true; | ||
| notifyListeners(); | ||
|
|
||
| try { | ||
| // Data fetch from the mocked backend service layer | ||
| _pages = await _service.fetchOnboardingData(); | ||
| } catch (e) { | ||
| // Future API integrations: Error state handler can go here | ||
| debugPrint("Failed to load onboarding payload: $e"); | ||
| } finally { | ||
| _isLoading = false; | ||
| notifyListeners(); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Inspecting async lifecycle safety in OnboardingController..."
rg -n "Future<void> _loadData|notifyListeners|void dispose|_isDisposed" lib/controllers/onboarding_controller.dartRepository: StabilityNexus/Zplit
Length of output: 264
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Inspecting full OnboardingController structure ==="
wc -l lib/controllers/onboarding_controller.dart
echo -e "\n=== Constructor and _loadData() ==="
sed -n '1,45p' lib/controllers/onboarding_controller.dart
echo -e "\n=== dispose() method and context (lines 70-85) ==="
sed -n '70,85p' lib/controllers/onboarding_controller.dartRepository: StabilityNexus/Zplit
Length of output: 1614
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Searching for constructor calls to _loadData ==="
rg -n "OnboardingController|_loadData\(\)" lib/controllers/onboarding_controller.dart | head -20Repository: StabilityNexus/Zplit
Length of output: 258
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Searching for all async methods in controller ==="
rg -n "async|notifyListeners" lib/controllers/onboarding_controller.dart
echo -e "\n=== Full methods that contain async and notifyListeners ==="
ast-grep --pattern 'Future<$_> $_() async {
$$$
notifyListeners()
$$$
}'Repository: StabilityNexus/Zplit
Length of output: 296
Guard async completion from notifying after dispose.
_loadData() is called from the constructor without awaiting. If the controller is disposed before _service.fetchOnboardingData() completes, the finally block (line 33) will call notifyListeners() on a disposed ChangeNotifier, causing a Flutter assertion error. Add a disposal guard.
Proposed fix
class OnboardingController extends ChangeNotifier {
final OnboardingService _service = OnboardingService();
final PageController pageController = PageController();
+ bool _isDisposed = false;
@@
Future<void> _loadData() async {
+ if (_isDisposed) return;
_isLoading = true;
notifyListeners();
@@
} finally {
+ if (_isDisposed) return;
_isLoading = false;
notifyListeners();
}
}
@@
`@override`
void dispose() {
+ _isDisposed = true;
pageController.dispose();
super.dispose();
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/controllers/onboarding_controller.dart` around lines 21 - 34, The async
_loadData() can call notifyListeners() after the controller is disposed; add a
disposal guard: add a private bool _disposed = false, set _disposed = true in
the controller's dispose() override, and wrap state updates and
notifyListeners() inside checks (if (!_disposed) { ... }) in _loadData (around
assigning _pages, setting _isLoading, and both notifyListeners() calls) so no
notifications run on a disposed ChangeNotifier; keep using
_service.fetchOnboardingData() as before and only update state when !_disposed.
| child: contact.avatarUrl.isEmpty | ||
| ? Text( | ||
| contact.name[0].toUpperCase(), |
There was a problem hiding this comment.
Potential crash if contact.name is empty.
Accessing contact.name[0] will throw a RangeError if the name is an empty string. Add a bounds check.
🛡️ Safe access
child: contact.avatarUrl.isEmpty
? Text(
- contact.name[0].toUpperCase(),
+ contact.name.isNotEmpty ? contact.name[0].toUpperCase() : '?',
style: const TextStyle(Apply the same fix at line 239.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| child: contact.avatarUrl.isEmpty | |
| ? Text( | |
| contact.name[0].toUpperCase(), | |
| child: contact.avatarUrl.isEmpty | |
| ? Text( | |
| contact.name.isNotEmpty ? contact.name[0].toUpperCase() : '?', | |
| style: const TextStyle( |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/views/profile/add_friends_screen.dart` around lines 126 - 128, The code
uses contact.name[0].toUpperCase() when rendering the avatar placeholder which
will throw if contact.name is empty; update the widget in
add_friends_screen.dart to guard access by checking contact.name.isNotEmpty
(e.g., use contact.name.isNotEmpty ? contact.name[0].toUpperCase() : '?' or a
fallback character) instead of unconditionally indexing, and apply the identical
bounds-check/fallback fix to the other occurrence referenced around line 239 so
both avatar placeholders never index into an empty string.
| Expanded( | ||
| child: ListView.builder( | ||
| padding: const EdgeInsets.symmetric(horizontal: 4), | ||
| itemCount: _contacts.length, | ||
| itemBuilder: (context, index) { | ||
| final contact = _contacts[index]; | ||
| // Basic search filter | ||
| if (_searchQuery.isNotEmpty && | ||
| !contact.name.toLowerCase().contains(_searchQuery.toLowerCase())) { | ||
| return const SizedBox.shrink(); | ||
| } |
There was a problem hiding this comment.
Search filter approach causes incorrect itemCount and potential UX issues.
Using SizedBox.shrink() inside the itemBuilder while keeping the full itemCount creates problems:
- Incorrect scroll extent estimation
- Invisible gaps in the list
- Accessibility issues with screen readers
Filter the list before building instead.
🔧 Recommended fix
+ List<Contact> get _filteredContacts => _contacts
+ .where((c) => _searchQuery.isEmpty ||
+ c.name.toLowerCase().contains(_searchQuery.toLowerCase()))
+ .toList();
+
// In build method:
Expanded(
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 4),
- itemCount: _contacts.length,
+ itemCount: _filteredContacts.length,
itemBuilder: (context, index) {
- final contact = _contacts[index];
- // Basic search filter
- if (_searchQuery.isNotEmpty &&
- !contact.name.toLowerCase().contains(_searchQuery.toLowerCase())) {
- return const SizedBox.shrink();
- }
+ final contact = _filteredContacts[index];
return ListTile(📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Expanded( | |
| child: ListView.builder( | |
| padding: const EdgeInsets.symmetric(horizontal: 4), | |
| itemCount: _contacts.length, | |
| itemBuilder: (context, index) { | |
| final contact = _contacts[index]; | |
| // Basic search filter | |
| if (_searchQuery.isNotEmpty && | |
| !contact.name.toLowerCase().contains(_searchQuery.toLowerCase())) { | |
| return const SizedBox.shrink(); | |
| } | |
| List<Contact> get _filteredContacts => _contacts | |
| .where((c) => _searchQuery.isEmpty || | |
| c.name.toLowerCase().contains(_searchQuery.toLowerCase())) | |
| .toList(); | |
| `@override` | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| // ... scaffold code ... | |
| body: Column( | |
| children: [ | |
| // ... search bar code ... | |
| Expanded( | |
| child: ListView.builder( | |
| padding: const EdgeInsets.symmetric(horizontal: 4), | |
| itemCount: _filteredContacts.length, | |
| itemBuilder: (context, index) { | |
| final contact = _filteredContacts[index]; | |
| return ListTile( | |
| // ... rest of ListTile code ... |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/views/profile/add_friends_screen.dart` around lines 217 - 227, The
current ListView.builder in add_friends_screen.dart uses _contacts.length with
an itemBuilder that returns SizedBox.shrink() for non-matching entries, which
causes wrong scroll extent and accessibility gaps; instead compute a filtered
list (e.g., final visibleContacts = _contacts.where(...).toList() using
_searchQuery and contact.name), then use ListView.builder with itemCount:
visibleContacts.length and build each row from visibleContacts[index] (update
any references to contact to use visibleContacts) so only matching items are
rendered.
| <form | ||
| onSubmit={(e) => { | ||
| e.preventDefault(); | ||
| // TODO: Implement waitlist submission | ||
| alert('Waitlist feature coming soon! Follow us on GitHub for updates.'); | ||
| }} |
There was a problem hiding this comment.
Waitlist submission flow is placeholder-only.
The form currently discards user input and only shows alert(...). Since this is presented as a real waitlist capture, the user flow is effectively broken.
I can help wire this to a minimal API route (/api/waitlist) with validation + non-blocking success/error UI and no alert. Want me to draft that patch?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Zplit-Website/components/home/FinalCTA.tsx` around lines 23 - 28, Replace the
placeholder alert in the FinalCTA component's form onSubmit handler with a real
async submission to a minimal API endpoint (e.g., POST /api/waitlist): validate
the input (email) client-side, call an async function (keep the handler name
inline in the form onSubmit) that sends JSON to /api/waitlist, handle
success/error non-blockingly by setting local state (e.g., successMessage /
errorMessage) and rendering those messages in the component, disable the submit
button while the request is in flight, and remove any alert(...) usage; also add
basic error handling for network/validation failures and ensure the API route
performs server-side validation and returns appropriate status codes.
| <motion.button | ||
| whileHover={{ scale: 1.05, translateY: -2 }} | ||
| whileTap={{ scale: 0.95 }} | ||
| className="flex items-center gap-2 rounded-full bg-emerald-900 px-6 py-3 font-semibold text-white shadow-lg transition-all hover:bg-emerald-800 hover:shadow-xl dark:bg-emerald-600 dark:hover:bg-emerald-500" | ||
| > | ||
| <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> | ||
| <path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" /> | ||
| </svg> | ||
| Download on iOS | ||
| </motion.button> | ||
| <motion.button | ||
| whileHover={{ scale: 1.05, translateY: -2 }} | ||
| whileTap={{ scale: 0.95 }} | ||
| className="flex items-center gap-2 rounded-full border-2 border-emerald-900 px-6 py-3 font-semibold text-emerald-900 transition-all hover:bg-emerald-900 hover:text-white dark:border-emerald-600 dark:text-emerald-400 dark:hover:bg-emerald-600 dark:hover:text-white" | ||
| > | ||
| <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> | ||
| <path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-3.198l2.807 1.626a1 1 0 0 1 0 1.73l-2.808 1.626L15.206 12l2.492-2.491zM5.864 2.658L16.802 8.99l-2.303 2.303-8.635-8.635z" /> | ||
| </svg> | ||
| Get on Android | ||
| </motion.button> |
There was a problem hiding this comment.
Primary download CTAs are non-functional.
Both hero CTA buttons render but perform no action. This is a broken core conversion path on the landing page.
Proposed fix
- <motion.button
+ <motion.a
whileHover={{ scale: 1.05, translateY: -2 }}
whileTap={{ scale: 0.95 }}
+ href="https://apps.apple.com/app/idYOUR_APP_ID"
+ target="_blank"
+ rel="noopener noreferrer"
className="flex items-center gap-2 rounded-full bg-emerald-900 px-6 py-3 font-semibold text-white shadow-lg transition-all hover:bg-emerald-800 hover:shadow-xl dark:bg-emerald-600 dark:hover:bg-emerald-500"
>
@@
- </motion.button>
+ </motion.a>
- <motion.button
+ <motion.a
whileHover={{ scale: 1.05, translateY: -2 }}
whileTap={{ scale: 0.95 }}
+ href="https://play.google.com/store/apps/details?id=YOUR_PACKAGE_ID"
+ target="_blank"
+ rel="noopener noreferrer"
className="flex items-center gap-2 rounded-full border-2 border-emerald-900 px-6 py-3 font-semibold text-emerald-900 transition-all hover:bg-emerald-900 hover:text-white dark:border-emerald-600 dark:text-emerald-400 dark:hover:bg-emerald-600 dark:hover:text-white"
>
@@
- </motion.button>
+ </motion.a>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <motion.button | |
| whileHover={{ scale: 1.05, translateY: -2 }} | |
| whileTap={{ scale: 0.95 }} | |
| className="flex items-center gap-2 rounded-full bg-emerald-900 px-6 py-3 font-semibold text-white shadow-lg transition-all hover:bg-emerald-800 hover:shadow-xl dark:bg-emerald-600 dark:hover:bg-emerald-500" | |
| > | |
| <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> | |
| <path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" /> | |
| </svg> | |
| Download on iOS | |
| </motion.button> | |
| <motion.button | |
| whileHover={{ scale: 1.05, translateY: -2 }} | |
| whileTap={{ scale: 0.95 }} | |
| className="flex items-center gap-2 rounded-full border-2 border-emerald-900 px-6 py-3 font-semibold text-emerald-900 transition-all hover:bg-emerald-900 hover:text-white dark:border-emerald-600 dark:text-emerald-400 dark:hover:bg-emerald-600 dark:hover:text-white" | |
| > | |
| <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> | |
| <path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-3.198l2.807 1.626a1 1 0 0 1 0 1.73l-2.808 1.626L15.206 12l2.492-2.491zM5.864 2.658L16.802 8.99l-2.303 2.303-8.635-8.635z" /> | |
| </svg> | |
| Get on Android | |
| </motion.button> | |
| <motion.a | |
| whileHover={{ scale: 1.05, translateY: -2 }} | |
| whileTap={{ scale: 0.95 }} | |
| href="https://apps.apple.com/app/idYOUR_APP_ID" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="flex items-center gap-2 rounded-full bg-emerald-900 px-6 py-3 font-semibold text-white shadow-lg transition-all hover:bg-emerald-800 hover:shadow-xl dark:bg-emerald-600 dark:hover:bg-emerald-500" | |
| > | |
| <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> | |
| <path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" /> | |
| </svg> | |
| Download on iOS | |
| </motion.a> | |
| <motion.a | |
| whileHover={{ scale: 1.05, translateY: -2 }} | |
| whileTap={{ scale: 0.95 }} | |
| href="https://play.google.com/store/apps/details?id=YOUR_PACKAGE_ID" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="flex items-center gap-2 rounded-full border-2 border-emerald-900 px-6 py-3 font-semibold text-emerald-900 transition-all hover:bg-emerald-900 hover:text-white dark:border-emerald-600 dark:text-emerald-400 dark:hover:bg-emerald-600 dark:hover:text-white" | |
| > | |
| <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> | |
| <path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-3.198l2.807 1.626a1 1 0 0 1 0 1.73l-2.808 1.626L15.206 12l2.492-2.491zM5.864 2.658L16.802 8.99l-2.303 2.303-8.635-8.635z" /> | |
| </svg> | |
| Get on Android | |
| </motion.a> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Zplit-Website/components/home/Hero.tsx` around lines 56 - 75, The hero
primary CTA buttons (the two motion.button elements rendering "Download on iOS"
and "Get on Android") are non-functional because they have no click handlers or
links; replace each motion.button with a clickable element (either motion.a or
add an onClick) that navigates to the correct store URL (App Store and Google
Play), include target="_blank" rel="noopener noreferrer" and an appropriate
aria-label for accessibility, and ensure the className and motion props are
preserved so visuals/animations remain unchanged.
| import { motion } from 'framer-motion'; | ||
| import Image from 'next/image'; | ||
|
|
||
| export default function Footer() { |
There was a problem hiding this comment.
Replace hardcoded copyright year with runtime year.
The hardcoded © 2025 is already outdated and will keep drifting.
🗓️ Suggested patch
export default function Footer() {
+ const currentYear = new Date().getFullYear();
+
return (
@@
<p className="text-sm text-zinc-600 dark:text-zinc-400">
- © 2025 Zplit. Open source under MIT License. Built with ❤️ for privacy.
+ © {currentYear} Zplit. Open source under MIT License. Built with ❤️ for privacy.
</p>Also applies to: 65-67
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Zplit-Website/components/shared/Footer.tsx` at line 5, In the Footer
component (export default function Footer) replace the hardcoded "© 2025" with a
runtime year by computing const year = new Date().getFullYear() and using {year}
in the JSX; do the same for the other hardcoded occurrences referenced (around
lines 65–67) so the displayed copyright updates automatically each year.
| <motion.button | ||
| whileHover={{ scale: 1.05 }} | ||
| whileTap={{ scale: 0.95 }} | ||
| className="group relative hidden overflow-hidden rounded-full bg-gradient-to-r from-emerald-600 to-teal-600 px-5 py-2 text-sm font-semibold text-white shadow-lg shadow-emerald-500/30 transition-all hover:shadow-xl hover:shadow-emerald-500/40 sm:block" | ||
| > | ||
| <span className="absolute inset-0 bg-gradient-to-r from-emerald-700 to-teal-700 opacity-0 transition-opacity group-hover:opacity-100"></span> | ||
| <span className="relative">Download App</span> | ||
| </motion.button> |
There was a problem hiding this comment.
Download App actions are non-functional in both desktop and mobile menus.
These controls are visible as primary navigation CTAs but currently do nothing.
Proposed fix
- <motion.button
+ <motion.a
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
+ href="https://example.com/download"
+ target="_blank"
+ rel="noopener noreferrer"
className="group relative hidden overflow-hidden rounded-full bg-gradient-to-r from-emerald-600 to-teal-600 px-5 py-2 text-sm font-semibold text-white shadow-lg shadow-emerald-500/30 transition-all hover:shadow-xl hover:shadow-emerald-500/40 sm:block"
>
@@
- </motion.button>
+ </motion.a>
@@
- <button className="rounded-lg bg-gradient-to-r from-emerald-600 to-teal-600 px-4 py-3 text-sm font-semibold text-white shadow-lg">
+ <a
+ href="https://example.com/download"
+ target="_blank"
+ rel="noopener noreferrer"
+ className="rounded-lg bg-gradient-to-r from-emerald-600 to-teal-600 px-4 py-3 text-center text-sm font-semibold text-white shadow-lg"
+ >
Download App
- </button>
+ </a>Also applies to: 241-243
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Zplit-Website/components/shared/Navbar.tsx` around lines 160 - 167, The
"Download App" controls in the Navbar component are purely visual and lack
handlers; update the motion.button elements in Navbar (the desktop Download App
button and the corresponding mobile Download App control around lines
referenced) to include an actionable onClick (or wrap with an <a> link) that
opens the appropriate app download URL or store page (use target="_blank"
rel="noopener noreferrer" for external links) and handle any tracking or
analytics call if needed; ensure both the desktop motion.button and the mobile
counterpart use the same onClick/link behavior so the CTA is functional across
viewports.
| "package_name": "com.example.zplit", | ||
| "sha256_cert_fingerprints": [ | ||
| "32:DA:ED:8E:C8:68:01:57:5A:A6:D2:8B:40:9F:E9:E5:BC:CB:7B:37:7B:ED:5E:40:07:49:6D:0A:3D:91:9F:1B" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== assetlinks.json target package + fingerprints =="
python - <<'PY'
import json
from pathlib import Path
p = Path("Zplit-Website/public/.well-known/assetlinks.json")
data = json.loads(p.read_text())
for i, entry in enumerate(data, 1):
target = entry.get("target", {})
print(f"entry[{i}] package_name={target.get('package_name')}")
for fp in target.get("sha256_cert_fingerprints", []):
print(f" sha256={fp}")
PY
echo
echo "== Android signing + applicationId config =="
rg -n -C2 'applicationId|buildTypes|release|signingConfig' android/app/build.gradle.ktsRepository: StabilityNexus/Zplit
Length of output: 978
🏁 Script executed:
#!/bin/bash
# Look for keystore files in the repository
echo "=== Searching for keystore files ==="
find . -type f \( -name "*.keystore" -o -name "*.jks" -o -name "*.pem" \) 2>/dev/null | head -20
echo
echo "=== Searching for signing config references in gradle files ==="
rg -l "signingConfigs|keystore|storeFile" android/ --type gradle --type properties
echo
echo "=== Check for gradle.properties or local.properties ==="
find . -name "gradle.properties" -o -name "local.properties" | xargs cat 2>/dev/null | head -50Repository: StabilityNexus/Zplit
Length of output: 440
Verify and synchronize certificate fingerprint with release signing configuration.
The assetlinks.json on line 10 must match the certificate fingerprint of your actual release signing key. Currently, android/app/build.gradle.kts signs release builds with debug keys (line 37), and no custom release signing configuration exists in the repository. The TODO comment at lines 35-36 explicitly flags this as incomplete. When you add proper release signing, you must update this fingerprint in lockstep—App Links will silently fail if they become misaligned.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Zplit-Website/public/.well-known/assetlinks.json` around lines 8 - 10, The
assetlinks.json sha256_cert_fingerprints entry does not match your actual
release signing key; update your release signing configuration in
android/app/build.gradle.kts (replace the temporary debug signing config and the
TODO block with a proper signingConfigs.release using your release keystore,
storePassword, keyAlias, and keyPassword) and then compute the release key's
SHA-256 fingerprint and replace the value in assetlinks.json (ensure
"package_name": "com.example.zplit" remains correct). After adding
signingConfigs.release and wiring it into the release buildType, verify the
fingerprint by running keytool (or your CI signing step) and synchronize that
exact fingerprint into the sha256_cert_fingerprints array so App Links will
work.
|
@akshat3410 actually this issue is already assigned to someone else ...and also why there is an entire zplit website in the file changes ?Please clean up the branch and remove commits that aren't related to the friend screen feature |
|
I think there’s a misunderstanding I didn’t add any Zplit website changes in this PR. The changes are only for the friend screen feature. |
This PR implements the interactive Add Friends flow from the Figma designs and integrates a real native Contacts permission request sequence on top of the newly added Start/Onboarding screens.
The PR is based off on: #31
Implemented
Demo video
Screen.Recording.2026-03-28.at.12.08.51.PM.mov
Summary by CodeRabbit
New Features
Chores