Skip to content

Commit 56cc920

Browse files
committed
Implemented basenames and identity, some UI improvements
1 parent 884df78 commit 56cc920

File tree

16 files changed

+433
-2513
lines changed

16 files changed

+433
-2513
lines changed

packages/foundry/broadcast/Deploy.s.sol/84532/run-1729023043.json

Lines changed: 210 additions & 0 deletions
Large diffs are not rendered by default.

packages/foundry/broadcast/Deploy.s.sol/84532/run-latest.json

Lines changed: 76 additions & 76 deletions
Large diffs are not rendered by default.

packages/foundry/contracts/BasedProfile.sol

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
88
//////////////////////////////////////////////////////////////*/
99

1010
struct Profile {
11-
string username;
1211
string bio;
13-
string imageURL;
1412
string email;
1513
}
1614

@@ -28,18 +26,7 @@ contract BasedProfile {
2826
EXTERNAL FUNCTIONS
2927
//////////////////////////////////////////////////////////////*/
3028

31-
function setProfile(
32-
string memory _username,
33-
string memory _bio,
34-
string memory _imageURL,
35-
string memory _email
36-
) public {
37-
if (
38-
keccak256(bytes(_username))
39-
!= keccak256(bytes(profiles[msg.sender].username))
40-
) {
41-
setUsername(_username);
42-
}
29+
function setProfile(string memory _bio, string memory _email) public {
4330
if (
4431
bytes(_email).length != 0
4532
&& keccak256(bytes(_email))
@@ -49,82 +36,12 @@ contract BasedProfile {
4936
profiles[msg.sender].email = _email;
5037
}
5138
profiles[msg.sender].bio = _bio;
52-
profiles[msg.sender].imageURL = _imageURL;
53-
}
54-
55-
// We keep this a separate function in case someone just wants to change username
56-
// and save some gas
57-
function setUsername(
58-
string memory _username
59-
) public {
60-
string memory futureUsername = _rulesForUsernameCharacters(_username);
61-
62-
require(
63-
nameToAddress[_rulesForUsernameCharacters(_username)] == address(0),
64-
"Username already taken"
65-
);
66-
67-
if (
68-
keccak256(bytes(profiles[msg.sender].username))
69-
!= keccak256(bytes(futureUsername))
70-
) {
71-
delete nameToAddress[profiles[msg.sender].username];
72-
}
73-
profiles[msg.sender].username = futureUsername;
74-
75-
nameToAddress[futureUsername] = msg.sender;
7639
}
7740

7841
/*//////////////////////////////////////////////////////////////
7942
INTERNAL FUNCTIONS
8043
//////////////////////////////////////////////////////////////*/
8144

82-
function _rulesForUsernameCharacters(
83-
string memory input
84-
) internal pure returns (string memory) {
85-
bytes memory inputBytes = bytes(input);
86-
if (inputBytes.length < 3 || inputBytes.length > 17) {
87-
revert("Invalid username length: must be between 3 and 17 characters");
88-
}
89-
90-
bytes memory verifiedString = new bytes(inputBytes.length);
91-
bool lastCharacterWasPeriod = false;
92-
93-
for (uint256 i = 0; i < inputBytes.length; i++) {
94-
bytes1 character = inputBytes[i];
95-
96-
// Convert uppercase letters to lowercase
97-
if (character >= 0x41 && character <= 0x5A) {
98-
// A-Z
99-
character = bytes1(uint8(character) + 32);
100-
}
101-
102-
// Check for valid characters
103-
if (
104-
(character >= 0x61 && character <= 0x7A) // a-z
105-
|| (character >= 0x30 && character <= 0x39) // 0-9
106-
|| character == 0x5F // _
107-
|| character == 0x2E // .
108-
) {
109-
// Check for consecutive periods
110-
if (character == 0x2E) {
111-
// .
112-
if (lastCharacterWasPeriod) {
113-
revert("Invalid username: consecutive periods are not allowed");
114-
}
115-
lastCharacterWasPeriod = true;
116-
} else {
117-
lastCharacterWasPeriod = false;
118-
}
119-
verifiedString[i] = character;
120-
} else {
121-
revert("Invalid character in username");
122-
}
123-
}
124-
125-
return string(verifiedString);
126-
}
127-
12845
function _isValidEmail(
12946
string memory email
13047
) internal pure returns (bool) {

packages/nextjs/app/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import "@coinbase/onchainkit/styles.css";
12
import "@rainbow-me/rainbowkit/styles.css";
23
import { ScaffoldEthAppWithProviders } from "~~/components/ScaffoldEthAppWithProviders";
34
import { ThemeProvider } from "~~/components/ThemeProvider";

packages/nextjs/app/profile/_components/ProfileInfo.tsx

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import React, { useEffect, useState } from "react";
2-
import ProfilePictureUpload from "./ProfilePictureUpload";
32
import { FundButton, getOnrampBuyUrl } from "@coinbase/onchainkit/fund";
3+
import { Avatar, Badge, Identity, Name } from "@coinbase/onchainkit/identity";
44
import { useAccount } from "wagmi";
55
import { PencilIcon } from "@heroicons/react/24/outline";
66
import { InputBase } from "~~/components/punk-society/InputBase";
77
import { LoadingBars } from "~~/components/punk-society/LoadingBars";
8+
import { PunkBalance } from "~~/components/punk-society/PunkBalance";
9+
import { PunkConnectButton } from "~~/components/punk-society/PunkConnectButton";
810
import { TextInput } from "~~/components/punk-society/TextInput";
9-
import { Address, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth";
11+
import { Address } from "~~/components/scaffold-eth";
1012
import { useScaffoldReadContract, useScaffoldWriteContract } from "~~/hooks/scaffold-eth";
1113
import { notification } from "~~/utils/scaffold-eth";
1214

@@ -17,11 +19,7 @@ interface ProfileInfoProps {
1719
}
1820

1921
const ProfileInfo: React.FC<ProfileInfoProps> = ({ address }) => {
20-
const defaultProfilePicture = "/guest-profile.jpg";
21-
22-
const [username, setUsername] = useState("");
2322
const [bio, setBio] = useState("");
24-
const [profilePicture, setProfilePicture] = useState<string>("");
2523
const [email, setEmail] = useState<string>("");
2624
const [isEditing, setIsEditing] = useState(false); // New state for edit mode
2725
const [loadingProfile, setLoadingProfile] = useState(true);
@@ -49,15 +47,9 @@ const ProfileInfo: React.FC<ProfileInfoProps> = ({ address }) => {
4947

5048
const handleEditProfile = async () => {
5149
try {
52-
// Check if the current profile picture is the default one
53-
if (profilePicture === defaultProfilePicture) {
54-
// Unset the current profile picture before editing the profile
55-
setProfilePicture("");
56-
}
57-
5850
await punkProfileWriteAsync({
5951
functionName: "setProfile",
60-
args: [username, bio, profilePicture, email],
52+
args: [bio, email],
6153
});
6254

6355
notification.success("Profile Edited Successfully");
@@ -72,10 +64,8 @@ const ProfileInfo: React.FC<ProfileInfoProps> = ({ address }) => {
7264

7365
useEffect(() => {
7466
if (!isEditing && punkProfile) {
75-
setUsername(punkProfile[0] || "");
76-
setBio(punkProfile[1] || "");
77-
setProfilePicture(punkProfile[2] ? punkProfile[2] : defaultProfilePicture);
78-
setEmail(punkProfile[3] || "");
67+
setBio(punkProfile[0] || "");
68+
setEmail(punkProfile[1] || "");
7969
setLoadingProfile(false);
8070
}
8171
}, [punkProfile, isEditing]);
@@ -97,35 +87,48 @@ const ProfileInfo: React.FC<ProfileInfoProps> = ({ address }) => {
9787
) : (
9888
<div className="relative flex flex-col md:flex-row justify-between items-center bg-base-100 p-6 rounded-lg shadow-md w-full my-2">
9989
{/* Profile Picture */}
100-
<div className="avatar ">
101-
<ProfilePictureUpload
102-
isEditing={isEditing}
103-
profilePicture={profilePicture}
104-
setProfilePicture={setProfilePicture}
105-
/>
106-
</div>
90+
91+
<Identity
92+
className="bg-transparent p-0 w-28 h-28"
93+
address={address}
94+
schemaId="0xf8b05c79f090979bf4a80270aba232dff11a10d9ca55c4f88de95317970f0de9"
95+
>
96+
<Avatar className="w-28 h-28" />
97+
</Identity>
98+
10799
{/* User Info Section */}
108100
<div className="flex flex-col justify-center items-center">
109101
{isEditing ? (
110102
""
111103
) : (
112104
<>
113-
<h2 className="text-2xl font-bold">{username || "Guest user"}</h2>
105+
<Identity
106+
className="bg-transparent p-0 "
107+
address={address}
108+
schemaId="0xf8b05c79f090979bf4a80270aba232dff11a10d9ca55c4f88de95317970f0de9"
109+
>
110+
<Name className="text-2xl font-bold text-black dark:text-white">
111+
<Badge />
112+
</Name>
113+
</Identity>
114+
{/* <h2 className="text-2xl font-bold">{username || "Guest user"}</h2> */}
114115
<span>
115116
{email ? (
116117
<a href={`mailto:${email}`} className="text-blue-500 underline">
117118
{email}
118119
</a>
119120
) : (
120-
"no email provided"
121+
""
121122
)}
122123
</span>
123124
{bio && <p className="text-base-content">{bio}</p>}
124125
<div className="mt-2">
125126
{address === connectedAddress ? (
126127
<div className="flex flex-col md:flex-row items-center justify-center gap-3">
127128
<div>
128-
<RainbowKitCustomConnectButton />
129+
<PunkBalance address={connectedAddress} />
130+
131+
<PunkConnectButton />
129132
</div>
130133
<div className="bg-base-200 rounded-lg">
131134
<FundButton fundingUrl={onrampBuyUrl} />
@@ -146,10 +149,13 @@ const ProfileInfo: React.FC<ProfileInfoProps> = ({ address }) => {
146149
{isEditing ? (
147150
<div className="flex flex-col justify-center items-center flex-grow text-center gap-3 md:mx-auto mt-4 md:mt-0">
148151
<>
149-
<InputBase placeholder="Your Name" value={username} onChange={setUsername} />
150152
<TextInput placeholder="Your Bio" content={bio} setContent={setBio} />
151153
{/* <InputBase placeholder="Your Bio" value={bio} onChange={setBio} /> */}
152154
<InputBase placeholder="Your Email" value={email} onChange={setEmail} />
155+
156+
<a className="underline" href="https://www.base.org/names" target="_blank" rel="noopener noreferrer">
157+
Change your basename and profile picture
158+
</a>
153159
</>
154160
</div>
155161
) : (

packages/nextjs/app/profile/_components/_bookmarked/PostCard.tsx

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from "react";
44
import Image from "next/image";
55
import Link from "next/link";
66
import BookmarkButton from "../../../../components/punk-society/BookmarkButton";
7+
import { Avatar, Identity } from "@coinbase/onchainkit/identity";
78
import { formatEther } from "viem";
89
import { useAccount } from "wagmi";
910
import { MagnifyingGlassPlusIcon, ShareIcon, ShoppingCartIcon, XMarkIcon } from "@heroicons/react/24/outline";
@@ -28,13 +29,6 @@ export const PostCard = ({ post }: { post: Post }) => {
2829
const { address: connectedAddress } = useAccount();
2930
const { writeContractAsync } = useScaffoldWriteContract("BasedShop");
3031

31-
const { data: profileInfo } = useScaffoldReadContract({
32-
contractName: "BasedProfile",
33-
functionName: "profiles",
34-
args: [post.user],
35-
watch: true,
36-
});
37-
3832
const { data: articlePrice } = useScaffoldReadContract({
3933
contractName: "BasedShop",
4034
functionName: "articlePrices",
@@ -56,10 +50,6 @@ export const PostCard = ({ post }: { post: Post }) => {
5650
watch: true,
5751
});
5852

59-
const defaultProfilePicture = "/guest-profile.jpg";
60-
61-
const profilePicture = profileInfo && profileInfo[2] ? profileInfo[2] : defaultProfilePicture;
62-
6353
const handleOpenModal = () => {
6454
setIsModalOpen(true);
6555
};
@@ -131,14 +121,13 @@ export const PostCard = ({ post }: { post: Post }) => {
131121
<div className="flex flex-row justify-center items-center gap-3">
132122
<p className="my-0 text-sm">{post.date ? formatDate(Number(post.date)) : "No date available"}</p>
133123
<Link href={`/profile/${post.user}`} passHref>
134-
<div
135-
className="w-8 h-8 rounded-full flex items-center justify-center cursor-pointer"
136-
style={{
137-
backgroundImage: `url(${profilePicture})`,
138-
backgroundSize: "cover",
139-
backgroundPosition: "center",
140-
}}
141-
></div>
124+
<Identity
125+
className="bg-transparent p-0 w-7 h-7"
126+
address={post.user}
127+
schemaId="0xf8b05c79f090979bf4a80270aba232dff11a10d9ca55c4f88de95317970f0de9"
128+
>
129+
<Avatar className="w-7 h-7" />
130+
</Identity>
142131
</Link>
143132
</div>
144133
</div>
@@ -206,8 +195,8 @@ export const PostCard = ({ post }: { post: Post }) => {
206195
Buy
207196
</button>
208197

209-
<button className="p-2 rounded-full bg-red-600 text-white">
210-
<ShoppingCartIcon className="h-5 w-5" />
198+
<button className="flex flex-row p-2 rounded-full bg-red-600 text-white">
199+
+ <ShoppingCartIcon className="h-5 w-5" />
211200
</button>
212201
</div>
213202
</div>

packages/nextjs/app/profile/_components/_bought/PostCard.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import { useEffect, useRef, useState } from "react";
44
import Image from "next/image";
5+
import Link from "next/link";
56
import BookmarkButton from "../../../../components/punk-society/BookmarkButton";
6-
import { ProfileAddress } from "../../../../components/punk-society/ProfileAddress";
7+
import { Avatar, Badge, Identity, Name } from "@coinbase/onchainkit/identity";
78
import { formatEther } from "viem";
89
import { MagnifyingGlassPlusIcon, ShareIcon, XMarkIcon } from "@heroicons/react/24/outline";
910
import { useScaffoldReadContract } from "~~/hooks/scaffold-eth";
@@ -99,7 +100,20 @@ export const PostCard = ({ post }: { post: Post }) => {
99100
<div className="flex flex-col justify-center w-2/3 pl-4">
100101
<span className="my-0 text-lg">{post.description ?? "No description available."}</span>
101102
<span className="mt-2 flex flex-row items-center justify-start gap-3 text-lg italic">
102-
Contact seller: <ProfileAddress address={post.user} />
103+
Contact seller:{" "}
104+
<Link href={`/profile/${post.user}`} passHref>
105+
<Identity
106+
className="bg-transparent p-0 "
107+
address={post.user}
108+
schemaId="0xf8b05c79f090979bf4a80270aba232dff11a10d9ca55c4f88de95317970f0de9"
109+
>
110+
<Avatar className="w-7 h-7" />
111+
<Name>
112+
<Badge />
113+
</Name>
114+
</Identity>
115+
</Link>
116+
{/* <ProfileAddress address={post.user} /> */}
103117
</span>
104118
</div>
105119
</div>

packages/nextjs/components/Footer.tsx

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,15 @@ import Link from "next/link";
33
import { usePathname } from "next/navigation";
44
import Create from "../app/create/Create";
55
import Modal from "../app/create/Modal";
6-
import { useAccount } from "wagmi";
76
import { HeartIcon } from "@heroicons/react/24/outline";
87
import { BellIcon, HomeIcon, MagnifyingGlassIcon, PlusIcon, ShoppingCartIcon } from "@heroicons/react/24/solid";
9-
import { useScaffoldReadContract } from "~~/hooks/scaffold-eth";
108

119
export const Footer = () => {
1210
const pathname = usePathname();
1311
const [showPlusIcon, setShowPlusIcon] = useState(true);
1412
const [isModalOpen, setIsModalOpen] = useState(false);
1513
const [isAnimating, setIsAnimating] = useState(false);
1614

17-
const { address: connectedAddress } = useAccount();
18-
19-
const { data: punkProfile } = useScaffoldReadContract({
20-
contractName: "BasedProfile",
21-
functionName: "profiles",
22-
args: [connectedAddress],
23-
watch: true,
24-
});
25-
26-
const defaultProfilePicture = "/guest-profile.jpg";
27-
28-
const profilePicture = punkProfile && punkProfile[2] ? punkProfile[2] : defaultProfilePicture;
29-
3015
useEffect(() => {
3116
let lastScrollY = window.scrollY;
3217

@@ -136,18 +121,6 @@ export const Footer = () => {
136121
className={`h-6 w-6 ${pathname === "/search" ? "text-blue-600" : "hover:text-blue-600"}`}
137122
/>
138123
</Link>
139-
<div className="flex flex-row items-center justify-center gap-3">
140-
<Link href={`/profile/${connectedAddress}`} passHref>
141-
<div
142-
className="w-8 h-8 rounded-full flex items-center justify-center cursor-pointer"
143-
style={{
144-
backgroundImage: `url(${profilePicture})`,
145-
backgroundSize: "cover",
146-
backgroundPosition: "center",
147-
}}
148-
></div>
149-
</Link>
150-
</div>
151124
</footer>
152125
</>
153126
);

0 commit comments

Comments
 (0)