Merge branch 'login' into types_refactor

This commit is contained in:
anovazzi1 2023-08-16 18:19:36 -03:00
commit e33e236466
39 changed files with 5711 additions and 2253 deletions

File diff suppressed because it is too large Load diff

View file

@ -12,11 +12,13 @@
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.3",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",

View file

@ -46,6 +46,10 @@ export default function App() {
}>
>([]);
const isLoginPage = location.pathname.includes("login");
const isAdminPage = location.pathname.includes("admin");
const isSignUpPage = location.pathname.includes("signup");
// Use effect hook to update alertsList when a new alert is added
useEffect(() => {
// If there is an error alert open with data, add it to the alertsList
@ -133,7 +137,7 @@ export default function App() {
}}
FallbackComponent={CrashErrorComponent}
>
<Header />
{!isLoginPage && !isSignUpPage && <Header />}
<Router />
</ErrorBoundary>
<div></div>

View file

@ -0,0 +1,118 @@
import { useState } from "react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../../components/ui/select";
import { PaginatorComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
export default function PaginatorComponent({
pageSize = 10,
pageIndex = 1,
rowsCount = [10, 20, 30],
totalRowsCount = 0,
paginate,
}: PaginatorComponentType) {
const [size, setPageSize] = useState(pageSize);
const [index, setPageIndex] = useState(pageIndex);
const [maxIndex, setMaxPageIndex] = useState(
Math.ceil(totalRowsCount / pageSize)
);
return (
<>
<div className="flex items-center justify-between px-2">
<div className="flex-1 text-sm text-muted-foreground"></div>
<div className="flex items-center space-x-6 lg:space-x-8">
<div className="flex items-center space-x-2">
<p className="text-sm font-medium">Rows per page</p>
<Select
onValueChange={(pageSize: string) => {
setPageSize(Number(pageSize));
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
paginate(Number(pageSize), index);
}}
>
<SelectTrigger className="w-[100px]">
<SelectValue placeholder="10" />
</SelectTrigger>
<SelectContent>
{rowsCount.map((item, i) => (
<SelectItem key={i} value={item.toString()}>
{item}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
Page {index} of {maxIndex}
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => {
setPageIndex(1);
paginate(size, 1);
}}
>
<span className="sr-only">Go to first page</span>
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
</Button>
<Button
onClick={() => {
if (index <= 1) {
setPageIndex(1);
paginate(size, 1);
} else {
{
setPageIndex(index - 1);
paginate(size, index - 1);
}
}
}}
variant="outline"
className="h-8 w-8 p-0"
>
<span className="sr-only">Go to previous page</span>
<IconComponent name="ChevronLeft" className="h-4 w-4" />
</Button>
<Button
onClick={() => {
if (index >= maxIndex) {
setPageIndex(maxIndex);
paginate(size, maxIndex);
} else {
setPageIndex(index + 1);
paginate(size, index + 1);
}
}}
variant="outline"
className="h-8 w-8 p-0"
>
<span className="sr-only">Go to next page</span>
<IconComponent name="ChevronRight" className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => {
setPageIndex(maxIndex);
paginate(size, maxIndex);
}}
>
<span className="sr-only">Go to last page</span>
<IconComponent name="ChevronsRight" className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</>
);
}

View file

@ -1,4 +1,3 @@
import { createElement } from "react";
import { IconComponentProps } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
@ -7,9 +6,12 @@ export default function IconComponent({
className,
iconColor,
}: IconComponentProps): JSX.Element {
const TargetIcon = createElement(nodeIconsLucide[name], {
className,
style: { color: iconColor },
});
return TargetIcon as JSX.Element;
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
return (
<TargetIcon
strokeWidth={1.5}
className={className}
style={{ color: iconColor }}
/>
);
}

View file

@ -31,24 +31,12 @@ export default function Header(): JSX.Element {
return (
<div className="header-arrangement">
<div className="header-start-display">
{tabId === "" || !tabId ? (
<div className="ml-2">
<a
href="https://www.langflow.org/"
target="_blank"
rel="noreferrer"
className="header-waitlist-link-box"
>
<span className="pr-1 text-2xl">⛓️</span>
<span>Join The Waitlist</span>
</a>
</div>
) : (
<Link to="/">
<span className="ml-4 text-2xl">⛓️</span>
</Link>
)}
<Link to="/">
<span className="ml-4 text-2xl">⛓️</span>
</Link>
<Button variant="outline" className="">
Sign out
</Button>
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
<MenuBar flows={flows} tabId={tabId} />
)}

View file

@ -1,3 +1,4 @@
import * as Form from "@radix-ui/react-form";
import { useEffect, useState } from "react";
import { InputComponentType } from "../../types/components";
import { classNames } from "../../utils/utils";
@ -7,8 +8,12 @@ export default function InputComponent({
value,
onChange,
disabled,
required = false,
isForm = false,
password,
editNode = false,
placeholder = "Type something...",
className,
}: InputComponentType): JSX.Element {
const [pwdVisible, setPwdVisible] = useState(false);
@ -21,20 +26,47 @@ export default function InputComponent({
return (
<div className="relative w-full">
<Input
value={value}
disabled={disabled}
className={classNames(
password && !pwdVisible && value !== "" ? " text-clip password " : "",
editNode ? " input-edit-node " : "",
password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : ""
)}
placeholder={password && editNode ? "Key" : "Type something..."}
onChange={(event) => {
onChange(event.target.value);
}}
/>
{isForm ? (
<Form.Control asChild>
<Input
value={value}
disabled={disabled}
required={required}
className={classNames(
password && !pwdVisible && value !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : "",
password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : "",
className
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => {
onChange(e.target.value);
}}
/>
</Form.Control>
) : (
<Input
value={value}
disabled={disabled}
required={required}
className={classNames(
password && !pwdVisible && value !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : "",
password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : "",
className
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => {
onChange(e.target.value);
}}
/>
)}
{password && (
<button
className={classNames(

View file

@ -27,4 +27,4 @@ const PopoverContent = React.forwardRef<
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };
export { Popover, PopoverContent, PopoverTrigger };

View file

@ -0,0 +1,120 @@
"use client";
import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown } from "lucide-react";
import * as React from "react";
import { cn } from "../../utils/utils";
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover 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",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none 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">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
};

View file

@ -508,3 +508,93 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
"/api/v1/custom_component",
"/api/v1/validate/prompt",
];
export const CONTROL_INPUT_STATE = {
password: "",
cnfPassword: "",
username: "",
};
export const CONTROL_LOGIN_STATE = {
username: "",
password: "",
};
export const tabsCode = [];
export function tabsArray(codes: string[], method: number) {
if (!method) return;
if (method === 0) {
return [
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: codes[0],
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: codes[1],
},
{
name: "Python Code",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[2],
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[3],
},
];
}
return [
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: codes[0],
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: codes[1],
},
{
name: "Python Code",
mode: "python",
language: "py",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
code: codes[2],
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[3],
},
{
name: "Tweaks",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[4],
},
];
}

View file

@ -0,0 +1,77 @@
import { createContext, useEffect, useState } from "react";
import { AuthContextType, userData } from "../types/contexts/auth";
const initialValue: AuthContextType = {
isAuthenticated: false,
accessToken: null,
login: () => {},
logout: () => {},
refreshAccessToken: () => Promise.resolve(),
userData: null,
setUserData: () => {},
};
const AuthContext = createContext<AuthContextType>(initialValue);
export function AuthProvider({ children }): React.ReactElement {
const [accessToken, setAccessToken] = useState<string | null>(null);
const [userData, setUserData] = useState<userData>(null);
useEffect(() => {
const storedAccessToken = localStorage.getItem("access_token");
if (storedAccessToken) {
setAccessToken(storedAccessToken);
}
}, []);
function login(newAccessToken: string, refreshToken: string) {
localStorage.setItem("access_token", newAccessToken);
setAccessToken(newAccessToken);
// Store refreshToken if needed
}
function logout() {
localStorage.removeItem("access_token");
// Clear refreshToken if used
setAccessToken(null);
}
async function refreshAccessToken(refreshToken: string) {
try {
// Call your API to refresh the access token using the refresh token
const response = await fetch("/api/refresh-token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ refreshToken }),
});
if (response.ok) {
const data = await response.json();
login(data.accessToken, refreshToken);
} else {
logout();
}
} catch (error) {
logout();
}
}
return (
// !! to convert string to boolean
<AuthContext.Provider
value={{
isAuthenticated: !!accessToken,
accessToken,
login,
logout,
refreshAccessToken,
setUserData,
userData,
}}
>
{children}
</AuthContext.Provider>
);
}

View file

@ -1,6 +1,5 @@
import axios, { AxiosError, AxiosInstance } from "axios";
import { useContext, useEffect, useRef } from "react";
import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
// Create a new Axios instance
@ -16,32 +15,31 @@ function ApiInterceptor(): null {
const interceptor = api.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url!)) {
return Promise.reject(error);
}
let retryCount = 0;
while (retryCount < 4) {
await sleep(5000); // Sleep for 5 seconds
retryCount++;
try {
const response = await axios.request(error.config!);
return response;
} catch (error) {
if (retryCount === 3) {
setErrorData({
title: "There was an error on web connection, please: ",
list: [
"Refresh the page",
"Use a new flow tab",
"Check if the backend is up",
"Endpoint: " + (error as AxiosError).config?.url,
],
});
return Promise.reject(error);
}
}
}
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
// return Promise.reject(error);
// }
// let retryCount = 0;
// while (retryCount < 4) {
// await sleep(5000); // Sleep for 5 seconds
// retryCount++;
// try {
// const response = await axios.request(error.config);
// return response;
// } catch (error) {
// if (retryCount === 3) {
// setErrorData({
// title: "There was an error on web connection, please: ",
// list: [
// "Refresh the page",
// "Use a new flow tab",
// "Check if the backend is up",
// "Endpoint: " + error.config?.url,
// ],
// });
// return Promise.reject(error);
// }
// }
// }
}
);

View file

@ -0,0 +1,66 @@
import { useState } from "react";
import { Button } from "../../components/ui/button";
import { ConfirmationModalType } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
import BaseModal from "../baseModal";
export default function ConfirmationModal({
title,
titleHeader,
modalContent,
modalContentTitle,
cancelText,
confirmationText,
children,
icon,
data,
index,
onConfirm,
}: ConfirmationModalType) {
const Icon: any = nodeIconsLucide[icon];
const [open, setOpen] = useState(false);
return (
<BaseModal size="x-small" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader}>
<span className="pr-2">{title}</span>
<Icon
name="icon"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
{modalContentTitle != "" && (
<>
<strong>{modalContentTitle}</strong>
<br></br>
</>
)}
<span>{modalContent}</span>
</BaseModal.Content>
<BaseModal.Footer>
<Button
className="ml-3"
onClick={() => {
setOpen(false);
onConfirm(index, data);
}}
>
{confirmationText}
</Button>
<Button
variant="outline"
onClick={() => {
setOpen(false);
}}
>
{cancelText}
</Button>
</BaseModal.Footer>
</BaseModal>
);
}

View file

@ -0,0 +1,241 @@
import * as Form from "@radix-ui/react-form";
import { useEffect, useState } from "react";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
import { UserManagementType } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
import BaseModal from "../baseModal";
export default function UserManagementModal({
title,
titleHeader,
cancelText,
confirmationText,
children,
icon,
data,
index,
onConfirm,
}: UserManagementType) {
const Icon: any = nodeIconsLucide[icon];
const [open, setOpen] = useState(false);
const [password, setPassword] = useState(data?.password ?? "");
const [username, setUserName] = useState(data?.user ?? "");
const [confirmPassword, setConfirmPassword] = useState(data?.password ?? "");
useEffect(() => {
if (!data) {
resetForm();
}
}, [data, open]);
function resetForm() {
setPassword("");
setUserName("");
setConfirmPassword("");
}
return (
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader}>
<span className="pr-2">{title}</span>
<Icon
name="icon"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<Form.Root
onSubmit={(event) => {
if (password !== confirmPassword) {
event.preventDefault();
return;
}
const data = Object.fromEntries(new FormData(event.currentTarget));
resetForm();
onConfirm(index ?? -1, data);
setOpen(false);
event.preventDefault();
}}
>
<div className="grid gap-5">
<Form.Field name="username">
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Username{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
</div>
<Form.Control asChild>
<input
onChange={(input) => {
setUserName(input.target.value);
}}
value={username}
className="primary-input"
required
placeholder="Username"
/>
</Form.Control>
<Form.Message match="valueMissing" className="field-invalid">
Please enter your username
</Form.Message>
</Form.Field>
<div className="flex flex-row">
<div className="mr-3 basis-1/2">
<Form.Field
name="password"
serverInvalid={password != confirmPassword}
>
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Password{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
</div>
<InputComponent
onChange={(input) => {
setPassword(input);
}}
value={password}
password={true}
isForm
className="primary-input"
required
placeholder="Password"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please enter a password
</Form.Message>
{password != confirmPassword && (
<Form.Message className="field-invalid">
Passwords do not match
</Form.Message>
)}
</Form.Field>
</div>
<div className="basis-1/2">
<Form.Field
name="confirmpassword"
serverInvalid={password != confirmPassword}
>
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Confirm password{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
</div>
<InputComponent
onChange={(input) => {
setConfirmPassword(input);
}}
value={confirmPassword}
password={true}
isForm
className="primary-input"
required
placeholder="Confirm your password"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please confirm your password
</Form.Message>
</Form.Field>
</div>
</div>
{/*
<Form.Field name="email">
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Email <span className="font-medium text-destructive">*</span>
</Form.Label>
<Form.Message className="field-invalid" match="valueMissing">
Please enter your email
</Form.Message>
<Form.Message className="field-invalid" match="typeMismatch">
Please provide a valid email
</Form.Message>
</div>
<Form.Control asChild>
<input className="primary-input" type="email" required />
</Form.Control>
</Form.Field> */}
{/*
<Form.Field name="birth">
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Date of birth{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
<Form.Message className="field-invalid" match="valueMissing">
Please enter your date of birth
</Form.Message>
</div>
<Form.Control asChild>
<input
type="date"
className="primary-input"
required
max={new Date().toISOString().split("T")[0]}
/>
</Form.Control>
</Form.Field> */}
</div>
<div className="float-right">
<Form.Submit asChild>
<Button className="mr-3 mt-8">{confirmationText}</Button>
</Form.Submit>
<Button
variant="outline"
onClick={() => {
setOpen(false);
}}
>
{cancelText}
</Button>
</div>
</Form.Root>
</BaseModal.Content>
</BaseModal>
);
}

View file

@ -48,7 +48,15 @@ interface BaseModalProps {
open?: boolean;
setOpen?: (open: boolean) => void;
disable?: boolean;
size?: "smaller" | "small" | "medium" | "large" | "large-h-full";
size?:
| "x-small"
| "smaller"
| "small"
| "medium"
| "large"
| "large-h-full"
| "small-h-full"
| "medium-h-full";
}
function BaseModal({
open,
@ -74,6 +82,10 @@ function BaseModal({
let height: string;
switch (size) {
case "x-small":
minWidth = "min-w-[20vw]";
height = "h-[10vh]";
break;
case "smaller":
minWidth = "min-w-[40vw]";
height = "h-[27vh]";
@ -82,10 +94,16 @@ function BaseModal({
minWidth = "min-w-[40vw]";
height = "h-[40vh]";
break;
case "small-h-full":
minWidth = "min-w-[40vw]";
break;
case "medium":
minWidth = "min-w-[60vw]";
height = "h-[60vh]";
break;
case "medium-h-full":
minWidth = "min-w-[60vw]";
break;
case "large":
minWidth = "min-w-[80vw]";
height = "h-[80vh]";
@ -109,7 +127,9 @@ function BaseModal({
{triggerChild}
</DialogTrigger>
<DialogContent className={minWidth}>
{headerChild}
<div className="truncate-doubleline word-break-break-word">
{headerChild}
</div>
<div className={`mt-2 flex flex-col ${height} w-full `}>
{ContentChild}
</div>

View file

@ -0,0 +1,31 @@
import { useNavigate } from "react-router-dom";
import { Button } from "../../../components/ui/button";
import { Input } from "../../../components/ui/input";
export default function LoginAdminPage() {
const navigate = useNavigate();
function loginAdmin() {
navigate("/admin/");
}
return (
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="flex w-72 flex-col items-center justify-center gap-2">
<span className="mb-4 text-5xl">⛓️</span>
<span className="mb-6 text-2xl font-semibold text-primary">Admin</span>
<Input className="bg-background" placeholder="Email address" />
<Input className="bg-background" placeholder="Password" />
<Button
onClick={() => {
loginAdmin();
}}
variant="default"
className="w-full"
>
Login
</Button>
</div>
</div>
);
}

View file

@ -0,0 +1,401 @@
import _ from "lodash";
import { X } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import PaginatorComponent from "../../components/PaginatorComponent";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "../../components/ui/table";
import ConfirmationModal from "../../modals/ConfirmationModal";
import UserManagementModal from "../../modals/UserManagementModal";
export default function AdminPage() {
const [inputValue, setInputValue] = useState("");
const [size, setPageSize] = useState(10);
const [index, setPageIndex] = useState(1);
const userList = useRef([
{
user: generateRandomString(50),
email: generateRandomString(50) + "@example.com",
password: generateRandomString(50),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
]);
const [filterUserList, setFilterUserList] = useState(userList.current);
function generateRandomString(length) {
const characters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let result = "";
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
result += characters.charAt(randomIndex);
}
return result;
}
function generateRandomDate() {
const start = new Date(2010, 0, 1);
const end = new Date();
const randomTimestamp =
start.getTime() + Math.random() * (end.getTime() - start.getTime());
const randomDate = new Date(randomTimestamp);
const options = { year: "numeric", month: "short", day: "numeric" };
return randomDate.toLocaleDateString("en-US");
}
const [editUser, setEditUser] = useState(-1);
const [editedUser, setEditedUser] = useState("");
const [modalEditOpen, setModalEditOpen] = useState(false);
const [modalDeleteOpen, setModalDeleteOpen] = useState(false);
useEffect(() => {
resetFilter();
}, []);
const handleInputChange = (event, index) => {
const user = _.cloneDeepWith(userList.current);
user[index].password = event.target.value;
userList.current = user;
const userFilter = _.cloneDeepWith(filterUserList);
userFilter[index].password = event.target.value;
setFilterUserList(userFilter);
setEditedUser(event.target.value);
};
function handleChangePagination(pageIndex: number, pageSize: number) {
setPageIndex(pageIndex);
setPageSize(pageSize);
const startIndex = (pageIndex - 1) * pageSize;
const endIndex = startIndex + pageSize;
const newList = userList.current.slice(startIndex, endIndex);
setFilterUserList(newList);
}
function resetFilter() {
setFilterUserList(userList.current);
setPageIndex(1);
setPageSize(10);
const startIndex = (index - 1) * size;
const endIndex = index + size - 1;
const newList = userList.current.slice(startIndex, endIndex);
console.log(userList.current);
setFilterUserList(newList);
}
function handleFilterUsers(input: string) {
setInputValue(input);
if (input === "") {
resetFilter();
} else {
const filteredList = userList.current.filter(
(user) =>
user.user.toLowerCase().includes(input.toLowerCase()) ||
user.email.toLowerCase().includes(input.toLowerCase())
);
setFilterUserList(filteredList);
}
}
function handleDeleteUser(index) {
const user = _.cloneDeepWith(userList.current);
user.splice(index, 1);
userList.current = user;
const userFilter = _.cloneDeepWith(filterUserList);
userFilter.splice(index, 1);
setFilterUserList(userFilter);
resetFilter();
}
function handleEditUser(index, user) {
const newUser = _.cloneDeepWith(userList.current);
newUser[index].password = user.password;
newUser[index].user = user.username;
userList.current = newUser;
resetFilter();
}
function handleNewUser(user) {
const newUser = {
user: user.username,
email: generateRandomString(50) + "@example.com",
password: user.password,
register_date: generateRandomDate(),
};
userList.current.unshift(newUser);
console.log(userList.current);
resetFilter();
}
return (
<>
<div className="main-page-panel">
<div className="m-auto flex h-full flex-row justify-center">
<div className="basis-5/6">
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
<div className="flex items-center justify-between space-y-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">
Welcome back!
</h2>
<p className="text-muted-foreground">
Here&apos;s a list of all users!
</p>
</div>
<div className="flex items-center space-x-2"></div>
</div>
{userList.current.length === 0 && (
<>
<div className="flex items-center justify-between">
<h2>There's no users left :)</h2>
</div>
</>
)}
{userList.current.length > 0 && (
<>
<div className="flex items-center justify-between">
<div className="flex flex-1 items-center space-x-2">
<Input
value={inputValue}
placeholder="Filter users..."
className="h-8 w-[150px] lg:w-[250px]"
onChange={(e) => handleFilterUsers(e.target.value)}
/>
{inputValue.length > 0 && (
<Button
onClick={() => {
resetFilter();
setInputValue("");
}}
variant="ghost"
className="h-8 px-2 lg:px-3"
>
Reset
<X className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<div>
<UserManagementModal
title="New User"
titleHeader={"Add a new user"}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
onConfirm={(index, user) => {
handleNewUser(user);
}}
>
<Button>New User</Button>
</UserManagementModal>
</div>
</div>
<div
className="overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted
custom-scroll"
>
<Table className="table-fixed bg-muted outline-1 ">
<TableHeader>
<TableRow>
<TableHead className="h-10">User</TableHead>
<TableHead className="h-10">Password</TableHead>
<TableHead className="h-10 w-[100px] text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filterUserList.map((user, index) => (
<TableRow key={user.user}>
<TableCell className="truncate py-2 font-medium">
{user.user}
</TableCell>
<TableCell className="truncate py-2">
{user.password}
</TableCell>
<TableCell className="flex w-[100px] py-2 text-right">
<div className="flex">
<UserManagementModal
title="Edit"
titleHeader={`${user.user}`}
cancelText="Cancel"
confirmationText="Edit"
icon={"UserPlus2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleEditUser(index, user);
}}
>
<ShadTooltip content="Edit" side="top">
<IconComponent
name="Pencil"
className="h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</UserManagementModal>
<ConfirmationModal
title="Delete"
titleHeader="Delete User"
modalContentTitle="Attention!"
modalContent="Are you sure you want to delete this user? This action cannot be undone."
cancelText="Cancel"
confirmationText="Delete"
icon={"UserMinus2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDeleteUser(index);
}}
>
<ShadTooltip content="Delete" side="top">
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</ConfirmationModal>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<PaginatorComponent
pageIndex={index}
pageSize={size}
totalRowsCount={filterUserList.length}
paginate={(pageIndex, pageSize) => {
handleChangePagination(pageSize, pageIndex);
}}
></PaginatorComponent>
</>
)}
</div>
</div>
</div>
</div>
</>
);
}

View file

@ -0,0 +1,59 @@
import { useState } from "react";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import BaseModal from "../../modals/baseModal";
export default function DeleteAccountPage() {
const [showConfirmation, setShowConfirmation] = useState(false);
const handleDeleteAccount = () => {
// Implement your account deletion logic here
// For example, make an API call to delete the account
// Upon successful deletion, you can redirect the user to another page
console.log("Account deleted!");
// Implement the logic to redirect the user after account deletion.
// For example, use react-router-dom's useHistory hook.
};
return (
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="flex w-72 flex-col items-center justify-center gap-2">
<span className="mb-4 text-5xl">⛓️</span>
<span className="mb-4 text-center text-2xl font-semibold text-primary">
Delete your account
</span>
<Input className="bg-background" placeholder="Confirm password" />
<BaseModal
open={showConfirmation}
setOpen={setShowConfirmation}
size="x-small"
>
<BaseModal.Header description="This action is irreversible and will permanently erase all your data and information associated with the account. ">
<h3>Are you sure ?</h3>
</BaseModal.Header>
<BaseModal.Trigger>
<Button
variant="default"
className="w-full hover:bg-status-red"
onClick={() => setShowConfirmation(true)}
>
Delete account
</Button>
</BaseModal.Trigger>
<BaseModal.Content>
<div className="flex h-full w-full flex-col justify-end">
<Button
variant="default"
className="w-full hover:bg-status-red"
onClick={() => handleDeleteAccount()}
>
Delete account
</Button>
</div>
</BaseModal.Content>
</BaseModal>
</div>
</div>
);
}

View file

@ -0,0 +1,105 @@
import * as Form from "@radix-ui/react-form";
import { useState } from "react";
import { Link } from "react-router-dom";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { CONTROL_LOGIN_STATE } from "../../constants/constants";
import {
inputHandlerEventType,
loginInputStateType,
} from "../../types/components";
export default function LoginPage(): JSX.Element {
const [inputState, setInputState] =
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
const { password, username } = inputState;
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
return (
<Form.Root
onSubmit={(event) => {
if (password === "") {
event.preventDefault();
return;
}
const data = Object.fromEntries(new FormData(event.currentTarget));
event.preventDefault();
}}
className="h-full w-full"
>
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="flex w-72 flex-col items-center justify-center gap-2">
<span className="mb-4 text-5xl">⛓️</span>
<span className="mb-6 text-2xl font-semibold text-primary">
Sign in to Langflow
</span>
<div className="mb-3 w-full">
<Form.Field name="username">
<Form.Label className="data-[invalid]:label-invalid">
Username <span className="font-medium text-destructive">*</span>
</Form.Label>
<Form.Control asChild>
<Input
onChange={({ target: { value } }) => {
handleInput({ target: { name: "username", value } });
}}
value={username}
className="w-full"
required
placeholder="Username"
/>
</Form.Control>
<Form.Message match="valueMissing" className="field-invalid">
Please enter your username
</Form.Message>
</Form.Field>
</div>
<div className="mb-3 w-full">
<Form.Field name="password">
<Form.Label className="data-[invalid]:label-invalid">
Password <span className="font-medium text-destructive">*</span>
</Form.Label>
<InputComponent
onChange={(value) => {
handleInput({ target: { name: "password", value } });
}}
value={password}
isForm
password={true}
required
placeholder="Password"
className="w-full"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please enter your password
</Form.Message>
</Form.Field>
</div>
<div className="w-full">
<Form.Submit asChild>
<Button className="mr-3 mt-6 w-full">Sign in</Button>
</Form.Submit>
</div>
<div className="w-full">
<Link to="">
<Button className="w-full" variant="outline">
Don't have an account?&nbsp;<b>Sign Up</b>
</Button>
</Link>
</div>
</div>
</div>
</Form.Root>
);
}

View file

@ -0,0 +1,137 @@
import * as Form from "@radix-ui/react-form";
import { FormEvent, useState } from "react";
import { Link } from "react-router-dom";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { CONTROL_INPUT_STATE } from "../../constants/constants";
import {
inputHandlerEventType,
signUpInputStateType,
} from "../../types/components";
export default function SignUp(): JSX.Element {
const [inputState, setInputState] =
useState<signUpInputStateType>(CONTROL_INPUT_STATE);
const { password, cnfPassword, username } = inputState;
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
return (
<Form.Root
onSubmit={(event: FormEvent<HTMLFormElement>) => {
if (password === "") {
event.preventDefault();
return;
}
const data = Object.fromEntries(new FormData(event.currentTarget));
event.preventDefault();
}}
className="h-full w-full"
>
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="flex w-72 flex-col items-center justify-center gap-2">
<span className="mb-4 text-5xl">⛓️</span>
<span className="mb-6 text-2xl font-semibold text-primary">
Sign up to Langflow
</span>
<div className="mb-3 w-full">
<Form.Field name="username">
<Form.Label className="data-[invalid]:label-invalid">
Username <span className="font-medium text-destructive">*</span>
</Form.Label>
<Form.Control asChild>
<Input
onChange={({ target: { value } }) => {
handleInput({ target: { name: "username", value } });
}}
value={username}
className="w-full"
required
placeholder="Username"
/>
</Form.Control>
<Form.Message match="valueMissing" className="field-invalid">
Please enter your username
</Form.Message>
</Form.Field>
</div>
<div className="mb-3 w-full">
<Form.Field name="password" serverInvalid={password != cnfPassword}>
<Form.Label className="data-[invalid]:label-invalid">
Password <span className="font-medium text-destructive">*</span>
</Form.Label>
<InputComponent
onChange={(value) => {
handleInput({ target: { name: "password", value } });
}}
value={password}
isForm
password={true}
required
placeholder="Password"
className="w-full"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please enter a password
</Form.Message>
{password != cnfPassword && (
<Form.Message className="field-invalid">
Passwords do not match
</Form.Message>
)}
</Form.Field>
</div>
<div className="w-full">
<Form.Field
name="confirmpassword"
serverInvalid={password != cnfPassword}
>
<Form.Label className="data-[invalid]:label-invalid">
Confirm your password{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
<InputComponent
onChange={(value) => {
handleInput({ target: { name: "cnfPassword", value } });
}}
value={cnfPassword}
isForm
password={true}
required
placeholder="Confirm your password"
className="w-full"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please confirm your password
</Form.Message>
</Form.Field>
</div>
<div className="w-full">
<Form.Submit asChild>
<Button className="mr-3 mt-6 w-full">Sign up</Button>
</Form.Submit>
</div>
<div className="w-full">
<Link to="/login">
<Button className="w-full" variant="outline">
Already have an account?&nbsp;<b>Sign in</b>
</Button>
</Link>
</div>
</div>
</div>
</Form.Root>
);
}

View file

@ -1,7 +1,11 @@
import { Route, Routes } from "react-router-dom";
import AdminPage from "./pages/AdminPage";
import LoginAdminPage from "./pages/AdminPage/LoginPage";
import CommunityPage from "./pages/CommunityPage";
import FlowPage from "./pages/FlowPage";
import HomePage from "./pages/MainPage";
import DeleteAccountPage from "./pages/deleteAccountPage";
import LoginPage from "./pages/loginPage";
const Router = () => {
return (
@ -12,6 +16,16 @@ const Router = () => {
<Route path="" element={<FlowPage />} />
</Route>
<Route path="*" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
{/* <Route path="/signup" element={<SignUp />} /> */}
<Route path="/login/admin" element={<LoginAdminPage />} />
<Route path="/admin" element={<AdminPage />} />
<Route path="/account">
<Route path="delete" element={<DeleteAccountPage />}></Route>
</Route>
</Routes>
);
};

View file

@ -1025,4 +1025,12 @@
.chat-message-highlight {
@apply rounded-md bg-indigo-100 px-0.5 dark:bg-indigo-900;
}
.field-invalid{
@apply font-medium text-[0.8rem] text-destructive absolute
}
.label-invalid{
@apply text-destructive
}
}

View file

@ -10,9 +10,13 @@ export type InputComponentType = {
disabled?: boolean;
onChange: (value: string) => void;
password: boolean;
required?: boolean;
isForm?: boolean;
editNode?: boolean;
onChangePass?: (value: boolean | boolean) => void;
showPass?: boolean;
placeholder?: string;
className?: string;
};
export type ToggleComponentType = {
enabled: boolean;
@ -167,7 +171,7 @@ export interface IVarHighlightType {
export type IconComponentProps = {
name: string;
className: string;
className?: string;
iconColor?: string;
};
@ -206,6 +210,57 @@ export interface languageMap {
[key: string]: string | undefined;
}
export type signUpInputStateType = {
password: string;
cnfPassword: string;
username: string;
};
export type inputHandlerEventType = {
target: {
value: string;
name: string;
};
};
export type PaginatorComponentType = {
pageSize: number;
pageIndex: number;
rowsCount?: number[];
totalRowsCount: number;
paginate: (pageIndex: number, pageSize: number) => void;
};
export type ConfirmationModalType = {
title: string;
titleHeader: string;
modalContent: string;
modalContentTitle: string;
cancelText: string;
confirmationText: string;
children: ReactElement;
icon: string;
data: any;
index: number;
onConfirm: (index, data) => void;
};
export type UserManagementType = {
title: string;
titleHeader: string;
cancelText: string;
confirmationText: string;
children: ReactElement;
icon: string;
data?: any;
index?: number;
onConfirm: (index, data) => void;
};
export type loginInputStateType = {
username: string;
password: string;
};
export type groupedObjType = {
family: string;
type: string;

View file

@ -0,0 +1,16 @@
export type AuthContextType = {
isAuthenticated: boolean;
accessToken: string | null;
login: (accessToken: string, refreshToken: string) => void;
logout: () => void;
refreshAccessToken: (refreshToken: string) => Promise<void>;
userData: userData | null;
setUserData: (userData: userData | null) => void;
};
export type userData = {
id: string;
name: string;
email: string;
role: string;
};

View file

@ -5,6 +5,8 @@ import {
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
Circle,
Clipboard,
@ -44,6 +46,7 @@ import {
MoonIcon,
MoreHorizontal,
Paperclip,
Pencil,
Plus,
Redo,
Rocket,
@ -57,6 +60,8 @@ import {
Trash2,
Undo,
Upload,
UserMinus2,
UserPlus2,
Users2,
Variable,
Wand2,
@ -65,6 +70,7 @@ import {
XCircle,
Zap,
} from "lucide-react";
import { FaApple, FaGithub } from "react-icons/fa";
import { AirbyteIcon } from "../icons/Airbyte";
import { AnthropicIcon } from "../icons/Anthropic";
import { BingIcon } from "../icons/Bing";
@ -277,4 +283,11 @@ export const nodeIconsLucide: iconsType = {
Upload,
MessageSquare,
MoreHorizontal,
UserMinus2,
UserPlus2,
Pencil,
ChevronsRight,
ChevronsLeft,
FaGithub,
FaApple,
};