TryGhost/Ghost

View on GitHub
apps/admin-x-activitypub/src/components/Activities.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import APAvatar, {AvatarBadge} from './global/APAvatar';
import ActivityItem from './activities/ActivityItem';
import MainNavigation from './navigation/MainNavigation';
import React from 'react';
import {Button} from '@tryghost/admin-x-design-system';
import {useBrowseInboxForUser, useFollowersForUser} from '../MainContent';

interface ActivitiesProps {}

// eslint-disable-next-line no-shadow
enum ACTVITY_TYPE {
    LIKE = 'Like',
    FOLLOW = 'Follow'
}

type Actor = {
    id: string
    name: string
    preferredUsername: string
    url: string
}

type ActivityObject = {
    name: string
    url: string
}

type Activity = {
    id: string
    type: ACTVITY_TYPE
    object?: ActivityObject
    actor: Actor
}

const getActorUsername = (actor: Actor): string => {
    const url = new URL(actor.url);
    const domain = url.hostname;

    return `@${actor.preferredUsername}@${domain}`;
};

const getActivityDescription = (activity: Activity): string => {
    switch (activity.type) {
    case ACTVITY_TYPE.FOLLOW:
        return 'Followed you';
    case ACTVITY_TYPE.LIKE:
        if (activity.object) {
            return `Liked your article "${activity.object.name}"`;
        }
    }

    return '';
};

const getActivityUrl = (activity: Activity): string | null => {
    if (activity.object) {
        return activity.object.url;
    }

    return null;
};

const getActorUrl = (activity: Activity): string | null => {
    if (activity.actor) {
        return activity.actor.url;
    }

    return null;
};

const getActivityBadge = (activity: Activity): AvatarBadge => {
    switch (activity.type) {
    case ACTVITY_TYPE.FOLLOW:
        return 'user-fill';
    case ACTVITY_TYPE.LIKE:
        if (activity.object) {
            return 'heart-fill';
        }
    }    
};

const isFollower = (id: string, followerIds: string[]): boolean => {
    return followerIds.includes(id);
};

const Activities: React.FC<ActivitiesProps> = ({}) => {
    const user = 'index';
    const {data: activityData} = useBrowseInboxForUser(user);
    const activities = (activityData || [])
        .filter((activity) => {
            return [ACTVITY_TYPE.FOLLOW, ACTVITY_TYPE.LIKE].includes(activity.type);
        })
        .reverse(); // Endpoint currently returns items oldest-newest
    const {data: followerData} = useFollowersForUser(user);
    const followers = followerData || [];

    return (
        <>
            <MainNavigation title='Activities' />
            <div className='z-0 flex w-full flex-col items-center'>
                {activities.length === 0 && (
                    <div className='mt-8 font-bold'>This is an empty state when there are no activities</div>
                )}
                {activities.length > 0 && (
                    <div className='mt-8 flex w-full max-w-[560px] flex-col'>
                        {activities?.map(activity => (
                            <ActivityItem key={activity.id} url={getActivityUrl(activity) || getActorUrl(activity)}>
                                <APAvatar author={activity.actor} badge={getActivityBadge(activity)} />
                                <div>
                                    <div className='text-grey-600'>
                                        <span className='mr-1 font-bold text-black'>{activity.actor.name}</span>
                                        {getActorUsername(activity.actor)}
                                    </div>
                                    <div className='text-sm'>{getActivityDescription(activity)}</div>
                                </div>
                                {isFollower(activity.actor.id, followers) === false && (
                                    <Button className='ml-auto' label='Follow' link onClick={(e) => {
                                        e?.preventDefault();

                                        alert('Implement me!');
                                    }} />
                                )}
                            </ActivityItem>
                        ))}
                    </div>
                )}
            </div>
        </>
    );
};

export default Activities;