feat: add missing titles and responsiveness improvements

This commit is contained in:
Artur
2024-08-23 14:11:54 -03:00
parent b37124701a
commit d0db567660
26 changed files with 121 additions and 1535 deletions

View File

@@ -123,18 +123,18 @@ const DonationFormModal: React.FC<Props> = ({ project }) => {
return (
<div className="space-y-4">
<div className="flex flex-col space-y-8 py-4">
<div className="flex items-center space-x-4">
<div className="py-4 flex flex-col space-y-8">
<div className="flex flex-col items-center sm:space-x-4 sm:flex-row">
<Image
alt={project.title}
src={project.coverImage}
width={96}
width={200}
height={96}
objectFit="cover"
className="rounded-xl"
className="w-36 rounded-xl"
/>
<div className="flex flex-col">
<h2 className="font-semibold">{project.title}</h2>
<div className="flex flex-col justify-center">
<h2 className="text-center sm:text-left font-semibold">{project.title}</h2>
<h3 className="text-gray-500">Pledge your support</h3>
</div>
</div>
@@ -181,9 +181,9 @@ const DonationFormModal: React.FC<Props> = ({ project }) => {
<FormItem>
<FormLabel>Amount</FormLabel>
<FormControl>
<div className="flex flex-row space-x-2 items-center">
<div className="flex flex-row gap-2 items-center flex-wrap ">
<Input
className="w-40"
className="w-40 mr-auto"
type="number"
inputMode="numeric"
leftIcon={DollarSign}
@@ -243,7 +243,7 @@ const DonationFormModal: React.FC<Props> = ({ project }) => {
)}
/>
<div className="mt-4 flex flex-wrap space-x-2">
<div className="mt-4 flex flex-col sm:flex-row space-y-2 sm:space-x-2 sm:space-y-0">
<Button
type="button"
onClick={form.handleSubmit(handleBtcPay)}

View File

@@ -1,7 +1,6 @@
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import { zodResolver } from '@hookform/resolvers/zod'
import { ReloadIcon } from '@radix-ui/react-icons'
import { signIn, useSession } from 'next-auth/react'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
@@ -12,6 +11,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '
import { Button } from './ui/button'
import { useToast } from './ui/use-toast'
import { useFundSlug } from '../utils/use-fund-slug'
import Spinner from './Spinner'
const schema = z.object({
email: z.string().email(),
@@ -133,8 +133,7 @@ function LoginFormModal({ close, openPasswordResetModal, openRegisterModal }: Pr
</Button>
<Button className="grow basis-0" type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting && <ReloadIcon className="mr-2 h-4 w-4 animate-spin" />}{' '}
Login
{form.formState.isSubmitting && <Spinner />} Login
</Button>
</div>
</form>

View File

@@ -113,19 +113,19 @@ const MembershipFormModal: React.FC<Props> = ({ project }) => {
return (
<div className="space-y-4">
<div className="flex flex-col space-y-8 py-4">
<div className="flex items-center space-x-4">
<div className="py-4 flex flex-col space-y-8">
<div className="flex flex-col items-center sm:space-x-4 sm:flex-row">
<Image
alt={project.title}
src={project.coverImage}
width={96}
width={200}
height={96}
objectFit="cover"
className="rounded-xl"
className="w-36 rounded-xl"
/>
<div className="flex flex-col">
<h2 className="font-sans font-bold">{project.title}</h2>
<h3 className="text-textgray font-sans">Annual membership</h3>
<div className="flex flex-col justify-center">
<h2 className="text-center sm:text-left font-semibold">{project.title}</h2>
<h3 className="text-gray-500">Pledge your support</h3>
</div>
</div>
</div>
@@ -167,7 +167,7 @@ const MembershipFormModal: React.FC<Props> = ({ project }) => {
<div className="flex flex-col space-y-3">
<FormLabel>Amount</FormLabel>
<span className="flex flex-row">
<DollarSign />
<DollarSign className="text-primary" />
100.00
</span>
</div>
@@ -236,13 +236,11 @@ const MembershipFormModal: React.FC<Props> = ({ project }) => {
)}
/>
<div className="mt-4 flex flex-wrap space-x-2">
<div className="mt-4 flex flex-col sm:flex-row space-y-2 sm:space-x-2 sm:space-y-0">
<Button
type="button"
onClick={form.handleSubmit(handleBtcPay)}
disabled={
!form.formState.isValid || form.formState.isSubmitting || recurring === 'yes'
}
disabled={!form.formState.isValid || form.formState.isSubmitting}
className="grow basis-0"
>
{payMembershipWithCryptoMutation.isPending ? (

View File

@@ -39,7 +39,7 @@ const MobileNav = () => {
</svg>
</button>
<div
className={`fixed left-0 top-0 z-10 h-full w-full transform bg-gray-200 opacity-95 duration-300 ease-in-out ${
className={`fixed left-0 top-0 z-10 h-full w-full transform bg-background opacity-95 duration-300 ease-in-out ${
navShow ? 'translate-x-0' : 'translate-x-full'
}`}
>

View File

@@ -1,27 +1,14 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { ReloadIcon } from '@radix-ui/react-icons'
import { signIn, useSession } from 'next-auth/react'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import {
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from './ui/dialog'
import { DialogContent, DialogDescription, DialogHeader, DialogTitle } from './ui/dialog'
import { Input } from './ui/input'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from './ui/form'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from './ui/form'
import { Button } from './ui/button'
import { useToast } from './ui/use-toast'
import { trpc } from '../utils/trpc'
import Spinner from './Spinner'
const schema = z.object({
email: z.string().email(),
@@ -38,8 +25,7 @@ function PasswordResetFormModal({ close }: Props) {
resolver: zodResolver(schema),
})
const requestPasswordResetMutation =
trpc.auth.requestPasswordReset.useMutation()
const requestPasswordResetMutation = trpc.auth.requestPasswordReset.useMutation()
async function onSubmit(data: PasswordResetFormInputs) {
await requestPasswordResetMutation.mutateAsync(data)
@@ -60,10 +46,7 @@ function PasswordResetFormModal({ close }: Props) {
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col space-y-4"
>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col space-y-4">
<FormField
control={form.control}
name="email"
@@ -79,10 +62,7 @@ function PasswordResetFormModal({ close }: Props) {
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting && (
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" />
)}{' '}
Reset Password
{form.formState.isSubmitting && <Spinner />} Reset Password
</Button>
</form>
</Form>

View File

@@ -31,7 +31,7 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project, customImageStyles })
}, [project.coverImage])
return (
<Link href={`/${project.fund}/projects/${project.slug}`} passHref>
<Link href={`/${project.fund}/projects/${project.slug}`} passHref target="_blank">
<figure
className={cn(
'max-w-sm min-h-[460px] h-full space-y-2 flex flex-col rounded-xl border-b-4 bg-white',

View File

@@ -1,5 +1,4 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { ReloadIcon } from '@radix-ui/react-icons'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
@@ -25,6 +24,7 @@ import { Button } from './ui/button'
import { useToast } from './ui/use-toast'
import { trpc } from '../utils/trpc'
import { useFundSlug } from '../utils/use-fund-slug'
import Spinner from './Spinner'
const schema = z
.object({
@@ -149,8 +149,7 @@ function RegisterFormModal({ close, openLoginModal }: Props) {
</Button>
<Button type="submit" disabled={form.formState.isSubmitting} className="grow basis-0">
{form.formState.isSubmitting && <ReloadIcon className="mr-2 h-4 w-4 animate-spin" />}{' '}
Register
{form.formState.isSubmitting && <Spinner />} Register
</Button>
</div>
</form>

View File

@@ -2,9 +2,9 @@ const Spinner = () => {
return (
<svg
role="status"
className="mr-2 w-8 h-8 text-black animate-spin fill-primary"
className="mr-2 w-6 h-6 animate-spin fill-primary"
viewBox="0 0 100 101"
fill="none"
fill="inherit"
xmlns="http://www.w3.org/2000/svg"
>
<path

View File

@@ -33,7 +33,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
'fixed left-[50%] top-[50%] z-50 grid w-full sm:max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}

View File

@@ -94,7 +94,7 @@ const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) =
return (
<>
<Head>
<title>Monero Fund - {project.title}</title>
<title>Monero Fund | {project.title}</title>
</Head>
<div className="divide-y divide-gray-200">

View File

@@ -5,6 +5,7 @@ import ProjectCard from '../../../components/ProjectCard'
import { ProjectItem } from '../../../utils/types'
import { getProjects } from '../../../utils/md'
import { useFundSlug } from '../../../utils/use-fund-slug'
import { funds, fundSlugs } from '../../../utils/funds'
const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
const [modalOpen, setModalOpen] = useState(false)
@@ -29,10 +30,8 @@ const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
return (
<>
<Head>
<title>MAGIC Monero Fund | Projects</title>
</Head>
<section className="p-4 md:p-8 flex flex-col items-center">
<Head>{fundSlug && <title>{funds[fundSlug].title} | Projects</title>}</Head>
<section className="flex flex-col items-center">
<div className="flex justify-between items-center pb-8 w-full">
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Projects
@@ -53,8 +52,15 @@ const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
export default AllProjects
export async function getStaticProps({ params }: { params: any }) {
const projects = await getProjects('monero')
export function getStaticPaths() {
return {
paths: fundSlugs.map((fundSlug) => `/${fundSlug}/projects`),
fallback: false,
}
}
export async function getStaticProps({ params, ...asd }: { params: any }) {
const projects = await getProjects(params.fund)
return {
props: {

View File

@@ -1,11 +1,9 @@
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { ReloadIcon } from '@radix-ui/react-icons'
import { zodResolver } from '@hookform/resolvers/zod'
import { useRouter } from 'next/router'
import { jwtDecode } from 'jwt-decode'
import { z } from 'zod'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import {
Form,
@@ -20,6 +18,7 @@ import { Button } from '../../../components/ui/button'
import { toast } from '../../../components/ui/use-toast'
import { trpc } from '../../../utils/trpc'
import { useFundSlug } from '../../../utils/use-fund-slug'
import Spinner from '../../../components/Spinner'
const schema = z
.object({
@@ -145,8 +144,7 @@ function ResetPassword() {
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting && <ReloadIcon className="mr-2 h-4 w-4 animate-spin" />}{' '}
Reset Password
{form.formState.isSubmitting && <Spinner />} Reset Password
</Button>
</form>
</Form>

View File

@@ -9,10 +9,10 @@ import { trpc } from '../utils/trpc'
import { useFundSlug } from '../utils/use-fund-slug'
import '../styles/globals.css'
import { funds } from '../utils/funds'
function MyApp({ Component, pageProps, router }: AppProps) {
function MyApp({ Component, pageProps }: AppProps) {
const fundSlug = useFundSlug()
console.log(fundSlug || 'general')
return (
<SessionProvider session={pageProps.session}>
@@ -24,6 +24,7 @@ function MyApp({ Component, pageProps, router }: AppProps) {
>
<Head>
<meta content="width=device-width, initial-scale=1" name="viewport" />
<title>{fundSlug ? funds[fundSlug].title : 'MAGIC Grants Campaigns'}</title>
</Head>
<Layout>
<Component {...pageProps} />

View File

@@ -53,13 +53,17 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Support <Typing />
</h1>
<p className="text-xl leading-7 text-gray-500">
<p className="max-w-3xl text-xl leading-7 text-gray-500">
Help us to provide sustainable funding for free and open-source contributors working on
freedom tech and projects that help Monero flourish.
</p>
<div className="flex flex-wrap py-4 space-x-4">
<Button onClick={() => setDonateModalOpen(true)} size="lg">
<div className="flex flex-col md:flex-row my-4 gap-2">
<Button
className="text-sm md:text-base"
onClick={() => setDonateModalOpen(true)}
size="lg"
>
Donate to Monero Comittee General Fund
</Button>

View File

@@ -1,294 +0,0 @@
import { useEffect, useState } from 'react'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/router'
import { GetServerSidePropsContext, NextPage } from 'next/types'
import Head from 'next/head'
import ErrorPage from 'next/error'
import Image from 'next/image'
import xss from 'xss'
import { ProjectDonationStats, ProjectItem } from '../../../utils/types'
import { getProjectBySlug } from '../../../utils/md'
import markdownToHtml from '../../../utils/markdownToHtml'
import PageHeading from '../../../components/PageHeading'
import Progress from '../../../components/Progress'
import { prisma } from '../../../server/services'
import { Button } from '../../../components/ui/button'
import { Dialog, DialogContent } from '../../../components/ui/dialog'
import DonationFormModal from '../../../components/DonationFormModal'
import MembershipFormModal from '../../../components/MembershipFormModal'
import LoginFormModal from '../../../components/LoginFormModal'
import RegisterFormModal from '../../../components/RegisterFormModal'
import PasswordResetFormModal from '../../../components/PasswordResetFormModal'
import CustomLink from '../../../components/CustomLink'
import { trpc } from '../../../utils/trpc'
import { getFundSlugFromUrlPath } from '../../../utils/funds'
import { useFundSlug } from '../../../utils/use-fund-slug'
type SingleProjectPageProps = {
project: ProjectItem
projects: ProjectItem[]
donationStats: ProjectDonationStats
}
const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) => {
const router = useRouter()
const [donateModalOpen, setDonateModalOpen] = useState(false)
const [memberModalOpen, setMemberModalOpen] = useState(false)
const [registerIsOpen, setRegisterIsOpen] = useState(false)
const [loginIsOpen, setLoginIsOpen] = useState(false)
const [passwordResetIsOpen, setPasswordResetIsOpen] = useState(false)
const session = useSession()
const fundSlug = useFundSlug()
const userHasMembershipQuery = trpc.donation.userHasMembership.useQuery(
{ projectSlug: project.slug },
{ enabled: false }
)
const {
slug,
title,
summary,
coverImage,
git,
twitter,
content,
nym,
website,
personalTwitter,
personalWebsite,
goal,
isFunded,
} = project
function formatBtc(bitcoin: number) {
if (bitcoin > 0.1) {
return `${bitcoin.toFixed(3) || 0.0} BTC`
} else {
return `${Math.floor(bitcoin * 100000000).toLocaleString()} sats`
}
}
function formatUsd(dollars: number): string {
if (dollars == 0) {
return '$0'
} else if (dollars / 1000 > 1) {
return `$${Math.round(dollars / 1000)}k+`
} else {
return `$${dollars.toFixed(0)}`
}
}
useEffect(() => {
if (session.status === 'authenticated') {
console.log('refetching')
userHasMembershipQuery.refetch()
}
}, [session.status])
if (!router.isFallback && !slug) {
return <ErrorPage statusCode={404} />
}
return (
<>
<Head>
<title>Monero Fund - {project.title}</title>
</Head>
<div className="divide-y divide-gray-200">
<PageHeading project={project}>
<div className="flex flex-col items-center space-x-2 pt-8 xl:block">
<Image
src={coverImage}
alt="avatar"
width={300}
height={300}
className="h-72 w-72 mx-auto object-contain xl:hidden"
/>
<div className="space-y-4">
{!project.isFunded && (
<div className="flex flex-col space-y-2">
<Button onClick={() => setDonateModalOpen(true)}>Donate</Button>
{!userHasMembershipQuery.data && (
<Button
onClick={() =>
session.status === 'authenticated'
? setMemberModalOpen(true)
: setRegisterIsOpen(true)
}
variant="outline"
>
Get Annual Membership
</Button>
)}
{!!userHasMembershipQuery.data && (
<CustomLink href={`${fundSlug}/account/my-memberships`}>
<Button variant="outline">My Memberships</Button>{' '}
</CustomLink>
)}
</div>
)}
<h1 className="mb-4 font-bold">Raised</h1>
<Progress
current={
donationStats.xmr.fiatAmount +
donationStats.btc.fiatAmount +
donationStats.usd.fiatAmount
}
goal={goal}
/>
<ul className="font-semibold space-y-1">
<li className="flex items-center space-x-1">
<span className="text-green-500 text-xl">{`${formatUsd(donationStats.xmr.fiatAmount + donationStats.btc.fiatAmount + donationStats.usd.fiatAmount)}`}</span>{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count + donationStats.btc.count + donationStats.usd.count}{' '}
donations total
</span>
</li>
<li>
{donationStats.xmr.amount} XMR{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count} donations
</span>
</li>
<li>
{formatBtc(donationStats.btc.amount)}{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.btc.count} donations
</span>
</li>
<li>
{`${formatUsd(donationStats.usd.amount)}`} Fiat{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.usd.count} donations
</span>
</li>
</ul>
</div>
</div>
<article
className="prose max-w-none pb-8 pt-8 xl:col-span-2"
dangerouslySetInnerHTML={{ __html: xss(content || '') }}
/>
</PageHeading>
</div>
<Dialog open={donateModalOpen} onOpenChange={setDonateModalOpen}>
<DialogContent>
<DonationFormModal project={project} />
</DialogContent>
</Dialog>
<Dialog open={memberModalOpen} onOpenChange={setMemberModalOpen}>
<DialogContent>
<MembershipFormModal project={project} />
</DialogContent>
</Dialog>
{session.status !== 'authenticated' && (
<>
<Dialog open={loginIsOpen} onOpenChange={setLoginIsOpen}>
<DialogContent>
<LoginFormModal
close={() => setLoginIsOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
openPasswordResetModal={() => setPasswordResetIsOpen(true)}
/>
</DialogContent>
</Dialog>
<Dialog open={registerIsOpen} onOpenChange={setRegisterIsOpen}>
<DialogContent>
<RegisterFormModal
openLoginModal={() => setLoginIsOpen(true)}
close={() => setRegisterIsOpen(false)}
/>
</DialogContent>
</Dialog>
<Dialog open={passwordResetIsOpen} onOpenChange={setPasswordResetIsOpen}>
<DialogContent>
<PasswordResetFormModal close={() => setPasswordResetIsOpen(false)} />
</DialogContent>
</Dialog>
</>
)}
</>
)
}
export default Project
export async function getServerSideProps({ params, resolvedUrl }: GetServerSidePropsContext) {
const fundSlug = getFundSlugFromUrlPath(resolvedUrl)
if (!params?.slug) return {}
if (!fundSlug) return {}
const project = getProjectBySlug(params.slug as string, fundSlug)
const content = await markdownToHtml(project.content || '')
const donationStats = {
xmr: {
count: project.isFunded ? project.numdonationsxmr || 0 : 0,
amount: project.isFunded ? project.totaldonationsxmr || 0 : 0,
fiatAmount: project.isFunded ? project.totaldonationsinfiatxmr || 0 : 0,
},
btc: {
count: project.isFunded ? project.numdonationsbtc || 0 : 0,
amount: project.isFunded ? project.totaldonationsbtc || 0 : 0,
fiatAmount: project.isFunded ? project.totaldonationsinfiatbtc || 0 : 0,
},
usd: {
count: project.isFunded ? project.fiatnumdonations || 0 : 0,
amount: project.isFunded ? project.fiattotaldonations || 0 : 0,
fiatAmount: project.isFunded ? project.fiattotaldonationsinfiat || 0 : 0,
},
}
if (!project.isFunded) {
const donations = await prisma.donation.findMany({
where: { projectSlug: params.slug as string, fundSlug },
})
donations.forEach((donation) => {
if (donation.cryptoCode === 'XMR') {
donationStats.xmr.count += 1
donationStats.xmr.amount += donation.cryptoAmount || 0
donationStats.xmr.fiatAmount += donation.fiatAmount
}
if (donation.cryptoCode === 'BTC') {
donationStats.btc.count += 1
donationStats.btc.amount += donation.cryptoAmount || 0
donationStats.btc.fiatAmount += donation.fiatAmount
}
if (donation.cryptoCode === null) {
donationStats.usd.count += 1
donationStats.usd.amount += donation.fiatAmount
donationStats.usd.fiatAmount += donation.fiatAmount
console.log(donation)
}
})
}
return {
props: {
project: {
...project,
content,
},
donationStats,
},
}
}

View File

@@ -1,65 +0,0 @@
import { useEffect, useState } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import ProjectCard from '../../../components/ProjectCard'
import { ProjectItem } from '../../../utils/types'
import { getProjects } from '../../../utils/md'
import { useFundSlug } from '../../../utils/use-fund-slug'
const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
const [modalOpen, setModalOpen] = useState(false)
const [selectedProject, setSelectedProject] = useState<ProjectItem>()
const [sortedProjects, setSortedProjects] = useState<ProjectItem[]>()
const fundSlug = useFundSlug()
useEffect(() => {
setSortedProjects(projects.sort(() => 0.5 - Math.random()))
}, [projects])
function closeModal() {
setModalOpen(false)
}
function openPaymentModal(project: ProjectItem) {
setSelectedProject(project)
setModalOpen(true)
}
if (!fundSlug) return <></>
return (
<>
<Head>
<title>MAGIC Monero Fund | Projects</title>
</Head>
<section className="p-4 md:p-8 flex flex-col items-center">
<div className="flex justify-between items-center pb-8 w-full">
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Projects
</h1>
</div>
<ul className="grid md:grid-cols-3 gap-4 max-w-5xl">
{sortedProjects &&
sortedProjects.map((p, i) => (
<li key={i} className="">
<ProjectCard project={p} />
</li>
))}
</ul>
</section>
</>
)
}
export default AllProjects
export async function getStaticProps({ params }: { params: any }) {
const projects = await getProjects('monero')
return {
props: {
projects,
},
revalidate: 120,
}
}

View File

@@ -53,13 +53,17 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Support <Typing />
</h1>
<p className="text-xl leading-7 text-gray-500">
<p className="max-w-3xl text-xl leading-7 text-gray-500">
Help us to provide sustainable funding for free and open-source contributors working on
freedom tech and projects that help Monero flourish.
</p>
<div className="flex flex-wrap py-4 space-x-4">
<Button onClick={() => setDonateModalOpen(true)} size="lg">
<div className="flex flex-col md:flex-row my-4 gap-2">
<Button
className="text-sm md:text-base"
onClick={() => setDonateModalOpen(true)}
size="lg"
>
Donate to Monero Comittee General Fund
</Button>

View File

@@ -1,292 +0,0 @@
import { useEffect, useState } from 'react'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/router'
import { GetServerSidePropsContext, NextPage } from 'next/types'
import Head from 'next/head'
import ErrorPage from 'next/error'
import Image from 'next/image'
import xss from 'xss'
import { ProjectDonationStats, ProjectItem } from '../../../utils/types'
import { getProjectBySlug } from '../../../utils/md'
import markdownToHtml from '../../../utils/markdownToHtml'
import PageHeading from '../../../components/PageHeading'
import Progress from '../../../components/Progress'
import { prisma } from '../../../server/services'
import { Button } from '../../../components/ui/button'
import { Dialog, DialogContent } from '../../../components/ui/dialog'
import DonationFormModal from '../../../components/DonationFormModal'
import MembershipFormModal from '../../../components/MembershipFormModal'
import LoginFormModal from '../../../components/LoginFormModal'
import RegisterFormModal from '../../../components/RegisterFormModal'
import PasswordResetFormModal from '../../../components/PasswordResetFormModal'
import CustomLink from '../../../components/CustomLink'
import { trpc } from '../../../utils/trpc'
import { getFundSlugFromUrlPath } from '../../../utils/funds'
type SingleProjectPageProps = {
project: ProjectItem
projects: ProjectItem[]
donationStats: ProjectDonationStats
}
const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) => {
const router = useRouter()
const [donateModalOpen, setDonateModalOpen] = useState(false)
const [memberModalOpen, setMemberModalOpen] = useState(false)
const [registerIsOpen, setRegisterIsOpen] = useState(false)
const [loginIsOpen, setLoginIsOpen] = useState(false)
const [passwordResetIsOpen, setPasswordResetIsOpen] = useState(false)
const session = useSession()
const userHasMembershipQuery = trpc.donation.userHasMembership.useQuery(
{ projectSlug: project.slug },
{ enabled: false }
)
const {
slug,
title,
summary,
coverImage,
git,
twitter,
content,
nym,
website,
personalTwitter,
personalWebsite,
goal,
isFunded,
} = project
function formatBtc(bitcoin: number) {
if (bitcoin > 0.1) {
return `${bitcoin.toFixed(3) || 0.0} BTC`
} else {
return `${Math.floor(bitcoin * 100000000).toLocaleString()} sats`
}
}
function formatUsd(dollars: number): string {
if (dollars == 0) {
return '$0'
} else if (dollars / 1000 > 1) {
return `$${Math.round(dollars / 1000)}k+`
} else {
return `$${dollars.toFixed(0)}`
}
}
useEffect(() => {
if (session.status === 'authenticated') {
console.log('refetching')
userHasMembershipQuery.refetch()
}
}, [session.status])
if (!router.isFallback && !slug) {
return <ErrorPage statusCode={404} />
}
return (
<>
<Head>
<title>Monero Fund - {project.title}</title>
</Head>
<div className="divide-y divide-gray-200">
<PageHeading project={project}>
<div className="flex flex-col items-center space-x-2 pt-8 xl:block">
<Image
src={coverImage}
alt="avatar"
width={300}
height={300}
className="h-72 w-72 mx-auto object-contain xl:hidden"
/>
<div className="space-y-4">
{!project.isFunded && (
<div className="flex flex-col space-y-2">
<Button onClick={() => setDonateModalOpen(true)}>Donate</Button>
{!userHasMembershipQuery.data && (
<Button
onClick={() =>
session.status === 'authenticated'
? setMemberModalOpen(true)
: setRegisterIsOpen(true)
}
variant="outline"
>
Get Annual Membership
</Button>
)}
{!!userHasMembershipQuery.data && (
<CustomLink href="/account/my-memberships">
<Button variant="outline">My Memberships</Button>{' '}
</CustomLink>
)}
</div>
)}
<h1 className="mb-4 font-bold">Raised</h1>
<Progress
current={
donationStats.xmr.fiatAmount +
donationStats.btc.fiatAmount +
donationStats.usd.fiatAmount
}
goal={goal}
/>
<ul className="font-semibold space-y-1">
<li className="flex items-center space-x-1">
<span className="text-green-500 text-xl">{`${formatUsd(donationStats.xmr.fiatAmount + donationStats.btc.fiatAmount + donationStats.usd.fiatAmount)}`}</span>{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count + donationStats.btc.count + donationStats.usd.count}{' '}
donations total
</span>
</li>
<li>
{donationStats.xmr.amount} XMR{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count} donations
</span>
</li>
<li>
{formatBtc(donationStats.btc.amount)}{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.btc.count} donations
</span>
</li>
<li>
{`${formatUsd(donationStats.usd.amount)}`} Fiat{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.usd.count} donations
</span>
</li>
</ul>
</div>
</div>
<article
className="prose max-w-none pb-8 pt-8 xl:col-span-2"
dangerouslySetInnerHTML={{ __html: xss(content || '') }}
/>
</PageHeading>
</div>
<Dialog open={donateModalOpen} onOpenChange={setDonateModalOpen}>
<DialogContent>
<DonationFormModal project={project} />
</DialogContent>
</Dialog>
<Dialog open={memberModalOpen} onOpenChange={setMemberModalOpen}>
<DialogContent>
<MembershipFormModal project={project} />
</DialogContent>
</Dialog>
{session.status !== 'authenticated' && (
<>
<Dialog open={loginIsOpen} onOpenChange={setLoginIsOpen}>
<DialogContent>
<LoginFormModal
close={() => setLoginIsOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
openPasswordResetModal={() => setPasswordResetIsOpen(true)}
/>
</DialogContent>
</Dialog>
<Dialog open={registerIsOpen} onOpenChange={setRegisterIsOpen}>
<DialogContent>
<RegisterFormModal
openLoginModal={() => setLoginIsOpen(true)}
close={() => setRegisterIsOpen(false)}
/>
</DialogContent>
</Dialog>
<Dialog open={passwordResetIsOpen} onOpenChange={setPasswordResetIsOpen}>
<DialogContent>
<PasswordResetFormModal close={() => setPasswordResetIsOpen(false)} />
</DialogContent>
</Dialog>
</>
)}
</>
)
}
export default Project
export async function getServerSideProps({ params, resolvedUrl }: GetServerSidePropsContext) {
const fundSlug = getFundSlugFromUrlPath(resolvedUrl)
if (!params?.slug) return {}
if (!fundSlug) return {}
const project = getProjectBySlug(params.slug as string, fundSlug)
const content = await markdownToHtml(project.content || '')
const donationStats = {
xmr: {
count: project.isFunded ? project.numdonationsxmr : 0,
amount: project.isFunded ? project.totaldonationsxmr : 0,
fiatAmount: project.isFunded ? project.totaldonationsinfiatxmr : 0,
},
btc: {
count: project.isFunded ? project.numdonationsbtc : 0,
amount: project.isFunded ? project.totaldonationsbtc : 0,
fiatAmount: project.isFunded ? project.totaldonationsinfiatbtc : 0,
},
usd: {
count: project.isFunded ? project.fiatnumdonations : 0,
amount: project.isFunded ? project.fiattotaldonations : 0,
fiatAmount: project.isFunded ? project.fiattotaldonationsinfiat : 0,
},
}
if (!project.isFunded) {
const donations = await prisma.donation.findMany({
where: { projectSlug: params.slug as string, fundSlug },
})
donations.forEach((donation) => {
if (donation.cryptoCode === 'XMR') {
donationStats.xmr.count += 1
donationStats.xmr.amount += donation.cryptoAmount || 0
donationStats.xmr.fiatAmount += donation.fiatAmount
}
if (donation.cryptoCode === 'BTC') {
donationStats.btc.count += 1
donationStats.btc.amount += donation.cryptoAmount || 0
donationStats.btc.fiatAmount += donation.fiatAmount
}
if (donation.cryptoCode === null) {
donationStats.usd.count += 1
donationStats.usd.amount += donation.fiatAmount
donationStats.usd.fiatAmount += donation.fiatAmount
console.log(donation)
}
})
}
return {
props: {
project: {
...project,
content,
},
donationStats,
},
}
}

View File

@@ -1,65 +0,0 @@
import { useEffect, useState } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import ProjectCard from '../../../components/ProjectCard'
import { ProjectItem } from '../../../utils/types'
import { getProjects } from '../../../utils/md'
import { useFundSlug } from '../../../utils/use-fund-slug'
const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
const [modalOpen, setModalOpen] = useState(false)
const [selectedProject, setSelectedProject] = useState<ProjectItem>()
const [sortedProjects, setSortedProjects] = useState<ProjectItem[]>()
const fundSlug = useFundSlug()
useEffect(() => {
setSortedProjects(projects.sort(() => 0.5 - Math.random()))
}, [projects])
function closeModal() {
setModalOpen(false)
}
function openPaymentModal(project: ProjectItem) {
setSelectedProject(project)
setModalOpen(true)
}
if (!fundSlug) return <></>
return (
<>
<Head>
<title>MAGIC Monero Fund | Projects</title>
</Head>
<section className="p-4 md:p-8 flex flex-col items-center">
<div className="flex justify-between items-center pb-8 w-full">
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Projects
</h1>
</div>
<ul className="grid md:grid-cols-3 gap-4 max-w-5xl">
{sortedProjects &&
sortedProjects.map((p, i) => (
<li key={i} className="">
<ProjectCard project={p} />
</li>
))}
</ul>
</section>
</>
)
}
export default AllProjects
export async function getStaticProps({ params }: { params: any }) {
const projects = await getProjects('monero')
return {
props: {
projects,
},
revalidate: 120,
}
}

View File

@@ -1,3 +1,4 @@
import Link from 'next/link'
import { funds } from '../utils/funds'
import { Button } from '../components/ui/button'
import SocialIcon from '../components/social-icons'
@@ -9,9 +10,8 @@ import ProjectList from '../components/ProjectList'
import { getProjects } from '../utils/md'
import { cn } from '../utils/cn'
import { ProjectItem } from '../utils/types'
import Link from 'next/link'
function Root({ projects }: { projects: ProjectItem[] }) {
function Home({ projects }: { projects: ProjectItem[] }) {
return (
<div className="flex flex-col items-start space-y-10">
<div className="w-full space-y-4">
@@ -19,34 +19,36 @@ function Root({ projects }: { projects: ProjectItem[] }) {
Funds
</h1>
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{Object.values(funds).map((fund, fundIndex) => (
<div
key={fund.slug}
className="w-full min-h-72 p-6 space-y-4 flex flex-col rounded-lg bg-white"
className="w-full min-h-72 p-6 space-y-4 flex flex-col justify-between rounded-lg bg-white"
>
<div className="flex items-center gap-3">
{fund.slug === 'monero' && <MoneroLogo className="h-10 w-10" />}
{fund.slug === 'firo' && <FiroLogo className="w-10 h-10" />}
{fund.slug === 'privacyguides' && <PrivacyGuidesLogo className="w-10 h-10" />}
{fund.slug === 'general' && <MagicLogo className="w-10 h-10" />}
<div className="w-full space-y-4">
<div className="flex items-center space-x-3">
{fund.slug === 'monero' && <MoneroLogo className="h-10 w-10" />}
{fund.slug === 'firo' && <FiroLogo className="w-10 h-10" />}
{fund.slug === 'privacyguides' && <PrivacyGuidesLogo className="w-10 h-10" />}
{fund.slug === 'general' && <MagicLogo className="w-10 h-10" />}
<h1 className="text-2xl font-bold leading-9 tracking-tight text-gray-900">
{fund.title}
</h1>
</div>
<h1 className="text-2xl font-bold leading-9 tracking-tight text-gray-900">
{fund.title}
</h1>
</div>
<span className="w-full text-muted-foreground">{fund.summary}</span>
<span className="w-full text-muted-foreground block">{fund.summary}</span>
<div className="flex flex-row space-x-2">
{!!fund.website && <SocialIcon kind="website" href={fund.website} />}
{!!fund.git && <SocialIcon kind="github" href={fund.git} />}
{!!fund.twitter && <SocialIcon kind="twitter" href={fund.twitter} />}
<div className="flex flex-row space-x-2">
{!!fund.website && <SocialIcon kind="website" href={fund.website} />}
{!!fund.git && <SocialIcon kind="github" href={fund.git} />}
{!!fund.twitter && <SocialIcon kind="twitter" href={fund.twitter} />}
</div>
</div>
<Button
className={cn(
'self-end',
'hidden self-end sm:block',
fund.slug === 'monero' && 'text-monero bg-monero/10 hover:bg-monero',
fund.slug === 'firo' && 'text-firo bg-firo/10 hover:bg-firo',
fund.slug === 'privacyguides' &&
@@ -55,9 +57,26 @@ function Root({ projects }: { projects: ProjectItem[] }) {
)}
size="lg"
variant="light"
style={{ marginTop: 'auto' }}
>
<Link href={`/${fund.slug}`}>View Campaigns</Link>
<Link href={`/${fund.slug}`} target="_blank">
View Campaigns
</Link>
</Button>
<Button
className={cn(
'self-end sm:hidden',
fund.slug === 'monero' && 'text-monero bg-monero/10 hover:bg-monero',
fund.slug === 'firo' && 'text-firo bg-firo/10 hover:bg-firo',
fund.slug === 'privacyguides' &&
'text-privacyguides bg-privacyguides/10 hover:bg-privacyguides',
fund.slug === 'general' && 'text-general bg-general/10 hover:bg-general'
)}
variant="light"
>
<Link href={`/${fund.slug}`} target="_blank">
View Campaigns
</Link>
</Button>
</div>
))}
@@ -73,7 +92,7 @@ function Root({ projects }: { projects: ProjectItem[] }) {
)
}
export default Root
export default Home
export async function getStaticProps({ params }: { params: any }) {
const projects = await getProjects()

View File

@@ -53,13 +53,17 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Support <Typing />
</h1>
<p className="text-xl leading-7 text-gray-500">
<p className="max-w-3xl text-lg leading-7 text-gray-500">
Help us to provide sustainable funding for free and open-source contributors working on
freedom tech and projects that help Monero flourish.
</p>
<div className="flex flex-wrap py-4 space-x-4">
<Button onClick={() => setDonateModalOpen(true)} size="lg">
<div className="flex flex-col md:flex-row my-4 gap-2">
<Button
className="text-sm md:text-base"
onClick={() => setDonateModalOpen(true)}
size="lg"
>
Donate to Monero Comittee General Fund
</Button>

View File

@@ -1,292 +0,0 @@
import { useEffect, useState } from 'react'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/router'
import { GetServerSidePropsContext, NextPage } from 'next/types'
import Head from 'next/head'
import ErrorPage from 'next/error'
import Image from 'next/image'
import xss from 'xss'
import { ProjectDonationStats, ProjectItem } from '../../../utils/types'
import { getProjectBySlug } from '../../../utils/md'
import markdownToHtml from '../../../utils/markdownToHtml'
import PageHeading from '../../../components/PageHeading'
import Progress from '../../../components/Progress'
import { prisma } from '../../../server/services'
import { Button } from '../../../components/ui/button'
import { Dialog, DialogContent } from '../../../components/ui/dialog'
import DonationFormModal from '../../../components/DonationFormModal'
import MembershipFormModal from '../../../components/MembershipFormModal'
import LoginFormModal from '../../../components/LoginFormModal'
import RegisterFormModal from '../../../components/RegisterFormModal'
import PasswordResetFormModal from '../../../components/PasswordResetFormModal'
import CustomLink from '../../../components/CustomLink'
import { trpc } from '../../../utils/trpc'
import { getFundSlugFromUrlPath } from '../../../utils/funds'
type SingleProjectPageProps = {
project: ProjectItem
projects: ProjectItem[]
donationStats: ProjectDonationStats
}
const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) => {
const router = useRouter()
const [donateModalOpen, setDonateModalOpen] = useState(false)
const [memberModalOpen, setMemberModalOpen] = useState(false)
const [registerIsOpen, setRegisterIsOpen] = useState(false)
const [loginIsOpen, setLoginIsOpen] = useState(false)
const [passwordResetIsOpen, setPasswordResetIsOpen] = useState(false)
const session = useSession()
const userHasMembershipQuery = trpc.donation.userHasMembership.useQuery(
{ projectSlug: project.slug },
{ enabled: false }
)
const {
slug,
title,
summary,
coverImage,
git,
twitter,
content,
nym,
website,
personalTwitter,
personalWebsite,
goal,
isFunded,
} = project
function formatBtc(bitcoin: number) {
if (bitcoin > 0.1) {
return `${bitcoin.toFixed(3) || 0.0} BTC`
} else {
return `${Math.floor(bitcoin * 100000000).toLocaleString()} sats`
}
}
function formatUsd(dollars: number): string {
if (dollars == 0) {
return '$0'
} else if (dollars / 1000 > 1) {
return `$${Math.round(dollars / 1000)}k+`
} else {
return `$${dollars.toFixed(0)}`
}
}
useEffect(() => {
if (session.status === 'authenticated') {
console.log('refetching')
userHasMembershipQuery.refetch()
}
}, [session.status])
if (!router.isFallback && !slug) {
return <ErrorPage statusCode={404} />
}
return (
<>
<Head>
<title>Monero Fund - {project.title}</title>
</Head>
<div className="divide-y divide-gray-200">
<PageHeading project={project}>
<div className="flex flex-col items-center space-x-2 pt-8 xl:block">
<Image
src={coverImage}
alt="avatar"
width={300}
height={300}
className="h-72 w-72 mx-auto object-contain xl:hidden"
/>
<div className="space-y-4">
{!project.isFunded && (
<div className="flex flex-col space-y-2">
<Button onClick={() => setDonateModalOpen(true)}>Donate</Button>
{!userHasMembershipQuery.data && (
<Button
onClick={() =>
session.status === 'authenticated'
? setMemberModalOpen(true)
: setRegisterIsOpen(true)
}
variant="outline"
>
Get Annual Membership
</Button>
)}
{!!userHasMembershipQuery.data && (
<CustomLink href="/account/my-memberships">
<Button variant="outline">My Memberships</Button>{' '}
</CustomLink>
)}
</div>
)}
<h1 className="mb-4 font-bold">Raised</h1>
<Progress
current={
donationStats.xmr.fiatAmount +
donationStats.btc.fiatAmount +
donationStats.usd.fiatAmount
}
goal={goal}
/>
<ul className="font-semibold space-y-1">
<li className="flex items-center space-x-1">
<span className="text-green-500 text-xl">{`${formatUsd(donationStats.xmr.fiatAmount + donationStats.btc.fiatAmount + donationStats.usd.fiatAmount)}`}</span>{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count + donationStats.btc.count + donationStats.usd.count}{' '}
donations total
</span>
</li>
<li>
{donationStats.xmr.amount} XMR{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count} donations
</span>
</li>
<li>
{formatBtc(donationStats.btc.amount)}{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.btc.count} donations
</span>
</li>
<li>
{`${formatUsd(donationStats.usd.amount)}`} Fiat{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.usd.count} donations
</span>
</li>
</ul>
</div>
</div>
<article
className="prose max-w-none pb-8 pt-8 xl:col-span-2"
dangerouslySetInnerHTML={{ __html: xss(content || '') }}
/>
</PageHeading>
</div>
<Dialog open={donateModalOpen} onOpenChange={setDonateModalOpen}>
<DialogContent>
<DonationFormModal project={project} />
</DialogContent>
</Dialog>
<Dialog open={memberModalOpen} onOpenChange={setMemberModalOpen}>
<DialogContent>
<MembershipFormModal project={project} />
</DialogContent>
</Dialog>
{session.status !== 'authenticated' && (
<>
<Dialog open={loginIsOpen} onOpenChange={setLoginIsOpen}>
<DialogContent>
<LoginFormModal
close={() => setLoginIsOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
openPasswordResetModal={() => setPasswordResetIsOpen(true)}
/>
</DialogContent>
</Dialog>
<Dialog open={registerIsOpen} onOpenChange={setRegisterIsOpen}>
<DialogContent>
<RegisterFormModal
openLoginModal={() => setLoginIsOpen(true)}
close={() => setRegisterIsOpen(false)}
/>
</DialogContent>
</Dialog>
<Dialog open={passwordResetIsOpen} onOpenChange={setPasswordResetIsOpen}>
<DialogContent>
<PasswordResetFormModal close={() => setPasswordResetIsOpen(false)} />
</DialogContent>
</Dialog>
</>
)}
</>
)
}
export default Project
export async function getServerSideProps({ params, resolvedUrl }: GetServerSidePropsContext) {
const fundSlug = getFundSlugFromUrlPath(resolvedUrl)
if (!params?.slug) return {}
if (!fundSlug) return {}
const project = getProjectBySlug(params.slug as string, fundSlug)
const content = await markdownToHtml(project.content || '')
const donationStats = {
xmr: {
count: project.isFunded ? project.numdonationsxmr : 0,
amount: project.isFunded ? project.totaldonationsxmr : 0,
fiatAmount: project.isFunded ? project.totaldonationsinfiatxmr : 0,
},
btc: {
count: project.isFunded ? project.numdonationsbtc : 0,
amount: project.isFunded ? project.totaldonationsbtc : 0,
fiatAmount: project.isFunded ? project.totaldonationsinfiatbtc : 0,
},
usd: {
count: project.isFunded ? project.fiatnumdonations : 0,
amount: project.isFunded ? project.fiattotaldonations : 0,
fiatAmount: project.isFunded ? project.fiattotaldonationsinfiat : 0,
},
}
if (!project.isFunded) {
const donations = await prisma.donation.findMany({
where: { projectSlug: params.slug as string, fundSlug },
})
donations.forEach((donation) => {
if (donation.cryptoCode === 'XMR') {
donationStats.xmr.count += 1
donationStats.xmr.amount += donation.cryptoAmount || 0
donationStats.xmr.fiatAmount += donation.fiatAmount
}
if (donation.cryptoCode === 'BTC') {
donationStats.btc.count += 1
donationStats.btc.amount += donation.cryptoAmount || 0
donationStats.btc.fiatAmount += donation.fiatAmount
}
if (donation.cryptoCode === null) {
donationStats.usd.count += 1
donationStats.usd.amount += donation.fiatAmount
donationStats.usd.fiatAmount += donation.fiatAmount
console.log(donation)
}
})
}
return {
props: {
project: {
...project,
content,
},
donationStats,
},
}
}

View File

@@ -1,65 +0,0 @@
import { useEffect, useState } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import ProjectCard from '../../../components/ProjectCard'
import { ProjectItem } from '../../../utils/types'
import { getProjects } from '../../../utils/md'
import { useFundSlug } from '../../../utils/use-fund-slug'
const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
const [modalOpen, setModalOpen] = useState(false)
const [selectedProject, setSelectedProject] = useState<ProjectItem>()
const [sortedProjects, setSortedProjects] = useState<ProjectItem[]>()
const fundSlug = useFundSlug()
useEffect(() => {
setSortedProjects(projects.sort(() => 0.5 - Math.random()))
}, [projects])
function closeModal() {
setModalOpen(false)
}
function openPaymentModal(project: ProjectItem) {
setSelectedProject(project)
setModalOpen(true)
}
if (!fundSlug) return <></>
return (
<>
<Head>
<title>MAGIC Monero Fund | Projects</title>
</Head>
<section className="p-4 md:p-8 flex flex-col items-center">
<div className="flex justify-between items-center pb-8 w-full">
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Projects
</h1>
</div>
<ul className="grid md:grid-cols-3 gap-4 max-w-5xl">
{sortedProjects &&
sortedProjects.map((p, i) => (
<li key={i} className="">
<ProjectCard project={p} />
</li>
))}
</ul>
</section>
</>
)
}
export default AllProjects
export async function getStaticProps({ params }: { params: any }) {
const projects = await getProjects('monero')
return {
props: {
projects,
},
revalidate: 120,
}
}

View File

@@ -53,13 +53,17 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Support <Typing />
</h1>
<p className="text-xl leading-7 text-gray-500">
<p className="max-w-3xl text-xl leading-7 text-gray-500">
Help us to provide sustainable funding for free and open-source contributors working on
freedom tech and projects that help Monero flourish.
</p>
<div className="flex flex-wrap py-4 space-x-4">
<Button onClick={() => setDonateModalOpen(true)} size="lg">
<div className="flex flex-col md:flex-row my-4 gap-2">
<Button
className="text-sm md:text-base"
onClick={() => setDonateModalOpen(true)}
size="lg"
>
Donate to Monero Comittee General Fund
</Button>

View File

@@ -1,292 +0,0 @@
import { useEffect, useState } from 'react'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/router'
import { GetServerSidePropsContext, NextPage } from 'next/types'
import Head from 'next/head'
import ErrorPage from 'next/error'
import Image from 'next/image'
import xss from 'xss'
import { ProjectDonationStats, ProjectItem } from '../../../utils/types'
import { getProjectBySlug } from '../../../utils/md'
import markdownToHtml from '../../../utils/markdownToHtml'
import PageHeading from '../../../components/PageHeading'
import Progress from '../../../components/Progress'
import { prisma } from '../../../server/services'
import { Button } from '../../../components/ui/button'
import { Dialog, DialogContent } from '../../../components/ui/dialog'
import DonationFormModal from '../../../components/DonationFormModal'
import MembershipFormModal from '../../../components/MembershipFormModal'
import LoginFormModal from '../../../components/LoginFormModal'
import RegisterFormModal from '../../../components/RegisterFormModal'
import PasswordResetFormModal from '../../../components/PasswordResetFormModal'
import CustomLink from '../../../components/CustomLink'
import { trpc } from '../../../utils/trpc'
import { getFundSlugFromUrlPath } from '../../../utils/funds'
type SingleProjectPageProps = {
project: ProjectItem
projects: ProjectItem[]
donationStats: ProjectDonationStats
}
const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) => {
const router = useRouter()
const [donateModalOpen, setDonateModalOpen] = useState(false)
const [memberModalOpen, setMemberModalOpen] = useState(false)
const [registerIsOpen, setRegisterIsOpen] = useState(false)
const [loginIsOpen, setLoginIsOpen] = useState(false)
const [passwordResetIsOpen, setPasswordResetIsOpen] = useState(false)
const session = useSession()
const userHasMembershipQuery = trpc.donation.userHasMembership.useQuery(
{ projectSlug: project.slug },
{ enabled: false }
)
const {
slug,
title,
summary,
coverImage,
git,
twitter,
content,
nym,
website,
personalTwitter,
personalWebsite,
goal,
isFunded,
} = project
function formatBtc(bitcoin: number) {
if (bitcoin > 0.1) {
return `${bitcoin.toFixed(3) || 0.0} BTC`
} else {
return `${Math.floor(bitcoin * 100000000).toLocaleString()} sats`
}
}
function formatUsd(dollars: number): string {
if (dollars == 0) {
return '$0'
} else if (dollars / 1000 > 1) {
return `$${Math.round(dollars / 1000)}k+`
} else {
return `$${dollars.toFixed(0)}`
}
}
useEffect(() => {
if (session.status === 'authenticated') {
console.log('refetching')
userHasMembershipQuery.refetch()
}
}, [session.status])
if (!router.isFallback && !slug) {
return <ErrorPage statusCode={404} />
}
return (
<>
<Head>
<title>Monero Fund - {project.title}</title>
</Head>
<div className="divide-y divide-gray-200">
<PageHeading project={project}>
<div className="flex flex-col items-center space-x-2 pt-8 xl:block">
<Image
src={coverImage}
alt="avatar"
width={300}
height={300}
className="h-72 w-72 mx-auto object-contain xl:hidden"
/>
<div className="space-y-4">
{!project.isFunded && (
<div className="flex flex-col space-y-2">
<Button onClick={() => setDonateModalOpen(true)}>Donate</Button>
{!userHasMembershipQuery.data && (
<Button
onClick={() =>
session.status === 'authenticated'
? setMemberModalOpen(true)
: setRegisterIsOpen(true)
}
variant="outline"
>
Get Annual Membership
</Button>
)}
{!!userHasMembershipQuery.data && (
<CustomLink href="/account/my-memberships">
<Button variant="outline">My Memberships</Button>{' '}
</CustomLink>
)}
</div>
)}
<h1 className="mb-4 font-bold">Raised</h1>
<Progress
current={
donationStats.xmr.fiatAmount +
donationStats.btc.fiatAmount +
donationStats.usd.fiatAmount
}
goal={goal}
/>
<ul className="font-semibold space-y-1">
<li className="flex items-center space-x-1">
<span className="text-green-500 text-xl">{`${formatUsd(donationStats.xmr.fiatAmount + donationStats.btc.fiatAmount + donationStats.usd.fiatAmount)}`}</span>{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count + donationStats.btc.count + donationStats.usd.count}{' '}
donations total
</span>
</li>
<li>
{donationStats.xmr.amount} XMR{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count} donations
</span>
</li>
<li>
{formatBtc(donationStats.btc.amount)}{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.btc.count} donations
</span>
</li>
<li>
{`${formatUsd(donationStats.usd.amount)}`} Fiat{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.usd.count} donations
</span>
</li>
</ul>
</div>
</div>
<article
className="prose max-w-none pb-8 pt-8 xl:col-span-2"
dangerouslySetInnerHTML={{ __html: xss(content || '') }}
/>
</PageHeading>
</div>
<Dialog open={donateModalOpen} onOpenChange={setDonateModalOpen}>
<DialogContent>
<DonationFormModal project={project} />
</DialogContent>
</Dialog>
<Dialog open={memberModalOpen} onOpenChange={setMemberModalOpen}>
<DialogContent>
<MembershipFormModal project={project} />
</DialogContent>
</Dialog>
{session.status !== 'authenticated' && (
<>
<Dialog open={loginIsOpen} onOpenChange={setLoginIsOpen}>
<DialogContent>
<LoginFormModal
close={() => setLoginIsOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
openPasswordResetModal={() => setPasswordResetIsOpen(true)}
/>
</DialogContent>
</Dialog>
<Dialog open={registerIsOpen} onOpenChange={setRegisterIsOpen}>
<DialogContent>
<RegisterFormModal
openLoginModal={() => setLoginIsOpen(true)}
close={() => setRegisterIsOpen(false)}
/>
</DialogContent>
</Dialog>
<Dialog open={passwordResetIsOpen} onOpenChange={setPasswordResetIsOpen}>
<DialogContent>
<PasswordResetFormModal close={() => setPasswordResetIsOpen(false)} />
</DialogContent>
</Dialog>
</>
)}
</>
)
}
export default Project
export async function getServerSideProps({ params, resolvedUrl }: GetServerSidePropsContext) {
const fundSlug = getFundSlugFromUrlPath(resolvedUrl)
if (!params?.slug) return {}
if (!fundSlug) return {}
const project = getProjectBySlug(params.slug as string, fundSlug)
const content = await markdownToHtml(project.content || '')
const donationStats = {
xmr: {
count: project.isFunded ? project.numdonationsxmr : 0,
amount: project.isFunded ? project.totaldonationsxmr : 0,
fiatAmount: project.isFunded ? project.totaldonationsinfiatxmr : 0,
},
btc: {
count: project.isFunded ? project.numdonationsbtc : 0,
amount: project.isFunded ? project.totaldonationsbtc : 0,
fiatAmount: project.isFunded ? project.totaldonationsinfiatbtc : 0,
},
usd: {
count: project.isFunded ? project.fiatnumdonations : 0,
amount: project.isFunded ? project.fiattotaldonations : 0,
fiatAmount: project.isFunded ? project.fiattotaldonationsinfiat : 0,
},
}
if (!project.isFunded) {
const donations = await prisma.donation.findMany({
where: { projectSlug: params.slug as string, fundSlug },
})
donations.forEach((donation) => {
if (donation.cryptoCode === 'XMR') {
donationStats.xmr.count += 1
donationStats.xmr.amount += donation.cryptoAmount || 0
donationStats.xmr.fiatAmount += donation.fiatAmount
}
if (donation.cryptoCode === 'BTC') {
donationStats.btc.count += 1
donationStats.btc.amount += donation.cryptoAmount || 0
donationStats.btc.fiatAmount += donation.fiatAmount
}
if (donation.cryptoCode === null) {
donationStats.usd.count += 1
donationStats.usd.amount += donation.fiatAmount
donationStats.usd.fiatAmount += donation.fiatAmount
console.log(donation)
}
})
}
return {
props: {
project: {
...project,
content,
},
donationStats,
},
}
}

View File

@@ -1,65 +0,0 @@
import { useEffect, useState } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import ProjectCard from '../../../components/ProjectCard'
import { ProjectItem } from '../../../utils/types'
import { getProjects } from '../../../utils/md'
import { useFundSlug } from '../../../utils/use-fund-slug'
const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
const [modalOpen, setModalOpen] = useState(false)
const [selectedProject, setSelectedProject] = useState<ProjectItem>()
const [sortedProjects, setSortedProjects] = useState<ProjectItem[]>()
const fundSlug = useFundSlug()
useEffect(() => {
setSortedProjects(projects.sort(() => 0.5 - Math.random()))
}, [projects])
function closeModal() {
setModalOpen(false)
}
function openPaymentModal(project: ProjectItem) {
setSelectedProject(project)
setModalOpen(true)
}
if (!fundSlug) return <></>
return (
<>
<Head>
<title>MAGIC Monero Fund | Projects</title>
</Head>
<section className="p-4 md:p-8 flex flex-col items-center">
<div className="flex justify-between items-center pb-8 w-full">
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Projects
</h1>
</div>
<ul className="grid md:grid-cols-3 gap-4 max-w-5xl">
{sortedProjects &&
sortedProjects.map((p, i) => (
<li key={i} className="">
<ProjectCard project={p} />
</li>
))}
</ul>
</section>
</>
)
}
export default AllProjects
export async function getStaticProps({ params }: { params: any }) {
const projects = await getProjects('monero')
return {
props: {
projects,
},
revalidate: 120,
}
}