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 4 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
4 changes: 4 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

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,9 @@ export default function HomeKit() {
/>
</div>
</div>

{/* Subscription Status Widget */}
Copy link
Collaborator

Choose a reason for hiding this comment

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

we could delete this comment

<SubscriptionStatusWidget navExpanded={navExpanded} />
Copy link

@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
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

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
135 changes: 135 additions & 0 deletions app/components/home/SubscriptionStatusWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import useBillingState, {
BillingState,
ProStatus,
} from '@/app/hooks/useBillingState'
import { useMainStore } from '@/app/store/useMainStore'

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

const mockBillingState: BillingState = {
proStatus: ProStatus.FREE_TRIAL,
subscriptionStartAt: null,
subscriptionEndAt: null,
isScheduledForCancellation: false,
trialDays: 14,
trialStartAt: null,
daysLeft: 1,
isTrialActive: true,
hasCompletedTrial: false,
}

export function SubscriptionStatusWidget({
wordsUsed = 1000,
navExpanded = true,
}: SubscriptionStatusWidgetProps) {
// const billingState = useBillingState()
const billingState = mockBillingState as BillingState // Use mock for testing
console.log({ billingState })
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 trialPercentage = Math.min(
100,
(daysUsed / billingState.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 = 5000
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>
)
}