├── .github └── logo.png ├── public ├── favicon.ico ├── loading.gif ├── principal.jpg ├── vantagem-1.jpg ├── vantagem-2.jpg └── vantagem-3.jpg ├── .env.example ├── postcss.config.js ├── src ├── logic │ ├── core │ │ ├── financas │ │ │ ├── TipoTransacao.ts │ │ │ ├── Transacao.ts │ │ │ └── ServicosTransacao.ts │ │ ├── comum │ │ │ └── Id.ts │ │ ├── usuario │ │ │ ├── Usuario.ts │ │ │ └── ServicosUsuario.ts │ │ └── index.ts │ ├── utils │ │ ├── Texto.ts │ │ ├── Cpf.ts │ │ ├── Telefone.ts │ │ ├── Dinheiro.ts │ │ └── Data.ts │ └── firebase │ │ ├── config │ │ └── app.ts │ │ ├── auth │ │ └── Autenticacao.ts │ │ └── db │ │ └── Colecao.ts ├── components │ ├── landing │ │ ├── comum │ │ │ ├── Logo.tsx │ │ │ ├── Area.tsx │ │ │ └── ImagemResponsiva.tsx │ │ ├── cabecalho │ │ │ ├── index.tsx │ │ │ ├── MenuItem.tsx │ │ │ └── Menu.tsx │ │ ├── index.tsx │ │ ├── rodape │ │ │ ├── RedeSocial.tsx │ │ │ ├── RedeSociais.tsx │ │ │ └── index.tsx │ │ ├── destaque │ │ │ ├── index.tsx │ │ │ └── Slogan.tsx │ │ ├── vantagens │ │ │ ├── Vantagem.tsx │ │ │ └── index.tsx │ │ └── depoimentos │ │ │ ├── Depoimento.tsx │ │ │ └── index.tsx │ ├── template │ │ ├── Conteudo.tsx │ │ ├── Cabecalho.tsx │ │ ├── Carregando.tsx │ │ ├── NaoEncontrado.tsx │ │ ├── BoasVindas.tsx │ │ ├── Pagina.tsx │ │ ├── TituloPagina.tsx │ │ ├── MiniFormulario.tsx │ │ ├── MenuUsuario.tsx │ │ └── CampoMesAno.tsx │ ├── autenticacao │ │ └── ForcarAutenticacao.tsx │ ├── financas │ │ ├── SumarioItem.tsx │ │ ├── Sumario.tsx │ │ ├── Lista.tsx │ │ ├── Grade.tsx │ │ ├── index.tsx │ │ └── Formulario.tsx │ └── usuario │ │ └── Formularios.tsx ├── data │ ├── constants │ │ ├── usuarioFalso.ts │ │ └── transacoesFalsas.ts │ ├── hooks │ │ ├── useFormulario.ts │ │ └── useTransacao.ts │ └── contexts │ │ └── AutenticacaoContext.tsx ├── pages │ ├── _document.tsx │ ├── api │ │ └── hello.ts │ ├── index.tsx │ ├── _app.tsx │ └── usuario.tsx └── styles │ └── globals.css ├── tailwind.config.js ├── next.config.js ├── .gitignore ├── tsconfig.json ├── package.json ├── LICENSE.md └── README.md /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformacaodev/bitcent/HEAD/.github/logo.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformacaodev/bitcent/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformacaodev/bitcent/HEAD/public/loading.gif -------------------------------------------------------------------------------- /public/principal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformacaodev/bitcent/HEAD/public/principal.jpg -------------------------------------------------------------------------------- /public/vantagem-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformacaodev/bitcent/HEAD/public/vantagem-1.jpg -------------------------------------------------------------------------------- /public/vantagem-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformacaodev/bitcent/HEAD/public/vantagem-2.jpg -------------------------------------------------------------------------------- /public/vantagem-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformacaodev/bitcent/HEAD/public/vantagem-3.jpg -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_FIREBASE_PROJECT_ID= 2 | NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= 3 | NEXT_PUBLIC_FIREBASE_API_KEY= -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/logic/core/financas/TipoTransacao.ts: -------------------------------------------------------------------------------- 1 | export enum TipoTransacao { 2 | RECEITA = 'receita', 3 | DESPESA = 'despesa', 4 | } -------------------------------------------------------------------------------- /src/logic/core/comum/Id.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid' 2 | 3 | export default class Id { 4 | static novo(): string { 5 | return uuid() 6 | } 7 | } -------------------------------------------------------------------------------- /src/logic/core/usuario/Usuario.ts: -------------------------------------------------------------------------------- 1 | export default interface Usuario { 2 | id: string 3 | nome: string 4 | email: string 5 | imagemUrl: string | null 6 | cpf?: string 7 | telefone?: string 8 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/**/*.{js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | -------------------------------------------------------------------------------- /src/components/landing/comum/Logo.tsx: -------------------------------------------------------------------------------- 1 | export default function Logo() { 2 | return ( 3 |
4 | Bit 5 | CENT 6 |
7 | ) 8 | } -------------------------------------------------------------------------------- /src/data/constants/usuarioFalso.ts: -------------------------------------------------------------------------------- 1 | import Id from "@/logic/core/comum/Id"; 2 | import Usuario from "@/logic/core/usuario/Usuario"; 3 | 4 | export default { 5 | id: Id.novo(), 6 | nome: 'João da Silva', 7 | email: 'jjjjoao@xmail.com', 8 | imagemUrl: null, 9 | } as Usuario -------------------------------------------------------------------------------- /src/logic/utils/Texto.ts: -------------------------------------------------------------------------------- 1 | export default class Texto { 2 | static entre(valor: string, min: number, max: number, trim: boolean = true): boolean { 3 | const valorFinal = (trim ? valor?.trim?.() : valor) ?? '' 4 | return valorFinal.length >= min && valorFinal.length <= max 5 | } 6 | } -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | images: { 5 | domains: [ 6 | 'source.unsplash.com', 7 | 'images.unsplash.com', 8 | 'lh3.googleusercontent.com', 9 | ] 10 | } 11 | } 12 | 13 | module.exports = nextConfig 14 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;500;600;700;800;900;1000&display=swap'); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | html { 8 | font-family: 'Nunito', sans-serif; 9 | background-color: #000; 10 | color: #fff; 11 | } -------------------------------------------------------------------------------- /src/logic/firebase/config/app.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp, FirebaseApp } from 'firebase/app' 2 | 3 | const app: FirebaseApp = initializeApp({ 4 | apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, 5 | authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, 6 | projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, 7 | }) 8 | 9 | export { app } -------------------------------------------------------------------------------- /src/logic/core/index.ts: -------------------------------------------------------------------------------- 1 | import ServicosTransacao from "./financas/ServicosTransacao"; 2 | import ServicosUsuario from "./usuario/ServicosUsuario"; 3 | 4 | class Servicos { 5 | get transacao() { return new ServicosTransacao() } 6 | get usuario() { return new ServicosUsuario() } 7 | } 8 | 9 | const servicos = new Servicos() 10 | export default servicos -------------------------------------------------------------------------------- /src/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 | -------------------------------------------------------------------------------- /src/components/template/Conteudo.tsx: -------------------------------------------------------------------------------- 1 | interface ConteudoProps { 2 | children: any 3 | className?: string 4 | } 5 | 6 | export default function Conteudo(props: ConteudoProps) { 7 | return ( 8 |
12 | {props.children} 13 |
14 | ) 15 | } -------------------------------------------------------------------------------- /src/logic/core/financas/Transacao.ts: -------------------------------------------------------------------------------- 1 | import { TipoTransacao } from "./TipoTransacao" 2 | 3 | export default interface Transacao { 4 | id?: string 5 | descricao: string 6 | valor: number 7 | data: Date 8 | tipo: TipoTransacao 9 | } 10 | 11 | export const transacaoVazia: Transacao = { 12 | descricao: '', 13 | valor: 0, 14 | data: new Date(), 15 | tipo: TipoTransacao.DESPESA 16 | } -------------------------------------------------------------------------------- /src/components/template/Cabecalho.tsx: -------------------------------------------------------------------------------- 1 | import BoasVindas from "./BoasVindas"; 2 | import MenuUsuario from "./MenuUsuario"; 3 | 4 | export default function Cabecalho() { 5 | return ( 6 |
10 | 11 | 12 |
13 | ) 14 | } -------------------------------------------------------------------------------- /src/components/landing/cabecalho/index.tsx: -------------------------------------------------------------------------------- 1 | import Area from "../comum/Area"; 2 | import Logo from "../comum/Logo"; 3 | import Menu from "./Menu"; 4 | 5 | export default function Cabecalho() { 6 | return ( 7 | 8 |
9 | 10 | 11 |
12 | 13 | ) 14 | } -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Financas from "@/components/financas"; 2 | import Landing from "@/components/landing"; 3 | import Carregando from "@/components/template/Carregando"; 4 | import AutenticacaoContext from "@/data/contexts/AutenticacaoContext"; 5 | import { useContext } from "react"; 6 | 7 | export default function Home() { 8 | const { usuario, carregando } = useContext(AutenticacaoContext) 9 | 10 | if(carregando) return 11 | return usuario ? : 12 | } 13 | -------------------------------------------------------------------------------- /src/logic/utils/Cpf.ts: -------------------------------------------------------------------------------- 1 | export default class Cpf { 2 | private static _padrao = '???.???.???-??' 3 | 4 | static formatar(valor: string): string { 5 | const nums = Cpf.desformatar(valor).split('') 6 | return nums.reduce((formatado: string, num: string) => { 7 | return formatado.replace('?', num) 8 | }, Cpf._padrao).split('?')[0].replace(/[-.]$/, '') 9 | } 10 | 11 | static desformatar(valor: string): string { 12 | return valor.replace(/[^0-9]+/g, '') 13 | } 14 | } -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AutenticacaoProvider } from '@/data/contexts/AutenticacaoContext' 2 | import '@/styles/globals.css' 3 | import { MantineProvider } from '@mantine/core' 4 | import type { AppProps } from 'next/app' 5 | 6 | export default function App({ Component, pageProps }: AppProps) { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/components/template/Carregando.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import loading from '../../../public/loading.gif' 3 | import Pagina from './Pagina' 4 | 5 | export default function Carregando() { 6 | return ( 7 | 8 | loading 15 | 16 | ) 17 | } -------------------------------------------------------------------------------- /src/components/template/NaoEncontrado.tsx: -------------------------------------------------------------------------------- 1 | import { IconCircleX } from "@tabler/icons-react"; 2 | 3 | interface NaoEncontradoProps { 4 | children: any 5 | } 6 | 7 | export default function NaoEncontrado(props: NaoEncontradoProps) { 8 | return ( 9 |
13 | 14 | {props.children} 15 |
16 | ) 17 | } -------------------------------------------------------------------------------- /src/components/landing/index.tsx: -------------------------------------------------------------------------------- 1 | import Pagina from "../template/Pagina"; 2 | import Cabecalho from "./cabecalho"; 3 | import Depoimentos from "./depoimentos"; 4 | import Destaque from "./destaque"; 5 | import Rodape from "./rodape"; 6 | import Vantagens from "./vantagens"; 7 | 8 | export default function Landing() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ) 18 | } -------------------------------------------------------------------------------- /src/logic/utils/Telefone.ts: -------------------------------------------------------------------------------- 1 | export default class Telefone { 2 | private static _padrao = '(??) ?????-????' 3 | 4 | static formatar(valor: string): string { 5 | const nums = Telefone.desformatar(valor).split('') 6 | return nums.reduce((formatado: string, num: string) => { 7 | return formatado.replace('?', num) 8 | }, Telefone._padrao).split('?')[0].trim().replace(/[()-]$/, '') 9 | } 10 | 11 | static desformatar(valor: string): string { 12 | return valor.replace(/[^0-9]+/g, '') 13 | } 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /src/logic/utils/Dinheiro.ts: -------------------------------------------------------------------------------- 1 | export default class Dinheiro { 2 | private static _lingua = "pt-BR" 3 | private static _moeda = "BRL" 4 | 5 | static formatar(num: number): string { 6 | return (num ?? 0).toLocaleString(Dinheiro._lingua, { 7 | style: "currency", currency: Dinheiro._moeda 8 | }) 9 | } 10 | 11 | static desformatar(valor: string): number { 12 | const nums = valor.replace(/[^0-9]+/g, "") 13 | const i = nums.length - 2 14 | return Number(`${nums.substring(0, i)}.${nums.substring(i)}`) 15 | } 16 | } -------------------------------------------------------------------------------- /src/components/landing/comum/Area.tsx: -------------------------------------------------------------------------------- 1 | interface AreaProps { 2 | children: any 3 | className?: string 4 | id?: string 5 | } 6 | 7 | export default function Area(props: AreaProps) { 8 | return ( 9 |
13 |
17 | {props.children} 18 |
19 |
20 | ) 21 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "paths": { 18 | "@/*": ["./src/*"] 19 | } 20 | }, 21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /src/components/landing/rodape/RedeSocial.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Link from "next/link" 3 | 4 | interface RedeSocialProps { 5 | icone: any 6 | url: string 7 | } 8 | 9 | export default function RedeSocial(props: RedeSocialProps) { 10 | return ( 11 | 12 |
13 | {React.cloneElement(props.icone, { 14 | size: 35, 15 | strokeWidth: 1, 16 | className: "text-indigo-400", 17 | })} 18 |
19 | 20 | ) 21 | } -------------------------------------------------------------------------------- /src/components/template/BoasVindas.tsx: -------------------------------------------------------------------------------- 1 | // https://unicode-table.com/en/1F44B/ 2 | 3 | import AutenticacaoContext from "@/data/contexts/AutenticacaoContext" 4 | import { useContext } from "react" 5 | 6 | export default function BoasVindas() { 7 | 8 | const { usuario } = useContext(AutenticacaoContext) 9 | 10 | function renderizarNome() { 11 | return ( 12 | 13 | {usuario?.nome?.split(' ')[0]} 14 | 15 | ) 16 | } 17 | 18 | return ( 19 |
20 | Olá {renderizarNome()} 👋 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/landing/comum/ImagemResponsiva.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | 3 | interface ImagemResponsivaProps { 4 | imagem: any 5 | className?: string 6 | } 7 | 8 | export default function ImagemResponsiva(props: ImagemResponsivaProps) { 9 | return ( 10 | Imagem 22 | ) 23 | } -------------------------------------------------------------------------------- /src/components/landing/rodape/RedeSociais.tsx: -------------------------------------------------------------------------------- 1 | import { IconBrandFacebook, IconBrandGithub, IconBrandInstagram, IconBrandYoutube } from "@tabler/icons-react" 2 | import RedeSocial from "./RedeSocial" 3 | 4 | export default function RedesSociais() { 5 | return ( 6 |
7 | } url="https://www.youtube.com/@cod3r" /> 8 | } url="https://www.instagram.com/cod3rcursos" /> 9 | } url="https://www.facebook.com/cod3rcursos/" /> 10 | } url="https://github.com/cod3rcursos" /> 11 |
12 | ) 13 | } -------------------------------------------------------------------------------- /src/components/landing/destaque/index.tsx: -------------------------------------------------------------------------------- 1 | import Area from "../comum/Area"; 2 | import Slogan from "./Slogan"; 3 | import principal from "../../../../public/principal.jpg" 4 | import ImagemResponsiva from "../comum/ImagemResponsiva"; 5 | 6 | export default function Destaque() { 7 | return ( 8 | 9 |
13 | 14 | 18 |
19 | 20 | ) 21 | } -------------------------------------------------------------------------------- /src/components/autenticacao/ForcarAutenticacao.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { useRouter } from 'next/router' 3 | import AutenticacaoContext from '@/data/contexts/AutenticacaoContext' 4 | import Carregando from '../template/Carregando' 5 | 6 | interface ForcarAutenticacaoProps { 7 | children: any 8 | } 9 | 10 | export default function ForcarAutenticacao(props: ForcarAutenticacaoProps) { 11 | const router = useRouter() 12 | const { usuario, carregando } = useContext(AutenticacaoContext) 13 | 14 | if (carregando) { 15 | return 16 | } else if (usuario?.email) { 17 | return props.children 18 | } else { 19 | router.push('/') 20 | return 21 | } 22 | } -------------------------------------------------------------------------------- /src/data/hooks/useFormulario.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react" 2 | 3 | export default function useFormulario(dadosIniciais?: T) { 4 | const [dados, setDados] = useState(dadosIniciais ?? {} as T) 5 | 6 | const alterarDados = useCallback(function (dados: T) { 7 | setDados(dados) 8 | }, []) 9 | 10 | const alterarAtributo = useCallback(function (atributo: string, fn?: Function) { 11 | return (valorOuEvento: any) => { 12 | const v = valorOuEvento?.target?.value ?? valorOuEvento 13 | setDados({ ...dados, [atributo]: fn?.(v) ?? v }) 14 | } 15 | }, [dados]) 16 | 17 | return { 18 | dados, 19 | alterarDados, 20 | alterarAtributo 21 | } 22 | } -------------------------------------------------------------------------------- /src/components/template/Pagina.tsx: -------------------------------------------------------------------------------- 1 | import ForcarAutenticacao from "../autenticacao/ForcarAutenticacao" 2 | 3 | interface PaginaProps { 4 | externa?: boolean 5 | children: any 6 | className?: string 7 | } 8 | 9 | export default function Pagina(props: PaginaProps) { 10 | function renderizar() { 11 | return ( 12 |
17 | {props.children} 18 |
19 | ) 20 | } 21 | 22 | return props.externa ? renderizar() : ( 23 | 24 | {renderizar()} 25 | 26 | ) 27 | } -------------------------------------------------------------------------------- /src/components/landing/cabecalho/MenuItem.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | 3 | interface MenuItemProps { 4 | children: any 5 | url?: string 6 | onClick?: () => void 7 | className?: string 8 | } 9 | 10 | export default function MenuItem(props: MenuItemProps) { 11 | function renderizarBotao() { 12 | return ( 13 |
18 | {props.children} 19 |
20 | ) 21 | } 22 | 23 | return props.url ? ( 24 | {renderizarBotao()} 25 | ) : renderizarBotao() 26 | } -------------------------------------------------------------------------------- /src/pages/usuario.tsx: -------------------------------------------------------------------------------- 1 | import Cabecalho from "@/components/template/Cabecalho"; 2 | import Conteudo from "@/components/template/Conteudo"; 3 | import Pagina from "@/components/template/Pagina"; 4 | import TituloPagina from "@/components/template/TituloPagina"; 5 | import { IconForms } from "@tabler/icons-react"; 6 | import usuario from "@/data/constants/usuarioFalso"; 7 | import Formularios from "@/components/usuario/Formularios"; 8 | 9 | export default function CadastroUsuario() { 10 | 11 | return ( 12 | 13 | 14 | 15 | } 17 | principal="Dados Cadastrais" 18 | secundario={`Informações de ${usuario.email}`} 19 | /> 20 | 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitcent", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@emotion/react": "^11.10.6", 13 | "@mantine/core": "^6.0.1", 14 | "@mantine/dates": "^6.0.1", 15 | "@mantine/hooks": "^6.0.1", 16 | "@tabler/icons-react": "^2.9.0", 17 | "@types/node": "18.15.0", 18 | "@types/react": "18.0.28", 19 | "@types/react-dom": "18.0.11", 20 | "dayjs": "^1.11.7", 21 | "eslint": "8.35.0", 22 | "eslint-config-next": "13.2.4", 23 | "firebase": "^9.17.2", 24 | "next": "13.2.4", 25 | "react": "18.2.0", 26 | "react-dom": "18.2.0", 27 | "sharp": "^0.32.1", 28 | "typescript": "4.9.5", 29 | "uuid": "^9.0.0" 30 | }, 31 | "devDependencies": { 32 | "@types/uuid": "^9.0.1", 33 | "autoprefixer": "^10.4.14", 34 | "postcss": "^8.4.21", 35 | "tailwindcss": "^3.2.7" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Cod3r 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/components/template/TituloPagina.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | interface TituloPaginaProps { 4 | principal: string 5 | icone?: any 6 | secundario?: string 7 | className?: string 8 | } 9 | 10 | export default function TituloPagina(props: TituloPaginaProps) { 11 | return ( 12 |
13 | {props.icone && ( 14 |
{React.cloneElement(props.icone, { 17 | stroke: 1, 18 | size: props.secundario ? 50 : 24 19 | })}
20 | )} 21 |
22 |

23 | {props.principal} 24 |

25 | {props.secundario && ( 26 |

27 | {props.secundario} 28 |

29 | )} 30 |
31 |
32 | ) 33 | } -------------------------------------------------------------------------------- /src/components/landing/rodape/index.tsx: -------------------------------------------------------------------------------- 1 | import Area from "../comum/Area"; 2 | import Logo from "../comum/Logo"; 3 | import RedesSociais from "./RedeSociais"; 4 | 5 | export default function Rodape() { 6 | return ( 7 | 8 |
9 | 10 |
11 |
Plataforma financeira
12 |
que simplifica sua vida
13 |
14 |
15 |
16 | 17 |

18 | COD3R ® {new Date().getFullYear()} - Todos os direitos reservados 19 |

20 |
21 | 22 | ) 23 | } -------------------------------------------------------------------------------- /src/components/landing/cabecalho/Menu.tsx: -------------------------------------------------------------------------------- 1 | import AutenticacaoContext from '@/data/contexts/AutenticacaoContext' 2 | import { IconBrandGoogle } from '@tabler/icons-react' 3 | import { useContext } from 'react' 4 | import MenuItem from './MenuItem' 5 | 6 | export default function Menu() { 7 | const { loginGoogle } = useContext(AutenticacaoContext) 8 | 9 | return ( 10 |
11 | 12 | Início 13 | 14 | 15 | Vantagens 16 | 17 | 18 | Depoimentos 19 | 20 | 24 |
25 | 26 | Login 27 |
28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /src/components/financas/SumarioItem.tsx: -------------------------------------------------------------------------------- 1 | import Dinheiro from "@/logic/utils/Dinheiro" 2 | import React from "react" 3 | 4 | 5 | export interface SumarioItemProps { 6 | titulo: string 7 | valor: number 8 | icone: any 9 | valorClassName?: string 10 | iconeClassName?: string 11 | } 12 | 13 | export default function SumarioItem(props: SumarioItemProps) { 14 | return ( 15 |
20 |
{props.titulo}
21 |
22 | 23 | {Dinheiro.formatar(props.valor)} 24 | 25 | 26 | {React.cloneElement(props.icone, { 27 | size: 60, 28 | strokeWidth: 1, 29 | className: `${props.iconeClassName ?? ''}`, 30 | })} 31 | 32 |
33 |
34 | ) 35 | } -------------------------------------------------------------------------------- /src/components/landing/vantagens/Vantagem.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ImagemResponsiva from "../comum/ImagemResponsiva" 3 | 4 | export interface VantagemProps { 5 | imagem: any 6 | titulo: string 7 | subtitulo: string 8 | inverter?: boolean 9 | } 10 | 11 | export default function Vantagem(props: VantagemProps) { 12 | return ( 13 |
17 | 21 |
25 |
{props.titulo}
29 | 30 | {props.subtitulo} 31 | 32 |
33 |
34 | ) 35 | } -------------------------------------------------------------------------------- /src/logic/core/financas/ServicosTransacao.ts: -------------------------------------------------------------------------------- 1 | import Colecao from "@/logic/firebase/db/Colecao"; 2 | import Data from "@/logic/utils/Data"; 3 | import Usuario from "../usuario/Usuario"; 4 | import Transacao from "./Transacao"; 5 | 6 | export default class ServicosTransacao { 7 | private _colecao = new Colecao() 8 | 9 | async salvar(transacao: Transacao, usuario: Usuario) { 10 | return this._colecao.salvar( 11 | `financas/${usuario.email}/transacoes`, 12 | transacao 13 | ) 14 | } 15 | 16 | async excluir(transacao: Transacao, usuario: Usuario) { 17 | return this._colecao.excluir( 18 | `financas/${usuario.email}/transacoes`, 19 | transacao.id 20 | ) 21 | } 22 | 23 | async consultar(usuario: Usuario) { 24 | const caminho = `financas/${usuario.email}/transacoes` 25 | return await this._colecao.consultar(caminho, 'data', 'asc') 26 | } 27 | 28 | async consultarPorMes(usuario: Usuario, data: Date) { 29 | const caminho = `financas/${usuario.email}/transacoes` 30 | return await this._colecao.consultarComFiltros(caminho, [ 31 | { atributo: 'data', op: ">=", valor: Data.primeiroDia(data) }, 32 | { atributo: 'data', op: "<=", valor: Data.ultimoDia(data) }, 33 | ]) 34 | } 35 | } -------------------------------------------------------------------------------- /src/components/landing/depoimentos/Depoimento.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | 3 | interface DepoimentoProps { 4 | avatar: string 5 | nome: string 6 | titulo: string 7 | texto: string 8 | destaque?: boolean 9 | } 10 | 11 | export default function Depoimento(props: DepoimentoProps) { 12 | return ( 13 |
18 | Avatar 28 |
29 | {props.nome} 32 | {props.titulo} 35 |
36 |

37 | {props.texto} 38 |

39 |
40 | ) 41 | } -------------------------------------------------------------------------------- /src/components/template/MiniFormulario.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@mantine/core" 2 | 3 | interface MiniFormularioProps { 4 | titulo: string 5 | descricao: string 6 | msgRodape: string 7 | podeSalvar: boolean 8 | salvar: () => void 9 | children: any 10 | } 11 | 12 | export default function MiniFormulario(props: MiniFormularioProps) { 13 | return ( 14 |
18 |
19 |
{props.titulo}
20 |
{props.descricao}
21 |
22 | {props.children} 23 |
24 |
25 |
29 | {props.msgRodape} 30 | 35 |
36 |
37 | ) 38 | } -------------------------------------------------------------------------------- /src/logic/core/usuario/ServicosUsuario.ts: -------------------------------------------------------------------------------- 1 | import Autenticacao, { CancelarMonitoramento, MonitorarUsuario } from "@/logic/firebase/auth/Autenticacao" 2 | import Colecao from "@/logic/firebase/db/Colecao" 3 | import Usuario from "./Usuario" 4 | 5 | export default class ServicosUsuario { 6 | private _autenticacao = new Autenticacao() 7 | private _colecao = new Colecao() 8 | 9 | monitorarAutenticacao(observador: MonitorarUsuario): CancelarMonitoramento { 10 | return this._autenticacao.monitorar(async usuario => { 11 | observador(usuario ? { 12 | ...usuario, 13 | ...await this.consultar(usuario.email) 14 | } : null) 15 | }) 16 | } 17 | 18 | async loginGoogle(): Promise { 19 | const usuario = await this._autenticacao.loginGoogle() 20 | if (!usuario) return null 21 | 22 | let usuarioDoBanco = await this.consultar(usuario.email) 23 | if (!usuarioDoBanco) usuarioDoBanco = await this.salvar(usuario) 24 | 25 | return { ...usuario, ...usuarioDoBanco } 26 | } 27 | 28 | logout(): Promise { 29 | return this._autenticacao.logout() 30 | } 31 | 32 | async salvar(usuario: Usuario) { 33 | return await this._colecao.salvar( 34 | 'usuarios', usuario, usuario.email 35 | ) 36 | } 37 | 38 | async consultar(email: string) { 39 | return await this._colecao.consultarPorId( 40 | 'usuarios', email 41 | ) 42 | } 43 | } -------------------------------------------------------------------------------- /src/logic/utils/Data.ts: -------------------------------------------------------------------------------- 1 | export default class Data { 2 | 3 | private static _lingua = 'pt-BR' 4 | 5 | static ddmmyy = { 6 | formatar(dt: Date, separador: string = '/'): string { 7 | const dia = dt.getDate().toString().padStart(2, '0') 8 | const mes = (dt.getMonth() + 1).toString().padStart(2, '0') 9 | return `${dia}${separador}${mes}${separador}${dt.getFullYear()}` 10 | } 11 | } 12 | 13 | static mmyy = { 14 | formatar(dt: Date, lingua?: string): string { 15 | return dt?.toLocaleDateString?.(lingua ?? Data._lingua, { 16 | month: 'long', 17 | year: 'numeric', 18 | } as Intl.DateTimeFormatOptions) 19 | }, 20 | } 21 | 22 | static ddmm = { 23 | formatar(dt: Date): string { 24 | return dt?.toLocaleDateString?.( 25 | Data._lingua, { 26 | day: '2-digit', 27 | month: 'short', 28 | } as Intl.DateTimeFormatOptions 29 | ) 30 | } 31 | } 32 | 33 | static meses() { 34 | return Array(12).fill(0).map((_, i) => new Date(2000, i, 1) 35 | .toLocaleDateString(Data._lingua, { month: 'short' }) 36 | .toUpperCase() 37 | .substring(0, 3)) 38 | } 39 | 40 | static primeiroDia(dt: Date) { 41 | return new Date(dt.getFullYear(), dt.getMonth(), 1) 42 | } 43 | 44 | static ultimoDia(dt: Date) { 45 | return new Date(dt.getFullYear(), dt.getMonth() + 1, 0, 23, 59, 59) 46 | } 47 | } -------------------------------------------------------------------------------- /src/logic/firebase/auth/Autenticacao.ts: -------------------------------------------------------------------------------- 1 | import Usuario from "@/logic/core/usuario/Usuario"; 2 | 3 | import { 4 | Auth, getAuth, GoogleAuthProvider, onIdTokenChanged, signInWithPopup, signOut, User 5 | } from "firebase/auth" 6 | import { app } from "../config/app"; 7 | 8 | export type MonitorarUsuario = (usuario: Usuario | null) => void 9 | export type CancelarMonitoramento = () => void 10 | 11 | export default class Autenticacao { 12 | private _auth: Auth 13 | 14 | constructor() { 15 | this._auth = getAuth(app) 16 | } 17 | 18 | async loginGoogle(): Promise { 19 | const resp = await signInWithPopup(this._auth, new GoogleAuthProvider()) 20 | return this.converterParaUsuario(resp.user) 21 | } 22 | 23 | logout(): Promise { 24 | return signOut(this._auth) 25 | } 26 | 27 | monitorar(notificar: MonitorarUsuario): CancelarMonitoramento { 28 | return onIdTokenChanged(this._auth, async (usuarioFirebase) => { 29 | const usuario = this.converterParaUsuario(usuarioFirebase) 30 | notificar(usuario) 31 | }) 32 | } 33 | 34 | private converterParaUsuario(usuarioFirebase: User | null): Usuario | null { 35 | if(!usuarioFirebase?.email) return null 36 | const nomeAlternativo = usuarioFirebase.email!.split('@')[0] 37 | 38 | return { 39 | id: usuarioFirebase.uid, 40 | nome: usuarioFirebase.displayName ?? nomeAlternativo, 41 | email: usuarioFirebase.email, 42 | imagemUrl: usuarioFirebase.photoURL 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/components/landing/vantagens/index.tsx: -------------------------------------------------------------------------------- 1 | import Area from "../comum/Area"; 2 | import vantagem1 from "../../../../public/vantagem-1.jpg" 3 | import vantagem2 from "../../../../public/vantagem-2.jpg" 4 | import vantagem3 from "../../../../public/vantagem-3.jpg" 5 | import Vantagem from "./Vantagem"; 6 | 7 | export default function Vantagens() { 8 | return ( 9 | 10 |
11 | 16 | 17 | 23 | 28 |
29 | 30 | ) 31 | } -------------------------------------------------------------------------------- /src/data/constants/transacoesFalsas.ts: -------------------------------------------------------------------------------- 1 | import Id from "@/logic/core/comum/Id"; 2 | import { TipoTransacao } from "@/logic/core/financas/TipoTransacao"; 3 | import Transacao from "@/logic/core/financas/Transacao"; 4 | 5 | const transacoesFalsas: Transacao[] = [ 6 | { 7 | id: Id.novo(), 8 | descricao: 'Salário', 9 | data: new Date(2023, 4, 1), 10 | valor: 7123.34, 11 | tipo: TipoTransacao.RECEITA, 12 | }, 13 | { 14 | id: Id.novo(), 15 | descricao: 'Conta de Luz', 16 | valor: 320.00, 17 | data: new Date(2023, 4, 3), 18 | tipo: TipoTransacao.DESPESA, 19 | }, 20 | { 21 | id: Id.novo(), 22 | descricao: 'Aluguel + Cond.', 23 | valor: 1817.59, 24 | data: new Date(2023, 4, 3), 25 | tipo: TipoTransacao.DESPESA, 26 | }, 27 | { 28 | id: Id.novo(), 29 | descricao: 'Cartão de Crédito', 30 | valor: 2200.00, 31 | data: new Date(2023, 4, 10), 32 | tipo: TipoTransacao.DESPESA, 33 | }, 34 | { 35 | id: Id.novo(), 36 | descricao: 'Conta de Água', 37 | valor: 174.35, 38 | data: new Date(2023, 4, 8), 39 | tipo: TipoTransacao.DESPESA, 40 | }, 41 | { 42 | id: Id.novo(), 43 | descricao: 'Mensalidade MBA', 44 | valor: 750.00, 45 | data: new Date(2023, 4, 2), 46 | tipo: TipoTransacao.DESPESA, 47 | }, 48 | 49 | { 50 | id: Id.novo(), 51 | descricao: 'Investimentos', 52 | data: new Date(2023, 4, 1), 53 | valor: 2123.34, 54 | tipo: TipoTransacao.RECEITA, 55 | }, 56 | ] 57 | 58 | export default transacoesFalsas -------------------------------------------------------------------------------- /src/data/hooks/useTransacao.ts: -------------------------------------------------------------------------------- 1 | import servicos from "@/logic/core" 2 | import Transacao from "@/logic/core/financas/Transacao" 3 | import { useCallback, useContext, useEffect, useState } from "react" 4 | import AutenticacaoContext from "../contexts/AutenticacaoContext" 5 | 6 | export type TipoExibicao = "lista" | "grade" 7 | 8 | export default function useTransacao() { 9 | const { usuario } = useContext(AutenticacaoContext) 10 | const [data, setData] = useState(new Date()) 11 | const [tipoExibicao, setTipoExibicao] = useState("lista") 12 | const [transacoes, setTransacoes] = useState([]) 13 | const [transacao, setTransacao] = useState(null) 14 | 15 | const buscarTransacoes = useCallback(async function () { 16 | if(!usuario) return 17 | const transacoes = await servicos.transacao.consultarPorMes(usuario, data) 18 | setTransacoes(transacoes) 19 | }, [usuario, data]) 20 | 21 | useEffect(() => { 22 | buscarTransacoes() 23 | }, [buscarTransacoes, data]) 24 | 25 | async function salvar(transacao: Transacao) { 26 | if(!usuario) return 27 | servicos.transacao.salvar(transacao, usuario) 28 | setTransacao(null) 29 | await buscarTransacoes() 30 | } 31 | 32 | async function excluir(transacao: Transacao) { 33 | if(!usuario) return 34 | await servicos.transacao.excluir(transacao, usuario) 35 | setTransacao(null) 36 | await buscarTransacoes() 37 | } 38 | 39 | return { 40 | data, 41 | transacoes, 42 | transacao, 43 | tipoExibicao, 44 | salvar, 45 | excluir, 46 | selecionar: setTransacao, 47 | alterarData: setData, 48 | alterarExibicao: setTipoExibicao 49 | } 50 | } -------------------------------------------------------------------------------- /src/components/financas/Sumario.tsx: -------------------------------------------------------------------------------- 1 | import { TipoTransacao } from '@/logic/core/financas/TipoTransacao' 2 | import Transacao from '@/logic/core/financas/Transacao' 3 | import Dinheiro from '@/logic/utils/Dinheiro' 4 | import { IconArrowsDoubleSwNe, IconCash, IconCreditCard } from '@tabler/icons-react' 5 | import SumarioItem from './SumarioItem' 6 | 7 | interface SumarioProps { 8 | transacoes: Transacao[] 9 | className?: string 10 | } 11 | 12 | export default function Sumario(props: SumarioProps) { 13 | const totalizar = (total: number, r: Transacao) => total + r.valor 14 | 15 | const receitas = props.transacoes 16 | .filter((r) => r.tipo === TipoTransacao.RECEITA) 17 | .reduce(totalizar, 0) 18 | 19 | const despesas = props.transacoes 20 | .filter((r) => r.tipo === TipoTransacao.DESPESA) 21 | .reduce(totalizar, 0) 22 | 23 | const total = receitas - despesas 24 | 25 | return ( 26 |
29 | } 33 | iconeClassName="text-green-500" 34 | /> 35 | } 39 | iconeClassName="text-red-500" 40 | /> 41 | } 45 | iconeClassName="text-yellow-500" 46 | valorClassName={total > 0 ? 'text-green-500' : total < 0 ? 'text-red-500' : ''} 47 | /> 48 |
49 | ) 50 | } -------------------------------------------------------------------------------- /src/components/financas/Lista.tsx: -------------------------------------------------------------------------------- 1 | import Transacao from "@/logic/core/financas/Transacao" 2 | import Data from "@/logic/utils/Data" 3 | import Dinheiro from "@/logic/utils/Dinheiro" 4 | import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react" 5 | 6 | interface ListaProps { 7 | transacoes: Transacao[] 8 | selecionarTransacao?: (transacao: Transacao) => void 9 | } 10 | 11 | export default function Lista(props: ListaProps) { 12 | function renderizarTipo(transacao: Transacao) { 13 | return ( 14 | 19 | {transacao.tipo === 'receita' 20 | ? 21 | : } 22 | 23 | ) 24 | } 25 | 26 | function renderizarLinha(transacao: Transacao, indice: number) { 27 | return ( 28 |
props.selecionarTransacao?.(transacao)}> 32 | {renderizarTipo(transacao)} 33 | {transacao.descricao} 34 | {Data.ddmmyy.formatar(transacao.data)} 35 | {Dinheiro.formatar(transacao.valor)} 36 |
37 | ) 38 | } 39 | 40 | return ( 41 |
45 | {props.transacoes.map(renderizarLinha)} 46 |
47 | ) 48 | } -------------------------------------------------------------------------------- /src/components/template/MenuUsuario.tsx: -------------------------------------------------------------------------------- 1 | import AutenticacaoContext from "@/data/contexts/AutenticacaoContext"; 2 | import { Avatar, Menu } from "@mantine/core"; 3 | import { IconArrowsRightLeft, IconLogout, IconUser } from "@tabler/icons-react"; 4 | import Link from "next/link"; 5 | import { useContext } from "react"; 6 | 7 | export default function MenuUsuario() { 8 | 9 | const { usuario, logout } = useContext(AutenticacaoContext) 10 | 11 | return ( 12 | 13 | 14 |
15 |
16 | {usuario?.nome} 17 | {usuario?.email} 18 |
19 | 24 |
25 |
26 | 27 | Usuário 28 | 29 | } 31 | >Finanças 32 | 33 | 34 | } 36 | >Meus Dados 37 | 38 | 39 | } 42 | onClick={logout} 43 | >Sair do Sistema 44 | 45 |
46 | ) 47 | } -------------------------------------------------------------------------------- /src/data/contexts/AutenticacaoContext.tsx: -------------------------------------------------------------------------------- 1 | import servicos from "@/logic/core" 2 | import Usuario from "@/logic/core/usuario/Usuario" 3 | import { createContext, useEffect, useState } from "react" 4 | 5 | interface AutenticacaoProps { 6 | carregando: boolean 7 | usuario: Usuario | null 8 | loginGoogle: () => Promise 9 | logout: () => Promise 10 | atualizarUsuario: (novoUsuario: Usuario) => Promise 11 | } 12 | 13 | const AutenticacaoContext = createContext({ 14 | carregando: true, 15 | usuario: null, 16 | loginGoogle: async () => null, 17 | logout: async () => {}, 18 | atualizarUsuario: async () => {} 19 | }) 20 | 21 | export function AutenticacaoProvider(props: any) { 22 | const [carregando, setCarregando] = useState(true) 23 | const [usuario, setUsuario] = useState(null) 24 | 25 | useEffect(() => { 26 | const cancelar = servicos.usuario.monitorarAutenticacao((usuario) => { 27 | setUsuario(usuario) 28 | setCarregando(false) 29 | }) 30 | return () => cancelar() 31 | }, []) 32 | 33 | async function atualizarUsuario(novoUsuario: Usuario) { 34 | if (usuario && usuario.email !== novoUsuario.email) return logout() 35 | if (usuario && novoUsuario && usuario.email === novoUsuario.email) { 36 | await servicos.usuario.salvar(novoUsuario) 37 | setUsuario(novoUsuario) 38 | } 39 | } 40 | 41 | async function loginGoogle() { 42 | const usuario = await servicos.usuario.loginGoogle() 43 | setUsuario(usuario) 44 | return usuario 45 | } 46 | 47 | async function logout() { 48 | await servicos.usuario.logout() 49 | setUsuario(null) 50 | } 51 | 52 | return ( 53 | 60 | {props.children} 61 | 62 | ) 63 | } 64 | 65 | export default AutenticacaoContext -------------------------------------------------------------------------------- /src/components/landing/depoimentos/index.tsx: -------------------------------------------------------------------------------- 1 | import Area from "../comum/Area"; 2 | import Depoimento from "./Depoimento"; 3 | 4 | export default function Depoimentos() { 5 | return ( 6 | 10 |
11 |

12 | As pessoas estão dizendo... 13 |

14 |
15 | 21 | 28 | 34 |
35 |
36 | 37 | ) 38 | } -------------------------------------------------------------------------------- /src/components/financas/Grade.tsx: -------------------------------------------------------------------------------- 1 | import { TipoTransacao } from "@/logic/core/financas/TipoTransacao" 2 | import Transacao from "@/logic/core/financas/Transacao" 3 | import Data from "@/logic/utils/Data" 4 | import Dinheiro from "@/logic/utils/Dinheiro" 5 | import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react" 6 | 7 | interface GradeProps { 8 | transacoes: Transacao[] 9 | selecionarTransacao?: (transacao: Transacao) => void 10 | } 11 | 12 | export default function Grade(props: GradeProps) { 13 | function renderizarItem(transacao: Transacao) { 14 | return ( 15 |
props.selecionarTransacao?.(transacao)}> 19 |
27 |
28 | {transacao.descricao} 29 | 30 | {Data.ddmm.formatar(transacao.data)} 31 | 32 |
33 | 34 | {Dinheiro.formatar(transacao.valor)} 35 | 36 | {transacao.tipo === TipoTransacao.RECEITA ? ( 37 | 42 | ) : ( 43 | 48 | )} 49 |
50 | ) 51 | } 52 | 53 | return ( 54 |
55 | {props.transacoes.map(renderizarItem)} 56 |
57 | ) 58 | } -------------------------------------------------------------------------------- /src/components/usuario/Formularios.tsx: -------------------------------------------------------------------------------- 1 | import useFormulario from "@/data/hooks/useFormulario"; 2 | import MiniFormulario from "../template/MiniFormulario"; 3 | import Usuario from "@/logic/core/usuario/Usuario"; 4 | import { TextInput } from "@mantine/core"; 5 | import Texto from "@/logic/utils/Texto"; 6 | import Cpf from "@/logic/utils/Cpf"; 7 | import Telefone from "@/logic/utils/Telefone"; 8 | import { useContext, useEffect } from "react"; 9 | import AutenticacaoContext from "@/data/contexts/AutenticacaoContext"; 10 | 11 | export default function Formularios() { 12 | const { usuario, atualizarUsuario } = useContext(AutenticacaoContext) 13 | const { dados, alterarAtributo, alterarDados } = useFormulario() 14 | 15 | useEffect(() => { 16 | if(!usuario) return 17 | alterarDados(usuario) 18 | }, [usuario, alterarDados]) 19 | 20 | async function salvar() { 21 | if(!usuario) return 22 | await atualizarUsuario(dados) 23 | } 24 | 25 | return ( 26 |
27 | 34 | 38 | 39 | 46 | 50 | 51 | 58 | 62 | 63 |
64 | ) 65 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Logo 3 |

4 | 5 |

6 | Repositório Bitcent versão completa 7 |

8 | 9 |

10 | Semana Transformação.DEV #01 11 | License 12 |

13 | 14 |

15 | Bitcent 16 |

17 | 18 |
19 | 20 | ## Tecnologias 21 | 22 | Lista de tecnologias utilizadas no projeto: 23 | 24 | - [React](https://reactjs.org) 25 | - [Next.js](https://nextjs.org/) 26 | - [Firebase](https://firebase.google.com/) 27 | - [TypeScript](https://www.typescriptlang.org/) 28 | - [TailwindCSS](https://tailwindcss.com/) 29 | - [Mantine](https://mantine.dev/) 30 | 31 | ## Executando o projeto 32 | 33 | 1. Clone o repositório: 34 | 35 | ```bash 36 | $ git clone https://github.com/transformacaodev/bitcent 37 | $ cd bitcent 38 | ``` 39 | 40 | 2. Crie um projeto no Firebase e ative o Firestore e Autenticação com Google. 41 | 42 | - Permissões do Firestore: 43 | 44 | ``` 45 | rules_version = '2'; 46 | service cloud.firestore { 47 | match /databases/{database}/documents { 48 | match /{document=**} { 49 | allow read, write: if false; 50 | } 51 | 52 | match /financas/{email}/transacoes/{id} { 53 | allow read: if (request.auth != null && request.auth.token.email == email); 54 | allow write: if (request.auth != null && request.auth.token.email == email); 55 | } 56 | 57 | match /usuarios/{email} { 58 | allow read: if (request.auth != null && request.auth.token.email == email); 59 | allow write: if (request.auth != null && request.auth.token.email == email); 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | 66 | 67 | 68 | 3. É preciso criar um arquivo `.env.local` na raiz do projeto com as seguintes variáveis: 69 | 70 | ```bash 71 | NEXT_PUBLIC_FIREBASE_PROJECT_ID= 72 | NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= 73 | NEXT_PUBLIC_FIREBASE_API_KEY= 74 | ``` 75 | Usar as credenciais do seu projeto no Firebase. 76 | 77 | 4. Dentro da pasta do projeto, execute os comandos abaixo: 78 | 79 | ```bash 80 | # Instalar as dependências 81 | $ npm install 82 | 83 | # Iniciar o projeto 84 | $ npm run dev 85 | ``` 86 | O app estará disponível no endereço http://localhost:3000. 87 | 88 | ## Sobre o Projeto 89 | 90 | Bitcent é uma aplicação web para controle de finanças pessoais com landing page e dashboard. O projeto utiliza o Firebase para autenticação e armazenamento de dados. 91 | 92 | Projeto foi desenvolvido durante a **[Semana Tranformação.DEV](https://transformacao.dev/)**, que ocorreu nos dias 8 a 12 de Maio de 2023. 93 | 94 | 95 | ## License 96 | 97 | Esse projeto está sob a [licença MIT](LICENSE.md). 98 | 99 | --- 100 | 101 | Cod3r com ❤️ - [Nossa Comunidade no Discord](https://discord.gg/JexVkcc3vr) -------------------------------------------------------------------------------- /src/components/financas/index.tsx: -------------------------------------------------------------------------------- 1 | import useTransacao, { TipoExibicao } from "@/data/hooks/useTransacao"; 2 | import { transacaoVazia } from "@/logic/core/financas/Transacao"; 3 | import { Button, SegmentedControl } from "@mantine/core"; 4 | import { IconLayoutGrid, IconList, IconPlus } from "@tabler/icons-react"; 5 | import Cabecalho from "../template/Cabecalho"; 6 | import CampoMesAno from "../template/CampoMesAno"; 7 | import Conteudo from "../template/Conteudo"; 8 | import NaoEncontrado from "../template/NaoEncontrado"; 9 | import Pagina from "../template/Pagina"; 10 | import Formulario from "./Formulario"; 11 | import Grade from "./Grade"; 12 | import Lista from "./Lista"; 13 | import Sumario from "./Sumario"; 14 | 15 | export default function Financas() { 16 | const { 17 | data, alterarData, alterarExibicao, tipoExibicao, 18 | transacoes, transacao, selecionar, salvar, excluir 19 | } = useTransacao() 20 | 21 | function renderizarControles() { 22 | return ( 23 |
24 | 28 |
29 | 34 | , value: 'lista' }, 37 | { label: , value: 'grade' } 38 | ]} 39 | onChange={tipo => alterarExibicao(tipo as TipoExibicao)} 40 | /> 41 |
42 |
43 | ) 44 | } 45 | 46 | function renderizarTransacoes() { 47 | const props = { transacoes, selecionarTransacao: selecionar } 48 | return tipoExibicao === 'lista' 49 | ? 50 | : 51 | } 52 | 53 | return ( 54 | 55 | 56 | 57 | 58 | {renderizarControles()} 59 | {transacao ? ( 60 | selecionar(null)} 65 | /> 66 | ) : transacoes.length > 0 ? ( 67 | renderizarTransacoes() 68 | ) : ( 69 | 70 | Nenhuma transação encontrada 71 | 72 | )} 73 | 74 | 75 | ) 76 | } -------------------------------------------------------------------------------- /src/components/financas/Formulario.tsx: -------------------------------------------------------------------------------- 1 | import "dayjs/locale/pt-br" 2 | import Transacao from "@/logic/core/financas/Transacao"; 3 | import Dinheiro from "@/logic/utils/Dinheiro"; 4 | import { Button, Group, Radio, TextInput } from "@mantine/core"; 5 | import { DatePickerInput } from "@mantine/dates"; 6 | import { TipoTransacao } from "@/logic/core/financas/TipoTransacao"; 7 | import useFormulario from "@/data/hooks/useFormulario"; 8 | 9 | interface FormularioProps { 10 | transacao: Transacao 11 | salvar?: (transacao: Transacao) => void 12 | excluir?: (transacao: Transacao) => void 13 | cancelar?: () => void 14 | } 15 | 16 | export default function Formulario(props: FormularioProps) { 17 | const { dados, alterarAtributo } = useFormulario(props.transacao) 18 | 19 | return ( 20 |
24 |
Formulário
25 |
26 | 31 | 36 | 43 | 47 | 48 | 49 | 50 | 51 | 52 |
53 |
54 | 58 | 62 |
63 | {props.transacao.id && ( 64 | 68 | )} 69 |
70 |
71 | ) 72 | } -------------------------------------------------------------------------------- /src/logic/firebase/db/Colecao.ts: -------------------------------------------------------------------------------- 1 | import Id from '@/logic/core/comum/Id' 2 | import { 3 | collection, 4 | deleteDoc, 5 | doc, 6 | DocumentData, 7 | DocumentSnapshot, 8 | getDoc, 9 | getDocs, 10 | getFirestore, 11 | orderBy, 12 | OrderByDirection, 13 | query, 14 | QueryConstraint, 15 | setDoc, 16 | WhereFilterOp, 17 | where 18 | } from 'firebase/firestore' 19 | import { app } from '../config/app' 20 | 21 | export interface Filtro { 22 | atributo: string 23 | op: WhereFilterOp 24 | valor: any 25 | } 26 | 27 | export default class Colecao { 28 | 29 | async salvar(caminho: string, entidade: any, id?: string): Promise { 30 | const db = getFirestore(app) 31 | const idFinal = id ?? entidade.id ?? Id.novo() 32 | const docRef = doc(db, caminho, idFinal) 33 | await setDoc(docRef, entidade) 34 | 35 | return { 36 | ...entidade, 37 | id: entidade.id ?? idFinal 38 | } 39 | } 40 | 41 | async excluir(caminho: string, id?: string): Promise { 42 | if (!id) return false 43 | const db = getFirestore(app) 44 | const docRef = doc(db, caminho, id) 45 | const itemNoBanco = await getDoc(docRef) 46 | if (!itemNoBanco.exists()) return false 47 | await deleteDoc(docRef) 48 | return true 49 | } 50 | 51 | async consultar(caminho: string, ordenarPor?: string, direcao?: OrderByDirection): Promise { 52 | const db = getFirestore(app) 53 | const colecaoRef = collection(db, caminho) 54 | const filtro: QueryConstraint[] = [] 55 | const ordenacao = ordenarPor ? [orderBy(ordenarPor, direcao)] : [] 56 | const consulta = query(colecaoRef, ...filtro, ...ordenacao) 57 | const resultado = await getDocs(consulta) 58 | return resultado.docs.map(this._converterDoFirebase) ?? [] 59 | } 60 | 61 | async consultarPorId(caminho: string, id: string): Promise { 62 | if (!id) return null 63 | const db = getFirestore(app) 64 | const docRef = doc(db, caminho, id) 65 | const resultado = await getDoc(docRef) 66 | return this._converterDoFirebase(resultado) 67 | } 68 | 69 | async consultarComFiltros(caminho: string, filtros: Filtro[], 70 | ordenarPor?: string, direcao?: OrderByDirection): Promise { 71 | const db = getFirestore(app) 72 | const colecaoRef = collection(db, caminho) 73 | 74 | const filtrosWhere = filtros?.map(f => where(f.atributo, f.op, f.valor)) ?? [] 75 | const ordenacao = ordenarPor ? [orderBy(ordenarPor, direcao)] : [] 76 | 77 | const consulta = query(colecaoRef, ...filtrosWhere, ...ordenacao) 78 | const resultado = await getDocs(consulta) 79 | return resultado.docs.map(this._converterDoFirebase) ?? [] 80 | } 81 | 82 | private _converterDoFirebase(snapshot: DocumentSnapshot) { 83 | if(!snapshot.exists()) return null 84 | const entidade: any = { ...snapshot.data(), id: snapshot.id } 85 | if (!entidade) return entidade 86 | return Object.keys(entidade).reduce((obj: any, atributo: string) => { 87 | const valor: any = entidade[atributo] 88 | return { ...obj, [atributo]: valor.toDate?.() ?? valor } 89 | }, {}) 90 | } 91 | } -------------------------------------------------------------------------------- /src/components/landing/destaque/Slogan.tsx: -------------------------------------------------------------------------------- 1 | import AutenticacaoContext from '@/data/contexts/AutenticacaoContext' 2 | import { IconArrowRight, IconVideo } from '@tabler/icons-react' 3 | import { useContext } from 'react' 4 | 5 | export default function Slogan() { 6 | const { loginGoogle } = useContext(AutenticacaoContext) 7 | 8 | function renderizarFrase() { 9 | return ( 10 |
16 |
17 |
18 | 25 | Melhor 26 |
27 |
maneira
28 |
29 |
30 | de 31 | organizar 32 |
33 |
34 | seu 35 | 41 | 42 | dinheiro 43 | 44 |
45 |
46 | ) 47 | } 48 | 49 | return ( 50 |
51 | {renderizarFrase()} 52 |
53 | Plataforma financeira que simplifica sua vida! 54 |
55 |
56 |
64 | 65 | Iniciar Agora 66 | 67 | 68 |
69 |
75 | 76 | 77 | Assista o Vídeo 78 | 79 |
80 |
81 |
82 | ) 83 | } 84 | -------------------------------------------------------------------------------- /src/components/template/CampoMesAno.tsx: -------------------------------------------------------------------------------- 1 | import Data from "@/logic/utils/Data"; 2 | import { Button, NumberInput, Popover } from "@mantine/core"; 3 | import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react"; 4 | import { useState } from "react"; 5 | 6 | 7 | export interface CampoMesAnoProps { 8 | data?: Date 9 | dataMudou?: (data: Date) => void 10 | } 11 | 12 | export default function CampoMesAno(props: CampoMesAnoProps) { 13 | const hoje = new Date() 14 | 15 | const [data, setData] = useState(new Date( 16 | props.data?.getFullYear() ?? hoje.getFullYear(), 17 | props.data?.getMonth() ?? hoje.getMonth(), 18 | 1 19 | )) 20 | 21 | function alterarAno(ano: number) { 22 | if (!ano) return 23 | const novaData = new Date(data) 24 | novaData.setFullYear(ano) 25 | setData(novaData) 26 | props.dataMudou?.(novaData) 27 | } 28 | 29 | function alterarMes(mes: number) { 30 | const novaData = new Date(data) 31 | novaData.setMonth(mes) 32 | setData(novaData) 33 | props.dataMudou?.(novaData) 34 | } 35 | 36 | function incrementar() { 37 | const novaData = new Date(data) 38 | novaData.setMonth(novaData.getMonth() + 1) 39 | setData(novaData) 40 | props.dataMudou?.(novaData) 41 | } 42 | 43 | function decrementar() { 44 | const novaData = new Date(data) 45 | novaData.setMonth(novaData.getMonth() - 1) 46 | setData(novaData) 47 | props.dataMudou?.(novaData) 48 | } 49 | 50 | return ( 51 |
52 | 58 | 59 | 60 | 65 | 66 | 67 |
68 | 72 |
73 |
74 | {Data.meses().map((mes, i) => { 75 | const selecionada = data.getMonth() === i 76 | return ( 77 | 83 | ) 84 | })} 85 |
86 |
87 |
88 | 94 |
95 | ) 96 | } --------------------------------------------------------------------------------