@@ -16,7 +18,9 @@ export default function HighScoreDapps({ dapps }: HighScoreDappsProps) {
navigate(`/apps/${item.id}`)}
+ title={`/apps/${item.id}`}
/>
);
})}
diff --git a/apps/u3/src/components/explore/poster/layout/links/LinkCard.tsx b/apps/u3/src/components/poster/layout/links/LinkCard.tsx
similarity index 100%
rename from apps/u3/src/components/explore/poster/layout/links/LinkCard.tsx
rename to apps/u3/src/components/poster/layout/links/LinkCard.tsx
diff --git a/apps/u3/src/components/poster/layout/links/TopLinks.tsx b/apps/u3/src/components/poster/layout/links/TopLinks.tsx
new file mode 100644
index 00000000..112c426a
--- /dev/null
+++ b/apps/u3/src/components/poster/layout/links/TopLinks.tsx
@@ -0,0 +1,36 @@
+import { useNavigate } from 'react-router-dom';
+import { isMobile } from 'react-device-detect';
+import LinkCard, { type LinkCardData } from './LinkCard';
+import { encodeLinkURL } from '@/utils/news/link';
+
+export type TopLinksData = Array;
+export type TopLinksProps = { links: TopLinksData };
+
+const ROUTE_PREFIX = '/b/links';
+export default function TopLinks({ links }: TopLinksProps) {
+ const navigate = useNavigate();
+ return (
+
+
+ Top Links
+
+
+ {links.map((item) => {
+ return (
+
+ isMobile
+ ? window.open(item?.url, '_blank')
+ : navigate(`${ROUTE_PREFIX}/all/${encodeLinkURL(item?.url)}`)
+ }
+ title={`${ROUTE_PREFIX}/all/${encodeLinkURL(item?.url)}`}
+ />
+ );
+ })}
+
+
+ );
+}
diff --git a/apps/u3/src/components/explore/poster/layout/posts/FarcasterPostCard.tsx b/apps/u3/src/components/poster/layout/posts/FarcasterPostCard.tsx
similarity index 93%
rename from apps/u3/src/components/explore/poster/layout/posts/FarcasterPostCard.tsx
rename to apps/u3/src/components/poster/layout/posts/FarcasterPostCard.tsx
index 66403ae4..e6a93f77 100644
--- a/apps/u3/src/components/explore/poster/layout/posts/FarcasterPostCard.tsx
+++ b/apps/u3/src/components/poster/layout/posts/FarcasterPostCard.tsx
@@ -3,8 +3,8 @@ import {
FarCast,
FarCastEmbedMeta,
FarCastEmbedMetaCast,
-} from '../../../../../services/social/types';
-import useFarcasterUserData from '../../../../../hooks/social/farcaster/useFarcasterUserData';
+} from '../../../../services/social/types';
+import useFarcasterUserData from '../../../../hooks/social/farcaster/useFarcasterUserData';
import { PostCard, Top1PostCard, type PostCardData } from './PostCard';
import { getEmbeds, isImg } from '@/utils/social/farcaster/getEmbeds';
import { getFarcasterEmbedMetadata } from '@/services/social/api/farcaster';
diff --git a/apps/u3/src/components/explore/poster/layout/posts/PostCard.tsx b/apps/u3/src/components/poster/layout/posts/PostCard.tsx
similarity index 100%
rename from apps/u3/src/components/explore/poster/layout/posts/PostCard.tsx
rename to apps/u3/src/components/poster/layout/posts/PostCard.tsx
diff --git a/apps/u3/src/components/explore/poster/layout/posts/TopPosts.tsx b/apps/u3/src/components/poster/layout/posts/TopPosts.tsx
similarity index 77%
rename from apps/u3/src/components/explore/poster/layout/posts/TopPosts.tsx
rename to apps/u3/src/components/poster/layout/posts/TopPosts.tsx
index 78b4303d..48e819a0 100644
--- a/apps/u3/src/components/explore/poster/layout/posts/TopPosts.tsx
+++ b/apps/u3/src/components/poster/layout/posts/TopPosts.tsx
@@ -1,4 +1,5 @@
-import { SocialPlatform } from '../../../../../services/social/types';
+import { useNavigate } from 'react-router-dom';
+import { SocialPlatform } from '../../../../services/social/types';
import FarcasterPostCard from './FarcasterPostCard';
export type TopPostsData = Array<{ data: any; platform: SocialPlatform }>;
@@ -7,6 +8,7 @@ export type TopPostsProps = {
farcasterUserData: { [key: string]: { type: number; value: string }[] };
};
export default function TopPosts({ posts, farcasterUserData }: TopPostsProps) {
+ const navigate = useNavigate();
const top1Post = posts[0];
const topPosts = posts.slice(1, 4);
return (
@@ -25,7 +27,9 @@ export default function TopPosts({ posts, farcasterUserData }: TopPostsProps) {
data={data}
farcasterUserData={farcasterUserData}
isFirst
- className="flex-1"
+ className="flex-1 cursor-pointer"
+ onClick={() => navigate(`/social/post-detail/fcast/${id}`)}
+ title={`/social/post-detail/fcast/${id}`}
/>
);
}
@@ -45,7 +49,9 @@ export default function TopPosts({ posts, farcasterUserData }: TopPostsProps) {
key={id}
data={data}
farcasterUserData={farcasterUserData}
- className="pt-[10px]"
+ className="pt-[10px] cursor-pointer"
+ onClick={() => navigate(`/social/post-detail/fcast/${id}`)}
+ title={`/social/post-detail/fcast/${id}`}
/>
);
}
diff --git a/apps/u3/src/components/explore/poster/layout/topics/TopTopics.tsx b/apps/u3/src/components/poster/layout/topics/TopTopics.tsx
similarity index 68%
rename from apps/u3/src/components/explore/poster/layout/topics/TopTopics.tsx
rename to apps/u3/src/components/poster/layout/topics/TopTopics.tsx
index 7398435f..1470e26c 100644
--- a/apps/u3/src/components/explore/poster/layout/topics/TopTopics.tsx
+++ b/apps/u3/src/components/poster/layout/topics/TopTopics.tsx
@@ -1,3 +1,4 @@
+import { useNavigate } from 'react-router-dom';
import TopicCard, { TopicData } from './TopicCard';
export type TopTopicsData = Array;
@@ -5,6 +6,7 @@ export type TopTopicsProps = {
topics: TopTopicsData;
};
export default function TopTopics({ topics }: TopTopicsProps) {
+ const navigate = useNavigate();
return (
@@ -16,7 +18,9 @@ export default function TopTopics({ topics }: TopTopicsProps) {
navigate(`/social/channel/${item.channel_id}`)}
+ title={`/social/channel/${item.channel_id}`}
/>
);
})}
diff --git a/apps/u3/src/components/explore/poster/layout/topics/TopicCard.tsx b/apps/u3/src/components/poster/layout/topics/TopicCard.tsx
similarity index 97%
rename from apps/u3/src/components/explore/poster/layout/topics/TopicCard.tsx
rename to apps/u3/src/components/poster/layout/topics/TopicCard.tsx
index 02ac5e40..d339cfc7 100644
--- a/apps/u3/src/components/explore/poster/layout/topics/TopicCard.tsx
+++ b/apps/u3/src/components/poster/layout/topics/TopicCard.tsx
@@ -2,6 +2,7 @@ import { ComponentPropsWithRef } from 'react';
import { cn } from '@/lib/utils';
export type TopicData = {
+ channel_id: string;
logo: string;
name: string;
postCount: number;
diff --git a/apps/u3/src/components/explore/poster/mint/FirstMintButton.tsx b/apps/u3/src/components/poster/mint/FirstMintButton.tsx
similarity index 100%
rename from apps/u3/src/components/explore/poster/mint/FirstMintButton.tsx
rename to apps/u3/src/components/poster/mint/FirstMintButton.tsx
diff --git a/apps/u3/src/components/explore/poster/mint/FreeMintButton.tsx b/apps/u3/src/components/poster/mint/FreeMintButton.tsx
similarity index 85%
rename from apps/u3/src/components/explore/poster/mint/FreeMintButton.tsx
rename to apps/u3/src/components/poster/mint/FreeMintButton.tsx
index a01ccb70..f23f5b84 100644
--- a/apps/u3/src/components/explore/poster/mint/FreeMintButton.tsx
+++ b/apps/u3/src/components/poster/mint/FreeMintButton.tsx
@@ -6,10 +6,12 @@ import useMint from '@/hooks/poster/useMint';
type FreeMintButtonProps = ComponentPropsWithRef<'button'> & {
tokenId: number;
+ isFirstMint?: boolean;
onSuccess?: (tokenId: number) => void;
};
export default function FreeMintButton({
tokenId,
+ isFirstMint,
onSuccess,
...props
}: FreeMintButtonProps) {
@@ -40,8 +42,14 @@ export default function FreeMintButton({
>
{(() => {
if (isLoading) {
+ if (isFirstMint) {
+ return 'First Minting...';
+ }
return 'Free Minting...';
}
+ if (isFirstMint) {
+ return 'First Mint';
+ }
return 'Free Mint';
})()}
diff --git a/apps/u3/src/components/explore/poster/mint/MintInfo.tsx b/apps/u3/src/components/poster/mint/MintInfo.tsx
similarity index 91%
rename from apps/u3/src/components/explore/poster/mint/MintInfo.tsx
rename to apps/u3/src/components/poster/mint/MintInfo.tsx
index d25e996d..e1714f41 100644
--- a/apps/u3/src/components/explore/poster/mint/MintInfo.tsx
+++ b/apps/u3/src/components/poster/mint/MintInfo.tsx
@@ -8,10 +8,7 @@ interface Props extends ComponentPropsWithRef<'div'> {
network: string;
standard: string;
contract: string;
- firstMinter?: {
- avatar?: string;
- displayName?: string;
- };
+ firstMinter?: string;
mintersCount?: number;
};
}
@@ -42,10 +39,10 @@ export default function MintInfo({ data, className, ...props }: Props) {
- {!!firstMinter && !!firstMinter?.displayName && (
+ {!!firstMinter && (
First Minter
- {firstMinter.displayName}
+ {shortPubKey(firstMinter)}
)}
diff --git a/apps/u3/src/components/explore/poster/mint/MintSuccessModalBody.tsx b/apps/u3/src/components/poster/mint/MintSuccessModalBody.tsx
similarity index 100%
rename from apps/u3/src/components/explore/poster/mint/MintSuccessModalBody.tsx
rename to apps/u3/src/components/poster/mint/MintSuccessModalBody.tsx
diff --git a/apps/u3/src/components/explore/poster/mint/PosterMint.tsx b/apps/u3/src/components/poster/mint/PosterMint.tsx
similarity index 61%
rename from apps/u3/src/components/explore/poster/mint/PosterMint.tsx
rename to apps/u3/src/components/poster/mint/PosterMint.tsx
index 27350132..7c2a03af 100644
--- a/apps/u3/src/components/explore/poster/mint/PosterMint.tsx
+++ b/apps/u3/src/components/poster/mint/PosterMint.tsx
@@ -8,12 +8,11 @@ import {
casterZoraChainId,
casterZoraNetwork,
} from '@/constants/zora';
-import { getBase64FromUrl } from '@/utils/shared/getBase64FromUrl';
-import useCasterCollection from '@/hooks/poster/useCasterCollection';
-import FirstMintButton from './FirstMintButton';
import ColorButton from '@/components/common/button/ColorButton';
import FreeMintButton from './FreeMintButton';
import SwitchNetworkButton from './SwitchNetworkButton';
+import useCasterLastTokenInfo from '@/hooks/poster/useCasterLastTokenInfo';
+import useCasterOwnerInfoWithTokenId from '@/hooks/poster/useCasterOwnerInfoWithTokenId';
interface Props extends ComponentPropsWithRef<'div'> {
img: string;
@@ -31,37 +30,23 @@ export default function PosterMint({
const { chain } = useNetwork();
const { address } = useAccount();
const { isLogin, login } = useLogin();
- const [firstMinted, setFirstMinted] = useState(true);
const [minted, setMinted] = useState(true);
const [updatedMintersCount, setUpdatedMintersCount] = useState(0);
- const {
- isAdmin,
- lastTokenFromToday,
- ownerMinted,
- lastTokenId,
- lastTokenInfo,
- } = useCasterCollection({
- owner: address,
+ const { lastTokenId, totalMinted, mintInfo } = useCasterLastTokenInfo();
+ const isFirstMint = totalMinted === 0 && !mintInfo?.originatorAddress;
+
+ const { isMinted } = useCasterOwnerInfoWithTokenId({
+ ownerAddress: address,
+ tokenId: lastTokenId,
});
- const { totalMinted } = lastTokenInfo || { totalMinted: 0 };
- useEffect(() => {
- setFirstMinted(lastTokenFromToday);
- }, [lastTokenFromToday]);
- useEffect(() => {
- setMinted(ownerMinted);
- }, [ownerMinted]);
useEffect(() => {
- setUpdatedMintersCount(firstMinted ? Number(totalMinted) : 0);
- }, [firstMinted, totalMinted]);
+ setMinted(isMinted);
+ }, [isMinted]);
- const [imgBase64, setPosterImgBase64] = useState('');
useEffect(() => {
- (async () => {
- const response = await getBase64FromUrl(img);
- setPosterImgBase64(response as string);
- })();
- }, [img]);
+ setUpdatedMintersCount(Number(totalMinted));
+ }, [totalMinted]);
return (
@@ -71,15 +56,13 @@ export default function PosterMint({
network: casterZoraNetwork.name,
standard: 'ERC1155',
contract: casterZora1155ToMintAddress,
- firstMinter: {
- avatar: '',
- displayName: '',
- },
+ firstMinter: mintInfo?.originatorAddress || '',
mintersCount: updatedMintersCount,
}}
/>
{(() => {
+ if (minted) return null;
if (!isLogin) {
return (
@@ -90,23 +73,10 @@ export default function PosterMint({
if (chain?.id !== Number(casterZoraChainId)) {
return ;
}
- if (!firstMinted) {
- if (!isAdmin) return null;
- return (
- {
- setFirstMinted(true);
- setMinted(true);
- onFirstMintSuccess?.(tokenId, address);
- }}
- />
- );
- }
if (!minted) {
return (
{
diff --git a/apps/u3/src/components/explore/poster/mint/SwitchNetworkButton.tsx b/apps/u3/src/components/poster/mint/SwitchNetworkButton.tsx
similarity index 100%
rename from apps/u3/src/components/explore/poster/mint/SwitchNetworkButton.tsx
rename to apps/u3/src/components/poster/mint/SwitchNetworkButton.tsx
diff --git a/apps/u3/src/components/explore/poster/mint/TokenShare.tsx b/apps/u3/src/components/poster/mint/TokenShare.tsx
similarity index 98%
rename from apps/u3/src/components/explore/poster/mint/TokenShare.tsx
rename to apps/u3/src/components/poster/mint/TokenShare.tsx
index 41b11339..63efa9d2 100644
--- a/apps/u3/src/components/explore/poster/mint/TokenShare.tsx
+++ b/apps/u3/src/components/poster/mint/TokenShare.tsx
@@ -3,7 +3,7 @@ import FarcasterIcon from '@/components/common/icons/FarcasterIcon';
import LensIcon from '@/components/common/icons/LensIcon';
import { TwitterLine } from '@/components/common/icons/twitter';
import { SocialPlatform } from '@/services/social/types';
-import ColorButton from '../../../common/button/ColorButton';
+import ColorButton from '../../common/button/ColorButton';
import { cn } from '@/lib/utils';
import useLogin from '@/hooks/shared/useLogin';
import { useFarcasterCtx } from '@/contexts/social/FarcasterCtx';
diff --git a/apps/u3/src/components/profile/ProfileFollowBtn.tsx b/apps/u3/src/components/profile/ProfileFollowBtn.tsx
index 78cf8744..8f9b0d6f 100644
--- a/apps/u3/src/components/profile/ProfileFollowBtn.tsx
+++ b/apps/u3/src/components/profile/ProfileFollowBtn.tsx
@@ -172,7 +172,6 @@ export default function ProfileFollowBtn({
}
const Wrapper = styled(SocialButtonPrimary)`
- color: #000;
font-family: Baloo Bhai 2;
font-size: 12px;
font-style: normal;
diff --git a/apps/u3/src/components/profile/profile-info/ProfileInfoCard.tsx b/apps/u3/src/components/profile/profile-info/ProfileInfoCard.tsx
index 4a1c5847..2f475f63 100644
--- a/apps/u3/src/components/profile/profile-info/ProfileInfoCard.tsx
+++ b/apps/u3/src/components/profile/profile-info/ProfileInfoCard.tsx
@@ -50,11 +50,12 @@ export default function ProfileInfoCard({
[canNavigateToProfile, identity]
);
if (didLoading || hasProfileLoading) {
- return (
-
-
-
- );
+ return null;
+ // return (
+ //
+ //
+ //
+ // );
}
if (did && hasProfile) {
return (
@@ -87,13 +88,13 @@ export default function ProfileInfoCard({
return null;
}
-const LoadingWrapper = styled.div`
- padding: 20px;
- width: 360px;
- box-sizing: border-box;
- background: #1b1e23;
- border-radius: 20px;
- display: flex;
- justify-content: center;
- align-items: center;
-`;
+// const LoadingWrapper = styled.div`
+// padding: 20px;
+// width: 360px;
+// box-sizing: border-box;
+// background: #1b1e23;
+// border-radius: 20px;
+// display: flex;
+// justify-content: center;
+// align-items: center;
+// `;
diff --git a/apps/u3/src/components/profile/profile-info/ProfileInfoCardLayout.tsx b/apps/u3/src/components/profile/profile-info/ProfileInfoCardLayout.tsx
index 1825bfef..5f1723b7 100644
--- a/apps/u3/src/components/profile/profile-info/ProfileInfoCardLayout.tsx
+++ b/apps/u3/src/components/profile/profile-info/ProfileInfoCardLayout.tsx
@@ -67,11 +67,12 @@ export default function ProfileInfoCardLayout({
const showMessageBtn = !!address;
if (loading) {
- return (
-
-
-
- );
+ return null;
+ // return (
+ //
+ //
+ //
+ // );
}
if (isU3Profile) {
@@ -141,6 +142,10 @@ export default function ProfileInfoCardLayout({
);
}
+ if (!address) {
+ return null;
+ }
+
return (
{
diff --git a/apps/u3/src/components/profile/profile-info/TooltipProfileNavigateLink.tsx b/apps/u3/src/components/profile/profile-info/TooltipProfileNavigateLink.tsx
index 2a691f05..6926f4e0 100644
--- a/apps/u3/src/components/profile/profile-info/TooltipProfileNavigateLink.tsx
+++ b/apps/u3/src/components/profile/profile-info/TooltipProfileNavigateLink.tsx
@@ -1,7 +1,8 @@
-import { useMemo, useState } from 'react';
+import { useEffect, useMemo, useRef, useState } from 'react';
import { Link, LinkProps, TooltipTrigger } from 'react-aria-components';
import { useNavigate } from 'react-router-dom';
import styled from 'styled-components';
+import { isMobile } from 'react-device-detect';
import TooltipBase from '../../common/tooltip/TooltipBase';
import ProfileInfoCard from './ProfileInfoCard';
import { FollowType } from '../ProfilePageFollowNav';
@@ -24,18 +25,47 @@ export default function TooltipProfileNavigateLink({
return '';
}, [identity]);
const [isOpen, setIsOpen] = useState(false);
+ const linkRef = useRef(null);
+
+ useEffect(() => {
+ if (!linkRef.current || !isOpen) return;
+ const el = linkRef.current;
+
+ const handleMouseWheel = (e: WheelEvent) => {
+ setIsOpen(false);
+ };
+ el.addEventListener('wheel', handleMouseWheel);
+
+ // eslint-disable-next-line consistent-return
+ return () => {
+ el.removeEventListener('wheel', handleMouseWheel);
+ };
+ }, [isOpen]);
+
+ const linkEl = (
+ {
+ e.continuePropagation();
+ if (profileUrl) navigate(profileUrl);
+ }}
+ {...linkProps}
+ >
+ {children}
+
+ );
+ if (isMobile) {
+ return linkEl;
+ }
return (
-
- {
- e.continuePropagation();
- if (profileUrl) navigate(profileUrl);
- }}
- {...linkProps}
- >
- {children}
-
+
+ {linkEl}
- {data.title || data.url}
- {!!data?.createAt && (
-
- {defaultFormatFromNow(data.createAt)}
-
- )}
+
+
+ {data.title || data.url}
+
+ {!!data?.createAt && (
+
+ {defaultFormatFromNow(data.createAt)}
+
+ )}
+
);
@@ -53,18 +56,10 @@ const ListItemInner = styled.div`
gap: 20px;
`;
-const TitleText = styled(EllipsisText)`
- flex: 1;
- font-weight: 500;
- font-size: 16px;
- line-height: 19px;
- color: #ffffff;
-`;
-
const TimeText = styled.span`
font-weight: 400;
font-size: 14px;
- line-height: 17px;
+ line-height: 18px;
color: #718096;
`;
@@ -94,22 +89,4 @@ const IconLink = styled(LinkBox)`
export const SaveExploreListItemMobile = styled(SaveExploreListItem)`
padding: 10px;
height: auto;
-
- & > div {
- position: relative;
- }
-
- .timeText {
- position: absolute;
- right: 10px;
- bottom: 2px;
- }
-
- .bottomBox {
- padding-right: 100px;
- .iconLink {
- padding: 0;
- height: auto;
- }
- }
`;
diff --git a/apps/u3/src/components/save/SyncingBotSaves.tsx b/apps/u3/src/components/save/SyncingBotSaves.tsx
new file mode 100644
index 00000000..e8c74f3d
--- /dev/null
+++ b/apps/u3/src/components/save/SyncingBotSaves.tsx
@@ -0,0 +1,112 @@
+import { useFavorAction } from '@us3r-network/link';
+import { useSession } from '@us3r-network/auth-with-rainbowkit';
+import { useEffect, useState } from 'react';
+import { Link } from '@us3r-network/data-model';
+import {
+ getSavedCasts,
+ setSavedCastsSynced,
+} from '@/services/social/api/farcaster';
+import { getAddressWithDidPkh } from '../../utils/shared/did';
+
+export default function SyncingBotSaves({
+ onComplete,
+}: {
+ onComplete?: (saves) => void;
+}) {
+ const session = useSession();
+ const [saves, setSaves] = useState([]);
+ const [saveIndex, setSaveIndex] = useState(-1);
+
+ useEffect(() => {
+ const walletAddress = getAddressWithDidPkh(session.id);
+ getSavedCasts(walletAddress).then((res) => {
+ const links = res.map((item) => {
+ const { castHash, text } = item;
+ return {
+ url: `https://u3.xyz/social/post-detail/fcast/${Buffer.from(
+ castHash.data
+ ).toString('hex')}`,
+ title: text
+ ? text.slice(0, 200)
+ : 'Saved Farcaster Cast using U3 Bot',
+ type: 'link',
+ data: JSON.stringify(item),
+ };
+ });
+ setSaves(links);
+ setSaveIndex(0);
+ });
+ }, [session]);
+
+ return saves && saves.length > 0 && saveIndex >= 0 ? (
+ {
+ console.log('onSuccessfullyFavor', isFavored, linkId);
+ if (isFavored) {
+ const linkData = JSON.parse(saves[saveIndex].data);
+ const { id, walletAddress } = linkData;
+ console.log(id, walletAddress);
+ setSavedCastsSynced(Buffer.from(walletAddress).toString('hex'), id);
+ if (saveIndex >= saves.length - 1) {
+ onComplete(saves);
+ } else {
+ setSaveIndex(saveIndex + 1);
+ }
+ }
+ }}
+ onFailedFavor={(errMsg: string) => {
+ console.log('onFailedFavor', errMsg);
+ }}
+ />
+ ) : null;
+}
+
+function FavoringLink({
+ link,
+ onSuccessfullyFavor,
+ onFailedFavor,
+}: {
+ link?: Link | undefined;
+ onSuccessfullyFavor?: (isFavored: boolean, linkId: string) => void;
+ onFailedFavor?: (errMsg: string) => void;
+}) {
+ // console.log('FavoringLink', link);
+ const { isFavored, isFavoring, onFavor } = useFavorAction('', link, {
+ onSuccessfullyFavor: (done: boolean, newLinkId: string) => {
+ // console.log('onSuccessfullyFavor in FavoringLink', done, newLinkId);
+ onSuccessfullyFavor(done, newLinkId);
+ },
+ onFailedFavor: (err: string) => {
+ // console.log('onFailedFavor in FavoringLink', err);
+ onFailedFavor(err);
+ },
+ });
+ useEffect(() => {
+ console.log('link: ', link, isFavored);
+ if (link)
+ setTimeout(() => {
+ console.log('onFavor fire!');
+ if (!isFavored && !isFavoring) onFavor();
+ }, 500);
+ }, [link]);
+
+ if (isFavoring)
+ return (
+ {`Favoring Bot Saved Link '${link.title}' ......`}
+ );
+ if (isFavored)
+ return (
+ {`Favored Bot Saved Link '${link.title}' ......`}
+ );
+ // return (
+ //
+ //
{link?.title}
+ //
{link?.url}
+ //
{link?.type}
+ //
+ //
+ // );
+}
diff --git a/apps/u3/src/components/social/Embed.tsx b/apps/u3/src/components/social/Embed.tsx
index f7f97e2b..e8077447 100644
--- a/apps/u3/src/components/social/Embed.tsx
+++ b/apps/u3/src/components/social/Embed.tsx
@@ -1,37 +1,59 @@
/* eslint-disable react/no-array-index-key */
/* eslint-disable @typescript-eslint/no-explicit-any */
-import { useEffect, useMemo, useRef, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
-import { UserDataType } from '@farcaster/hub-web';
-import styled from 'styled-components';
+import {
+ CastId,
+ Message,
+ UserDataType,
+ makeFrameAction,
+} from '@farcaster/hub-web';
import dayjs from 'dayjs';
-
+import { toHex } from 'viem';
+import { toast } from 'react-toastify';
import {
+ FarCast,
FarCastEmbedMeta,
FarCastEmbedMetaCast,
} from '../../services/social/types';
+import { PostCardEmbedWrapper, PostCardImgWrapper } from './PostCard';
import {
- PostCardCastWrapper,
- PostCardEmbedWrapper,
- PostCardImgWrapper,
- PostCardNftWrapper,
-} from './PostCard';
-import { getFarcasterEmbedMetadata } from '../../services/social/api/farcaster';
+ getFarcasterEmbedCast,
+ getFarcasterEmbedMetadata,
+ postFrameActionApi,
+} from '../../services/social/api/farcaster';
import ModalImg from './ModalImg';
import U3ZoraMinter from './farcaster/U3ZoraMinter';
import LinkModal from '../news/links/LinkModal';
+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';
+
+const ValidFrameButtonValue = [
+ [0, 0, 0, 0].join(''),
+ [1, 0, 0, 0].join(''),
+ [1, 1, 0, 0].join(''),
+ [1, 1, 1, 0].join(''),
+ [1, 1, 1, 1].join(''),
+];
export default function Embed({
embedImgs,
embedWebpages,
+ embedCasts,
+ cast,
}: {
embedImgs: { url: string }[];
embedWebpages: { url: string }[];
+ embedCasts: { castId: { fid: number; hash: string } }[];
+ cast: FarCast;
}) {
const viewRef = useRef(null);
- const [metadata, setMetadata] = useState<
- (FarCastEmbedMeta | FarCastEmbedMetaCast)[]
- >([]);
+ const [metadata, setMetadata] = useState([]);
+ const [metadataCasts, setMetadataCasts] = useState(
+ []
+ );
const [modalImgIdx, setModalImgIdx] = useState(-1);
const getEmbedWebpagesMetadata = async () => {
@@ -48,11 +70,26 @@ export default function Embed({
}
};
+ const getEmbedCastsMetadata = async () => {
+ const castIds = embedCasts.map((embed) => embed.castId);
+ if (castIds.length === 0) return;
+ try {
+ const res = await getFarcasterEmbedCast(castIds[0]);
+ const { metadata: respMetadata } = res.data.data;
+ const data = respMetadata.flatMap((m) => (m ? [m] : []));
+ setMetadataCasts(data);
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ }
+ };
+
useEffect(() => {
if (!viewRef.current) return;
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
getEmbedWebpagesMetadata();
+ getEmbedCastsMetadata();
observer.disconnect();
}
});
@@ -64,16 +101,23 @@ export default function Embed({
};
}, [viewRef]);
- if (embedImgs.length === 0 && embedWebpages.length === 0) return null;
+ if (
+ embedImgs.length === 0 &&
+ embedWebpages.length === 0 &&
+ embedCasts.length === 0
+ ) {
+ return null;
+ }
return (
-
+
{embedImgs.length > 0 && (
<>
{embedImgs.map((img, idx) => (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
>
)}
-
- {metadata.map((item: FarCastEmbedMeta | FarCastEmbedMetaCast) => {
- if ((item as any).type === 'cast') {
- const { cast } = item as FarCastEmbedMetaCast;
- return (
-
- );
+
+ {[...metadataCasts].map((item) => {
+ if (item.cast === undefined) return null;
+ return (
+
+ );
+ })}
+ {[...metadata].map((item: FarCastEmbedMeta) => {
+ if (item.collection) {
+ return ;
}
- if ((item as any).collection) {
+ if (checkFarcastFrameValid(item)) {
return (
-
+
);
}
- return (
-
- );
+ return ;
})}
-
+
+ );
+}
+
+function EmbedCastFrame({
+ data,
+ cast,
+}: {
+ data: FarCastEmbedMeta;
+ cast: FarCast;
+}) {
+ const castId: CastId = useFarcasterCastId({ cast });
+ const { encryptedSigner, isConnected, currFid } = useFarcasterCtx();
+
+ const [frameData, setFrameData] = useState(data);
+
+ const postFrameAction = useCallback(
+ async (index: number) => {
+ if (!castId) {
+ console.error('no castId');
+ toast.error('no castId');
+ return;
+ }
+ if (!encryptedSigner || !currFid) {
+ console.error('no encryptedSigner');
+ toast.error('no encryptedSigner');
+ return;
+ }
+ const url = data.fcFramePostUrl || data.url;
+ const trustedDataResult = await makeFrameAction(
+ {
+ url: Buffer.from(url),
+ buttonIndex: index,
+ castId,
+ },
+ {
+ fid: currFid,
+ network: FARCASTER_NETWORK,
+ },
+ encryptedSigner
+ );
+ if (trustedDataResult.isErr()) {
+ throw new Error(trustedDataResult.error.message);
+ }
+ const trustedDataValue = trustedDataResult.value;
+ const untrustedData = {
+ fid: currFid,
+ url,
+ messageHash: toHex(trustedDataValue.hash),
+ network: FARCASTER_NETWORK,
+ buttonIndex: index,
+ castId: {
+ fid: castId.fid,
+ hash: toHex(castId.hash),
+ },
+ };
+ const trustedData = {
+ messageBytes: Buffer.from(
+ Message.encode(trustedDataValue).finish()
+ ).toString('hex'),
+ };
+ const postData = {
+ untrustedData,
+ trustedData,
+ };
+ const resp = await postFrameActionApi(postData);
+ if (resp.data.code !== 0) {
+ toast.error(resp.data.msg);
+ return;
+ }
+ 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}
+
+ );
+ })}
+
+ )}
+
);
}
@@ -148,7 +291,8 @@ function EmbedCast({ data }: { data: FarCastEmbedMetaCast }) {
}, [data.cast]);
return (
- {
e.stopPropagation();
navigate(
@@ -158,27 +302,38 @@ function EmbedCast({ data }: { data: FarCastEmbedMetaCast }) {
);
}}
>
-
-
+
+
-
-
{userData.username}
-
+
+
+ {userData.username}
+
+
@{userData.uname}
{' '}·{' '}
{dayjs(data.cast.created_at).fromNow()}
-
{data.cast.text}
+
+ {data.cast.text}
+
- {castImg &&
}
-
+ {castImg && (
+
+ )}
+
);
}
@@ -188,16 +343,25 @@ function EmbedNFT({ item }: { item: FarCastEmbedMeta }) {
}
return (
-
{
e.stopPropagation();
}}
>
-
-
-
{item.collection}
+
+
+
+ {item.collection}
+
-
+
);
}
@@ -234,14 +398,18 @@ export function EmbedWebsite({
>
{(isImg(img || '') && (
)) || (
-
+
)}
@@ -261,7 +429,7 @@ export function EmbedWebsite({
)}
- {showMenuBtn && (
+ {/* {showMenuBtn && (
{
e.stopPropagation();
@@ -119,7 +120,7 @@ export default function PostCard({
followAction={followAction}
/>
- )}
+ )} */}
{contentRender ? contentRender() : data?.content}
@@ -186,7 +187,7 @@ export const PostShareMenuBtn = styled(MultiPlatformShareMenuBtn)`
height: 12px;
cursor: pointer;
path {
- stroke: #ffffff;
+ stroke: #718096;
}
}
`;
@@ -257,11 +258,15 @@ export function PostCardUserInfo({
- {data.name}
+
+ {data.name}
+
{PlatFormIcon}
- @{data.handle} · {dayjs(data.createdAt).fromNow()}
+
+ @{data.handle} · {dayjs(data.createdAt).fromNow()}
+
@@ -297,6 +302,7 @@ const Avatar = styled.img`
object-fit: cover;
`;
const Name = styled.div`
+ width: 100%;
display: flex;
align-items: center;
gap: 10px;
@@ -309,6 +315,7 @@ const Name = styled.div`
line-height: normal;
`;
const Handle = styled.div`
+ width: 100%;
display: flex;
align-items: center;
gap: 5px;
@@ -324,9 +331,9 @@ const Handle = styled.div`
export const PostCardShowMoreWrapper = styled.div`
> button {
border: none;
- background: linear-gradient(87deg, #cd62ff 0%, #62aaff 100%);
+ background: #454c99;
-webkit-background-clip: text;
- color: transparent;
+ color: #5057aa;
padding: 0;
cursor: pointer;
}
@@ -370,7 +377,7 @@ export const PostCardImgWrapper = styled.div<{ len: number }>`
? 'calc(33% - 12px)'
: props.len === 2
? 'calc(50% - 10px)'
- : '70%'};
+ : '100%'};
border-radius: 10px;
overflow: hidden;
cursor: pointer;
@@ -386,112 +393,6 @@ export const PostCardVideoWrapper = styled.div`
max-width: 100%;
`;
-export const PostCardCastWrapper = styled.div`
- border-radius: 10px;
- color: #fff;
- padding: 20px;
- cursor: pointer;
- display: flex;
- gap: 10px;
- justify-content: space-between;
- background-color: #14171a;
- > div {
- > div {
- display: flex;
- align-items: center;
- gap: 10px;
- > img {
- width: 21px;
- height: 21px;
- border-radius: 50%;
- }
- > div {
- display: flex;
- align-items: center;
- gap: 5px;
- }
- .username {
- color: #fff;
- font-family: Rubik;
- font-size: 12px;
- font-style: normal;
- font-weight: 700;
- line-height: normal;
- margin-right: 5px;
- }
- .uname {
- color: #718096;
- font-family: Rubik;
- font-size: 12px;
- font-style: normal;
- font-weight: 400;
- line-height: normal;
- }
- }
- > p {
- color: #c8c4c4;
- margin-bottom: 0;
- margin-top: 10px;
- padding: 0;
- word-break: break-all;
- text-overflow: ellipsis;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 3;
- overflow: hidden;
- }
- }
- > img {
- flex-shrink: 0;
- width: 100px;
- height: 100px;
- border-radius: 10px;
- overflow: hidden;
- }
-`;
-
-export const PostCardNftWrapper = styled.div`
- color: #fff;
- width: 100%;
- border-radius: 10px;
- overflow: hidden;
- background-color: #14171a;
- cursor: initial;
- > img {
- width: 100%;
- }
- > div {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 20px;
- > h4 {
- margin: 0;
- color: #fff;
- font-family: Rubik;
- font-size: 16px;
- font-style: normal;
- font-weight: 400;
- line-height: 30px; /* 187.5% */
- }
- > button {
- cursor: pointer;
- border-radius: 10px;
- background: linear-gradient(81deg, #cd62ff 0%, #62aaff 100%);
- padding: 10px 20px;
- border: none;
- outline: none;
- color: inherit;
- color: #000;
- font-family: Rubik;
- font-size: 16px;
- font-style: normal;
- font-weight: 700;
- line-height: normal;
- }
- }
-`;
-
export const PostCardEmbedWrapper = styled.div`
color: #fff;
border-radius: 10px;
@@ -499,7 +400,7 @@ export const PostCardEmbedWrapper = styled.div`
background-color: #14171a;
text-decoration: none;
width: 100%;
- min-height: 373px;
+ min-height: 349px;
display: flex;
flex-direction: column;
cursor: default;
diff --git a/apps/u3/src/components/social/PostLike.tsx b/apps/u3/src/components/social/PostLike.tsx
index 569009b6..66c8b264 100644
--- a/apps/u3/src/components/social/PostLike.tsx
+++ b/apps/u3/src/components/social/PostLike.tsx
@@ -118,7 +118,9 @@ export const PostLikeAvatarWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
- background: #fff;
+ background: #5057aa;
+ color: #fff;
+ font-size: 10px;
`;
export const PostLikeAvatar = styled.img`
width: 100%;
diff --git a/apps/u3/src/components/social/PostReply.tsx b/apps/u3/src/components/social/PostReply.tsx
index 190084da..686c1271 100644
--- a/apps/u3/src/components/social/PostReply.tsx
+++ b/apps/u3/src/components/social/PostReply.tsx
@@ -55,10 +55,7 @@ const PostReplyWrapper = styled.div<{
cursor: ${(props) => (props.disabled ? '' : 'pointer')};
color: #718096;
- background: ${(props) =>
- props.replied
- ? 'linear-gradient(78deg, #cd62ff 0%, #62aaff 100%)'
- : 'initial'};
+ background: ${(props) => (props.replied ? '#454C99' : 'initial')};
background-clip: ${(props) => (props.replied ? 'text' : 'initial')};
-webkit-background-clip: ${(props) => (props.replied ? 'text' : 'initial')};
-webkit-text-fill-color: ${(props) =>
@@ -77,16 +74,13 @@ const PostReplyWrapper = styled.div<{
align-items: center;
justify-content: center;
border-radius: 50%;
- background: ${(props) =>
- props.hover
- ? 'linear-gradient(45deg, rgba(205, 98, 255, 0.20) 0%, rgba(98, 170, 255, 0.20) 100%);'
- : 'transparent'};
+ background: ${(props) => (props.hover ? '#454C99' : 'transparent')};
}
&:hover {
${(props) =>
!props.disabled &&
`
- background: linear-gradient(78deg, #cd62ff 0%, #62aaff 100%);
+ background: #454C99;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
diff --git a/apps/u3/src/components/social/QuickSearchModal.tsx b/apps/u3/src/components/social/QuickSearchModal.tsx
index 94a1af78..5b354920 100644
--- a/apps/u3/src/components/social/QuickSearchModal.tsx
+++ b/apps/u3/src/components/social/QuickSearchModal.tsx
@@ -207,7 +207,10 @@ export default function QuickSearchModal({
inputRef.current?.focus();
}}
zIndex={40}
- className="top-[100px] md:w-[800px] w-full mb-2 box-border overflow-hidden"
+ className={cn(
+ 'top-[100px] md:w-[800px] w-full mb-2 box-border overflow-hidden',
+ 'max-sm:top-[50px]'
+ )}
>
);
}
-const PageHeader = styled.div`
- width: 100%;
- display: flex;
- justify-content: space-between;
- border: 1px solid #39424c;
- border-radius: 100px;
- font-size: 16px;
- line-height: 28px;
- color: #ffffff;
- white-space: pre;
-
- i {
- color: #39424c;
- }
-
- .tab {
- cursor: pointer;
- flex: 1;
- text-align: center;
- color: #718096;
- padding: 5px 0;
- }
-
- .active {
- color: black;
- position: relative;
- background: #718096;
- box-shadow: 0px 0px 8px rgba(20, 23, 26, 0.08),
- 0px 0px 4px rgba(20, 23, 26, 0.04);
- border-radius: 100px;
- /* &:after {
- content: '';
- position: absolute;
- left: 0;
- bottom: -10px;
- width: 100%;
- height: 2px;
- background: white;
- } */
- }
-`;
-
const SocialNavWrapper = styled.div`
width: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
${!isMobile &&
- `
+ `
height: 70px;
gap: 40px;
top: 0;
diff --git a/apps/u3/src/components/social/button/SocialButton.tsx b/apps/u3/src/components/social/button/SocialButton.tsx
index d5c0adfd..b7eeac02 100644
--- a/apps/u3/src/components/social/button/SocialButton.tsx
+++ b/apps/u3/src/components/social/button/SocialButton.tsx
@@ -1,7 +1,8 @@
import styled from 'styled-components';
-import { ButtonPrimary } from '../../common/button/ButtonBase';
+import ColorButton from '@/components/common/button/ColorButton';
+import { ButtonPrimaryLine } from '@/components/common/button/ButtonBase';
-export const SocialButtonPrimary = styled(ButtonPrimary)`
+export const SocialButtonPrimary = styled(ColorButton)`
height: 40px;
padding: 10px 20px;
box-sizing: border-box;
@@ -11,9 +12,8 @@ export const SocialButtonPrimary = styled(ButtonPrimary)`
gap: 10px;
flex-shrink: 0;
border-radius: 10px;
- background: linear-gradient(87deg, #cd62ff 0%, #62aaff 100%);
- color: #000;
+ color: #fff;
font-family: Rubik;
font-size: 16px;
font-style: normal;
@@ -21,16 +21,10 @@ export const SocialButtonPrimary = styled(ButtonPrimary)`
line-height: normal;
`;
-export const SocialButtonPrimaryLine = styled(SocialButtonPrimary)`
- border: 1px solid #cd62ff;
-
+export const SocialButtonPrimaryLine = styled(ButtonPrimaryLine)`
font-family: Baloo Bhai 2;
font-size: 12px;
font-style: normal;
font-weight: 700;
line-height: normal;
- background: linear-gradient(83deg, #cd62ff 0%, #62aaff 100%);
- background-clip: text;
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
`;
diff --git a/apps/u3/src/components/social/farcaster/FCast.tsx b/apps/u3/src/components/social/farcaster/FCast.tsx
index b30ef519..35435363 100644
--- a/apps/u3/src/components/social/farcaster/FCast.tsx
+++ b/apps/u3/src/components/social/farcaster/FCast.tsx
@@ -38,8 +38,8 @@ import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { pinupCastApi } from '@/services/social/api/farcaster';
import useLogin from '@/hooks/shared/useLogin';
-import { farcasterHandleToBioLinkHandle } from '@/utils/profile/biolink';
import { SaveButton } from '@/components/shared/button/SaveButton';
+import FCastTips from './FCastTips';
export default function FCast({
cast,
@@ -175,6 +175,7 @@ export default function FCast({
e.stopPropagation();
}}
>
+
{isAdmin && (
)}
- {showMenuBtn && (
+ {/* {showMenuBtn && (
- )}
+ )} */}
@@ -235,6 +236,8 @@ export default function FCast({
)}
{(cast.parent_url || cast.rootParentUrl) && (
diff --git a/apps/u3/src/components/social/farcaster/FCastRecast.tsx b/apps/u3/src/components/social/farcaster/FCastRecast.tsx
index 66473d8d..eadeaa84 100644
--- a/apps/u3/src/components/social/farcaster/FCastRecast.tsx
+++ b/apps/u3/src/components/social/farcaster/FCastRecast.tsx
@@ -218,11 +218,6 @@ function Repost({
toChannels: string[];
}) => {
if (!encryptedSigner) return;
- const url = `https://warpcast.com/${creatorData.userName}/0x${Buffer.from(
- castId.hash
- )
- .toString('hex')
- .substring(0, 8)}`;
const tcs = (data.toChannels || [])
.map((channelId) => {
@@ -236,7 +231,7 @@ function Repost({
await makeCastAdd(
{
text: data.castBody.text,
- embeds: [{ url }, { castId }],
+ embeds: [{ castId }],
embedsDeprecated: [],
mentions: data.castBody.mentions || [],
mentionsPositions: data.castBody.mentionsPositions || [],
@@ -257,7 +252,7 @@ function Repost({
await makeCastAdd(
{
text: data.castBody.text,
- embeds: [{ url }],
+ embeds: [{ castId }],
embedsDeprecated: [],
mentions: data.castBody.mentions || [],
mentionsPositions: data.castBody.mentionsPositions || [],
diff --git a/apps/u3/src/components/social/farcaster/FCastReply.tsx b/apps/u3/src/components/social/farcaster/FCastReply.tsx
index 7b449289..81927a1f 100644
--- a/apps/u3/src/components/social/farcaster/FCastReply.tsx
+++ b/apps/u3/src/components/social/farcaster/FCastReply.tsx
@@ -8,7 +8,7 @@ import useLogin from '@/hooks/shared/useLogin';
import { FCastChannelPicker } from '@/components/social/farcaster/FCastChannelPicker';
import FarcasterInput from '@/components/social/farcaster/FarcasterInput';
-import { Button } from '@/components/ui/button';
+import ColorButton from '@/components/common/button/ColorButton';
export function ReplyCast({
replyAction,
@@ -63,8 +63,8 @@ export function ReplyCast({
disabled
/>
-
+
diff --git a/apps/u3/src/components/social/farcaster/FCastTips.tsx b/apps/u3/src/components/social/farcaster/FCastTips.tsx
new file mode 100644
index 00000000..fdf6f915
--- /dev/null
+++ b/apps/u3/src/components/social/farcaster/FCastTips.tsx
@@ -0,0 +1,270 @@
+import { useCallback, useState } from 'react';
+import { Cross2Icon } from '@radix-ui/react-icons';
+import { useAccount, useBalance, useNetwork } from 'wagmi';
+import { useConnectModal } from '@rainbow-me/rainbowkit';
+import {
+ prepareWriteContract,
+ switchNetwork,
+ waitForTransaction,
+ writeContract,
+} from '@wagmi/core';
+import { toast } from 'react-toastify';
+import { parseEther } from 'viem';
+import { base } from 'viem/chains';
+
+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,
+ 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';
+
+export default function FCastTips({
+ userData,
+ cast,
+}: {
+ userData: UserData;
+ cast: FarCast;
+}) {
+ const [openModal, setOpenModal] = useState(false);
+ const { isLogin: isLoginU3, login: loginU3 } = useLogin();
+ const { address } = useAccount();
+ const { openConnectModal } = useConnectModal();
+ const [userinfo, setUserInfo] = useState<{ address: string; fname: string }>({
+ address: '',
+ fname: '',
+ });
+ const [loading, setLoading] = useState(false);
+ const loadUserinfo = useCallback(async () => {
+ try {
+ setLoading(true);
+ const { data } = await getUserinfoWithFid(userData.fid);
+ setUserInfo(data.data);
+ } catch (e) {
+ console.error(e);
+ } finally {
+ setLoading(false);
+ }
+ }, [userData]);
+
+ return (
+ <>
+ {
+ e.stopPropagation();
+ if (!isLoginU3) {
+ loginU3();
+ return;
+ }
+ if (!address) {
+ openConnectModal();
+ return;
+ }
+ loadUserinfo();
+ setOpenModal(true);
+ }}
+ >
+
+ Tips
+
+ {openModal && (
+
+ )}
+ >
+ );
+}
+
+function TipsModal({
+ open,
+ setOpen,
+ loading,
+ userinfo,
+ userData,
+ cast,
+}: {
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ loading: boolean;
+ userData: UserData;
+ userinfo: { address: string; fname: string };
+ cast: FarCast;
+}) {
+ return (
+ {
+ setOpen(false);
+ }}
+ contentTop="40%"
+ className="w-full md:w-[320px]"
+ >
+
+
+
+
+ Tips
+
+
{
+ setOpen(false);
+ }}
+ >
+
+
+
+
+
+ {(loading && (
+
+
+
+ )) ||
+ (userinfo.address && (
+
{
+ setOpen(false);
+ }}
+ />
+ )) || connot load valid address
}
+
+
+ );
+}
+
+function TipTransaction({
+ fname,
+ address,
+ castHash,
+ successCallback,
+}: {
+ fname: string;
+ address: string;
+ castHash: string;
+ successCallback?: () => void;
+}) {
+ const { currFid } = useFarcasterCtx();
+ const tipsCount = [69, 420, 42069];
+ const { address: accountAddr } = useAccount();
+ const result = useBalance({
+ address: accountAddr,
+ formatUnits: 'ether',
+ token: DegenAddress,
+ chainId: base.id,
+ });
+ const network = useNetwork();
+ const [tipAmount, setTipAmount] = useState(tipsCount[1]);
+ const [transactionHash, setTransactionHash] = useState('');
+ const tipAction = useCallback(async () => {
+ const left = result?.data?.formatted?.toString() || '0';
+ if (Number(left) < tipAmount) {
+ toast.error(`not enough $DEGEN, left: ${left}`);
+ return;
+ }
+ try {
+ if (network.chain?.id !== base.id) {
+ await switchNetwork({ chainId: base.id });
+ }
+ const { request: transferDegenRequest } = await prepareWriteContract({
+ address: DegenAddress,
+ abi: DegenABI,
+ chainId: base.id,
+ functionName: 'transfer',
+ args: [`0x${address}`, parseEther(tipAmount.toString())],
+ });
+ const degenTxHash = await writeContract(transferDegenRequest);
+ const degenTxReceipt = await waitForTransaction({
+ hash: degenTxHash.hash,
+ chainId: base.id,
+ });
+ 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 {
+ console.error('transaction failed', degenTxHash.hash, degenTxReceipt);
+ toast.error(`mint action failed: ${degenTxReceipt.status}`);
+ }
+ } catch (e) {
+ toast.error(e.message.split('\n')[0]);
+ }
+ }, [address, tipAmount, result]);
+
+ return (
+
+ {/*
$Degen: {result?.data?.formatted?.toString() || '0'}
*/}
+
+ {tipsCount.map((item) => {
+ return (
+
{
+ setTipAmount(item);
+ }}
+ >
+ {item}
+
+ );
+ })}
+
+
+ or
+ {
+ setTipAmount(Number(e.target.value));
+ }}
+ />
+ $DEGEN
+
+
+
+ to @{fname} (0x{shortPubKey(address, { len: 4 })})
+
+
+ );
+}
diff --git a/apps/u3/src/components/social/farcaster/FarcasterQRModal.tsx b/apps/u3/src/components/social/farcaster/FarcasterQRModal.tsx
index 2634e862..817456d9 100644
--- a/apps/u3/src/components/social/farcaster/FarcasterQRModal.tsx
+++ b/apps/u3/src/components/social/farcaster/FarcasterQRModal.tsx
@@ -1,12 +1,8 @@
import QRCode from 'react-qr-code';
-import styled from 'styled-components';
-import ModalContainer from '../../common/modal/ModalContainer';
-import {
- ModalCloseBtn,
- ModalDescription,
- ModalTitle,
-} from '../../common/modal/ModalWidgets';
+import { ModalCloseBtn, ModalTitle } from '../../common/modal/ModalWidgets';
+import ModalContainerFixed from '@/components/common/modal/ModalContainerFixed';
+import { cn } from '@/lib/utils';
type Token = {
token: string;
@@ -20,115 +16,80 @@ export default function FarcasterQRModal({
afterCloseAction,
showQR,
warpcastErr,
+ deepLinkUrl,
}: {
warpcastErr: string;
token: Token;
open: boolean;
showQR: boolean;
+ deepLinkUrl: string;
closeModal: () => void;
afterCloseAction: () => void;
}) {
return (
-
-
-
- Login with mobile
+
+
{(warpcastErr && (
-
+
{warpcastErr}
please try again in a few minutes
-
+
)) || (
-
-
-
+
+
+
Scan the QR code with the camera app on your device with
Warpcast installed.
-
-
+
Download Warpcast
-
-
-
+
+
+
{(showQR &&
) || (
-
Loading QR code...
+
+ Loading QR code...
+
)}
-
-
+ {showQR && deepLinkUrl && (
+
+ )}
+
+
)}
-
-
+
+
);
}
-
-const ModalBody = styled.div`
- width: fit-content;
- max-width: 480px;
- /* height: 220px; */
- flex-shrink: 0;
-
- padding: 30px;
- box-sizing: border-box;
-
- display: flex;
- flex-direction: column;
- gap: 30px;
-`;
-const ModalHeader = styled.div`
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 10px;
-`;
-const Description = styled.span`
- color: #718096;
- font-family: Rubik;
- font-size: 16px;
- font-style: normal;
- font-weight: 400;
- line-height: 30px; /* 187.5% */
-`;
-const ModalContent = styled.div`
- display: flex;
- gap: 40px;
- justify-content: space-between;
-`;
-const Left = styled.div`
- width: 50%;
- display: flex;
- flex-direction: column;
- gap: 10px;
-`;
-const Right = styled.div`
- width: 50%;
- height: 200px;
- display: flex;
- justify-content: center;
- align-items: center;
- svg {
- width: 200px;
- height: 200px;
- }
-`;
-const DownloadLink = styled.a`
- font-family: Rubik;
- font-size: 12px;
- font-style: normal;
- font-weight: 400;
- line-height: 30px; /* 250% */
-
- background: linear-gradient(88deg, #cd62ff 0%, #62aaff 99.21%);
- background-clip: text;
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
-`;
diff --git a/apps/u3/src/components/social/farcaster/FarcasterSignerSelectModal.tsx b/apps/u3/src/components/social/farcaster/FarcasterSignerSelectModal.tsx
index b04247ed..1a58257c 100644
--- a/apps/u3/src/components/social/farcaster/FarcasterSignerSelectModal.tsx
+++ b/apps/u3/src/components/social/farcaster/FarcasterSignerSelectModal.tsx
@@ -258,7 +258,7 @@ const UserDataBox = styled.div<{ select: boolean }>`
justify-self: end;
}
button.go {
- background: linear-gradient(85deg, #cd62ff 0%, #62aaff 100%);
+ background: #454c99;
height: 40px;
width: 40px;
border-radius: 10px;
@@ -332,12 +332,12 @@ const Ops = styled.div`
height: 40px;
flex-shrink: 0;
border-radius: 10px;
- background: linear-gradient(85deg, #cd62ff 0%, #62aaff 100%);
+ background: #454c99;
border: none;
outline: none;
cursor: pointer;
- color: #000;
+ color: #fff;
font-family: Baloo Bhai 2;
font-size: 12px;
font-style: normal;
diff --git a/apps/u3/src/components/social/farcaster/FarcasterVerifyModal.tsx b/apps/u3/src/components/social/farcaster/FarcasterVerifyModal.tsx
index 81d4b6c3..97e0ab81 100644
--- a/apps/u3/src/components/social/farcaster/FarcasterVerifyModal.tsx
+++ b/apps/u3/src/components/social/farcaster/FarcasterVerifyModal.tsx
@@ -87,12 +87,12 @@ const Ops = styled.div`
height: 40px;
flex-shrink: 0;
border-radius: 10px;
- background: linear-gradient(85deg, #cd62ff 0%, #62aaff 100%);
+ background: #454c99;
border: none;
outline: none;
cursor: pointer;
- color: #000;
+ color: #fff;
font-family: Baloo Bhai 2;
font-size: 12px;
font-style: normal;
diff --git a/apps/u3/src/components/social/farcaster/U3ZoraMinter.tsx b/apps/u3/src/components/social/farcaster/U3ZoraMinter.tsx
index 36107d02..fe8638a9 100644
--- a/apps/u3/src/components/social/farcaster/U3ZoraMinter.tsx
+++ b/apps/u3/src/components/social/farcaster/U3ZoraMinter.tsx
@@ -1,4 +1,10 @@
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import {
+ ComponentPropsWithRef,
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+} from 'react';
import { UrlMetadata } from '@mod-protocol/core';
import { useAccount, useNetwork } from 'wagmi';
import { useConnectModal } from '@rainbow-me/rainbowkit';
@@ -13,8 +19,6 @@ import { getMetadataWithMod } from '@/services/social/api/farcaster';
import { FarCastEmbedMeta } from '@/services/social/types';
import useLogin from '@/hooks/shared/useLogin';
-import { PostCardNftWrapper } from '../PostCard';
-
type ModTransactionData = {
status: string;
orderIds: string[];
@@ -161,21 +165,28 @@ export default function U3ZoraMinter({
}, []);
return (
- {
e.stopPropagation();
}}
>
-
-
-
{embedMetadata.collection}
- {(minting &&
) ||
+
+
+
+ {embedMetadata.collection}
+
+ {(minting && Minting) ||
(modMetadataCheckDone &&
((modData &&
modData.nft?.tokenId &&
((transactionHash && (
-
+
)) || (
-
+
))) || (
-
+
)))}
-
+
+ );
+}
+function MintButton(props: ComponentPropsWithRef<'button'>) {
+ return (
+
);
}
diff --git a/apps/u3/src/components/social/farcaster/signup/Steps.tsx b/apps/u3/src/components/social/farcaster/signup/Steps.tsx
index c8c731c0..bebdb06a 100644
--- a/apps/u3/src/components/social/farcaster/signup/Steps.tsx
+++ b/apps/u3/src/components/social/farcaster/signup/Steps.tsx
@@ -32,7 +32,7 @@ const StyledTitleBox = styled.h1`
font-weight: 700;
line-height: normal;
- background: linear-gradient(88deg, #cd62ff 0%, #62aaff 99.21%);
+ background: #454c99;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
diff --git a/apps/u3/src/components/social/farcaster/signupv2/AddAccountKey.tsx b/apps/u3/src/components/social/farcaster/signupv2/AddAccountKey.tsx
new file mode 100644
index 00000000..71b9e733
--- /dev/null
+++ b/apps/u3/src/components/social/farcaster/signupv2/AddAccountKey.tsx
@@ -0,0 +1,173 @@
+/* eslint-disable no-underscore-dangle */
+import { useCallback } from 'react';
+import { createWalletClient, custom, fromHex, toHex } from 'viem';
+import { optimism } from 'viem/chains';
+import { toast } from 'react-toastify';
+import { NobleEd25519Signer, ViemWalletEip712Signer } from '@farcaster/hub-web';
+import { useConnectModal } from '@rainbow-me/rainbowkit';
+import { useAccount, usePublicClient, useWalletClient } from 'wagmi';
+import {
+ waitForTransaction,
+ writeContract,
+ prepareWriteContract,
+} from '@wagmi/core';
+import * as ed25519 from '@noble/ed25519';
+
+import { cn } from '@/lib/utils';
+import useLogin from '@/hooks/shared/useLogin';
+
+import Title from './Title';
+import { KeyContract } from './Contract';
+import {
+ getDefaultFarcaster,
+ setDefaultFarcaster,
+} from '@/utils/social/farcaster/farcaster-default';
+
+export default function AddAccountKey({
+ fid,
+ signer,
+ setSigner,
+}: {
+ fid: number;
+ signer: NobleEd25519Signer | null;
+ setSigner: (s: NobleEd25519Signer | null) => void;
+}) {
+ const { openConnectModal } = useConnectModal();
+ const { isLogin, login } = useLogin();
+ const { address } = useAccount();
+ const account = useAccount();
+ const wallet = useWalletClient();
+
+ const publicClient = usePublicClient({
+ chainId: optimism.id,
+ });
+
+ const addSigner = useCallback(async () => {
+ if (!fid) {
+ return;
+ }
+ console.log('addSigner', fid);
+ const signerPublicKeyLocalStorageKey = `signerPublicKey-${fid}`;
+ const signerPrivateKeyLocalStorageKey = `signerPrivateKey-${fid}`;
+ const existingPrivateKey = localStorage.getItem(
+ signerPrivateKeyLocalStorageKey
+ );
+ if (existingPrivateKey) {
+ const ed25519Signer = new NobleEd25519Signer(
+ Buffer.from(existingPrivateKey, 'hex')
+ );
+ setSigner(ed25519Signer);
+ return;
+ }
+ // const privateKey = ed25519.utils.randomPrivateKey();
+ // const publicKey = toHex(ed25519.getPublicKey(privateKey));
+ const privateKey = ed25519.utils.randomPrivateKey();
+ const publicKey = toHex(await ed25519.getPublicKeyAsync(privateKey));
+
+ console.log('publicKey', publicKey);
+ console.log(
+ `Created new signer for test with private key: ${toHex(privateKey)}`
+ );
+ // const deadline = BigInt(Math.floor(Date.now() / 1000) + 60 * 60);
+ const client = createWalletClient({
+ account: wallet.data.account,
+ transport: custom((window as any).ethereum),
+ });
+ // console.log('client', client.account.address);
+ const eip712signer = new ViemWalletEip712Signer(client);
+ const metadata = await eip712signer.getSignedKeyRequestMetadata({
+ requestFid: BigInt(fid),
+ key: fromHex(publicKey, 'bytes'),
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 60), // 1 hour from now
+ });
+
+ if (metadata.isErr()) {
+ toast.error(metadata.error.message);
+ return;
+ }
+
+ const metadataHex = toHex(metadata.unwrapOr(new Uint8Array()));
+
+ try {
+ const { request: signerAddRequest } = await prepareWriteContract({
+ ...KeyContract,
+ functionName: 'add',
+ args: [1, publicKey, 1, metadataHex], // keyType, publicKey, metadataType, metadata
+ enabled: Boolean(metadata),
+ });
+
+ const signerAddTxHash = await writeContract(signerAddRequest);
+ const signerAddTxReceipt = await waitForTransaction({
+ hash: signerAddTxHash.hash,
+ chainId: optimism.id,
+ });
+ console.log('signerAddTxReceipt', signerAddTxReceipt);
+
+ if (!getDefaultFarcaster()) {
+ setDefaultFarcaster(`${fid}`);
+ }
+
+ localStorage.setItem(signerPublicKeyLocalStorageKey, publicKey);
+ localStorage.setItem(
+ signerPrivateKeyLocalStorageKey,
+ ed25519.etc.bytesToHex(privateKey)
+ );
+ const ed25519Signer = new NobleEd25519Signer(privateKey);
+ setSigner(ed25519Signer);
+ } catch (error) {
+ console.error(error);
+ toast.error(error.message);
+ }
+ }, [fid, account, publicClient]);
+
+ return (
+
+
+
+ Add a signer
+
+
+
+ {(fid && signer && (
+
+ )) || (
+
+
+ A signer is a key pair that lets you create new message or “casts”
+
+
+ {(fid && (
+
+
+
+ )) ||
+ null}
+
+ )}
+
+
+ );
+}
diff --git a/apps/u3/src/components/social/farcaster/signupv2/Contract.ts b/apps/u3/src/components/social/farcaster/signupv2/Contract.ts
new file mode 100644
index 00000000..db4368ec
--- /dev/null
+++ b/apps/u3/src/components/social/farcaster/signupv2/Contract.ts
@@ -0,0 +1,44 @@
+import {
+ ID_GATEWAY_ADDRESS,
+ idGatewayABI,
+ KEY_GATEWAY_ADDRESS,
+ keyGatewayABI,
+ ID_REGISTRY_ADDRESS,
+ idRegistryABI,
+ STORAGE_REGISTRY_ADDRESS,
+ storageRegistryABI,
+} from '@farcaster/hub-web';
+
+import { optimism } from 'viem/chains';
+import { zeroAddress } from 'viem';
+
+export const CHAIN = optimism;
+
+export const IdGateway = {
+ abi: idGatewayABI,
+ address: ID_GATEWAY_ADDRESS,
+ chain: CHAIN,
+};
+export const IdContract = {
+ abi: idRegistryABI,
+ address: ID_REGISTRY_ADDRESS,
+ chain: CHAIN,
+};
+// export const KeyContract = {
+// abi: keyRegistryABI,
+// address: KEY_REGISTRY_ADDRESS,
+// chain: CHAIN,
+// };
+export const KeyContract = {
+ abi: keyGatewayABI,
+ address: KEY_GATEWAY_ADDRESS,
+ chain: CHAIN,
+};
+
+export const RentContract = {
+ abi: storageRegistryABI,
+ address: STORAGE_REGISTRY_ADDRESS,
+ chain: CHAIN,
+};
+
+export const RECOVERY_ADDRESS = zeroAddress; // Optional, using the default value means the account will not be recoverable later if the mnemonic is lost
diff --git a/apps/u3/src/components/social/farcaster/signupv2/FnameRegister.tsx b/apps/u3/src/components/social/farcaster/signupv2/FnameRegister.tsx
new file mode 100644
index 00000000..0204427b
--- /dev/null
+++ b/apps/u3/src/components/social/farcaster/signupv2/FnameRegister.tsx
@@ -0,0 +1,180 @@
+import { useAccount, useNetwork, useWalletClient } from 'wagmi';
+import { toHex } from 'viem';
+import axios from 'axios';
+import { toast } from 'react-toastify';
+import { useConnectModal } from '@rainbow-me/rainbowkit';
+import { useCallback, useState } from 'react';
+import { NobleEd25519Signer, ViemWalletEip712Signer } from '@farcaster/hub-web';
+import { switchNetwork } from '@wagmi/core';
+import { mainnet } from 'viem/chains';
+
+import { cn } from '@/lib/utils';
+
+import Title from './Title';
+import { Input } from '@/components/ui/input';
+import useLogin from '@/hooks/shared/useLogin';
+
+export default function AddAccountKey({
+ fid,
+ fname,
+ signer,
+ setFname,
+ makePrimaryName,
+}: {
+ fid: number;
+ fname: string;
+ signer?: NobleEd25519Signer;
+ setFname: (n: string) => void;
+ makePrimaryName: (n: string) => Promise;
+}) {
+ const { openConnectModal } = useConnectModal();
+ const { isLogin, login } = useLogin();
+ const { address } = useAccount();
+ const wallet = useWalletClient();
+ const network = useNetwork();
+ const [name, setName] = useState(fname || '');
+
+ const registerFname = useCallback(async () => {
+ if (!fid) {
+ return;
+ }
+ try {
+ const response = await axios.get(
+ `https://fnames.farcaster.xyz/transfers/current?fid=${fid}`
+ );
+ const fnameExist = response.data.transfer.username;
+ if (fnameExist) {
+ console.log(`Fid ${fid} already has fname: ${fnameExist}`);
+ setFname(fnameExist);
+ return;
+ }
+ } catch (e) {
+ // console.log(e);
+ }
+
+ if (network.chain?.id !== mainnet.id) {
+ await switchNetwork({ chainId: mainnet.id });
+ }
+
+ console.log('signUserNameProofClaim', name, address, fid);
+ const eip712signer = new ViemWalletEip712Signer(wallet.data);
+ const timestamp = Math.floor(Date.now() / 1000);
+ const userNameProofSignature = await eip712signer.signUserNameProofClaim({
+ name,
+ timestamp: BigInt(timestamp),
+ owner: address,
+ });
+ if (userNameProofSignature.isErr()) {
+ toast.error(userNameProofSignature.error.message);
+ return;
+ }
+
+ const userNameProofSignatureHex = toHex(
+ userNameProofSignature.unwrapOr(new Uint8Array())
+ );
+
+ const { data } = await axios.post(
+ 'https://fnames.farcaster.xyz/transfers',
+ {
+ name, // Name to register
+ from: 0, // Fid to transfer from (0 for a new registration)
+ to: fid, // Fid to transfer to (0 to unregister)
+ fid, // Fid making the request (must match from or to)
+ owner: address, // Custody address of fid making the request
+ timestamp, // Current timestamp in seconds
+ signature: userNameProofSignatureHex,
+ }
+ );
+ toast.success('Fname registered!');
+ console.log(data);
+ setTimeout(() => {
+ makePrimaryName(name);
+ }, 1000);
+ setFname(name);
+ }, [name, address, fid, wallet, makePrimaryName, setFname]);
+
+ return (
+
+
+
+ Register an fname
+
+
+
+ {(fid && fname && (
+
+
+
Your Fname is:
+
{fname}
+
+
+
+
+
+ )) || (
+
+
+
+ Acquire a free offchain ENS username issued by Farcster.
+
+ {(fid && signer && (
+
{
+ setName(e.target.value);
+ }}
+ />
+ )) ||
+ null}
+
+
+ {(fid && signer && (
+
+ )) ||
+ null}
+
+
+ )}
+
+
+ );
+}
diff --git a/apps/u3/src/components/social/farcaster/signupv2/RegisterAndPay.tsx b/apps/u3/src/components/social/farcaster/signupv2/RegisterAndPay.tsx
new file mode 100644
index 00000000..0fabf342
--- /dev/null
+++ b/apps/u3/src/components/social/farcaster/signupv2/RegisterAndPay.tsx
@@ -0,0 +1,134 @@
+import { useCallback } from 'react';
+import { toast } from 'react-toastify';
+import { idRegistryABI } from '@farcaster/hub-web';
+import { useConnectModal } from '@rainbow-me/rainbowkit';
+import { switchNetwork, waitForTransaction, writeContract } from '@wagmi/core';
+import { useAccount, useNetwork, usePublicClient } from 'wagmi';
+import { optimism } from 'viem/chains';
+import { decodeEventLog } from 'viem';
+
+import useLogin from '@/hooks/shared/useLogin';
+import { IdContract, IdGateway, RECOVERY_ADDRESS } from './Contract';
+import Title from './Title';
+import { cn } from '@/lib/utils';
+import { shortAddress } from '@/utils/message/xmtp';
+
+export default function RegisterAndPay({
+ fid,
+ setFid,
+}: {
+ fid: number;
+ setFid: (fid: number) => void;
+}) {
+ const { openConnectModal } = useConnectModal();
+ const { address } = useAccount();
+ const { isLogin, login } = useLogin();
+ const network = useNetwork();
+ const publicClient = usePublicClient({
+ chainId: optimism.id,
+ });
+
+ const registerAndPay = useCallback(async () => {
+ if (network.chain?.id !== optimism.id) {
+ await switchNetwork({ chainId: optimism.id });
+ }
+ const balance = await publicClient.getBalance({ address });
+ console.log('balance', balance);
+ const existingFid = await publicClient.readContract({
+ ...IdContract,
+ functionName: 'idOf',
+ args: [address],
+ });
+ console.log('existingFid', existingFid);
+ if (existingFid > 0n) {
+ setFid(Number(existingFid));
+ return;
+ }
+ const price = await publicClient.readContract({
+ ...IdGateway,
+ functionName: 'price',
+ });
+ console.log('register price', price);
+ if (balance < price) {
+ toast.error('You have not enough balance.');
+ return;
+ }
+ try {
+ const { request: registerRequest } = await publicClient.simulateContract({
+ ...IdGateway,
+ functionName: 'register',
+ args: [RECOVERY_ADDRESS],
+ value: price,
+ });
+ const registerTxHash = await writeContract(registerRequest);
+ const registerTxReceipt = await waitForTransaction({
+ hash: registerTxHash.hash,
+ chainId: optimism.id,
+ });
+ console.log('registerTxReceipt', registerTxReceipt);
+ // Now extract the FID from the logs
+ const registerLog = decodeEventLog({
+ abi: idRegistryABI,
+ data: registerTxReceipt.logs[0].data,
+ topics: registerTxReceipt.logs[0].topics,
+ });
+ console.log('registerLog', registerLog);
+ const fidRegister = parseInt((registerLog.args as any).id, 10);
+ console.log('fidRegister', fidRegister);
+ setFid(fidRegister);
+ } catch (e) {
+ console.error(e);
+ toast.error(e.message);
+ }
+ // const idGateway = readContract(IdGateway);
+ }, [network, address]);
+
+ return (
+
+
+
+ Register a new Farcaster ID
+
+
+
+ {(fid && (
+
+ )) || (
+
+
+ Register a new Farcaster ID with{' '}
+ {shortAddress(address)}
+
+
+
+
+
+
+ )}
+
+
+ );
+}
diff --git a/apps/u3/src/components/social/farcaster/signupv2/RentStorage.tsx b/apps/u3/src/components/social/farcaster/signupv2/RentStorage.tsx
new file mode 100644
index 00000000..5fef4882
--- /dev/null
+++ b/apps/u3/src/components/social/farcaster/signupv2/RentStorage.tsx
@@ -0,0 +1,120 @@
+import { useCallback } from 'react';
+import { toast } from 'react-toastify';
+import { useConnectModal } from '@rainbow-me/rainbowkit';
+import {
+ prepareWriteContract,
+ switchNetwork,
+ waitForTransaction,
+ writeContract,
+} from '@wagmi/core';
+import { useAccount, useNetwork, usePublicClient } from 'wagmi';
+import { optimism } from 'viem/chains';
+
+import useLogin from '@/hooks/shared/useLogin';
+import { RentContract } from './Contract';
+import Title from './Title';
+import { cn } from '@/lib/utils';
+
+export default function RentStorage({
+ fid,
+ hasStorage,
+ setHasStorage,
+}: {
+ fid: number;
+ hasStorage: boolean;
+ setHasStorage: (h: boolean) => void;
+}) {
+ const { openConnectModal } = useConnectModal();
+ const { address } = useAccount();
+ const { isLogin, login } = useLogin();
+ const network = useNetwork();
+ const publicClient = usePublicClient({
+ chainId: optimism.id,
+ });
+
+ const rentStorage = useCallback(async () => {
+ if (network.chain?.id !== optimism.id) {
+ await switchNetwork({ chainId: optimism.id });
+ }
+ const balance = await publicClient.getBalance({ address });
+ console.log('balance', balance);
+
+ const price = await publicClient.readContract({
+ ...RentContract,
+ functionName: 'unitPrice',
+ });
+ console.log('rent price', price);
+ if (balance < price) {
+ toast.error('You have not enough balance.');
+ return;
+ }
+ try {
+ const { request: RentRequest } = await prepareWriteContract({
+ ...RentContract,
+ functionName: 'rent',
+ args: [fid, 1], // keyType, publicKey, metadataType, metadata
+ value: price,
+ enabled: Boolean(price),
+ });
+ const rentTxHash = await writeContract(RentRequest);
+ const rentTxReceipt = await waitForTransaction({
+ hash: rentTxHash.hash,
+ chainId: optimism.id,
+ });
+ console.log('registerTxReceipt', rentTxReceipt);
+ // Now extract the FID from the logs
+ setHasStorage(true);
+ } catch (e) {
+ console.error(e);
+ toast.error(e.message.split('\n')[0]);
+ }
+ }, [network, address]);
+
+ return (
+
+
+
+ Rent storage
+
+
+
+ {(fid && hasStorage && (
+
+
Your have the storage.
+
+ )) || (
+
+
Renting to store casts.
+
+ {(fid && !hasStorage && (
+
+
+
+ )) ||
+ null}
+
+ )}
+
+
+ );
+}
diff --git a/apps/u3/src/components/social/farcaster/signupv2/Title.tsx b/apps/u3/src/components/social/farcaster/signupv2/Title.tsx
new file mode 100644
index 00000000..ba9aad1e
--- /dev/null
+++ b/apps/u3/src/components/social/farcaster/signupv2/Title.tsx
@@ -0,0 +1,26 @@
+import Checked from '@/components/common/icons/checked';
+import Unchecked from '@/components/common/icons/unchecked';
+import { cn } from '@/lib/utils';
+
+export default function Title({
+ checked,
+ text,
+}: {
+ checked: boolean;
+ text: string;
+}) {
+ return (
+
+ {checked ? : }
+
+ {text}
+
+
+ );
+}
diff --git a/apps/u3/src/components/ui/dropdown-menu.tsx b/apps/u3/src/components/ui/dropdown-menu.tsx
index 434fe72c..0d0223ec 100644
--- a/apps/u3/src/components/ui/dropdown-menu.tsx
+++ b/apps/u3/src/components/ui/dropdown-menu.tsx
@@ -10,11 +10,88 @@ import {
DotFilledIcon,
} from '@radix-ui/react-icons';
+import { isMobile } from 'react-device-detect';
import { cn } from '@/lib/utils';
-const DropdownMenu = DropdownMenuPrimitive.Root;
+// see issue: https://github.com/radix-ui/primitives/issues/1912
+const TouchDeviceDropdownMenuContext = React.createContext<{
+ open: boolean;
+ setOpen: React.Dispatch>;
+}>({
+ open: false,
+ setOpen: () => {},
+});
-const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
+function useTouchDeviceDropdownMenuCtx() {
+ const contex = React.useContext(TouchDeviceDropdownMenuContext);
+ if (!contex) {
+ throw new Error(
+ 'useTouchDeviceDropdownMenuCtx must be used within a TouchDeviceDropdownMenuContext'
+ );
+ }
+ return contex;
+}
+
+// const DropdownMenu = DropdownMenuPrimitive.Root;
+const DropdownMenu = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ open: openMenu, onOpenChange, ...props }) => {
+ const [open, setOpen] = React.useState(openMenu);
+ React.useEffect(() => {
+ if (openMenu !== undefined) {
+ setOpen(openMenu);
+ }
+ }, [openMenu]);
+ return (
+ ({
+ open,
+ setOpen,
+ }),
+ [open, setOpen]
+ )}
+ >
+ {
+ if (openMenu !== undefined && onOpenChange) {
+ onOpenChange(o);
+ } else {
+ setOpen(openMenu);
+ }
+ }}
+ {...props}
+ />
+
+ );
+});
+
+// const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
+
+const DropdownMenuTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>((props, ref) => {
+ const { open, setOpen } = useTouchDeviceDropdownMenuCtx();
+ return (
+ {
+ e.preventDefault();
+ },
+ onClick: () => {
+ setOpen(!open);
+ },
+ }
+ : {})}
+ {...props}
+ />
+ );
+});
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
diff --git a/apps/u3/src/container/Asset.tsx b/apps/u3/src/container/Asset.tsx
index 2fc08a9c..325bd39b 100644
--- a/apps/u3/src/container/Asset.tsx
+++ b/apps/u3/src/container/Asset.tsx
@@ -18,7 +18,7 @@ import PageTitle from '../components/layout/PageTitle';
import MobilePageHeader from '../components/layout/mobile/MobilePageHeader';
export default function Asset() {
- const { profile } = useProfileState()!;
+ const { profile } = useProfileState();
const { wallet } = useParams();
const session = useSession();
const sessId = session?.id || '';
diff --git a/apps/u3/src/container/Gallery.tsx b/apps/u3/src/container/Gallery.tsx
index 1db67dfb..b6d97f86 100644
--- a/apps/u3/src/container/Gallery.tsx
+++ b/apps/u3/src/container/Gallery.tsx
@@ -16,7 +16,7 @@ import PageTitle from '../components/layout/PageTitle';
import MobilePageHeader from '../components/layout/mobile/MobilePageHeader';
export default function Gallery() {
- const { profile } = useProfileState()!;
+ const { profile } = useProfileState();
const { wallet } = useParams();
const session = useSession();
const sessId = session?.id;
diff --git a/apps/u3/src/container/Notification.tsx b/apps/u3/src/container/Notification.tsx
index 11bfd02a..6b5de71a 100644
--- a/apps/u3/src/container/Notification.tsx
+++ b/apps/u3/src/container/Notification.tsx
@@ -29,7 +29,7 @@ function NotificationPage() {
return (
<>
-
+
Subscrib Notifications
diff --git a/apps/u3/src/container/PosterGallery.tsx b/apps/u3/src/container/PosterGallery.tsx
new file mode 100644
index 00000000..90dd1de8
--- /dev/null
+++ b/apps/u3/src/container/PosterGallery.tsx
@@ -0,0 +1,99 @@
+import { useCallback, useEffect, useState } from 'react';
+import InfiniteScroll from 'react-infinite-scroll-component';
+import Loading from '@/components/common/loading/Loading';
+import GalleryItem from '@/components/poster/gallery/GalleryItem';
+import { fetchPosterList } from '@/services/poster/api/poster';
+import { PosterPageInfo } from '@/services/poster/types/poster';
+
+const PAGE_SIZE = 16;
+
+export default function PosterGallery() {
+ const [posters, setPosters] = useState([]);
+ const [pageInfo, setPageInfo] = useState
({
+ hasNextPage: true,
+ });
+ const [loading, setLoading] = useState(false);
+ const loadFirst = useCallback(async () => {
+ setLoading(true);
+ try {
+ const res = await fetchPosterList({
+ pageSize: PAGE_SIZE,
+ });
+ const data = res.data?.data;
+ setPageInfo(data.pageInfo);
+ setPosters(data.posters);
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+ const loadMore = useCallback(async () => {
+ if (loading) return;
+ setLoading(true);
+ try {
+ const res = await fetchPosterList({
+ pageSize: PAGE_SIZE,
+ endCursor: pageInfo.endCursor,
+ });
+ const data = res.data?.data;
+ setPageInfo(data.pageInfo);
+ setPosters((prev) => [...prev, ...data.posters]);
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setLoading(false);
+ }
+ }, [pageInfo, loading]);
+ useEffect(() => {
+ loadFirst();
+ }, [loadFirst]);
+
+ return (
+
+
+ Poster Gallery
+
+ {posters.length === 0 && loading ? (
+
+
+
+ ) : (
+
{
+ if (loading) return;
+ if (!pageInfo.hasNextPage) return;
+ loadMore();
+ }}
+ hasMore={pageInfo.hasNextPage}
+ loader={
+
+
+
+ }
+ scrollableTarget="layout-main-wrapper"
+ >
+
+ {posters.map((item) => {
+ return ;
+ })}
+
+
+ )}
+
+ );
+}
diff --git a/apps/u3/src/container/Save.tsx b/apps/u3/src/container/Save.tsx
index ddbddd34..347a8ab1 100644
--- a/apps/u3/src/container/Save.tsx
+++ b/apps/u3/src/container/Save.tsx
@@ -1,8 +1,8 @@
-import { useMemo } from 'react';
+import { useMemo, useState } from 'react';
import styled from 'styled-components';
import { usePersonalFavors } from '@us3r-network/link';
import { isMobile } from 'react-device-detect';
-
+import { uniqBy } from 'lodash';
import { MainWrapper } from '../components/layout/Index';
import Loading from '../components/common/loading/Loading';
import PageTitle from '../components/layout/PageTitle';
@@ -14,6 +14,7 @@ import {
} from '../utils/news/content';
import { getDappLinkDataWithJsonValue } from '../utils/dapp/dapp';
import { getEventLinkDataWithJsonValue } from '../utils/news/event';
+import SyncingBotSaves from '@/components/save/SyncingBotSaves';
// import { DappLinkData } from '../services/dapp/types/dapp';
// import { ContentLinkData } from '../services/news/types/contents';
// import { EventLinkData } from '../services/news/types/event';
@@ -51,43 +52,64 @@ const EmptyDesc = styled.span`
export default function Save() {
const { isFetching, personalFavors } = usePersonalFavors();
-
- const list = personalFavors
- .filter((item) => !!item?.link)
- .map((item) => {
- const { link, createAt } = item;
- let linkData;
- let title = '';
- let logo = '';
- switch (link.type) {
- case 'dapp':
- linkData = getDappLinkDataWithJsonValue(link?.data);
- title = linkData?.name || link.title;
- logo = linkData?.image || '';
- break;
- case 'content':
- linkData = getContentLinkDataWithJsonValue(link?.data);
- title = linkData?.title || link.title;
- logo =
- getContentPlatformLogoWithJsonValue(linkData?.value) ||
- linkData?.platform?.logo ||
- '';
- break;
- case 'event':
- linkData = getEventLinkDataWithJsonValue(link?.data);
- title = linkData?.name || link.title;
- logo = linkData?.image || linkData?.platform?.logo || '';
- break;
- default:
- break;
- }
- return { ...link, id: link.id, title, logo, createAt };
- });
+ const [savedLinks, setSavedLinks] = useState([]);
+ // console.log('personalFavors', personalFavors);
+ const list = useMemo(
+ () => [
+ ...savedLinks.map((item) => {
+ const createAt = item.createAt || new Date().getTime();
+ return { ...item, createAt };
+ }),
+ ...uniqBy(
+ personalFavors
+ .filter((item) => !!item?.link && item.link.type !== 'test')
+ .map((item) => {
+ const { link, createAt } = item;
+ let linkData;
+ let title = '';
+ let logo = '';
+ switch (link.type) {
+ case 'dapp':
+ linkData = getDappLinkDataWithJsonValue(link?.data);
+ title = linkData?.name || link.title;
+ logo = linkData?.image || '';
+ break;
+ case 'content':
+ linkData = getContentLinkDataWithJsonValue(link?.data);
+ title = linkData?.title || link.title;
+ logo =
+ getContentPlatformLogoWithJsonValue(linkData?.value) ||
+ linkData?.platform?.logo ||
+ '';
+ break;
+ case 'event':
+ linkData = getEventLinkDataWithJsonValue(link?.data);
+ title = linkData?.name || link.title;
+ logo = linkData?.image || linkData?.platform?.logo || '';
+ break;
+ default:
+ linkData = JSON.parse(link?.data);
+ title = linkData?.title || link.title;
+ logo = linkData?.image || '';
+ break;
+ }
+ return { ...link, id: link.id, title, logo, createAt };
+ }),
+ 'id'
+ ),
+ ],
+ [personalFavors, savedLinks]
+ );
const isEmpty = useMemo(() => list.length === 0, [list]);
-
return (
{isMobile ? null : Saves}
+ {
+ console.log('onComplete SyncingBotSaves');
+ setSavedLinks(saves);
+ }}
+ />
{isFetching ? (
diff --git a/apps/u3/src/container/news/LinkMobile.tsx b/apps/u3/src/container/news/LinkMobile.tsx
index bbcef800..763d3b37 100644
--- a/apps/u3/src/container/news/LinkMobile.tsx
+++ b/apps/u3/src/container/news/LinkMobile.tsx
@@ -43,7 +43,7 @@ export default function LinkMobile() {
) : data ? (
-
+
{/* {data.linkStreamId && } */}
) : (
diff --git a/apps/u3/src/container/social/FarcasterLayout.tsx b/apps/u3/src/container/social/FarcasterLayout.tsx
index d9456bb9..06794c9a 100644
--- a/apps/u3/src/container/social/FarcasterLayout.tsx
+++ b/apps/u3/src/container/social/FarcasterLayout.tsx
@@ -85,7 +85,6 @@ export default function FarcasterLayout() {
}, [storageCheck]);
useEffect(() => {
- console.log(`Your FID is: ${idOf as string}`);
if (idOf) {
setFid(Number(idOf));
} else if (chain?.id !== 1) {
diff --git a/apps/u3/src/container/social/FarcasterSignupV2.tsx b/apps/u3/src/container/social/FarcasterSignupV2.tsx
new file mode 100644
index 00000000..6dc3d8a5
--- /dev/null
+++ b/apps/u3/src/container/social/FarcasterSignupV2.tsx
@@ -0,0 +1,91 @@
+import { toast } from 'react-toastify';
+import { useNavigate, useOutletContext } from 'react-router-dom';
+import { useCallback } from 'react';
+import { UserDataType, makeUserDataAdd } from '@farcaster/hub-web';
+
+import RegisterAndPay from '@/components/social/farcaster/signupv2/RegisterAndPay';
+import AddAccountKey from '@/components/social/farcaster/signupv2/AddAccountKey';
+import FnameRegister from '@/components/social/farcaster/signupv2/FnameRegister';
+import { FARCASTER_NETWORK, FARCASTER_WEB_CLIENT } from '@/constants/farcaster';
+import RentStorage from '@/components/social/farcaster/signupv2/RentStorage';
+import { ChevronRightDouble } from '@/components/common/icons/chevon-right-double';
+import ColorButton from '@/components/common/button/ColorButton';
+
+export default function FarcasterSignupV2() {
+ const {
+ fid,
+ fname,
+ signer,
+ hasStorage,
+ setHasStorage,
+ setSigner,
+ setFid,
+ setFname,
+ } = useOutletContext();
+ const navigate = useNavigate();
+
+ const makePrimaryName = useCallback(
+ async (name: string) => {
+ if (!name || !signer || !fid || !hasStorage) return;
+ try {
+ // eslint-disable-next-line no-underscore-dangle
+ const cast = (
+ await makeUserDataAdd(
+ {
+ type: UserDataType.USERNAME,
+ value: name,
+ },
+ { fid, network: FARCASTER_NETWORK },
+ signer
+ )
+ )._unsafeUnwrap();
+ const result = await FARCASTER_WEB_CLIENT.submitMessage(cast);
+ if (result.isErr()) {
+ throw new Error(result.error.message);
+ }
+ toast.success('successfully primary name to farcaster');
+ } catch (error: unknown) {
+ console.error(error);
+ toast.error('failed to primary name');
+ }
+ },
+ [fid, signer, hasStorage]
+ );
+ return (
+
+
+ Sign up for Farcaster
+
+
+
+ {(fid && fname && signer && hasStorage && (
+ {
+ navigate('/farcaster/profile');
+ }}
+ >
+ Setup your profile
+
+
+ )) ||
+ null}
+
+
+ );
+}
diff --git a/apps/u3/src/container/social/SocialLayout.tsx b/apps/u3/src/container/social/SocialLayout.tsx
index b15ba621..b5e52d1d 100644
--- a/apps/u3/src/container/social/SocialLayout.tsx
+++ b/apps/u3/src/container/social/SocialLayout.tsx
@@ -8,7 +8,6 @@ import {
} from 'react-router-dom';
import { isMobile } from 'react-device-detect';
-import PinedChannels from 'src/components/social/PinedChannels';
import useChannelFeeds from 'src/hooks/social/useChannelFeeds';
import { resetFarcasterFollowingData } from 'src/hooks/social/farcaster/useFarcasterFollowing';
import { resetAllFollowingData } from 'src/hooks/social/useAllFollowing';
@@ -27,6 +26,7 @@ import SocialWhoToFollow from '../../components/social/SocialWhoToFollow';
import TrendChannel from '../../components/social/farcaster/TrendChannel';
import { LivepeerProvider } from '../../contexts/social/LivepeerCtx';
import { AllFirst } from './SocialAllFollowing';
+import { cn } from '@/lib/utils';
export default function SocialLayoutContainer() {
return (
@@ -98,11 +98,16 @@ function SocialLayout() {
-
)}
-
+
-
+
{!isMobile && (
@@ -138,11 +143,6 @@ function SocialLayout() {
)}
- {isMobile && (
-
-
-
- )}
);
}
@@ -197,12 +197,6 @@ const MainLeft = styled.div`
const MainRight = styled.div`
width: 350px;
`;
-const MainCenter = styled.div`
- width: 600px;
- margin-top: 20px;
- box-sizing: border-box;
- height: fit-content;
-`;
const MainOutletWrapper = styled.div`
width: 100%;
max-width: 600px;
@@ -237,8 +231,3 @@ const RightWrapper = styled(MainRight)`
gap: 20px;
}
`;
-const AddPostButtonWraper = styled.div`
- position: fixed;
- right: 20px;
- bottom: 60px;
-`;
diff --git a/apps/u3/src/contexts/U3LoginContext.tsx b/apps/u3/src/contexts/U3LoginContext.tsx
index 20669185..cedc4c16 100644
--- a/apps/u3/src/contexts/U3LoginContext.tsx
+++ b/apps/u3/src/contexts/U3LoginContext.tsx
@@ -18,7 +18,10 @@ import {
setU3ExtensionCookie,
UserAdaptationCookie,
} from '../utils/shared/cookie';
-import { removeHomeBannerHiddenFromStore } from '../utils/shared/homeStore';
+import {
+ removeFarcasterInfoFromStore,
+ removeHomeBannerHiddenFromStore,
+} from '../utils/shared/homeStore';
import { getAddressWithDidPkh } from '../utils/shared/did';
interface U3LoginContextValue {
@@ -73,6 +76,7 @@ export default function U3LoginProvider({
// u3退出登录
const u3logout = useCallback(() => {
setUser(null);
+ removeFarcasterInfoFromStore();
removeHomeBannerHiddenFromStore();
removeU3ExtensionCookie();
needU3Login = true;
diff --git a/apps/u3/src/contexts/social/FarcasterCtx.tsx b/apps/u3/src/contexts/social/FarcasterCtx.tsx
index b0a7fd31..3769774b 100644
--- a/apps/u3/src/contexts/social/FarcasterCtx.tsx
+++ b/apps/u3/src/contexts/social/FarcasterCtx.tsx
@@ -108,6 +108,7 @@ export interface FarcasterContextData {
getChannelFromUrl: (url: string) => FarcasterChannel | null;
openPostModal: boolean;
setOpenPostModal: React.Dispatch>;
+ setOpenModalName: React.Dispatch>;
}
const FarcasterContext = createContext(null);
@@ -176,6 +177,7 @@ export default function FarcasterProvider({
setWarpcastErr,
qrCheckStatus,
qrUserData,
+ deepLinkUrl,
} = useFarcasterQR();
const [openModalName, setOpenModalName] = useState('');
const [openPostModal, setOpenPostModal] = useState(false);
@@ -242,7 +244,9 @@ export default function FarcasterProvider({
}, [walletFid, walletSigner, walletUserData]);
useEffect(() => {
- if (!walletCheckStatus || !qrCheckStatus) return;
+ if (!walletCheckStatus && !qrCheckStatus) {
+ return;
+ }
const defaultFid = getDefaultFarcaster();
if (!defaultFid) {
return;
@@ -281,7 +285,7 @@ export default function FarcasterProvider({
}, [session, profile, signer, currFid, currUserInfo]);
useHotkeys(
- 'meta+p',
+ 'meta+p,ctrl+p',
(e) => {
e.preventDefault();
if (!isLogin) {
@@ -290,15 +294,17 @@ export default function FarcasterProvider({
}
setOpenPostModal(true);
},
+ { preventDefault: true },
[isLogin]
);
useHotkeys(
- 'meta+k',
+ 'meta+k,ctrl+k',
(e) => {
e.preventDefault();
setOpenModalName(QuickSearchModalName);
},
+ { preventDefault: true },
[]
);
@@ -345,12 +351,14 @@ export default function FarcasterProvider({
getChannelFromUrl,
openPostModal,
setOpenPostModal,
+ setOpenModalName,
}}
>
{children}
{
setWarpcastErr('');
@@ -394,7 +402,7 @@ export default function FarcasterProvider({
}}
registerAction={() => {
setSignerSelectModalOpen(false);
- navigate('/farcaster/signup');
+ navigate('/farcaster/signupv2');
}}
selectType={selectType}
setSelectType={(type: string) => {
diff --git a/apps/u3/src/hooks/poster/useCasterCollection.tsx b/apps/u3/src/hooks/poster/useCasterCollection.tsx
deleted file mode 100644
index f7315904..00000000
--- a/apps/u3/src/hooks/poster/useCasterCollection.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import { useMemo } from 'react';
-import { useContractRead } from 'wagmi';
-import dayjs from 'dayjs';
-import {
- casterZora1155ToMintAddress,
- casterZoraFixedPriceStrategyAddress,
- ZoraCreator1155ImplAbi,
- ZoraCreatorFixedPriceSaleStrategyAbi,
-} from '../../constants/zora';
-
-function isToday(timestamp) {
- const now = dayjs();
- const today = now.startOf('day');
- const tomorrow = now.add(1, 'day').startOf('day');
-
- return timestamp >= today && timestamp < tomorrow;
-}
-
-export type TokenInfo = {
- uri: string;
- maxSupply: number;
- totalMinted: number;
-};
-
-export default function useCasterCollection({
- owner,
-}: {
- owner: string | null;
-}) {
- const { data: collectionName } = useContractRead({
- address: casterZora1155ToMintAddress,
- abi: ZoraCreator1155ImplAbi,
- functionName: 'name',
- });
-
- const { data: nextTokenId } = useContractRead({
- address: casterZora1155ToMintAddress,
- abi: ZoraCreator1155ImplAbi,
- functionName: 'nextTokenId',
- });
-
- const lastTokenId = nextTokenId ? Number(nextTokenId) - 1 : 0;
-
- const { data: contractBaseId } = useContractRead({
- address: casterZora1155ToMintAddress,
- abi: ZoraCreator1155ImplAbi,
- functionName: 'CONTRACT_BASE_ID',
- });
-
- const { data: permissionBitAdmin } = useContractRead({
- address: casterZora1155ToMintAddress,
- abi: ZoraCreator1155ImplAbi,
- functionName: 'PERMISSION_BIT_ADMIN',
- });
-
- const { data: isAdmin } = useContractRead({
- address: casterZora1155ToMintAddress,
- abi: ZoraCreator1155ImplAbi,
- functionName: 'isAdminOrRole',
- args: [owner, contractBaseId, permissionBitAdmin],
- });
-
- const { data: lastTokenInfo } = useContractRead({
- address: casterZora1155ToMintAddress,
- abi: ZoraCreator1155ImplAbi,
- functionName: 'getTokenInfo',
- args: [lastTokenId],
- });
-
- const { data: sale } = useContractRead({
- address: casterZoraFixedPriceStrategyAddress,
- abi: ZoraCreatorFixedPriceSaleStrategyAbi,
- functionName: 'sale',
- args: [casterZora1155ToMintAddress, lastTokenId],
- });
- const { saleStart } = (sale || {}) as { saleStart: bigint };
-
- const lastTokenFromToday = useMemo(
- () => lastTokenId > 0 && saleStart && isToday(Number(saleStart) * 1000),
- [lastTokenId, saleStart]
- );
-
- const { data: balanceOf } = useContractRead({
- address: casterZora1155ToMintAddress,
- abi: ZoraCreator1155ImplAbi,
- functionName: 'balanceOf',
- args: [owner, lastTokenId],
- });
- const ownerMinted = useMemo(
- () => balanceOf && Number(balanceOf) === 1,
- [balanceOf]
- );
-
- return {
- collectionName,
- isAdmin,
- lastTokenId,
- lastTokenInfo: lastTokenInfo as TokenInfo,
- lastTokenFromToday,
- ownerMinted,
- };
-}
diff --git a/apps/u3/src/hooks/poster/useCasterLastTokenInfo.ts b/apps/u3/src/hooks/poster/useCasterLastTokenInfo.ts
new file mode 100644
index 00000000..dfb720c2
--- /dev/null
+++ b/apps/u3/src/hooks/poster/useCasterLastTokenInfo.ts
@@ -0,0 +1,37 @@
+import { useEffect, useState } from 'react';
+import { createPublicClient, http } from 'viem';
+import {
+ casterZora1155ToMintAddress,
+ casterZoraNetwork,
+ ZoraCreator1155ImplAbi,
+} from '../../constants/zora';
+import useCasterTokenInfoWithTokenId from './useCasterTokenInfoWithTokenId';
+
+const publicClient = createPublicClient({
+ chain: casterZoraNetwork,
+ transport: http(),
+});
+
+export default function useCasterLastTokenInfo() {
+ const [lastTokenId, setLastTokenId] = useState(0);
+ useEffect(() => {
+ const readNextTokenId = async () => {
+ const res = await publicClient.readContract({
+ address: casterZora1155ToMintAddress,
+ abi: ZoraCreator1155ImplAbi,
+ functionName: 'nextTokenId',
+ });
+ setLastTokenId(res ? Number(res) - 1 : 0);
+ };
+ readNextTokenId();
+ }, []);
+
+ const lastTokenInfo = useCasterTokenInfoWithTokenId({
+ tokenId: lastTokenId,
+ });
+
+ return {
+ ...lastTokenInfo,
+ lastTokenId,
+ };
+}
diff --git a/apps/u3/src/hooks/poster/useCasterOwnerInfoWithTokenId.ts b/apps/u3/src/hooks/poster/useCasterOwnerInfoWithTokenId.ts
new file mode 100644
index 00000000..1ffb8d98
--- /dev/null
+++ b/apps/u3/src/hooks/poster/useCasterOwnerInfoWithTokenId.ts
@@ -0,0 +1,80 @@
+import { useEffect, useState } from 'react';
+import { createPublicClient, http } from 'viem';
+import {
+ casterZora1155ToMintAddress,
+ casterZoraNetwork,
+ ZoraCreator1155ImplAbi,
+} from '../../constants/zora';
+
+const publicClient = createPublicClient({
+ chain: casterZoraNetwork,
+ transport: http(),
+});
+
+export default function useCasterOwnerInfoWithTokenId({
+ tokenId,
+ ownerAddress,
+}: {
+ tokenId: number;
+ ownerAddress: string;
+}) {
+ const [isAdmin, setIsAdmin] = useState(false);
+ const [isMinted, setIsMinted] = useState(false);
+ const [isMintedLoading, setIsMintedLoading] = useState(false);
+
+ useEffect(() => {
+ const readIsAdmin = async () => {
+ const contractBaseId = await publicClient.readContract({
+ address: casterZora1155ToMintAddress,
+ abi: ZoraCreator1155ImplAbi,
+ functionName: 'CONTRACT_BASE_ID',
+ });
+
+ const permissionBitAdmin = await publicClient.readContract({
+ address: casterZora1155ToMintAddress,
+ abi: ZoraCreator1155ImplAbi,
+ functionName: 'PERMISSION_BIT_ADMIN',
+ });
+
+ const res = await publicClient.readContract({
+ address: casterZora1155ToMintAddress,
+ abi: ZoraCreator1155ImplAbi,
+ functionName: 'isAdminOrRole',
+ args: [ownerAddress, contractBaseId, permissionBitAdmin],
+ });
+ setIsAdmin(res as boolean);
+ };
+ if (ownerAddress) {
+ readIsAdmin();
+ }
+ }, [ownerAddress]);
+
+ useEffect(() => {
+ const readIsMinted = async () => {
+ try {
+ setIsMintedLoading(true);
+ const res = await publicClient.readContract({
+ address: casterZora1155ToMintAddress,
+ abi: ZoraCreator1155ImplAbi,
+ functionName: 'balanceOf',
+ args: [ownerAddress, tokenId],
+ });
+ const minted = res && Number(res) === 1;
+ setIsMinted(minted);
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setIsMintedLoading(false);
+ }
+ };
+ if (tokenId && ownerAddress) {
+ readIsMinted();
+ }
+ }, [ownerAddress, tokenId]);
+
+ return {
+ isAdmin,
+ isMinted,
+ isMintedLoading,
+ };
+}
diff --git a/apps/u3/src/hooks/poster/useCasterTokenInfoWithTokenId.ts b/apps/u3/src/hooks/poster/useCasterTokenInfoWithTokenId.ts
new file mode 100644
index 00000000..cb442356
--- /dev/null
+++ b/apps/u3/src/hooks/poster/useCasterTokenInfoWithTokenId.ts
@@ -0,0 +1,106 @@
+import { useEffect, useMemo, useState } from 'react';
+import { createPublicClient, http } from 'viem';
+import { ZDK } from '@zoralabs/zdk';
+import { Token } from '@zoralabs/zdk/dist/queries/queries-sdk';
+import {
+ casterZora1155ToMintAddress,
+ casterZoraFixedPriceStrategyAddress,
+ casterZoraNetwork,
+ casterZoraNetworkInfo,
+ ZORA_API_ENDPOINT,
+ ZoraCreator1155ImplAbi,
+ ZoraCreatorFixedPriceSaleStrategyAbi,
+} from '../../constants/zora';
+import { getSaleStatus } from '@/utils/shared/zora';
+
+const args = {
+ endPoint: ZORA_API_ENDPOINT,
+ networks: [casterZoraNetworkInfo],
+};
+const zdk = new ZDK(args);
+
+const publicClient = createPublicClient({
+ chain: casterZoraNetwork,
+ transport: http(),
+});
+
+export type Sale = {
+ saleStart: bigint;
+ saleEnd: bigint;
+};
+
+export type TokenInfo = Token &
+ Sale & {
+ uri: string;
+ maxSupply: number;
+ totalMinted: number;
+ };
+
+export enum SaleStatus {
+ Unknown = -1,
+ NotStarted = 0,
+ InProgress = 1,
+ Ended = 2,
+}
+
+export default function useCasterTokenInfoWithTokenId({
+ tokenId,
+}: {
+ tokenId: number;
+}) {
+ const [tokenInfo, setTokenInfo] = useState({
+ uri: '',
+ maxSupply: 0,
+ totalMinted: 0,
+ } as TokenInfo);
+ useEffect(() => {
+ const readTokenInfo = async () => {
+ const res = await publicClient.readContract({
+ address: casterZora1155ToMintAddress,
+ abi: ZoraCreator1155ImplAbi,
+ functionName: 'getTokenInfo',
+ args: [tokenId],
+ });
+ setTokenInfo(res as TokenInfo);
+ };
+ const readSale = async () => {
+ const res = await publicClient.readContract({
+ address: casterZoraFixedPriceStrategyAddress,
+ abi: ZoraCreatorFixedPriceSaleStrategyAbi,
+ functionName: 'sale',
+ args: [casterZora1155ToMintAddress, tokenId],
+ });
+ setTokenInfo((pre) => ({ ...pre, ...(res as Sale) }));
+ };
+ const readFirstMint = async () => {
+ const res = await zdk.token({
+ token: {
+ address: casterZora1155ToMintAddress,
+ tokenId: String(tokenId),
+ },
+ includeFullDetails: true,
+ });
+ setTokenInfo((pre) => ({
+ ...pre,
+ ...((res.token?.token || {}) as Token),
+ }));
+ };
+ if (tokenId) {
+ readTokenInfo();
+ readSale();
+ readFirstMint();
+ }
+ }, [tokenId]);
+
+ const { saleStart, saleEnd } = tokenInfo;
+
+ const saleStatus = useMemo(
+ () => getSaleStatus(Number(saleStart), Number(saleEnd)),
+ [saleStart, saleEnd]
+ );
+
+ return {
+ ...tokenInfo,
+ saleStatus,
+ };
+}
diff --git a/apps/u3/src/hooks/social/farcaster/useFarcasterQR.ts b/apps/u3/src/hooks/social/farcaster/useFarcasterQR.ts
index 9ea4920f..7bd9300f 100644
--- a/apps/u3/src/hooks/social/farcaster/useFarcasterQR.ts
+++ b/apps/u3/src/hooks/social/farcaster/useFarcasterQR.ts
@@ -3,7 +3,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import axios from 'axios';
import { toast } from 'react-toastify';
-import { isMobile } from 'react-device-detect';
import {
generateKeyPair,
@@ -28,6 +27,10 @@ import {
BIOLINK_FARCASTER_NETWORK,
BIOLINK_PLATFORMS,
} from 'src/utils/profile/biolink';
+import {
+ getDefaultFarcaster,
+ setDefaultFarcaster,
+} from '@/utils/social/farcaster/farcaster-default';
const stopSign = {
stop: false,
@@ -79,6 +82,7 @@ export default function useFarcasterQR() {
token: '',
deepLink: '',
});
+ const [deepLinkUrl, setDeepLinkUrl] = useState(null);
const [mounted, setMounted] = useState(false);
const [qrCheckStatus, setQrCheckStatus] = useState('');
const [qrUserData, setQrUserData] = useState<
@@ -112,6 +116,9 @@ export default function useFarcasterQR() {
if (signedKeyRequest.state === 'completed') {
setSignedKeyRequest(signedKeyRequest);
+ if (!getDefaultFarcaster()) {
+ setDefaultFarcaster(`${signedKeyRequest.userFid}`);
+ }
restoreFromQRcode();
signerSuccess = true;
// wirte to u3 db
@@ -178,11 +185,9 @@ export default function useFarcasterQR() {
setPrivateKey(keyPair.privateKey);
pollForSigner(token, keyPair);
setToken({ token, deepLink: deeplinkUrl });
- if (isMobile) {
- window.open(deeplinkUrl, '_blank');
- } else {
- setShowQR(true);
- }
+
+ setDeepLinkUrl(deeplinkUrl);
+ setShowQR(true);
}, [pollForSigner]);
const restoreFromQRcode = useCallback(async () => {
@@ -211,6 +216,9 @@ export default function useFarcasterQR() {
setPrivateKey(farsignBiolinkData.privateKey);
setSignedKeyRequest(farsignBiolinkData.signedKeyRequest);
signedKeyRequest = farsignBiolinkData.signedKeyRequest;
+ // console.log('get qrcode signer from db: ', signedKeyRequest);
+ if (signedKeyRequest.userFid)
+ setDefaultFarcaster(signedKeyRequest.userFid);
}
}
} else {
@@ -271,5 +279,6 @@ export default function useFarcasterQR() {
openQRModal,
qrCheckStatus,
qrUserData,
+ deepLinkUrl,
};
}
diff --git a/apps/u3/src/hooks/social/farcaster/useFarcasterWallet.ts b/apps/u3/src/hooks/social/farcaster/useFarcasterWallet.ts
index bdb064b0..138d3e45 100644
--- a/apps/u3/src/hooks/social/farcaster/useFarcasterWallet.ts
+++ b/apps/u3/src/hooks/social/farcaster/useFarcasterWallet.ts
@@ -23,6 +23,11 @@ import {
} from 'src/utils/profile/biolink';
import { FarcasterBioLinkData } from './useFarcasterQR';
+import { IdContract } from '@/components/social/farcaster/signupv2/Contract';
+import {
+ getDefaultFarcaster,
+ setDefaultFarcaster,
+} from '@/utils/social/farcaster/farcaster-default';
const opPublicClient = createPublicClient({
chain: optimism,
@@ -75,6 +80,12 @@ export default function useFarcasterWallet() {
`signerPublicKey-${fid}`,
farcasterBiolinkData.publicKey
);
+ // console.log(
+ // 'get wallet signer from db: ',
+ // fid,
+ // getDefaultFarcaster()
+ // );
+ if (!getDefaultFarcaster() && fid) setDefaultFarcaster(`${fid}`);
privateKey = farcasterBiolinkData.privateKey;
}
}
@@ -92,6 +103,7 @@ export default function useFarcasterWallet() {
if (!fid) return;
try {
const resp = await getFnames(fid);
+ console.log('fnameCheck: ', resp.data);
if (resp.data.code !== 0) return;
return resp.data.data.username;
} catch (error) {
@@ -105,6 +117,7 @@ export default function useFarcasterWallet() {
const resp = await axios.get(
`https://api.farcaster.u3.xyz/v1/storageLimitsByFid?fid=${fid}`
);
+ console.log('storageCheck: ', resp.data);
if (resp.data.limits?.length > 0) {
return Boolean(resp.data.limits?.[0].limit); // mainnet
}
@@ -122,32 +135,37 @@ export default function useFarcasterWallet() {
};
const getWalletFid = useCallback(async () => {
- const data = await opPublicClient.readContract({
- address: IdRegistryContract,
- abi: IdRegistryABI,
- functionName: 'idOf',
- args: [address],
- });
- if (!data) {
- setWalletCheckStatus('done');
- return;
- }
- const fid = Number(data);
- const checkResult = await Promise.all([
- signerCheck(fid),
- fnameCheck(fid),
- storageCheck(fid),
- ]);
- if (!checkResult[0] || !checkResult[1] || !checkResult[2]) {
+ try {
+ const data = await opPublicClient.readContract({
+ ...IdContract,
+ functionName: 'idOf',
+ args: [address],
+ });
+ console.log('wallet fid: ', data);
+ if (!data) {
+ setWalletCheckStatus('done');
+ return;
+ }
+ const fid = Number(data);
+ const checkResult = await Promise.all([
+ signerCheck(fid),
+ fnameCheck(fid),
+ storageCheck(fid),
+ ]);
+ if (!checkResult[0] || !checkResult[1] || !checkResult[2]) {
+ setWalletCheckStatus('done');
+ return;
+ }
+
+ setWalletFid(fid);
+ setWalletSigner(checkResult[0]);
+ setFname(checkResult[1]);
+ setHasStorage(checkResult[2]);
+ getCurrUserInfo(fid);
+ } catch (error) {
+ console.error(error);
setWalletCheckStatus('done');
- return;
}
-
- setWalletFid(fid);
- setWalletSigner(checkResult[0]);
- setFname(checkResult[1]);
- setHasStorage(checkResult[2]);
- getCurrUserInfo(fid);
}, [address]);
useEffect(() => {
diff --git a/apps/u3/src/route/nav.tsx b/apps/u3/src/route/nav.tsx
index fe2fbf2e..88ad0d4f 100644
--- a/apps/u3/src/route/nav.tsx
+++ b/apps/u3/src/route/nav.tsx
@@ -27,14 +27,14 @@ export type CustomNavObject = {
route?: CutomRouteObject;
component?: JSX.Element;
};
-export const navs: CustomNavObject[] = [
- {
+const navMap = {
+ explore: {
name: 'Explore',
activeRouteKeys: [RouteKey.home],
icon: React.createElement(CompassSvg),
route: getRoute(RouteKey.home),
},
- {
+ news: {
name: 'Apps',
activeRouteKeys: [
RouteKey.newsLayout,
@@ -51,7 +51,7 @@ export const navs: CustomNavObject[] = [
icon: React.createElement(NewsSvg),
route: getRoute(RouteKey.newsLayout),
},
- {
+ social: {
name: 'Social',
activeRouteKeys: [
RouteKey.social,
@@ -62,7 +62,7 @@ export const navs: CustomNavObject[] = [
icon: React.createElement(SocialSvg),
route: getRoute(RouteKey.socialLayout),
},
- {
+ notifications: {
name: 'notifications',
activeRouteKeys: [RouteKey.notification],
component: isMobile ? (
@@ -71,27 +71,36 @@ export const navs: CustomNavObject[] = [
),
},
+ apps: {
+ name: 'Apps',
+ activeRouteKeys: [RouteKey.dappStore, RouteKey.dapp],
+ icon: React.createElement(DappSvg),
+ route: getRoute(RouteKey.dappStore),
+ },
+ save: {
+ name: 'Save',
+ activeRouteKeys: [RouteKey.save],
+ icon: React.createElement(BookmarkSvg),
+ route: getRoute(RouteKey.save),
+ },
+};
+export const navs: CustomNavObject[] = [
...(isMobile
? [
- {
- name: 'Apps',
- activeRouteKeys: [RouteKey.dappStore, RouteKey.dapp],
- icon: React.createElement(DappSvg),
- route: getRoute(RouteKey.dappStore),
- },
+ navMap.explore,
+ navMap.social,
+ navMap.news,
+ navMap.notifications,
+ navMap.apps,
+ navMap.save,
]
- : []),
- ...(isMobile
- ? []
: [
- {
- name: 'Save',
- activeRouteKeys: [RouteKey.save],
- icon: React.createElement(BookmarkSvg),
- route: getRoute(RouteKey.save),
- },
+ navMap.explore,
+ navMap.news,
+ navMap.social,
+ navMap.notifications,
+ navMap.save,
]),
-
// {
// name: 'profile',
// activeRouteKeys: [RouteKey.profile],
diff --git a/apps/u3/src/route/routes.tsx b/apps/u3/src/route/routes.tsx
index cf43f6b9..dad53403 100644
--- a/apps/u3/src/route/routes.tsx
+++ b/apps/u3/src/route/routes.tsx
@@ -13,6 +13,8 @@ import LoadableFallback from '../components/layout/LoadableFallback';
export enum RouteKey {
home = 'home',
+ // poster
+ posterGallery = 'posterGallery',
// profile
profile = 'profile',
profileByUser = 'profileByUser',
@@ -102,6 +104,13 @@ export const routes: CutomRouteObject[] = [
key: RouteKey.home,
title: 'Explore',
},
+ // poster
+ {
+ path: '/poster-gallery',
+ element: loadContainerElement('PosterGallery'),
+ key: RouteKey.posterGallery,
+ title: 'Poster Gallery',
+ },
// profile
{
path: '/u',
@@ -325,8 +334,8 @@ export const routes: CutomRouteObject[] = [
key: RouteKey.farcasterData,
},
{
- path: 'signup',
- element: loadContainerElement('social/FarcasterSignup'),
+ path: 'signupv2',
+ element: loadContainerElement('social/FarcasterSignupV2'),
key: RouteKey.farcasterSignup,
} as CutomRouteObject,
{
diff --git a/apps/u3/src/route/svgs/compass.svg b/apps/u3/src/route/svgs/compass.svg
index 2180a28c..0374b6ba 100644
--- a/apps/u3/src/route/svgs/compass.svg
+++ b/apps/u3/src/route/svgs/compass.svg
@@ -1,4 +1,4 @@
diff --git a/apps/u3/src/services/news/api/contents.ts b/apps/u3/src/services/news/api/contents.ts
index 0bf90827..c3c10e88 100644
--- a/apps/u3/src/services/news/api/contents.ts
+++ b/apps/u3/src/services/news/api/contents.ts
@@ -100,9 +100,11 @@ export function updateContent(
}
export function contentParse(url: string): RequestPromise {
- return request({
- url: `/contents/parser?url=${url}`,
- });
+ // return request({
+ // url: `/contents/parser?url=${url}`,
+ // });
+ // parse api is deprecated
+ return null;
}
export function favorsContent(id: number, token: string) {
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/poster/api/poster.ts b/apps/u3/src/services/poster/api/poster.ts
new file mode 100644
index 00000000..8ae05a58
--- /dev/null
+++ b/apps/u3/src/services/poster/api/poster.ts
@@ -0,0 +1,21 @@
+import { PosterEntity, PosterPageInfo } from '../types/poster';
+import request, { RequestPromise } from '../../shared/api/request';
+import { ApiResp } from '@/services/shared/types';
+
+export type FetchPosterListParams = {
+ pageSize?: number;
+ endCursor?: number;
+};
+export type FetchPosterListResponse = {
+ posters: PosterEntity[];
+ pageInfo: PosterPageInfo;
+};
+export function fetchPosterList(
+ params: FetchPosterListParams
+): RequestPromise> {
+ return request({
+ url: `/posters`,
+ method: 'get',
+ params,
+ });
+}
diff --git a/apps/u3/src/services/poster/types/poster.ts b/apps/u3/src/services/poster/types/poster.ts
new file mode 100644
index 00000000..deeb2f43
--- /dev/null
+++ b/apps/u3/src/services/poster/types/poster.ts
@@ -0,0 +1,29 @@
+export type PosterEntity = {
+ id: number;
+ createdAt: Date;
+ updatedAt: Date;
+ metadata: string;
+ tokenId?: number;
+ chainId?: number;
+ tokenContract?: string;
+ priceStrategyContract?: string;
+ saleStart?: string;
+ saleEnd?: string;
+};
+
+export type PosterMetadata = {
+ name: string;
+ description: string;
+ external_url: string;
+ properties: {
+ imageOriginUrl: string;
+ url: string;
+ posterDataJson: string;
+ createAt: Date;
+ };
+};
+
+export type PosterPageInfo = {
+ endCursor?: number;
+ hasNextPage: boolean;
+};
diff --git a/apps/u3/src/services/social/abi/degen/contract.ts b/apps/u3/src/services/social/abi/degen/contract.ts
new file mode 100644
index 00000000..2ddbcd22
--- /dev/null
+++ b/apps/u3/src/services/social/abi/degen/contract.ts
@@ -0,0 +1,604 @@
+export const DegenAddress = '0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed';
+export const DegenABI = [
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: 'mintingAllowedAfter_',
+ type: 'uint256',
+ },
+ ],
+ stateMutability: 'nonpayable',
+ type: 'constructor',
+ },
+ { inputs: [], name: 'CheckpointUnorderedInsertion', type: 'error' },
+ { inputs: [], name: 'DegenMintCapExceeded', type: 'error' },
+ { inputs: [], name: 'ECDSAInvalidSignature', type: 'error' },
+ {
+ inputs: [{ internalType: 'uint256', name: 'length', type: 'uint256' }],
+ name: 'ECDSAInvalidSignatureLength',
+ type: 'error',
+ },
+ {
+ inputs: [{ internalType: 'bytes32', name: 's', type: 'bytes32' }],
+ name: 'ECDSAInvalidSignatureS',
+ type: 'error',
+ },
+ {
+ inputs: [
+ { internalType: 'uint256', name: 'increasedSupply', type: 'uint256' },
+ { internalType: 'uint256', name: 'cap', type: 'uint256' },
+ ],
+ name: 'ERC20ExceededSafeSupply',
+ type: 'error',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'spender', type: 'address' },
+ { internalType: 'uint256', name: 'allowance', type: 'uint256' },
+ { internalType: 'uint256', name: 'needed', type: 'uint256' },
+ ],
+ name: 'ERC20InsufficientAllowance',
+ type: 'error',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'sender', type: 'address' },
+ { internalType: 'uint256', name: 'balance', type: 'uint256' },
+ { internalType: 'uint256', name: 'needed', type: 'uint256' },
+ ],
+ name: 'ERC20InsufficientBalance',
+ type: 'error',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'approver', type: 'address' }],
+ name: 'ERC20InvalidApprover',
+ type: 'error',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'receiver', type: 'address' }],
+ name: 'ERC20InvalidReceiver',
+ type: 'error',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'sender', type: 'address' }],
+ name: 'ERC20InvalidSender',
+ type: 'error',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'spender', type: 'address' }],
+ name: 'ERC20InvalidSpender',
+ type: 'error',
+ },
+ {
+ inputs: [{ internalType: 'uint256', name: 'deadline', type: 'uint256' }],
+ name: 'ERC2612ExpiredSignature',
+ type: 'error',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'signer', type: 'address' },
+ { internalType: 'address', name: 'owner', type: 'address' },
+ ],
+ name: 'ERC2612InvalidSigner',
+ type: 'error',
+ },
+ {
+ inputs: [
+ { internalType: 'uint256', name: 'timepoint', type: 'uint256' },
+ { internalType: 'uint48', name: 'clock', type: 'uint48' },
+ ],
+ name: 'ERC5805FutureLookup',
+ type: 'error',
+ },
+ { inputs: [], name: 'ERC6372InconsistentClock', type: 'error' },
+ { inputs: [], name: 'EnforcedPause', type: 'error' },
+ { inputs: [], name: 'ExpectedPause', type: 'error' },
+ {
+ inputs: [
+ { internalType: 'address', name: 'account', type: 'address' },
+ { internalType: 'uint256', name: 'currentNonce', type: 'uint256' },
+ ],
+ name: 'InvalidAccountNonce',
+ type: 'error',
+ },
+ { inputs: [], name: 'InvalidShortString', type: 'error' },
+ {
+ inputs: [
+ { internalType: 'uint256', name: 'blockTimestamp', type: 'uint256' },
+ { internalType: 'uint256', name: 'mintingAllowedAfter', type: 'uint256' },
+ ],
+ name: 'MintAllowedAfterDeployOnly',
+ type: 'error',
+ },
+ { inputs: [], name: 'MintToZeroAddressBlocked', type: 'error' },
+ { inputs: [], name: 'MintingDateNotReached', type: 'error' },
+ {
+ inputs: [{ internalType: 'address', name: 'owner', type: 'address' }],
+ name: 'OwnableInvalidOwner',
+ type: 'error',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
+ name: 'OwnableUnauthorizedAccount',
+ type: 'error',
+ },
+ {
+ inputs: [
+ { internalType: 'uint8', name: 'bits', type: 'uint8' },
+ { internalType: 'uint256', name: 'value', type: 'uint256' },
+ ],
+ name: 'SafeCastOverflowedUintDowncast',
+ type: 'error',
+ },
+ {
+ inputs: [{ internalType: 'string', name: 'str', type: 'string' }],
+ name: 'StringTooLong',
+ type: 'error',
+ },
+ {
+ inputs: [{ internalType: 'uint256', name: 'expiry', type: 'uint256' }],
+ name: 'VotesExpiredSignature',
+ type: 'error',
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: 'address',
+ name: 'owner',
+ type: 'address',
+ },
+ {
+ indexed: true,
+ internalType: 'address',
+ name: 'spender',
+ type: 'address',
+ },
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'value',
+ type: 'uint256',
+ },
+ ],
+ name: 'Approval',
+ type: 'event',
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: 'address',
+ name: 'delegator',
+ type: 'address',
+ },
+ {
+ indexed: true,
+ internalType: 'address',
+ name: 'fromDelegate',
+ type: 'address',
+ },
+ {
+ indexed: true,
+ internalType: 'address',
+ name: 'toDelegate',
+ type: 'address',
+ },
+ ],
+ name: 'DelegateChanged',
+ type: 'event',
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: 'address',
+ name: 'delegate',
+ type: 'address',
+ },
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'previousVotes',
+ type: 'uint256',
+ },
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'newVotes',
+ type: 'uint256',
+ },
+ ],
+ name: 'DelegateVotesChanged',
+ type: 'event',
+ },
+ { anonymous: false, inputs: [], name: 'EIP712DomainChanged', type: 'event' },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: 'address',
+ name: 'previousOwner',
+ type: 'address',
+ },
+ {
+ indexed: true,
+ internalType: 'address',
+ name: 'newOwner',
+ type: 'address',
+ },
+ ],
+ name: 'OwnershipTransferred',
+ type: 'event',
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: false,
+ internalType: 'address',
+ name: 'account',
+ type: 'address',
+ },
+ ],
+ name: 'Paused',
+ type: 'event',
+ },
+ {
+ anonymous: false,
+ inputs: [
+ { indexed: true, internalType: 'address', name: 'from', type: 'address' },
+ { indexed: true, internalType: 'address', name: 'to', type: 'address' },
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'value',
+ type: 'uint256',
+ },
+ ],
+ name: 'Transfer',
+ type: 'event',
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: false,
+ internalType: 'address',
+ name: 'account',
+ type: 'address',
+ },
+ ],
+ name: 'Unpaused',
+ type: 'event',
+ },
+ {
+ inputs: [],
+ name: 'CLOCK_MODE',
+ outputs: [{ internalType: 'string', name: '', type: 'string' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'DOMAIN_SEPARATOR',
+ outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'MINIMUM_TIME_BETWEEN_MINTS',
+ outputs: [{ internalType: 'uint32', name: '', type: 'uint32' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'MINT_CAP',
+ outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'TOKEN_INITIAL_SUPPLY',
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'TOKEN_NAME',
+ outputs: [{ internalType: 'string', name: '', type: 'string' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'TOKEN_SYMBOL',
+ outputs: [{ internalType: 'string', name: '', type: 'string' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'owner', type: 'address' },
+ { internalType: 'address', name: 'spender', type: 'address' },
+ ],
+ name: 'allowance',
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'spender', type: 'address' },
+ { internalType: 'uint256', name: 'value', type: 'uint256' },
+ ],
+ name: 'approve',
+ outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
+ name: 'balanceOf',
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [{ internalType: 'uint256', name: 'value', type: 'uint256' }],
+ name: 'burn',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'account', type: 'address' },
+ { internalType: 'uint256', name: 'value', type: 'uint256' },
+ ],
+ name: 'burnFrom',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'account', type: 'address' },
+ { internalType: 'uint32', name: 'pos', type: 'uint32' },
+ ],
+ name: 'checkpoints',
+ outputs: [
+ {
+ components: [
+ { internalType: 'uint48', name: '_key', type: 'uint48' },
+ { internalType: 'uint208', name: '_value', type: 'uint208' },
+ ],
+ internalType: 'struct Checkpoints.Checkpoint208',
+ name: '',
+ type: 'tuple',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'clock',
+ outputs: [{ internalType: 'uint48', name: '', type: 'uint48' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'decimals',
+ outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'delegatee', type: 'address' }],
+ name: 'delegate',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'delegatee', type: 'address' },
+ { internalType: 'uint256', name: 'nonce', type: 'uint256' },
+ { internalType: 'uint256', name: 'expiry', type: 'uint256' },
+ { internalType: 'uint8', name: 'v', type: 'uint8' },
+ { internalType: 'bytes32', name: 'r', type: 'bytes32' },
+ { internalType: 'bytes32', name: 's', type: 'bytes32' },
+ ],
+ name: 'delegateBySig',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
+ name: 'delegates',
+ outputs: [{ internalType: 'address', name: '', type: 'address' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'eip712Domain',
+ outputs: [
+ { internalType: 'bytes1', name: 'fields', type: 'bytes1' },
+ { internalType: 'string', name: 'name', type: 'string' },
+ { internalType: 'string', name: 'version', type: 'string' },
+ { internalType: 'uint256', name: 'chainId', type: 'uint256' },
+ { internalType: 'address', name: 'verifyingContract', type: 'address' },
+ { internalType: 'bytes32', name: 'salt', type: 'bytes32' },
+ { internalType: 'uint256[]', name: 'extensions', type: 'uint256[]' },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [{ internalType: 'uint256', name: 'timepoint', type: 'uint256' }],
+ name: 'getPastTotalSupply',
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'account', type: 'address' },
+ { internalType: 'uint256', name: 'timepoint', type: 'uint256' },
+ ],
+ name: 'getPastVotes',
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
+ name: 'getVotes',
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'to', type: 'address' },
+ { internalType: 'uint96', name: 'amount', type: 'uint96' },
+ ],
+ name: 'mint',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'mintingAllowedAfter',
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'name',
+ outputs: [{ internalType: 'string', name: '', type: 'string' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'owner', type: 'address' }],
+ name: 'nonces',
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
+ name: 'numCheckpoints',
+ outputs: [{ internalType: 'uint32', name: '', type: 'uint32' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'owner',
+ outputs: [{ internalType: 'address', name: '', type: 'address' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'pause',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'paused',
+ outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'owner', type: 'address' },
+ { internalType: 'address', name: 'spender', type: 'address' },
+ { internalType: 'uint256', name: 'value', type: 'uint256' },
+ { internalType: 'uint256', name: 'deadline', type: 'uint256' },
+ { internalType: 'uint8', name: 'v', type: 'uint8' },
+ { internalType: 'bytes32', name: 'r', type: 'bytes32' },
+ { internalType: 'bytes32', name: 's', type: 'bytes32' },
+ ],
+ name: 'permit',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'renounceOwnership',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'symbol',
+ outputs: [{ internalType: 'string', name: '', type: 'string' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'totalSupply',
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'to', type: 'address' },
+ { internalType: 'uint256', name: 'value', type: 'uint256' },
+ ],
+ name: 'transfer',
+ outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [
+ { internalType: 'address', name: 'from', type: 'address' },
+ { internalType: 'address', name: 'to', type: 'address' },
+ { internalType: 'uint256', name: 'value', type: 'uint256' },
+ ],
+ name: 'transferFrom',
+ outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }],
+ name: 'transferOwnership',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'unpause',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+];
diff --git a/apps/u3/src/services/social/api/farcaster.ts b/apps/u3/src/services/social/api/farcaster.ts
index f38e7dee..f4b59900 100644
--- a/apps/u3/src/services/social/api/farcaster.ts
+++ b/apps/u3/src/services/social/api/farcaster.ts
@@ -17,7 +17,9 @@ export type FarcasterNotification = {
casts_hash?: Buffer;
casts_text?: string;
replies_text?: string;
+ replies_hash?: Buffer;
replies_parent_hash?: Buffer;
+ casts_mentions?: number[];
};
export type FarcasterPageInfo = {
@@ -101,6 +103,23 @@ export function getFarcasterEmbedMetadata(urls: string[]): AxiosPromise<
});
}
+export function getFarcasterEmbedCast({
+ fid,
+ hash,
+}: {
+ fid: number;
+ hash: string;
+}) {
+ return axios({
+ url: `${REACT_APP_API_SOCIAL_URL}/3r-farcaster/embed-cast`,
+ method: 'get',
+ params: {
+ fid,
+ hash,
+ },
+ });
+}
+
export function getFarcasterNotifications({
fid,
endFarcasterCursor,
@@ -455,3 +474,64 @@ export async function getMetadataWithMod(
const metadata = await resp.json();
return metadata;
}
+
+const REACT_APP_API_SOCIAL_URL_PROD = 'https://api.u3.xyz';
+export async function getSavedCasts(walletAddress: string, pageSize?: number) {
+ if (walletAddress.startsWith('0x')) {
+ walletAddress = walletAddress.slice(2);
+ }
+ const resp = await request({
+ url: `${REACT_APP_API_SOCIAL_URL_PROD}/3r-bot/saved-casts`,
+ method: 'get',
+ params: {
+ walletAddress,
+ pageSize,
+ },
+ });
+ return resp?.data?.data?.saves;
+}
+
+export function setSavedCastsSynced(walletAddress: string, lastedId: number) {
+ if (walletAddress.startsWith('0x')) {
+ walletAddress = walletAddress.slice(2);
+ }
+ return request({
+ url: `${REACT_APP_API_SOCIAL_URL_PROD}/3r-bot/sync-casts`,
+ method: 'post',
+ data: {
+ walletAddress,
+ lastedId,
+ },
+ headers: {
+ needToken: true,
+ },
+ });
+}
+
+export function getUserinfoWithFid(fid: string) {
+ return axios({
+ url: `${REACT_APP_API_SOCIAL_URL}/3r-farcaster/userinfo/fid/${fid}`,
+ method: 'get',
+ });
+}
+
+export function postFrameActionApi(data: any) {
+ return axios({
+ url: `${REACT_APP_API_SOCIAL_URL}/3r-farcaster/frame-action/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/services/social/types/index.ts b/apps/u3/src/services/social/types/index.ts
index 4f4b2fb4..79cb48aa 100644
--- a/apps/u3/src/services/social/types/index.ts
+++ b/apps/u3/src/services/social/types/index.ts
@@ -28,6 +28,13 @@ export type FarCastEmbedMeta = {
twitter?: string;
provider?: string;
video?: string;
+ fcFrame?: string;
+ fcFrameButton1?: string;
+ fcFrameButton2?: string;
+ fcFrameButton3?: string;
+ fcFrameButton4?: string;
+ fcFrameImage?: string;
+ fcFramePostUrl?: string;
};
export type FarCast = {
diff --git a/apps/u3/src/utils/pwa/notification.ts b/apps/u3/src/utils/pwa/notification.ts
index 0f5a94be..2d98ebf1 100644
--- a/apps/u3/src/utils/pwa/notification.ts
+++ b/apps/u3/src/utils/pwa/notification.ts
@@ -8,8 +8,6 @@ export const requestPermission = async () => {
}
};
-const registration = await navigator.serviceWorker.getRegistration();
-
export const sendNotification = async (body) => {
console.log('sendNotification', body);
if (Notification.permission === 'granted') {
@@ -23,11 +21,13 @@ export const sendNotification = async (body) => {
}
};
-const 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) {
diff --git a/apps/u3/src/utils/shared/homeStore.ts b/apps/u3/src/utils/shared/homeStore.ts
index 48c6976a..3c080254 100644
--- a/apps/u3/src/utils/shared/homeStore.ts
+++ b/apps/u3/src/utils/shared/homeStore.ts
@@ -1,3 +1,9 @@
+import { removeDefaultFarcaster } from '../social/farcaster/farcaster-default';
+import {
+ removeFarsignPrivateKey,
+ removeFarsignSigner,
+} from '../social/farcaster/farsign-utils';
+
/*
* @Author: shixuewen friendlysxw@163.com
* @Date: 2022-12-30 18:47:46
@@ -20,3 +26,9 @@ export function removeHomeBannerHiddenFromStore() {
export function verifyHomeBannerHiddenByStore(): boolean {
return getHomeBannerHiddenForStore() === 1;
}
+
+export function removeFarcasterInfoFromStore() {
+ removeFarsignPrivateKey();
+ removeFarsignSigner();
+ removeDefaultFarcaster();
+}
diff --git a/apps/u3/src/utils/shared/time.ts b/apps/u3/src/utils/shared/time.ts
index 81f37831..2131dfc5 100644
--- a/apps/u3/src/utils/shared/time.ts
+++ b/apps/u3/src/utils/shared/time.ts
@@ -23,3 +23,11 @@ export const defaultFormatDate = (date: string | number | Date) =>
export const defaultFormatFromNow = (date: string | number | Date) =>
dayjs(date).fromNow();
+
+export const isSecondTimestamp = (timestamp: number) => {
+ return timestamp && timestamp.toString().length === 10;
+};
+
+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 450b51bc..198e3c35 100644
--- a/apps/u3/src/utils/shared/zora.ts
+++ b/apps/u3/src/utils/shared/zora.ts
@@ -10,6 +10,7 @@ import {
pgn,
} from 'viem/chains';
import { ZDKNetwork, ZDKChain } from '@zoralabs/zdk';
+import { isMillisecondTimestamp, isSecondTimestamp } from './time';
export const zoraMainnetChainIds: number[] = [
mainnet.id,
@@ -52,6 +53,16 @@ export const getZoraNetworkInfo = (chainId) => {
network: ZDKNetwork.Zora,
chain: ZDKChain.ZoraGoerli,
}
+ : Number(chainId) === zora.id
+ ? {
+ network: ZDKNetwork.Zora,
+ chain: ZDKChain.ZoraMainnet,
+ }
+ : Number(chainId) === base.id
+ ? {
+ network: ZDKNetwork.Base,
+ chain: ZDKChain.BaseMainnet,
+ }
: null;
};
@@ -68,6 +79,10 @@ export const getZoraNetwork = (chainId) => {
? sepolia
: Number(chainId) === zoraTestnet.id
? zoraTestnet
+ : Number(chainId) === zora.id
+ ? zora
+ : Number(chainId) === base.id
+ ? base
: null;
};
@@ -84,6 +99,10 @@ export const getZoraNetworkExplorer = (chainId) => {
? sepolia.blockExplorers.default.url
: Number(chainId) === zoraTestnet.id
? zoraTestnet.blockExplorers.default.url
+ : Number(chainId) === zora.id
+ ? zora.blockExplorers.default.url
+ : Number(chainId) === base.id
+ ? base.blockExplorers.default.url
: null;
};
export const getZoraHost = (chainId) => {
@@ -136,3 +155,50 @@ export const getZoraMintFeeWithChain = (chainId, contractMintFee: any) => {
}
return contractMintFee;
};
+
+export enum SaleStatus {
+ Unknown = -1,
+ NotStarted = 0,
+ InProgress = 1,
+ Ended = 2,
+}
+export const getSaleStatus = (saleStart: number, saleEnd: number) => {
+ const nowMillisecondTimestamp = Date.now();
+ const nowSecondTimestamp = Math.floor(nowMillisecondTimestamp / 1000);
+
+ const compareFn = (now: number, start: number, end: number) => {
+ if (Number(now) < Number(start)) {
+ return SaleStatus.NotStarted;
+ }
+ if (Number(now) > Number(end)) {
+ return SaleStatus.Ended;
+ }
+ return SaleStatus.InProgress;
+ };
+
+ if (isSecondTimestamp(saleStart) && isSecondTimestamp(saleEnd)) {
+ return compareFn(nowSecondTimestamp, saleStart, saleEnd);
+ }
+
+ if (isMillisecondTimestamp(saleStart) && isMillisecondTimestamp(saleEnd)) {
+ return compareFn(nowMillisecondTimestamp, saleStart, saleEnd);
+ }
+
+ if (isMillisecondTimestamp(saleStart) && isSecondTimestamp(saleEnd)) {
+ return compareFn(nowSecondTimestamp, saleStart, Number(saleEnd) * 100);
+ }
+
+ if (isSecondTimestamp(saleStart) && isMillisecondTimestamp(saleEnd)) {
+ return compareFn(nowMillisecondTimestamp, Number(saleStart) * 100, saleEnd);
+ }
+
+ if (String(saleStart).length > 13) {
+ return SaleStatus.NotStarted;
+ }
+
+ if (String(saleEnd).length > 13) {
+ return SaleStatus.InProgress;
+ }
+
+ return SaleStatus.Unknown;
+};
diff --git a/apps/u3/src/utils/social/farcaster/farcaster-default.ts b/apps/u3/src/utils/social/farcaster/farcaster-default.ts
index d9b7a3f2..ed6e8177 100644
--- a/apps/u3/src/utils/social/farcaster/farcaster-default.ts
+++ b/apps/u3/src/utils/social/farcaster/farcaster-default.ts
@@ -7,3 +7,7 @@ export const setDefaultFarcaster = (fid: string) => {
export const getDefaultFarcaster = () => {
return localStorage.getItem(Key);
};
+
+export const removeDefaultFarcaster = () => {
+ return localStorage.removeItem(Key);
+};
diff --git a/apps/u3/src/utils/social/farcaster/farsign-utils.ts b/apps/u3/src/utils/social/farcaster/farsign-utils.ts
index 5d68bc95..470ff8fb 100644
--- a/apps/u3/src/utils/social/farcaster/farsign-utils.ts
+++ b/apps/u3/src/utils/social/farcaster/farsign-utils.ts
@@ -44,7 +44,7 @@ function setPrivateKey(privateKey: string) {
function getPrivateKey() {
const privateKey = localStorage.getItem(
`farsign-privateKey-${FARCASTER_CLIENT_NAME}`
- )!;
+ );
return privateKey;
}
diff --git a/apps/u3/src/utils/social/farcaster/getEmbeds.ts b/apps/u3/src/utils/social/farcaster/getEmbeds.ts
index 263af7d6..2f7a8bb7 100644
--- a/apps/u3/src/utils/social/farcaster/getEmbeds.ts
+++ b/apps/u3/src/utils/social/farcaster/getEmbeds.ts
@@ -16,11 +16,17 @@ export function getEmbeds(cast: FarCast): {
webpages: {
url: string;
}[];
+ casts: {
+ castId: { fid: number; hash: string };
+ }[];
} {
const imgs = [];
const webpages = [];
+ const casts = [];
for (const embed of cast.embeds) {
- if (embed?.url) {
+ if (embed?.castId) {
+ casts.push(embed);
+ } else if (embed?.url) {
if (isImg(embed.url)) {
imgs.push({
url: embed.url,
@@ -32,5 +38,5 @@ export function getEmbeds(cast: FarCast): {
}
}
}
- return { imgs, webpages };
+ return { imgs, webpages, casts };
}