mirror of
https://github.com/aljazceru/landscape-template.git
synced 2025-12-27 11:14:33 +01:00
feat: build donations components
- Donate Card - Stat Card - Donations Stats - Header
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import DonateCard from './DonateCard';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'Donations/Componets/Donate Card',
|
||||
component: DonateCard,
|
||||
} as ComponentMeta<typeof DonateCard>;
|
||||
|
||||
const Template: ComponentStory<typeof DonateCard> = (args) => <div className="max-w-[326px]"><DonateCard {...args as any} /></div>;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
103
src/features/Donations/components/DonateCard/DonateCard.tsx
Normal file
103
src/features/Donations/components/DonateCard/DonateCard.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React, { FormEvent, useState } from 'react';
|
||||
import { PaymentStatus, useVote } from 'src/utils/hooks';
|
||||
import Confetti from "react-confetti";
|
||||
import { useWindowSize } from '@react-hookz/web';
|
||||
import { Vote_Item_Type } from 'src/graphql';
|
||||
|
||||
const defaultOptions = [
|
||||
{ text: '500', value: 500 },
|
||||
{ text: '1,000', value: 1000 },
|
||||
{ text: '5,000', value: 5000 },
|
||||
{ text: '25,000', value: 25000 },
|
||||
]
|
||||
|
||||
|
||||
|
||||
export default function DonateCard() {
|
||||
const { width, height } = useWindowSize()
|
||||
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState(-1);
|
||||
const [donationAmount, setDonationAmount] = useState<number>();
|
||||
|
||||
const { vote, paymentStatus } = useVote({
|
||||
itemId: 123,
|
||||
itemType: Vote_Item_Type.Project
|
||||
})
|
||||
|
||||
const onChangeInput = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSelectedOption(-1);
|
||||
setDonationAmount(Number(event.target.value));
|
||||
};
|
||||
|
||||
const onSelectOption = (idx: number) => {
|
||||
setSelectedOption(idx);
|
||||
setDonationAmount(defaultOptions[idx].value);
|
||||
}
|
||||
|
||||
const requestPayment = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (donationAmount)
|
||||
vote(donationAmount, {
|
||||
onSuccess: () => {
|
||||
setTimeout(() => {
|
||||
setDonationAmount(undefined);
|
||||
setSelectedOption(-1);
|
||||
}, 4000);
|
||||
},
|
||||
onError: () => {
|
||||
setTimeout(() => {
|
||||
setDonationAmount(undefined);
|
||||
setSelectedOption(-1);
|
||||
}, 4000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-gray-50 border w-full shadow-2xl p-24 rounded-xl relative"
|
||||
>
|
||||
<h2 className='text-h5 font-bold'>Donate to BOLT🔩FUN</h2>
|
||||
<form onSubmit={requestPayment} className="mt-32 ">
|
||||
<div className="input-wrapper">
|
||||
<input
|
||||
className={` input-text input-removed-arrows`}
|
||||
value={donationAmount} onChange={onChangeInput}
|
||||
type="number"
|
||||
placeholder="1,000"
|
||||
autoFocus
|
||||
/>
|
||||
<p className='px-16 shrink-0 self-center text-primary-400'>
|
||||
Sats
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex mt-16 justify-between">
|
||||
{defaultOptions.map((option, idx) =>
|
||||
<button
|
||||
type='button'
|
||||
key={idx}
|
||||
className={`btn border px-12 rounded-md py-8 text-body5 bg-primary-100 hover:bg-primary-50 text-primary-600 ${idx === selectedOption && "border-primary-500 bg-primary-100 hover:bg-primary-100 text-primary-600"}`}
|
||||
onClick={() => onSelectOption(idx)}
|
||||
>
|
||||
{option.text}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{paymentStatus === PaymentStatus.FETCHING_PAYMENT_DETAILS && <p className="text-body6 mt-12 text-yellow-500">Please wait while we the fetch payment details.</p>}
|
||||
{paymentStatus === PaymentStatus.NOT_PAID && <p className="text-body6 mt-12 text-red-500">You did not confirm the payment. Please try again.</p>}
|
||||
{paymentStatus === PaymentStatus.PAID && <p className="text-body6 mt-12 text-green-500">The invoice was paid! Please wait while we confirm it.</p>}
|
||||
{paymentStatus === PaymentStatus.AWAITING_PAYMENT && <p className="text-body6 mt-12 text-yellow-500">Waiting for your payment...</p>}
|
||||
{paymentStatus === PaymentStatus.PAYMENT_CONFIRMED && <p className="text-body6 mt-12 text-green-500">Thanks for your vote</p>}
|
||||
<button
|
||||
type='submit'
|
||||
className="btn btn-primary w-full mt-32"
|
||||
disabled={paymentStatus !== PaymentStatus.DEFAULT && paymentStatus !== PaymentStatus.NOT_PAID}
|
||||
>
|
||||
{paymentStatus === PaymentStatus.DEFAULT || paymentStatus === PaymentStatus.NOT_PAID ? "Make a donation" : "Donating..."}
|
||||
</button>
|
||||
</form>
|
||||
{paymentStatus === PaymentStatus.PAYMENT_CONFIRMED && <Confetti width={width} height={height} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
export * from './pages/DonationsPage'
|
||||
export * from './pages/DonatePage/DonatePage'
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
|
||||
import DonatePage from './DonatePage';
|
||||
|
||||
export default {
|
||||
title: 'Donations/Donate Page/Page',
|
||||
component: DonatePage,
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
} as ComponentMeta<typeof DonatePage>;
|
||||
|
||||
|
||||
const Template: ComponentStory<typeof DonatePage> = (args) => <DonatePage {...args as any} ></DonatePage>
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
export default function HackathonsPage() {
|
||||
export default function DonatePage() {
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -0,0 +1,32 @@
|
||||
import { BiCoinStack } from "react-icons/bi";
|
||||
import { FiGrid } from "react-icons/fi";
|
||||
import { IoMedalOutline, IoRocketOutline } from "react-icons/io5";
|
||||
import StatCard from "../StatCard/StatCard";
|
||||
|
||||
|
||||
export default function DonationStats() {
|
||||
return (
|
||||
<div className="grid sm:grid-cols-2 md:grid-cols-4 gap-16">
|
||||
<StatCard
|
||||
color="#8B5CF6"
|
||||
label={<><BiCoinStack className='scale-125 mr-8' /> <span className="align-middle">Donations</span></>}
|
||||
value='$2.6k'
|
||||
/>
|
||||
<StatCard
|
||||
color="#F59E0B"
|
||||
label={<><IoRocketOutline className='scale-125 mr-8' /> <span className="align-middle">Tournaments</span></>}
|
||||
value='1'
|
||||
/>
|
||||
<StatCard
|
||||
color="#22C55E"
|
||||
label={<><IoMedalOutline className='scale-125 mr-8' /> <span className="align-middle">Prizes</span></>}
|
||||
value='2.5k'
|
||||
/>
|
||||
<StatCard
|
||||
color="#3B82F6"
|
||||
label={<><FiGrid className='scale-125 mr-8' /> <span className="align-middle">Applications</span></>}
|
||||
value='36'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { FiGrid } from 'react-icons/fi'
|
||||
import DonationStats from './DonationStats';
|
||||
|
||||
export default {
|
||||
title: 'Donations/Donate Page/DonationStats',
|
||||
component: DonationStats,
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
} as ComponentMeta<typeof DonationStats>;
|
||||
|
||||
|
||||
const Template: ComponentStory<typeof DonationStats> = (args) => <div className="max-w-[910px] mx-auto"><DonationStats {...args as any} ></DonationStats></div>
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
|
||||
import Header from './Header';
|
||||
|
||||
export default {
|
||||
title: 'Donations/Donate Page/Header',
|
||||
component: Header,
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
} as ComponentMeta<typeof Header>;
|
||||
|
||||
|
||||
const Template: ComponentStory<typeof Header> = (args) => <Header {...args as any} ></Header>
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
}
|
||||
|
||||
|
||||
26
src/features/Donations/pages/DonatePage/Header/Header.tsx
Normal file
26
src/features/Donations/pages/DonatePage/Header/Header.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import DonateCard from 'src/features/Donations/components/DonateCard/DonateCard'
|
||||
import DonationStats from '../DonationStats/DonationStats'
|
||||
import styles from './styles.module.scss'
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<div className={`${styles.header}`}>
|
||||
<div className="flex items-center gap-24 flex-col md:flex-row">
|
||||
<div>
|
||||
<h1 className="text-[54px] font-bolder">
|
||||
Donate
|
||||
</h1>
|
||||
<p className='text-h3 font-bolder mt-24'>
|
||||
Help fund <span className="text-primary-600">BOLT🔩FUN</span>, as well as other <span className="text-primary-600">Makers</span> working on lightning apps through tournaments and prize pools
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-w-[326px]">
|
||||
<DonateCard />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-52 md:mt-80">
|
||||
<DonationStats />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
@import "/src/styles/mixins";
|
||||
|
||||
.header {
|
||||
padding: 56px 24px;
|
||||
background: #ffecf9;
|
||||
background: linear-gradient(40deg, white -5%, #ffb7d963 74%, #e3faff61 100%);
|
||||
|
||||
& > div {
|
||||
max-width: calc(min(100%, 910px));
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@include gt-md {
|
||||
padding: 156px 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { FiGrid } from 'react-icons/fi'
|
||||
import StatCard from './StatCard';
|
||||
|
||||
export default {
|
||||
title: 'Donations/Donate Page/StatCard',
|
||||
component: StatCard,
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
} as ComponentMeta<typeof StatCard>;
|
||||
|
||||
|
||||
const Template: ComponentStory<typeof StatCard> = (args) => <div className="max-w-[220px]"><StatCard {...args} ></StatCard></div>
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
color: "#3B82F6",
|
||||
label: <><FiGrid className='scale-125 mr-8' /> Applications</>,
|
||||
value: '36'
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
interface Props {
|
||||
label: ReactNode,
|
||||
value: ReactNode,
|
||||
color: string
|
||||
}
|
||||
|
||||
export default function StatCard(props: Props) {
|
||||
return (
|
||||
<div className="bg-white p-24 rounded-16 text-center"
|
||||
style={{
|
||||
color: props.color,
|
||||
}}
|
||||
>
|
||||
<p className="text-body4">
|
||||
{props.label}
|
||||
</p>
|
||||
<p className="text-h2 mt-8 font-bolder">
|
||||
{props.value}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user