Skip to content

Commit

Permalink
feat: implement community ui
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Sep 26, 2024
1 parent 58d96fc commit d85da4d
Show file tree
Hide file tree
Showing 51 changed files with 521 additions and 208 deletions.
18 changes: 9 additions & 9 deletions anchor/src/pubkey-protocol-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ export const PUBKEY_PROTOCOL_SEED_PROFILE = new TextEncoder().encode('profile')
export const PUBKEY_PROTOCOL_SEED_POINTER = new TextEncoder().encode('pointer')
export const PUBKEY_PROTOCOl_SEED_COMMUNITY = new TextEncoder().encode('community')

// Helper method to get the Community PDA
export function getPubKeyCommunityPda({ programId, slug }: { programId: PublicKey; slug: string }) {
const hash = sha256(
Uint8Array.from([...PUBKEY_PROTOCOL_PREFIX, ...PUBKEY_PROTOCOl_SEED_COMMUNITY, ...stringToUint8Array(slug)]),
)

return PublicKey.findProgramAddressSync([hash], programId)
}

// Helper method to get the PubKeyProfile PDA
export function getPubKeyProfilePda({ programId, username }: { programId: PublicKey; username: string }) {
return PublicKey.findProgramAddressSync(
Expand Down Expand Up @@ -63,15 +72,6 @@ export function getPubKeyPointerPda({
return PublicKey.findProgramAddressSync([hash], programId)
}

// Helper method to get the Community PDA
export function getPubKeyCommunityPda({ programId, slug }: { programId: PublicKey; slug: string }) {
const hash = sha256(
Uint8Array.from([...PUBKEY_PROTOCOL_PREFIX, ...PUBKEY_PROTOCOl_SEED_COMMUNITY, ...stringToUint8Array(slug)]),
)

return PublicKey.findProgramAddressSync([hash], programId)
}

export enum PubKeyIdentityProvider {
Discord = 'Discord',
Github = 'Github',
Expand Down
69 changes: 69 additions & 0 deletions sdk/src/lib/pubkey-protocol-sdk.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { AnchorProvider, Program } from '@coral-xyz/anchor'
import {
getPubKeyCommunityPda,
getPubKeyPointerPda,
getPubKeyProfilePda,
getPubkeyProtocolProgram,
PUBKEY_PROTOCOL_PROGRAM_ID,
PubKeyCommunity,
PubKeyIdentityProvider,
PubKeyPointer,
PubKeyProfile,
Expand All @@ -26,6 +28,12 @@ export interface PubKeyProfileSdkOptions {
readonly provider: AnchorProvider
}

export interface GetCommunityBySlug {
slug: string
}
export interface GetCommunityPdaOptions {
slug: string
}
export interface GetPointerPdaOptions {
provider: PubKeyIdentityProvider
providerId: string
Expand Down Expand Up @@ -75,6 +83,14 @@ export interface AddAuthorityOptions {
username: string
}

export interface CreateCommunityOptions {
avatarUrl: string
authority: PublicKey
feePayer: PublicKey
name: string
slug: string
}

export interface CreateProfileOptions {
avatarUrl: string
authority: PublicKey
Expand Down Expand Up @@ -142,6 +158,23 @@ export class PubkeyProtocolSdk {
return this.createTransaction({ ix, feePayer })
}

async createCommunity({ authority, avatarUrl, feePayer, name, slug }: CreateCommunityOptions) {
const [community] = this.getCommunityPda({ slug })

const ix = await this.program.methods
.createCommunity({ avatarUrl, name, slug, discord: '', github: '', website: '', x: '' })
.accountsStrict({
authority,
feePayer,
community,
pointer: PublicKey.default,
systemProgram: SystemProgram.programId,
})
.instruction()

return this.createTransaction({ ix, feePayer })
}

async createProfile({ authority, avatarUrl, feePayer, name, username }: CreateProfileOptions) {
const [profile] = this.getProfilePda({ username })
const [pointer] = this.getPointerPda({ provider: PubKeyIdentityProvider.Solana, providerId: authority.toString() })
Expand All @@ -159,6 +192,42 @@ export class PubkeyProtocolSdk {
return this.createTransaction({ ix, feePayer })
}

async getCommunity({ communityPda }: { communityPda: PublicKey }): Promise<PubKeyCommunity> {
return this.program.account.community.fetch(communityPda).then((res) => {
// FIXME: Properly clean up the PubKey Community Data.
return {
...res,
publicKey: communityPda,
providers: [],
} as PubKeyCommunity
})
}

async getCommunities(): Promise<PubKeyCommunity[]> {
return this.program.account.community.all().then((accounts) =>
accounts.map(({ account, publicKey }) => {
// FIXME: Properly clean up the PubKey Community Data.
return {
publicKey,
avatarUrl: account.avatarUrl,
bump: account.bump,
name: account.name,
slug: account.slug,
} as PubKeyCommunity
}),
)
}

async getCommunityBySlug({ slug }: GetCommunityBySlug): Promise<PubKeyCommunity> {
const [communityPda] = this.getCommunityPda({ slug })

return this.getCommunity({ communityPda })
}

getCommunityPda({ slug }: GetCommunityPdaOptions): [PublicKey, number] {
return getPubKeyCommunityPda({ programId: this.programId, slug })
}

async getProfiles(): Promise<PubKeyProfile[]> {
return this.program.account.profile.all().then((accounts) =>
accounts.map(({ account, publicKey }) => ({
Expand Down
4 changes: 3 additions & 1 deletion web/src/app/features/pubkey-protocol/data-access/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
export * from './pubkey-protocol-provider'
export * from './use-mutation-add-authority'
export * from './use-mutation-add-identity'
export * from './use-mutation-create-community'
export * from './use-mutation-create-profile'
export * from './use-mutation-remove-authority'
export * from './use-mutation-remove-identity'
export * from './use-mutation-update-avatar-url'
export * from './use-pubkey-protocol-sdk'
export * from './use-query-get-community-by-slug'
export * from './use-query-get-pointers'
export * from './use-query-get-profile-by-provider-nullable'
export * from './use-query-get-profile-by-username'
export * from './use-query-get-profiles'
export { useGetProfileByProviderNullable } from './use-get-profile-by-provider-nullable'
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { AccountInfo, ParsedAccountData, PublicKey, VersionedTransaction } from
import { useQueryClient } from '@tanstack/react-query'
import { createContext, ReactNode, useContext } from 'react'
import { Cluster } from '../../cluster/cluster-data-access'
import { useKeypair } from '../../keypair/data-access'
import { uiToastLink } from '../../keypair/data-access/lib/keypair-data-access'
import { uiToastLink, useKeypair } from '../../keypair/data-access'
import { WalletButton } from '../../solana/solana-provider'
import { usePubkeyProtocolSdk } from './use-pubkey-protocol-sdk'
import { useQueryGetProgramAccount } from './use-query-get-program-account'
Expand Down Expand Up @@ -113,6 +112,6 @@ export function PubkeyProtocolProvider({ children }: { children: ReactNode }) {
)
}

export function usePubKeyProfile() {
export function usePubKeyProtocol() {
return useContext(Context)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { AddAuthorityOptions } from '@pubkey-protocol/sdk'
import { useMutation } from '@tanstack/react-query'
import { usePubKeyProfile } from './pubkey-protocol-provider'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export function useMutationAddAuthority() {
const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProfile()
const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol()

return useMutation({
mutationFn: (options: AddAuthorityOptions) => sdk.addProfileAuthority(options).then(signAndConfirmTransaction),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { AddIdentityOptions } from '@pubkey-protocol/sdk'
import { useMutation } from '@tanstack/react-query'
import { usePubKeyProfile } from './pubkey-protocol-provider'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export type PubKeyAddIdentity = Omit<AddIdentityOptions, 'authority' | 'feePayer'>

export function useMutationAddIdentity() {
const { authority, feePayer, sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProfile()
const { authority, feePayer, sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol()

return useMutation({
mutationFn: (options: PubKeyAddIdentity) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CreateCommunityOptions } from '@pubkey-protocol/sdk'
import { useMutation } from '@tanstack/react-query'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export type PubKeyCommunityCreateInput = Omit<CreateCommunityOptions, 'authority' | 'feePayer'>

export function useMutationCreateCommunity() {
const { authority, feePayer, sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol()

return useMutation({
mutationFn: (options: PubKeyCommunityCreateInput) =>
sdk
.createCommunity({
...options,
avatarUrl: options.avatarUrl || `https://api.dicebear.com/9.x/bottts-neutral/svg?seed=${options.slug}`,
authority,
feePayer,
})
.then(signAndConfirmTransaction),
onError,
onSuccess,
})
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { CreateProfileOptions } from '@pubkey-protocol/sdk'
import { useMutation } from '@tanstack/react-query'
import { usePubKeyProfile } from './pubkey-protocol-provider'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export type PubKeyProfileCreateInput = Omit<CreateProfileOptions, 'authority' | 'feePayer'>

export function useMutationCreateProfile() {
const { authority, feePayer, sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProfile()
const { authority, feePayer, sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol()

return useMutation({
mutationFn: (options: PubKeyProfileCreateInput) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { RemoveAuthorityOptions } from '@pubkey-protocol/sdk'
import { useMutation } from '@tanstack/react-query'
import { usePubKeyProfile } from './pubkey-protocol-provider'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export function useMutationRemoveAuthority() {
const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProfile()
const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol()

return useMutation({
mutationFn: (options: RemoveAuthorityOptions) => sdk.removeAuthority(options).then(signAndConfirmTransaction),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { RemoveIdentityOptions } from '@pubkey-protocol/sdk'
import { useMutation } from '@tanstack/react-query'
import { usePubKeyProfile } from './pubkey-protocol-provider'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export function useMutationRemoveIdentity() {
const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProfile()
const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol()

return useMutation({
mutationFn: (options: RemoveIdentityOptions) => sdk.removeIdentity(options).then(signAndConfirmTransaction),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { UpdateAvatarUrlOptions } from '@pubkey-protocol/sdk'
import { useMutation } from '@tanstack/react-query'
import { usePubKeyProfile } from './pubkey-protocol-provider'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export function useMutationUpdateAvatarUrl() {
const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProfile()
const { sdk, signAndConfirmTransaction, onError, onSuccess } = usePubKeyProtocol()

return useMutation({
mutationFn: (options: UpdateAvatarUrlOptions) => sdk.updateProfileDetails(options).then(signAndConfirmTransaction),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { usePubKeyProtocol } from './pubkey-protocol-provider'
import { useQuery } from '@tanstack/react-query'

export function useQueryGetCommunityBySlug({ slug }: { slug: string }) {
const { sdk, cluster } = usePubKeyProtocol()

return useQuery({
queryKey: ['pubkey-protocol', 'getCommunityBySlug', { cluster, slug }],
queryFn: () => sdk.getCommunityBySlug({ slug }),
})
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useQuery } from '@tanstack/react-query'
import { usePubKeyProfile } from './pubkey-protocol-provider'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export function useQueryGetPointers() {
const { cluster, sdk } = usePubKeyProfile()
const { cluster, sdk } = usePubKeyProtocol()

return useQuery({
queryKey: ['pubkey-protocol', 'getPointers', { cluster }],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { PubKeyIdentityProvider } from '@pubkey-protocol/anchor'
import { useQuery } from '@tanstack/react-query'
import { usePubKeyProfile } from './pubkey-protocol-provider'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export function useGetProfileByProviderNullable({
export function useQueryGetProfileByProviderNullable({
provider,
providerId,
}: {
provider: PubKeyIdentityProvider
providerId: string
}) {
const { cluster, sdk } = usePubKeyProfile()
const { cluster, sdk } = usePubKeyProtocol()

return useQuery({
queryKey: ['pubkey-protocol', 'getProfileByProviderNullable', { cluster, provider, providerId }],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useQuery } from '@tanstack/react-query'
import { usePubKeyProfile } from './pubkey-protocol-provider'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export function useQueryGetProfileByUsername({ username }: { username: string }) {
const { sdk, cluster } = usePubKeyProfile()
const { sdk, cluster } = usePubKeyProtocol()

return useQuery({
queryKey: ['pubkey-protocol', 'getProfileByUsername', { cluster, username }],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { useQuery } from '@tanstack/react-query'
import { usePubKeyProfile } from './pubkey-protocol-provider'
import { usePubKeyProtocol } from './pubkey-protocol-provider'

export function useQueryGetProfiles() {
const { cluster, sdk } = usePubKeyProfile()
const { cluster, sdk } = usePubKeyProtocol()

return useQuery({
queryKey: ['pubkey-protocol', 'getProfiles', { cluster }],
queryFn: () => sdk.getProfiles(),
})
}
export function useQueryGetCommunities() {
const { cluster, sdk } = usePubKeyProtocol()

return useQuery({
queryKey: ['pubkey-protocol', 'getCommunities', { cluster }],
queryFn: () => sdk.getCommunities(),
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { toastError, toastSuccess, UiCard, UiPage } from '@pubkey-ui/core'
import { IconUserPlus } from '@tabler/icons-react'
import { useMutationCreateCommunity } from '../data-access'
import { PubkeyProtocolUiCommunityCreateForm } from '../ui'

export function PubkeyProtocolFeatureCommunityCreate() {
const mutation = useMutationCreateCommunity()

return (
<UiPage leftAction={<IconUserPlus />} title="Create Community">
<UiCard title="Create Community">
<PubkeyProtocolUiCommunityCreateForm
submit={(input) =>
mutation
.mutateAsync(input)
.then(() => toastSuccess(`Community created`))
.catch((err) => toastError(`Error: ${err}`))
}
/>
</UiCard>
</UiPage>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { UiLoader, UiPage, UiWarning } from '@pubkey-ui/core'
import { IconUsersGroup } from '@tabler/icons-react'
import { useParams } from 'react-router-dom'
import { useQueryGetCommunityBySlug } from '../data-access'
import { PubkeyProtocolUiCommunityCard } from '../ui'

export function PubkeyProtocolFeatureCommunityDetail() {
const { slug } = useParams() as { slug: string }

const query = useQueryGetCommunityBySlug({ slug })

return (
<UiPage leftAction={<IconUsersGroup />} title={slug}>
{query.isLoading ? (
<UiLoader />
) : query.data ? (
<PubkeyProtocolUiCommunityCard community={query.data} />
) : (
<UiWarning message="User not found" />
)}
</UiPage>
)
}
Loading

0 comments on commit d85da4d

Please sign in to comment.