Major addition. User register, login and update is now functional on frontend, removed UUID5 for UUID4 for users and many more changes

This commit is contained in:
Thastertyn 2025-03-16 22:24:54 +01:00
parent d87c280109
commit d0690f2f06
67 changed files with 3389 additions and 1066 deletions

2
.gitignore vendored
View File

@ -293,3 +293,5 @@ cython_debug/
#.idea/
!frontend/src/lib
frontend/openapi.json

View File

@ -8,6 +8,7 @@
// #endregion
// #region Frontend settings
"prettier.configPath": "./frontend/.prettierrc" // Prettier config override
"prettier.configPath": "./frontend/.prettierrc", // Prettier config override
"biome.lspBin": "${workspaceFolder}/frontend"
// #endregion
}

View File

@ -4,7 +4,6 @@ from app.api.routes import cart_routes, login_routes, shop, user_routes, utils_r
api_router = APIRouter()
api_router.include_router(cart_routes.router)
api_router.include_router(user_routes.router)
api_router.include_router(utils_routes.router)
api_router.include_router(login_routes.router)

View File

@ -1,18 +1,18 @@
from fastapi import APIRouter, HTTPException, status
from fastapi import APIRouter
from app.api.dependencies import CurrentOwnerUser, SessionDep
from app.crud.shop_crud import create_shop
from app.schemas.shop_schemas import ShopCreate
from app.schemas.user_schemas import Token
from app.crud.shop_crud import create_shop
router = APIRouter(prefix="/shop", tags=["Dashboard"])
@router.post("", response_model=bool, status_code=201)
@router.post("", status_code=201)
def register_new_shop(
session: SessionDep,
current_user: CurrentOwnerUser,
shop_data: ShopCreate
) -> Token:
) -> bool:
create_shop(session, shop_data, current_user)
return True

View File

@ -7,7 +7,7 @@ from sqlalchemy.exc import IntegrityError
from app.api.dependencies import CurrentOwnerUser, SessionDep
from app.crud import user_crud
from app.database.models.user_model import UserRole
from app.schemas.user_schemas import UserRegister, UserUpdate
from app.schemas.user_schemas import UserPublic, UserRegister, UserUpdate
logger = logging.getLogger(__name__)
@ -21,8 +21,8 @@ router = APIRouter(
@router.get("", summary="Get information about currently logged in user")
async def get_user(session: SessionDep, current_user: CurrentOwnerUser):
pass
async def get_user(current_user: CurrentOwnerUser) -> UserPublic:
return current_user
@router.post("", summary="Register new user", status_code=status.HTTP_201_CREATED, response_model=bool)
@ -40,11 +40,8 @@ async def register(session: SessionDep, user_data: UserRegister):
column_name = "email"
detail = f"{field_mapping.get(column_name, column_name or 'Entry').capitalize()} already in use"
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=detail
)
logger.warning("%s already exists in the database", column_name.capitalize)
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=detail)
except Exception as e:
if isinstance(e, HTTPException):
raise
@ -53,10 +50,11 @@ async def register(session: SessionDep, user_data: UserRegister):
@router.delete("", summary="Delete user")
async def delete_user():
async def delete_user() -> bool:
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="delete_user() not implemented")
@router.put("", summary="Update user details")
async def update_user(data: UserUpdate):
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="update_user() not implemented")
async def update_user(session: SessionDep, data: UserUpdate, current_user: CurrentOwnerUser) -> bool:
user_crud.update_user(session, data, current_user)
return True

View File

@ -1,5 +1,6 @@
import logging
from typing import Optional
from uuid import UUID, uuid4
from uuid import uuid4
from sqlmodel import Session, select
@ -7,12 +8,19 @@ from app.database.models.shop_model import Shop, ShopStatus
from app.database.models.user_model import User
from app.schemas.shop_schemas import ShopCreate
logger = logging.getLogger(__name__)
def get_shop_id_from_uuid(session: Session, shop_id: int) -> Optional[UUID]:
def get_shop_by_id(session: Session, shop_id: int) -> Optional[Shop]:
logger.debug("Getting shop by shop_id - %d", id)
stmt = select(Shop).where(Shop.id == shop_id)
db_shop = session.exec(stmt).one_or_none()
return db_shop
def get_shop_by_uuid(session: Session, shop_uuid: str) -> Optional[Shop]:
logger.debug("Getting shop by UUID - %s" , shop_uuid)
stmt = select(Shop).where(Shop.uuid == shop_uuid)
db_shop = session.exec(stmt).one_or_none()
return db_shop
def create_shop(session: Session, shop_data: ShopCreate, creator: User) -> None:
shop_uuid = uuid4()

View File

@ -1,37 +1,32 @@
import logging
from typing import Optional
from uuid import UUID
from uuid import UUID, uuid4
from sqlmodel import Session, select
from fastapi import HTTPException, status
from sqlmodel import Session, select, and_
from app.core.security import get_password_hash, verify_password
from app.crud.shop_crud import get_shop_id_from_uuid
from app.crud.shop_crud import get_shop_by_uuid
from app.database.models.user_model import User, UserRole
from app.schemas.user_schemas import UserRegister
from app.schemas.user_schemas import UserRegister, UserUpdate
from app.utils.models import generate_user_uuid5
logger = logging.getLogger(__name__)
def get_user_by_generated_uuid(session: Session, email: str, shop_uuid: Optional[UUID]) -> Optional[User]:
logger.debug("Getting shop id by UUID - %s", shop_uuid)
shop_id = get_shop_id_from_uuid(session, shop_uuid)
logger.debug("Generating user UUID5")
user_uuid = generate_user_uuid5(email, shop_id)
stmt = select(User).where(User.uuid == user_uuid)
logger.debug("Executing select query")
db_user = session.exec(stmt).one_or_none()
return db_user
def create_user(session: Session, user_register: UserRegister, shop_uuid: Optional[UUID], user_role: UserRole):
logger.debug("Getting shop id by UUID - %s", shop_uuid)
shop_id = get_shop_id_from_uuid(session, shop_uuid)
logger.debug("Generating user UUID5")
user_uuid = generate_user_uuid5(user_register.email, shop_id)
if shop_uuid:
logger.debug("Fetching shop by UUID")
shop_id = get_shop_by_uuid(session, shop_uuid).id
else:
logger.debug("No shop UUID provided -> Owner account is being created")
shop_id = None
logger.debug("Hashing password")
hashed_password = get_password_hash(user_register.password)
new_user = User(
uuid=user_uuid,
uuid=uuid4(),
shop_id=shop_id,
email=user_register.email,
username=user_register.username,
@ -39,20 +34,50 @@ def create_user(session: Session, user_register: UserRegister, shop_uuid: Option
user_role=user_role,
password=hashed_password
)
logger.debug("Inserting new user")
session.add(new_user)
session.commit()
def update_user(session: Session, user_update: UserUpdate, current_user: User):
current_user.email = user_update.email
current_user.username = user_update.username
current_user.phone_number = user_update.phone_number
current_user.first_name = user_update.first_name
current_user.last_name = user_update.last_name
session.commit()
def get_user_by_uuid(session: Session, email: str, shop_uuid: Optional[UUID]) -> Optional[User]:
if shop_uuid:
shop_id = get_shop_by_uuid(session, shop_uuid).id
else:
shop_id = None
stmt = select(User).where(and_(
User.email == email,
User.shop_id == shop_id
))
logger.debug("Executing select query")
db_user = session.exec(stmt).one_or_none()
return db_user
def authenticate(session: Session, email: str, password: str, shop_uuid: Optional[int]) -> Optional[User]:
logger.debug("Getting shop id by UUID - %s", shop_uuid)
shop_id = get_shop_id_from_uuid(session, shop_uuid)
if shop_uuid:
shop_id = get_shop_by_uuid(session, shop_uuid).id
else:
shop_id = None
logger.debug("Fetching user from db by email - %s", email)
db_user = get_user_by_generated_uuid(session, email, shop_id)
db_user = get_user_by_uuid(session, email, shop_id)
if db_user is None:
logger.warn("Didn't find User with email=%s for shop=%s", email, shop_uuid)
logger.warning("Didn't find User with email=%s for shop=%s", email, shop_uuid)
return None
if not verify_password(plain_password=password, hashed_password=db_user.password):
logger.warn("Found user with email=%s for shop=%s", email, shop_uuid)
logger.warning("Found user with email=%s for shop=%s", email, shop_uuid)
return None
return db_user

View File

@ -4,7 +4,7 @@ from typing import Optional
from uuid import UUID
from sqlalchemy import Column
from sqlmodel import Enum, Field, Relationship, SQLModel
from sqlmodel import Enum, Field, Relationship, SQLModel, UniqueConstraint
class UserRole(PyEnum):
@ -17,6 +17,9 @@ class UserRole(PyEnum):
class User(SQLModel, table=True):
__tablename__ = "user"
__table_args__ = (
UniqueConstraint("email", "shop_id", name="uix_email_shop_id"),
)
id: int = Field(primary_key=True)
uuid: UUID = Field(nullable=False, unique=True)

View File

@ -1,5 +1,5 @@
import re
from typing import Optional
import uuid
from pydantic import BaseModel, EmailStr, Field, model_validator
@ -7,9 +7,13 @@ from pydantic import BaseModel, EmailStr, Field, model_validator
class UserRegister(BaseModel):
username: str = Field(min_length=3, max_length=64)
email: EmailStr = Field()
phone_number: str = Field(min_length=2, max_length=16, pattern=r'^\+[1-9]\d{1,14}$')
password: str = Field(min_length=8, max_length=128, examples=["Abc123#!"],
description="Password must conform to this regex: \n```\n^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$\n```")
phone_number: str = Field(min_length=2, max_length=16, pattern=r"^\+[1-9]\d{1,14}$")
password: str = Field(
min_length=8,
max_length=128,
examples=["Abc123#!"],
description="Password must conform to this regex: \n```\n^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$\n```",
)
@model_validator(mode="after")
def validate_using_regex(self):
@ -17,28 +21,28 @@ class UserRegister(BaseModel):
return self
def __validate_password(self):
password_regex = r"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$"
password_regex = r"^(?=[^A-Z]*[A-Z])(?=[^a-z]*[a-z])(?=\D*\d)(?=[^#?!@$ %^&*-]*[#?!@$ %^&*-]).{8,128}$"
if not re.match(password_regex, self.password):
raise ValueError("Password is too weak")
class UserUpdate(BaseModel):
username: Optional[str] = Field(None, min_length=3, max_length=64)
first_name: Optional[str] = Field(None, max_length=64)
last_name: Optional[str] = Field(None, max_length=64)
email: Optional[EmailStr] = Field(None)
phone_number: Optional[str] = Field(None, min_length=2, max_length=16, pattern=r'^\+[1-9]\d{1,14}$')
password: Optional[str] = Field(None, min_length=8, max_length=128, examples=["Abc123#!"],
description="Password must conform to this regex: \n```\n^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$\n```")
email: EmailStr | None = Field()
phone_number: str | None = Field(min_length=2, max_length=16, pattern=r"^\+[1-9]\d{1,14}$")
username: str | None = Field(min_length=3, max_length=64)
first_name: str | None = Field(None, max_length=64)
last_name: str | None = Field(None, max_length=64)
@model_validator(mode="after")
def validate_using_regex(self):
self.__validate_password()
return self
def __validate_password(self):
password_regex = r"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$"
if not re.match(password_regex, self.password):
raise ValueError("Password is too weak")
class UserPublic(BaseModel):
uuid: uuid.UUID
username: str
email: str
first_name: str | None
last_name: str | None
phone_number: str
class Token(BaseModel):
access_token: str

View File

@ -1,6 +1,12 @@
import logging
from typing import Optional
from uuid import uuid5, UUID, NAMESPACE_DNS
from uuid import NAMESPACE_DNS, UUID, uuid5
logger = logging.getLogger(__name__)
def generate_user_uuid5(email: str, shop_id: Optional[int]) -> UUID:
unique_string = f"{email}-{shop_id}" if shop_id else email
return uuid5(NAMESPACE_DNS, unique_string)
generated_uuid = uuid5(NAMESPACE_DNS, unique_string)
logger.debug("Generated user UUID5 - %s", generated_uuid)
return generated_uuid

View File

@ -30,7 +30,6 @@ select = [
ignore = [
"E501", # line too long, handled by black
"B008", # do not perform function calls in argument defaults
"W191", # indentation contains tabs
"B904", # Allow raising exceptions without from e, for HTTPException
"UP007",
]

0
frontend/.env.example Normal file
View File

30
frontend/biome.json Normal file
View File

@ -0,0 +1,30 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": []
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}

View File

@ -0,0 +1,16 @@
import { defineConfig } from "@hey-api/openapi-ts";
export default defineConfig({
input: "./openapi.json",
output: {
path: "./src/client"
},
plugins: [
"legacy/axios",
{
name: "@hey-api/sdk",
asClass: true,
operationId: true
}
]
});

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@
"format:check": "prettier --check .",
"format": "prettier --write .",
"knip": "knip",
"generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client @hey-api/client-axios"
"generate-client": "openapi-ts"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
@ -51,6 +51,7 @@
"react-day-picker": "^9.6.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.0",
"react-phone-number-input": "^3.4.12",
"recharts": "^2.14.1",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",

View File

@ -0,0 +1,21 @@
import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from './ApiResult';
export class ApiError extends Error {
public readonly url: string;
public readonly status: number;
public readonly statusText: string;
public readonly body: unknown;
public readonly request: ApiRequestOptions;
constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
super(message);
this.name = 'ApiError';
this.url = response.url;
this.status = response.status;
this.statusText = response.statusText;
this.body = response.body;
this.request = request;
}
}

View File

@ -0,0 +1,21 @@
export type ApiRequestOptions<T = unknown> = {
readonly body?: any;
readonly cookies?: Record<string, unknown>;
readonly errors?: Record<number | string, string>;
readonly formData?: Record<string, unknown> | any[] | Blob | File;
readonly headers?: Record<string, unknown>;
readonly mediaType?: string;
readonly method:
| 'DELETE'
| 'GET'
| 'HEAD'
| 'OPTIONS'
| 'PATCH'
| 'POST'
| 'PUT';
readonly path?: Record<string, unknown>;
readonly query?: Record<string, unknown>;
readonly responseHeader?: string;
readonly responseTransformer?: (data: unknown) => Promise<T>;
readonly url: string;
};

View File

@ -0,0 +1,7 @@
export type ApiResult<TData = any> = {
readonly body: TData;
readonly ok: boolean;
readonly status: number;
readonly statusText: string;
readonly url: string;
};

View File

@ -0,0 +1,126 @@
export class CancelError extends Error {
constructor(message: string) {
super(message);
this.name = 'CancelError';
}
public get isCancelled(): boolean {
return true;
}
}
export interface OnCancel {
readonly isResolved: boolean;
readonly isRejected: boolean;
readonly isCancelled: boolean;
(cancelHandler: () => void): void;
}
export class CancelablePromise<T> implements Promise<T> {
private _isResolved: boolean;
private _isRejected: boolean;
private _isCancelled: boolean;
readonly cancelHandlers: (() => void)[];
readonly promise: Promise<T>;
private _resolve?: (value: T | PromiseLike<T>) => void;
private _reject?: (reason?: unknown) => void;
constructor(
executor: (
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: unknown) => void,
onCancel: OnCancel
) => void
) {
this._isResolved = false;
this._isRejected = false;
this._isCancelled = false;
this.cancelHandlers = [];
this.promise = new Promise<T>((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
const onResolve = (value: T | PromiseLike<T>): void => {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this._isResolved = true;
if (this._resolve) this._resolve(value);
};
const onReject = (reason?: unknown): void => {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this._isRejected = true;
if (this._reject) this._reject(reason);
};
const onCancel = (cancelHandler: () => void): void => {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this.cancelHandlers.push(cancelHandler);
};
Object.defineProperty(onCancel, 'isResolved', {
get: (): boolean => this._isResolved,
});
Object.defineProperty(onCancel, 'isRejected', {
get: (): boolean => this._isRejected,
});
Object.defineProperty(onCancel, 'isCancelled', {
get: (): boolean => this._isCancelled,
});
return executor(onResolve, onReject, onCancel as OnCancel);
});
}
get [Symbol.toStringTag]() {
return "Cancellable Promise";
}
public then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
): Promise<TResult1 | TResult2> {
return this.promise.then(onFulfilled, onRejected);
}
public catch<TResult = never>(
onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null
): Promise<T | TResult> {
return this.promise.catch(onRejected);
}
public finally(onFinally?: (() => void) | null): Promise<T> {
return this.promise.finally(onFinally);
}
public cancel(): void {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this._isCancelled = true;
if (this.cancelHandlers.length) {
try {
for (const cancelHandler of this.cancelHandlers) {
cancelHandler();
}
} catch (error) {
console.warn('Cancellation threw an error', error);
return;
}
}
this.cancelHandlers.length = 0;
if (this._reject) this._reject(new CancelError('Request aborted'));
}
public get isCancelled(): boolean {
return this._isCancelled;
}
}

View File

@ -0,0 +1,57 @@
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import type { ApiRequestOptions } from './ApiRequestOptions';
type Headers = Record<string, string>;
type Middleware<T> = (value: T) => T | Promise<T>;
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
export class Interceptors<T> {
_fns: Middleware<T>[];
constructor() {
this._fns = [];
}
eject(fn: Middleware<T>): void {
const index = this._fns.indexOf(fn);
if (index !== -1) {
this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)];
}
}
use(fn: Middleware<T>): void {
this._fns = [...this._fns, fn];
}
}
export type OpenAPIConfig = {
BASE: string;
CREDENTIALS: 'include' | 'omit' | 'same-origin';
ENCODE_PATH?: ((path: string) => string) | undefined;
HEADERS?: Headers | Resolver<Headers> | undefined;
PASSWORD?: string | Resolver<string> | undefined;
TOKEN?: string | Resolver<string> | undefined;
USERNAME?: string | Resolver<string> | undefined;
VERSION: string;
WITH_CREDENTIALS: boolean;
interceptors: {
request: Interceptors<AxiosRequestConfig>;
response: Interceptors<AxiosResponse>;
};
};
export const OpenAPI: OpenAPIConfig = {
BASE: '',
CREDENTIALS: 'include',
ENCODE_PATH: undefined,
HEADERS: undefined,
PASSWORD: undefined,
TOKEN: undefined,
USERNAME: undefined,
VERSION: '0.0.1',
WITH_CREDENTIALS: false,
interceptors: {
request: new Interceptors(),
response: new Interceptors(),
},
};

View File

@ -0,0 +1,347 @@
import axios from 'axios';
import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios';
import { ApiError } from './ApiError';
import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from './ApiResult';
import { CancelablePromise } from './CancelablePromise';
import type { OnCancel } from './CancelablePromise';
import type { OpenAPIConfig } from './OpenAPI';
export const isString = (value: unknown): value is string => {
return typeof value === 'string';
};
export const isStringWithValue = (value: unknown): value is string => {
return isString(value) && value !== '';
};
export const isBlob = (value: any): value is Blob => {
return value instanceof Blob;
};
export const isFormData = (value: unknown): value is FormData => {
return value instanceof FormData;
};
export const isSuccess = (status: number): boolean => {
return status >= 200 && status < 300;
};
export const base64 = (str: string): string => {
try {
return btoa(str);
} catch (err) {
// @ts-ignore
return Buffer.from(str).toString('base64');
}
};
export const getQueryString = (params: Record<string, unknown>): string => {
const qs: string[] = [];
const append = (key: string, value: unknown) => {
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
};
const encodePair = (key: string, value: unknown) => {
if (value === undefined || value === null) {
return;
}
if (value instanceof Date) {
append(key, value.toISOString());
} else if (Array.isArray(value)) {
value.forEach(v => encodePair(key, v));
} else if (typeof value === 'object') {
Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v));
} else {
append(key, value);
}
};
Object.entries(params).forEach(([key, value]) => encodePair(key, value));
return qs.length ? `?${qs.join('&')}` : '';
};
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
const encoder = config.ENCODE_PATH || encodeURI;
const path = options.url
.replace('{api-version}', config.VERSION)
.replace(/{(.*?)}/g, (substring: string, group: string) => {
if (options.path?.hasOwnProperty(group)) {
return encoder(String(options.path[group]));
}
return substring;
});
const url = config.BASE + path;
return options.query ? url + getQueryString(options.query) : url;
};
export const getFormData = (options: ApiRequestOptions): FormData | undefined => {
if (options.formData) {
const formData = new FormData();
const process = (key: string, value: unknown) => {
if (isString(value) || isBlob(value)) {
formData.append(key, value);
} else {
formData.append(key, JSON.stringify(value));
}
};
Object.entries(options.formData)
.filter(([, value]) => value !== undefined && value !== null)
.forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach(v => process(key, v));
} else {
process(key, value);
}
});
return formData;
}
return undefined;
};
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
export const resolve = async <T>(options: ApiRequestOptions<T>, resolver?: T | Resolver<T>): Promise<T | undefined> => {
if (typeof resolver === 'function') {
return (resolver as Resolver<T>)(options);
}
return resolver;
};
export const getHeaders = async <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>): Promise<Record<string, string>> => {
const [token, username, password, additionalHeaders] = await Promise.all([
// @ts-ignore
resolve(options, config.TOKEN),
// @ts-ignore
resolve(options, config.USERNAME),
// @ts-ignore
resolve(options, config.PASSWORD),
// @ts-ignore
resolve(options, config.HEADERS),
]);
const headers = Object.entries({
Accept: 'application/json',
...additionalHeaders,
...options.headers,
})
.filter(([, value]) => value !== undefined && value !== null)
.reduce((headers, [key, value]) => ({
...headers,
[key]: String(value),
}), {} as Record<string, string>);
if (isStringWithValue(token)) {
headers['Authorization'] = `Bearer ${token}`;
}
if (isStringWithValue(username) && isStringWithValue(password)) {
const credentials = base64(`${username}:${password}`);
headers['Authorization'] = `Basic ${credentials}`;
}
if (options.body !== undefined) {
if (options.mediaType) {
headers['Content-Type'] = options.mediaType;
} else if (isBlob(options.body)) {
headers['Content-Type'] = options.body.type || 'application/octet-stream';
} else if (isString(options.body)) {
headers['Content-Type'] = 'text/plain';
} else if (!isFormData(options.body)) {
headers['Content-Type'] = 'application/json';
}
} else if (options.formData !== undefined) {
if (options.mediaType) {
headers['Content-Type'] = options.mediaType;
}
}
return headers;
};
export const getRequestBody = (options: ApiRequestOptions): unknown => {
if (options.body) {
return options.body;
}
return undefined;
};
export const sendRequest = async <T>(
config: OpenAPIConfig,
options: ApiRequestOptions<T>,
url: string,
body: unknown,
formData: FormData | undefined,
headers: Record<string, string>,
onCancel: OnCancel,
axiosClient: AxiosInstance
): Promise<AxiosResponse<T>> => {
const controller = new AbortController();
let requestConfig: AxiosRequestConfig = {
data: body ?? formData,
headers,
method: options.method,
signal: controller.signal,
url,
withCredentials: config.WITH_CREDENTIALS,
};
onCancel(() => controller.abort());
for (const fn of config.interceptors.request._fns) {
requestConfig = await fn(requestConfig);
}
try {
return await axiosClient.request(requestConfig);
} catch (error) {
const axiosError = error as AxiosError<T>;
if (axiosError.response) {
return axiosError.response;
}
throw error;
}
};
export const getResponseHeader = (response: AxiosResponse<unknown>, responseHeader?: string): string | undefined => {
if (responseHeader) {
const content = response.headers[responseHeader];
if (isString(content)) {
return content;
}
}
return undefined;
};
export const getResponseBody = (response: AxiosResponse<unknown>): unknown => {
if (response.status !== 204) {
return response.data;
}
return undefined;
};
export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
const errors: Record<number, string> = {
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Timeout',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Payload Too Large',
414: 'URI Too Long',
415: 'Unsupported Media Type',
416: 'Range Not Satisfiable',
417: 'Expectation Failed',
418: 'Im a teapot',
421: 'Misdirected Request',
422: 'Unprocessable Content',
423: 'Locked',
424: 'Failed Dependency',
425: 'Too Early',
426: 'Upgrade Required',
428: 'Precondition Required',
429: 'Too Many Requests',
431: 'Request Header Fields Too Large',
451: 'Unavailable For Legal Reasons',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported',
506: 'Variant Also Negotiates',
507: 'Insufficient Storage',
508: 'Loop Detected',
510: 'Not Extended',
511: 'Network Authentication Required',
...options.errors,
}
const error = errors[result.status];
if (error) {
throw new ApiError(options, result, error);
}
if (!result.ok) {
const errorStatus = result.status ?? 'unknown';
const errorStatusText = result.statusText ?? 'unknown';
const errorBody = (() => {
try {
return JSON.stringify(result.body, null, 2);
} catch (e) {
return undefined;
}
})();
throw new ApiError(options, result,
`Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`
);
}
};
/**
* Request method
* @param config The OpenAPI configuration object
* @param options The request options from the service
* @param axiosClient The axios client instance to use
* @returns CancelablePromise<T>
* @throws ApiError
*/
export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>, axiosClient: AxiosInstance = axios): CancelablePromise<T> => {
return new CancelablePromise(async (resolve, reject, onCancel) => {
try {
const url = getUrl(config, options);
const formData = getFormData(options);
const body = getRequestBody(options);
const headers = await getHeaders(config, options);
if (!onCancel.isCancelled) {
let response = await sendRequest<T>(config, options, url, body, formData, headers, onCancel, axiosClient);
for (const fn of config.interceptors.response._fns) {
response = await fn(response);
}
const responseBody = getResponseBody(response);
const responseHeader = getResponseHeader(response, options.responseHeader);
let transformedBody = responseBody;
if (options.responseTransformer && isSuccess(response.status)) {
transformedBody = await options.responseTransformer(responseBody)
}
const result: ApiResult = {
url,
ok: isSuccess(response.status),
status: response.status,
statusText: response.statusText,
body: responseHeader ?? transformedBody,
};
catchErrorCodes(options, result);
resolve(result.body);
}
} catch (error) {
reject(error);
}
});
};

View File

@ -0,0 +1,6 @@
// This file is auto-generated by @hey-api/openapi-ts
export { ApiError } from './core/ApiError';
export { CancelablePromise, CancelError } from './core/CancelablePromise';
export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI';
export * from './sdk.gen';
export * from './types.gen';

View File

@ -0,0 +1,438 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
import type { UserGetUserResponse, UserUpdateUserData, UserUpdateUserResponse, UserRegisterData, UserRegisterResponse, UserDeleteUserResponse, DashboardLoginAccessTokenData, DashboardLoginAccessTokenResponse, DashboardRegisterNewShopData, DashboardRegisterNewShopResponse, ShopLoginAccessTokenData, ShopLoginAccessTokenResponse, ShopDeleteUserData, ShopDeleteUserResponse, ShopLogoutResponse, ShopRegisterData, ShopRegisterResponse, ShopUpdateUserData, ShopUpdateUserResponse, UtilsHealthCheckResponse, UtilsTestDbResponse } from './types.gen';
export class DashboardService {
/**
* Get information about currently logged in user
* @returns UserPublic Successful Response
* @throws ApiError
*/
public static userGetUser(): CancelablePromise<UserGetUserResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/user'
});
}
/**
* Update user details
* @param data The data for the request.
* @param data.requestBody
* @returns boolean Successful Response
* @throws ApiError
*/
public static userUpdateUser(data: UserUpdateUserData): CancelablePromise<UserUpdateUserResponse> {
return __request(OpenAPI, {
method: 'PUT',
url: '/user',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Register new user
* @param data The data for the request.
* @param data.requestBody
* @returns boolean Successful Response
* @throws ApiError
*/
public static userRegister(data: UserRegisterData): CancelablePromise<UserRegisterResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/user',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Delete user
* @returns boolean Successful Response
* @throws ApiError
*/
public static userDeleteUser(): CancelablePromise<UserDeleteUserResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/user'
});
}
/**
* Login Access Token
* OAuth2 compatible token login for the dashboard.
*
* This endpoint generates an access token required for authenticating future
* requests to the dashboard section of the application. The token is valid for
* a predefined expiration period.
*
* - **username**: User's email
* - **password**: User's password
*
* **Note:** This login is restricted to dashboard access only and cannot be
* used for tenant accounts access to shops
* @param data The data for the request.
* @param data.formData
* @returns Token Successful Response
* @throws ApiError
*/
public static dashboardLoginAccessToken(data: DashboardLoginAccessTokenData): CancelablePromise<DashboardLoginAccessTokenResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/login/access-token',
formData: data.formData,
mediaType: 'application/x-www-form-urlencoded',
errors: {
422: 'Validation Error'
}
});
}
/**
* Register New Shop
* @param data The data for the request.
* @param data.requestBody
* @returns boolean Successful Response
* @throws ApiError
*/
public static dashboardRegisterNewShop(data: DashboardRegisterNewShopData): CancelablePromise<DashboardRegisterNewShopResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/shop',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
}
export class LoginService {
/**
* Login Access Token
* OAuth2 compatible token login for the dashboard.
*
* This endpoint generates an access token required for authenticating future
* requests to the dashboard section of the application. The token is valid for
* a predefined expiration period.
*
* - **username**: User's email
* - **password**: User's password
*
* **Note:** This login is restricted to dashboard access only and cannot be
* used for tenant accounts access to shops
* @param data The data for the request.
* @param data.formData
* @returns Token Successful Response
* @throws ApiError
*/
public static dashboardLoginAccessToken(data: DashboardLoginAccessTokenData): CancelablePromise<DashboardLoginAccessTokenResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/login/access-token',
formData: data.formData,
mediaType: 'application/x-www-form-urlencoded',
errors: {
422: 'Validation Error'
}
});
}
/**
* Login Access Token
* OAuth2 compatible token login, get an access token for future requests
* @param data The data for the request.
* @param data.formData
* @returns Token Successful Response
* @throws ApiError
*/
public static shopLoginAccessToken(data: ShopLoginAccessTokenData): CancelablePromise<ShopLoginAccessTokenResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/shop/{shop_uuid}/login/access-token',
formData: data.formData,
mediaType: 'application/x-www-form-urlencoded',
errors: {
422: 'Validation Error'
}
});
}
}
export class ShopService {
/**
* Login Access Token
* OAuth2 compatible token login, get an access token for future requests
* @param data The data for the request.
* @param data.formData
* @returns Token Successful Response
* @throws ApiError
*/
public static shopLoginAccessToken(data: ShopLoginAccessTokenData): CancelablePromise<ShopLoginAccessTokenResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/shop/{shop_uuid}/login/access-token',
formData: data.formData,
mediaType: 'application/x-www-form-urlencoded',
errors: {
422: 'Validation Error'
}
});
}
/**
* Delete user
* @param data The data for the request.
* @param data.shopUuid
* @returns unknown Successful Response
* @throws ApiError
*/
public static shopDeleteUser(data: ShopDeleteUserData): CancelablePromise<ShopDeleteUserResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/shop/{shop_uuid}/user/delete',
path: {
shop_uuid: data.shopUuid
},
errors: {
422: 'Validation Error'
}
});
}
/**
* User logout
* @returns unknown Successful Response
* @throws ApiError
*/
public static shopLogout(): CancelablePromise<ShopLogoutResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/shop/{shop_uuid}/user/logout'
});
}
/**
* Register new user
* @param data The data for the request.
* @param data.shopUuid
* @param data.requestBody
* @returns unknown Successful Response
* @throws ApiError
*/
public static shopRegister(data: ShopRegisterData): CancelablePromise<ShopRegisterResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/shop/{shop_uuid}/user/register',
path: {
shop_uuid: data.shopUuid
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Update user details
* @param data The data for the request.
* @param data.requestBody
* @returns unknown Successful Response
* @throws ApiError
*/
public static shopUpdateUser(data: ShopUpdateUserData): CancelablePromise<ShopUpdateUserResponse> {
return __request(OpenAPI, {
method: 'PUT',
url: '/shop/{shop_uuid}/user/update',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
}
export class UserService {
/**
* Get information about currently logged in user
* @returns UserPublic Successful Response
* @throws ApiError
*/
public static userGetUser(): CancelablePromise<UserGetUserResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/user'
});
}
/**
* Update user details
* @param data The data for the request.
* @param data.requestBody
* @returns boolean Successful Response
* @throws ApiError
*/
public static userUpdateUser(data: UserUpdateUserData): CancelablePromise<UserUpdateUserResponse> {
return __request(OpenAPI, {
method: 'PUT',
url: '/user',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Register new user
* @param data The data for the request.
* @param data.requestBody
* @returns boolean Successful Response
* @throws ApiError
*/
public static userRegister(data: UserRegisterData): CancelablePromise<UserRegisterResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/user',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Delete user
* @returns boolean Successful Response
* @throws ApiError
*/
public static userDeleteUser(): CancelablePromise<UserDeleteUserResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/user'
});
}
/**
* Delete user
* @param data The data for the request.
* @param data.shopUuid
* @returns unknown Successful Response
* @throws ApiError
*/
public static shopDeleteUser(data: ShopDeleteUserData): CancelablePromise<ShopDeleteUserResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/shop/{shop_uuid}/user/delete',
path: {
shop_uuid: data.shopUuid
},
errors: {
422: 'Validation Error'
}
});
}
/**
* User logout
* @returns unknown Successful Response
* @throws ApiError
*/
public static shopLogout(): CancelablePromise<ShopLogoutResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/shop/{shop_uuid}/user/logout'
});
}
/**
* Register new user
* @param data The data for the request.
* @param data.shopUuid
* @param data.requestBody
* @returns unknown Successful Response
* @throws ApiError
*/
public static shopRegister(data: ShopRegisterData): CancelablePromise<ShopRegisterResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/shop/{shop_uuid}/user/register',
path: {
shop_uuid: data.shopUuid
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Update user details
* @param data The data for the request.
* @param data.requestBody
* @returns unknown Successful Response
* @throws ApiError
*/
public static shopUpdateUser(data: ShopUpdateUserData): CancelablePromise<ShopUpdateUserResponse> {
return __request(OpenAPI, {
method: 'PUT',
url: '/shop/{shop_uuid}/user/update',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
}
export class UtilsService {
/**
* Health Check
* Ping the API whether it's alive or not
* @returns boolean Successful Response
* @throws ApiError
*/
public static utilsHealthCheck(): CancelablePromise<UtilsHealthCheckResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/utils/health-check/'
});
}
/**
* Test Db
* Ping database using select 1 to see if connection works
* @returns boolean Successful Response
* @throws ApiError
*/
public static utilsTestDb(): CancelablePromise<UtilsTestDbResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/utils/test-db/'
});
}
}

View File

@ -0,0 +1,142 @@
// This file is auto-generated by @hey-api/openapi-ts
export type Body_Dashboard_login_access_token = {
grant_type?: (string | null);
username: string;
password: string;
scope?: string;
client_id?: (string | null);
client_secret?: (string | null);
};
export type Body_Shop_login_access_token = {
grant_type?: (string | null);
username: string;
password: string;
scope?: string;
client_id?: (string | null);
client_secret?: (string | null);
};
export type HTTPValidationError = {
detail?: Array<ValidationError>;
};
export type ShopAddress = {
street: string;
city: string;
state: string;
postal_code: string;
country: string;
};
export type ShopCreate = {
name: string;
description: string;
currency: string;
contact_email: string;
contact_phone_number: string;
address: ShopAddress;
};
export type Token = {
access_token: string;
token_type?: string;
};
export type UserPublic = {
uuid: string;
username: string;
email: string;
first_name: (string | null);
last_name: (string | null);
phone_number: string;
};
export type UserRegister = {
username: string;
email: string;
phone_number: string;
/**
* Password must conform to this regex:
* ```
* ^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$
* ```
*/
password: string;
};
export type UserUpdate = {
email: (string | null);
phone_number: (string | null);
username: (string | null);
first_name?: (string | null);
last_name?: (string | null);
};
export type ValidationError = {
loc: Array<(string | number)>;
msg: string;
type: string;
};
export type UserGetUserResponse = (UserPublic);
export type UserUpdateUserData = {
requestBody: UserUpdate;
};
export type UserUpdateUserResponse = (boolean);
export type UserRegisterData = {
requestBody: UserRegister;
};
export type UserRegisterResponse = (boolean);
export type UserDeleteUserResponse = (boolean);
export type DashboardLoginAccessTokenData = {
formData: Body_Dashboard_login_access_token;
};
export type DashboardLoginAccessTokenResponse = (Token);
export type DashboardRegisterNewShopData = {
requestBody: ShopCreate;
};
export type DashboardRegisterNewShopResponse = (boolean);
export type ShopLoginAccessTokenData = {
formData: Body_Shop_login_access_token;
};
export type ShopLoginAccessTokenResponse = (Token);
export type ShopDeleteUserData = {
shopUuid: unknown;
};
export type ShopDeleteUserResponse = (unknown);
export type ShopLogoutResponse = (unknown);
export type ShopRegisterData = {
requestBody: UserRegister;
shopUuid: unknown;
};
export type ShopRegisterResponse = (unknown);
export type ShopUpdateUserData = {
requestBody: {
[key: string]: unknown;
};
};
export type ShopUpdateUserResponse = (unknown);
export type UtilsHealthCheckResponse = (boolean);
export type UtilsTestDbResponse = (boolean);

View File

@ -1,175 +0,0 @@
"use client";
import * as React from "react";
import {
AudioWaveform,
BookOpen,
Bot,
Command,
Frame,
GalleryVerticalEnd,
Map,
PieChart,
Settings2,
SquareTerminal
} from "lucide-react";
import { NavMain } from "@/components/nav-main";
import { NavProjects } from "@/components/nav-projects";
import { NavUser } from "@/components/nav-user";
import { TeamSwitcher } from "@/components/team-switcher";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarRail
} from "@/components/ui/sidebar";
// This is sample data.
const data = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg"
},
teams: [
{
name: "Acme Inc",
logo: GalleryVerticalEnd,
plan: "Enterprise"
},
{
name: "Acme Corp.",
logo: AudioWaveform,
plan: "Startup"
},
{
name: "Evil Corp.",
logo: Command,
plan: "Free"
}
],
navMain: [
{
title: "Playground",
url: "#",
icon: SquareTerminal,
isActive: true,
items: [
{
title: "History",
url: "#"
},
{
title: "Starred",
url: "#"
},
{
title: "Settings",
url: "#"
}
]
},
{
title: "Models",
url: "#",
icon: Bot,
items: [
{
title: "Genesis",
url: "#"
},
{
title: "Explorer",
url: "#"
},
{
title: "Quantum",
url: "#"
}
]
},
{
title: "Documentation",
url: "#",
icon: BookOpen,
items: [
{
title: "Introduction",
url: "#"
},
{
title: "Get Started",
url: "#"
},
{
title: "Tutorials",
url: "#"
},
{
title: "Changelog",
url: "#"
}
]
},
{
title: "Settings",
url: "#",
icon: Settings2,
items: [
{
title: "General",
url: "#"
},
{
title: "Team",
url: "#"
},
{
title: "Billing",
url: "#"
},
{
title: "Limits",
url: "#"
}
]
}
],
projects: [
{
name: "Design Engineering",
url: "#",
icon: Frame
},
{
name: "Sales & Marketing",
url: "#",
icon: PieChart
},
{
name: "Travel",
url: "#",
icon: Map
}
]
};
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
<TeamSwitcher teams={data.teams} />
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavProjects projects={data.projects} />
</SidebarContent>
<SidebarFooter>
<NavUser user={data.user} />
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
}

View File

@ -0,0 +1,32 @@
import { Link } from "@tanstack/react-router";
import { Button } from "./ui/button";
import useAuth from "@/hooks/useAuth";
const DynamicLoginButton = () => {
const { logout, user } = useAuth();
if (user) {
return (
<>
<Button variant="secondary" className="hidden px-2 md:block">
<Link to="/dashboard">Dashboard</Link>
</Button>
<Button className="ml-2 mr-2 hidden md:block" onClick={logout}>
Logout
</Button>
</>
);
}
return (
<>
<Button variant="secondary" className="hidden px-2 md:block">
<Link to="/sign-in">Login</Link>
</Button>
<Button className="ml-2 mr-2 hidden md:block">
<Link to="/sign-up">Get Started</Link>
</Button>
</>
);
};
export default DynamicLoginButton;

View File

@ -1,29 +1,33 @@
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarRail
} from "@/components/ui/sidebar";
import { NavGroup } from "@/components/layout/nav-group";
import { NavUser } from "@/components/layout/nav-user";
import { TeamSwitcher } from "@/components/layout/team-switcher";
import { sidebarData } from "./data/sidebar-data";
import { cn } from "@/lib/utils";
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar collapsible="icon" variant="floating" {...props}>
<SidebarHeader>
<TeamSwitcher teams={sidebarData.teams} />
<h1
className={cn(
"header-fixed peer/header flex h-16 w-[inherit] items-center gap-3 rounded-md bg-background p-4 text-xl font-bold sm:gap-4"
)}
{...props}>
SwagShop
</h1>
</SidebarHeader>
<SidebarContent>
{sidebarData.navGroups.map((props) => (
<NavGroup key={props.title} {...props} />
))}
</SidebarContent>
<SidebarFooter>
{/* <SidebarFooter>
<NavUser user={sidebarData.user} />
</SidebarFooter>
</SidebarFooter> */}
<SidebarRail />
</Sidebar>
);

View File

@ -1,22 +1,17 @@
import {
IconBarrierBlock,
IconBrowserCheck,
IconBug,
IconChecklist,
IconError404,
IconCoin,
IconForklift,
IconHelp,
IconLayoutDashboard,
IconLock,
IconLockAccess,
IconMessages,
IconNotification,
IconPackages,
IconPackage,
IconPalette,
IconServerOff,
IconPercentage,
IconSettings,
IconTag,
IconTool,
IconUserCog,
IconUserOff,
IconUsers
} from "@tabler/icons-react";
import { AudioWaveform, Command, GalleryVerticalEnd } from "lucide-react";
@ -47,100 +42,43 @@ export const sidebarData: SidebarData = {
],
navGroups: [
{
title: "General",
title: "Dashboard",
items: [
{
title: "Dashboard",
url: "/",
url: "/dashboard",
icon: IconLayoutDashboard
},
{
title: "Tasks",
url: "/tasks",
icon: IconChecklist
title: "Products",
url: "/dashboard/products",
icon: IconPackage
},
{
title: "Apps",
url: "/apps",
icon: IconPackages
title: "Inventory",
url: "/dashboard/tasks",
icon: IconForklift
},
{
title: "Chats",
url: "/chats",
badge: "3",
icon: IconMessages
title: "Sales",
icon: IconCoin,
items: [
{
title: "Discounts",
url: "/dashboard/sales/discounts",
icon: IconPercentage
},
{
title: "Users",
url: "/users",
title: "Coupons",
url: "/dashboard/sales/coupons",
icon: IconTag
}
]
},
{
title: "Customers",
url: "/dashboard/users",
icon: IconUsers
},
{
title: "NBA",
url: "/nba",
icon: IconChecklist
}
]
},
{
title: "Pages",
items: [
{
title: "Auth",
icon: IconLockAccess,
items: [
{
title: "Sign In",
url: "/sign-in"
},
{
title: "Sign In (2 Col)",
url: "/sign-in-2"
},
{
title: "Sign Up",
url: "/sign-up"
},
{
title: "Forgot Password",
url: "/forgot-password"
},
{
title: "OTP",
url: "/otp"
}
]
},
{
title: "Errors",
icon: IconBug,
items: [
{
title: "Unauthorized",
url: "/401",
icon: IconLock
},
{
title: "Forbidden",
url: "/403",
icon: IconUserOff
},
{
title: "Not Found",
url: "/404",
icon: IconError404
},
{
title: "Internal Server Error",
url: "/500",
icon: IconServerOff
},
{
title: "Maintenance Error",
url: "/503",
icon: IconBarrierBlock
}
]
}
]
},
@ -153,27 +91,27 @@ export const sidebarData: SidebarData = {
items: [
{
title: "Profile",
url: "/settings",
url: "/dashboard/settings",
icon: IconUserCog
},
{
title: "Account",
url: "/settings/account",
url: "/dashboard/settings/account",
icon: IconTool
},
{
title: "Appearance",
url: "/settings/appearance",
url: "/dashboard/settings/appearance",
icon: IconPalette
},
{
title: "Notifications",
url: "/settings/notifications",
url: "/dashboard/settings/notifications",
icon: IconNotification
},
{
title: "Display",
url: "/settings/display",
url: "/dashboard/settings/display",
icon: IconBrowserCheck
}
]

View File

@ -8,7 +8,7 @@ import { Menu } from "lucide-react";
import { Card } from "@/components/ui/card";
import { ThemeSwitch } from "./theme-switch";
import { Button } from "@/components/ui/button";
import { Link } from "@tanstack/react-router";
import DynamicLoginButton from "./dynamic-login-button";
const MainNavbar = () => {
return (
@ -27,11 +27,7 @@ const MainNavbar = () => {
</ul>
<div className="flex items-center">
<Button variant="secondary" className="hidden px-2 md:block">
<Link to="/sign-in">Login</Link>
</Button>
<Button className="ml-2 mr-2 hidden md:block"><Link to="/sign-up">Get Started</Link></Button>
<DynamicLoginButton />
<div className="mr-2 flex items-center gap-2 md:hidden">
<DropdownMenu>

View File

@ -11,51 +11,55 @@ import {
DropdownMenuShortcut,
DropdownMenuTrigger
} from "@/components/ui/dropdown-menu";
import useAuth from "@/hooks/useAuth";
export function ProfileDropdown() {
const { user, logout } = useAuth();
if (!user) return "";
let shorthandUsername = "";
if (user.first_name && user.last_name) {
shorthandUsername = (user.first_name[0] + user.last_name[0]).toUpperCase();
} else {
shorthandUsername = user.username[0].toUpperCase();
}
return (
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-8 w-8">
<AvatarImage src="/avatars/01.png" alt="@shadcn" />
<AvatarFallback>SN</AvatarFallback>
<AvatarFallback>{shorthandUsername}</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">satnaing</p>
<p className="text-sm font-medium leading-none">{user.username}</p>
<p className="text-xs leading-none text-muted-foreground">
satnaingdev@gmail.com
{user.email}
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<Link to="/settings">
<Link to="/dashboard/settings">
Profile
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/settings">
Billing
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/settings">
<Link to="/dashboard/settings">
Settings
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</Link>
</DropdownMenuItem>
<DropdownMenuItem>New Team</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<DropdownMenuItem onClick={logout}>
Log out
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>

View File

@ -7,7 +7,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}

View File

@ -0,0 +1,167 @@
import * as React from "react";
import { CheckIcon, ChevronsUpDown } from "lucide-react";
import * as RPNInput from "react-phone-number-input";
import flags from "react-phone-number-input/flags";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from "@/components/ui/command";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
type PhoneInputProps = Omit<
React.ComponentProps<"input">,
"onChange" | "value" | "ref"
> &
Omit<RPNInput.Props<typeof RPNInput.default>, "onChange"> & {
onChange?: (value: RPNInput.Value) => void;
};
const PhoneInput: React.ForwardRefExoticComponent<PhoneInputProps> =
React.forwardRef<React.ElementRef<typeof RPNInput.default>, PhoneInputProps>(
({ className, onChange, ...props }, ref) => {
return (
<RPNInput.default
ref={ref}
className={cn("flex", className)}
flagComponent={FlagComponent}
countrySelectComponent={CountrySelect}
inputComponent={InputComponent}
smartCaret={false}
/**
* Handles the onChange event.
*
* react-phone-number-input might trigger the onChange event as undefined
* when a valid phone number is not entered. To prevent this,
* the value is coerced to an empty string.
*
* @param {E164Number | undefined} value - The entered value
*/
onChange={(value) => onChange?.(value || ("" as RPNInput.Value))}
{...props}
/>
);
}
);
PhoneInput.displayName = "PhoneInput";
const InputComponent = React.forwardRef<
HTMLInputElement,
React.ComponentProps<"input">
>(({ className, ...props }, ref) => (
<Input
className={cn("rounded-e-lg rounded-s-none", className)}
{...props}
ref={ref}
/>
));
InputComponent.displayName = "InputComponent";
type CountryEntry = { label: string; value: RPNInput.Country | undefined };
type CountrySelectProps = {
disabled?: boolean;
value: RPNInput.Country;
options: CountryEntry[];
onChange: (country: RPNInput.Country) => void;
};
const CountrySelect = ({
disabled,
value: selectedCountry,
options: countryList,
onChange
}: CountrySelectProps) => {
return (
<Popover>
<PopoverTrigger asChild>
<Button
type="button"
variant="outline"
className="flex gap-1 rounded-e-none rounded-s-lg border-r-0 px-3 focus:z-10"
disabled={disabled}>
<FlagComponent
country={selectedCountry}
countryName={selectedCountry}
/>
<ChevronsUpDown
className={cn(
"-mr-2 size-4 opacity-50",
disabled ? "hidden" : "opacity-100"
)}
/>
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0">
<Command>
<CommandInput placeholder="Search country..." />
<CommandList>
<ScrollArea className="h-72">
<CommandEmpty>No country found.</CommandEmpty>
<CommandGroup>
{countryList.map(({ value, label }) =>
value ? (
<CountrySelectOption
key={value}
country={value}
countryName={label}
selectedCountry={selectedCountry}
onChange={onChange}
/>
) : null
)}
</CommandGroup>
</ScrollArea>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
};
interface CountrySelectOptionProps extends RPNInput.FlagProps {
selectedCountry: RPNInput.Country;
onChange: (country: RPNInput.Country) => void;
}
const CountrySelectOption = ({
country,
countryName,
selectedCountry,
onChange
}: CountrySelectOptionProps) => {
return (
<CommandItem className="gap-2" onSelect={() => onChange(country)}>
<FlagComponent country={country} countryName={countryName} />
<span className="flex-1 text-sm">{countryName}</span>
<span className="text-sm text-foreground/50">{`+${RPNInput.getCountryCallingCode(country)}`}</span>
<CheckIcon
className={`ml-auto size-4 ${country === selectedCountry ? "opacity-100" : "opacity-0"}`}
/>
</CommandItem>
);
};
const FlagComponent = ({ country, countryName }: RPNInput.FlagProps) => {
const Flag = flags[country];
return (
<span className="flex h-4 w-6 overflow-hidden rounded-sm bg-foreground/20 [&_svg]:size-full">
{Flag && <Flag title={countryName} />}
</span>
);
};
export { PhoneInput };

View File

@ -4,7 +4,7 @@ import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
import { VariantProps, cva } from "class-variance-authority";
import { PanelLeft } from "lucide-react";
import { cn } from "@/lib/utils";
import { useIsMobile } from "@/hooks/use-mobile";
import { useIsMobile } from "@/hooks/useMobile";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";

View File

@ -1,69 +1,105 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { useNavigate } from "@tanstack/react-router"
import { useState } from "react"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "@tanstack/react-router";
import { useEffect, useState } from "react";
import { handleServerError } from "@/utils/handle-server-error"
import { handleServerError } from "@/utils/handle-server-error";
import { ApiError } from "@/errors/api-error";
import {
DashboardService,
LoginService,
ShopLoginAccessTokenData,
UserPublic,
UserRegister,
UserService,
UserUpdate
} from "@/client";
import { toast } from "./useToast";
const isLoggedIn = () => {
return localStorage.getItem("access_token") !== null
}
return localStorage.getItem("access_token") !== null;
};
const useAuth = () => {
const [error, setError] = useState<string | null>(null)
const navigate = useNavigate()
const queryClient = useQueryClient()
const [error, setError] = useState<string | null>(null);
const [loggedIn, setLoggedIn] = useState(isLoggedIn());
const navigate = useNavigate();
const queryClient = useQueryClient();
const { data: user } = useQuery<UserPublic | null, Error>({
queryKey: ["currentUser"],
queryFn: UsersService.readUserMe,
enabled: isLoggedIn(),
})
queryFn: DashboardService.userGetUser,
enabled: loggedIn
});
const signUpMutation = useMutation({
mutationFn: (data: UserRegister) =>
UsersService.registerUser({ requestBody: data }),
DashboardService.userRegister({ requestBody: data }),
onSuccess: () => navigate({ to: "/sign-in" }),
onError: (err: ApiError) => handleServerError(err),
onSettled: () => queryClient.invalidateQueries({ queryKey: ["users"] })
});
onSuccess: () => {
navigate({ to: "/sign-in" })
},
onError: (err: ApiError) => {
handleServerError(err)
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["users"] })
},
})
const login = async (data: AccessToken) => {
const response = await LoginService.loginAccessToken({
formData: data,
})
localStorage.setItem("access_token", response.access_token)
const login = async (data: ShopLoginAccessTokenData) => {
const response = await LoginService.dashboardLoginAccessToken({
formData: {
username: data.formData.username,
password: data.formData.password
}
});
localStorage.setItem("access_token", response.access_token);
setLoggedIn(true);
await queryClient.invalidateQueries({ queryKey: ["currentUser"] });
};
const loginMutation = useMutation({
mutationFn: login,
onSuccess: () => {
navigate({ to: "/" })
},
onError: (err: ApiError) => {
handleError(err)
},
})
onSuccess: () => navigate({ to: "/" }),
onError: (err: ApiError) => handleServerError(err)
});
const logout = () => {
localStorage.removeItem("access_token")
navigate({ to: "/sign-in" })
localStorage.removeItem("access_token");
setLoggedIn(false);
queryClient.invalidateQueries({ queryKey: ["currentUser"] });
navigate({ to: "/", replace: true, reloadDocument: true });
};
const updateAccountMutation = useMutation({
mutationFn: (data: UserUpdate) =>
UserService.userUpdateUser({ requestBody: data }),
onSuccess: () => {
toast({ title: "Account updated successfully" });
queryClient.invalidateQueries({ queryKey: ["currentUser"] });
},
onError: (err: ApiError) => handleServerError(err)
});
useEffect(() => {
const handleStorageChange = (event: StorageEvent) => {
if (event.key === "access_token") {
const tokenExists = event.newValue !== null;
setLoggedIn(tokenExists);
if (!tokenExists) {
queryClient.removeQueries({ queryKey: ["currentUser"] });
} else {
queryClient.invalidateQueries({ queryKey: ["currentUser"] });
}
}
};
window.addEventListener("storage", handleStorageChange);
return () => window.removeEventListener("storage", handleStorageChange);
}, [queryClient]);
return {
signUpMutation,
loginMutation,
updateAccountMutation,
logout,
user,
error,
resetError: () => setError(null),
}
}
resetError: () => setError(null)
};
};
export { isLoggedIn }
export default useAuth
export { isLoggedIn };
export default useAuth;

View File

@ -15,6 +15,12 @@ import { ThemeProvider } from "./context/theme-context";
import "./index.css";
// Generated Routes
import { routeTree } from "./routeTree.gen";
import { OpenAPI } from "./client";
OpenAPI.BASE = import.meta.env.VITE_API_URL;
OpenAPI.TOKEN = async () => {
return localStorage.getItem("access_token") ?? "";
};
const queryClient = new QueryClient({
defaultOptions: {

View File

@ -1,4 +1,4 @@
import { HTMLAttributes, useState } from "react";
import { HTMLAttributes } from "react";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
@ -15,6 +15,7 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PasswordInput } from "@/components/password-input";
import useAuth from "@/hooks/useAuth";
type UserAuthFormProps = HTMLAttributes<HTMLDivElement>;
@ -34,7 +35,7 @@ const formSchema = z.object({
});
export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
const [isLoading, setIsLoading] = useState(false);
const { loginMutation, resetError } = useAuth();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
@ -44,13 +45,13 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
}
});
function onSubmit(data: z.infer<typeof formSchema>) {
setIsLoading(true);
console.log(data);
setTimeout(() => {
setIsLoading(false);
}, 3000);
async function onSubmit(data: z.infer<typeof formSchema>) {
resetError();
try {
await loginMutation.mutateAsync({formData: {username: data.email, password: data.password}});
} catch {
// Error handled in useAuth hook
}
}
return (
@ -91,8 +92,8 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
</FormItem>
)}
/>
<Button className="mt-2" disabled={isLoading}>
Login
<Button className="mt-2" disabled={loginMutation.isPending}>
{loginMutation.isPending ? "Logging in..." : "Login"}
</Button>
<div className="relative my-2">

View File

@ -16,6 +16,8 @@ import { Input } from "@/components/ui/input";
import { PasswordInput } from "@/components/password-input";
import { registerUser } from "@/lib/api";
import { ApiError } from "@/errors/api-error";
import useAuth from "@/hooks/useAuth";
import { PhoneInput } from "@/components/ui/phone-number-input";
type SignUpFormProps = HTMLAttributes<HTMLDivElement>;
@ -64,6 +66,7 @@ const formSchema = z
});
export function SignUpForm({ className, ...props }: SignUpFormProps) {
const { signUpMutation } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
@ -85,12 +88,7 @@ export function SignUpForm({ className, ...props }: SignUpFormProps) {
setSuccessMessage(null);
try {
await registerUser({
username: data.username,
email: data.email,
password: data.password,
phone_number: data.phone_number
});
signUpMutation.mutate(data);
setSuccessMessage("Account created successfully!");
} catch (error: unknown) {
setError(error instanceof ApiError ? error.response : "Something went wrong.");
@ -137,7 +135,7 @@ export function SignUpForm({ className, ...props }: SignUpFormProps) {
<FormItem className="space-y-1">
<FormLabel>Phone Number</FormLabel>
<FormControl>
<Input placeholder="+1234567890" {...field} />
<PhoneInput {...field} />
</FormControl>
<FormMessage />
</FormItem>

View File

@ -1,4 +1,3 @@
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@ -33,9 +32,6 @@ export default function Dashboard() {
<Main>
<div className="mb-2 flex items-center justify-between space-y-2">
<h1 className="text-2xl font-bold tracking-tight">Dashboard</h1>
<div className="flex items-center space-x-2">
<Button>Download</Button>
</div>
</div>
<Tabs
orientation="vertical"

View File

@ -18,8 +18,6 @@ export default function MainPage() {
<Pricing />
<Faq />
<CallToAction />
{/* [Optional] Consider inserting an image or illustration here to reinforce the platforms purpose. */}
</div>
</>
);

View File

@ -1,3 +0,0 @@
export default function Nba() {
return <h1>NBA Testing</h1>;
}

View File

@ -27,7 +27,7 @@ const appText = new Map<string, string>([
["notConnected", "Not Connected"]
]);
export default function Apps() {
export default function Products() {
const [sort, setSort] = useState("ascending");
const [appType, setAppType] = useState("all");
const [searchTerm, setSearchTerm] = useState("");

View File

@ -1,86 +1,63 @@
import { z } from "zod";
import { format } from "date-fns";
import { useForm } from "react-hook-form";
import { CalendarIcon, CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import { zodResolver } from "@hookform/resolvers/zod";
import { cn } from "@/lib/utils";
import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from "@/components/ui/command";
import {
import
{
Form,
FormControl,
FormDescription,
FormField,
FormControl, FormField,
FormItem,
FormLabel,
FormMessage
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger
} from "@/components/ui/popover";
import useAuth from "@/hooks/useAuth";
const languages = [
{ label: "English", value: "en" },
{ label: "French", value: "fr" },
{ label: "German", value: "de" },
{ label: "Spanish", value: "es" },
{ label: "Portuguese", value: "pt" },
{ label: "Russian", value: "ru" },
{ label: "Japanese", value: "ja" },
{ label: "Korean", value: "ko" },
{ label: "Chinese", value: "zh" }
] as const;
// const languages = [
// { label: "English", value: "en" },
// { label: "French", value: "fr" },
// { label: "German", value: "de" },
// { label: "Spanish", value: "es" },
// { label: "Portuguese", value: "pt" },
// { label: "Russian", value: "ru" },
// { label: "Japanese", value: "ja" },
// { label: "Korean", value: "ko" },
// { label: "Chinese", value: "zh" }
// ] as const;
const accountFormSchema = z.object({
name: z
.string()
.min(2, {
message: "Name must be at least 2 characters."
})
.max(30, {
message: "Name must not be longer than 30 characters."
}),
dob: z.date({
required_error: "A date of birth is required."
}),
language: z.string({
required_error: "Please select a language."
})
username: z.string().min(3).max(30),
email: z.string().email(),
phone_number: z.string(),
// password: z.string().min(8).max(128),
first_name: z.string().optional(),
last_name: z.string().optional(),
// dob: z.date({
// required_error: "A date of birth is required."
// }),
// language: z.string({
// required_error: "Please select a language."
// })
});
type AccountFormValues = z.infer<typeof accountFormSchema>;
// This can come from your database or API.
const defaultValues: Partial<AccountFormValues> = {
name: ""
};
export function AccountForm() {
const { updateAccountMutation, user } = useAuth();
const form = useForm<AccountFormValues>({
resolver: zodResolver(accountFormSchema),
defaultValues
resolver: zodResolver(accountFormSchema)
});
function onSubmit(data: AccountFormValues) {
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
)
updateAccountMutation.mutate({
email: data.email,
// password: data.password,
password: "null",
phone_number: data.phone_number,
username: data.username,
first_name: data.first_name,
last_name: data.last_name
});
}
@ -89,125 +66,57 @@ export function AccountForm() {
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="name"
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="Your name" {...field} />
<Input placeholder="Your username" defaultValue={user?.username} {...field} />
</FormControl>
<FormDescription>
This is the name that will be displayed on your profile and in
emails.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="dob"
name="email"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Date of birth</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Button
variant={"outline"}
className={cn(
"w-[240px] pl-3 text-left font-normal",
!field.value && "text-muted-foreground"
)}>
{field.value ? (
format(field.value, "MMM d, yyyy")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
<Input placeholder="Your email" defaultValue={user?.email} {...field} />
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
disabled={(date: Date) =>
date > new Date() || date < new Date("1900-01-01")
}
/>
</PopoverContent>
</Popover>
<FormDescription>
Your date of birth is used to calculate your age.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="language"
name="phone_number"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Language</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormItem>
<FormLabel>Phone Number</FormLabel>
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-[200px] justify-between",
!field.value && "text-muted-foreground"
)}>
{field.value
? languages.find(
(language) => language.value === field.value
)?.label
: "Select language"}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
<Input placeholder="Your phone number" defaultValue={user?.phone_number} {...field} />
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search language..." />
<CommandEmpty>No language found.</CommandEmpty>
<CommandGroup>
<CommandList>
{languages.map((language) => (
<CommandItem
value={language.label}
key={language.value}
onSelect={() => {
form.setValue("language", language.value);
}}>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
language.value === field.value
? "opacity-100"
: "opacity-0"
)}
/>
{language.label}
</CommandItem>
))}
</CommandList>
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
<FormDescription>
This is the language that will be used in the dashboard.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Update account</Button>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" placeholder="New password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Update Account</Button>
</form>
</Form>
);

View File

@ -1,9 +1,9 @@
import { Outlet } from "@tanstack/react-router";
import {
IconBrowserCheck,
IconLock,
IconNotification,
IconPalette,
IconTool,
IconUser
} from "@tabler/icons-react";
import { Separator } from "@/components/ui/separator";
@ -53,26 +53,26 @@ const sidebarNavItems = [
{
title: "Profile",
icon: <IconUser size={18} />,
href: "/settings"
href: "/dashboard/settings"
},
{
title: "Account",
icon: <IconTool size={18} />,
href: "/settings/account"
title: "Security",
icon: <IconLock size={18} />,
href: "/dashboard/settings/security"
},
{
title: "Appearance",
icon: <IconPalette size={18} />,
href: "/settings/appearance"
href: "/dashboard/settings/appearance"
},
{
title: "Notifications",
icon: <IconNotification size={18} />,
href: "/settings/notifications"
href: "/dashboard/settings/notifications"
},
{
title: "Display",
icon: <IconBrowserCheck size={18} />,
href: "/settings/display"
href: "/dashboard/settings/display"
}
];

View File

@ -1,8 +1,7 @@
import { z } from "zod";
import { useFieldArray, useForm } from "react-hook-form";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Link } from "@tanstack/react-router";
import { cn } from "@/lib/utils";
import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button";
import {
@ -15,63 +14,71 @@ import {
FormMessage
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { PhoneInput } from "@/components/ui/phone-number-input";
import useAuth from "@/hooks/useAuth";
const profileFormSchema = z.object({
username: z
.string()
.min(2, {
message: "Username must be at least 2 characters."
.string({
required_error: "Please enter your desired username"
})
.max(30, {
message: "Username must not be longer than 30 characters."
.min(3, {
message: "Username must be at least 3 characters."
})
.max(64, {
message: "Username must not be longer than 64 characters."
}),
email: z
.string({
required_error: "Please select an email to display."
.string()
.email({
message: "Please enter your email address."
})
.email(),
bio: z.string().max(160).min(4),
urls: z
.array(
z.object({
value: z.string().url({ message: "Please enter a valid URL." })
phone_number: z
.string({
required_error: "Please input a valid phone number."
})
.min(2, {
message: "Phone number must be at least 2 characters."
})
.max(16, {
message: "Phone number must not be longer than 16 characters."
}),
first_name: z
.string()
.max(64, {
message: "First name must not be longer than 64 characters."
})
.optional(),
last_name: z
.string()
.max(64, {
message: "Last name must not be longer than 64 characters."
})
)
.optional()
});
type ProfileFormValues = z.infer<typeof profileFormSchema>;
// This can come from your database or API.
const defaultValues: Partial<ProfileFormValues> = {
bio: "I own a computer.",
urls: [
{ value: "https://shadcn.com" },
{ value: "http://twitter.com/shadcn" }
]
export default function ProfileForm() {
const { user, updateAccountMutation } = useAuth();
const defaultValues: ProfileFormValues = {
username: user?.username ?? "",
email: user?.email ?? "",
phone_number: user?.phone_number ?? "",
first_name: user?.first_name ?? "",
last_name: user?.last_name ?? ""
};
export default function ProfileForm() {
const form = useForm<ProfileFormValues>({
resolver: zodResolver(profileFormSchema),
defaultValues,
mode: "onChange"
});
const { fields, append } = useFieldArray({
name: "urls",
control: form.control
});
function onSubmit(data: ProfileFormValues) {
updateAccountMutation.mutate(data);
toast({
title: "You submitted the following values:",
description: (
@ -92,11 +99,11 @@ export default function ProfileForm() {
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
<Input placeholder="Username" {...field} />
</FormControl>
<FormDescription>
This is your public display name. It can be your real name or a
pseudonym. You can only change this once every 30 days.
pseudonym.
</FormDescription>
<FormMessage />
</FormItem>
@ -108,18 +115,9 @@ export default function ProfileForm() {
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
</SelectTrigger>
<Input type="email" {...field} />
</FormControl>
<SelectContent>
<SelectItem value="m@example.com">m@example.com</SelectItem>
<SelectItem value="m@google.com">m@google.com</SelectItem>
<SelectItem value="m@support.com">m@support.com</SelectItem>
</SelectContent>
</Select>
<FormDescription>
You can manage verified email addresses in your{" "}
<Link to="/">email settings</Link>.
@ -130,56 +128,44 @@ export default function ProfileForm() {
/>
<FormField
control={form.control}
name="bio"
name="first_name"
render={({ field }) => (
<FormItem>
<FormLabel>Bio</FormLabel>
<FormLabel>First Name</FormLabel>
<FormControl>
<Textarea
placeholder="Tell us a little bit about yourself"
className="resize-none"
{...field}
/>
<Input placeholder="John" {...field} />
</FormControl>
<FormDescription>
You can <span>@mention</span> other users and organizations to
link to them.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div>
{fields.map((field, index) => (
<FormField
control={form.control}
key={field.id}
name={`urls.${index}.value`}
name="last_name"
render={({ field }) => (
<FormItem>
<FormLabel className={cn(index !== 0 && "sr-only")}>
URLs
</FormLabel>
<FormDescription className={cn(index !== 0 && "sr-only")}>
Add links to your website, blog, or social media profiles.
</FormDescription>
<FormLabel>Last Name</FormLabel>
<FormControl>
<Input {...field} />
<Input placeholder="Doe" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phone_number"
render={({ field }) => (
<FormItem>
<FormLabel>Phone number</FormLabel>
<FormControl>
<PhoneInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
))}
<Button
type="button"
variant="outline"
size="sm"
className="mt-2"
onClick={() => append({ value: "" })}>
Add URL
</Button>
</div>
<Button type="submit">Update profile</Button>
</form>
</Form>

View File

@ -32,41 +32,47 @@ const authSignIn2LazyImport = createFileRoute('/(auth)/sign-in-2')()
const authForgotPasswordLazyImport = createFileRoute(
'/(auth)/forgot-password',
)()
const AuthenticatedSettingsRouteLazyImport = createFileRoute(
'/_authenticated/settings',
)()
const AuthenticatedUsersIndexLazyImport = createFileRoute(
'/_authenticated/users/',
)()
const AuthenticatedTasksIndexLazyImport = createFileRoute(
'/_authenticated/tasks/',
)()
const AuthenticatedSettingsIndexLazyImport = createFileRoute(
'/_authenticated/settings/',
)()
const AuthenticatedNbaIndexLazyImport = createFileRoute(
'/_authenticated/nba/',
)()
const AuthenticatedHelpCenterIndexLazyImport = createFileRoute(
'/_authenticated/help-center/',
)()
const AuthenticatedChatsIndexLazyImport = createFileRoute(
'/_authenticated/chats/',
const AuthenticatedDashboardSettingsRouteLazyImport = createFileRoute(
'/_authenticated/dashboard/settings',
)()
const AuthenticatedAppsIndexLazyImport = createFileRoute(
'/_authenticated/apps/',
const AuthenticatedDashboardUsersIndexLazyImport = createFileRoute(
'/_authenticated/dashboard/users/',
)()
const AuthenticatedSettingsNotificationsLazyImport = createFileRoute(
'/_authenticated/settings/notifications',
const AuthenticatedDashboardTasksIndexLazyImport = createFileRoute(
'/_authenticated/dashboard/tasks/',
)()
const AuthenticatedSettingsDisplayLazyImport = createFileRoute(
'/_authenticated/settings/display',
const AuthenticatedDashboardSettingsIndexLazyImport = createFileRoute(
'/_authenticated/dashboard/settings/',
)()
const AuthenticatedSettingsAppearanceLazyImport = createFileRoute(
'/_authenticated/settings/appearance',
const AuthenticatedDashboardSalesIndexLazyImport = createFileRoute(
'/_authenticated/dashboard/sales/',
)()
const AuthenticatedSettingsAccountLazyImport = createFileRoute(
'/_authenticated/settings/account',
const AuthenticatedDashboardProductsIndexLazyImport = createFileRoute(
'/_authenticated/dashboard/products/',
)()
const AuthenticatedDashboardChatsIndexLazyImport = createFileRoute(
'/_authenticated/dashboard/chats/',
)()
const AuthenticatedDashboardSettingsNotificationsLazyImport = createFileRoute(
'/_authenticated/dashboard/settings/notifications',
)()
const AuthenticatedDashboardSettingsDisplayLazyImport = createFileRoute(
'/_authenticated/dashboard/settings/display',
)()
const AuthenticatedDashboardSettingsAppearanceLazyImport = createFileRoute(
'/_authenticated/dashboard/settings/appearance',
)()
const AuthenticatedDashboardSettingsAccountLazyImport = createFileRoute(
'/_authenticated/dashboard/settings/account',
)()
const AuthenticatedDashboardSalesDiscountsLazyImport = createFileRoute(
'/_authenticated/dashboard/sales/discounts',
)()
const AuthenticatedDashboardSalesCouponsLazyImport = createFileRoute(
'/_authenticated/dashboard/sales/coupons',
)()
// Create/Update Routes
@ -148,15 +154,6 @@ const authForgotPasswordLazyRoute = authForgotPasswordLazyImport
import('./routes/(auth)/forgot-password.lazy').then((d) => d.Route),
)
const AuthenticatedSettingsRouteLazyRoute =
AuthenticatedSettingsRouteLazyImport.update({
id: '/settings',
path: '/settings',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/settings/route.lazy').then((d) => d.Route),
)
const authSignInRoute = authSignInImport.update({
id: '/(auth)/sign-in',
path: '/sign-in',
@ -175,41 +172,6 @@ const auth500Route = auth500Import.update({
getParentRoute: () => rootRoute,
} as any)
const AuthenticatedUsersIndexLazyRoute =
AuthenticatedUsersIndexLazyImport.update({
id: '/users/',
path: '/users/',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/users/index.lazy').then((d) => d.Route),
)
const AuthenticatedTasksIndexLazyRoute =
AuthenticatedTasksIndexLazyImport.update({
id: '/tasks/',
path: '/tasks/',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/tasks/index.lazy').then((d) => d.Route),
)
const AuthenticatedSettingsIndexLazyRoute =
AuthenticatedSettingsIndexLazyImport.update({
id: '/',
path: '/',
getParentRoute: () => AuthenticatedSettingsRouteLazyRoute,
} as any).lazy(() =>
import('./routes/_authenticated/settings/index.lazy').then((d) => d.Route),
)
const AuthenticatedNbaIndexLazyRoute = AuthenticatedNbaIndexLazyImport.update({
id: '/nba/',
path: '/nba/',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/nba/index.lazy').then((d) => d.Route),
)
const AuthenticatedHelpCenterIndexLazyRoute =
AuthenticatedHelpCenterIndexLazyImport.update({
id: '/help-center/',
@ -221,25 +183,6 @@ const AuthenticatedHelpCenterIndexLazyRoute =
),
)
const AuthenticatedChatsIndexLazyRoute =
AuthenticatedChatsIndexLazyImport.update({
id: '/chats/',
path: '/chats/',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/chats/index.lazy').then((d) => d.Route),
)
const AuthenticatedAppsIndexLazyRoute = AuthenticatedAppsIndexLazyImport.update(
{
id: '/apps/',
path: '/apps/',
getParentRoute: () => AuthenticatedRouteRoute,
} as any,
).lazy(() =>
import('./routes/_authenticated/apps/index.lazy').then((d) => d.Route),
)
const AuthenticatedDashboardIndexRoute =
AuthenticatedDashboardIndexImport.update({
id: '/dashboard/',
@ -247,46 +190,145 @@ const AuthenticatedDashboardIndexRoute =
getParentRoute: () => AuthenticatedRouteRoute,
} as any)
const AuthenticatedSettingsNotificationsLazyRoute =
AuthenticatedSettingsNotificationsLazyImport.update({
const AuthenticatedDashboardSettingsRouteLazyRoute =
AuthenticatedDashboardSettingsRouteLazyImport.update({
id: '/dashboard/settings',
path: '/dashboard/settings',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/dashboard/settings/route.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedDashboardUsersIndexLazyRoute =
AuthenticatedDashboardUsersIndexLazyImport.update({
id: '/dashboard/users/',
path: '/dashboard/users/',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/dashboard/users/index.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedDashboardTasksIndexLazyRoute =
AuthenticatedDashboardTasksIndexLazyImport.update({
id: '/dashboard/tasks/',
path: '/dashboard/tasks/',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/dashboard/tasks/index.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedDashboardSettingsIndexLazyRoute =
AuthenticatedDashboardSettingsIndexLazyImport.update({
id: '/',
path: '/',
getParentRoute: () => AuthenticatedDashboardSettingsRouteLazyRoute,
} as any).lazy(() =>
import('./routes/_authenticated/dashboard/settings/index.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedDashboardSalesIndexLazyRoute =
AuthenticatedDashboardSalesIndexLazyImport.update({
id: '/dashboard/sales/',
path: '/dashboard/sales/',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/dashboard/sales/index.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedDashboardProductsIndexLazyRoute =
AuthenticatedDashboardProductsIndexLazyImport.update({
id: '/dashboard/products/',
path: '/dashboard/products/',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/dashboard/products/index.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedDashboardChatsIndexLazyRoute =
AuthenticatedDashboardChatsIndexLazyImport.update({
id: '/dashboard/chats/',
path: '/dashboard/chats/',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/dashboard/chats/index.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedDashboardSettingsNotificationsLazyRoute =
AuthenticatedDashboardSettingsNotificationsLazyImport.update({
id: '/notifications',
path: '/notifications',
getParentRoute: () => AuthenticatedSettingsRouteLazyRoute,
getParentRoute: () => AuthenticatedDashboardSettingsRouteLazyRoute,
} as any).lazy(() =>
import('./routes/_authenticated/settings/notifications.lazy').then(
(d) => d.Route,
),
import(
'./routes/_authenticated/dashboard/settings/notifications.lazy'
).then((d) => d.Route),
)
const AuthenticatedSettingsDisplayLazyRoute =
AuthenticatedSettingsDisplayLazyImport.update({
const AuthenticatedDashboardSettingsDisplayLazyRoute =
AuthenticatedDashboardSettingsDisplayLazyImport.update({
id: '/display',
path: '/display',
getParentRoute: () => AuthenticatedSettingsRouteLazyRoute,
getParentRoute: () => AuthenticatedDashboardSettingsRouteLazyRoute,
} as any).lazy(() =>
import('./routes/_authenticated/settings/display.lazy').then(
import('./routes/_authenticated/dashboard/settings/display.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedSettingsAppearanceLazyRoute =
AuthenticatedSettingsAppearanceLazyImport.update({
const AuthenticatedDashboardSettingsAppearanceLazyRoute =
AuthenticatedDashboardSettingsAppearanceLazyImport.update({
id: '/appearance',
path: '/appearance',
getParentRoute: () => AuthenticatedSettingsRouteLazyRoute,
getParentRoute: () => AuthenticatedDashboardSettingsRouteLazyRoute,
} as any).lazy(() =>
import('./routes/_authenticated/settings/appearance.lazy').then(
import('./routes/_authenticated/dashboard/settings/appearance.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedSettingsAccountLazyRoute =
AuthenticatedSettingsAccountLazyImport.update({
const AuthenticatedDashboardSettingsAccountLazyRoute =
AuthenticatedDashboardSettingsAccountLazyImport.update({
id: '/account',
path: '/account',
getParentRoute: () => AuthenticatedSettingsRouteLazyRoute,
getParentRoute: () => AuthenticatedDashboardSettingsRouteLazyRoute,
} as any).lazy(() =>
import('./routes/_authenticated/settings/account.lazy').then(
import('./routes/_authenticated/dashboard/settings/account.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedDashboardSalesDiscountsLazyRoute =
AuthenticatedDashboardSalesDiscountsLazyImport.update({
id: '/dashboard/sales/discounts',
path: '/dashboard/sales/discounts',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/dashboard/sales/discounts.lazy').then(
(d) => d.Route,
),
)
const AuthenticatedDashboardSalesCouponsLazyRoute =
AuthenticatedDashboardSalesCouponsLazyImport.update({
id: '/dashboard/sales/coupons',
path: '/dashboard/sales/coupons',
getParentRoute: () => AuthenticatedRouteRoute,
} as any).lazy(() =>
import('./routes/_authenticated/dashboard/sales/coupons.lazy').then(
(d) => d.Route,
),
)
@ -330,13 +372,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof authSignInImport
parentRoute: typeof rootRoute
}
'/_authenticated/settings': {
id: '/_authenticated/settings'
path: '/settings'
fullPath: '/settings'
preLoaderRoute: typeof AuthenticatedSettingsRouteLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/(auth)/forgot-password': {
id: '/(auth)/forgot-password'
path: '/forgot-password'
@ -393,33 +428,12 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof errors503LazyImport
parentRoute: typeof rootRoute
}
'/_authenticated/settings/account': {
id: '/_authenticated/settings/account'
path: '/account'
fullPath: '/settings/account'
preLoaderRoute: typeof AuthenticatedSettingsAccountLazyImport
parentRoute: typeof AuthenticatedSettingsRouteLazyImport
}
'/_authenticated/settings/appearance': {
id: '/_authenticated/settings/appearance'
path: '/appearance'
fullPath: '/settings/appearance'
preLoaderRoute: typeof AuthenticatedSettingsAppearanceLazyImport
parentRoute: typeof AuthenticatedSettingsRouteLazyImport
}
'/_authenticated/settings/display': {
id: '/_authenticated/settings/display'
path: '/display'
fullPath: '/settings/display'
preLoaderRoute: typeof AuthenticatedSettingsDisplayLazyImport
parentRoute: typeof AuthenticatedSettingsRouteLazyImport
}
'/_authenticated/settings/notifications': {
id: '/_authenticated/settings/notifications'
path: '/notifications'
fullPath: '/settings/notifications'
preLoaderRoute: typeof AuthenticatedSettingsNotificationsLazyImport
parentRoute: typeof AuthenticatedSettingsRouteLazyImport
'/_authenticated/dashboard/settings': {
id: '/_authenticated/dashboard/settings'
path: '/dashboard/settings'
fullPath: '/dashboard/settings'
preLoaderRoute: typeof AuthenticatedDashboardSettingsRouteLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/dashboard/': {
id: '/_authenticated/dashboard/'
@ -428,20 +442,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedDashboardIndexImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/apps/': {
id: '/_authenticated/apps/'
path: '/apps'
fullPath: '/apps'
preLoaderRoute: typeof AuthenticatedAppsIndexLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/chats/': {
id: '/_authenticated/chats/'
path: '/chats'
fullPath: '/chats'
preLoaderRoute: typeof AuthenticatedChatsIndexLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/help-center/': {
id: '/_authenticated/help-center/'
path: '/help-center'
@ -449,32 +449,88 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedHelpCenterIndexLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/nba/': {
id: '/_authenticated/nba/'
path: '/nba'
fullPath: '/nba'
preLoaderRoute: typeof AuthenticatedNbaIndexLazyImport
'/_authenticated/dashboard/sales/coupons': {
id: '/_authenticated/dashboard/sales/coupons'
path: '/dashboard/sales/coupons'
fullPath: '/dashboard/sales/coupons'
preLoaderRoute: typeof AuthenticatedDashboardSalesCouponsLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/settings/': {
id: '/_authenticated/settings/'
'/_authenticated/dashboard/sales/discounts': {
id: '/_authenticated/dashboard/sales/discounts'
path: '/dashboard/sales/discounts'
fullPath: '/dashboard/sales/discounts'
preLoaderRoute: typeof AuthenticatedDashboardSalesDiscountsLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/dashboard/settings/account': {
id: '/_authenticated/dashboard/settings/account'
path: '/account'
fullPath: '/dashboard/settings/account'
preLoaderRoute: typeof AuthenticatedDashboardSettingsAccountLazyImport
parentRoute: typeof AuthenticatedDashboardSettingsRouteLazyImport
}
'/_authenticated/dashboard/settings/appearance': {
id: '/_authenticated/dashboard/settings/appearance'
path: '/appearance'
fullPath: '/dashboard/settings/appearance'
preLoaderRoute: typeof AuthenticatedDashboardSettingsAppearanceLazyImport
parentRoute: typeof AuthenticatedDashboardSettingsRouteLazyImport
}
'/_authenticated/dashboard/settings/display': {
id: '/_authenticated/dashboard/settings/display'
path: '/display'
fullPath: '/dashboard/settings/display'
preLoaderRoute: typeof AuthenticatedDashboardSettingsDisplayLazyImport
parentRoute: typeof AuthenticatedDashboardSettingsRouteLazyImport
}
'/_authenticated/dashboard/settings/notifications': {
id: '/_authenticated/dashboard/settings/notifications'
path: '/notifications'
fullPath: '/dashboard/settings/notifications'
preLoaderRoute: typeof AuthenticatedDashboardSettingsNotificationsLazyImport
parentRoute: typeof AuthenticatedDashboardSettingsRouteLazyImport
}
'/_authenticated/dashboard/chats/': {
id: '/_authenticated/dashboard/chats/'
path: '/dashboard/chats'
fullPath: '/dashboard/chats'
preLoaderRoute: typeof AuthenticatedDashboardChatsIndexLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/dashboard/products/': {
id: '/_authenticated/dashboard/products/'
path: '/dashboard/products'
fullPath: '/dashboard/products'
preLoaderRoute: typeof AuthenticatedDashboardProductsIndexLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/dashboard/sales/': {
id: '/_authenticated/dashboard/sales/'
path: '/dashboard/sales'
fullPath: '/dashboard/sales'
preLoaderRoute: typeof AuthenticatedDashboardSalesIndexLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/dashboard/settings/': {
id: '/_authenticated/dashboard/settings/'
path: '/'
fullPath: '/settings/'
preLoaderRoute: typeof AuthenticatedSettingsIndexLazyImport
parentRoute: typeof AuthenticatedSettingsRouteLazyImport
fullPath: '/dashboard/settings/'
preLoaderRoute: typeof AuthenticatedDashboardSettingsIndexLazyImport
parentRoute: typeof AuthenticatedDashboardSettingsRouteLazyImport
}
'/_authenticated/tasks/': {
id: '/_authenticated/tasks/'
path: '/tasks'
fullPath: '/tasks'
preLoaderRoute: typeof AuthenticatedTasksIndexLazyImport
'/_authenticated/dashboard/tasks/': {
id: '/_authenticated/dashboard/tasks/'
path: '/dashboard/tasks'
fullPath: '/dashboard/tasks'
preLoaderRoute: typeof AuthenticatedDashboardTasksIndexLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
'/_authenticated/users/': {
id: '/_authenticated/users/'
path: '/users'
fullPath: '/users'
preLoaderRoute: typeof AuthenticatedUsersIndexLazyImport
'/_authenticated/dashboard/users/': {
id: '/_authenticated/dashboard/users/'
path: '/dashboard/users'
fullPath: '/dashboard/users'
preLoaderRoute: typeof AuthenticatedDashboardUsersIndexLazyImport
parentRoute: typeof AuthenticatedRouteImport
}
}
@ -482,53 +538,65 @@ declare module '@tanstack/react-router' {
// Create and export the route tree
interface AuthenticatedSettingsRouteLazyRouteChildren {
AuthenticatedSettingsAccountLazyRoute: typeof AuthenticatedSettingsAccountLazyRoute
AuthenticatedSettingsAppearanceLazyRoute: typeof AuthenticatedSettingsAppearanceLazyRoute
AuthenticatedSettingsDisplayLazyRoute: typeof AuthenticatedSettingsDisplayLazyRoute
AuthenticatedSettingsNotificationsLazyRoute: typeof AuthenticatedSettingsNotificationsLazyRoute
AuthenticatedSettingsIndexLazyRoute: typeof AuthenticatedSettingsIndexLazyRoute
interface AuthenticatedDashboardSettingsRouteLazyRouteChildren {
AuthenticatedDashboardSettingsAccountLazyRoute: typeof AuthenticatedDashboardSettingsAccountLazyRoute
AuthenticatedDashboardSettingsAppearanceLazyRoute: typeof AuthenticatedDashboardSettingsAppearanceLazyRoute
AuthenticatedDashboardSettingsDisplayLazyRoute: typeof AuthenticatedDashboardSettingsDisplayLazyRoute
AuthenticatedDashboardSettingsNotificationsLazyRoute: typeof AuthenticatedDashboardSettingsNotificationsLazyRoute
AuthenticatedDashboardSettingsIndexLazyRoute: typeof AuthenticatedDashboardSettingsIndexLazyRoute
}
const AuthenticatedSettingsRouteLazyRouteChildren: AuthenticatedSettingsRouteLazyRouteChildren =
const AuthenticatedDashboardSettingsRouteLazyRouteChildren: AuthenticatedDashboardSettingsRouteLazyRouteChildren =
{
AuthenticatedSettingsAccountLazyRoute:
AuthenticatedSettingsAccountLazyRoute,
AuthenticatedSettingsAppearanceLazyRoute:
AuthenticatedSettingsAppearanceLazyRoute,
AuthenticatedSettingsDisplayLazyRoute:
AuthenticatedSettingsDisplayLazyRoute,
AuthenticatedSettingsNotificationsLazyRoute:
AuthenticatedSettingsNotificationsLazyRoute,
AuthenticatedSettingsIndexLazyRoute: AuthenticatedSettingsIndexLazyRoute,
AuthenticatedDashboardSettingsAccountLazyRoute:
AuthenticatedDashboardSettingsAccountLazyRoute,
AuthenticatedDashboardSettingsAppearanceLazyRoute:
AuthenticatedDashboardSettingsAppearanceLazyRoute,
AuthenticatedDashboardSettingsDisplayLazyRoute:
AuthenticatedDashboardSettingsDisplayLazyRoute,
AuthenticatedDashboardSettingsNotificationsLazyRoute:
AuthenticatedDashboardSettingsNotificationsLazyRoute,
AuthenticatedDashboardSettingsIndexLazyRoute:
AuthenticatedDashboardSettingsIndexLazyRoute,
}
const AuthenticatedSettingsRouteLazyRouteWithChildren =
AuthenticatedSettingsRouteLazyRoute._addFileChildren(
AuthenticatedSettingsRouteLazyRouteChildren,
const AuthenticatedDashboardSettingsRouteLazyRouteWithChildren =
AuthenticatedDashboardSettingsRouteLazyRoute._addFileChildren(
AuthenticatedDashboardSettingsRouteLazyRouteChildren,
)
interface AuthenticatedRouteRouteChildren {
AuthenticatedSettingsRouteLazyRoute: typeof AuthenticatedSettingsRouteLazyRouteWithChildren
AuthenticatedDashboardSettingsRouteLazyRoute: typeof AuthenticatedDashboardSettingsRouteLazyRouteWithChildren
AuthenticatedDashboardIndexRoute: typeof AuthenticatedDashboardIndexRoute
AuthenticatedAppsIndexLazyRoute: typeof AuthenticatedAppsIndexLazyRoute
AuthenticatedChatsIndexLazyRoute: typeof AuthenticatedChatsIndexLazyRoute
AuthenticatedHelpCenterIndexLazyRoute: typeof AuthenticatedHelpCenterIndexLazyRoute
AuthenticatedNbaIndexLazyRoute: typeof AuthenticatedNbaIndexLazyRoute
AuthenticatedTasksIndexLazyRoute: typeof AuthenticatedTasksIndexLazyRoute
AuthenticatedUsersIndexLazyRoute: typeof AuthenticatedUsersIndexLazyRoute
AuthenticatedDashboardSalesCouponsLazyRoute: typeof AuthenticatedDashboardSalesCouponsLazyRoute
AuthenticatedDashboardSalesDiscountsLazyRoute: typeof AuthenticatedDashboardSalesDiscountsLazyRoute
AuthenticatedDashboardChatsIndexLazyRoute: typeof AuthenticatedDashboardChatsIndexLazyRoute
AuthenticatedDashboardProductsIndexLazyRoute: typeof AuthenticatedDashboardProductsIndexLazyRoute
AuthenticatedDashboardSalesIndexLazyRoute: typeof AuthenticatedDashboardSalesIndexLazyRoute
AuthenticatedDashboardTasksIndexLazyRoute: typeof AuthenticatedDashboardTasksIndexLazyRoute
AuthenticatedDashboardUsersIndexLazyRoute: typeof AuthenticatedDashboardUsersIndexLazyRoute
}
const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = {
AuthenticatedSettingsRouteLazyRoute:
AuthenticatedSettingsRouteLazyRouteWithChildren,
AuthenticatedDashboardSettingsRouteLazyRoute:
AuthenticatedDashboardSettingsRouteLazyRouteWithChildren,
AuthenticatedDashboardIndexRoute: AuthenticatedDashboardIndexRoute,
AuthenticatedAppsIndexLazyRoute: AuthenticatedAppsIndexLazyRoute,
AuthenticatedChatsIndexLazyRoute: AuthenticatedChatsIndexLazyRoute,
AuthenticatedHelpCenterIndexLazyRoute: AuthenticatedHelpCenterIndexLazyRoute,
AuthenticatedNbaIndexLazyRoute: AuthenticatedNbaIndexLazyRoute,
AuthenticatedTasksIndexLazyRoute: AuthenticatedTasksIndexLazyRoute,
AuthenticatedUsersIndexLazyRoute: AuthenticatedUsersIndexLazyRoute,
AuthenticatedDashboardSalesCouponsLazyRoute:
AuthenticatedDashboardSalesCouponsLazyRoute,
AuthenticatedDashboardSalesDiscountsLazyRoute:
AuthenticatedDashboardSalesDiscountsLazyRoute,
AuthenticatedDashboardChatsIndexLazyRoute:
AuthenticatedDashboardChatsIndexLazyRoute,
AuthenticatedDashboardProductsIndexLazyRoute:
AuthenticatedDashboardProductsIndexLazyRoute,
AuthenticatedDashboardSalesIndexLazyRoute:
AuthenticatedDashboardSalesIndexLazyRoute,
AuthenticatedDashboardTasksIndexLazyRoute:
AuthenticatedDashboardTasksIndexLazyRoute,
AuthenticatedDashboardUsersIndexLazyRoute:
AuthenticatedDashboardUsersIndexLazyRoute,
}
const AuthenticatedRouteRouteWithChildren =
@ -540,7 +608,6 @@ export interface FileRoutesByFullPath {
'/500': typeof errors500LazyRoute
'/otp': typeof authOtpRoute
'/sign-in': typeof authSignInRoute
'/settings': typeof AuthenticatedSettingsRouteLazyRouteWithChildren
'/forgot-password': typeof authForgotPasswordLazyRoute
'/sign-in-2': typeof authSignIn2LazyRoute
'/sign-up': typeof authSignUpLazyRoute
@ -548,18 +615,21 @@ export interface FileRoutesByFullPath {
'/403': typeof errors403LazyRoute
'/404': typeof errors404LazyRoute
'/503': typeof errors503LazyRoute
'/settings/account': typeof AuthenticatedSettingsAccountLazyRoute
'/settings/appearance': typeof AuthenticatedSettingsAppearanceLazyRoute
'/settings/display': typeof AuthenticatedSettingsDisplayLazyRoute
'/settings/notifications': typeof AuthenticatedSettingsNotificationsLazyRoute
'/dashboard/settings': typeof AuthenticatedDashboardSettingsRouteLazyRouteWithChildren
'/dashboard': typeof AuthenticatedDashboardIndexRoute
'/apps': typeof AuthenticatedAppsIndexLazyRoute
'/chats': typeof AuthenticatedChatsIndexLazyRoute
'/help-center': typeof AuthenticatedHelpCenterIndexLazyRoute
'/nba': typeof AuthenticatedNbaIndexLazyRoute
'/settings/': typeof AuthenticatedSettingsIndexLazyRoute
'/tasks': typeof AuthenticatedTasksIndexLazyRoute
'/users': typeof AuthenticatedUsersIndexLazyRoute
'/dashboard/sales/coupons': typeof AuthenticatedDashboardSalesCouponsLazyRoute
'/dashboard/sales/discounts': typeof AuthenticatedDashboardSalesDiscountsLazyRoute
'/dashboard/settings/account': typeof AuthenticatedDashboardSettingsAccountLazyRoute
'/dashboard/settings/appearance': typeof AuthenticatedDashboardSettingsAppearanceLazyRoute
'/dashboard/settings/display': typeof AuthenticatedDashboardSettingsDisplayLazyRoute
'/dashboard/settings/notifications': typeof AuthenticatedDashboardSettingsNotificationsLazyRoute
'/dashboard/chats': typeof AuthenticatedDashboardChatsIndexLazyRoute
'/dashboard/products': typeof AuthenticatedDashboardProductsIndexLazyRoute
'/dashboard/sales': typeof AuthenticatedDashboardSalesIndexLazyRoute
'/dashboard/settings/': typeof AuthenticatedDashboardSettingsIndexLazyRoute
'/dashboard/tasks': typeof AuthenticatedDashboardTasksIndexLazyRoute
'/dashboard/users': typeof AuthenticatedDashboardUsersIndexLazyRoute
}
export interface FileRoutesByTo {
@ -575,18 +645,20 @@ export interface FileRoutesByTo {
'/403': typeof errors403LazyRoute
'/404': typeof errors404LazyRoute
'/503': typeof errors503LazyRoute
'/settings/account': typeof AuthenticatedSettingsAccountLazyRoute
'/settings/appearance': typeof AuthenticatedSettingsAppearanceLazyRoute
'/settings/display': typeof AuthenticatedSettingsDisplayLazyRoute
'/settings/notifications': typeof AuthenticatedSettingsNotificationsLazyRoute
'/dashboard': typeof AuthenticatedDashboardIndexRoute
'/apps': typeof AuthenticatedAppsIndexLazyRoute
'/chats': typeof AuthenticatedChatsIndexLazyRoute
'/help-center': typeof AuthenticatedHelpCenterIndexLazyRoute
'/nba': typeof AuthenticatedNbaIndexLazyRoute
'/settings': typeof AuthenticatedSettingsIndexLazyRoute
'/tasks': typeof AuthenticatedTasksIndexLazyRoute
'/users': typeof AuthenticatedUsersIndexLazyRoute
'/dashboard/sales/coupons': typeof AuthenticatedDashboardSalesCouponsLazyRoute
'/dashboard/sales/discounts': typeof AuthenticatedDashboardSalesDiscountsLazyRoute
'/dashboard/settings/account': typeof AuthenticatedDashboardSettingsAccountLazyRoute
'/dashboard/settings/appearance': typeof AuthenticatedDashboardSettingsAppearanceLazyRoute
'/dashboard/settings/display': typeof AuthenticatedDashboardSettingsDisplayLazyRoute
'/dashboard/settings/notifications': typeof AuthenticatedDashboardSettingsNotificationsLazyRoute
'/dashboard/chats': typeof AuthenticatedDashboardChatsIndexLazyRoute
'/dashboard/products': typeof AuthenticatedDashboardProductsIndexLazyRoute
'/dashboard/sales': typeof AuthenticatedDashboardSalesIndexLazyRoute
'/dashboard/settings': typeof AuthenticatedDashboardSettingsIndexLazyRoute
'/dashboard/tasks': typeof AuthenticatedDashboardTasksIndexLazyRoute
'/dashboard/users': typeof AuthenticatedDashboardUsersIndexLazyRoute
}
export interface FileRoutesById {
@ -596,7 +668,6 @@ export interface FileRoutesById {
'/(auth)/500': typeof auth500Route
'/(auth)/otp': typeof authOtpRoute
'/(auth)/sign-in': typeof authSignInRoute
'/_authenticated/settings': typeof AuthenticatedSettingsRouteLazyRouteWithChildren
'/(auth)/forgot-password': typeof authForgotPasswordLazyRoute
'/(auth)/sign-in-2': typeof authSignIn2LazyRoute
'/(auth)/sign-up': typeof authSignUpLazyRoute
@ -605,18 +676,21 @@ export interface FileRoutesById {
'/(errors)/404': typeof errors404LazyRoute
'/(errors)/500': typeof errors500LazyRoute
'/(errors)/503': typeof errors503LazyRoute
'/_authenticated/settings/account': typeof AuthenticatedSettingsAccountLazyRoute
'/_authenticated/settings/appearance': typeof AuthenticatedSettingsAppearanceLazyRoute
'/_authenticated/settings/display': typeof AuthenticatedSettingsDisplayLazyRoute
'/_authenticated/settings/notifications': typeof AuthenticatedSettingsNotificationsLazyRoute
'/_authenticated/dashboard/settings': typeof AuthenticatedDashboardSettingsRouteLazyRouteWithChildren
'/_authenticated/dashboard/': typeof AuthenticatedDashboardIndexRoute
'/_authenticated/apps/': typeof AuthenticatedAppsIndexLazyRoute
'/_authenticated/chats/': typeof AuthenticatedChatsIndexLazyRoute
'/_authenticated/help-center/': typeof AuthenticatedHelpCenterIndexLazyRoute
'/_authenticated/nba/': typeof AuthenticatedNbaIndexLazyRoute
'/_authenticated/settings/': typeof AuthenticatedSettingsIndexLazyRoute
'/_authenticated/tasks/': typeof AuthenticatedTasksIndexLazyRoute
'/_authenticated/users/': typeof AuthenticatedUsersIndexLazyRoute
'/_authenticated/dashboard/sales/coupons': typeof AuthenticatedDashboardSalesCouponsLazyRoute
'/_authenticated/dashboard/sales/discounts': typeof AuthenticatedDashboardSalesDiscountsLazyRoute
'/_authenticated/dashboard/settings/account': typeof AuthenticatedDashboardSettingsAccountLazyRoute
'/_authenticated/dashboard/settings/appearance': typeof AuthenticatedDashboardSettingsAppearanceLazyRoute
'/_authenticated/dashboard/settings/display': typeof AuthenticatedDashboardSettingsDisplayLazyRoute
'/_authenticated/dashboard/settings/notifications': typeof AuthenticatedDashboardSettingsNotificationsLazyRoute
'/_authenticated/dashboard/chats/': typeof AuthenticatedDashboardChatsIndexLazyRoute
'/_authenticated/dashboard/products/': typeof AuthenticatedDashboardProductsIndexLazyRoute
'/_authenticated/dashboard/sales/': typeof AuthenticatedDashboardSalesIndexLazyRoute
'/_authenticated/dashboard/settings/': typeof AuthenticatedDashboardSettingsIndexLazyRoute
'/_authenticated/dashboard/tasks/': typeof AuthenticatedDashboardTasksIndexLazyRoute
'/_authenticated/dashboard/users/': typeof AuthenticatedDashboardUsersIndexLazyRoute
}
export interface FileRouteTypes {
@ -627,7 +701,6 @@ export interface FileRouteTypes {
| '/500'
| '/otp'
| '/sign-in'
| '/settings'
| '/forgot-password'
| '/sign-in-2'
| '/sign-up'
@ -635,18 +708,21 @@ export interface FileRouteTypes {
| '/403'
| '/404'
| '/503'
| '/settings/account'
| '/settings/appearance'
| '/settings/display'
| '/settings/notifications'
| '/dashboard/settings'
| '/dashboard'
| '/apps'
| '/chats'
| '/help-center'
| '/nba'
| '/settings/'
| '/tasks'
| '/users'
| '/dashboard/sales/coupons'
| '/dashboard/sales/discounts'
| '/dashboard/settings/account'
| '/dashboard/settings/appearance'
| '/dashboard/settings/display'
| '/dashboard/settings/notifications'
| '/dashboard/chats'
| '/dashboard/products'
| '/dashboard/sales'
| '/dashboard/settings/'
| '/dashboard/tasks'
| '/dashboard/users'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
@ -661,18 +737,20 @@ export interface FileRouteTypes {
| '/403'
| '/404'
| '/503'
| '/settings/account'
| '/settings/appearance'
| '/settings/display'
| '/settings/notifications'
| '/dashboard'
| '/apps'
| '/chats'
| '/help-center'
| '/nba'
| '/settings'
| '/tasks'
| '/users'
| '/dashboard/sales/coupons'
| '/dashboard/sales/discounts'
| '/dashboard/settings/account'
| '/dashboard/settings/appearance'
| '/dashboard/settings/display'
| '/dashboard/settings/notifications'
| '/dashboard/chats'
| '/dashboard/products'
| '/dashboard/sales'
| '/dashboard/settings'
| '/dashboard/tasks'
| '/dashboard/users'
id:
| '__root__'
| '/'
@ -680,7 +758,6 @@ export interface FileRouteTypes {
| '/(auth)/500'
| '/(auth)/otp'
| '/(auth)/sign-in'
| '/_authenticated/settings'
| '/(auth)/forgot-password'
| '/(auth)/sign-in-2'
| '/(auth)/sign-up'
@ -689,18 +766,21 @@ export interface FileRouteTypes {
| '/(errors)/404'
| '/(errors)/500'
| '/(errors)/503'
| '/_authenticated/settings/account'
| '/_authenticated/settings/appearance'
| '/_authenticated/settings/display'
| '/_authenticated/settings/notifications'
| '/_authenticated/dashboard/settings'
| '/_authenticated/dashboard/'
| '/_authenticated/apps/'
| '/_authenticated/chats/'
| '/_authenticated/help-center/'
| '/_authenticated/nba/'
| '/_authenticated/settings/'
| '/_authenticated/tasks/'
| '/_authenticated/users/'
| '/_authenticated/dashboard/sales/coupons'
| '/_authenticated/dashboard/sales/discounts'
| '/_authenticated/dashboard/settings/account'
| '/_authenticated/dashboard/settings/appearance'
| '/_authenticated/dashboard/settings/display'
| '/_authenticated/dashboard/settings/notifications'
| '/_authenticated/dashboard/chats/'
| '/_authenticated/dashboard/products/'
| '/_authenticated/dashboard/sales/'
| '/_authenticated/dashboard/settings/'
| '/_authenticated/dashboard/tasks/'
| '/_authenticated/dashboard/users/'
fileRoutesById: FileRoutesById
}
@ -767,14 +847,16 @@ export const routeTree = rootRoute
"/_authenticated": {
"filePath": "_authenticated/route.tsx",
"children": [
"/_authenticated/settings",
"/_authenticated/dashboard/settings",
"/_authenticated/dashboard/",
"/_authenticated/apps/",
"/_authenticated/chats/",
"/_authenticated/help-center/",
"/_authenticated/nba/",
"/_authenticated/tasks/",
"/_authenticated/users/"
"/_authenticated/dashboard/sales/coupons",
"/_authenticated/dashboard/sales/discounts",
"/_authenticated/dashboard/chats/",
"/_authenticated/dashboard/products/",
"/_authenticated/dashboard/sales/",
"/_authenticated/dashboard/tasks/",
"/_authenticated/dashboard/users/"
]
},
"/(auth)/500": {
@ -786,17 +868,6 @@ export const routeTree = rootRoute
"/(auth)/sign-in": {
"filePath": "(auth)/sign-in.tsx"
},
"/_authenticated/settings": {
"filePath": "_authenticated/settings/route.lazy.tsx",
"parent": "/_authenticated",
"children": [
"/_authenticated/settings/account",
"/_authenticated/settings/appearance",
"/_authenticated/settings/display",
"/_authenticated/settings/notifications",
"/_authenticated/settings/"
]
},
"/(auth)/forgot-password": {
"filePath": "(auth)/forgot-password.lazy.tsx"
},
@ -821,52 +892,71 @@ export const routeTree = rootRoute
"/(errors)/503": {
"filePath": "(errors)/503.lazy.tsx"
},
"/_authenticated/settings/account": {
"filePath": "_authenticated/settings/account.lazy.tsx",
"parent": "/_authenticated/settings"
},
"/_authenticated/settings/appearance": {
"filePath": "_authenticated/settings/appearance.lazy.tsx",
"parent": "/_authenticated/settings"
},
"/_authenticated/settings/display": {
"filePath": "_authenticated/settings/display.lazy.tsx",
"parent": "/_authenticated/settings"
},
"/_authenticated/settings/notifications": {
"filePath": "_authenticated/settings/notifications.lazy.tsx",
"parent": "/_authenticated/settings"
"/_authenticated/dashboard/settings": {
"filePath": "_authenticated/dashboard/settings/route.lazy.tsx",
"parent": "/_authenticated",
"children": [
"/_authenticated/dashboard/settings/account",
"/_authenticated/dashboard/settings/appearance",
"/_authenticated/dashboard/settings/display",
"/_authenticated/dashboard/settings/notifications",
"/_authenticated/dashboard/settings/"
]
},
"/_authenticated/dashboard/": {
"filePath": "_authenticated/dashboard/index.tsx",
"parent": "/_authenticated"
},
"/_authenticated/apps/": {
"filePath": "_authenticated/apps/index.lazy.tsx",
"parent": "/_authenticated"
},
"/_authenticated/chats/": {
"filePath": "_authenticated/chats/index.lazy.tsx",
"parent": "/_authenticated"
},
"/_authenticated/help-center/": {
"filePath": "_authenticated/help-center/index.lazy.tsx",
"parent": "/_authenticated"
},
"/_authenticated/nba/": {
"filePath": "_authenticated/nba/index.lazy.tsx",
"/_authenticated/dashboard/sales/coupons": {
"filePath": "_authenticated/dashboard/sales/coupons.lazy.tsx",
"parent": "/_authenticated"
},
"/_authenticated/settings/": {
"filePath": "_authenticated/settings/index.lazy.tsx",
"parent": "/_authenticated/settings"
},
"/_authenticated/tasks/": {
"filePath": "_authenticated/tasks/index.lazy.tsx",
"/_authenticated/dashboard/sales/discounts": {
"filePath": "_authenticated/dashboard/sales/discounts.lazy.tsx",
"parent": "/_authenticated"
},
"/_authenticated/users/": {
"filePath": "_authenticated/users/index.lazy.tsx",
"/_authenticated/dashboard/settings/account": {
"filePath": "_authenticated/dashboard/settings/account.lazy.tsx",
"parent": "/_authenticated/dashboard/settings"
},
"/_authenticated/dashboard/settings/appearance": {
"filePath": "_authenticated/dashboard/settings/appearance.lazy.tsx",
"parent": "/_authenticated/dashboard/settings"
},
"/_authenticated/dashboard/settings/display": {
"filePath": "_authenticated/dashboard/settings/display.lazy.tsx",
"parent": "/_authenticated/dashboard/settings"
},
"/_authenticated/dashboard/settings/notifications": {
"filePath": "_authenticated/dashboard/settings/notifications.lazy.tsx",
"parent": "/_authenticated/dashboard/settings"
},
"/_authenticated/dashboard/chats/": {
"filePath": "_authenticated/dashboard/chats/index.lazy.tsx",
"parent": "/_authenticated"
},
"/_authenticated/dashboard/products/": {
"filePath": "_authenticated/dashboard/products/index.lazy.tsx",
"parent": "/_authenticated"
},
"/_authenticated/dashboard/sales/": {
"filePath": "_authenticated/dashboard/sales/index.lazy.tsx",
"parent": "/_authenticated"
},
"/_authenticated/dashboard/settings/": {
"filePath": "_authenticated/dashboard/settings/index.lazy.tsx",
"parent": "/_authenticated/dashboard/settings"
},
"/_authenticated/dashboard/tasks/": {
"filePath": "_authenticated/dashboard/tasks/index.lazy.tsx",
"parent": "/_authenticated"
},
"/_authenticated/dashboard/users/": {
"filePath": "_authenticated/dashboard/users/index.lazy.tsx",
"parent": "/_authenticated"
}
}

View File

@ -1,6 +1,14 @@
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, redirect } from "@tanstack/react-router";
import SignIn from "@/pages/auth/sign-in";
import { isLoggedIn } from "@/hooks/useAuth";
export const Route = createFileRoute("/(auth)/sign-in")({
component: SignIn
component: SignIn,
beforeLoad: async() => {
if(isLoggedIn()) {
throw redirect({
to:"/dashboard"
})
}
}
});

View File

@ -1,6 +0,0 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import Apps from "@/pages/apps";
export const Route = createLazyFileRoute("/_authenticated/apps/")({
component: Apps
});

View File

@ -1,6 +1,6 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import Chats from "@/pages/chats";
export const Route = createLazyFileRoute("/_authenticated/chats/")({
export const Route = createLazyFileRoute("/_authenticated/dashboard/chats/")({
component: Chats
});

View File

@ -0,0 +1,6 @@
import Products from '@/pages/products'
import { createLazyFileRoute } from '@tanstack/react-router'
export const Route = createLazyFileRoute('/_authenticated/dashboard/products/')({
component: Products,
})

View File

@ -0,0 +1,11 @@
import { createLazyFileRoute } from '@tanstack/react-router'
export const Route = createLazyFileRoute(
'/_authenticated/dashboard/sales/coupons',
)({
component: RouteComponent,
})
function RouteComponent() {
return <div>Hello "/_authenticated/dashboard/discounts/coupons"!</div>
}

View File

@ -0,0 +1,11 @@
import { createLazyFileRoute } from '@tanstack/react-router'
export const Route = createLazyFileRoute(
'/_authenticated/dashboard/sales/discounts',
)({
component: RouteComponent,
})
function RouteComponent() {
return <div>Hello "/_authenticated/dashboard/discounts/discounts"!</div>
}

View File

@ -0,0 +1,11 @@
import { createLazyFileRoute } from '@tanstack/react-router'
export const Route = createLazyFileRoute(
'/_authenticated/dashboard/sales/',
)({
component: RouteComponent,
})
function RouteComponent() {
return <div>Hello "/_authenticated/dashboard/discounts/"!</div>
}

View File

@ -1,6 +1,6 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import SettingsAccount from "@/pages/settings/account";
export const Route = createLazyFileRoute("/_authenticated/settings/account")({
export const Route = createLazyFileRoute("/_authenticated/dashboard/settings/account")({
component: SettingsAccount
});

View File

@ -1,6 +1,6 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import SettingsAppearance from "@/pages/settings/appearance";
export const Route = createLazyFileRoute("/_authenticated/settings/appearance")(
export const Route = createLazyFileRoute("/_authenticated/dashboard/settings/appearance")(
{ component: SettingsAppearance }
);

View File

@ -1,6 +1,6 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import SettingsDisplay from "@/pages/settings/display";
export const Route = createLazyFileRoute("/_authenticated/settings/display")({
export const Route = createLazyFileRoute("/_authenticated/dashboard/settings/display")({
component: SettingsDisplay
});

View File

@ -1,6 +1,6 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import SettingsProfile from "@/pages/settings/profile";
export const Route = createLazyFileRoute("/_authenticated/settings/")({
export const Route = createLazyFileRoute("/_authenticated/dashboard/settings/")({
component: SettingsProfile
});

View File

@ -2,7 +2,7 @@ import { createLazyFileRoute } from "@tanstack/react-router";
import SettingsNotifications from "@/pages/settings/notifications";
export const Route = createLazyFileRoute(
"/_authenticated/settings/notifications"
"/_authenticated/dashboard/settings/notifications"
)({
component: SettingsNotifications
});

View File

@ -1,6 +1,6 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import Settings from "@/pages/settings";
export const Route = createLazyFileRoute("/_authenticated/settings")({
export const Route = createLazyFileRoute("/_authenticated/dashboard/settings")({
component: Settings
});

View File

@ -1,6 +1,6 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import Tasks from "@/pages/tasks";
export const Route = createLazyFileRoute("/_authenticated/tasks/")({
export const Route = createLazyFileRoute("/_authenticated/dashboard/tasks/")({
component: Tasks
});

View File

@ -1,6 +1,6 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import Users from "@/pages/users";
export const Route = createLazyFileRoute("/_authenticated/users/")({
export const Route = createLazyFileRoute("/_authenticated/dashboard/users/")({
component: Users
});

View File

@ -1,6 +0,0 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import Nba from "@/pages/nba";
export const Route = createLazyFileRoute("/_authenticated/nba/")({
component: Nba
});

View File

@ -5,12 +5,13 @@ import { SearchProvider } from "@/context/search-context";
import { SidebarProvider } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/layout/app-sidebar";
import SkipToMain from "@/components/skip-to-main";
import { isLoggedIn } from "@/hooks/useAuth";
export const Route = createFileRoute("/_authenticated")({
beforeLoad: async ({ location }) => {
if (false) {
if(!isLoggedIn()) {
throw redirect({
to: "/401",
to: "/sign-in",
search: {
redirect: location.href
}

11
scripts/generate-client.sh Executable file
View File

@ -0,0 +1,11 @@
#! /usr/bin/env bash
set -e
set -x
cd backend
python -c "import app.main; import json; print(json.dumps(app.main.app.openapi()))" > ../openapi.json
cd ..
mv openapi.json frontend/
cd frontend
npm run generate-client