Skip to content

Commit

Permalink
react: useAddProfileInterests and useRemoveProfileInterests
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysu committed Apr 16, 2024
1 parent a27d2b4 commit 9bb0abd
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 0 deletions.
2 changes: 2 additions & 0 deletions examples/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
UseProfileActionHistory,
UseProfileFollowers,
UseProfileFollowing,
UseProfileInterests,
UseProfileManagers,
UseProfiles,
UseRecommendProfileToggle,
Expand Down Expand Up @@ -151,6 +152,7 @@ export function App() {
<Route path="useBlockedProfiles" element={<UseBlockedProfiles />} />
<Route path="useReportProfile" element={<UseReportProfile />} />
<Route path="useRecommendProfileToggle" element={<UseRecommendProfileToggle />} />
<Route path="useProfileInterests" element={<UseProfileInterests />} />
</Route>

<Route path="/discovery">
Expand Down
5 changes: 5 additions & 0 deletions examples/web/src/profiles/ProfilesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ const profileHooks = [
description: 'Recommend a profile.',
path: '/profiles/useRecommendProfileToggle',
},
{
label: 'useProfileInterests',
description: 'Add and remove profile interests.',
path: '/profiles/useProfileInterests',
},
];

export function ProfilesPage() {
Expand Down
121 changes: 121 additions & 0 deletions examples/web/src/profiles/UseProfileInterests.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {
useAddProfileInterests,
useRemoveProfileInterests,
ProfileInterestTypes,
Profile,
} from '@lens-protocol/react-web';
import { useMemo } from 'react';

import { RequireProfileSession } from '../components/auth';

type Interest = {
parent: string;
value: ProfileInterestTypes;
label: string;
isSubcategory?: boolean;
};

function capitalize(label: string): string {
return label
.toLowerCase()
.split(' ')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}

function createInterests(categories: ProfileInterestTypes[]): Interest[] {
const interests: Interest[] = [];

categories.forEach((item) => {
const parts = item.split('__');
const category = parts[0];
const subcategory = parts[1];

const label = capitalize(
subcategory ? subcategory.replace(/_/g, ' ') : category.replace(/_/g, ' '),
);

if (subcategory) {
interests.push({
parent: category,
value: item,
label: label,
});
} else {
if (!interests.some((interest) => interest.parent === category && !interest.isSubcategory)) {
interests.push({
parent: category,
value: item,
label: label,
isSubcategory: true,
});
}
}
});

return interests;
}

function UseProfileInterestsInner({ profile }: { profile: Profile }) {
const { execute: addInterests } = useAddProfileInterests();
const { execute: removeInterests } = useRemoveProfileInterests();

const groupedInterests = useMemo(() => {
// transform ProfileInterestTypes to Interest[]
const interests = createInterests(Object.values(ProfileInterestTypes));

// Group interests by category
return interests.reduce((acc, interest) => {
acc[interest.parent] = acc[interest.parent] || [];
acc[interest.parent].push(interest);
return acc;
}, {} as Record<string, Interest[]>);
}, []);

const handleClick = async (interest: ProfileInterestTypes) => {
const request = {
interests: [interest],
};

if (profile.interests.includes(interest)) {
await removeInterests(request);
} else {
await addInterests(request);
}
};

return (
<div>
{Object.entries(groupedInterests).map(([category, items], idx) => (
<div key={idx}>
<h3>{capitalize(category.replace(/_/g, ' '))}</h3>
<div>
{items.map((item, index) => (
<button
key={index}
onClick={() => handleClick(item.value)}
style={profile.interests.includes(item.value) ? { fontWeight: 'bold' } : {}}
>
{item.label}
</button>
))}
</div>
</div>
))}
</div>
);
}

export function UseProfileInterests() {
return (
<div>
<h2>
<code>useAddProfileInterests & useRemoveProfileInterests</code>
</h2>

<RequireProfileSession message="Log in to view this example.">
{({ profile }) => <UseProfileInterestsInner profile={profile} />}
</RequireProfileSession>
</div>
);
}
1 change: 1 addition & 0 deletions examples/web/src/profiles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './UseProfile';
export * from './UseProfileActionHistory';
export * from './UseProfileFollowers';
export * from './UseProfileFollowing';
export * from './UseProfileInterests';
export * from './UseProfileManagers';
export * from './UseProfiles';
export * from './UseRecommendProfileToggle';
Expand Down
32 changes: 32 additions & 0 deletions packages/domain/src/use-cases/profile/ManageProfileInterests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ProfileId } from '../../entities';

export type ProfileInterestsRequest = {
profileId: ProfileId;
};

export interface IProfileInterestsGateway {
add(request: ProfileInterestsRequest): Promise<void>;
remove(request: ProfileInterestsRequest): Promise<void>;
}

export interface IProfileInterestsPresenter {
add(request: ProfileInterestsRequest): Promise<void>;
remove(request: ProfileInterestsRequest): Promise<void>;
}

export class ManageProfileInterests {
constructor(
private readonly gateway: IProfileInterestsGateway,
private readonly presenter: IProfileInterestsPresenter,
) {}

async add(request: ProfileInterestsRequest) {
void this.gateway.add(request);
await this.presenter.add(request);
}

async remove(request: ProfileInterestsRequest) {
void this.gateway.remove(request);
await this.presenter.remove(request);
}
}
1 change: 1 addition & 0 deletions packages/domain/src/use-cases/profile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './DismissRecommendedProfiles';
export * from './FollowPolicy';
export * from './FollowProfile';
export * from './LinkHandle';
export * from './ManageProfileInterests';
export * from './ReportProfile';
export * from './SetProfileMetadata';
export * from './ToggleProfileProperty';
Expand Down
43 changes: 43 additions & 0 deletions packages/react/src/profile/adapters/ProfileInterestsGateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
AddProfileInterestsData,
AddProfileInterestsDocument,
AddProfileInterestsVariables,
ProfileInterestTypes,
RemoveProfileInterestsData,
RemoveProfileInterestsDocument,
RemoveProfileInterestsVariables,
SafeApolloClient,
} from '@lens-protocol/api-bindings';
import { ProfileId } from '@lens-protocol/domain/entities';
import { IProfileInterestsGateway } from '@lens-protocol/domain/use-cases/profile';

export type ProfileInterestsRequest = {
profileId: ProfileId;
interests: ProfileInterestTypes[];
};

export class ProfileInterestsGateway implements IProfileInterestsGateway {
constructor(private apolloClient: SafeApolloClient) {}

async add(request: ProfileInterestsRequest) {
await this.apolloClient.mutate<AddProfileInterestsData, AddProfileInterestsVariables>({
mutation: AddProfileInterestsDocument,
variables: {
request: {
interests: request.interests,
},
},
});
}

async remove(request: ProfileInterestsRequest) {
await this.apolloClient.mutate<RemoveProfileInterestsData, RemoveProfileInterestsVariables>({
mutation: RemoveProfileInterestsDocument,
variables: {
request: {
interests: request.interests,
},
},
});
}
}
29 changes: 29 additions & 0 deletions packages/react/src/profile/adapters/ProfileInterestsPresenter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ProfileInterestTypes } from '@lens-protocol/api-bindings';
import { IProfileInterestsPresenter } from '@lens-protocol/domain/use-cases/profile';

import { IProfileCacheManager } from './IProfileCacheManager';
import { ProfileInterestsRequest } from './ProfileInterestsGateway';

export class ProfileInterestsPresenter implements IProfileInterestsPresenter {
constructor(private readonly profileCacheManager: IProfileCacheManager) {}

async add(request: ProfileInterestsRequest) {
this.profileCacheManager.update(request.profileId, (current) => {
return {
...current,
interests: [...current.interests, ...request.interests],
};
});
}

async remove(request: ProfileInterestsRequest) {
this.profileCacheManager.update(request.profileId, (current) => {
return {
...current,
interests: current.interests.filter(
(interest) => !request.interests.includes(interest as ProfileInterestTypes),
),
};
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ManageProfileInterests } from '@lens-protocol/domain/use-cases/profile';

import { useSharedDependencies } from '../../shared';
import { ProfileInterestsGateway, ProfileInterestsRequest } from './ProfileInterestsGateway';
import { ProfileInterestsPresenter } from './ProfileInterestsPresenter';

export function useProfileInterestsController() {
const { apolloClient, profileCacheManager } = useSharedDependencies();

const add = async (request: ProfileInterestsRequest) => {
const presenter = new ProfileInterestsPresenter(profileCacheManager);
const gateway = new ProfileInterestsGateway(apolloClient);
const manageInterests = new ManageProfileInterests(gateway, presenter);

await manageInterests.add(request);
};

const remove = async (request: ProfileInterestsRequest) => {
const presenter = new ProfileInterestsPresenter(profileCacheManager);
const gateway = new ProfileInterestsGateway(apolloClient);
const manageInterests = new ManageProfileInterests(gateway, presenter);

await manageInterests.remove(request);
};

return {
add,
remove,
};
}
2 changes: 2 additions & 0 deletions packages/react/src/profile/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* Hooks
*/
export * from './useAddProfileInterests';
export * from './useBlockedProfiles';
export * from './useLazyProfile';
export * from './useLazyProfiles';
Expand All @@ -12,6 +13,7 @@ export * from './useProfileFollowing';
export * from './useProfileManagers';
export * from './useProfiles';
export * from './useRecommendProfileToggle';
export * from './useRemoveProfileInterests';
export * from './useReportProfile';
export * from './useWhoActedOnPublication';

Expand Down
39 changes: 39 additions & 0 deletions packages/react/src/profile/useAddProfileInterests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ProfileInterestTypes } from '@lens-protocol/api-bindings';
import { invariant, success } from '@lens-protocol/shared-kernel';

import { SessionType, useSession } from '../authentication';
import { UseDeferredTask, useDeferredTask } from '../helpers/tasks';
import { useProfileInterestsController } from './adapters/useProfileInterestsController';

export { ProfileInterestTypes };

export type AddProfileInterestsArgs = {
interests: ProfileInterestTypes[];
};

/**
* Add profile interests.
*
* You MUST be authenticated via {@link useLogin} to use this hook.
*
* @category Profiles
* @group Hooks
*/
export function useAddProfileInterests(): UseDeferredTask<void, never, AddProfileInterestsArgs> {
const { data: session } = useSession();
const { add } = useProfileInterestsController();

return useDeferredTask(async (request) => {
invariant(
session?.type === SessionType.WithProfile,
'You must be authenticated with a profile to use this hook. Use `useLogin` hook to authenticate.',
);

await add({
profileId: session.profile.id,
interests: request.interests,
});

return success();
});
}
Loading

0 comments on commit 9bb0abd

Please sign in to comment.