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: implement profile pictures #28

Merged
merged 13 commits into from
Aug 31, 2022
Merged
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"classnames": "^2.3.1",
"ethers": "^5.6.9",
"eventemitter3": "^4.0.7",
"js-waku": "^0.25.0-rc.1",
"js-waku": "^0.24.0-e3bef47",
"protons-runtime": "^3.1.0",
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
Expand Down
19 changes: 11 additions & 8 deletions src/components/modals/create-avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Store
import { useRef, useState } from 'react'
import { useStore } from '../../store'
import { setStore } from '../../store'
import cancel from '../../assets/imgs/cancel.svg?url'
import checkMarkBlue from '../../assets/imgs/checkMarkBlue.svg?url'
import iconRotate from '../../assets/imgs/iconRotate.svg?url'
Expand All @@ -16,11 +16,8 @@ interface Props {

export const CreateAvatar = ({ children }: Props) => {
const [avatar, setAvatar] = useState<string>('')
const cropperRef = useRef<CropperRef>(null)

const [shown, setShown] = useState<boolean>()

const [profile, setProfile] = useStore.profile()
const cropperRef = useRef<CropperRef>(null)

const onFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
if (!(event.target instanceof HTMLInputElement)) {
Expand Down Expand Up @@ -85,9 +82,15 @@ export const CreateAvatar = ({ children }: Props) => {
className="btn-icon"
onClick={(e) => {
e.stopPropagation()
updateAvatar().then((newAvatar) =>
setProfile({ ...profile, avatar: newAvatar })
)
updateAvatar().then((newAvatar) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
setStore.profile.avatar(newAvatar)

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
setStore.profile.lastUpdate(new Date())
})
setShown(false)
}}
>
Expand Down
4 changes: 2 additions & 2 deletions src/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -7846,7 +7846,7 @@ body #app figure.avatar.avatar-sm {
width: auto;
align-items: center;
}
body #app figure.avatar.avatar-sm img {
body #app figure.avatar.avatar-sm img.avatar-img {
width: 40px;
height: auto;
margin-right: 10px;
Expand Down Expand Up @@ -8587,7 +8587,7 @@ body #app .account-wallet .flex-space {
body #app .account-wallet .flex-space figure.avatar .username {
font-size: 14px;
}
body #app .account-wallet .flex-space figure.avatar.avatar-sm img {
body #app .account-wallet .flex-space figure.avatar.avatar-sm img.avatar-img {
width: 60px;
}
}
Expand Down
22 changes: 20 additions & 2 deletions src/hooks/use-waku.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { multiaddr } from '@multiformats/multiaddr'
import { Waku } from 'js-waku'
import { waitForRemotePeer, Waku } from 'js-waku'
import { createWaku, CreateOptions } from 'js-waku/lib/create_waku'
import {
createContext,
Expand All @@ -9,6 +9,9 @@ import {
useState,
} from 'react'

// Types
import type { Protocols } from 'js-waku'

// Config
const DEFAULT_SETTINGS: CreateOptions = {}

Expand Down Expand Up @@ -43,10 +46,25 @@ export const WakuProvider = ({
)
}

export const useWaku = () => {
export const useWakuContext = () => {
const context = useContext(WakuContext)
if (!context) {
throw new Error('no context')
}
return context
}

export const useWaku = (protocols?: Protocols[]) => {
const { waku } = useWakuContext()
const [waiting, setWaiting] = useState(true)

useEffect(() => {
if (!waku) {
return
}

waitForRemotePeer(waku, protocols).then(() => setWaiting(false))
}, [waku])

return { waku, waiting }
}
10 changes: 8 additions & 2 deletions src/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@ export function updateLocalStore<Store extends Record<string, unknown>>(
}
}

export function readLocalStore<T>(key: string, prefix?: string): T | undefined {
export function readLocalStore<T, K extends keyof T>(
key: string,
prefix?: string,
reviver?: T extends Record<string, unknown>
? (key: keyof T, value: string) => T[K]
: undefined
): T | undefined {
try {
const json = localStorage.getItem(getKey(key, prefix))
return json ? (JSON.parse(json) as T) : undefined
return json && JSON.parse(json, reviver)
} catch (err) {
return undefined
}
Expand Down
8 changes: 8 additions & 0 deletions src/lib/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ export const bufferToHex = (buffer: ArrayBuffer) => {
.map((x) => x.toString(16).padStart(2, '0'))
.join('')
}

export const displayAddress = (address: string) => {
return address.substring(0, 6) + '..' + address.substring(38)
}

export const dataUriToBlob = async (dataUri: string) => {
return await (await fetch(dataUri)).blob()
}
1 change: 1 addition & 0 deletions src/pages/create-account/choose-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const ChoosePassword = () => {
...profile,
encryptedWallet,
address: wallet.address,
lastUpdate: new Date(),
})
setLoading(false)
navigate(ACCOUNT_CREATED)
Expand Down
15 changes: 14 additions & 1 deletion src/pages/marketplaces/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { MarketplaceItem } from './item'

// Components
import { Redirect } from '../../components/redirect'
import { CreateAvatar } from '../../components/modals/create-avatar'

// Lib
import { formatBalance } from '../../lib/tools'
Expand All @@ -21,6 +22,9 @@ import { formatBalance } from '../../lib/tools'
import avatarDefault from '../../assets/imgs/avatar.svg?url'
import exit from '../../assets/imgs/exit.svg?url'

// Services
import { useSyncProfile } from '../../services/profile'

export const Marketplaces = () => {
const [profile, setProfile] = useStore.profile()

Expand All @@ -33,6 +37,9 @@ export const Marketplaces = () => {
watch: true,
})

// Keep the profile in sync
useSyncProfile()

if (!profile?.address) {
return <Redirect to={LOGIN} />
}
Expand All @@ -47,7 +54,13 @@ export const Marketplaces = () => {
<div className="container">
<main className="flex-space">
<figure className="avatar avatar-sm">
<img src={profile?.avatar || avatarDefault} alt="user avatar" />
<CreateAvatar>
<img
className="avatar-img"
src={profile?.avatar || avatarDefault}
alt="user avatar"
/>
</CreateAvatar>
<figcaption>
<a href="#" className="username">
{profile?.username}
Expand Down
65 changes: 57 additions & 8 deletions src/pages/marketplaces/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,30 @@ import { useParams, useNavigate } from 'react-router'
import { useAccount } from 'wagmi'

// Hooks
import { useWaku } from '../../hooks/use-waku'
import { useWakuContext } from '../../hooks/use-waku'

// Lib
import { bufferToHex, displayAddress } from '../../lib/tools'

// Services
import {
useMarketplaceContract,
useMarketplaceTokenDecimals,
} from './services/marketplace'
import { createReply, useItemReplies } from './services/marketplace-item'
import {
createReply,
ItemReplyClean,
useItemReplies,
} from './services/marketplace-item'
import { Item, useMarketplaceItems } from './services/marketplace-items'
import { useProfile } from '../../services/profile'
import { useProfilePicture } from '../../services/profile-picture'

// Assets
import avatarDefault from '../../assets/imgs/avatar.svg?url'

// Protos
import { ProfilePicture as ProfilePictureProto } from '../../protos/ProfilePicture'

type ReplyFormProps = {
item: Item
Expand All @@ -24,7 +39,7 @@ type ReplyFormProps = {
const ReplyForm = ({ item, marketplace, decimals }: ReplyFormProps) => {
const [text, setText] = useState('')

const { waku } = useWaku()
const { waku } = useWakuContext()
const { connector } = useAccount()

const postReply = async (event: FormEvent<HTMLElement>) => {
Expand Down Expand Up @@ -55,6 +70,43 @@ const ReplyForm = ({ item, marketplace, decimals }: ReplyFormProps) => {
)
}

const ProfilePicture = ({ picture }: { picture?: ProfilePictureProto }) => {
const avatar = useMemo(() => {
if (!picture) {
return avatarDefault
}

const blob = new Blob([picture.data], { type: picture?.type })
return URL.createObjectURL(blob)
}, [picture])

return <img src={avatar} />
}

const formatFrom = (address: string, username?: string) => {
if (!username) {
return displayAddress(address)
}

return `${username} (${displayAddress(address)})`
}

const Reply = ({ reply }: { reply: ItemReplyClean }) => {
const data = useProfile(reply.from)
const { profile } = data
const { picture } = useProfilePicture(
profile?.pictureHash ? bufferToHex(profile.pictureHash) : ''
)

return (
<li>
<p>From: {formatFrom(reply.from, profile?.username)}</p>
<ProfilePicture picture={picture} />
<p>{reply.text}</p>
</li>
)
}

export const MarketplaceItem = () => {
const { id, item: itemIdString } = useParams<{ id: string; item: string }>()
if (!id || !itemIdString) {
Expand All @@ -64,7 +116,7 @@ export const MarketplaceItem = () => {
const itemId = BigNumber.from(itemIdString)

const { address } = useAccount()
const { waku } = useWaku()
const { waku } = useWakuContext()
const { decimals } = useMarketplaceTokenDecimals(id)
const contract = useMarketplaceContract(id)
const { connector } = useAccount()
Expand Down Expand Up @@ -114,10 +166,7 @@ export const MarketplaceItem = () => {
{replies.length ? (
<ul>
{replies.map((reply) => (
<li key={reply.signature}>
<p>From: {reply.from}</p>
<p>{reply.text}</p>
</li>
<Reply key={reply.signature} reply={reply} />
))}
</ul>
) : (
Expand Down
4 changes: 2 additions & 2 deletions src/pages/marketplaces/list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState } from 'react'
import { useParams } from 'react-router'

// Hooks
import { useWaku } from '../../hooks/use-waku'
import { useWakuContext } from '../../hooks/use-waku'

// Types
import type { FormEvent } from 'react'
Expand All @@ -17,7 +17,7 @@ export const MarketplaceListItem = () => {

const [description, setDescription] = useState<string>()
const [price, setPrice] = useState<number>()
const { waku } = useWaku()
const { waku } = useWakuContext()
const { connector } = useAccount()

const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/marketplaces/marketplace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Link, useParams } from 'react-router-dom'
import { useAccount } from 'wagmi'

// Hooks
import { useWaku } from '../../hooks/use-waku'
import { useWakuContext } from '../../hooks/use-waku'

// Routes
import { MARKETPLACE_ADD } from '../../routes'
Expand Down Expand Up @@ -55,7 +55,7 @@ export const Marketplace = () => {
}

const { address } = useAccount()
const { waku } = useWaku()
const { waku } = useWakuContext()
const { loading, waiting, items, lastUpdate } = useMarketplaceItems(waku, id)
const { decimals } = useMarketplaceTokenDecimals(id)
const name = useMarketplaceName(id)
Expand Down
4 changes: 2 additions & 2 deletions src/pages/marketplaces/services/marketplace-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import type { BigNumber, Signer } from 'ethers'
// Protos
import { ItemReply } from '../../../protos/ItemReply'

type CreateReply = {
export type CreateReply = {
text: string
}

type ItemReplyClean = {
export type ItemReplyClean = {
marketplace: string
item: bigint
text: string
Expand Down
2 changes: 1 addition & 1 deletion src/pages/marketplaces/services/marketplace-items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const useGetWakuItems = (
marketplace: string
) => {
const [waiting, setWaiting] = useState(true)
const [loading, setLoading] = useState(false)
const [loading, setLoading] = useState(true)
const [items, setItems] = useState<WakuItem[]>([])
const [lastUpdate, setLastUpdate] = useState(Date.now())

Expand Down
Loading