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
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
registry=https://registry.npmjs.org/
19 changes: 16 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { Geist, Geist_Mono } from "next/font/google";
import { Analytics } from "@vercel/analytics/next";
import {Toaster} from "@/components/ui/sonner";
import "./globals.css";
import { cookies } from "next/headers";
import { LocaleProvider } from "@/components/LocaleProvider";
import { getDictionary, type Locale } from "@/i18n";

const geistSans = Geist({
variable: "--font-geist-sans",
Expand All @@ -19,17 +22,27 @@ export const metadata: Metadata = {
description: "OpenStock is an open-source alternative to expensive market platforms. Track real-time prices, set personalized alerts, and explore detailed company insights — built openly, for everyone, forever free.",
};

export default function RootLayout({
const locales: Locale[] = ['en', 'zh-CN'];
const defaultLocale: Locale = 'en';
Comment on lines +25 to +26

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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Eliminate duplication: import locale constants from i18n module.

The locales array and defaultLocale are duplicated from i18n/index.ts. This violates DRY and creates a maintenance risk where the two definitions could diverge.

♻️ Proposed refactor to use centralized constants
-const locales: Locale[] = ['en', 'zh-CN'];
-const defaultLocale: Locale = 'en';
+import { defaultLocale } from '@/i18n';
+
+const locales: Locale[] = ['en', 'zh-CN']; // Note: consider exporting this from i18n/index.ts as well

Or better yet, if you export locales from i18n/index.ts:

-const locales: Locale[] = ['en', 'zh-CN'];
-const defaultLocale: Locale = 'en';
+import { defaultLocale } from '@/i18n';
+import type { Locale } from '@/i18n';
+
+// Derive locales from dictionaries keys
+const locales = Object.keys(dictionaries) as Locale[];

However, the cleanest approach is to export a locales array from i18n/index.ts:

In i18n/index.ts:

export const locales = Object.keys(dictionaries) as Locale[];

Then in layout.tsx:

-const locales: Locale[] = ['en', 'zh-CN'];
-const defaultLocale: Locale = 'en';
+import { defaultLocale, dictionaries } from '@/i18n';
+import type { Locale } from '@/i18n';
+
+const locales = Object.keys(dictionaries) as Locale[];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/layout.tsx` around lines 25 - 26, Replace the duplicated locale
definitions in app/layout.tsx by importing the centralized constants from the
i18n module: remove the local const locales and const defaultLocale and instead
import the exported symbols (e.g., locales and defaultLocale or locales and
derive default from i18n) from i18n/index.ts (the file that defines dictionaries
and exports locales), then use those imported names in layout.tsx (referencing
the existing locales and defaultLocale identifiers in your layout component).


export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies();
const localeCookie = cookieStore.get('NEXT_LOCALE')?.value as Locale;
const locale: Locale = localeCookie && locales.includes(localeCookie) ? localeCookie : defaultLocale;
const dictionary = getDictionary(locale);

return (
<html lang="en" className="dark">
<html lang={locale} className="dark">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<LocaleProvider locale={locale} dictionary={dictionary}>
{children}
</LocaleProvider>
<Toaster/>
<Analytics />
</body>
Expand Down
25 changes: 15 additions & 10 deletions components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
'use client';

import Link from "next/link";
import Image from "next/image";
import OpenDevSocietyBranding from "./OpenDevSocietyBranding";
import { useDictionary } from "@/hooks/useDictionary";

const Footer = () => {
const dict = useDictionary();

return (
<footer className="bg-gray-900 text-white border-t border-gray-800">
<div className="container mx-auto px-4 py-12">
Expand All @@ -19,11 +24,11 @@ const Footer = () => {
/>
</Link>
<p className="text-gray-400 mb-6 max-w-md">
OpenStock is an open-source alternative to expensive market platforms. Track real-time prices, set personalized alerts, and explore detailed company insights — built openly, for everyone, forever free.
{dict.footer.description}
</p>
<div className="mb-8">
<Link href="/about" className="text-teal-400 hover:text-teal-300 font-medium inline-flex items-center gap-1 group">
Learn about our mission
{dict.footer.learnMore}
<span className="group-hover:translate-x-1 transition-transform">→</span>
</Link>
</div>
Expand All @@ -35,7 +40,7 @@ const Footer = () => {
className="text-gray-400 hover:text-white transition-colors duration-200 relative group"
>
<span className="relative">
GitHub
{dict.footer.github}
<span className="absolute left-0 bottom-0 w-0 h-0.5 bg-white transition-all duration-300 group-hover:w-full"></span>
</span>
</Link>
Expand All @@ -46,7 +51,7 @@ const Footer = () => {
className="text-gray-400 hover:text-blue-400 transition-colors duration-200 relative group"
>
<span className="relative">
LinkedIn
{dict.footer.linkedin}
<span className="absolute left-0 bottom-0 w-0 h-0.5 bg-blue-400 transition-all duration-300 group-hover:w-full"></span>
</span>
</Link>
Expand All @@ -57,7 +62,7 @@ const Footer = () => {
className="text-gray-400 hover:text-blue-600 transition-colors duration-200 relative group"
>
<span className="relative">
Discord
{dict.footer.discord}
<span className="absolute left-0 bottom-0 w-0 h-0.5 bg-blue-600 transition-all duration-300 group-hover:w-full"></span>
</span>
</Link>
Expand All @@ -66,28 +71,28 @@ const Footer = () => {

{/* Resources */}
<div>
<h3 className="text-lg font-semibold mb-4">Resources</h3>
<h3 className="text-lg font-semibold mb-4">{dict.footer.resources}</h3>
<ul className="space-y-2">
<li>
<Link href="/api-docs" className="text-gray-400 hover:text-white transition-colors duration-200 relative group">
<span className="relative">
API Documentation
{dict.footer.apiDocumentation}
<span className="absolute left-0 bottom-0 w-0 h-0.5 bg-white transition-all duration-300 group-hover:w-full"></span>
</span>
</Link>
</li>
<li>
<Link href="/help" className="text-gray-400 hover:text-white transition-colors duration-200 relative group">
<span className="relative">
Help Center
{dict.footer.helpCenter}
<span className="absolute left-0 bottom-0 w-0 h-0.5 bg-white transition-all duration-300 group-hover:w-full"></span>
</span>
</Link>
</li>
<li>
<Link href="/terms" className="text-gray-400 hover:text-white transition-colors duration-200 relative group">
<span className="relative">
Terms of Service
{dict.footer.termsOfService}
<span className="absolute left-0 bottom-0 w-0 h-0.5 bg-white transition-all duration-300 group-hover:w-full"></span>
</span>
</Link>
Expand All @@ -101,7 +106,7 @@ const Footer = () => {
<div className="flex flex-col md:flex-row justify-between items-center">
{/* Copyright */}
<div className="text-gray-400 text-sm mb-4 md:mb-0">
© {new Date().getFullYear()} Open Dev Society. All rights reserved.
© {new Date().getFullYear()} {dict.footer.copyright}
</div>

{/* Open Dev Society Branding */}
Expand Down
51 changes: 51 additions & 0 deletions components/LanguageSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use client';

import { useTransition } from 'react';
import { useRouter, usePathname } from 'next/navigation';
import { Globe } from 'lucide-react';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { useLocale } from '@/hooks/useLocale';

export default function LanguageSwitcher() {
const router = useRouter();
const pathname = usePathname();
const locale = useLocale();
const [isPending, startTransition] = useTransition();

const handleLocaleChange = (newLocale: string) => {
startTransition(() => {
const segments = pathname.split('/');
// Remove existing locale segment if present
if (segments[1] === 'en' || segments[1] === 'zh-CN') {
segments[1] = newLocale;
} else {
segments.splice(1, 0, newLocale);
}
router.push(segments.join('/'));
document.cookie = `NEXT_LOCALE=${newLocale};path=/;max-age=31536000`;
});
};
Comment on lines +21 to +33

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.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Refactor to use setLocale from LocaleProvider and eliminate hardcoded locale values.

This implementation has several maintainability concerns:

  1. Hardcoded locale values (line 25): The explicit checks for 'en' and 'zh-CN' duplicate the locale configuration that should be centralized in i18n/index.ts. When new locales are added, this code must be manually updated.

  2. Manual cookie manipulation (line 31): According to the review context, LocaleProvider already exports a setLocale function that "updates both state and persists the NEXT_LOCALE cookie with one-year max age." Manually setting the cookie here bypasses that contract and creates two separate cookie-setting paths.

  3. Fragile path manipulation (lines 23-30): The logic assumes segments[1] is always safe to access and replace, but doesn't handle edge cases like empty paths, trailing slashes, or already-localized nested routes.

♻️ Recommended refactoring approach

Import setLocale from the LocaleProvider context and supportedLocales from i18n configuration:

+import { useContext } from 'react';
+import { LocaleContext } from '@/components/LocaleProvider';
+import { supportedLocales } from '@/i18n';

Then simplify the handler to delegate locale persistence and path logic:

+const { setLocale } = useContext(LocaleContext);
+
 const handleLocaleChange = (newLocale: string) => {
   startTransition(() => {
+    setLocale(newLocale);
     const segments = pathname.split('/');
-    // Remove existing locale segment if present
-    if (segments[1] === 'en' || segments[1] === 'zh-CN') {
+    if (supportedLocales.includes(segments[1] as any)) {
       segments[1] = newLocale;
     } else {
       segments.splice(1, 0, newLocale);
     }
     router.push(segments.join('/'));
-    document.cookie = `NEXT_LOCALE=${newLocale};path=/;max-age=31536000`;
   });
 };

This ensures a single source of truth for supported locales and consistent cookie handling.

📝 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
const handleLocaleChange = (newLocale: string) => {
startTransition(() => {
const segments = pathname.split('/');
// Remove existing locale segment if present
if (segments[1] === 'en' || segments[1] === 'zh-CN') {
segments[1] = newLocale;
} else {
segments.splice(1, 0, newLocale);
}
router.push(segments.join('/'));
document.cookie = `NEXT_LOCALE=${newLocale};path=/;max-age=31536000`;
});
};
import { useContext } from 'react';
import { LocaleContext } from '@/components/LocaleProvider';
import { supportedLocales } from '@/i18n';
// Add this line in the component before the handleLocaleChange function:
const { setLocale } = useContext(LocaleContext);
const handleLocaleChange = (newLocale: string) => {
startTransition(() => {
setLocale(newLocale);
const segments = pathname.split('/');
if (supportedLocales.includes(segments[1] as any)) {
segments[1] = newLocale;
} else {
segments.splice(1, 0, newLocale);
}
router.push(segments.join('/'));
});
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/LanguageSwitcher.tsx` around lines 21 - 33, The handler
handleLocaleChange currently hardcodes locales, manually mutates cookies, and
does fragile path segment logic; replace this by importing and calling setLocale
from LocaleProvider (which updates state and persists NEXT_LOCALE) and use
supportedLocales from i18n to detect/validate current locale in the path, then
compute the new pathname robustly (handling empty paths/trailing slashes) and
call router.push with the updated path; remove the direct document.cookie write
and the hardcoded 'en'/'zh-CN' checks so locale logic is centralized in
setLocale/supportedLocales.


return (
<Select value={locale} onValueChange={handleLocaleChange} disabled={isPending}>
<SelectTrigger className="w-[130px] h-9 bg-gray-700 border-gray-600 text-gray-300 hover:bg-gray-600 hover:text-white">
<Globe className="h-4 w-4 mr-2" />
<SelectValue placeholder="Language" />
</SelectTrigger>
<SelectContent className="bg-gray-800 border-gray-700 text-gray-200">
<SelectItem value="en" className="hover:bg-gray-700 focus:bg-gray-700">
English
</SelectItem>
<SelectItem value="zh-CN" className="hover:bg-gray-700 focus:bg-gray-700">
简体中文
</SelectItem>
</SelectContent>
</Select>
Comment on lines +36 to +49

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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Render locale options dynamically from i18n configuration.

The SelectItem elements hardcode both locale values ("en", "zh-CN") and display labels ("English", "简体中文"). According to the review context, i18n/index.ts exports both supportedLocales and human-readable localeNames. Hardcoding these values creates a maintenance burden when adding new locales.

♻️ Proposed fix to render options dynamically

Import the i18n configuration:

+import { supportedLocales, localeNames } from '@/i18n';

Replace the hardcoded SelectItem list with a mapped array:

 <SelectContent className="bg-gray-800 border-gray-700 text-gray-200">
-  <SelectItem value="en" className="hover:bg-gray-700 focus:bg-gray-700">
-    English
-  </SelectItem>
-  <SelectItem value="zh-CN" className="hover:bg-gray-700 focus:bg-gray-700">
-    简体中文
-  </SelectItem>
+  {supportedLocales.map((loc) => (
+    <SelectItem key={loc} value={loc} className="hover:bg-gray-700 focus:bg-gray-700">
+      {localeNames[loc]}
+    </SelectItem>
+  ))}
 </SelectContent>
📝 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
<Select value={locale} onValueChange={handleLocaleChange} disabled={isPending}>
<SelectTrigger className="w-[130px] h-9 bg-gray-700 border-gray-600 text-gray-300 hover:bg-gray-600 hover:text-white">
<Globe className="h-4 w-4 mr-2" />
<SelectValue placeholder="Language" />
</SelectTrigger>
<SelectContent className="bg-gray-800 border-gray-700 text-gray-200">
<SelectItem value="en" className="hover:bg-gray-700 focus:bg-gray-700">
English
</SelectItem>
<SelectItem value="zh-CN" className="hover:bg-gray-700 focus:bg-gray-700">
简体中文
</SelectItem>
</SelectContent>
</Select>
import { supportedLocales, localeNames } from '@/i18n';
// ... other imports and component code ...
<Select value={locale} onValueChange={handleLocaleChange} disabled={isPending}>
<SelectTrigger className="w-[130px] h-9 bg-gray-700 border-gray-600 text-gray-300 hover:bg-gray-600 hover:text-white">
<Globe className="h-4 w-4 mr-2" />
<SelectValue placeholder="Language" />
</SelectTrigger>
<SelectContent className="bg-gray-800 border-gray-700 text-gray-200">
{supportedLocales.map((loc) => (
<SelectItem key={loc} value={loc} className="hover:bg-gray-700 focus:bg-gray-700">
{localeNames[loc]}
</SelectItem>
))}
</SelectContent>
</Select>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/LanguageSwitcher.tsx` around lines 36 - 49, Replace the hardcoded
SelectItem entries with a dynamic map over your i18n config: import
supportedLocales and localeNames from i18n/index.ts, then inside the
SelectContent iterate supportedLocales to render a SelectItem for each locale
using the locale string for the value and localeNames[locale] (or a fallback)
for the display text; keep existing props/handlers (locale, handleLocaleChange,
isPending, SelectTrigger/SelectValue) and preserve the current className/styling
on SelectItem and SelectContent.

);
}
43 changes: 43 additions & 0 deletions components/LocaleProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use client';

import React, { createContext, useEffect, useState } from 'react';
import { type Locale, getDictionary } from '@/i18n';
import type { Dictionary } from '@/i18n/en';

interface LocaleContextType {
locale: Locale;
dictionary: Dictionary;
setLocale: (locale: Locale) => void;
}

export const LocaleContext = createContext<LocaleContextType>({
locale: 'en',
dictionary: getDictionary('en'),
setLocale: () => {},
});

interface LocaleProviderProps {
children: React.ReactNode;
locale: Locale;
dictionary: Dictionary;
}

export function LocaleProvider({ children, locale: initialLocale, dictionary }: LocaleProviderProps) {
const [locale, setLocale] = useState<Locale>(initialLocale);
const [currentDictionary, setCurrentDictionary] = useState<Dictionary>(dictionary);

useEffect(() => {
setCurrentDictionary(getDictionary(locale));
}, [locale]);
Comment on lines +25 to +31

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 | 🟠 Major | ⚡ Quick win

Sync provider state when locale props change after mount.

useState(initialLocale) and useState(dictionary) only read props once. If parent props change later, provider state can become stale and show the wrong language until a manual toggle.

Proposed fix
 export function LocaleProvider({ children, locale: initialLocale, dictionary }: LocaleProviderProps) {
     const [locale, setLocale] = useState<Locale>(initialLocale);
     const [currentDictionary, setCurrentDictionary] = useState<Dictionary>(dictionary);

+    useEffect(() => {
+        setLocale(initialLocale);
+        setCurrentDictionary(dictionary);
+    }, [initialLocale, dictionary]);
+
     useEffect(() => {
         setCurrentDictionary(getDictionary(locale));
     }, [locale]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/LocaleProvider.tsx` around lines 25 - 31, LocaleProvider currently
seeds state from props once, so if parent updates initialLocale or dictionary
later the provider stays stale; add an effect that watches initialLocale and
dictionary and calls setLocale(initialLocale) and
setCurrentDictionary(dictionary || getDictionary(initialLocale)) (or update
currentDictionary via getDictionary when only initialLocale changes) so the
component syncs to prop changes; update the existing useEffect usages (the one
that calls getDictionary(locale)) to coexist with this prop-sync effect and
reference LocaleProvider, initialLocale, dictionary, setLocale,
setCurrentDictionary, and getDictionary when making the changes.


const changeLocale = (newLocale: Locale) => {
setLocale(newLocale);
document.cookie = `NEXT_LOCALE=${newLocale};path=/;max-age=31536000`;

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 | 🟡 Minor | ⚡ Quick win

Add SameSite attribute to cookie for security.

The NEXT_LOCALE cookie should include a SameSite attribute to protect against CSRF attacks. Consider adding at minimum SameSite=Lax.

🔒 Proposed fix to add SameSite attribute
-        document.cookie = `NEXT_LOCALE=${newLocale};path=/;max-age=31536000`;
+        document.cookie = `NEXT_LOCALE=${newLocale};path=/;max-age=31536000;SameSite=Lax`;
📝 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
document.cookie = `NEXT_LOCALE=${newLocale};path=/;max-age=31536000`;
document.cookie = `NEXT_LOCALE=${newLocale};path=/;max-age=31536000;SameSite=Lax`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/LocaleProvider.tsx` at line 35, The NEXT_LOCALE cookie assignment
in LocaleProvider (document.cookie in components/LocaleProvider.tsx) is missing
a SameSite attribute; update the cookie string in the locale setter (where
document.cookie = `NEXT_LOCALE=${newLocale}...`) to include a SameSite attribute
(e.g., append `;SameSite=Lax`) and, if you need cross-site usage, use
`SameSite=None;Secure` instead to ensure the cookie is sent securely.

};

return (
<LocaleContext.Provider value={{ locale, dictionary: currentDictionary, setLocale: changeLocale }}>
{children}
</LocaleContext.Provider>
);
}
14 changes: 10 additions & 4 deletions components/NavItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@


import React, { createContext, useContext } from 'react'
import {NAV_ITEMS} from "@/lib/constants";
import Link from "next/link";
import {usePathname} from "next/navigation";
import SearchCommand from "@/components/SearchCommand";
import { Heart } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useDictionary } from '@/hooks/useDictionary';

// Create context for popup state
const DonatePopupContext = createContext<{
Expand All @@ -20,6 +20,7 @@ export const useDonatePopup = () => useContext(DonatePopupContext);

const NavItems = ({initialStocks}: { initialStocks: StockWithWatchlistStatus[]}) => {
const pathname = usePathname()
const dict = useDictionary();

const isActive = (path: string) => {
if (path ==='/') return pathname === '/'
Expand All @@ -35,12 +36,17 @@ const NavItems = ({initialStocks}: { initialStocks: StockWithWatchlistStatus[]})
return (
<DonatePopupContext.Provider value={{ openDonatePopup }}>
<ul className="flex flex-col sm:flex-row p-2 gap-3 sm:gap-10 font-medium">
{NAV_ITEMS.map(({href, label}) => {
{dict.nav && [
{ href: '/', label: dict.nav.dashboard },
{ href: '/search', label: dict.nav.search },
{ href: '/watchlist', label: dict.nav.watchlist },
{ href: '/api-docs', label: dict.nav.apiDocs },
].map(({href, label}) => {
if (href === '/search') return (
<li key="search-trigger">
<SearchCommand
renderAs="text"
label="Search"
label={dict.common.search}
initialStocks={initialStocks}
/>
</li>
Expand All @@ -58,7 +64,7 @@ const NavItems = ({initialStocks}: { initialStocks: StockWithWatchlistStatus[]})
size="sm"
>
<Heart className="h-4 w-4 fill-current" />
Donate
{dict.nav.donate}
</Button>
</li>
</ul>
Expand Down
5 changes: 3 additions & 2 deletions database/mongoose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import mongoose from "mongoose";
const MONGODB_URI = process.env.MONGODB_URI;

// FIX: Set Google DNS and force IPv4 to avoid querySrv ECONNREFUSED
import dns from 'dns';
import * as dns from 'dns';
try {
// This is often more effective than setServers for Node 17+
if (dns.setDefaultResultOrder) {
Expand All @@ -30,7 +30,8 @@ if (!cached) {

export const connectToDatabase = async () => {
if (!MONGODB_URI) {
throw new Error("MongoDB URI is missing");
console.warn("MongoDB URI is missing — skipping database connection");
return null;
}
Comment on lines 31 to 35

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all consumers of connectToDatabase to verify they handle null

# Search for imports and usage of connectToDatabase
rg -n "connectToDatabase" --type ts --type tsx -C 3

Repository: Open-Dev-Society/OpenStock

Length of output: 98


🏁 Script executed:

# First, let's examine the mongoose.ts file to see the actual function signature and implementation
cat -n database/mongoose.ts

Repository: Open-Dev-Society/OpenStock

Length of output: 1786


🏁 Script executed:

# Search for connectToDatabase usage with corrected file type (remove tsx)
rg -n "connectToDatabase" --type ts -C 3

Repository: Open-Dev-Society/OpenStock

Length of output: 11379


Add explicit return type and fix consumers to handle null return value.

The function signature lacks an explicit return type but can return null when MONGODB_URI is missing (line 34). This creates a type safety gap where consumers might not handle the null case, leading to potential runtime errors.

Update the function signature:

Return type fix
-export const connectToDatabase = async () => {
+export const connectToDatabase = async (): Promise<typeof mongoose | null> => {

Critical: Multiple consumers do not check for null before using the return value:

  • scripts/test-db.ts:5, lib/inngest/functions.ts:214, lib/inngest/functions.ts:278, lib/actions/watchlist.actions.ts:11, lib/actions/watchlist.actions.ts:35, lib/actions/watchlist.actions.ts:48, lib/actions/watchlist.actions.ts:60, lib/actions/alert.actions.ts:15, lib/actions/alert.actions.ts:32, lib/actions/alert.actions.ts:44, lib/actions/alert.actions.ts:57 call it without null checks
  • lib/inngest/functions.ts:302, lib/inngest/functions.ts:340, lib/actions/watchlist.actions.ts:75, lib/actions/user.actions.ts:7, lib/actions/auth.actions.ts:41 assign the result to a variable and access .connection.db without checking if the returned value is null first

Only lib/better-auth/auth.ts:16-18 correctly checks for null and throws an appropriate error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/mongoose.ts` around lines 31 - 35, connectToDatabase should declare
an explicit return type (e.g., Promise<mongoose.Connection | null> or
Promise<YourDbClientType | null>) and consistently return that type when
MONGODB_URI is missing; update the signature of connectToDatabase to reflect
Promise<... | null>. Then update every consumer that assumes a non-null
result—specifically any code that uses the returned value to access
.connection.db or uses it without a null-check—to handle the null case: either
guard with if (!db) { throw/new error/early-return } or add proper conditional
logic before accessing .connection.db; ensure call sites that assign the result
to a variable check for null before property access and adjust types
accordingly.


if (cached.conn) return cached.conn;
Expand Down
10 changes: 10 additions & 0 deletions hooks/useDictionary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client';

import { useContext } from 'react';
import { LocaleContext } from '@/components/LocaleProvider';
import type { Dictionary } from '@/i18n/en';

export function useDictionary(): Dictionary {
const { dictionary } = useContext(LocaleContext);
return dictionary;
}
10 changes: 10 additions & 0 deletions hooks/useLocale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client';

import { useContext } from 'react';
import { LocaleContext } from '@/components/LocaleProvider';
import type { Locale } from '@/i18n';

export function useLocale(): Locale {
const { locale } = useContext(LocaleContext);
return locale;
}
Loading