){
67 | if(e.target.files && e.target.files[0]){
68 | const image = e.target.files[0];
69 |
70 | if(image.type !== "image/jpeg" && image.type !== "image/png"){
71 | toast.warning("Formato não permitido!")
72 | return;
73 | }
74 |
75 | setImage(image);
76 | setPreviewImage(URL.createObjectURL(image))
77 |
78 | }
79 | }
80 |
81 |
82 | return(
83 |
84 | Novo produto
85 |
86 |
148 |
149 | )
150 | }
--------------------------------------------------------------------------------
/src/app/dashboard/product/components/form/styles.module.scss:
--------------------------------------------------------------------------------
1 | .container{
2 | max-width: 720px;
3 | margin: 20px auto;
4 | padding: 0 16px;
5 | display: flex;
6 | flex-direction: column;
7 |
8 | h1{
9 | color: var(--white);
10 | }
11 | }
12 |
13 | .form{
14 | display: flex;
15 | flex-direction: column;
16 | margin: 16px 0;
17 | gap: 16px;
18 |
19 | select {
20 | width: 100%;
21 | height: 40px;
22 | border-radius: 8px;
23 | background-color: var(--dark-900);
24 | color: var(--white);
25 | padding: 0 8px;
26 | border: 1px solid var(--gray-100);
27 | }
28 |
29 | textarea{
30 | width: 100%;
31 | min-height: 120px;
32 | resize: none;
33 | padding: 8px;
34 | color: var(--white);
35 | }
36 |
37 | }
38 |
39 | .labelImage{
40 | width: 100%;
41 | height: 280px;
42 | position: relative;
43 | background-color: var(--dark-900);
44 | border-radius: 8px;
45 | display: flex;
46 | align-items: center;
47 | justify-content: center;
48 | cursor: pointer;
49 | flex-direction: column;
50 | margin-bottom: 16px;
51 | border: 1px solid var(--gray-100);
52 |
53 | input{
54 | display: none;
55 | }
56 |
57 | span{
58 | z-index: 99;
59 | opacity: 0.8;
60 | transition: all 0.6s;
61 |
62 | &:hover{
63 | opacity: 1;
64 | transform: scale(1.1);
65 | }
66 | }
67 | }
68 |
69 | .preview{
70 | width: 100%;
71 | height: 100%;
72 | border-radius: 8px;
73 | object-fit: cover;
74 | }
75 |
76 | .input{
77 | border:0;
78 | height: 40px;
79 | background-color: var(--dark-900);
80 | border: 1px solid var(--gray-100);
81 | border-radius: 8px;
82 | padding: 0 8px;
83 | color: var(--white);
84 | }
--------------------------------------------------------------------------------
/src/app/dashboard/product/page.tsx:
--------------------------------------------------------------------------------
1 | import { Form } from './components/form'
2 | import { api } from '@/services/api'
3 | import { getCookieServer } from '@/lib/cookieServer'
4 |
5 | export default async function Product(){
6 |
7 | const token = getCookieServer();
8 |
9 | const response = await api.get("/category", {
10 | headers: {
11 | Authorization: `Bearer ${token}`
12 | }
13 | })
14 |
15 | return(
16 |
17 | )
18 | }
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sm4rtdev/pizza_frontend/458c5d16c0363e23e3d4b04791c982fcb5be5bb5/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/app/globals.scss:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | outline: 0;
6 | }
7 |
8 | :root{
9 |
10 | --white: #FFF;
11 | --black: #000;
12 |
13 | --dark-900: #101026;
14 | --dark-700: #1d1d2e;
15 |
16 | --gray-100: #8a8a8a;
17 | --green-900: #3fffa3;
18 | --red-900: #FF3f4b;
19 |
20 | }
21 |
22 | button{
23 | cursor: pointer;
24 | }
25 |
26 | a{
27 | color: inherit;
28 | text-rendering: none;
29 | }
30 |
31 | body{
32 | background: var(--dark-700);
33 | }
34 |
35 | body, input, textarea, select, button{
36 | font: 400, 1rem sans-serif; // 1rem = 16px
37 | }
38 |
39 | @media (max-width: 720px){
40 | html{
41 | font-size: 87.5%;
42 | }
43 | }
44 |
45 | @media (max-width: 1080px){
46 | html{
47 | font-size: 93.75%;
48 | }
49 | }
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.scss";
4 | import { Toaster } from "sonner";
5 |
6 | const inter = Inter({ subsets: ["latin"] });
7 |
8 | export const metadata: Metadata = {
9 | title: "Sujeito Pizza - A melhor pizzaria",
10 | description: "A melhor pizzaria do Brasil",
11 | };
12 |
13 | export default function RootLayout({
14 | children,
15 | }: Readonly<{
16 | children: React.ReactNode;
17 | }>) {
18 | return (
19 |
20 |
21 |
31 | {children}
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/src/app/page.module.scss:
--------------------------------------------------------------------------------
1 |
2 | .containerCenter{
3 | min-height: 100vh;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: center;
8 | }
9 |
10 | .login{
11 | margin-top: 24px;
12 | display: flex;
13 | flex-direction: column;
14 | align-items: center;
15 | justify-content: center;
16 | gap: 16px;
17 | width: 600px;
18 |
19 | h1{
20 | color: var(--white);
21 | }
22 |
23 |
24 | form{
25 | color: var(--white);
26 | padding-bottom: 16px;
27 | font-size: 18px;
28 | display: flex;
29 | flex-direction: column;
30 | width: 90%;
31 | gap: 16px;
32 | }
33 |
34 | form button{
35 | height: 40px;
36 | font-size: 16px;
37 | background-color: var(--red-900);
38 | border:0;
39 | border-radius: 8px;
40 | color: var(--white);
41 | display: flex;
42 | align-items: center;
43 | justify-content: center;
44 | transition: all 0.5s;
45 | }
46 |
47 |
48 | form button:hover{
49 | transform: scale(1.05);
50 | }
51 | }
52 |
53 | .input{
54 | height: 40px;
55 | border: 1px solid var(--gray-100);
56 | padding: 0 16px;
57 | border-radius: 8px;
58 | background-color: var(--dark-900);
59 | color: var(--white);
60 | font-size: 16px;
61 | }
62 |
63 | .input::placeholder{
64 | color: rgba(255, 255, 255, 0.700);
65 | }
66 |
67 | .text{
68 | color: var(--white);
69 | text-decoration: none;
70 | }
71 |
72 | @media (max-width: 620px){
73 | .login{
74 | width: 90%;
75 | }
76 | }
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import styles from './page.module.scss'
2 | import logoImg from '/public/logo.svg'
3 | import Image from 'next/image'
4 | import Link from 'next/link'
5 | import { api } from '@/services/api'
6 | import { redirect } from 'next/navigation'
7 | import { cookies } from 'next/headers'
8 |
9 | export default function Page(){
10 |
11 | async function handleLogin(formData: FormData){
12 | "use server"
13 |
14 | const email = formData.get("email")
15 | const password = formData.get("password")
16 |
17 | if(email === "" || password === ""){
18 | return;
19 | }
20 |
21 | try{
22 |
23 | const response = await api.post("/session", {
24 | email,
25 | password
26 | })
27 |
28 | if(!response.data.token){
29 | return;
30 | }
31 |
32 | console.log(response.data);
33 |
34 | const expressTime = 60 * 60 * 24 * 30 * 1000;
35 | cookies().set("session", response.data.token, {
36 | maxAge: expressTime,
37 | path: "/",
38 | httpOnly: false,
39 | secure: process.env.NODE_ENV === "production"
40 | })
41 |
42 | }catch(err){
43 | console.log(err);
44 | return;
45 | }
46 |
47 | redirect("/dashboard")
48 |
49 | }
50 |
51 | return(
52 | <>
53 |
54 |
58 |
59 |
87 |
88 |
89 | >
90 | )
91 | }
--------------------------------------------------------------------------------
/src/app/signup/page.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image"
2 | import Link from "next/link"
3 | import styles from '../page.module.scss'
4 | import logoImg from '/public/logo.svg'
5 | import { api } from '@/services/api'
6 | import { redirect } from 'next/navigation'
7 |
8 | export default function Signup(){
9 |
10 | async function handleRegister(formData: FormData){
11 | "use server"
12 |
13 | const name = formData.get("name")
14 | const email = formData.get("email")
15 | const password = formData.get("password")
16 |
17 | if( name === "" || email === "" || password === ""){
18 | console.log("PREENCHA TODOS OS CAMPOS")
19 | return;
20 | }
21 |
22 | try{
23 | await api.post("/users", {
24 | name,
25 | email,
26 | password
27 | })
28 |
29 | }catch(err){
30 | console.log("error")
31 | console.log(err)
32 | }
33 |
34 | redirect("/")
35 | }
36 |
37 | return(
38 | <>
39 |
40 |
44 |
45 |
82 |
83 |
84 | >
85 | )
86 | }
--------------------------------------------------------------------------------
/src/lib/cookieClient.ts:
--------------------------------------------------------------------------------
1 | import { getCookie } from "cookies-next"
2 |
3 | export function getCookieClient(){
4 | const token = getCookie("session")
5 | return token;
6 | }
--------------------------------------------------------------------------------
/src/lib/cookieServer.ts:
--------------------------------------------------------------------------------
1 | import { cookies } from 'next/headers'
2 |
3 | export function getCookieServer(){
4 | const token = cookies().get("session")?.value;
5 |
6 | return token || null;
7 | }
--------------------------------------------------------------------------------
/src/lib/helper.ts:
--------------------------------------------------------------------------------
1 | import { OrderItemProps } from "@/providers/order";
2 |
3 |
4 | export function calculateTotalOrder(orders: OrderItemProps[]){
5 | return orders.reduce((total, item) =>{
6 | const itemTotal = parseFloat(item.product.price) * item.amount;
7 | return total + itemTotal
8 | }, 0)
9 | }
--------------------------------------------------------------------------------
/src/lib/order.type.ts:
--------------------------------------------------------------------------------
1 | export interface OrderProps{
2 | id: string;
3 | table: string;
4 | name: string;
5 | draft: boolean;
6 | status: boolean;
7 | }
--------------------------------------------------------------------------------
/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { getCookieServer } from '@/lib/cookieServer'
3 | import { api } from "@/services/api"
4 |
5 | export async function middleware(req: NextRequest){
6 | const { pathname } = req.nextUrl
7 |
8 | if(pathname.startsWith("/_next") || pathname === "/"){
9 | return NextResponse.next();
10 | }
11 |
12 | const token = getCookieServer();
13 |
14 | if(pathname.startsWith("/dashboard")){
15 | if(!token){
16 | return NextResponse.redirect(new URL("/", req.url))
17 | }
18 |
19 | const isValid = await validateToken(token)
20 | console.log(isValid);
21 |
22 | if(!isValid){
23 | return NextResponse.redirect(new URL("/", req.url))
24 | }
25 | }
26 |
27 | return NextResponse.next();
28 |
29 | }
30 |
31 |
32 | async function validateToken(token: string){
33 | if (!token) return false;
34 |
35 | try{
36 | await api.get("/me", {
37 | headers:{
38 | Authorization: `Bearer ${token}`
39 | }
40 | })
41 |
42 | return true;
43 | }catch(err){
44 | return false;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/providers/order.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { createContext, ReactNode, useState} from "react"
4 | import { api } from "@/services/api";
5 | import { getCookieClient } from "@/lib/cookieClient";
6 | import { toast } from "sonner";
7 | import { useRouter } from "next/navigation";
8 |
9 | export interface OrderItemProps{
10 | id: string;
11 | amount: number;
12 | create_at: string;
13 | order_id: string;
14 | product_id: string;
15 | product:{
16 | id: string;
17 | name: string;
18 | price: string;
19 | description: string;
20 | banner: string;
21 | category_id: string;
22 | };
23 | order:{
24 | id: string;
25 | table: number;
26 | name: string | null;
27 | draft: boolean;
28 | status: boolean;
29 | }
30 | }
31 |
32 |
33 | type OrderContextData = {
34 | isOpen: boolean;
35 | onRequestOpen: (order_id: string) => Promise;
36 | onRequestClose: () => void;
37 | order: OrderItemProps[];
38 | finishOrder: (order_id: string) => Promise;
39 | }
40 |
41 | type OrderProviderProps = {
42 | children: ReactNode;
43 | }
44 |
45 | export const OrderContext = createContext({} as OrderContextData)
46 |
47 | export function OrderProvider({children}: OrderProviderProps){
48 | const [isOpen, setIsOpen] = useState(false);
49 | const [order, setOrder] = useState([])
50 | const router = useRouter();
51 |
52 | async function onRequestOpen(order_id: string){
53 | //console.log(order_id)
54 | const token = getCookieClient();
55 | const response = await api.get('/order/detail', {
56 | headers:{
57 | Authorization:`Bearer ${token}`
58 | },
59 | params:{
60 | order_id: order_id
61 | }
62 | })
63 | setOrder(response.data);
64 | setIsOpen(true);
65 | }
66 |
67 | function onRequestClose(){
68 | setIsOpen(false);
69 | }
70 |
71 | async function finishOrder(order_id: string) {
72 | const token = getCookieClient();
73 | const data = {
74 | order_id: order_id,
75 | }
76 | try{
77 | await api.put('/order/finish', data,{
78 | headers:{
79 | Authorization:`Bearer ${token}`
80 | }
81 | })
82 | }catch(err){
83 | console.log(err);
84 | toast.error("Falha ao finalizar o pedido!")
85 | return;
86 | }
87 | toast.success("Pedido finalizado!")
88 | router.refresh();
89 | setIsOpen(false);
90 |
91 | }
92 |
93 | return(
94 |
103 | {children}
104 |
105 | )
106 | }
--------------------------------------------------------------------------------
/src/services/api.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export const api = axios.create({
4 | baseURL: "http://localhost:3333"
5 | })
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./src/*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------