├── components ├── faq │ ├── index.ts │ └── faq.tsx ├── hero │ ├── index.ts │ └── hero.tsx ├── seo │ ├── index.ts │ └── seo.tsx ├── layout │ ├── index.ts │ ├── theme-toggle.tsx │ ├── layout.tsx │ ├── logo.tsx │ ├── header.tsx │ ├── navigation.tsx │ └── footer.tsx ├── features │ ├── index.ts │ └── features.tsx ├── highlights │ ├── index.ts │ └── highlights.tsx ├── mobile-nav │ ├── index.ts │ └── mobile-nav.tsx ├── nav-link │ ├── index.ts │ └── nav-link.tsx ├── button-link │ ├── index.ts │ └── button-link.tsx ├── announcement-banner │ ├── index.ts │ └── announcement-banner.tsx ├── section │ ├── index.ts │ ├── section.tsx │ └── section-title.tsx ├── testimonials │ ├── index.ts │ ├── testimonials.tsx │ └── testimonial.tsx ├── logos │ ├── index.ts │ ├── next.tsx │ ├── chakra.tsx │ └── react.tsx ├── motion │ ├── box.tsx │ ├── page-transition.tsx │ ├── fall-in-place.tsx │ └── float.tsx ├── typography │ └── index.tsx ├── context │ └── app-context.tsx ├── gradients │ └── background-gradient.tsx ├── profile │ └── index.tsx └── pricing │ └── pricing.tsx ├── .gitignore ├── public └── static │ ├── images │ ├── eelco.jpg │ ├── avatar.jpg │ ├── avatar2.jpg │ ├── avatar3.jpg │ ├── metamask.png │ ├── phantom.png │ ├── ethereum.svg │ ├── bitcoin.svg │ ├── usdc.svg │ ├── solana.svg │ └── logo.svg │ ├── favicons │ ├── favicon.ico │ ├── apple-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── ms-icon-70x70.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── apple-icon-precomposed.png │ ├── browserconfig.xml │ └── manifest.json │ └── screenshots │ ├── list.png │ ├── billing.png │ ├── dashboard.png │ └── landingspage.png ├── .eslintrc.json ├── .env.example ├── next-env.d.ts ├── theme ├── components │ ├── index.ts │ ├── section.ts │ ├── button.ts │ ├── cta.ts │ ├── section-title.ts │ └── features.ts ├── foundations │ └── typography.ts └── index.ts ├── pages ├── api │ └── hello.ts ├── _app.tsx ├── _document.tsx ├── dashboard │ └── index.tsx ├── project │ └── [link].tsx ├── index.tsx └── create │ └── index.tsx ├── hooks ├── use-route-changed.ts └── use-scrollspy.ts ├── next.config.mjs ├── posts └── post-01.mdx ├── utils ├── firebaseConfig.tsx ├── types.tsx ├── web3.tsx └── firestore.tsx ├── tsconfig.json ├── data ├── testimonials.tsx ├── pricing.tsx ├── faq.tsx ├── config.tsx ├── logo.tsx └── appulse.tsx ├── package.json ├── package copy.json └── README.md /components/faq/index.ts: -------------------------------------------------------------------------------- 1 | export * from './faq' 2 | -------------------------------------------------------------------------------- /components/hero/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hero' 2 | -------------------------------------------------------------------------------- /components/seo/index.ts: -------------------------------------------------------------------------------- 1 | export * from './seo' 2 | -------------------------------------------------------------------------------- /components/layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layout' 2 | -------------------------------------------------------------------------------- /components/features/index.ts: -------------------------------------------------------------------------------- 1 | export * from './features' 2 | -------------------------------------------------------------------------------- /components/highlights/index.ts: -------------------------------------------------------------------------------- 1 | export * from './highlights' 2 | -------------------------------------------------------------------------------- /components/mobile-nav/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mobile-nav' 2 | -------------------------------------------------------------------------------- /components/nav-link/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nav-link' 2 | -------------------------------------------------------------------------------- /components/button-link/index.ts: -------------------------------------------------------------------------------- 1 | export * from './button-link' 2 | -------------------------------------------------------------------------------- /components/announcement-banner/index.ts: -------------------------------------------------------------------------------- 1 | export * from './announcement-banner' 2 | -------------------------------------------------------------------------------- /components/section/index.ts: -------------------------------------------------------------------------------- 1 | export * from './section' 2 | export * from './section-title' 3 | -------------------------------------------------------------------------------- /components/testimonials/index.ts: -------------------------------------------------------------------------------- 1 | export * from './testimonial' 2 | export * from './testimonials' 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | .next/ 4 | .vscode/ 5 | .env.local 6 | package-lock.json 7 | pnpm-lock.yaml -------------------------------------------------------------------------------- /components/logos/index.ts: -------------------------------------------------------------------------------- 1 | export * from './chakra' 2 | export * from './next' 3 | export * from './react' 4 | -------------------------------------------------------------------------------- /public/static/images/eelco.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/images/eelco.jpg -------------------------------------------------------------------------------- /public/static/images/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/images/avatar.jpg -------------------------------------------------------------------------------- /public/static/images/avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/images/avatar2.jpg -------------------------------------------------------------------------------- /public/static/images/avatar3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/images/avatar3.jpg -------------------------------------------------------------------------------- /public/static/images/metamask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/images/metamask.png -------------------------------------------------------------------------------- /public/static/images/phantom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/images/phantom.png -------------------------------------------------------------------------------- /public/static/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/favicon.ico -------------------------------------------------------------------------------- /public/static/screenshots/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/screenshots/list.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon.png -------------------------------------------------------------------------------- /public/static/screenshots/billing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/screenshots/billing.png -------------------------------------------------------------------------------- /public/static/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/static/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/static/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /public/static/favicons/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/ms-icon-70x70.png -------------------------------------------------------------------------------- /public/static/screenshots/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/screenshots/dashboard.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/static/favicons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/static/favicons/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/static/favicons/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/static/screenshots/landingspage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/screenshots/landingspage.png -------------------------------------------------------------------------------- /public/static/favicons/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/android-icon-36x36.png -------------------------------------------------------------------------------- /public/static/favicons/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/android-icon-48x48.png -------------------------------------------------------------------------------- /public/static/favicons/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/android-icon-72x72.png -------------------------------------------------------------------------------- /public/static/favicons/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/android-icon-96x96.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon-180x180.png -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "import/no-anonymous-default-export": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/static/favicons/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/android-icon-144x144.png -------------------------------------------------------------------------------- /public/static/favicons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/android-icon-192x192.png -------------------------------------------------------------------------------- /public/static/favicons/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saas98/Seifylabs/HEAD/public/static/favicons/apple-icon-precomposed.png -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_FIREBASE_API_KEY= 2 | NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= 3 | NEXT_PUBLIC_FIREBASE_PROJECT_ID= 4 | NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= 5 | NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= 6 | NEXT_PUBLIC_FIREBASE_APP_ID= -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /public/static/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /components/motion/box.tsx: -------------------------------------------------------------------------------- 1 | import { chakra, ChakraProps, HTMLChakraProps } from '@chakra-ui/react' 2 | import { motion, HTMLMotionProps } from 'framer-motion' 3 | 4 | export interface IMotionBoxProps 5 | extends HTMLMotionProps<'div'>, 6 | Omit {} 7 | 8 | export const MotionBox = motion(chakra.div) 9 | -------------------------------------------------------------------------------- /theme/components/index.ts: -------------------------------------------------------------------------------- 1 | import { default as Button } from './button' 2 | import { default as CTA } from './cta' 3 | import { default as Features, Feature } from './features' 4 | import { default as SectionTitle } from './section-title' 5 | import { default as Section } from './section' 6 | 7 | export default { Button, CTA, Features, Feature, SectionTitle, Section } 8 | -------------------------------------------------------------------------------- /theme/foundations/typography.ts: -------------------------------------------------------------------------------- 1 | export const fontSizes = { 2 | xs: '0.75rem', 3 | sm: '0.8125rem', 4 | md: '0.875rem', 5 | lg: '1', 6 | xl: '1.125rem', 7 | '2xl': '1.25rem', 8 | '3xl': '1.5rem', 9 | '4xl': '1.875rem', 10 | '5xl': '2', 11 | '6xl': '3.5rem', 12 | '7xl': '3rem', 13 | '8xl': '4rem', 14 | '9xl': '5rem', 15 | '10xl': '6rem', 16 | } 17 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /components/motion/page-transition.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { HTMLMotionProps } from 'framer-motion' 3 | 4 | import { MotionBox, IMotionBoxProps } from './box' 5 | 6 | export const PageTransition: React.FC = (props) => ( 7 | 12 | ) 13 | -------------------------------------------------------------------------------- /theme/components/section.ts: -------------------------------------------------------------------------------- 1 | const Section = { 2 | baseStyle: { 3 | pt: 28, 4 | pb: 28, 5 | px: [4, null], 6 | }, 7 | variants: { 8 | subtle: {}, 9 | solid: { 10 | bg: 'primary.400', 11 | }, 12 | alternate: ({ colorMode }: any) => ({ 13 | bg: colorMode === 'dark' ? 'gray.800' : 'gray.50', 14 | }), 15 | }, 16 | defaultProps: { 17 | variant: 'subtle', 18 | }, 19 | } 20 | 21 | export default Section 22 | -------------------------------------------------------------------------------- /components/button-link/button-link.tsx: -------------------------------------------------------------------------------- 1 | import { Button, ButtonProps } from '@chakra-ui/react' 2 | import NextLink, { LinkProps } from 'next/link' 3 | 4 | export type ButtonLinkProps = LinkProps & ButtonProps 5 | 6 | export const ButtonLink: React.FC = ({ 7 | href, 8 | children, 9 | ...props 10 | }) => { 11 | return ( 12 | 13 | 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /components/layout/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | import { IconButton, useColorMode } from '@chakra-ui/react' 2 | import { FiMoon, FiSun } from 'react-icons/fi' 3 | 4 | const ThemeToggle = () => { 5 | const { colorMode, toggleColorMode } = useColorMode() 6 | return ( 7 | : } 11 | borderRadius="md" 12 | onClick={toggleColorMode} 13 | /> 14 | ) 15 | } 16 | 17 | export default ThemeToggle 18 | -------------------------------------------------------------------------------- /components/seo/seo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { NextSeo, NextSeoProps } from 'next-seo' 3 | import siteConfig from 'data/config' 4 | 5 | export interface ISEOProps extends NextSeoProps {} 6 | 7 | export const SEO = ({ title, description, ...props }: ISEOProps) => ( 8 | 16 | ) 17 | -------------------------------------------------------------------------------- /hooks/use-route-changed.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import { useEffect } from 'react' 3 | 4 | const useRouteChanged = (fn: () => void) => { 5 | const router = useRouter() 6 | useEffect(() => { 7 | const handleRouteChange = (url: string) => { 8 | fn() 9 | console.log('App is changing to: ', url) 10 | } 11 | 12 | router.events.on('routeChangeComplete', handleRouteChange) 13 | 14 | return () => { 15 | router.events.off('routeChangeComplete', handleRouteChange) 16 | } 17 | }, [router.events, fn]) 18 | } 19 | 20 | export default useRouteChanged 21 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: false, 4 | webpack(config) { 5 | config.module.rules.push({ 6 | test: /\.svg$/, 7 | use: [ 8 | { 9 | loader: '@svgr/webpack', 10 | options: { 11 | svgoConfig: { 12 | plugins: [ 13 | { 14 | name: 'removeViewBox', 15 | active: false, 16 | }, 17 | ], 18 | }, 19 | }, 20 | }, 21 | ], 22 | }) 23 | return config 24 | }, 25 | } 26 | 27 | export default nextConfig 28 | -------------------------------------------------------------------------------- /components/motion/fall-in-place.tsx: -------------------------------------------------------------------------------- 1 | import { MotionBox, IMotionBoxProps } from './box' 2 | import React from 'react' 3 | 4 | export const FallInPlace: React.FC = ( 5 | props 6 | ) => { 7 | const { children, delay = 0.2, ...rest } = props 8 | return ( 9 | 20 | {children} 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /components/typography/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | chakra, 3 | HTMLChakraProps, 4 | Text, 5 | TextProps, 6 | useColorModeValue, 7 | } from '@chakra-ui/react' 8 | 9 | export const Em: React.FC> = ({ children, ...props }) => { 10 | return ( 11 | 17 | {children} 18 | 19 | ) 20 | } 21 | 22 | // @todo make this configurable 23 | export const Br: React.FC> = (props) => { 24 | return ( 25 | 26 |
27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /theme/components/button.ts: -------------------------------------------------------------------------------- 1 | import { mode } from '@chakra-ui/theme-tools' 2 | 3 | type Dict = Record 4 | 5 | export default { 6 | variants: { 7 | 'nav-link': (props: Dict) => { 8 | const { isActive } = props 9 | 10 | const hoverColor = mode('gray.900', 'white')(props) 11 | return { 12 | outline: 'none', 13 | fontWeight: '500', 14 | color: isActive 15 | ? hoverColor 16 | : mode('gray.700', 'whiteAlpha.700')(props), 17 | transition: 'color .2s ease-in', 18 | _hover: { 19 | textDecoration: 'none', 20 | color: hoverColor, 21 | }, 22 | } 23 | }, 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /components/motion/float.tsx: -------------------------------------------------------------------------------- 1 | import { MotionBox, IMotionBoxProps } from './box' 2 | import React from 'react' 3 | 4 | export const Float: React.FC< 5 | IMotionBoxProps & { delay?: number; steps?: number[] } 6 | > = (props) => { 7 | const { children, delay = 0.2, steps = [10, -10, 10], ...rest } = props 8 | return ( 9 | 22 | {children} 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /components/nav-link/nav-link.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, Button, ButtonProps } from "@chakra-ui/react"; 2 | 3 | import Link from "next/link"; 4 | 5 | export interface INavLinkProps extends ButtonProps { 6 | isActive?: boolean; 7 | href?: string; 8 | id?: string; 9 | } 10 | 11 | export const NavLink = forwardRef((props, ref) => { 12 | const { href, type, isActive, ...rest } = props; 13 | 14 | return ( 15 | 18 | ) : ( 19 | 24 | )} 25 | 26 | 27 | ) 28 | } 29 | 30 | export default Profile; 31 | 32 | export const SignupModal = ({open, setOpen} : {open: boolean, setOpen: Function}) => { 33 | const { setUser } = useApp(); 34 | const router = useRouter(); 35 | 36 | const register = (res: object | null) => { 37 | if (res) { 38 | const { address, signature } = res as WalletSignResult; 39 | findOrAddUser(address, signature).then(user => { 40 | if (setUser) { 41 | setUser(user); 42 | localStorage.setItem('user', JSON.stringify(user ?? undefined)); 43 | if (user) { 44 | router.push('/dashboard', {scroll: false}); 45 | } else { 46 | toast.error("Wrong signature! Try again!"); 47 | router.push('/', {scroll: false}); 48 | } 49 | } 50 | }) 51 | } 52 | } 53 | return ( 54 | { 55 | setOpen(false); 56 | }}> 57 | 58 | 59 | 60 | 61 | Sign up with your wallet 62 | Connect wallet to assign payee address 63 | 64 | { 65 | connectEthereum().then(res => register(res)); 66 | setOpen(false) 67 | }}> 68 | Ethereum 69 | Ethereum 70 | 71 | { 72 | connectSolana().then(res => register(res)); 73 | setOpen(false) 74 | }}> 75 | Solana 76 | Solana 77 | 78 | 79 | 80 | 81 | 82 | ); 83 | } -------------------------------------------------------------------------------- /components/logos/react.tsx: -------------------------------------------------------------------------------- 1 | import { useColorModeValue } from '@chakra-ui/react' 2 | import * as React from 'react' 3 | 4 | export const ReactLogo = (props) => { 5 | return ( 6 | 14 | 18 | 22 | 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /data/appulse.tsx: -------------------------------------------------------------------------------- 1 | import { useColorModeValue } from '@chakra-ui/react' 2 | 3 | export const Logo = () => { 4 | return ( 5 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /components/announcement-banner/announcement-banner.tsx: -------------------------------------------------------------------------------- 1 | import NextLink from "next/link"; 2 | import { 3 | Box, 4 | Container, 5 | Flex, 6 | HStack, 7 | Icon, 8 | LinkBox, 9 | LinkOverlay, 10 | useColorModeValue, 11 | Button, 12 | } from "@chakra-ui/react"; 13 | import { 14 | Banner, 15 | BannerActions, 16 | BannerContent, 17 | BannerDescription, 18 | BannerTitle, 19 | } from "@saas-ui/react"; 20 | import { FiArrowRight } from "react-icons/fi"; 21 | import { FallInPlace } from "../motion/fall-in-place"; 22 | 23 | export interface IAnnouncementBannerProps { 24 | title: string; 25 | description: string; 26 | href: string; 27 | action?: string; 28 | } 29 | 30 | export const AnnouncementBanner: React.FC = ( 31 | props 32 | ) => { 33 | const { title, description, href, action } = props; 34 | if (!title) { 35 | return null; 36 | } 37 | 38 | return ( 39 | 40 | 41 | 42 | 43 | 84 | 85 | 86 | {title} 87 | 88 | 92 | 93 | {action && ( 94 | 95 | 113 | 114 | )} 115 | 116 | 117 | 118 | 119 | 120 | 121 | ); 122 | }; 123 | -------------------------------------------------------------------------------- /components/pricing/pricing.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Heading, 4 | HStack, 5 | Icon, 6 | SimpleGrid, 7 | StackProps, 8 | Text, 9 | VStack, 10 | } from "@chakra-ui/react"; 11 | import { 12 | ButtonLink, 13 | ButtonLinkProps, 14 | } from "components/button-link/button-link"; 15 | import { BackgroundGradient } from "components/gradients/background-gradient"; 16 | import { Section, ISectionProps, SectionTitle } from "components/section"; 17 | import React from "react"; 18 | import { FiCheck } from "react-icons/fi"; 19 | 20 | export interface IPricingPlan { 21 | id: string; 22 | title: React.ReactNode; 23 | description: React.ReactNode; 24 | price: React.ReactNode; 25 | features: Array; 26 | action: ButtonLinkProps & { label?: string }; 27 | isRecommended?: boolean; 28 | } 29 | 30 | export interface IPricingProps extends ISectionProps { 31 | description: React.ReactNode; 32 | plans: Array; 33 | } 34 | 35 | export const Pricing: React.FC = (props) => { 36 | const { children, plans, title, description, ...rest } = props; 37 | 38 | return ( 39 |
40 | 41 | 42 | 43 | 44 | 45 | {plans?.map((plan) => ( 46 | 63 | 64 | {plan.features.map((feature, i) => 65 | feature ? ( 66 | 67 | ) : ( 68 |
69 | ) 70 | )} 71 |
72 | 73 |
74 | ))} 75 |
76 | 77 | {children} 78 |
79 |
80 | ); 81 | }; 82 | 83 | const PricingFeatures: React.FC> = ({ 84 | children, 85 | }) => { 86 | return ( 87 | 94 | {children} 95 | 96 | ); 97 | }; 98 | 99 | export interface IPricingFeatureProps { 100 | title: React.ReactNode; 101 | iconColor?: string; 102 | } 103 | 104 | const PricingFeature: React.FC = (props) => { 105 | const { title, iconColor = "primary.500" } = props; 106 | return ( 107 | 108 | 109 | 110 | {title} 111 | 112 | 113 | ); 114 | }; 115 | 116 | export interface IPricingBoxProps extends Omit { 117 | title: React.ReactNode; 118 | description: React.ReactNode; 119 | price: React.ReactNode; 120 | } 121 | 122 | const PricingBox: React.FC = (props) => { 123 | const { title, description, price, children, ...rest } = props; 124 | return ( 125 | 140 | 141 | {title} 142 | 143 | {description} 144 | 145 | {price} 146 | 147 | 148 | {children} 149 | 150 | 151 | ); 152 | }; 153 | -------------------------------------------------------------------------------- /utils/firestore.tsx: -------------------------------------------------------------------------------- 1 | import { addDoc, and, collection, doc, DocumentData, endBefore, getCountFromServer, getDoc, getDocs, getFirestore, limit, or, orderBy, Query, query, QueryCompositeFilterConstraint, QueryConstraint, QueryDocumentSnapshot, QueryNonFilterConstraint, setDoc, startAfter, where } from "firebase/firestore"; 2 | import firebaseApp from './firebaseConfig'; 3 | import { Filter, User } from "./types"; 4 | 5 | const db = getFirestore(firebaseApp); 6 | export default db; 7 | 8 | export const saveDocument = async (collectionName: string, document: object) : Promise => { 9 | const snapshot = await addDoc(collection(db, collectionName), document); 10 | return snapshot.id; 11 | } 12 | 13 | export const getDocument = async (collectionName: string, id: string) : Promise => { 14 | const snapshot = await getDoc(doc(db, collectionName, id)); 15 | if (snapshot.exists()) { 16 | return snapshot.data(); 17 | } else { 18 | return null; 19 | } 20 | } 21 | 22 | export const getDocuments = async (q: Query) : Promise => { 23 | const docs = await getDocs(q); 24 | if (docs.empty) { 25 | return []; 26 | } else { 27 | return docs.docs; 28 | } 29 | } 30 | 31 | export const getCountOfCollection = async (collectionName: string) : Promise => { 32 | const snapshot = await getCountFromServer(collection(db, collectionName)); 33 | return snapshot.data().count; 34 | } 35 | 36 | export const updateDocument = async (collectionName: string, id: string, document: object) => { 37 | await setDoc(doc(db, collectionName, id), document); 38 | } 39 | 40 | export const genQuery = (collectionName:string, filter: Filter) : Query => { 41 | const constraints : QueryConstraint[] = []; 42 | if (filter.orderBy) { 43 | constraints.push(orderBy(filter.orderBy.fieldName, filter.orderBy.direct)); 44 | } 45 | if (filter.limit) { 46 | constraints.push(limit(filter.limit)) 47 | } 48 | if (filter.endBefore) { 49 | constraints.push(endBefore(filter.endBefore)) 50 | } 51 | if (filter.startAfter) { 52 | constraints.push(startAfter(startAfter)) 53 | } 54 | if (filter.orWhere) { 55 | const filterConstraint = filter.where ? filter.where?.map(w => where(w.fieldName, w.operator, w.value)): []; 56 | return query(collection(db, collectionName), and(...filterConstraint, or(...filter.orWhere.map(w => where(w.fieldName, w.operator, w.value))))) 57 | } 58 | if (filter.where) { 59 | filter.where.map(w => constraints.push(where(w.fieldName, w.operator, w.value))) 60 | } 61 | return query(collection(db, collectionName), ...constraints); 62 | 63 | } 64 | 65 | export const saveProject = async (project: object) => { 66 | return await saveDocument("projects", project); 67 | } 68 | 69 | export const updateProject = async(id: string, project: object) => { 70 | await updateDocument("projects", id, project); 71 | } 72 | 73 | export const getProject = async (id: string) : Promise => { 74 | return await getDocument("projects", id); 75 | } 76 | 77 | export const getProjectsCount = async () : Promise => { 78 | return await getCountOfCollection("projects"); 79 | } 80 | 81 | export const saveUser = async (user: object) : Promise => { 82 | return await saveDocument("users", user); 83 | } 84 | 85 | export const getUserById = async (id: string) : Promise => { 86 | return await getDocument("users", id); 87 | } 88 | 89 | export const getUserByAddress = async (address: string, signature: string) : Promise => { 90 | const res = await getDocs(genQuery("users", { 91 | where: [{ 92 | fieldName: "address", 93 | operator: "==", 94 | value: address 95 | }, { 96 | fieldName: "signature", 97 | operator: "==", 98 | value: signature 99 | }] 100 | })); 101 | if (res.empty) 102 | return null; 103 | return res.docs[0]; 104 | } 105 | 106 | export const updateUser = async (id: string, user: object) => { 107 | await updateDocument("users", id, user); 108 | } 109 | 110 | export const findOrAddUser = async (address: string, signature: string) : Promise => { 111 | const res = await getUserById(address); 112 | if (res) { 113 | return res.signature === signature ? { 114 | ...res, 115 | id: address 116 | } as User : null; 117 | } else { 118 | await setDoc(doc(db, "users", address), { 119 | address, 120 | signature 121 | }) 122 | return { 123 | id: address, 124 | address, 125 | signature, 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /components/mobile-nav/mobile-nav.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | BoxProps, 4 | Button, 5 | Center, 6 | CenterProps, 7 | CloseButton, 8 | Flex, 9 | Grid, 10 | GridItem, 11 | HStack, 12 | IconButton, 13 | IconButtonProps, 14 | LinkProps, 15 | Stack, 16 | useBreakpointValue, 17 | useColorModeValue, 18 | useUpdateEffect, 19 | } from '@chakra-ui/react' 20 | import { AnimatePresence, motion, useElementScroll } from 'framer-motion' 21 | import useRouteChanged from 'hooks/use-route-changed' 22 | // import { getRoutes } from '@/layouts/mdx' 23 | import NextLink from 'next/link' 24 | import { useRouter } from 'next/router' 25 | import * as React from 'react' 26 | import { AiOutlineMenu } from 'react-icons/ai' 27 | import { RemoveScroll } from 'react-remove-scroll' 28 | 29 | import siteConfig from 'data/config' 30 | import { Logo } from 'components/layout/logo' 31 | import { Link } from '@saas-ui/react' 32 | 33 | interface INavLinkProps extends LinkProps { 34 | label: string 35 | href?: string 36 | isActive?: boolean 37 | } 38 | 39 | function NavLink({ href, children, isActive, ...rest }: INavLinkProps) { 40 | const { pathname } = useRouter() 41 | const bgActiveHoverColor = useColorModeValue('gray.100', 'whiteAlpha.100') 42 | 43 | const [, group] = href?.split('/') || [] 44 | isActive = isActive ?? pathname.includes(group) 45 | 46 | return ( 47 | 64 | {children} 65 | 66 | ) 67 | } 68 | 69 | interface IMobileNavContentProps { 70 | isOpen?: boolean 71 | onClose?: () => void 72 | } 73 | 74 | export function MobileNavContent(props: IMobileNavContentProps) { 75 | const { isOpen, onClose = () => {} } = props 76 | const closeBtnRef = React.useRef(null) 77 | const { pathname } = useRouter() 78 | const bgColor = useColorModeValue('whiteAlpha.900', 'blackAlpha.900') 79 | 80 | useRouteChanged(onClose) 81 | 82 | /** 83 | * Scenario: Menu is open on mobile, and user resizes to desktop/tablet viewport. 84 | * Result: We'll close the menu 85 | */ 86 | const showOnBreakpoint = useBreakpointValue({ base: true, lg: false }) 87 | 88 | React.useEffect(() => { 89 | if (showOnBreakpoint == false) { 90 | onClose() 91 | } 92 | }, [showOnBreakpoint, onClose]) 93 | 94 | useUpdateEffect(() => { 95 | if (isOpen) { 96 | requestAnimationFrame(() => { 97 | closeBtnRef.current?.focus() 98 | }) 99 | } 100 | }, [isOpen]) 101 | 102 | return ( 103 | 104 | {isOpen && ( 105 | 106 | 112 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | {siteConfig.header.links.map( 134 | ({ href, id, label, ...props }, i) => { 135 | return ( 136 | 141 | {label} 142 | 143 | ) 144 | } 145 | )} 146 | 147 | 148 | 149 | 150 | 151 | )} 152 | 153 | ) 154 | } 155 | 156 | export const MobileNavButton = React.forwardRef( 157 | (props: IconButtonProps, ref: React.Ref) => { 158 | return ( 159 | } 166 | {...props} 167 | aria-label="Open menu" 168 | /> 169 | ) 170 | } 171 | ) 172 | 173 | MobileNavButton.displayName = 'MobileNavButton' 174 | -------------------------------------------------------------------------------- /public/static/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /pages/dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Button, Container, Flex, Image, Table, TableContainer, Tag, Tbody, Td, Text, Th, Thead, Tr } from "@chakra-ui/react"; 3 | import { SearchInput } from "@saas-ui/react"; 4 | import { ButtonLink } from "components/button-link"; 5 | import { genQuery, getDocuments, getProjectsCount } from "utils/firestore"; 6 | import { QueryDocumentSnapshot } from "firebase/firestore"; 7 | import { ProjectType } from "utils/types"; 8 | import useApp from "components/context/app-context"; 9 | 10 | const Dashboard = () => { 11 | const { user, setUser, checkIfAuthenticated } = useApp(); 12 | const [projects, setProjects] = React.useState([]); 13 | const [keyword, setKeyword] = React.useState(''); 14 | const [page, setPage] = React.useState(1); 15 | const [pageSize, setPageSize] = React.useState(10); 16 | const [count, setCount] = React.useState(0); 17 | const [wholeCount, setWholeCount] = React.useState(0); 18 | const [firstVisible, setFirstVisible] = React.useState(); 19 | const [lastVisible, setLastVisible] = React.useState(); 20 | const [q, setQuery] = React.useState(genQuery('projects', { 21 | orderBy: { 22 | fieldName: "createdAt", 23 | direct: "desc" 24 | }, 25 | limit: pageSize 26 | })); 27 | 28 | const fetch = async () => { 29 | getProjectsCount().then(count => setWholeCount(count)) 30 | getDocuments(q).then(docs => { 31 | if (docs.length == 0) { 32 | setProjects([]); 33 | } else { 34 | setFirstVisible(docs[0]); 35 | setLastVisible(docs[docs.length - 1]); 36 | setCount(docs.length); 37 | setProjects(docs.map(doc => ({...doc.data(), id: doc.id} as ProjectType))) 38 | } 39 | }); 40 | } 41 | 42 | React.useEffect(() => { 43 | checkIfAuthenticated && checkIfAuthenticated(); 44 | }, [checkIfAuthenticated]) 45 | 46 | React.useEffect(() => { 47 | fetch(); 48 | }, [q]) 49 | 50 | return ( 51 | 52 | 53 | 54 | 55 | My Escrow Project 56 | Viewing {count} Projects 57 | 58 | 59 | setKeyword(e.target.value)}/> 60 | + Create 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {projects.map((project, index) => ( 77 | 80 | 86 | 106 | 107 | 108 | 109 | 110 | 111 | ))} 112 | 113 |
Escrow ProjectStatusRoleAmountDate
81 | 82 | {project.wallet} 83 | {project.title} 84 | 85 | 87 | {project.status ?? "Pending"} 105 | {"Payer"}{project.amount} {project.token}{`${project.deadline}`.substring(0, 10)}View
114 |
115 | 116 | 117 | Showing {(page - 1) * pageSize + 1} to {(page - 1) * pageSize + count} of {wholeCount} results 118 | 119 | 120 | 131 | 142 | 143 | 144 |
145 |
146 | ) 147 | } 148 | 149 | export default Dashboard; -------------------------------------------------------------------------------- /pages/project/[link].tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Container, Flex, Grid, GridItem, Heading, HStack, Image, Modal, ModalCloseButton, ModalContent, ModalOverlay, Text, Textarea } from "@chakra-ui/react"; 2 | import * as React from "react"; 3 | import { useRouter } from "next/router"; 4 | import { DatePicker, DatePickerCalendar, DatePickerTimeField } from "@saas-ui/date-picker"; 5 | import { CheckCircleIcon } from "@chakra-ui/icons"; 6 | import { getProject, updateProject } from "utils/firestore"; 7 | import { formatWalletAddress } from "utils/web3"; 8 | import { ProjectType } from "utils/types"; 9 | import { parseDate } from "@internationalized/date"; 10 | 11 | const Project = () => { 12 | 13 | const router = useRouter(); 14 | const [balance, setBalance] = React.useState(0); 15 | const [payeeAddress, setPayeeAddress] = React.useState('0xf76359e6a368b44269601354cB09d94365Cd70D7') 16 | const [open, setOpen] = React.useState(false); 17 | const [step, setStep] = React.useState(1); 18 | const [project, setProject] = React.useState(); 19 | const [spinner, setSpinner] = React.useState(false); 20 | 21 | const accept = async () => { 22 | if (!payeeAddress) return; 23 | setSpinner(true); 24 | await updateProject(`${router.query.link}`, { 25 | ...project, 26 | status: "Accepted", 27 | payeeAddress 28 | }) 29 | setStep(2); 30 | setSpinner(false); 31 | } 32 | const reject = async () => { 33 | await updateProject(`${router.query.link}`, { 34 | ...project, 35 | status: "Rejected", 36 | }) 37 | router.push('/dashboard'); 38 | } 39 | 40 | React.useEffect(() => { 41 | async function fetch() { 42 | if (router.query.link) { 43 | const project = await getProject(`${router.query.link}`); 44 | setProject({...project} as ProjectType); 45 | } 46 | } 47 | fetch(); 48 | }, [router.query.link]) 49 | 50 | return ( 51 | 52 | 53 | Project Proposal of {`"${project?.title}"`} 54 | 55 | 56 | Scope of Work 57 |