← back to jdy8739__glassbox

Function bodies 106 total

All specs Real LLM only Function bodies
AnalysisResult function · typescript · L585-L593 (9 LOC)
apps/web/src/app/[lang]/analysis/result/page.tsx
export default function AnalysisResult() {
  return (
    <ErrorBoundary fallback={<CalculationErrorFallback />}>
      <Suspense fallback={<div className="min-h-screen px-6 py-8 flex items-center justify-center"><div className="text-black dark:text-white">{/* Loading handled by AnalysisResultContent */}</div></div>}>
        <AnalysisResultContent />
      </Suspense>
    </ErrorBoundary>
  );
}
useFetchPortfolioData function · typescript · L85-L136 (52 LOC)
apps/web/src/app/[lang]/analysis/result/useFetchPortfolioData.ts
export function useFetchPortfolioData(portfolioId: string | null) {
  const queryClient = useQueryClient();
  const params = useSearchParams();

  const portfolioQuery = useQuery<PortfolioData>({
    queryKey: ['portfolio', portfolioId || params.get('tickers')],
    queryFn: () => portfolioId ? fetchSavedPortfolio(portfolioId) : fetchFreshAnalysis(params),
    retry: false,
    staleTime: Infinity,
  });

  const reanalyzeMutation = useMutation({
    mutationFn: (data: AnalyzePortfolioRequest) => analyzePortfolio(data),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['portfolio'] }),
  });

  const savePortfolioMutation = useMutation({
    mutationFn: (data: CreatePortfolioRequest) => savePortfolio(data),
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ['portfolios'],
        refetchType: 'none'
      });
    },
  });

  const updatePortfolioMutation = useMutation({
    mutationFn: ({ id, data }: { id: string; data: UpdatePortfolioRequest 
IntroductionLayout function · typescript · L13-L19 (7 LOC)
apps/web/src/app/[lang]/introduction/layout.tsx
export default function IntroductionLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return children;
}
IntroErrorFallback function · typescript · L10-L22 (13 LOC)
apps/web/src/app/[lang]/introduction/page.tsx
function IntroErrorFallback() {
  return (
    <div className="min-h-screen flex items-center justify-center px-6">
      <div className="glass-card p-8 max-w-md text-center space-y-4">
        <h1 className="text-2xl font-bold text-black dark:text-white">Failed to Load Introduction</h1>
        <p className="text-black/70 dark:text-white/70">Please try again or contact support.</p>
        <LocalizedLink href="/" className="glass-button inline-block px-6 py-3">
          Back to Home
        </LocalizedLink>
      </div>
    </div>
  );
}
IntroductionPage function · typescript · L527-L533 (7 LOC)
apps/web/src/app/[lang]/introduction/page.tsx
export default function IntroductionPage() {
  return (
    <ErrorBoundary fallback={<IntroErrorFallback />}>
      <IntroductionContent />
    </ErrorBoundary>
  );
}
generateMetadata function · typescript · L25-L76 (52 LOC)
apps/web/src/app/[lang]/layout.tsx
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { lang } = await params;

  const titles: Record<Language, string> = {
    en: 'Glassbox - Portfolio Optimization Tool',
    ko: 'Glassbox - 포트폴리오 최적화 도구',
  };

  const descriptions: Record<Language, string> = {
    en: 'Transparent portfolio optimization and beta hedging with Glass UI design. Calculate efficient frontiers, optimize allocations, and hedge market risk.',
    ko: '투명한 포트폴리오 최적화 및 베타 헤징 - Glass UI 디자인. 효율적 투자선을 계산하고, 자산 배분을 최적화하며, 시장 리스크를 헤징하세요.',
  };

  // Use absolute URLs for better SEO (requires NEXT_PUBLIC_SITE_URL env variable)
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://glassbox.space';

  return {
    title: titles[lang as Language] || titles.en,
    description: descriptions[lang as Language] || descriptions.en,
    icons: {
      icon: '/favicon.svg',
    },
    alternates: {
      languages: {
        en: `${siteUrl}/en`,
        ko: `${siteUrl}/ko`,
    
LangLayout function · typescript · L78-L112 (35 LOC)
apps/web/src/app/[lang]/layout.tsx
export default async function LangLayout({ children, params }: Props) {
  const { lang } = await params;

  // Validate language - return 404 if not supported
  if (!isSupportedLanguage(lang)) {
    notFound();
  }

  // Use absolute URLs for hreflang tags (better SEO)
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://glassbox.space';

  return (
    <html lang={lang} suppressHydrationWarning>
      <head>
        {/* hreflang tags for SEO - must use absolute URLs */}
        <link rel="alternate" hrefLang="en" href={`${siteUrl}/en`} />
        <link rel="alternate" hrefLang="ko" href={`${siteUrl}/ko`} />
        <link rel="alternate" hrefLang="x-default" href={`${siteUrl}/en`} />

        {/* Theme initialization script */}
        <script
          dangerouslySetInnerHTML={{
            __html: `(function(){try{const t=localStorage.getItem('theme')||'system';const d=t==='dark'||(t==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches);if(d)document.docume
Source: Repobility analyzer · https://repobility.com
LoginPage function · typescript · L229-L239 (11 LOC)
apps/web/src/app/[lang]/login/page.tsx
export default function LoginPage({ params }: { params: Promise<{ lang: string }> }) {
  return (
    <Suspense fallback={
      <div className="min-h-screen flex items-center justify-center">
        <div className="w-8 h-8 border-4 border-cyan-500 border-t-transparent rounded-full animate-spin" />
      </div>
    }>
      <LoginContent params={params} />
    </Suspense>
  );
}
Home function · typescript · L434-L440 (7 LOC)
apps/web/src/app/[lang]/page.tsx
export default function Home() {
  return (
    <ErrorBoundary fallback={<LandingErrorFallback />}>
      <HomeContent />
    </ErrorBoundary>
  );
}
PasswordResetPage function · typescript · L9-L108 (100 LOC)
apps/web/src/app/[lang]/password-reset/page.tsx
export default function PasswordResetPage() {
  const { t } = useTranslation();
  const [isLoading, setIsLoading] = useState(false);
  const [isSent, setIsSent] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setIsLoading(true);
    // Simulate password reset email sending
    await new Promise(resolve => setTimeout(resolve, 2000));
    setIsLoading(false);
    setIsSent(true);
  };

  return (
    <main className="min-h-screen flex items-center justify-center relative overflow-hidden py-20 px-4 bg-slate-50 dark:bg-slate-950">
      {/* Background Elements */}
      <div className="fixed inset-0 overflow-hidden pointer-events-none">
        <div className="absolute top-20 left-10 w-72 h-72 bg-purple-500/10 rounded-full blur-3xl animate-pulse"></div>
        <div className="absolute bottom-40 right-20 w-96 h-96 bg-cyan-500/10 rounded-full blur-3xl animate-pulse delay-1000"></div>
        <div className="absolute top-1/2 left-1/2 -tr
AnalysisSettings function · typescript · L13-L64 (52 LOC)
apps/web/src/app/[lang]/portfolio/new/components/AnalysisSettings.tsx
export function AnalysisSettings({ dateRange, setDateRange, variant = 'desktop' }: AnalysisSettingsProps) {
  const { t } = useTranslation();
  const isMobile = variant === 'mobile';

  const content = (
    <div className={isMobile ? 'space-y-4' : 'space-y-2'}>
      <label className={`flex items-center gap-2 ${isMobile ? 'text-sm' : 'text-xs'} font-semibold text-black dark:text-white`}>
        <Lightbulb className={`${isMobile ? 'w-4 h-4' : 'w-3 h-3'} text-cyan-500`} />
        {t('portfolio.builder.analysis.settings.label')}
        <Tooltip content={t('portfolio.builder.analysis.settings.tooltip')} width={250}>
          <Info className={`${isMobile ? 'w-4 h-4' : 'w-3 h-3'} text-black/40 dark:text-white/40 cursor-help`} />
        </Tooltip>
      </label>

      <div className={isMobile ? 'grid grid-cols-2 gap-3' : 'space-y-3'}>
        <div>
          <p className={`text-xs text-black/60 dark:text-white/60 ${isMobile ? 'mb-2' : 'mb-1'}`}>
            {t('portfolio.builder.analys
AssetList function · typescript · L14-L126 (113 LOC)
apps/web/src/app/[lang]/portfolio/new/components/AssetList.tsx
export function AssetList({ items, colors, onRemove, onUpdateQuantity }: AssetListProps) {
  const { t } = useTranslation();
  if (items.length === 0) return null;

  return (
    <div className="space-y-3">
      {items.map((item, index) => {
        const color = colors[index % colors.length];
        
        return (
          <div
            key={item.symbol}
            className="group relative overflow-hidden rounded-2xl border border-white/40 dark:border-white/10 bg-white/40 dark:bg-black/20 backdrop-blur-xl transition-all duration-300 hover:scale-[1.01] hover:shadow-lg"
          >
            {/* Dynamic Gradient Background */}
            <div 
              className="absolute inset-0 opacity-10 group-hover:opacity-20 transition-opacity duration-300"
              style={{ background: `linear-gradient(135deg, ${color} 0%, transparent 100%)` }}
            />

            <div className="relative p-4 flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-5">
              
AuthRequiredDialog function · typescript · L17-L62 (46 LOC)
apps/web/src/app/[lang]/portfolio/new/components/AuthRequiredDialog.tsx
export function AuthRequiredDialog({ isOpen, onClose, pathname }: AuthRequiredDialogProps) {
  const { t } = useTranslation();

  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in">
      <div className="bg-white dark:bg-slate-900/95 backdrop-blur-xl rounded-2xl shadow-2xl max-w-md w-full p-6 space-y-6 border border-slate-200 dark:border-cyan-500/20">
        <div className="flex items-start gap-4">
          <div className="p-3 rounded-full bg-cyan-100 dark:bg-cyan-900/30">
            <Rocket className="w-6 h-6 text-cyan-600 dark:text-cyan-400" />
          </div>
          <div className="flex-1">
            <h3 className="text-xl font-bold text-slate-900 dark:text-white mb-2">
              {t('auth.required.title')}
            </h3>
            <p className="text-sm text-slate-600 dark:text-slate-300 leading-relaxed">
              {t('auth.required.message')}
           
BuilderErrorFallback function · typescript · L7-L43 (37 LOC)
apps/web/src/app/[lang]/portfolio/new/components/BuilderErrorFallback.tsx
export function BuilderErrorFallback() {
  const { t } = useTranslation();
  return (
    <main className="min-h-screen px-6 py-8 flex items-center justify-center">
      <div className="glass-panel p-8 max-w-md w-full text-center space-y-6 border-orange-500/20 bg-orange-500/5">
        <div className="mx-auto w-16 h-16 rounded-full bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center text-orange-500">
          <AlertCircle className="w-8 h-8" />
        </div>

        <div>
          <h2 className="text-xl font-bold text-black dark:text-white mb-2">
            {t('portfolio.builder.error.title')}
          </h2>
          <p className="text-sm text-black/60 dark:text-white/60">
            {t('portfolio.builder.error.description')}
          </p>
        </div>

        <div className="flex gap-3 justify-center">
          <Link
            href="/"
            className="px-4 py-2 rounded-lg bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 text-b
MobileBottomBar function · typescript · L12-L43 (32 LOC)
apps/web/src/app/[lang]/portfolio/new/components/MobileBottomBar.tsx
export function MobileBottomBar({ itemCount, validationError, onAnalyze }: MobileBottomBarProps) {
  const { t } = useTranslation();

  return (
    <div className="fixed bottom-0 left-0 right-0 p-4 bg-white/80 dark:bg-black/80 backdrop-blur-xl border-t border-white/20 z-50 lg:hidden">
      <div className="flex items-center gap-4 max-w-lg mx-auto">
        <div className="flex-1">
          <p className="text-xs text-black/50 dark:text-white/50 font-medium">
            {t('portfolio.builder.summary.label')}
          </p>

          <div className="flex items-baseline gap-2">
            <span className="text-lg font-bold text-black dark:text-white">
              {itemCount} {t('portfolio.builder.summary.assets')}
            </span>

            <span className="text-sm text-black/60 dark:text-white/60">~ 00k</span>
          </div>
        </div>

        <button
          onClick={onAnalyze}
          disabled={itemCount === 0 || !!validationError}
          className="glass-butt
Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
PageHeader function · typescript · L9-L36 (28 LOC)
apps/web/src/app/[lang]/portfolio/new/components/PageHeader.tsx
export function PageHeader() {
  const { t } = useTranslation();

  return (
    <div className="space-y-6">
      <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-black/10 dark:bg-white/10 border border-black/20 dark:border-white/20 backdrop-blur-sm">
        <span className="w-2 h-2 rounded-full bg-purple-400 animate-pulse"></span>
        <span className="text-sm font-medium text-black dark:text-white/80">
          {t('portfolio.builder.step')}
        </span>
      </div>

      <div className="space-y-4">
        <h1 className="text-5xl sm:text-6xl font-bold text-black dark:text-white">
          {t('portfolio.builder.title.part1')}
          <br />
          <span className="bg-gradient-to-r from-purple-300 to-cyan-300 bg-clip-text text-transparent">
            {t('portfolio.builder.title.part2')}
          </span>
        </h1>

        <p className="text-xl text-black dark:text-white/70 max-w-2xl">
          {t('portfolio.builder.description')}
       
PortfolioDonutChart function · typescript · L17-L90 (74 LOC)
apps/web/src/app/[lang]/portfolio/new/components/PortfolioDonutChart.tsx
export function PortfolioDonutChart({ items, colors }: PortfolioDonutChartProps) {
  const { t } = useTranslation();
  const data = useMemo(() => {
    if (items.length === 0) return [{ name: t('portfolio.chart.empty'), value: 1 }];
    return items.map((item) => ({
      name: item.symbol,
      value: item.quantity,
    }));
  }, [items, t]);

  const isEmpty = items.length === 0;

  return (
    <div className="h-[280px] w-full relative group [&_*]:outline-none [&_*]:focus:outline-none">
      {/* Background Glow Effect */}
      <div className="absolute inset-0 bg-gradient-to-b from-cyan-400/5 to-purple-400/5 rounded-full blur-3xl scale-75 animate-pulse"></div>

      <ResponsiveContainer width="100%" height="100%" debounce={50}>
        <PieChart>
          <Pie
            data={data}
            cx="50%"
            cy="50%"
            innerRadius={80}
            outerRadius={100}
            paddingAngle={isEmpty ? 0 : 4}
            dataKey="value"
            stroke="none"
PortfolioSummaryPanel function · typescript · L34-L114 (81 LOC)
apps/web/src/app/[lang]/portfolio/new/components/PortfolioSummaryPanel.tsx
export function PortfolioSummaryPanel({
  items,
  colors,
  dateRange,
  setDateRange,
  validationError,
  onAnalyze,
}: PortfolioSummaryPanelProps) {
  const { t } = useTranslation();

  return (
    <div className="hidden lg:block lg:col-span-4 sticky top-24">
      <div className="glass-panel p-6 space-y-6">
        <h3 className="text-xl font-bold text-black dark:text-white flex items-center gap-2">
          <TrendingUp className="w-5 h-5 text-cyan-400" />
          {t('portfolio.builder.preview.title')}
        </h3>

        {/* Donut Chart */}
        <PortfolioDonutChart items={items} colors={colors} />

        {/* Stats Summary */}
        {items.length > 0 && (
          <div className="grid grid-cols-2 gap-3 py-4 border-t border-b border-black/10 dark:border-white/10">
            <div>
              <p className="text-xs text-black/50 dark:text-white/50">
                {t('portfolio.builder.preview.total')}
              </p>
              <p className="text-lg font-b
QuickAddAssets function · typescript · L27-L92 (66 LOC)
apps/web/src/app/[lang]/portfolio/new/components/QuickAddAssets.tsx
export function QuickAddAssets({ onAdd }: QuickAddAssetsProps) {
  const { t } = useTranslation();

  return (
    <div className="space-y-5">
      <div className="flex items-center gap-3 pb-2">
        <div className="flex items-center gap-2">
          <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-cyan-500 to-purple-500 flex items-center justify-center">
            <Sparkles className="w-4 h-4 text-white" />
          </div>
          <label className="text-sm font-bold text-black dark:text-white">
            {t('portfolio.quick-add.title')}
          </label>
        </div>
        <div className="flex-1 h-px bg-gradient-to-r from-cyan-500/30 to-transparent rounded-full"></div>
      </div>

      <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
        {ASSETS.map((asset) => {
          const colorConfig = COLOR_MAP[asset.color] || COLOR_MAP.cyan;

          const Icon = asset.icon;
          return (
            <button
              key={asset
StarterTemplates function · typescript · L75-L107 (33 LOC)
apps/web/src/app/[lang]/portfolio/new/components/StarterTemplates.tsx
export function StarterTemplates({ onSelect }: StarterTemplatesProps) {
  const { t } = useTranslation();

  return (
    <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
      {TEMPLATES.map((template) => {
        const Icon = template.icon;
        return (
          <button
            key={template.id}
            onClick={() => onSelect(template.items)}
            className={`glass-card-gradient ${template.color} text-left p-5 group hover:scale-[1.02] transition-all`}
          >
            <div className="flex items-start justify-between mb-3">
              <div className="p-3 rounded-xl bg-white/20 dark:bg-black/20 backdrop-blur-md">
                <Icon className="w-6 h-6 text-black dark:text-white" />
              </div>
              <span className="text-xs font-bold px-2 py-1 rounded-full bg-white/20 dark:bg-black/20 text-black dark:text-white">
                {template.items.length} {t('portfolio.template.assets')}
              </span>
            </div>
   
useDialogs function · typescript · L7-L25 (19 LOC)
apps/web/src/app/[lang]/portfolio/new/hooks/useDialogs.ts
export function useDialogs() {
  const [showTodayWarning, setShowTodayWarning] = useState(false);
  const [showAuthDialog, setShowAuthDialog] = useState(false);
  const [showClearConfirm, setShowClearConfirm] = useState(false);

  return {
    // Today warning dialog
    showTodayWarning,
    setShowTodayWarning,

    // Auth required dialog
    showAuthDialog,
    setShowAuthDialog,

    // Clear confirmation dialog
    showClearConfirm,
    setShowClearConfirm,
  };
}
PortfolioBuilder function · typescript · L272-L278 (7 LOC)
apps/web/src/app/[lang]/portfolio/new/page.tsx
export default function PortfolioBuilder() {
  return (
    <ErrorBoundary fallback={<BuilderErrorFallback />}>
      <PortfolioBuilderContent />
    </ErrorBoundary>
  );
}
usePortfolioBuilder function · typescript · L13-L94 (82 LOC)
apps/web/src/app/[lang]/portfolio/new/usePortfolioBuilder.ts
export function usePortfolioBuilder() {
  // Portfolio state
  const [items, setItems] = useState<PortfolioItem[]>([]);
  const [dateRange, setDateRange] = useState({ startDate: '', endDate: '' });

  // Search state
  const [searchInput, setSearchInput] = useState('');
  const debouncedSearchInput = useDebounce(searchInput, 300);
  const [showDropdown, setShowDropdown] = useState(false);

  // Storage hook
  const { restoreDraft, saveDraft, hasRestoredDraft, setHasRestoredDraft } = usePortfolioStorage();

  // Restore portfolio from sessionStorage on mount
  // Guard against double-restore (React Strict Mode, hot reload)
  useEffect(() => {
    if (hasRestoredDraft) return; // Already restored, skip

    const draft = restoreDraft();
    if (draft) {
      setItems(draft.items);
      setDateRange(draft.dateRange);
      setHasRestoredDraft(true);
    }
  }, [restoreDraft, hasRestoredDraft, setHasRestoredDraft]);

  // Query for searching tickers
  const searchQuery = useQuery({
    q
Repobility · code-quality intelligence platform · https://repobility.com
PortfolioCard function · typescript · L18-L138 (121 LOC)
apps/web/src/app/[lang]/portfolios/components/PortfolioCard.tsx
export function PortfolioCard({ portfolio, onDelete, isDeleting, colors }: PortfolioCardProps) {
  const { t } = useTranslation();
  const params = useParams();
  const locale = (params?.lang as DateLocale) || 'en';
  const stats = portfolio.analysisSnapshot?.maxSharpe?.stats;

  // Calculate composition based on quantities (simple visual approximation)
  const totalQuantity = portfolio.quantities.reduce((a, b) => a + b, 0);
  const composition = portfolio.tickers.map((ticker, index) => ({
    ticker,
    percentage: (portfolio.quantities[index] / totalQuantity) * 100,
    color: colors[index % colors.length]
  }));

  return (
    <div className={`glass-panel p-5 group hover:border-cyan-500/30 transition-all flex flex-col h-full ${isDeleting ? 'opacity-50 pointer-events-none' : ''}`}>
      {/* Header */}
      <div className="flex justify-between items-start mb-4">
        <div>
          <h3 className="text-lg font-bold text-black dark:text-white group-hover:text-cyan-600 dark:group
LibraryErrorFallback function · typescript · L24-L60 (37 LOC)
apps/web/src/app/[lang]/portfolios/page.tsx
function LibraryErrorFallback() {
  const { t } = useTranslation();
  return (
    <main className="min-h-screen px-6 py-8 flex items-center justify-center">
      <div className="glass-panel p-8 max-w-md w-full text-center space-y-6 border-orange-500/20 bg-orange-500/5">
        <div className="mx-auto w-16 h-16 rounded-full bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center text-orange-500">
          <AlertCircle className="w-8 h-8" />
        </div>

        <div>
          <h2 className="text-xl font-bold text-black dark:text-white mb-2">
            {t('portfolio.library.error.title')}
          </h2>
          <p className="text-sm text-black/60 dark:text-white/60">
            {t('portfolio.library.error.description')}
          </p>
        </div>

        <div className="flex gap-3 justify-center">
          <Link
            href="/"
            className="px-4 py-2 rounded-lg bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 text-black da
PortfolioLibraryContent function · typescript · L62-L261 (200 LOC)
apps/web/src/app/[lang]/portfolios/page.tsx
function PortfolioLibraryContent() {
  const { t } = useTranslation();
  const { data: session, status } = useSession();
  const queryClient = useQueryClient();
  const [searchQuery, setSearchQuery] = useState('');
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
  const [portfolioToDelete, setPortfolioToDelete] = useState<string | null>(null);
  const [deleteError, setDeleteError] = useState<string | null>(null);

  const isAuthenticated = status === 'authenticated';
  const isAuthLoading = status === 'loading';

  // Fetch portfolios with useQuery - only runs when authenticated
  const { data: portfolios = [], isLoading } = useQuery<Portfolio[]>({
    queryKey: ['portfolios'],
    queryFn: getAllPortfolios,
    enabled: isAuthenticated, // Only fetch when authenticated
  });

  // Delete mutation with optimistic updates
  const deleteMutation = useMutation({
    mutationFn: deletePortfolio,
    onMutate: async (portfolioId) => {
      // Cancel outgoing refetches
  
PortfolioLibrary function · typescript · L263-L269 (7 LOC)
apps/web/src/app/[lang]/portfolios/page.tsx
export default function PortfolioLibrary() {
  return (
    <ErrorBoundary fallback={<LibraryErrorFallback />}>
      <PortfolioLibraryContent />
    </ErrorBoundary>
  );
}
SignupPage function · typescript · L286-L296 (11 LOC)
apps/web/src/app/[lang]/signup/page.tsx
export default function SignupPage() {
  return (
    <Suspense fallback={
      <div className="min-h-screen flex items-center justify-center">
        <div className="w-8 h-8 border-4 border-cyan-500 border-t-transparent rounded-full animate-spin" />
      </div>
    }>
      <SignupContent />
    </Suspense>
  );
}
ThemeProvider function · typescript · L18-L56 (39 LOC)
apps/web/src/app/providers.tsx
export function ThemeProvider({ children, lang = 'en' }: { children: ReactNode; lang?: string }) {
  const { theme, resolvedTheme, setTheme } = useThemeState();

  // Create i18n instance once with the server-provided language
  const [i18n] = useState(() => createI18nInstance(lang));

  // Ensure QueryClient is created once per component lifecycle
  const [queryClient] = useState(() => new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 5 * 60 * 1000, // 5 minutes
        gcTime: 10 * 60 * 1000, // 10 minutes (garbage collection)
        retry: 1, // Retry failed requests once
        refetchOnWindowFocus: false, // Don't refetch when window regains focus
      },
      mutations: {
        retry: 1, // Retry failed mutations once
      },
    },
  }));

  const value = useMemo(() => ({
    theme,
    resolvedTheme,
    setTheme
  }), [theme, resolvedTheme, setTheme]);

  return (
    <SessionProvider>
      <I18nextProvider i18n={i18n}>
      <QueryClientProvi
useTheme function · typescript · L58-L64 (7 LOC)
apps/web/src/app/providers.tsx
export function useTheme() {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}
robots function · typescript · L3-L14 (12 LOC)
apps/web/src/app/robots.ts
export default function robots(): MetadataRoute.Robots {
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://glassbox.space';

  return {
    rules: {
      userAgent: '*',
      allow: '/',
      disallow: ['/api/', '/profile/'],
    },
    sitemap: `${siteUrl}/sitemap.xml`,
  };
}
Repobility · MCP-ready · https://repobility.com
sitemap function · typescript · L4-L34 (31 LOC)
apps/web/src/app/sitemap.ts
export default function sitemap(): MetadataRoute.Sitemap {
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://glassbox.space';
  const lastModified = new Date();

  // Base routes to be localized
  const routes = ['', '/portfolio/new', '/portfolios'];
  
  const sitemapEntries: MetadataRoute.Sitemap = [];

  // Add localized versions for each route
  SUPPORTED_LANGUAGES.forEach((lang) => {
    routes.forEach((route) => {
      sitemapEntries.push({
        url: `${siteUrl}/${lang}${route}`,
        lastModified,
        changeFrequency: route === '' ? 'weekly' : 'monthly',
        priority: route === '' ? 1.0 : 0.8,
      });
    });
  });

  // Add root redirect path
  sitemapEntries.push({
    url: siteUrl,
    lastModified,
    changeFrequency: 'monthly',
    priority: 0.5,
  });

  return sitemapEntries;
}
BackButton function · typescript · L12-L23 (12 LOC)
apps/web/src/components/back-button.tsx
export function BackButton({ href, label }: BackButtonProps) {
  const { t } = useTranslation();
  return (
    <LocalizedLink
      href={href}
      className="text-sm font-semibold text-black/60 dark:text-white/60 hover:text-black dark:hover:text-white transition-colors duration-200 flex items-center gap-2 group"
    >
      <ArrowLeft className="w-4 h-4 transition-transform group-hover:-translate-x-0.5" />
      <span className="hidden sm:inline">{label ?? t('common.button.back')}</span>
    </LocalizedLink>
  );
}
ErrorBoundary.render method · typescript · L35-L84 (50 LOC)
apps/web/src/components/error-boundary.tsx
  public render() {
    if (this.state.hasError) {
      if (this.props.fallback) {
        return this.props.fallback;
      }

      return (
        <div className="min-h-[400px] w-full flex items-center justify-center p-6">
          <div className="glass-panel p-8 max-w-md w-full text-center space-y-6 border-red-500/20 bg-red-500/5">
            <div className="mx-auto w-16 h-16 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center text-red-500 mb-4">
              <AlertTriangle className="w-8 h-8" />
            </div>
            
            <div>
              <h2 className="text-xl font-bold text-black dark:text-white mb-2">
                Something went wrong
              </h2>
              <p className="text-sm text-black/60 dark:text-white/60 mb-4">
                We encountered an unexpected error while rendering this component.
              </p>
              {this.state.error && (
                <div className="bg-black/5 dark:bg-white/5 p-3
ExportDropdown function · typescript · L18-L102 (85 LOC)
apps/web/src/components/export-dropdown.tsx
export function ExportDropdown({
  portfolioName,
  items,
  analysis,
  align = 'right',
  className = '',
  showLabel = true
}: ExportDropdownProps) {
  const { t } = useTranslation();
  const [isOpen, setIsOpen] = useState(false);

  if (!analysis) return null;

  const handleExport = (type: 'csv' | 'pdf') => {
    try {
      const exportData = {
        portfolioName,
        items,
        analysis,
        timestamp: new Date(),
      };

      if (type === 'csv') exportAsCSV(exportData);
      else exportAsPDF(exportData);
    } catch (error) {
      // Failed silently as per console cleanup request
    } finally {
      setIsOpen(false);
    }
  };

  return (
    <div className={`relative ${className}`}>
      <button
        onClick={(e) => {
          e.preventDefault();
          e.stopPropagation();
          setIsOpen(!isOpen);
        }}
        className="h-9 w-9 lg:w-auto lg:px-3 flex-shrink-0 flex items-center justify-center lg:justify-start gap-2 rounded-lg text-xs 
GlassboxIcon function · typescript · L1-L37 (37 LOC)
apps/web/src/components/glassbox-icon.tsx
export function GlassboxIcon() {
  return (
    <svg
      viewBox="0 0 512 512"
      xmlns="http://www.w3.org/2000/svg"
      className="w-full h-full"
      fill="none"
    >
      <defs>
        {/* Vibrant Gradient: Cyan -> Purple -> Pink */}
        <linearGradient id="mainGrad" x1="64" y1="64" x2="448" y2="448" gradientUnits="userSpaceOnUse">
          <stop offset="0" stopColor="#22d3ee"/> {/* Cyan 400 */}
          <stop offset="0.5" stopColor="#c084fc"/> {/* Purple 400 */}
          <stop offset="1" stopColor="#f472b6"/> {/* Pink 400 */}
        </linearGradient>
        
        {/* Glossy Reflection */}
        <linearGradient id="glassShine" x1="256" y1="64" x2="256" y2="448" gradientUnits="userSpaceOnUse">
          <stop offset="0" stopColor="white" stopOpacity="0.5"/>
          <stop offset="1" stopColor="white" stopOpacity="0"/>
        </linearGradient>
      </defs>
      
      {/* Isometric Cube Silhouette */}
      <path d="M256 64L448 176V336L256 448L64 336V176L2
HeroVisual function · typescript · L5-L93 (89 LOC)
apps/web/src/components/landing/hero-visual.tsx
export function HeroVisual() {
  return (
    <div className="relative w-full max-w-[600px] mx-auto perspective-1000 group">
      {/* Floating Elements Background */}
      <div className="absolute -top-20 -right-20 w-64 h-64 bg-cyan-400/20 rounded-full blur-3xl animate-pulse"></div>
      <div className="absolute -bottom-20 -left-20 w-64 h-64 bg-purple-400/20 rounded-full blur-3xl animate-pulse delay-700"></div>

      {/* Main Glass Dashboard Card - Tilted */}
      <div 
        className="relative glass-panel p-6 transform rotate-y-12 rotate-x-6 transition-transform duration-700 group-hover:rotate-y-6 group-hover:rotate-x-3 shadow-2xl border-white/40 dark:border-white/10 bg-white/40 dark:bg-black/40 backdrop-blur-xl"
        style={{ transformStyle: 'preserve-3d' }}
      >
        {/* Header Mockup */}
        <div className="flex items-center justify-between mb-8 border-b border-white/10 pb-4">
          <div className="flex items-center gap-3">
            <div className="w-3 h
LandingErrorFallback function · typescript · L6-L36 (31 LOC)
apps/web/src/components/landing/LandingErrorFallback.tsx
export function LandingErrorFallback() {
  const { t } = useTranslation();
  return (
    <main className="min-h-screen px-6 py-8 flex items-center justify-center">
      <div className="glass-panel p-8 max-w-md w-full text-center space-y-6 border-orange-500/20 bg-orange-500/5">
        <div className="mx-auto w-16 h-16 rounded-full bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center text-orange-500">
          <AlertCircle className="w-8 h-8" />
        </div>

        <div>
          <h2 className="text-xl font-bold text-black dark:text-white mb-2">
            {t('landing.error.title')}
          </h2>
          <p className="text-sm text-black/60 dark:text-white/60">
            {t('landing.error.message')}
          </p>
        </div>

        <div className="flex gap-3 justify-center">
          <button
            onClick={() => window.location.reload()}
            className="px-4 py-2 rounded-lg bg-orange-500 text-white hover:bg-orange-600 transition-colors f
LocalizedLink function · typescript · L27-L35 (9 LOC)
apps/web/src/components/LocalizedLink.tsx
  function LocalizedLink({ href, ...props }, ref) {
    const params = useParams();
    const currentLang = (params?.lang as Language) || DEFAULT_LANGUAGE;

    // Auto-prefix href with current language
    const localizedHref = getLocalizedPath(href, currentLang);

    return <Link ref={ref} href={localizedHref} {...props} />;
  }
Source: Repobility analyzer · https://repobility.com
SessionProvider function · typescript · L14-L22 (9 LOC)
apps/web/src/components/SessionProvider.tsx
export function SessionProvider({ children, session }: { children: ReactNode; session?: Session | null }) {
  return (
    <NextAuthSessionProvider basePath="/auth" session={session}>
      <OAuthSyncWrapper>
        {children}
      </OAuthSyncWrapper>
    </NextAuthSessionProvider>
  );
}
Tooltip function · typescript · L12-L88 (77 LOC)
apps/web/src/components/Tooltip.tsx
export function Tooltip({ children, content, width = 250 }: TooltipProps) {
  const [isVisible, setIsVisible] = useState(false);
  const [position, setPosition] = useState({ top: 0, left: 0 });
  const triggerRef = useRef<HTMLDivElement>(null);
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  const updatePosition = useCallback(() => {
    if (triggerRef.current) {
      const rect = triggerRef.current.getBoundingClientRect();
      setPosition({
        top: rect.top + window.scrollY - 8, // 8px gap above trigger
        left: rect.left + window.scrollX + rect.width / 2, // Center horizontally
      });
    }
  }, []);

  const handleMouseEnter = () => {
    updatePosition();
    setIsVisible(true);
  };

  const handleMouseLeave = () => {
    setIsVisible(false);
  };

  useEffect(() => {
    if (!isVisible) {
      return;
    }
    window.addEventListener('scroll', updatePosition);
    window.addEventListener('resize', updatePo
useDebounce function · typescript · L9-L23 (15 LOC)
apps/web/src/hooks/useDebounce.ts
export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}
useLocalizedRouter function · typescript · L20-L37 (18 LOC)
apps/web/src/hooks/useLocalizedRouter.ts
export function useLocalizedRouter() {
  const router = useNextRouter();
  const params = useParams();
  const currentLang = (params?.lang as Language) || DEFAULT_LANGUAGE;

  return {
    ...router,
    currentLang,
    push: (href: string, options?: any) => {
      const localizedHref = getLocalizedPath(href, currentLang);
      return router.push(localizedHref, options);
    },
    replace: (href: string, options?: any) => {
      const localizedHref = getLocalizedPath(href, currentLang);
      return router.replace(localizedHref, options);
    },
  };
}
useOAuthSync function · typescript · L11-L64 (54 LOC)
apps/web/src/hooks/useOAuthSync.ts
export function useOAuthSync() {
  const { data: session, status } = useSession();
  const syncedRef = useRef(false);

  useEffect(() => {
    // Only run once per component lifecycle
    if (syncedRef.current) return;

    // Wait for session to load
    if (status !== 'authenticated' || !session?.user?.email) return;

    // Make client-side call to get backend cookie
    // We ALWAYS call this because we can't check if httpOnly cookie exists
    const syncWithBackend = async () => {
      try {
        const backendUrl = process.env.NEXT_PUBLIC_API_URL;
        if (!backendUrl) {
          console.error('[OAuth Sync] NEXT_PUBLIC_API_URL not set');
          return;
        }

        console.log('[OAuth Sync] Syncing backend cookie for:', session.user.email);
        console.log('[OAuth Sync] Backend URL:', backendUrl);

        const response = await fetch(`${backendUrl}/users/sync`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          cr
usePortfolioStorage function · typescript · L18-L76 (59 LOC)
apps/web/src/hooks/usePortfolioStorage.ts
export function usePortfolioStorage() {
  const [hasRestoredDraft, setHasRestoredDraft] = useState(false);

  const saveDraft = useCallback((items: PortfolioItem[], dateRange: { startDate: string; endDate: string }) => {
    try {
      sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ items, dateRange }));
    } catch (error) {
      console.error('Failed to save portfolio draft:', error);
    }
  }, []);

  const restoreDraft = useCallback((): PortfolioDraft | null => {
    try {
      const draft = sessionStorage.getItem(STORAGE_KEY);
      if (!draft) return null;

      const data = JSON.parse(draft) as PortfolioDraft;

      // Comprehensive validation
      const isValid =
        data &&
        typeof data === 'object' &&
        Array.isArray(data.items) &&
        data.dateRange &&
        typeof data.dateRange === 'object' &&
        typeof data.dateRange.startDate === 'string' &&
        typeof data.dateRange.endDate === 'string';

      if (isValid) {
        sessionSt
useTheme function · typescript · L16-L65 (50 LOC)
apps/web/src/hooks/useTheme.ts
export function useTheme() {
  const [theme, setThemeState] = useState<ThemePreference>('system');
  const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>('light');
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    const initialTheme = (localStorage.getItem('theme') as ThemePreference | null) || 'system';
    const resolved = getResolvedTheme(initialTheme);
    setThemeState(initialTheme);
    setResolvedTheme(resolved);
    applyResolvedTheme(resolved);

    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    const handleChange = (e: MediaQueryListEvent) => {
      if (initialTheme === 'system') {
        const nextTheme = e.matches ? 'dark' : 'light';
        setResolvedTheme(nextTheme);
        applyResolvedTheme(nextTheme);
      }
    };

    if (mediaQuery.addEventListener) {
      mediaQuery.addEventListener('change', handleChange);
    } else if (mediaQuery.addListener) {
      mediaQuery.addListener(handleChange);
    }
detectLanguage function · typescript · L14-L55 (42 LOC)
apps/web/src/lib/detect-language.ts
export function detectLanguage(
  acceptLanguageHeader: string,
  supportedLanguages: string[] = ['en', 'ko'],
  defaultLanguage: string = 'en'
): string {
  if (!acceptLanguageHeader) {
    return defaultLanguage;
  }

  // Parse Accept-Language header into array of { lang, quality }
  const languages = acceptLanguageHeader
    .split(',')
    .map((lang) => {
      const parts = lang.trim().split(';');
      const code = parts[0].toLowerCase();
      const qMatch = parts[1]?.match(/q=([0-9.]+)/);
      const quality = qMatch ? parseFloat(qMatch[1]) : 1.0;

      return { code, quality };
    })
    // Sort by quality (highest first)
    .sort((a, b) => b.quality - a.quality);

  // Find first language that matches our supported languages
  for (const { code } of languages) {
    // Extract base language code (e.g., 'ko' from 'ko-KR')
    const baseCode = code.split('-')[0];

    // Check if base code matches any supported language
    if (supportedLanguages.includes(baseCode)) {
    
Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
getLocalizedPath function · typescript · L16-L22 (7 LOC)
apps/web/src/lib/i18n/paths.ts
export function getLocalizedPath(path: string, lang: Language): string {
  // Remove leading slash for consistency
  const cleanPath = path.startsWith('/') ? path.slice(1) : path;

  // Build localized path
  return `/${lang}${cleanPath ? `/${cleanPath}` : ''}`;
}
stripLangFromPath function · typescript · L31-L40 (10 LOC)
apps/web/src/lib/i18n/paths.ts
export function stripLangFromPath(path: string): string {
  const segments = path.split('/').filter(Boolean);

  // If first segment is a language, remove it
  if (segments.length > 0 && SUPPORTED_LANGUAGES.includes(segments[0] as Language)) {
    segments.shift();
  }

  return `/${segments.join('/')}`;
}
extractLangFromPath function · typescript · L50-L58 (9 LOC)
apps/web/src/lib/i18n/paths.ts
export function extractLangFromPath(path: string): Language | null {
  const segments = path.split('/').filter(Boolean);

  if (segments.length > 0 && SUPPORTED_LANGUAGES.includes(segments[0] as Language)) {
    return segments[0] as Language;
  }

  return null;
}
‹ prevpage 2 / 3next ›