diff --git a/.github/workflows/u3-firebase-hosting-merge.yml b/.github/workflows/u3-firebase-hosting-merge.yml
index 1aabecbd..5e29e5d1 100644
--- a/.github/workflows/u3-firebase-hosting-merge.yml
+++ b/.github/workflows/u3-firebase-hosting-merge.yml
@@ -7,6 +7,7 @@ name: Deploy u3 to Firebase Hosting on merge
branches:
- u3
- u3-dev
+ - u3-pwa
jobs:
prod_build_and_deploy:
if: github.ref == 'refs/heads/u3'
@@ -46,6 +47,7 @@ jobs:
REACT_APP_CASTER_NFT_FIXED_PRICE_STRATEGY: "${{ vars.REACT_APP_CASTER_NFT_FIXED_PRICE_STRATEGY }}"
REACT_APP_CASTER_NFT_CHAIN_ID: "${{ vars.REACT_APP_CASTER_NFT_CHAIN_ID }}"
REACT_APP_CASTER_NFT_RECIPIENT_ADDRESS: "${{ vars.REACT_APP_CASTER_NFT_RECIPIENT_ADDRESS }}"
+ REACT_APP_VAPID_PUBLIC_KEY: "${{ vars.REACT_APP_VAPID_PUBLIC_KEY }}"
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: "${{ secrets.GITHUB_TOKEN }}"
@@ -92,6 +94,7 @@ jobs:
REACT_APP_CASTER_NFT_FIXED_PRICE_STRATEGY: "${{ vars.REACT_APP_CASTER_NFT_FIXED_PRICE_STRATEGY }}"
REACT_APP_CASTER_NFT_CHAIN_ID: "${{ vars.REACT_APP_CASTER_NFT_CHAIN_ID }}"
REACT_APP_CASTER_NFT_RECIPIENT_ADDRESS: "${{ vars.REACT_APP_CASTER_NFT_RECIPIENT_ADDRESS }}"
+ REACT_APP_VAPID_PUBLIC_KEY: "${{ vars.REACT_APP_VAPID_PUBLIC_KEY }}"
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: "${{ secrets.GITHUB_TOKEN }}"
@@ -100,3 +103,50 @@ jobs:
projectId: us3r-network
target: u3-dev
entryPoint: "./apps/u3/"
+ pwa_build_and_deploy:
+ if: github.ref == 'refs/heads/u3-pwa'
+ runs-on: ubuntu-latest
+ environment:
+ name: development
+ url: https://dev.u3.xyz
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: "20"
+ - run: |
+ cd apps/u3
+ yarn install --ignore-engines
+ yarn build
+ env:
+ CI: false
+ REACT_APP_NAME: "U3 DEV"
+ NODE_OPTIONS: "--max_old_space_size=4096"
+ REACT_APP_API_BASE_URL: "${{ vars.REACT_APP_API_BASE_URL }}"
+ REACT_APP_S3_API_BASE_URL: "${{ vars.REACT_APP_S3_API_BASE_URL }}"
+ REACT_APP_US3R_UPLOAD_IMAGE_ENDPOINT: "${{ vars.REACT_APP_US3R_UPLOAD_IMAGE_ENDPOINT }}"
+ REACT_APP_CERAMIC_HOST: "${{ vars.REACT_APP_CERAMIC_HOST }}"
+ REACT_APP_CHROME_EXTENSION_URL: "${{ vars.REACT_APP_CHROME_EXTENSION_URL }}"
+ REACT_APP_API_SOCIAL_URL: "${{ vars.REACT_APP_API_SOCIAL_URL }}"
+ REACT_APP_XMTP_ENV: "${{ vars.REACT_APP_XMTP_ENV }}"
+ REACT_APP_LENS_ENV: "${{ vars.REACT_APP_LENS_ENV }}"
+ REACT_APP_FARCASTER_HUB_URL: "${{ vars.REACT_APP_FARCASTER_HUB_URL }}"
+ REACT_APP_FARCASTER_NETWORK: "${{ vars.REACT_APP_FARCASTER_NETWORK }}"
+ REACT_APP_NFT_STORAGE_API_KEY: "${{ vars.REACT_APP_NFT_STORAGE_API_KEY }}"
+ REACT_APP_DAPP_NFT_TO_MINT: "${{ vars.REACT_APP_DAPP_NFT_TO_MINT }}"
+ REACT_APP_DAPP_NFT_FIXED_PRICE_STRATEGY: "${{ vars.REACT_APP_DAPP_NFT_FIXED_PRICE_STRATEGY }}"
+ REACT_APP_DAPP_NFT_CHAIN_ID: "${{ vars.REACT_APP_DAPP_NFT_CHAIN_ID }}"
+ REACT_APP_DAPP_NFT_RECIPIENT_ADDRESS: "${{ vars.REACT_APP_DAPP_NFT_RECIPIENT_ADDRESS }}"
+ REACT_APP_CASTER_NFT_TO_MINT: "${{ vars.REACT_APP_CASTER_NFT_TO_MINT }}"
+ REACT_APP_CASTER_NFT_FIXED_PRICE_STRATEGY: "${{ vars.REACT_APP_CASTER_NFT_FIXED_PRICE_STRATEGY }}"
+ REACT_APP_CASTER_NFT_CHAIN_ID: "${{ vars.REACT_APP_CASTER_NFT_CHAIN_ID }}"
+ REACT_APP_CASTER_NFT_RECIPIENT_ADDRESS: "${{ vars.REACT_APP_CASTER_NFT_RECIPIENT_ADDRESS }}"
+ REACT_APP_VAPID_PUBLIC_KEY: "${{ vars.REACT_APP_VAPID_PUBLIC_KEY }}"
+ - uses: FirebaseExtended/action-hosting-deploy@v0
+ with:
+ repoToken: "${{ secrets.GITHUB_TOKEN }}"
+ firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_US3R_NETWORK }}"
+ channelId: live
+ projectId: us3r-network
+ target: u3-pwa
+ entryPoint: "./apps/u3/"
diff --git a/apps/u3/.env.development b/apps/u3/.env.development
index 6a2753a9..d692e4b0 100644
--- a/apps/u3/.env.development
+++ b/apps/u3/.env.development
@@ -60,3 +60,5 @@ REACT_APP_CASTER_NFT_RECIPIENT_ADDRESS = 0x885d0069e238C7929F0351689A9493fECad9
# REACT_APP_CASTER_NFT_FIXED_PRICE_STRATEGY = 0x04E2516A2c207E84a1839755675dfd8eF6302F0a
# REACT_APP_CASTER_NFT_CHAIN_ID = 999
# REACT_APP_CASTER_NFT_RECIPIENT_ADDRESS = 0x885d0069e238C7929F0351689A9493fECad952Fe
+
+REACT_APP_VAPID_PUBLIC_KEY = BGMGngE6KTyCIbeBwtNLSObcu0IZM_QH8PVd4c4M5trYPonjjXGDM3aIhqFLIWFRyEF7XiHGB0CBcclsQTSWJoM
\ No newline at end of file
diff --git a/apps/u3/.firebaserc b/apps/u3/.firebaserc
index 56c2060d..5e26d933 100644
--- a/apps/u3/.firebaserc
+++ b/apps/u3/.firebaserc
@@ -10,6 +10,9 @@
],
"u3-dev": [
"us3r-u3-dev"
+ ],
+ "u3-pwa": [
+ "us3r-u3-pwa"
]
}
}
diff --git a/apps/u3/.vscode/settings.json b/apps/u3/.vscode/settings.json
index c6f336cf..6fdb18f0 100644
--- a/apps/u3/.vscode/settings.json
+++ b/apps/u3/.vscode/settings.json
@@ -21,6 +21,7 @@
"unfollowed",
"unpinup",
"upsert",
+ "Upvote",
"viem",
"Warpcast",
"Whatsnew",
diff --git a/apps/u3/firebase.json b/apps/u3/firebase.json
index 1fb7bb29..8ed4d836 100644
--- a/apps/u3/firebase.json
+++ b/apps/u3/firebase.json
@@ -29,6 +29,21 @@
"destination": "/index.html"
}
]
+ },
+ {
+ "target": "u3-pwa",
+ "public": "build",
+ "ignore": [
+ "firebase.json",
+ "**/.*",
+ "**/node_modules/**"
+ ],
+ "rewrites": [
+ {
+ "source": "**",
+ "destination": "/index.html"
+ }
+ ]
}
]
}
diff --git a/apps/u3/package.json b/apps/u3/package.json
index e9f389b0..761378c0 100644
--- a/apps/u3/package.json
+++ b/apps/u3/package.json
@@ -20,11 +20,13 @@
"@noble/ed25519": "^2.0.0",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
+ "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-slot": "^1.0.2",
+ "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@rainbow-me/rainbowkit": "^1.3.1",
"@react-spring/web": "^9.6.1",
@@ -122,7 +124,7 @@
},
"scripts": {
"start": "craco start",
- "build": "craco build",
+ "build": "craco build NODE_OPTIONS=--max-old-space-size=4096",
"build:report": "craco build --report",
"test": "craco test",
"eject": "react-scripts eject",
diff --git a/apps/u3/public/service-worker-dev.js b/apps/u3/public/service-worker-dev.js
new file mode 100644
index 00000000..1ffc7a76
--- /dev/null
+++ b/apps/u3/public/service-worker-dev.js
@@ -0,0 +1,12 @@
+/* eslint-disable */
+
+// listen for push event
+self.addEventListener('push', (event) => {
+ let { title, body, icon } = event.data.json();
+ if (!title || title === 'undefined') title = 'U3 - Your Web3 Gateway';
+ if (!body) return;
+ self.registration.showNotification(title, {
+ body,
+ icon: icon || `logo192.png`,
+ });
+});
diff --git a/apps/u3/public/service-worker.js b/apps/u3/public/service-worker.js
new file mode 100644
index 00000000..1ffc7a76
--- /dev/null
+++ b/apps/u3/public/service-worker.js
@@ -0,0 +1,12 @@
+/* eslint-disable */
+
+// listen for push event
+self.addEventListener('push', (event) => {
+ let { title, body, icon } = event.data.json();
+ if (!title || title === 'undefined') title = 'U3 - Your Web3 Gateway';
+ if (!body) return;
+ self.registration.showNotification(title, {
+ body,
+ icon: icon || `logo192.png`,
+ });
+});
diff --git a/apps/u3/src/components/common/icons/DegenTip.tsx b/apps/u3/src/components/common/icons/DegenTip.tsx
new file mode 100644
index 00000000..1994409f
--- /dev/null
+++ b/apps/u3/src/components/common/icons/DegenTip.tsx
@@ -0,0 +1,25 @@
+export default function DegenTip({ className }: { className: string }) {
+ return (
+
+ );
+}
diff --git a/apps/u3/src/components/notification/NotificationModal.tsx b/apps/u3/src/components/notification/NotificationModal.tsx
index f88877f9..2fda6759 100644
--- a/apps/u3/src/components/notification/NotificationModal.tsx
+++ b/apps/u3/src/components/notification/NotificationModal.tsx
@@ -17,6 +17,7 @@ import LensIcon from '../common/icons/LensIcon';
import FarcasterIcon from '../common/icons/FarcasterIcon';
import Loading from '../common/loading/Loading';
import { useNav } from '../../contexts/NavCtx';
+// import { NotificationSettingsGroup } from './PushNotificationsToogleBtn';
export default function NotificationModal() {
const { openNotificationModal, setOpenNotificationModal } = useNav();
@@ -27,6 +28,9 @@ export default function NotificationModal() {
Notifications
+ {/*
+
+
*/}
setOpenNotificationModal(false)} />
{notifications && notifications.length > 0 && (
diff --git a/apps/u3/src/components/notification/PushNotificationsToogleBtn.tsx b/apps/u3/src/components/notification/PushNotificationsToogleBtn.tsx
new file mode 100644
index 00000000..2fae5858
--- /dev/null
+++ b/apps/u3/src/components/notification/PushNotificationsToogleBtn.tsx
@@ -0,0 +1,182 @@
+import { useEffect, useState } from 'react';
+import { toast } from 'react-toastify';
+import WebPushService from '@/utils/pwa/WebPushService';
+import { sendNotification } from '@/utils/pwa/notification';
+import {
+ NotificationSetting,
+ NotificationSettingType,
+} from '@/services/notification/types/notification-settings';
+import useLogin from '@/hooks/shared/useLogin';
+import {
+ addNotificationSetting,
+ fethNotificationSettings,
+ updateNotificationSetting,
+} from '@/services/notification/api/notification-settings';
+import { ApiRespCode } from '@/services/shared/types';
+import { useFarcasterCtx } from '@/contexts/social/FarcasterCtx';
+import Switch from '../common/switch/Switch';
+import ColorButton from '../common/button/ColorButton';
+
+export function NotificationSettingsGroup() {
+ const {
+ currFid,
+ isConnected: isLoginFarcaster,
+ currUserInfo: farcasterUserInfo,
+ openFarcasterQR,
+ } = useFarcasterCtx();
+ const { isLogin } = useLogin();
+ const [settings, setSettings] = useState([]);
+ const [loadingTypes, setLoadingTypes] = useState(
+ []
+ );
+ const [settingsLoading, setSettingsLoading] = useState(true);
+ useEffect(() => {
+ if (isLogin) {
+ setSettingsLoading(true);
+ fethNotificationSettings()
+ .then((res) => {
+ setSettings(res?.data?.data || []);
+ })
+ .catch((err) => {
+ console.log(err);
+ setSettings([]);
+ })
+ .finally(() => {
+ setSettingsLoading(false);
+ });
+ } else {
+ setSettings([]);
+ setSettingsLoading(false);
+ }
+ }, [isLogin]);
+
+ const upsertSetting = async (setting: Partial) => {
+ if (!isLogin) return;
+ const index = settings.findIndex((s) => s.type === setting.type);
+
+ try {
+ setLoadingTypes((prev) => {
+ if (prev.includes(setting.type)) return prev;
+ return [...prev, setting.type];
+ });
+ if (index >= 0 && settings[index]?.id) {
+ // update
+ const res = await updateNotificationSetting(settings[index].id, {
+ ...settings[index],
+ ...setting,
+ });
+ if (res.data.code === ApiRespCode.SUCCESS) {
+ setSettings((prev) => {
+ return [
+ ...prev.slice(0, index),
+ {
+ ...prev[index],
+ ...setting,
+ },
+ ...prev.slice(index + 1),
+ ] as NotificationSetting[];
+ });
+ }
+ } else {
+ // add
+ const res = await addNotificationSetting({
+ type: setting.type,
+ ...setting,
+ });
+ if (res.data.code === ApiRespCode.SUCCESS) {
+ setSettings((prev) => {
+ return [...prev, res.data.data] as NotificationSetting[];
+ });
+ }
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ throw error;
+ } finally {
+ setLoadingTypes((prev) => {
+ if (prev.includes(setting.type)) {
+ return prev.filter((t) => t !== setting.type);
+ }
+ return prev;
+ });
+ }
+ };
+
+ const webpushSubscribed = settings.some(
+ (setting) =>
+ setting.type === NotificationSettingType.WEB_PUSH &&
+ setting?.enable === true &&
+ !!setting?.subscription
+ );
+
+ const webpushLoading = loadingTypes.includes(
+ NotificationSettingType.WEB_PUSH
+ );
+ const webpushDisabled = settingsLoading || webpushLoading;
+
+ const handlePushChange = async (checked: boolean) => {
+ try {
+ if (!checked) {
+ const payload = await WebPushService.unsubscribe();
+ await upsertSetting({
+ type: NotificationSettingType.WEB_PUSH,
+ fid: currFid ? String(currFid) : undefined,
+ enable: false,
+ subscription: payload ? JSON.stringify(payload) : undefined,
+ });
+ } else {
+ if (!WebPushService.hasPermission()) {
+ await WebPushService.requestPermission();
+ }
+ let subscription = await WebPushService.getSubscription();
+ if (!subscription) {
+ subscription = await WebPushService.subscribe();
+ }
+
+ await upsertSetting({
+ type: NotificationSettingType.WEB_PUSH,
+ fid: currFid ? String(currFid) : undefined,
+ enable: true,
+ subscription: JSON.stringify(subscription),
+ });
+ sendNotification(`Subscribed to notifications`);
+ }
+ } catch (error) {
+ toast.error(error.message);
+ console.error(error);
+ }
+ };
+
+ return (
+ //
+ //
Web Push
+ <>
+
+ {(() => {
+ if (webpushLoading) {
+ if (webpushSubscribed) {
+ return 'Unsubscribing...';
+ }
+ return 'Subscribing...';
+ }
+ return 'Subscribe Notifications';
+ })()}
+
+ {/* {!(isLoginFarcaster && farcasterUserInfo) && (
+
openFarcasterQR()}
+ >
+ Login Farcaster
+
+ )} */}
+ >
+ //
+ );
+}
diff --git a/apps/u3/src/components/poster/gallery/GalleryItem.tsx b/apps/u3/src/components/poster/gallery/GalleryItem.tsx
index 4117a1e4..3974e74e 100644
--- a/apps/u3/src/components/poster/gallery/GalleryItem.tsx
+++ b/apps/u3/src/components/poster/gallery/GalleryItem.tsx
@@ -44,7 +44,10 @@ export default function GalleryItem({
} = useCasterTokenInfoWithTokenId({
tokenId: Number(tokenId),
});
- const saleStautsWithPoster = getSaleStatus(data.saleStart, data.saleEnd);
+ const saleStautsWithPoster = getSaleStatus(
+ Number(data.saleStart),
+ Number(data.saleEnd)
+ );
const saleStatus =
data?.saleStart && data?.saleEnd
? saleStautsWithPoster
diff --git a/apps/u3/src/components/social/Embed.tsx b/apps/u3/src/components/social/Embed.tsx
index e8077447..0ba78e93 100644
--- a/apps/u3/src/components/social/Embed.tsx
+++ b/apps/u3/src/components/social/Embed.tsx
@@ -11,6 +11,7 @@ import {
import dayjs from 'dayjs';
import { toHex } from 'viem';
import { toast } from 'react-toastify';
+import { Cross2Icon, CaretLeftIcon } from '@radix-ui/react-icons';
import {
FarCast,
FarCastEmbedMeta,
@@ -21,6 +22,7 @@ import {
getFarcasterEmbedCast,
getFarcasterEmbedMetadata,
postFrameActionApi,
+ postFrameActionRedirectApi,
} from '../../services/social/api/farcaster';
import ModalImg from './ModalImg';
import U3ZoraMinter from './farcaster/U3ZoraMinter';
@@ -29,6 +31,8 @@ import ColorButton from '../common/button/ColorButton';
import { useFarcasterCtx } from '@/contexts/social/FarcasterCtx';
import { FARCASTER_NETWORK } from '@/constants/farcaster';
import useFarcasterCastId from '@/hooks/social/farcaster/useFarcasterCastId';
+import ModalContainerFixed from '../common/modal/ModalContainerFixed';
+import { cn } from '@/lib/utils';
const ValidFrameButtonValue = [
[0, 0, 0, 0].join(''),
@@ -172,6 +176,7 @@ function EmbedCastFrame({
const castId: CastId = useFarcasterCastId({ cast });
const { encryptedSigner, isConnected, currFid } = useFarcasterCtx();
+ const [frameRedirect, setFrameRedirect] = useState('');
const [frameData, setFrameData] = useState(data);
const postFrameAction = useCallback(
@@ -186,10 +191,9 @@ function EmbedCastFrame({
toast.error('no encryptedSigner');
return;
}
- const url = data.fcFramePostUrl || data.url;
const trustedDataResult = await makeFrameAction(
{
- url: Buffer.from(url),
+ url: Buffer.from(data.url),
buttonIndex: index,
castId,
},
@@ -202,10 +206,11 @@ function EmbedCastFrame({
if (trustedDataResult.isErr()) {
throw new Error(trustedDataResult.error.message);
}
+
const trustedDataValue = trustedDataResult.value;
const untrustedData = {
fid: currFid,
- url,
+ url: frameData.fcFramePostUrl || frameData.url,
messageHash: toHex(trustedDataValue.hash),
network: FARCASTER_NETWORK,
buttonIndex: index,
@@ -220,49 +225,131 @@ function EmbedCastFrame({
).toString('hex'),
};
const postData = {
+ actionUrl: frameData.fcFramePostUrl || frameData.url,
untrustedData,
trustedData,
};
- const resp = await postFrameActionApi(postData);
- if (resp.data.code !== 0) {
- toast.error(resp.data.msg);
- return;
+ const buttonAction = frameData[`fcFrameButton${index}Action`] || 'post';
+ console.log('buttonAction', buttonAction);
+ if (buttonAction === 'post') {
+ const resp = await postFrameActionApi(postData);
+ if (resp.data.code !== 0) {
+ toast.error(resp.data.msg);
+ return;
+ }
+ setFrameData(resp.data.data?.metadata);
+ } else if (buttonAction === 'post_redirect') {
+ const resp = await postFrameActionRedirectApi(postData);
+ if (resp.data.code !== 0) {
+ toast.error(resp.data.msg);
+ return;
+ }
+ setFrameRedirect(resp.data.data?.redirectUrl || '');
}
- setFrameData(resp.data.data?.metadata);
},
[frameData, currFid, encryptedSigner, castId]
);
return (
-
-
-
-
- {isConnected && (
-
- {[
- frameData.fcFrameButton1,
- frameData.fcFrameButton2,
- frameData.fcFrameButton3,
- frameData.fcFrameButton4,
- ].map((item, idx) => {
- if (!item) return null;
- return (
-
{
- e.stopPropagation();
- postFrameAction(idx + 1);
- }}
- >
- {item}
-
- );
- })}
+ <>
+
+
+
+ {isConnected && (
+
+ {[
+ frameData.fcFrameButton1,
+ frameData.fcFrameButton2,
+ frameData.fcFrameButton3,
+ frameData.fcFrameButton4,
+ ].map((item, idx) => {
+ if (!item) return null;
+ return (
+ {
+ e.stopPropagation();
+ postFrameAction(idx + 1);
+ }}
+ >
+ {item}
+
+ );
+ })}
+
+ )}
+
+ {frameRedirect && (
+
{
+ setFrameRedirect('');
+ }}
+ />
)}
-
+ >
+ );
+}
+
+function EmbedCastFrameRedirect({
+ url,
+ resetUrl,
+}: {
+ url: string;
+ resetUrl: () => void;
+}) {
+ return (
+
{
+ resetUrl();
+ }}
+ className="w-full md:w-[420px]"
+ >
+ {
+ e.stopPropagation();
+ }}
+ >
+
+
⚠️ Leaving u3
+
+
+
+ You are about to leave u3, please connect your wallet carefully and
+ take care of your funds.
+
+
+
+
+
+
+
);
}
diff --git a/apps/u3/src/components/social/farcaster/FCastTips.tsx b/apps/u3/src/components/social/farcaster/FCastTips.tsx
index fdb53886..8fcfeaca 100644
--- a/apps/u3/src/components/social/farcaster/FCastTips.tsx
+++ b/apps/u3/src/components/social/farcaster/FCastTips.tsx
@@ -1,6 +1,8 @@
-import { useCallback, useState } from 'react';
+/* eslint-disable no-underscore-dangle */
+import { useCallback, useEffect, useState } from 'react';
import { Cross2Icon } from '@radix-ui/react-icons';
import { useAccount, useBalance, useNetwork } from 'wagmi';
+import { makeCastAdd } from '@farcaster/hub-web';
import { useConnectModal } from '@rainbow-me/rainbowkit';
import {
prepareWriteContract,
@@ -16,11 +18,20 @@ import { UserData } from '@/utils/social/farcaster/user-data';
import ModalContainer from '@/components/common/modal/ModalContainer';
import { cn } from '@/lib/utils';
import useLogin from '@/hooks/shared/useLogin';
-import { getUserinfoWithFid } from '@/services/social/api/farcaster';
+import {
+ getUserDegenTipAllowance,
+ getUserinfoWithFid,
+ notifyTipApi,
+} from '@/services/social/api/farcaster';
import { shortPubKey } from '@/utils/shared/shortPubKey';
import Loading from '@/components/common/loading/Loading';
import { DegenABI, DegenAddress } from '@/services/social/abi/degen/contract';
import { FarCast } from '@/services/social/types';
+import DegenTip from '@/components/common/icons/DegenTip';
+import { useFarcasterCtx } from '@/contexts/social/FarcasterCtx';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { FARCASTER_NETWORK, FARCASTER_WEB_CLIENT } from '@/constants/farcaster';
+import { Checkbox } from '@/components/ui/checkbox';
export default function FCastTips({
userData,
@@ -37,18 +48,63 @@ export default function FCastTips({
address: '',
fname: '',
});
+ const { currFid, encryptedSigner } = useFarcasterCtx();
+ const [allowance, setAllowance] = useState
('0');
const [loading, setLoading] = useState(false);
+
const loadUserinfo = useCallback(async () => {
try {
setLoading(true);
+ const { data: allowanceData } = await getUserDegenTipAllowance(address);
const { data } = await getUserinfoWithFid(userData.fid);
setUserInfo(data.data);
+ setAllowance(allowanceData.data?.[0]?.tip_allowance || '0');
+ setReplyTipAllowance(allowanceData.data?.[0]?.tip_allowance || '0');
+ setReplyTipAmountTotal('0');
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
- }, [userData]);
+ }, [userData, address]);
+
+ const directReply = useCallback(async () => {
+ const allowanceValue = getReplyTipAmount();
+ try {
+ const castToReply = (
+ await makeCastAdd(
+ {
+ text: `${allowanceValue} $DEGEN`,
+ embeds: [],
+ embedsDeprecated: [],
+ mentions: [],
+ mentionsPositions: [],
+ parentCastId: {
+ hash: Buffer.from(cast.hash.data),
+ fid: Number(cast.fid),
+ },
+ },
+ { fid: currFid, network: FARCASTER_NETWORK },
+ encryptedSigner
+ )
+ )._unsafeUnwrap();
+ const r = await FARCASTER_WEB_CLIENT.submitMessage(castToReply);
+ if (r.isErr()) {
+ throw new Error(r.error.message);
+ }
+ setReplyTipAmount(allowanceValue);
+ setReplyTipTimes(Number(getReplyTipTimes()) - 1);
+ setReplyTipAmountTotal(
+ (
+ Number(getReplyTipAmountTotal()) + Number(getReplyTipAmount())
+ ).toString()
+ );
+ toast.success('allowance tip posted');
+ } catch (error) {
+ console.error(error);
+ toast.success('allowance tip failed');
+ }
+ }, [currFid, encryptedSigner, cast]);
return (
<>
@@ -56,6 +112,10 @@ export default function FCastTips({
className="flex items-center gap-2 font-[12px] cursor-pointer"
onClick={(e) => {
e.stopPropagation();
+ const replyDirect =
+ getUseReplyTipDefault() &&
+ Number(getReplyTipTimes()) > 0 &&
+ Number(getReplyTipAmountTotal()) < Number(getReplyTipAllowance());
if (!isLoginU3) {
loginU3();
return;
@@ -64,11 +124,18 @@ export default function FCastTips({
openConnectModal();
return;
}
+
+ if (replyDirect) {
+ directReply();
+ return;
+ }
+
loadUserinfo();
setOpenModal(true);
}}
>
- 🎁 Tips
+
+ Tips
{openModal && (
)}
>
@@ -89,12 +158,16 @@ function TipsModal({
loading,
userinfo,
userData,
+ cast,
+ allowance,
}: {
open: boolean;
setOpen: (open: boolean) => void;
loading: boolean;
userData: UserData;
userinfo: { address: string; fname: string };
+ cast: FarCast;
+ allowance: string;
}) {
return (
@@ -136,6 +209,8 @@ function TipsModal({
{
setOpen(false);
}}
@@ -149,12 +224,17 @@ function TipsModal({
function TipTransaction({
fname,
address,
+ cast,
+ allowance,
successCallback,
}: {
fname: string;
address: string;
+ cast: FarCast;
+ allowance: string;
successCallback?: () => void;
}) {
+ const { currFid, encryptedSigner } = useFarcasterCtx();
const tipsCount = [69, 420, 42069];
const { address: accountAddr } = useAccount();
const result = useBalance({
@@ -165,7 +245,11 @@ function TipTransaction({
});
const network = useNetwork();
const [tipAmount, setTipAmount] = useState(tipsCount[1]);
+ const [allowanceValue, setAllowanceValue] = useState('');
const [transactionHash, setTransactionHash] = useState('');
+ const [tab, setTab] = useState('TabReply');
+ const [count, setCount] = useState(0);
+
const tipAction = useCallback(async () => {
const left = result?.data?.formatted?.toString() || '0';
if (Number(left) < tipAmount) {
@@ -188,9 +272,17 @@ function TipTransaction({
hash: degenTxHash.hash,
chainId: base.id,
});
+ const castHash = Buffer.from(cast.hash.data).toString('hex');
console.log('degenTxReceipt', degenTxReceipt);
if (degenTxReceipt.status === 'success') {
setTransactionHash(degenTxHash.hash);
+ // notify
+ await notifyTipApi({
+ fromFid: currFid,
+ amount: tipAmount,
+ txHash: degenTxHash.hash,
+ castHash,
+ });
toast.success('tip success');
successCallback?.();
} else {
@@ -200,51 +292,241 @@ function TipTransaction({
} catch (e) {
toast.error(e.message.split('\n')[0]);
}
- }, [address, tipAmount, result]);
+ }, [address, tipAmount, result, cast]);
+
+ const allowanceAction = useCallback(async () => {
+ try {
+ const castToReply = (
+ await makeCastAdd(
+ {
+ text: `${allowanceValue} $DEGEN`,
+ embeds: [],
+ embedsDeprecated: [],
+ mentions: [],
+ mentionsPositions: [],
+ parentCastId: {
+ hash: Buffer.from(cast.hash.data),
+ fid: Number(cast.fid),
+ },
+ // parentUrl,
+ },
+ { fid: currFid, network: FARCASTER_NETWORK },
+ encryptedSigner
+ )
+ )._unsafeUnwrap();
+ const r = await FARCASTER_WEB_CLIENT.submitMessage(castToReply);
+ if (r.isErr()) {
+ throw new Error(r.error.message);
+ }
+ setReplyTipAmount(allowanceValue);
+ setReplyTipAmountTotal(allowanceValue);
+ setReplyTipTimes(5);
+ toast.success('allowance tip posted');
+ successCallback?.();
+ } catch (error) {
+ console.error(error);
+ toast.success('allowance tip failed');
+ }
+ }, [allowanceValue, currFid, encryptedSigner]);
+
+ useEffect(() => {
+ if (Number(allowance) > 0) {
+ setTab('TabReply');
+ }
+ }, [allowance]);
+
+ const useAllowance = getUseReplyTipDefault();
+
+ const allowanceNum = Number.isNaN(Number(allowance)) ? 0 : Number(allowance);
return (
-
- {/*
$Degen: {result?.data?.formatted?.toString() || '0'}
*/}
-
- {tipsCount.map((item) => {
- return (
-
{
+ if (v === 'TabTransaction') {
+ localStorage.setItem('tipTab', 'TabTransaction');
+ }
+ setTab(v);
+ }}
+ className="h-60"
+ >
+
+
+ Allowance
+
+
+ Token
+
+
+
+
+
+ {tipsCount.map((item) => {
+ const isAllowance = allowanceNum >= item;
+ return (
+
{
+ if (isAllowance) setAllowanceValue(`${item}`);
+ }}
+ >
+ ${item}
+
+ );
+ })}
+
+
+
or
+
+ {
+ setAllowanceValue(e.target.value);
+ }}
+ />
+ $DEGEN
+
+
+
+
+
+
+
{
+ if (v) {
+ setUseReplyTipDefault();
+ } else {
+ setUseReplyTipDefault('false');
+ }
+ setCount(count + 1);
+ }}
+ />
+
+ Use as default for next 5 tips
+
+
+
+
+
+
+
+ {tipsCount.map((item) => {
+ return (
+
{
+ setTipAmount(item);
+ }}
+ >
+ ${item}
+
+ );
+ })}
+
+
+
or
+
+ {
+ setTipAmount(Number(e.target.value));
+ }}
+ />
+ $DEGEN
- );
- })}
-
-
- or
- {
- setTipAmount(Number(e.target.value));
- }}
- />
- $DEGEN
-
-
-
- to @{fname} (0x{shortPubKey(address, { len: 4 })})
-
-
+
+
+
+
+
+
+ to @{fname} (0x{shortPubKey(address, { len: 4 })})
+
+
+
+
+
);
}
+
+function setReplyTipAllowance(allowance: string) {
+ localStorage.setItem('tipAllowance', allowance);
+}
+
+function getReplyTipAllowance() {
+ return localStorage.getItem('tipAllowance') || '0';
+}
+
+function setReplyTipAmount(num: string) {
+ localStorage.setItem('tipReplyAmount', num);
+}
+
+function setReplyTipAmountTotal(num: string) {
+ localStorage.setItem('tipReplyAmountTotal', num);
+}
+
+function getReplyTipAmountTotal() {
+ return localStorage.getItem('tipReplyAmountTotal') || '0';
+}
+
+function getReplyTipAmount() {
+ return localStorage.getItem('tipReplyAmount') || '0';
+}
+
+function setReplyTipTimes(times: number) {
+ localStorage.setItem('tipReplyTimes', times.toString());
+}
+
+function getReplyTipTimes() {
+ return localStorage.getItem('tipReplyTimes') || '0';
+}
+
+function setUseReplyTipDefault(value: string = 'true') {
+ localStorage.setItem('useReplyTipDefault', value);
+}
+
+function getUseReplyTipDefault() {
+ return localStorage.getItem('useReplyTipDefault') === 'true';
+}
diff --git a/apps/u3/src/components/ui/checkbox.tsx b/apps/u3/src/components/ui/checkbox.tsx
new file mode 100644
index 00000000..45fdfc22
--- /dev/null
+++ b/apps/u3/src/components/ui/checkbox.tsx
@@ -0,0 +1,32 @@
+/* eslint-disable react/prop-types */
+
+'use client';
+
+import * as React from 'react';
+import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
+import { CheckIcon } from '@radix-ui/react-icons';
+
+import { cn } from '@/lib/utils';
+
+const Checkbox = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+));
+Checkbox.displayName = CheckboxPrimitive.Root.displayName;
+
+export { Checkbox };
diff --git a/apps/u3/src/components/ui/tabs.tsx b/apps/u3/src/components/ui/tabs.tsx
new file mode 100644
index 00000000..24c3dbad
--- /dev/null
+++ b/apps/u3/src/components/ui/tabs.tsx
@@ -0,0 +1,57 @@
+/* eslint-disable react/prop-types */
+
+'use client';
+
+import * as React from 'react';
+import * as TabsPrimitive from '@radix-ui/react-tabs';
+
+import { cn } from '@/lib/utils';
+
+const Tabs = TabsPrimitive.Root;
+
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsContent.displayName = TabsPrimitive.Content.displayName;
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/apps/u3/src/container/Notification.tsx b/apps/u3/src/container/Notification.tsx
index 7ef232b0..717a6469 100644
--- a/apps/u3/src/container/Notification.tsx
+++ b/apps/u3/src/container/Notification.tsx
@@ -5,6 +5,8 @@ import {
useNotificationStore,
} from '@/contexts/notification/NotificationStoreCtx';
import NotificationList from '@/components/notification/ui/NotificationList';
+import { NotificationSettingsGroup } from '@/components/notification/PushNotificationsToogleBtn';
+// import isInstalledPwa from '@/utils/shared/isInstalledPwa';
export default function Notification() {
const isAuthenticated = useIsAuthenticated();
@@ -26,13 +28,22 @@ function NotificationPage() {
const { notifications, loading, hasMore, loadMore, farcasterUserData } =
useNotificationStore();
+ // const isPwa = isInstalledPwa();
return (
-
+ <>
+ {/* {isPwa && ( */}
+
+
+
+ {/* )} */}
+
+
+ >
);
}
diff --git a/apps/u3/src/hooks/poster/useCasterTokenInfoWithTokenId.ts b/apps/u3/src/hooks/poster/useCasterTokenInfoWithTokenId.ts
index 16aaefc7..cb442356 100644
--- a/apps/u3/src/hooks/poster/useCasterTokenInfoWithTokenId.ts
+++ b/apps/u3/src/hooks/poster/useCasterTokenInfoWithTokenId.ts
@@ -95,7 +95,7 @@ export default function useCasterTokenInfoWithTokenId({
const { saleStart, saleEnd } = tokenInfo;
const saleStatus = useMemo(
- () => getSaleStatus(String(saleStart), String(saleEnd)),
+ () => getSaleStatus(Number(saleStart), Number(saleEnd)),
[saleStart, saleEnd]
);
diff --git a/apps/u3/src/index.tsx b/apps/u3/src/index.tsx
index a8d0d1be..d65b35f8 100644
--- a/apps/u3/src/index.tsx
+++ b/apps/u3/src/index.tsx
@@ -32,7 +32,10 @@ root.render(
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://cra.link/PWA
-serviceWorkerRegistration.unregister();
+// serviceWorkerRegistration.unregister();
+serviceWorkerRegistration.register({
+ bypassNodeEnvProduction: true,
+});
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
diff --git a/apps/u3/src/service-worker.ts b/apps/u3/src/service-worker.ts
index 9060969d..60589072 100644
--- a/apps/u3/src/service-worker.ts
+++ b/apps/u3/src/service-worker.ts
@@ -81,3 +81,25 @@ self.addEventListener('message', (event) => {
});
// Any other custom service worker logic can go here.
+// listen for push event
+self.addEventListener('push', (event) => {
+ const { title, body, icon } = event.data.json();
+ if (!body) return;
+ const defaultTitle = 'U3 - Your Web3 Gateway';
+ self.registration.showNotification(title || defaultTitle, {
+ body,
+ icon: icon || `${process.env.PUBLIC_URL}/logo192.png`,
+ });
+});
+
+// const CALL_BACK_INTERVAL = 1000 * 60 * 60;
+// self.addEventListener('activate', () => {
+// console.log('activate interval subscribe......');
+// setInterval(() => {
+// console.log('fire notification!');
+// self.registration.showNotification('U3', {
+// body: 'Checkout new content on Farcaster',
+// icon: `${process.env.PUBLIC_URL}/logo192.png`,
+// });
+// }, CALL_BACK_INTERVAL);
+// });
diff --git a/apps/u3/src/serviceWorkerRegistration.ts b/apps/u3/src/serviceWorkerRegistration.ts
index 61915edc..6615b877 100644
--- a/apps/u3/src/serviceWorkerRegistration.ts
+++ b/apps/u3/src/serviceWorkerRegistration.ts
@@ -23,10 +23,12 @@ const isLocalhost = Boolean(
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
+ bypassNodeEnvProduction?: boolean;
};
export function register(config?: Config) {
- if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ if (nodeEnvProductionCheck(config) && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
@@ -37,7 +39,8 @@ export function register(config?: Config) {
}
window.addEventListener('load', () => {
- const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+ // const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+ const swUrl = getServiceWorkerUrl();
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
@@ -144,3 +147,17 @@ export function unregister() {
});
}
}
+
+function nodeEnvProductionCheck(config) {
+ if (config && config.bypassNodeEnvProduction) {
+ return config.bypassNodeEnvProduction;
+ }
+ return process.env.NODE_ENV === 'production';
+}
+
+function getServiceWorkerUrl() {
+ if (process.env.NODE_ENV === 'production') {
+ return `${process.env.PUBLIC_URL}/service-worker.js`;
+ }
+ return `/service-worker-dev.js`;
+}
diff --git a/apps/u3/src/services/notification/api/notification-settings.ts b/apps/u3/src/services/notification/api/notification-settings.ts
new file mode 100644
index 00000000..3b29a1f9
--- /dev/null
+++ b/apps/u3/src/services/notification/api/notification-settings.ts
@@ -0,0 +1,65 @@
+import request, { RequestPromise } from '../../shared/api/request';
+import { ApiResp } from '@/services/shared/types';
+import {
+ NotificationSetting,
+ NotificationSettingType,
+} from '../types/notification-settings';
+
+export function fethNotificationSettings(): RequestPromise<
+ ApiResp
+> {
+ return request({
+ url: `/notifications/settings`,
+ method: 'get',
+ headers: {
+ needToken: true,
+ },
+ });
+}
+
+export function addNotificationSetting(params: {
+ type: NotificationSettingType;
+ fid?: string;
+ subscription?: string;
+}): RequestPromise> {
+ return request({
+ url: `/notifications/settings`,
+ method: 'post',
+ headers: {
+ needToken: true,
+ },
+ data: params,
+ });
+}
+
+export function updateNotificationSetting(
+ id: number,
+ data: {
+ id: number;
+ type: NotificationSettingType;
+ enabled?: boolean;
+ fid?: string;
+ subscription?: string;
+ }
+): RequestPromise> {
+ return request({
+ url: `/notifications/settings/${id}`,
+ method: 'post',
+ headers: {
+ needToken: true,
+ },
+ data,
+ });
+}
+
+export function deleteNotificationSetting(
+ id: number
+): RequestPromise> {
+ return request({
+ url: `/notifications/setting/${id}`,
+ method: 'delete',
+ headers: {
+ needToken: true,
+ },
+ });
+}
diff --git a/apps/u3/src/services/notification/types/notification-settings.ts b/apps/u3/src/services/notification/types/notification-settings.ts
new file mode 100644
index 00000000..9ae32842
--- /dev/null
+++ b/apps/u3/src/services/notification/types/notification-settings.ts
@@ -0,0 +1,12 @@
+export enum NotificationSettingType {
+ WEB_PUSH = 'WEB_PUSH',
+}
+export type NotificationSetting = {
+ id: number;
+ type: NotificationSettingType;
+ enable?: boolean;
+ fid?: string;
+ subscription?: string;
+ createdAt?: Date;
+ updatedAt?: Date;
+};
diff --git a/apps/u3/src/services/social/api/farcaster.ts b/apps/u3/src/services/social/api/farcaster.ts
index f954487a..2070a410 100644
--- a/apps/u3/src/services/social/api/farcaster.ts
+++ b/apps/u3/src/services/social/api/farcaster.ts
@@ -515,6 +515,13 @@ export function getUserinfoWithFid(fid: string) {
});
}
+export function getUserDegenTipAllowance(addr: string) {
+ return axios({
+ url: `${REACT_APP_API_SOCIAL_URL}/3r-farcaster/degen-tip/allowance?address=${addr}`,
+ method: 'get',
+ });
+}
+
export function postFrameActionApi(data: any) {
return axios({
url: `${REACT_APP_API_SOCIAL_URL}/3r-farcaster/frame-action/proxy`,
@@ -522,3 +529,24 @@ export function postFrameActionApi(data: any) {
data,
});
}
+
+export function postFrameActionRedirectApi(data: any) {
+ return axios({
+ url: `${REACT_APP_API_SOCIAL_URL}/3r-farcaster/frame-action-redirect/proxy`,
+ method: 'post',
+ data,
+ });
+}
+
+export function notifyTipApi(data: {
+ txHash: string;
+ amount: number;
+ fromFid: number;
+ castHash: string;
+}) {
+ return axios({
+ url: `${REACT_APP_API_SOCIAL_URL}/3r-bot/tip/notify`,
+ method: 'post',
+ data,
+ });
+}
diff --git a/apps/u3/src/utils/pwa/WebPushService.ts b/apps/u3/src/utils/pwa/WebPushService.ts
new file mode 100644
index 00000000..84fc5d35
--- /dev/null
+++ b/apps/u3/src/utils/pwa/WebPushService.ts
@@ -0,0 +1,47 @@
+class WebPushService {
+ static hasPermission() {
+ return Notification.permission === 'granted';
+ }
+
+ static async requestPermission() {
+ return Notification.requestPermission();
+ }
+
+ static async getSubscription() {
+ return navigator.serviceWorker.ready.then(async (registration) => {
+ return registration.pushManager.getSubscription();
+ });
+ }
+
+ static async subscribe() {
+ const applicationServerKey = process.env.REACT_APP_VAPID_PUBLIC_KEY;
+ if (!applicationServerKey) {
+ throw new Error('VAPID public key not found');
+ }
+ const registration = await navigator.serviceWorker.ready;
+ if (!registration) {
+ throw new Error('Service Worker not ready');
+ }
+ if (!('pushManager' in registration)) {
+ throw new Error("PushManager isn't available");
+ }
+ if (!('subscribe' in registration.pushManager)) {
+ throw new Error('subscribe method not available');
+ }
+ const subscription = await registration.pushManager.subscribe({
+ userVisibleOnly: true,
+ applicationServerKey,
+ });
+ return subscription;
+ }
+
+ static async unsubscribe() {
+ const subscription = await this.getSubscription();
+ if (subscription) {
+ await subscription.unsubscribe();
+ }
+ return subscription;
+ }
+}
+
+export default WebPushService;
diff --git a/apps/u3/src/utils/pwa/notification.ts b/apps/u3/src/utils/pwa/notification.ts
new file mode 100644
index 00000000..2d98ebf1
--- /dev/null
+++ b/apps/u3/src/utils/pwa/notification.ts
@@ -0,0 +1,41 @@
+export const requestPermission = async () => {
+ if (!('Notification' in window)) {
+ throw new Error('Notification not supported');
+ }
+ const permission = await window.Notification.requestPermission();
+ if (permission !== 'granted') {
+ throw new Error('Permission not granted for Notification');
+ }
+};
+
+export const sendNotification = async (body) => {
+ console.log('sendNotification', body);
+ if (Notification.permission === 'granted') {
+ showNotification(body);
+ } else if (Notification.permission !== 'denied') {
+ const permission = await Notification.requestPermission();
+
+ if (permission === 'granted') {
+ showNotification(body);
+ }
+ }
+};
+
+const showNotification = async (body) => {
+ const registration = await navigator.serviceWorker.getRegistration();
+ const title = 'U3 - Your Web3 Gateway';
+
+ const payload = {
+ body,
+ icon: `${process.env.PUBLIC_URL}/logo192.png`,
+ };
+
+ if ('showNotification' in registration) {
+ console.log('showNotification in registration', title, payload);
+ registration.showNotification(title, payload);
+ } else {
+ console.log('showNotification NOT in registration', title, payload);
+ // eslint-disable-next-line no-new
+ new Notification(title, payload);
+ }
+};
diff --git a/apps/u3/src/utils/shared/time.ts b/apps/u3/src/utils/shared/time.ts
index 428714a4..2131dfc5 100644
--- a/apps/u3/src/utils/shared/time.ts
+++ b/apps/u3/src/utils/shared/time.ts
@@ -24,10 +24,10 @@ export const defaultFormatDate = (date: string | number | Date) =>
export const defaultFormatFromNow = (date: string | number | Date) =>
dayjs(date).fromNow();
-export const isSecondTimestamp = (timestamp) => {
- return timestamp.toString().length === 10;
+export const isSecondTimestamp = (timestamp: number) => {
+ return timestamp && timestamp.toString().length === 10;
};
-export const isMillisecondTimestamp = (timestamp) => {
- return timestamp.toString().length === 13;
+export const isMillisecondTimestamp = (timestamp: number) => {
+ return timestamp && timestamp.toString().length === 13;
};
diff --git a/apps/u3/src/utils/shared/zora.ts b/apps/u3/src/utils/shared/zora.ts
index a9c62cea..198e3c35 100644
--- a/apps/u3/src/utils/shared/zora.ts
+++ b/apps/u3/src/utils/shared/zora.ts
@@ -162,15 +162,11 @@ export enum SaleStatus {
InProgress = 1,
Ended = 2,
}
-export const getSaleStatus = (saleStart: string, saleEnd: string) => {
+export const getSaleStatus = (saleStart: number, saleEnd: number) => {
const nowMillisecondTimestamp = Date.now();
const nowSecondTimestamp = Math.floor(nowMillisecondTimestamp / 1000);
- const compareFn = (
- now: string | number,
- start: string | number,
- end: string | number
- ) => {
+ const compareFn = (now: number, start: number, end: number) => {
if (Number(now) < Number(start)) {
return SaleStatus.NotStarted;
}
@@ -179,6 +175,7 @@ export const getSaleStatus = (saleStart: string, saleEnd: string) => {
}
return SaleStatus.InProgress;
};
+
if (isSecondTimestamp(saleStart) && isSecondTimestamp(saleEnd)) {
return compareFn(nowSecondTimestamp, saleStart, saleEnd);
}
@@ -195,11 +192,11 @@ export const getSaleStatus = (saleStart: string, saleEnd: string) => {
return compareFn(nowMillisecondTimestamp, Number(saleStart) * 100, saleEnd);
}
- if (saleStart.length > 13) {
+ if (String(saleStart).length > 13) {
return SaleStatus.NotStarted;
}
- if (saleEnd.length > 13) {
+ if (String(saleEnd).length > 13) {
return SaleStatus.InProgress;
}