Skip to content

Commit

Permalink
Merge pull request #10258 from hicommonwealth/salman/Issue#7989/inter…
Browse files Browse the repository at this point in the history
…-community-navigation

Inter-Community navigation
  • Loading branch information
salman-neslit authored Dec 20, 2024
2 parents 59bfa38 + ac9cf7e commit 9249a26
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 117 deletions.
36 changes: 19 additions & 17 deletions packages/commonwealth/client/scripts/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { openFeatureProvider } from './helpers/feature-flags';
import useAppStatus from './hooks/useAppStatus';
import { trpc, trpcClient } from './utils/trpcClient';
import { AddToHomeScreenPrompt } from './views/components/AddToHomeScreenPrompt';
import { KnockFeedWrapper } from './views/components/KnockNotifications/KnockFeedWrapper';
import { Mava } from './views/components/Mava';

OpenFeature.setProvider(openFeatureProvider);
Expand All @@ -31,23 +32,24 @@ const App = () => {
<trpc.Provider client={trpcClient} queryClient={queryClient}>
{/*@ts-expect-error StrictNullChecks*/}
<OpenFeatureProvider client={undefined}>
{isLoading ? (
<Splash />
) : (
<>
<Mava />
<ReactNativeBridge />
<RouterProvider router={router()} />
{isAddedToHomeScreen || isMarketingPage ? null : (
<AddToHomeScreenPrompt
isIOS={isIOS}
isAndroid={isAndroid}
displayDelayMilliseconds={1000}
/>
)}
</>
)}

<KnockFeedWrapper>
{isLoading ? (
<Splash />
) : (
<>
<Mava />
<ReactNativeBridge />
<RouterProvider router={router()} />
{isAddedToHomeScreen || isMarketingPage ? null : (
<AddToHomeScreenPrompt
isIOS={isIOS}
isAndroid={isAndroid}
displayDelayMilliseconds={1000}
/>
)}
</>
)}
</KnockFeedWrapper>
<ToastContainer />
{import.meta.env.DEV && <ReactQueryDevtools />}
</OpenFeatureProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
useKnockClient,
useNotifications,
useNotificationStore,
} from '@knocklabs/react';
import { useEffect } from 'react';

const KNOCK_IN_APP_FEED_ID =
process.env.KNOCK_IN_APP_FEED_ID || 'fc6e68e5-b7b9-49c1-8fab-6dd7e3510ffb';

const useFetchNotifications = () => {
const knockClient = useKnockClient();
const feedClient = useNotifications(knockClient, KNOCK_IN_APP_FEED_ID);

const { items } = useNotificationStore(feedClient);

useEffect(() => {
const fetchNotifications = async () => {
try {
await feedClient.fetch();
} catch (error) {
console.error('Failed to fetch notifications:', error);
}
};

fetchNotifications(); // eslint-disable-line @typescript-eslint/no-floating-promises
}, [feedClient]);

return { items };
};

export default useFetchNotifications;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Knock from '@knocklabs/client';
import { KnockFeedProvider, KnockProvider } from '@knocklabs/react';
import React, { ReactNode, useEffect } from 'react';
import useUserStore from 'state/ui/user';

const KNOCK_PUBLIC_API_KEY =
process.env.KNOCK_PUBLIC_API_KEY ||
'pk_test_Hd4ZpzlVcz9bqepJQoo9BvZHokgEqvj4T79fPdKqpYM';

const KNOCK_IN_APP_FEED_ID =
process.env.KNOCK_IN_APP_FEED_ID || 'fc6e68e5-b7b9-49c1-8fab-6dd7e3510ffb';

const knock = new Knock(KNOCK_PUBLIC_API_KEY);

const getBrowserTimezone = (): string => {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
};

interface KnockFeedWrapperProps {
children: ReactNode;
}

export const KnockFeedWrapper = ({ children }: KnockFeedWrapperProps) => {
const user = useUserStore();

useEffect(() => {
if (!user.id || !user.isLoggedIn) return;
if (!user.knockJWT) {
console.warn('user knockJWT not set! Will not attempt to identify.');
return;
}

const timezone = getBrowserTimezone();
async function doAsync() {
knock.authenticate(`${user.id}`, user.knockJWT);
await knock.user.identify({
id: user.id,
email: user.email,
timezone,
});
}

doAsync().catch(console.error);
}, [user.email, user.id, user.isLoggedIn, user.knockJWT]);

return (
<KnockProvider
apiKey={KNOCK_PUBLIC_API_KEY}
userId={`${user.id}`}
userToken={user.knockJWT}
>
<KnockFeedProvider feedId={KNOCK_IN_APP_FEED_ID} colorMode="light">
{children}
</KnockFeedProvider>
</KnockProvider>
);
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import Knock from '@knocklabs/client';
import {
KnockFeedProvider,
KnockProvider,
NotificationFeedPopover,
NotificationIconButton,
} from '@knocklabs/react';
import '@knocklabs/react-notification-feed/dist/index.css';
import React, { memo, useEffect, useRef, useState } from 'react';
import React, { memo, useRef, useState } from 'react';
import useUserStore from 'state/ui/user';
import {
handleIconClick,
Expand All @@ -23,58 +22,19 @@ const KNOCK_PUBLIC_API_KEY =
const KNOCK_IN_APP_FEED_ID =
process.env.KNOCK_IN_APP_FEED_ID || 'fc6e68e5-b7b9-49c1-8fab-6dd7e3510ffb';

const knock = new Knock(KNOCK_PUBLIC_API_KEY);

const getBrowserTimezone = (): string => {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
};

export const KnockNotifications = memo(function KnockNotifications() {
const user = useUserStore();
const [isVisible, setIsVisible] = useState(false);

const notifButtonRef = useRef(null);

useEffect(() => {
if (!user.id || !user.isLoggedIn) {
return;
}

if (!user.knockJWT) {
console.warn('user knockJWT not set! Will not attempt to identify.');
return;
}

const timezone = getBrowserTimezone();
async function doAsync() {
knock.authenticate(`${user.id}`, user.knockJWT);

await knock.user.identify({
id: user.id,
email: user.email,
timezone,
});
}

doAsync().catch(console.error);
}, [user.email, user.id, user.isLoggedIn, user.knockJWT]);

if (!user.id || !user.isLoggedIn) {
return null;
}

if (!user.knockJWT) {
return null;
}

return (
<div className="KnockNotifications">
<KnockProvider
apiKey={KNOCK_PUBLIC_API_KEY}
userId={`${user.id}`}
userToken={user.knockJWT}
>
{/* Optionally, use the KnockFeedProvider to connect an in-app feed */}
<KnockFeedProvider feedId={KNOCK_IN_APP_FEED_ID} colorMode="light">
<div>
<CWTooltip
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import clsx from 'clsx';
import React from 'react';
import { Skeleton } from '../Skeleton';
import './cw_community_avatar.scss';
Expand All @@ -8,12 +9,14 @@ import { ComponentType } from './types';

type CommunityAvatarProps = {
community: {
id?: string;
name: string;
iconUrl: string;
};
onClick?: () => void;
size?: IconSize;
showSkeleton?: boolean;
selectedCommunity?: string;
};

const CWCommunityAvatarSkeleton = () => {
Expand All @@ -26,7 +29,13 @@ const CWCommunityAvatarSkeleton = () => {

// eslint-disable-next-line react/no-multi-comp
export const CWCommunityAvatar = (props: CommunityAvatarProps) => {
const { community, onClick, size = 'large', showSkeleton } = props;
const {
community,
onClick,
size = 'large',
showSkeleton,
selectedCommunity,
} = props;

if (showSkeleton) {
return <CWCommunityAvatarSkeleton />;
Expand All @@ -35,16 +44,24 @@ export const CWCommunityAvatar = (props: CommunityAvatarProps) => {
const sizeIsAboveLarge =
size !== 'small' && size !== 'medium' && size !== 'large';

const isSelected = selectedCommunity === community.id;
return (
<div
className={getClasses<{ onClick: boolean; size: IconSize }>(
{ onClick: !!onClick, size },
className={getClasses<{
onClick: boolean;
size: IconSize;
isSelected: boolean;
}>(
{ onClick: !!onClick, size, isSelected },
ComponentType.CommunityAvatar,
)}
onClick={onClick}
>
{community?.iconUrl ? (
<img className="community-image" src={community.iconUrl} />
<img
className={clsx('community-image', { isSelected: isSelected })}
src={community.iconUrl}
/>
) : (
<div className={getClasses<{ size: IconSize }>({ size }, 'no-image')}>
<CWText
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@import '../../../styles/';

.SideBarNotificationIcon {
.notification-icon-container {
position: absolute;
top: 0;
right: 0;
display: inline-block;
}

.notification-icon {
font-size: 24px;
color: $neutral-800;
position: relative;
cursor: pointer;
}

.notification-badge {
position: absolute;
top: 22px;
right: -5px;
background-color: $rorange-400;
color: white;
font-size: 12px;
font-weight: bold;
padding: 2px 6px;
border-radius: 12px;
line-height: 1;
border: 2px solid white;
}

.hidden-feed {
display: none;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import '@knocklabs/react-notification-feed/dist/index.css';
import React from 'react';
import './SidebarNotificationIcon.scss';

type SideBarNotificationIconProps = {
unreadCount: number;
};

export const SideBarNotificationIcon = ({
unreadCount,
}: SideBarNotificationIconProps) => (
<div className="SideBarNotificationIcon">
<div className="notification-icon-container">
<div className="notification-icon">
<i className="bell-icon" />
{unreadCount > 0 && (
<span className="notification-badge">
{unreadCount > 99 ? '99+' : unreadCount}
</span>
)}
</div>
</div>
</div>
);
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'lodash';

import { FeedItem } from '@knocklabs/client';
import app from 'state';
import { Contest } from 'views/pages/CommunityManagement/Contests/ContestsList';
import { isContestActive } from 'views/pages/CommunityManagement/Contests/utils';
Expand Down Expand Up @@ -41,3 +42,11 @@ export const getUniqueTopicIdsIncludedInActiveContest = (

return [...new Set(topicIds)];
};

export const calculateUnreadCount = (
communityName: string,
items: FeedItem[],
) =>
items.filter(
(item) => !item.read_at && item?.data?.community_name === communityName,
).length;
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@
padding-top: 32px;
}

.CommunityAvatar {
border-radius: $border-radius-corners-wider;

.community-image {
width: 100%;
height: 100%;
border-radius: $border-radius-corners-wider;

&.isSelected {
border-radius: $border-radius-round;
}
}

&.isSelected {
border-radius: $border-radius-round;
border: 2px solid $primary-600;
padding: 2px;
}
}

.community-nav-bar {
align-items: center;
display: flex;
Expand All @@ -36,5 +56,13 @@
overflow-y: auto;
padding: 12px 0;
width: 100%;

&::-webkit-scrollbar {
width: 4px;
}

.community-avatar-container {
position: relative;
}
}
}
Loading

0 comments on commit 9249a26

Please sign in to comment.