Skip to content
Open
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ You can start editing the page by modifying `app/page.tsx`. The page auto-update

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

### Environment Variables

Set `CREATOR_USERNAME` to the username that should receive the special highlight in the guestbook.

## Learn More

To learn more about Next.js, take a look at the following resources:
Expand Down
25 changes: 23 additions & 2 deletions src/actions/guestbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { revalidatePath } from 'next/cache';

export async function getGuestbookEntries(page = 1, limit = 50) {
const offset = (page - 1) * limit;

const creatorUsername = process.env.CREATOR_USERNAME?.toLowerCase();

// Get ALL entries (both main messages and replies) in chronological order
const entries = await db
Expand All @@ -29,6 +31,7 @@ export async function getGuestbookEntries(page = 1, limit = 50) {
.limit(limit)
.offset(offset);


// For replies, we need to get the original message info
const entriesWithReplyInfo = await Promise.all(
entries.map(async (entry) => {
Expand Down Expand Up @@ -57,13 +60,21 @@ export async function getGuestbookEntries(page = 1, limit = 50) {
})
);

const entriesWithCreator = entriesWithReplyInfo.map(entry => {
const entryUsername = (entry.displayUsername || entry.username || entry.name)?.toLowerCase();
return {
...entry,
isCreator: !!creatorUsername && entryUsername === creatorUsername,
};
});

// Get total count for pagination (all entries)
const totalCount = await db
.select({ count: sql<number>`count(*)` })
.from(guestbook);

return {
entries: entriesWithReplyInfo,
entries: entriesWithCreator,
pagination: {
page,
limit,
Expand All @@ -77,6 +88,8 @@ export async function getGuestbookEntries(page = 1, limit = 50) {

export async function getUserPosts(userId: string, page = 1, limit = 50) {
const offset = (page - 1) * limit;

const creatorUsername = process.env.CREATOR_USERNAME?.toLowerCase();

const entries = await db
.select({
Expand All @@ -95,14 +108,22 @@ export async function getUserPosts(userId: string, page = 1, limit = 50) {
.limit(limit)
.offset(offset);

const entriesWithCreator = entries.map(entry => {
const entryUsername = (entry.displayUsername || entry.username || entry.name)?.toLowerCase();
return {
...entry,
isCreator: !!creatorUsername && entryUsername === creatorUsername,
};
});

// Get total count for this user
const totalCount = await db
.select({ count: sql<number>`count(*)` })
.from(guestbook)
.where(eq(guestbook.userId, userId));

return {
entries,
entries: entriesWithCreator,
pagination: {
page,
limit,
Expand Down
35 changes: 20 additions & 15 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface GuestbookEntryType {
replyToId: string | null;
replyToMessage?: string;
replyToUsername?: string | null;
isCreator: boolean;
}

interface PaginationInfo {
Expand Down Expand Up @@ -353,23 +354,27 @@ export default function Home() {
<div
key={entry.id}
id={`message-${entry.id}`}
className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl shadow-lg border border-white/10 transition-all hover:shadow-xl hover:shadow-primary/10 hover:scale-[1.02] hover:border-white/20"
className={entry.isCreator ?
'bg-gradient-to-r from-fuchsia-500 via-rose-500 to-orange-500 p-[2px] rounded-2xl shadow-lg shadow-rose-500/40' : ''}
style={{ animationDelay: `${index * 0.1}s` }}
>
<GuestbookEntry
id={entry.id}
message={entry.message}
username={entry.displayUsername || entry.username || entry.name}
createdAt={entry.createdAt}
onUsernameAction={handleUsernameAction}
isUserIgnored={ignoredUsers.includes(entry.displayUsername || entry.username || entry.name || '')}
onReply={session?.user?.emailVerified ? handleReply : undefined}
onScrollToMessage={handleScrollToMessage}
isReply={!!entry.replyToId}
replyToUsername={'replyToUsername' in entry ? entry.replyToUsername : undefined}
replyToMessage={'replyToMessage' in entry ? entry.replyToMessage : undefined}
replyToMessageId={entry.replyToId || undefined}
/>
<div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl shadow-lg border border-white/10 transition-all hover:shadow-xl hover:shadow-primary/10 hover:scale-[1.02] hover:border-white/20">
<GuestbookEntry
id={entry.id}
message={entry.message}
username={entry.displayUsername || entry.username || entry.name}
createdAt={entry.createdAt}
onUsernameAction={handleUsernameAction}
isUserIgnored={ignoredUsers.includes(entry.displayUsername || entry.username || entry.name || '')}
onReply={session?.user?.emailVerified ? handleReply : undefined}
onScrollToMessage={handleScrollToMessage}
isReply={!!entry.replyToId}
isCreator={entry.isCreator}
replyToUsername={'replyToUsername' in entry ? entry.replyToUsername : undefined}
replyToMessage={'replyToMessage' in entry ? entry.replyToMessage : undefined}
replyToMessageId={entry.replyToId || undefined}
/>
</div>
</div>
))}
</div>
Expand Down
19 changes: 12 additions & 7 deletions src/app/user/[username]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface GuestbookEntryType {
displayUsername: string | null;
name: string | null;
userId: string;
isCreator: boolean;
}

interface PaginationInfo {
Expand Down Expand Up @@ -211,15 +212,19 @@ export default function UserPage() {
{entries.map((entry, index) => (
<div
key={entry.id}
className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl shadow-lg border border-white/10 transition-all hover:shadow-xl hover:shadow-primary/10 hover:scale-[1.02] hover:border-white/20"
className={entry.isCreator ?
'bg-gradient-to-r from-fuchsia-500 via-rose-500 to-orange-500 p-[2px] rounded-2xl shadow-lg shadow-rose-500/40' : ''}
style={{ animationDelay: `${index * 0.1}s` }}
>
<GuestbookEntry
id={entry.id}
message={entry.message}
username={entry.displayUsername || entry.username || entry.name}
createdAt={entry.createdAt}
/>
<div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl shadow-lg border border-white/10 transition-all hover:shadow-xl hover:shadow-primary/10 hover:scale-[1.02] hover:border-white/20">
<GuestbookEntry
id={entry.id}
message={entry.message}
username={entry.displayUsername || entry.username || entry.name}
createdAt={entry.createdAt}
isCreator={entry.isCreator}
/>
</div>
</div>
))}
</div>
Expand Down
11 changes: 8 additions & 3 deletions src/components/guestbook-entry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useState } from 'react';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { Button } from '@/components/ui/button';
import { Eye, EyeOff, Reply, Send, X, Quote } from 'lucide-react';
import { Eye, EyeOff, Reply, Send, X, Quote, Sparkles } from 'lucide-react';

interface GuestbookEntryProps {
id: string;
Expand All @@ -18,6 +18,7 @@ interface GuestbookEntryProps {
replyToMessage?: string;
replyToMessageId?: string;
onScrollToMessage?: (messageId: string) => void;
isCreator?: boolean;
}

export function GuestbookEntry({
Expand All @@ -32,7 +33,8 @@ export function GuestbookEntry({
replyToUsername,
replyToMessage,
replyToMessageId,
onScrollToMessage
onScrollToMessage,
isCreator = false
}: GuestbookEntryProps) {
const [showReplyForm, setShowReplyForm] = useState(false);
const [replyText, setReplyText] = useState('');
Expand Down Expand Up @@ -79,8 +81,11 @@ export function GuestbookEntry({
{username ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="font-medium text-primary hover:text-primary/80 transition-colors">
<button className={
`font-medium hover:text-primary/80 transition-colors ${isCreator ? 'text-transparent bg-gradient-to-r from-fuchsia-400 via-rose-400 to-orange-400 bg-clip-text' : 'text-primary'}`
}>
{username}
{isCreator && <Sparkles className="w-3 h-3 inline ml-1" />}
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
Expand Down