From a4e2cce4aab5232db21788175ed338ee7163a97c Mon Sep 17 00:00:00 2001
From: ingawei <46611122+ingawei@users.noreply.github.com>
Date: Wed, 13 Jul 2022 14:57:34 -0700
Subject: [PATCH] initial commit for manalinks UI improvements (#642)
* manalinks UI improvements
* made manalink look more like card
* changed new link to pulsing indigo instead of green
---
web/components/button.tsx | 37 +++
web/components/manalink-card.tsx | 41 +++
.../manalinks/create-links-button.tsx | 197 +++++++++++++++
web/components/subtitle.tsx | 15 ++
web/get-manalink-url.ts | 3 +
web/pages/links.tsx | 236 ++++--------------
6 files changed, 340 insertions(+), 189 deletions(-)
create mode 100644 web/components/button.tsx
create mode 100644 web/components/manalinks/create-links-button.tsx
create mode 100644 web/components/subtitle.tsx
create mode 100644 web/get-manalink-url.ts
diff --git a/web/components/button.tsx b/web/components/button.tsx
new file mode 100644
index 00000000..3b59581b
--- /dev/null
+++ b/web/components/button.tsx
@@ -0,0 +1,37 @@
+import { ReactNode } from 'react'
+import clsx from 'clsx'
+
+export default function Button(props: {
+ className?: string
+ onClick?: () => void
+ color: 'green' | 'red' | 'blue' | 'indigo' | 'yellow' | 'gray'
+ children?: ReactNode
+ type?: 'button' | 'reset' | 'submit'
+}) {
+ const {
+ className,
+ onClick,
+ children,
+ color = 'indigo',
+ type = 'button',
+ } = props
+
+ return (
+
+ )
+}
diff --git a/web/components/manalink-card.tsx b/web/components/manalink-card.tsx
index 97f5951c..fec05919 100644
--- a/web/components/manalink-card.tsx
+++ b/web/components/manalink-card.tsx
@@ -67,3 +67,44 @@ export function ManalinkCard(props: {
)
}
+
+export function ManalinkCardPreview(props: {
+ className?: string
+ info: ManalinkInfo
+ defaultMessage: string
+}) {
+ const { className, defaultMessage, info } = props
+ const { expiresTime, maxUses, uses, amount, message } = info
+ return (
+
+
+
+ {maxUses != null
+ ? `${maxUses - uses}/${maxUses} uses left`
+ : `Unlimited use`}
+
+
+ {expiresTime != null
+ ? `Expires ${fromNow(expiresTime)}`
+ : 'Never expires'}
+
+
+
+
+
+
+ {formatMoney(amount)}
+ {message || defaultMessage}
+
+
+
+ )
+}
diff --git a/web/components/manalinks/create-links-button.tsx b/web/components/manalinks/create-links-button.tsx
new file mode 100644
index 00000000..d74980cf
--- /dev/null
+++ b/web/components/manalinks/create-links-button.tsx
@@ -0,0 +1,197 @@
+import clsx from 'clsx'
+import { useState } from 'react'
+import { Col } from '../layout/col'
+import { Row } from '../layout/row'
+import { Title } from '../title'
+import { User } from 'common/user'
+import { ManalinkCardPreview, ManalinkInfo } from 'web/components/manalink-card'
+import { createManalink } from 'web/lib/firebase/manalinks'
+import { Modal } from 'web/components/layout/modal'
+import Textarea from 'react-expanding-textarea'
+import dayjs from 'dayjs'
+import Button from '../button'
+import { getManalinkUrl } from 'web/pages/links'
+import { DuplicateIcon } from '@heroicons/react/outline'
+
+export function CreateLinksButton(props: {
+ user: User
+ highlightedSlug: string
+ setHighlightedSlug: (slug: string) => void
+}) {
+ const { user, highlightedSlug, setHighlightedSlug } = props
+ const [open, setOpen] = useState(false)
+
+ return (
+ <>
+ setOpen(newOpen)}>
+
+ {
+ const slug = await createManalink({
+ fromId: user.id,
+ amount: newManalink.amount,
+ expiresTime: newManalink.expiresTime,
+ maxUses: newManalink.maxUses,
+ message: newManalink.message,
+ })
+ setHighlightedSlug(slug || '')
+ }}
+ />
+
+
+
+
+ >
+ )
+}
+
+function CreateManalinkForm(props: {
+ highlightedSlug: string
+ user: User
+ onCreate: (m: ManalinkInfo) => Promise
+}) {
+ const { user, onCreate, highlightedSlug } = props
+ const [isCreating, setIsCreating] = useState(false)
+ const [finishedCreating, setFinishedCreating] = useState(false)
+ const [copyPressed, setCopyPressed] = useState(false)
+ setTimeout(() => setCopyPressed(false), 300)
+
+ const [newManalink, setNewManalink] = useState({
+ expiresTime: null,
+ amount: 100,
+ maxUses: 1,
+ uses: 0,
+ message: '',
+ })
+
+ return (
+ <>
+ {!finishedCreating && (
+
+ )}
+ {finishedCreating && (
+ <>
+
+
+
+
+ {getManalinkUrl(highlightedSlug)}
+
+ {
+ navigator.clipboard.writeText(getManalinkUrl(highlightedSlug))
+ setCopyPressed(true)
+ }}
+ className="my-auto ml-2 h-5 w-5 cursor-pointer transition hover:opacity-50"
+ />
+
+ >
+ )}
+ >
+ )
+}
diff --git a/web/components/subtitle.tsx b/web/components/subtitle.tsx
new file mode 100644
index 00000000..85d19778
--- /dev/null
+++ b/web/components/subtitle.tsx
@@ -0,0 +1,15 @@
+import clsx from 'clsx'
+
+export function Subtitle(props: { text: string; className?: string }) {
+ const { text, className } = props
+ return (
+
+ {text}
+
+ )
+}
diff --git a/web/get-manalink-url.ts b/web/get-manalink-url.ts
new file mode 100644
index 00000000..89a0c8a6
--- /dev/null
+++ b/web/get-manalink-url.ts
@@ -0,0 +1,3 @@
+export default function getManalinkUrl(slug: string) {
+ return `${location.protocol}//${location.host}/link/${slug}`
+}
diff --git a/web/pages/links.tsx b/web/pages/links.tsx
index 12cde274..ede997df 100644
--- a/web/pages/links.tsx
+++ b/web/pages/links.tsx
@@ -4,41 +4,29 @@ import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/solid'
import { Claim, Manalink } from 'common/manalink'
import { formatMoney } from 'common/util/format'
import { Col } from 'web/components/layout/col'
+import { Row } from 'web/components/layout/row'
import { Page } from 'web/components/page'
import { SEO } from 'web/components/SEO'
import { Title } from 'web/components/title'
+import { Subtitle } from 'web/components/subtitle'
import { useUser } from 'web/hooks/use-user'
-import { createManalink, useUserManalinks } from 'web/lib/firebase/manalinks'
+import { useUserManalinks } from 'web/lib/firebase/manalinks'
import { fromNow } from 'web/lib/util/time'
import { useUserById } from 'web/hooks/use-users'
import { ManalinkTxn } from 'common/txn'
-import { User } from 'common/user'
-import { Tabs } from 'web/components/layout/tabs'
import { Avatar } from 'web/components/avatar'
import { RelativeTimestamp } from 'web/components/relative-timestamp'
import { UserLink } from 'web/components/user-page'
-import { ManalinkCard, ManalinkInfo } from 'web/components/manalink-card'
-
-import Textarea from 'react-expanding-textarea'
+import { CreateLinksButton } from 'web/components/manalinks/create-links-button'
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
dayjs.extend(customParseFormat)
-function getLinkUrl(slug: string) {
+export function getManalinkUrl(slug: string) {
return `${location.protocol}//${location.host}/link/${slug}`
}
-// TODO: incredibly gross, but the tab component is wrongly designed and
-// keeps the tab state inside of itself, so this seems like the only
-// way we can tell it to switch tabs from outside after initial render.
-function setTabIndex(tabIndex: number) {
- const tabHref = document.getElementById(`tab-${tabIndex}`)
- if (tabHref) {
- tabHref.click()
- }
-}
-
export default function LinkPage() {
const user = useUser()
const links = useUserManalinks(user?.id ?? '')
@@ -58,166 +46,31 @@ export default function LinkPage() {
-
- {
- const slug = await createManalink({
- fromId: user.id,
- amount: newManalink.amount,
- expiresTime: newManalink.expiresTime,
- maxUses: newManalink.maxUses,
- message: newManalink.message,
- })
- setTabIndex(1)
- setHighlightedSlug(slug || '')
- }}
- />
- ),
- },
- {
- title: 'Unclaimed links',
- content: (
-
- ),
- },
- // TODO: we have no use case for this atm and it's also really inefficient
- // {
- // title: 'Claimed',
- // content: ,
- // },
- ]}
- />
+
+
+ {user && (
+
+ )}
+
+
+ You can use manalinks to send mana to other people, even if they
+ don't yet have a Manifold account.
+
+
+
)
}
-function CreateManalinkForm(props: {
- user: User
- onCreate: (m: ManalinkInfo) => Promise
-}) {
- const { user, onCreate } = props
- const [isCreating, setIsCreating] = useState(false)
- const [newManalink, setNewManalink] = useState({
- expiresTime: null,
- amount: 100,
- maxUses: 1,
- uses: 0,
- message: '',
- })
- return (
- <>
-
- You can use manalinks to send mana to other people, even if they
- don't yet have a Manifold account.
-
-
-
-
- This is what the person you send the link to will see:
-
- >
- )
-}
-
export function ClaimsList(props: { txns: ManalinkTxn[] }) {
const { txns } = props
return (
@@ -334,8 +187,8 @@ function LinkSummaryRow(props: {
}) {
const { link, highlight, expanded, onToggle } = props
const className = clsx(
- 'whitespace-nowrap text-sm hover:cursor-pointer',
- highlight ? 'bg-primary' : 'text-gray-500 hover:bg-sky-50 bg-white'
+ 'whitespace-nowrap text-sm hover:cursor-pointer text-gray-500 hover:bg-sky-50 bg-white',
+ highlight ? 'bg-indigo-100 rounded-lg animate-pulse' : ''
)
return (
@@ -350,7 +203,7 @@ function LinkSummaryRow(props: {
{formatMoney(link.amount)}
|
- {getLinkUrl(link.slug)} |
+ {getManalinkUrl(link.slug)} |
{link.claimedUserIds.length} |
{link.maxUses == null ? '∞' : link.maxUses} |
@@ -365,22 +218,27 @@ function LinksTable(props: { links: Manalink[]; highlightedSlug?: string }) {
return links.length == 0 ? (
You don't currently have any outstanding manalinks.
) : (
-
-
-
- |
- Amount |
- Link |
- Uses |
- Max Uses |
- Expires |
-
-
-
- {links.map((link) => (
-
- ))}
-
-
+
+
+
+
+ |
+ Amount |
+ Link |
+ Uses |
+ Max Uses |
+ Expires |
+
+
+
+ {links.map((link) => (
+
+ ))}
+
+
+
)
}
|