Skip to content
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: push protocol integration #109

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/(general)/integration/push-protocol/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IntegrationOgImage } from '@/components/ui/social/og-image-integrations'

export const runtime = 'edge'
export const size = {
width: 1200,
height: 630,
}

export default IntegrationOgImage('push_protocol')
129 changes: 129 additions & 0 deletions app/(general)/integration/push-protocol/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
'use client'

import { useState } from 'react'

import { ApiNotificationType } from '@pushprotocol/restapi'
import { motion } from 'framer-motion'
import Balancer from 'react-wrap-balancer'

import { WalletConnect } from '@/components/blockchain/wallet-connect'
import { IsWalletConnected } from '@/components/shared/is-wallet-connected'
import { IsWalletDisconnected } from '@/components/shared/is-wallet-disconnected'
import { LinkComponent } from '@/components/shared/link-component'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { FADE_DOWN_ANIMATION_VARIANTS } from '@/config/design'
import { turboIntegrations } from '@/data/turbo-integrations'
import { ChannelCard, ChannelSearch, ENV, getMockedNotification } from '@/integrations/push-protocol'
import { NotificationBell } from '@/integrations/push-protocol/components/notification-bell'
import { PUSH_CHANNEL_ADDRESS } from '@/integrations/push-protocol/utils/constants'

export default function PageIntegration() {
const [mockedNotifications, setMockedNotifications] = useState<ApiNotificationType[]>([])

const [channelAddress, setChannelAddress] = useState(PUSH_CHANNEL_ADDRESS)
const [env, setEnv] = useState(ENV.STAGING)

// Shows mock notificatins in inbox after subscribing if there is no notifications to show.
const handleSubscribe = () => {
const mockedNotification = getMockedNotification({ env })
setMockedNotifications([mockedNotification])
}

return (
<>
<div className="flex-center flex flex-1 flex-col items-center justify-center">
<motion.div
animate="show"
className="max-w-screen-xl px-5 text-center xl:px-0"
initial="hidden"
viewport={{ once: true }}
whileInView="show"
variants={{
hidden: {},
show: {
transition: {
staggerChildren: 0.15,
},
},
}}>
<motion.h1
className="text-gradient-sand my-4 text-center text-4xl font-bold tracking-[-0.02em] drop-shadow-sm md:text-8xl md:leading-[6rem]"
variants={FADE_DOWN_ANIMATION_VARIANTS}>
{turboIntegrations.push_protocol.name}
</motion.h1>
<motion.p className="my-4 text-lg" variants={FADE_DOWN_ANIMATION_VARIANTS}>
<Balancer>{turboIntegrations.push_protocol.description}</Balancer>
</motion.p>
<motion.div className="my-4 text-xl" variants={FADE_DOWN_ANIMATION_VARIANTS}>
<LinkComponent isExternal href={turboIntegrations.push_protocol.url}>
<button className="btn btn-primary">Documentation</button>
</LinkComponent>
</motion.div>
</motion.div>
</div>
<section className="w-full lg:mt-10">
<div className="container flex w-full flex-col items-center">
<IsWalletDisconnected>
<WalletConnect />
</IsWalletDisconnected>
<IsWalletConnected>
<div className="mb-5 flex w-full">
<div className="mx-auto">
<NotificationBell env={ENV.STAGING} mockedNotifications={mockedNotifications} />
</div>
</div>
<div className="mb-3 w-full">
<div className="card">
<div className="mb-4 flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
<div className="grow">
<input
className="input"
defaultValue={channelAddress}
placeholder="Enter Channel Address"
onChange={(e) => setChannelAddress(e.target.value)}
/>
</div>
<div className="w-full md:w-56">
<Select value={env} onValueChange={(value) => setEnv(value as ENV)}>
<SelectTrigger className="input text-gray-600 placeholder:text-neutral-400 dark:text-gray-600 dark:placeholder:text-neutral-400">
<SelectValue placeholder={env === ENV.STAGING ? 'Goerli' : 'Mainnet'}>
{env === ENV.STAGING ? 'Goerli' : 'Mainnet'}
</SelectValue>
</SelectTrigger>
<SelectContent className="bg-white dark:bg-white">
<SelectItem value={ENV.STAGING}>Goerli</SelectItem>
<SelectItem value={ENV.PROD}>Mainnet</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex w-full flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="grow">
<ChannelCard channelAddress={channelAddress} env={env} onSubscribe={() => handleSubscribe()} />
</div>
</div>
<hr className="my-4" />
<div className="flex items-center justify-between">
<h3 className="text-center">Channel Preview</h3>
<p className="text-center text-sm text-gray-500">Preview and subscribe channel</p>
</div>
</div>
</div>
<div className="mb-3 w-full">
<div className="card">
<div>
<ChannelSearch />
</div>
<hr className="my-4" />
<div className="flex items-center justify-between">
<h3 className="text-center">Search Channels</h3>
<p className="text-center text-sm text-gray-500">Search for PUSH channels</p>
</div>
</div>
</div>
</IsWalletConnected>
</div>
</section>
</>
)
}
9 changes: 9 additions & 0 deletions app/(general)/integration/push-protocol/twitter-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Image from './opengraph-image'

export const runtime = 'edge'
export const size = {
width: 1200,
height: 630,
}

export default Image
15 changes: 15 additions & 0 deletions app/(general)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,21 @@ const features = [
</div>
),
},
{
title: turboIntegrations.push_protocol.name,
description: turboIntegrations.push_protocol.description,
href: turboIntegrations.push_protocol.href,
demo: (
<div className="flex items-center justify-center space-x-20">
<IsLightTheme>
<Image alt="Push Protocol logo" height={100} src={turboIntegrations.push_protocol.imgDark} width={100} />
</IsLightTheme>
<IsDarkTheme>
<Image alt="Push Protocol logo" height={100} src={turboIntegrations.push_protocol.imgLight} width={100} />
</IsDarkTheme>
</div>
),
},
{
title: turboIntegrations.starter.name,
description: turboIntegrations.starter.description,
Expand Down
8 changes: 8 additions & 0 deletions data/turbo-integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ export const turboIntegrations = {
imgLight: '/integrations/pooltogether.svg',
imgDark: '/integrations/pooltogether.svg',
},
push_protocol: {
name: 'Push Protocol',
href: '/integration/push-protocol',
url: 'https://push.org/',
description: 'Push Protocol is a web3 communication network, enabling cross-chain notifications and messaging for dapps, wallets, and services.',
imgLight: '/integrations/push.svg',
imgDark: '/integrations/push.svg',
},
connext: {
name: 'Connext',
href: '/integration/connext',
Expand Down
2 changes: 1 addition & 1 deletion integrations/openai/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const models = ['gpt-4', 'gpt-3.5-turbo', 'text-davinci-003', 'text-davin
* See more details at https://platform.openai.com/docs/api-reference/chat/create
*/
export interface ModelConfig {
model: (typeof models)[number]
model: typeof models[number]
/**
* A list of messages describing the conversation so far.
*/
Expand Down
114 changes: 114 additions & 0 deletions integrations/push-protocol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Push Protocol - TurboETH Integration

[Push Protocol](https://push.org/) is a web3 communication network, enabling cross-chain notifications and messaging for dapps, wallets, and services.

This integration provides useful hooks and components from the main Push Protocol features.

## Features

- Feeds/Notifications (Retrieve, Send)
- Channels (Retrieve, Search)
- Subscriptions (Retrieve, Subscribe/Unsubscribe)
- Chats

---

## Components

`NotificationFeed`
Renders inbox and spam notifications

`NotificationItem`
Renders single notification item

`ChannelCard`
Renders simple card with channel information and an interactive subscribe button

`ChannelSearch`
Renders channel search inputs along channel cards for search results

`Chat`
Renders native Push Protocol support chat window on the bottom-right corner of the screen

---

## Hooks

These hooks are just push protocol-specific wrappers for `react-query`. So it utilizes all the features `react-query` has.

```tsx
const { data, isLoading, error, refetch } = useNotifications({
user: address as string,
env: ENV.STAGING,
spam: false,
})
```

Subscribe to a channel hook

```tsx
const { data, isLoading, error, mutateAsync: subscribe } = useSubscribe()
```

Send notification hook

```tsx
const { data, isLoading, error, mutateAsync: sendNotification } = useSendNotification()

await sendNotification({..args})
```

### Query Hooks

`useChannel` : Returns Push channel based on parameters

`useSearchChannels` : Returns Push channel based on search parameters

`useNotifications` : Returns Push notifications based on parameters

`useChats` : Returns chats based on parameters

`useUserSubscriptions` : Returns user channel subscriptions based on parameters

### Mutation Hooks

`useSendNotifications` : Returns mutation for sending notifications

`useSubscribe` : Returns mutation for subscribe action

`useUnsubscribe` : Returns mutation for unsubscribe action

`useCreateUser` : Returns mutation for creating user

## File Structure

```
integrations/push
├── components
│ ├── channel-card.tsx
│ ├── channel-search.tsx
│ ├── chat.tsx
│ ├── index.ts
│ ├── loadable.tsx
│ ├── notification-bell.tsx
│ ├── notification-feed.tsx
gogabidzia marked this conversation as resolved.
Show resolved Hide resolved
│ ├── notification-item.tsx
│ └── subscribe-button.tsx
├── hooks
│ ├── index.ts
│ ├── use-channel.ts
│ ├── use-chats.ts
│ ├── use-create-user.ts
│ ├── use-notifications.ts
│ ├── use-search-channels.ts
│ ├── use-send-notifications.ts
│ ├── use-subscribe-channel.ts
│ ├── use-unsubscribe-channel.ts
│ └── use-user-subscriptions.ts
└── utils
├── constants.ts
├── helpers.ts
└── types.ts
├── index.ts
└── README.md
```
71 changes: 71 additions & 0 deletions integrations/push-protocol/components/channel-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useEffect, useState } from 'react'

import Image from 'next/image'
import CopyToClipboard from 'react-copy-to-clipboard'
import { HiUser } from 'react-icons/hi'

import { ENV } from '..'
import { useChannel } from '../hooks'
import { strLimit, truncateAddress } from '../utils/helpers'
import { Loadable } from './loadable'
import { SubscribeButton } from './subscribe-button'

export type ChannelCardProps = {
channelAddress: string
env: ENV
onSubscribe?: () => void
onUnsubscribe?: () => void
}

export function ChannelCard({ env, channelAddress, onSubscribe, onUnsubscribe }: ChannelCardProps) {
const [copied, setCopied] = useState(false)

const {
data: channel,
isLoading: channelIsLoading,
error,
} = useChannel({
channel: channelAddress,
env: env,
})

useEffect(() => {
if (!copied) return
setTimeout(() => setCopied(false), 3000)
}, [copied])

if (!channelAddress) return <>No Channel Address Specified...</>
if (error) return <>Error loading channel...</>

return (
<Loadable isLoading={channelIsLoading}>
{channel && (
<div className="flex w-full space-x-4">
<div className="flex flex-col relative">
<Image alt={channel.name} className="w-24 md:w-32 rounded-xl" height={100} src={channel.icon} width={100} />
</div>
<div className="flex grow flex-col">
<p className="overflow-hidden text-ellipsis whitespace-nowrap text-xl">{channel.name}</p>
<p className="hidden text-xs md:block">{strLimit(channel.info, 100)}</p>
<div className="mt-auto">
<div className="mt-2 flex flex-col items-start space-y-2 md:flex-row md:items-end md:space-y-0 md:space-x-2">
<SubscribeButton channelAddress={channelAddress} env={env} onSubscribe={onSubscribe} onUnsubscribe={onUnsubscribe} />
<div className="flex space-x-2">
<div className="flex space-x-1 rounded-full bg-pink-200 px-2 py-1 text-xs text-pink-600">
<HiUser />
<div>{channel.subscriber_count}</div>
</div>
<CopyToClipboard text={channel.channel} onCopy={() => setCopied(true)}>
<button className="rounded-full bg-slate-200 px-2 py-1 text-xs text-gray-700">
{copied ? <>Copied!</> : <>{truncateAddress(channel.channel)}</>}
</button>
</CopyToClipboard>
</div>
</div>
</div>
</div>
</div>
)}
</Loadable>
)
}
Loading