├── .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 |
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 |
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 |
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 |
14 |
17 |
20 |
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 |
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 |
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 |
3 |
4 |
5 |
6 | Repositório Bitcent versão completa
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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 | }
32 | onClick={() => selecionar(transacaoVazia)}
33 | >Nova transação
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 | }
--------------------------------------------------------------------------------