-
Notifications
You must be signed in to change notification settings - Fork 108
Billing status widget home page #446
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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' | ||
|
|
||
| export default function HomeKit() { | ||
| const { navExpanded, currentPage, setCurrentPage } = useMainStore() | ||
|
|
@@ -231,6 +232,9 @@ export default function HomeKit() { | |
| /> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Subscription Status Widget */} | ||
|
||
| <SubscriptionStatusWidget navExpanded={navExpanded} /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify if The Please confirm whether:
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' -C2Length 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 -A2Length of output: 188002 Wire The Pass the calculated total words from This requires extracting the word count calculation to be accessible to both 🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @julgmz, understood! Thanks for clarifying that the hardcoded 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
|
||
| </div> | ||
|
|
||
| {/* Main Content */} | ||
|
|
||
| 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, | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| export function SubscriptionStatusWidget({ | ||
| wordsUsed = 1000, | ||
| navExpanded = true, | ||
| }: SubscriptionStatusWidgetProps) { | ||
| // const billingState = useBillingState() | ||
| const billingState = mockBillingState as BillingState // Use mock for testing | ||
| console.log({ billingState }) | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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, | ||
| ) | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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) | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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> | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
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.tsxshould be renamed tosubscription-status-widget.tsxto comply with the project's naming standards.As per coding guidelines:
Apply this diff to update the import path:
And rename the file
app/components/home/SubscriptionStatusWidget.tsxtoapp/components/home/subscription-status-widget.tsx.📝 Committable suggestion
🤖 Prompt for AI Agents