Skip to content

Commit

Permalink
feat: show creator + badge on user profile (#597)
Browse files Browse the repository at this point in the history
  • Loading branch information
pradel committed Jul 13, 2022
1 parent 1aae1a9 commit 696ef4f
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/bright-kids-juggle.md
@@ -0,0 +1,5 @@
---
'@sigle/app': minor
---

Display The Explorer Guild creator + badge on the profile page of a user.
60 changes: 60 additions & 0 deletions server/src/api/modules/users/[userId].test.ts
@@ -0,0 +1,60 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { FastifyInstance } from 'fastify';
import { TestBaseDB, TestDBUser } from '../../../jest/db';
import { prisma } from '../../../prisma';
import { redis } from '../../../redis';
import { buildFastifyServer } from '../../../server';

let server: FastifyInstance;

beforeAll(() => {
server = buildFastifyServer();
});

afterAll(async () => {
await TestBaseDB.cleanup();
await redis.quit();
await prisma.$disconnect();
});

beforeEach(async () => {
await prisma.user.deleteMany({});
await prisma.subscription.deleteMany({});
});

it('Should return public user without subscription', async () => {
const stacksAddress = 'SP3VCX5NFQ8VCHFS9M6N40ZJNVTRT4HZ62WFH5C4Q';
const user = await TestDBUser.seedUser({
stacksAddress,
});

const response = await server.inject({
method: 'GET',
url: `/api/users/${stacksAddress}`,
});

expect(response.statusCode).toBe(200);
expect(response.json()).toEqual({ id: user.id, stacksAddress });
});

it('Should return public user with subscription', async () => {
const stacksAddress = 'SP3VCX5NFQ8VCHFS9M6N40ZJNVTRT4HZ62WFH5C4Q';
const user = await TestDBUser.seedUserWithSubscription({
stacksAddress,
});

const response = await server.inject({
method: 'GET',
url: `/api/users/${stacksAddress}`,
});

expect(response.statusCode).toBe(200);
expect(response.json()).toEqual({
id: user.id,
stacksAddress,
subscription: {
id: expect.any(String),
nftId: expect.any(Number),
},
});
});
74 changes: 74 additions & 0 deletions server/src/api/modules/users/[userId].ts
@@ -0,0 +1,74 @@
import { FastifyInstance } from 'fastify';
import { prisma } from '../../../prisma';

type GetUserByAddressResponse = {
id: string;
stacksAddress: string;
subscription?: {
id: string;
nftId: number;
};
} | null;
const getUserByAddressResponseSchema = {
type: 'object',
nullable: true,
properties: {
id: { type: 'string' },
stacksAddress: { type: 'string' },
subscription: {
type: 'object',
nullable: true,
properties: {
id: { type: 'string' },
nftId: { type: 'number' },
},
},
},
};

export async function createGetUserByAddressEndpoint(fastify: FastifyInstance) {
return fastify.get<{
Reply: GetUserByAddressResponse;
Params: { userAddress: string };
}>(
'/api/users/:userAddress',
{
schema: {
response: {
200: getUserByAddressResponseSchema,
},
},
},
async (req, res) => {
const { userAddress } = req.params;

const user = await prisma.user.findUnique({
where: { stacksAddress: userAddress },
select: {
id: true,
stacksAddress: true,
subscriptions: {
select: {
id: true,
nftId: true,
},
where: {
status: 'ACTIVE',
},
take: 1,
},
},
});

return res.send(
user
? {
id: user.id,
stacksAddress: user.stacksAddress,
subscription: user.subscriptions[0],
}
: null
);
}
);
}
1 change: 0 additions & 1 deletion server/src/api/modules/users/me.ts
Expand Up @@ -7,7 +7,6 @@ type GetUserMeResponse = {
};
const getUserMeResponseSchema = {
type: 'object',
nullable: true,
properties: {
id: { type: 'string' },
stacksAddress: { type: 'string' },
Expand Down
3 changes: 3 additions & 0 deletions server/src/server.ts
Expand Up @@ -12,6 +12,7 @@ import { fastifyAuthPlugin } from './api/plugins/auth';
import { createSubscriptionCreatorPlusEndpoint } from './api/modules/subscriptions/creatorPlus';
import { createGetSubscriptionEndpoint } from './api/modules/subscriptions/getSubscription';
import { createGetUserMeEndpoint } from './api/modules/users/me';
import { createGetUserByAddressEndpoint } from './api/modules/users/[userId]';

export const buildFastifyServer = (
opts: FastifyServerOptions<Server, FastifyLoggerInstance> = {}
Expand Down Expand Up @@ -103,6 +104,8 @@ export const buildFastifyServer = (
});
});

createGetUserByAddressEndpoint(fastify);

/**
* All the protected routes must be placed there.
*/
Expand Down
5 changes: 5 additions & 0 deletions sigle/next.config.js
Expand Up @@ -9,6 +9,11 @@ const { withPlausibleProxy } = require('next-plausible');
dotenv.config();

const nextConfig = {
experimental: {
images: {
allowFutureImage: true,
},
},
swcMinify: true,
env: {
APP_URL: process.env.APP_URL,
Expand Down
7 changes: 7 additions & 0 deletions sigle/public/img/badges/creatorPlusDark.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions sigle/public/img/badges/creatorPlusLight.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions sigle/src/hooks/users.ts
Expand Up @@ -27,3 +27,34 @@ export const useGetUserMe = (
},
options
);

type UserByAddressResponse = {
id: string;
stacksAddress: string;
subscription?: {
id: string;
nftId: number;
};
} | null;

export const useGetUserByAddress = (
stacksAddress: string,
options: UseQueryOptions<UserByAddressResponse, Error> = {}
) =>
useQuery<UserByAddressResponse, Error>(
['get-user-by-address', stacksAddress],
async () => {
const res = await fetch(
`${sigleConfig.apiUrl}/api/users/${stacksAddress}`,
{
method: 'GET',
}
);
const json = await res.json();
if (!res.ok) {
throw json;
}
return json;
},
options
);
37 changes: 37 additions & 0 deletions sigle/src/modules/publicHome/components/PublicHome.tsx
@@ -1,5 +1,7 @@
import React from 'react';
import { NextSeo } from 'next-seo';
import Image from 'next/future/image';
import { useTheme } from 'next-themes';
import { StoryFile, SettingsFile } from '../../../types';
import { PoweredBy } from '../../publicStory/PoweredBy';
import { AppHeader } from '../../layout/components/AppHeader';
Expand All @@ -13,6 +15,9 @@ import {
TabsContent,
TabsList,
Typography,
Tooltip,
TooltipTrigger,
TooltipContent,
} from '../../../ui';
import { sigleConfig } from '../../../config';
import { styled } from '../../../stitches.config';
Expand All @@ -25,6 +30,7 @@ import {
import { generateAvatar } from '../../../utils/boringAvatar';
import { useFeatureFlags } from '../../../utils/featureFlags';
import { StoryCard } from '../../storyCard/StoryCard';
import { useGetUserByAddress } from '../../../hooks/users';

const ExtraInfoLink = styled('a', {
color: '$gray9',
Expand Down Expand Up @@ -102,8 +108,10 @@ interface PublicHomeProps {
}

export const PublicHome = ({ file, settings, userInfo }: PublicHomeProps) => {
const { resolvedTheme } = useTheme();
const { user } = useAuth();
const { isExperimentalFollowEnabled } = useFeatureFlags();
const { data: userInfoByAddress } = useGetUserByAddress(userInfo.address);
const { data: userFollowing } = useGetUserFollowing({
enabled: !!user && userInfo.username !== user.username,
});
Expand Down Expand Up @@ -208,6 +216,35 @@ export const PublicHome = ({ file, settings, userInfo }: PublicHomeProps) => {
>
{userInfo.username}
</Box>
{userInfoByAddress?.subscription && (
<Tooltip delayDuration={200}>
<TooltipTrigger asChild>
<a
href={`${sigleConfig.gammaUrl}/${userInfoByAddress.subscription.nftId}`}
target="_blank"
rel="noreferrer"
>
<Image
src={
resolvedTheme === 'dark'
? '/img/badges/creatorPlusDark.svg'
: '/img/badges/creatorPlusLight.svg'
}
alt="Creator + badge"
width={20}
height={20}
/>
</a>
</TooltipTrigger>
<TooltipContent
css={{ boxShadow: 'none' }}
side="right"
sideOffset={8}
>
Creator + Explorer #{userInfoByAddress.subscription.nftId}
</TooltipContent>
</Tooltip>
)}
</Flex>
<Flex css={{ pt: '$3' }} gap="3" align="center">
{settings.siteUrl && (
Expand Down
2 changes: 1 addition & 1 deletion sigle/src/pages/api/feed/[username].test.ts
Expand Up @@ -51,7 +51,7 @@ describe('test feed api', () => {
item: expect.any(Array),
lastBuildDate: expect.any(String),
link: 'https://app.sigle.io/sigleapp.id.blockstack',
title: 'Sigle official blog',
title: 'Sigle',
});
// Last items should never change
expect(
Expand Down

0 comments on commit 696ef4f

Please sign in to comment.