Skip to content

Commit

Permalink
optimistic addition/removal of frames; improved warpcast sign-in
Browse files Browse the repository at this point in the history
  • Loading branch information
JFrankfurt committed Sep 19, 2024
1 parent 7c21622 commit 208f213
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import Frame from 'apps/web/src/components/Basenames/UsernameProfileSectionFrame
import { SuggestionCard } from 'apps/web/src/components/Basenames/UsernameProfileSectionFrames/SuggestionCard';
import { Button, ButtonSizes, ButtonVariants } from 'apps/web/src/components/Button/Button';
import Input from 'apps/web/src/components/Input';
import useReadBaseEnsTextRecords from 'apps/web/src/hooks/useReadBaseEnsTextRecords';
import { isValidUrl } from 'apps/web/src/utils/urls';
import { UsernameTextRecordKeys } from 'apps/web/src/utils/usernames';
import { ActionType } from 'libs/base-ui/utils/logEvent';
import Image, { StaticImageData } from 'next/image';
import Link from 'next/link';
Expand All @@ -25,8 +23,8 @@ import nftProduct from './ui/nftProduct.svg';
import payouts from './ui/payouts.svg';
import previewBackground from './ui/preview-background.svg';
import emptyPreviewFrame from './ui/preview-frame.svg';
import swap from './ui/swap.svg';
import starActive from './ui/starActive.svg';
import swap from './ui/swap.svg';

export default function FrameBuilder() {
const params = useParams();
Expand All @@ -45,12 +43,7 @@ export default function FrameBuilder() {
scrollTo({ top: 0, behavior: 'smooth' });
}, []);

const { profileUsername, profileAddress } = useUsernameProfile();
const { existingTextRecords } = useReadBaseEnsTextRecords({
address: profileAddress,
username: profileUsername,
});
const homeframeUrlString = existingTextRecords[UsernameTextRecordKeys.Frames] ?? '';
const { profileAddress } = useUsernameProfile();

const [swapTokenSymbol, setSwapTokenSymbol] = useState('');
const handleSwapTokenSymbolChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -126,7 +119,7 @@ export default function FrameBuilder() {
[logEventWithContext],
);

const { pendingFrameChange, setFrameRecord } = useFrameContext();
const { pendingFrameChange, addFrame } = useFrameContext();

const handlePaycasterClick = useCallback(() => {
if (basename) {
Expand Down Expand Up @@ -162,21 +155,17 @@ export default function FrameBuilder() {
} else {
setNewFrameUrl('');
}
}, [profileAddress]);
}, [handleNextStep, logEventWithContext, profileAddress]);

const handleAddFrameClick = useCallback(() => {
if (!newFrameUrl) return;
setFrameRecord(homeframeUrlString ? `${homeframeUrlString}|${newFrameUrl}` : newFrameUrl)
.then(() => {
logEventWithContext('basename_profile_frame_posted', ActionType.click, {
context: newFrameUrl,
});
router.push(`/name/${basename}`);
})
addFrame(newFrameUrl)
.then(() => router.push(`/name/${basename}`))
.catch(console.warn);
}, [newFrameUrl, setFrameRecord, homeframeUrlString, logEventWithContext, router, basename]);
}, [newFrameUrl, addFrame, router, basename]);

// corresponds to tailwind's md: rule
// this might be breaking ssr hydration
const isDesktop = useMediaQuery('(min-width: 768px)');
const isMobile = useMediaQuery('(max-width: 769px)');

Expand Down Expand Up @@ -258,7 +247,8 @@ export default function FrameBuilder() {
slice.so
</a>
</li>
<li>Paste a link to the product you want to sell on your profile</li>
<li>Find the product you want to sell on your profile</li>
<li>Click on the “share“ icon to copy a link to the product and paste it here</li>
</ol>
<Input
placeholder="https://"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import useBasenameChain, { isBasenameSupportedChain } from 'apps/web/src/hooks/u
import useReadBaseEnsTextRecords from 'apps/web/src/hooks/useReadBaseEnsTextRecords';
import { UsernameTextRecordKeys } from 'apps/web/src/utils/usernames';
import { ActionType } from 'libs/base-ui/utils/logEvent';
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { namehash } from 'viem';
import { useAccount, useChainId, useConfig, useWriteContract } from 'wagmi';
import { sendTransaction, signTypedData, switchChain } from 'wagmi/actions';
Expand All @@ -43,6 +43,12 @@ function parseChainId(id: string): number {
return parseInt(id.split('eip155:')[1]);
}

function removeUrl(urls: string, urlSubstringToRemove: string): string {
const urlArray = urls.split('|');
const filteredUrls = urlArray.filter((url) => !url.includes(urlSubstringToRemove));
return filteredUrls.filter(Boolean).join('|');
}

export type FrameContextValue = {
currentWalletIsProfileOwner?: boolean;
frameUrlRecord: string;
Expand All @@ -60,6 +66,10 @@ export type FrameContextValue = {
pendingFrameChange: boolean;
setShowFarcasterQRModal: (b: boolean) => void;
setFrameRecord: (url: string) => Promise<`0x${string}` | undefined>;
frameUrls: string[];
addFrame: (url: string) => Promise<`0x${string}` | undefined>;
removeFrame: (url: string) => Promise<`0x${string}` | undefined>;
existingTextRecordsIsLoading: boolean;
};

export const FrameContext = createContext<FrameContextValue | null>(null);
Expand All @@ -82,13 +92,15 @@ export function FramesProvider({ children }: FramesProviderProps) {
const { address } = useAccount();
const { logError } = useErrors();
const { profileUsername, profileAddress, currentWalletIsProfileOwner } = useUsernameProfile();
const { existingTextRecords, refetchExistingTextRecords } = useReadBaseEnsTextRecords({
address: profileAddress,
username: profileUsername,
refetchInterval: currentWalletIsProfileOwner ? 1000 * 5 : Infinity,
});
const { existingTextRecords, existingTextRecordsIsLoading, refetchExistingTextRecords } =
useReadBaseEnsTextRecords({
address: profileAddress,
username: profileUsername,
refetchInterval: currentWalletIsProfileOwner ? 1000 * 5 : Infinity,
});

const frameUrlRecord = existingTextRecords[UsernameTextRecordKeys.Frames];

const { frameContext: farcasterFrameContext } = useFarcasterFrameContext({
fallbackContext: fallbackFrameContext,
});
Expand Down Expand Up @@ -197,6 +209,11 @@ export function FramesProvider({ children }: FramesProviderProps) {

const { writeContractAsync, isPending: pendingFrameChange } = useWriteContract();
const { basenameChain } = useBasenameChain(profileUsername);

const [optimisticFrameUrls, setOptimisticFrameUrls] = useState<string[]>([]);
useEffect(() => {
setOptimisticFrameUrls(frameUrlRecord.split('|').filter(Boolean));
}, [frameUrlRecord]);
const setFrameRecord = useCallback(
async (frameUrl: string) => {
async function doTransaction() {
Expand Down Expand Up @@ -238,6 +255,31 @@ export function FramesProvider({ children }: FramesProviderProps) {
],
);

const removeFrame = useCallback(
async (url: string) => {
const newRecord = removeUrl(frameUrlRecord, url);
logEventWithContext('basename_profile_frame_removed', ActionType.click, {
context: url,
});
setOptimisticFrameUrls(newRecord.split('|').filter(Boolean));
return setFrameRecord(newRecord);
},
[frameUrlRecord, logEventWithContext, setFrameRecord],
);

const addFrame = useCallback(
async (url: string) => {
const newUrls = [...optimisticFrameUrls, url];
const newRecord = newUrls.join('|');
logEventWithContext('basename_profile_frame_posted', ActionType.click, {
context: url,
});
setOptimisticFrameUrls(newUrls);
return setFrameRecord(newRecord);
},
[logEventWithContext, optimisticFrameUrls, setFrameRecord],
);

const value = useMemo(
() => ({
currentWalletIsProfileOwner,
Expand All @@ -259,7 +301,10 @@ export function FramesProvider({ children }: FramesProviderProps) {
showFarcasterQRModal,
setShowFarcasterQRModal,
pendingFrameChange,
setFrameRecord,
addFrame,
removeFrame,
existingTextRecordsIsLoading,
frameUrls: optimisticFrameUrls,
}),
[
currentWalletIsProfileOwner,
Expand All @@ -275,7 +320,10 @@ export function FramesProvider({ children }: FramesProviderProps) {
frameInteractionError,
showFarcasterQRModal,
pendingFrameChange,
setFrameRecord,
addFrame,
removeFrame,
existingTextRecordsIsLoading,
optimisticFrameUrls,
],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Modal from 'apps/web/src/components/Modal';
import { useFIDQuery } from 'apps/web/src/hooks/useFarcasterUserByFID';
import QRCode from 'qrcode.react';
import { useCallback, useMemo } from 'react';
import FarcasterIcon from './white-purple-farcaster-icon.svg';

export default function FarcasterAccountModal() {
const { farcasterSignerState, showFarcasterQRModal, setShowFarcasterQRModal } = useFrameContext();
Expand All @@ -31,7 +32,7 @@ export default function FarcasterAccountModal() {

return (
<Modal isOpen={showFarcasterQRModal} onClose={handleModalClose}>
<div className="max-w-lg rounded-lg bg-white">
<div className="max-w-72 rounded-lg bg-white">
{/* Sign-in section when the user is not signed in */}
{!farcasterUser && (
<div className="flex flex-col items-center gap-4">
Expand Down Expand Up @@ -72,7 +73,11 @@ function IdentityState({ user, onLogout }: { user: FarcasterSigner; onLogout: ()
farcasterSignerState.logout().catch(console.warn).finally(onLogout);
}, [farcasterSignerState, onLogout]);
if (user.status === 'pending_approval') {
return <p className="flex items-center justify-center">Sign in with Warpcast</p>;
return (
<p className="mb-2 flex items-center justify-center text-2xl text-illoblack">
Sign in with Warpcast
</p>
);
}
if (user.status === 'approved') {
const farcasterIdentity = data?.users[0];
Expand Down Expand Up @@ -111,12 +116,24 @@ function IdentityState({ user, onLogout }: { user: FarcasterSigner; onLogout: ()
return null;
}

const imageSettings = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
src: FarcasterIcon.src,
x: undefined,
y: undefined,
height: 60,
width: 60,
opacity: 1,
excavate: true,
};
function SelectedIdentity({ user }: { user: FarcasterSigner }) {
if (user.status === 'pending_approval') {
return (
<div className="mt-4 flex flex-col items-center gap-2 border-t pt-4">
Scan with your camera app
<QRCode value={user.signerApprovalUrl} size={128} />
<div className="flex flex-col items-center gap-2">
<p className="mb-3 text-palette-foregroundMuted">
Scan this QR code to sign in. You may need to use warps to connect
</p>
<QRCode value={user.signerApprovalUrl} size={276} level="H" imageSettings={imageSettings} />
<div className="text-muted-foreground lg:hidden">OR</div>
<a
href={user.signerApprovalUrl}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,21 @@ import Frame from 'apps/web/src/components/Basenames/UsernameProfileSectionFrame
import { ActionType } from 'libs/base-ui/utils/logEvent';
import { useCallback } from 'react';

function removeUrl(urls: string, urlSubstringToRemove: string): string {
const urlArray = urls.split('|');
const filteredUrls = urlArray.filter((url) => !url.includes(urlSubstringToRemove));
return filteredUrls.filter(Boolean).join('|');
}

export default function FrameListItem({ url }: { url: string }) {
const { frameUrlRecord, setFrameRecord } = useFrameContext();
const { removeFrame } = useFrameContext();
const { currentWalletIsProfileOwner } = useUsernameProfile();
const { logEventWithContext } = useAnalytics();

const handleRemoveFrameClick = useCallback(() => {
const newFrameUrlRecord = removeUrl(frameUrlRecord, url);
setFrameRecord(newFrameUrlRecord)
removeFrame(url)
.catch(console.error)
.finally(() => {
logEventWithContext('basename_profile_frame_removed', ActionType.click, { context: url });
});
}, [frameUrlRecord, logEventWithContext, setFrameRecord, url]);
}, [logEventWithContext, removeFrame, url]);

return (
<div className="relative">
<div className="relative mb-4 break-inside-avoid">
{currentWalletIsProfileOwner && (
<Popover.Root>
<Popover.Trigger asChild>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import { useAnalytics } from 'apps/web/contexts/Analytics';
import { useUsernameProfile } from 'apps/web/src/components/Basenames/UsernameProfileContext';
import {
FramesProvider,
Expand All @@ -10,31 +11,25 @@ import FrameListItem from 'apps/web/src/components/Basenames/UsernameProfileSect
import UsernameProfileSectionTitle from 'apps/web/src/components/Basenames/UsernameProfileSectionTitle';
import { Button, ButtonSizes } from 'apps/web/src/components/Button/Button';
import ImageAdaptive from 'apps/web/src/components/ImageAdaptive';
import useReadBaseEnsTextRecords from 'apps/web/src/hooks/useReadBaseEnsTextRecords';
import { UsernameTextRecordKeys } from 'apps/web/src/utils/usernames';
import { ActionType } from 'libs/base-ui/utils/logEvent';
import { StaticImageData } from 'next/image';
import Link from 'next/link';
import { useCallback } from 'react';
import cornerGarnish from './corner-garnish.svg';
import frameIcon from './frame-icon.svg';
import { useAnalytics } from 'apps/web/contexts/Analytics';
import { ActionType } from 'libs/base-ui/utils/logEvent';

function SectionContent() {
const { profileUsername, profileAddress, currentWalletIsProfileOwner } = useUsernameProfile();
const { frameInteractionError, setFrameInteractionError } = useFrameContext();
const { profileUsername, currentWalletIsProfileOwner } = useUsernameProfile();
const {
frameInteractionError,
setFrameInteractionError,
frameUrls,
existingTextRecordsIsLoading,
} = useFrameContext();
const handleErrorClick = useCallback(
() => setFrameInteractionError(''),
[setFrameInteractionError],
);
const { existingTextRecords, existingTextRecordsIsLoading } = useReadBaseEnsTextRecords({
address: profileAddress,
username: profileUsername,
refetchInterval: currentWalletIsProfileOwner ? 5000 : Infinity,
});
const homeframeUrlString = existingTextRecords[UsernameTextRecordKeys.Frames] ?? '';
const frameUrls = homeframeUrlString.split('|').filter(Boolean);

const { logEventWithContext } = useAnalytics();
const handleAddFrameLinkClick = useCallback(() => {
logEventWithContext('basename_profile_frame_try_now_clicked', ActionType.click);
Expand Down Expand Up @@ -96,7 +91,7 @@ function SectionContent() {
{frameInteractionError}
</Button>
)}
<div className="grid grid-flow-row-dense auto-rows-min grid-cols-1 gap-4 p-4 xl:grid-cols-2">
<div className="columns-1 gap-4 p-4 sm:columns-2 lg:columns-3">
{frameUrls.map((url) => (
<FrameListItem url={url} key={url} />
))}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 208f213

Please sign in to comment.