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: 2 additions & 2 deletions app/(auth)/api/auth/guest/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { NextResponse } from 'next/server';

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const redirectUrl = searchParams.get('redirectUrl') || '/';
const redirectUrl = searchParams.get('redirectUrl') || '/chat';

const token = await getToken({
req: request,
Expand All @@ -14,7 +14,7 @@ export async function GET(request: Request) {
});

if (token) {
return NextResponse.redirect(new URL('/', request.url));
return NextResponse.redirect(new URL('/chat', request.url));
}

return signIn('guest', { redirect: true, redirectTo: redirectUrl });
Expand Down
2 changes: 1 addition & 1 deletion app/(auth)/auth.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { NextAuthConfig } from 'next-auth';
export const authConfig = {
pages: {
signIn: '/login',
newUser: '/',
newUser: '/chat',
},
providers: [
// added later in auth.ts since it requires bcrypt which is only compatible with Node.js
Expand Down
8 changes: 4 additions & 4 deletions app/(chat)/api/gmail/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ export async function GET(request: NextRequest) {
jar.delete(VERIFIER_COOKIE);

if (!sessionId) {
return NextResponse.redirect(new URL('/?error=no-session', request.url));
return NextResponse.redirect(new URL('/chat?error=no-session', request.url));
}

const url = new URL(request.url);
const returnedState = url.searchParams.get('state') || undefined;
const hasCode = url.searchParams.has('code');

if (!stateCookie || !verifier || !hasCode || returnedState !== stateCookie) {
return NextResponse.redirect(new URL('/?error=invalid_state', request.url));
return NextResponse.redirect(new URL('/chat?error=invalid_state', request.url));
}

try {
Expand Down Expand Up @@ -64,9 +64,9 @@ export async function GET(request: NextRequest) {
if (tokens.id_token) jar.set('gc_id_token', tokens.id_token, cookieOptions);
if (tokens.expires_at) jar.set('gc_expires_at', String(tokens.expires_at), cookieOptions);

return NextResponse.redirect(new URL('/?connected=1', request.url));
return NextResponse.redirect(new URL('/chat?connected=1', request.url));
} catch {
return NextResponse.redirect(new URL('/?error=oauth_failed', request.url));
return NextResponse.redirect(new URL('/chat?error=oauth_failed', request.url));
}
}

8 changes: 4 additions & 4 deletions app/(chat)/page.tsx → app/(chat)/chat/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';

import { Chat } from '@/components/chat';
import { DataStreamHandler } from '@/components/data-stream-handler';
import { DEFAULT_CHAT_MODEL } from '@/lib/ai/models';
import { auth } from '@/app/(auth)/auth';
import { generateUUID } from '@/lib/utils';
import { DataStreamHandler } from '@/components/data-stream-handler';
import { auth } from '../(auth)/auth';
import { redirect } from 'next/navigation';

export default async function Page() {
const session = await auth();
Expand All @@ -31,7 +31,7 @@ export default async function Page() {
initialVisibilityType="private"
isReadonly={false}
session={session}
autoResume={false}
autoResume={false}
initialApiKey={apiKeyFromCookie?.value ?? ''}
/>
<DataStreamHandler id={id} />
Expand Down
70 changes: 70 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Link from 'next/link';
import { motion } from 'framer-motion';

import { Button } from '@/components/ui/button';
import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';

function FeatureCard({ title, description }: { title: string; description: string }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
<Card className="h-full bg-background/50 backdrop-blur-sm">
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
</Card>
</motion.div>
);
}

export default function LandingPage() {
return (
<main
className="flex min-h-screen flex-col items-center justify-center gap-16 bg-gradient-to-b from-background to-muted p-8 text-center"
>
<section className="space-y-6">
<motion.h1
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="text-5xl font-bold tracking-tight"
>
Cerch AI
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.6 }}
className="mx-auto max-w-2xl text-xl text-muted-foreground"
>
Unlock insights from your people and company data with an AI assistant
built for everyone.
</motion.p>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4, duration: 0.6 }}
>
<Button asChild size="lg">
<Link href="/chat">Try Demo</Link>
</Button>
</motion.div>
</section>
<section className="grid w-full max-w-4xl gap-8 md:grid-cols-2">
<FeatureCard
title="For Individuals"
description="Research topics and learn faster with a conversational interface."
/>
<FeatureCard
title="For Companies"
description="Connect internal documents and data to streamline everyday work."
/>
</section>
</main>
);
}
6 changes: 5 additions & 1 deletion middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export async function middleware(request: NextRequest) {
return NextResponse.next();
}

if (pathname === '/') {
return NextResponse.next();
}

const token = await getToken({
req: request,
secret: process.env.AUTH_SECRET,
Expand All @@ -34,7 +38,7 @@ export async function middleware(request: NextRequest) {
const isGuest = guestRegex.test(token?.email ?? '');

if (token && !isGuest && ['/login', '/register'].includes(pathname)) {
return NextResponse.redirect(new URL('/', request.url));
return NextResponse.redirect(new URL('/chat', request.url));
}

return NextResponse.next();
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/chat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ test.describe('Chat activity', () => {
});

test('Create message from url query', async ({ page }) => {
await page.goto('/?query=Why is the sky blue?');
await page.goto('/chat?query=Why is the sky blue?');

await chatPage.isGenerationComplete();

Expand Down
34 changes: 17 additions & 17 deletions tests/e2e/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ test.describe
test('Authenticate as guest user when a new session is loaded', async ({
page,
}) => {
const response = await page.goto('/');
const response = await page.goto('/chat');

if (!response) {
throw new Error('Failed to load page');
Expand All @@ -25,14 +25,14 @@ test.describe
}

expect(chain).toEqual([
'http://localhost:3000/',
'http://localhost:3000/api/auth/guest?redirectUrl=http%3A%2F%2Flocalhost%3A3000%2F',
'http://localhost:3000/',
'http://localhost:3000/chat',
'http://localhost:3000/api/auth/guest?redirectUrl=http%3A%2F%2Flocalhost%3A3000%2Fchat',
'http://localhost:3000/chat',
]);
});

test('Log out is not available for guest users', async ({ page }) => {
await page.goto('/');
await page.goto('/chat');

const sidebarToggleButton = page.getByTestId('sidebar-toggle-button');
await sidebarToggleButton.click();
Expand All @@ -51,7 +51,7 @@ test.describe
test('Do not authenticate as guest user when an existing non-guest session is active', async ({
adaContext,
}) => {
const response = await adaContext.page.goto('/');
const response = await adaContext.page.goto('/chat');

if (!response) {
throw new Error('Failed to load page');
Expand All @@ -66,7 +66,7 @@ test.describe
request = request.redirectedFrom();
}

expect(chain).toEqual(['http://localhost:3000/']);
expect(chain).toEqual(['http://localhost:3000/chat']);
});

test('Allow navigating to /login as guest user', async ({ page }) => {
Expand All @@ -82,7 +82,7 @@ test.describe
});

test('Do not show email in user menu for guest user', async ({ page }) => {
await page.goto('/');
await page.goto('/chat');

const sidebarToggleButton = page.getByTestId('sidebar-toggle-button');
await sidebarToggleButton.click();
Expand Down Expand Up @@ -115,14 +115,14 @@ test.describe
test('Log into account that exists', async ({ page }) => {
await authPage.login(testUser.email, testUser.password);

await page.waitForURL('/');
await page.waitForURL('/chat');
await expect(page.getByPlaceholder('Send a message...')).toBeVisible();
});

test('Display user email in user menu', async ({ page }) => {
await authPage.login(testUser.email, testUser.password);

await page.waitForURL('/');
await page.waitForURL('/chat');
await expect(page.getByPlaceholder('Send a message...')).toBeVisible();

const userEmail = await page.getByTestId('user-email');
Expand All @@ -137,21 +137,21 @@ test.describe
page,
}) => {
await authPage.login(testUser.email, testUser.password);
await page.waitForURL('/');
await page.waitForURL('/chat');

const userEmail = await page.getByTestId('user-email');
await expect(userEmail).toHaveText(testUser.email);

await page.goto('/api/auth/guest');
await page.waitForURL('/');
await page.waitForURL('/chat');

const updatedUserEmail = await page.getByTestId('user-email');
await expect(updatedUserEmail).toHaveText(testUser.email);
});

test('Log out is available for non-guest users', async ({ page }) => {
await authPage.login(testUser.email, testUser.password);
await page.waitForURL('/');
await page.waitForURL('/chat');

authPage.openSidebar();

Expand All @@ -170,18 +170,18 @@ test.describe
page,
}) => {
await authPage.login(testUser.email, testUser.password);
await page.waitForURL('/');
await page.waitForURL('/chat');

await page.goto('/register');
await expect(page).toHaveURL('/');
await expect(page).toHaveURL('/chat');
});

test('Do not navigate to /login for non-guest users', async ({ page }) => {
await authPage.login(testUser.email, testUser.password);
await page.waitForURL('/');
await page.waitForURL('/chat');

await page.goto('/login');
await expect(page).toHaveURL('/');
await expect(page).toHaveURL('/chat');
});
});

Expand Down
2 changes: 1 addition & 1 deletion tests/pages/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class AuthPage {

async logout(email: string, password: string) {
await this.login(email, password);
await this.page.waitForURL('/');
await this.page.waitForURL('/chat');

await this.openSidebar();

Expand Down
2 changes: 1 addition & 1 deletion tests/pages/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class ChatPage {
}

async createNewChat() {
await this.page.goto('/');
await this.page.goto('/chat');
}

public getCurrentURL(): string {
Expand Down