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: Active WebMo Page UI #107

Merged
merged 1 commit into from
Feb 27, 2024
Merged
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
13 changes: 7 additions & 6 deletions src/components/layout/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import React, { useMemo } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { runtime } from 'webextension-polyfill'

import { usePopup } from '@/providers/popup.state'

import { ArrowBack, Settings } from '../icons'
import { ROUTES } from '../router-provider'
import { usePopup } from '@/providers/popup.state'
import { Switch } from '../switch'

const Logo = runtime.getURL('assets/images/logo.svg')
Expand Down Expand Up @@ -41,14 +42,14 @@ export const Header = () => {
}

return (
<div className="flex flex-row items-center justify-between py-8">
<div className="flex flex-row items-center">
<div className="flex flex-row items-center justify-between h-8">
<div className="flex flex-row items-center gap-3">
<img src={Logo} alt="Web Monetization Logo" className="h-6" />
<p className="ml-3 text-strong text-xl">Web Monetization</p>
<p className="text-strong text-xl">Web Monetization</p>
</div>
<div className="flex flex-row items-center">
<div className="flex flex-row items-center gap-3">
<NavigationButton />
<Switch checked={wmEnabled} onChange={switchWmEnabled} className="ml-2" />
<Switch checked={wmEnabled} onChange={switchWmEnabled} />
</div>
</div>
)
Expand Down
4 changes: 2 additions & 2 deletions src/components/layout/main-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { Outlet } from 'react-router-dom'
import { Header } from './header'

const Divider = () => {
return <div className="mb-8 bg-divider-gradient w-100 h-1" />
return <div className="bg-divider-gradient w-100 h-1" />
}

export const MainLayout = () => {
return (
<div className="flex flex-col w-popup h-popup border-base px-6">
<div className="flex flex-col gap-8 w-popup h-popup border-base px-6 pt-8">
<Header />
<Divider />
<main>
Expand Down
3 changes: 2 additions & 1 deletion src/components/slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const sliderClasses = `
[&::-moz-range-thumb]:h-5
[&::-moz-range-thumb]:bg-switch-base
[&::-moz-range-thumb]:rounded-full
[&::-webkit-slider-thumb]:disabled:bg-disabled-strong
w-full h-1 bg-disabled-strong rounded-lg
appearance-none cursor-pointer dark:bg-disabled-strong
`
Expand All @@ -42,7 +43,7 @@ export const Slider = forwardRef<HTMLInputElement, SliderProps>(function Slider(
aria-disabled={disabled ?? false}
aria-invalid={!!errorMessage}
aria-describedby={errorMessage}
value={value}
value={disabled ? 0 : value}
onChange={onChange}
{...props}
/>
Expand Down
15 changes: 1 addition & 14 deletions src/popup/Popup.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ body {
width: fit-content;
height: fit-content;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
font-family: 'Titillium Web', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
Expand All @@ -28,19 +28,6 @@ body {
box-sizing: border-box;
}

.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-basis: auto;
padding: 0 20px 10px;

img {
height: 96px;
}
}

.pointerForm {
width: 100%;
border-radius: 8px;
Expand Down
5 changes: 4 additions & 1 deletion src/popup/index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<!DOCTYPE html>
<html>
<html lang="en">

<head>
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Titillium+Web:wght@400;500;700&display=swap" rel="stylesheet">
</head>

<body>
Expand Down
258 changes: 65 additions & 193 deletions src/popup/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,212 +1,84 @@
import React, { useEffect, useState } from 'react'
import { runtime } from 'webextension-polyfill'
import React, { useState } from 'react'

import { Button } from '@/components/button'
import { DollarSign, WarningSign } from '@/components/icons'
import { Input } from '@/components/input'
import { Label } from '@/components/label'
import { Slider } from '@/components/slider'
import { Switch } from '@/components/switch'
import { usePopup } from '@/providers/popup.state'
import { formatCurrency } from '@/utils/formatCurrency'
import { sendMessage, sendMessageToActiveTab } from '@/utils/sendMessages'
import { getStorageKey } from '@/utils/storage'

const Success = runtime.getURL('assets/images/web-monetization-success.svg')
const Fail = runtime.getURL('assets/images/web-monetization-fail.svg')
const CheckIcon = runtime.getURL('assets/images/check.svg')
const DollarIcon = runtime.getURL('assets/images/dollar.svg')
const CloseIcon = runtime.getURL('assets/images/close.svg')

// --- Temporary code until real UI implemented ---

interface IProps {
isMonetizationReady: boolean
}

const PopupFooter: React.FC<IProps> = ({ isMonetizationReady }) => (
<footer className="flex items-center justify-center px-4">
{isMonetizationReady ? (
<span>This site is Web Monetization ready</span>
) : (
<span>This site isn&apos;t Web Monetization ready</span>
)}
</footer>
)

// --- End of Temporary code until real UI implemented ---

export const Home = () => {
const [remainingBalance, setRemainingBalance] = useState(0)
const [rateOfPay, setRateOfPay] = useState(0.36)
const [loading, setLoading] = useState(false)
const [paymentStarted, setPaymentStarted] = useState(false)
const [spent, setSpent] = useState(0)
const [sendingPaymentPointer, setSendingPaymentPointer] = useState('')
const [isMonetizationReady, setIsMonetizationReady] = useState(false)
const [receivingPaymentPointer, setReceivingPaymentPointer] = useState('')
const [formData, setFormData] = useState({
paymentPointer: sendingPaymentPointer || '',
amount: 0,
})

useEffect(() => {
checkMonetizationReady()
getSendingPaymentPointer()
listenForIncomingPayment()
getRateOfPay()
getRemainingBalance()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const getRateOfPay = async () => {
const response = await getStorageKey('rateOfPay')
response && setRateOfPay(response)
}

const getRemainingBalance = async () => {
const response = await getStorageKey('amount')
response && setRemainingBalance(response)
}

const checkMonetizationReady = async () => {
const response = await sendMessageToActiveTab({ type: 'IS_MONETIZATION_READY' })
setIsMonetizationReady(response.data.monetization)
setReceivingPaymentPointer(response.data.paymentPointer)
}

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setFormData(prevState => ({ ...prevState, [event.target.name]: event.target.value }))
}

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()

setLoading(true)
const data = {
amount: formData.amount,
paymentPointer: formData.paymentPointer,
incomingPayment: receivingPaymentPointer,
}
const {
data: { wmEnabled, rateOfPay, amount, amountType },
setData,
} = usePopup()
const [tipAmount, setTipAmount] = useState('')

await sendMessage({ type: 'SET_INCOMING_POINTER', data })
}

const getSendingPaymentPointer = async () => {
const response = await sendMessage({ type: 'GET_SENDING_PAYMENT_POINTER' })
setSendingPaymentPointer(response.data.sendingPaymentPointerUrl)

const { sendingPaymentPointerUrl: paymentPointer, amount } = response.data
if (paymentPointer && amount) {
setFormData({
paymentPointer: response.data.sendingPaymentPointerUrl,
amount: response.data.amount,
})
}
}

const listenForIncomingPayment = async () => {
const listener = (message: any) => {
if (message.type === 'SPENT_AMOUNT') {
setSpent(message.data.spentAmount)
setPaymentStarted(true)
}

if (loading) {
setLoading(false)
}
}

runtime.onMessage.addListener(listener)
return () => {
runtime.onMessage.removeListener(listener)
}
const updateRateOfPay = async (event: any) => {
setData(prevState => ({ ...prevState, rateOfPay: event.target.value }))
}

const stopPayments = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault()
setPaymentStarted(false)
setTimeout(() => {
if (loading) {
setLoading(false)
}
}, 1000)
await sendMessageToActiveTab({ type: 'STOP_PAYMENTS' })
const updateStreamType = async (event: any) => {
setData(prevState => ({
...prevState,
amountType: { ...prevState.amountType, recurring: event.target.checked },
}))
}

const updateRateOfPay = async (event: any) => {
setRateOfPay(event.target.value)
await sendMessage({
type: 'SET_STORAGE_KEY',
data: { key: 'rateOfPay', value: event.target.value },
})
if (!wmEnabled) {
return (
<div className="flex items-center gap-2">
<WarningSign />
<p className="text-base text-medium">Web Monetization has been turned off.</p>
</div>
)
}

return (
<>
{!!spent && (
<div className="spentAmount">
${spent}/<span>$20</span>
<div className="flex flex-col gap-8 basis-auto justify-center">
<div className="grid gap-4 w-full">
<div className="px-2 text-base font-medium text-medium">Current rate of pay</div>
<Slider
min={0}
max={1}
step={0.01}
value={rateOfPay}
onChange={updateRateOfPay}
disabled={!amountType.recurring}
/>
<div className="px-2 flex items-center justify-between w-full">
<span>{!amountType.recurring ? '0c' : formatCurrency(rateOfPay)} per hour</span>
<span>Remaining balance: ${amount}</span>
</div>
)}
<div className="content">
<div className="grid gap-4 w-full">
<div className="px-2 text-base font-medium text-medium">Current rate of pay</div>
<Slider min={0} max={1} step={0.01} value={rateOfPay} onChange={updateRateOfPay} />
<div className="flex items-center justify-between w-full">
<span>{formatCurrency(rateOfPay)} per hour</span>
<span>Remaining balance: ${remainingBalance}</span>
</div>
</div>

{isMonetizationReady ? (
<>
<img src={Success} alt="Success" />

<form
onSubmit={handleSubmit}
className={`pointerForm ${paymentStarted ? 'active' : ''}`}>
<div className="input-wrapper">
<label htmlFor="paymentPointer">Payment pointer</label>
<div className="input">
<input
type="text"
name="paymentPointer"
value={formData.paymentPointer}
onInput={handleChange}
disabled={paymentStarted}
placeholder="https://ilp.rafiki.money/alice"
/>
</div>
</div>
</div>

<div className="input-wrapper">
<label htmlFor="pointer">Amount</label>
<div className="input">
<img src={DollarIcon} alt="dollar" />
<input
type="text"
name="amount"
value={formData.amount}
onInput={handleChange}
disabled={paymentStarted}
placeholder="0.05"
/>
</div>
</div>
<div className="flex items-center gap-4 h-7">
<Switch size="small" checked={amountType.recurring} onChange={updateStreamType} />
<span className="text-medium text-base">Continuous payments stream</span>
</div>

<div className="actions">
{paymentStarted ? (
<button type="button" className="stop-btn" onClick={stopPayments}>
<img src={CloseIcon} alt="Stop" />
</button>
) : (
<button type="submit" className={`submit-btn ${loading ? 'loading' : ''}`}>
<img src={CheckIcon} alt="Check" />
</button>
)}
</div>
</form>
</>
) : (
<img src={Fail} alt="Fail" />
)}
<div className="h-px bg-nav-active" />

<div className="flex flex-col gap-4">
<Label className="text-base font-medium text-medium">
Pay <span className="text-primary">https://alexlakatos.com/</span>
</Label>
<Input
value={tipAmount}
type="number"
id="amount"
name="amount"
placeholder="0.00"
onChange={event => setTipAmount(event.target.value)}
icon={<DollarSign />}
/>
</div>
<PopupFooter isMonetizationReady={isMonetizationReady} />
</>

<Button aria-label="Send now" className="text-base font-medium">
Send now
</Button>
</div>
)
}
Loading
Loading