date-fns#add TypeScript Examples
The following examples show how to use
date-fns#add.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: wallets-assets.service.ts From xBull-Wallet with GNU Affero General Public License v3.0 | 6 votes |
shouldRequestAssetInformation$ = this.requestAssetInformation$.asObservable()
.pipe(switchMap(params => {
return of(true)
.pipe(map(_ => {
if (params.asset._id === 'native') {
return false;
}
if (params.forceUpdate) {
return true;
}
if (!params.asset.lastTimeUpdated) {
return true;
}
const lastUpdate = new Date(params.asset.lastTimeUpdated);
// TODO: maybe we should make this time dynamic and configurable form the settings
const nextUpdate = add(lastUpdate, { minutes: 15 });
const now = new Date();
return !params.asset.assetFullDataLoaded && isAfter(now, nextUpdate);
}))
.pipe(filter(Boolean))
.pipe(map(_ => ({
asset: params.asset as IWalletAssetModel,
horizonApi: params.horizonApi,
})));
}))
.pipe(map(({ asset, horizonApi}) => {
return {
_id: asset._id,
assetIssuer: asset.assetIssuer,
assetCode: asset.assetCode,
horizonApi,
};
}));
Example #2
Source File: helpers.tsx From vvs-ui with GNU General Public License v3.0 | 6 votes |
getDateForBlock = async (currentBlock: number, block: number) => {
const blocksUntilBlock = block - currentBlock
const secondsUntilStart = blocksUntilBlock * CRONOS_BLOCK_TIME
// if block already happened we can get timestamp via .getBlock(block)
if (currentBlock > block) {
try {
const { timestamp } = await simpleRpcProvider.getBlock(block)
return toDate(timestamp * 1000)
} catch {
add(new Date(), { seconds: secondsUntilStart })
}
}
return add(new Date(), { seconds: secondsUntilStart })
}
Example #3
Source File: make-stat-on-interval-days-basis.ts From merged-pr-stat with MIT License | 6 votes |
async function main(): Promise<void> {
program
.requiredOption("--start <date>")
.requiredOption("--end <date>")
.requiredOption("--interval-days <days>")
.requiredOption("--query <query>");
program.parse(process.argv);
const startDate = parseISO(program.start);
const endDate = parseISO(program.end);
const query = program.query as string;
const intervalDays = parseInt(program.intervalDays);
const allStats = [];
for (let start = startDate; start < endDate; start = addDays(start, intervalDays)) {
const end = add(start, { days: intervalDays, seconds: -1 });
console.error(format(start, "yyyy-MM-dd HH:mm:ss"));
console.error(format(end, "yyyy-MM-dd HH:mm:ss"));
const stdout = execFileSync(
"merged-pr-stat",
["--start", start.toISOString(), "--end", end.toISOString(), "--query", query],
{ encoding: "utf8" }
);
const result = {
startDate: format(start, "yyyy-MM-dd HH:mm:ss"),
endDate: format(end, "yyyy-MM-dd HH:mm:ss"),
...JSON.parse(stdout),
};
allStats.push(result);
}
process.stdout.write(csvStringify(allStats, { header: true }));
}
Example #4
Source File: make-rotate-stat-on-interval-days-basis.ts From merged-pr-stat with MIT License | 6 votes |
// Make stat between (<date> - <aggregationDays>) and <date>
// and move aggregation period by <intervalDays>.
async function main(): Promise<void> {
program
.requiredOption("--start <date>")
.requiredOption("--end <date>")
.requiredOption("--interval-days <days>")
.requiredOption("--aggregation-days <days>")
.requiredOption("--query <query>");
program.parse(process.argv);
const startDate = parseISO(program.start);
const endDate = parseISO(program.end);
const query = program.query as string;
const intervalDays = parseInt(program.intervalDays);
const aggregationDays = parseInt(program.aggregationDays);
const allStats = [];
for (let start = startDate; start < endDate; start = addDays(start, intervalDays)) {
const aggregateFrom = add(start, { days: -aggregationDays });
const aggregateTo = start;
console.error(format(aggregateFrom, "yyyy-MM-dd HH:mm:ss"));
console.error(format(aggregateTo, "yyyy-MM-dd HH:mm:ss"));
const stdout = execFileSync(
"merged-pr-stat",
["--start", aggregateFrom.toISOString(), "--end", aggregateTo.toISOString(), "--query", query],
{ encoding: "utf8" }
);
const result = {
startDate: format(aggregateFrom, "yyyy-MM-dd HH:mm:ss"),
endDate: format(aggregateTo, "yyyy-MM-dd HH:mm:ss"),
...JSON.parse(stdout),
};
allStats.push(result);
}
process.stdout.write(csvStringify(allStats, { header: true }));
}
Example #5
Source File: make-monthly-stat.ts From merged-pr-stat with MIT License | 6 votes |
async function main(): Promise<void> {
program.requiredOption("--start <yyyy/MM>").requiredOption("--end <yyyy/MM>").requiredOption("--query <query>");
program.parse(process.argv);
const startDate = parse(program.start, "yyyy/MM", new Date());
const endDate = parse(program.end, "yyyy/MM", new Date());
const query = program.query as string;
const allStats = [];
for (let start = startDate; start <= endDate; start = addMonths(start, 1)) {
const end = add(start, { months: 1, seconds: -1 });
console.error(format(start, "yyyy-MM-dd HH:mm:ss"));
console.error(format(end, "yyyy-MM-dd HH:mm:ss"));
const stdout = execFileSync(
"merged-pr-stat",
["--start", start.toISOString(), "--end", end.toISOString(), "--query", query],
{ encoding: "utf8" }
);
const result = {
startDate: format(start, "yyyy-MM-dd HH:mm:ss"),
endDate: format(end, "yyyy-MM-dd HH:mm:ss"),
...JSON.parse(stdout),
};
allStats.push(result);
}
process.stdout.write(csvStringify(allStats, { header: true }));
}
Example #6
Source File: utils.tsx From gio-design with Apache License 2.0 | 6 votes |
getDefaultViewDates = (defaultDate: Date) =>
[startOfDay(defaultDate), add(startOfDay(defaultDate), { months: 1 })] as [Date, Date]
Example #7
Source File: helpers.tsx From glide-frontend with GNU General Public License v3.0 | 6 votes |
getDateForBlock = async (currentBlock: number, block: number) => {
const blocksUntilBlock = block - currentBlock
const secondsUntilStart = blocksUntilBlock * ESC_BLOCK_TIME
// if block already happened we can get timestamp via .getBlock(block)
if (currentBlock > block) {
try {
const { timestamp } = await simpleRpcProvider.getBlock(block)
return toDate(timestamp * 1000)
} catch {
add(new Date(), { seconds: secondsUntilStart })
}
}
return add(new Date(), { seconds: secondsUntilStart })
}
Example #8
Source File: Account.ts From remix-hexagonal-architecture with MIT License | 6 votes |
export function generateResetPasswordToken(
account: VerifiedAccount,
generateId: GenerateId,
clock: Clock
): AccountForgotPassword {
return {
type: "forgot-password",
id: account.id,
email: account.email,
passwordResetToken: generateId.generate(),
passwordResetExpiration: add(clock.now(), { days: 7 }),
};
}
Example #9
Source File: leveling.ts From aero-bot with MIT License | 5 votes |
/**
* Adds the specified amount of XP to the member.
* @param guildId the ID of the guild the member is in
* @param userId the ID of the member to add XP to
* @param xpToAdd the number of XP to add
* @param message the message that triggered this
*/
export async function addXP(
guildId: string,
userId: string,
xpToAdd: number,
message: Message
) {
const result = (await members.findOneAndUpdate(
{
guildId,
userId,
},
{
guildId,
userId,
$inc: {
xp: xpToAdd,
},
nextXPAdd: add(Date.now(), { minutes: 1 }),
},
{
upsert: true,
new: true,
setDefaultsOnInsert: true,
}
)) as IMemberInfo;
let needed = getNeededXP(result.level);
while (result.xp >= needed) {
result.level++;
result.xp -= needed;
await result.save();
message.channel.send(
`${message.member} has just advanced to level ${result.level}`
);
needed = getNeededXP(result.level);
}
return {
level: result.level,
xp: result.xp,
neededXP: getNeededXP(result.level),
} as LevelData;
}
Example #10
Source File: types-pg.test.ts From rds-data with MIT License | 5 votes |
d = add(new Date(), { days: 10 })
Example #11
Source File: helpers.tsx From glide-frontend with GNU General Public License v3.0 | 5 votes |
processAuctionData = async (auctionId: number, auctionResponse: AuctionsResponse): Promise<Auction> => {
const processedAuctionData = {
...auctionResponse,
topLeaderboard: auctionResponse.leaderboard.toNumber(),
initialBidAmount: ethersToBigNumber(auctionResponse.initialBidAmount).div(DEFAULT_TOKEN_DECIMAL).toNumber(),
leaderboardThreshold: ethersToBigNumber(auctionResponse.leaderboardThreshold),
startBlock: auctionResponse.startBlock.toNumber(),
endBlock: auctionResponse.endBlock.toNumber(),
}
// Get all required datas and blocks
const currentBlock = await simpleRpcProvider.getBlockNumber()
const startDate = await getDateForBlock(currentBlock, processedAuctionData.startBlock)
const endDate = await getDateForBlock(currentBlock, processedAuctionData.endBlock)
const farmStartDate = add(endDate, { hours: 12 })
const blocksToFarmStartDate = hoursToSeconds(12) / ESC_BLOCK_TIME
const farmStartBlock = processedAuctionData.endBlock + blocksToFarmStartDate
const farmDurationInBlocks = hoursToSeconds(7 * 24) / ESC_BLOCK_TIME
const farmEndBlock = farmStartBlock + farmDurationInBlocks
const farmEndDate = add(farmStartDate, { weeks: 1 })
const auctionStatus = getAuctionStatus(
currentBlock,
processedAuctionData.startBlock,
processedAuctionData.endBlock,
processedAuctionData.status,
)
return {
id: auctionId,
startDate,
endDate,
auctionDuration: differenceInHours(endDate, startDate),
farmStartBlock,
farmStartDate,
farmEndBlock,
farmEndDate,
...processedAuctionData,
status: auctionStatus,
}
}
Example #12
Source File: helpers.ts From js-client with MIT License | 5 votes |
recalculateZoomEnd = (minZoomWindow: number, count: number, start: Date, end: Date): Date => {
const origDeltaS = end.getTime() / 1000 - start.getTime() / 1000;
const deltaS = Math.ceil(origDeltaS / (minZoomWindow * count)) * (minZoomWindow * count);
const newEnd = add(start, { seconds: deltaS });
return newEnd;
}
Example #13
Source File: YearMonthSelect.tsx From UUI with MIT License | 5 votes |
YearMonthSelect = UUIFunctionComponent({
name: 'YearMonthSelect',
nodes: {
Root: 'div',
Container: 'div',
Button: UUIButton,
YearLabel: 'div',
MonthLabel: 'div',
ChevronsLeftIcon: Icons.ChevronsLeft,
ChevronLeftIcon: Icons.ChevronLeft,
ChevronsRightIcon: Icons.ChevronsRight,
ChevronRightIcon: Icons.ChevronRight,
},
propTypes: YearMonthSelectPropTypes,
}, (props: YearMonthSelectFeatureProps, { nodes, NodeDataProps }) => {
const {
Root, Container,
Button, YearLabel, MonthLabel,
ChevronLeftIcon, ChevronsLeftIcon, ChevronRightIcon, ChevronsRightIcon,
} = nodes
const yearLabel = format(props.value, 'yyyy')
const monthLabel = format(props.value, 'M')
const handleChange = useCallback((type: 'years' | 'months', value: number) => {
props.onChange(add(props.value, { [type]: value }))
}, [props])
return (
<Root>
<Container {...NodeDataProps({ 'type': 'actions' })}>
<Button onClick={() => { handleChange('years', -1) }}>
<ChevronsLeftIcon width={18} height={18} />
</Button>
<Button onClick={() => { handleChange('months', -1) }}>
<ChevronLeftIcon width={14} height={14} />
</Button>
</Container>
<Container {...NodeDataProps({ 'type': 'labels' })}>
<YearLabel>{yearLabel}</YearLabel>
<MonthLabel>{monthLabel}</MonthLabel>
</Container>
<Container {...NodeDataProps({ 'type': 'actions' })}>
<Button onClick={() => { handleChange('months', 1) }}>
<ChevronRightIcon width={14} height={14} />
</Button>
<Button onClick={() => { handleChange('years', 1) }}>
<ChevronsRightIcon width={18} height={18} />
</Button>
</Container>
</Root>
)
})
Example #14
Source File: index.tsx From interbtc-ui with Apache License 2.0 | 5 votes |
getFormattedUnlockDate = (remainingBlockNumbersToUnstake: number, formatPattern: string) => {
const unlockDate = add(new Date(), {
seconds: remainingBlockNumbersToUnstake * BLOCK_TIME
});
return format(unlockDate, formatPattern);
}
Example #15
Source File: make-long-term-log.ts From merged-pr-stat with MIT License | 5 votes |
async function main(): Promise<void> {
program.requiredOption("--start <date>").requiredOption("--end <date>").requiredOption("--query <query>");
program.parse(process.argv);
const startDate = parseISO(program.start);
const endDate = parseISO(program.end);
const query = program.query as string;
const intervalDays = 7;
const allLogs = [];
process.stdout.write(
"title,author,url,createdAt,mergedAt,additions,deletions,authoredDate,leadTimeSeconds,timeToMergeSeconds\n"
);
for (let start = startDate; start < endDate; start = addDays(start, intervalDays)) {
const end = min([add(start, { days: intervalDays, seconds: -1 }), endDate]);
console.error(format(start, "yyyy-MM-dd HH:mm:ss"));
console.error(format(end, "yyyy-MM-dd HH:mm:ss"));
const stdout = execFileSync(
"merged-pr-stat",
["log", "--start", start.toISOString(), "--end", end.toISOString(), "--query", query],
{ encoding: "utf8" }
);
const logs: any[] = JSON.parse(stdout);
process.stdout.write(
csvStringify(
logs.map((l) => [
l.title,
l.author,
l.url,
l.createdAt,
l.mergedAt,
l.additions,
l.deletions,
l.authoredDate,
l.leadTimeSeconds,
l.timeToMergeSeconds,
])
)
);
}
}
Example #16
Source File: go.ts From logseq-plugin-vim-shortcuts with MIT License | 5 votes |
parsePageName = async (pageName: string) => {
const config = await logseq.App.getUserConfigs();
const page = await logseq.Editor.getCurrentPage();
switch (pageName) {
case "@":
case "@index":
return "Contents";
case "@today":
pageName = format(new Date(), config.preferredDateFormat);
return pageName;
case "@yesterday":
pageName = format(
sub(new Date(), {
days: 1,
}),
config.preferredDateFormat
);
return pageName;
case "@tomorrow":
pageName = format(
add(new Date(), {
days: 1,
}),
config.preferredDateFormat
);
return pageName;
case "@prev":
if (page && page["journal?"]) {
pageName = format(
sub(new Date(page.name), {
days: 1,
}),
config.preferredDateFormat
);
return pageName;
} else {
pageName = format(
sub(new Date(), {
days: 1,
}),
config.preferredDateFormat
);
return pageName;
}
case "@next":
if (page && page["journal?"]) {
pageName = format(
add(new Date(page.name), {
days: 1,
}),
config.preferredDateFormat
);
return pageName;
} else {
pageName = format(
add(new Date(), {
days: 1,
}),
config.preferredDateFormat
);
return pageName;
}
case "@back":
await backward();
return;
case "@forward":
await forward();
return;
default:
return pageName;
}
}
Example #17
Source File: FiltersBar.tsx From atlas with GNU General Public License v3.0 | 4 votes |
FiltersBar: React.FC<ReturnType<typeof useFiltersBar> & FiltersBarProps> = ({
setVideoWhereInput,
videoWhereInput,
activeFilters,
setOwnedNftWhereInput,
filters: {
setIsFiltersOpen,
isFiltersOpen,
dateUploadedFilter,
setDateUploadedFilter,
excludeMatureContentRatingFilter,
setExcludeMatureContentRatingFilter,
excludePaidPromotionalMaterialFilter,
setExcludePaidPromotionalMaterialFilter,
videoLengthFilter,
setVideoLengthFilter,
nftStatusFilter,
setNftStatusFilter,
categoriesFilter,
setCategoriesFilter,
language,
setLanguage,
},
canClearFilters: {
canClearOtherFilters,
canClearDateUploadedFilter,
canClearVideoLengthFilter,
canClearAllFilters,
canClearNftStatusFilter,
clearNftStatusFilter,
clearCategoriesFilter,
clearAllFilters,
clearDateUploadedFilter,
clearVideoLengthFilter,
clearOtherFilters,
canClearCategoriesFilter,
},
}) => {
const smMatch = useMediaMatch('sm')
const betweenBaseAndSMMatch = !smMatch
const categoriesPopoverRef = useRef<PopoverImperativeHandle>(null)
const datePopoverRef = useRef<PopoverImperativeHandle>(null)
const lengthPopoverRef = useRef<PopoverImperativeHandle>(null)
const othersPopoverRef = useRef<PopoverImperativeHandle>(null)
const { categories } = useCategories()
const nftStatusInputs = useMemo(
() => (
<FilterContentContainer>
{nftStatuses.map((status) => (
<Checkbox
name="nft-status"
label={status.name}
key={`nft-status-${status.id}`}
value={!!nftStatusFilter?.includes(status.id)}
onChange={(value) => {
setNftStatusFilter((statuses) =>
value ? [...(statuses ?? []), status.id] : statuses?.filter((id) => id !== status.id)
)
}}
/>
))}
</FilterContentContainer>
),
[nftStatusFilter, setNftStatusFilter]
)
const categoriesInputs = useMemo(
() => (
<FilterContentContainer>
{categories &&
categories.map((category) => (
<Checkbox
name="category-filter"
label={category.name as string}
key={`category-filter-${category.id}`}
value={!!categoriesFilter?.includes(category.id)}
onChange={(value) => {
setCategoriesFilter((categories) =>
value ? [...(categories ?? []), category.id] : categories?.filter((id) => id !== category.id)
)
}}
/>
))}
</FilterContentContainer>
),
[categories, categoriesFilter, setCategoriesFilter]
)
const dateUploadedInputs = useMemo(
() => (
<FilterContentContainer>
<RadioButton
onChange={() => {
setDateUploadedFilter(1)
}}
name="date-uploaded"
label="Last 24 hours"
value={1}
selectedValue={dateUploadedFilter}
/>
<RadioButton
onChange={() => {
setDateUploadedFilter(7)
}}
name="date-uploaded"
label="Last 7 days"
value={7}
selectedValue={dateUploadedFilter}
/>
<RadioButton
onChange={() => {
setDateUploadedFilter(30)
}}
name="date-uploaded"
label="Last 30 days"
value={30}
selectedValue={dateUploadedFilter}
/>
<RadioButton
onChange={() => {
setDateUploadedFilter(365)
}}
name="date-uploaded"
label="Last 365 days"
value={365}
selectedValue={dateUploadedFilter}
/>
</FilterContentContainer>
),
[dateUploadedFilter, setDateUploadedFilter]
)
const videoLengthInputs = useMemo(
() => (
<FilterContentContainer>
<RadioButton
onChange={() => {
setVideoLengthFilter('0-to-4')
}}
name="length"
label="Less than 4 minutes"
value="0-to-4"
selectedValue={videoLengthFilter}
/>
<RadioButton
onChange={() => {
setVideoLengthFilter('4-to-10')
}}
name="length"
label="4 to 10 minutes"
value="4-to-10"
selectedValue={videoLengthFilter}
/>
<RadioButton
onChange={() => {
setVideoLengthFilter('10-to-9999')
}}
name="length"
label="More than 10 minutes"
value="10-to-9999"
selectedValue={videoLengthFilter}
/>
</FilterContentContainer>
),
[setVideoLengthFilter, videoLengthFilter]
)
const otherFiltersInputs = useMemo(
() => (
<FilterContentContainer>
<Checkbox
onChange={setExcludePaidPromotionalMaterialFilter}
name="other-filters"
label="Paid promotional material"
value={!!excludePaidPromotionalMaterialFilter}
/>
<Checkbox
onChange={setExcludeMatureContentRatingFilter}
name="other-filters"
label="Mature content rating"
value={!!excludeMatureContentRatingFilter}
/>
</FilterContentContainer>
),
[
excludeMatureContentRatingFilter,
excludePaidPromotionalMaterialFilter,
setExcludeMatureContentRatingFilter,
setExcludePaidPromotionalMaterialFilter,
]
)
if (betweenBaseAndSMMatch) {
return (
<MobileFilterDialog
onExitClick={() => setIsFiltersOpen(false)}
show={isFiltersOpen}
title="Filters"
content={
<>
{activeFilters.includes('language') && (
<MobileFilterContainer>
<Text secondary variant="h100">
Language
</Text>
<Select
items={[{ name: 'All languages', value: 'undefined' }, ...languages]}
placeholder="Any language"
size="small"
value={language}
onChange={setLanguage}
/>
</MobileFilterContainer>
)}
{activeFilters.includes('nftStatus') && (
<MobileFilterContainer>
<Text secondary variant="h100">
Status
</Text>
{nftStatusInputs}
</MobileFilterContainer>
)}
{categories && activeFilters.includes('categories') && (
<MobileFilterContainer>
<Text secondary variant="h100">
Categories
</Text>
{categoriesInputs}
</MobileFilterContainer>
)}
{activeFilters.includes('date') && (
<MobileFilterContainer>
<Text secondary variant="h100">
Date uploaded
</Text>
{dateUploadedInputs}
</MobileFilterContainer>
)}
{activeFilters.includes('date-minted') && (
<MobileFilterContainer>
<Text secondary variant="h100">
Date minted
</Text>
{dateUploadedInputs}
</MobileFilterContainer>
)}
{activeFilters.includes('length') && (
<MobileFilterContainer>
<Text secondary variant="h100">
Length
</Text>
{videoLengthInputs}
</MobileFilterContainer>
)}
{activeFilters.includes('other') && (
<MobileFilterContainer>
<OtherFilterStyledText secondary variant="h100">
<OtherFilterStyledIcon />
Exclude:
</OtherFilterStyledText>
{otherFiltersInputs}
</MobileFilterContainer>
)}
</>
}
primaryButton={{
text: 'Apply',
onClick: () => {
setVideoWhereInput((value) => ({
...value,
createdAt_gte: dateUploadedFilter
? add(new Date(), {
days: -dateUploadedFilter,
})
: undefined,
hasMarketing_eq: excludePaidPromotionalMaterialFilter ? !excludePaidPromotionalMaterialFilter : undefined,
isExplicit_eq: excludeMatureContentRatingFilter ? !excludeMatureContentRatingFilter : undefined,
category: categoriesFilter
? {
id_in: categoriesFilter,
}
: undefined,
language:
language !== 'undefined'
? {
iso_eq: language as string,
}
: undefined,
...getDurationRules(),
}))
setOwnedNftWhereInput((value) => {
return {
...value,
OR: [
nftStatusFilter?.includes('AuctionTypeEnglish')
? {
transactionalStatusAuction: {
auctionType_json: { isTypeOf_eq: 'AuctionTypeEnglish' },
},
}
: {},
nftStatusFilter?.includes('AuctionTypeOpen')
? {
transactionalStatusAuction: {
auctionType_json: { isTypeOf_eq: 'AuctionTypeOpen' },
},
}
: {},
nftStatusFilter?.includes('TransactionalStatusBuyNow')
? {
transactionalStatus_json: { isTypeOf_eq: 'TransactionalStatusBuyNow' },
}
: {},
nftStatusFilter?.includes('TransactionalStatusIdle')
? {
transactionalStatus_json: { isTypeOf_eq: 'TransactionalStatusIdle' },
}
: {},
],
}
})
setIsFiltersOpen(false)
},
}}
secondaryButton={{
text: 'Clear',
disabled: !canClearAllFilters,
onClick: () => {
clearAllFilters()
setIsFiltersOpen(false)
},
}}
/>
)
}
return (
<CSSTransition
in={isFiltersOpen}
timeout={parseInt(transitions.timings.routing)}
classNames="filters"
mountOnEnter
unmountOnExit
>
<FiltersContainer open={true}>
<FiltersInnerContainer>
{activeFilters.includes('nftStatus') && (
<DialogPopover
ref={categoriesPopoverRef}
trigger={
<Button variant="secondary" badge={nftStatusFilter?.length}>
Status
</Button>
}
dividers
primaryButton={{
text: 'Apply',
disabled: !nftStatusFilter && !canClearNftStatusFilter,
onClick: () => {
categoriesPopoverRef.current?.hide()
setOwnedNftWhereInput((value) => {
return {
...value,
OR: [
nftStatusFilter?.includes('AuctionTypeEnglish')
? {
transactionalStatusAuction: {
auctionType_json: { isTypeOf_eq: 'AuctionTypeEnglish' },
},
}
: {},
nftStatusFilter?.includes('AuctionTypeOpen')
? {
transactionalStatusAuction: {
auctionType_json: { isTypeOf_eq: 'AuctionTypeOpen' },
},
}
: {},
nftStatusFilter?.includes('TransactionalStatusBuyNow')
? {
transactionalStatus_json: { isTypeOf_eq: 'TransactionalStatusBuyNow' },
}
: {},
nftStatusFilter?.includes('TransactionalStatusIdle')
? {
transactionalStatus_json: { isTypeOf_eq: 'TransactionalStatusIdle' },
}
: {},
],
}
})
},
}}
secondaryButton={{
text: 'Clear',
onClick: clearNftStatusFilter,
disabled: nftStatusFilter === undefined,
}}
>
{nftStatusInputs}
</DialogPopover>
)}
{categories && activeFilters.includes('categories') && (
<DialogPopover
ref={categoriesPopoverRef}
trigger={
<Button variant="secondary" badge={canClearCategoriesFilter && categoriesFilter?.length}>
Categories
</Button>
}
dividers
primaryButton={{
text: 'Apply',
disabled: (!categoriesFilter || !categoriesFilter.length) && !canClearCategoriesFilter,
onClick: () => {
categoriesPopoverRef.current?.hide()
setVideoWhereInput((value) => ({
...value,
category: {
id_in: categoriesFilter,
},
}))
},
}}
secondaryButton={{
text: 'Clear',
onClick: clearCategoriesFilter,
disabled: categoriesFilter === undefined,
}}
>
{categoriesInputs}
</DialogPopover>
)}
{activeFilters.includes('date') && (
<DialogPopover
ref={datePopoverRef}
trigger={
<Button badge={canClearDateUploadedFilter} variant="secondary">
Date uploaded
</Button>
}
primaryButton={{
text: 'Apply',
disabled: !dateUploadedFilter && !canClearDateUploadedFilter,
onClick: () => {
datePopoverRef.current?.hide()
setVideoWhereInput((value) => ({
...value,
createdAt_gte: dateUploadedFilter
? add(new Date(), {
days: -dateUploadedFilter,
})
: undefined,
}))
},
}}
secondaryButton={{
text: 'Clear',
onClick: clearDateUploadedFilter,
disabled: dateUploadedFilter === undefined,
}}
>
{dateUploadedInputs}
</DialogPopover>
)}
{activeFilters.includes('date-minted') && (
<DialogPopover
ref={datePopoverRef}
trigger={
<Button badge={canClearDateUploadedFilter} variant="secondary">
Date minted
</Button>
}
primaryButton={{
text: 'Apply',
disabled: !dateUploadedFilter && !canClearDateUploadedFilter,
onClick: () => {
datePopoverRef.current?.hide()
setVideoWhereInput((value) => ({
...value,
createdAt_gte: dateUploadedFilter
? add(new Date(), {
days: -dateUploadedFilter,
})
: undefined,
}))
},
}}
secondaryButton={{
text: 'Clear',
onClick: clearDateUploadedFilter,
disabled: dateUploadedFilter === undefined,
}}
>
{dateUploadedInputs}
</DialogPopover>
)}
{activeFilters.includes('length') && (
<DialogPopover
ref={lengthPopoverRef}
trigger={
<Button badge={canClearVideoLengthFilter} variant="secondary">
Length
</Button>
}
primaryButton={{
text: 'Apply',
disabled: !videoLengthFilter && !canClearVideoLengthFilter,
onClick: () => {
lengthPopoverRef.current?.hide()
setVideoWhereInput((value) => ({
...value,
...getDurationRules(videoLengthFilter),
}))
},
}}
secondaryButton={{
text: 'Clear',
onClick: clearVideoLengthFilter,
disabled: videoLengthFilter === undefined,
}}
>
{videoLengthInputs}
</DialogPopover>
)}
{activeFilters.includes('other') && (
<DialogPopover
ref={othersPopoverRef}
trigger={
<Button
badge={+(videoWhereInput?.hasMarketing_eq === false) + +(videoWhereInput?.isExplicit_eq === false)}
variant="secondary"
>
Other filters
</Button>
}
primaryButton={{
text: 'Apply',
disabled:
!excludePaidPromotionalMaterialFilter && !excludeMatureContentRatingFilter && !canClearOtherFilters,
onClick: () => {
othersPopoverRef.current?.hide()
setVideoWhereInput((value) => ({
...value,
hasMarketing_eq: excludePaidPromotionalMaterialFilter
? !excludePaidPromotionalMaterialFilter
: undefined,
isExplicit_eq: excludeMatureContentRatingFilter ? !excludeMatureContentRatingFilter : undefined,
}))
},
}}
secondaryButton={{
text: 'Clear',
onClick: clearOtherFilters,
disabled: !excludePaidPromotionalMaterialFilter && !excludeMatureContentRatingFilter,
}}
>
<OtherFilterStyledText secondary variant="h100">
<OtherFilterStyledIcon />
Exclude:
</OtherFilterStyledText>
{otherFiltersInputs}
</DialogPopover>
)}
</FiltersInnerContainer>
{canClearAllFilters && (
<ClearAllButton onClick={clearAllFilters} variant="tertiary" icon={<SvgActionClose />}>
Clear all
</ClearAllButton>
)}
</FiltersContainer>
</CSSTransition>
)
}
Example #18
Source File: index.tsx From interbtc-ui with Apache License 2.0 | 4 votes |
Staking = (): JSX.Element => {
const [blockLockTimeExtension, setBlockLockTimeExtension] = React.useState<number>(0);
const dispatch = useDispatch();
const { t } = useTranslation();
const { governanceTokenBalance, bridgeLoaded, address, prices } = useSelector((state: StoreType) => state.general);
const {
register,
handleSubmit,
watch,
reset,
formState: { errors },
trigger
} = useForm<StakingFormData>({
mode: 'onChange', // 'onBlur'
defaultValues: {
[LOCKING_AMOUNT]: '0',
[LOCK_TIME]: '0'
}
});
const lockingAmount = watch(LOCKING_AMOUNT) || '0';
const lockTime = watch(LOCK_TIME) || '0';
const {
isIdle: currentBlockNumberIdle,
isLoading: currentBlockNumberLoading,
data: currentBlockNumber,
error: currentBlockNumberError
} = useQuery<number, Error>([GENERIC_FETCHER, 'system', 'getCurrentBlockNumber'], genericFetcher<number>(), {
enabled: !!bridgeLoaded
});
useErrorHandler(currentBlockNumberError);
const {
isIdle: voteGovernanceTokenBalanceIdle,
isLoading: voteGovernanceTokenBalanceLoading,
data: voteGovernanceTokenBalance,
error: voteGovernanceTokenBalanceError,
refetch: voteGovernanceTokenBalanceRefetch
} = useQuery<VoteGovernanceTokenMonetaryAmount, Error>(
[GENERIC_FETCHER, 'escrow', 'votingBalance', address],
genericFetcher<VoteGovernanceTokenMonetaryAmount>(),
{
enabled: !!bridgeLoaded
}
);
useErrorHandler(voteGovernanceTokenBalanceError);
// My currently claimable rewards
const {
isIdle: claimableRewardAmountIdle,
isLoading: claimableRewardAmountLoading,
data: claimableRewardAmount,
error: claimableRewardAmountError,
refetch: claimableRewardAmountRefetch
} = useQuery<GovernanceTokenMonetaryAmount, Error>(
[GENERIC_FETCHER, 'escrow', 'getRewards', address],
genericFetcher<GovernanceTokenMonetaryAmount>(),
{
enabled: !!bridgeLoaded
}
);
useErrorHandler(claimableRewardAmountError);
// Projected governance token rewards
const {
isIdle: projectedRewardAmountAndAPYIdle,
isLoading: projectedRewardAmountAndAPYLoading,
data: projectedRewardAmountAndAPY,
error: rewardAmountAndAPYError,
refetch: rewardAmountAndAPYRefetch
} = useQuery<EstimatedRewardAmountAndAPY, Error>(
[GENERIC_FETCHER, 'escrow', 'getRewardEstimate', address],
genericFetcher<EstimatedRewardAmountAndAPY>(),
{
enabled: !!bridgeLoaded
}
);
useErrorHandler(rewardAmountAndAPYError);
// Estimated governance token Rewards & APY
const monetaryLockingAmount = newMonetaryAmount(lockingAmount, GOVERNANCE_TOKEN, true);
const {
isIdle: estimatedRewardAmountAndAPYIdle,
isLoading: estimatedRewardAmountAndAPYLoading,
data: estimatedRewardAmountAndAPY,
error: estimatedRewardAmountAndAPYError
} = useQuery<EstimatedRewardAmountAndAPY, Error>(
[GENERIC_FETCHER, 'escrow', 'getRewardEstimate', address, monetaryLockingAmount, blockLockTimeExtension],
genericFetcher<EstimatedRewardAmountAndAPY>(),
{
enabled: !!bridgeLoaded
}
);
useErrorHandler(estimatedRewardAmountAndAPYError);
const {
isIdle: stakedAmountAndEndBlockIdle,
isLoading: stakedAmountAndEndBlockLoading,
data: stakedAmountAndEndBlock,
error: stakedAmountAndEndBlockError,
refetch: stakedAmountAndEndBlockRefetch
} = useQuery<StakedAmountAndEndBlock, Error>(
[GENERIC_FETCHER, 'escrow', 'getStakedBalance', address],
genericFetcher<StakedAmountAndEndBlock>(),
{
enabled: !!bridgeLoaded
}
);
useErrorHandler(stakedAmountAndEndBlockError);
const initialStakeMutation = useMutation<void, Error, LockingAmountAndTime>(
(variables: LockingAmountAndTime) => {
if (currentBlockNumber === undefined) {
throw new Error('Something went wrong!');
}
const unlockHeight = currentBlockNumber + convertWeeksToBlockNumbers(variables.time);
return window.bridge.escrow.createLock(variables.amount, unlockHeight);
},
{
onSuccess: () => {
voteGovernanceTokenBalanceRefetch();
stakedAmountAndEndBlockRefetch();
claimableRewardAmountRefetch();
rewardAmountAndAPYRefetch();
reset({
[LOCKING_AMOUNT]: '0.0',
[LOCK_TIME]: '0'
});
}
}
);
const moreStakeMutation = useMutation<void, Error, LockingAmountAndTime>(
(variables: LockingAmountAndTime) => {
return (async () => {
if (stakedAmountAndEndBlock === undefined) {
throw new Error('Something went wrong!');
}
if (checkIncreaseLockAmountAndExtendLockTime(variables.time, variables.amount)) {
const unlockHeight = stakedAmountAndEndBlock.endBlock + convertWeeksToBlockNumbers(variables.time);
const txs = [
window.bridge.api.tx.escrow.increaseAmount(variables.amount.toString(variables.amount.currency.rawBase)),
window.bridge.api.tx.escrow.increaseUnlockHeight(unlockHeight)
];
const batch = window.bridge.api.tx.utility.batchAll(txs);
await DefaultTransactionAPI.sendLogged(
window.bridge.api,
window.bridge.account as AddressOrPair,
batch,
undefined, // don't await success event
true // don't wait for finalized blocks
);
} else if (checkOnlyIncreaseLockAmount(variables.time, variables.amount)) {
return await window.bridge.escrow.increaseAmount(variables.amount);
} else if (checkOnlyExtendLockTime(variables.time, variables.amount)) {
const unlockHeight = stakedAmountAndEndBlock.endBlock + convertWeeksToBlockNumbers(variables.time);
return await window.bridge.escrow.increaseUnlockHeight(unlockHeight);
} else {
throw new Error('Something went wrong!');
}
})();
},
{
onSuccess: () => {
voteGovernanceTokenBalanceRefetch();
stakedAmountAndEndBlockRefetch();
claimableRewardAmountRefetch();
rewardAmountAndAPYRefetch();
reset({
[LOCKING_AMOUNT]: '0.0',
[LOCK_TIME]: '0'
});
}
}
);
React.useEffect(() => {
if (!lockTime) return;
const lockTimeValue = Number(lockTime);
setBlockLockTimeExtension(convertWeeksToBlockNumbers(lockTimeValue));
}, [lockTime]);
React.useEffect(() => {
reset({
[LOCKING_AMOUNT]: '',
[LOCK_TIME]: ''
});
}, [address, reset]);
const votingBalanceGreaterThanZero = voteGovernanceTokenBalance?.gt(ZERO_VOTE_GOVERNANCE_TOKEN_AMOUNT);
const extendLockTimeSet = votingBalanceGreaterThanZero && parseInt(lockTime) > 0;
const increaseLockingAmountSet =
votingBalanceGreaterThanZero && monetaryLockingAmount.gt(ZERO_GOVERNANCE_TOKEN_AMOUNT);
React.useEffect(() => {
if (extendLockTimeSet) {
trigger(LOCKING_AMOUNT);
}
}, [lockTime, extendLockTimeSet, trigger]);
React.useEffect(() => {
if (increaseLockingAmountSet) {
trigger(LOCK_TIME);
}
}, [lockingAmount, increaseLockingAmountSet, trigger]);
const getStakedAmount = () => {
if (stakedAmountAndEndBlockIdle || stakedAmountAndEndBlockLoading) {
return undefined;
}
if (stakedAmountAndEndBlock === undefined) {
throw new Error('Something went wrong!');
}
return stakedAmountAndEndBlock.amount;
};
const stakedAmount = getStakedAmount();
const availableBalance = React.useMemo(() => {
if (!governanceTokenBalance || stakedAmountAndEndBlockIdle || stakedAmountAndEndBlockLoading) return;
if (stakedAmount === undefined) {
throw new Error('Something went wrong!');
}
return governanceTokenBalance.sub(stakedAmount).sub(TRANSACTION_FEE_AMOUNT);
}, [governanceTokenBalance, stakedAmountAndEndBlockIdle, stakedAmountAndEndBlockLoading, stakedAmount]);
const onSubmit = (data: StakingFormData) => {
if (!bridgeLoaded) return;
if (currentBlockNumber === undefined) {
throw new Error('Something went wrong!');
}
const lockingAmountWithFallback = data[LOCKING_AMOUNT] || '0';
const lockTimeWithFallback = data[LOCK_TIME] || '0'; // Weeks
const monetaryAmount = newMonetaryAmount(lockingAmountWithFallback, GOVERNANCE_TOKEN, true);
const numberTime = parseInt(lockTimeWithFallback);
if (votingBalanceGreaterThanZero) {
moreStakeMutation.mutate({
amount: monetaryAmount,
time: numberTime
});
} else {
initialStakeMutation.mutate({
amount: monetaryAmount,
time: numberTime
});
}
};
const validateLockingAmount = (value: string): string | undefined => {
const valueWithFallback = value || '0';
const monetaryLockingAmount = newMonetaryAmount(valueWithFallback, GOVERNANCE_TOKEN, true);
if (!extendLockTimeSet && monetaryLockingAmount.lte(ZERO_GOVERNANCE_TOKEN_AMOUNT)) {
return 'Locking amount must be greater than zero!';
}
if (availableBalance === undefined) {
throw new Error('Something went wrong!');
}
if (monetaryLockingAmount.gt(availableBalance)) {
return 'Locking amount must not be greater than available balance!';
}
const planckLockingAmount = monetaryLockingAmount.to.Planck();
const lockBlocks = convertWeeksToBlockNumbers(parseInt(lockTime));
// This is related to the on-chain implementation where currency values are integers.
// So less tokens than the period would likely round to 0.
// So on the UI, as long as you require more planck to be locked than the number of blocks the user locks for,
// it should be good.
if (!extendLockTimeSet && planckLockingAmount.lte(Big(lockBlocks))) {
return 'Planck to be locked must be greater than the number of blocks you lock for!';
}
return undefined;
};
const validateLockTime = (value: string): string | undefined => {
const valueWithFallback = value || '0';
const numericValue = parseInt(valueWithFallback);
if (votingBalanceGreaterThanZero && numericValue === 0 && monetaryLockingAmount.gt(ZERO_GOVERNANCE_TOKEN_AMOUNT)) {
return undefined;
}
if (availableLockTime === undefined) {
throw new Error('Something went wrong!');
}
if (numericValue < STAKE_LOCK_TIME.MIN || numericValue > availableLockTime) {
return `Please enter a number between ${STAKE_LOCK_TIME.MIN}-${availableLockTime}.`;
}
return undefined;
};
const renderVoteStakedAmountLabel = () => {
if (voteGovernanceTokenBalanceIdle || voteGovernanceTokenBalanceLoading) {
return '-';
}
if (voteGovernanceTokenBalance === undefined) {
throw new Error('Something went wrong!');
}
return displayMonetaryAmount(voteGovernanceTokenBalance);
};
const renderProjectedRewardAmountLabel = () => {
if (projectedRewardAmountAndAPYIdle || projectedRewardAmountAndAPYLoading) {
return '-';
}
if (projectedRewardAmountAndAPY === undefined) {
throw new Error('Something went wrong!');
}
return displayMonetaryAmount(projectedRewardAmountAndAPY.amount);
};
const renderStakedAmountLabel = () => {
return stakedAmount === undefined ? '-' : displayMonetaryAmount(stakedAmount);
};
const hasStakedAmount = stakedAmount?.gt(ZERO_GOVERNANCE_TOKEN_AMOUNT);
const getRemainingBlockNumbersToUnstake = () => {
if (
stakedAmountAndEndBlockIdle ||
stakedAmountAndEndBlockLoading ||
currentBlockNumberIdle ||
currentBlockNumberLoading
) {
return undefined;
}
if (stakedAmountAndEndBlock === undefined) {
throw new Error('Something went wrong!');
}
if (currentBlockNumber === undefined) {
throw new Error('Something went wrong!');
}
return hasStakedAmount
? stakedAmountAndEndBlock.endBlock - currentBlockNumber // If the user has staked
: null; // If the user has not staked
};
const remainingBlockNumbersToUnstake = getRemainingBlockNumbersToUnstake();
const getAvailableLockTime = () => {
if (remainingBlockNumbersToUnstake === undefined) {
return undefined;
}
// If the user has staked
if (hasStakedAmount) {
if (remainingBlockNumbersToUnstake === null) {
throw new Error('Something went wrong!');
}
const remainingWeeksToUnstake = convertBlockNumbersToWeeks(remainingBlockNumbersToUnstake);
return Math.floor(STAKE_LOCK_TIME.MAX - remainingWeeksToUnstake);
// If the user has not staked
} else {
return STAKE_LOCK_TIME.MAX;
}
};
const availableLockTime = getAvailableLockTime();
const renderAvailableBalanceLabel = () => {
return availableBalance === undefined ? '-' : displayMonetaryAmount(availableBalance);
};
const renderUnlockDateLabel = () => {
if (errors[LOCK_TIME]) {
return '-';
}
const unlockDate = add(new Date(), {
weeks: parseInt(lockTime)
});
return format(unlockDate, YEAR_MONTH_DAY_PATTERN);
};
const renderNewUnlockDateLabel = () => {
if (remainingBlockNumbersToUnstake === undefined) {
return '-';
}
if (errors[LOCK_TIME]) {
return '-';
}
let remainingLockSeconds;
if (hasStakedAmount) {
if (remainingBlockNumbersToUnstake === null) {
throw new Error('Something went wrong!');
}
remainingLockSeconds = remainingBlockNumbersToUnstake * BLOCK_TIME;
} else {
remainingLockSeconds = 0;
}
const unlockDate = add(new Date(), {
weeks: parseInt(lockTime),
seconds: remainingLockSeconds
});
return format(unlockDate, YEAR_MONTH_DAY_PATTERN);
};
const renderNewVoteGovernanceTokenGainedLabel = () => {
const newTotalStakeAmount = getNewTotalStake();
if (voteGovernanceTokenBalance === undefined || newTotalStakeAmount === undefined) {
return '-';
}
const newVoteGovernanceTokenAmountGained = newTotalStakeAmount.sub(voteGovernanceTokenBalance);
const rounded = newVoteGovernanceTokenAmountGained.toBig(VOTE_GOVERNANCE_TOKEN.base).round(5);
const typed = newMonetaryAmount(rounded, VOTE_GOVERNANCE_TOKEN, true);
return `${displayMonetaryAmount(typed)} ${VOTE_GOVERNANCE_TOKEN_SYMBOL}`;
};
const getNewTotalStake = () => {
if (remainingBlockNumbersToUnstake === undefined || stakedAmount === undefined) {
return undefined;
}
const extendingLockTime = parseInt(lockTime); // Weeks
let newLockTime: number;
let newLockingAmount: GovernanceTokenMonetaryAmount;
if (remainingBlockNumbersToUnstake === null) {
// If the user has not staked
newLockTime = extendingLockTime;
newLockingAmount = monetaryLockingAmount;
} else {
// If the user has staked
const currentLockTime = convertBlockNumbersToWeeks(remainingBlockNumbersToUnstake); // Weeks
// New lock-time that is applied to the entire staked governance token
newLockTime = currentLockTime + extendingLockTime; // Weeks
// New total staked governance token
newLockingAmount = monetaryLockingAmount.add(stakedAmount);
}
// Multiplying the new total staked governance token with the staking time divided by the maximum lock time
return newLockingAmount.mul(newLockTime).div(STAKE_LOCK_TIME.MAX);
};
const renderNewTotalStakeLabel = () => {
const newTotalStakeAmount = getNewTotalStake();
if (newTotalStakeAmount === undefined) {
return '-';
}
return `${displayMonetaryAmount(newTotalStakeAmount)} ${VOTE_GOVERNANCE_TOKEN_SYMBOL}`;
};
const renderEstimatedAPYLabel = () => {
if (
estimatedRewardAmountAndAPYIdle ||
estimatedRewardAmountAndAPYLoading ||
errors[LOCK_TIME] ||
errors[LOCKING_AMOUNT]
) {
return '-';
}
if (estimatedRewardAmountAndAPY === undefined) {
throw new Error('Something went wrong!');
}
return `${safeRoundTwoDecimals(estimatedRewardAmountAndAPY.apy.toString())} %`;
};
const renderEstimatedRewardAmountLabel = () => {
if (
estimatedRewardAmountAndAPYIdle ||
estimatedRewardAmountAndAPYLoading ||
errors[LOCK_TIME] ||
errors[LOCKING_AMOUNT]
) {
return '-';
}
if (estimatedRewardAmountAndAPY === undefined) {
throw new Error('Something went wrong!');
}
return `${displayMonetaryAmount(estimatedRewardAmountAndAPY.amount)} ${GOVERNANCE_TOKEN_SYMBOL}`;
};
const renderClaimableRewardAmountLabel = () => {
if (claimableRewardAmountIdle || claimableRewardAmountLoading) {
return '-';
}
if (claimableRewardAmount === undefined) {
throw new Error('Something went wrong!');
}
return displayMonetaryAmount(claimableRewardAmount);
};
const handleConfirmClick = (event: React.MouseEvent<HTMLButtonElement>) => {
// TODO: should be handled based on https://kentcdodds.com/blog/application-state-management-with-react
if (!accountSet) {
dispatch(showAccountModalAction(true));
event.preventDefault();
}
};
const valueInUSDOfLockingAmount = getUsdAmount(monetaryLockingAmount, prices.governanceToken?.usd);
const claimRewardsButtonEnabled = claimableRewardAmount?.gt(ZERO_GOVERNANCE_TOKEN_AMOUNT);
const unlockFirst =
hasStakedAmount &&
// eslint-disable-next-line max-len
// `remainingBlockNumbersToUnstake !== null` is redundant because if `hasStakedAmount` is truthy `remainingBlockNumbersToUnstake` cannot be null
remainingBlockNumbersToUnstake !== null &&
remainingBlockNumbersToUnstake !== undefined &&
remainingBlockNumbersToUnstake <= 0;
const accountSet = !!address;
const lockTimeFieldDisabled =
votingBalanceGreaterThanZero === undefined ||
remainingBlockNumbersToUnstake === undefined ||
availableLockTime === undefined ||
availableLockTime <= 0 ||
unlockFirst;
const lockingAmountFieldDisabled = availableBalance === undefined;
const initializing =
currentBlockNumberIdle ||
currentBlockNumberLoading ||
voteGovernanceTokenBalanceIdle ||
voteGovernanceTokenBalanceLoading ||
claimableRewardAmountIdle ||
claimableRewardAmountLoading ||
projectedRewardAmountAndAPYIdle ||
projectedRewardAmountAndAPYLoading ||
estimatedRewardAmountAndAPYIdle ||
estimatedRewardAmountAndAPYLoading ||
stakedAmountAndEndBlockIdle ||
stakedAmountAndEndBlockLoading;
let submitButtonLabel: string;
if (initializing) {
submitButtonLabel = 'Loading...';
} else {
if (accountSet) {
// TODO: should improve readability by handling nested conditions
if (votingBalanceGreaterThanZero) {
const numericLockTime = parseInt(lockTime);
if (checkIncreaseLockAmountAndExtendLockTime(numericLockTime, monetaryLockingAmount)) {
submitButtonLabel = 'Add more stake and extend lock time';
} else if (checkOnlyIncreaseLockAmount(numericLockTime, monetaryLockingAmount)) {
submitButtonLabel = 'Add more stake';
} else if (checkOnlyExtendLockTime(numericLockTime, monetaryLockingAmount)) {
submitButtonLabel = 'Extend lock time';
} else {
submitButtonLabel = 'Stake';
}
} else {
submitButtonLabel = 'Stake';
}
} else {
submitButtonLabel = t('connect_wallet');
}
}
return (
<>
<MainContainer>
{process.env.REACT_APP_RELAY_CHAIN_NAME === KUSAMA && (
<WarningBanner
className={SHARED_CLASSES}
message='Block times are currently higher than expected. Lock times may be longer than expected.'
/>
)}
<Panel className={SHARED_CLASSES}>
<form className={clsx('p-8', 'space-y-8')} onSubmit={handleSubmit(onSubmit)}>
<TitleWithUnderline text={`Stake ${GOVERNANCE_TOKEN_SYMBOL}`} />
<BalancesUI
stakedAmount={renderStakedAmountLabel()}
voteStakedAmount={renderVoteStakedAmountLabel()}
projectedRewardAmount={renderProjectedRewardAmountLabel()}
/>
<ClaimRewardsButton
claimableRewardAmount={renderClaimableRewardAmountLabel()}
disabled={claimRewardsButtonEnabled === false}
/>
{/* eslint-disable-next-line max-len */}
{/* `remainingBlockNumbersToUnstake !== null` is redundant because if `hasStakedAmount` is truthy `remainingBlockNumbersToUnstake` cannot be null */}
{hasStakedAmount && remainingBlockNumbersToUnstake !== null && (
<WithdrawButton
stakedAmount={renderStakedAmountLabel()}
remainingBlockNumbersToUnstake={remainingBlockNumbersToUnstake}
/>
)}
<TotalsUI />
<div className='space-y-2'>
<AvailableBalanceUI
label='Available balance'
balance={renderAvailableBalanceLabel()}
tokenSymbol={GOVERNANCE_TOKEN_SYMBOL}
/>
<TokenField
id={LOCKING_AMOUNT}
name={LOCKING_AMOUNT}
label={GOVERNANCE_TOKEN_SYMBOL}
min={0}
ref={register({
required: {
value: extendLockTimeSet ? false : true,
message: 'This field is required!'
},
validate: (value) => validateLockingAmount(value)
})}
approxUSD={`≈ $ ${valueInUSDOfLockingAmount}`}
error={!!errors[LOCKING_AMOUNT]}
helperText={errors[LOCKING_AMOUNT]?.message}
disabled={lockingAmountFieldDisabled}
/>
</div>
<LockTimeField
id={LOCK_TIME}
name={LOCK_TIME}
min={0}
ref={register({
required: {
value: votingBalanceGreaterThanZero ? false : true,
message: 'This field is required!'
},
validate: (value) => validateLockTime(value)
})}
error={!!errors[LOCK_TIME]}
helperText={errors[LOCK_TIME]?.message}
optional={votingBalanceGreaterThanZero}
disabled={lockTimeFieldDisabled}
/>
{votingBalanceGreaterThanZero ? (
<InformationUI
label='New unlock Date'
value={renderNewUnlockDateLabel()}
tooltip='Your original lock date plus the extended lock time.'
/>
) : (
<InformationUI
label='Unlock Date'
value={renderUnlockDateLabel()}
tooltip='Your staked amount will be locked until this date.'
/>
)}
<InformationUI
label={t('staking_page.new_vote_governance_token_gained', {
voteGovernanceTokenSymbol: VOTE_GOVERNANCE_TOKEN_SYMBOL
})}
value={renderNewVoteGovernanceTokenGainedLabel()}
tooltip={t('staking_page.the_increase_in_your_vote_governance_token_balance', {
voteGovernanceTokenSymbol: VOTE_GOVERNANCE_TOKEN_SYMBOL
})}
/>
{votingBalanceGreaterThanZero && (
<InformationUI
label='New total Stake'
value={`${renderNewTotalStakeLabel()}`}
tooltip='Your total stake after this transaction'
/>
)}
<InformationUI
label='Estimated APY'
value={renderEstimatedAPYLabel()}
tooltip={`The APY may change as the amount of total ${VOTE_GOVERNANCE_TOKEN_SYMBOL} changes.`}
/>
<InformationUI
label={`Estimated ${GOVERNANCE_TOKEN_SYMBOL} Rewards`}
value={renderEstimatedRewardAmountLabel()}
tooltip={t('staking_page.the_estimated_amount_of_governance_token_you_will_receive_as_rewards', {
governanceTokenSymbol: GOVERNANCE_TOKEN_SYMBOL,
voteGovernanceTokenSymbol: VOTE_GOVERNANCE_TOKEN_SYMBOL
})}
/>
<SubmitButton
disabled={initializing || unlockFirst}
pending={initialStakeMutation.isLoading || moreStakeMutation.isLoading}
onClick={handleConfirmClick}
endIcon={
unlockFirst ? (
<InformationTooltip label='Please unstake first.' forDisabledAction={unlockFirst} />
) : null
}
>
{submitButtonLabel}
</SubmitButton>
</form>
</Panel>
</MainContainer>
{(initialStakeMutation.isError || moreStakeMutation.isError) && (
<ErrorModal
open={initialStakeMutation.isError || moreStakeMutation.isError}
onClose={() => {
initialStakeMutation.reset();
moreStakeMutation.reset();
}}
title='Error'
description={initialStakeMutation.error?.message || moreStakeMutation.error?.message || ''}
/>
)}
</>
);
}
Example #19
Source File: DateTimeRangePicker.tsx From UUI with MIT License | 4 votes |
DateTimeRangePicker = UUIFunctionComponent({
name: 'DateTimeRangePicker',
nodes: {
Root: 'div',
ConnectIcon: Icons.ArrowRight,
CalendarIcon: Icons.Calendar,
Popover: UUIPopover,
TextField: UUITextField,
Activator: 'div',
Container: 'div',
Toolbar: 'div',
Main: 'div',
StartSection: 'div',
EndSection: 'div',
Section: 'div',
PickerButtons: UUIPickerButtons,
YearMonthSelect: UUIYearMonthSelect,
DateSelect: UUIDateSelect,
TimeSelect: UUITimeSelect,
DateTimeShortcut: UUIDateTimeShortcut,
},
propTypes: DateTimeRangePickerPropTypes,
}, (props: DateTimeRangePickerFeatureProps, { nodes }) => {
const {
Root, ConnectIcon, CalendarIcon, Popover, TextField,
Activator, Container, Main, StartSection, EndSection, Section,
YearMonthSelect, DateSelect, TimeSelect, DateTimeShortcut, PickerButtons,
} = nodes
const startTimeSelectRef = useRef<any | null>(null)
const endTimeSelectRef = useRef<any | null>(null)
const startInputRef = useRef<HTMLInputElement | null>(null)
const endInputRef = useRef<HTMLInputElement | null>(null)
const [active, setActive] = useState(false)
const [whichFocusing, setWhichFocusing] = useState<'start' | 'end'>()
const [hoverDate, setHoverDate] = useState<Date>()
const initialInnerValue = useMemo(() => {
if (props.value === null) {
return {
startDate: null,
endDate: null,
startInput: '',
endInput: '',
startYearMonth: startOfMonth(new Date),
endYearMonth: add(startOfMonth(new Date), { months: 1 }),
}
}
return {
startDate: props.value[0],
endDate: props.value[1],
startInput: formatDateTime(props.value[0]),
endInput: formatDateTime(props.value[1]),
startYearMonth: startOfMonth(props.value[0]),
endYearMonth: isSameMonth(props.value[0], props.value[1]) ? add(startOfMonth(props.value[1]), { months: 1 }) : props.value[1],
}
}, [props.value])
const [innerValue, setInnerValue, resetInnerValue] = usePendingValue<DateTimeRangePickerInnerValue>(initialInnerValue, (value) => {
if (value.startDate && value.endDate) {
handleValueOnChange([value.startDate, value.endDate])
closePopover()
}
}, { resetWhenInitialValueChanged: true })
const selectedDates = useMemo(() => {
return compact([innerValue.startDate, innerValue.endDate])
}, [innerValue.endDate, innerValue.startDate])
const timeSelectScrollToValue = useCallback((type: 'start' | 'end', value: Date, animate?: boolean) => {
if (type === 'start' && startTimeSelectRef.current) {
startTimeSelectRef.current.scrollToValue(value, animate)
}
if (type === 'end' && endTimeSelectRef.current) {
endTimeSelectRef.current.scrollToValue(value, animate)
}
}, [])
const openPopover = useCallback(() => { setActive(true) }, [])
const closePopover = useCallback(() => { setActive(false) }, [])
const handleValueOnChange = useCallback((value: DateTimeRangePickerValue | null) => {
const sortedValue = value?.sort((i, j) => Number(i) - Number(j)) || null
props.onChange(sortedValue)
}, [props])
/**
*
*/
const handleInputOnSubmit = useCallback((type: 'start' | 'end') => {
if (innerValue.startDate && innerValue.endDate) {
const originalInput = formatDateTime(type === 'start' ? innerValue.startDate : innerValue.endDate)
const input = type === 'start' ? innerValue.startInput : innerValue.endInput
if (originalInput === input) return;
try {
if (input === '') {
handleValueOnChange(null)
} else {
const result = tryParseDateTimeFromString(input)
handleValueOnChange(type === 'start' ? [result, innerValue.endDate] : [innerValue.startDate, result])
}
} catch {
resetInnerValue()
}
}
}, [handleValueOnChange, innerValue.endInput, innerValue.endDate, innerValue.startInput, innerValue.startDate, resetInnerValue])
/**
* handle user change year or month in YearMonthSelect.
*/
const handleStartYearMonthSelect = useCallback((value: Date) => {
setInnerValue((oldValue) => {
const startYearMonthDate = value
let endYearMonthDate = oldValue.endYearMonth
if (!isBefore(startYearMonthDate, endYearMonthDate)) {
endYearMonthDate = add(startYearMonthDate, { months: 1 })
}
return {
...oldValue,
startYearMonth: startYearMonthDate,
endYearMonth: endYearMonthDate,
}
})
}, [setInnerValue])
const handleEndYearMonthSelect = useCallback((value: Date) => {
setInnerValue((oldValue) => {
const endYearMonthDate = value
let startYearMonthDate = oldValue.startYearMonth
if (!isAfter(endYearMonthDate, startYearMonthDate)) {
startYearMonthDate = add(endYearMonthDate, { months: -1 })
}
return {
...oldValue,
startYearMonth: startYearMonthDate,
endYearMonth: endYearMonthDate,
}
})
}, [setInnerValue])
/**
* handle user select date in DateSelect.
*/
const handleDateSelect = useCallback((value: Date) => {
let newStartValue = innerValue.startDate
let newEndValue = innerValue.endDate
if (
(newStartValue !== null && newEndValue !== null) ||
(newStartValue === null && newEndValue === null)
) {
if (whichFocusing === 'end') {
newStartValue = null
newEndValue = value
} else {
newStartValue = value
newEndValue = null
}
} else {
if (newStartValue === null) newStartValue = value
if (newEndValue === null) newEndValue = value
if (isAfter(newStartValue, newEndValue)) {
const tmp = new Date(newStartValue)
newStartValue = new Date(newEndValue)
newEndValue = tmp
}
}
setInnerValue((oldValue) => {
return {
...oldValue,
startDate: newStartValue,
startInput: formatDateTime(newStartValue),
endDate: newEndValue,
endInput: formatDateTime(newEndValue),
}
})
}, [innerValue.endDate, innerValue.startDate, setInnerValue, whichFocusing])
/**
* handle user select date in TimeSelect.
*/
const handleTimeSelect = useCallback((type: 'start' | 'end') => {
return (value: Date) => {
setInnerValue((oldValue) => {
const oldDate = type === 'start' ? oldValue.startDate : oldValue.endDate
const newDate = set(oldDate || getZeroDate(), {
hours: value.getHours(),
minutes: value.getMinutes(),
seconds: value.getSeconds(),
})
const newInput = formatDateTime(newDate)
return {
...oldValue,
...(type === 'start' ? {
startDate: newDate,
startInput: newInput,
} : {}),
...(type === 'end' ? {
endDate: newDate,
endInput: newInput,
} : {}),
}
})
}
}, [setInnerValue])
return (
<Root>
<Popover
placement={'bottom-start'}
active={active}
onClickAway={() => {
resetInnerValue();
timeSelectScrollToValue('start', props.value ? props.value[0] : getZeroDate(), false)
timeSelectScrollToValue('end', props.value ? props.value[1] : getZeroDate(), false)
setTimeout(() => { closePopover() }, 10)
}}
activator={
<Activator
onClick={() => {
openPopover()
setTimeout(() => {
const focusedElement = ReactHelper.document?.activeElement
if (startInputRef.current === focusedElement || endInputRef.current === focusedElement) return;
if (startInputRef.current) {
startInputRef.current.focus()
}
}, 0)
}}
>
<TextField
placeholder={props.startPlaceholder}
value={innerValue.startInput}
onChange={(value) => { setInnerValue((oldValue) => ({ ...oldValue, startInput: value })) }}
customize={{
Input: {
ref: startInputRef,
onFocus: () => {
setWhichFocusing('start')
},
onBlur: () => {
setWhichFocusing(undefined)
handleInputOnSubmit('start')
},
onKeyDown: (event) => {
if (event.key === 'Enter') {
handleInputOnSubmit('start')
}
}
}
}}
/>
<ConnectIcon />
<TextField
placeholder={props.endPlaceholder}
value={innerValue.endInput}
onChange={(value) => { setInnerValue((oldValue) => ({ ...oldValue, endInput: value })) }}
customize={{
Input: {
ref: endInputRef,
onFocus: () => {
setWhichFocusing('end')
},
onBlur: () => {
setWhichFocusing(undefined)
handleInputOnSubmit('end')
},
onKeyDown: (event) => {
if (event.key === 'Enter') {
handleInputOnSubmit('end')
}
}
}
}}
/>
<CalendarIcon />
</Activator>
}
>
<Container>
<Main tabIndex={-1}>
{props.shortcuts && (
<DateTimeShortcut
options={props.shortcuts}
onSelect={(value) => {
handleValueOnChange(value)
timeSelectScrollToValue('start', value ? value[0] : getZeroDate(), false)
timeSelectScrollToValue('end', value ? value[1] : getZeroDate(), false)
closePopover()
}}
/>
)}
<StartSection>
<YearMonthSelect
value={innerValue.startYearMonth}
onChange={handleStartYearMonthSelect}
/>
<Section>
<DateSelect
yearMonth={innerValue.startYearMonth}
selectedDates={selectedDates}
onSelect={handleDateSelect}
hoverDate={hoverDate}
onHoverDateChange={(date) => { setHoverDate(date) }}
/>
<TimeSelect
ref={startTimeSelectRef}
value={innerValue.startDate || getZeroDate()}
onChange={handleTimeSelect('start')}
/>
</Section>
</StartSection>
<EndSection>
<YearMonthSelect
value={innerValue.endYearMonth}
onChange={handleEndYearMonthSelect}
/>
<Section>
<DateSelect
yearMonth={innerValue.endYearMonth}
selectedDates={selectedDates}
onSelect={handleDateSelect}
hoverDate={hoverDate}
onHoverDateChange={(date) => { setHoverDate(date) }}
/>
<TimeSelect
ref={endTimeSelectRef}
value={innerValue.endDate || getZeroDate()}
onChange={handleTimeSelect('end')}
/>
</Section>
</EndSection>
</Main>
<PickerButtons
confirmLabel={props.confirmLabel}
cancelLabel={props.cancelLabel}
onCancel={() => {
resetInnerValue()
timeSelectScrollToValue('start', props.value ? props.value[0] : getZeroDate(), false)
timeSelectScrollToValue('end', props.value ? props.value[1] : getZeroDate(), false)
setTimeout(() => { closePopover() }, 10)
}}
onConfirm={() => {
setInnerValue((value) => value, true)
if (innerValue.startDate && innerValue.endDate) {
let data = [innerValue.startDate, innerValue.endDate]
if (isAfter(innerValue.startDate, innerValue.endDate)) {
data = data.reverse()
}
timeSelectScrollToValue('start', data[0], false)
timeSelectScrollToValue('end', data[1], false)
} else {
timeSelectScrollToValue('start', getZeroDate(), false)
timeSelectScrollToValue('end', getZeroDate(), false)
}
setTimeout(() => { closePopover() }, 10)
}}
/>
</Container>
</Popover>
</Root>
)
})
Example #20
Source File: DateSelect.tsx From UUI with MIT License | 4 votes |
DateSelect = UUIFunctionComponent({
name: 'DateSelect',
nodes: {
Root: 'div',
Calendar: 'div',
WeekGrid: 'div',
WeekItem: 'div',
DayGrid: 'div',
DayItem: 'div',
},
propTypes: DateSelectPropTypes,
}, (props: DateSelectFeatureProps, { nodes, NodeDataProps }) => {
const {
Root, Calendar,
WeekGrid, WeekItem,
DayGrid, DayItem,
} = nodes
const betweenIncludeDates = useCallback((date: Date, range: [Date, Date] | Date[]) => {
return !isBefore(date, range[0]) && !isAfter(date, range[1])
}, [])
const dateInfo = useMemo(() => {
const firstDayInMonth = startOfMonth(props.yearMonth)
const weekdayOfFirstDayInMonth = getDay(firstDayInMonth)
const weekdays = range(0, 7).map((i) => {
let date = new Date(props.yearMonth)
date = startOfWeek(date)
date = add(date, { days: i })
return {
key: format(date, 'yyyy-MM-dd'),
date: date,
label: format(date, 'EEEEEE', { locale: zhCN }),
};
});
const days = range(
1 - weekdayOfFirstDayInMonth,
1 - weekdayOfFirstDayInMonth + 6*7,
).map((i) => {
const date = add(firstDayInMonth, { days: i - 1 })
const selected = props.selectedDates.findIndex((i) => isSameDay(date, i)) !== -1
const inSelectedRange = (() => {
if (props.selectedDates.length >= 2) {
return betweenIncludeDates(date, props.selectedDates)
}
return false;
})()
return {
key: format(date, 'yyyy-MM-dd'),
date: date,
label: getDate(date),
active: isSameMonth(props.yearMonth, date),
selected: selected,
inSelectedRange: inSelectedRange,
}
})
return {
weekdays,
days
}
}, [props.yearMonth, props.selectedDates, betweenIncludeDates])
return (
<Root>
<Calendar>
<WeekGrid>
{dateInfo.weekdays.map((weekday) => {
return (
<WeekItem key={weekday.key}>
{weekday.label}
</WeekItem>
);
})}
</WeekGrid>
<DayGrid
onMouseLeave={() => {
props.onHoverDateChange && props.onHoverDateChange(undefined)
}}
>
{dateInfo.days.map((day) => {
const hovering = props.hoverDate ? isSameDay(day.date, props.hoverDate) : false
const inHoverRange = (() => {
if (props.selectedDates.length === 1 && props.hoverDate) {
return betweenIncludeDates(day.date, [props.selectedDates[0], props.hoverDate].sort((i, j) => Number(i) - Number(j)))
}
return false
})()
return (
<DayItem
{...NodeDataProps({
'active': day.active,
'selected': day.selected,
'in-selected-range': day.inSelectedRange,
'in-hover-range': inHoverRange,
'hovering': hovering,
})}
key={day.key}
onClick={() => {
props.onSelect(day.date)
}}
onMouseEnter={() => {
props.onHoverDateChange && props.onHoverDateChange(day.date)
}}
onMouseLeave={() => {
props.onHoverDateChange && props.onHoverDateChange(undefined)
}}
>
{day.label}
</DayItem>
)
})}
</DayGrid>
</Calendar>
</Root>
)
})
Example #21
Source File: DateRangePicker.tsx From UUI with MIT License | 4 votes |
DateRangePicker = UUIFunctionComponent({
name: 'DateRangePicker',
nodes: {
Root: 'div',
ConnectIcon: Icons.ArrowRight,
CalendarIcon: Icons.Calendar,
Popover: UUIPopover,
TextField: UUITextField,
Activator: 'div',
Container: 'div',
Toolbar: 'div',
Main: 'div',
StartSection: 'div',
EndSection: 'div',
YearMonthSelect: UUIYearMonthSelect,
DateSelect: UUIDateSelect,
DateTimeShortcut: UUIDateTimeShortcut,
},
propTypes: DateRangePickerPropTypes,
}, (props: DateRangePickerFeatureProps, { nodes }) => {
const {
Root, ConnectIcon, CalendarIcon, Popover, TextField,
Activator, Container, Toolbar, Main, StartSection, EndSection,
YearMonthSelect, DateSelect, DateTimeShortcut,
} = nodes
const startInputRef = useRef<HTMLInputElement | null>(null)
const endInputRef = useRef<HTMLInputElement | null>(null)
const [whichFocusing, setWhichFocusing] = useState<'start' | 'end'>()
const [active, setActive] = useState(false)
const [hoverDate, setHoverDate] = useState<Date>()
const initialInnerValue = useMemo<DateRangePickerInnerValue>(() => {
if (props.value === null) {
return {
startDate: null,
endDate: null,
startInput: '',
endInput: '',
startYearMonth: startOfMonth(new Date),
endYearMonth: add(startOfMonth(new Date), { months: 1 }),
}
}
return {
startDate: props.value[0],
endDate: props.value[1],
startInput: formatDate(props.value[0]),
endInput: formatDate(props.value[1]),
startYearMonth: startOfMonth(props.value[0]),
endYearMonth: isSameMonth(props.value[0], props.value[1]) ? add(startOfMonth(props.value[1]), { months: 1 }) : props.value[1],
}
}, [props.value])
const [innerValue, setInnerValue, resetInnerValue] = usePendingValue<DateRangePickerInnerValue>(initialInnerValue, (value) => {
if (value.startDate && value.endDate) {
handleValueOnChange([value.startDate, value.endDate])
setActive(false)
}
}, { resetWhenInitialValueChanged: true })
const selectedDates = useMemo(() => {
return compact([innerValue.startDate, innerValue.endDate])
}, [innerValue.endDate, innerValue.startDate])
const handleValueOnChange = useCallback((value: [Date, Date] | null) => {
const sortedValue = value?.sort((i, j) => Number(i) - Number(j)) || null
props.onChange(sortedValue)
}, [props])
/**
*
*/
const handleInputOnSubmit = useCallback((type: 'start' | 'end') => {
if (innerValue.startDate && innerValue.endDate) {
const originalInput = formatDate(props.value && (type === 'start' ? props.value[0] : props.value[1]))
const input = type === 'start' ? innerValue.startInput : innerValue.endInput
if (originalInput === input) return;
try {
if (input === '') {
handleValueOnChange(null)
} else {
const result = tryParseDateFromString(input)
handleValueOnChange(type === 'start' ? [result, innerValue.endDate] : [innerValue.startDate, result])
}
} catch {
resetInnerValue()
}
}
}, [handleValueOnChange, innerValue.endInput, innerValue.endDate, innerValue.startInput, innerValue.startDate, props.value, resetInnerValue])
/**
* handle user change year or month in YearMonthSelect.
*/
const handleStartYearMonthSelect = useCallback((value: Date) => {
setInnerValue((oldValue) => {
const startYearMonthDate = value
let endYearMonthDate = oldValue.endYearMonth
if (!isBefore(startYearMonthDate, endYearMonthDate)) {
endYearMonthDate = add(startYearMonthDate, { months: 1 })
}
return {
...oldValue,
startYearMonth: startYearMonthDate,
endYearMonth: endYearMonthDate,
}
})
}, [setInnerValue])
const handleEndYearMonthSelect = useCallback((value: Date) => {
setInnerValue((oldValue) => {
const endYearMonthDate = value
let startYearMonthDate = oldValue.startYearMonth
if (!isAfter(endYearMonthDate, startYearMonthDate)) {
startYearMonthDate = add(endYearMonthDate, { months: -1 })
}
return {
...oldValue,
startYearMonth: startYearMonthDate,
endYearMonth: endYearMonthDate,
}
})
}, [setInnerValue])
/**
* handle user select date in DateSelect.
*/
const handleDateSelect = useCallback((value: Date) => {
let shouldSubmit = false
let newStartValue = innerValue.startDate
let newEndValue = innerValue.endDate
if (
(newStartValue !== null && newEndValue !== null) ||
(newStartValue === null && newEndValue === null)
) {
if (whichFocusing === 'end') {
newStartValue = null
newEndValue = value
} else {
newStartValue = value
newEndValue = null
}
} else {
if (newStartValue === null) newStartValue = value
if (newEndValue === null) newEndValue = value
if (isAfter(newStartValue, newEndValue)) {
const tmp = new Date(newStartValue)
newStartValue = new Date(newEndValue)
newEndValue = tmp
}
shouldSubmit = true
}
setInnerValue((oldValue) => {
return {
...oldValue,
startDate: newStartValue,
startInput: formatDate(newStartValue),
endDate: newEndValue,
endInput: formatDate(newEndValue),
}
}, shouldSubmit)
}, [innerValue.endDate, innerValue.startDate, setInnerValue, whichFocusing])
return (
<Root>
<Popover
placement={'bottom-start'}
active={active}
onClickAway={() => { setActive(false); resetInnerValue(); }}
activator={
<Activator
onClick={() => {
setActive(true)
setTimeout(() => {
if (whichFocusing === undefined && startInputRef.current) {
startInputRef.current.focus()
}
}, 0)
}}
>
<TextField
placeholder={props.startPlaceholder}
value={innerValue.startInput}
onChange={(value) => { setInnerValue((oldValue) => ({ ...oldValue, startInput: value })) }}
customize={{
Input: {
ref: startInputRef,
onFocus: () => {
setWhichFocusing('start')
},
onBlur: () => {
setWhichFocusing(undefined)
handleInputOnSubmit('start')
},
onKeyDown: (event) => {
if (event.key === 'Enter') {
handleInputOnSubmit('start')
}
}
}
}}
/>
<ConnectIcon />
<TextField
placeholder={props.endPlaceholder}
value={innerValue.endInput}
onChange={(value) => { setInnerValue((oldValue) => ({ ...oldValue, endInput: value })) }}
customize={{
Input: {
ref: endInputRef,
onFocus: () => {
setWhichFocusing('end')
},
onBlur: () => {
setWhichFocusing(undefined)
handleInputOnSubmit('end')
},
onKeyDown: (event) => {
if (event.key === 'Enter') {
handleInputOnSubmit('end')
}
}
}
}}
/>
<CalendarIcon />
</Activator>
}
>
<Container>
<Toolbar>
{props.shortcuts && (
<DateTimeShortcut
options={props.shortcuts}
onSelect={(value) => {
handleValueOnChange(value)
setActive(false)
}}
/>
)}
</Toolbar>
<Main tabIndex={-1}>
<StartSection>
<YearMonthSelect
value={innerValue.startYearMonth}
onChange={handleStartYearMonthSelect}
/>
<DateSelect
yearMonth={innerValue.startYearMonth}
selectedDates={selectedDates}
onSelect={handleDateSelect}
hoverDate={hoverDate}
onHoverDateChange={(date) => { setHoverDate(date) }}
/>
</StartSection>
<EndSection>
<YearMonthSelect
value={innerValue.endYearMonth}
onChange={handleEndYearMonthSelect}
/>
<DateSelect
yearMonth={innerValue.endYearMonth}
selectedDates={selectedDates}
onSelect={handleDateSelect}
hoverDate={hoverDate}
onHoverDateChange={(date) => { setHoverDate(date) }}
/>
</EndSection>
</Main>
</Container>
</Popover>
</Root>
)
})
Example #22
Source File: tracker.spec.ts From tempomat with MIT License | 4 votes |
describe('tracker', () => {
test('can be started for an issue', async () => {
await clearStore()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
expect(await getTracker('ABC-123')).toEqual({
issueKey: 'ABC-123',
description: undefined,
activeTimestamp: baseDate.getTime(),
isActive: true,
intervals: []
})
})
test('can be started with description', async () => {
await clearStore()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
description: 'description',
now: baseDate
})
expect(await getTracker('ABC-123')).toEqual({
issueKey: 'ABC-123',
description: 'description',
activeTimestamp: baseDate.getTime(),
isActive: true,
intervals: []
})
})
test('cannot be started twice', async () => {
await clearStore()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
description: 'description',
now: baseDate
})
expect(await getTracker('ABC-123')).toEqual({
issueKey: 'ABC-123',
description: undefined,
activeTimestamp: baseDate.getTime(),
isActive: true,
intervals: []
})
})
test('can be started for an alias', async () => {
await clearStore()
await aliases.set('lunch', 'ABC-123')
await tempo.startTracker({
issueKeyOrAlias: 'lunch',
now: baseDate
})
expect(await getTracker('ABC-123')).toEqual({
issueKey: 'ABC-123',
description: undefined,
activeTimestamp: baseDate.getTime(),
isActive: true,
intervals: []
})
})
test('can be stopped', async () => {
await clearStore()
authenticate()
const apiMock = mockWorklogResponse()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
description: 'start description',
now: baseDate
})
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 10 })
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
description: 'start description',
timeSpentSeconds: 600,
startDate: '2020-02-28',
startTime: '12:00:00'
})
})
test('can be started after being stopped', async () => {
await clearStore()
authenticate()
mockWorklogResponse()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 10 })
})
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
description: 'second start',
now: add(baseDate, { minutes: 60 })
})
expect(await getTracker('ABC-123')).toEqual({
issueKey: 'ABC-123',
description: 'second start',
activeTimestamp: baseDate.getTime() + 60 * 60 * 1_000,
isActive: true,
intervals: []
})
})
test('cannot be stopped if not started', async () => {
await clearStore()
const apiMock = mockWorklogResponse()
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 10 })
})
expect(apiMock).not.toBeCalled()
})
test('can be stopped for an alias', async () => {
await clearStore()
authenticate()
await aliases.set('lunch', 'ABC-123')
const apiMock = mockWorklogResponse()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.stopTracker({
issueKeyOrAlias: 'lunch',
now: add(baseDate, { minutes: 10 })
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 600,
startDate: '2020-02-28',
startTime: '12:00:00'
})
})
test('can be stopped with remaining estimate for each interval', async () => {
await clearStore()
authenticate()
const apiMock = mockWorklogResponse()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.pauseTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 10 })
})
await tempo.resumeTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 20 })
})
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
remainingEstimate: '1h',
now: add(baseDate, { minutes: 30 })
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 600,
startDate: '2020-02-28',
startTime: '12:00:00',
remainingEstimateSeconds: 3600
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 600,
startDate: '2020-02-28',
startTime: '12:20:00',
remainingEstimateSeconds: 3600
})
})
test('does not log intervals below 1 minute', async () => {
await clearStore()
const apiMock = mockWorklogResponse()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { seconds: 59 })
})
expect(apiMock).not.toBeCalled()
})
test('can be paused and resumed', async () => {
await clearStore()
authenticate()
const apiMock = mockWorklogResponse()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.pauseTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 10 })
})
await tempo.resumeTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 20 })
})
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 30 })
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 600,
startDate: '2020-02-28',
startTime: '12:00:00'
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 600,
startDate: '2020-02-28',
startTime: '12:20:00'
})
})
test('does not have to be resumed to be stopped', async () => {
await clearStore()
authenticate()
const apiMock = mockWorklogResponse()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.pauseTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 10 })
})
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 30 })
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 600,
startDate: '2020-02-28',
startTime: '12:00:00'
})
})
test('can be paused consecutively multiple times', async () => {
await clearStore()
authenticate()
const apiMock = mockWorklogResponse()
const pauses = Array.from(Array(5), (_, i) => (i + 2))
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
for (const pause of pauses) {
await tempo.pauseTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: pause })
})
}
await tempo.resumeTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 20 })
})
for (const pause of pauses) {
await tempo.pauseTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 20 + pause })
})
}
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 40 })
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 120,
startDate: '2020-02-28',
startTime: '12:00:00'
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 120,
startDate: '2020-02-28',
startTime: '12:20:00'
})
})
test('does not have to be paused to be resumed', async () => {
await clearStore()
authenticate()
const apiMock = mockWorklogResponse()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.resumeTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 10 })
})
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 20 })
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 1200,
startDate: '2020-02-28',
startTime: '12:00:00'
})
})
test('can be resumed consecutively multiple times', async () => {
await clearStore()
authenticate()
const apiMock = mockWorklogResponse()
const resumes = Array.from(Array(5), (_, i) => (i + 2))
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
for (const resume of resumes) {
await tempo.resumeTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: resume })
})
}
await tempo.pauseTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 20 })
})
for (const resume of resumes) {
await tempo.resumeTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 20 + resume })
})
}
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 40 })
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 1200,
startDate: '2020-02-28',
startTime: '12:00:00'
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 1080,
startDate: '2020-02-28',
startTime: '12:22:00'
})
})
test('is not removed if worklog fails to update', async () => {
await clearStore()
authenticate()
mockWorklogFailure()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 10 })
})
expect(await getTracker('ABC-123')).toEqual({
issueKey: 'ABC-123',
description: undefined,
activeTimestamp: baseDate.getTime(),
isActive: false,
intervals: [{
start: baseDate.getTime(),
end: add(baseDate, { minutes: 10 }).getTime()
}]
})
})
test('overwrites description for stop', async () => {
await clearStore()
authenticate()
const apiMock = mockWorklogResponse()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
description: 'start description',
now: baseDate
})
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
description: 'stop description',
now: add(baseDate, { minutes: 10 })
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
description: 'stop description',
timeSpentSeconds: 600,
startDate: '2020-02-28',
startTime: '12:00:00'
})
})
test('removes only logged intervals for failure', async () => {
await clearStore()
authenticate()
api.addWorklog = jest.fn()
.mockReturnValueOnce({ issue: { key: 'ABC-123', self: 'https://example.atlassian.net/rest/api/2/issue/ABC-123' } })
.mockRejectedValueOnce(new Error('Failed to upload worklog!'))
.mockRejectedValueOnce(new Error('Failed to upload worklog!'))
.mockReturnValueOnce({ issue: { key: 'ABC-123', self: 'https://example.atlassian.net/rest/api/2/issue/ABC-123' } })
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.pauseTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 5 })
})
await tempo.resumeTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 10 })
})
await tempo.pauseTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 15 })
})
await tempo.resumeTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 20 })
})
await tempo.pauseTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 25 })
})
await tempo.resumeTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 30 })
})
await tempo.stopTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 60 })
})
expect(await getTracker('ABC-123')).toEqual({
issueKey: 'ABC-123',
description: undefined,
activeTimestamp: add(baseDate, { minutes: 30 }).getTime(),
isActive: false,
intervals: [
{
start: add(baseDate, { minutes: 10 }).getTime(),
end: add(baseDate, { minutes: 15 }).getTime()
},
{
start: add(baseDate, { minutes: 20 }).getTime(),
end: add(baseDate, { minutes: 25 }).getTime()
}]
})
})
test('can be deleted for an issue', async () => {
await clearStore()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.deleteTracker({ issueKeyOrAlias: 'ABC-123' })
expect(await getTracker('ABC-123')).toBeUndefined()
})
test('can be deleted for an alias', async () => {
await clearStore()
await aliases.set('lunch', 'ABC-123')
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.deleteTracker({ issueKeyOrAlias: 'lunch' })
expect(await getTracker('ABC-123')).toBeUndefined()
})
test('is not deleted for different key', async () => {
await clearStore()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.deleteTracker({ issueKeyOrAlias: 'ABC-124' })
expect(await getTracker('ABC-123')).not.toBeUndefined()
})
test('stop previous flag can start a tracker for the same issue', async () => {
await clearStore()
authenticate()
const apiMock = mockWorklogResponse()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 30 }),
stopPreviousTracker: true,
description: 'second tracker'
})
expect(apiMock).toBeCalledWith({
issueKey: 'ABC-123',
timeSpentSeconds: 1800,
startDate: '2020-02-28',
startTime: '12:00:00',
description: undefined
})
expect(await getTracker('ABC-123')).toEqual({
issueKey: 'ABC-123',
description: 'second tracker',
activeTimestamp: add(baseDate, { minutes: 30 }).getTime(),
isActive: true,
intervals: []
})
})
test('stop previous flag does not start a tracker for the same issue for log failures', async () => {
await clearStore()
authenticate()
mockWorklogFailure()
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: baseDate
})
await tempo.startTracker({
issueKeyOrAlias: 'ABC-123',
now: add(baseDate, { minutes: 30 }),
stopPreviousTracker: true
})
expect(await getTracker('ABC-123')).toEqual({
issueKey: 'ABC-123',
description: undefined,
activeTimestamp: baseDate.getTime(),
isActive: false,
intervals: [{
start: baseDate.getTime(),
end: add(baseDate, { minutes: 30 }).getTime()
}]
})
})
})
Example #23
Source File: networkRevenueProvider.ts From akashlytics with GNU General Public License v3.0 | 4 votes |
getWeb3IndexRevenue = async (debug: boolean) => {
if (!debug && cachedRevenue && cachedRevenueDate && Math.abs(differenceInMinutes(cachedRevenueDate, new Date())) < 30) {
return cachedRevenue;
}
while (isCalculatingRevenue || isSyncingPrices) {
await sleep(5000);
}
const dailyNetworkRevenues = await getDailyRevenue();
// console.log(dailyNetworkRevenues[0]);
let days = dailyNetworkRevenues.map((r) => ({
date: r.date.getTime() / 1000,
revenue: round(r.usd, 2),
revenueUAkt: r.uakt,
aktPrice: r.aktPrice,
dateStr: r.date
}));
const today = getTodayUTC();
const oneDayAgo = add(today, { days: -1 });
const twoDaysAgo = add(today, { days: -2 });
const oneWeekAgo = add(today, { weeks: -1 });
const twoWeeksAgo = add(today, { weeks: -2 });
const thirtyDaysAgo = add(today, { days: -30 });
const sixtyDaysAgo = add(today, { days: -60 });
const ninetyDaysAgo = add(today, { days: -90 });
let totalRevenue: number = 0,
oneDayAgoRevenue: number = 0,
twoDaysAgoRevenue: number = 0,
oneWeekAgoRevenue: number = 0,
twoWeeksAgoRevenue: number = 0,
thirtyDaysAgoRevenue: number = 0,
sixtyDaysAgoRevenue: number = 0,
ninetyDaysAgoRevenue: number = 0;
let totalRevenueUAkt: number = 0,
oneDayAgoRevenueUAkt: number = 0,
twoDaysAgoRevenueUAkt: number = 0,
oneWeekAgoRevenueUAkt: number = 0,
twoWeeksAgoRevenueUAkt: number = 0,
thirtyDaysAgoRevenueUAkt: number = 0,
sixtyDaysAgoRevenueUAkt: number = 0,
ninetyDaysAgoRevenueUAkt: number = 0;
days.forEach((b) => {
const date = new Date(b.date * 1000);
if (date <= ninetyDaysAgo) {
ninetyDaysAgoRevenue += b.revenue;
ninetyDaysAgoRevenueUAkt += b.revenueUAkt;
}
if (date <= sixtyDaysAgo) {
sixtyDaysAgoRevenue += b.revenue;
sixtyDaysAgoRevenueUAkt += b.revenueUAkt;
}
if (date <= thirtyDaysAgo) {
thirtyDaysAgoRevenue += b.revenue;
thirtyDaysAgoRevenueUAkt += b.revenueUAkt;
}
if (date <= twoWeeksAgo) {
twoWeeksAgoRevenue += b.revenue;
twoWeeksAgoRevenueUAkt += b.revenueUAkt;
}
if (date <= oneWeekAgo) {
oneWeekAgoRevenue += b.revenue;
oneWeekAgoRevenueUAkt += b.revenueUAkt;
}
if (date <= twoDaysAgo) {
twoDaysAgoRevenue += b.revenue;
twoDaysAgoRevenueUAkt += b.revenueUAkt;
}
if (date <= oneDayAgo) {
oneDayAgoRevenue += b.revenue;
oneDayAgoRevenueUAkt += b.revenueUAkt;
}
totalRevenue += b.revenue;
totalRevenueUAkt += b.revenueUAkt;
}, 0);
if (!debug) {
days = days.map(({ dateStr, revenueUAkt, aktPrice, ...others }) => others) as any;
}
let revenueStats = {
now: round(totalRevenue),
oneDayAgo: round(oneDayAgoRevenue),
twoDaysAgo: round(twoDaysAgoRevenue),
oneWeekAgo: round(oneWeekAgoRevenue),
twoWeeksAgo: round(twoWeeksAgoRevenue),
thirtyDaysAgo: round(thirtyDaysAgoRevenue),
sixtyDaysAgo: round(sixtyDaysAgoRevenue),
ninetyDaysAgo: round(ninetyDaysAgoRevenue)
};
if (debug) {
revenueStats = {
...revenueStats,
nowAkt: uaktToAKT(totalRevenueUAkt, 6),
oneDayAgoAkt: uaktToAKT(oneDayAgoRevenueUAkt, 6),
twoDaysAgoAkt: uaktToAKT(twoDaysAgoRevenueUAkt, 6),
oneWeekAgoAkt: uaktToAKT(oneWeekAgoRevenueUAkt, 6),
twoWeeksAgAkt: uaktToAKT(twoWeeksAgoRevenueUAkt, 6),
thirtyDaysAgoAkt: uaktToAKT(thirtyDaysAgoRevenueUAkt, 6),
sixtyDaysAgoAkt: uaktToAKT(sixtyDaysAgoRevenueUAkt, 6),
ninetyDaysAgoAkt: uaktToAKT(ninetyDaysAgoRevenueUAkt, 6)
} as any;
}
const responseObj = {
revenue: revenueStats,
days
};
cachedRevenue = responseObj;
cachedRevenueDate = new Date();
return responseObj;
}