-
Notifications
You must be signed in to change notification settings - Fork 1
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: introduce useSocial
and useSocialProfile
hooks
#22
base: beta
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import * as React from 'react'; | ||
import Social from '../controllers/Social'; | ||
|
||
type SocialContextType = { | ||
social: Social; | ||
}; | ||
|
||
export const SocialContext = React.createContext<SocialContextType | undefined>(undefined); | ||
|
||
type SocialProviderProps = { | ||
children: React.ReactNode; | ||
contractId?: string; | ||
onProvision?: (social: Social) => void; | ||
}; | ||
|
||
export const SocialProvider = ({ | ||
children, | ||
contractId, | ||
onProvision, | ||
}: SocialProviderProps) => { | ||
const [social] = React.useState<Social>(new Social({ contractId })); | ||
|
||
React.useEffect(() => { | ||
if (contractId) { | ||
social.setContractId(contractId); | ||
} | ||
}, [contractId, social]); | ||
|
||
React.useEffect(() => { | ||
if (onProvision) { | ||
onProvision(social); | ||
} | ||
}, [social, onProvision]); | ||
|
||
return ( | ||
<SocialContext.Provider value={{ social }}> | ||
{children} | ||
</SocialContext.Provider> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const SOCIAL_IPFS_BASE_URL = 'https://ipfs.near.social/ipfs'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './Fees'; | ||
export * from './SocialNearIpfs'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './useSocialProfile'; | ||
export * from './useSocial'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import React from 'react'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import { SocialProvider } from '../components/SocialProvider'; | ||
import { useSocial } from '@app/hooks'; | ||
import { Account } from 'near-api-js'; | ||
import createEphemeralAccount from '@test/helpers/createEphemeralAccount'; | ||
|
||
jest.mock('@app/hooks', () => ({ | ||
|
||
useSocial: jest.fn(() => ({ | ||
social: { | ||
async get({ keys }: { keys: string[] }) { | ||
return keys.reduce((acc, key) => { | ||
acc[key] = { profile: { name: 'TestUser' } }; | ||
return acc; | ||
}, {} as Record<string, any>); | ||
}, | ||
async getVersion() { | ||
return '1.0.0'; | ||
}, | ||
async set({ data }: { data: Record<string, any> }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As before, |
||
return { data }; | ||
}, | ||
setContractId(contractId: string) { | ||
return contractId; | ||
}, | ||
}, | ||
})), | ||
})); | ||
|
||
describe('useSocial hook', () => { | ||
let signer: Account; | ||
|
||
beforeEach(async () => { | ||
const result = await createEphemeralAccount(); | ||
signer = result.account; | ||
}); | ||
|
||
it('should fetch data using get method', async () => { | ||
const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( | ||
<SocialProvider> | ||
{children} | ||
</SocialProvider> | ||
); | ||
|
||
const { result } = renderHook(() => useSocial(), { wrapper }); | ||
|
||
const { social } = result.current; | ||
|
||
let data: any; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this will fail the linter. For objects, we can use |
||
await React.act(async () => { | ||
data = await social.get({ keys: ['unknown.test.near'], signer }); | ||
}); | ||
|
||
expect(data).toEqual({ 'unknown.test.near': { profile: { name: 'TestUser' } } }); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { useContext } from 'react'; | ||
|
||
import { SocialContext } from '../components/SocialProvider'; | ||
Check failure on line 3 in src/hooks/useSocial.ts GitHub Actions / Build Package
|
||
|
||
export const useSocial = () => { | ||
const context = useContext(SocialContext); | ||
if (context === undefined) { | ||
throw new Error('useSocial must be used within a SocialProvider'); | ||
} | ||
return context; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import React from 'react'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import { SocialProvider } from '../components/SocialProvider'; | ||
import { useSocialProfile } from '@app/hooks'; | ||
import { Account } from 'near-api-js'; | ||
import createEphemeralAccount from '@test/helpers/createEphemeralAccount'; | ||
|
||
jest.mock('@app/hooks', () => ({ | ||
useSocialProfile: jest.fn(() => ({ | ||
profile: { name: 'TestUser', description: 'Test description' }, | ||
profileImageUrl: 'https://example.com/image.jpg', | ||
error: null, | ||
})), | ||
})); | ||
|
||
describe('useSocialProfile hook', () => { | ||
let signer: Account; | ||
|
||
beforeEach(async () => { | ||
const result = await createEphemeralAccount(); | ||
signer = result.account; | ||
}); | ||
|
||
it('should fetch profile data for a given accountId', async () => { | ||
const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( | ||
<SocialProvider> | ||
{children} | ||
</SocialProvider> | ||
); | ||
|
||
const { result } = renderHook(() => useSocialProfile(signer.accountId, signer), { wrapper }); | ||
const { profile } = result.current; | ||
|
||
expect(profile).toEqual({ name: 'TestUser', description: 'Test description' }); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { useEffect, useState } from 'react'; | ||
import { useSocial } from './useSocial'; | ||
import { SOCIAL_IPFS_BASE_URL } from '../constants'; | ||
import { ISocialProfile } from '@app/types'; | ||
import { Account } from 'near-api-js'; | ||
|
||
export const useSocialProfile = ( | ||
accountId: string | null | undefined, | ||
signer: Account | ||
) => { | ||
const { social } = useSocial(); | ||
const [profile, setProfile] = useState<ISocialProfile | undefined>(undefined); | ||
const [error, setError] = useState<string | null>(null); | ||
|
||
const profileImageUrl = | ||
profile?.image?.url ?? profile?.image?.ipfs_cid | ||
? `${SOCIAL_IPFS_BASE_URL}/${profile?.image?.ipfs_cid}` | ||
: undefined; | ||
|
||
useEffect(() => { | ||
if (!accountId || !social) return; | ||
|
||
const fetchProfile = async () => { | ||
try { | ||
const response = await social.get({ | ||
signer, | ||
keys: [`${accountId}/profile/**`], | ||
}); | ||
|
||
setProfile( | ||
(response[accountId] as { profile?: ISocialProfile })?.profile ?? {} | ||
); | ||
} catch (error) { | ||
console.error(error); | ||
setError('Failed to fetch profile data'); | ||
} | ||
}; | ||
|
||
fetchProfile(); | ||
}, [accountId, social, signer]); | ||
|
||
return { | ||
profile, | ||
profileImageUrl, | ||
error, | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
interface ISocialProfile { | ||
backgroundImage?: { | ||
ipfs_cid?: string; | ||
url?: string; | ||
}; | ||
description?: string; | ||
linktree?: { | ||
github?: string; | ||
twitter?: string; | ||
website?: string; | ||
}; | ||
name?: string; | ||
image?: { | ||
ipfs_cid?: string; | ||
url?: string; | ||
}; | ||
tags?: Record<string, ''>; | ||
} | ||
|
||
export default ISocialProfile; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.reduce
allows you to declare the the type to cast, rather than using theas
notation.