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
2 changes: 1 addition & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const config = {
headers: [
{
key: 'Access-Control-Allow-Origin',
value: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
value: process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000',
},
{
key: 'Access-Control-Allow-Methods',
Expand Down
6 changes: 5 additions & 1 deletion src/app/(authenticated)/news/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ export default function NewsPage() {
return (
<DashboardShell className="p-0 md:p-8">
<div className="flex justify-center w-full">
<ContentFeed className="w-full max-w-4xl"/>
<ContentFeed
className="w-full max-w-4xl"
postsPerPage={5}
enablePagination={true}
/>
</div>
</DashboardShell>
);
Expand Down
55 changes: 50 additions & 5 deletions src/components/content-feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,25 @@ interface FlyerWithAuthor {
// Dynamically import EmojiPicker to avoid SSR issues
const EmojiPicker = dynamic(() => import("emoji-picker-react").then((mod) => mod.default), { ssr: false })

export function ContentFeed({ className }: { className?: string }) {
interface ContentFeedProps {
className?: string
postsPerPage?: number
enablePagination?: boolean
}

export function ContentFeed({
className,
postsPerPage = 3,
enablePagination = false
}: ContentFeedProps) {
const [open, setOpen] = useState(false)
const [fileUrl] = useState<string | undefined>(undefined)
const [images, setImages] = useState<string[]>([])
const [loading] = useState(false)
const [visiblePostsCount, setVisiblePostsCount] = useState(enablePagination ? postsPerPage : postsPerPage)
const { toast } = useToast()
const utils = api.useUtils()
const loadMoreRef = useRef<HTMLDivElement>(null)

const { data: posts, isLoading: isLoadingPosts } = api.post.list.useQuery()
const { data: events } = api.event.list.useQuery()
Expand All @@ -112,6 +124,31 @@ export function ContentFeed({ className }: { className?: string }) {
const eventsWithRoleConfig = events as EventWithAuthor[] | undefined
const flyersWithRoleConfig = flyers as FlyerWithAuthor[] | undefined

// Intersection Observer para carregar mais posts
useEffect(() => {
if (!enablePagination || !postsWithRoleConfig || visiblePostsCount >= postsWithRoleConfig.length) return

const observer = new IntersectionObserver(
(entries) => {
if (entries[0]?.isIntersecting) {
setVisiblePostsCount(prev => Math.min(prev + postsPerPage, postsWithRoleConfig.length))
}
},
{ threshold: 0.1 }
)

const currentRef = loadMoreRef.current
if (currentRef) {
observer.observe(currentRef)
}

return () => {
if (currentRef) {
observer.unobserve(currentRef)
}
}
}, [enablePagination, postsWithRoleConfig, visiblePostsCount, postsPerPage])

const createPost = api.post.create.useMutation({
onSuccess: async () => {
toast({
Expand Down Expand Up @@ -206,7 +243,7 @@ export function ContentFeed({ className }: { className?: string }) {
<TabsContent value="posts" className="mt-4">
{isLoadingPosts ? (
<div className="space-y-4">
{Array.from({ length: 3 }).map((_, i) => (
{Array.from({ length: enablePagination ? postsPerPage : 3 }).map((_, i) => (
<div key={i} className="space-y-2">
<Skeleton className="h-4 w-1/3" />
<Skeleton className="h-4 w-full" />
Expand All @@ -217,9 +254,17 @@ export function ContentFeed({ className }: { className?: string }) {
<p className="text-sm text-muted-foreground text-center py-4">Nenhum post publicado ainda.</p>
) : (
<div className="space-y-4">
{postsWithRoleConfig?.map((post) => (
<PostItem key={post.id} post={post} />
))}
{postsWithRoleConfig
?.slice(0, enablePagination ? visiblePostsCount : postsWithRoleConfig.length)
.map((post) => (
<PostItem key={post.id} post={post} />
))}
{/* Elemento de referência para carregar mais posts */}
{enablePagination && visiblePostsCount < (postsWithRoleConfig?.length || 0) && (
<div ref={loadMoreRef} className="flex justify-center py-4">
<div className="text-sm text-muted-foreground">Carregando mais posts...</div>
</div>
)}
</div>
)}
</TabsContent>
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/birthdays-carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function BirthdaysCarousel({ itens, className }: BirthdayCarouselProps) {
>
<CarouselContent>
{itens.map((item, index) => (
<CarouselItem key={index} className="aspect-square md:h-96">
<CarouselItem key={index} className="w-full h-96">
<div className="relative w-full h-full">
<OptimizedImage
alt={item.title}
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/main-carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function MainCarousel({ itens, className }: MainCarouselProps) {
>
<CarouselContent>
{itens.map((item, index) => (
<CarouselItem key={index} className="w-full md:h-96 aspect-video">
<CarouselItem key={index} className="w-full h-96">
<div className="relative w-full h-full">
<OptimizedImage
alt={item.title}
Expand Down
6 changes: 3 additions & 3 deletions src/components/ui/image-carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ export function ImageCarousel({
{validImages.map((image, index) => (
<CarouselItem key={index} className={cn(
"w-full",
aspectRatio === "square" && "aspect-square",
aspectRatio === "video" && "aspect-video",
aspectRatio === "auto" && "aspect-auto"
aspectRatio === "square" && "h-96",
aspectRatio === "video" && "h-56",
aspectRatio === "auto" && "h-64"
)}>
<OptimizedImage
src={image}
Expand Down
55 changes: 44 additions & 11 deletions src/components/ui/optimized-image.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use client"

import Image from "next/image"
import { useState } from "react"
import { cn } from "@/lib/utils"

interface OptimizedImageProps {
src: string
Expand All @@ -10,6 +12,7 @@ interface OptimizedImageProps {
className?: string
priority?: boolean
fill?: boolean
blurIntensity?: number
}

export function OptimizedImage({
Expand All @@ -19,19 +22,49 @@ export function OptimizedImage({
height,
className,
priority = false,
fill = false
fill = false,
blurIntensity = 8
}: OptimizedImageProps) {
const [isLoaded, setIsLoaded] = useState(false)

return (
<Image
src={src}
alt={alt}
fill={fill}
width={!fill ? width : undefined}
height={!fill ? height : undefined}
className={className}
priority={priority}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
<div className="relative overflow-hidden w-full h-full">
{/* Imagem de fundo borrada - sempre preenche todo o espaço */}
<Image
src={src}
alt=""
fill={fill}
width={!fill ? width : undefined}
height={!fill ? height : undefined}
className={cn(
"absolute inset-0 scale-110",
`blur-[${blurIntensity}px]`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Dynamic Tailwind class will not work.

The template literal blur-[${blurIntensity}px] will not work with Tailwind's JIT compiler. Tailwind generates classes at build time and cannot interpolate runtime variables into arbitrary values within cn() or template literals in className strings.

Apply this diff to use inline styles for the blur effect:

       <Image
         src={src}
         alt=""
         fill={fill}
         width={!fill ? width : undefined}
         height={!fill ? height : undefined}
-        className={cn(
-          "absolute inset-0 scale-110",
-          `blur-[${blurIntensity}px]`,
-          "object-cover",
-          "transition-opacity duration-500",
-          isLoaded ? "opacity-100" : "opacity-0"
-        )}
+        className={cn(
+          "absolute inset-0 scale-110 object-cover transition-opacity duration-500",
+          isLoaded ? "opacity-100" : "opacity-0"
+        )}
+        style={{ filter: `blur(${blurIntensity}px)` }}
         priority={priority}
         sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
       />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`blur-[${blurIntensity}px]`,
<Image
src={src}
alt=""
fill={fill}
width={!fill ? width : undefined}
height={!fill ? height : undefined}
className={cn(
"absolute inset-0 scale-110 object-cover transition-opacity duration-500",
isLoaded ? "opacity-100" : "opacity-0"
)}
style={{ filter: `blur(${blurIntensity}px)` }}
priority={priority}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
🤖 Prompt for AI Agents
In src/components/ui/optimized-image.tsx around line 41, the dynamic Tailwind
class `blur-[${blurIntensity}px]` will not be generated by Tailwind JIT at
runtime; replace the runtime template class with an inline style: remove the
`blur-[${blurIntensity}px]` entry from the className and instead add a style
prop that sets filter: `blur(${blurIntensity}px)` (ensure you coerce
blurIntensity to a number and only apply the style when > 0, falling back to no
filter otherwise). Also keep any static Tailwind blur-related utility classes if
needed for non-dynamic cases.

"object-cover",
"transition-opacity duration-500",
isLoaded ? "opacity-100" : "opacity-0"
)}
priority={priority}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

{/* Imagem principal */}
<Image
src={src}
alt={alt}
fill={fill}
width={!fill ? width : undefined}
height={!fill ? height : undefined}
className={cn(
"relative z-10",
className, // Aplicar classes personalizadas à imagem principal
"transition-opacity duration-300",
isLoaded ? "opacity-100" : "opacity-0"
)}
priority={priority}
onLoad={() => setIsLoaded(true)}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
)
}

Loading