Skip to content

Commit b10692f

Browse files
committed
fixes
1 parent 731f422 commit b10692f

File tree

2 files changed

+141
-124
lines changed

2 files changed

+141
-124
lines changed

src/components/ShowcaseCard.tsx

Lines changed: 71 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -76,80 +76,82 @@ export function ShowcaseCard({
7676
</a>
7777

7878
{/* Footer with libraries and voting */}
79-
<div className="px-4 pb-4 flex items-center justify-between gap-2 mt-auto">
80-
{/* Libraries */}
81-
<div className="flex flex-wrap gap-1.5 min-w-0 flex-1">
82-
{showcase.libraries.slice(0, 3).map((libId) => {
83-
const lib = libraryMap.get(libId as LibraryId)
84-
return (
85-
<span
86-
key={libId}
87-
className="px-2 py-0.5 text-xs font-medium rounded-full bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 truncate"
88-
>
89-
{lib?.name?.replace('TanStack ', '') || libId}
79+
<div className="px-4 pb-4 mt-auto">
80+
<div className="flex items-end justify-between gap-2">
81+
{/* Libraries */}
82+
<div className="flex flex-wrap gap-1.5 min-w-0">
83+
{showcase.libraries.slice(0, 3).map((libId) => {
84+
const lib = libraryMap.get(libId as LibraryId)
85+
return (
86+
<span
87+
key={libId}
88+
className="px-2 py-0.5 text-xs font-medium rounded-full bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300"
89+
>
90+
{lib?.name?.replace('TanStack ', '') || libId}
91+
</span>
92+
)
93+
})}
94+
{showcase.libraries.length > 3 && (
95+
<span className="px-2 py-0.5 text-xs font-medium rounded-full bg-gray-100 dark:bg-gray-700 text-gray-500">
96+
+{showcase.libraries.length - 3}
9097
</span>
91-
)
92-
})}
93-
{showcase.libraries.length > 3 && (
94-
<span className="px-2 py-0.5 text-xs font-medium rounded-full bg-gray-100 dark:bg-gray-700 text-gray-500">
95-
+{showcase.libraries.length - 3}
96-
</span>
97-
)}
98-
</div>
99-
100-
{/* Voting */}
101-
<div className="flex items-center gap-1 shrink-0">
102-
<button
103-
type="button"
104-
onClick={(e) => {
105-
e.preventDefault()
106-
e.stopPropagation()
107-
onVote?.(1)
108-
}}
109-
disabled={isVoting}
110-
className={twMerge(
111-
'p-1.5 rounded-md transition-colors',
112-
currentUserVote === 1
113-
? 'text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-900/30'
114-
: 'text-gray-400 hover:text-emerald-600 dark:hover:text-emerald-400 hover:bg-gray-100 dark:hover:bg-gray-700',
115-
isVoting && 'opacity-50 cursor-not-allowed',
11698
)}
117-
title="Upvote"
118-
>
119-
<ThumbsUp
120-
className="w-4 h-4"
121-
fill={currentUserVote === 1 ? 'currentColor' : 'none'}
122-
/>
123-
</button>
99+
</div>
124100

125-
{displayScore > 0 && (
126-
<span className="text-sm font-medium text-gray-600 dark:text-gray-400 min-w-[1.5rem] text-center">
127-
{displayScore}
128-
</span>
129-
)}
101+
{/* Voting */}
102+
<div className="flex items-center gap-1 shrink-0">
103+
<button
104+
type="button"
105+
onClick={(e) => {
106+
e.preventDefault()
107+
e.stopPropagation()
108+
onVote?.(1)
109+
}}
110+
disabled={isVoting}
111+
className={twMerge(
112+
'p-1.5 rounded-md transition-colors',
113+
currentUserVote === 1
114+
? 'text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-900/30'
115+
: 'text-gray-400 hover:text-emerald-600 dark:hover:text-emerald-400 hover:bg-gray-100 dark:hover:bg-gray-700',
116+
isVoting && 'opacity-50 cursor-not-allowed',
117+
)}
118+
title="Upvote"
119+
>
120+
<ThumbsUp
121+
className="w-4 h-4"
122+
fill={currentUserVote === 1 ? 'currentColor' : 'none'}
123+
/>
124+
</button>
130125

131-
<button
132-
type="button"
133-
onClick={(e) => {
134-
e.preventDefault()
135-
e.stopPropagation()
136-
onVote?.(-1)
137-
}}
138-
disabled={isVoting}
139-
className={twMerge(
140-
'p-1.5 rounded-md transition-colors',
141-
currentUserVote === -1
142-
? 'text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/30'
143-
: 'text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700',
144-
isVoting && 'opacity-50 cursor-not-allowed',
126+
{displayScore > 0 && (
127+
<span className="text-sm font-medium text-gray-600 dark:text-gray-400 min-w-[1.5rem] text-center">
128+
{displayScore}
129+
</span>
145130
)}
146-
title="Downvote"
147-
>
148-
<ThumbsDown
149-
className="w-4 h-4"
150-
fill={currentUserVote === -1 ? 'currentColor' : 'none'}
151-
/>
152-
</button>
131+
132+
<button
133+
type="button"
134+
onClick={(e) => {
135+
e.preventDefault()
136+
e.stopPropagation()
137+
onVote?.(-1)
138+
}}
139+
disabled={isVoting}
140+
className={twMerge(
141+
'p-1.5 rounded-md transition-colors',
142+
currentUserVote === -1
143+
? 'text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/30'
144+
: 'text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700',
145+
isVoting && 'opacity-50 cursor-not-allowed',
146+
)}
147+
title="Downvote"
148+
>
149+
<ThumbsDown
150+
className="w-4 h-4"
151+
fill={currentUserVote === -1 ? 'currentColor' : 'none'}
152+
/>
153+
</button>
154+
</div>
153155
</div>
154156
</div>
155157
</div>

src/routes/api/discord/interactions.tsx

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -42,72 +42,87 @@ export const Route = createFileRoute('/api/discord/interactions')({
4242
server: {
4343
handlers: {
4444
POST: async ({ request }: { request: Request }) => {
45-
if (!DISCORD_PUBLIC_KEY) {
46-
return new Response(
47-
JSON.stringify({ error: 'Discord public key not configured' }),
48-
{ status: 500, headers: { 'Content-Type': 'application/json' } },
49-
)
50-
}
45+
try {
46+
if (!DISCORD_PUBLIC_KEY) {
47+
console.error('[Discord] DISCORD_PUBLIC_KEY not configured')
48+
return new Response(
49+
JSON.stringify({ error: 'Discord public key not configured' }),
50+
{ status: 500, headers: { 'Content-Type': 'application/json' } },
51+
)
52+
}
5153

52-
const signature = request.headers.get('X-Signature-Ed25519')
53-
const timestamp = request.headers.get('X-Signature-Timestamp')
54+
const signature = request.headers.get('X-Signature-Ed25519')
55+
const timestamp = request.headers.get('X-Signature-Timestamp')
5456

55-
if (!signature || !timestamp) {
56-
return new Response(
57-
JSON.stringify({ error: 'Missing signature headers' }),
58-
{ status: 401, headers: { 'Content-Type': 'application/json' } },
59-
)
60-
}
57+
if (!signature || !timestamp) {
58+
console.error('[Discord] Missing signature headers')
59+
return new Response(
60+
JSON.stringify({ error: 'Missing signature headers' }),
61+
{ status: 401, headers: { 'Content-Type': 'application/json' } },
62+
)
63+
}
6164

62-
const body = await request.text()
65+
const body = await request.text()
6366

64-
const isValid = await verifyKey(
65-
body,
66-
signature,
67-
timestamp,
68-
DISCORD_PUBLIC_KEY,
69-
)
67+
const isValid = await verifyKey(
68+
body,
69+
signature,
70+
timestamp,
71+
DISCORD_PUBLIC_KEY,
72+
)
7073

71-
if (!isValid) {
72-
return new Response(JSON.stringify({ error: 'Invalid signature' }), {
73-
status: 401,
74-
headers: { 'Content-Type': 'application/json' },
75-
})
76-
}
74+
if (!isValid) {
75+
console.error('[Discord] Invalid signature')
76+
return new Response(
77+
JSON.stringify({ error: 'Invalid signature' }),
78+
{ status: 401, headers: { 'Content-Type': 'application/json' } },
79+
)
80+
}
7781

78-
const interaction: Interaction = JSON.parse(body)
82+
const interaction: Interaction = JSON.parse(body)
83+
console.log('[Discord] Interaction type:', interaction.type)
7984

80-
// Handle PING (Discord uses this to verify endpoint)
81-
if (interaction.type === InteractionType.PING) {
82-
return new Response(
83-
JSON.stringify({ type: InteractionResponseType.PONG }),
84-
{ status: 200, headers: { 'Content-Type': 'application/json' } },
85-
)
86-
}
85+
// Handle PING (Discord uses this to verify endpoint)
86+
if (interaction.type === InteractionType.PING) {
87+
console.log('[Discord] Responding to PING')
88+
return new Response(
89+
JSON.stringify({ type: InteractionResponseType.PONG }),
90+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
91+
)
92+
}
8793

88-
// Handle slash commands
89-
if (interaction.type === InteractionType.APPLICATION_COMMAND) {
90-
const commandName = interaction.data?.name
91-
92-
let response
93-
switch (commandName) {
94-
case 'tanstack':
95-
response = handleStatusCommand()
96-
break
97-
default:
98-
response = handleUnknownCommand(commandName || 'unknown')
94+
// Handle slash commands
95+
if (interaction.type === InteractionType.APPLICATION_COMMAND) {
96+
const commandName = interaction.data?.name
97+
console.log('[Discord] Command:', commandName)
98+
99+
let response
100+
switch (commandName) {
101+
case 'tanstack':
102+
response = handleStatusCommand()
103+
break
104+
default:
105+
response = handleUnknownCommand(commandName || 'unknown')
106+
}
107+
108+
return new Response(JSON.stringify(response), {
109+
status: 200,
110+
headers: { 'Content-Type': 'application/json' },
111+
})
99112
}
100113

101-
return new Response(JSON.stringify(response), {
102-
status: 200,
103-
headers: { 'Content-Type': 'application/json' },
104-
})
114+
console.error('[Discord] Unknown interaction type:', interaction.type)
115+
return new Response(
116+
JSON.stringify({ error: 'Unknown interaction type' }),
117+
{ status: 400, headers: { 'Content-Type': 'application/json' } },
118+
)
119+
} catch (error) {
120+
console.error('[Discord] Handler error:', error)
121+
return new Response(
122+
JSON.stringify({ error: 'Internal server error' }),
123+
{ status: 500, headers: { 'Content-Type': 'application/json' } },
124+
)
105125
}
106-
107-
return new Response(
108-
JSON.stringify({ error: 'Unknown interaction type' }),
109-
{ status: 400, headers: { 'Content-Type': 'application/json' } },
110-
)
111126
},
112127
},
113128
},

0 commit comments

Comments
 (0)