-
Notifications
You must be signed in to change notification settings - Fork 3
dev #32
base: main
Are you sure you want to change the base?
dev #32
Changes from 4 commits
fc03b64
9dc3948
ecb5567
a621c4f
5cdbd52
7dac1a2
ce2405e
56ffbed
397a72f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,13 +7,15 @@ export async function POST(req: NextRequest) { | |
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | ||
| } | ||
|
|
||
| let data: unknown; | ||
| let data: { project_id: string; content: string; media_url?: string; description?: string | null }; | ||
| try { | ||
| data = await req.json(); | ||
| } catch { | ||
| return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); | ||
| } | ||
|
|
||
| if (data.description === "") { | ||
| data.description = null; | ||
| } | ||
|
Comment on lines
+21
to
+23
|
||
| try { | ||
| const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/devlogs/`, { | ||
| method: "POST", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { NextResponse } from "next/server"; | ||
|
|
||
| // TODO: This is a placeholder implementation. Backend needs to be updated to: | ||
| // 1. Add a ShopConfig model/table to store form URLs and other shop settings | ||
| // 2. Create /api/v1/shop/config endpoint to fetch/update shop configuration | ||
| // 3. This route should then proxy to the backend instead of using env vars | ||
|
|
||
| export async function GET() { | ||
| const formUrl = process.env.NEXT_PUBLIC_SHOP_FORM_URL; | ||
|
|
||
| if (!formUrl) { | ||
| return NextResponse.json( | ||
| { error: "Shop form URL not configured" }, | ||
| { status: 503 } | ||
| ); | ||
| } | ||
|
|
||
| return NextResponse.json({ formUrl }); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,93 @@ | ||||||
| "use client"; | ||||||
|
|
||||||
| import { useSearchParams } from "next/navigation"; | ||||||
| import { useEffect, useState, Suspense } from "react"; | ||||||
|
|
||||||
| function ShopRedirect() { | ||||||
| const searchParams = useSearchParams(); | ||||||
| const item = searchParams.get("item"); | ||||||
|
|
||||||
| const [error, setError] = useState<string | null>(null); | ||||||
| const [loading, setLoading] = useState(true); | ||||||
|
|
||||||
| useEffect(() => { | ||||||
| if (!item) { | ||||||
| setError("Missing item parameter. Please specify what you want to purchase."); | ||||||
| setLoading(false); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| async function fetchAndRedirect() { | ||||||
| try { | ||||||
| const res = await fetch("/api/shop/form-url"); | ||||||
|
|
||||||
| if (!res.ok) { | ||||||
| const data = await res.json(); | ||||||
| setError(data.error || "Failed to load shop configuration"); | ||||||
|
||||||
| setError(data.error || "Failed to load shop configuration"); | |
| setError((data && (data.error || data.detail)) || "Failed to load shop configuration"); |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The non-null assertion operator (!) on line 32 is unnecessary because the item parameter is already checked for null/undefined on line 14, and the function returns early if it's falsy. You can safely use item without the assertion: ${formUrl}?item=${encodeURIComponent(item)}
| const redirectUrl = `${formUrl}?item=${encodeURIComponent(item!)}`; | |
| const redirectUrl = `${formUrl}?item=${encodeURIComponent(item)}`; |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The redirectUrl constructed on line 32 should be validated to ensure the formUrl from the API is a trusted URL before redirecting. While the backend should control this, client-side validation would add defense in depth. Consider validating that formUrl is a known trusted domain before performing the redirect.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| "use client"; | ||
|
|
||
| import { useState, useEffect } from "react"; | ||
| import React, { useState, useEffect } from "react"; | ||
| import { useRouter } from "next/navigation"; | ||
|
|
||
| type HackatimeProject = { | ||
|
|
@@ -20,11 +20,11 @@ type ProjectData = { | |
| export default function EditProject({ | ||
| projectId, | ||
| initialData, | ||
| onCancel, | ||
| onCancelAction, | ||
| }: { | ||
| projectId: number; | ||
| initialData: ProjectData; | ||
| onCancel: () => void; | ||
| onCancelAction: () => void; | ||
|
Comment on lines
+24
to
+28
|
||
| }) { | ||
| const router = useRouter(); | ||
| const [formData, setFormData] = useState(initialData); | ||
|
|
@@ -73,7 +73,7 @@ export default function EditProject({ | |
| } | ||
|
|
||
| router.refresh(); | ||
| onCancel(); | ||
| onCancelAction(); | ||
| } catch (err) { | ||
| setError(err instanceof Error ? err.message : "An error occurred"); | ||
| } finally { | ||
|
|
@@ -97,7 +97,7 @@ export default function EditProject({ | |
| <h2 className="text-2xl font-bold text-gray-900">Edit Project</h2> | ||
| <button | ||
| type="button" | ||
| onClick={onCancel} | ||
| onClick={onCancelAction} | ||
| className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400" | ||
| aria-label="Close" | ||
| > | ||
|
|
@@ -228,7 +228,7 @@ export default function EditProject({ | |
| <div className="flex gap-3 pt-4 border-t-2 border-gray-100"> | ||
| <button | ||
| type="button" | ||
| onClick={onCancel} | ||
| onClick={onCancelAction} | ||
| className="flex-1 px-6 py-3 border-2 border-gray-300 text-gray-700 rounded-lg font-bold hover:bg-gray-50 transition-all focus:outline-none focus:ring-2 focus:ring-gray-400" | ||
| disabled={loading} | ||
| > | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||
| "use client"; | ||||||||||||||||||
|
|
||||||||||||||||||
| import { useState } from "react"; | ||||||||||||||||||
| import { useRouter } from "next/navigation"; | ||||||||||||||||||
|
|
||||||||||||||||||
| import EditProject from "./EditProject"; | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -17,8 +18,41 @@ type Project = { | |||||||||||||||||
| shipped: boolean; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| export default function ProjectDetailsClient({ project }: { project: Project }) { | ||||||||||||||||||
| type Props = { | ||||||||||||||||||
| project: Project; | ||||||||||||||||||
| devlogCount: number; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| export default function ProjectDetailsClient({ project, devlogCount }: Props) { | ||||||||||||||||||
| const router = useRouter(); | ||||||||||||||||||
| const [isEditing, setIsEditing] = useState(false); | ||||||||||||||||||
| const [isShipping, setIsShipping] = useState(false); | ||||||||||||||||||
| const [shipError, setShipError] = useState<string | null>(null); | ||||||||||||||||||
|
|
||||||||||||||||||
| const canShip = !project.shipped && devlogCount > 0; | ||||||||||||||||||
|
|
||||||||||||||||||
| async function handleShip() { | ||||||||||||||||||
| setIsShipping(true); | ||||||||||||||||||
| setShipError(null); | ||||||||||||||||||
|
|
||||||||||||||||||
| try { | ||||||||||||||||||
| const res = await fetch(`/api/projects/${project.project_id}/ship`, { | ||||||||||||||||||
| method: "POST", | ||||||||||||||||||
| credentials: "include", | ||||||||||||||||||
|
||||||||||||||||||
| credentials: "include", |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message displayed on lines 105-107 lacks accessibility attributes. Consider adding role="alert" and aria-live="polite" to the error container so screen readers announce the error when it appears.
| <p className="text-red-600 text-sm mt-2">{shipError}</p> | |
| <p | |
| className="text-red-600 text-sm mt-2" | |
| role="alert" | |
| aria-live="polite" | |
| > | |
| {shipError} | |
| </p> |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message (lines 105-107) is placed as a third child in the flex container with justify-between (line 69). This will position the error horizontally alongside the project info and buttons, which is likely not the intended layout. Consider moving the error message outside this flex container (after line 108) or restructuring the layout to display the error below the buttons on a new line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type annotation on line 10 is unsafe because
req.json()returnsany, which doesn't guarantee the data will match this type at runtime. Consider adding runtime validation using a library like Zod, or keeping the type asunknownand performing explicit type checking before using the data properties.