diff --git a/backend/app/__init__.py b/backend/app/__init__.py index d71070f..218f478 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -5,7 +5,8 @@ from .extensions import init_mongo, init_jwt, init_redis from .errors.handlers import register_error_handlers from .routes.usage_routes import usage_ns from .routes.auth_routes import auth_ns -from .routes.billing_routes import billing_ns +from .routes.billing_routes import billing_ns +from .routes.users_routes import user_ns from flask_cors import CORS def create_app(): @@ -37,7 +38,8 @@ def create_app(): api.add_namespace(usage_ns, path='/usage') api.add_namespace(auth_ns, path='/auth') - api.add_namespace(billing_ns, path='/billing') + api.add_namespace(billing_ns, path='/billing') + api.add_namespace(user_ns, path='/users') register_error_handlers(app) diff --git a/backend/app/db/models.py b/backend/app/db/models.py index ffaac16..ce77618 100644 --- a/backend/app/db/models.py +++ b/backend/app/db/models.py @@ -1,6 +1,8 @@ from flask_bcrypt import generate_password_hash, check_password_hash from flask import current_app from pymongo import MongoClient +from bson.objectid import ObjectId +from bson.errors import InvalidId class UserModel: def __init__(self): @@ -13,7 +15,33 @@ class UserModel: return self.collection.insert_one(user) def find_by_email(self, email): - return self.collection.find_one({"email": email}) + return self.collection.find_one({"email": email}) + + def get_user_by_id(self, user_id): + object_id = ObjectId(user_id) + return self.collection.find_one({"_id": object_id},{"email": 1, "roles": 1,}) + + def update_user(self, user_id, update_data: dict): + object_id = ObjectId(user_id) + + if "password" in update_data: + update_data["password"] = generate_password_hash(update_data["password"]).decode('utf-8') + + result = self.collection.update_one( + {"_id": object_id}, + {"$set": update_data} + ) + + return result.modified_count > 0 + + def delete_user(self, user_id): + object_id = ObjectId(user_id) + result = self.collection.delete_one({"_id": object_id}) + return result.deleted_count > 0 + + def list_users(self): + users = self.collection.find({}, {"email": 1, "roles": 1,}) + return users def verify_password(self, hashed_password, plain_password): return check_password_hash(hashed_password, plain_password) diff --git a/backend/app/docs/auth_models.py b/backend/app/docs/auth_models.py index 547f888..19e2671 100644 --- a/backend/app/docs/auth_models.py +++ b/backend/app/docs/auth_models.py @@ -2,7 +2,13 @@ from flask_restx import fields, Namespace auth_ns = Namespace('auth', description='Authentication') -signup_model = auth_ns.model('Signup', { +signup_model = auth_ns.model('SignUp', { 'email': fields.String(required=True), - 'password': fields.String(required=True) + 'password': fields.String(required=True), + 'roles': fields.List(fields.String, required=False, default=["user"]) +}) + +signin_model = auth_ns.model('SignIn', { + 'email': fields.String(required=True), + 'password': fields.String(required=True) }) \ No newline at end of file diff --git a/backend/app/docs/role_models.py b/backend/app/docs/role_models.py new file mode 100644 index 0000000..c7c29fc --- /dev/null +++ b/backend/app/docs/role_models.py @@ -0,0 +1,8 @@ +from flask_restx import Resource, Namespace, fields + +role_ns = Namespace('roles', description="Role management") + +role_model = role_ns.model('Role', { + 'email': fields.String(required=True, description='User email'), + 'roles': fields.List(fields.String, required=True, description='List of roles') +}) \ No newline at end of file diff --git a/backend/app/docs/user_models.py b/backend/app/docs/user_models.py new file mode 100644 index 0000000..c9256a8 --- /dev/null +++ b/backend/app/docs/user_models.py @@ -0,0 +1,11 @@ +from flask_restx import fields, Namespace + +user_ns = Namespace('user', description='Users') + +update_user = user_ns.model('Update', { + 'email': fields.String(required=False, description='User email'), + 'password': fields.String(required=False, description='User password'), + 'roles': fields.String(required=False, description='User roles') +}) + + \ No newline at end of file diff --git a/backend/app/errors/handlers.py b/backend/app/errors/handlers.py index 3f4089f..114988c 100644 --- a/backend/app/errors/handlers.py +++ b/backend/app/errors/handlers.py @@ -3,15 +3,21 @@ from flask import jsonify from werkzeug.exceptions import HTTPException from pydantic import ValidationError import traceback +from bson.errors import InvalidId + def register_error_handlers(app): @app.errorhandler(ValidationError) def handle_validation_error(e): return jsonify({"error": e.errors()}), 400 + @app.errorhandler(InvalidId) + def handle_invalid_id_error(e): + return jsonify({"error": "Invalid ID format"}), 400 + @app.errorhandler(HTTPException) def handle_http_exception(e): - return jsonify({"errror": e.description}), e.code + return jsonify({"error": e.description}), e.code @app.errorhandler(Exception) def handle_unexpected_exception(e): diff --git a/backend/app/routes/auth_routes.py b/backend/app/routes/auth_routes.py index 50f8ff5..fe86c11 100644 --- a/backend/app/routes/auth_routes.py +++ b/backend/app/routes/auth_routes.py @@ -1,31 +1,50 @@ -from flask_restx import Namespace, Resource, fields -from flask import request +from flask_restx import Resource +from flask import request, current_app from flask_jwt_extended import create_access_token from app.db.models import UserModel -from app.docs.auth_models import auth_ns, signup_model +from app.docs.auth_models import auth_ns, signup_model, signin_model +from app.schemas.auth_sigin_schema import SigInRequest +from app.schemas.auth_sigup_schema import SigUpRequest @auth_ns.route('/signup') class SignUp(Resource): @auth_ns.expect(signup_model) def post(self): data = request.get_json() - user_model = UserModel() - if user_model.find_by_email(data['email']): - return {'message': 'User already exists'}, 400 - user_model.create_user(data['email'], data['password']) - return {'message': 'success'}, 201 + validated = SigUpRequest(**data) + + roles = data.get("roles", []) + + user_model = UserModel() + if user_model.find_by_email(validated.email): + return {'message': 'User already exists'}, 400 + + result = user_model.create_user(validated.email, validated.password) + + user_model.update_user(result.inserted_id, {"email": validated.email, "roles": roles}) + + return {'message': 'success'}, 201 @auth_ns.route('/login') class Login(Resource): - @auth_ns.expect(signup_model) + @auth_ns.expect(signin_model) def post(self): data = request.get_json() + + validated = SigInRequest(**data) + user_model = UserModel() - user = user_model.find_by_email(data['email']) + user = user_model.find_by_email(validated.email) if not user or not user_model.verify_password(user['password'], data['password']): - return {'message': 'Invalid credentials'}, 401 + return {'message': 'Invalid credentials'}, 401 + + roles = user.get("roles", []) if user else [] + + access_token = create_access_token( + identity=user['email'], + additional_claims={"roles": roles} + ) - access_token = create_access_token(identity=user['email']) return {'access_token': access_token}, 200 diff --git a/backend/app/routes/billing_routes.py b/backend/app/routes/billing_routes.py index 2be2713..0c070d0 100644 --- a/backend/app/routes/billing_routes.py +++ b/backend/app/routes/billing_routes.py @@ -1,11 +1,10 @@ -from flask_restx import Namespace, Resource, fields -from flask import request, current_app -import requests -from bson import json_util -from app.db.models import UserModel +from flask_restx import Resource +from flask import request +import requests from app.docs.biling_models import billing_ns, product_model, update_price_model from app.config import Config - +from app.utils.role_required import role_required +from flask_jwt_extended import jwt_required BILLING_API_URL = Config.BILLING_API_URL HEADERS = { @@ -17,6 +16,8 @@ HEADERS = { @billing_ns.route('/product') class CreateProduct(Resource): @billing_ns.expect(product_model) + @jwt_required() + @role_required('admin', 'user') def post(self): data = request.get_json() @@ -27,6 +28,8 @@ class CreateProduct(Resource): @billing_ns.route('/product/') class UpdateProduct(Resource): @billing_ns.expect(update_price_model) + @jwt_required() + @role_required('admin', 'user') def patch(self, product_id): data = request.get_json() @@ -36,6 +39,8 @@ class UpdateProduct(Resource): @billing_ns.route('/products') class ListProducts(Resource): + @jwt_required() + @role_required('admin', 'user') def get(self): response = requests.get(url=f'{BILLING_API_URL}/billing/products', headers=HEADERS) return response.json(), response.status_code \ No newline at end of file diff --git a/backend/app/routes/usage_routes.py b/backend/app/routes/usage_routes.py index 4962540..36fde75 100644 --- a/backend/app/routes/usage_routes.py +++ b/backend/app/routes/usage_routes.py @@ -1,5 +1,4 @@ -import os -import hashlib +import os from bson import json_util from flask_restx import Resource from flask_jwt_extended import jwt_required @@ -17,6 +16,7 @@ from app.docs.usage_models import ( transcription_data_query_params, usage_ns) from app.utils.current_date import is_current_date from app.utils.hash_key import set_hash_key +from app.utils.role_required import role_required TMP_DIR = '/tmp' @@ -28,6 +28,7 @@ class TranscriptionExport(Resource): @usage_ns.response(400, 'Validation error') @usage_ns.response(404, 'File not found') @jwt_required() + @role_required('admin', 'user') def get(self): """ Export transcription report in XLSX. @@ -84,6 +85,7 @@ class TranscriptionUsageData(Resource): @usage_ns.response(200, 'success') @usage_ns.response(400, 'Validation error') @jwt_required() + @role_required('admin', 'user') def get(self): """ Get transcription report data. @@ -123,6 +125,7 @@ class TranscriptionModelPrices(Resource): @usage_ns.response(200, 'success') @usage_ns.response(400, 'Validation error') @jwt_required() + @role_required('admin', 'user') def get(self): """ Get model pricing and supplier information @@ -161,6 +164,7 @@ class UpdateUsageCost(Resource): @usage_ns.response(200, 'success') @usage_ns.response(400, 'Validation error') @jwt_required() + @role_required('admin', 'user') def patch(self): """ Updates the total cost of using products by company @@ -190,6 +194,7 @@ class UpdateModelPrice(Resource): @usage_ns.response(200, 'Sucess') @usage_ns.response(400, 'Validation error') @jwt_required() + @role_required('admin', 'user') def patch(self, id): """ Updates the price of a specific model by ID. diff --git a/backend/app/routes/users_routes.py b/backend/app/routes/users_routes.py new file mode 100644 index 0000000..166ad11 --- /dev/null +++ b/backend/app/routes/users_routes.py @@ -0,0 +1,75 @@ +from flask_restx import Resource +from app.docs.user_models import user_ns, update_user +from flask import current_app, request +from bson import json_util +from app.db.models import UserModel +from app.schemas.update_user_schema import UpdateUserRequest +from app.utils.role_required import role_required +from flask_jwt_extended import jwt_required + +@user_ns.route('') +@user_ns.doc(security='Bearer Auth') +@user_ns.response(200, 'success') +class Users(Resource): + + @jwt_required() + @role_required('admin', 'user') + def get(self): + user_model = UserModel() + users = user_model.list_users() + return current_app.response_class( + response=json_util.dumps({"success": True, "data": users}), + mimetype='application/json' + ) + + +@user_ns.route('/') +@user_ns.doc(security='Bearer Auth') +@user_ns.response(200, 'success') +@user_ns.response(400, 'Validation error') +class User(Resource): + def __init__(self, api=None, *args, **kwargs): + super().__init__(api, *args, **kwargs) + self.user_model = UserModel() + + @jwt_required() + @role_required('admin', 'user') + def get(self, user_id): + if user := self.user_model.get_user_by_id(user_id): + + return current_app.response_class( + response=json_util.dumps({"success": True, "user": user}), + mimetype='application/json' + ) + + return {"success": False, 'message': 'User not found'}, 404 + + + @user_ns.expect(update_user) + @jwt_required() + @role_required('admin', 'user') + def patch(self, user_id): + data = request.get_json() + + validated = UpdateUserRequest(**data) + + if not self.user_model.get_user_by_id(user_id): + return {"success": False, 'message': 'User not found'}, 404 + + update_data = validated.model_dump(exclude_none=True) + + self.user_model.update_user(user_id, update_data) + + return {"success": True, 'message': f'User {user_id} updated!'}, 200 + + + @jwt_required() + @role_required('admin', 'user') + def delete(self, user_id): + + if not self.user_model.get_user_by_id(user_id): + return {"success": False, 'message': 'User not found'}, 404 + + self.user_model.delete_user(user_id) + + return {"success": True, 'message': f'User {user_id} deleted!'}, 200 \ No newline at end of file diff --git a/backend/app/schemas/auth_sigin_schema.py b/backend/app/schemas/auth_sigin_schema.py new file mode 100644 index 0000000..a5ea8fc --- /dev/null +++ b/backend/app/schemas/auth_sigin_schema.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + +class SigInRequest(BaseModel): + email: str + password: str \ No newline at end of file diff --git a/backend/app/schemas/auth_sigup_schema.py b/backend/app/schemas/auth_sigup_schema.py new file mode 100644 index 0000000..e961489 --- /dev/null +++ b/backend/app/schemas/auth_sigup_schema.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel +from typing import List, Literal + +class SigUpRequest(BaseModel): + email: str + password: str + roles: List[Literal["admin", "user"]] \ No newline at end of file diff --git a/backend/app/schemas/update_user_schema.py b/backend/app/schemas/update_user_schema.py new file mode 100644 index 0000000..f451bbb --- /dev/null +++ b/backend/app/schemas/update_user_schema.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel +from typing import List, Optional, Literal + +class UpdateUserRequest(BaseModel): + email: Optional[str] = None + password: Optional[str] = None + roles: Optional[List[str]] = None \ No newline at end of file diff --git a/backend/app/utils/role_required.py b/backend/app/utils/role_required.py new file mode 100644 index 0000000..e7c4d8e --- /dev/null +++ b/backend/app/utils/role_required.py @@ -0,0 +1,16 @@ +from functools import wraps +from flask_jwt_extended import get_jwt +from flask import abort + + +def role_required(*required_roles): + def wrapper(fn): + @wraps(fn) + def decorator(*args, **kwargs): + claims = get_jwt() + user_roles = claims.get("roles", []) + if not any(role in user_roles for role in required_roles): + abort(403, description="Access forbidden: insufficient role") + return fn(*args, **kwargs) + return decorator + return wrapper diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx index 139cd8a..8d96c89 100644 --- a/frontend/app/dashboard/page.tsx +++ b/frontend/app/dashboard/page.tsx @@ -8,22 +8,38 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { LogOut } from "lucide-react" import TranscriptionTable from "@/components/transcription-table" import ModelPricesTable from "@/components/model-prices-table" -import CostUpdateForm from "@/components/cost-update-form" import ProductManagement from "@/components/product-management" +import UserManagement from "@/components/user-management" +import { isAdmin, isTokenExpired, getCurrentUserEmail, getUserRoles } from "@/lib/auth" export default function Dashboard() { const [isAuthenticated, setIsAuthenticated] = useState(false) const [isLoading, setIsLoading] = useState(true) + const [userIsAdmin, setUserIsAdmin] = useState(false) + const [userEmail, setUserEmail] = useState(null) + const [userRoles, setUserRoles] = useState([]) const router = useRouter() useEffect(() => { const token = localStorage.getItem("access_token") - if (!token) { + + if (!token || isTokenExpired()) { + // Token não existe ou expirou + localStorage.removeItem("access_token") router.push("/") - } else { - setIsAuthenticated(true) - setIsLoading(false) + return } + + // Token válido, obter informações do usuário + const email = getCurrentUserEmail() + const roles = getUserRoles() + const adminStatus = isAdmin() + + setUserEmail(email) + setUserRoles(roles) + setUserIsAdmin(adminStatus) + setIsAuthenticated(true) + setIsLoading(false) }, [router]) const handleLogout = () => { @@ -48,7 +64,15 @@ export default function Dashboard() {
-

Dashboard - Custo de Produtos

+
+

Dashboard - Sistema de Transcrição

+ {userEmail && ( +

+ Logado como: {userEmail} + {userRoles.length > 0 && ({userRoles.join(", ")})} +

+ )} +
diff --git a/frontend/components/ui/checkbox.tsx b/frontend/components/ui/checkbox.tsx new file mode 100644 index 0000000..5c40f15 --- /dev/null +++ b/frontend/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/frontend/components/ui_old/alert.tsx b/frontend/components/ui_old/alert.tsx deleted file mode 100644 index 1421354..0000000 --- a/frontend/components/ui_old/alert.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const alertVariants = cva( - "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", - { - variants: { - variant: { - default: "bg-card text-card-foreground", - destructive: - "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -function Alert({ - className, - variant, - ...props -}: React.ComponentProps<"div"> & VariantProps) { - return ( -
- ) -} - -function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function AlertDescription({ - className, - ...props -}: React.ComponentProps<"div">) { - return ( -
- ) -} - -export { Alert, AlertTitle, AlertDescription } diff --git a/frontend/components/ui_old/badge.tsx b/frontend/components/ui_old/badge.tsx deleted file mode 100644 index 0205413..0000000 --- a/frontend/components/ui_old/badge.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const badgeVariants = cva( - "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", - { - variants: { - variant: { - default: - "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", - secondary: - "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", - destructive: - "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", - outline: - "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -function Badge({ - className, - variant, - asChild = false, - ...props -}: React.ComponentProps<"span"> & - VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : "span" - - return ( - - ) -} - -export { Badge, badgeVariants } diff --git a/frontend/components/ui_old/button.tsx b/frontend/components/ui_old/button.tsx deleted file mode 100644 index a2df8dc..0000000 --- a/frontend/components/ui_old/button.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", - { - variants: { - variant: { - default: - "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", - destructive: - "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", - outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", - secondary: - "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", - ghost: - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", - sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) - -function Button({ - className, - variant, - size, - asChild = false, - ...props -}: React.ComponentProps<"button"> & - VariantProps & { - asChild?: boolean - }) { - const Comp = asChild ? Slot : "button" - - return ( - - ) -} - -export { Button, buttonVariants } diff --git a/frontend/components/ui_old/card.tsx b/frontend/components/ui_old/card.tsx deleted file mode 100644 index d05bbc6..0000000 --- a/frontend/components/ui_old/card.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import * as React from "react" - -import { cn } from "@/lib/utils" - -function Card({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardHeader({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardDescription({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardAction({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardContent({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function CardFooter({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -export { - Card, - CardHeader, - CardFooter, - CardTitle, - CardAction, - CardDescription, - CardContent, -} diff --git a/frontend/components/ui_old/input.tsx b/frontend/components/ui_old/input.tsx deleted file mode 100644 index 03295ca..0000000 --- a/frontend/components/ui_old/input.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import * as React from "react" - -import { cn } from "@/lib/utils" - -function Input({ className, type, ...props }: React.ComponentProps<"input">) { - return ( - - ) -} - -export { Input } diff --git a/frontend/components/ui_old/label.tsx b/frontend/components/ui_old/label.tsx deleted file mode 100644 index fb5fbc3..0000000 --- a/frontend/components/ui_old/label.tsx +++ /dev/null @@ -1,24 +0,0 @@ -"use client" - -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" - -import { cn } from "@/lib/utils" - -function Label({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -export { Label } diff --git a/frontend/components/ui_old/select.tsx b/frontend/components/ui_old/select.tsx deleted file mode 100644 index dcbbc0c..0000000 --- a/frontend/components/ui_old/select.tsx +++ /dev/null @@ -1,185 +0,0 @@ -"use client" - -import * as React from "react" -import * as SelectPrimitive from "@radix-ui/react-select" -import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" - -import { cn } from "@/lib/utils" - -function Select({ - ...props -}: React.ComponentProps) { - return -} - -function SelectGroup({ - ...props -}: React.ComponentProps) { - return -} - -function SelectValue({ - ...props -}: React.ComponentProps) { - return -} - -function SelectTrigger({ - className, - size = "default", - children, - ...props -}: React.ComponentProps & { - size?: "sm" | "default" -}) { - return ( - - {children} - - - - - ) -} - -function SelectContent({ - className, - children, - position = "popper", - ...props -}: React.ComponentProps) { - return ( - - - - - {children} - - - - - ) -} - -function SelectLabel({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function SelectItem({ - className, - children, - ...props -}: React.ComponentProps) { - return ( - - - - - - - {children} - - ) -} - -function SelectSeparator({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function SelectScrollUpButton({ - className, - ...props -}: React.ComponentProps) { - return ( - - - - ) -} - -function SelectScrollDownButton({ - className, - ...props -}: React.ComponentProps) { - return ( - - - - ) -} - -export { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectScrollDownButton, - SelectScrollUpButton, - SelectSeparator, - SelectTrigger, - SelectValue, -} diff --git a/frontend/components/ui_old/table.tsx b/frontend/components/ui_old/table.tsx deleted file mode 100644 index 51b74dd..0000000 --- a/frontend/components/ui_old/table.tsx +++ /dev/null @@ -1,116 +0,0 @@ -"use client" - -import * as React from "react" - -import { cn } from "@/lib/utils" - -function Table({ className, ...props }: React.ComponentProps<"table">) { - return ( -
- - - ) -} - -function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { - return ( - - ) -} - -function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { - return ( - - ) -} - -function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { - return ( - tr]:last:border-b-0", - className - )} - {...props} - /> - ) -} - -function TableRow({ className, ...props }: React.ComponentProps<"tr">) { - return ( - - ) -} - -function TableHead({ className, ...props }: React.ComponentProps<"th">) { - return ( -
[role=checkbox]]:translate-y-[2px]", - className - )} - {...props} - /> - ) -} - -function TableCell({ className, ...props }: React.ComponentProps<"td">) { - return ( - [role=checkbox]]:translate-y-[2px]", - className - )} - {...props} - /> - ) -} - -function TableCaption({ - className, - ...props -}: React.ComponentProps<"caption">) { - return ( -
- ) -} - -export { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, -} diff --git a/frontend/components/ui_old/tabs.tsx b/frontend/components/ui_old/tabs.tsx deleted file mode 100644 index 497ba5e..0000000 --- a/frontend/components/ui_old/tabs.tsx +++ /dev/null @@ -1,66 +0,0 @@ -"use client" - -import * as React from "react" -import * as TabsPrimitive from "@radix-ui/react-tabs" - -import { cn } from "@/lib/utils" - -function Tabs({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function TabsList({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function TabsTrigger({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function TabsContent({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/frontend/components/ui_old/textarea.tsx b/frontend/components/ui_old/textarea.tsx deleted file mode 100644 index 7f21b5e..0000000 --- a/frontend/components/ui_old/textarea.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from "react" - -import { cn } from "@/lib/utils" - -function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { - return ( -