Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.5.2",
"emoji-picker-react": "^4.12.2",
"framer-motion": "^12.23.26",
"geist": "^1.3.0",
"jimp": "0.22.12",
"leaflet": "^1.9.4",
Expand Down
38 changes: 38 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 59 additions & 18 deletions src/app/(authenticated)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { MainCarousel } from "@/components/dashboard/main-carousel"
import { BirthdaysCarousel } from "@/components/dashboard/birthdays-carousel"
import { api } from "@/trpc/react"
import { cn } from "@/lib/utils"
import { LinkIcon, LucideGraduationCap } from "lucide-react"
import { LinkIcon, LucideGraduationCap, Coffee, Sparkles } from "lucide-react"
import { motion } from "framer-motion"
import { VideosCarousel } from "@/components/dashboard/videos-carousel"
import Link from "next/link"
import { NewsDisplay } from "@/components/dashboard/news-displ"
Expand All @@ -19,6 +20,17 @@ import { CompleteProfileModal } from "@/components/complete-profile-modal"
import { WelcomeCard } from "@/components/dashboard/welcome-card"
import { useState, useEffect, useMemo } from "react"

// Variantes de animação para o footer
const fadeInUp = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
}

const transitionDefault = {
duration: 0.5,
ease: [0.4, 0, 0.2, 1] as const
}

export default function DashboardPage() {
const [showProfileModal, setShowProfileModal] = useState(false)

Expand Down Expand Up @@ -354,8 +366,8 @@ export default function DashboardPage() {
</div>
{/* Footer com Redes Sociais */}
<div className="mt-8 lg:mt-12 bg-muted/50 border-t">
<div className="max-w-6xl mx-auto px-4 md:px-2 py-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="max-w-7xl mx-auto px-4 md:px-6 lg:px-8 py-8 md:py-10">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8 lg:gap-10">
{/* Redes Sociais */}
<div className="space-y-3">
<h3 className="font-semibold text-sm text-muted-foreground uppercase tracking-wide">
Expand Down Expand Up @@ -401,23 +413,52 @@ export default function DashboardPage() {
</div>
</div>

{/* Créditos */}
<div className="flex items-end justify-center md:justify-end">
<div className="text-center md:text-right">
<div className="text-sm text-muted-foreground mb-2">
Elo &copy; 2025
</div>
<div className="flex items-center justify-center md:justify-end gap-2 text-sm">
<span>Built with ❤️ by</span>
<Link
href={'https://allpines.com.br'}
className="flex items-center gap-1 font-bold text-primary hover:underline"
{/* Copyright e desenvolvedor */}
<div className="flex flex-col items-center md:items-end justify-end space-y-3">
<motion.div
variants={fadeInUp}
transition={transitionDefault}
initial="hidden"
animate="visible"
className="flex flex-col items-center md:items-end gap-2"
>
<p className="text-muted-foreground text-sm md:text-base text-center md:text-right">
©️ {new Date().getFullYear()} Elo | Intranet
</p>

{/* Link criativo para a empresa desenvolvedora */}
<motion.a
href="https://www.allpines.com.br"
target="_blank"
rel="noreferrer"
className="group flex items-center gap-1.5 rounded-full bg-gradient-to-r from-primary/10 via-primary/5 to-primary/10 dark:from-primary/20 dark:via-primary/10 dark:to-primary/20 border border-primary/20 dark:border-primary/30 px-3 py-1.5 text-foreground text-xs transition-all hover:from-primary/20 hover:via-primary/10 hover:to-primary/20 dark:hover:from-primary/30 dark:hover:via-primary/20 dark:hover:to-primary/30 hover:shadow-md hover:border-primary/30 dark:hover:border-primary/40"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<span className="text-muted-foreground text-xs">Feito com</span>
<motion.span
animate={{
rotate: [0, -5, 5, -5, 0],
y: [0, -2, 0],
}}
transition={{
duration: 2,
repeat: Number.POSITIVE_INFINITY,
ease: [0.4, 0, 0.6, 1] as const,
}}
>
<Coffee className="h-3 w-3 text-amber-500 dark:text-amber-400" />
</motion.span>
<span className="text-muted-foreground text-xs">por</span>
<motion.span
className="font-semibold text-primary text-xs"
whileHover={{ x: 2 }}
>
<Image src="/logoAllpines.webp" height={16} width={16} alt="Allpines" className="rounded"/>
Allpines
</Link>
</div>
</div>
</motion.span>
<Sparkles className="h-3 w-3 text-primary/70 dark:text-primary/80 transition-transform group-hover:rotate-12" />
</motion.a>
</motion.div>
</div>
</div>
</div>
Expand Down
3 changes: 1 addition & 2 deletions src/components/dashboard/news-displ.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,13 @@ function PostItemCompact({ post }: PostItemProps) {
<CardContent className="p-0 flex flex-col h-full">
{/* Imagem no topo */}
{imageUrls.length > 0 && (
<div className="relative h-48 md:h-56 overflow-hidden bg-muted">
<div className="relative h-56 md:h-56 overflow-hidden">
<ImageCarousel
images={imageUrls}
alt={post.title}
aspectRatio="auto"
showArrows={imageUrls.length > 1}
showDots={imageUrls.length > 1}
className="h-full w-full"
imageFit="cover"
/>
</div>
Expand Down
103 changes: 66 additions & 37 deletions src/components/ui/image-carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,25 @@ export function ImageCarousel({

setCurrent(carouselApi.selectedScrollSnap())

carouselApi.on("select", () => {
const onSelect = () => {
setCurrent(carouselApi.selectedScrollSnap())
})
}

carouselApi.on("select", onSelect)
carouselApi.on("reInit", onSelect)

return () => {
carouselApi.off("select", onSelect)
carouselApi.off("reInit", onSelect)
}
}, [carouselApi])

// Auto play functionality
useEffect(() => {
if (carouselApi && validImages.length > 0) {
carouselApi.reInit()
}
}, [carouselApi, validImages.length])

useEffect(() => {
if (!autoPlay || validImages.length <= 1 || isHovered) return

Expand All @@ -67,7 +80,6 @@ export function ImageCarousel({
return () => clearInterval(interval)
}, [autoPlay, validImages.length, autoPlayInterval, isHovered, carouselApi])


const allowedAspects = useMemo(() => ([
{ value: 1, className: "aspect-square" },
{ value: 4 / 3, className: "aspect-[4/3]" },
Expand Down Expand Up @@ -127,31 +139,41 @@ export function ImageCarousel({
})
}, [allowedAspects, aspectRatio])

// Verificar se deve usar altura total (quando className contém h-full)
// CORREÇÃO: Detectar se h-full ou w-full está no className
const shouldUseFullHeight = className?.includes('h-full') ?? false
const shouldUseFullWidth = className?.includes('w-full') ?? false

const renderImage = (image: string, index: number) => (
<div className={cn(
"relative w-full overflow-hidden",
shouldUseFullHeight ? "h-full" : resolveAspectClass(index),
)}>
<OptimizedImage
src={image}
alt={`${alt} ${index + 1}`}
fill
const renderImage = (image: string, index: number) => {
// CORREÇÃO: Se h-full está presente, NÃO usar aspect-ratio
// Deixar o container pai controlar a altura
const aspectClass = shouldUseFullHeight ? "" : resolveAspectClass(index)

return (
<div
className={cn(
imageFit === "contain"
? "object-contain object-center"
: shouldUseFullHeight
? "object-cover object-top"
: "object-cover"
"relative w-full overflow-hidden bg-muted",
// CORREÇÃO: Aplicar aspect-ratio APENAS se não for h-full
aspectClass,
// Se h-full, forçar altura 100%
shouldUseFullHeight && "h-full"
)}
priority={index === current}
onLoadingComplete={(img) => handleImageLoad(index, img.target as HTMLImageElement)}
imageFit={imageFit}
/>
</div>
)
>
<OptimizedImage
src={image}
alt={`${alt} ${index + 1}`}
fill
className={cn(
imageFit === "contain"
? "object-contain"
: "object-cover"
)}
priority={index === 0}
onLoadingComplete={(img) => handleImageLoad(index, img.target as HTMLImageElement)}
imageFit={imageFit}
/>
</div>
)
}

if (!images || images.length === 0) {
console.warn('ImageCarousel: No images provided')
Expand All @@ -163,7 +185,6 @@ export function ImageCarousel({
return null
}

// Se há apenas uma imagem, não mostra o carrossel
if (validImages.length === 1) {
return (
<div
Expand All @@ -182,8 +203,10 @@ export function ImageCarousel({
<div
className={cn(
"relative w-full overflow-hidden rounded-lg border max-w-[100vw] md:max-w-none",
// CORREÇÃO: Remover h-full e w-full do className antes de aplicar
// Eles serão aplicados internamente conforme necessário
shouldUseFullHeight && "h-full",
className
shouldUseFullWidth && "w-full",
)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
Expand All @@ -194,24 +217,32 @@ export function ImageCarousel({
opts={{
loop: true,
align: "center",
slidesToScroll: 1,
}}
>
<CarouselContent className={shouldUseFullHeight ? "h-full" : ""}>
<CarouselContent
className={cn(shouldUseFullHeight && "h-full", "-ml-0")}
>
{validImages.map((image, index) => (
<CarouselItem key={index} className={cn("w-full", shouldUseFullHeight && "h-full")}>
<CarouselItem
key={`${image}-${index}`}
className={cn(
"w-full pl-0 basis-full",
shouldUseFullHeight && "h-full"
)}
>
{renderImage(image, index)}
</CarouselItem>
))}
</CarouselContent>
</Carousel>

{/* Setas de navegação - apenas em desktop */}
{showArrows && (
<>
<Button
variant="ghost"
size="sm"
className="absolute left-2 top-1/2 -translate-y-1/2 h-8 w-8 p-0 bg-black/20 hover:bg-black/40 text-white hidden md:flex"
className="absolute left-2 top-1/2 -translate-y-1/2 h-8 w-8 p-0 bg-black/20 hover:bg-black/40 text-white hidden md:flex z-10"
onClick={() => carouselApi?.scrollPrev()}
disabled={validImages.length <= 1}
>
Expand All @@ -220,7 +251,7 @@ export function ImageCarousel({
<Button
variant="ghost"
size="sm"
className="absolute right-2 top-1/2 -translate-y-1/2 h-8 w-8 p-0 bg-black/20 hover:bg-black/40 text-white hidden md:flex"
className="absolute right-2 top-1/2 -translate-y-1/2 h-8 w-8 p-0 bg-black/20 hover:bg-black/40 text-white hidden md:flex z-10"
onClick={() => carouselApi?.scrollNext()}
disabled={validImages.length <= 1}
>
Expand All @@ -229,9 +260,8 @@ export function ImageCarousel({
</>
)}

{/* Indicadores de posição (bolinhas) */}
{showDots && validImages.length > 1 && (
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 flex space-x-1">
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 flex space-x-1 z-10">
{validImages.map((_, index) => (
<button
key={index}
Expand All @@ -248,12 +278,11 @@ export function ImageCarousel({
</div>
)}

{/* Contador de imagens */}
{validImages.length > 1 && (
<div className="absolute top-3 right-3 bg-black/50 text-white text-xs px-2 py-1 rounded-full">
<div className="absolute top-3 right-3 bg-black/50 text-white text-xs px-2 py-1 rounded-full z-10">
{current + 1} / {validImages.length}
</div>
)}
</div>
)
}
}
Loading
Loading