-
Notifications
You must be signed in to change notification settings - Fork 3
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
feat(popup): add ouf of funds and add funds screens #439
Merged
Merged
Changes from 9 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
7b47198
WIP: add boilerplate; with back-button support
sidvishnoi 510e742
ui first pass
sidvishnoi 187483d
use i18n on text
sidvishnoi 80ae614
nits & cleanup
sidvishnoi 0b6f85f
Update src/popup/components/OutOfFunds.tsx
sidvishnoi 00fc94e
lint fix
sidvishnoi dd4f510
Merge branch 'main' into out_of_funds_UI
sidvishnoi 9e28318
update copy a bit
sidvishnoi fb50505
Merge branch 'main' into out_of_funds_UI
sidvishnoi ab2e3b2
Merge branch 'main' into out_of_funds_UI
sidvishnoi 803fe67
make `state.recurring` boolean only
sidvishnoi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,198 @@ | ||
import React from 'react' | ||
import { Button } from './ui/Button' | ||
import type { RecurringGrant, OneTimeGrant } from '@/shared/types' | ||
import { useForm } from 'react-hook-form' | ||
import type { RecurringGrant, OneTimeGrant, AmountValue } from '@/shared/types' | ||
import type { AddFundsPayload, Response } from '@/shared/messages' | ||
import type { WalletAddress } from '@interledger/open-payments' | ||
import { getCurrencySymbol, transformBalance } from '../lib/utils' | ||
import { | ||
charIsNumber, | ||
formatNumber, | ||
getCurrencySymbol, | ||
transformBalance | ||
} from '@/popup/lib/utils' | ||
import { useTranslation } from '@/popup/lib/context' | ||
import { getNextOccurrence } from '@/shared/helpers' | ||
import { ErrorMessage } from '@/popup/components/ErrorMessage' | ||
import { Button } from '@/popup/components/ui/Button' | ||
import { Input } from '@/popup/components/ui/Input' | ||
|
||
interface OutOfFundsProps { | ||
info: Pick<WalletAddress, 'id' | 'assetCode' | 'assetScale'> | ||
grantRecurring?: RecurringGrant['amount'] | ||
grantOneTime?: OneTimeGrant['amount'] | ||
requestAddFunds: (details: AddFundsPayload) => Promise<Response> | ||
onChooseOption: (recurring: boolean) => void | ||
} | ||
|
||
export const OutOfFunds = ({ | ||
info, | ||
grantOneTime, | ||
grantRecurring, | ||
requestAddFunds | ||
onChooseOption | ||
}: OutOfFundsProps) => { | ||
if (!grantOneTime && !grantRecurring) { | ||
throw new Error('Provide at least one of grantOneTime and grantRecurring') | ||
} | ||
const t = useTranslation() | ||
|
||
const currencySymbol = getCurrencySymbol(info.assetCode) | ||
const amount = transformBalance( | ||
grantRecurring?.value || grantOneTime!.value, | ||
info.assetScale | ||
return ( | ||
<div className="flex flex-col gap-4"> | ||
<ErrorMessage | ||
error={t('outOfFunds_error_title')} | ||
className="mb-0 text-error" | ||
/> | ||
<div className="px-2 text-xs text-medium"> | ||
<p>{t('outOfFunds_error_text')}</p> | ||
<p>{t('outOfFunds_error_textHint')}</p> | ||
{grantRecurring?.value && ( | ||
<p className="mt-1"> | ||
<RecurringAutoRenewInfo | ||
info={info} | ||
grantRecurring={grantRecurring} | ||
/> | ||
</p> | ||
)} | ||
</div> | ||
|
||
<div className="w-100 h-0.5 bg-disabled" /> | ||
|
||
<Button onClick={() => onChooseOption(true)}> | ||
{t('outOfFunds_action_optionRecurring')} | ||
</Button> | ||
<Button onClick={() => onChooseOption(false)}> | ||
{t('outOfFunds_action_optionOneTime')} | ||
</Button> | ||
</div> | ||
) | ||
} | ||
|
||
const requestTopUpOneTime = () => { | ||
return requestAddFunds({ amount: amount, recurring: false }) | ||
} | ||
interface AddFundsProps { | ||
info: Pick<WalletAddress, 'id' | 'assetCode' | 'assetScale'> | ||
recurring: false | 'P1M' | ||
sidvishnoi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
defaultAmount: AmountValue | ||
requestAddFunds: (details: AddFundsPayload) => Promise<Response> | ||
} | ||
|
||
const requestTopUpRecurring = () => { | ||
return requestAddFunds({ amount: amount, recurring: true }) | ||
} | ||
export function AddFunds({ | ||
info, | ||
defaultAmount, | ||
recurring, | ||
requestAddFunds | ||
}: AddFundsProps) { | ||
const t = useTranslation() | ||
const { | ||
register, | ||
handleSubmit, | ||
formState: { errors, isSubmitting }, | ||
setError, | ||
setValue | ||
} = useForm({ | ||
criteriaMode: 'firstError', | ||
mode: 'onSubmit', | ||
reValidateMode: 'onBlur', | ||
defaultValues: { | ||
amount: transformBalance(defaultAmount, info.assetScale) | ||
} | ||
}) | ||
|
||
const currencySymbol = getCurrencySymbol(info.assetCode) | ||
|
||
return ( | ||
<div className="flex flex-col gap-2"> | ||
<h2 className="text-xl">Out of funds</h2> | ||
|
||
<h3 className="text-lg"> | ||
Top-up: <span>{currencySymbol + amount}</span> | ||
</h3> | ||
<RecurringAutoRenewInfo grantRecurring={grantRecurring} info={info} /> | ||
<Button onClick={() => requestTopUpRecurring()}>Recurring</Button> | ||
<Button onClick={() => requestTopUpOneTime()}>One-time</Button> | ||
</div> | ||
<form | ||
className="flex flex-col gap-4" | ||
onSubmit={handleSubmit(async (data) => { | ||
const response = await requestAddFunds({ | ||
amount: data.amount, | ||
recurring: !!recurring | ||
}) | ||
if (!response.success) { | ||
setError('root', { message: response.message }) | ||
} | ||
})} | ||
> | ||
<Input | ||
type="url" | ||
label={t('outOfFundsAddFunds_label_walletAddress')} | ||
value={info.id} | ||
readOnly | ||
disabled | ||
/> | ||
|
||
<Input | ||
type="text" | ||
inputMode="numeric" | ||
addOn={currencySymbol} | ||
label={t('outOfFundsAddFunds_label_amount')} | ||
description={ | ||
recurring | ||
? t('outOfFundsAddFunds_label_amountDescriptionRecurring', [ | ||
getNextOccurrenceDate('P1M') | ||
]) | ||
: t('outOfFundsAddFunds_label_amountDescriptionOneTime') | ||
} | ||
placeholder="5.00" | ||
onKeyDown={(e) => { | ||
if ( | ||
!charIsNumber(e.key) && | ||
e.key !== 'Backspace' && | ||
e.key !== 'ArrowLeft' && | ||
e.key !== 'ArrowRight' && | ||
e.key !== 'Delete' && | ||
e.key !== 'Tab' | ||
) { | ||
e.preventDefault() | ||
} | ||
}} | ||
errorMessage={errors.amount?.message} | ||
{...register('amount', { | ||
required: { value: true, message: 'Amount is required.' }, | ||
valueAsNumber: false, | ||
onBlur: (e: React.FocusEvent<HTMLInputElement>) => { | ||
setValue('amount', formatNumber(+e.currentTarget.value, 2)) | ||
} | ||
})} | ||
/> | ||
|
||
{(errors.root || errors.amount) && ( | ||
<ErrorMessage | ||
error={errors.root?.message || errors.amount?.message} | ||
className="mb-0 py-1 text-xs text-error" | ||
/> | ||
)} | ||
|
||
<Button type="submit" disabled={isSubmitting} loading={isSubmitting}> | ||
{recurring | ||
? t('outOfFundsAddFunds_action_addRecurring') | ||
: t('outOfFundsAddFunds_action_addOneTime')} | ||
</Button> | ||
</form> | ||
) | ||
} | ||
|
||
function RecurringAutoRenewInfo({ | ||
grantRecurring, | ||
info | ||
}: Pick<OutOfFundsProps, 'grantRecurring' | 'info'>) { | ||
const t = useTranslation() | ||
|
||
if (!grantRecurring) return null | ||
|
||
const currencySymbol = getCurrencySymbol(info.assetCode) | ||
const amount = transformBalance(grantRecurring.value, info.assetScale) | ||
const renewDate = getNextOccurrence(grantRecurring.interval, new Date()) | ||
const renewDateLocalized = renewDate.toLocaleString(undefined, { | ||
dateStyle: 'medium', | ||
timeStyle: 'short' | ||
}) | ||
|
||
return ( | ||
<p> | ||
If you do nothing, you will have {currencySymbol} | ||
{amount} available on{' '} | ||
<time dateTime={renewDate.toISOString()}> | ||
{renewDate.toLocaleString(undefined, { | ||
dateStyle: 'long', | ||
timeStyle: 'medium' | ||
})} | ||
</time> | ||
</p> | ||
return t('outOfFunds_error_textDoNothing', [ | ||
`${currencySymbol}${amount}`, | ||
renewDateLocalized | ||
]) | ||
} | ||
|
||
function getNextOccurrenceDate(period: 'P1M', baseDate = new Date()) { | ||
const date = getNextOccurrence( | ||
`R/${baseDate.toISOString()}/${period}`, | ||
baseDate | ||
) | ||
return date.toLocaleDateString(undefined, { dateStyle: 'medium' }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,14 @@ const NavigationButton = () => { | |
return useMemo(() => { | ||
if (!connected) return null | ||
|
||
if (location.pathname.includes('/s/')) { | ||
return ( | ||
<Link to={location.pathname.split('/s/')[0]}> | ||
<ArrowBack className="h-6" /> | ||
</Link> | ||
) | ||
} | ||
|
||
Comment on lines
+15
to
+22
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. |
||
return location.pathname === `${ROUTES_PATH.SETTINGS}` ? ( | ||
<Link to={ROUTES_PATH.HOME}> | ||
<ArrowBack className="h-6" /> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import React from 'react' | ||
import { useLocation } from 'react-router-dom' | ||
import { AddFunds } from '@/popup/components/OutOfFunds' | ||
import { usePopupState } from '@/popup/lib/context' | ||
import { addFunds } from '@/popup/lib/messages' | ||
|
||
export type State = { recurring: boolean } | ||
|
||
export const Component = () => { | ||
const { | ||
state: { grants, walletAddress } | ||
} = usePopupState() | ||
const location = useLocation() | ||
|
||
const state: State = { recurring: false, ...location.state } | ||
const defaultAmount = grants.recurring?.value ?? grants.oneTime!.value | ||
|
||
return ( | ||
<AddFunds | ||
info={walletAddress} | ||
defaultAmount={defaultAmount} | ||
recurring={state.recurring ? 'P1M' : false} | ||
requestAddFunds={async (data) => { | ||
const res = await addFunds(data) | ||
return res | ||
}} | ||
/> | ||
) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Good approach. Let's avoid having other folders in
pages
. For the future, defining sub-routes should happen like this.