Skip to content

Commit

Permalink
feat: Auth
Browse files Browse the repository at this point in the history
  • Loading branch information
bracesproul committed Oct 18, 2024
1 parent f35aac3 commit 091c071
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 168 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.2",
"@supabase/supabase-js": "^2.45.4",
"@supabase/supabase-js": "^2.45.5",
"@types/react-syntax-highlighter": "^15.5.13",
"@uiw/react-codemirror": "^4.23.5",
"@uiw/react-md-editor": "^4.0.4",
Expand Down
143 changes: 5 additions & 138 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,144 +1,11 @@
"use client";
import { ArtifactRenderer } from "@/components/artifacts/ArtifactRenderer";
import { ContentComposerChatInterface } from "@/components/ContentComposer";
import { useToast } from "@/hooks/use-toast";
import { useGraph } from "@/hooks/useGraph";
import { useStore } from "@/hooks/useStore";
import { getLanguageTemplate } from "@/lib/get_language_template";
import { cn } from "@/lib/utils";
import { ProgrammingLanguageOptions } from "@/types";
import { AIMessage } from "@langchain/core/messages";
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { Canvas } from "@/components/Canvas";
import { AuthProvider } from "../contexts/AuthContext";

export default function Home() {
const { toast } = useToast();
const [chatStarted, setChatStarted] = useState(false);
const [pendingArtifactSelection, setPendingArtifactSelection] = useState<
string | null
>(null);
const [isEditing, setIsEditing] = useState(false);
const {
streamMessage,
setMessages,
setArtifacts,
artifacts,
messages,
setSelectedArtifact,
selectedArtifactId,
createThread,
setArtifactContent,
assistantId,
} = useGraph();
const {
reflections,
deleteReflections,
getReflections,
isLoadingReflections,
} = useStore(assistantId);

const createThreadWithChatStarted = async () => {
setChatStarted(false);
return createThread();
};

const handleQuickStart = (
type: "text" | "code",
language?: ProgrammingLanguageOptions
) => {
if (type === "code" && !language) {
toast({
title: "Language not selected",
description: "Please select a language to continue",
duration: 5000,
});
return;
}
setChatStarted(true);

const artifactId = uuidv4();
const artifact = {
id: artifactId,
title: `Quickstart ${type}`,
content:
type === "code"
? getLanguageTemplate(language ?? "javascript")
: "# Hello world",
type,
language: language ?? "english",
};
setArtifacts((prevArtifacts) => [...prevArtifacts, artifact]);
setMessages((prevMessages) => {
const newMessage = new AIMessage({
content: "",
tool_calls: [
{
id: artifactId,
args: { title: artifact.title },
name: "artifact_ui",
},
],
});
return [...prevMessages, newMessage];
});
setIsEditing(true);
setPendingArtifactSelection(artifactId);
};

useEffect(() => {
if (pendingArtifactSelection) {
setSelectedArtifact(pendingArtifactSelection);
setPendingArtifactSelection(null);
}
}, [artifacts, pendingArtifactSelection, setSelectedArtifact]);

return (
<main className="h-screen flex flex-row">
<div
className={cn(
"transition-all duration-700",
chatStarted ? "w-[35%]" : "w-full",
"h-full mr-auto bg-gray-50/70 shadow-inner-right"
)}
>
<ContentComposerChatInterface
handleGetReflections={getReflections}
handleDeleteReflections={deleteReflections}
reflections={reflections}
isLoadingReflections={isLoadingReflections}
setSelectedArtifact={setSelectedArtifact}
streamMessage={streamMessage}
setArtifacts={setArtifacts}
messages={messages}
setMessages={setMessages}
createThread={createThreadWithChatStarted}
setChatStarted={setChatStarted}
showNewThreadButton={chatStarted}
handleQuickStart={handleQuickStart}
/>
</div>
{chatStarted && (
<div className="w-full ml-auto">
<ArtifactRenderer
handleGetReflections={getReflections}
handleDeleteReflections={deleteReflections}
reflections={reflections}
isLoadingReflections={isLoadingReflections}
setIsEditing={setIsEditing}
isEditing={isEditing}
setArtifactContent={setArtifactContent}
setSelectedArtifactById={setSelectedArtifact}
messages={messages}
setMessages={setMessages}
artifact={
selectedArtifactId
? artifacts.find((a) => a.id === selectedArtifactId)
: undefined
}
streamMessage={streamMessage}
/>
</div>
)}
</main>
<AuthProvider>
<Canvas />
</AuthProvider>
);
}
144 changes: 144 additions & 0 deletions src/components/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
"use client";
import { ArtifactRenderer } from "@/components/artifacts/ArtifactRenderer";
import { ContentComposerChatInterface } from "@/components/ContentComposer";
import { useToast } from "@/hooks/use-toast";
import { useGraph } from "@/hooks/useGraph";
import { useStore } from "@/hooks/useStore";
import { getLanguageTemplate } from "@/lib/get_language_template";
import { cn } from "@/lib/utils";
import { ProgrammingLanguageOptions } from "@/types";
import { AIMessage } from "@langchain/core/messages";
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";

export function Canvas() {
const { toast } = useToast();
const [chatStarted, setChatStarted] = useState(false);
const [pendingArtifactSelection, setPendingArtifactSelection] = useState<
string | null
>(null);
const [isEditing, setIsEditing] = useState(false);
const {
streamMessage,
setMessages,
setArtifacts,
artifacts,
messages,
setSelectedArtifact,
selectedArtifactId,
createThread,
setArtifactContent,
assistantId,
} = useGraph();
const {
reflections,
deleteReflections,
getReflections,
isLoadingReflections,
} = useStore(assistantId);

const createThreadWithChatStarted = async () => {
setChatStarted(false);
return createThread();
};

const handleQuickStart = (
type: "text" | "code",
language?: ProgrammingLanguageOptions
) => {
if (type === "code" && !language) {
toast({
title: "Language not selected",
description: "Please select a language to continue",
duration: 5000,
});
return;
}
setChatStarted(true);

const artifactId = uuidv4();
const artifact = {
id: artifactId,
title: `Quickstart ${type}`,
content:
type === "code"
? getLanguageTemplate(language ?? "javascript")
: "# Hello world",
type,
language: language ?? "english",
};
setArtifacts((prevArtifacts) => [...prevArtifacts, artifact]);
setMessages((prevMessages) => {
const newMessage = new AIMessage({
content: "",
tool_calls: [
{
id: artifactId,
args: { title: artifact.title },
name: "artifact_ui",
},
],
});
return [...prevMessages, newMessage];
});
setIsEditing(true);
setPendingArtifactSelection(artifactId);
};

useEffect(() => {
if (pendingArtifactSelection) {
setSelectedArtifact(pendingArtifactSelection);
setPendingArtifactSelection(null);
}
}, [artifacts, pendingArtifactSelection, setSelectedArtifact]);

return (
<main className="h-screen flex flex-row">
<div
className={cn(
"transition-all duration-700",
chatStarted ? "w-[35%]" : "w-full",
"h-full mr-auto bg-gray-50/70 shadow-inner-right"
)}
>
<ContentComposerChatInterface
handleGetReflections={getReflections}
handleDeleteReflections={deleteReflections}
reflections={reflections}
isLoadingReflections={isLoadingReflections}
setSelectedArtifact={setSelectedArtifact}
streamMessage={streamMessage}
setArtifacts={setArtifacts}
messages={messages}
setMessages={setMessages}
createThread={createThreadWithChatStarted}
setChatStarted={setChatStarted}
showNewThreadButton={chatStarted}
handleQuickStart={handleQuickStart}
/>
</div>
{chatStarted && (
<div className="w-full ml-auto">
<ArtifactRenderer
handleGetReflections={getReflections}
handleDeleteReflections={deleteReflections}
reflections={reflections}
isLoadingReflections={isLoadingReflections}
setIsEditing={setIsEditing}
isEditing={isEditing}
setArtifactContent={setArtifactContent}
setSelectedArtifactById={setSelectedArtifact}
messages={messages}
setMessages={setMessages}
artifact={
selectedArtifactId
? artifacts.find((a) => a.id === selectedArtifactId)
: undefined
}
streamMessage={streamMessage}
/>
</div>
)}
</main>
);
}
16 changes: 16 additions & 0 deletions src/components/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useAuth } from "../contexts/AuthContext";
import { useRouter } from "next/router";

const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const auth = useAuth();
const router = useRouter();

if (!auth?.user) {
router.push("/login");
return null;
}

return children;
};

export default ProtectedRoute;
63 changes: 63 additions & 0 deletions src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { createSupabaseClient } from "@/lib/supabase";
import React, { createContext, useState, useEffect, useContext } from "react";
import {
SignInWithPasswordCredentials,
SignUpWithPasswordCredentials,
User,
} from "@supabase/supabase-js";

interface AuthContextType {
signUp: (data: SignUpWithPasswordCredentials) => Promise<any>;
signIn: (data: SignInWithPasswordCredentials) => Promise<any>;
signOut: () => Promise<any>;
user: User | undefined;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const supabase = createSupabaseClient();
const [user, setUser] = useState<User>();
const [loading, setLoading] = useState(true);

useEffect(() => {
const getSupabaseUser = async () => {
const {
data: { session },
} = await supabase.auth.getSession();
setUser(session?.user);
setLoading(false);
};

getSupabaseUser();

const { data: listener } = supabase.auth.onAuthStateChange(
async (event, session) => {
setUser(session?.user);
setLoading(false);
}
);

return () => {
listener?.subscription.unsubscribe();
};
}, [supabase.auth]);

const value = {
signUp: (data: SignUpWithPasswordCredentials) => supabase.auth.signUp(data),
signIn: (data: SignInWithPasswordCredentials) =>
supabase.auth.signInWithPassword(data),
signOut: () => supabase.auth.signOut(),
user,
};

return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};

export const useAuth = () => {
return useContext(AuthContext);
};
14 changes: 14 additions & 0 deletions src/lib/supabase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createClient } from "@supabase/supabase-js";

export const createSupabaseClient = () => {
if (!process.env.NEXT_PUBLIC_SUPABASE_URL) {
throw new Error("NEXT_PUBLIC_SUPABASE_URL is not defined");
}
if (!process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
throw new Error("NEXT_PUBLIC_SUPABASE_ANON_KEY is not defined");
}
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
};
Loading

0 comments on commit 091c071

Please sign in to comment.