feat: add home page

This commit is contained in:
Artur
2024-08-22 20:39:29 -03:00
parent 7433c2e72f
commit b37124701a
42 changed files with 690 additions and 360 deletions

35
components/FiroLogo.tsx Normal file
View 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

View File

@@ -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>

View File

@@ -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
View 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

View 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

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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}

View File

@@ -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>
)
}

View File

@@ -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'

View File

@@ -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'

View File

@@ -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

View File

@@ -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."
&mdash; [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)

View File

@@ -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.

View File

@@ -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.

View File

@@ -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'

View File

@@ -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} />

View File

@@ -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} />

View File

@@ -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
}

View File

@@ -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,
}
}

View File

@@ -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} />

View File

@@ -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>

View File

@@ -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,
}
}

View File

@@ -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
}

View File

@@ -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,
}
}

View File

@@ -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,
}
}

View File

@@ -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
}

View File

@@ -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,
}
}

View File

@@ -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,
}
}

View File

@@ -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,
}
}

View File

@@ -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
}

View File

@@ -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,
}
}

View File

@@ -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,
}
}

View File

@@ -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
}

View File

@@ -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
View 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

View File

@@ -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' },

View File

@@ -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%;

View File

@@ -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'),
},
},
}),

View File

@@ -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,
},
}

View File

@@ -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
}

View File

@@ -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 = {