diff --git a/.env b/.env
index 8e115d0fe5..bedfcf9eea 100644
--- a/.env
+++ b/.env
@@ -1,6 +1,7 @@
BILLING_PLAN_ENV=dev
VSCODE_EXTENSION_BASE_URL=vscode://RooVeterinaryInc.roo-cline
+CURSOR_EXTENSION_BASE_URL=cursor://RooVeterinaryInc.roo-cline
# Clerk
# https://clerk.com/docs/deployments/clerk-environment-variables#sign-in-and-sign-up-redirects
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
index 0e436a643c..211803d989 100644
--- a/.storybook/preview.ts
+++ b/.storybook/preview.ts
@@ -1,4 +1,4 @@
-import '../src/styles/global.css';
+import '../src/styles/globals.css';
import type { Preview } from '@storybook/react';
diff --git a/src/app/(authenticated)/extension/sign-in/DeepLink.tsx b/src/app/(authenticated)/extension/sign-in/DeepLink.tsx
new file mode 100644
index 0000000000..221e20544d
--- /dev/null
+++ b/src/app/(authenticated)/extension/sign-in/DeepLink.tsx
@@ -0,0 +1,51 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import Link from 'next/link';
+
+import { Button, Card, CardContent, CardFooter } from '@/components/ui';
+import { Logo } from '@/components/layout';
+
+interface DeepLinkProps {
+ vsCodeUrl: string;
+ cursorUrl: string;
+}
+
+export const DeepLink = ({ vsCodeUrl, cursorUrl }: DeepLinkProps) => {
+ const [redirectAttempted, setRedirectAttempted] = useState(false);
+
+ useEffect(() => {
+ window.location.href = vsCodeUrl;
+ const timer = setTimeout(() => setRedirectAttempted(true), 2000);
+ return () => clearTimeout(timer);
+ }, [vsCodeUrl]);
+
+ return (
+
+
+
+
+
+
+
+ You've successfully authenticated. We're attempting to
+ open VSCode with your credentials.
+
+
+
+
+
+
+
+ {redirectAttempted
+ ? 'You can close this window after VSCode opens.'
+ : 'Redirecting automatically...'}
+
+
+
+ );
+};
diff --git a/src/app/(authenticated)/extension/sign-in/page.tsx b/src/app/(authenticated)/extension/sign-in/page.tsx
new file mode 100644
index 0000000000..1aab66ee91
--- /dev/null
+++ b/src/app/(authenticated)/extension/sign-in/page.tsx
@@ -0,0 +1,31 @@
+import { redirect } from 'next/navigation';
+import { auth } from '@clerk/nextjs/server';
+
+import { getSignInToken } from '@/lib/server/clerk';
+import { Env } from '@/lib/server/env';
+
+import { DeepLink } from './DeepLink';
+
+export default async function Page(params: {
+ searchParams: { state?: string };
+}) {
+ const { userId } = await auth();
+
+ if (!userId) {
+ redirect('/sign-in');
+ }
+
+ const code = await getSignInToken(userId);
+
+ if (!code) {
+ redirect('/sign-in');
+ }
+
+ const { state = '' } = await params.searchParams;
+ const searchParams = new URLSearchParams({ state, code });
+ const path = `/auth/clerk/callback?${searchParams.toString()}`;
+ const vsCodeUrl = new URL(path, Env.VSCODE_EXTENSION_BASE_URL);
+ const cursorUrl = new URL(path, Env.CURSOR_EXTENSION_BASE_URL);
+
+ return ;
+}
diff --git a/src/app/(authenticated)/extension/sign-in/route.ts b/src/app/(authenticated)/extension/sign-in/route.ts
deleted file mode 100644
index 99f59233d9..0000000000
--- a/src/app/(authenticated)/extension/sign-in/route.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { getSignInToken } from '@/lib/server/clerk';
-import { Env } from '@/lib/server/env';
-import { auth } from '@clerk/nextjs/server';
-import { redirect } from 'next/navigation';
-import { type NextRequest } from 'next/server';
-
-export async function GET(request: NextRequest) {
- const authObj = await auth();
- const userId = authObj.userId;
- const state = request.nextUrl.searchParams.get('state') || '';
-
- if (!userId) {
- return new Response('Unauthorized', { status: 401 });
- }
-
- const signInToken = await getSignInToken(authObj.userId);
- if (!signInToken) {
- throw new Error("couldn't sign in");
- }
-
- const url = new URL(`${Env.VSCODE_EXTENSION_BASE_URL}/auth/clerk/callback`);
- url.searchParams.append('state', state);
- url.searchParams.append('code', signInToken);
- redirect(url.href);
-}
diff --git a/src/app/api/events/route.ts b/src/app/api/events/route.ts
new file mode 100644
index 0000000000..30fcee7f07
--- /dev/null
+++ b/src/app/api/events/route.ts
@@ -0,0 +1,17 @@
+import { auth } from '@clerk/nextjs/server';
+import { NextRequest, NextResponse } from 'next/server';
+
+export async function POST(request: NextRequest) {
+ const { userId } = await auth();
+
+ if (!userId) {
+ return NextResponse.json(
+ { error: 'Unauthorized request' },
+ { status: 401 },
+ );
+ }
+
+ console.log(await request.json());
+
+ return NextResponse.json({ success: true });
+}
diff --git a/src/app/api/ping/route.ts b/src/app/api/ping/route.ts
index ac768c175e..e27943b52e 100644
--- a/src/app/api/ping/route.ts
+++ b/src/app/api/ping/route.ts
@@ -4,13 +4,12 @@ import { NextResponse } from 'next/server';
import { logger } from '@/lib/server/logger';
/**
- * API endpoint for testing Clerk authentication
- * Verifies/parses JWT and logs authenticated user information
+ * API endpoint for testing Clerk authentication.
+ * Verifies/parses JWT and logs authenticated user information.
*/
export async function GET() {
const authObj = await auth();
- // If not authenticated
if (!authObj.userId) {
return NextResponse.json(
{ error: 'Unauthorized request' },
@@ -18,35 +17,18 @@ export async function GET() {
);
}
- // Get the JWT token
+ // Get the JWT token.
const token = await authObj.getToken();
- // Extract user information from the auth object
- // Only include properties that exist on the auth object
- const userInfo = {
- userId: authObj.userId,
- sessionId: authObj.sessionId,
- orgId: authObj.orgId,
- orgRole: authObj.orgRole,
- // Note: To get additional user data like email, firstName, lastName,
- // you would need to use Clerk's methods like clerkClient.users.getUser()
- };
+ // Extract user information from the auth object.
+ // Only include properties that exist on the auth object.
+ // Note: To get additional user data like email, firstName, lastName,
+ // you would need to use Clerk's methods like clerkClient.users.getUser().
+ const { userId, sessionId, orgId, orgRole } = authObj;
+ const userInfo = { userId, sessionId, orgId, orgRole };
- // Log the user information
- logger.info({
- event: 'ping_endpoint_accessed',
- userInfo: {
- userId: authObj.userId,
- sessionId: authObj.sessionId,
- orgId: authObj.orgId,
- orgRole: authObj.orgRole,
- },
- hasToken: !!token, // Just log if token exists, not the actual token for security
- });
+ // Just log if token exists, not the actual token for security.
+ logger.info({ event: 'ping_endpoint_accessed', userInfo, hasToken: !!token });
- // Return the user information
- return NextResponse.json({
- authenticated: true,
- userInfo,
- });
+ return NextResponse.json(userInfo);
}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index 29f98ef9ee..903736b26f 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-sm text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer",
{
variants: {
variant: {
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
new file mode 100644
index 0000000000..7cda3847f4
--- /dev/null
+++ b/src/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from 'react';
+
+import { cn } from '@/lib/utils';
+
+function Card({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+};
diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts
index b64503aeb9..e395365f1f 100644
--- a/src/components/ui/index.ts
+++ b/src/components/ui/index.ts
@@ -1,3 +1,4 @@
export * from './button';
+export * from './card';
export * from './dropdown-menu';
export * from './separator';
diff --git a/src/lib/server/env.ts b/src/lib/server/env.ts
index 6912a58da3..d27e9e92ee 100644
--- a/src/lib/server/env.ts
+++ b/src/lib/server/env.ts
@@ -10,6 +10,7 @@ export const Env = createEnv({
STRIPE_WEBHOOK_SECRET: z.string().min(1),
BILLING_PLAN_ENV: z.enum(['dev', 'test', 'prod']),
VSCODE_EXTENSION_BASE_URL: z.string().min(1),
+ CURSOR_EXTENSION_BASE_URL: z.string().min(1),
},
client: {
NEXT_PUBLIC_APP_URL: z.string().optional(),
@@ -34,6 +35,7 @@ export const Env = createEnv({
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
BILLING_PLAN_ENV: process.env.BILLING_PLAN_ENV,
VSCODE_EXTENSION_BASE_URL: process.env.VSCODE_EXTENSION_BASE_URL,
+ CURSOR_EXTENSION_BASE_URL: process.env.CURSOR_EXTENSION_BASE_URL,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
diff --git a/src/types/Auth.ts b/src/types/Auth.ts
deleted file mode 100644
index 7903e44bfd..0000000000
--- a/src/types/Auth.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import type { EnumValues } from './enum';
-
-export const ORG_ROLE = {
- ADMIN: 'org:admin',
- MEMBER: 'org:member',
-} as const;
-
-export type OrgRole = EnumValues;
-
-export const ORG_PERMISSION = {
- // Add Organization Permissions here.
-} as const;
-
-export type OrgPermission = EnumValues;
diff --git a/src/types/Subscription.ts b/src/types/Subscription.ts
deleted file mode 100644
index c35827213a..0000000000
--- a/src/types/Subscription.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import type { EnumValues } from './enum';
-
-export const PLAN_ID = {
- FREE: 'free',
- PREMIUM: 'premium',
- ENTERPRISE: 'enterprise',
-} as const;
-
-export type PlanId = EnumValues;
-
-export const BILLING_INTERVAL = {
- MONTH: 'month',
- YEAR: 'year',
-} as const;
-
-export type BillingInterval = EnumValues;
-
-export const SUBSCRIPTION_STATUS = {
- ACTIVE: 'active',
- PENDING: 'pending',
-} as const;
-
-export type IStripeSubscription = {
- stripeSubscriptionId: string | null;
- stripeSubscriptionPriceId: string | null;
- stripeSubscriptionStatus: string | null;
- stripeSubscriptionCurrentPeriodEnd: number | null;
-};
diff --git a/src/types/enum.ts b/src/types/enum.ts
deleted file mode 100644
index c273087f69..0000000000
--- a/src/types/enum.ts
+++ /dev/null
@@ -1 +0,0 @@
-export type EnumValues = Type[keyof Type];
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
deleted file mode 100644
index 5c887a534f..0000000000
--- a/src/types/global.d.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { OrgPermission, OrgRole } from '@/types/auth';
-
-declare global {
- interface ClerkAuthorization {
- permission: OrgPermission;
- role: OrgRole;
- }
-}