Merge branch 'main' into group-adding-removing
This commit is contained in:
commit
7d39f27786
|
@ -20,6 +20,7 @@ import { Text } from '@tiptap/extension-text'
|
||||||
// other tiptap extensions
|
// other tiptap extensions
|
||||||
import { Image } from '@tiptap/extension-image'
|
import { Image } from '@tiptap/extension-image'
|
||||||
import { Link } from '@tiptap/extension-link'
|
import { Link } from '@tiptap/extension-link'
|
||||||
|
import Iframe from './tiptap-iframe'
|
||||||
|
|
||||||
export function parseTags(text: string) {
|
export function parseTags(text: string) {
|
||||||
const regex = /(?:^|\s)(?:[#][a-z0-9_]+)/gi
|
const regex = /(?:^|\s)(?:[#][a-z0-9_]+)/gi
|
||||||
|
@ -80,6 +81,7 @@ export const exhibitExts = [
|
||||||
|
|
||||||
Image,
|
Image,
|
||||||
Link,
|
Link,
|
||||||
|
Iframe,
|
||||||
]
|
]
|
||||||
// export const exhibitExts = [StarterKit as unknown as Extension, Image]
|
// export const exhibitExts = [StarterKit as unknown as Extension, Image]
|
||||||
|
|
||||||
|
|
92
common/util/tiptap-iframe.ts
Normal file
92
common/util/tiptap-iframe.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// Adopted from https://github.com/ueberdosis/tiptap/blob/main/demos/src/Experiments/Embeds/Vue/iframe.ts
|
||||||
|
|
||||||
|
import { Node } from '@tiptap/core'
|
||||||
|
|
||||||
|
export interface IframeOptions {
|
||||||
|
allowFullscreen: boolean
|
||||||
|
HTMLAttributes: {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@tiptap/core' {
|
||||||
|
interface Commands<ReturnType> {
|
||||||
|
iframe: {
|
||||||
|
setIframe: (options: { src: string }) => ReturnType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These classes style the outer wrapper and the inner iframe;
|
||||||
|
// Adopted from css in https://github.com/ueberdosis/tiptap/blob/main/demos/src/Experiments/Embeds/Vue/index.vue
|
||||||
|
const wrapperClasses = 'relative h-auto w-full overflow-hidden'
|
||||||
|
const iframeClasses = 'absolute top-0 left-0 h-full w-full'
|
||||||
|
|
||||||
|
export default Node.create<IframeOptions>({
|
||||||
|
name: 'iframe',
|
||||||
|
|
||||||
|
group: 'block',
|
||||||
|
|
||||||
|
atom: true,
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
allowFullscreen: true,
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'iframe-wrapper' + ' ' + wrapperClasses,
|
||||||
|
// Tailwind JIT doesn't seem to pick up `pb-[20rem]`, so we hack this in:
|
||||||
|
style: 'padding-bottom: 20rem;',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
src: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
frameborder: {
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
allowfullscreen: {
|
||||||
|
default: this.options.allowFullscreen,
|
||||||
|
parseHTML: () => this.options.allowFullscreen,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [{ tag: 'iframe' }]
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return [
|
||||||
|
'div',
|
||||||
|
this.options.HTMLAttributes,
|
||||||
|
[
|
||||||
|
'iframe',
|
||||||
|
{
|
||||||
|
...HTMLAttributes,
|
||||||
|
class: HTMLAttributes.class + ' ' + iframeClasses,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
addCommands() {
|
||||||
|
return {
|
||||||
|
setIframe:
|
||||||
|
(options: { src: string }) =>
|
||||||
|
({ tr, dispatch }) => {
|
||||||
|
const { selection } = tr
|
||||||
|
const node = this.type.create(options)
|
||||||
|
|
||||||
|
if (dispatch) {
|
||||||
|
tr.replaceRangeWith(selection.from, selection.to, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
|
@ -17,6 +17,7 @@ initAdmin()
|
||||||
|
|
||||||
const adminFirestore = admin.firestore()
|
const adminFirestore = admin.firestore()
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const addGroupIdToContracts = async () => {
|
const addGroupIdToContracts = async () => {
|
||||||
const groups = await getValues<Group>(adminFirestore.collection('groups'))
|
const groups = await getValues<Group>(adminFirestore.collection('groups'))
|
||||||
const contracts = await getValues<Contract>(
|
const contracts = await getValues<Contract>(
|
||||||
|
|
|
@ -39,11 +39,12 @@ const indexPrefix = ENV === 'DEV' ? 'dev-' : ''
|
||||||
|
|
||||||
const sortIndexes = [
|
const sortIndexes = [
|
||||||
{ label: 'Newest', value: indexPrefix + 'contracts-newest' },
|
{ label: 'Newest', value: indexPrefix + 'contracts-newest' },
|
||||||
{ label: 'Oldest', value: indexPrefix + 'contracts-oldest' },
|
// { label: 'Oldest', value: indexPrefix + 'contracts-oldest' },
|
||||||
{ label: 'Most popular', value: indexPrefix + 'contracts-score' },
|
{ label: 'Most popular', value: indexPrefix + 'contracts-score' },
|
||||||
{ label: 'Most traded', value: indexPrefix + 'contracts-most-traded' },
|
{ label: 'Most traded', value: indexPrefix + 'contracts-most-traded' },
|
||||||
{ label: '24h volume', value: indexPrefix + 'contracts-24-hour-vol' },
|
{ label: '24h volume', value: indexPrefix + 'contracts-24-hour-vol' },
|
||||||
{ label: 'Last updated', value: indexPrefix + 'contracts-last-updated' },
|
{ label: 'Last updated', value: indexPrefix + 'contracts-last-updated' },
|
||||||
|
{ label: 'Subsidy', value: indexPrefix + 'contracts-liquidity' },
|
||||||
{ label: 'Close date', value: indexPrefix + 'contracts-close-date' },
|
{ label: 'Close date', value: indexPrefix + 'contracts-close-date' },
|
||||||
{ label: 'Resolve date', value: indexPrefix + 'contracts-resolve-date' },
|
{ label: 'Resolve date', value: indexPrefix + 'contracts-resolve-date' },
|
||||||
]
|
]
|
||||||
|
|
|
@ -49,7 +49,7 @@ export function ContractLeaderboard(props: {
|
||||||
|
|
||||||
return users && users.length > 0 ? (
|
return users && users.length > 0 ? (
|
||||||
<Leaderboard
|
<Leaderboard
|
||||||
title="🏅 Top bettors"
|
title="🏅 Top traders"
|
||||||
users={users || []}
|
users={users || []}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { useMutation } from 'react-query'
|
||||||
import { exhibitExts } from 'common/util/parse'
|
import { exhibitExts } from 'common/util/parse'
|
||||||
import { FileUploadButton } from './file-upload-button'
|
import { FileUploadButton } from './file-upload-button'
|
||||||
import { linkClass } from './site-link'
|
import { linkClass } from './site-link'
|
||||||
|
import Iframe from 'common/util/tiptap-iframe'
|
||||||
|
|
||||||
const proseClass = clsx(
|
const proseClass = clsx(
|
||||||
'prose prose-p:my-0 prose-li:my-0 prose-blockquote:not-italic max-w-none prose-quoteless leading-relaxed',
|
'prose prose-p:my-0 prose-li:my-0 prose-blockquote:not-italic max-w-none prose-quoteless leading-relaxed',
|
||||||
|
@ -56,6 +57,7 @@ export function useTextEditor(props: {
|
||||||
class: clsx('no-underline !text-indigo-700', linkClass),
|
class: clsx('no-underline !text-indigo-700', linkClass),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
Iframe,
|
||||||
],
|
],
|
||||||
content: defaultValue,
|
content: defaultValue,
|
||||||
})
|
})
|
||||||
|
@ -69,12 +71,20 @@ export function useTextEditor(props: {
|
||||||
(file) => file.type.startsWith('image')
|
(file) => file.type.startsWith('image')
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!imageFiles.length) {
|
if (imageFiles.length) {
|
||||||
return // if no files pasted, use default paste handler
|
event.preventDefault()
|
||||||
|
upload.mutate(imageFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
// If the pasted content is iframe code, directly inject it
|
||||||
upload.mutate(imageFiles)
|
const text = event.clipboardData?.getData('text/plain').trim() ?? ''
|
||||||
|
const isValidIframe = /^<iframe.*<\/iframe>$/.test(text)
|
||||||
|
if (isValidIframe) {
|
||||||
|
editor.chain().insertContent(text).run()
|
||||||
|
return true // Prevent the code from getting pasted as text
|
||||||
|
}
|
||||||
|
|
||||||
|
return // Otherwise, use default paste handler
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { sortBy, sumBy, uniqBy } from 'lodash'
|
import { sortBy, sumBy, uniqBy } from 'lodash'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import Confetti from 'react-confetti'
|
||||||
|
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { Page } from 'web/components/page'
|
import { Page } from 'web/components/page'
|
||||||
|
@ -16,11 +19,11 @@ import { useRouter } from 'next/router'
|
||||||
import Custom404 from '../404'
|
import Custom404 from '../404'
|
||||||
import { useCharityTxns } from 'web/hooks/use-charity-txns'
|
import { useCharityTxns } from 'web/hooks/use-charity-txns'
|
||||||
import { useWindowSize } from 'web/hooks/use-window-size'
|
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||||
import Confetti from 'react-confetti'
|
|
||||||
import { Donation } from 'web/components/charity/feed-items'
|
import { Donation } from 'web/components/charity/feed-items'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { manaToUSD } from 'common/util/format'
|
import { manaToUSD } from 'common/util/format'>>>>>>> main
|
||||||
import { track } from 'web/lib/service/analytics'
|
import { track } from 'web/lib/service/analytics'
|
||||||
|
import { SEO } from 'web/components/SEO'
|
||||||
|
|
||||||
export default function CharityPageWrapper() {
|
export default function CharityPageWrapper() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
@ -63,6 +66,11 @@ function CharityPage(props: { charity: Charity }) {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<SEO
|
||||||
|
title={name}
|
||||||
|
description={description}
|
||||||
|
url="/groups"
|
||||||
|
/>
|
||||||
{showConfetti && (
|
{showConfetti && (
|
||||||
<Confetti
|
<Confetti
|
||||||
width={width ? width : 500}
|
width={width ? width : 500}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { searchInAny } from 'common/util/parse'
|
||||||
import { getUser } from 'web/lib/firebase/users'
|
import { getUser } from 'web/lib/firebase/users'
|
||||||
import { SiteLink } from 'web/components/site-link'
|
import { SiteLink } from 'web/components/site-link'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
|
import { SEO } from 'web/components/SEO'
|
||||||
|
|
||||||
export async function getStaticProps() {
|
export async function getStaticProps() {
|
||||||
const txns = await getAllCharityTxns()
|
const txns = await getAllCharityTxns()
|
||||||
|
@ -114,6 +115,11 @@ export default function Charity(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
<SEO
|
||||||
|
title="Manifold for Charity"
|
||||||
|
description="Donate your prediction market earnings to charity on Manifold."
|
||||||
|
url="/charity"
|
||||||
|
/>
|
||||||
<Col className="w-full rounded px-4 py-6 sm:px-8 xl:w-[125%]">
|
<Col className="w-full rounded px-4 py-6 sm:px-8 xl:w-[125%]">
|
||||||
<Col className="">
|
<Col className="">
|
||||||
<Title className="!mt-0" text="Manifold for Charity" />
|
<Title className="!mt-0" text="Manifold for Charity" />
|
||||||
|
@ -128,6 +134,9 @@ export default function Charity(props: {
|
||||||
</SiteLink>
|
</SiteLink>
|
||||||
!
|
!
|
||||||
</span> */}
|
</span> */}
|
||||||
|
<span className="text-gray-600">
|
||||||
|
Convert your M$ earnings into real charitable donations.
|
||||||
|
</span>
|
||||||
<DonatedStats
|
<DonatedStats
|
||||||
stats={[
|
stats={[
|
||||||
{
|
{
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { TextEditor, useTextEditor } from 'web/components/editor'
|
||||||
import { Checkbox } from 'web/components/checkbox'
|
import { Checkbox } from 'web/components/checkbox'
|
||||||
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
||||||
import { Title } from 'web/components/title'
|
import { Title } from 'web/components/title'
|
||||||
|
import { SEO } from 'web/components/SEO'
|
||||||
|
|
||||||
export const getServerSideProps = redirectIfLoggedOut('/')
|
export const getServerSideProps = redirectIfLoggedOut('/')
|
||||||
|
|
||||||
|
@ -63,6 +64,11 @@ export default function Create() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
<SEO
|
||||||
|
title="Create a market"
|
||||||
|
description="Create a play-money prediction market on any question."
|
||||||
|
url="/create"
|
||||||
|
/>
|
||||||
<div className="mx-auto w-full max-w-2xl">
|
<div className="mx-auto w-full max-w-2xl">
|
||||||
<div className="rounded-lg px-6 py-4 sm:py-0">
|
<div className="rounded-lg px-6 py-4 sm:py-0">
|
||||||
<Title className="!mt-0" text="Create a market" />
|
<Title className="!mt-0" text="Create a market" />
|
||||||
|
|
|
@ -492,7 +492,7 @@ function GroupLeaderboards(props: {
|
||||||
<SortedLeaderboard
|
<SortedLeaderboard
|
||||||
users={members}
|
users={members}
|
||||||
scoreFunction={(user) => traderScores[user.id] ?? 0}
|
scoreFunction={(user) => traderScores[user.id] ?? 0}
|
||||||
title="🏅 Top bettors"
|
title="🏅 Top traders"
|
||||||
header="Profit"
|
header="Profit"
|
||||||
maxToShow={maxToShow}
|
maxToShow={maxToShow}
|
||||||
/>
|
/>
|
||||||
|
@ -508,7 +508,7 @@ function GroupLeaderboards(props: {
|
||||||
<>
|
<>
|
||||||
<Leaderboard
|
<Leaderboard
|
||||||
className="max-w-xl"
|
className="max-w-xl"
|
||||||
title="🏅 Top bettors"
|
title="🏅 Top traders"
|
||||||
users={topTraders}
|
users={topTraders}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { Avatar } from 'web/components/avatar'
|
||||||
import { JoinOrLeaveGroupButton } from 'web/components/groups/groups-button'
|
import { JoinOrLeaveGroupButton } from 'web/components/groups/groups-button'
|
||||||
import { UserLink } from 'web/components/user-page'
|
import { UserLink } from 'web/components/user-page'
|
||||||
import { searchInAny } from 'common/util/parse'
|
import { searchInAny } from 'common/util/parse'
|
||||||
|
import { SEO } from 'web/components/SEO'
|
||||||
|
|
||||||
export async function getStaticProps() {
|
export async function getStaticProps() {
|
||||||
const groups = await listAllGroups().catch((_) => [])
|
const groups = await listAllGroups().catch((_) => [])
|
||||||
|
@ -100,6 +101,11 @@ export default function Groups(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
<SEO
|
||||||
|
title="Groups"
|
||||||
|
description="Manifold Groups are communities centered around a collection of prediction markets. Discuss and compete on questions with your friends."
|
||||||
|
url="/groups"
|
||||||
|
/>
|
||||||
<Col className="items-center">
|
<Col className="items-center">
|
||||||
<Col className="w-full max-w-2xl px-4 sm:px-2">
|
<Col className="w-full max-w-2xl px-4 sm:px-2">
|
||||||
<Row className="items-center justify-between">
|
<Row className="items-center justify-between">
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Col } from 'web/components/layout/col'
|
||||||
import { ManifoldLogo } from 'web/components/nav/manifold-logo'
|
import { ManifoldLogo } from 'web/components/nav/manifold-logo'
|
||||||
import { redirectIfLoggedIn } from 'web/lib/firebase/server-auth'
|
import { redirectIfLoggedIn } from 'web/lib/firebase/server-auth'
|
||||||
import { useSaveReferral } from 'web/hooks/use-save-referral'
|
import { useSaveReferral } from 'web/hooks/use-save-referral'
|
||||||
|
import { SEO } from 'web/components/SEO'
|
||||||
|
|
||||||
export const getServerSideProps = redirectIfLoggedIn('/home', async (_) => {
|
export const getServerSideProps = redirectIfLoggedIn('/home', async (_) => {
|
||||||
// These hardcoded markets will be shown in the frontpage for signed-out users:
|
// These hardcoded markets will be shown in the frontpage for signed-out users:
|
||||||
|
@ -30,6 +31,11 @@ export default function Home(props: { hotContracts: Contract[] }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
<SEO
|
||||||
|
title="Manifold Markets"
|
||||||
|
description="Create a play-money prediction market on any topic you care about
|
||||||
|
and bet with your friends on what will happen!"
|
||||||
|
/>
|
||||||
<div className="px-4 pt-2 md:mt-0 lg:hidden">
|
<div className="px-4 pt-2 md:mt-0 lg:hidden">
|
||||||
<ManifoldLogo />
|
<ManifoldLogo />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { useEffect, useState } from 'react'
|
||||||
import { Title } from 'web/components/title'
|
import { Title } from 'web/components/title'
|
||||||
import { Tabs } from 'web/components/layout/tabs'
|
import { Tabs } from 'web/components/layout/tabs'
|
||||||
import { useTracking } from 'web/hooks/use-tracking'
|
import { useTracking } from 'web/hooks/use-tracking'
|
||||||
|
import { SEO } from 'web/components/SEO'
|
||||||
|
|
||||||
export async function getStaticProps() {
|
export async function getStaticProps() {
|
||||||
const props = await fetchProps()
|
const props = await fetchProps()
|
||||||
|
@ -78,7 +79,7 @@ export default function Leaderboards(_props: {
|
||||||
<>
|
<>
|
||||||
<Col className="mx-4 items-center gap-10 lg:flex-row">
|
<Col className="mx-4 items-center gap-10 lg:flex-row">
|
||||||
<Leaderboard
|
<Leaderboard
|
||||||
title="🏅 Top bettors"
|
title="🏅 Top traders"
|
||||||
users={topTraders}
|
users={topTraders}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
|
@ -123,6 +124,11 @@ export default function Leaderboards(_props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
<SEO
|
||||||
|
title="Leaderboards"
|
||||||
|
description="Manifold's leaderboards show the top traders and market creators."
|
||||||
|
url="/leaderboards"
|
||||||
|
/>
|
||||||
<Title text={'Leaderboards'} className={'hidden md:block'} />
|
<Title text={'Leaderboards'} className={'hidden md:block'} />
|
||||||
<Tabs
|
<Tabs
|
||||||
currentPageForAnalytics={'leaderboards'}
|
currentPageForAnalytics={'leaderboards'}
|
||||||
|
|
|
@ -8,7 +8,7 @@ export default function Markets() {
|
||||||
<Page>
|
<Page>
|
||||||
<SEO
|
<SEO
|
||||||
title="Explore"
|
title="Explore"
|
||||||
description="Discover what's new, trending, or soon-to-close. Or search among our hundreds of markets."
|
description="Discover what's new, trending, or soon-to-close. Or search thousands of prediction markets."
|
||||||
url="/markets"
|
url="/markets"
|
||||||
/>
|
/>
|
||||||
<ContractSearch />
|
<ContractSearch />
|
||||||
|
|
|
@ -391,7 +391,7 @@ function IncomeNotificationItem(props: {
|
||||||
reasonText = !simple
|
reasonText = !simple
|
||||||
? `Bonus for ${
|
? `Bonus for ${
|
||||||
parseInt(sourceText) / UNIQUE_BETTOR_BONUS_AMOUNT
|
parseInt(sourceText) / UNIQUE_BETTOR_BONUS_AMOUNT
|
||||||
} unique bettors`
|
} unique traders`
|
||||||
: 'bonus on'
|
: 'bonus on'
|
||||||
} else if (sourceType === 'tip') {
|
} else if (sourceType === 'tip') {
|
||||||
reasonText = !simple ? `tipped you` : `in tips on`
|
reasonText = !simple ? `tipped you` : `in tips on`
|
||||||
|
|
|
@ -21,7 +21,12 @@ export default function ReferralsPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<SEO title="Referrals" description="" url="/add-funds" />
|
<SEO
|
||||||
|
title="Referrals"
|
||||||
|
description={`Manifold's referral program. Invite new users to Manifold and get M${REFERRAL_AMOUNT} if they
|
||||||
|
sign up!`}
|
||||||
|
url="/referrals"
|
||||||
|
/>
|
||||||
|
|
||||||
<Col className="items-center">
|
<Col className="items-center">
|
||||||
<Col className="h-full rounded bg-white p-4 py-8 sm:p-8 sm:shadow-md">
|
<Col className="h-full rounded bg-white p-4 py-8 sm:p-8 sm:shadow-md">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user