Skip to content

Commit

Permalink
Merge pull request #112 from Mintplex-Labs/ui-v2
Browse files Browse the repository at this point in the history
UI v2
timothycarambat authored Jan 18, 2024
2 parents c61d11b + bb46198 commit 2174e93
Showing 146 changed files with 6,557 additions and 4,713 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -6,4 +6,6 @@ node_modules
__pycache__
v-env
.DS_Store
yarn-error.log
yarn-error.log
yarn.lock
frontend/.env.development
2 changes: 1 addition & 1 deletion backend/endpoints/v1/workspaces/index.js
Original file line number Diff line number Diff line change
@@ -305,6 +305,7 @@ function workspaceEndpoints(app) {
documents: "countForEntity",
vectors: "calcVectors",
"cache-size": "calcVectorCache",
dimensions: "calcDimensions",
};

if (!Object.keys(methods).includes(statistic)) {
@@ -314,7 +315,6 @@ function workspaceEndpoints(app) {
return;
}

console.log(workspace);
const value = await WorkspaceDocument[methods[statistic]](
"workspace_id",
workspace.id
24 changes: 24 additions & 0 deletions backend/models/workspaceDocument.js
Original file line number Diff line number Diff line change
@@ -159,6 +159,30 @@ const WorkspaceDocument = {

return totalBytes;
},

calcDimensions: async function (field = "workspace_id", value = null) {
try {
const { OrganizationConnection } = require("./organizationConnection");

const workspace = await prisma.organization_workspaces.findUnique({
where: { id: value },
include: { organization: true },
});

const connector = await OrganizationConnection.get({
organization_id: workspace.organization.id,
});

const vectorDb = selectConnector(connector);
const dimensions = await vectorDb.indexDimensions(workspace.fname);

return dimensions;
} catch (e) {
console.error(e);
return 0;
}
},

// Will get both the remote and local count of vectors to see if the numbers match.
vectorCount: async function (field = "organization_id", value = null) {
try {
6 changes: 6 additions & 0 deletions backend/utils/vectordatabases/providers/chroma/index.js
Original file line number Diff line number Diff line change
@@ -98,6 +98,12 @@ class Chroma {
return { result: totalVectors, error: null };
}

// TODO: Solve this issue
async indexDimensions() {
// Chroma does not support this, defaulting to openai's 1536
return 1536;
}

// Collections === namespaces for Chroma to normalize interfaces
async collections() {
return await this.namespaces();
1 change: 0 additions & 1 deletion backend/utils/vectordatabases/providers/weaviate/index.js
Original file line number Diff line number Diff line change
@@ -66,7 +66,6 @@ class Weaviate {
var totalVectors = 0;
for (const collection of collections) {
if (!collection || !collection.name) continue;
console.log({ dim: await this.indexDimensions(collection.name) });
totalVectors +=
(await this.namespaceWithClient(client, collection.name))
?.vectorCount || 0;
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@
"dependencies": {
"@dqbd/tiktoken": "^1.0.7",
"@metamask/jazzicon": "^2.0.0",
"@phosphor-icons/react": "^2.0.15",
"jsvectormap": "^1.5.1",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
7 changes: 5 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -17,10 +17,13 @@ const OrganizationSettingsView = lazy(
const OrganizationDashboard = lazy(() => import('./pages/Dashboard'));
const WorkspaceDashboard = lazy(() => import('./pages/WorkspaceDashboard'));
const DocumentView = lazy(() => import('./pages/DocumentView'));
const SystemSetup = lazy(() => import('./pages/Authentication/SystemSetup'));
const OnboardingSecuritySetup = lazy(
() => import('./pages/Onboarding/security')
);

// Onboarding v2
const OnboardingFlow = lazy(() => import('./pages/OnboardingFlow'));

const OrganizationJobsView = lazy(() => import('./pages/Jobs'));
const OrganizationToolsView = lazy(() => import('./pages/Tools'));
const SystemSettingsView = lazy(() => import('./pages/SystemSettings'));
@@ -75,6 +78,7 @@ function App() {
element={<PrivateRoute Component={DocumentView} />}
/>

<Route path="/onboarding-setup" element={<OnboardingFlow />} />
<Route
path="/onboarding"
element={<PrivateRoute Component={OnboardingHome} />}
@@ -103,7 +107,6 @@ function App() {

<Route path="/auth/sign-up" element={<SignUp />} />
<Route path="/auth/sign-in" element={<SignIn />} />
<Route path="/system-setup" element={<SystemSetup />} />
<Route
path="/system-settings"
element={<AdminRoute Component={SystemSettingsView} />}
41 changes: 36 additions & 5 deletions frontend/src/components/DocumentPaginator/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CaretDown } from '@phosphor-icons/react';
import { numberWithCommas } from '../../utils/numbers';

function generatePageItems(total: number, current: number) {
@@ -30,17 +31,39 @@ export default function DocumentListPagination({
gotoPage,
}: IPaginationProps) {
const pageItems = generatePageItems(pageCount, currentPage);

const hasPrevious = currentPage > 1;
const hasNext = currentPage < pageCount;

const goToPrevious = () => {
if (currentPage > 1) gotoPage(currentPage - 1);
};

const goToNext = () => {
if (currentPage < pageCount) gotoPage(currentPage + 1);
};

if (pageCount < 2) return <div className="mb-18"></div>;

return (
<div className="my-4 flex justify-center">
<div className="my-4 -mt-8 mb-8 flex justify-center">
{hasPrevious && (
<button
onClick={goToPrevious}
className="rotate-90 px-2 text-white/20 transition-all duration-300 hover:text-sky-400"
>
<CaretDown size={20} weight="bold" />
</button>
)}
<ul className="pagination pagination-sm">
{pageItems.map((item, i) =>
typeof item === 'number' ? (
<button
key={item}
className={`border px-3 py-2 text-sm ${
className={`border px-3 py-2 text-sm transition-all duration-300 hover:border-sky-400 hover:bg-sky-400/20 ${
currentPage === item
? 'border-blue-500 text-blue-500'
: 'border-gray-300 text-gray-500'
? 'border-sky-400 bg-sky-400 bg-opacity-20 text-white'
: 'border-white border-opacity-20 text-white text-opacity-60'
} ${i === 0 ? 'rounded-l-lg' : ''} ${
i === pageItems.length - 1 ? 'rounded-r-lg' : ''
}`}
@@ -51,13 +74,21 @@ export default function DocumentListPagination({
) : (
<button
key={item}
className={`border border-gray-300 px-3 py-2 text-sm text-gray-500`}
className={`border border-white border-opacity-20 px-3 py-2 text-sm text-gray-500`}
>
...
</button>
)
)}
</ul>
{hasNext && (
<button
onClick={goToNext}
className="-rotate-90 px-2 text-white/20 transition-all duration-300 hover:text-sky-400"
>
<CaretDown size={20} weight="bold" />
</button>
)}
</div>
);
}
195 changes: 0 additions & 195 deletions frontend/src/components/Header/Notifications/index.tsx

This file was deleted.

105 changes: 4 additions & 101 deletions frontend/src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import { Link } from 'react-router-dom';
import Logo from '../../images/logo/logo-light.png';
import { CheckCircle, Copy } from 'react-feather';
import { useEffect, useState } from 'react';
import paths from '../../utils/paths';
import { STORE_TOKEN, STORE_USER } from '../../utils/constants';
import truncate from 'truncate';
import Notifications from './Notifications';

export default function Header(props: {
entity?: any | null;
@@ -14,15 +7,11 @@ export default function Header(props: {
sidebarOpen: string | boolean | undefined;
setSidebarOpen: (arg0: boolean) => void;
extendedItems?: any;
quickActions: boolean;
}) {
const [copied, setCopied] = useState(false);
if (!props.entity) return null;
const { entity, property, nameProp, extendedItems = <></> } = props;

const handleCopy = () => {
window.navigator.clipboard.writeText(entity[property]);
setCopied(true);
};
const { extendedItems = <></> } = props;

useEffect(() => {
function manageCopy() {
@@ -35,94 +24,8 @@ export default function Header(props: {
}, [copied]);

return (
<header className="sticky top-0 z-999 flex w-full bg-slate-900 drop-shadow-1 dark:bg-boxdark dark:drop-shadow-none md:bg-white">
<div className="flex flex-grow items-center justify-between px-4 py-4 shadow-2 md:px-6 2xl:px-11">
<div className="flex items-center gap-2 sm:gap-4 lg:hidden">
{/* <!-- Hamburger Toggle BTN --> */}
<button
aria-controls="sidebar"
onClick={(e) => {
e.stopPropagation();
props.setSidebarOpen(!props.sidebarOpen);
}}
className="z-99999 block rounded-sm border border-stroke bg-white p-1.5 shadow-sm dark:border-strokedark dark:bg-boxdark lg:hidden"
>
<span className="relative block h-5.5 w-5.5 cursor-pointer">
<span className="du-block absolute right-0 h-full w-full">
<span
className={`relative left-0 top-0 my-1 block h-0.5 w-0 rounded-sm bg-black delay-[0] duration-200 ease-in-out dark:bg-white ${
!props.sidebarOpen && '!w-full delay-300'
}`}
></span>
<span
className={`relative left-0 top-0 my-1 block h-0.5 w-0 rounded-sm bg-black delay-150 duration-200 ease-in-out dark:bg-white ${
!props.sidebarOpen && 'delay-400 !w-full'
}`}
></span>
<span
className={`relative left-0 top-0 my-1 block h-0.5 w-0 rounded-sm bg-black delay-200 duration-200 ease-in-out dark:bg-white ${
!props.sidebarOpen && '!w-full delay-500'
}`}
></span>
</span>
<span className="absolute right-0 h-full w-full rotate-45">
<span
className={`absolute left-2.5 top-0 block h-full w-0.5 rounded-sm bg-black delay-300 duration-200 ease-in-out dark:bg-white ${
!props.sidebarOpen && '!h-0 !delay-[0]'
}`}
></span>
<span
className={`delay-400 absolute left-0 top-2.5 block h-0.5 w-full rounded-sm bg-black duration-200 ease-in-out dark:bg-white ${
!props.sidebarOpen && '!h-0 !delay-200'
}`}
></span>
</span>
</span>
</button>
{/* <!-- Hamburger Toggle BTN --> */}

<Link className="flex w-full justify-center lg:hidden" to="/">
<img src={Logo} alt="Logo" className="h-14" />
</Link>
</div>

<div className="hidden w-full sm:block">
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-x-4">
<p className="text-4xl font-semibold text-slate-800">
{truncate(entity[nameProp ?? 'name'], 20)}
</p>
<button
onClick={handleCopy}
disabled={copied}
className="transition-duration-300 font-mono flex items-center gap-x-2 rounded-md bg-slate-200 px-4 py-2 text-sm text-slate-700 transition disabled:bg-green-300"
>
<p className="">ID: {entity[property]}</p>
{copied ? (
<CheckCircle className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
)}
</button>
{extendedItems}
</div>
<div className="flex w-fit items-center gap-x-2">
<Notifications />
<button
onClick={() => {
if (!window) return;
window.localStorage.removeItem(STORE_USER);
window.localStorage.removeItem(STORE_TOKEN);
window.location.replace(paths.home());
}}
className="rounded-lg px-4 py-2 text-slate-800 hover:bg-slate-200"
>
Logout
</button>
</div>
</div>
</div>
</div>
<header className="mr-26 flex h-[76px] w-full rounded-t-xl bg-main">
<div className="flex w-full justify-between p-4">{extendedItems}</div>
</header>
);
}
472 changes: 472 additions & 0 deletions frontend/src/components/Modals/NewConnectorModal.tsx

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions frontend/src/components/Modals/SyncConnectorModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useState, memo } from 'react';
import Organization from '../../models/organization';
import { titleCase } from 'title-case';
import paths from '../../utils/paths';

const SyncConnectorModal = memo(
({ organization, connector }: { organization: any; connector: any }) => {
const [synced, setSynced] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<null | string>(null);
const sync = async () => {
setError(null);
setLoading(true);
const { job, error } = await Organization.syncConnector(
organization.slug,
connector.id
);

if (!job) {
setError(error);
setLoading(false);
setSynced(false);
return;
}

setLoading(false);
setSynced(true);
};

return (
<dialog
id="sync-connector-modal"
className="w-1/3 rounded-xl border-2 border-white/20 bg-main shadow"
onClick={(event) =>
event.target == event.currentTarget && event.currentTarget?.close()
}
>
<div className="overflow-y-scroll rounded-sm bg-main p-[20px]">
<div className="px-6.5 py-4">
<h3 className="text-lg font-medium text-white">
Sync Vector Database Connection
</h3>
<p className="mt-4 text-sm text-white/60">
Automatically sync existing information in your{' '}
{titleCase(connector.type)}{' '}
{connector.type === 'chroma' ? 'collections' : 'namespaces'} so
you can manage it more easily. This process can take a long time
to complete depending on how much data you have embedded already.
<br />
<br />
Once you start this process you can check on its progress in the{' '}
<a
href={paths.jobs(organization)}
className="font-semibold text-sky-400 hover:underline"
>
job queue.
</a>
</p>
</div>
<div className="w-full px-6">
{error && (
<p className="my-2 w-full rounded-lg border-red-800 bg-red-50 px-4 py-2 text-sm text-red-800">
{error}
</p>
)}
{synced ? (
<button
type="button"
onClick={() =>
window.location.replace(paths.jobs(organization))
}
className="w-full rounded-lg py-2 text-center text-gray-600 hover:bg-gray-400 hover:text-white"
>
Check progress
</button>
) : (
<button
type="button"
disabled={loading}
onClick={sync}
className="mb-4 h-11 w-full items-center rounded-lg bg-white p-2 text-center text-sm font-bold text-neutral-700 shadow-lg transition-all duration-300 hover:scale-105 hover:bg-opacity-90"
>
{loading ? 'Synchronizing...' : 'Synchronize embeddings'}
</button>
)}
</div>
</div>
</dialog>
);
}
);

export default SyncConnectorModal;
483 changes: 483 additions & 0 deletions frontend/src/components/Modals/UpdateConnectorModal.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect, memo } from 'react';
import Workspace from '../../../../../models/workspace';
import Workspace from '../../../../models/workspace';
import truncate from 'truncate';
import { humanFileSize, milliToHms } from '../../../../../utils/numbers';
import { humanFileSize, milliToHms } from '../../../../utils/numbers';
import { CheckCircle, XCircle } from 'react-feather';
import { Grid } from 'react-loading-icons';

@@ -49,7 +49,7 @@ function FileUploadProgressComponent({
<XCircle className="h-6 h-full w-6 w-full rounded-full bg-red-500 stroke-white p-1" />
</div>
<div className="flex flex-col">
<p className="font-mono overflow-x-scroll text-sm text-black dark:text-stone-200">
<p className="overflow-x-scroll font-mono text-sm text-black dark:text-stone-200">
{truncate(file.name, 30)}
</p>
<p className="font-mono text-xs text-red-700 dark:text-red-400">
@@ -70,7 +70,7 @@ function FileUploadProgressComponent({
)}
</div>
<div className="flex flex-col">
<p className="font-mono overflow-x-scroll text-sm text-black dark:text-stone-200">
<p className="overflow-x-scroll font-mono text-sm text-black dark:text-stone-200">
{truncate(file.name, 30)}
</p>
<p className="font-mono text-xs text-gray-700 dark:text-stone-400">
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { useCallback, useState, useEffect, ReactNode } from 'react';
import { APP_NAME } from '../../../../utils/constants';
import { APP_NAME } from '../../../utils/constants';
import { useDropzone } from 'react-dropzone';
import { v4 } from 'uuid';
import System from '../../../../models/system';
import { Frown } from 'react-feather';
import System from '../../../models/system';
import FileUploadProgress from './FileUploadProgress';
import { useParams } from 'react-router-dom';
import { SmileySad } from '@phosphor-icons/react';

export default function UploadDocumentModal({
workspaces,
workspace,
}: {
workspaces: any;
workspaces?: any[];
workspace?: any;
}) {
const { slug } = useParams();
const [targetWorkspace, setTargetWorkspace] = useState(null);
const [targetWorkspace, setTargetWorkspace] = useState(
workspace ? { ...workspace } : null
);
const [ready, setReady] = useState<boolean | null>(null);
const [files, setFiles] = useState([]);
const [fileTypes, setFileTypes] = useState({});
@@ -60,12 +64,12 @@ export default function UploadDocumentModal({
if (ready === null || !slug) {
return (
<ModalWrapper>
<div className="flex h-[20rem] w-full cursor-wait overflow-x-hidden overflow-y-scroll rounded-lg bg-stone-400 bg-opacity-20 outline-none transition-all duration-300">
<div className="flex h-[20rem] w-full cursor-wait overflow-x-hidden overflow-y-scroll rounded-xl border-2 border-white/20 bg-main shadow transition-all duration-300">
<div className="flex h-full w-full flex-col items-center justify-center gap-y-1">
<p className="text-xs text-slate-400">
Checking document processor is online - please wait.
</p>
<p className="text-xs text-slate-400">
<p className="text-xs text-white/60">
this should only take a few moments.
</p>
</div>
@@ -78,8 +82,8 @@ export default function UploadDocumentModal({
return (
<ModalWrapper>
<div className="flex h-[20rem] w-full overflow-x-hidden overflow-y-scroll rounded-lg bg-red-200 outline-none transition-all duration-300">
<div className="flex h-full w-full flex-col items-center justify-center gap-y-1 px-2 md:px-0">
<Frown className="h-8 w-8 text-red-800" />
<div className="flex h-full w-full flex-col items-center justify-center gap-y-1 px-2 text-red-800 md:px-0">
<SmileySad size={32} />
<p className="text-center text-xs text-red-800">
Document processor is offline.
</p>
@@ -92,7 +96,9 @@ export default function UploadDocumentModal({
);
}

if (ready === true && targetWorkspace === null) {
// When workspace was not given as a prop we already know the workspace to
// target so do not allow a re-selection.
if (ready === true && targetWorkspace === null && !workspace) {
const saveWorkspace = (e: any) => {
e.preventDefault();
const form = new FormData(e.target);
@@ -106,21 +112,21 @@ export default function UploadDocumentModal({

return (
<ModalWrapper>
<div className="flex h-[20rem] w-full overflow-x-hidden overflow-y-scroll rounded-lg bg-stone-400 bg-opacity-20 outline-none transition-all duration-300">
<div className="flex h-[20rem] w-full cursor-wait overflow-x-hidden overflow-y-scroll rounded-xl border border-white/5 bg-main-2 shadow transition-all duration-300">
<div className="flex h-full w-full flex-col items-center justify-center gap-y-1">
<p className="text-sm text-slate-800">
<p className="pb-2 text-sm text-white/60">
Please select the workspace you wish to upload documents to.
</p>
<form onSubmit={saveWorkspace} className="flex flex-col gap-y-1">
<form onSubmit={saveWorkspace} className="flex flex-col gap-y-4">
<select
name="workspaceId"
className="rounded-lg px-4 py-2 outline-none"
className="rounded-lg border border-white/10 bg-main-2 px-2 py-2 text-white/60"
>
{workspaces.map((ws: any) => {
return <option value={ws.id}>{ws.name}</option>;
})}
</select>
<button className="my-2 rounded-lg px-4 py-2 text-blue-800 hover:bg-blue-50">
<button className="w-full rounded-lg bg-white p-2 text-center text-sm font-bold text-neutral-700 shadow-lg transition-all duration-300 hover:scale-105 hover:bg-opacity-90">
Continue &rarr;
</button>
</form>
@@ -135,15 +141,15 @@ export default function UploadDocumentModal({
<div className="flex w-full flex-col gap-y-1">
<div
{...getRootProps()}
className="flex h-[20rem] w-full cursor-pointer overflow-x-hidden overflow-y-scroll rounded-lg bg-stone-400 bg-opacity-20 outline-none transition-all duration-300 hover:bg-opacity-40"
className="flex h-[20rem] w-full cursor-pointer overflow-x-hidden overflow-y-scroll rounded-lg border-2 border-dashed border-white/20 bg-main-2 shadow outline-none transition-all duration-300 hover:bg-white/10"
>
<input {...getInputProps()} />
{files.length === 0 ? (
<div className="flex h-full w-full flex-col items-center justify-center">
<div className="flex flex-col items-center justify-center pb-6 pt-5">
<svg
aria-hidden="true"
className="mb-3 h-10 w-10 text-gray-600 dark:text-slate-300"
className="mb-3 h-10 w-10 text-white/60"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -156,8 +162,8 @@ export default function UploadDocumentModal({
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
></path>
</svg>
<p className="mb-2 text-sm text-gray-600 dark:text-slate-300">
<span className="font-semibold">Click to upload</span> or drag
<p className="mb-2 text-sm text-white/60">
<span className="font-normal">Click to upload</span> or drag
and drop
</p>
<p className="text-xs text-gray-600 dark:text-slate-300"></p>
@@ -178,9 +184,9 @@ export default function UploadDocumentModal({
</div>
)}
</div>
<p className="text-xs text-gray-600 dark:text-stone-400 ">
supported file extensions are{' '}
<code className="font-mono rounded-sm bg-gray-200 px-1 text-xs text-gray-800 dark:bg-stone-800 dark:text-slate-400">
<p className="mt-2 text-xs text-white/60 ">
Supported file extensions are{' '}
<code className="rounded-md bg-white/80 px-1 font-mono text-xs text-main">
{Object.values(fileTypes).flat().join(' ')}
</code>
</p>
@@ -193,16 +199,14 @@ const ModalWrapper = ({ children }: { children: ReactNode }) => {
return (
<dialog
id="upload-document-modal"
className="w-1/2 rounded-lg outline-none"
className="w-1/2 rounded-xl border-2 border-white/20 bg-main shadow"
onClick={(event) => {
event.target == event.currentTarget && event.currentTarget?.close();
}}
>
<div className="my-4 flex w-full flex-col gap-y-1 p-[20px]">
<p className="text-lg font-semibold text-blue-600">
Upload new document
</p>
<p className="text-base text-slate-800">
<div className="flex w-full flex-col gap-y-1 p-[20px]">
<p className="text-lg font-medium text-white">Upload new document</p>
<p className="text-sm text-white/60">
Select a workspace and document you wish to upload and {APP_NAME} will
process, embed and store the data for you automatically.
</p>
77 changes: 77 additions & 0 deletions frontend/src/components/Modals/UploadModalNoKey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { AlertTriangle } from 'react-feather';
import { APP_NAME } from '../../utils/constants';
import System from '../../models/system';
import { ReactNode } from 'react';

export default function UploadModalNoKey() {
const updateSystemSetting = async (e: any) => {
e.preventDefault();
const form = new FormData(e.target);
const open_ai_api_key = form.get('open_ai_api_key');
await System.updateSettings({ open_ai_api_key });
window.location.reload();
};

return (
<ModalWrapper>
<div className="flex flex w-full flex-col items-center gap-y-2 rounded-lg border border-orange-500 bg-transparent px-4 py-2 text-orange-500">
<div className="flex w-full items-center gap-x-2 text-lg">
<AlertTriangle /> You cannot upload and embed documents without an
OpenAI API Key.
</div>
<p>
{APP_NAME} will automatically upload and embed your documents for you,
but for this to happen we must have an OpenAI key set.
</p>
<form onSubmit={updateSystemSetting} className="w-full">
<div className="">
<div className="mb-4.5">
<label className="mb-2.5 block">Your OpenAI API Key</label>
<input
required={true}
type="password"
name="open_ai_api_key"
placeholder="sk-xxxxxxxxxx"
autoComplete="off"
className="w-full rounded border-[1.5px] border-stroke bg-transparent px-5 py-3 font-medium outline-none transition focus:border-primary active:border-primary disabled:cursor-default disabled:bg-whiter dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary"
/>
</div>
<div className="flex flex-col gap-y-2">
<button
type="submit"
className="flex w-full justify-center rounded bg-orange-500 p-3 font-medium text-white"
>
Add OpenAI API Key
</button>
</div>
</div>
<p className="my-2 p-2 text-center text-sm text-orange-500">
This key will only be used for the embedding of documents you upload
via {APP_NAME}.
</p>
</form>
</div>
</ModalWrapper>
);
}

const ModalWrapper = ({ children }: { children: ReactNode }) => {
return (
<dialog
id="upload-document-modal"
className="w-1/2 rounded-xl border-2 border-white/20 bg-main shadow"
onClick={(event) => {
event.target == event.currentTarget && event.currentTarget?.close();
}}
>
<div className="flex w-full flex-col gap-y-1 p-[20px]">
<p className="text-lg font-medium text-white">Upload new document</p>
<p className="text-sm text-white/60">
Select a workspace and document you wish to upload and {APP_NAME} will
process, embed and store the data for you automatically.
</p>
</div>
<div className="my-2 flex w-full p-[20px]">{children}</div>
</dialog>
);
};
391 changes: 391 additions & 0 deletions frontend/src/components/Notifications/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,391 @@
import { ReactNode, useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import Organization from '../../models/organization';
import { databaseTimestampFromNow } from '../../utils/data';
import ChromaLogo from '../../images/vectordbs/chroma.png';
import PineconeLogo from '../../images/vectordbs/pinecone-inverted.png';
import qDrantLogo from '../../images/vectordbs/qdrant.png';
import WeaviateLogo from '../../images/vectordbs/weaviate.png';
import { Bell, Info, Warning, WarningOctagon } from '@phosphor-icons/react';

const POLLING_INTERVAL = 30_000;

export type INotification = {
id: number;
organization_id: number;
seen: boolean;
textContent: string;
symbol?:
| 'info'
| 'warning'
| 'error'
| 'chroma'
| 'pinecone'
| 'weaviate'
| 'qdrant';
link?: string;
target?: '_blank' | 'self';
createdAt: string;
lastUpdatedAt: string;
};

export default function Notifications() {
const { slug } = useParams();
const [loading, setLoading] = useState(true);
const [notifications, setNotifications] = useState<INotification[]>([]);
const [hasUnseen, setHasUnseen] = useState(false);
const [showNotifs, setShowNotifs] = useState(false);
const notificationRef = useRef(null);
const bellButtonRef = useRef(null);

async function handleClick() {
if (!showNotifs) {
!!slug && Organization.markNotificationsSeen(slug);
setShowNotifs(true);
setHasUnseen(false);
} else {
setShowNotifs(false);
}
}
useEffect(() => {
function handleClickOutside(event: any) {
if (
bellButtonRef.current &&
bellButtonRef.current.contains(event.target)
) {
return;
}
if (
notificationRef.current &&
!notificationRef.current.contains(event.target)
) {
setShowNotifs(false);
}
}

document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);

async function fetchNotifications() {
if (!slug) {
setLoading(false);
return;
}

const { notifications: _notifications } = await Organization.notifications(
slug
);
setNotifications(_notifications);
setHasUnseen(_notifications.some((notif) => notif.seen === false));
setLoading(false);
}

useEffect(() => {
if (!slug) return;
fetchNotifications();
setInterval(() => {
fetchNotifications();
}, POLLING_INTERVAL);
}, [slug]);

if (loading) return null;

return (
<div className="relative">
<button
ref={bellButtonRef}
type="button"
onClick={handleClick}
className={`group rounded-lg p-2 hover:bg-main-2 ${
showNotifs && 'bg-main-2'
}`}
>
<div className="relative">
<p
hidden={!hasUnseen}
className="absolute -top-[4px] right-0 h-[12px] w-[12px] rounded-full bg-red-600"
/>
<div className="text-sky-400">
<Bell size={24} weight={showNotifs ? 'fill' : 'bold'} />
</div>
</div>
</button>

<div
hidden={!showNotifs}
ref={notificationRef}
className="absolute right-0 top-12 z-1 max-h-[50vh] w-[20rem] overflow-y-auto rounded-lg border border-neutral-600 bg-main shadow-2xl"
>
<div className="sticky left-0 top-0 z-10 block rounded-t-lg border-b border-neutral-600 bg-main px-5 py-2 text-lg text-white shadow-lg">
Notifications
</div>
<div className="divide-y divide-gray-100">
{notifications.length === 0 ? (
<div className="flex px-4 py-3 hover:bg-main-2">
<div className="w-full pl-3 text-center">
<div className="mb-1.5 text-xs text-white">
No notifications
</div>
</div>
</div>
) : (
<div>
{notifications.map((notification) => (
<Notification
key={notification.id}
notification={notification}
/>
))}
</div>
)}
</div>
</div>
</div>
);
}

function NotificationImage({ notification }: { notification: INotification }) {
switch (notification.symbol) {
case 'info':
return (
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-2 text-white">
<Info size={24} weight="bold" />
</div>
);
case 'warning':
return (
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-yellow-300 bg-opacity-20 p-2 text-yellow-300">
<WarningOctagon size={24} weight="bold" />
</div>
);
case 'error':
return (
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-red-700/20 p-2 text-red-600">
<Warning size={24} weight="bold" />
</div>
);
case 'chroma':
return (
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-0">
<img alt="Chroma Logo" className="rounded-full" src={ChromaLogo} />
</div>
);
case 'pinecone':
return (
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-0">
<img
alt="Pinecone Logo"
className="rounded-full"
src={PineconeLogo}
/>
</div>
);
case 'qdrant':
return (
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-0">
<img alt="qDrant Logo" className="rounded-full" src={qDrantLogo} />
</div>
);
case 'weaviate':
return (
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-0">
<img
alt="Weaviate Logo"
className="rounded-full"
src={WeaviateLogo}
/>
</div>
);
default:
return (
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-2 text-white">
<Info size={24} weight="bold" />
</div>
);
}
}

function NotificationWrapper({
notification,
children,
}: {
notification: INotification;
children: ReactNode;
}) {
if (!!notification.link) {
return (
<a
key={notification.id}
href={notification?.link || '#'}
target={notification?.target || 'self'}
className={`flex px-4 py-3 transition-all duration-300 hover:bg-main-2 ${
!notification.seen
? 'border-l-2 !border-l-sky-400 bg-sky-400/10'
: 'border-l-2 !border-l-transparent'
}`}
>
{children}
</a>
);
}
return (
<div
key={notification.id}
className={`flex px-4 py-3 hover:bg-main-2 ${
!notification.seen
? 'border-l-2 !border-l-sky-400'
: 'border-l-2 !border-l-transparent'
}`}
>
{children}
</div>
);
}

function Notification({ notification }: { notification: INotification }) {
return (
<NotificationWrapper notification={notification}>
<div className="flex flex-shrink-0 items-center justify-center">
<NotificationImage notification={notification} />
</div>
<div className="w-full pl-3">
<div className="mb-1 text-sm font-medium leading-tight text-white">
{notification.textContent}
</div>
<div className="text-sm text-white text-opacity-60">
{databaseTimestampFromNow(notification.createdAt)}
</div>
</div>
</NotificationWrapper>
);
}

// <Notification
// key={'pinecone'}
// notification={{
// id: 1,
// organization_id: 1,
// seen: false,
// textContent: 'Pinecone is now available!',
// symbol: 'pinecone',
// link: 'https://pinecone.io',
// target: '_blank',
// createdAt: '2021-10-12T12:00:00Z',
// lastUpdatedAt: '2021-10-12T12:00:00Z',
// }}
// />
// <Notification
// key={'chroma'}
// notification={{
// id: 2,
// organization_id: 1,
// seen: false,
// textContent: 'Chroma is now available!',
// symbol: 'chroma',
// link: 'https://chroma.ml',
// target: '_blank',
// createdAt: '2021-10-12T12:00:00Z',
// lastUpdatedAt: '2021-10-12T12:00:00Z',
// }}
// />
// <Notification
// key={'qdrant'}
// notification={{
// id: 3,
// organization_id: 1,
// seen: false,
// textContent: 'qDrant is now available!',
// symbol: 'qdrant',
// link: 'https://qdrant.tech',
// target: '_blank',
// createdAt: '2021-10-12T12:00:00Z',
// lastUpdatedAt: '2021-10-12T12:00:00Z',
// }}
// />
// <Notification
// key={'weaviate'}
// notification={{
// id: 4,
// organization_id: 1,
// seen: false,
// textContent: 'Weaviate is now available!',
// symbol: 'weaviate',
// link: 'https://weaviate.com',
// target: '_blank',
// createdAt: '2021-10-12T12:00:00Z',
// lastUpdatedAt: '2021-10-12T12:00:00Z',
// }}
// />
// <Notification
// key={'error'}
// notification={{
// id: 5,
// organization_id: 1,
// seen: true,
// textContent: 'Something went wrong!',
// symbol: 'error',
// link: 'https://weaviate.com',
// target: '_blank',
// createdAt: '2021-10-12T12:00:00Z',
// lastUpdatedAt: '2021-10-12T12:00:00Z',
// }}
// />
// <Notification
// key={'warning'}
// notification={{
// id: 6,
// organization_id: 1,
// seen: true,
// textContent: 'Something went wrong!',
// symbol: 'warning',
// link: 'https://weaviate.com',
// target: '_blank',
// createdAt: '2021-10-12T12:00:00Z',
// lastUpdatedAt: '2021-10-12T12:00:00Z',
// }}
// />
// <Notification
// key={'info'}
// notification={{
// id: 7,
// organization_id: 1,
// seen: false,
// textContent: 'Something went wrong!',
// symbol: 'info',
// link: 'https://weaviate.com',
// target: '_blank',
// createdAt: '2021-10-12T12:00:00Z',
// lastUpdatedAt: '2021-10-12T12:00:00Z',
// }}
// />{' '}
// <Notification
// key={'info'}
// notification={{
// id: 7,
// organization_id: 1,
// seen: true,
// textContent: 'Something went wrong!',
// symbol: 'info',
// link: 'https://weaviate.com',
// target: '_blank',
// createdAt: '2021-10-12T12:00:00Z',
// lastUpdatedAt: '2021-10-12T12:00:00Z',
// }}
// />{' '}
// <Notification
// key={'info'}
// notification={{
// id: 7,
// organization_id: 1,
// seen: false,
// textContent: 'Something went wrong!',
// symbol: 'info',
// link: 'https://weaviate.com',
// target: '_blank',
// createdAt: '2021-10-12T12:00:00Z',
// lastUpdatedAt: '2021-10-12T12:00:00Z',
// }}
// />
4 changes: 2 additions & 2 deletions frontend/src/components/Preloader.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
export default function PreLoader() {
return (
<div className="h-16 w-16 animate-spin rounded-full border-4 border-solid border-primary border-t-transparent"></div>
<div className="h-16 w-16 animate-spin rounded-full border-4 border-solid border-primary border-white border-t-transparent"></div>
);
}

export function FullScreenLoader() {
return (
<div
id="preloader"
className="fixed left-0 top-0 z-999999 flex h-screen w-screen items-center justify-center bg-white"
className="fixed left-0 top-0 z-999999 flex h-screen w-screen items-center justify-center bg-main-bg"
>
<div className="h-16 w-16 animate-spin rounded-full border-4 border-solid border-primary border-t-transparent"></div>
</div>
21 changes: 12 additions & 9 deletions frontend/src/components/Sidebar/CreateOrganizationModal/index.tsx
Original file line number Diff line number Diff line change
@@ -18,10 +18,13 @@ export default function CreateOrganizationModal() {
};

return (
<dialog id="organization-creation-modal" className="w-1/3 rounded-lg">
<div className="w-full overflow-y-scroll rounded-sm bg-white p-[20px]">
<dialog
id="organization-creation-modal"
className="w-1/3 rounded-xl border-2 border-white/20 bg-main shadow"
>
<div className="w-full overflow-y-scroll rounded-sm p-[20px]">
<div className="px-6.5 py-4">
<h3 className="font-medium text-black dark:text-white">
<h3 className="text-lg font-medium text-white">
Create a New Organization
</h3>
</div>
@@ -35,7 +38,7 @@ export default function CreateOrganizationModal() {
<form onSubmit={handleSubmit}>
<div className="px-6.5">
<div className="mb-4.5">
<label className="mb-2.5 block text-black dark:text-white">
<label className="mb-2.5 block text-sm font-medium text-white">
Organization Name
</label>
<input
@@ -44,15 +47,15 @@ export default function CreateOrganizationModal() {
name="name"
placeholder="My Organization"
autoComplete="off"
className="w-full rounded border-[1.5px] border-stroke bg-transparent px-5 py-3 font-medium outline-none transition focus:border-primary active:border-primary disabled:cursor-default disabled:bg-whiter dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary"
className="placeholder-text-white/60 w-full rounded-lg border border-white/10 bg-main-2 px-2.5 py-2 text-sm text-white"
/>
</div>
<div className="flex flex-col gap-y-2">
<button
type="submit"
className="flex w-full justify-center rounded bg-blue-500 p-3 font-medium text-white"
className="w-full rounded-lg bg-white p-2 font-medium text-main shadow-lg transition-all duration-300 hover:scale-105 hover:bg-opacity-90"
>
Create Organization
Create Organization &rarr;
</button>
<button
type="button"
@@ -61,12 +64,12 @@ export default function CreateOrganizationModal() {
.getElementById('organization-creation-modal')
?.close();
}}
className="flex w-full justify-center rounded bg-transparent p-3 font-medium text-slate-500 hover:bg-slate-200"
className="w-full rounded-lg bg-transparent p-2 font-medium text-white transition-all duration-300 hover:bg-red-500/80 hover:bg-opacity-90 hover:text-white"
>
Cancel
</button>
</div>
<p className="my-2 rounded-lg border border-orange-800 bg-orange-100 p-2 text-center text-sm text-orange-800">
<p className="my-2 rounded-lg border border-white/20 bg-main-2 p-2 text-center text-sm text-white">
Once your organization exists you can start workspaces and
documents.
</p>
238 changes: 238 additions & 0 deletions frontend/src/components/Sidebar/OrganizationTab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import { NavLink, useParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
import paths from '../../../utils/paths';
import { CaretDown, Plus, MagnifyingGlass } from '@phosphor-icons/react';
import truncate from 'truncate';
import Organization from '../../../models/organization';
import { debounce } from 'lodash';
import InfiniteScroll from 'react-infinite-scroll-component';
import CreateWorkspaceModal from '../../../pages/Dashboard/WorkspacesList/CreateWorkspaceModal';

type OrganizationTabProps = {
organization: any;
i: number;
workspaces: any;
hasMoreWorkspaces: boolean;
loadMoreWorkspaces?: VoidFunction;
};

const debouncedSearch = debounce(
async (searchTerm, setResults, setIsSearching, slug) => {
if (!slug) return;
setIsSearching(true);

const { workspacesResults = [] } = await Organization.searchWorkspaces(
slug,
1, // Page 1
30, // 30 results per page
searchTerm
);

setResults(workspacesResults);
setIsSearching(false);
},
500
);

export default function OrganizationTab({
organization,
workspaces,
i,
hasMoreWorkspaces,
loadMoreWorkspaces,
}: OrganizationTabProps) {
const { slug } = useParams();
const [isActive, setIsActive] = useState(false);
const [menuOpen, setMenuOpen] = useState(true);
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);

const toggleMenu = () => {
setMenuOpen(!menuOpen);
};

const renderWorkspaceItem = (workspace: any) => (
<WorkspaceItem key={workspace.id} workspace={workspace} slug={slug} />
);

const loadMoreWorkspacesAndScrollToBottom = async () => {
loadMoreWorkspaces?.();
const organizationList = document.getElementById('organization-list');
if (organizationList) {
organizationList.scrollTop = organizationList.scrollHeight;
}
};

useEffect(() => {
if (searchTerm !== '') {
setIsSearching(true);
debouncedSearch(searchTerm, setSearchResults, setIsSearching, slug);
} else {
setSearchResults(workspaces);
setIsSearching(false);
}
}, [searchTerm, slug]);

return (
<li key={i}>
<NavLink
key={organization.id}
reloadDocument={!isActive}
to={paths.organization(organization)}
className={({ isActive: active }) => {
setIsActive(active);
return `group relative flex w-full items-center justify-between rounded-lg border border-transparent bg-main-2 px-4 py-3 font-medium text-white duration-300 ease-in-out hover:border-sky-400 hover:text-white ${
active ? 'border-sky-400 !text-white' : ''
}`;
}}
>
<div className="flex w-full flex-col" onClick={toggleMenu}>
<div className="flex w-full items-center justify-between">
<div className={`${isActive ? 'text-sky-400' : 'text-white/60'}`}>
{truncate(organization.name, 19)}
</div>
<div
className={`transition-all duration-300 ${
isActive && menuOpen ? 'text-sky-400' : 'rotate-180 '
}`}
>
<CaretDown weight="bold" />
</div>
</div>
</div>
</NavLink>
{isActive && (
<div
className={`${
menuOpen
? 'slide-down mb-4 mt-4 transition-all duration-300'
: 'slide-up'
}`}
style={{
animationDuration: '0.15s',
}}
>
<div className="mb-3.5 flex items-center justify-between px-3">
<div className="flex w-full items-center gap-x-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="12"
viewBox="0 0 16 12"
fill="none"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.20098 8.16845H13.601C14.0426 8.16845 14.401 8.52685 14.401 8.96845C14.401 9.41005 14.0426 9.76845 13.601 9.76845H7.20098C6.75938 9.76845 6.40098 9.41005 6.40098 8.96845C6.40098 8.52685 6.75938 8.16845 7.20098 8.16845ZM7.20098 2.56845H13.601C14.0426 2.56845 14.401 2.92685 14.401 3.36845C14.401 3.81005 14.0426 4.16845 13.601 4.16845H7.20098C6.75938 4.16845 6.40098 3.81005 6.40098 3.36845C6.40098 2.92685 6.75938 2.56845 7.20098 2.56845ZM4.80098 4.16845C4.80098 5.05245 5.51698 5.76845 6.40098 5.76845H14.401C15.285 5.76845 16.001 5.05245 16.001 4.16845V2.56845C16.001 1.68445 15.285 0.96845 14.401 0.96845H6.40098C5.51698 0.96845 4.80098 1.68445 4.80098 2.56845H1.60098L1.60098 0.854024H0.000976562V8.16767C0.000976562 9.05167 0.717782 9.76845 1.60178 9.76845H1.77617H4.80098C4.80098 10.6525 5.51698 11.3685 6.40098 11.3685H14.401C15.285 11.3685 16.001 10.6525 16.001 9.76845V8.16845C16.001 7.28445 15.285 6.56845 14.401 6.56845H6.40098C5.51698 6.56845 4.80098 7.28445 4.80098 8.16845H2.39778C1.95778 8.16845 1.60098 7.81158 1.60098 7.37158V4.16845H4.80098Z"
fill="#A8A9AB"
/>
</svg>
<div className="text-xs font-medium uppercase tracking-widest text-white/60">
Workspaces
</div>
</div>
<button
onClick={() => {
document
.getElementById('workspace-creation-modal')
?.showModal();
}}
>
<Plus className="text-sky-400" size={17} weight="bold" />
</button>
</div>
<div className="mx-3 flex items-center rounded-full bg-main-2 p-2">
<MagnifyingGlass
className="mx-1 h-4 w-4 text-white/60"
weight="bold"
/>
<input
type="text"
placeholder="Search"
onChange={(e) => setSearchTerm(e.target.value)}
className="border-none bg-transparent text-sm text-white/60 placeholder-white/60 focus:outline-none"
/>
</div>

{isSearching ? (
<LoadingWorkspaceItem />
) : searchTerm !== '' && searchResults.length > 0 ? (
<div className="mt-2 max-h-[150px] overflow-y-auto">
{searchResults.map((workspace, idx) => (
<WorkspaceItem key={idx} workspace={workspace} slug={slug} />
))}
</div>
) : searchTerm !== '' && searchResults.length === 0 ? (
<div className="mt-2">
<div className="flex w-full items-center justify-center rounded-sm text-xs text-white/60">
<p className="p-1">No results found.</p>
</div>
</div>
) : workspaces.length > 0 ? (
<div className="mt-2">
<InfiniteScroll
dataLength={workspaces.length}
scrollableTarget="organization-list"
height={workspaces.length > 5 ? 150 : workspaces.length * 30}
next={loadMoreWorkspacesAndScrollToBottom}
hasMore={hasMoreWorkspaces}
loader={<LoadingWorkspaceItem />}
>
{workspaces.map(renderWorkspaceItem)}
</InfiniteScroll>
</div>
) : (
<div className="mt-2">
<div className="flex w-48 items-center justify-center rounded-sm text-xs text-white/60">
<p className="p-1">
No workspaces,{' '}
<button
onClick={() => {
document
.getElementById('workspace-creation-modal')
?.showModal();
}}
className="italic underline hover:cursor-pointer"
>
create
</button>
.
</p>
</div>
</div>
)}
<CreateWorkspaceModal organization={organization} />
</div>
)}
</li>
);
}

function WorkspaceItem({ workspace, slug }: any) {
return (
<li className="mx-5 mt-1">
<NavLink
to={paths.workspace(slug, workspace.slug)}
className={({ isActive }) => {
return `text-sm font-normal leading-tight text-sky-400 hover:cursor-pointer hover:text-sky-400 hover:underline ${
isActive ? 'text-sky-400' : 'text-white/60'
}`;
}}
>
{truncate(workspace.name, 23)}
</NavLink>
</li>
);
}

function LoadingWorkspaceItem() {
return (
<div className="mt-2">
<div className="flex w-full animate-pulse items-center justify-center rounded-sm text-xs text-white/60">
<p className="p-1">Loading...</p>
</div>
</div>
);
}
3 changes: 2 additions & 1 deletion frontend/src/components/Sidebar/WorkspaceSearch/index.tsx
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { NavLink, useParams } from 'react-router-dom';
import paths from '../../../utils/paths';
import Organization from '../../../models/organization';
import { debounce } from 'lodash';
import truncate from 'truncate';

interface IWorkspaceItem {
workspace: {
@@ -127,7 +128,7 @@ export function WorkspaceItem({ workspace, slug }: IWorkspaceItem) {
(isActive && '!text-white')
}
>
{workspace.name}
{truncate(workspace.name, 10)}
</NavLink>
</li>
);
334 changes: 70 additions & 264 deletions frontend/src/components/Sidebar/index.tsx

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions frontend/src/components/UserMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useState, useRef, useEffect } from 'react';
import useUser from '../../hooks/useUser';
import paths from '../../utils/paths';
import { STORE_TOKEN, STORE_USER } from '../../utils/constants';

export default function UserMenu() {
const { user } = useUser();
const [showMenu, setShowMenu] = useState(false);
const menuRef = useRef(null);
const buttonRef = useRef();
const handleClose = (event: any) => {
if (
menuRef.current &&
!menuRef.current.contains(event.target) &&
!buttonRef.current.contains(event.target)
) {
setShowMenu(false);
}
};

useEffect(() => {
if (showMenu) {
document.addEventListener('mousedown', handleClose);
}
return () => document.removeEventListener('mousedown', handleClose);
}, [showMenu]);

return (
<div>
<button
ref={buttonRef}
onClick={() => setShowMenu(!showMenu)}
className="flex h-[29px] w-[29px] items-center justify-center rounded-full bg-sky-400 bg-opacity-20 text-sm font-medium text-sky-400"
>
{user?.email?.slice(0, 2).toUpperCase()}
</button>
{showMenu && (
<div
ref={menuRef}
className="items-center-justify-center absolute right-0 top-12 flex w-fit rounded-lg border border-white/20 bg-main p-4"
>
<div className="flex flex-col gap-y-2">
<button
onClick={() => {
if (!window) return;
window.localStorage.removeItem(STORE_USER);
window.localStorage.removeItem(STORE_TOKEN);
window.location.replace(paths.home());
}}
type="button"
className="w-full whitespace-nowrap rounded-md px-4 py-1.5 text-left text-white hover:bg-slate-200/20"
>
Sign out
</button>
</div>
</div>
)}
</div>
);
}
57 changes: 57 additions & 0 deletions frontend/src/components/VectorDBOption/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
type VectorDBOptionProps = {
name: string;
link: string;
description: string;
value: string;
image: string;
checked?: boolean;
onClick: (value: string) => void;
};

export default function VectorDBOption({
name,
link,
description,
value,
image,
checked = false,
onClick,
}: VectorDBOptionProps) {
return (
<div
onClick={() => onClick(value)}
style={{
background: checked
? `linear-gradient(180deg, #313236 0%, rgba(63, 65, 70, 0.00) 100%)`
: `linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0.06) 100%)`,
}}
className={`flex h-full w-60 cursor-pointer flex-col items-start justify-between rounded-2xl border-2 border-transparent px-5 py-4 text-white shadow-md transition-all duration-300 ${
checked ? 'border-white/60' : ''
} hover:border-white/60 hover:shadow-lg md:max-w-sm`}
>
<input
type="checkbox"
value={value}
className="peer hidden"
checked={checked}
readOnly={true}
formNoValidate={true}
/>
<label className="flex h-full w-full cursor-pointer flex-col items-start justify-between">
<div className="flex items-center">
<img src={image} alt={name} className="h-10 w-10 rounded" />
<div className="ml-4 text-sm font-semibold">{name}</div>
</div>
<div className="font-base mt-2 text-xs tracking-wide text-white">
{description}
</div>
<a
href={`https://${link}`}
className="mt-2 text-xs font-medium text-white underline"
>
{link}
</a>
</label>
</div>
);
}
Binary file removed frontend/src/fonts/Satoshi-Black.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Black.ttf
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Black.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Black.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-BlackItalic.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-BlackItalic.ttf
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-BlackItalic.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-BlackItalic.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Bold.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Bold.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Bold.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-BoldItalic.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-BoldItalic.ttf
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-BoldItalic.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-BoldItalic.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Italic.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Italic.ttf
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Italic.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Italic.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Light.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Light.ttf
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Light.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Light.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-LightItalic.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-LightItalic.ttf
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-LightItalic.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-LightItalic.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Medium.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Medium.ttf
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Medium.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Medium.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-MediumItalic.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-MediumItalic.ttf
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-MediumItalic.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-MediumItalic.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Regular.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Regular.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Regular.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Variable.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Variable.ttf
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Variable.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-Variable.woff2
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-VariableItalic.eot
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-VariableItalic.ttf
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-VariableItalic.woff
Binary file not shown.
Binary file removed frontend/src/fonts/Satoshi-VariableItalic.woff2
Binary file not shown.
11 changes: 11 additions & 0 deletions frontend/src/images/logo/logo-sky.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/src/images/undraws/onboarding.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/src/images/undraws/sign-in.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/src/images/vectordbs/chroma.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/src/images/vectordbs/pinecone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/src/images/vectordbs/qdrant.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/src/images/vectordbs/weaviate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 78 additions & 1 deletion frontend/src/index.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;

@font-face {
font-family: 'Satoshi-Bold';
src: url('./fonts/Satoshi-Bold.ttf') format('truetype');
font-weight: 700;
font-display: swap;
font-style: normal;
}

@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-Regular.ttf') format('truetype');
font-weight: 300;
font-display: swap;
font-style: normal;
}

.font-satoshi.font-bold {
font-family: 'Satoshi-Bold', sans-serif;
}

.font-satoshi {
font-family: 'Satoshi', sans-serif;
}

.font-jetbrains {
font-family: 'JetBrains Mono', monospace;
font-weight: 300;
}

.font-jetbrainsbold {
font-family: 'JetBrains Mono', monospace;
font-weight: 700;
}

/* Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
@@ -26,18 +61,60 @@ dialog {
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
display: flex;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
}

dialog[open] {
opacity: 1;
display: flex;
pointer-events: inherit;
}

dialog::backdrop {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(2px);
}

.login-input-gradient {
background: linear-gradient(
180deg,
rgba(61, 65, 71, 0.3) 0%,
rgba(44, 47, 53, 0.3) 100%
) !important;
box-shadow: 0px 4px 30px rgba(0, 0, 0, 0.25);
}

@keyframes slideDown {
from {
max-height: 0;
opacity: 0;
}

to {
max-height: 200px;
opacity: 1;
}
}

.slide-down {
animation: slideDown 0.3s ease-out forwards;
}

@keyframes slideUp {
from {
max-height: 200px;
opacity: 1;
}

to {
max-height: 0;
opacity: 0;
}
}

.slide-up {
animation: slideUp 0.3s ease-out forwards;
}
16 changes: 13 additions & 3 deletions frontend/src/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ReactNode, useState } from 'react';
import Header from '../components/Header';
import Sidebar from '../components/Sidebar';
import Notifications from '../components/Notifications';
import UserMenu from '../components/UserMenu';

interface DefaultLayoutProps {
headerEntity: any;
@@ -13,6 +15,7 @@ interface DefaultLayoutProps {
children: ReactNode;
hasMoreWorkspaces?: boolean;
loadMoreWorkspaces?: VoidFunction;
hasQuickActions?: boolean;
}

const AppLayout = ({
@@ -26,11 +29,12 @@ const AppLayout = ({
children,
hasMoreWorkspaces,
loadMoreWorkspaces,
hasQuickActions = false,
}: DefaultLayoutProps) => {
const [sidebarOpen, setSidebarOpen] = useState(false);

return (
<div className="dark:bg-boxdark-2 dark:text-bodydark">
<div className="bg-main-bg px-4 pt-4">
<div className="flex h-screen overflow-hidden">
<Sidebar
workspaces={workspaces}
@@ -42,7 +46,7 @@ const AppLayout = ({
loadMoreWorkspaces={loadMoreWorkspaces}
/>

<div className="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
<div className="no-scrollbar w-full overflow-x-hidden">
{!!headerEntity && (
<div className="flex w-full items-center">
<Header
@@ -52,11 +56,17 @@ const AppLayout = ({
sidebarOpen={sidebarOpen}
setSidebarOpen={setSidebarOpen}
extendedItems={headerExtendedItems}
quickActions={hasQuickActions}
/>
</div>
)}

<div className="absolute right-0 top-0 mr-9 mt-7 flex items-center gap-x-2">
<Notifications />
<UserMenu />
</div>
<main>
<div className="mx-auto max-w-screen-2xl p-4 md:p-6 2xl:p-10">
<div className="mx-auto overflow-y-auto rounded-tr-xl bg-main pr-6 pt-6">
{children}
</div>
</main>
2 changes: 1 addition & 1 deletion frontend/src/layout/DefaultLayout.tsx
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ interface DefaultLayoutProps {

const DefaultLayout = ({ children }: DefaultLayoutProps) => {
return (
<div className="dark:bg-boxdark-2 dark:text-bodydark">
<div className="z-1 bg-zinc-900">
<div className="flex h-screen overflow-hidden">
<div className="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
<main>
1 change: 0 additions & 1 deletion frontend/src/main.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App';
import './index.css';
import './satoshi.css';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<Router>
3 changes: 1 addition & 2 deletions frontend/src/models/document.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ISearchTypes } from '../pages/DocumentView/FragmentList/SearchView';
import { API_BASE } from '../utils/constants';
import { API_BASE, ISearchTypes } from '../utils/constants';
import { baseHeaders, getAPIUrlString } from '../utils/request';

const Document = {
2 changes: 1 addition & 1 deletion frontend/src/models/organization.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { INotification } from '../components/Header/Notifications';
import { INotification } from '../components/Notifications';
import { API_BASE } from '../utils/constants';
import { baseHeaders, getAPIUrlString } from '../utils/request';

3 changes: 1 addition & 2 deletions frontend/src/models/workspace.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ISearchTypes } from '../pages/WorkspaceDashboard/DocumentsList/SearchView';
import { API_BASE } from '../utils/constants';
import { API_BASE, ISearchTypes } from '../utils/constants';
import { baseHeaders, getAPIUrlString } from '../utils/request';

const Workspace = {
156 changes: 73 additions & 83 deletions frontend/src/pages/Authentication/SignIn.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Link } from 'react-router-dom';
import LogoDark from '../../images/logo/logo-dark.png';
import Logo from '../../images/logo/logo-light.png';
import DefaultLayout from '../../layout/DefaultLayout';
import ManageSvg from '../../images/undraws/manage.svg';
import SignInImg from '../../images/undraws/sign-in.png';
import PreLoader from '../../components/Preloader';
import { useEffect, useState } from 'react';
import { CheckCircle, Key, Mail, XCircle } from 'react-feather';
import { CheckCircle, XCircle } from 'react-feather';
import User from '../../models/user';
import { APP_NAME, STORE_TOKEN, STORE_USER } from '../../utils/constants';
import paths from '../../utils/paths';
@@ -55,7 +53,7 @@ const SignIn = () => {
window.localStorage.setItem(STORE_USER, JSON.stringify(user));
window.localStorage.setItem(STORE_TOKEN, token);
window.location.replace(
user.role === 'root' ? paths.systemSetup() : paths.dashboard()
user.role === 'root' ? paths.onboardingSetup() : paths.dashboard()
);
}
};
@@ -77,35 +75,15 @@ const SignIn = () => {

return (
<DefaultLayout>
<div className="bg-white">
<div className="">
<div className="flex flex-wrap items-center">
<div className="hidden w-full xl:block xl:w-1/2">
<div className="px-26 py-17.5 text-center">
<Link className="mb-5.5 inline-block" to="/">
<img
className="hidden h-[50px] dark:block"
src={Logo}
alt="Logo"
/>
<img
className="h-[50px] dark:hidden"
src={LogoDark}
alt="Logo"
/>
</Link>

<p className="2xl:px-20">
Did you know using {APP_NAME} can save you 75% on embedding
costs?
</p>

<span className="mt-15 inline-block">
<img src={ManageSvg} />
</span>
<div>
<img src={SignInImg} alt="Sign In" />
</div>
</div>

<div className="w-full border-stroke dark:border-strokedark xl:w-1/2 xl:border-l-2">
<div className="w-full border-stroke xl:w-1/2">
<div className="w-full p-4 sm:p-12.5 xl:p-17.5">
{stage !== 'ready' ? (
<ShowStatus
@@ -175,65 +153,77 @@ function ShowStatus({
function LoginForm({ handleSubmit }: { handleSubmit: any }) {
return (
<>
<span className="mb-1.5 block text-2xl font-medium">Sign back in</span>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="mb-2.5 block font-medium text-black dark:text-white">
Email
</label>
<div className="relative">
<input
required={true}
type="email"
name="email"
placeholder="Enter your email"
className="w-full rounded-lg border border-stroke bg-transparent py-4 pl-6 pr-10 outline-none focus:border-primary focus-visible:shadow-none dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary"
/>

<span className="absolute right-4 top-4">
<Mail className="h-[22px] w-[22px] text-gray-500" />
</span>
</div>
<div
style={{
background: `
radial-gradient(circle at center, transparent 40%, black 100%),
linear-gradient(180deg, #85F8FF 0%, #65A6F2 100%)
`,
width: '575px',
filter: 'blur(150px)',
opacity: '0.5',
}}
className="absolute right-0 top-0 z-0 h-full w-full"
/>
<div className="relative z-10 flex flex-col items-center">
<div className="mb-3 flex justify-center gap-x-2 text-center">
<span className="text-2xl font-bold text-white">Log in to</span>
<span className="text-2xl font-bold text-sky-300"> VectorAdmin</span>
</div>
<div className="mb-11 w-[308.65px] text-center">
<span className="mt-3 text-sm text-white text-opacity-90">
Welcome back, please log in to your account.
</span>
</div>
<form onSubmit={handleSubmit} className="z-10">
<div className="mb-3.5">
<div className="">
<input
required={true}
type="email"
name="email"
placeholder="Enter your email"
className="h-11 w-[300px] rounded-lg bg-neutral-800/60 p-2.5 text-white shadow-lg transition-all duration-300 focus:scale-105"
/>
</div>
</div>

<div className="mb-4">
<label className="mb-2.5 block font-medium text-black dark:text-white">
Password
</label>
<div className="relative">
<input
required={true}
type="password"
name="password"
min={8}
placeholder={`Your ${APP_NAME} account password`}
className="w-full rounded-lg border border-stroke bg-transparent py-4 pl-6 pr-10 outline-none focus:border-primary focus-visible:shadow-none dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary"
/>

<span className="absolute right-4 top-4">
<Key className="h-[22px] w-[22px] text-gray-500" />
</span>
<div className="mb-9">
<div className="">
<input
required={true}
type="password"
name="password"
min={8}
placeholder={`Your ${APP_NAME} password`}
className="h-11 w-[300px] rounded-lg bg-neutral-800/60 p-2.5 text-white shadow-lg transition-all duration-300 focus:scale-105"
/>
</div>
</div>
</div>

<div className="mb-5">
<button
type="submit"
className="w-full cursor-pointer rounded-lg border border-primary bg-primary p-4 text-white transition hover:bg-opacity-90"
>
Sign In
</button>
</div>
<div className="mb-5">
<button
type="submit"
className="h-11
w-[300px] items-center rounded-lg bg-white p-2 text-center text-sm font-bold leading-tight text-neutral-700 shadow-lg transition-all duration-300 hover:scale-105 hover:bg-opacity-90"
>
Sign In
</button>
</div>

<div className="mt-6 text-center">
<p>
Don't have a {APP_NAME} account?{' '}
<Link to={paths.signUp()} className="text-primary">
Sign Up
</Link>
</p>
</div>
</form>
<div className="mt-6 text-center text-sm text-white/90">
<p>
Don't have a {APP_NAME} account?{' '}
<Link
to={paths.signUp()}
className="font-semibold transition-all duration-300 hover:underline"
>
Sign Up
</Link>
</p>
</div>
</form>
</div>
</>
);
}
152 changes: 88 additions & 64 deletions frontend/src/pages/Authentication/SignUp.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Link } from 'react-router-dom';
import LogoDark from '../../images/logo/logo-dark.png';
import Logo from '../../images/logo/logo-light.png';
import ManageSvg from '../../images/undraws/manage.svg';
import SignInImg from '../../images/undraws/sign-in.png';
import DefaultLayout from '../../layout/DefaultLayout';
import { useState } from 'react';
import PreLoader from '../../components/Preloader';
import { CheckCircle, Key, Mail, XCircle } from 'react-feather';
import { CheckCircle, XCircle } from 'react-feather';
import User from '../../models/user';
import { APP_NAME, STORE_TOKEN, STORE_USER } from '../../utils/constants';
import paths from '../../utils/paths';
@@ -59,7 +57,30 @@ const SignUp = () => {

return (
<DefaultLayout>
<div className="bg-white">
<div className="">
<div className="flex flex-wrap items-center">
<div className="hidden w-full xl:block xl:w-1/2">
<div>
<img src={SignInImg} alt="Sign In" />
</div>
</div>

<div className="w-full border-stroke xl:w-1/2">
<div className="w-full p-4 sm:p-12.5 xl:p-17.5">
{stage !== 'ready' ? (
<ShowStatus
stage={stage}
results={results}
resetForm={resetStage}
/>
) : (
<LoginForm handleSubmit={handleSubmit} />
)}
</div>
</div>
</div>
</div>
{/* <div className="bg-white">
<div className="flex flex-wrap items-center">
<div className="hidden w-full xl:block xl:w-1/2">
<div className="px-26 py-17.5 text-center">
@@ -99,7 +120,7 @@ const SignUp = () => {
</div>
</div>
</div>
</div>
</div> */}
</DefaultLayout>
);
};
@@ -153,69 +174,72 @@ function ShowStatus({
function LoginForm({ handleSubmit }: { handleSubmit: any }) {
return (
<>
<span className="mb-1.5 block font-medium">New account</span>
<h2 className="mb-9 text-2xl font-bold text-black dark:text-white sm:text-title-xl2">
Sign Up for {APP_NAME}
</h2>

<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="mb-2.5 block font-medium text-black dark:text-white">
Email
</label>
<div className="relative">
<input
required={true}
type="email"
name="email"
placeholder="Enter your email"
className="w-full rounded-lg border border-stroke bg-transparent py-4 pl-6 pr-10 outline-none focus:border-primary focus-visible:shadow-none dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary"
/>

<span className="absolute right-4 top-4">
<Mail className="h-[22px] w-[22px] text-gray-500" />
</span>
</div>
<div
style={{
background: `
radial-gradient(circle at center, transparent 40%, black 100%),
linear-gradient(180deg, #85F8FF 0%, #65A6F2 100%)
`,
width: '575px',
filter: 'blur(150px)',
opacity: '0.5',
}}
className="absolute right-0 top-0 z-0 h-full w-full"
/>
<div className="relative z-10 flex flex-col items-center">
<div className="mb-3 flex justify-center gap-x-2 text-center">
<span className="text-2xl font-bold text-white">Sign Up for</span>
<span className="text-2xl font-bold text-sky-300">{APP_NAME}</span>
</div>
<form onSubmit={handleSubmit} className="z-10">
<div className="mb-3.5">
<div className="">
<input
required={true}
type="email"
name="email"
placeholder="Enter your email"
className="h-11 w-[300px] rounded-lg bg-neutral-800/60 p-2.5 text-white shadow-lg transition-all duration-300 focus:scale-105"
/>
</div>
</div>

<div className="mb-4">
<label className="mb-2.5 block font-medium text-black dark:text-white">
Password
</label>
<div className="relative">
<input
required={true}
type="password"
name="password"
min={8}
placeholder={`Your ${APP_NAME} account password`}
className="w-full rounded-lg border border-stroke bg-transparent py-4 pl-6 pr-10 outline-none focus:border-primary focus-visible:shadow-none dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary"
/>

<span className="absolute right-4 top-4">
<Key className="h-[22px] w-[22px] text-gray-500" />
</span>
<div className="mb-9">
<div className="">
<input
required={true}
type="password"
name="password"
min={8}
placeholder={`Your ${APP_NAME} password`}
className="h-11 w-[300px] rounded-lg bg-neutral-800/60 p-2.5 text-white shadow-lg transition-all duration-300 focus:scale-105"
/>
</div>
</div>
</div>

<div className="mb-5 flex flex-col gap-y-1">
<button
type="submit"
className="w-full cursor-pointer rounded-lg border border-primary bg-primary p-4 text-white transition hover:bg-opacity-90"
>
Create account
</button>
</div>
<div className="mb-5">
<button
type="submit"
className="h-11
w-[300px] items-center rounded-lg bg-white p-2 text-center text-sm font-bold leading-tight text-neutral-700 shadow-lg transition-all duration-300 hover:scale-105 hover:bg-opacity-90"
>
Create account
</button>
</div>

<div className="mt-6 text-center">
<p>
Already have an account?{' '}
<Link to={paths.signIn()} className="text-primary">
Sign in
</Link>
</p>
</div>
</form>
<div className="mt-6 text-center text-sm text-white/90">
<p>
Already have an account?{' '}
<Link
to={paths.signIn()}
className="font-semibold transition-all duration-300 hover:underline"
>
Log In
</Link>
</p>
</div>
</form>
</div>
</>
);
}
228 changes: 0 additions & 228 deletions frontend/src/pages/Authentication/SystemSetup.tsx

This file was deleted.

Loading

0 comments on commit 2174e93

Please sign in to comment.