Skip to content

Commit

Permalink
Merge pull request #145 from map3xyz/phil/map-226-allow-optional-expi…
Browse files Browse the repository at this point in the history
…rationtime-parameter

Phil/map 226 allow optional expirationtime parameter
  • Loading branch information
plondon authored Mar 3, 2023
2 parents 92adeea + 6f54c33 commit 2c8c80f
Show file tree
Hide file tree
Showing 17 changed files with 289 additions and 76 deletions.
1 change: 1 addition & 0 deletions jest/__mocks__/gqlMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const mocks: MockedResponse[] = [
getAssetsForOrgMock({ currency: 'USD', limit: 10, offset: 0 }),
getAssetsForOrgMock({ currency: 'USD', limit: 10, offset: 0 }),
getAssetsForOrgMock({ assetId: 'satoshi123' }),
getAssetsForOrgMock({ assetId: 'elon123' }),
getAssetsForOrgMock({
address: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
}),
Expand Down
81 changes: 81 additions & 0 deletions src/components/CountdownTimer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useContext, useEffect, useState } from 'react';

import { Context } from '../../providers/Store';

const circumference = Math.PI * 20;

const CountdownTimer: React.FC<Props> = () => {
const [state] = useContext(Context);

if (!state.expiration) {
return null;
}

const currentTime = new Date().getTime();
const progressedTime = currentTime - state.initTime.getTime();
const totalTime = state.expiration.getTime() - state.initTime.getTime();
const remainingTime = state.expiration.getTime() - currentTime;
const remainingTimeSeconds = Math.floor(remainingTime / 1000);
const completedPercentage = progressedTime / totalTime;
const [seconds, setSeconds] = useState(remainingTimeSeconds);
const [position, setPosition] = useState(0);

const countdown = () => {
if (seconds <= 0) {
return;
}
setSeconds((prev) => prev - 1);
};

useEffect(() => {
countdown();
const interval = setInterval(countdown, 1000);

return () => clearInterval(interval);
}, []);

useEffect(() => {
const position = circumference - circumference * completedPercentage;
setPosition(position);
}, [seconds]);

const hours = Math.floor(seconds / 3600)
.toString()
.padStart(2, '0');
const minutes = (Math.floor(seconds / 60) % 60).toString().padStart(2, '0');
const secondsLeft = (seconds % 60).toString().padStart(2, '0');

return seconds > 0 ? (
<span
aria-label={`Expires in ${
hours === '00' ? '' : `${hours}:`
}${minutes}:${secondsLeft}`}
className="hint--left relative h-[22px] w-[22px] rounded-full border-[2px] border-accent-light"
>
<svg
className="absolute left-[-5px] top-[-5px] h-7 w-7 scale-75 stroke-accent "
style={{ transform: 'rotateZ(-90deg)' }}
>
<circle
className="transition-all"
cx="50%"
cy="50%"
r="10"
style={{
fill: 'none',
strokeDasharray: `${circumference}px`,
strokeDashoffset: `${position}px`,
strokeLinecap: 'round',
strokeWidth: '2px',
}}
></circle>
</svg>
</span>
) : (
<span className="text-xxs text-red-600">Expired</span>
);
};

type Props = {};

export default CountdownTimer;
21 changes: 21 additions & 0 deletions src/components/StepTitle/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';

import CountdownTimer from '../CountdownTimer';

const StepTitle: React.FC<Props> = (props) => {
return (
<h3
className="flex items-center justify-between text-lg font-semibold dark:text-white"
data-testid={props.testId}
>
<span>{props.value}</span> <CountdownTimer />
</h3>
);
};

type Props = {
testId?: string;
value: string;
};

export default StepTitle;
52 changes: 51 additions & 1 deletion src/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ describe('Map3Sdk', () => {
};
expect(initFn).not.toThrow();
expect(warnSpy).toBeCalledWith(
'Warning: networkCode is required when amount is provided. Falling back to asset selection.'
'Warning: amount is provided but not assetId or address and network. Falling back to undefined amount.'
);
});
it('should check valid config.colors.primary', () => {
Expand Down Expand Up @@ -337,4 +337,54 @@ describe('Map3Sdk', () => {
});
expect(initFn).toThrow('options.callbacks.onAddressRequested is required.');
});
it('should warn if rate is provided but not assetId or address and network', () => {
const warnSpy = jest.spyOn(console, 'warn');

const initFn = () =>
initMap3Supercharge({
anonKey:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjb25zb2xlIiwib3JnX2lkIjoiYzljNDczMzYtNWM5MS00MDM0LWIyYTgtMGI1NzA5ZTAwMGI1Iiwicm9sZXMiOlsiYW5vbnltb3VzIl0sImlhdCI6MTY3NTg4ODUwOCwiZXhwIjoxNzA3NDI0NTA4fQ.GzuXjFzSVkE3L-LlhtvpXa3aIi48rvHgMY3hw6lS8KU',
options: {
callbacks: {
onAddressRequested: async () => {
return { address: '0x000000' };
},
},
selection: {
rate: 10_000,
},
},
userId: 'test',
});

expect(initFn).not.toThrow();
expect(warnSpy).toBeCalledWith(
'Warning: rate is provided but not assetId or address and network. Falling back to default rate.'
);
});
it('should allow an expiration time in the form of milliseconds or ISO 8601 in the future', () => {
const warnSpy = jest.spyOn(console, 'warn');

const initFn = () =>
initMap3Supercharge({
anonKey:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjb25zb2xlIiwib3JnX2lkIjoiYzljNDczMzYtNWM5MS00MDM0LWIyYTgtMGI1NzA5ZTAwMGI1Iiwicm9sZXMiOlsiYW5vbnltb3VzIl0sImlhdCI6MTY3NTg4ODUwOCwiZXhwIjoxNzA3NDI0NTA4fQ.GzuXjFzSVkE3L-LlhtvpXa3aIi48rvHgMY3hw6lS8KU',
options: {
callbacks: {
onAddressRequested: async () => {
return { address: '0x000000' };
},
},
selection: {
expiration: '2021-12-31T23:59:59.999Z',
},
},
userId: 'test',
});

expect(initFn).not.toThrow();
expect(warnSpy).toBeCalledWith(
'Warning: expiration is in the past or invalid. Falling back to default expiration.'
);
});
});
48 changes: 41 additions & 7 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ export interface Map3InitConfig {
address?: string;
amount?: string;
assetId?: string;
expiration?: string | number;
fiat?: string;
networkCode?: string;
paymentMethod?: 'binance-pay';
rate?: number;
shortcutAmounts?: number[];
};
style?: {
Expand Down Expand Up @@ -105,6 +107,11 @@ export class Map3 {
config.options.selection.fiat = 'USD';
}

const isAsset =
config.options.selection.assetId ||
(config.options.selection.address &&
config.options.selection.networkCode);

if (!ISO_4217_TO_SYMBOL[config.options.selection.fiat]) {
console.warn(
`Warning: fiat ${config.options.selection.fiat} is not supported. Falling back to USD.`
Expand Down Expand Up @@ -135,16 +142,37 @@ export class Map3 {
config.options.selection.address = undefined;
}

if (
config.options.selection.amount &&
!config.options.selection.networkCode
) {
if (config.options.selection.amount && !isAsset) {
console.warn(
'Warning: networkCode is required when amount is provided. Falling back to asset selection.'
'Warning: amount is provided but not assetId or address and network. Falling back to undefined amount.'
);
config.options.selection.amount = undefined;
}

if (config.options.selection.rate && !isAsset) {
console.warn(
'Warning: rate is provided but not assetId or address and network. Falling back to default rate.'
);
config.options.selection.rate = undefined;
}

if (config.options.selection.expiration) {
try {
const timeRemainingMs =
new Date(config.options.selection.expiration).getTime() -
new Date().getTime();

if (timeRemainingMs < 0) {
throw new Error('Expiration is in the past.');
}
} catch (e) {
console.warn(
'Warning: expiration is in the past or invalid. Falling back to default expiration.'
);
config.options.selection.expiration = undefined;
}
}

if (config.options.style?.appName) {
document.title = config.options.style.appName;
}
Expand All @@ -168,7 +196,14 @@ export class Map3 {
});

// orange-600
document.body.style.setProperty('--accent-color', 'rgb(234, 88, 12)');
const orange600 = 'rgb(234, 88, 12)';
document.body.style.setProperty('--accent-color', orange600);
document.body.style.setProperty(
'--accent-color-light',
colord(config.options.style?.colors?.accent || orange600)
.lighten(0.35)
.toHex()
);

// theme colors
if (config.options.style && config.options.style.colors) {
Expand Down Expand Up @@ -224,7 +259,6 @@ export class Map3 {
primaryColor.mix(shades[shade as keyof typeof shades], 0.5).toHex()
);
});
} else {
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ root.render(
},
},
selection: {
assetId: '6b562c23-d79f-4a34-a47f-cc7b28726821',
paymentMethod: 'binance-pay',
amount: '10000',
assetId: '53adbb94-6a68-4eeb-af49-6b6d9e84a1f4',
fiat: 'USD',
rate: 2,
},
style: {
theme: 'dark',
Expand Down
Loading

0 comments on commit 2c8c80f

Please sign in to comment.