Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env_sample
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Enable/disable authentication (set to "true" to enable)
VITE_ENABLE_AUTH=false

# Kinde Authentication Configuration
VITE_KINDE_CLIENT_ID=your_kinde_client_id
VITE_KINDE_DOMAIN=https://your_subdomain.kinde.com
VITE_KINDE_REDIRECT_URL=http://localhost:3000
Expand Down
79 changes: 79 additions & 0 deletions AUTH_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Authentication Setup Guide

This React Starter Kit includes optional authentication functionality that can be enabled or disabled using environment variables.

## Quick Start

### 1. Enable Authentication

To enable authentication, create a `.env` file in the root directory and set:

```bash
VITE_ENABLE_AUTH=true
```

### 2. Configure Kinde Authentication

You'll also need to set up your Kinde authentication credentials:

```bash
VITE_KINDE_CLIENT_ID=your_kinde_client_id
VITE_KINDE_DOMAIN=https://your_subdomain.kinde.com
VITE_KINDE_REDIRECT_URL=http://localhost:3000
VITE_KINDE_LOGOUT_URL=http://localhost:3000
```

### 3. Disable Authentication

To disable authentication, set:

```bash
VITE_ENABLE_AUTH=false
```

## How It Works

- **Authentication Disabled**: Shows a simple welcome page without any auth components
- **Authentication Enabled**: Shows login/signup forms and user dashboard with full authentication flow

## Features

When authentication is enabled:

- **LoggedOut Component**: Shows navbar with Sign In and Sign Up buttons
- **LoggedIn Component**: Shows navbar with user profile, account management, and logout
- **Responsive Design**: Built with Tailwind CSS for modern, mobile-friendly UI
- **User Profile**: Displays user information including name, email, and profile picture

## Environment Variables

| Variable | Description | Default |
| ------------------------- | ----------------------------- | -------------------------- |
| `VITE_ENABLE_AUTH` | Enable/disable authentication | `false` |
| `VITE_KINDE_CLIENT_ID` | Kinde client ID | Required when auth enabled |
| `VITE_KINDE_DOMAIN` | Kinde domain | Required when auth enabled |
| `VITE_KINDE_REDIRECT_URL` | Redirect URL after auth | Required when auth enabled |
| `VITE_KINDE_LOGOUT_URL` | Logout redirect URL | Required when auth enabled |

## Getting Started with Kinde

1. Sign up at [Kinde](https://kinde.com)
2. Create a new application
3. Get your client ID and domain
4. Configure your redirect URLs
5. Update your `.env` file with the credentials

## Development

```bash
# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build
```

The authentication feature is completely optional and won't affect your app's functionality when disabled.
232 changes: 232 additions & 0 deletions PROTECTED_ROUTE_USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
# Enhanced ProtectedRoute Component

The `ProtectedRoute` component has been enhanced to support custom access control beyond basic authentication. This allows you to implement role-based access control, feature flags, and other permission-based routing.

## Basic Usage

```tsx
import ProtectedRoute from "./components/ProtectedRoute";

// Basic authentication only
<ProtectedRoute>
<YourComponent />
</ProtectedRoute>
```

## Advanced Usage with Access Control

### Using KindeAuth Permissions

```tsx
// Check for specific permissions
<ProtectedRoute has={{ permissions: ['admin'] }} fallbackPath="/home">
<AdminPanel />
</ProtectedRoute>
```

### Using KindeAuth Roles

```tsx
<ProtectedRoute
has={{ roles: ['manager'] }}
fallbackPath="/upgrade"
>
<ManagerContent />
</ProtectedRoute>
```

## Access Control Examples

### Permission-Based Access

```tsx
// Check for admin permissions
<ProtectedRoute has={{ permissions: ['admin'] }}>
<AdminPanel />
</ProtectedRoute>

// Check for multiple permissions
<ProtectedRoute has={{ permissions: ['read', 'write'] }}>
<Editor />
</ProtectedRoute>
```

### Role-Based Access

```tsx
// Check for specific role
<ProtectedRoute has={{ roles: ['manager'] }}>
<ManagerDashboard />
</ProtectedRoute>

// Check for multiple roles
<ProtectedRoute has={{ roles: ['admin', 'supervisor'] }}>
<AdminDashboard />
</ProtectedRoute>
```

### Feature Flag Access

```tsx
// Check for feature flags
<ProtectedRoute has={{ featureFlags: ['beta-feature'] }}>
<BetaFeature />
</ProtectedRoute>
```

### Billing Entitlements

```tsx
// Check for billing entitlements
<ProtectedRoute has={{ billingEntitlements: ['premium'] }}>
<PremiumContent />
</ProtectedRoute>
```

## Integration with KindeAuth Features

The `ProtectedRoute` component now directly integrates with KindeAuth's `has()` function, making it easier to check permissions, roles, feature flags, and billing entitlements without writing custom access functions.

### Using KindeAuth Permissions

```tsx
// Check for specific permissions
<ProtectedRoute has={{ permissions: ['read:admin', 'write:admin'] }}>
<AdminPanel />
</ProtectedRoute>
```

### Using KindeAuth Roles

```tsx
// Check for specific roles
<ProtectedRoute has={{ roles: ['admin', 'supervisor'] }}>
<AdminDashboard />
</ProtectedRoute>
```

### Using Feature Flags

```tsx
// Check for feature flags
<ProtectedRoute has={{ featureFlags: ['beta-feature', 'new-ui'] }}>
<BetaFeature />
</ProtectedRoute>
```

### Using Billing Entitlements

```tsx
// Check for billing entitlements
<ProtectedRoute has={{ billingEntitlements: ['premium', 'enterprise'] }}>
<PremiumContent />
</ProtectedRoute>
```

## Component Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `React.ReactNode` | Yes | - | The component to render if access is granted |
| `has` | `HasParams` | No | - | KindeAuth has() parameters to check permissions/roles |
| `fallbackPath` | `string` | No | `"/"` | Path to redirect to if access is denied |

## HasParams Type

The `has` prop accepts the same parameters as KindeAuth's `has()` function:

```tsx
interface HasParams {
roles?: string[];
permissions?: string[];
featureFlags?: string[];
billingEntitlements?: string[];
forceApi?: boolean | {
roles?: boolean;
permissions?: boolean;
featureFlags?: boolean;
billingEntitlements?: true;
};
}
```
Comment on lines +126 to +151
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

Fix type bug: forceApi.billingEntitlements should be boolean, not the literal type true

This would mislead consumers and breaks accurate typing.

Apply:

 interface HasParams {
   roles?: string[];
   permissions?: string[];
   featureFlags?: string[];
   billingEntitlements?: string[];
   forceApi?: boolean | {
     roles?: boolean;
     permissions?: boolean;
     featureFlags?: boolean;
-    billingEntitlements?: true;
+    billingEntitlements?: boolean;
   };
 }
📝 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
## Component Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `React.ReactNode` | Yes | - | The component to render if access is granted |
| `has` | `HasParams` | No | - | KindeAuth has() parameters to check permissions/roles |
| `fallbackPath` | `string` | No | `"/"` | Path to redirect to if access is denied |
## HasParams Type
The `has` prop accepts the same parameters as KindeAuth's `has()` function:
```tsx
interface HasParams {
roles?: string[];
permissions?: string[];
featureFlags?: string[];
billingEntitlements?: string[];
forceApi?: boolean | {
roles?: boolean;
permissions?: boolean;
featureFlags?: boolean;
billingEntitlements?: true;
};
}
```
interface HasParams {
roles?: string[];
permissions?: string[];
featureFlags?: string[];
billingEntitlements?: string[];
forceApi?: boolean | {
roles?: boolean;
permissions?: boolean;
featureFlags?: boolean;
billingEntitlements?: boolean;
};
}
🧰 Tools
🪛 LanguageTool

[grammar] ~130-~130: There might be a mistake here.
Context: ...mponent to render if access is granted | | has | HasParams | No | - | KindeAu...

(QB_NEW_EN)


[grammar] ~131-~131: There might be a mistake here.
Context: ... parameters to check permissions/roles | | fallbackPath | string | No | "/"...

(QB_NEW_EN)

🤖 Prompt for AI Agents
In PROTECTED_ROUTE_USAGE.md around lines 126 to 151, the HasParams type
incorrectly declares forceApi.{billingEntitlements} as the literal type true;
change that property to boolean so forceApi is typed as boolean | { roles?:
boolean; permissions?: boolean; featureFlags?: boolean; billingEntitlements?:
boolean; } to accurately reflect possible values and avoid misleading consumers.


## Best Practices

1. **Always provide fallback paths** for better user experience
2. **Use descriptive access function names** for better code readability
3. **Handle undefined users gracefully** in your access functions
4. **Combine multiple conditions** when needed for complex access control
5. **Test your access functions** with different user scenarios

## Example Implementation

```tsx
// In your App.tsx or router configuration
<Routes>
<Route path="/" element={<PublicPage />} />

<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>

<Route
path="/admin"
element={
<ProtectedRoute
has={{ permissions: ['admin'] }}
fallbackPath="/dashboard"
>
<AdminPanel />
</ProtectedRoute>
}
/>

<Route
path="/premium"
element={
<ProtectedRoute
has={{ billingEntitlements: ['premium'] }}
fallbackPath="/upgrade"
>
<PremiumContent />
</ProtectedRoute>
}
/>
</Routes>
```

## Testing Access Control

You can test different access scenarios by:

1. **Setting up permissions and roles** in your KindeAuth dashboard
2. **Creating test users** with specific permissions/roles
3. **Using the examples page** at `/examples` to see different access controls in action
4. **Testing feature flags** by enabling/disabling them in KindeAuth

## Migration from Basic ProtectedRoute

If you were using the basic `ProtectedRoute` before, your existing code will continue to work without changes. The new `has` and `fallbackPath` props are optional.

## Migration from Custom Access Functions

If you were using custom access functions with the `access` prop, you can now use the `has` prop instead:

**Before:**
```tsx
<ProtectedRoute access={(user) => user?.email?.includes('admin') || false}>
<AdminPanel />
</ProtectedRoute>
```

**After:**
```tsx
<ProtectedRoute has={{ permissions: ['admin'] }}>
<AdminPanel />
</ProtectedRoute>
```
21 changes: 21 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
6 changes: 6 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}
Comment on lines +1 to +6
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

Avoid duplicating cn; keep a single source of truth under src/lib/utils.ts

You now have two identical cn implementations (here and in src/lib/utils.ts). This risks drift and confusing imports (plain vs "@/lib/utils"). Prefer one canonical module and re-export (or remove this file).

Apply one of the following:

Option A — re-export to preserve any existing imports:

-import { clsx, type ClassValue } from "clsx";
-import { twMerge } from "tailwind-merge";
-
-export function cn(...inputs: ClassValue[]): string {
-  return twMerge(clsx(inputs));
-}
+// Deprecated: prefer importing from "@/lib/utils"
+export { cn } from "@/lib/utils";

Option B — delete this file and update any imports to "@/lib/utils".

Run to find call sites that may still import from lib/utils:


🏁 Script executed:

#!/usr/bin/env bash
rg -nP --type ts --type tsx -C1 $'from\\s+[\'"](?:\\.?\\.?/)?lib/utils[\'"]|from\\s+[\'"]@/lib/utils[\'"]'

Length of output: 139


Here’s an updated search that skips the broken --type flags and explicitly targets .ts and .tsx files:


🏁 Script executed:

#!/usr/bin/env bash
# Look for imports of either relative lib/utils or the canonical @/lib/utils
rg -n -C1 -g '*.ts' -g '*.tsx' "from\s+['\"](?:\.\.?\/)?lib/utils['\"]|from\s+['\"]@/lib/utils['\"]"

Length of output: 259


Here’s a simpler, fixed-string search that should catch any remaining imports of lib/utils or @/lib/utils without regex errors:


🏁 Script executed:

#!/usr/bin/env bash
# Search .ts/.tsx files for literal imports of lib/utils or @/lib/utils
rg -n -C1 -g '*.ts' -g '*.tsx' \
  -e "from 'lib/utils'" \
  -e "from \"lib/utils\"" \
  -e "from '@/lib/utils'" \
  -e "from \"@/lib/utils\""

Length of output: 136


🏁 Script executed:

#!/usr/bin/env bash
# Search for any references to lib/utils or src/lib/utils in TypeScript files
rg -n -C1 -g '*.ts' -g '*.tsx' -e 'lib/utils' -e 'src/lib/utils'

Length of output: 195


Consolidate cn into a single source of truth

The codebase currently has two identical cn functions—one in lib/utils.ts and another in src/lib/utils.ts—which risks API drift and confusing imports. We found at least one remaining import of the duplicate in:

  • src/components/ui/button.tsx:5 import { cn } from "../../lib/utils";

Please consolidate by choosing one of the following:

Option A — Re-export the canonical implementation to preserve existing imports and mark this file deprecated:

--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -1,6 +1,3 @@
- import { clsx, type ClassValue } from "clsx";
- import { twMerge } from "tailwind-merge";
-
- export function cn(...inputs: ClassValue[]): string {
-   return twMerge(clsx(inputs));
- }
+ // Deprecated: use the implementation in "@/lib/utils"
+ export { cn } from "@/lib/utils";

Then update call sites like src/components/ui/button.tsx to:

-import { cn } from "../../lib/utils";
+import { cn } from "@/lib/utils";

Option B — Delete lib/utils.ts entirely and update all imports to point at "@/lib/utils" (or "src/lib/utils" if not aliased).

Either approach ensures there’s one canonical cn and avoids future divergence.

🤖 Prompt for AI Agents
In lib/utils.ts around lines 1-6, this duplicate cn implementation should be
consolidated; implement Option A by re-exporting the canonical cn from the
single source (src/lib/utils.ts or "@/lib/utils") and mark this file deprecated:
replace the function body with a single export that imports cn from the
canonical module and re-exports it, add a brief deprecation comment, then update
remaining call sites (e.g., src/components/ui/button.tsx) to import from the
canonical "@/lib/utils" path; run a global search/replace to update any other
../../lib/utils imports and run the typecheck/build to ensure no broken imports
remain.

Loading