v1.4: Segurança multi-tenant, file serving via API e UX humanizada
- Validação cross-tenant no login e rotas protegidas
- File serving via /api/files/{bucket}/{path} (eliminação DNS)
- Mensagens de erro humanizadas inline (sem pop-ups)
- Middleware tenant detection via headers customizados
- Upload de logos retorna URLs via API
- README atualizado com changelog v1.4 completo
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { InputHTMLAttributes, forwardRef, useState } from "react";
|
||||
import { InputHTMLAttributes, forwardRef, useState, ReactNode } from "react";
|
||||
import { EyeIcon, EyeSlashIcon, ExclamationCircleIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
error?: string;
|
||||
helperText?: string;
|
||||
leftIcon?: string;
|
||||
rightIcon?: string;
|
||||
leftIcon?: ReactNode;
|
||||
rightIcon?: ReactNode;
|
||||
onRightIconClick?: () => void;
|
||||
}
|
||||
|
||||
@@ -41,26 +42,26 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
)}
|
||||
<div className="relative">
|
||||
{leftIcon && (
|
||||
<i
|
||||
className={`${leftIcon} absolute left-3.5 top-1/2 -translate-y-1/2 text-[#7D7D7D] dark:text-gray-400 text-[20px]`}
|
||||
/>
|
||||
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-[#7D7D7D] dark:text-gray-400 w-5 h-5">
|
||||
{leftIcon}
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
type={inputType}
|
||||
className={`
|
||||
w-full px-3.5 py-3 text-[14px] font-normal
|
||||
border rounded-md bg-white dark:bg-gray-700 dark:text-white
|
||||
placeholder:text-zinc-500 dark:placeholder:text-gray-400
|
||||
transition-all
|
||||
w-full px-4 py-2.5 text-sm font-normal
|
||||
border rounded-lg bg-white dark:bg-gray-800 dark:text-white
|
||||
placeholder:text-gray-400 dark:placeholder:text-gray-500
|
||||
transition-all duration-200
|
||||
${leftIcon ? "pl-11" : ""}
|
||||
${isPassword || rightIcon ? "pr-11" : ""}
|
||||
${error
|
||||
? "border-red-500 focus:border-red-500"
|
||||
: "border-zinc-200 dark:border-gray-600 focus:border-brand-500"
|
||||
? "border-red-500 focus:border-red-500 focus:ring-4 focus:ring-red-500/10"
|
||||
: "border-gray-200 dark:border-gray-700 focus:border-brand-500 focus:ring-4 focus:ring-brand-500/10"
|
||||
}
|
||||
outline-none ring-0 focus:ring-0 shadow-none focus:shadow-none
|
||||
disabled:bg-zinc-100 disabled:cursor-not-allowed
|
||||
outline-none
|
||||
disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed
|
||||
${className}
|
||||
`}
|
||||
{...props}
|
||||
@@ -71,9 +72,11 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-900 transition-colors cursor-pointer"
|
||||
>
|
||||
<i
|
||||
className={`${showPassword ? "ri-eye-off-line" : "ri-eye-line"} text-[20px]`}
|
||||
/>
|
||||
{showPassword ? (
|
||||
<EyeSlashIcon className="w-5 h-5" />
|
||||
) : (
|
||||
<EyeIcon className="w-5 h-5" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{!isPassword && rightIcon && (
|
||||
@@ -82,13 +85,13 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
onClick={onRightIconClick}
|
||||
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-900 transition-colors cursor-pointer"
|
||||
>
|
||||
<i className={`${rightIcon} text-[20px]`} />
|
||||
<div className="w-5 h-5">{rightIcon}</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{error && (
|
||||
<p className="mt-1 text-[13px] text-red-500 flex items-center gap-1">
|
||||
<i className="ri-error-warning-line" />
|
||||
<ExclamationCircleIcon className="w-4 h-4" />
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user