mirror of
https://github.com/MODSetter/SurfSense.git
synced 2025-09-02 02:29:08 +00:00
fix: GraphQAchain Invalid prompts
This commit is contained in:
parent
6d8e1fe994
commit
4b3148fc3e
47 changed files with 62 additions and 5849 deletions
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "SurfSense-Frontend"]
|
||||||
|
path = SurfSense-Frontend
|
||||||
|
url = https://github.com/MODSetter/SurfSense-Frontend.git
|
1
SurfSense-Frontend
Submodule
1
SurfSense-Frontend
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 6439444ac27fe0e7919b61682d767812e61a43ba
|
3
backend/DataExample.py
Normal file
3
backend/DataExample.py
Normal file
File diff suppressed because one or more lines are too long
|
@ -3,20 +3,44 @@ from langchain_core.prompts import ChatPromptTemplate
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DATE_TODAY = "Today's date is " + datetime.now(timezone.utc).astimezone().isoformat() + '\n'
|
DATE_TODAY = "Today's date is " + datetime.now(timezone.utc).astimezone().isoformat() + '\n'
|
||||||
|
|
||||||
|
GRAPH_QUERY_GEN_TEMPLATE = DATE_TODAY + """You are a top tier Prompt Engineering Expert.
|
||||||
|
A User's Data is stored in a Knowledge Graph.
|
||||||
|
Your main task is to read the User Question below and give a optimized Question prompt in Natural Language.
|
||||||
|
Question prompt will be used by a LLM to easlily get data from Knowledge Graph's.
|
||||||
|
|
||||||
|
Make sure to only return the promt text thats it. Never change the meaning of users question.
|
||||||
|
|
||||||
|
Here are the examples of the User's Data Documents that is stored in Knowledge Graph:
|
||||||
|
{context}
|
||||||
|
|
||||||
|
Note: Do not include any explanations or apologies in your responses.
|
||||||
|
Do not include any text except the generated promt text.
|
||||||
|
|
||||||
|
Question: {question}
|
||||||
|
Prompt For Cypher Query Construction:"""
|
||||||
|
|
||||||
|
GRAPH_QUERY_GEN_PROMPT = PromptTemplate(
|
||||||
|
input_variables=["context", "question"], template=GRAPH_QUERY_GEN_TEMPLATE
|
||||||
|
)
|
||||||
|
|
||||||
CYPHER_QA_TEMPLATE = DATE_TODAY + """You are an assistant that helps to form nice and human understandable answers.
|
CYPHER_QA_TEMPLATE = DATE_TODAY + """You are an assistant that helps to form nice and human understandable answers.
|
||||||
The information part contains the provided information that you must use to construct an answer.
|
The information part contains the provided information that you must use to construct an answer.
|
||||||
The provided information is authoritative, you must never doubt it or try to use your internal knowledge to correct it.
|
The provided information is authoritative, you must never doubt it or try to use your internal knowledge to correct it.
|
||||||
Make the answer sound as a response to the question. Do not mention that you based the result on the given information.
|
Make the answer sound as a response to the question. Do not mention that you based the result on the given information.
|
||||||
|
Only give the answer if it satisfies the user requirements in Question. Else return exactly 'don't know' as answer.
|
||||||
|
|
||||||
Here are the examples:
|
Here are the examples:
|
||||||
|
|
||||||
Question: Website on which the most time was spend on?
|
Question: What type of general topics I explore the most?
|
||||||
Context:[{'d.VisitedWebPageURL': 'https://stackoverflow.com/questions/59873698/the-default-export-is-not-a-react-component-in-page-nextjs', 'totalDuration': 8889167}]
|
Context:[['Topic': 'Langchain', 'topicCount': 5], ['Topic': 'Graphrag', 'topicCount': 2], ['Topic': 'Ai', 'topicCount': 2], ['Topic': 'Fastapi', 'topicCount': 2], ['Topic': 'Nextjs', 'topicCount': 1]]
|
||||||
Helpful Answer: You visited https://stackoverflow.com/questions/59873698/the-default-export-is-not-a-react-component-in-page-nextjs for 8889167 milliseconds or 8889.167 seconds.
|
Helpful Answer: You mostly explore about Langchain, Graphrag, Ai, Fastapi and Nextjs.
|
||||||
|
|
||||||
Follow this example when generating answers.
|
Follow this example when generating answers.
|
||||||
If the provided information is empty, then and only then, return exactly 'don't know' as answer.
|
If the provided information is empty or incomplete, return exactly 'don't know' as answer.
|
||||||
|
|
||||||
Information:
|
Information:
|
||||||
{context}
|
{context}
|
||||||
|
@ -54,7 +78,6 @@ Note: Do not include any explanations or apologies in your responses.
|
||||||
Do not respond to any questions that might ask anything else than for you to construct a Cypher statement.
|
Do not respond to any questions that might ask anything else than for you to construct a Cypher statement.
|
||||||
Do not include any text except the generated Cypher statement.
|
Do not include any text except the generated Cypher statement.
|
||||||
|
|
||||||
|
|
||||||
The question is:
|
The question is:
|
||||||
{question}"""
|
{question}"""
|
||||||
CYPHER_GENERATION_PROMPT = PromptTemplate(
|
CYPHER_GENERATION_PROMPT = PromptTemplate(
|
||||||
|
|
|
@ -39,3 +39,7 @@ class UserQueryResponse(BaseModel):
|
||||||
response: str
|
response: str
|
||||||
relateddocs: List[DocMeta]
|
relateddocs: List[DocMeta]
|
||||||
|
|
||||||
|
|
||||||
|
class VectorSearchQuery(BaseModel):
|
||||||
|
searchquery: str
|
||||||
|
|
|
@ -6,13 +6,14 @@ from langchain_core.documents import Document
|
||||||
from langchain_openai import OpenAIEmbeddings
|
from langchain_openai import OpenAIEmbeddings
|
||||||
from langchain_community.vectorstores import Neo4jVector
|
from langchain_community.vectorstores import Neo4jVector
|
||||||
from envs import ACCESS_TOKEN_EXPIRE_MINUTES, ALGORITHM, API_SECRET_KEY, SECRET_KEY
|
from envs import ACCESS_TOKEN_EXPIRE_MINUTES, ALGORITHM, API_SECRET_KEY, SECRET_KEY
|
||||||
from prompts import CYPHER_QA_PROMPT, DOC_DESCRIPTION_PROMPT, SIMILARITY_SEARCH_PROMPT , CYPHER_GENERATION_PROMPT, DOCUMENT_METADATA_EXTRACTION_PROMT
|
from prompts import CYPHER_QA_PROMPT, DOC_DESCRIPTION_PROMPT, GRAPH_QUERY_GEN_PROMPT, SIMILARITY_SEARCH_PROMPT , CYPHER_GENERATION_PROMPT, DOCUMENT_METADATA_EXTRACTION_PROMT
|
||||||
from pydmodels import DescriptionResponse, UserQuery, DocMeta, RetrivedDocList, UserQueryResponse
|
from pydmodels import DescriptionResponse, UserQuery, DocMeta, RetrivedDocList, UserQueryResponse, VectorSearchQuery
|
||||||
from langchain_experimental.text_splitter import SemanticChunker
|
from langchain_experimental.text_splitter import SemanticChunker
|
||||||
|
|
||||||
#Our Imps
|
#Our Imps
|
||||||
from LLMGraphTransformer import LLMGraphTransformer
|
from LLMGraphTransformer import LLMGraphTransformer
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
|
from DataExample import examples
|
||||||
|
|
||||||
# Auth Libs
|
# Auth Libs
|
||||||
from fastapi import FastAPI, Depends, HTTPException, Request, status
|
from fastapi import FastAPI, Depends, HTTPException, Request, status
|
||||||
|
@ -39,8 +40,6 @@ def get_user_query_response(data: UserQuery, response_model=UserQueryResponse):
|
||||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||||
|
|
||||||
|
|
||||||
query = data.query
|
|
||||||
|
|
||||||
graph = Neo4jGraph(url=data.neourl, username=data.neouser, password=data.neopass)
|
graph = Neo4jGraph(url=data.neourl, username=data.neouser, password=data.neopass)
|
||||||
|
|
||||||
llm = ChatOpenAI(
|
llm = ChatOpenAI(
|
||||||
|
@ -51,6 +50,13 @@ def get_user_query_response(data: UserQuery, response_model=UserQueryResponse):
|
||||||
api_key=data.openaikey
|
api_key=data.openaikey
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Query Expansion
|
||||||
|
searchchain = GRAPH_QUERY_GEN_PROMPT | llm
|
||||||
|
|
||||||
|
qry = searchchain.invoke({"question": data.query, "context": examples})
|
||||||
|
|
||||||
|
query = qry.content
|
||||||
|
|
||||||
embeddings = OpenAIEmbeddings(
|
embeddings = OpenAIEmbeddings(
|
||||||
model="text-embedding-ada-002",
|
model="text-embedding-ada-002",
|
||||||
api_key=data.openaikey,
|
api_key=data.openaikey,
|
||||||
|
@ -97,18 +103,21 @@ def get_user_query_response(data: UserQuery, response_model=UserQueryResponse):
|
||||||
|
|
||||||
docstoreturn = [i for n, i in enumerate(docstoreturn) if i not in docstoreturn[n + 1:]]
|
docstoreturn = [i for n, i in enumerate(docstoreturn) if i not in docstoreturn[n + 1:]]
|
||||||
|
|
||||||
|
# responsegrp = chain.invoke({"query": query})
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = chain.invoke({"query": query})
|
responsegrp = chain.invoke({"query": query})
|
||||||
if "don't know" in response["result"]:
|
|
||||||
|
if "don't know" in responsegrp["result"]:
|
||||||
raise Exception("No response from graph")
|
raise Exception("No response from graph")
|
||||||
|
|
||||||
structured_llm = llm.with_structured_output(RetrivedDocList)
|
structured_llm = llm.with_structured_output(VectorSearchQuery)
|
||||||
doc_extract_chain = DOCUMENT_METADATA_EXTRACTION_PROMT | structured_llm
|
doc_extract_chain = DOCUMENT_METADATA_EXTRACTION_PROMT | structured_llm
|
||||||
|
|
||||||
query = doc_extract_chain.invoke(response["intermediate_steps"][1]["context"])
|
newquery = doc_extract_chain.invoke(responsegrp["intermediate_steps"][1]["context"])
|
||||||
|
|
||||||
docs = vector_index.similarity_search(query.searchquery,k=5)
|
docs = vector_index.similarity_search(newquery.searchquery,k=5)
|
||||||
|
|
||||||
docstoreturn = []
|
docstoreturn = []
|
||||||
|
|
||||||
|
@ -127,12 +136,12 @@ def get_user_query_response(data: UserQuery, response_model=UserQueryResponse):
|
||||||
|
|
||||||
docstoreturn = [i for n, i in enumerate(docstoreturn) if i not in docstoreturn[n + 1:]]
|
docstoreturn = [i for n, i in enumerate(docstoreturn) if i not in docstoreturn[n + 1:]]
|
||||||
|
|
||||||
return UserQueryResponse(relateddocs=docstoreturn,response=response["result"])
|
return UserQueryResponse(relateddocs=docstoreturn,response=responsegrp["result"])
|
||||||
except:
|
except:
|
||||||
# Fallback to Similarity Search RAG
|
# Fallback to Similarity Search RAG
|
||||||
searchchain = SIMILARITY_SEARCH_PROMPT | llm
|
searchchain = SIMILARITY_SEARCH_PROMPT | llm
|
||||||
|
|
||||||
response = searchchain.invoke({"question": query, "context": docs})
|
response = searchchain.invoke({"question": data.query, "context": docs})
|
||||||
|
|
||||||
return UserQueryResponse(relateddocs=docstoreturn,response=response.content)
|
return UserQueryResponse(relateddocs=docstoreturn,response=response.content)
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
export const API_SECRET_KEY = ""
|
export const API_SECRET_KEY = "surfsense"
|
||||||
export const BACKEND_URL = ""
|
export const BACKEND_URL = "http://127.0.0.1:8000"
|
|
@ -1,9 +0,0 @@
|
||||||
# YOUR SURFDENSE BACKEND API SECRET KEY
|
|
||||||
NEXT_PUBLIC_API_SECRET_KEY="ANY STRING VALUE MAKE SURE IT MACHES THE VALUE IN BACKEND"
|
|
||||||
|
|
||||||
#YOUR SURFSENSE BACKEND URL
|
|
||||||
NEXT_PUBLIC_BACKEND_URL="http://localhost:8000"
|
|
||||||
|
|
||||||
# Recaptcha v2 to prevent Registration spam
|
|
||||||
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=""
|
|
||||||
RECAPTCHA_SECRET_KEY=""
|
|
36
web/.gitignore
vendored
36
web/.gitignore
vendored
|
@ -1,36 +0,0 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
.yarn/install-state.gz
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env*.local
|
|
||||||
.env
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript
|
|
||||||
*.tsbuildinfo
|
|
||||||
next-env.d.ts
|
|
|
@ -1,36 +0,0 @@
|
||||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
First, run the development server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
# or
|
|
||||||
yarn dev
|
|
||||||
# or
|
|
||||||
pnpm dev
|
|
||||||
# or
|
|
||||||
bun dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
||||||
|
|
||||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
||||||
|
|
||||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
|
|
@ -1,8 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import MarkdownPreview from '@uiw/react-markdown-preview';
|
|
||||||
|
|
||||||
export default function MarkDownTest({source} : {source: string}) {
|
|
||||||
return (
|
|
||||||
<MarkdownPreview source={source} style={{ padding: 16 }} />
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,344 +0,0 @@
|
||||||
"use client";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import logo from "@/public/SurfSense.png";
|
|
||||||
import { FileCheck } from "lucide-react";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from "@/components/ui/collapsible";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import MarkDownTest from "./markdown";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
|
|
||||||
type Document = {
|
|
||||||
BrowsingSessionId: string;
|
|
||||||
VisitedWebPageURL: string;
|
|
||||||
VisitedWebPageTitle: string;
|
|
||||||
VisitedWebPageDateWithTimeInISOString: string;
|
|
||||||
VisitedWebPageReffererURL: string;
|
|
||||||
VisitedWebPageVisitDurationInMilliseconds: number;
|
|
||||||
VisitedWebPageContent: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// type Description = {
|
|
||||||
// response: string;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// type NormalResponse = {
|
|
||||||
// response: string;
|
|
||||||
// relateddocs: Document[];
|
|
||||||
// };
|
|
||||||
|
|
||||||
// type ChatMessage = {
|
|
||||||
// type: string;
|
|
||||||
// userquery: string;
|
|
||||||
// message: NormalResponse | string;
|
|
||||||
// };
|
|
||||||
|
|
||||||
function ProtectedPage() {
|
|
||||||
// const navigate = useNavigate();
|
|
||||||
const router = useRouter();
|
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [currentChat, setCurrentChat] = useState<any[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const verifyToken = async () => {
|
|
||||||
const token = window.localStorage.getItem('token');
|
|
||||||
// console.log(token)
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL!}/verify-token/${token}`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Token verification failed');
|
|
||||||
}else{
|
|
||||||
const NEO4JURL = localStorage.getItem('neourl');
|
|
||||||
const NEO4JUSERNAME = localStorage.getItem('neouser');
|
|
||||||
const NEO4JPASSWORD = localStorage.getItem('neopass');
|
|
||||||
const OPENAIKEY = localStorage.getItem('openaikey');
|
|
||||||
|
|
||||||
const check = (NEO4JURL && NEO4JUSERNAME && NEO4JPASSWORD && OPENAIKEY)
|
|
||||||
if(!check){
|
|
||||||
router.push('/settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
window.localStorage.removeItem('token');
|
|
||||||
router.push('/login');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
verifyToken();
|
|
||||||
}, [router]);
|
|
||||||
|
|
||||||
const handleSubmit = async (formData: any) => {
|
|
||||||
setLoading(true);
|
|
||||||
const query = formData.get("query");
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
console.log("Query cant be empty!!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestOptions = {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
query: query,
|
|
||||||
neourl: localStorage.getItem('neourl'),
|
|
||||||
neouser: localStorage.getItem('neouser'),
|
|
||||||
neopass: localStorage.getItem('neopass'),
|
|
||||||
openaikey: localStorage.getItem('openaikey'),
|
|
||||||
apisecretkey: process.env.NEXT_PUBLIC_API_SECRET_KEY
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL!}/`, requestOptions)
|
|
||||||
.then(res=>res.json())
|
|
||||||
.then(data=> {
|
|
||||||
let cur = currentChat;
|
|
||||||
cur.push({
|
|
||||||
type: "normal",
|
|
||||||
userquery: query,
|
|
||||||
message: data,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
setCurrentChat([...cur]);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDocDescription = async (document: Document) => {
|
|
||||||
setLoading(true);
|
|
||||||
const requestOptions = {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
query: JSON.stringify(document),
|
|
||||||
neourl: localStorage.getItem('neourl'),
|
|
||||||
neouser: localStorage.getItem('neouser'),
|
|
||||||
neopass: localStorage.getItem('neopass'),
|
|
||||||
openaikey: localStorage.getItem('openaikey'),
|
|
||||||
apisecretkey: process.env.NEXT_PUBLIC_API_SECRET_KEY
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${process.env.NEXT_PUBLIC_BACKEND_URL!}/kb/doc`,
|
|
||||||
requestOptions
|
|
||||||
);
|
|
||||||
const res = await response.json();
|
|
||||||
|
|
||||||
let cur = currentChat;
|
|
||||||
cur.push({
|
|
||||||
type: "description",
|
|
||||||
doctitle: document.VisitedWebPageTitle,
|
|
||||||
message: res.response,
|
|
||||||
});
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
setCurrentChat([...cur]);
|
|
||||||
// console.log(document);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (currentChat) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden mt-16">
|
|
||||||
<div className="group w-full overflow-auto pl-0 peer-[[data-state=open]]:lg:pl-[250px] peer-[[data-state=open]]:xl:pl-[300px]">
|
|
||||||
<div className="pb-[200px] pt-4 md:pt-10">
|
|
||||||
<div className="mx-auto max-w-4xl px-4 flex flex-col gap-3">
|
|
||||||
<div className="bg-background flex flex-col gap-2 rounded-lg border p-8">
|
|
||||||
<h1 className="text-sm font-semibold">
|
|
||||||
Welcome to SurfSense
|
|
||||||
</h1>
|
|
||||||
<p className="text-muted-foreground leading-normal">
|
|
||||||
🧠 Ask Your Knowledge Graph Brain 🧠
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{currentChat.map((chat, index) => {
|
|
||||||
// console.log("chat", chat);
|
|
||||||
if (chat.type === "normal") {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="bg-background flex flex-col gap-2 rounded-lg border p-8"
|
|
||||||
key={index}
|
|
||||||
>
|
|
||||||
<p className="text-3xl font-semibold">
|
|
||||||
{chat.userquery}
|
|
||||||
</p>
|
|
||||||
<p className="font-sm font-semibold">
|
|
||||||
SurfSense Response:
|
|
||||||
</p>
|
|
||||||
<MarkDownTest source={chat.message.response} />
|
|
||||||
<p className="font-sm font-semibold">
|
|
||||||
Related Browsing Sessions
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{
|
|
||||||
//@ts-ignore
|
|
||||||
chat.message.relateddocs.map((doc) => {
|
|
||||||
return (
|
|
||||||
<Collapsible className="border rounded-lg p-3">
|
|
||||||
<CollapsibleTrigger className="flex justify-between gap-2 mb-2">
|
|
||||||
<FileCheck />
|
|
||||||
{doc.VisitedWebPageTitle}
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent className="flex flex-col gap-4">
|
|
||||||
<Table>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell className="font-medium">
|
|
||||||
Browsing Session Id
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{doc.BrowsingSessionId}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell className="font-medium">
|
|
||||||
URL
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{doc.VisitedWebPageURL}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell className="font-medium">
|
|
||||||
Reffering URL
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{doc.VisitedWebPageReffererURL}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
<TableRow>
|
|
||||||
<TableCell className="font-medium">
|
|
||||||
Date & Time Visited
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{
|
|
||||||
doc.VisitedWebPageDateWithTimeInISOString
|
|
||||||
}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
<TableRow>
|
|
||||||
<TableCell className="font-medium">
|
|
||||||
Visit Duration (In Milliseconds)
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{
|
|
||||||
doc.VisitedWebPageVisitDurationInMilliseconds
|
|
||||||
}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => getDocDescription(doc)}
|
|
||||||
className="text-gray-900 w-full hover:text-white border border-gray-800 hover:bg-gray-900 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2 dark:border-gray-600 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-800"
|
|
||||||
>
|
|
||||||
Get More Information
|
|
||||||
</button>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chat.type === "description") {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="bg-background flex flex-col gap-2 rounded-lg border p-8"
|
|
||||||
key={index}
|
|
||||||
>
|
|
||||||
<p className="text-3xl font-semibold">
|
|
||||||
{chat.doctitle}
|
|
||||||
</p>
|
|
||||||
<MarkDownTest source={chat.message} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="h-px w-full"></div>
|
|
||||||
</div>
|
|
||||||
<div className="from-muted/30 to-muted/30 animate-in dark:from-background/10 dark:to-background/80 inset-x-0 bottom-0 w-full duration-300 ease-in-out peer-[[data-state=open]]:group-[]:lg:pl-[250px] peer-[[data-state=open]]:group-[]:xl:pl-[300px] dark:from-10%">
|
|
||||||
<div className="mx-auto sm:max-w-4xl sm:px-4">
|
|
||||||
|
|
||||||
<div className={loading ? "rounded-md p-4 w-full my-4" : "hidden"}>
|
|
||||||
<div className="animate-pulse flex space-x-4">
|
|
||||||
<div className="rounded-full bg-slate-700 h-10 w-10">
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 space-y-6 py-1">
|
|
||||||
<div className="h-2 bg-slate-700 rounded"></div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="grid grid-cols-3 gap-4">
|
|
||||||
<div className="h-2 bg-slate-700 rounded col-span-2"></div>
|
|
||||||
<div className="h-2 bg-slate-700 rounded col-span-1"></div>
|
|
||||||
</div>
|
|
||||||
<div className="h-2 bg-slate-700 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-background space-y-4 border-t px-4 py-2 shadow-lg sm:rounded-t-xl sm:border md:py-4">
|
|
||||||
<form action={handleSubmit}>
|
|
||||||
<div className="bg-background relative flex max-h-60 w-full grow flex-col overflow-hidden px-8 sm:rounded-md sm:border sm:px-12">
|
|
||||||
<Image
|
|
||||||
className="inline-flex items-center justify-center whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input shadow-sm hover:bg-accent hover:text-accent-foreground h-9 w-9 bg-background absolute left-0 top-[13px] size-8 rounded-full p-0 sm:left-4"
|
|
||||||
src={logo}
|
|
||||||
alt="aiicon"
|
|
||||||
/>
|
|
||||||
<span className="sr-only">New Chat</span>
|
|
||||||
<textarea
|
|
||||||
placeholder="Send a message."
|
|
||||||
className="min-h-[60px] w-full resize-none bg-transparent px-4 py-[1.3rem] focus-within:outline-none sm:text-sm"
|
|
||||||
name="query"
|
|
||||||
></textarea>
|
|
||||||
<div className="absolute right-0 top-[13px] sm:right-4">
|
|
||||||
<button
|
|
||||||
className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground shadow hover:bg-primary/90 h-9 w-9"
|
|
||||||
type="submit"
|
|
||||||
data-state="closed"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 256 256"
|
|
||||||
fill="currentColor"
|
|
||||||
className="size-4"
|
|
||||||
>
|
|
||||||
<path d="M200 32v144a8 8 0 0 1-8 8H67.31l34.35 34.34a8 8 0 0 1-11.32 11.32l-48-48a8 8 0 0 1 0-11.32l48-48a8 8 0 0 1 11.32 11.32L67.31 168H184V32a8 8 0 0 1 16 0Z"></path>
|
|
||||||
</svg>
|
|
||||||
<span className="sr-only">Send message</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProtectedPage;
|
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
|
@ -1,69 +0,0 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
:root {
|
|
||||||
--background: 0 0% 100%;
|
|
||||||
--foreground: 0 0% 3.9%;
|
|
||||||
--card: 0 0% 100%;
|
|
||||||
--card-foreground: 0 0% 3.9%;
|
|
||||||
--popover: 0 0% 100%;
|
|
||||||
--popover-foreground: 0 0% 3.9%;
|
|
||||||
--primary: 0 0% 9%;
|
|
||||||
--primary-foreground: 0 0% 98%;
|
|
||||||
--secondary: 0 0% 96.1%;
|
|
||||||
--secondary-foreground: 0 0% 9%;
|
|
||||||
--muted: 0 0% 96.1%;
|
|
||||||
--muted-foreground: 0 0% 45.1%;
|
|
||||||
--accent: 0 0% 96.1%;
|
|
||||||
--accent-foreground: 0 0% 9%;
|
|
||||||
--destructive: 0 84.2% 60.2%;
|
|
||||||
--destructive-foreground: 0 0% 98%;
|
|
||||||
--border: 0 0% 89.8%;
|
|
||||||
--input: 0 0% 89.8%;
|
|
||||||
--ring: 0 0% 3.9%;
|
|
||||||
--radius: 0.5rem;
|
|
||||||
--chart-1: 12 76% 61%;
|
|
||||||
--chart-2: 173 58% 39%;
|
|
||||||
--chart-3: 197 37% 24%;
|
|
||||||
--chart-4: 43 74% 66%;
|
|
||||||
--chart-5: 27 87% 67%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: 0 0% 3.9%;
|
|
||||||
--foreground: 0 0% 98%;
|
|
||||||
--card: 0 0% 3.9%;
|
|
||||||
--card-foreground: 0 0% 98%;
|
|
||||||
--popover: 0 0% 3.9%;
|
|
||||||
--popover-foreground: 0 0% 98%;
|
|
||||||
--primary: 0 0% 98%;
|
|
||||||
--primary-foreground: 0 0% 9%;
|
|
||||||
--secondary: 0 0% 14.9%;
|
|
||||||
--secondary-foreground: 0 0% 98%;
|
|
||||||
--muted: 0 0% 14.9%;
|
|
||||||
--muted-foreground: 0 0% 63.9%;
|
|
||||||
--accent: 0 0% 14.9%;
|
|
||||||
--accent-foreground: 0 0% 98%;
|
|
||||||
--destructive: 0 62.8% 30.6%;
|
|
||||||
--destructive-foreground: 0 0% 98%;
|
|
||||||
--border: 0 0% 14.9%;
|
|
||||||
--input: 0 0% 14.9%;
|
|
||||||
--ring: 0 0% 83.1%;
|
|
||||||
--chart-1: 220 70% 50%;
|
|
||||||
--chart-2: 160 60% 45%;
|
|
||||||
--chart-3: 30 80% 55%;
|
|
||||||
--chart-4: 280 65% 60%;
|
|
||||||
--chart-5: 340 75% 55%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
|
||||||
@apply border-border;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
import type { Metadata } from "next";
|
|
||||||
import { Inter } from "next/font/google";
|
|
||||||
import "./globals.css";
|
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
|
||||||
import { MainNavbar } from "@/components/homepage/NavBar";
|
|
||||||
import { Toaster } from "@/components/ui/toaster"
|
|
||||||
import { Footer } from "@/components/homepage/Footer";
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "SurfSense - A Knowledge Graph Brain for World Wide Web Surfers.",
|
|
||||||
description:
|
|
||||||
"Save anything you see or browse on the Internet and save it to ask AI about it.",
|
|
||||||
openGraph: {
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: "https://surfsense.net/og-image.png",
|
|
||||||
width: 1200,
|
|
||||||
height: 627,
|
|
||||||
alt: "SurfSense - A Knowledge Graph Brain for World Wide Web Surfers.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
card: "summary_large_image",
|
|
||||||
site: "https://surfsense.net",
|
|
||||||
creator: "https://surfsense.net",
|
|
||||||
title: "SurfSense - A Knowledge Graph Brain for World Wide Web Surfers.",
|
|
||||||
description:
|
|
||||||
"Save anything you see or browse on the Internet and save it to ask AI about it.",
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: "https://surfsense.net/og-image.png",
|
|
||||||
width: 1200,
|
|
||||||
height: 627,
|
|
||||||
alt: "SurfSense - A Knowledge Graph Brain for World Wide Web Surfers.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<body className={inter.className}>
|
|
||||||
<ThemeProvider
|
|
||||||
attribute="class"
|
|
||||||
defaultTheme="system"
|
|
||||||
enableSystem
|
|
||||||
disableTransitionOnChange
|
|
||||||
>
|
|
||||||
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<MainNavbar />
|
|
||||||
<div className="grow">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Toaster />
|
|
||||||
</ThemeProvider>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { LoginForm } from "@/components/logins/LoginForm"
|
|
||||||
|
|
||||||
const page = () => {
|
|
||||||
return (
|
|
||||||
<><LoginForm /></>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default page
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { HomePage } from "@/components/homepage/HomePage";
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HomePage />
|
|
||||||
</>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
"use client"
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
|
|
||||||
const FillEnvVariables = () => {
|
|
||||||
const [neourl, setNeourl] = useState('');
|
|
||||||
const [neouser, setNeouser] = useState('');
|
|
||||||
const [neopass, setNeopass] = useState('');
|
|
||||||
const [openaikey, setOpenaiKey] = useState('');
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const validateForm = () => {
|
|
||||||
if (!neourl || !neouser || !neopass || !openaikey) {
|
|
||||||
setError('All values are required');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
setError('');
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (event: { preventDefault: () => void; }) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (!validateForm()) return;
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
localStorage.setItem('neourl', neourl);
|
|
||||||
localStorage.setItem('neouser', neouser);
|
|
||||||
localStorage.setItem('neopass', neopass);
|
|
||||||
localStorage.setItem('openaikey', openaikey);
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
router.push('/chat')
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="bg-gray-50 dark:bg-gray-900">
|
|
||||||
<div className="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
|
||||||
<a href="#" className="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white">
|
|
||||||
<img className="w-8 h-8 mr-2" src="./icon-128.png" alt="logo" />
|
|
||||||
SurfSense
|
|
||||||
</a>
|
|
||||||
<div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
|
|
||||||
<div className="p-6 space-y-4 md:space-y-6 sm:p-8">
|
|
||||||
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
|
||||||
Required Values
|
|
||||||
</h1>
|
|
||||||
<form className="space-y-4 md:space-y-6" onSubmit={handleSubmit}>
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Neo4J URL</label>
|
|
||||||
<input type="text" value={neourl} onChange={(e) => setNeourl(e.target.value)} name="neourl" id="neourl" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Neo4J Username</label>
|
|
||||||
<input type="text" value={neouser} onChange={(e) => setNeouser(e.target.value)} name="neouser" id="neouser" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Neo4J Password</label>
|
|
||||||
<input type="text" value={neopass} onChange={(e) => setNeopass(e.target.value)} name="neopass" id="neopass" placeholder="••••••••" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">OpenAI API Key</label>
|
|
||||||
<input type="text" value={openaikey} onChange={(e) => setOpenaiKey(e.target.value)} name="openaikey" id="openaikey" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" />
|
|
||||||
</div>
|
|
||||||
<button type="submit" className="mt-4 w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">{loading ? 'Saving....' : 'Save & Proceed'}</button>
|
|
||||||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FillEnvVariables
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { RegisterForm } from "@/components/logins/RegisterForm"
|
|
||||||
|
|
||||||
const page = () => {
|
|
||||||
return (
|
|
||||||
<RegisterForm />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default page
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://ui.shadcn.com/schema.json",
|
|
||||||
"style": "default",
|
|
||||||
"rsc": true,
|
|
||||||
"tsx": true,
|
|
||||||
"tailwind": {
|
|
||||||
"config": "tailwind.config.ts",
|
|
||||||
"css": "app/globals.css",
|
|
||||||
"baseColor": "neutral",
|
|
||||||
"cssVariables": true,
|
|
||||||
"prefix": ""
|
|
||||||
},
|
|
||||||
"aliases": {
|
|
||||||
"components": "@/components",
|
|
||||||
"utils": "@/lib/utils",
|
|
||||||
"ui": "@/components/ui",
|
|
||||||
"examples": "@/components/examples",
|
|
||||||
"blocks": "@/components/blocks"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { useToast } from "../ui/use-toast";
|
|
||||||
export const FillEnvVariables = () => {
|
|
||||||
const { toast } = useToast()
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<div className="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
|
||||||
<a href="#" className="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white">
|
|
||||||
<img className="w-8 h-8 mr-2" src="./icon-128.png" alt="logo" />
|
|
||||||
SurfSense
|
|
||||||
</a>
|
|
||||||
<div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
|
|
||||||
<div className="p-6 space-y-4 md:space-y-6 sm:p-8">
|
|
||||||
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
|
||||||
Required Values
|
|
||||||
</h1>
|
|
||||||
<form className="space-y-4 md:space-y-6" action="#">
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Neo4J URL</label>
|
|
||||||
<input type="email" name="email" id="email" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Neo4J Username</label>
|
|
||||||
<input type="email" name="email" id="email" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Neo4J Password</label>
|
|
||||||
<input type="password" name="password" id="password" placeholder="••••••••" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">OpenAI API Key</label>
|
|
||||||
<input type="email" name="email" id="email" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" />
|
|
||||||
</div>
|
|
||||||
<button type="submit" className="mt-4 w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Save & Proceed</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
export const Footer = () => {
|
|
||||||
return (
|
|
||||||
<footer className="mt-2 w-full md:flex overflow-y-hidden items-center justify-between gap-4 px-8 py-8 text-sm text-zinc-500 overflow-hidden text-center">
|
|
||||||
<p>© 2024 SurfSense.net</p>
|
|
||||||
<div className="flex gap-5 justify-around my-2">
|
|
||||||
<a className="group/mail flex items-center" target="_blank" href="mailto:hi@dhravya.dev">Contact<svg className="group-hover/mail:opacity-100 opacity-0 transition hidden md:block" width="24px" height="24px" viewBox="-2.4 -2.4 28.80 28.80" fill="none" xmlns="http://www.w3.org/2000/svg" transform="matrix(1, 0, 0, 1, 0, 0)rotate(0)"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M7 17L17 7M17 7H8M17 7V16" stroke="currentColor" stroke-width="0.792" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg></a><a className="group/twit flex items-center" target="_blank" href="https://twitter.com/supermemoryai">Twitter<svg className="group-hover/twit:opacity-100 opacity-0 transition hidden md:block" width="24px" height="24px" viewBox="-2.4 -2.4 28.80 28.80" fill="none" xmlns="http://www.w3.org/2000/svg" transform="matrix(1, 0, 0, 1, 0, 0)rotate(0)"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M7 17L17 7M17 7H8M17 7V16" stroke="currentColor" stroke-width="0.792" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg></a><a className="group/git flex items-center" target="_blank" href="https://github.com/dhravya/supermemory">Github<svg className="group-hover/git:opacity-100 opacity-0 transition hidden md:block" width="24px" height="24px" viewBox="-2.4 -2.4 28.80 28.80" fill="none" xmlns="http://www.w3.org/2000/svg" transform="matrix(1, 0, 0, 1, 0, 0)rotate(0)"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M7 17L17 7M17 7H8M17 7V16" stroke="currentColor" stroke-width="0.792" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg></a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import React from "react";
|
|
||||||
import { AuroraBackground } from "../ui/aurora-background";
|
|
||||||
|
|
||||||
import icon from "../../public/SurfSense.png"
|
|
||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
export function HomePage() {
|
|
||||||
return (
|
|
||||||
<AuroraBackground>
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0.0, y: 40 }}
|
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{
|
|
||||||
delay: 0.3,
|
|
||||||
duration: 0.8,
|
|
||||||
ease: "easeInOut",
|
|
||||||
}}
|
|
||||||
className="relative flex flex-col gap-4 items-center justify-center px-4"
|
|
||||||
>
|
|
||||||
<div className="flex items-center mb-4 text-5xl font-semibold text-gray-900 dark:text-white">
|
|
||||||
<Image className="w-64 h-64 rounded-full" src={icon} alt="logo" />
|
|
||||||
</div>
|
|
||||||
<div className="text-3xl md:text-7xl font-bold dark:text-white text-center">
|
|
||||||
SurfSense
|
|
||||||
</div>
|
|
||||||
{/* <div className="text-lg font-semibold dark:text-neutral-200">Beta v0.0.1</div> */}
|
|
||||||
<div className="font-extralight text-base md:text-4xl dark:text-neutral-200 py-4">
|
|
||||||
A Knowledge Graph 🧠 Brain 🧠 for World Wide Web Surfers.
|
|
||||||
</div>
|
|
||||||
<button className="relative inline-flex h-12 overflow-hidden rounded-full p-[1px] focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50">
|
|
||||||
<span className="absolute inset-[-1000%] animate-[spin_2s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,#E2CBFF_0%,#393BB2_50%,#E2CBFF_100%)]" />
|
|
||||||
<Link href={'/signup'} className="inline-flex h-full w-full cursor-pointer items-center justify-center rounded-full bg-slate-950 px-8 py-4 text-2xl font-medium text-white backdrop-blur-3xl">
|
|
||||||
Sign Up
|
|
||||||
</Link>
|
|
||||||
</button>
|
|
||||||
</motion.div>
|
|
||||||
</AuroraBackground>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
"use client";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { HoveredLink, Menu, MenuItem, ProductItem } from "../ui/navbar-menu";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { ThemeToggle } from "./theme-toggle";
|
|
||||||
import Image from "next/image";
|
|
||||||
import logo from "../../public/SurfSense.png"
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
export function MainNavbar() {
|
|
||||||
return (
|
|
||||||
<div className="relative w-full flex items-center justify-around">
|
|
||||||
<Navbar className="top-2 px-2" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Navbar({ className }: { className?: string }) {
|
|
||||||
const [active, setActive] = useState<string | null>(null);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn("fixed top-10 inset-x-0 max-w-7xl mx-auto z-50", className)}
|
|
||||||
>
|
|
||||||
<Menu setActive={setActive}>
|
|
||||||
<Link href={"/"} className="flex items-center text-2xl font-semibold text-gray-900 dark:text-white">
|
|
||||||
<Image className="hidden sm:block w-8 h-8 mr-2" src={logo} alt="logo" />
|
|
||||||
<span className="hidden md:block">SurfSense</span>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Link href={"/login"}>
|
|
||||||
<button className="px-4 py-2 rounded-md border border-black bg-white text-black text-sm hover:shadow-[4px_4px_0px_0px_rgba(0,0,0)] transition duration-200">
|
|
||||||
Log In
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link href={"/signup"}>
|
|
||||||
<button className="px-4 py-2 rounded-md border border-black bg-white text-black text-sm hover:shadow-[4px_4px_0px_0px_rgba(0,0,0)] transition duration-200">
|
|
||||||
Sign Up
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link href={"/settings"}>
|
|
||||||
<button className="px-4 py-2 rounded-md border border-black bg-white text-black text-sm hover:shadow-[4px_4px_0px_0px_rgba(0,0,0)] transition duration-200">
|
|
||||||
Settings
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link href={"/chat"} className="grow">
|
|
||||||
<button className="px-4 py-2 rounded-md border border-black bg-white text-black text-sm hover:shadow-[4px_4px_0px_0px_rgba(0,0,0)] transition duration-200">
|
|
||||||
🧠
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ThemeToggle />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { Moon, Sun } from "lucide-react"
|
|
||||||
import { useTheme } from "next-themes"
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu"
|
|
||||||
|
|
||||||
export function ThemeToggle() {
|
|
||||||
const { setTheme } = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button className="bg-transparent" variant="ghost" size="sm">
|
|
||||||
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
||||||
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
|
||||||
<span className="sr-only">Toggle theme</span>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
|
||||||
Light
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
|
||||||
Dark
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
|
||||||
System
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
"use client"
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const LoginForm = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const [username, setUsername] = useState('');
|
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const validateForm = () => {
|
|
||||||
if (!username || !password) {
|
|
||||||
setError('Username and password are required');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
setError('');
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (event: any) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (!validateForm()) return;
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const formDetails = new URLSearchParams();
|
|
||||||
formDetails.append('username', username);
|
|
||||||
formDetails.append('password', password);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL!}/token`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: formDetails,
|
|
||||||
});
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
window.localStorage.setItem('token', data.access_token);
|
|
||||||
router.push('/chat');
|
|
||||||
// navigate('/protected');
|
|
||||||
} else {
|
|
||||||
const errorData = await response.json();
|
|
||||||
setError(errorData.detail || 'Authentication failed!');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setLoading(false);
|
|
||||||
setError('An error occurred. Please try again later.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<section>
|
|
||||||
<div className="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
|
||||||
<a href="#" className="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white">
|
|
||||||
<img className="w-8 h-8 mr-2" src={"./icon-128.png"} alt="logo" />
|
|
||||||
SurfSense
|
|
||||||
</a>
|
|
||||||
<div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
|
|
||||||
<div className="p-6 space-y-4 md:space-y-6 sm:p-8">
|
|
||||||
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
|
||||||
Sign in to your account
|
|
||||||
</h1>
|
|
||||||
<form className="space-y-4 md:space-y-6" onSubmit={handleSubmit}>
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Username</label>
|
|
||||||
<input name="email" id="email" value={username}
|
|
||||||
onChange={(e) => setUsername(e.target.value)} className="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
id="password"
|
|
||||||
placeholder="••••••••"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
className="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button type="submit" className="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
|
|
||||||
{loading ? 'Logging in...' : 'Login'}
|
|
||||||
</button>
|
|
||||||
<p className="text-sm font-light text-gray-500 dark:text-gray-400">
|
|
||||||
Don’t have an account yet? <Link href={"/signup"} className="font-medium text-primary-600 hover:underline dark:text-primary-500">Sign up</Link>
|
|
||||||
</p>
|
|
||||||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
"use client"
|
|
||||||
import React, { FormEvent, useState } from "react";
|
|
||||||
import ReCAPTCHA from "react-google-recaptcha";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useToast } from "../ui/use-toast";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
export const RegisterForm = () => {
|
|
||||||
const [captcha, setCaptcha] = useState<string | null>();
|
|
||||||
const router = useRouter();
|
|
||||||
const [username, setUsername] = useState('');
|
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
const [confpassword, setConfPassword] = useState('');
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const { toast } = useToast()
|
|
||||||
|
|
||||||
const validateForm = () => {
|
|
||||||
if (!username || !password || !confpassword) {
|
|
||||||
if(password !== confpassword){
|
|
||||||
setError('Password and Confirm Password doesnt match');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
setError('Username and password are required');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
setError('');
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (event: FormEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
if (captcha) {
|
|
||||||
if (!validateForm()) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
const toSend = {
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
apisecretkey: process.env.NEXT_PUBLIC_API_SECRET_KEY!
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL!}/register`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(toSend),
|
|
||||||
});
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
toast({
|
|
||||||
title: "Registered Successfully",
|
|
||||||
description: "Redirecting to Login",
|
|
||||||
})
|
|
||||||
router.push('/login');
|
|
||||||
} else {
|
|
||||||
const errorData = await response.json();
|
|
||||||
setError(errorData.detail || 'Authentication failed!');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setLoading(false);
|
|
||||||
setError('An error occurred. Please try again later.');
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
setLoading(false);
|
|
||||||
setError('Recaptcha Failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<div className="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
|
||||||
<div className="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white">
|
|
||||||
<img className="w-8 h-8 mr-2" src="./icon-128.png" alt="logo" />
|
|
||||||
SurfSense
|
|
||||||
</div>
|
|
||||||
<div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
|
|
||||||
<div className="p-6 space-y-4 md:space-y-6 sm:p-8">
|
|
||||||
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
|
||||||
Create an account
|
|
||||||
</h1>
|
|
||||||
<form className="space-y-4 md:space-y-6" onSubmit={handleSubmit}>
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Username</label>
|
|
||||||
<input value={username}
|
|
||||||
onChange={(e) => setUsername(e.target.value)} type="username" name="username" id="username" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="username" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
|
|
||||||
<input value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)} type="password" name="password" id="password" placeholder="••••••••" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm password</label>
|
|
||||||
<input value={confpassword}
|
|
||||||
onChange={(e) => setConfPassword(e.target.value)}
|
|
||||||
type="password" name="confpassword" id="confpassword" placeholder="••••••••" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" />
|
|
||||||
</div>
|
|
||||||
<ReCAPTCHA sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!} className="mx-auto" onChange={setCaptcha} />
|
|
||||||
<button type="submit" className="mt-4 w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"> {loading ? 'Creating...' : 'Create Account'}</button>
|
|
||||||
<p className="text-sm font-light text-gray-500 dark:text-gray-400">
|
|
||||||
Already have an account? <Link href={"/login"} className="font-medium text-primary-600 hover:underline dark:text-primary-500">Login here</Link>
|
|
||||||
</p>
|
|
||||||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
|
||||||
import { type ThemeProviderProps } from "next-themes/dist/types"
|
|
||||||
|
|
||||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
|
||||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
"use client";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import React, { ReactNode } from "react";
|
|
||||||
|
|
||||||
interface AuroraBackgroundProps extends React.HTMLProps<HTMLDivElement> {
|
|
||||||
children: ReactNode;
|
|
||||||
showRadialGradient?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AuroraBackground = ({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
showRadialGradient = true,
|
|
||||||
...props
|
|
||||||
}: AuroraBackgroundProps) => {
|
|
||||||
return (
|
|
||||||
<main>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"relative flex flex-col h-[100vh] items-center justify-center bg-zinc-50 dark:bg-zinc-900 text-slate-950 transition-bg",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<div className="absolute inset-0 overflow-hidden">
|
|
||||||
<div
|
|
||||||
// I'm sorry but this is what peak developer performance looks like // trigger warning
|
|
||||||
className={cn(
|
|
||||||
`
|
|
||||||
[--white-gradient:repeating-linear-gradient(100deg,var(--white)_0%,var(--white)_7%,var(--transparent)_10%,var(--transparent)_12%,var(--white)_16%)]
|
|
||||||
[--dark-gradient:repeating-linear-gradient(100deg,var(--black)_0%,var(--black)_7%,var(--transparent)_10%,var(--transparent)_12%,var(--black)_16%)]
|
|
||||||
[--aurora:repeating-linear-gradient(100deg,var(--blue-500)_10%,var(--indigo-300)_15%,var(--blue-300)_20%,var(--violet-200)_25%,var(--blue-400)_30%)]
|
|
||||||
[background-image:var(--white-gradient),var(--aurora)]
|
|
||||||
dark:[background-image:var(--dark-gradient),var(--aurora)]
|
|
||||||
[background-size:300%,_200%]
|
|
||||||
[background-position:50%_50%,50%_50%]
|
|
||||||
filter blur-[10px] invert dark:invert-0
|
|
||||||
after:content-[""] after:absolute after:inset-0 after:[background-image:var(--white-gradient),var(--aurora)]
|
|
||||||
after:dark:[background-image:var(--dark-gradient),var(--aurora)]
|
|
||||||
after:[background-size:200%,_100%]
|
|
||||||
after:animate-aurora after:[background-attachment:fixed] after:mix-blend-difference
|
|
||||||
pointer-events-none
|
|
||||||
absolute -inset-[10px] opacity-50 will-change-transform`,
|
|
||||||
|
|
||||||
showRadialGradient &&
|
|
||||||
`[mask-image:radial-gradient(ellipse_at_100%_0%,black_10%,var(--transparent)_70%)]`
|
|
||||||
)}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,56 +0,0 @@
|
||||||
import * as React from "react"
|
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const buttonVariants = cva(
|
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
||||||
destructive:
|
|
||||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
||||||
outline:
|
|
||||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
||||||
secondary:
|
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: "h-10 px-4 py-2",
|
|
||||||
sm: "h-9 rounded-md px-3",
|
|
||||||
lg: "h-11 rounded-md px-8",
|
|
||||||
icon: "h-10 w-10",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export interface ButtonProps
|
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
||||||
VariantProps<typeof buttonVariants> {
|
|
||||||
asChild?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
||||||
const Comp = asChild ? Slot : "button"
|
|
||||||
return (
|
|
||||||
<Comp
|
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Button.displayName = "Button"
|
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
|
|
@ -1,11 +0,0 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
|
||||||
|
|
||||||
const Collapsible = CollapsiblePrimitive.Root
|
|
||||||
|
|
||||||
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
|
||||||
|
|
||||||
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
|
||||||
|
|
||||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
|
|
@ -1,200 +0,0 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
|
||||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
|
||||||
|
|
||||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
|
||||||
|
|
||||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
|
||||||
|
|
||||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
|
||||||
|
|
||||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
|
||||||
|
|
||||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
|
||||||
|
|
||||||
const DropdownMenuSubTrigger = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
||||||
inset?: boolean
|
|
||||||
}
|
|
||||||
>(({ className, inset, children, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.SubTrigger
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
|
||||||
inset && "pl-8",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<ChevronRight className="ml-auto h-4 w-4" />
|
|
||||||
</DropdownMenuPrimitive.SubTrigger>
|
|
||||||
))
|
|
||||||
DropdownMenuSubTrigger.displayName =
|
|
||||||
DropdownMenuPrimitive.SubTrigger.displayName
|
|
||||||
|
|
||||||
const DropdownMenuSubContent = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.SubContent
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
DropdownMenuSubContent.displayName =
|
|
||||||
DropdownMenuPrimitive.SubContent.displayName
|
|
||||||
|
|
||||||
const DropdownMenuContent = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
||||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.Portal>
|
|
||||||
<DropdownMenuPrimitive.Content
|
|
||||||
ref={ref}
|
|
||||||
sideOffset={sideOffset}
|
|
||||||
className={cn(
|
|
||||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</DropdownMenuPrimitive.Portal>
|
|
||||||
))
|
|
||||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
|
||||||
|
|
||||||
const DropdownMenuItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
||||||
inset?: boolean
|
|
||||||
}
|
|
||||||
>(({ className, inset, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.Item
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
||||||
inset && "pl-8",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
|
||||||
|
|
||||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
||||||
>(({ className, children, checked, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.CheckboxItem
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
checked={checked}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
<Check className="h-4 w-4" />
|
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
</span>
|
|
||||||
{children}
|
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
|
||||||
))
|
|
||||||
DropdownMenuCheckboxItem.displayName =
|
|
||||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
|
||||||
|
|
||||||
const DropdownMenuRadioItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.RadioItem
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
<Circle className="h-2 w-2 fill-current" />
|
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
|
||||||
</span>
|
|
||||||
{children}
|
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
|
||||||
))
|
|
||||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
|
||||||
|
|
||||||
const DropdownMenuLabel = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
||||||
inset?: boolean
|
|
||||||
}
|
|
||||||
>(({ className, inset, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.Label
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"px-2 py-1.5 text-sm font-semibold",
|
|
||||||
inset && "pl-8",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
|
||||||
|
|
||||||
const DropdownMenuSeparator = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<DropdownMenuPrimitive.Separator
|
|
||||||
ref={ref}
|
|
||||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
|
||||||
|
|
||||||
const DropdownMenuShortcut = ({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
|
||||||
|
|
||||||
export {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuCheckboxItem,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuShortcut,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuPortal,
|
|
||||||
DropdownMenuSub,
|
|
||||||
DropdownMenuSubContent,
|
|
||||||
DropdownMenuSubTrigger,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
"use client";
|
|
||||||
import React from "react";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
export default function LampDemo() {
|
|
||||||
return (
|
|
||||||
<LampContainer>
|
|
||||||
<motion.h1
|
|
||||||
initial={{ opacity: 0.5, y: 100 }}
|
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{
|
|
||||||
delay: 0.3,
|
|
||||||
duration: 0.8,
|
|
||||||
ease: "easeInOut",
|
|
||||||
}}
|
|
||||||
className="mt-8 bg-gradient-to-br from-slate-300 to-slate-500 py-4 bg-clip-text text-center text-4xl font-medium tracking-tight text-transparent md:text-7xl"
|
|
||||||
>
|
|
||||||
Build lamps <br /> the right way
|
|
||||||
</motion.h1>
|
|
||||||
</LampContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LampContainer = ({
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-slate-950 w-full rounded-md z-0",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="relative flex w-full flex-1 scale-y-125 items-center justify-center isolate z-0 ">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0.5, width: "15rem" }}
|
|
||||||
whileInView={{ opacity: 1, width: "30rem" }}
|
|
||||||
transition={{
|
|
||||||
delay: 0.3,
|
|
||||||
duration: 0.8,
|
|
||||||
ease: "easeInOut",
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
backgroundImage: `conic-gradient(var(--conic-position), var(--tw-gradient-stops))`,
|
|
||||||
}}
|
|
||||||
className="absolute inset-auto right-1/2 h-56 overflow-visible w-[30rem] bg-gradient-conic from-cyan-500 via-transparent to-transparent text-white [--conic-position:from_70deg_at_center_top]"
|
|
||||||
>
|
|
||||||
<div className="absolute w-[100%] left-0 bg-slate-950 h-40 bottom-0 z-20 [mask-image:linear-gradient(to_top,white,transparent)]" />
|
|
||||||
<div className="absolute w-40 h-[100%] left-0 bg-slate-950 bottom-0 z-20 [mask-image:linear-gradient(to_right,white,transparent)]" />
|
|
||||||
</motion.div>
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0.5, width: "15rem" }}
|
|
||||||
whileInView={{ opacity: 1, width: "30rem" }}
|
|
||||||
transition={{
|
|
||||||
delay: 0.3,
|
|
||||||
duration: 0.8,
|
|
||||||
ease: "easeInOut",
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
backgroundImage: `conic-gradient(var(--conic-position), var(--tw-gradient-stops))`,
|
|
||||||
}}
|
|
||||||
className="absolute inset-auto left-1/2 h-56 w-[30rem] bg-gradient-conic from-transparent via-transparent to-cyan-500 text-white [--conic-position:from_290deg_at_center_top]"
|
|
||||||
>
|
|
||||||
<div className="absolute w-40 h-[100%] right-0 bg-slate-950 bottom-0 z-20 [mask-image:linear-gradient(to_left,white,transparent)]" />
|
|
||||||
<div className="absolute w-[100%] right-0 bg-slate-950 h-40 bottom-0 z-20 [mask-image:linear-gradient(to_top,white,transparent)]" />
|
|
||||||
</motion.div>
|
|
||||||
<div className="absolute top-1/2 h-48 w-full translate-y-12 scale-x-150 bg-slate-950 blur-2xl"></div>
|
|
||||||
<div className="absolute top-1/2 z-50 h-48 w-full bg-transparent opacity-10 backdrop-blur-md"></div>
|
|
||||||
<div className="absolute inset-auto z-50 h-36 w-[28rem] -translate-y-1/2 rounded-full bg-cyan-500 opacity-50 blur-3xl"></div>
|
|
||||||
<motion.div
|
|
||||||
initial={{ width: "8rem" }}
|
|
||||||
whileInView={{ width: "16rem" }}
|
|
||||||
transition={{
|
|
||||||
delay: 0.3,
|
|
||||||
duration: 0.8,
|
|
||||||
ease: "easeInOut",
|
|
||||||
}}
|
|
||||||
className="absolute inset-auto z-30 h-36 w-64 -translate-y-[6rem] rounded-full bg-cyan-400 blur-2xl"
|
|
||||||
></motion.div>
|
|
||||||
<motion.div
|
|
||||||
initial={{ width: "15rem" }}
|
|
||||||
whileInView={{ width: "30rem" }}
|
|
||||||
transition={{
|
|
||||||
delay: 0.3,
|
|
||||||
duration: 0.8,
|
|
||||||
ease: "easeInOut",
|
|
||||||
}}
|
|
||||||
className="absolute inset-auto z-50 h-0.5 w-[30rem] -translate-y-[7rem] bg-cyan-400 "
|
|
||||||
></motion.div>
|
|
||||||
|
|
||||||
<div className="absolute inset-auto z-40 h-44 w-full -translate-y-[12.5rem] bg-slate-950 "></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative z-50 flex -translate-y-80 flex-col items-center px-5">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,121 +0,0 @@
|
||||||
"use client";
|
|
||||||
import React from "react";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import Link from "next/link";
|
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
const transition = {
|
|
||||||
type: "spring",
|
|
||||||
mass: 0.5,
|
|
||||||
damping: 11.5,
|
|
||||||
stiffness: 100,
|
|
||||||
restDelta: 0.001,
|
|
||||||
restSpeed: 0.001,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MenuItem = ({
|
|
||||||
setActive,
|
|
||||||
active,
|
|
||||||
item,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
setActive: (item: string) => void;
|
|
||||||
active: string | null;
|
|
||||||
item: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div onMouseEnter={() => setActive(item)} className="relative ">
|
|
||||||
<motion.p
|
|
||||||
transition={{ duration: 0.3 }}
|
|
||||||
className="cursor-pointer text-black hover:opacity-[0.9] dark:text-white"
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</motion.p>
|
|
||||||
{active !== null && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scale: 0.85, y: 10 }}
|
|
||||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
||||||
transition={transition}
|
|
||||||
>
|
|
||||||
{active === item && (
|
|
||||||
<div className="absolute top-[calc(100%_+_1.2rem)] left-1/2 transform -translate-x-1/2 pt-4">
|
|
||||||
<motion.div
|
|
||||||
transition={transition}
|
|
||||||
layoutId="active" // layoutId ensures smooth animation
|
|
||||||
className="bg-white dark:bg-black backdrop-blur-sm rounded-2xl overflow-hidden border border-black/[0.2] dark:border-white/[0.2] shadow-xl"
|
|
||||||
>
|
|
||||||
<motion.div
|
|
||||||
layout // layout ensures smooth animation
|
|
||||||
className="w-max h-full p-4"
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Menu = ({
|
|
||||||
setActive,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
setActive: (item: string | null) => void;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<nav
|
|
||||||
onMouseLeave={() => setActive(null)} // resets the state
|
|
||||||
className="relative rounded-full border dark:bg-black/20 dark:border-white/[0.2] bg-white/20 shadow-input flex justify-center md:justify-between space-x-4 px-10 py-4 place-items-center backdrop-blur-lg"
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ProductItem = ({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
href,
|
|
||||||
src,
|
|
||||||
}: {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
href: string;
|
|
||||||
src: string;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Link href={href} className="flex space-x-2">
|
|
||||||
<Image
|
|
||||||
src={src}
|
|
||||||
width={140}
|
|
||||||
height={70}
|
|
||||||
alt={title}
|
|
||||||
className="flex-shrink-0 rounded-md shadow-2xl"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<h4 className="text-xl font-bold mb-1 text-black dark:text-white">
|
|
||||||
{title}
|
|
||||||
</h4>
|
|
||||||
<p className="text-neutral-700 text-sm max-w-[10rem] dark:text-neutral-300">
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const HoveredLink = ({ children, ...rest }: any) => {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
{...rest}
|
|
||||||
className="text-neutral-700 dark:text-neutral-200 hover:text-black "
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,117 +0,0 @@
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const Table = React.forwardRef<
|
|
||||||
HTMLTableElement,
|
|
||||||
React.HTMLAttributes<HTMLTableElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<div className="relative w-full overflow-auto">
|
|
||||||
<table
|
|
||||||
ref={ref}
|
|
||||||
className={cn("w-full caption-bottom text-sm", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
Table.displayName = "Table"
|
|
||||||
|
|
||||||
const TableHeader = React.forwardRef<
|
|
||||||
HTMLTableSectionElement,
|
|
||||||
React.HTMLAttributes<HTMLTableSectionElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
|
||||||
))
|
|
||||||
TableHeader.displayName = "TableHeader"
|
|
||||||
|
|
||||||
const TableBody = React.forwardRef<
|
|
||||||
HTMLTableSectionElement,
|
|
||||||
React.HTMLAttributes<HTMLTableSectionElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<tbody
|
|
||||||
ref={ref}
|
|
||||||
className={cn("[&_tr:last-child]:border-0", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableBody.displayName = "TableBody"
|
|
||||||
|
|
||||||
const TableFooter = React.forwardRef<
|
|
||||||
HTMLTableSectionElement,
|
|
||||||
React.HTMLAttributes<HTMLTableSectionElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<tfoot
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableFooter.displayName = "TableFooter"
|
|
||||||
|
|
||||||
const TableRow = React.forwardRef<
|
|
||||||
HTMLTableRowElement,
|
|
||||||
React.HTMLAttributes<HTMLTableRowElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<tr
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableRow.displayName = "TableRow"
|
|
||||||
|
|
||||||
const TableHead = React.forwardRef<
|
|
||||||
HTMLTableCellElement,
|
|
||||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<th
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableHead.displayName = "TableHead"
|
|
||||||
|
|
||||||
const TableCell = React.forwardRef<
|
|
||||||
HTMLTableCellElement,
|
|
||||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<td
|
|
||||||
ref={ref}
|
|
||||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableCell.displayName = "TableCell"
|
|
||||||
|
|
||||||
const TableCaption = React.forwardRef<
|
|
||||||
HTMLTableCaptionElement,
|
|
||||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<caption
|
|
||||||
ref={ref}
|
|
||||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableCaption.displayName = "TableCaption"
|
|
||||||
|
|
||||||
export {
|
|
||||||
Table,
|
|
||||||
TableHeader,
|
|
||||||
TableBody,
|
|
||||||
TableFooter,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
TableCaption,
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as ToastPrimitives from "@radix-ui/react-toast"
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
|
||||||
import { X } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const ToastProvider = ToastPrimitives.Provider
|
|
||||||
|
|
||||||
const ToastViewport = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<ToastPrimitives.Viewport
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
|
||||||
|
|
||||||
const toastVariants = cva(
|
|
||||||
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "border bg-background text-foreground",
|
|
||||||
destructive:
|
|
||||||
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const Toast = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
|
||||||
VariantProps<typeof toastVariants>
|
|
||||||
>(({ className, variant, ...props }, ref) => {
|
|
||||||
return (
|
|
||||||
<ToastPrimitives.Root
|
|
||||||
ref={ref}
|
|
||||||
className={cn(toastVariants({ variant }), className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
Toast.displayName = ToastPrimitives.Root.displayName
|
|
||||||
|
|
||||||
const ToastAction = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ToastPrimitives.Action>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<ToastPrimitives.Action
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
ToastAction.displayName = ToastPrimitives.Action.displayName
|
|
||||||
|
|
||||||
const ToastClose = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ToastPrimitives.Close>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<ToastPrimitives.Close
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
toast-close=""
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</ToastPrimitives.Close>
|
|
||||||
))
|
|
||||||
ToastClose.displayName = ToastPrimitives.Close.displayName
|
|
||||||
|
|
||||||
const ToastTitle = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ToastPrimitives.Title>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<ToastPrimitives.Title
|
|
||||||
ref={ref}
|
|
||||||
className={cn("text-sm font-semibold", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
|
||||||
|
|
||||||
const ToastDescription = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ToastPrimitives.Description>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<ToastPrimitives.Description
|
|
||||||
ref={ref}
|
|
||||||
className={cn("text-sm opacity-90", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
ToastDescription.displayName = ToastPrimitives.Description.displayName
|
|
||||||
|
|
||||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
|
|
||||||
|
|
||||||
type ToastActionElement = React.ReactElement<typeof ToastAction>
|
|
||||||
|
|
||||||
export {
|
|
||||||
type ToastProps,
|
|
||||||
type ToastActionElement,
|
|
||||||
ToastProvider,
|
|
||||||
ToastViewport,
|
|
||||||
Toast,
|
|
||||||
ToastTitle,
|
|
||||||
ToastDescription,
|
|
||||||
ToastClose,
|
|
||||||
ToastAction,
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Toast,
|
|
||||||
ToastClose,
|
|
||||||
ToastDescription,
|
|
||||||
ToastProvider,
|
|
||||||
ToastTitle,
|
|
||||||
ToastViewport,
|
|
||||||
} from "@/components/ui/toast"
|
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
|
||||||
|
|
||||||
export function Toaster() {
|
|
||||||
const { toasts } = useToast()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ToastProvider>
|
|
||||||
{toasts.map(function ({ id, title, description, action, ...props }) {
|
|
||||||
return (
|
|
||||||
<Toast key={id} {...props}>
|
|
||||||
<div className="grid gap-1">
|
|
||||||
{title && <ToastTitle>{title}</ToastTitle>}
|
|
||||||
{description && (
|
|
||||||
<ToastDescription>{description}</ToastDescription>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{action}
|
|
||||||
<ToastClose />
|
|
||||||
</Toast>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
<ToastViewport />
|
|
||||||
</ToastProvider>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
// Inspired by react-hot-toast library
|
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
import type {
|
|
||||||
ToastActionElement,
|
|
||||||
ToastProps,
|
|
||||||
} from "@/components/ui/toast"
|
|
||||||
|
|
||||||
const TOAST_LIMIT = 1
|
|
||||||
const TOAST_REMOVE_DELAY = 1000000
|
|
||||||
|
|
||||||
type ToasterToast = ToastProps & {
|
|
||||||
id: string
|
|
||||||
title?: React.ReactNode
|
|
||||||
description?: React.ReactNode
|
|
||||||
action?: ToastActionElement
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionTypes = {
|
|
||||||
ADD_TOAST: "ADD_TOAST",
|
|
||||||
UPDATE_TOAST: "UPDATE_TOAST",
|
|
||||||
DISMISS_TOAST: "DISMISS_TOAST",
|
|
||||||
REMOVE_TOAST: "REMOVE_TOAST",
|
|
||||||
} as const
|
|
||||||
|
|
||||||
let count = 0
|
|
||||||
|
|
||||||
function genId() {
|
|
||||||
count = (count + 1) % Number.MAX_SAFE_INTEGER
|
|
||||||
return count.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActionType = typeof actionTypes
|
|
||||||
|
|
||||||
type Action =
|
|
||||||
| {
|
|
||||||
type: ActionType["ADD_TOAST"]
|
|
||||||
toast: ToasterToast
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: ActionType["UPDATE_TOAST"]
|
|
||||||
toast: Partial<ToasterToast>
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: ActionType["DISMISS_TOAST"]
|
|
||||||
toastId?: ToasterToast["id"]
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: ActionType["REMOVE_TOAST"]
|
|
||||||
toastId?: ToasterToast["id"]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
toasts: ToasterToast[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
|
||||||
|
|
||||||
const addToRemoveQueue = (toastId: string) => {
|
|
||||||
if (toastTimeouts.has(toastId)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
toastTimeouts.delete(toastId)
|
|
||||||
dispatch({
|
|
||||||
type: "REMOVE_TOAST",
|
|
||||||
toastId: toastId,
|
|
||||||
})
|
|
||||||
}, TOAST_REMOVE_DELAY)
|
|
||||||
|
|
||||||
toastTimeouts.set(toastId, timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const reducer = (state: State, action: Action): State => {
|
|
||||||
switch (action.type) {
|
|
||||||
case "ADD_TOAST":
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
||||||
}
|
|
||||||
|
|
||||||
case "UPDATE_TOAST":
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
toasts: state.toasts.map((t) =>
|
|
||||||
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
case "DISMISS_TOAST": {
|
|
||||||
const { toastId } = action
|
|
||||||
|
|
||||||
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
|
||||||
// but I'll keep it here for simplicity
|
|
||||||
if (toastId) {
|
|
||||||
addToRemoveQueue(toastId)
|
|
||||||
} else {
|
|
||||||
state.toasts.forEach((toast) => {
|
|
||||||
addToRemoveQueue(toast.id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
toasts: state.toasts.map((t) =>
|
|
||||||
t.id === toastId || toastId === undefined
|
|
||||||
? {
|
|
||||||
...t,
|
|
||||||
open: false,
|
|
||||||
}
|
|
||||||
: t
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "REMOVE_TOAST":
|
|
||||||
if (action.toastId === undefined) {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
toasts: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const listeners: Array<(state: State) => void> = []
|
|
||||||
|
|
||||||
let memoryState: State = { toasts: [] }
|
|
||||||
|
|
||||||
function dispatch(action: Action) {
|
|
||||||
memoryState = reducer(memoryState, action)
|
|
||||||
listeners.forEach((listener) => {
|
|
||||||
listener(memoryState)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type Toast = Omit<ToasterToast, "id">
|
|
||||||
|
|
||||||
function toast({ ...props }: Toast) {
|
|
||||||
const id = genId()
|
|
||||||
|
|
||||||
const update = (props: ToasterToast) =>
|
|
||||||
dispatch({
|
|
||||||
type: "UPDATE_TOAST",
|
|
||||||
toast: { ...props, id },
|
|
||||||
})
|
|
||||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: "ADD_TOAST",
|
|
||||||
toast: {
|
|
||||||
...props,
|
|
||||||
id,
|
|
||||||
open: true,
|
|
||||||
onOpenChange: (open) => {
|
|
||||||
if (!open) dismiss()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: id,
|
|
||||||
dismiss,
|
|
||||||
update,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function useToast() {
|
|
||||||
const [state, setState] = React.useState<State>(memoryState)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
listeners.push(setState)
|
|
||||||
return () => {
|
|
||||||
const index = listeners.indexOf(setState)
|
|
||||||
if (index > -1) {
|
|
||||||
listeners.splice(index, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [state])
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
toast,
|
|
||||||
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { useToast, toast }
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { type ClassValue, clsx } from "clsx"
|
|
||||||
import { twMerge } from "tailwind-merge"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs))
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
/** @type {import('next').NextConfig} */
|
|
||||||
const nextConfig = {};
|
|
||||||
|
|
||||||
export default nextConfig;
|
|
|
@ -1,42 +0,0 @@
|
||||||
{
|
|
||||||
"name": "web",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "next dev",
|
|
||||||
"build": "next build",
|
|
||||||
"start": "next start",
|
|
||||||
"lint": "next lint"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-collapsible": "^1.1.0",
|
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
|
||||||
"@radix-ui/react-toast": "^1.2.1",
|
|
||||||
"@types/react-google-recaptcha": "^2.1.9",
|
|
||||||
"@uiw/react-markdown-preview": "^5.1.2",
|
|
||||||
"class-variance-authority": "^0.7.0",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"framer-motion": "^11.3.24",
|
|
||||||
"lucide-react": "^0.426.0",
|
|
||||||
"next": "14.2.5",
|
|
||||||
"next-themes": "^0.3.0",
|
|
||||||
"react": "^18",
|
|
||||||
"react-dom": "^18",
|
|
||||||
"react-element-to-jsx-string": "^15.0.0",
|
|
||||||
"react-google-recaptcha": "^3.1.0",
|
|
||||||
"react-markdown": "^9.0.1",
|
|
||||||
"tailwind-merge": "^2.4.0",
|
|
||||||
"tailwindcss-animate": "^1.0.7"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^20",
|
|
||||||
"@types/react": "^18",
|
|
||||||
"@types/react-dom": "^18",
|
|
||||||
"autoprefixer": "^10.4.20",
|
|
||||||
"postcss": "^8.4.41",
|
|
||||||
"tailwindcss": "^3.4.9",
|
|
||||||
"typescript": "^5"
|
|
||||||
},
|
|
||||||
"packageManager": "^pnpm@6.32.4"
|
|
||||||
}
|
|
3428
web/pnpm-lock.yaml
generated
3428
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -1,8 +0,0 @@
|
||||||
/** @type {import('postcss-load-config').Config} */
|
|
||||||
const config = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
Binary file not shown.
Before Width: | Height: | Size: 68 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.5 KiB |
|
@ -1,160 +0,0 @@
|
||||||
import type { Config } from "tailwindcss";
|
|
||||||
|
|
||||||
const {
|
|
||||||
default: flattenColorPalette,
|
|
||||||
} = require("tailwindcss/lib/util/flattenColorPalette");
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
darkMode: ["class"],
|
|
||||||
content: [
|
|
||||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
||||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
|
||||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
||||||
],
|
|
||||||
prefix: "",
|
|
||||||
theme: {
|
|
||||||
container: {
|
|
||||||
center: true,
|
|
||||||
padding: "2rem",
|
|
||||||
screens: {
|
|
||||||
"2xl": "1400px",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
border: "hsl(var(--border))",
|
|
||||||
input: "hsl(var(--input))",
|
|
||||||
ring: "hsl(var(--ring))",
|
|
||||||
background: "hsl(var(--background))",
|
|
||||||
foreground: "hsl(var(--foreground))",
|
|
||||||
primary: {
|
|
||||||
DEFAULT: "hsl(var(--primary))",
|
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
|
||||||
"50": "#eff6ff",
|
|
||||||
"100": "#dbeafe",
|
|
||||||
"200": "#bfdbfe",
|
|
||||||
"300": "#93c5fd",
|
|
||||||
"400": "#60a5fa",
|
|
||||||
"500": "#3b82f6",
|
|
||||||
"600": "#2563eb",
|
|
||||||
"700": "#1d4ed8",
|
|
||||||
"800": "#1e40af",
|
|
||||||
"900": "#1e3a8a",
|
|
||||||
"950": "#172554",
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
DEFAULT: "hsl(var(--secondary))",
|
|
||||||
foreground: "hsl(var(--secondary-foreground))",
|
|
||||||
},
|
|
||||||
destructive: {
|
|
||||||
DEFAULT: "hsl(var(--destructive))",
|
|
||||||
foreground: "hsl(var(--destructive-foreground))",
|
|
||||||
},
|
|
||||||
muted: {
|
|
||||||
DEFAULT: "hsl(var(--muted))",
|
|
||||||
foreground: "hsl(var(--muted-foreground))",
|
|
||||||
},
|
|
||||||
accent: {
|
|
||||||
DEFAULT: "hsl(var(--accent))",
|
|
||||||
foreground: "hsl(var(--accent-foreground))",
|
|
||||||
},
|
|
||||||
popover: {
|
|
||||||
DEFAULT: "hsl(var(--popover))",
|
|
||||||
foreground: "hsl(var(--popover-foreground))",
|
|
||||||
},
|
|
||||||
card: {
|
|
||||||
DEFAULT: "hsl(var(--card))",
|
|
||||||
foreground: "hsl(var(--card-foreground))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
lg: "var(--radius)",
|
|
||||||
md: "calc(var(--radius) - 2px)",
|
|
||||||
sm: "calc(var(--radius) - 4px)",
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
"accordion-down": {
|
|
||||||
from: { height: "0" },
|
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
|
||||||
},
|
|
||||||
"accordion-up": {
|
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
|
||||||
to: { height: "0" },
|
|
||||||
},
|
|
||||||
aurora: {
|
|
||||||
from: {
|
|
||||||
backgroundPosition: "50% 50%, 50% 50%",
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
backgroundPosition: "350% 50%, 350% 50%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
shimmer: {
|
|
||||||
from: {
|
|
||||||
backgroundPosition: "0 0",
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
backgroundPosition: "-200% 0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
|
||||||
aurora: "aurora 60s linear infinite",
|
|
||||||
shimmer: "shimmer 2s linear infinite",
|
|
||||||
},
|
|
||||||
fontFamily: {
|
|
||||||
'body': [
|
|
||||||
'Inter',
|
|
||||||
'ui-sans-serif',
|
|
||||||
'system-ui',
|
|
||||||
'-apple-system',
|
|
||||||
'system-ui',
|
|
||||||
'Segoe UI',
|
|
||||||
'Roboto',
|
|
||||||
'Helvetica Neue',
|
|
||||||
'Arial',
|
|
||||||
'Noto Sans',
|
|
||||||
'sans-serif',
|
|
||||||
'Apple Color Emoji',
|
|
||||||
'Segoe UI Emoji',
|
|
||||||
'Segoe UI Symbol',
|
|
||||||
'Noto Color Emoji'
|
|
||||||
],
|
|
||||||
'sans': [
|
|
||||||
'Inter',
|
|
||||||
'ui-sans-serif',
|
|
||||||
'system-ui',
|
|
||||||
'-apple-system',
|
|
||||||
'system-ui',
|
|
||||||
'Segoe UI',
|
|
||||||
'Roboto',
|
|
||||||
'Helvetica Neue',
|
|
||||||
'Arial',
|
|
||||||
'Noto Sans',
|
|
||||||
'sans-serif',
|
|
||||||
'Apple Color Emoji',
|
|
||||||
'Segoe UI Emoji',
|
|
||||||
'Segoe UI Symbol',
|
|
||||||
'Noto Color Emoji'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: [require("tailwindcss-animate"), addVariablesForColors],
|
|
||||||
} satisfies Config;
|
|
||||||
|
|
||||||
// This plugin adds each Tailwind color as a global CSS variable, e.g. var(--gray-200).
|
|
||||||
function addVariablesForColors({ addBase, theme }: any) {
|
|
||||||
let allColors = flattenColorPalette(theme("colors"));
|
|
||||||
let newVars = Object.fromEntries(
|
|
||||||
Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
|
|
||||||
);
|
|
||||||
|
|
||||||
addBase({
|
|
||||||
":root": newVars,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config;
|
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"incremental": true,
|
|
||||||
"plugins": [
|
|
||||||
{
|
|
||||||
"name": "next"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue