mirror of
https://github.com/MAGICGrants/campaign-site.git
synced 2026-01-09 12:27:59 -05:00
feat: add missing titles and responsiveness improvements
This commit is contained in:
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user