diff --git a/app/api/enhance-prompt/route.ts b/app/api/enhance-prompt/route.ts index 1c1c731..f1e3b66 100644 --- a/app/api/enhance-prompt/route.ts +++ b/app/api/enhance-prompt/route.ts @@ -55,10 +55,17 @@ interface EnhancePromptErrorResponse { type EnhancePromptResponse = EnhancePromptSuccessResponse | EnhancePromptErrorResponse /** - * POST /api/enhance-prompt + * Handle POST /api/enhance-prompt: authenticate the user, enforce per-user rate limits, validate input, + * and return an enhanced prompt or negative prompt generated by the LLM. * - * Enhances an image generation prompt or negative prompt using LLM. - * Uses the request.signal to handle client disconnection/cancellation. + * Validates that `prompt` is present and `type` is either `"prompt"` or `"negative"`, then calls the + * appropriate enhancement routine while passing the request's abort signal for cancellation support. + * + * @param request - Incoming NextRequest; `request.signal` is used to cancel in-flight enhancement calls. + * @returns On success, an object with `{ success: true, data: { enhancedText: string } }`. + * On failure, an object with `{ success: false, error: { code: string, message: string } }` + * and an appropriate HTTP status code (e.g., 401 for unauthenticated, 429 for rate limit, + * 400 for validation errors, 499 for client-cancelled, 4xx/5xx for other failures). */ export async function POST( request: NextRequest @@ -198,4 +205,3 @@ export async function POST( ) } } - diff --git a/app/api/suggestions/route.ts b/app/api/suggestions/route.ts index 06a8a74..5821af8 100644 --- a/app/api/suggestions/route.ts +++ b/app/api/suggestions/route.ts @@ -47,11 +47,12 @@ interface SuggestionsErrorResponse { type SuggestionsResponse = SuggestionsSuccessResponse | SuggestionsErrorResponse /** - * POST /api/suggestions + * Produce contextual prompt suggestions for an authenticated user based on the request body. * - * Generates contextual prompt suggestions based on user input. - * Optimized for speed with minimal processing. - */ + * Enforces authentication and per-user rate limiting; includes rate-limit headers on responses. + * Empty prompts produce an empty suggestions array. If suggestion generation is cancelled, returns a 499 CANCELLED error; on unexpected errors returns an empty suggestions array (`success: true`) to avoid disrupting the UI. + * + * @returns A SuggestionsResponse: on success (`success: true`) contains `data.suggestions` (string[]); on failure (`success: false`) contains `error` with `code` and `message`. Rate-limit and authentication failures are returned with appropriate HTTP status codes and accompanying headers. export async function POST( request: NextRequest ): Promise> { @@ -147,4 +148,3 @@ export async function POST( }) } } - diff --git a/app/api/user/balance/route.ts b/app/api/user/balance/route.ts index 3541894..ad6d522 100644 --- a/app/api/user/balance/route.ts +++ b/app/api/user/balance/route.ts @@ -17,10 +17,13 @@ const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!) const POLLINATIONS_BALANCE_URL = "https://gen.pollinations.ai/api/usage?limit=100" /** - * GET /api/user/balance + * Retrieve the authenticated user's Pollinations pending spend/balance. * - * Fetches the user's pending spend/balance from Pollinations API. - * Requires authentication and a stored Pollinations API key. + * Proxies a request to the Pollinations balance endpoint using the user's stored (and decrypted) Pollinations API key. + * Requires the requester to be authenticated; responds with appropriate HTTP status codes for authentication failures, + * missing API key, decryption or unexpected errors, or with the original Pollinations API status when that request fails. + * + * @returns A NextResponse containing the Pollinations balance JSON on success, or a JSON error payload with an appropriate HTTP status (`401`, `404`, `500`, or the Pollinations API's status code) on failure. */ export async function GET(): Promise { try { @@ -115,4 +118,4 @@ export async function GET(): Promise { { status: 500 } ) } -} +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 08a4619..3d03e3f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -117,6 +117,14 @@ export const metadata: Metadata = { }, } +/** + * Root application layout that wraps pages with global providers, theming, fonts, and shared UI. + * + * Wraps `children` with HTML/body and a hierarchy of providers (theme, auth, data, query), renders the site header, toast container, performance insights, and analytics. + * + * @param children - The page content to render inside the layout + * @returns The root HTML element containing global providers and the rendered page content + */ export default function RootLayout({ children, }: Readonly<{ @@ -147,4 +155,3 @@ export default function RootLayout({ ) } - diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx index 7279791..74e09fc 100644 --- a/app/pricing/page.tsx +++ b/app/pricing/page.tsx @@ -132,6 +132,13 @@ const featureComparison = [ { feature: "NSFW Generations", starter: true, pro: true, competitors: false }, ] +/** + * Render the pricing page UI and coordinate user checkout and Stripe redirect feedback. + * + * Renders the full pricing experience (hero/value proposition, tier cards with CTAs, feature comparison table, FAQs, final CTA, and footer). Handles CTA actions and checkout flows: redirects immediately for the Starter trial, shows an informational message for Competitors, requires sign-in before starting a paid subscription, verifies Stripe configuration, initiates a Pro checkout session via the Convex action, redirects to Stripe Checkout, and shows toast notifications for Stripe success or cancellation redirects. + * + * @returns The React element for the pricing page UI. + */ function PricingContent() { const [loadingTier, setLoadingTier] = useState(null) const { isSignedIn } = useUser() @@ -526,4 +533,4 @@ export default function PricingPage() { ) -} +} \ No newline at end of file diff --git a/app/robots.ts b/app/robots.ts index 7ff348b..ee98e7c 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -1,5 +1,12 @@ import type { MetadataRoute } from "next" +/** + * Create the Next.js robots metadata describing crawl rules and sitemap location. + * + * The sitemap URL is constructed from the NEXT_PUBLIC_APP_URL environment variable, falling back to "https://bloomstudio.fun" when unset. + * + * @returns A MetadataRoute.Robots object with crawling rules (userAgent `"*"`, allow `"/"`, disallow `["/api/", "/studio/"]`) and `sitemap` set to the site's sitemap URL. + */ export default function robots(): MetadataRoute.Robots { const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://bloomstudio.fun" @@ -12,4 +19,4 @@ export default function robots(): MetadataRoute.Robots { }, sitemap: `${baseUrl}/sitemap.xml`, } -} +} \ No newline at end of file diff --git a/app/sitemap.ts b/app/sitemap.ts index 83c0ed5..8dd199d 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,5 +1,12 @@ import type { MetadataRoute } from "next" +/** + * Produce the sitemap configuration for the application. + * + * The returned array contains sitemap entries for the site; the root entry's URL is taken from `NEXT_PUBLIC_APP_URL` or falls back to "https://bloomstudio.fun", with `lastModified` set to the current time, `changeFrequency` set to `"daily"`, and `priority` set to `1`. + * + * @returns An array of sitemap entries for the site, including the root entry described above. + */ export default function sitemap(): MetadataRoute.Sitemap { const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://bloomstudio.fun" @@ -12,4 +19,4 @@ export default function sitemap(): MetadataRoute.Sitemap { }, // Add other static pages here ] -} +} \ No newline at end of file diff --git a/components/gallery/image-history.tsx b/components/gallery/image-history.tsx index 4bb0386..50afe57 100644 --- a/components/gallery/image-history.tsx +++ b/components/gallery/image-history.tsx @@ -11,8 +11,11 @@ import { Loader2 } from "lucide-react" import Image from "next/image" /** - * Component to display the user's generated image history. - * Supports infinite scrolling with a "Load More" button. + * Render the user's generated image history as a responsive grid with controls for visibility, deletion, and pagination. + * + * Displays skeleton placeholders while the first page is loading, an empty state when there are no images, and a grid of image cards otherwise. Each card shows the prompt and model name and provides a visibility toggle and delete action. When additional pages are available, a "Load More" button is shown to fetch more items. + * + * @returns The React element representing the image history UI */ export function ImageHistory() { const { results, status, loadMore } = useImageHistoryWithDisplayData() @@ -115,4 +118,4 @@ export function ImageHistory() { )} ) -} +} \ No newline at end of file diff --git a/components/studio/api-key-onboarding-modal.tsx b/components/studio/api-key-onboarding-modal.tsx index b0168b5..42ebf75 100644 --- a/components/studio/api-key-onboarding-modal.tsx +++ b/components/studio/api-key-onboarding-modal.tsx @@ -36,6 +36,18 @@ interface ApiKeyOnboardingModalProps { onClose?: () => void } +/** + * Displays a modal that guides authenticated users through obtaining and saving a Pollinations API key. + * + * The modal can operate in automatic mode (opens for authenticated users who do not yet have a saved API key) + * or in controlled mode when `forceOpen` is provided. It validates the entered key, posts it to the server, + * and invokes callbacks on completion or close. + * + * @param onComplete - Optional callback invoked after a key is successfully saved. + * @param forceOpen - When provided, forces the modal's open state (enables controlled mode). + * @param onClose - Optional callback invoked when the modal is closed (used in controlled mode). + * @returns The onboarding modal JSX; in automatic mode returns `null` when the modal should not be shown. + */ export function ApiKeyOnboardingModal({ onComplete, forceOpen, onClose }: ApiKeyOnboardingModalProps) { const [apiKey, setApiKey] = React.useState("") const [isSaving, setIsSaving] = React.useState(false) @@ -287,4 +299,4 @@ export function ApiKeyOnboardingModal({ onComplete, forceOpen, onClose }: ApiKey ) -} +} \ No newline at end of file diff --git a/components/studio/features/history/gallery-feature.tsx b/components/studio/features/history/gallery-feature.tsx index 8f435cb..7f7bfa0 100644 --- a/components/studio/features/history/gallery-feature.tsx +++ b/components/studio/features/history/gallery-feature.tsx @@ -24,15 +24,14 @@ export interface GalleryFeatureProps { } /** - * GalleryFeature component - composes hook logic with view - * - * @example - * ```tsx - * - * ``` + * Render a gallery UI by forwarding gallery-related props to GalleryView. + * + * Renders the GalleryView component with the provided active image, selection callback, and thumbnail size. + * + * @param activeImageId - ID of the currently active/highlighted image, if any + * @param onSelectImage - Callback invoked with the selected thumbnail's data when a thumbnail is chosen + * @param thumbnailSize - Size of thumbnails to display ("sm", "md", or "lg") + * @returns The gallery view element */ export function GalleryFeature({ activeImageId, @@ -46,4 +45,4 @@ export function GalleryFeature({ thumbnailSize={thumbnailSize} /> ) -} +} \ No newline at end of file diff --git a/components/studio/layout/studio-shell.tsx b/components/studio/layout/studio-shell.tsx index 39e365a..466a953 100644 --- a/components/studio/layout/studio-shell.tsx +++ b/components/studio/layout/studio-shell.tsx @@ -68,18 +68,11 @@ export interface StudioShellProps { } /** - * StudioShell - The main Studio composition component - * - * This component: - * 1. Initializes all feature hooks at the top level - * 2. Provides contexts for cross-feature communication - * 3. Handles generation orchestration (combining prompt + settings) - * 4. Renders the layout with composed features - * - * @example - * ```tsx - * - * ``` + * Compose and render the Studio interface while wiring feature hooks, cross-feature contexts, and image generation orchestration. + * + * Initializes prompt, generation, UI, and gallery hooks; coordinates single and batch image generation flows; manages upgrade and keyboard shortcuts; and renders the header, sidebar, canvas, gallery, and associated modals (API key onboarding, upgrade modal, and image lightbox). + * + * @param defaultLayout - Optional mapping of layout regions to their default sizes (e.g., `{ sidebar: 22, gallery: 18 }`) used to seed the studio layout */ export function StudioShell({ defaultLayout }: StudioShellProps) { // ======================================== @@ -429,4 +422,4 @@ export function StudioShell({ defaultLayout }: StudioShellProps) { /> ) -} +} \ No newline at end of file diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx index e6db3f2..4da5663 100644 --- a/components/ui/checkbox.tsx +++ b/components/ui/checkbox.tsx @@ -6,6 +6,13 @@ import { CheckIcon, MinusIcon } from "lucide-react" import { cn } from "@/lib/utils" +/** + * A styled checkbox that wraps Radix UI's Checkbox root and shows a check or minus icon for indeterminate state. + * + * @param className - Optional additional CSS class names to apply to the root element + * @param props - All other props are forwarded to Radix UI's Checkbox root (for example: `checked`, `disabled`, `onCheckedChange`) + * @returns The rendered CheckboxPrimitive.Root element with an indicator that shows a minus icon when `checked` is `"indeterminate"` and a check icon otherwise + */ function Checkbox({ className, ...props @@ -33,4 +40,4 @@ function Checkbox({ ) } -export { Checkbox } +export { Checkbox } \ No newline at end of file diff --git a/hooks/use-image-lightbox.ts b/hooks/use-image-lightbox.ts index a0bdd46..5b2390a 100644 --- a/hooks/use-image-lightbox.ts +++ b/hooks/use-image-lightbox.ts @@ -26,6 +26,26 @@ interface UseImageLightboxProps { isOpen: boolean } +/** + * Provides state and event handlers for displaying and interacting with an image lightbox. + * + * Manages copy-to-clipboard state for the image prompt, zooming, natural vs rendered image sizes, + * and drag-to-scroll behavior when zoomed; also exposes a scroll container ref. + * + * @param image - The image object shown in the lightbox (may be null); its optional `prompt` is used by copy behavior. + * @param isOpen - Whether the lightbox is currently open; used to reset transient state when changed. + * @returns An object with: + * - `copied`: `true` when the image prompt was recently copied to the clipboard. + * - `isZoomed`: `true` when the image is displayed zoomed in. + * - `naturalSize`: The image's natural `{ width, height }` in pixels. + * - `isDragging`: `true` while a drag-to-scroll operation is active. + * - `scrollContainerRef`: Ref to the scrollable container element. + * - `canZoom`: `true` when the image's natural size is sufficiently larger than its rendered size. + * - `handleCopyPrompt`: Click handler that copies the image prompt to the clipboard. + * - `handleImageLoad`: Image load handler that updates natural and rendered sizes. + * - `toggleZoom`: Click handler that toggles zoom state (centers content when enabling zoom). + * - `handleMouseDown`, `handleMouseMove`, `handleMouseUp`, `handleMouseLeave`: Mouse handlers for drag-to-scroll when zoomed. + */ export function useImageLightbox({ image, isOpen }: UseImageLightboxProps) { const [copied, setCopied] = React.useState(false) const [isZoomed, setIsZoomed] = React.useState(false) @@ -147,4 +167,4 @@ export function useImageLightbox({ image, isOpen }: UseImageLightboxProps) { handleMouseUp, handleMouseLeave } -} +} \ No newline at end of file diff --git a/hooks/use-studio-ui.ts b/hooks/use-studio-ui.ts index 0819172..d28769c 100644 --- a/hooks/use-studio-ui.ts +++ b/hooks/use-studio-ui.ts @@ -46,8 +46,8 @@ export interface UseStudioUIReturn { } /** - * Hook for managing Studio UI state. - * + * Manage Studio UI state for sidebar, gallery, and fullscreen/lightbox with stable callbacks. + * * @example * ```tsx * const { @@ -56,13 +56,15 @@ export interface UseStudioUIReturn { * showGallery, * openLightbox, * } = useStudioUI() - * + * * // Toggle sidebar * - * + * * // Open lightbox * openLightbox(image)} /> * ``` + * + * @returns An object exposing sidebar and gallery visibility (`showLeftSidebar`, `showGallery`) with their setters and toggle callbacks, fullscreen state (`isFullscreen`) with its setter, the current `lightboxImage` with its setter, and `openLightbox` / `closeLightbox` handlers. */ export function useStudioUI(): UseStudioUIReturn { // ======================================== @@ -127,4 +129,4 @@ export function useStudioUI(): UseStudioUIReturn { openLightbox, closeLightbox, } -} +} \ No newline at end of file diff --git a/lib/storage/r2-client.ts b/lib/storage/r2-client.ts index 5d3cc7c..b8c18a1 100644 --- a/lib/storage/r2-client.ts +++ b/lib/storage/r2-client.ts @@ -13,7 +13,13 @@ import { import { withRetry, isRetryableError } from "./retry" import crypto from "crypto" -// Validate required environment variables +/** + * Retrieve a required environment variable by name. + * + * @param name - The environment variable key to read from process.env + * @returns The value of the environment variable + * @throws Error if the environment variable is not set or is empty + */ function getEnvVar(name: string): string { const value = process.env[name] if (!value) { @@ -122,9 +128,16 @@ export async function imageExists(key: string): Promise { } /** - * Generate a unique object key for an image + * Generate a unique storage key for an image. * - * Format: {type}/{userId}/{timestamp}-{randomId}.{ext} + * The key is formatted as `{type}/{userHash}/{timestamp}-{randomId}.{ext}` where `userHash` + * is the SHA-256 hash of `userId`, `timestamp` is milliseconds since the Unix epoch, + * `randomId` is a UUID, and `ext` is derived from `contentType` (defaults to `jpg` if absent). + * + * @param userId - The identifier for the user; its SHA-256 hash is used in the key + * @param type - Top-level path segment, either `"generated"` or `"reference"` + * @param contentType - MIME type used to derive the file extension (e.g., `"image/png"`) + * @returns The generated object key string */ export function generateImageKey( userId: string, @@ -152,4 +165,4 @@ export function getPublicUrl(key: string): string { */ export function _resetClient(): void { _client = null -} +} \ No newline at end of file