Skip to content
Merged
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
32 changes: 22 additions & 10 deletions app/(main)/home-page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import { SymbolHeader } from "@/components/SymbolHeader";
import { TabNavigation } from "@/components/TabNavigation";
import { LoadingSpinner } from "@/components/LoadingSpinner";
import { usePricingTier } from "@/lib/use-pricing-tier";
import {
EXPLANATIONS_PROVIDER_CHANGED_EVENT,
getAIProviderHeaders,
} from "@/lib/explanation-provider";
import { AIPredictionPanel } from "@/components/AIPredictionPanel";
import { StockOfTheDayPanel } from "@/components/StockOfTheDayPanel";
const OverviewTab = dynamic(
Expand Down Expand Up @@ -379,12 +383,20 @@ export function HomePageClient() {
};
}, []);

const getAIProviderHeader = (): HeadersInit => {
if (typeof window === "undefined") return {};
const provider = localStorage.getItem("explanations_provider");
if (!provider) return {};
return { "x-ai-provider": provider };
};
const [aiProviderVersion, setAiProviderVersion] = useState(0);

useEffect(() => {
const onProviderChanged = () => setAiProviderVersion((v) => v + 1);
window.addEventListener(
EXPLANATIONS_PROVIDER_CHANGED_EVENT,
onProviderChanged
);
return () =>
window.removeEventListener(
EXPLANATIONS_PROVIDER_CHANGED_EVENT,
onProviderChanged
);
}, []);

useEffect(() => {
const fetchAIPrediction = async () => {
Expand All @@ -398,7 +410,7 @@ export function HomePageClient() {
try {
const response = await fetch(
`/api/market/ai-prediction/${selectedSymbol}`,
{ headers: getAIProviderHeader(), credentials: "include" }
{ headers: getAIProviderHeaders(), credentials: "include" }
);
if (!response.ok) {
const body = (await response.json().catch(() => ({}))) as {
Expand All @@ -423,7 +435,7 @@ export function HomePageClient() {
};

fetchAIPrediction();
}, [selectedSymbol, hasAIAccess]);
}, [selectedSymbol, hasAIAccess, aiProviderVersion]);

useEffect(() => {
const fetchStockOfTheDay = async () => {
Expand All @@ -436,7 +448,7 @@ export function HomePageClient() {
setStockOfTheDayLoading(true);
try {
const response = await fetch("/api/market/stock-of-the-day", {
headers: getAIProviderHeader(),
headers: getAIProviderHeaders(),
credentials: "include",
});
if (!response.ok) {
Expand All @@ -462,7 +474,7 @@ export function HomePageClient() {
};

fetchStockOfTheDay();
}, [hasAIAccess, selectedSymbol]);
}, [hasAIAccess, selectedSymbol, aiProviderVersion]);

const handleTimeRangeChange = (range: TimeRange) => {
setTimeRange(range);
Expand Down
5 changes: 5 additions & 0 deletions app/(main)/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ProfileSettings } from "@/components/ProfileSettings";

export default function ProfilePage() {
return <ProfileSettings />;
}
28 changes: 20 additions & 8 deletions app/(main)/stock-of-the-day/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import { useEffect, useState } from "react";
import { usePricingTier } from "@/lib/use-pricing-tier";
import {
EXPLANATIONS_PROVIDER_CHANGED_EVENT,
getAIProviderHeaders,
} from "@/lib/explanation-provider";
import { StockOfTheDayPanel } from "@/components/StockOfTheDayPanel";
import type { StockOfTheDay } from "@/types";

Expand All @@ -19,6 +23,20 @@ export default function StockOfTheDayPage() {
const [item, setItem] = useState<StockOfTheDay | null>(null);
const [loading, setLoading] = useState(false);
const [loadError, setLoadError] = useState<string | null>(null);
const [aiProviderVersion, setAiProviderVersion] = useState(0);

useEffect(() => {
const onProviderChanged = () => setAiProviderVersion((v) => v + 1);
window.addEventListener(
EXPLANATIONS_PROVIDER_CHANGED_EVENT,
onProviderChanged
);
return () =>
window.removeEventListener(
EXPLANATIONS_PROVIDER_CHANGED_EVENT,
onProviderChanged
);
}, []);

useEffect(() => {
const load = async () => {
Expand All @@ -28,16 +46,10 @@ export default function StockOfTheDayPage() {
return;
}

const aiHeaders: Record<string, string> = {};
if (typeof window !== "undefined") {
const provider = localStorage.getItem("explanations_provider");
if (provider) aiHeaders["x-ai-provider"] = provider;
}

setLoading(true);
try {
const response = await fetch("/api/market/stock-of-the-day", {
headers: aiHeaders,
headers: getAIProviderHeaders(),
credentials: "include",
cache: "no-store",
});
Expand All @@ -61,7 +73,7 @@ export default function StockOfTheDayPage() {
};

load();
}, [hasAIAccess]);
}, [hasAIAccess, aiProviderVersion]);

useEffect(() => {
const loadBYOKAccess = async () => {
Expand Down
8 changes: 7 additions & 1 deletion app/api/subscription/current/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@ export async function GET(request: NextRequest) {
}

const tier = await subscriptionService.getCurrentTier(auth.id);
const record = await subscriptionService.getSubscriptionRecord(auth.id);

return NextResponse.json({
success: true,
data: { tier },
data: {
tier,
currentPeriodEnd: record?.currentPeriodEnd ?? null,
cancelAtPeriodEnd: record?.cancelAtPeriodEnd ?? false,
status: record?.status ?? null,
},
timestamp: new Date(),
});
} catch (error) {
Expand Down
19 changes: 6 additions & 13 deletions components/AIPredictionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,22 +127,15 @@ export function AIPredictionPanel({
{isMissingByokApiKeyMessage(error) && (
<div className="mt-3 space-y-2">
<p className="text-xs opacity-90">
Add your API key under the avatar menu → profile → API keys,
then pick the same provider as your explanation model.
Add your API key on the Profile page under API keys, then
pick the same provider as your explanation model.
</p>
<button
type="button"
onClick={() => {
if (typeof window !== "undefined") {
window.dispatchEvent(
new Event("open-user-profile-menu")
);
}
}}
<a
href="/profile"
className="inline-flex items-center rounded-lg bg-blue-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-blue-700"
>
Open profile menu
</button>
Open profile
</a>
</div>
)}
</div>
Expand Down
8 changes: 4 additions & 4 deletions components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ export function Navigation({

return (
<nav
className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700"
className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 overflow-visible"
aria-label="Main navigation"
data-testid="navigation"
data-active-section={activeSection}
>
<div className="max-w-7xl xl:max-w-[1400px] mx-auto px-4 sm:px-6">
<div className="flex items-center justify-between h-16 gap-4">
<div className="flex items-center justify-between h-16 gap-4 overflow-visible">
<Link
href="/"
className="flex-shrink-0 text-lg font-bold text-gray-900 dark:text-gray-100"
Expand All @@ -45,7 +45,7 @@ export function Navigation({
<span className="sm:hidden">SE</span>
</Link>

<div className="hidden md:flex items-center gap-1">
<div className="hidden md:flex items-center gap-2">
{MAIN_NAV.map((link) => (
<Link
key={link.id}
Expand All @@ -71,7 +71,7 @@ export function Navigation({
/>
</div>

<div className="flex items-center gap-2">
<div className="relative z-[10060] flex items-center gap-2 flex-shrink-0">
<UserProfileMenu />
<ThemeToggle />
<button
Expand Down
Loading
Loading