Merge branch 'login' of personal:logspace-ai/langflow into login
This commit is contained in:
commit
4a7ccda51c
11 changed files with 294 additions and 172 deletions
|
|
@ -6,18 +6,19 @@ from fastapi.security import OAuth2PasswordBearer
|
|||
from fastapi import Depends, HTTPException, status
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from langflow.database.models.token import TokenData
|
||||
from langflow.database.models.user import get_user, User
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.database.models.token import TokenData
|
||||
from langflow.database.models.user import User, get_user_by_username
|
||||
|
||||
|
||||
# TODO: Move to env - Test propose!!!!!
|
||||
# TODO: Move to env - JUST FOR TEST!!!!!
|
||||
SECRET_KEY = "698619adad2d916f1f32d264540976964b3c0d3828e0870a65add5800a8cc6b9"
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 60
|
||||
REFRESH_TOKEN_EXPIRE_MINUTES = 70
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
|
||||
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
|
|
@ -28,18 +29,57 @@ def get_password_hash(password):
|
|||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: timedelta = None): # type: ignore
|
||||
def create_token(data: dict, expires_delta: timedelta):
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
else:
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=15)
|
||||
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
to_encode["exp"] = expire
|
||||
|
||||
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
|
||||
|
||||
def create_user_tokens(username: str) -> dict:
|
||||
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_token(
|
||||
data={"sub": username},
|
||||
expires_delta=access_token_expires,
|
||||
)
|
||||
|
||||
refresh_token_expires = timedelta(minutes=REFRESH_TOKEN_EXPIRE_MINUTES)
|
||||
refresh_token = create_token(
|
||||
data={"sub": username, "type": "rf"},
|
||||
expires_delta=refresh_token_expires,
|
||||
)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"refresh_token": refresh_token,
|
||||
"token_type": "bearer",
|
||||
}
|
||||
|
||||
|
||||
def create_refresh_token(refresh_token: str):
|
||||
try:
|
||||
payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub") # type: ignore
|
||||
token_type: str = payload.get("type") # type: ignore
|
||||
|
||||
if username is None or token_type is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token"
|
||||
)
|
||||
|
||||
return create_user_tokens(username)
|
||||
|
||||
except JWTError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token",
|
||||
) from e
|
||||
|
||||
|
||||
def authenticate_user(db: Session, username: str, password: str):
|
||||
if user := get_user(db, username):
|
||||
if user := get_user_by_username(db, username):
|
||||
return user if verify_password(password, user.password) else False
|
||||
else:
|
||||
return False
|
||||
|
|
@ -47,7 +87,7 @@ def authenticate_user(db: Session, username: str, password: str):
|
|||
|
||||
async def get_current_user(
|
||||
token: Annotated[str, Depends(oauth2_scheme)], db: Session = Depends(get_session)
|
||||
):
|
||||
) -> User:
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
|
|
@ -56,13 +96,15 @@ async def get_current_user(
|
|||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub") # type: ignore
|
||||
if username is None:
|
||||
token_type: str = payload.get("type") # type: ignore
|
||||
|
||||
if username is None or token_type:
|
||||
raise credentials_exception
|
||||
token_data = TokenData(username=username)
|
||||
except JWTError as e:
|
||||
raise credentials_exception from e
|
||||
|
||||
user = get_user(db, token_data.username) # type: ignore
|
||||
user = get_user_by_username(db, token_data.username) # type: ignore
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from pydantic import BaseModel
|
|||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,17 @@ class UserListModel(SQLModel):
|
|||
updated_at: datetime = Field()
|
||||
|
||||
|
||||
def get_user(db: Session, username: str) -> User:
|
||||
class UserPatchModel(SQLModel):
|
||||
username: str = Field()
|
||||
is_disabled: bool = Field()
|
||||
is_superuser: bool = Field()
|
||||
|
||||
|
||||
def get_user_by_username(db: Session, username: str) -> User:
|
||||
db_user = db.query(User).filter(User.username == username).first()
|
||||
return User.from_orm(db_user) if db_user else None # type: ignore
|
||||
|
||||
|
||||
def get_user_by_id(db: Session, id: UUID) -> User:
|
||||
db_user = db.query(User).filter(User.id == id).first()
|
||||
return User.from_orm(db_user) if db_user else None # type: ignore
|
||||
|
|
|
|||
|
|
@ -1,41 +1,39 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.database.models.token import Token
|
||||
from langflow.auth.auth import (
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES,
|
||||
authenticate_user,
|
||||
create_access_token,
|
||||
create_user_tokens,
|
||||
create_refresh_token,
|
||||
)
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.database.models.user import User
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def create_user_token(user: User) -> dict:
|
||||
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.username},
|
||||
expires_delta=access_token_expires,
|
||||
)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
|
||||
@router.post("/token", response_model=Token)
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login_to_get_access_token(
|
||||
form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_session)
|
||||
):
|
||||
if user := authenticate_user(db, form_data.username, form_data.password):
|
||||
return create_user_token(user)
|
||||
return create_user_tokens(user.username)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/refresh")
|
||||
async def refresh_token(token: str):
|
||||
if token:
|
||||
return create_refresh_token(token)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,24 +1,52 @@
|
|||
from typing import List
|
||||
from sqlmodel import Session, select
|
||||
from uuid import UUID
|
||||
from datetime import timezone, datetime
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.auth.auth import get_current_active_user
|
||||
from langflow.database.models.user import UserAddModel, UserListModel, User
|
||||
|
||||
from passlib.context import CryptContext
|
||||
from langflow.auth.auth import get_current_active_user, get_password_hash
|
||||
from langflow.database.models.user import (
|
||||
User,
|
||||
UserAddModel,
|
||||
UserListModel,
|
||||
UserPatchModel,
|
||||
get_user_by_id,
|
||||
get_user_by_username,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["Login"])
|
||||
|
||||
|
||||
def get_password_hash(password):
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
return pwd_context.hash(password)
|
||||
@router.post("/user", response_model=UserListModel)
|
||||
def add_user(
|
||||
user: UserAddModel,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
) -> User:
|
||||
"""
|
||||
Add a new user to the database.
|
||||
"""
|
||||
new_user = User(**user.dict())
|
||||
try:
|
||||
new_user.password = get_password_hash(user.password)
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail="User exists") from e
|
||||
|
||||
return new_user
|
||||
|
||||
|
||||
@router.get("/user", response_model=UserListModel)
|
||||
def read_current_user(current_user: User = Depends(get_current_active_user)):
|
||||
def read_current_user(current_user: User = Depends(get_current_active_user)) -> User:
|
||||
"""
|
||||
Retrieve the current user's data.
|
||||
"""
|
||||
return current_user
|
||||
|
||||
|
||||
|
|
@ -28,52 +56,88 @@ def read_all_users(
|
|||
limit: int = 10,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
):
|
||||
query = select(User)
|
||||
query = query.offset(skip).limit(limit)
|
||||
) -> List[UserListModel]:
|
||||
"""
|
||||
Retrieve a list of users from the database with pagination.
|
||||
"""
|
||||
query = select(User).offset(skip).limit(limit)
|
||||
users = db.execute(query).fetchall()
|
||||
|
||||
return db.execute(query).fetchall()
|
||||
return [UserListModel(**dict(user.User)) for user in users]
|
||||
|
||||
|
||||
@router.post("/user", response_model=User)
|
||||
def add_user(
|
||||
user: UserAddModel,
|
||||
@router.patch("/user/{user_id}", response_model=UserListModel)
|
||||
def update_user(
|
||||
user_id: UUID,
|
||||
user: UserPatchModel,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
):
|
||||
new_user = User(**user.dict())
|
||||
try:
|
||||
new_user.password = get_password_hash(user.password)
|
||||
) -> User:
|
||||
"""
|
||||
Update an existing user's data.
|
||||
"""
|
||||
user_db = get_user_by_username(db, user.username)
|
||||
if user_db and user_db.id != user_id:
|
||||
raise HTTPException(status_code=409, detail="Username already exists")
|
||||
|
||||
db.add(new_user)
|
||||
user_db = get_user_by_id(db, user_id)
|
||||
if not user_db:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
try:
|
||||
user_data = user.dict(exclude_unset=True)
|
||||
for key, value in user_data.items():
|
||||
setattr(user_db, key, value)
|
||||
|
||||
user_db.updated_at = datetime.now(timezone.utc)
|
||||
user_db = db.merge(user_db)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
if db.identity_key(instance=user_db) is not None:
|
||||
db.refresh(user_db)
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="User exists",
|
||||
) from e
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
return new_user
|
||||
return user_db
|
||||
|
||||
|
||||
# TODO: Remove - Just for testing purposes
|
||||
@router.delete("/user/{user_id}")
|
||||
def delete_user(
|
||||
user_id: UUID,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
) -> dict:
|
||||
"""
|
||||
Delete a user from the database.
|
||||
"""
|
||||
user_db = db.query(User).filter(User.id == user_id).first()
|
||||
if not user_db:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
db.delete(user_db)
|
||||
db.commit()
|
||||
|
||||
return {"detail": "User deleted"}
|
||||
|
||||
|
||||
# TODO: REMOVE - Just for testing purposes
|
||||
@router.post("/super_user", response_model=User)
|
||||
def add_super_user_to_testing_purposes(db: Session = Depends(get_session)):
|
||||
def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev(
|
||||
db: Session = Depends(get_session),
|
||||
) -> User:
|
||||
"""
|
||||
Add a superuser for testing purposes.
|
||||
(This should be removed in production)
|
||||
"""
|
||||
new_user = User(username="superuser", password="12345", is_superuser=True)
|
||||
|
||||
try:
|
||||
new_user.password = get_password_hash(new_user.password)
|
||||
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="User exists",
|
||||
) from e
|
||||
raise HTTPException(status_code=400, detail="User exists") from e
|
||||
|
||||
return new_user
|
||||
|
|
|
|||
|
|
@ -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,6 +8,8 @@ export default function InputComponent({
|
|||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
required = false,
|
||||
isForm = false,
|
||||
password,
|
||||
editNode = false,
|
||||
placeholder = "Type something...",
|
||||
|
|
@ -23,21 +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" : "",
|
||||
className
|
||||
)}
|
||||
placeholder={password && editNode ? "Key" : placeholder}
|
||||
onChange={(e) => {
|
||||
onChange(e.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(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Button } from "../../components/ui/button";
|
|||
import { UserManagementType } from "../../types/components";
|
||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
|
||||
export default function UserManagementModal({
|
||||
title,
|
||||
|
|
@ -84,6 +85,7 @@ export default function UserManagementModal({
|
|||
value={username}
|
||||
className="primary-input"
|
||||
required
|
||||
placeholder="Username"
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.Message match="valueMissing" className="field-invalid">
|
||||
|
|
@ -109,16 +111,17 @@ export default function UserManagementModal({
|
|||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={(input) => {
|
||||
setPassword(input.target.value);
|
||||
}}
|
||||
value={password}
|
||||
className="primary-input"
|
||||
required
|
||||
/>
|
||||
</Form.Control>
|
||||
<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>
|
||||
|
|
@ -148,16 +151,17 @@ export default function UserManagementModal({
|
|||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={(input) => {
|
||||
setConfirmPassword(input.target.value);
|
||||
}}
|
||||
value={confirmPassword}
|
||||
className="primary-input"
|
||||
required
|
||||
/>
|
||||
</Form.Control>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -39,20 +39,8 @@ export default function LoginPage(): JSX.Element {
|
|||
<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
|
||||
Sign in to Langflow
|
||||
</span>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
<Button variant="primary" className="w-full py-6">
|
||||
<IconComponent name="FaApple" className="h-6 w-6" />
|
||||
</Button>
|
||||
<Button variant="primary" className="w-full py-6">
|
||||
<IconComponent name="FaGithub" className="h-6 w-6" />
|
||||
</Button>
|
||||
<Button variant="primary" className="w-full py-6">
|
||||
<IconComponent name="GoogleSearchRun" className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground">or</span>
|
||||
<div className="mb-3 w-full">
|
||||
<Form.Field name="username">
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
|
|
@ -77,32 +65,26 @@ export default function LoginPage(): JSX.Element {
|
|||
</Form.Field>
|
||||
</div>
|
||||
<div className="mb-3 w-full">
|
||||
<Form.Field name="password" serverInvalid={password === ""}>
|
||||
<Form.Field name="password">
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Password <span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
|
||||
<Form.Control asChild>
|
||||
<InputComponent
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "password", value } });
|
||||
}}
|
||||
value={password}
|
||||
password={true}
|
||||
placeholder="Password"
|
||||
className="w-full"
|
||||
/>
|
||||
</Form.Control>
|
||||
<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>
|
||||
|
||||
{password === "" && (
|
||||
<Form.Message className="field-invalid">
|
||||
Please enter a valid password
|
||||
</Form.Message>
|
||||
)}
|
||||
</Form.Field>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
|
|
@ -111,7 +93,7 @@ export default function LoginPage(): JSX.Element {
|
|||
</Form.Submit>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Link to="/signup">
|
||||
<Link to="">
|
||||
<Button className="w-full" variant="outline">
|
||||
Don't have an account? <b>Sign Up</b>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -39,20 +39,8 @@ export default function SignUp(): JSX.Element {
|
|||
<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
|
||||
Sign up to Langflow
|
||||
</span>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
<Button variant="primary" className="w-full py-6">
|
||||
<IconComponent name="FaApple" className="h-6 w-6" />
|
||||
</Button>
|
||||
<Button variant="primary" className="w-full py-6">
|
||||
<IconComponent name="FaGithub" className="h-6 w-6" />
|
||||
</Button>
|
||||
<Button variant="primary" className="w-full py-6">
|
||||
<IconComponent name="GoogleSearchRun" className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground">or</span>
|
||||
<div className="mb-3 w-full">
|
||||
<Form.Field name="username">
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
|
|
@ -81,18 +69,17 @@ export default function SignUp(): JSX.Element {
|
|||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Password <span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
|
||||
<Form.Control asChild>
|
||||
<InputComponent
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "password", value } });
|
||||
}}
|
||||
value={password}
|
||||
password={true}
|
||||
placeholder="Password"
|
||||
className="w-full"
|
||||
/>
|
||||
</Form.Control>
|
||||
<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
|
||||
|
|
@ -115,15 +102,17 @@ export default function SignUp(): JSX.Element {
|
|||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
|
||||
<Form.Control asChild>
|
||||
<InputComponent
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "cnfPassword", value } });
|
||||
}}
|
||||
value={cnfPassword}
|
||||
password={true}
|
||||
/>
|
||||
</Form.Control>
|
||||
<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
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const Router = () => {
|
|||
<Route path="*" element={<HomePage />} />
|
||||
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/signup" element={<SignUp />} />
|
||||
{/* <Route path="/signup" element={<SignUp />} /> */}
|
||||
<Route path="/login/admin" element={<LoginAdminPage />} />
|
||||
|
||||
<Route path="/admin" element={<AdminPage />} />
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ export type InputComponentType = {
|
|||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
password: boolean;
|
||||
required?: boolean;
|
||||
isForm?: boolean;
|
||||
editNode?: boolean;
|
||||
onChangePass?: (value: boolean | boolean) => void;
|
||||
showPass?: boolean;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue