Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lens): first integration #14

Merged
merged 6 commits into from
Sep 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
transpilePackages: ["@lens-protocol"],
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@biconomy/paymaster": "^3.1.1-alpha.0",
"@biconomy/web3-auth": "^3.1.0",
"@headlessui/react": "^1.7.17",
"@lens-protocol/react-web": "^1.3.1",
"@types/node": "20.6.3",
"@types/react": "18.2.22",
"@types/react-dom": "18.2.7",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useCreateProfile } from "@lens-protocol/react-web";
import React, { useCallback } from "react";

type Props = {
displayLabel?: boolean;
};
export const CreateLensProfileForm: React.FC<Props> = ({
displayLabel = false
}) => {
//TODO: handle loading
const {
execute: createProfile,
// isPending: isCreateProfilePending,
error: createProfileError
} = useCreateProfile();

const onCreateSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
//TODO
//eslint-disable-next-line @typescript-eslint/no-explicit-any
const handle = (e.target as any).elements["create-handle"].value;
console.log("creating profile", handle);
try {
await createProfile({ handle });
} catch (error) {
console.error("create profile failed", error);
}
},
[createProfile]
);

return (
<form
className="flex flex-col gap-2 w-full items-center justify-between"
onSubmit={onCreateSubmit}
>
<div className="flex flex-col gap-2 items-start justify-start flex-grow w-full">
{displayLabel && (
<label htmlFor="create-handle" className="">
Create handle
</label>
)}
<input
type="text"
id="create-handle"
className="block w-full px-4 py-2 text-sm border rounded-lg ring-transparent bg-stone-600 border-gray-600 dark:placeholder-gray-400 text-white"
placeholder="Handle name"
required
/>
</div>
<button
type="submit"
className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-md w-1/2"
>
Create
</button>
{createProfileError && (
<p className="text-red-500">{createProfileError.message}</p>
)}
</form>
);
};
1 change: 1 addition & 0 deletions src/components/Lens/CreateLensProfileForm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./CreateLensProfileForm";
68 changes: 68 additions & 0 deletions src/components/Lens/LensAccountCard/LensAccountCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { CreateLensProfileForm, SelectLensProfile } from "@/components";
import { BeatLoader } from "react-spinners";
import {
useActiveProfile,
useActiveProfileSwitch,
useProfilesOwnedByMe
} from "@lens-protocol/react-web";

export default function LensAccountCard() {
const {
data: activeProfile,
error: activeProfileError,
loading: activeProfileLoading
} = useActiveProfile();

//TODO: handle loading
const {
execute: switchActiveProfile
// isPending: swtichActiveProfilePending
} = useActiveProfileSwitch();

//TODO: handle pagination
const {
data: profiles,
loading: profilesOwnedLoading
// hasMore,
// next
} = useProfilesOwnedByMe({
limit: 10
});

const isLoading = activeProfileLoading || profilesOwnedLoading;

if (isLoading) return <BeatLoader className="w-8 h-8" />;

if (!activeProfile && profiles?.length === 0) {
return (
<div className="w-full px-4 py-8 bg-[#2b2b2b] rounded-md self-center flex flex-col gap-8 items-start justify-start">
<div className="flex flex-col gap-0 items-center justify-start text-center w-full">
<h1 className="text-2xl font-bold">
No handles found for this account
</h1>
<h3 className="text-sm text-gray-300">
Start today by creating a new one
</h3>
</div>
<div className="w-full md:w-1/2 self-center">
<CreateLensProfileForm />
</div>
</div>
);
}

return (
<div className="px-4 py-8 bg-[#2b2b2b] rounded-md self-center flex flex-col gap-2 items-start justify-start w-full">
{activeProfileLoading && <p>Loading...</p>}
{activeProfileError && <p>Error: {activeProfileError.message}</p>}
{activeProfile === null && <p>No active profile</p>}
{profilesOwnedLoading && <p>Loading profiles...</p>}
<SelectLensProfile
activeProfile={activeProfile ?? undefined}
profiles={profiles ?? []}
onChange={switchActiveProfile}
/>
<CreateLensProfileForm displayLabel={true} />
</div>
);
}
1 change: 1 addition & 0 deletions src/components/Lens/LensAccountCard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./LensAccountCard";
29 changes: 29 additions & 0 deletions src/components/Lens/SelectLensProfile/SelectLensProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Profile, ProfileId } from "@lens-protocol/react-web";

type Props = {
activeProfile?: Profile;
profiles: Profile[];
onChange: (profileId: ProfileId) => void;
};
export const SelectLensProfile: React.FC<Props> = ({
activeProfile,
profiles,
onChange
}) => {
return (
<div className="w-full flex flex-col md:flex-row justify-between gap-2 items-start md:items-center">
<p>Select a profile to use</p>
<select
className="px-1 py-2 bg-stone-600 border-gray-600 dark:placeholder-gray-400 text-white rounded-md"
value={activeProfile?.id}
onChange={(e) => onChange(e.target.value as ProfileId)}
>
{profiles.map((profile) => (
<option key={profile.id} value={profile.id}>
{profile.handle}
</option>
))}
</select>
</div>
);
};
1 change: 1 addition & 0 deletions src/components/Lens/SelectLensProfile/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./SelectLensProfile";
3 changes: 3 additions & 0 deletions src/components/Lens/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./CreateLensProfileForm";
export * from "./LensAccountCard";
export * from "./SelectLensProfile";
11 changes: 10 additions & 1 deletion src/components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@ import { useCallback, useState } from "react";
import Link from "next/link";
import { IconContext } from "react-icons";
import { FaGem, FaBars, FaMagnifyingGlass, FaX } from "react-icons/fa6";
import { SocialLoginWrapper } from "../SocialLoginWrapper";
import dynamic from "next/dynamic";

const SocialLoginWrapper = dynamic(
() =>
import("@/components/SocialLoginWrapper").then(
(res) => res.SocialLoginWrapper
),
{
ssr: false
}
);
export const Navbar = () => {
const [open, setOpen] = useState(false);

Expand Down
7 changes: 4 additions & 3 deletions src/components/SocialLoginWrapper/SocialLoginWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { memo, useCallback, useEffect, useRef, useState } from "react";
import BeatLoader from "react-spinners/BeatLoader";
import { FaArrowRightToBracket } from "react-icons/fa6";

const localhostUrl = "http://127.0.0.1:3000/";
const productionUrl = "https://rubyring-zeta.vercel.app";

export const SocialLoginWrapper = memo(() => <SocialLoginContent />);
Expand Down Expand Up @@ -57,8 +58,8 @@ const SocialLoginContent = () => {
setLoginLoading(true);
const sdk = new SocialLogin();

const signature1 = await sdk.whitelistUrl(
"http://127.0.0.1:3000/" // TODO: It is important to make sure that you update the whitelist URL with your production url when you are ready to go live!
const localSignature = await sdk.whitelistUrl(
localhostUrl // TODO: It is important to make sure that you update the whitelist URL with your production url when you are ready to go live!
);

const productionSignature = await sdk.whitelistUrl(productionUrl);
Expand All @@ -67,7 +68,7 @@ const SocialLoginContent = () => {
chainId: ethers.utils.hexValue(ChainId.POLYGON_MUMBAI).toString(),
network: "testnet",
whitelistUrls: {
"http://127.0.0.1:3000/": signature1,
[localhostUrl]: localSignature,
[productionUrl]: productionSignature
}
});
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./AccountBox";
export * from "./Lens";
export * from "./Navbar";
export * from "./SocialLoginWrapper";
14 changes: 12 additions & 2 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import dynamic from "next/dynamic";
import "@biconomy/web3-auth/dist/src/style.css";
import "../styles/global.css";
import type { AppProps } from "next/app";
import { CreateLensAccountHandler } from "@/providers";

const BiconomyLensProvider = dynamic(
() =>
import("@/providers/BiconomyLensProvider").then(
(res) => res.BiconomyLensProvider
),
{ ssr: false }
);

const Navbar = dynamic(
() => import("../components/Navbar").then((res) => res.Navbar),
Expand All @@ -13,9 +22,10 @@ const Navbar = dynamic(

export default function App({ Component, pageProps }: AppProps) {
return (
<>
<BiconomyLensProvider>
<CreateLensAccountHandler />
<Navbar />
<Component {...pageProps} />
</>
</BiconomyLensProvider>
);
}
25 changes: 17 additions & 8 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
// Use inter from next/font
"use client";
import { Inter } from "next/font/google";
import { Suspense } from "react";
import { useAccountStore } from "@/store";
import { BeatLoader } from "react-spinners";
import { WithLensContext } from "@/providers";
import dynamic from "next/dynamic";

const inter = Inter({ subsets: ["latin"] });

const LensAccountCard = dynamic(
() =>
import("@/components/Lens/LensAccountCard/LensAccountCard").then(
(res) => res.default
),
{
ssr: false
}
);

export default function Home() {
const { socialAccountLoading, smartAccountAddress } = useAccountStore();
return (
<main className={inter.className}>
<Suspense fallback={<div>Loading...</div>}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "80vh"
}}
>
<div className="flex flex-col h-[80vh] w-full justify-center items-center gap-4">
{!socialAccountLoading && !smartAccountAddress && (
<h1>Login to start using the app</h1>
)}
Expand All @@ -36,6 +42,9 @@ export default function Home() {
Logged in as: {smartAccountAddress}
</h1>
)}
<div className="w-1/2 self-center">
<WithLensContext Component={<LensAccountCard />} />
</div>
</div>
</Suspense>
</main>
Expand Down
48 changes: 48 additions & 0 deletions src/providers/BiconomyLensProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useAccountStore } from "@/store";
import {
IBindings,
LensConfig,
LensProvider,
development
} from "@lens-protocol/react-web";
import { ethers } from "ethers";
import React, { createContext, useMemo } from "react";

type BiconomyLensProviderProps = {
children: React.ReactNode;
};

export const BiconomyLensContext = createContext<boolean>(false);

export const BiconomyLensProvider = ({
children
}: BiconomyLensProviderProps) => {
const { smartAccount, accountPrivateKey } = useAccountStore();

const biconomyBindings: IBindings | null = useMemo(() => {
if (!smartAccount || !accountPrivateKey) return null;

// can't use smartAccount.signer because of this https://github.com/ethers-io/ethers.js/issues/948#issuecomment-1016993929
const wallet = new ethers.Wallet(accountPrivateKey, smartAccount.provider);

console.log({ accountPrivateKey });

return {
getProvider: async () => smartAccount?.provider,
getSigner: async () => wallet
};
}, [smartAccount, accountPrivateKey]);

if (!biconomyBindings) return <>{children}</>;

const lensConfig: LensConfig = {
bindings: biconomyBindings,
environment: development
};

return (
<BiconomyLensContext.Provider value={true}>
<LensProvider config={lensConfig}>{children}</LensProvider>
</BiconomyLensContext.Provider>
);
};
Loading