-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
💸 feat: Balance Tab in Settings Dialog #6537
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 46 commits
d0090d8
91ec113
da32349
e3acd18
71effb1
c925f9f
6128270
a2d5c07
a370c19
cad4489
d7f9fe1
ceccfb0
a849ff8
a10e754
3cbae93
b812cb1
b2ea660
cd1b6bf
a4b5234
5c0fbe4
00b0313
7929a6f
fbbd0d0
ee86a11
91da375
696298d
ed37d84
422a64d
2663aa2
a49c1ff
c34dd83
2d5465a
ef861d7
09dacff
2786aed
5422dcd
ca463ea
e2fdfc1
20034cc
3a73031
6a44e0a
0a33546
e99dde2
79ee302
8b6b82d
42acd7e
2f19ca0
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 |
---|---|---|
@@ -1,9 +1,24 @@ | ||
const Balance = require('~/models/Balance'); | ||
|
||
async function balanceController(req, res) { | ||
const { tokenCredits: balance = '' } = | ||
(await Balance.findOne({ user: req.user.id }, 'tokenCredits').lean()) ?? {}; | ||
res.status(200).send('' + balance); | ||
const balanceData = await Balance.findOne( | ||
{ user: req.user.id }, | ||
'-_id tokenCredits autoRefillEnabled refillIntervalValue refillIntervalUnit lastRefill refillAmount', | ||
).lean(); | ||
|
||
if (!balanceData) { | ||
return res.status(404).json({ error: 'Balance not found' }); | ||
} | ||
|
||
// If auto-refill is not enabled, remove auto-refill related fields from the response | ||
if (!balanceData.autoRefillEnabled) { | ||
delete balanceData.refillIntervalValue; | ||
delete balanceData.refillIntervalUnit; | ||
delete balanceData.lastRefill; | ||
delete balanceData.refillAmount; | ||
} | ||
|
||
res.status(200).json(balanceData); | ||
} | ||
|
||
module.exports = balanceController; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import React from 'react'; | ||
import { TranslationKeys, useLocalize } from '~/hooks'; | ||
import { Label } from '~/components'; | ||
import HoverCardSettings from '~/components/Nav/SettingsTabs/HoverCardSettings'; | ||
|
||
interface AutoRefillSettingsProps { | ||
lastRefill: Date; | ||
refillAmount: number; | ||
refillIntervalUnit: 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'months'; | ||
refillIntervalValue: number; | ||
} | ||
|
||
/** | ||
* Adds a time interval to a given date. | ||
* @param {Date} date - The starting date. | ||
* @param {number} value - The numeric value of the interval. | ||
* @param {'seconds'|'minutes'|'hours'|'days'|'weeks'|'months'} unit - The unit of time. | ||
* @returns {Date} A new Date representing the starting date plus the interval. | ||
*/ | ||
const addIntervalToDate = ( | ||
date: Date, | ||
value: number, | ||
unit: 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'months', | ||
): Date => { | ||
const result = new Date(date); | ||
switch (unit) { | ||
case 'seconds': | ||
result.setSeconds(result.getSeconds() + value); | ||
break; | ||
case 'minutes': | ||
result.setMinutes(result.getMinutes() + value); | ||
break; | ||
case 'hours': | ||
result.setHours(result.getHours() + value); | ||
break; | ||
case 'days': | ||
result.setDate(result.getDate() + value); | ||
break; | ||
case 'weeks': | ||
result.setDate(result.getDate() + value * 7); | ||
break; | ||
case 'months': | ||
result.setMonth(result.getMonth() + value); | ||
break; | ||
default: | ||
break; | ||
} | ||
return result; | ||
}; | ||
|
||
const AutoRefillSettings: React.FC<AutoRefillSettingsProps> = ({ | ||
lastRefill, | ||
refillAmount, | ||
refillIntervalUnit, | ||
refillIntervalValue, | ||
}) => { | ||
const localize = useLocalize(); | ||
|
||
const lastRefillDate = lastRefill ? new Date(lastRefill) : null; | ||
const nextRefill = lastRefillDate | ||
? addIntervalToDate(lastRefillDate, refillIntervalValue, refillIntervalUnit) | ||
: null; | ||
|
||
// Return the localized unit based on singular/plural values | ||
const getLocalizedIntervalUnit = ( | ||
value: number, | ||
unit: 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'months', | ||
): string => { | ||
let key: TranslationKeys; | ||
switch (unit) { | ||
case 'seconds': | ||
key = value === 1 ? 'com_nav_balance_second' : 'com_nav_balance_seconds'; | ||
break; | ||
case 'minutes': | ||
key = value === 1 ? 'com_nav_balance_minute' : 'com_nav_balance_minutes'; | ||
break; | ||
case 'hours': | ||
key = value === 1 ? 'com_nav_balance_hour' : 'com_nav_balance_hours'; | ||
break; | ||
case 'days': | ||
key = value === 1 ? 'com_nav_balance_day' : 'com_nav_balance_days'; | ||
break; | ||
case 'weeks': | ||
key = value === 1 ? 'com_nav_balance_week' : 'com_nav_balance_weeks'; | ||
break; | ||
case 'months': | ||
key = value === 1 ? 'com_nav_balance_month' : 'com_nav_balance_months'; | ||
break; | ||
default: | ||
key = 'com_nav_balance_seconds'; | ||
} | ||
return localize(key); | ||
}; | ||
|
||
return ( | ||
<div className="space-y-4"> | ||
<h3 className="text-lg font-medium">{localize('com_nav_balance_auto_refill_settings')}</h3> | ||
<div className="mb-1 flex justify-between text-sm"> | ||
<span>{localize('com_nav_balance_last_refill')}</span> | ||
<span>{lastRefillDate ? lastRefillDate.toLocaleString() : '-'}</span> | ||
</div> | ||
<div className="mb-1 flex justify-between text-sm"> | ||
<span>{localize('com_nav_balance_refill_amount')}</span> | ||
<span>{refillAmount !== undefined ? refillAmount : '-'}</span> | ||
</div> | ||
<div className="mb-1 flex justify-between text-sm"> | ||
<span>{localize('com_nav_balance_interval')}</span> | ||
<span> | ||
{localize('com_nav_balance_every')} {refillIntervalValue}{' '} | ||
{getLocalizedIntervalUnit(refillIntervalValue, refillIntervalUnit)} | ||
</span> | ||
</div> | ||
<div className="flex items-center justify-between"> | ||
{/* Left Section: Label */} | ||
<div className="flex items-center space-x-2"> | ||
<Label className="font-light">{localize('com_nav_balance_next_refill')}</Label> | ||
<HoverCardSettings side="bottom" text="com_nav_balance_next_refill_info" /> | ||
</div> | ||
|
||
{/* Right Section: tokenCredits Value */} | ||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200" role="note"> | ||
{nextRefill ? nextRefill.toLocaleString() : '-'} | ||
</span> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default AutoRefillSettings; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React from 'react'; | ||
import { useGetStartupConfig, useGetUserBalance } from '~/data-provider'; | ||
import { useAuthContext, useLocalize } from '~/hooks'; | ||
import TokenCreditsItem from './TokenCreditsItem'; | ||
import AutoRefillSettings from './AutoRefillSettings'; | ||
|
||
function Balance() { | ||
const localize = useLocalize(); | ||
const { isAuthenticated } = useAuthContext(); | ||
const { data: startupConfig } = useGetStartupConfig(); | ||
const balanceQuery = useGetUserBalance({ | ||
enabled: !!isAuthenticated && startupConfig?.balance?.enabled, | ||
}); | ||
const balanceData = balanceQuery.data; | ||
|
||
return ( | ||
<div className="flex flex-col gap-4 p-4 text-sm text-text-primary"> | ||
{/* Token credits display */} | ||
<TokenCreditsItem tokenCredits={balanceData?.tokenCredits} /> | ||
|
||
{/* Auto-refill display */} | ||
{balanceData?.autoRefillEnabled ? ( | ||
<> | ||
<AutoRefillSettings | ||
lastRefill={balanceData.lastRefill!} | ||
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. Using non-null assertions on auto-refill properties (lastRefill, refillAmount, refillIntervalUnit, refillIntervalValue) can lead to runtime errors if any of these fields are unexpectedly undefined. Consider adding explicit runtime checks or providing default values before rendering the AutoRefillSettings component. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
refillAmount={balanceData.refillAmount!} | ||
refillIntervalUnit={balanceData.refillIntervalUnit!} | ||
refillIntervalValue={balanceData.refillIntervalValue!} | ||
/> | ||
</> | ||
) : ( | ||
<div className="text-sm text-gray-600"> | ||
{localize('com_nav_balance_auto_refill_disabled')} | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
export default React.memo(Balance); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,29 @@ | ||||||
import React from 'react'; | ||||||
import { useLocalize } from '~/hooks'; | ||||||
import { Label } from '~/components'; | ||||||
import HoverCardSettings from '~/components/Nav/SettingsTabs/HoverCardSettings'; | ||||||
|
||||||
interface TokenCreditsItemProps { | ||||||
tokenCredits?: number; | ||||||
} | ||||||
|
||||||
const TokenCreditsItem: React.FC<TokenCreditsItemProps> = ({ tokenCredits }) => { | ||||||
const localize = useLocalize(); | ||||||
|
||||||
return ( | ||||||
<div className="flex items-center justify-between"> | ||||||
{/* Left Section: Label */} | ||||||
<div className="flex items-center space-x-2"> | ||||||
<Label className="font-light">{localize('com_nav_balance')}</Label> | ||||||
<HoverCardSettings side="bottom" text="com_nav_info_user_name_display" /> | ||||||
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. The translation key
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
</div> | ||||||
|
||||||
{/* Right Section: tokenCredits Value */} | ||||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200" role="note"> | ||||||
{tokenCredits !== undefined ? tokenCredits.toFixed(2) : '0.00'} | ||||||
</span> | ||||||
</div> | ||||||
); | ||||||
}; | ||||||
|
||||||
export default TokenCreditsItem; |
Uh oh!
There was an error while loading. Please reload this page.