mirror of
https://github.com/MAGICGrants/campaign-site.git
synced 2026-01-09 12:27:59 -05:00
feat: add home page
This commit is contained in:
35
components/FiroLogo.tsx
Normal file
35
components/FiroLogo.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { SVGProps } from 'react'
|
||||
|
||||
function FiroLogo(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 520 520"
|
||||
xmlSpace="preserve"
|
||||
{...props}
|
||||
>
|
||||
<style type="text/css">{'\n\t.st0{fill:#FEFEFE;}\n\t.st1{fill:#9B1C2E;}\n'}</style>
|
||||
<g>
|
||||
<g>
|
||||
<circle className="st0" cx={260} cy={260} r={252.7} />
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
className="st1"
|
||||
d="M155.6,370.7c5.9,0,11.2-3.2,14-8.4l37.3-70.6h-57.5c-8.7,0-15.8-7.1-15.8-15.8v-31.6 c0-8.7,7.1-15.8,15.8-15.8h90.9l70.6-133.9c2.7-5.2,8.1-8.4,14-8.4h118.8C397.5,37.4,332.3,7,260,7C120.3,7,7,120.3,7,260 c0,39.7,9.2,77.3,25.5,110.7H155.6z"
|
||||
/>
|
||||
<path
|
||||
className="st1"
|
||||
d="M364.4,149.3c-5.9,0-11.2,3.2-14,8.4l-37.3,70.6h57.5c8.7,0,15.8,7.1,15.8,15.8v31.6 c0,8.7-7.1,15.8-15.8,15.8h-90.9l-70.6,133.9c-2.7,5.2-8.1,8.4-14,8.4H76.4C122.5,482.6,187.7,513,260,513 c139.7,0,253-113.3,253-253c0-39.7-9.2-77.3-25.5-110.7H364.4z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default FiroLogo
|
||||
@@ -6,7 +6,7 @@ import Link from './CustomLink'
|
||||
import MobileNav from './MobileNav'
|
||||
import ThemeSwitch from './ThemeSwitch'
|
||||
import headerNavLinks from '../data/headerNavLinks'
|
||||
import Logo from './Logo'
|
||||
import MagicLogo from './MagicLogo'
|
||||
import { Dialog, DialogContent, DialogTrigger } from './ui/dialog'
|
||||
import { Button } from './ui/button'
|
||||
import RegisterFormModal from './RegisterFormModal'
|
||||
@@ -39,98 +39,106 @@ const Header = () => {
|
||||
}
|
||||
}, [router.query.loginEmail])
|
||||
|
||||
if (!fundSlug) return <></>
|
||||
|
||||
const fund = funds[fundSlug]
|
||||
const fund = fundSlug ? funds[fundSlug] : null
|
||||
|
||||
return (
|
||||
<header className="flex items-center justify-between py-10">
|
||||
<div>
|
||||
<Link
|
||||
href={`/${fundSlug}`}
|
||||
aria-label={fund.title}
|
||||
href={fundSlug ? `/${fundSlug}` : '/'}
|
||||
aria-label="Home"
|
||||
className="flex items-center mr-3 gap-4"
|
||||
>
|
||||
<Logo className="w-12 h-12" />
|
||||
<span className="text-foreground text-lg font-bold">{fund.title}</span>
|
||||
<MagicLogo className="w-12 h-12" />
|
||||
{
|
||||
<span className="text-foreground text-lg font-bold">
|
||||
{fund ? fund.title : 'MAGIC Grants'}
|
||||
</span>
|
||||
}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center text-base leading-5">
|
||||
{headerNavLinks.map((link) => (
|
||||
<CustomLink
|
||||
key={link.title}
|
||||
href={`/${fundSlug}/${link.href}`}
|
||||
className={
|
||||
link.isButton
|
||||
? 'rounded border border-primary bg-transparent px-4 py-2 font-semibold text-primary hover:border-transparent hover:bg-primary hover:text-white'
|
||||
: 'hidden p-1 font-medium text-gray-900 sm:p-4 md:inline-block'
|
||||
}
|
||||
>
|
||||
{link.title}
|
||||
</CustomLink>
|
||||
))}
|
||||
|
||||
{session.status !== 'authenticated' && (
|
||||
<>
|
||||
<Dialog open={loginIsOpen} onOpenChange={setLoginIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" className="w-24">
|
||||
Login
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<LoginFormModal
|
||||
close={() => setLoginIsOpen(false)}
|
||||
openRegisterModal={() => setRegisterIsOpen(true)}
|
||||
openPasswordResetModal={() => setPasswordResetIsOpen(true)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{!!fund && (
|
||||
<div className="flex gap-2 items-center text-base leading-5">
|
||||
{headerNavLinks.map((link) => (
|
||||
<CustomLink
|
||||
key={link.title}
|
||||
href={`/${fundSlug}/${link.href}`}
|
||||
className={
|
||||
link.isButton
|
||||
? 'rounded border border-primary bg-transparent px-4 py-2 font-semibold text-primary hover:border-transparent hover:bg-primary hover:text-white'
|
||||
: 'hidden p-1 font-medium text-gray-900 sm:p-4 md:inline-block'
|
||||
}
|
||||
>
|
||||
{link.title}
|
||||
</CustomLink>
|
||||
))}
|
||||
|
||||
<Dialog open={registerIsOpen} onOpenChange={setRegisterIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="w-24">Register</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<RegisterFormModal
|
||||
close={() => setRegisterIsOpen(false)}
|
||||
openLoginModal={() => setLoginIsOpen(true)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)}
|
||||
{session.status !== 'authenticated' && (
|
||||
<>
|
||||
<Dialog open={loginIsOpen} onOpenChange={setLoginIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" className="w-24">
|
||||
Login
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<LoginFormModal
|
||||
close={() => setLoginIsOpen(false)}
|
||||
openRegisterModal={() => setRegisterIsOpen(true)}
|
||||
openPasswordResetModal={() => setPasswordResetIsOpen(true)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* <ThemeSwitch /> */}
|
||||
<Dialog open={registerIsOpen} onOpenChange={setRegisterIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="w-24">Register</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<RegisterFormModal
|
||||
close={() => setRegisterIsOpen(false)}
|
||||
openLoginModal={() => setLoginIsOpen(true)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)}
|
||||
|
||||
{session.status === 'authenticated' && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Avatar>
|
||||
<AvatarFallback>
|
||||
{session.data.user?.email?.slice(0, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<CustomLink href={`/${fundSlug}/account/my-donations`} className="text-foreground">
|
||||
<DropdownMenuItem>My Donations</DropdownMenuItem>
|
||||
</CustomLink>
|
||||
<CustomLink href={`/${fundSlug}/account/my-memberships`} className="text-foreground">
|
||||
<DropdownMenuItem>My Memberships</DropdownMenuItem>
|
||||
</CustomLink>
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => signOut({ callbackUrl: `/${fundSlug}` })}>
|
||||
Logout
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
{/* <ThemeSwitch /> */}
|
||||
|
||||
<MobileNav />
|
||||
</div>
|
||||
{session.status === 'authenticated' && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Avatar>
|
||||
<AvatarFallback>
|
||||
{session.data.user?.email?.slice(0, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<CustomLink href={`/${fundSlug}/account/my-donations`} className="text-foreground">
|
||||
<DropdownMenuItem>My Donations</DropdownMenuItem>
|
||||
</CustomLink>
|
||||
<CustomLink
|
||||
href={`/${fundSlug}/account/my-memberships`}
|
||||
className="text-foreground"
|
||||
>
|
||||
<DropdownMenuItem>My Memberships</DropdownMenuItem>
|
||||
</CustomLink>
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => signOut({ callbackUrl: `/${fundSlug}` })}>
|
||||
Logout
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
|
||||
<MobileNav />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Dialog open={passwordResetIsOpen} onOpenChange={setPasswordResetIsOpen}>
|
||||
<DialogContent>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SVGProps } from 'react'
|
||||
|
||||
function Logo(props: SVGProps<SVGSVGElement>) {
|
||||
function MagicLogo(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="1000"
|
||||
@@ -115,4 +115,4 @@ function Logo(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export default Logo
|
||||
export default MagicLogo
|
||||
58
components/MoneroLogo.tsx
Normal file
58
components/MoneroLogo.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { SVGProps } from 'react'
|
||||
|
||||
function MoneroLogo(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="85.086304mm"
|
||||
height="85.084808mm"
|
||||
viewBox="0 0 85.086304 85.084808"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<defs id="defs1">
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath2">
|
||||
<path
|
||||
d="M 0,841.889 H 595.275 V 0 H 0 Z"
|
||||
transform="translate(-193.3003,-554.52051)"
|
||||
id="path2"
|
||||
/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath4">
|
||||
<path
|
||||
d="M 0,841.889 H 595.275 V 0 H 0 Z"
|
||||
transform="translate(-187.7847,-507.50881)"
|
||||
id="path4"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="layer1">
|
||||
<path
|
||||
id="path1"
|
||||
d="m 0,0 c -20.377,0 -36.903,-16.524 -36.903,-36.902 0,-4.074 0.66,-7.992 1.88,-11.657 h 11.036 V -17.51 L 0,-41.497 23.987,-17.51 v -31.049 h 11.037 c 1.22,3.665 1.88,7.583 1.88,11.657 C 36.904,-16.524 20.378,0 0,0"
|
||||
style={{
|
||||
fill: '#ff6600',
|
||||
fillOpacity: 1,
|
||||
fillRule: 'nonzero',
|
||||
stroke: 'none',
|
||||
}}
|
||||
transform="matrix(1.1528216,0,0,-1.1528216,42.542576,0)"
|
||||
clipPath="url(#clipPath2)"
|
||||
/>
|
||||
<path
|
||||
id="path3"
|
||||
d="M 0,0 -10.468,10.469 V -9.068 h -4.002 -4.002 -7.546 c 6.478,-10.628 18.178,-17.726 31.533,-17.726 13.355,0 25.056,7.098 31.533,17.726 H 29.501 22.344 21.499 V 10.469 L 11.03,0 5.515,-5.515 Z"
|
||||
style={{
|
||||
fill: '#4c4c4c',
|
||||
fillOpacity: 1,
|
||||
fillRule: 'nonzero',
|
||||
stroke: 'none',
|
||||
}}
|
||||
transform="matrix(1.1528216,0,0,-1.1528216,36.184075,54.196107)"
|
||||
clipPath="url(#clipPath4)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
export default MoneroLogo
|
||||
36
components/PrivacyGuidesLogo.tsx
Normal file
36
components/PrivacyGuidesLogo.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { SVGProps } from 'react'
|
||||
|
||||
function PrivacyGuidesLogo(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 33 34"
|
||||
xmlSpace="preserve"
|
||||
style={{
|
||||
fillRule: 'evenodd',
|
||||
clipRule: 'evenodd',
|
||||
strokeLinejoin: 'round',
|
||||
strokeMiterlimit: 2,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M4.581,4.337c-0.113,0.379 -0.049,0.822 0.077,1.707l1.604,11.224c0.277,1.939 0.415,2.909 0.782,3.775c0.325,0.768 0.781,1.474 1.346,2.087c0.638,0.691 1.465,1.217 3.117,2.269l2.349,1.495c1.126,0.716 1.69,1.075 2.295,1.214c0.465,0.108 0.947,0.121 1.416,0.042c-0.388,-0.887 -0.603,-1.867 -0.603,-2.897c0,-3.996 3.24,-7.236 7.236,-7.236c1.166,0 2.268,0.276 3.243,0.766c0.069,-0.432 0.14,-0.929 0.223,-1.514l0,-0.001l1.604,-11.224c0.126,-0.885 0.19,-1.328 0.077,-1.707c-0.099,-0.334 -0.292,-0.632 -0.557,-0.859c-0.3,-0.257 -0.73,-0.38 -1.59,-0.626l-9.441,-2.697c-0.296,-0.085 -0.444,-0.127 -0.594,-0.144c-0.134,-0.015 -0.268,-0.015 -0.402,0c-0.15,0.017 -0.298,0.059 -0.594,0.144l-9.441,2.697c-0.86,0.246 -1.29,0.369 -1.59,0.626c-0.265,0.227 -0.458,0.525 -0.557,0.859Z"
|
||||
style={{
|
||||
fill: '#ffd06f',
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d="M13.246,2.719c0.066,-0.007 0.134,-0.007 0.201,0c0.057,0.007 0.122,0.022 0.446,0.114l9.44,2.698c0.444,0.126 0.727,0.208 0.94,0.287c0.202,0.075 0.274,0.124 0.311,0.156c0.132,0.113 0.229,0.262 0.278,0.429c0.014,0.047 0.03,0.133 0.016,0.348c-0.015,0.226 -0.056,0.518 -0.122,0.974l-1.346,9.426c-4.125,0.397 -7.351,3.873 -7.351,8.102c0,0.835 0.126,1.641 0.36,2.4l-0.451,0.286c-1.183,0.753 -1.594,1.001 -2.012,1.097c-0.401,0.092 -0.818,0.092 -1.22,0c-0.417,-0.096 -0.829,-0.344 -2.012,-1.097l-2.349,-1.494c-1.693,-1.078 -2.398,-1.535 -2.938,-2.12c-0.495,-0.536 -0.894,-1.153 -1.178,-1.825c-0.31,-0.733 -0.436,-1.564 -0.72,-3.551l-1.603,-11.224c-0.066,-0.456 -0.107,-0.748 -0.121,-0.974c-0.015,-0.215 0.001,-0.301 0.015,-0.348c0.05,-0.167 0.146,-0.316 0.279,-0.429c0.036,-0.032 0.109,-0.081 0.31,-0.156c0.213,-0.079 0.496,-0.161 0.94,-0.287l9.44,-2.698c0.324,-0.092 0.389,-0.107 0.447,-0.114Zm13.306,5.231l-1.318,9.228c4.007,0.508 7.106,3.93 7.106,8.075c0,4.496 -3.644,8.141 -8.14,8.141c-3.01,0 -5.639,-1.634 -7.048,-4.064l-0.212,0.136l-0.135,0.085c-0.996,0.634 -1.683,1.072 -2.443,1.248c-0.668,0.154 -1.364,0.154 -2.032,0c-0.76,-0.176 -1.447,-0.614 -2.443,-1.248l-0.134,-0.085l-2.466,-1.57l0,0c-1.541,-0.98 -2.461,-1.565 -3.179,-2.344c-0.637,-0.689 -1.149,-1.483 -1.515,-2.347c-0.413,-0.976 -0.567,-2.054 -0.825,-3.863l-1.628,-11.392c-0.059,-0.416 -0.111,-0.778 -0.131,-1.081c-0.021,-0.323 -0.012,-0.648 0.087,-0.98c0.148,-0.501 0.439,-0.949 0.835,-1.289c0.264,-0.226 0.557,-0.366 0.86,-0.478c0.285,-0.106 0.636,-0.206 1.04,-0.322l0.031,-0.009l9.44,-2.697l0.05,-0.014c0.247,-0.071 0.465,-0.133 0.693,-0.159c0.2,-0.022 0.402,-0.022 0.603,0c0.227,0.026 0.445,0.088 0.692,0.159l0.05,0.014l9.471,2.706c0.404,0.116 0.755,0.216 1.04,0.322c0.304,0.112 0.596,0.252 0.86,0.478c0.397,0.34 0.687,0.788 0.835,1.289c0.099,0.332 0.108,0.657 0.087,0.98c-0.02,0.303 -0.072,0.665 -0.131,1.08l0,0.001Zm-2.352,10.972c-3.497,0 -6.332,2.835 -6.332,6.331c0,3.497 2.835,6.332 6.332,6.332c3.497,0 6.331,-2.835 6.331,-6.332c0,-3.496 -2.834,-6.331 -6.331,-6.331Zm4.313,4.197c0.319,-0.384 0.268,-0.954 -0.116,-1.274c-0.384,-0.32 -0.954,-0.268 -1.274,0.116l-3.888,4.666l-2.013,-2.013c-0.354,-0.353 -0.926,-0.353 -1.28,0c-0.353,0.353 -0.353,0.926 0,1.279l2.714,2.713c0.18,0.18 0.427,0.276 0.68,0.264c0.254,-0.011 0.492,-0.129 0.654,-0.324l4.523,-5.427Zm-19.689,-10.529c0,-2.497 2.024,-4.522 4.522,-4.522c2.498,0 4.522,2.025 4.522,4.522c0,1.48 -0.71,2.794 -1.809,3.619l0,3.617c0,1.499 -1.214,2.714 -2.713,2.714c-1.499,0 -2.713,-1.215 -2.713,-2.714l0,-3.617c-1.099,-0.825 -1.809,-2.139 -1.809,-3.619Zm5.426,4.523l-1.808,0l0,2.713c0,0.5 0.405,0.905 0.904,0.905c0.5,0 0.904,-0.405 0.904,-0.905l0,-2.713Zm-0.904,-1.809c1.499,0 2.713,-1.215 2.713,-2.714c0,-1.498 -1.214,-2.713 -2.713,-2.713c-1.499,0 -2.713,1.215 -2.713,2.713c0,1.499 1.214,2.714 2.713,2.714Z"
|
||||
style={{
|
||||
fill: '#28323f',
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrivacyGuidesLogo
|
||||
@@ -1,18 +1,22 @@
|
||||
type ProgressProps = {
|
||||
percent: number
|
||||
}
|
||||
type ProgressProps = { current: number; goal: number }
|
||||
|
||||
const Progress = ({ current, goal }: ProgressProps) => {
|
||||
const percent = Math.floor((current / goal) * 100)
|
||||
|
||||
const Progress = ({ percent }: ProgressProps) => {
|
||||
return (
|
||||
<div className="w-full flex flex-col items-center space-y-2">
|
||||
<div className="w-full flex flex-col items-center space-y-1">
|
||||
{/* <div className="w-full flex flex-row justify-between font-semibold">
|
||||
<span>0</span> <span>${numberFormat.format(goal)}</span>
|
||||
</div> */}
|
||||
|
||||
<div className="w-full bg-primary/15 rounded-full h-4">
|
||||
<div
|
||||
className="bg-green-500 h-4 rounded-full text-xs"
|
||||
style={{ width: `${percent}%` }}
|
||||
></div>
|
||||
style={{ width: `${percent < 100 ? percent : 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span className="text-sm font-semibold">{percent}%</span>
|
||||
<span className="text-sm font-semibold">{percent < 100 ? percent : 100}%</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import { useState, useEffect } from 'react'
|
||||
|
||||
import { ProjectItem } from '../utils/types'
|
||||
import { useFundSlug } from '../utils/use-fund-slug'
|
||||
import Progress from './Progress'
|
||||
import { cn } from '../utils/cn'
|
||||
|
||||
const numberFormat = Intl.NumberFormat('en', { notation: 'compact', compactDisplay: 'short' })
|
||||
|
||||
export type ProjectCardProps = {
|
||||
project: ProjectItem
|
||||
@@ -12,7 +16,6 @@ export type ProjectCardProps = {
|
||||
|
||||
const ProjectCard: React.FC<ProjectCardProps> = ({ project, customImageStyles }) => {
|
||||
const [isHorizontal, setIsHorizontal] = useState<boolean | null>(null)
|
||||
const fundSlug = useFundSlug()
|
||||
|
||||
useEffect(() => {
|
||||
const img = document.createElement('img')
|
||||
@@ -27,23 +30,17 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project, customImageStyles })
|
||||
}
|
||||
}, [project.coverImage])
|
||||
|
||||
let cardStyle
|
||||
// if (tags.includes('Nostr')) {
|
||||
// cardStyle =
|
||||
// 'h-full space-y-4 rounded-xl border-b-4 border-purple-600 bg-stone-100 dark:border-purple-600 dark:bg-stone-900'
|
||||
// } else if (tags.includes('Lightning')) {
|
||||
// cardStyle =
|
||||
// 'h-full space-y-4 rounded-xl border-b-4 border-yellow-300 bg-stone-100 dark:border-yellow-300 dark:bg-stone-900'
|
||||
// } else if (tags.includes('Bitcoin')) {
|
||||
// cardStyle =
|
||||
// 'h-full space-y-4 rounded-xl border-b-4 border-orange-400 bg-stone-100 dark:border-orange-400 dark:bg-stone-900'
|
||||
// } else {
|
||||
cardStyle = 'h-full space-y-4 rounded-xl border-b-4 border-primary bg-primary/5'
|
||||
// }
|
||||
|
||||
return (
|
||||
<figure className={cardStyle}>
|
||||
<Link href={`/${fundSlug}/projects/${project.slug}`} passHref>
|
||||
<Link href={`/${project.fund}/projects/${project.slug}`} passHref>
|
||||
<figure
|
||||
className={cn(
|
||||
'max-w-sm min-h-[460px] h-full space-y-2 flex flex-col rounded-xl border-b-4 bg-white',
|
||||
project.fund === 'monero' && 'border-monero',
|
||||
project.fund === 'firo' && 'border-firo',
|
||||
project.fund === 'privacyguides' && 'border-privacyguides',
|
||||
project.fund === 'general' && 'border-primary'
|
||||
)}
|
||||
>
|
||||
<div className="flex h-36 w-full sm:h-52">
|
||||
<Image
|
||||
alt={project.title}
|
||||
@@ -51,20 +48,32 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project, customImageStyles })
|
||||
width={1200}
|
||||
height={1200}
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
objectFit: 'contain',
|
||||
...customImageStyles,
|
||||
}}
|
||||
priority={true}
|
||||
className="cursor-pointer rounded-t-xl bg-white"
|
||||
/>
|
||||
</div>
|
||||
<figcaption className="p-2">
|
||||
<h2 className="font-bold">{project.title}</h2>
|
||||
<div className="mb-4 text-sm">by {project.nym}</div>
|
||||
<div className="mb-2 line-clamp-3">{project.summary}</div>
|
||||
|
||||
<figcaption className="p-5 flex flex-col grow space-y-4 justify-between">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div>
|
||||
<h2 className="font-bold">{project.title}</h2>
|
||||
<span className="text-sm text-gray-700">by {project.nym}</span>
|
||||
</div>
|
||||
|
||||
<span className="line-clamp-3 text-gray-400">{project.summary}</span>
|
||||
|
||||
<span className="font-bold">
|
||||
Goal: <span className="text-green-500">${numberFormat.format(project.goal)}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Progress current={20000} goal={10000} />
|
||||
</figcaption>
|
||||
</Link>
|
||||
</figure>
|
||||
</figure>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,17 +16,11 @@ const ProjectList: React.FC<ProjectListProps> = ({
|
||||
exclude,
|
||||
projects,
|
||||
}) => {
|
||||
const [sortedProjects, setSortedProjects] = useState<ProjectItem[]>()
|
||||
|
||||
useEffect(() => {
|
||||
setSortedProjects(projects.filter((p) => p.slug !== exclude).sort(() => 0.5 - Math.random()))
|
||||
}, [projects])
|
||||
|
||||
return (
|
||||
<section className="bg-light items-left flex flex-col">
|
||||
<ul className="grid max-w-5xl grid-cols-2 gap-4 md:grid-cols-4">
|
||||
{sortedProjects &&
|
||||
sortedProjects.slice(0, 4).map((p, i) => (
|
||||
<ul className="mx-auto grid max-w-5xl grid-cols-1 sm:mx-0 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{projects &&
|
||||
projects.slice(0, 6).map((p, i) => (
|
||||
<li key={i} className="">
|
||||
<ProjectCard
|
||||
project={p}
|
||||
|
||||
@@ -40,7 +40,9 @@ const SocialIcon = ({ kind, href, size = 5 }: Props) => {
|
||||
href={href}
|
||||
>
|
||||
<span className="sr-only">{kind}</span>
|
||||
<SocialSvg className={`fill-current text-gray-700 hover:text-primary h-${size} w-${size}`} />
|
||||
<SocialSvg
|
||||
className={`fill-current text-gray-600 hover:text-primary transition-colors h-${size} w-${size}`}
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
fund: firo
|
||||
title: 'Firo Example Campaign'
|
||||
summary: "The EAE attack is one of Monero's privacy weak points. Churning may be a solution."
|
||||
nym: 'Dr. Nathan Borggren'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
fund: general
|
||||
title: 'General Example Campaign'
|
||||
summary: "The EAE attack is one of Monero's privacy weak points. Churning may be a solution."
|
||||
nym: 'Dr. Nathan Borggren'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
fund: monero
|
||||
title: 'vtnerd Monero and Monero-LWS dev work for Q1/Q2 2024'
|
||||
summary: 'This development work will improve security, performance, and usability with an end goal of helping to broaden the user base. '
|
||||
date: '2024-01-26'
|
||||
@@ -12,7 +13,7 @@ personalWebsite: 'https://www.leeclagett.com'
|
||||
type: 'Other Free and Open Source Project'
|
||||
staticXMRaddress: '8454MvGDuZPFP1WKSMcgbRJqV1sXHz7Z3KyURMpkoLXR3CJUZiebjymjQGc6YvTWqhFZEtJwELbcgFHZ9qGPwPsF7fWLWPT'
|
||||
goal: 28800
|
||||
isFunded: false
|
||||
isFunded: true
|
||||
numdonationsxmr: 43
|
||||
totaldonationsinfiatxmr: 28800
|
||||
totaldonationsxmr: 226.1
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
fund: monero
|
||||
title: 'Research to Defeat EAE Attack and Analyze Effectiveness of Churning Procedures'
|
||||
summary: "The EAE attack is one of Monero's privacy weak points. Churning may be a solution."
|
||||
nym: 'Dr. Nathan Borggren'
|
||||
@@ -23,9 +24,13 @@ fiatnumdonations: 0
|
||||
fiattotaldonationsinfiat: 0
|
||||
fiattotaldonations: 0
|
||||
---
|
||||
|
||||
### Funded Goal: 220 XMR (86 XMR contributed from MAGIC Monero Fund general fund)
|
||||
|
||||
### Start: June 2023
|
||||
|
||||
### End: September 2023
|
||||
|
||||
### Result: [Research paper](/pdf/Borggren-Sept-2023-Probing-the-Attacks-on-the-Privacy-of-the-Monero-Blockchain.pdf)
|
||||
|
||||
The EAE (Eve-Alice-Eve) attack is a threat to Monero user privacy. When a Monero user sends and/or receives funds repeatedly with an entity (Eve) that aims to trace funds, enough information in the local transaction graph and ring signatures may be available to make a probabalistic guess about the true destination or source of funds. There are different names for this attack and related attacks, which mostly differ in how many colluding entities are involved and how much information they may possess: EABE, poisoned outputs, Overseer, Flashlight, Tainted Dust, and Knacc attacks. One of the videos of the Breaking Monero series [discusses the EAE attack in detail](https://www.monerooutreach.org/breaking-monero/poisoned-outputs.html).
|
||||
@@ -36,8 +41,7 @@ Dr. Nathan Borggren has stepped up to investigate these important research quest
|
||||
|
||||
Dr. Borggren has proposed using Topological Data Analysis and Bayesian statistical methods to analyze the EAE attack and related attacks on Monero user privacy. The false positive and false negative rate of the EAE attack will be investigated through simulations and possibly analysis of Monero's mainnet blockchain. The full details of the plan are in [the research proposal](https://github.com/MAGICGrants/Monero-Fund/blob/main/projects/borggren_research-MAGIC-submission.pdf).
|
||||
|
||||
|
||||
> "I think that the EAE attack is one of Monero's biggest *practical* attack surfaces currently, and I see value in quantification plus real world data-informed best practices."
|
||||
> "I think that the EAE attack is one of Monero's biggest _practical_ attack surfaces currently, and I see value in quantification plus real world data-informed best practices."
|
||||
|
||||
— [isthmus](https://github.com/Mitchellpkt), Monero Research Lab researcher and lead author of ["Fingerprinting a flood: Forensic statistical analysis of the mid-2021 Monero transaction volume anomaly"](https://mitchellpkt.medium.com/fingerprinting-a-flood-forensic-statistical-analysis-of-the-mid-2021-monero-transaction-volume-a19cbf41ce60)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
fund: monero
|
||||
title: 'ETH<>XMR Atomic Swap Continued Development'
|
||||
summary: "A trustless way to exchange Monero and Ethereum."
|
||||
summary: 'A trustless way to exchange Monero and Ethereum.'
|
||||
nym: 'noot'
|
||||
website: 'https://github.com/AthanorLabs/atomic-swap'
|
||||
coverImage: '/img/project/Ethereum_logo.png'
|
||||
@@ -21,9 +22,13 @@ fiatnumdonations: 8
|
||||
fiattotaldonationsinfiat: 5139
|
||||
fiattotaldonations: 5139
|
||||
---
|
||||
|
||||
### Funded Goal: 24,000 USD (5,000 USD contributed from the MAGIC Monero Fund general fund)
|
||||
|
||||
### Start: September 2022
|
||||
|
||||
### End: August 2022
|
||||
|
||||
### Result: [Mainnet beta software release](https://reddit.com/r/Monero/comments/1382rva/ethxmr_atomic_swap_beta_release/)
|
||||
|
||||
The MAGIC Monero Fund raised funds for noot to continue development on ETH-XMR atomic swaps. View [the campaign here](https://www.gofundme.com/f/noot-ethxmr-atomic-swap-development-4-months).
|
||||
@@ -51,6 +56,3 @@ The current implementation of the swap does not store anything to disk apart fro
|
||||
### General maintenance and bugfixes
|
||||
|
||||
See https://github.com/noot/atomic-swap/issues for open issues on the repo. Issues not covered by the above work are part of this sec-tion. This includes RPC calls and documentation, codebase maintenance, testing, and fixes of any bugs found during testing.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
fund: monero
|
||||
title: 'Ring Signature Resiliency to AI Analysis'
|
||||
summary: "A test of machine learning attacks on Monero's untraceability."
|
||||
nym: 'ACK-J'
|
||||
@@ -21,9 +22,13 @@ fiatnumdonations: 0
|
||||
fiattotaldonationsinfiat: 0
|
||||
fiattotaldonations: 0
|
||||
---
|
||||
|
||||
### Funded Goal: 12,000 USD (12,000 USD contributed from MAGIC Monero Fund general fund)
|
||||
|
||||
### Start: March 2022
|
||||
|
||||
### End: August 2022
|
||||
|
||||
### Result: [Research paper](https://raw.githubusercontent.com/ACK-J/Monero-Dataset-Pipeline/main/Lord_of_the_Rings__An_Empirical_Analysis_of_Monero_s_Ring_Signature_Resilience_to_Artificially_Intelligent_Attacks.pdf)
|
||||
|
||||
ACK-J created a series of Monero transactions using different spend patterns and applied artificial intelligence models to determine the resiliency of ring signatures against these models, absent external information. The MAGIC Monero Fund approved the research grant from its general fund.
|
||||
@@ -35,6 +40,3 @@ The transcript of a short inverview with ACK-J discussing the results is [here](
|
||||
## Main results
|
||||
|
||||
With 11 ring members, public information on the Monero blockchain could aid an attacker in predicting the true spend of a transaction greater than the random guessing probability of 9% (1/11). With this model, the likelihood of a correct guess grew to 13.3%, a modest increase. Since the data was collected, Monero increased its ring size to 16; thus, the accuracy should now be lower, but we do not have numbers for this.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
fund: privacyguides
|
||||
title: 'PG Example Campaign'
|
||||
summary: "The EAE attack is one of Monero's privacy weak points. Churning may be a solution."
|
||||
nym: 'Dr. Nathan Borggren'
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FundSlug } from '@prisma/client'
|
||||
import markdownToHtml from '../../utils/markdownToHtml'
|
||||
import { getSingleFile } from '../../utils/md'
|
||||
import BigDumbMarkdown from '../../components/BigDumbMarkdown'
|
||||
import { FundSlug, fundSlugs } from '../../server/utils/funds'
|
||||
import { fundSlugs } from '../../utils/funds'
|
||||
|
||||
export default function About({ content }: { content: string }) {
|
||||
return <BigDumbMarkdown content={content} />
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FundSlug } from '@prisma/client'
|
||||
import markdownToHtml from '../../utils/markdownToHtml'
|
||||
import { getSingleFile } from '../../utils/md'
|
||||
import BigDumbMarkdown from '../../components/BigDumbMarkdown'
|
||||
import { FundSlug, fundSlugs } from '../../server/utils/funds'
|
||||
import { fundSlugs } from '../../utils/funds'
|
||||
|
||||
export default function Terms({ content }: { content: string }) {
|
||||
return <BigDumbMarkdown content={content} />
|
||||
|
||||
@@ -139,13 +139,12 @@ const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) =
|
||||
<h1 className="mb-4 font-bold">Raised</h1>
|
||||
|
||||
<Progress
|
||||
percent={Math.floor(
|
||||
((donationStats.xmr.fiatAmount +
|
||||
donationStats.btc.fiatAmount +
|
||||
donationStats.usd.fiatAmount) /
|
||||
goal) *
|
||||
100
|
||||
)}
|
||||
current={
|
||||
donationStats.xmr.fiatAmount +
|
||||
donationStats.btc.fiatAmount +
|
||||
donationStats.usd.fiatAmount
|
||||
}
|
||||
goal={goal}
|
||||
/>
|
||||
|
||||
<ul className="font-semibold space-y-1">
|
||||
@@ -266,13 +265,13 @@ export async function getServerSideProps({ params, resolvedUrl }: GetServerSideP
|
||||
donations.forEach((donation) => {
|
||||
if (donation.cryptoCode === 'XMR') {
|
||||
donationStats.xmr.count += 1
|
||||
donationStats.xmr.amount += donation.cryptoAmount
|
||||
donationStats.xmr.amount += donation.cryptoAmount || 0
|
||||
donationStats.xmr.fiatAmount += donation.fiatAmount
|
||||
}
|
||||
|
||||
if (donation.cryptoCode === 'BTC') {
|
||||
donationStats.btc.count += 1
|
||||
donationStats.btc.amount += donation.cryptoAmount
|
||||
donationStats.btc.amount += donation.cryptoAmount || 0
|
||||
donationStats.btc.fiatAmount += donation.fiatAmount
|
||||
}
|
||||
|
||||
|
||||
@@ -54,11 +54,12 @@ const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
|
||||
export default AllProjects
|
||||
|
||||
export async function getStaticProps({ params }: { params: any }) {
|
||||
const projects = getProjects('monero')
|
||||
const projects = await getProjects('monero')
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects,
|
||||
},
|
||||
revalidate: 120,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FundSlug } from '@prisma/client'
|
||||
import markdownToHtml from '../../utils/markdownToHtml'
|
||||
import { getSingleFile } from '../../utils/md'
|
||||
import BigDumbMarkdown from '../../components/BigDumbMarkdown'
|
||||
import { FundSlug, fundSlugs } from '../../server/utils/funds'
|
||||
import { fundSlugs } from '../../utils/funds'
|
||||
|
||||
export default function Terms({ content }: { content: string }) {
|
||||
return <BigDumbMarkdown content={content} />
|
||||
|
||||
@@ -12,10 +12,16 @@ import '../styles/globals.css'
|
||||
|
||||
function MyApp({ Component, pageProps, router }: AppProps) {
|
||||
const fundSlug = useFundSlug()
|
||||
console.log(fundSlug || 'general')
|
||||
|
||||
return (
|
||||
<SessionProvider session={pageProps.session}>
|
||||
<ThemeProvider attribute="class" forcedTheme={fundSlug as string} enableSystem={false}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
forcedTheme={fundSlug || 'general'}
|
||||
themes={['monero', 'general', 'firo', 'priacyguides']}
|
||||
enableSystem={false}
|
||||
>
|
||||
<Head>
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
||||
</Head>
|
||||
|
||||
@@ -166,11 +166,12 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
|
||||
export default Home
|
||||
|
||||
export async function getStaticProps({ params }: { params: any }) {
|
||||
const projects = getProjects(fund.slug)
|
||||
const projects = await getProjects(fund.slug)
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects,
|
||||
},
|
||||
revalidate: 120,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,13 +137,12 @@ const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) =
|
||||
<h1 className="mb-4 font-bold">Raised</h1>
|
||||
|
||||
<Progress
|
||||
percent={Math.floor(
|
||||
((donationStats.xmr.fiatAmount +
|
||||
donationStats.btc.fiatAmount +
|
||||
donationStats.usd.fiatAmount) /
|
||||
goal) *
|
||||
100
|
||||
)}
|
||||
current={
|
||||
donationStats.xmr.fiatAmount +
|
||||
donationStats.btc.fiatAmount +
|
||||
donationStats.usd.fiatAmount
|
||||
}
|
||||
goal={goal}
|
||||
/>
|
||||
|
||||
<ul className="font-semibold space-y-1">
|
||||
@@ -240,19 +239,19 @@ export async function getServerSideProps({ params, resolvedUrl }: GetServerSideP
|
||||
|
||||
const donationStats = {
|
||||
xmr: {
|
||||
count: project.isFunded ? project.numdonationsxmr : 0,
|
||||
amount: project.isFunded ? project.totaldonationsxmr : 0,
|
||||
fiatAmount: project.isFunded ? project.totaldonationsinfiatxmr : 0,
|
||||
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,
|
||||
amount: project.isFunded ? project.totaldonationsbtc : 0,
|
||||
fiatAmount: project.isFunded ? project.totaldonationsinfiatbtc : 0,
|
||||
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,
|
||||
amount: project.isFunded ? project.fiattotaldonations : 0,
|
||||
fiatAmount: project.isFunded ? project.fiattotaldonationsinfiat : 0,
|
||||
count: project.isFunded ? project.fiatnumdonations || 0 : 0,
|
||||
amount: project.isFunded ? project.fiattotaldonations || 0 : 0,
|
||||
fiatAmount: project.isFunded ? project.fiattotaldonationsinfiat || 0 : 0,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -264,13 +263,13 @@ export async function getServerSideProps({ params, resolvedUrl }: GetServerSideP
|
||||
donations.forEach((donation) => {
|
||||
if (donation.cryptoCode === 'XMR') {
|
||||
donationStats.xmr.count += 1
|
||||
donationStats.xmr.amount += donation.cryptoAmount
|
||||
donationStats.xmr.amount += donation.cryptoAmount || 0
|
||||
donationStats.xmr.fiatAmount += donation.fiatAmount
|
||||
}
|
||||
|
||||
if (donation.cryptoCode === 'BTC') {
|
||||
donationStats.btc.count += 1
|
||||
donationStats.btc.amount += donation.cryptoAmount
|
||||
donationStats.btc.amount += donation.cryptoAmount || 0
|
||||
donationStats.btc.fiatAmount += donation.fiatAmount
|
||||
}
|
||||
|
||||
@@ -54,11 +54,12 @@ const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
|
||||
export default AllProjects
|
||||
|
||||
export async function getStaticProps({ params }: { params: any }) {
|
||||
const projects = getProjects('monero')
|
||||
const projects = await getProjects('monero')
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects,
|
||||
},
|
||||
revalidate: 120,
|
||||
}
|
||||
}
|
||||
@@ -166,11 +166,12 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
|
||||
export default Home
|
||||
|
||||
export async function getStaticProps({ params }: { params: any }) {
|
||||
const projects = getProjects(fund.slug)
|
||||
const projects = await getProjects(fund.slug)
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects,
|
||||
},
|
||||
revalidate: 120,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,13 +135,12 @@ const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) =
|
||||
<h1 className="mb-4 font-bold">Raised</h1>
|
||||
|
||||
<Progress
|
||||
percent={Math.floor(
|
||||
((donationStats.xmr.fiatAmount +
|
||||
donationStats.btc.fiatAmount +
|
||||
donationStats.usd.fiatAmount) /
|
||||
goal) *
|
||||
100
|
||||
)}
|
||||
current={
|
||||
donationStats.xmr.fiatAmount +
|
||||
donationStats.btc.fiatAmount +
|
||||
donationStats.usd.fiatAmount
|
||||
}
|
||||
goal={goal}
|
||||
/>
|
||||
|
||||
<ul className="font-semibold space-y-1">
|
||||
@@ -262,13 +261,13 @@ export async function getServerSideProps({ params, resolvedUrl }: GetServerSideP
|
||||
donations.forEach((donation) => {
|
||||
if (donation.cryptoCode === 'XMR') {
|
||||
donationStats.xmr.count += 1
|
||||
donationStats.xmr.amount += donation.cryptoAmount
|
||||
donationStats.xmr.amount += donation.cryptoAmount || 0
|
||||
donationStats.xmr.fiatAmount += donation.fiatAmount
|
||||
}
|
||||
|
||||
if (donation.cryptoCode === 'BTC') {
|
||||
donationStats.btc.count += 1
|
||||
donationStats.btc.amount += donation.cryptoAmount
|
||||
donationStats.btc.amount += donation.cryptoAmount || 0
|
||||
donationStats.btc.fiatAmount += donation.fiatAmount
|
||||
}
|
||||
|
||||
|
||||
@@ -54,11 +54,12 @@ const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
|
||||
export default AllProjects
|
||||
|
||||
export async function getStaticProps({ params }: { params: any }) {
|
||||
const projects = getProjects('monero')
|
||||
const projects = await getProjects('monero')
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects,
|
||||
},
|
||||
revalidate: 120,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,87 @@
|
||||
import { redirect } from 'next/navigation'
|
||||
import { funds } from '../utils/funds'
|
||||
import { Button } from '../components/ui/button'
|
||||
import SocialIcon from '../components/social-icons'
|
||||
import FiroLogo from '../components/FiroLogo'
|
||||
import PrivacyGuidesLogo from '../components/PrivacyGuidesLogo'
|
||||
import MagicLogo from '../components/MagicLogo'
|
||||
import MoneroLogo from '../components/MoneroLogo'
|
||||
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() {
|
||||
return <></>
|
||||
function Root({ projects }: { projects: ProjectItem[] }) {
|
||||
return (
|
||||
<div className="flex flex-col items-start space-y-10">
|
||||
<div className="w-full space-y-4">
|
||||
<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">
|
||||
Funds
|
||||
</h1>
|
||||
|
||||
<div className="grid 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"
|
||||
>
|
||||
<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" />}
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<Button
|
||||
className={cn(
|
||||
'self-end',
|
||||
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'
|
||||
)}
|
||||
size="lg"
|
||||
variant="light"
|
||||
style={{ marginTop: 'auto' }}
|
||||
>
|
||||
<Link href={`/${fund.slug}`}>View Campaigns</Link>
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
Campaigns
|
||||
</h1>
|
||||
|
||||
<ProjectList projects={projects} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Root
|
||||
|
||||
export function getServerSideProps() {
|
||||
export async function getStaticProps({ params }: { params: any }) {
|
||||
const projects = await getProjects()
|
||||
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/general',
|
||||
permanent: true,
|
||||
props: {
|
||||
projects,
|
||||
},
|
||||
revalidate: 120,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,11 +166,12 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
|
||||
export default Home
|
||||
|
||||
export async function getStaticProps({ params }: { params: any }) {
|
||||
const projects = getProjects(fund.slug)
|
||||
const projects = await getProjects(fund.slug)
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects,
|
||||
},
|
||||
revalidate: 120,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,13 +135,12 @@ const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) =
|
||||
<h1 className="mb-4 font-bold">Raised</h1>
|
||||
|
||||
<Progress
|
||||
percent={Math.floor(
|
||||
((donationStats.xmr.fiatAmount +
|
||||
donationStats.btc.fiatAmount +
|
||||
donationStats.usd.fiatAmount) /
|
||||
goal) *
|
||||
100
|
||||
)}
|
||||
current={
|
||||
donationStats.xmr.fiatAmount +
|
||||
donationStats.btc.fiatAmount +
|
||||
donationStats.usd.fiatAmount
|
||||
}
|
||||
goal={goal}
|
||||
/>
|
||||
|
||||
<ul className="font-semibold space-y-1">
|
||||
@@ -262,13 +261,13 @@ export async function getServerSideProps({ params, resolvedUrl }: GetServerSideP
|
||||
donations.forEach((donation) => {
|
||||
if (donation.cryptoCode === 'XMR') {
|
||||
donationStats.xmr.count += 1
|
||||
donationStats.xmr.amount += donation.cryptoAmount
|
||||
donationStats.xmr.amount += donation.cryptoAmount || 0
|
||||
donationStats.xmr.fiatAmount += donation.fiatAmount
|
||||
}
|
||||
|
||||
if (donation.cryptoCode === 'BTC') {
|
||||
donationStats.btc.count += 1
|
||||
donationStats.btc.amount += donation.cryptoAmount
|
||||
donationStats.btc.amount += donation.cryptoAmount || 0
|
||||
donationStats.btc.fiatAmount += donation.fiatAmount
|
||||
}
|
||||
|
||||
|
||||
@@ -54,11 +54,12 @@ const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
|
||||
export default AllProjects
|
||||
|
||||
export async function getStaticProps({ params }: { params: any }) {
|
||||
const projects = getProjects('monero')
|
||||
const projects = await getProjects('monero')
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects,
|
||||
},
|
||||
revalidate: 120,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,11 +166,12 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
|
||||
export default Home
|
||||
|
||||
export async function getStaticProps({ params }: { params: any }) {
|
||||
const projects = getProjects(fund.slug)
|
||||
const projects = await getProjects(fund.slug)
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects,
|
||||
},
|
||||
revalidate: 120,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,13 +135,12 @@ const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) =
|
||||
<h1 className="mb-4 font-bold">Raised</h1>
|
||||
|
||||
<Progress
|
||||
percent={Math.floor(
|
||||
((donationStats.xmr.fiatAmount +
|
||||
donationStats.btc.fiatAmount +
|
||||
donationStats.usd.fiatAmount) /
|
||||
goal) *
|
||||
100
|
||||
)}
|
||||
current={
|
||||
donationStats.xmr.fiatAmount +
|
||||
donationStats.btc.fiatAmount +
|
||||
donationStats.usd.fiatAmount
|
||||
}
|
||||
goal={goal}
|
||||
/>
|
||||
|
||||
<ul className="font-semibold space-y-1">
|
||||
@@ -262,13 +261,13 @@ export async function getServerSideProps({ params, resolvedUrl }: GetServerSideP
|
||||
donations.forEach((donation) => {
|
||||
if (donation.cryptoCode === 'XMR') {
|
||||
donationStats.xmr.count += 1
|
||||
donationStats.xmr.amount += donation.cryptoAmount
|
||||
donationStats.xmr.amount += donation.cryptoAmount || 0
|
||||
donationStats.xmr.fiatAmount += donation.fiatAmount
|
||||
}
|
||||
|
||||
if (donation.cryptoCode === 'BTC') {
|
||||
donationStats.btc.count += 1
|
||||
donationStats.btc.amount += donation.cryptoAmount
|
||||
donationStats.btc.amount += donation.cryptoAmount || 0
|
||||
donationStats.btc.fiatAmount += donation.fiatAmount
|
||||
}
|
||||
|
||||
|
||||
@@ -54,11 +54,12 @@ const AllProjects: NextPage<{ projects: ProjectItem[] }> = ({ projects }) => {
|
||||
export default AllProjects
|
||||
|
||||
export async function getStaticProps({ params }: { params: any }) {
|
||||
const projects = getProjects('monero')
|
||||
const projects = await getProjects('monero')
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects,
|
||||
},
|
||||
revalidate: 120,
|
||||
}
|
||||
}
|
||||
|
||||
22
public/firo_logo.svg
Normal file
22
public/firo_logo.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 520 520" style="enable-background:new 0 0 520 520;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FEFEFE;}
|
||||
.st1{fill:#9B1C2E;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<circle class="st0" cx="260" cy="260" r="252.7"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M155.6,370.7c5.9,0,11.2-3.2,14-8.4l37.3-70.6h-57.5c-8.7,0-15.8-7.1-15.8-15.8v-31.6
|
||||
c0-8.7,7.1-15.8,15.8-15.8h90.9l70.6-133.9c2.7-5.2,8.1-8.4,14-8.4h118.8C397.5,37.4,332.3,7,260,7C120.3,7,7,120.3,7,260
|
||||
c0,39.7,9.2,77.3,25.5,110.7H155.6z"/>
|
||||
<path class="st1" d="M364.4,149.3c-5.9,0-11.2,3.2-14,8.4l-37.3,70.6h57.5c8.7,0,15.8,7.1,15.8,15.8v31.6
|
||||
c0,8.7-7.1,15.8-15.8,15.8h-90.9l-70.6,133.9c-2.7,5.2-8.1,8.4-14,8.4H76.4C122.5,482.6,187.7,513,260,513
|
||||
c139.7,0,253-113.3,253-253c0-39.7-9.2-77.3-25.5-110.7H364.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -312,7 +312,7 @@ export const donationRouter = router({
|
||||
const donations = await prisma.donation.findMany({
|
||||
where: {
|
||||
userId,
|
||||
stripeSubscriptionId: null,
|
||||
membershipExpiresAt: null,
|
||||
fundSlug: input.fundSlug,
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
#f7f0f1 (firo off-white background) as the background color for the MAGIC Firo Fund
|
||||
#FFD06F (privacy guides yellow) as the accent color for the MAGIC Privacy Guides Fund */
|
||||
@layer base {
|
||||
:root {
|
||||
--monero: 24 100% 50%;
|
||||
--firo: 351 69% 36%;
|
||||
--privacyguides: 40 100% 50%;
|
||||
--general: 220 86% 58%;
|
||||
}
|
||||
|
||||
.monero {
|
||||
--background: 223, 100%, 97%;
|
||||
--foreground: 223, 84%, 5%;
|
||||
@@ -18,7 +25,7 @@
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 24 100% 50%;
|
||||
--primary: var(--monero);
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--primary-hover: 24 100% 45%;
|
||||
|
||||
@@ -44,7 +51,7 @@
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 351 69% 36%;
|
||||
--primary: var(--firo);
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--primary-hover: 351 69% 26%;
|
||||
|
||||
@@ -70,7 +77,7 @@
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 40 100% 72%;
|
||||
--primary: var(--privacyguides);
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--primary-hover: 40 100% 62%;
|
||||
|
||||
@@ -96,7 +103,7 @@
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 220 86% 58%;
|
||||
--primary: var(--general);
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--primary-hover: 220 86% 48%;
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ module.exports = {
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
monero: 'hsl(var(--monero))',
|
||||
firo: 'hsl(var(--firo))',
|
||||
privacyguides: 'hsl(var(--privacyguides))',
|
||||
general: 'hsl(var(--general))',
|
||||
border: 'hsl(var(--border))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
@@ -71,133 +75,70 @@ module.exports = {
|
||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||
},
|
||||
typography: (theme) => ({
|
||||
assds: {
|
||||
css: {
|
||||
color: theme('colors.gray.700'),
|
||||
a: {
|
||||
color: theme('colors.primary.DEFAULT'),
|
||||
'&:hover': {
|
||||
color: `${theme('colors.primary.DEFAULT_HOVER')} !important`,
|
||||
},
|
||||
code: { color: theme('colors.primary.400') },
|
||||
},
|
||||
h1: {
|
||||
fontWeight: '700',
|
||||
letterSpacing: theme('letterSpacing.tight'),
|
||||
color: theme('colors.gray.900'),
|
||||
},
|
||||
h2: {
|
||||
fontWeight: '700',
|
||||
letterSpacing: theme('letterSpacing.tight'),
|
||||
color: theme('colors.gray.900'),
|
||||
},
|
||||
h3: {
|
||||
fontWeight: '600',
|
||||
color: theme('colors.gray.900'),
|
||||
},
|
||||
'h4,h5,h6': {
|
||||
color: theme('colors.gray.900'),
|
||||
},
|
||||
pre: {
|
||||
backgroundColor: theme('colors.gray.800'),
|
||||
},
|
||||
code: {
|
||||
color: theme('colors.pink.500'),
|
||||
backgroundColor: theme('colors.gray.100'),
|
||||
paddingLeft: '4px',
|
||||
paddingRight: '4px',
|
||||
paddingTop: '2px',
|
||||
paddingBottom: '2px',
|
||||
borderRadius: '0.25rem',
|
||||
},
|
||||
'code::before': {
|
||||
content: 'none',
|
||||
},
|
||||
'code::after': {
|
||||
content: 'none',
|
||||
},
|
||||
details: {
|
||||
backgroundColor: theme('colors.gray.100'),
|
||||
paddingLeft: '4px',
|
||||
paddingRight: '4px',
|
||||
paddingTop: '2px',
|
||||
paddingBottom: '2px',
|
||||
borderRadius: '0.25rem',
|
||||
},
|
||||
hr: { borderColor: theme('colors.gray.200') },
|
||||
'ol li::marker': {
|
||||
fontWeight: '600',
|
||||
color: theme('colors.gray.500'),
|
||||
},
|
||||
'ul li::marker': {
|
||||
backgroundColor: theme('colors.gray.500'),
|
||||
},
|
||||
strong: { color: theme('colors.gray.600') },
|
||||
blockquote: {
|
||||
color: theme('colors.gray.900'),
|
||||
borderLeftColor: theme('colors.gray.200'),
|
||||
css: {
|
||||
color: theme('colors.gray.700'),
|
||||
a: {
|
||||
color: theme('colors.primary.DEFAULT'),
|
||||
'&:hover': {
|
||||
color: `${theme('colors.primary.DEFAULT_HOVER')} !important`,
|
||||
},
|
||||
code: { color: theme('colors.primary.400') },
|
||||
},
|
||||
},
|
||||
boo: {
|
||||
css: {
|
||||
color: theme('colors.gray.300'),
|
||||
a: {
|
||||
color: theme('colors.primary.DEFAULT'),
|
||||
'&:hover': {
|
||||
color: `${theme('colors.primary.DEFAULT_HOVER')} !important`,
|
||||
},
|
||||
code: { color: theme('colors.primary.400') },
|
||||
},
|
||||
h1: {
|
||||
fontWeight: '700',
|
||||
letterSpacing: theme('letterSpacing.tight'),
|
||||
color: theme('colors.gray.100'),
|
||||
},
|
||||
h2: {
|
||||
fontWeight: '700',
|
||||
letterSpacing: theme('letterSpacing.tight'),
|
||||
color: theme('colors.gray.100'),
|
||||
},
|
||||
h3: {
|
||||
fontWeight: '600',
|
||||
color: theme('colors.gray.100'),
|
||||
},
|
||||
'h4,h5,h6': {
|
||||
color: theme('colors.gray.100'),
|
||||
},
|
||||
pre: {
|
||||
backgroundColor: theme('colors.gray.800'),
|
||||
},
|
||||
code: {
|
||||
backgroundColor: theme('colors.gray.800'),
|
||||
},
|
||||
details: {
|
||||
backgroundColor: theme('colors.gray.800'),
|
||||
},
|
||||
hr: { borderColor: theme('colors.gray.700') },
|
||||
'ol li::marker': {
|
||||
fontWeight: '600',
|
||||
color: theme('colors.gray.400'),
|
||||
},
|
||||
'ul li::marker': {
|
||||
backgroundColor: theme('colors.gray.400'),
|
||||
},
|
||||
strong: { color: theme('colors.gray.100') },
|
||||
thead: {
|
||||
th: {
|
||||
color: theme('colors.gray.100'),
|
||||
},
|
||||
},
|
||||
tbody: {
|
||||
tr: {
|
||||
borderBottomColor: theme('colors.gray.700'),
|
||||
},
|
||||
},
|
||||
blockquote: {
|
||||
color: theme('colors.gray.100'),
|
||||
borderLeftColor: theme('colors.gray.700'),
|
||||
},
|
||||
h1: {
|
||||
fontWeight: '700',
|
||||
letterSpacing: theme('letterSpacing.tight'),
|
||||
color: theme('colors.gray.900'),
|
||||
},
|
||||
h2: {
|
||||
fontWeight: '700',
|
||||
letterSpacing: theme('letterSpacing.tight'),
|
||||
color: theme('colors.gray.900'),
|
||||
},
|
||||
h3: {
|
||||
fontWeight: '600',
|
||||
color: theme('colors.gray.900'),
|
||||
},
|
||||
'h4,h5,h6': {
|
||||
color: theme('colors.gray.900'),
|
||||
},
|
||||
pre: {
|
||||
backgroundColor: theme('colors.gray.800'),
|
||||
},
|
||||
code: {
|
||||
color: theme('colors.pink.500'),
|
||||
backgroundColor: theme('colors.gray.100'),
|
||||
paddingLeft: '4px',
|
||||
paddingRight: '4px',
|
||||
paddingTop: '2px',
|
||||
paddingBottom: '2px',
|
||||
borderRadius: '0.25rem',
|
||||
},
|
||||
'code::before': {
|
||||
content: 'none',
|
||||
},
|
||||
'code::after': {
|
||||
content: 'none',
|
||||
},
|
||||
details: {
|
||||
backgroundColor: theme('colors.gray.100'),
|
||||
paddingLeft: '4px',
|
||||
paddingRight: '4px',
|
||||
paddingTop: '2px',
|
||||
paddingBottom: '2px',
|
||||
borderRadius: '0.25rem',
|
||||
},
|
||||
hr: { borderColor: theme('colors.gray.200') },
|
||||
'ol li::marker': {
|
||||
fontWeight: '600',
|
||||
color: theme('colors.gray.500'),
|
||||
},
|
||||
'ul li::marker': {
|
||||
backgroundColor: theme('colors.gray.500'),
|
||||
},
|
||||
strong: { color: theme('colors.gray.600') },
|
||||
blockquote: {
|
||||
color: theme('colors.gray.900'),
|
||||
borderLeftColor: theme('colors.gray.200'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -7,48 +7,93 @@ export const funds: Record<FundSlug, ProjectItem & { slug: FundSlug }> = {
|
||||
nym: 'MagicMonero',
|
||||
website: 'https://monerofund.org',
|
||||
personalWebsite: 'https://monerofund.org',
|
||||
title: 'MAGIC Monero Fund',
|
||||
summary: 'Support contributors to Monero',
|
||||
title: 'Monero Fund',
|
||||
summary:
|
||||
'Help us to provide sustainable funding for free and open-source contributors working on freedom tech and projects that help Monero flourish.',
|
||||
coverImage: '/img/crystalball.jpg',
|
||||
git: 'magicgrants',
|
||||
twitter: 'magicgrants',
|
||||
// The attributes below can be ignored
|
||||
goal: 100000,
|
||||
fund: 'monero',
|
||||
fiatnumdonations: 0,
|
||||
fiattotaldonations: 0,
|
||||
fiattotaldonationsinfiat: 0,
|
||||
numdonationsbtc: 0,
|
||||
numdonationsxmr: 0,
|
||||
totaldonationsbtc: 0,
|
||||
totaldonationsinfiatbtc: 0,
|
||||
totaldonationsinfiatxmr: 0,
|
||||
totaldonationsxmr: 0,
|
||||
},
|
||||
firo: {
|
||||
slug: 'firo',
|
||||
nym: 'MagicFiro',
|
||||
website: 'https://monerofund.org',
|
||||
personalWebsite: 'https://monerofund.org',
|
||||
title: 'MAGIC Firo Fund',
|
||||
title: 'Firo Fund',
|
||||
summary: 'Support contributors to Firo',
|
||||
coverImage: '/img/crystalball.jpg',
|
||||
git: 'magicgrants',
|
||||
twitter: 'magicgrants',
|
||||
// The attributes below can be ignored
|
||||
goal: 100000,
|
||||
fund: 'firo',
|
||||
fiatnumdonations: 0,
|
||||
fiattotaldonations: 0,
|
||||
fiattotaldonationsinfiat: 0,
|
||||
numdonationsbtc: 0,
|
||||
numdonationsxmr: 0,
|
||||
totaldonationsbtc: 0,
|
||||
totaldonationsinfiatbtc: 0,
|
||||
totaldonationsinfiatxmr: 0,
|
||||
totaldonationsxmr: 0,
|
||||
},
|
||||
privacyguides: {
|
||||
slug: 'privacyguides',
|
||||
nym: 'MagicPrivacyGuides',
|
||||
website: 'https://monerofund.org',
|
||||
personalWebsite: 'https://monerofund.org',
|
||||
title: 'MAGIC Privacy Guides Fund',
|
||||
title: 'Privacy Guides Fund',
|
||||
summary: 'Support contributors to Privacy Guides',
|
||||
coverImage: '/img/crystalball.jpg',
|
||||
git: 'magicgrants',
|
||||
twitter: 'magicgrants',
|
||||
// The attributes below can be ignored
|
||||
goal: 100000,
|
||||
fund: 'privacyguides',
|
||||
fiatnumdonations: 0,
|
||||
fiattotaldonations: 0,
|
||||
fiattotaldonationsinfiat: 0,
|
||||
numdonationsbtc: 0,
|
||||
numdonationsxmr: 0,
|
||||
totaldonationsbtc: 0,
|
||||
totaldonationsinfiatbtc: 0,
|
||||
totaldonationsinfiatxmr: 0,
|
||||
totaldonationsxmr: 0,
|
||||
},
|
||||
general: {
|
||||
slug: 'general',
|
||||
nym: 'MagicGeneral',
|
||||
website: 'https://monerofund.org',
|
||||
personalWebsite: 'https://monerofund.org',
|
||||
title: 'MAGIC General Fund',
|
||||
title: 'General Fund',
|
||||
summary: 'Support contributors to MAGIC',
|
||||
coverImage: '/img/crystalball.jpg',
|
||||
git: 'magicgrants',
|
||||
twitter: 'magicgrants',
|
||||
// The attributes below can be ignored
|
||||
goal: 100000,
|
||||
fund: 'general',
|
||||
fiatnumdonations: 0,
|
||||
fiattotaldonations: 0,
|
||||
fiattotaldonationsinfiat: 0,
|
||||
numdonationsbtc: 0,
|
||||
numdonationsxmr: 0,
|
||||
totaldonationsbtc: 0,
|
||||
totaldonationsinfiatbtc: 0,
|
||||
totaldonationsinfiatxmr: 0,
|
||||
totaldonationsxmr: 0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
88
utils/md.ts
88
utils/md.ts
@@ -4,6 +4,10 @@ import matter from 'gray-matter'
|
||||
import sanitize from 'sanitize-filename'
|
||||
import { FundSlug } from '@prisma/client'
|
||||
|
||||
import { fundSlugs } from './funds'
|
||||
import { ProjectItem } from './types'
|
||||
import { prisma } from '../server/services'
|
||||
|
||||
const directories: Record<FundSlug, string> = {
|
||||
monero: join(process.cwd(), 'docs/monero/projects'),
|
||||
firo: join(process.cwd(), 'docs/firo/projects'),
|
||||
@@ -12,6 +16,7 @@ const directories: Record<FundSlug, string> = {
|
||||
}
|
||||
|
||||
const FIELDS = [
|
||||
'fund',
|
||||
'title',
|
||||
'summary',
|
||||
'slug',
|
||||
@@ -38,8 +43,11 @@ const FIELDS = [
|
||||
'fiattotaldonations',
|
||||
]
|
||||
|
||||
export function getProjectSlugs(fund: FundSlug) {
|
||||
return fs.readdirSync(directories[fund])
|
||||
const projectSlugsByFund: Record<FundSlug, string[]> = {
|
||||
monero: fs.readdirSync(directories.monero),
|
||||
firo: fs.readdirSync(directories.firo),
|
||||
privacyguides: fs.readdirSync(directories.privacyguides),
|
||||
general: fs.readdirSync(directories.general),
|
||||
}
|
||||
|
||||
export function getSingleFile(path: string) {
|
||||
@@ -47,10 +55,10 @@ export function getSingleFile(path: string) {
|
||||
return fs.readFileSync(fullPath, 'utf8')
|
||||
}
|
||||
|
||||
export function getProjectBySlug(slug: string, fund: FundSlug) {
|
||||
export function getProjectBySlug(slug: string, fundSlug: FundSlug) {
|
||||
const fields = FIELDS
|
||||
const realSlug = slug.replace(/\.md$/, '')
|
||||
const fullPath = join(directories[fund], `${sanitize(realSlug)}.md`)
|
||||
const fullPath = join(directories[fundSlug], `${sanitize(realSlug)}.md`)
|
||||
const fileContents = fs.readFileSync(fullPath, 'utf8')
|
||||
const { data, content } = matter(fileContents)
|
||||
|
||||
@@ -58,6 +66,16 @@ export function getProjectBySlug(slug: string, fund: FundSlug) {
|
||||
|
||||
// Ensure only the minimal needed data is exposed
|
||||
fields.forEach((field) => {
|
||||
items.numdonationsxmr = 0
|
||||
items.totaldonationsinfiatxmr = 0
|
||||
items.totaldonationsxmr = 0
|
||||
items.numdonationsbtc = 0
|
||||
items.totaldonationsinfiatbtc = 0
|
||||
items.totaldonationsbtc = 0
|
||||
items.fiatnumdonations = 0
|
||||
items.fiattotaldonationsinfiat = 0
|
||||
items.fiattotaldonations = 0
|
||||
|
||||
if (field === 'slug') {
|
||||
items[field] = realSlug
|
||||
}
|
||||
@@ -70,12 +88,64 @@ export function getProjectBySlug(slug: string, fund: FundSlug) {
|
||||
}
|
||||
})
|
||||
|
||||
return items
|
||||
return items as ProjectItem
|
||||
}
|
||||
|
||||
export function getProjects(fund: FundSlug) {
|
||||
const slugs = getProjectSlugs(fund)
|
||||
const posts = slugs.map((slug) => getProjectBySlug(slug, fund))
|
||||
export async function getProjects(fundSlug?: FundSlug) {
|
||||
let projects: ProjectItem[]
|
||||
|
||||
return posts
|
||||
if (fundSlug) {
|
||||
const slugs = projectSlugsByFund[fundSlug]
|
||||
projects = slugs.map((slug) => getProjectBySlug(slug, fundSlug))
|
||||
} else {
|
||||
projects = fundSlugs
|
||||
.map((_fundSlug) =>
|
||||
projectSlugsByFund[_fundSlug].map(
|
||||
(slug) => getProjectBySlug(slug, _fundSlug) as ProjectItem
|
||||
)
|
||||
)
|
||||
.flat()
|
||||
}
|
||||
|
||||
projects = projects
|
||||
.sort(() => 0.5 - Math.random())
|
||||
.sort((a, b) => {
|
||||
// Make active campaigns always come first
|
||||
if (!a.isFunded && b.isFunded) return -1
|
||||
if (a.isFunded && !b.isFunded) return 1
|
||||
return 0
|
||||
})
|
||||
.slice(0, 6)
|
||||
|
||||
await Promise.all(
|
||||
projects.map(async (project) => {
|
||||
if (project.isFunded) return
|
||||
|
||||
const donations = await prisma.donation.findMany({
|
||||
where: { projectSlug: project.slug, fundSlug: project.fund },
|
||||
})
|
||||
|
||||
donations.forEach((donation) => {
|
||||
if (donation.cryptoCode === 'XMR') {
|
||||
project.numdonationsxmr += 1
|
||||
project.totaldonationsxmr += donation.cryptoAmount || 0
|
||||
project.totaldonationsinfiatxmr += donation.fiatAmount
|
||||
}
|
||||
|
||||
if (donation.cryptoCode === 'BTC') {
|
||||
project.numdonationsbtc += 1
|
||||
project.totaldonationsbtc += donation.cryptoAmount || 0
|
||||
project.totaldonationsinfiatbtc += donation.fiatAmount
|
||||
}
|
||||
|
||||
if (donation.cryptoCode === null) {
|
||||
project.fiatnumdonations += 1
|
||||
project.fiattotaldonations += donation.fiatAmount
|
||||
project.fiattotaldonationsinfiat += donation.fiatAmount
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
return projects
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { FundSlug } from '@prisma/client'
|
||||
|
||||
export type ProjectItem = {
|
||||
slug: string
|
||||
fund: FundSlug
|
||||
nym: string
|
||||
content?: string
|
||||
title: string
|
||||
@@ -14,15 +17,15 @@ export type ProjectItem = {
|
||||
staticXMRaddress?: string
|
||||
goal: number
|
||||
isFunded?: boolean
|
||||
numdonationsxmr?: number
|
||||
totaldonationsinfiatxmr?: number
|
||||
totaldonationsxmr?: number
|
||||
numdonationsbtc?: number
|
||||
totaldonationsinfiatbtc?: number
|
||||
totaldonationsbtc?: number
|
||||
fiatnumdonations?: number
|
||||
fiattotaldonationsinfiat?: number
|
||||
fiattotaldonations?: number
|
||||
numdonationsxmr: number
|
||||
totaldonationsinfiatxmr: number
|
||||
totaldonationsxmr: number
|
||||
numdonationsbtc: number
|
||||
totaldonationsinfiatbtc: number
|
||||
totaldonationsbtc: number
|
||||
fiatnumdonations: number
|
||||
fiattotaldonationsinfiat: number
|
||||
fiattotaldonations: number
|
||||
}
|
||||
|
||||
export type PayReq = {
|
||||
|
||||
Reference in New Issue
Block a user