Refactor auth #65
@@ -26,11 +26,12 @@
|
|||||||
name: 'aqua';
|
name: 'aqua';
|
||||||
default: true;
|
default: true;
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
--color-primary: hsl(217 91% 60%);
|
/* Azul principal (ligeiramente mais escuro que o anterior) */
|
||||||
|
--color-primary: hsl(217 91% 55%);
|
||||||
--color-primary-content: hsl(0 0% 100%);
|
--color-primary-content: hsl(0 0% 100%);
|
||||||
--color-secondary: hsl(217 91% 60%);
|
--color-secondary: hsl(217 91% 55%);
|
||||||
--color-secondary-content: hsl(0 0% 100%);
|
--color-secondary-content: hsl(0 0% 100%);
|
||||||
--color-accent: hsl(217 91% 60%);
|
--color-accent: hsl(217 91% 55%);
|
||||||
--color-accent-content: hsl(0 0% 100%);
|
--color-accent-content: hsl(0 0% 100%);
|
||||||
--color-neutral: hsl(217 20% 17%);
|
--color-neutral: hsl(217 20% 17%);
|
||||||
--color-neutral-content: hsl(0 0% 100%);
|
--color-neutral-content: hsl(0 0% 100%);
|
||||||
@@ -62,11 +63,11 @@
|
|||||||
@plugin 'daisyui/theme' {
|
@plugin 'daisyui/theme' {
|
||||||
name: 'sgse-blue';
|
name: 'sgse-blue';
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
--color-primary: hsl(217 91% 60%);
|
--color-primary: hsl(217 91% 55%);
|
||||||
--color-primary-content: hsl(0 0% 100%);
|
--color-primary-content: hsl(0 0% 100%);
|
||||||
--color-secondary: hsl(217 91% 60%);
|
--color-secondary: hsl(217 91% 55%);
|
||||||
--color-secondary-content: hsl(0 0% 100%);
|
--color-secondary-content: hsl(0 0% 100%);
|
||||||
--color-accent: hsl(217 91% 60%);
|
--color-accent: hsl(217 91% 55%);
|
||||||
--color-accent-content: hsl(0 0% 100%);
|
--color-accent-content: hsl(0 0% 100%);
|
||||||
--color-neutral: hsl(217 20% 17%);
|
--color-neutral: hsl(217 20% 17%);
|
||||||
--color-neutral-content: hsl(0 0% 100%);
|
--color-neutral-content: hsl(0 0% 100%);
|
||||||
@@ -266,17 +267,19 @@
|
|||||||
@plugin 'daisyui/theme' {
|
@plugin 'daisyui/theme' {
|
||||||
name: 'sgse-corporate';
|
name: 'sgse-corporate';
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
--color-primary: hsl(217 91% 60%);
|
--color-primary: hsl(217 91% 55%);
|
||||||
--color-primary-content: hsl(0 0% 100%);
|
--color-primary-content: hsl(0 0% 100%);
|
||||||
--color-secondary: hsl(217 91% 60%);
|
--color-secondary: hsl(217 91% 55%);
|
||||||
--color-secondary-content: hsl(0 0% 100%);
|
--color-secondary-content: hsl(0 0% 100%);
|
||||||
--color-accent: hsl(217 91% 60%);
|
--color-accent: hsl(217 91% 55%);
|
||||||
--color-accent-content: hsl(0 0% 100%);
|
--color-accent-content: hsl(0 0% 100%);
|
||||||
--color-neutral: hsl(217 30% 15%);
|
--color-neutral: hsl(217 30% 15%);
|
||||||
--color-neutral-content: hsl(0 0% 100%);
|
--color-neutral-content: hsl(0 0% 100%);
|
||||||
--color-base-100: hsl(217 30% 10%);
|
/* Aproxima do fundo do login (Tailwind slate-900 = #0f172a) */
|
||||||
--color-base-200: hsl(217 30% 15%);
|
--color-base-100: hsl(222 47% 11%);
|
||||||
--color-base-300: hsl(217 30% 20%);
|
/* Escala de contraste (slate-800 / slate-700 aproximados) */
|
||||||
|
--color-base-200: hsl(215 28% 17%);
|
||||||
|
--color-base-300: hsl(215 25% 23%);
|
||||||
--color-base-content: hsl(217 10% 90%);
|
--color-base-content: hsl(217 10% 90%);
|
||||||
--color-info: hsl(217 91% 60%);
|
--color-info: hsl(217 91% 60%);
|
||||||
--color-info-content: hsl(0 0% 100%);
|
--color-info-content: hsl(0 0% 100%);
|
||||||
@@ -300,11 +303,11 @@
|
|||||||
@plugin 'daisyui/theme' {
|
@plugin 'daisyui/theme' {
|
||||||
name: 'light';
|
name: 'light';
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
--color-primary: hsl(217 91% 60%);
|
--color-primary: hsl(217 91% 55%);
|
||||||
--color-primary-content: hsl(0 0% 100%);
|
--color-primary-content: hsl(0 0% 100%);
|
||||||
--color-secondary: hsl(217 91% 60%);
|
--color-secondary: hsl(217 91% 55%);
|
||||||
--color-secondary-content: hsl(0 0% 100%);
|
--color-secondary-content: hsl(0 0% 100%);
|
||||||
--color-accent: hsl(217 91% 60%);
|
--color-accent: hsl(217 91% 55%);
|
||||||
--color-accent-content: hsl(0 0% 100%);
|
--color-accent-content: hsl(0 0% 100%);
|
||||||
--color-neutral: hsl(217 20% 17%);
|
--color-neutral: hsl(217 20% 17%);
|
||||||
--color-neutral-content: hsl(0 0% 100%);
|
--color-neutral-content: hsl(0 0% 100%);
|
||||||
@@ -334,11 +337,11 @@
|
|||||||
@plugin 'daisyui/theme' {
|
@plugin 'daisyui/theme' {
|
||||||
name: 'dark';
|
name: 'dark';
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
--color-primary: hsl(217 91% 60%);
|
--color-primary: hsl(217 91% 55%);
|
||||||
--color-primary-content: hsl(0 0% 100%);
|
--color-primary-content: hsl(0 0% 100%);
|
||||||
--color-secondary: hsl(217 91% 60%);
|
--color-secondary: hsl(217 91% 55%);
|
||||||
--color-secondary-content: hsl(0 0% 100%);
|
--color-secondary-content: hsl(0 0% 100%);
|
||||||
--color-accent: hsl(217 91% 60%);
|
--color-accent: hsl(217 91% 55%);
|
||||||
--color-accent-content: hsl(0 0% 100%);
|
--color-accent-content: hsl(0 0% 100%);
|
||||||
--color-neutral: hsl(217 30% 15%);
|
--color-neutral: hsl(217 30% 15%);
|
||||||
--color-neutral-content: hsl(0 0% 100%);
|
--color-neutral-content: hsl(0 0% 100%);
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { class: className = '' }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={['absolute inset-0 h-full w-full', className]}>
|
||||||
|
<div
|
||||||
|
class="bg-primary/20 absolute top-[-10%] left-[-10%] h-[40%] w-[40%] animate-pulse rounded-full blur-[120px]"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="bg-secondary/20 absolute right-[-10%] bottom-[-10%] h-[40%] w-[40%] animate-pulse rounded-full blur-[120px] delay-700"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
14
apps/web/src/lib/components/DecorativeTopLine.svelte
Normal file
14
apps/web/src/lib/components/DecorativeTopLine.svelte
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { class: className = '' }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
'via-primary absolute top-0 left-0 h-1 w-full bg-linear-to-r from-transparent to-transparent opacity-50',
|
||||||
|
className
|
||||||
|
]}
|
||||||
|
></div>
|
||||||
22
apps/web/src/lib/components/ErrorMessage.svelte
Normal file
22
apps/web/src/lib/components/ErrorMessage.svelte
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { XCircle } from 'lucide-svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
message?: string | null;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { message = null, class: className = '' }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if message}
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
'border-error/20 bg-error/10 text-error-content/90 mb-6 flex items-center gap-3 rounded-lg border p-4 backdrop-blur-md',
|
||||||
|
className
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<XCircle class="h-5 w-5 shrink-0" />
|
||||||
|
<span class="text-sm font-medium">{message}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
19
apps/web/src/lib/components/GlassCard.svelte
Normal file
19
apps/web/src/lib/components/GlassCard.svelte
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
children?: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { class: className = '', children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
'border-base-content/10 bg-base-content/5 ring-base-content/10 relative overflow-hidden rounded-2xl border p-8 shadow-2xl ring-1 backdrop-blur-xl transition-all duration-300',
|
||||||
|
className
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header
|
<header
|
||||||
class="bg-base-200 border-base-100 sticky top-0 z-50 w-full border-b shadow-sm backdrop-blur-md transition-all duration-300"
|
class="bg-base-200 border-base-100 sticky top-0 z-50 w-full border-b py-3 shadow-sm backdrop-blur-md transition-all duration-300"
|
||||||
>
|
>
|
||||||
<div class=" flex h-16 w-full items-center justify-between px-4">
|
<div class=" flex h-16 w-full items-center justify-between px-4">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
|||||||
85
apps/web/src/lib/components/MenuToggleIcon.svelte
Normal file
85
apps/web/src/lib/components/MenuToggleIcon.svelte
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { prefersReducedMotion, Spring } from 'svelte/motion';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
class?: string;
|
||||||
|
stroke?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { open, class: className = '', stroke = 2 }: Props = $props();
|
||||||
|
|
||||||
|
const progress = Spring.of(() => (open ? 1 : 0), {
|
||||||
|
stiffness: 0.25,
|
||||||
|
damping: 0.65,
|
||||||
|
precision: 0.001
|
||||||
|
});
|
||||||
|
|
||||||
|
const clamp01 = (n: number) => Math.max(0, Math.min(1, n));
|
||||||
|
const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
|
||||||
|
|
||||||
|
let t = $derived(prefersReducedMotion.current ? (open ? 1 : 0) : progress.current);
|
||||||
|
let tFast = $derived(clamp01(t * 1.15));
|
||||||
|
|
||||||
|
// Fechado: hambúrguer. Aberto: "outro menu" (linhas deslocadas + comprimentos diferentes).
|
||||||
|
// Continua sendo ícone de menu (não vira X).
|
||||||
|
let topY = $derived(lerp(-6, -7, tFast));
|
||||||
|
let botY = $derived(lerp(6, 7, tFast));
|
||||||
|
|
||||||
|
let topX = $derived(lerp(0, 3.25, t));
|
||||||
|
let midX = $derived(lerp(0, -2.75, t));
|
||||||
|
let botX = $derived(lerp(0, 1.75, t));
|
||||||
|
|
||||||
|
// micro-inclinação só pra dar “vida”, sem cruzar em X
|
||||||
|
let topR = $derived(lerp(0, 2.5, tFast));
|
||||||
|
let botR = $derived(lerp(0, -2.5, tFast));
|
||||||
|
|
||||||
|
let topScaleX = $derived(lerp(1, 0.62, tFast));
|
||||||
|
let midScaleX = $derived(lerp(1, 0.92, tFast));
|
||||||
|
let botScaleX = $derived(lerp(1, 0.72, tFast));
|
||||||
|
|
||||||
|
let topOpacity = $derived(1);
|
||||||
|
let midOpacity = $derived(1);
|
||||||
|
let botOpacity = $derived(1);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="menu-toggle-icon {className}" aria-hidden="true" style="--stroke: {stroke}px">
|
||||||
|
<span
|
||||||
|
class="line"
|
||||||
|
style="--x: {topX}px; --y: {topY}px; --r: {topR}deg; --o: {topOpacity}; --sx: {topScaleX}"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
class="line"
|
||||||
|
style="--x: {midX}px; --y: 0px; --r: 0deg; --o: {midOpacity}; --sx: {midScaleX}"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
class="line"
|
||||||
|
style="--x: {botX}px; --y: {botY}px; --r: {botR}deg; --o: {botOpacity}; --sx: {botScaleX}"
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.menu-toggle-icon {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: calc(var(--stroke) / -2);
|
||||||
|
height: var(--stroke);
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: currentColor;
|
||||||
|
opacity: var(--o, 1);
|
||||||
|
transform-origin: center;
|
||||||
|
transform: translateX(var(--x, 0px)) translateY(var(--y, 0px)) rotate(var(--r, 0deg))
|
||||||
|
scaleX(var(--sx, 1));
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
14
apps/web/src/lib/components/ShineEffect.svelte
Normal file
14
apps/web/src/lib/components/ShineEffect.svelte
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { class: className = '' }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
'via-base-content/20 absolute inset-0 -translate-x-full bg-linear-to-r from-transparent to-transparent transition-transform duration-1000 group-hover:translate-x-full',
|
||||||
|
className
|
||||||
|
]}
|
||||||
|
></div>
|
||||||
62
apps/web/src/lib/components/login/LoginInput.svelte
Normal file
62
apps/web/src/lib/components/login/LoginInput.svelte
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Field } from '@ark-ui/svelte/field';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
type?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
autocomplete?: HTMLInputAttributes['autocomplete'];
|
||||||
|
disabled?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
error?: string | null;
|
||||||
|
right?: Snippet;
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
type = 'text',
|
||||||
|
placeholder = '',
|
||||||
|
autocomplete,
|
||||||
|
disabled = false,
|
||||||
|
required = false,
|
||||||
|
error = null,
|
||||||
|
right,
|
||||||
|
value = $bindable('')
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
const invalid = $derived(!!error);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field.Root {invalid} {required} class="space-y-2">
|
||||||
|
<div class="flex items-center justify-between gap-3">
|
||||||
|
<Field.Label
|
||||||
|
for={id}
|
||||||
|
class="text-base-content/60 text-xs font-semibold tracking-wider uppercase"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Field.Label>
|
||||||
|
{@render right?.()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group relative">
|
||||||
|
<Field.Input
|
||||||
|
{id}
|
||||||
|
{type}
|
||||||
|
{placeholder}
|
||||||
|
{disabled}
|
||||||
|
{autocomplete}
|
||||||
|
{required}
|
||||||
|
bind:value
|
||||||
|
class="border-base-content/10 bg-base-200/25 text-base-content placeholder-base-content/40 focus:border-primary/50 focus:bg-base-200/35 focus:ring-primary/20 w-full rounded-xl border px-4 py-3 transition-all duration-300 focus:ring-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if error}
|
||||||
|
<Field.ErrorText class="text-error text-sm font-medium">{error}</Field.ErrorText>
|
||||||
|
{/if}
|
||||||
|
</Field.Root>
|
||||||
@@ -4,11 +4,11 @@
|
|||||||
import PushNotificationManager from '$lib/components/PushNotificationManager.svelte';
|
import PushNotificationManager from '$lib/components/PushNotificationManager.svelte';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/Footer.svelte';
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/Header.svelte';
|
||||||
|
import MenuToggleIcon from '$lib/components/MenuToggleIcon.svelte';
|
||||||
import Sidebar from '$lib/components/Sidebar.svelte';
|
import Sidebar from '$lib/components/Sidebar.svelte';
|
||||||
import DashboardHeaderActions from '$lib/components/dashboard/DashboardHeaderActions.svelte';
|
import DashboardHeaderActions from '$lib/components/dashboard/DashboardHeaderActions.svelte';
|
||||||
import ChatWidget from '$lib/components/chat/ChatWidget.svelte';
|
import ChatWidget from '$lib/components/chat/ChatWidget.svelte';
|
||||||
import PresenceManager from '$lib/components/chat/PresenceManager.svelte';
|
import PresenceManager from '$lib/components/chat/PresenceManager.svelte';
|
||||||
import { Menu, X } from 'lucide-svelte';
|
|
||||||
|
|
||||||
const { children } = $props();
|
const { children } = $props();
|
||||||
|
|
||||||
@@ -44,11 +44,7 @@
|
|||||||
aria-label={sidebarOpen ? 'Fechar menu' : 'Abrir menu'}
|
aria-label={sidebarOpen ? 'Fechar menu' : 'Abrir menu'}
|
||||||
onclick={toggleSidebar}
|
onclick={toggleSidebar}
|
||||||
>
|
>
|
||||||
{#if sidebarOpen}
|
<MenuToggleIcon open={sidebarOpen} class="h-5 w-5" />
|
||||||
<X class="h-5 w-5" />
|
|
||||||
{:else}
|
|
||||||
<Menu class="h-5 w-5" />
|
|
||||||
{/if}
|
|
||||||
</button>
|
</button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,13 @@
|
|||||||
import logo from '$lib/assets/logo_governo_PE.png';
|
import logo from '$lib/assets/logo_governo_PE.png';
|
||||||
import { authClient } from '$lib/auth';
|
import { authClient } from '$lib/auth';
|
||||||
import { obterIPPublico } from '$lib/utils/deviceInfo';
|
import { obterIPPublico } from '$lib/utils/deviceInfo';
|
||||||
import { LogIn, XCircle } from 'lucide-svelte';
|
import AnimatedBackgroundElements from '$lib/components/AnimatedBackgroundElements.svelte';
|
||||||
|
import DecorativeTopLine from '$lib/components/DecorativeTopLine.svelte';
|
||||||
|
import ErrorMessage from '$lib/components/ErrorMessage.svelte';
|
||||||
|
import GlassCard from '$lib/components/GlassCard.svelte';
|
||||||
|
import LoginInput from '$lib/components/login/LoginInput.svelte';
|
||||||
|
import ShineEffect from '$lib/components/ShineEffect.svelte';
|
||||||
|
import { LogIn } from 'lucide-svelte';
|
||||||
|
|
||||||
interface GPSLocation {
|
interface GPSLocation {
|
||||||
latitude?: number;
|
latitude?: number;
|
||||||
@@ -167,114 +173,74 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main
|
<main
|
||||||
class="relative flex min-h-screen w-full items-center justify-center overflow-hidden bg-[#0f172a]"
|
class="bg-base-100 relative flex min-h-screen w-full items-center justify-center overflow-hidden"
|
||||||
>
|
>
|
||||||
<!-- Animated Background Elements -->
|
<AnimatedBackgroundElements />
|
||||||
<div class="absolute inset-0 h-full w-full">
|
|
||||||
<div
|
|
||||||
class="bg-primary/20 absolute top-[-10%] left-[-10%] h-[40%] w-[40%] animate-pulse rounded-full blur-[120px]"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
class="bg-secondary/20 absolute right-[-10%] bottom-[-10%] h-[40%] w-[40%] animate-pulse rounded-full blur-[120px] delay-700"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Glass Card -->
|
<!-- Glass Card -->
|
||||||
<div class="relative z-10 w-full max-w-md p-6">
|
<div class="relative z-10 w-full max-w-md p-6">
|
||||||
<div
|
<GlassCard>
|
||||||
class="relative overflow-hidden rounded-2xl border border-white/10 bg-white/5 p-8 shadow-2xl ring-1 ring-white/10 backdrop-blur-xl transition-all duration-300"
|
<DecorativeTopLine />
|
||||||
>
|
|
||||||
<!-- Decorative Top Line -->
|
|
||||||
<div
|
|
||||||
class="via-primary absolute top-0 left-0 h-1 w-full bg-linear-to-r from-transparent to-transparent opacity-50"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="mb-10 text-center">
|
<div class="mb-10 text-center">
|
||||||
<div
|
<div
|
||||||
class="mb-6 inline-flex items-center justify-center rounded-2xl bg-white/5 p-4 shadow-inner ring-1 ring-white/10"
|
class="bg-base-content/5 ring-base-content/10 mb-6 inline-flex items-center justify-center rounded-2xl p-4 shadow-inner ring-1"
|
||||||
>
|
>
|
||||||
<img src={logo} alt="Logo SGSE" class="h-12 w-auto object-contain" />
|
<img src={logo} alt="Logo SGSE" class="h-12 w-auto object-contain" />
|
||||||
</div>
|
</div>
|
||||||
<h1 class="mb-2 font-sans text-3xl font-bold tracking-tight text-white">
|
<h1 class="text-base-content mb-2 font-sans text-3xl font-bold tracking-tight">
|
||||||
Bem-vindo de volta
|
Bem-vindo de volta
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-sm font-medium text-gray-400">
|
<p class="text-base-content/60 text-sm font-medium">
|
||||||
Entre com suas credenciais para acessar o sistema
|
Entre com suas credenciais para acessar o sistema
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error Message -->
|
<!-- Error Message -->
|
||||||
{#if erroLogin}
|
<ErrorMessage message={erroLogin} />
|
||||||
<div
|
|
||||||
class="mb-6 flex items-center gap-3 rounded-lg border border-red-500/20 bg-red-500/10 p-4 text-red-200 backdrop-blur-md"
|
|
||||||
>
|
|
||||||
<XCircle class="h-5 w-5 shrink-0" />
|
|
||||||
<span class="text-sm font-medium">{erroLogin}</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- Form -->
|
<!-- Form -->
|
||||||
<form class="space-y-6" onsubmit={handleLogin}>
|
<form class="space-y-6" onsubmit={handleLogin}>
|
||||||
<div class="space-y-2">
|
<LoginInput
|
||||||
<label
|
id="login-matricula"
|
||||||
for="login-matricula"
|
label="Matrícula ou E-mail"
|
||||||
class="text-xs font-semibold tracking-wider text-gray-400 uppercase"
|
placeholder="Digite sua identificação"
|
||||||
>
|
bind:value={matricula}
|
||||||
Matrícula ou E-mail
|
required
|
||||||
</label>
|
disabled={carregandoLogin}
|
||||||
<div class="group relative">
|
autocomplete="username"
|
||||||
<input
|
/>
|
||||||
id="login-matricula"
|
|
||||||
type="text"
|
|
||||||
class="focus:border-primary/50 focus:ring-primary/20 w-full rounded-xl border border-white/10 bg-black/20 px-4 py-3 text-white placeholder-gray-500 transition-all duration-300 focus:bg-black/40 focus:ring-2 focus:outline-none"
|
|
||||||
placeholder="Digite sua identificação"
|
|
||||||
bind:value={matricula}
|
|
||||||
required
|
|
||||||
disabled={carregandoLogin}
|
|
||||||
autocomplete="username"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-2">
|
<LoginInput
|
||||||
<div class="flex items-center justify-between">
|
id="login-password"
|
||||||
<label
|
label="Senha"
|
||||||
for="login-password"
|
type="password"
|
||||||
class="text-xs font-semibold tracking-wider text-gray-400 uppercase"
|
placeholder="Digite sua senha"
|
||||||
>
|
bind:value={senha}
|
||||||
Senha
|
required
|
||||||
</label>
|
disabled={carregandoLogin}
|
||||||
|
autocomplete="current-password"
|
||||||
|
>
|
||||||
|
{#snippet right()}
|
||||||
<a
|
<a
|
||||||
href={resolve('/esqueci-senha')}
|
href={resolve('/esqueci-senha')}
|
||||||
class="text-primary hover:text-primary-focus text-xs font-medium transition-colors"
|
class="text-primary hover:text-primary-focus text-xs font-medium transition-colors"
|
||||||
>
|
>
|
||||||
Esqueceu a senha?
|
Esqueceu a senha?
|
||||||
</a>
|
</a>
|
||||||
</div>
|
{/snippet}
|
||||||
<div class="group relative">
|
</LoginInput>
|
||||||
<input
|
|
||||||
id="login-password"
|
|
||||||
type="password"
|
|
||||||
class="focus:border-primary/50 focus:ring-primary/20 w-full rounded-xl border border-white/10 bg-black/20 px-4 py-3 text-white placeholder-gray-500 transition-all duration-300 focus:bg-black/40 focus:ring-2 focus:outline-none"
|
|
||||||
placeholder="Digite sua senha"
|
|
||||||
bind:value={senha}
|
|
||||||
required
|
|
||||||
disabled={carregandoLogin}
|
|
||||||
autocomplete="current-password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="group bg-primary hover:bg-primary-focus hover:shadow-primary/25 relative w-full overflow-hidden rounded-xl px-4 py-3.5 text-sm font-bold text-white shadow-lg transition-all duration-300 disabled:cursor-not-allowed disabled:opacity-50"
|
class="group bg-primary hover:bg-primary-focus hover:shadow-primary/25 text-primary-content relative w-full overflow-hidden rounded-xl px-4 py-3.5 text-sm font-bold shadow-lg transition-all duration-300 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
disabled={carregandoLogin}
|
disabled={carregandoLogin}
|
||||||
>
|
>
|
||||||
<div class="relative z-10 flex items-center justify-center gap-2">
|
<div class="relative z-10 flex items-center justify-center gap-2">
|
||||||
{#if carregandoLogin}
|
{#if carregandoLogin}
|
||||||
<span
|
<span
|
||||||
class="h-5 w-5 animate-spin rounded-full border-2 border-white/30 border-t-white"
|
class="border-primary-content/30 border-t-primary-content h-5 w-5 animate-spin rounded-full border-2"
|
||||||
></span>
|
></span>
|
||||||
<span>Autenticando...</span>
|
<span>Autenticando...</span>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -283,28 +249,26 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<!-- Shine Effect -->
|
<!-- Shine Effect -->
|
||||||
<div
|
<ShineEffect />
|
||||||
class="absolute inset-0 -translate-x-full bg-linear-to-r from-transparent via-white/20 to-transparent transition-transform duration-1000 group-hover:translate-x-full"
|
|
||||||
></div>
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Footer Links -->
|
<!-- Footer Links -->
|
||||||
<div class="mt-8 text-center">
|
<div class="mt-8 text-center">
|
||||||
<p class="text-sm text-gray-500">
|
<p class="text-base-content/40 text-sm">
|
||||||
Precisa de ajuda?
|
Precisa de ajuda?
|
||||||
<a
|
<a
|
||||||
href={resolve('/abrir-chamado')}
|
href={resolve('/abrir-chamado')}
|
||||||
class="font-medium text-gray-300 decoration-1 transition-colors hover:text-white hover:underline"
|
class="text-base-content/70 hover:text-base-content font-medium decoration-1 transition-colors hover:underline"
|
||||||
>
|
>
|
||||||
Abrir um chamado
|
Abrir um chamado
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</GlassCard>
|
||||||
|
|
||||||
<!-- Footer Info -->
|
<!-- Footer Info -->
|
||||||
<div class="mt-8 text-center text-xs text-gray-600">
|
<div class="text-base-content/40 mt-8 text-center text-xs">
|
||||||
<p>© {new Date().getFullYear()} Governo de Pernambuco. Todos os direitos reservados.</p>
|
<p>© {new Date().getFullYear()} Governo de Pernambuco. Todos os direitos reservados.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user