Skip to content
This repository was archived by the owner on Jan 13, 2026. It is now read-only.
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
3 changes: 2 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"Bash(cargo clean:*)",
"Bash(awk:*)",
"WebFetch(domain:connectrpc.com)",
"Bash(git log:*)"
"Bash(git log:*)",
"Bash(bunx tsc:*)"
],
"deny": [],
"ask": []
Expand Down
3 changes: 3 additions & 0 deletions app/components/home/HomeKit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import DictionaryContent from './contents/DictionaryContent'
import NotesContent from './contents/NotesContent'
import SettingsContent from './contents/SettingsContent'
import AboutContent from './contents/AboutContent'
import { SubscriptionStatusWidget } from './SubscriptionStatusWidget'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Imported file violates kebab-case naming convention.

Based on coding guidelines, the imported component file should use kebab-case. The file SubscriptionStatusWidget.tsx should be renamed to subscription-status-widget.tsx to comply with the project's naming standards.

As per coding guidelines:

  • "Use kebab-case when naming directories, TypeScript, and other files"
  • "Use kebab-case for files and directories" (for tsx files)

Apply this diff to update the import path:

-import { SubscriptionStatusWidget } from './SubscriptionStatusWidget'
+import { SubscriptionStatusWidget } from './subscription-status-widget'

And rename the file app/components/home/SubscriptionStatusWidget.tsx to app/components/home/subscription-status-widget.tsx.

📝 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
import { SubscriptionStatusWidget } from './SubscriptionStatusWidget'
import { SubscriptionStatusWidget } from './subscription-status-widget'
🤖 Prompt for AI Agents
In app/components/home/HomeKit.tsx around line 20, the import uses
SubscriptionStatusWidget.tsx which violates the project's kebab-case rule;
rename the file at app/components/home/SubscriptionStatusWidget.tsx to
app/components/home/subscription-status-widget.tsx and update the import in
HomeKit.tsx to import from './subscription-status-widget'; also search and
update any other imports throughout the codebase that reference the old
PascalCase filename to the new kebab-case path.


export default function HomeKit() {
const { navExpanded, currentPage, setCurrentPage } = useMainStore()
Expand Down Expand Up @@ -231,6 +232,8 @@ export default function HomeKit() {
/>
</div>
</div>

<SubscriptionStatusWidget navExpanded={navExpanded} />
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Verify if wordsUsed should be wired to real usage data.

The SubscriptionStatusWidget component accepts a wordsUsed prop (defaulting to 1000) that displays usage against the 5,000-word free tier limit. Currently, this prop is not being passed, so it's using the hardcoded default value.

Please confirm whether:

  1. The default value is intentional for this MVP/demo
  2. Real usage data should be fetched (e.g., from a hook, API, or store) and passed to the widget

Run the following script to check if there are existing patterns for tracking word usage in the codebase:


🏁 Script executed:

#!/bin/bash
# Description: Search for word usage tracking patterns in the codebase

# Search for wordsUsed, word_count, or similar usage tracking
rg -nP --type=ts --type=tsx -C3 '\b(wordsUsed|wordCount|word_count|usage.*word|track.*word)\b'

# Search for FREE_TIER_WORD_LIMIT usage to understand how limits are enforced
rg -nP --type=ts --type=tsx -C3 'FREE_TIER_WORD_LIMIT'

# Look for any API or hooks related to usage tracking
ast-grep --pattern 'use$_Usage'
ast-grep --pattern 'get$_Usage'

Length of output: 55



🏁 Script executed:

#!/bin/bash
# Description: Search for word usage tracking patterns with corrected syntax

# Search for wordsUsed, wordCount, or similar patterns using glob
rg -nP -C3 '\b(wordsUsed|wordCount|word_count|usage.*word|track.*word)\b' --glob='*.ts' --glob='*.tsx'

# Search for FREE_TIER_WORD_LIMIT usage
rg -nP -C3 'FREE_TIER_WORD_LIMIT' --glob='*.ts' --glob='*.tsx'

# Search for usage-related hooks
rg -nP 'use.*Usage|useWords|useWordCount' --glob='*.ts' --glob='*.tsx'

# Search for SubscriptionStatusWidget definition and its usage
rg -nP 'SubscriptionStatusWidget' --glob='*.ts' --glob='*.tsx' -C2

Length of output: 5009



🏁 Script executed:

#!/bin/bash
# Description: Inspect hooks that may contain word usage data

# Find useBillingState hook definition
rg -nP 'useBillingState|export.*billingState' --glob='*.ts' --glob='*.tsx' -A15

# Find useMainStore hook definition and its store contents
rg -nP 'useMainStore|export.*MainStore' --glob='*.ts' --glob='*.tsx' -A20

# Search for any word/usage metrics in store or state files
rg -nP 'word|usage|quota|limit' --glob='*store*' --glob='*.ts' --glob='*.tsx' -i -B2 -A2

Length of output: 188002


Wire wordsUsed to real usage data from interactions instead of using hardcoded default.

The SubscriptionStatusWidget currently displays a hardcoded 1000 words regardless of actual user activity. Meanwhile, HomeContent already calculates real word usage from user interactions (calculateTotalWords), and the database schema supports tracking free_words_remaining.

Pass the calculated total words from HomeKit to the widget:

<SubscriptionStatusWidget navExpanded={navExpanded} wordsUsed={stats.totalWords} />

This requires extracting the word count calculation to be accessible to both HomeContent and SubscriptionStatusWidget, or lifting the stats calculation to HomeKit level.

🤖 Prompt for AI Agents
In app/components/home/HomeKit.tsx around line 236, SubscriptionStatusWidget is
rendered with a hardcoded word usage; lift or expose the real-word calculation
so the component receives actual usage: move or call the existing
calculateTotalWords/stats computation up into HomeKit (or extract it into a
shared helper) and pass the result into the widget via props (e.g.
wordsUsed={stats.totalWords}); also update SubscriptionStatusWidget to accept
and use the wordsUsed prop instead of the hardcoded 1000.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a temporary placeholder until the next feature comes and uses it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@julgmz, understood! Thanks for clarifying that the hardcoded wordsUsed value is intentional as a placeholder for now.

Would you like me to create a follow-up issue to track wiring the real usage data when the next feature is ready, or do you already have this tracked?


✏️ Learnings added
Learnt from: julgmz
Repo: heyito/ito PR: 446
File: app/components/home/HomeKit.tsx:236-236
Timestamp: 2025-11-24T21:51:44.018Z
Learning: In app/components/home/SubscriptionStatusWidget.tsx, the hardcoded default wordsUsed value (1000) is intentional as a temporary placeholder until a future feature implements real usage tracking.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

</div>

{/* Main Content */}
Expand Down
121 changes: 121 additions & 0 deletions app/components/home/SubscriptionStatusWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import useBillingState, {
BillingState,
ProStatus,
} from '@/app/hooks/useBillingState'
import { useMainStore } from '@/app/store/useMainStore'

interface SubscriptionStatusWidgetProps {
wordsUsed?: number
navExpanded?: boolean
}

const FREE_TIER_WORD_LIMIT = 5000

export function SubscriptionStatusWidget({
wordsUsed = 1000,
navExpanded = true,
}: SubscriptionStatusWidgetProps) {
const billingState = useBillingState()
const { setCurrentPage, setSettingsPage } = useMainStore()

const handleUpgradeClick = () => {
setCurrentPage('settings')
setSettingsPage('pricing-billing')
}

// Common styles
const cardClassName =
'w-full bg-white rounded-2xl border-2 border-neutral-100 shadow-sm p-2 space-y-1.5'
const progressBarContainerClassName =
'w-full h-1.5 bg-neutral-200 rounded-full overflow-hidden'
const progressBarFillClassName =
'h-full transition-all duration-300 bg-gradient-to-r'
const buttonBaseClassName =
'w-full text-white px-4 py-2.5 rounded-md text-sm font-semibold hover:bg-gray-800 cursor-pointer transition-colors mt-4'

// Hide widget when sidebar is collapsed
if (!navExpanded) {
return null
}

// Hide widget for active Pro subscribers
if (billingState.proStatus === ProStatus.ACTIVE_PRO) {
return null
}

// Show trial status if user is on free trial
if (billingState.proStatus === ProStatus.FREE_TRIAL) {
const daysUsed = billingState.trialDays - billingState.daysLeft
const trialDays = billingState.trialDays || 1
const trialPercentage = Math.min(100, (daysUsed / trialDays) * 100)

return (
<div className={cardClassName}>
{/* Header */}
<div className="text-sm font-bold">Pro Trial Active</div>

{/* Progress bar */}
<div className={progressBarContainerClassName}>
<div
className={`${progressBarFillClassName} from-purple-500 to-pink-500`}
style={{ width: `${trialPercentage}%` }}
/>
</div>

{/* Days remaining */}
<div className="text-xs">
{billingState.daysLeft} day{billingState.daysLeft !== 1 ? 's' : ''}{' '}
left on <span className="font-medium">Ito Pro</span>
</div>

{/* Upgrade button */}
<button
className={`${buttonBaseClassName} bg-gray-900`}
onClick={handleUpgradeClick}
>
Upgrade Now
</button>
</div>
)
}

// Show free tier status (Ito Starter)
const totalWords = FREE_TIER_WORD_LIMIT
const usagePercentage = Math.min(100, (wordsUsed / totalWords) * 100)

return (
<div className={cardClassName}>
{/* Header */}
<div className="text-xs text-neutral-500">Your plan</div>
<div className="text-lg font-bold">Ito Starter</div>

{/* Progress bar */}
<div className={progressBarContainerClassName}>
<div
className={`${progressBarFillClassName} from-blue-500 via-purple-500 to-pink-500`}
style={{ width: `${usagePercentage}%` }}
/>
</div>

{/* Usage text */}
<div className="text-xs">
You've used{' '}
<span className="font-medium">
{wordsUsed.toLocaleString()} of {totalWords.toLocaleString()}
</span>{' '}
words this week
</div>

{/* Upgrade button */}
<button
className={`${buttonBaseClassName} bg-gray-900 flex items-center justify-center gap-2`}
onClick={handleUpgradeClick}
>
<span>Get Ito</span>
<span className="bg-white text-gray-900 px-2 py-0.5 rounded font-bold text-xs">
PRO
</span>
</button>
</div>
)
}