Skip to content

feat: integrate ORPC client and replace Confetti component with SkiaL…#10

Merged
chakrihacker merged 2 commits into
mainfrom
feat/add-orpc
Aug 12, 2025
Merged

feat: integrate ORPC client and replace Confetti component with SkiaL…#10
chakrihacker merged 2 commits into
mainfrom
feat/add-orpc

Conversation

@chakrihacker

@chakrihacker chakrihacker commented Aug 12, 2025

Copy link
Copy Markdown
Contributor

User description

…oader


PR Type

Enhancement


Description

  • Integrate ORPC client for API communication

  • Replace Confetti component with reusable SkiaLoader

  • Update authentication client configuration

  • Add environment variables for API URL


Diagram Walkthrough

flowchart LR
  A["ORPC Client"] --> B["API Communication"]
  C["SkiaLoader"] --> D["Component Loading"]
  E["Auth Client"] --> F["Plugin Configuration"]
  G["Environment"] --> H["API URL Config"]
Loading

File Walkthrough

Relevant files
Enhancement
6 files
index.ts
Create ORPC client for API communication                                 
+12/-0   
skia-loader.tsx
Add reusable SkiaLoader component                                               
+16/-0   
confetti.tsx
Simplify confetti component implementation                             
+12/-7   
confetti-base.tsx
Remove confetti base component file                                           
+0/-17   
sign-up-success-card.tsx
Replace Confetti with SkiaLoader usage                                     
+2/-2     
auth-client.ts
Reorder auth client plugin initialization                               
+3/-1     
Dependencies
1 files
package.json
Add ORPC dependencies and expo-video                                         
+4/-0     
Configuration changes
2 files
.env
Add API URL environment variable                                                 
+3/-2     
app.json
Add expo-video plugin configuration                                           
+2/-1     

Summary by CodeRabbit

  • New Features

    • Enabled video playback support in the app.
    • Added a client to connect the app to a hosted API for server-powered features.
  • Improvements

    • Sign-up success animation now lazy-loads for faster startup.
    • Confetti implementation updated for more consistent visuals and behavior.
  • Chores

    • Updated app configuration, environment settings, and dependencies to support the above.

@sourcery-ai

sourcery-ai Bot commented Aug 12, 2025

Copy link
Copy Markdown

Reviewer's Guide

This PR integrates an ORPC client for remote procedure calls and refactors the Confetti UI implementation by replacing the previous Skia-based component with react-native-fast-confetti and a dynamic SkiaLoader, while also updating dependencies (expo-video and ORPC packages) and adjusting the auth-client plugin setup.

Sequence diagram for dynamic loading of Confetti with SkiaLoader

sequenceDiagram
    participant User as actor User
    participant SignUpSuccessCard
    participant SkiaLoader
    participant WithSkiaWeb
    participant ConfettiBase
    User->>SignUpSuccessCard: Triggers sign-up success
    SignUpSuccessCard->>SkiaLoader: Requests Confetti component
    SkiaLoader->>WithSkiaWeb: Loads Skia web environment
    WithSkiaWeb->>ConfettiBase: Dynamically imports Confetti
    ConfettiBase-->>WithSkiaWeb: Renders Confetti
    WithSkiaWeb-->>SkiaLoader: Returns rendered component
    SkiaLoader-->>SignUpSuccessCard: Displays Confetti
Loading

Entity relationship diagram for ORPC client and OpenAPI spec

erDiagram
    ORPC_CLIENT ||--o| OPENAPI_LINK: uses
    OPENAPI_LINK {
        string url
        object specJSON
    }
    ORPC_CLIENT {
        OPENAPI_LINK link
    }
Loading

Class diagram for new ORPC client integration

classDiagram
    class OpenAPILink {
        +specJSON: object
        +url: string
    }
    class ORPCClient {
        +link: OpenAPILink
    }
    OpenAPILink <|-- ORPCClient: link
Loading

File-Level Changes

Change Details Files
Refactor Confetti component and introduce dynamic SkiaLoader
  • Rename Confetti to ConfettiBase and switch implementation to react-native-fast-confetti with custom colors and infinite flag
  • Create SkiaLoader component to wrap WithSkiaWeb and dynamically import Skia-based components
  • Update SignUpSuccessCard to use SkiaLoader instead of direct Confetti import
  • Remove legacy confetti-base.tsx file
src/components/ui/confetti.tsx
src/components/ui/skia-loader.tsx
src/components/auth/sign-up-success-card.tsx
src/components/ui/confetti-base.tsx
Integrate ORPC client setup for API calls
  • Add src/orpc/index.ts with logic to fetch spec, configure OpenAPILink, and create ORPC client
  • Add ORPC-related dependencies (@orpc/client, @orpc/contract, @orpc/openapi-client) to package.json
src/orpc/index.ts
package.json
Add expo-video support
  • Add expo-video to package.json dependencies
  • Register expo-video plugin in app.json
package.json
app.json
Adjust auth-client plugin configuration
  • Initialize plugins array empty, push native plugins conditionally, then always push adminClient
  • Remove redundant initial adminClient call
src/lib/auth-client.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 12, 2025

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Introduces ORPC client initialization using EXPO_PUBLIC_API_URL, updates environment and Expo config, adds ORPC and expo-video dependencies, refactors confetti UI to lazy-load via SkiaLoader and removes confetti-base, adjusts auth client plugin ordering, and adds an Expo plugin entry.

Changes

Cohort / File(s) Summary of changes
Environment & App Config
./.env, app.json, package.json
Added EXPO_PUBLIC_API_URL; quoted BETTER_AUTH_SECRET and BETTER_AUTH_URL. Included expo-video plugin in app.json. Added dependencies: @orpc/client, @orpc/contract, @orpc/openapi-client, expo-video.
Auth Client Ordering
src/lib/auth-client.ts
Reordered plugin setup: expoClient (non-web) precedes adminClient; web uses adminClient only. No signature changes.
Confetti UI Refactor
src/components/auth/sign-up-success-card.tsx, src/components/ui/confetti.tsx, src/components/ui/confetti-base.tsx (deleted), src/components/ui/skia-loader.tsx (new)
Replaced static Confetti import with SkiaLoader-based lazy load; migrated confetti implementation to react-native-fast-confetti with default export; removed confetti-base; added SkiaLoader for Skia web wasm loading.
ORPC Client Setup
src/orpc/index.ts
Added ORPC client initialization using OpenAPI spec from {EXPO_PUBLIC_API_URL}/orpc/spec.json and base {apiUrl}/orpc/. Exports initialized client.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant Env as EXPO_PUBLIC_API_URL
  participant Server as API Server (/orpc)
  participant ORPC as ORPC Client

  App->>Env: Read EXPO_PUBLIC_API_URL (default http://localhost:3000)
  App->>Server: GET {apiUrl}/orpc/spec.json
  Server-->>App: OpenAPI spec
  App->>ORPC: createORPCClient(OpenAPILink(spec, base={apiUrl}/orpc/))
  ORPC-->>App: orpc instance ready
Loading
sequenceDiagram
  participant UI as SignUpSuccessCard
  participant Loader as SkiaLoader
  participant Module as ~/components/ui/confetti

  UI->>Loader: component={() => import(".../confetti")}
  Loader-->>UI: WithSkiaWeb wrapper (locateFile '/canvaskit.wasm')
  Loader->>Module: Dynamic import
  Module-->>Loader: default Confetti component
  Loader-->>UI: Render Confetti
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes

Possibly related PRs

  • Move Router file structure #4 — Also changes plugin initialization order in src/lib/auth-client.ts, directly overlapping with this PR’s adjustments.

Suggested labels

Review effort 4/5

Poem

A whisk of code, a sprinkle of confetti light,
I thump my paw—ORPC takes flight!
Skia sneaks in with wasm delight,
Env vars lined up, commas just right.
Hippity-hop, plugins in sight—
Ship it, ship it, through the night! 🐇🎉

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-orpc

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 Security concerns

Sensitive information exposure:
The .env file includes what appears to be real secrets (e.g., BETTER_AUTH_SECRET and a full DATABASE_URL with embedded API key). These should not be committed to the repo. Treat these as compromised, rotate them, and use environment-specific secret management (e.g., CI secrets, .env.local ignored by VCS).

⚡ Recommended focus areas for review

Possible Issue

Top-level await may not be supported in all build targets/environments; consider lazy initialization or exporting a promise-based client factory to avoid runtime/bundle issues.

const specJSON = await fetch(`${apiUrl}/orpc/spec.json`).then((r) => r.json());

const link = new OpenAPILink(specJSON, {
	url: `${apiUrl}/orpc/`,
});

export const orpc = createORPCClient(link);
Configuration

The auth client baseURL is hard-coded; prefer using the new EXPO_PUBLIC_API_URL to keep environments consistent with the ORPC client configuration.

export const authClient = createAuthClient({
	baseURL: "https://ship-server.onrender.com/api/auth", // Base URL of your Better Auth backend.
	// baseURL: "http://localhost:3000/api/auth", // Base URL of your Better Auth backend.
	plugins: plugins,
	fetchOptions: {
		credentials: "include", // Include cookies in requests.
Secrets in VCS

Committed secrets and URLs (e.g., BETTER_AUTH_SECRET) appear in the .env diff; ensure these are not checked into version control and are rotated if already exposed.

BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"
BETTER_AUTH_URL="http://localhost:3000" #Base URL of your app
EXPO_PUBLIC_API_URL="https://ship-server.onrender.com"

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @chakrihacker - I've reviewed your changes - here's some feedback:

Blocking issues:

  • Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)
  • Generic Secret detected (link)

General comments:

  • The new SkiaLoader wraps the Confetti component, but confetti.tsx no longer provides a Skia-based implementation—verify web vs. native rendering and consider renaming or refactoring the loader to match the updated export.
  • Initializing the ORPC client with a top-level await in src/orpc/index.ts may cause issues in some bundlers—consider fetching the spec and creating the client inside an async function or initialization hook instead of at module load.
  • expo-video was added to package.json and app.json but isn’t used in the code—if it’s not needed, removing it will keep dependencies lean.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new SkiaLoader wraps the Confetti component, but confetti.tsx no longer provides a Skia-based implementation—verify web vs. native rendering and consider renaming or refactoring the loader to match the updated export.
- Initializing the ORPC client with a top-level await in src/orpc/index.ts may cause issues in some bundlers—consider fetching the spec and creating the client inside an async function or initialization hook instead of at module load.
- expo-video was added to package.json and app.json but isn’t used in the code—if it’s not needed, removing it will keep dependencies lean.

## Individual Comments

### Comment 1
<location> `src/orpc/index.ts:6` </location>
<code_context>
+
+const apiUrl = process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000";
+
+const specJSON = await fetch(`${apiUrl}/orpc/spec.json`).then((r) => r.json());
+
+const link = new OpenAPILink(specJSON, {
</code_context>

<issue_to_address>
Using top-level await may cause issues in environments that do not support it.

Top-level await is only supported in ES2022 modules and select environments. Refactor to use an async initialization function, or confirm all consumers support top-level await to avoid runtime errors.
</issue_to_address>

### Comment 2
<location> `src/orpc/index.ts:4` </location>
<code_context>
+import { createORPCClient } from "@orpc/client";
+import { OpenAPILink } from "@orpc/openapi-client/fetch";
+
+const apiUrl = process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000";
+
+const specJSON = await fetch(`${apiUrl}/orpc/spec.json`).then((r) => r.json());
</code_context>

<issue_to_address>
Defaulting to localhost for API URL may cause issues in production.

Defaulting to localhost may break production usage. Instead, consider throwing an error or using a safer fallback if EXPO_PUBLIC_API_URL is missing.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
const apiUrl = process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000";
=======
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
if (!apiUrl) {
	throw new Error(
		"EXPO_PUBLIC_API_URL is not set. Please configure the API URL in your environment variables."
	);
}
>>>>>>> REPLACE

</suggested_fix>

## Security Issues

### Issue 1
<location> `.env:2` </location>

<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

*Source: gitleaks*
</issue_to_address>

### Issue 2
<location> `.env:2` </location>

<issue_to_address>
**security (generic.secrets.security.detected-generic-secret):** Generic Secret detected

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/orpc/index.ts

const apiUrl = process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000";

const specJSON = await fetch(`${apiUrl}/orpc/spec.json`).then((r) => r.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Using top-level await may cause issues in environments that do not support it.

Top-level await is only supported in ES2022 modules and select environments. Refactor to use an async initialization function, or confirm all consumers support top-level await to avoid runtime errors.

Comment thread src/orpc/index.ts Outdated
import { createORPCClient } from "@orpc/client";
import { OpenAPILink } from "@orpc/openapi-client/fetch";

const apiUrl = process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Defaulting to localhost for API URL may cause issues in production.

Defaulting to localhost may break production usage. Instead, consider throwing an error or using a safer fallback if EXPO_PUBLIC_API_URL is missing.

Suggested change
const apiUrl = process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000";
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
if (!apiUrl) {
throw new Error(
"EXPO_PUBLIC_API_URL is not set. Please configure the API URL in your environment variables."
);
}

Comment thread .env
DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiMDFKWjdKSlpQRkQzNzROREE2RzA1MUtXRUIiLCJ0ZW5hbnRfaWQiOiJhZDMzYmZkYWM2NzhmNTAxMmE1NzgwODAwYzFmMDZhYmM1OGM3OTM4MjY0MDBiZTBhYzFjYWI0ZGRiYjAyOTg1IiwiaW50ZXJuYWxfc2VjcmV0IjoiYzRmNGQ1MWYtOTcyMy00MWNiLTllMWQtOWMwMTI5YmI4MWY1In0.BwHw4GrvmaD5UXmrQuN_tnC7o2_OwtG_2lhMg2nQEXY"
BETTER_AUTH_SECRET=EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6
BETTER_AUTH_URL=http://localhost:3000 #Base URL of your app No newline at end of file
BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (generic-api-key): Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

Source: gitleaks

Comment thread .env
DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiMDFKWjdKSlpQRkQzNzROREE2RzA1MUtXRUIiLCJ0ZW5hbnRfaWQiOiJhZDMzYmZkYWM2NzhmNTAxMmE1NzgwODAwYzFmMDZhYmM1OGM3OTM4MjY0MDBiZTBhYzFjYWI0ZGRiYjAyOTg1IiwiaW50ZXJuYWxfc2VjcmV0IjoiYzRmNGQ1MWYtOTcyMy00MWNiLTllMWQtOWMwMTI5YmI4MWY1In0.BwHw4GrvmaD5UXmrQuN_tnC7o2_OwtG_2lhMg2nQEXY"
BETTER_AUTH_SECRET=EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6
BETTER_AUTH_URL=http://localhost:3000 #Base URL of your app No newline at end of file
BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (generic.secrets.security.detected-generic-secret): Generic Secret detected

Source: opengrep

@qodo-code-review

qodo-code-review Bot commented Aug 12, 2025

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Remove secrets from .env

The PR commits real secrets (DATABASE_URL with API key and BETTER_AUTH_SECRET)
into version control, which is a critical security risk and likely violates
least-privilege practices. Replace these with placeholders, load them from
secure env management (CI secrets, EAS/Expo secrets, dotenv ignored by VCS), and
rotate the exposed keys immediately.

Examples:

.env [1-2]
DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiMDFKWjdKSlpQRkQzNzROREE2RzA1MUtXRUIiLCJ0ZW5hbnRfaWQiOiJhZDMzYmZkYWM2NzhmNTAxMmE1NzgwODAwYzFmMDZhYmM1OGM3OTM4MjY0MDBiZTBhYzFjYWI0ZGRiYjAyOTg1IiwiaW50ZXJuYWxfc2VjcmV0IjoiYzRmNGQ1MWYtOTcyMy00MWNiLTllMWQtOWMwMTI5YmI4MWY1In0.BwHw4GrvmaD5UXmrQuN_tnC7o2_OwtG_2lhMg2nQEXY"
BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"

Solution Walkthrough:

Before:

# .env
DATABASE_URL="prisma+postgres://...[REAL_API_KEY]..."
BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"
...

After:

# .env.example (and .env should be in .gitignore)
# These values should be loaded from a secure environment
DATABASE_URL="YOUR_DATABASE_URL_WITH_KEY"
BETTER_AUTH_SECRET="YOUR_BETTER_AUTH_SECRET"
...
Suggestion importance[1-10]: 10

__

Why: This suggestion addresses a critical security vulnerability by pointing out that secrets are being committed to version control in the .env file.

High
Possible issue
Avoid top-level await usage

Top-level await will throw in environments without TLA support (e.g.,
Metro/older RN), causing module load failure. Wrap the async fetch in a lazy
initializer to avoid synchronous import errors and surface network problems
gracefully.

src/orpc/index.ts [4-12]

 const apiUrl = process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000";
 
-const specJSON = await fetch(`${apiUrl}/orpc/spec.json`).then((r) => r.json());
+async function initORPC() {
+	const res = await fetch(`${apiUrl}/orpc/spec.json`);
+	if (!res.ok) throw new Error(`Failed to load ORPC spec: ${res.status}`);
+	const specJSON = await res.json();
 
-const link = new OpenAPILink(specJSON, {
-	url: `${apiUrl}/orpc/`,
-});
+	const link = new OpenAPILink(specJSON, {
+		url: `${apiUrl}/orpc/`,
+	});
+	return createORPCClient(link);
+}
 
-export const orpc = createORPCClient(link);
+export const orpc = initORPC();
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that top-level await can cause module loading failures in environments that do not support it, and the proposed change improves compatibility and error handling.

Medium
Use requested file in locator

The locateFile callback ignores the file parameter, which can break when Skia
requests other assets or when the wasm filename changes. Use the provided
filename to preserve Skia's expected pathing. This prevents 404s and failed
canvas initialization on web.

src/components/ui/skia-loader.tsx [1-16]

 import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
 
 interface SkiaCanvasProps {
 	component: () => Promise<{ default: React.ComponentType }>;
 }
 
 export function SkiaLoader({ component }: SkiaCanvasProps) {
 	return (
 		<WithSkiaWeb
 			opts={{
-				locateFile: (file) => `/canvaskit.wasm`,
+				locateFile: (file) => `/${file}`,
 			}}
 			getComponent={component}
 		/>
 	);
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that hardcoding the filename in locateFile is brittle and improves robustness by using the file parameter, preventing potential future issues.

Medium
  • Update

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
.env (2)

2-3: Consider removing quotes from environment variable values.

The static analysis tool correctly identifies that quotes are not needed around environment variable values in .env files. The quotes will be included as part of the actual values.

-BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"
-BETTER_AUTH_URL="http://localhost:3000" #Base URL of your app
+BETTER_AUTH_SECRET=EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6
+BETTER_AUTH_URL=http://localhost:3000 #Base URL of your app

4-4: Add missing trailing newline.

The file should end with a blank line as indicated by the static analysis tool.

 EXPO_PUBLIC_API_URL="https://ship-server.onrender.com"
+
src/components/ui/confetti.tsx (1)

3-3: Consider export consistency with component naming.

The component is named ConfettiBase but exported as default. Consider either naming it Confetti to match the previous API or using a named export for consistency.

-export default function ConfettiBase() {
+export default function Confetti() {

or

-export default function ConfettiBase() {
+export function ConfettiBase() {
src/components/ui/skia-loader.tsx (1)

3-5: Enhance type safety with generic constraints.

The component type definition could be more specific to ensure the imported component is actually renderable.

 interface SkiaCanvasProps {
-	component: () => Promise<{ default: React.ComponentType }>;
+	component: () => Promise<{ default: React.ComponentType<any> }>;
 }
src/components/auth/sign-up-success-card.tsx (1)

36-36: Consider adding loading state for better UX.

While the lazy loading is implemented correctly, users may experience a blank space while the confetti component loads. Consider adding a loading indicator or fallback.

-			<SkiaLoader component={() => import("~/components/ui/confetti")} />
+			<React.Suspense fallback={<View className="h-32 w-full" />}>
+				<SkiaLoader component={() => import("~/components/ui/confetti")} />
+			</React.Suspense>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d57575e and db29399.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • .env (1 hunks)
  • app.json (1 hunks)
  • package.json (2 hunks)
  • src/components/auth/sign-up-success-card.tsx (2 hunks)
  • src/components/ui/confetti-base.tsx (0 hunks)
  • src/components/ui/confetti.tsx (1 hunks)
  • src/components/ui/skia-loader.tsx (1 hunks)
  • src/lib/auth-client.ts (2 hunks)
  • src/orpc/index.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/components/ui/confetti-base.tsx
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

src/**/*.{ts,tsx}: Use Platform.OS and process.env.EXPO_OS for conditional logic
Consider all platforms (iOS/Android/Web) when using native APIs

Files:

  • src/components/auth/sign-up-success-card.tsx
  • src/orpc/index.ts
  • src/components/ui/skia-loader.tsx
  • src/lib/auth-client.ts
  • src/components/ui/confetti.tsx
src/**/*.tsx

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

Import UI components from @/src/components/ui/ using path aliases

Files:

  • src/components/auth/sign-up-success-card.tsx
  • src/components/ui/skia-loader.tsx
  • src/components/ui/confetti.tsx
src/components/ui/**/*.tsx

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

src/components/ui/**/*.tsx: UI components use NativeWind's className prop with Tailwind utility classes
Use cva (class-variance-authority) for creating variant-based component APIs
Access theme values through CSS custom properties and Tailwind utilities
UI components in src/components/ui/ follow React.forwardRef pattern with displayName
Use Tailwind's responsive prefixes (sm:, md:, lg:) and NativeWind's responsive utilities
Use web: prefix for web-only styles, and conditional rendering for native-specific components
Place UI components in src/components/ui/ with proper TypeScript interfaces
Always use NativeWind's className prop with Tailwind utility classes
Define variants using cva (class-variance-authority) for type-safe component APIs
Use Tailwind's spacing utilities (p-4, m-2, gap-3, etc.)
Use Tailwind's typography utilities (text-lg, font-semibold, etc.)
Toggle between light/dark themes using className conditionals and CSS variables
Use platform-specific prefixes (web:, ios:, android:) for conditional styles

Files:

  • src/components/ui/skia-loader.tsx
  • src/components/ui/confetti.tsx
src/lib/auth-client.ts

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

src/lib/auth-client.ts: Auth client in src/lib/auth-client.ts with platform-specific plugins
Better Auth requires platform-specific plugin setup for native vs web storage

Files:

  • src/lib/auth-client.ts
{src/lib/auth-client.ts,src/app/(auth)/**/*.tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

{src/lib/auth-client.ts,src/app/(auth)/**/*.tsx}: Use authClient.useSession() hook for session state
Use authClient hooks for auth state management

Files:

  • src/lib/auth-client.ts
🧠 Learnings (3)
📚 Learning: 2025-07-28T12:52:53.959Z
Learnt from: CR
PR: fyndx/expo-universal-starter#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-28T12:52:53.959Z
Learning: Applies to src/lib/auth-client.ts : Auth client in src/lib/auth-client.ts with platform-specific plugins

Applied to files:

  • src/lib/auth-client.ts
📚 Learning: 2025-07-28T12:52:53.959Z
Learnt from: CR
PR: fyndx/expo-universal-starter#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-28T12:52:53.959Z
Learning: Applies to src/lib/auth-client.ts : Better Auth requires platform-specific plugin setup for native vs web storage

Applied to files:

  • src/lib/auth-client.ts
📚 Learning: 2025-07-28T12:52:53.959Z
Learnt from: CR
PR: fyndx/expo-universal-starter#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-28T12:52:53.959Z
Learning: Applies to {src/lib/auth-client.ts,src/app/(auth)/**/*.tsx} : Use authClient hooks for auth state management

Applied to files:

  • src/lib/auth-client.ts
🧬 Code Graph Analysis (1)
src/components/auth/sign-up-success-card.tsx (1)
src/components/ui/skia-loader.tsx (1)
  • SkiaLoader (7-16)
🪛 dotenv-linter (3.3.0)
.env

[warning] 2-2: [QuoteCharacter] The value has quote characters (', ")


[warning] 2-2: [UnorderedKey] The BETTER_AUTH_SECRET key should go before the DATABASE_URL key


[warning] 3-3: [EndingBlankLine] No blank line at the end of the file


[warning] 3-3: [UnorderedKey] The BETTER_AUTH_URL key should go before the DATABASE_URL key

🔇 Additional comments (14)
.env (1)

1-1: LGTM! Database connection string is properly configured.

The DATABASE_URL appears to be correctly formatted for Prisma Accelerate with the necessary API key and tenant information.

package.json (2)

62-62: LGTM! Expo Video dependency added correctly.

The expo-video dependency version ~2.2.2 is properly added and aligns with the plugin configuration in app.json.


20-22: ORPC dependencies are compatible as-is

The caret ranges (^1.7.10, ^1.8.0, ^1.7.11) all allow upgrades up to <2.0.0 and will resolve to the latest 1.8.0 releases. No compatibility issues detected.

Optional: for clarity and consistency, you may bump both @orpc/client and @orpc/openapi-client to ^1.8.0, but it isn’t strictly required.

app.json (1)

40-41: LGTM! Plugin configuration is correct.

The expo-video plugin is properly added to the plugins array and the configuration follows the expected format.

src/orpc/index.ts (3)

4-4: LGTM! Environment variable usage is correct.

The fallback to localhost is appropriate for development environments when EXPO_PUBLIC_API_URL is not set.


8-10: LGTM! OpenAPILink configuration is correct.

The OpenAPILink is properly configured with the fetched spec and the correct base URL for ORPC endpoints.


12-12: LGTM! Client export is properly configured.

The ORPC client is correctly created and exported for use throughout the application.

src/components/ui/confetti.tsx (2)

1-1: LGTM! Direct import from react-native-fast-confetti.

The direct import approach simplifies the component structure by removing the previous WASM-based loading complexity.


5-14: LGTM! Confetti configuration is well-defined.

The color palette provides good visual variety and isInfinite={false} ensures the confetti animation has a proper end state.

src/lib/auth-client.ts (2)

6-6: Initialization order follows platform requirements.

The change from inline plugin initialization to array manipulation allows proper ordering of platform-specific plugins, which aligns with Better Auth's requirements for Expo client configuration.


18-18: Plugin order ensures proper initialization.

Moving adminClient() after the platform-specific conditional ensures it's added last, which is appropriate for admin functionality that may depend on the platform-specific storage configuration.

src/components/ui/skia-loader.tsx (1)

1-1: Verify WithSkiaWeb import path and availability

Our checks against the published package show that:

  • WithSkiaWeb is not exported from the main entry point (lib/index.d.ts).
  • Attempting to fetch the web-module type definitions at lib/module/web/index.d.ts via unpkg returns HTTP 302 (not found).

This indicates the deep import

import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";

may not resolve correctly across versions. Please:

  • Confirm that node_modules/@shopify/react-native-skia/lib/module/web/index.{js,d.ts} actually exists in your installed version.
  • If it does exist, consider requesting or contributing a public export for WithSkiaWeb in the package’s main entry point.
  • Otherwise, update this import to a supported path (or file-relative import) that’s guaranteed to ship with the library.
src/components/auth/sign-up-success-card.tsx (2)

11-11: LGTM! Proper import of the new SkiaLoader component.

The import follows the established path alias pattern and correctly imports from the UI components directory.


36-36: Confetti component export verified
The src/components/ui/confetti.tsx file has a default export (export default function ConfettiBase), which satisfies the SkiaLoader’s requirement. No changes needed—this lazy‐loading usage is correct.

Comment thread .env
BETTER_AUTH_URL=http://localhost:3000 #Base URL of your app No newline at end of file
BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"
BETTER_AUTH_URL="http://localhost:3000" #Base URL of your app
EXPO_PUBLIC_API_URL="https://ship-server.onrender.com" No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the production API URL endpoint is accessible.

The EXPO_PUBLIC_API_URL points to a production server. Ensure this endpoint is reliable and accessible for all users.


🏁 Script executed:

#!/bin/bash
# Description: Verify the API endpoint is accessible and returns expected responses

# Test: Check if the API URL is accessible and if the ORPC spec endpoint exists
API_URL="https://ship-server.onrender.com"
echo "Testing main API URL..."
curl -I "$API_URL" --max-time 10

echo "Testing ORPC spec endpoint..."
curl -I "$API_URL/orpc/spec.json" --max-time 10

echo "Testing ORPC base endpoint..."
curl -I "$API_URL/orpc/" --max-time 10

Length of output: 3475


Production API endpoints returning 404

Verified the configured EXPO_PUBLIC_API_URL (https://ship-server.onrender.com) and its ORPC endpoints all respond with HTTP 404:

• GET HEAD https://ship-server.onrender.com → 404
• GET HEAD https://ship-server.onrender.com/orpc/ → 404
• GET HEAD https://ship-server.onrender.com/orpc/spec.json → 404

Please ensure the production API is correctly deployed and these endpoints exist, or update the URL in .env to point to a valid, accessible API base.

🤖 Prompt for AI Agents
.env around line 4: the EXPO_PUBLIC_API_URL is set to
"https://ship-server.onrender.com" but the domain's root and /orpc endpoints
return 404; either correct the URL to a valid production API base that serves
the expected ORPC endpoints (e.g., the deployed API hostname that returns
/orpc/spec.json) or redeploy the backend so these endpoints exist at the current
host; update the .env value to the reachable API base and verify GET
/orpc/spec.json returns 200 before merging.

Comment on lines +7 to +16
export function SkiaLoader({ component }: SkiaCanvasProps) {
return (
<WithSkiaWeb
opts={{
locateFile: (file) => `/canvaskit.wasm`,
}}
getComponent={component}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for dynamic imports.

The component doesn't handle cases where the dynamic import fails, which could lead to poor user experience.

 export function SkiaLoader({ component }: SkiaCanvasProps) {
+	const [error, setError] = React.useState<Error | null>(null);
+	
+	const wrappedComponent = React.useCallback(async () => {
+		try {
+			return await component();
+		} catch (err) {
+			setError(err instanceof Error ? err : new Error('Failed to load component'));
+			throw err;
+		}
+	}, [component]);
+	
+	if (error) {
+		return <Text>Failed to load component: {error.message}</Text>;
+	}
+	
 	return (
 		<WithSkiaWeb
 			opts={{
 				locateFile: (file) => `/canvaskit.wasm`,
 			}}
-			getComponent={component}
+			getComponent={wrappedComponent}
 		/>
 	);
 }
📝 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.

Suggested change
export function SkiaLoader({ component }: SkiaCanvasProps) {
return (
<WithSkiaWeb
opts={{
locateFile: (file) => `/canvaskit.wasm`,
}}
getComponent={component}
/>
);
}
export function SkiaLoader({ component }: SkiaCanvasProps) {
const [error, setError] = React.useState<Error | null>(null);
const wrappedComponent = React.useCallback(async () => {
try {
return await component();
} catch (err) {
setError(err instanceof Error ? err : new Error('Failed to load component'));
throw err;
}
}, [component]);
if (error) {
return <Text>Failed to load component: {error.message}</Text>;
}
return (
<WithSkiaWeb
opts={{
locateFile: (file) => `/canvaskit.wasm`,
}}
getComponent={wrappedComponent}
/>
);
}
🤖 Prompt for AI Agents
In src/components/ui/skia-loader.tsx around lines 7 to 16, the component calls a
dynamic import via getComponent without handling failures; update getComponent
(or wrap WithSkiaWeb usage) to catch import errors, log them, and surface a
fallback UI or error state. Specifically, change getComponent to an async
function that tries the dynamic import in a try/catch, logs the error (or pass
it to an onError handler), and returns a safe fallback component when the import
fails; alternatively wrap SkiaLoader in an error boundary and render a
user-facing fallback if the dynamic load rejects.

return (
<WithSkiaWeb
opts={{
locateFile: (file) => `/canvaskit.wasm`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Hardcoded CanvasKit path may cause deployment issues.

The hardcoded /canvaskit.wasm path assumes the file is in the public root. This may not work in all deployment environments or build configurations.

Consider making the path configurable:

-			opts={{
-				locateFile: (file) => `/canvaskit.wasm`,
-			}}
+			opts={{
+				locateFile: (file) => process.env.EXPO_PUBLIC_CANVASKIT_PATH || `/canvaskit.wasm`,
+			}}

🏁 Script executed:

#!/bin/bash
# Check if canvaskit.wasm is properly configured in public assets
fd "canvaskit.wasm" --type f
rg -i "canvaskit" package.json app.json metro.config.js webpack.config.js || echo "No CanvasKit configuration found"

Length of output: 257


Ensure CanvasKit path is configurable for non-root deployments

We do have public/canvaskit.wasm, so the current hard-coded /canvaskit.wasm will work when your app is served from the root. However, if you ever host under a sub-path (e.g. GitHub Pages, a CDN base path, etc.), that leading slash will break.

Please update src/components/ui/skia-loader.tsx to allow overriding via an environment variable (or via your framework’s PUBLIC_URL/BASE_URL):

--- a/src/components/ui/skia-loader.tsx
+++ b/src/components/ui/skia-loader.tsx
@@ -10,7 +10,13 @@ export function SkiaCanvas() {
   return (
     <CanvasKitWebGL
       opts={{
-        locateFile: (file) => `/canvaskit.wasm`,
+        locateFile: (file) => {
+          // Use PUBLIC_URL (CRA/Vite) or a custom EXPO_CANVASKIT_PATH if set,
+          // falling back to root-relative path:
+          const base =
+            process.env.PUBLIC_URL ??
+            process.env.EXPO_PUBLIC_CANVASKIT_PATH ??
+            '';
+          return `${base}/canvaskit.wasm`;
+        },
       }}
     />
   );

• This preserves the default behavior for root-mounted sites.
• It lets you inject a custom base path at build time via PUBLIC_URL (create-react-app, Vite) or EXPO_PUBLIC_CANVASKIT_PATH.

📝 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.

Suggested change
locateFile: (file) => `/canvaskit.wasm`,
export function SkiaCanvas() {
return (
<CanvasKitWebGL
opts={{
locateFile: (file) => {
// Use PUBLIC_URL (CRA/Vite) or a custom EXPO_PUBLIC_CANVASKIT_PATH if set,
// falling back to root-relative path:
const base =
process.env.PUBLIC_URL ??
process.env.EXPO_PUBLIC_CANVASKIT_PATH ??
'';
return `${base}/canvaskit.wasm`;
},
}}
/>
);
}

Comment thread src/orpc/index.ts

const apiUrl = process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000";

const specJSON = await fetch(`${apiUrl}/orpc/spec.json`).then((r) => r.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for the OpenAPI spec fetch.

The top-level await fetch lacks error handling, which could cause the module to fail during initialization if the API is unreachable or returns invalid JSON.

-const specJSON = await fetch(`${apiUrl}/orpc/spec.json`).then((r) => r.json());
+const specJSON = await fetch(`${apiUrl}/orpc/spec.json`)
+  .then((r) => {
+    if (!r.ok) {
+      throw new Error(`Failed to fetch ORPC spec: ${r.status} ${r.statusText}`);
+    }
+    return r.json();
+  })
+  .catch((error) => {
+    console.error('Failed to initialize ORPC client:', error);
+    throw error;
+  });
📝 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.

Suggested change
const specJSON = await fetch(`${apiUrl}/orpc/spec.json`).then((r) => r.json());
const specJSON = await fetch(`${apiUrl}/orpc/spec.json`)
.then((r) => {
if (!r.ok) {
throw new Error(`Failed to fetch ORPC spec: ${r.status} ${r.statusText}`);
}
return r.json();
})
.catch((error) => {
console.error('Failed to initialize ORPC client:', error);
throw error;
});
🤖 Prompt for AI Agents
In src/orpc/index.ts around line 6, the top-level await that fetches the OpenAPI
spec has no error handling and can crash module initialization if the network
request fails or the response is invalid JSON; wrap the fetch+json parsing in a
try/catch (or use .catch) to handle network and parsing errors, log or rethrow a
clear error with context (including the URL and status), optionally provide a
fallback or export a rejected promise so consumers can handle initialization
failure, and ensure the module doesn't throw unhandled exceptions during import.

@chakrihacker chakrihacker merged commit 2c23389 into main Aug 12, 2025
1 check was pending
@chakrihacker chakrihacker deleted the feat/add-orpc branch August 12, 2025 13:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🔭 Outside diff range comments (1)
src/lib/auth-client.ts (1)

21-23: Use EXPO_PUBLIC_API_URL for baseURL to align with environment-driven config

PR mentions adding an API URL env var. Hardcoding the Render URL makes switching environments harder.

Apply this diff:

-export const authClient = createAuthClient({
-  baseURL: "https://ship-server.onrender.com/api/auth", // Base URL of your Better Auth backend.
-  // baseURL: "http://localhost:3000/api/auth", // Base URL of your Better Auth backend.
+export const authClient = createAuthClient({
+  baseURL: `${apiBase}/api/auth`, // Base URL of your Better Auth backend.

And add this variable above (outside the selected range):

// Prefer env-driven configuration; EXPO_PUBLIC_* is replaced at build time by Expo.
const apiBase = process.env.EXPO_PUBLIC_API_URL ?? "https://ship-server.onrender.com";
♻️ Duplicate comments (2)
src/orpc/index.ts (2)

4-4: Do not silently default API base URL to localhost in production

Falling back to http://localhost:3000 can break production builds silently. Throw a clear error if EXPO_PUBLIC_API_URL is missing.

Apply:

-const apiUrl = process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000";
+const apiUrl = process.env.EXPO_PUBLIC_API_URL;
+if (!apiUrl) {
+  throw new Error("EXPO_PUBLIC_API_URL is not set. Configure it in your environment variables.");
+}

1-12: Avoid top-level await and module-scope network calls; add robust init with error handling

Top-level await and a module-scope fetch can fail on some runtimes (Hermes/SSR) and block app startup. Move initialization behind an async initializer with timeout, single-flight, and clear errors. This also aligns better with multi-platform constraints (iOS/Android/Web).

Apply:

-import { createORPCClient } from "@orpc/client";
-import { OpenAPILink } from "@orpc/openapi-client/fetch";
-
-const apiUrl = process.env.EXPO_PUBLIC_API_URL || "http://localhost:3000";
-
-const specJSON = await fetch(`${apiUrl}/orpc/spec.json`).then((r) => r.json());
-
-const link = new OpenAPILink(specJSON, {
-	url: `${apiUrl}/orpc/`,
-});
-
-export const orpc = createORPCClient(link);
+import { createORPCClient } from "@orpc/client";
+import { OpenAPILink } from "@orpc/openapi-client/fetch";
+
+const apiUrl = process.env.EXPO_PUBLIC_API_URL;
+if (!apiUrl) {
+  throw new Error("EXPO_PUBLIC_API_URL is not set. Configure it in your environment variables.");
+}
+
+type ORPCClient = ReturnType<typeof createORPCClient>;
+let orpcInstance: ORPCClient | null = null;
+let initPromise: Promise<ORPCClient> | null = null;
+
+/**
+ * Initializes and returns a singleton ORPC client instance.
+ * Safe to call multiple times; only the first call performs network I/O.
+ */
+export async function initORPC(): Promise<ORPCClient> {
+  if (orpcInstance) return orpcInstance;
+  if (initPromise) return initPromise;
+
+  initPromise = (async () => {
+    const controller = new AbortController();
+    const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout
+    try {
+      const res = await fetch(`${apiUrl}/orpc/spec.json`, { signal: controller.signal });
+      if (!res.ok) {
+        throw new Error(`Failed to fetch ORPC spec: ${res.status} ${res.statusText}`);
+      }
+      const specJSON = await res.json();
+      const link = new OpenAPILink(specJSON, { url: `${apiUrl}/orpc/` });
+      orpcInstance = createORPCClient(link);
+      return orpcInstance;
+    } finally {
+      clearTimeout(timeout);
+    }
+  })();
+
+  return initPromise;
+}

Follow-up:

  • Replace imports of orpc with initORPC and await at usage sites (e.g., loaders, effects, service layer), not at module top-level.
🧹 Nitpick comments (6)
src/lib/auth-client.ts (2)

18-19: Gate admin plugin behind an env flag to reduce surface area in non-admin builds

Unless you need admin endpoints in all builds, consider enabling this only when explicitly requested.

-plugins.push(adminClient());
+if (process.env.EXPO_PUBLIC_ENABLE_ADMIN === "true") {
+  plugins.push(adminClient());
+}

4-4: Optional: avoid bundling SecureStore on web by requiring it only on native

Not critical, but importing expo-secure-store at the top can bloat the web bundle. A conditional require avoids pulling native code into web builds.

Example approach (requires minor restructuring):

// Remove the top-level import of expo-secure-store
// import * as SecureStore from "expo-secure-store";

let nativePlugins: any[] = [];
if (process.env.EXPO_OS !== "web") {
  // eslint-disable-next-line @typescript-eslint/no-var-requires
  const SecureStore = require("expo-secure-store");
  nativePlugins.push(
    expoClient({
      scheme: "universalstarter",
      storagePrefix: "universal-starter",
      storage: SecureStore,
    })
  );
}

// ...then use: plugins: [...nativePlugins, adminClient()],
src/orpc/index.ts (1)

1-12: Consider cross-platform and SSR bootstrap timing

If you need immediate availability in React components, initialize in an app-level provider or bootstrap step, then inject via context. This avoids TLA while keeping consumers synchronous after hydration.

src/components/ui/skia-loader.tsx (2)

3-5: Add a type-only React import for correctness.

Avoid depending on global React types; import them explicitly.

+import type { ComponentType } from "react";

 interface SkiaCanvasProps {
-  component: () => Promise<{ default: React.ComponentType }>;
+  component: () => Promise<{ default: ComponentType<any> }>;
 }

10-12: Hard-coded wasm path may break behind basePath/CDN; use the provided filename and support basePath.

Using "/canvaskit.wasm" assumes site root. Respect configured base paths/CDNs.

-        locateFile: (file) => `/canvaskit.wasm`,
+        locateFile: (file) => `${process.env.EXPO_PUBLIC_BASE_PATH ?? ""}/${file}`,

If you keep the hard-coded path, please verify that /canvaskit.wasm is served from your app root in all web environments (dev, preview, prod).

src/components/auth/sign-up-success-card.tsx (1)

36-36: Position confetti as an overlay to avoid layout shifts; add web-only 'fixed'.

Currently, the confetti component may take layout space and push the card down. Wrap it in an absolutely positioned, non-interactive container.

-      <SkiaLoader component={() => import("~/components/ui/confetti")} />
+      <View className="absolute inset-0 pointer-events-none z-10 web:fixed">
+        <SkiaLoader component={() => import("~/components/ui/confetti")} />
+      </View>

This also follows the "use web: prefix for web-only styles" guidance.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d57575e and db29399.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • .env (1 hunks)
  • app.json (1 hunks)
  • package.json (2 hunks)
  • src/components/auth/sign-up-success-card.tsx (2 hunks)
  • src/components/ui/confetti-base.tsx (0 hunks)
  • src/components/ui/confetti.tsx (1 hunks)
  • src/components/ui/skia-loader.tsx (1 hunks)
  • src/lib/auth-client.ts (2 hunks)
  • src/orpc/index.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/components/ui/confetti-base.tsx
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

src/**/*.{ts,tsx}: Use Platform.OS and process.env.EXPO_OS for conditional logic
Consider all platforms (iOS/Android/Web) when using native APIs

Files:

  • src/components/auth/sign-up-success-card.tsx
  • src/components/ui/skia-loader.tsx
  • src/orpc/index.ts
  • src/components/ui/confetti.tsx
  • src/lib/auth-client.ts
src/**/*.tsx

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

Import UI components from @/src/components/ui/ using path aliases

Files:

  • src/components/auth/sign-up-success-card.tsx
  • src/components/ui/skia-loader.tsx
  • src/components/ui/confetti.tsx
src/components/ui/**/*.tsx

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

src/components/ui/**/*.tsx: UI components use NativeWind's className prop with Tailwind utility classes
Use cva (class-variance-authority) for creating variant-based component APIs
Access theme values through CSS custom properties and Tailwind utilities
UI components in src/components/ui/ follow React.forwardRef pattern with displayName
Use Tailwind's responsive prefixes (sm:, md:, lg:) and NativeWind's responsive utilities
Use web: prefix for web-only styles, and conditional rendering for native-specific components
Place UI components in src/components/ui/ with proper TypeScript interfaces
Always use NativeWind's className prop with Tailwind utility classes
Define variants using cva (class-variance-authority) for type-safe component APIs
Use Tailwind's spacing utilities (p-4, m-2, gap-3, etc.)
Use Tailwind's typography utilities (text-lg, font-semibold, etc.)
Toggle between light/dark themes using className conditionals and CSS variables
Use platform-specific prefixes (web:, ios:, android:) for conditional styles

Files:

  • src/components/ui/skia-loader.tsx
  • src/components/ui/confetti.tsx
src/lib/auth-client.ts

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

src/lib/auth-client.ts: Auth client in src/lib/auth-client.ts with platform-specific plugins
Better Auth requires platform-specific plugin setup for native vs web storage

Files:

  • src/lib/auth-client.ts
{src/lib/auth-client.ts,src/app/(auth)/**/*.tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

{src/lib/auth-client.ts,src/app/(auth)/**/*.tsx}: Use authClient.useSession() hook for session state
Use authClient hooks for auth state management

Files:

  • src/lib/auth-client.ts
🧠 Learnings (4)
📚 Learning: 2025-07-28T12:52:53.959Z
Learnt from: CR
PR: fyndx/expo-universal-starter#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-28T12:52:53.959Z
Learning: Applies to src/components/ui/**/*.tsx : Use web: prefix for web-only styles, and conditional rendering for native-specific components

Applied to files:

  • src/components/ui/skia-loader.tsx
📚 Learning: 2025-07-28T12:52:53.959Z
Learnt from: CR
PR: fyndx/expo-universal-starter#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-28T12:52:53.959Z
Learning: Applies to src/lib/auth-client.ts : Auth client in src/lib/auth-client.ts with platform-specific plugins

Applied to files:

  • src/lib/auth-client.ts
📚 Learning: 2025-07-28T12:52:53.959Z
Learnt from: CR
PR: fyndx/expo-universal-starter#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-28T12:52:53.959Z
Learning: Applies to src/lib/auth-client.ts : Better Auth requires platform-specific plugin setup for native vs web storage

Applied to files:

  • src/lib/auth-client.ts
📚 Learning: 2025-07-28T12:52:53.959Z
Learnt from: CR
PR: fyndx/expo-universal-starter#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-28T12:52:53.959Z
Learning: Applies to {src/lib/auth-client.ts,src/app/(auth)/**/*.tsx} : Use authClient hooks for auth state management

Applied to files:

  • src/lib/auth-client.ts
🧬 Code Graph Analysis (2)
src/components/auth/sign-up-success-card.tsx (1)
src/components/ui/skia-loader.tsx (1)
  • SkiaLoader (7-16)
src/lib/auth-client.ts (4)
src/containers/core/HeaderUser.tsx (3)
  • authClient (34-227)
  • authClient (207-209)
  • authClient (119-121)
src/models/admin/user-detail/session.model.ts (1)
  • ExtendedBetterAuthSession (9-12)
src/app/(public)/auth/index.tsx (1)
  • AuthIndex (6-8)
src/app/(public)/auth/_layout.tsx (1)
  • AuthLayout (6-28)
🪛 dotenv-linter (3.3.0)
.env

[warning] 2-2: [QuoteCharacter] The value has quote characters (', ")


[warning] 2-2: [UnorderedKey] The BETTER_AUTH_SECRET key should go before the DATABASE_URL key


[warning] 3-3: [EndingBlankLine] No blank line at the end of the file


[warning] 3-3: [UnorderedKey] The BETTER_AUTH_URL key should go before the DATABASE_URL key

🔇 Additional comments (7)
src/lib/auth-client.ts (2)

18-19: Plugin order looks correct (expoClient before adminClient on native)

This ensures storage is registered before admin capabilities. No action needed.


10-15: Deep link scheme matches Expo config
Verified that the scheme: "universalstarter" in src/lib/auth-client.ts matches the value in app.json. No changes needed.

package.json (2)

62-62: Confirm expo-video version aligns with Expo SDK 53 and plugin config

expo-video ~2.2.2 should match SDK 53, but double-check build-time config and native module autolinking on all platforms (iOS/Android/Web). If any issues arise, pin the exact version recommended by npx expo install expo-video for SDK 53.

Run locally:

  • npx expo install expo-video
  • EAS build previews for iOS and Android to ensure no plugin/config errors at prebuild time.

20-22: Validate ORPC setup & SSR fetch – all core checks passed

I confirmed:

  • ORPC imports and client instantiation live in src/orpc/index.ts.
  • Your lockfile (package-lock.json) includes @orpc/client, @orpc/contract, and @orpc/openapi-client.
  • Node v24.3.0 is in use, so a global fetch is available at runtime.

Next steps (manual verification):

  • Run production builds on iOS, Android (Metro + Hermes), and Web SSR to ensure the ORPC client bundles correctly.
  • Analyze your production bundle (e.g. using source-map-explorer or Metro’s bundle visualizer) to verify tree-shaking is removing unused ORPC code.

Let me know if you hit any issues during those build tests!

app.json (1)

40-42: Verify plugin necessity and build behavior for expo-video

Adding "expo-video" to plugins is fine; some Expo modules work without an explicit plugin. Ensure:

  • Prebuild generates expected native changes on iOS/Android.
  • No plugin conflict with react-native-edge-to-edge and expo-router.

Locally:

  • npx expo prebuild --clean (in a throwaway branch) and inspect ios/ and android/ diffs.
  • Run an EAS build preview for both platforms to confirm no config errors.
src/components/ui/confetti.tsx (1)

1-1: Verify and configure Expo Web support for react-native-fast-confetti

  • ✅ Dependency check: react-native-fast-confetti@^1.0.3 is already declared in package.json.
  • ⚙️ Web support caveat: the library relies on @shopify/react-native-skia, which does not auto-setup CanvasKit for Expo Web.
  • 📝 Action items:
    • Follow the React Native Skia Web setup guide to copy/serve the CanvasKit WASM and include any required polyfills:
    https://shopify.github.io/react-native-skia/docs/getting-started/web
    • Update your Expo web (webpack) configuration—either via withSkia plugin or a custom webpack.config.js—to load Skia’s WASM bundle.
    • Run the minimal example on Expo Web (or in Expo Go’s web mode) to confirm confetti renders correctly.
src/components/auth/sign-up-success-card.tsx (1)

11-11: Import is fine, but ensure SkiaLoader is platform-safe.

Once you split SkiaLoader into .web.tsx/.native.tsx, this import will be safe across iOS/Android/Web. Without that split, native builds may fail.

If you prefer not to split files, gate usage behind Platform.OS === "web" and render the component directly on native.

Comment thread .env
Comment on lines +2 to +4
BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"
BETTER_AUTH_URL="http://localhost:3000" #Base URL of your app
EXPO_PUBLIC_API_URL="https://ship-server.onrender.com" No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Housekeeping: .env hygiene and lint nits

Consider adding .env to .gitignore and committing a .env.example instead. Address dotenv-linter warnings only if desired; quoted values are acceptable and often safer.


🏁 Script executed:

#!/bin/bash
# Check if .env is ignored
if fd -H -a ".gitignore" >/dev/null; then
  echo "Found .gitignore:"
  cat .gitignore
  echo "-----"
  echo "Checking if .env is ignored:"
  rg -n "^\s*\.env(\.|$|$)" .gitignore || echo "'.env' not ignored"
else
  echo "No .gitignore found"
fi

# Search for any accidental secret leaks elsewhere
rg -n --hidden --glob '!.git' '(SECRET|API_KEY|DATABASE_URL|TOKEN)=' -S

Length of output: 1230


Ignore local .env and introduce an example file

  • .env is not currently ignored (only .env*.local is). Add this line to .gitignore:
    # local env files
    -.env*.local
    +.env
    +.env*.local
  • Remove the tracked file and prevent future commits:
    git rm --cached .env
    
  • Create a committed .env.example with placeholder keys/URLs instead of real secrets:
    DATABASE_URL="postgres://USER:PASSWORD@HOST:PORT/DB_NAME"
    BETTER_AUTH_SECRET="your-secret-here"
    BETTER_AUTH_URL="http://localhost:3000"
    EXPO_PUBLIC_API_URL="https://your-api.example.com"
  • Rotate the exposed DATABASE_URL and BETTER_AUTH_SECRET—they were leaked in the committed .env.

dotenv-linter warnings about quoted values can be ignored; quotes are often safer.

🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 2-2: [QuoteCharacter] The value has quote characters (', ")


[warning] 2-2: [UnorderedKey] The BETTER_AUTH_SECRET key should go before the DATABASE_URL key


[warning] 3-3: [EndingBlankLine] No blank line at the end of the file


[warning] 3-3: [UnorderedKey] The BETTER_AUTH_URL key should go before the DATABASE_URL key

🤖 Prompt for AI Agents
In .env around lines 2-4: the repo currently contains a committed .env with real
secrets and isn’t ignoring .env; add a line to .gitignore to ignore .env, run
git rm --cached .env to stop tracking the file and commit that change, create
and commit a new .env.example containing placeholder values (e.g. DATABASE_URL,
BETTER_AUTH_SECRET, BETTER_AUTH_URL, EXPO_PUBLIC_API_URL) instead of real
secrets, and rotate any leaked credentials (DATABASE_URL and BETTER_AUTH_SECRET)
immediately; you can ignore dotenv-linter warnings about quoted values.

Comment thread .env
Comment on lines +2 to +3
BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"
BETTER_AUTH_URL="http://localhost:3000" #Base URL of your app

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Secrets committed to VCS — rotate immediately and remove from repo

BETTER_AUTH_SECRET (and potentially DATABASE_URL above) are sensitive. Committing real secrets is a high-risk incident.

Immediate actions:

  • Revoke/rotate BETTER_AUTH_SECRET and any tokens contained in DATABASE_URL.
  • Remove real secrets from the repo history (e.g., git filter-repo) and replace with placeholders or move to CI secrets manager.
  • Commit only a sanitized .env.example with placeholders.

Apply placeholders in this file as a stopgap:

-BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"
-BETTER_AUTH_URL="http://localhost:3000" #Base URL of your app
+BETTER_AUTH_SECRET="__REPLACE_ME__"
+BETTER_AUTH_URL="http://localhost:3000" # Base URL of your app

Note: EXPO_PUBLIC_API_URL is fine to be public by design; keep production values in deployment envs and dev values locally.

📝 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.

Suggested change
BETTER_AUTH_SECRET="EiansbxbE4bzQ02mxu70UV0jJaoz9Lw6"
BETTER_AUTH_URL="http://localhost:3000" #Base URL of your app
BETTER_AUTH_SECRET="__REPLACE_ME__"
BETTER_AUTH_URL="http://localhost:3000" # Base URL of your app
🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 2-2: [QuoteCharacter] The value has quote characters (', ")


[warning] 2-2: [UnorderedKey] The BETTER_AUTH_SECRET key should go before the DATABASE_URL key


[warning] 3-3: [EndingBlankLine] No blank line at the end of the file


[warning] 3-3: [UnorderedKey] The BETTER_AUTH_URL key should go before the DATABASE_URL key

Comment on lines +3 to 16
export default function ConfettiBase() {
return (
<WithSkiaWeb
opts={{
locateFile: (file) => `/canvaskit.wasm`,
}}
getComponent={async () => require("./confetti-base")}
<Confetti
colors={[
"#FF6B6B",
"#4ECDC4",
"#45B7D1",
"#96CEB4",
"#FFEAA7",
"#DDA0DD",
]}
isInfinite={false}
/>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Adopt forwardRef + props pass-through and wrap with an overlay container.

Aligns with UI component conventions (forwardRef, optional className) and ensures the confetti overlays content rather than shifting layout.

-import { Confetti } from "react-native-fast-confetti";
+import { Confetti } from "react-native-fast-confetti";
+import { View } from "react-native";
+import type { ComponentProps } from "react";
 
-export default function ConfettiBase() {
-  return (
-    <Confetti
-      colors={[
-        "#FF6B6B",
-        "#4ECDC4",
-        "#45B7D1",
-        "#96CEB4",
-        "#FFEAA7",
-        "#DDA0DD",
-      ]}
-      isInfinite={false}
-    />
-  );
-}
+type RNConfettiProps = ComponentProps<typeof Confetti>;
+export interface ConfettiBaseProps extends RNConfettiProps {
+  // For NativeWind styling of the container
+  className?: string;
+}
+
+function DefaultConfetti(props: RNConfettiProps) {
+  return (
+    <Confetti
+      colors={[
+        "#FF6B6B",
+        "#4ECDC4",
+        "#45B7D1",
+        "#96CEB4",
+        "#FFEAA7",
+        "#DDA0DD",
+      ]}
+      isInfinite={false}
+      {...props}
+    />
+  );
+}
+
+export default function ConfettiBase({
+  className = "absolute inset-0 pointer-events-none",
+  ...props
+}: ConfettiBaseProps) {
+  return (
+    <View className={className}>
+      <DefaultConfetti {...props} />
+    </View>
+  );
+}

Committable suggestion skipped: line range outside the PR's diff.

@@ -0,0 +1,16 @@
import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Web-only import will break native builds — split by platform (.web/.native).

WithSkiaWeb is only available in the web bundle. Importing @shopify/react-native-skia/lib/module/web from a shared file will cause iOS/Android bundling/runtime failures. Create platform-specific files so native never imports the web entry.

Suggested structure (preferred):

// src/components/ui/skia-loader.web.tsx
import type { ComponentType } from "react";
import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";

interface SkiaCanvasProps {
  component: () => Promise<{ default: ComponentType<any> }>;
}

export function SkiaLoader({ component }: SkiaCanvasProps) {
  return (
    <WithSkiaWeb
      opts={{
        // Honor base paths/CDN if set; fall back to root
        locateFile: (file) => `${process.env.EXPO_PUBLIC_BASE_PATH ?? ""}/${file}`,
      }}
      getComponent={component}
    />
  );
}
// src/components/ui/skia-loader.native.tsx
import type { ComponentType } from "react";
import * as React from "react";

interface SkiaCanvasProps {
  component: () => Promise<{ default: ComponentType<any> }>;
}

export function SkiaLoader({ component }: SkiaCanvasProps) {
  const [Comp, setComp] = React.useState<ComponentType<any> | null>(null);

  React.useEffect(() => {
    let mounted = true;
    component().then((mod) => {
      if (mounted) setComp(() => mod.default);
    });
    return () => {
      mounted = false;
    };
  }, [component]);

  if (!Comp) return null;
  return <Comp />;
}

No changes required at call sites after this split.

Comment thread src/lib/auth-client.ts
import * as SecureStore from "expo-secure-store";

const plugins = [adminClient()];
const plugins = [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

TS typing bug: plugins inferred as never[] — pushes will fail under strict typing

In TypeScript, const plugins = [] infers never[], so plugins.push(...) will error. Add an explicit type to unblock compilation.

Apply this minimal fix:

-const plugins = [];
+const plugins: any[] = [];
📝 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.

Suggested change
const plugins = [];
const plugins: any[] = [];
🤖 Prompt for AI Agents
In src/lib/auth-client.ts around line 6, the plugins array is declared as const
plugins = [] which TypeScript infers as never[], causing pushes to fail under
strict typing; change the declaration to include an explicit type (e.g., const
plugins: YourPluginType[] = [] or const plugins: Array<Plugin | unknown> = [])
so subsequent plugins.push(...) calls are valid and the file compiles under
strict mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant