From 93d45185004322b9713b2c06f19632031d137c11 Mon Sep 17 00:00:00 2001 From: Damien Ostler Date: Sat, 2 Mar 2024 00:51:43 -0500 Subject: [PATCH] feat: refactoring + assets and reference images and reviews --- components/RequestDialog.tsx | 143 ----- components/artistPortfolioImage.tsx | 28 - components/{ => dashboard}/Onboarding.tsx | 17 +- .../admin/artistRequest.tsx} | 0 components/dashboard/artist/AssetImage.tsx | 41 ++ .../dashboard/artist/ReferenceImage.tsx | 41 ++ .../artist/editablePortfolio.tsx} | 10 +- .../artist/editablePortfolioImage.tsx | 59 ++ .../artist/portfolio.tsx} | 3 +- .../dashboard/artist/portfolioImage.tsx | 41 ++ components/{ => dashboard/artist}/reviews.tsx | 4 +- .../artistRequest.tsx} | 4 +- components/dashboard/customer/AssetImage.tsx | 41 ++ .../dashboard/customer/ReferenceImage.tsx | 41 ++ .../customer/orders.tsx} | 3 +- components/editableArtistPortfolioImage.tsx | 48 -- components/requestReferences.tsx | 28 - components/withOnboardingRequired.tsx | 325 ----------- navigation/vertical/index.ts | 16 +- package-lock.json | 100 +++- package.json | 4 + .../artist/requests/[requestId]/assets.tsx | 22 + .../requests/[requestId]/assets/[assetId].tsx | 24 + .../artist/requests/[requestId]/details.tsx | 16 + .../artist/requests/[requestId]/newasset.tsx | 52 ++ .../requests/[requestId]/references.tsx | 22 + .../[requestId]/references/[referenceId].tsx | 24 + pages/api/box/newRequest.tsx | 48 +- pages/api/requests/[requestId]/assets.tsx | 22 + .../requests/[requestId]/assets/[assetId].tsx | 24 + pages/api/requests/[requestId]/details.tsx | 16 + .../api/requests/[requestId]/newreference.tsx | 52 ++ pages/api/requests/[requestId]/references.tsx | 24 +- .../[requestId]/references/[referenceId].tsx | 24 + pages/box/[artistName].tsx | 122 +--- pages/dashboard/admin/requests.tsx | 2 +- pages/dashboard/artist/artistsettings.tsx | 4 +- pages/dashboard/artist/pagesettings.tsx | 6 +- pages/dashboard/artist/requests.tsx | 111 +--- .../dashboard/artist/requests/[requestId].tsx | 310 ++++++++++ pages/dashboard/index.tsx | 16 +- pages/dashboard/requests.tsx | 227 +------- pages/dashboard/requests/[requestId].tsx | 547 ++++++++++-------- 43 files changed, 1393 insertions(+), 1319 deletions(-) delete mode 100644 components/RequestDialog.tsx delete mode 100644 components/artistPortfolioImage.tsx rename components/{ => dashboard}/Onboarding.tsx (92%) rename components/{ArtistRequest.tsx => dashboard/admin/artistRequest.tsx} (100%) create mode 100644 components/dashboard/artist/AssetImage.tsx create mode 100644 components/dashboard/artist/ReferenceImage.tsx rename components/{editableArtistPortfolio.tsx => dashboard/artist/editablePortfolio.tsx} (90%) create mode 100644 components/dashboard/artist/editablePortfolioImage.tsx rename components/{artistPortfolio.tsx => dashboard/artist/portfolio.tsx} (96%) create mode 100644 components/dashboard/artist/portfolioImage.tsx rename components/{ => dashboard/artist}/reviews.tsx (95%) rename components/{artistDashboardRequest.tsx => dashboard/artistRequest.tsx} (95%) create mode 100644 components/dashboard/customer/AssetImage.tsx create mode 100644 components/dashboard/customer/ReferenceImage.tsx rename components/{Orders.tsx => dashboard/customer/orders.tsx} (98%) delete mode 100644 components/editableArtistPortfolioImage.tsx delete mode 100644 components/requestReferences.tsx delete mode 100644 components/withOnboardingRequired.tsx create mode 100644 pages/api/artist/requests/[requestId]/assets.tsx create mode 100644 pages/api/artist/requests/[requestId]/assets/[assetId].tsx create mode 100644 pages/api/artist/requests/[requestId]/details.tsx create mode 100644 pages/api/artist/requests/[requestId]/newasset.tsx create mode 100644 pages/api/artist/requests/[requestId]/references.tsx create mode 100644 pages/api/artist/requests/[requestId]/references/[referenceId].tsx create mode 100644 pages/api/requests/[requestId]/assets.tsx create mode 100644 pages/api/requests/[requestId]/assets/[assetId].tsx create mode 100644 pages/api/requests/[requestId]/details.tsx create mode 100644 pages/api/requests/[requestId]/newreference.tsx create mode 100644 pages/api/requests/[requestId]/references/[referenceId].tsx create mode 100644 pages/dashboard/artist/requests/[requestId].tsx diff --git a/components/RequestDialog.tsx b/components/RequestDialog.tsx deleted file mode 100644 index c2d3d96..0000000 --- a/components/RequestDialog.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import * as React from 'react'; -import { DataGrid, GridToolbar } from '@mui/x-data-grid'; -import { GridColDef } from '@mui/x-data-grid'; -import { Button, Stack, Typography } from '@mui/material'; -import { Chip, IconButton, Tooltip } from '@mui/material'; -import { AssignmentLateOutlined, AssignmentTurnedInOutlined, Check, Download, PriceCheck, PriceCheckOutlined, Refresh, ShoppingCartCheckout } from '@mui/icons-material'; -import { Card, CardContent } from '@mui/material'; -import Rating from '@mui/material/Rating'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Grid, TextField } from '@mui/material'; -import { useRouter } from 'next/router'; -import { useState, useEffect } from 'react'; - - -export default function RequestDialog({}) { - - - return ( - Request submitted on {formattedTime ?? ''} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {(!params.row.declined && !params.row.accepted && !params.row.paid && !params.row.completed ? ( - } label="Pending" variant="filled" color="secondary" /> - ):null)} - - - {(params.row.declined ? ( - } label="Declined" variant="filled" color="error" /> - ):null)} - - - {(params.row.accepted ? ( - } label="Accepted" variant="filled" color="info" /> - ):null)} - - - {(params.row.paid && params.row.acccepted ? ( - - ):null)} - - - {(params.row.paid==false && params.row.accepted ? ( - } label="Pending Payment" variant="filled" color="warning" /> - ):null)} - - - {(params.row.completed ? ( - } label="Completed" variant="filled" color="success" /> - ):null)} - - - - - - - - { - // setValue(newValue); - // }} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} \ No newline at end of file diff --git a/components/artistPortfolioImage.tsx b/components/artistPortfolioImage.tsx deleted file mode 100644 index e88d896..0000000 --- a/components/artistPortfolioImage.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as React from 'react'; -import ImageList from '@mui/material/ImageList'; -import ImageListItem from '@mui/material/ImageListItem'; -import { useEffect, useState } from "react"; - -import { CircularProgress } from '@mui/material'; - -import { IconButton } from '@mui/material'; - -const ArtistPortfolioImage = ({artistId,itemId}) => { - const [loaded, setLoaded] = useState(false); - const handleImageLoaded = () => { - setLoaded(true); - }; - - return ( - - {itemId} - ) -} -export default ArtistPortfolioImage \ No newline at end of file diff --git a/components/Onboarding.tsx b/components/dashboard/Onboarding.tsx similarity index 92% rename from components/Onboarding.tsx rename to components/dashboard/Onboarding.tsx index 1098d71..ba4e876 100644 --- a/components/Onboarding.tsx +++ b/components/dashboard/Onboarding.tsx @@ -1,19 +1,8 @@ import * as React from 'react'; -import Box from '@mui/material/Box'; -import Stepper from '@mui/material/Stepper'; -import Step from '@mui/material/Step'; -import StepLabel from '@mui/material/StepLabel'; -import StepContent from '@mui/material/StepContent'; -import Button from '@mui/material/Button'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import Grid from '@mui/material/Grid' -import TextField from '@mui/material/TextField'; -import ArtistDashboardRequest from './artistDashboardRequest'; -import ArtistPortfolio from '../components/artistPortfolio'; -import EditableArtistPortfolio from '../components/editableArtistPortfolio'; +import {Box,Stepper,Step,StepLabel,StepContent,Button,Paper,Typography,Grid,TextField} from "@mui/material" +import ArtistDashboardRequest from './artistRequest'; +import EditableArtistPortfolio from './artist/editablePortfolio'; import { useEffect, useState } from "react"; - import CurrencyTextField from '@lupus-ai/mui-currency-textfield'; import {Card, CardContent, CardHeader, Divider } from '@mui/material'; diff --git a/components/ArtistRequest.tsx b/components/dashboard/admin/artistRequest.tsx similarity index 100% rename from components/ArtistRequest.tsx rename to components/dashboard/admin/artistRequest.tsx diff --git a/components/dashboard/artist/AssetImage.tsx b/components/dashboard/artist/AssetImage.tsx new file mode 100644 index 0000000..273f31a --- /dev/null +++ b/components/dashboard/artist/AssetImage.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import ImageListItem from '@mui/material/ImageListItem'; +import { useState } from "react"; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; + +const AssetImage = ({ assetId, requestId }) => { + const [loaded, setLoaded] = useState(false); + const [openDialog, setOpenDialog] = useState(false); + const url = "/api/artist/requests/"+requestId+"/assets/"+assetId + const handleImageLoaded = () => { + setLoaded(true); + }; + + return ( + <> + setOpenDialog(true)}> + {assetId} + + {/* Dialog for displaying full-screen image */} + setOpenDialog(false)}> + + {assetId} + + + + ); +} + +export default AssetImage; diff --git a/components/dashboard/artist/ReferenceImage.tsx b/components/dashboard/artist/ReferenceImage.tsx new file mode 100644 index 0000000..a9d4ecb --- /dev/null +++ b/components/dashboard/artist/ReferenceImage.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import ImageListItem from '@mui/material/ImageListItem'; +import { useState } from "react"; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; + +const ReferenceImage = ({ referenceId, requestId }) => { + const [loaded, setLoaded] = useState(false); + const [openDialog, setOpenDialog] = useState(false); + const url = "/api/artist/requests/"+requestId+"/references/"+referenceId + const handleImageLoaded = () => { + setLoaded(true); + }; + + return ( + <> + setOpenDialog(true)}> + {referenceId} + + {/* Dialog for displaying full-screen image */} + setOpenDialog(false)}> + + {referenceId} + + + + ); +} + +export default ReferenceImage; diff --git a/components/editableArtistPortfolio.tsx b/components/dashboard/artist/editablePortfolio.tsx similarity index 90% rename from components/editableArtistPortfolio.tsx rename to components/dashboard/artist/editablePortfolio.tsx index 3e5072b..391fa06 100644 --- a/components/editableArtistPortfolio.tsx +++ b/components/dashboard/artist/editablePortfolio.tsx @@ -1,11 +1,9 @@ import * as React from 'react'; -import { ImageList, Box, Button, CircularProgress, Slider, IconButton } from '@mui/material'; import { useEffect, useState } from "react"; -import EditableArtistPortfolioImage from './editableArtistPortfolioImage'; -import FileOpenIcon from '@mui/icons-material/FileOpen'; -import { Tooltip } from '@mui/material'; -import { Grid } from '@mui/material'; +import { Grid, ImageList, Box, Tooltip, CircularProgress, Slider, IconButton } from '@mui/material'; import { FileUpload } from '@mui/icons-material'; +import EditableArtistPortfolioImage from './editablePortfolioImage'; + const EditableArtistPortfolio = ({ artistId }) => { const [portfolioData, setPortfolioData] = useState([]); const [columns, setColumns] = useState(2); @@ -64,7 +62,7 @@ const EditableArtistPortfolio = ({ artistId }) => { diff --git a/components/dashboard/artist/editablePortfolioImage.tsx b/components/dashboard/artist/editablePortfolioImage.tsx new file mode 100644 index 0000000..65168cc --- /dev/null +++ b/components/dashboard/artist/editablePortfolioImage.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { Dialog, DialogContent, ImageList,ImageListItem, ImageListItemBar } from '@mui/material'; +import { IconButton } from '@mui/material'; +import { useEffect, useState } from "react"; + +const EditableArtistPortfolioImage = ({ artistId, itemId, reload }) => { + const [loaded, setLoaded] = useState(false); + const [deleting, setDeleting] = useState(false); + const [openDialog, setOpenDialog] = useState(false); // State for controlling the dialog + + const handleImageLoaded = () => { + setLoaded(true); + }; + + const deleteButton = () => { + setDeleting(true); + fetch('/api/artist/portfolio/' + itemId + "/delete", { + method: 'DELETE' + }).then(response => { + reload().then(data => { + }) + }) + } + + return ( + <> + setOpenDialog(true)}> + {itemId} + + + + }> + + + {/* Dialog for displaying full-screen image */} + setOpenDialog(false)}> + + {itemId} + + + + ); +} + +export default EditableArtistPortfolioImage; diff --git a/components/artistPortfolio.tsx b/components/dashboard/artist/portfolio.tsx similarity index 96% rename from components/artistPortfolio.tsx rename to components/dashboard/artist/portfolio.tsx index 342f3e2..4c1b189 100644 --- a/components/artistPortfolio.tsx +++ b/components/dashboard/artist/portfolio.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; import {ImageList, Box, Typography, CircularProgress} from '@mui/material'; import { useEffect, useState } from "react"; -import ArtistPortfolioImage from './artistPortfolioImage'; - +import ArtistPortfolioImage from './portfolioImage'; const ArtistPortfolio = ({masonry,columns,artistId}) => { const [portfolioData, setPortfolioData] = useState([]); const [profileId, setArtistId] = useState(artistId) diff --git a/components/dashboard/artist/portfolioImage.tsx b/components/dashboard/artist/portfolioImage.tsx new file mode 100644 index 0000000..6125b99 --- /dev/null +++ b/components/dashboard/artist/portfolioImage.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import ImageListItem from '@mui/material/ImageListItem'; +import { useState } from "react"; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; + +const ArtistPortfolioImage = ({ artistId, itemId }) => { + const [loaded, setLoaded] = useState(false); + const [openDialog, setOpenDialog] = useState(false); + + const handleImageLoaded = () => { + setLoaded(true); + }; + + return ( + <> + setOpenDialog(true)}> + {itemId} + + {/* Dialog for displaying full-screen image */} + setOpenDialog(false)}> + + {itemId} + + + + ); +} + +export default ArtistPortfolioImage; diff --git a/components/reviews.tsx b/components/dashboard/artist/reviews.tsx similarity index 95% rename from components/reviews.tsx rename to components/dashboard/artist/reviews.tsx index 41e6dc1..34de384 100644 --- a/components/reviews.tsx +++ b/components/dashboard/artist/reviews.tsx @@ -12,8 +12,8 @@ import { DateField } from '@mui/x-date-pickers'; export default function Reviews({artistId}) { const router = useRouter(); const columns = [ - { field: 'requestId', headerName: 'Request ID', flex: 0.1}, - { field: 'message', headerName: 'Review Message', flex: 0.5}, + { field: 'requestId', headerName: 'ID', flex: 0.1}, + { field: 'message', headerName: 'Review', flex: 0.5}, { field: 'rating', headerName: 'Rating', flex: 0.2, renderCell: (params) => { return ; }} diff --git a/components/artistDashboardRequest.tsx b/components/dashboard/artistRequest.tsx similarity index 95% rename from components/artistDashboardRequest.tsx rename to components/dashboard/artistRequest.tsx index ca52729..4bd76bc 100644 --- a/components/artistDashboardRequest.tsx +++ b/components/dashboard/artistRequest.tsx @@ -5,7 +5,7 @@ import CircularProgress from '@mui/material/CircularProgress'; import Box from '@mui/material/Box'; -const ArtistDashboardRequest = () => { +const ArtistOnboardRequest = () => { const [sellerRequestData, setArtistRequestData] = useState(null); const getData = async () => { @@ -50,4 +50,4 @@ const ArtistDashboardRequest = () => { )) ) } -export default ArtistDashboardRequest \ No newline at end of file +export default ArtistOnboardRequest \ No newline at end of file diff --git a/components/dashboard/customer/AssetImage.tsx b/components/dashboard/customer/AssetImage.tsx new file mode 100644 index 0000000..45470d1 --- /dev/null +++ b/components/dashboard/customer/AssetImage.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import ImageListItem from '@mui/material/ImageListItem'; +import { useState } from "react"; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; + +const AssetImage = ({ assetId, requestId }) => { + const [loaded, setLoaded] = useState(false); + const [openDialog, setOpenDialog] = useState(false); + const url = "/api/requests/"+requestId+"/assets/"+assetId + const handleImageLoaded = () => { + setLoaded(true); + }; + + return ( + <> + setOpenDialog(true)}> + {assetId} + + {/* Dialog for displaying full-screen image */} + setOpenDialog(false)}> + + {assetId} + + + + ); +} + +export default AssetImage; diff --git a/components/dashboard/customer/ReferenceImage.tsx b/components/dashboard/customer/ReferenceImage.tsx new file mode 100644 index 0000000..53b2844 --- /dev/null +++ b/components/dashboard/customer/ReferenceImage.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import ImageListItem from '@mui/material/ImageListItem'; +import { useState } from "react"; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; + +const ReferenceImage = ({ referenceId, requestId }) => { + const [loaded, setLoaded] = useState(false); + const [openDialog, setOpenDialog] = useState(false); + const url = "/api/requests/"+requestId+"/references/"+referenceId + const handleImageLoaded = () => { + setLoaded(true); + }; + + return ( + <> + setOpenDialog(true)}> + {referenceId} + + {/* Dialog for displaying full-screen image */} + setOpenDialog(false)}> + + {referenceId} + + + + ); +} + +export default ReferenceImage; diff --git a/components/Orders.tsx b/components/dashboard/customer/orders.tsx similarity index 98% rename from components/Orders.tsx rename to components/dashboard/customer/orders.tsx index c8f601b..38523a2 100644 --- a/components/Orders.tsx +++ b/components/dashboard/customer/orders.tsx @@ -12,11 +12,12 @@ import {Check, Refresh } from '@mui/icons-material'; import PriceCheckIcon from '@mui/icons-material/PriceCheck'; import AssignmentTurnedInIcon from '@mui/icons-material/AssignmentTurnedIn'; import AssignmentLateIcon from '@mui/icons-material/AssignmentLate'; +import { useEffect, useState } from "react"; import dayjs from 'dayjs'; -export default function ServerPaginationGrid() { +export default function CustomerOrders() { const columns = [ { field: 'id', headerName: 'ID', flex: 0.1}, { field: 'status', headerName: 'Status', flex: 0.15, diff --git a/components/editableArtistPortfolioImage.tsx b/components/editableArtistPortfolioImage.tsx deleted file mode 100644 index 92b4162..0000000 --- a/components/editableArtistPortfolioImage.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import * as React from 'react'; -import ImageList from '@mui/material/ImageList'; -import ImageListItem from '@mui/material/ImageListItem'; -import { useEffect, useState } from "react"; -import DeleteIcon from '@mui/icons-material/Delete'; - -import { CircularProgress, ImageListItemBar } from '@mui/material'; - -import { IconButton } from '@mui/material'; - -const EditableArtistPortfolioImage = ({artistId,itemId,reload}) => { - const [loaded, setLoaded] = useState(false); - const [deleting, setDeleting] = useState(false); - const handleImageLoaded = () => { - setLoaded(true); - }; - - - const deleteButton = () => { - setDeleting(true); - fetch('/api/artist/portfolio/'+itemId+"/delete", { - method: 'DELETE' - }).then(response => { - reload().then(data => { - }) - }) - } - - return ( - - {itemId} - - - - }> - - ) -} -export default EditableArtistPortfolioImage \ No newline at end of file diff --git a/components/requestReferences.tsx b/components/requestReferences.tsx deleted file mode 100644 index f6fbc31..0000000 --- a/components/requestReferences.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import * as React from 'react'; -import {useEffect } from "react"; -import { ImageList, ImageListItem } from '@mui/material'; - -export default function RequestReferences({id}) { - const [open, setOpen] = React.useState(false); - const [refrences, setReferences] = React.useState([]); - - const getData = async () => { - const response = await fetch('/api/requests/' + id + '/references'); - const references = await response.json(); - setReferences(references); - - } - - useEffect(() => { - getData(); - }, []); - - return ( - {refrences.map((item) => ( - - {item.title} - - ))} - - ); -} \ No newline at end of file diff --git a/components/withOnboardingRequired.tsx b/components/withOnboardingRequired.tsx deleted file mode 100644 index 967341d..0000000 --- a/components/withOnboardingRequired.tsx +++ /dev/null @@ -1,325 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import Stepper from '@mui/material/Stepper'; -import Step from '@mui/material/Step'; -import StepLabel from '@mui/material/StepLabel'; -import StepContent from '@mui/material/StepContent'; -import Button from '@mui/material/Button'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import Grid from '@mui/material/Grid' -import TextField from '@mui/material/TextField'; -import ArtistDashboardRequest from '../components/OLd/artistDashboardRequest'; -import ArtistPortfolio from '../components/OLd/artistPortfolio'; -import EditableArtistPortfolio from '../components/OLd/editableArtistPortfolio'; -import { useEffect, useState } from "react"; - -import CurrencyTextField from '@lupus-ai/mui-currency-textfield'; -import {Card, CardContent, CardHeader, Divider } from '@mui/material'; - - -const steps = [ - { - label: 'Request Access As Artist', - description: `In order to start selling your art on our platform, you need to request access. Please include links to your social media and tag or DM us on the platform (@RequestDotBox). We may reach out for further verification and examples of your work.`, - }, - { - label: 'Onboard On Stripe', - description: - 'Our platform uses Stripe as a payment processor. You will be required to onboard with them with all of your payout information and business information.', - }, - { - label: 'Setup Your Portfolio', - description: `This is where you can setup your initial portfolio. You can upload any image format file to your portfolio. It will be automatically displayed on your artist page. You can add and remove from this later.`, - }, - { - label: 'Configure Your Artist Page', - description: `Every artist gets their own public facing page that they can send to anyone or post anywhere. You have full control over the colors, logos, and more of this page.`, - }, -]; - -export default function onboarding() { - const [activeStep, setActiveStep] = React.useState(0); - const [sellerRequestData, setArtistRequestData] = React.useState(null); - const [profileData, setArtistData] = React.useState(null); - const [isStripeOnboarded, setIsStripeOnboarded] = React.useState(false); - const [onBoardUrl, setOnBoardUrl] = React.useState(""); - - - - const handleNext = () => { - setActiveStep((prevActiveStep) => prevActiveStep + 1); - }; - - const handleBack = () => { - setActiveStep((prevActiveStep) => prevActiveStep - 1); - }; - - const handleReset = () => { - setActiveStep(0); - }; - - const getData = async () => { - const onboardCheckRequest = await fetch('/api/artist/onboarded', { method: "GET" }); - const onboardCheckResponse = await onboardCheckRequest.json(); - setIsStripeOnboarded(onboardCheckResponse["onboarded"]); - const onboardUrlRequest = await fetch('/api/artist/onboardurl', { method: "GET" }); - const onboardUrlResponse = await onboardUrlRequest.json(); - setOnBoardUrl(onboardUrlResponse["onboardUrl"]); - const response = await fetch('/api/artist/request'); - const sellerRequest = await response.json(); - setArtistRequestData(sellerRequest); - const profileResponse = await fetch('/api/artist/profile'); - const sellerProfile = await profileResponse.json(); - setArtistData(sellerProfile); - - setTimeout(getData, 5000); // Poll every 5 seconds (adjust as needed) - } - React.useEffect(() => { - getData(); - }, []); - - const requestButton = () => { - fetch('/api/artist/newRequest').then((response) => { - if (response.ok) { - fetch('/api/artist/request').then((requestResponse) => { - requestResponse.json().then((sellerRequest) => { - setArtistRequestData(sellerRequest); - }); - }); - } - }); - } - - let formattedTime = "" - if (sellerRequestData) { - const date = new Date(sellerRequestData["requestDate"]); - formattedTime = date.toLocaleTimeString('en-US', { month: 'long', day: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); // Example format - } - - if(activeStep==4){ - window.location.href="/dashboard" - } - - return ( - - - {steps.map((step, index) => ( - - Last step - ) : null - } - > - {step.label} - - {(index==0) ? ( - - - - {step.description} - - - - {(sellerRequestData && Object.keys(sellerRequestData).length>0) ? ( - - - - ):( - - - - )} - - -
- {(sellerRequestData && Object.keys(sellerRequestData).length>0) ? ( - (sellerRequestData["accepted"]) ? ( - - ) : ( - - ) - ): - ( - - - )} -
-
-
- ): null} - {(index==1) ? ( - - - - {step.description} - - - -
- {isStripeOnboarded==true ? ( - - ):( - - - )} -
-
-
- ): null} - {(index==2) ? ( - - - - {step.description} - - - - - - -
- -
-
-
- ): null} - {(index==3) ? ( - - - - {step.description} - - - - - - - - - - - - - - Artist Name - - - - - - Biography - - - - - - TAwehtwaehrewaoioirewaoihroiewahroiewahriewaohroiewahroiweahroiewahrhweaoirhewaiorhewaoirhewaoirhewaoirhweah - - - - - Portfolio - - - - - - - - - Requests - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
- ): null} -
- ))} -
- {activeStep === steps.length && ( - - All steps completed - you're finished - - - )} -
- ); -} \ No newline at end of file diff --git a/navigation/vertical/index.ts b/navigation/vertical/index.ts index 13fdbf0..9b63eaf 100644 --- a/navigation/vertical/index.ts +++ b/navigation/vertical/index.ts @@ -4,7 +4,7 @@ import Table from 'mdi-material-ui/Table' import CubeOutline from 'mdi-material-ui/CubeOutline' import HomeOutline from 'mdi-material-ui/HomeOutline' import SettingsApplicationsIcon from '@mui/icons-material/SettingsApplications'; - +import ListIcon from '@mui/icons-material/List'; // ** Type import import { VerticalNavItemsType } from '../../core/layouts/types' import { BankTransfer, Cart, Clipboard, PageFirst, StarOutline } from 'mdi-material-ui' @@ -37,6 +37,11 @@ const navigation = (): VerticalNavItemsType => { }, []); var result = [ + { + title: 'Dashboard', + icon: HomeOutline, + path: '/dashboard' + }, { sectionTitle: 'Admin' }, @@ -48,11 +53,6 @@ const navigation = (): VerticalNavItemsType => { { sectionTitle: 'General' }, - { - title: 'Dashboard', - icon: HomeOutline, - path: '/dashboard' - }, { title: 'Account Settings', icon: Settings, @@ -60,7 +60,7 @@ const navigation = (): VerticalNavItemsType => { }, { title: 'Your Requests', - icon: Cart, + icon: ListIcon, path: '/dashboard/requests' } ]; @@ -77,7 +77,7 @@ const navigation = (): VerticalNavItemsType => { }, { title: 'Incoming Requests', - icon: CubeOutline, + icon: ListIcon, path: '/dashboard/artist/requests' }, { diff --git a/package-lock.json b/package-lock.json index 95d384d..e4ea8c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,13 +17,17 @@ "@mui/x-data-grid": "^6.19.4", "@mui/x-date-pickers": "^6.19.4", "@novu/notification-center": "^0.22.0", + "@types/formidable": "^3.4.5", "apexcharts": "^3.45.2", + "axios": "^1.6.7", + "busboy": "^1.6.0", "dayjs": "^1.11.10", "formidable": "^3.5.1", "jwt-decode": "^4.0.0", "mdi-material-ui": "^7.8.0", "mui-color-input": "^2.0.2", "next": "latest", + "node-fetch": "^3.3.2", "nprogress": "^0.2.0", "openapi-typescript-fetch": "^1.1.3", "react": "^18.2.0", @@ -1271,11 +1275,18 @@ } } }, + "node_modules/@types/formidable": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-3.4.5.tgz", + "integrity": "sha512-s7YPsNVfnsng5L8sKnG/Gbb2tiwwJTY1conOkJzTMRvJAlLFW1nEua+ADsJQu8N1c0oTHx9+d5nqg10WuT9gHQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "18.19.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz", "integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -1590,6 +1601,14 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, "node_modules/date-fns": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", @@ -1728,6 +1747,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -1765,6 +1806,17 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/formidable": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", @@ -2120,6 +2172,41 @@ } } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/nprogress": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", @@ -2791,8 +2878,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/url-join": { "version": "4.0.1", @@ -2865,6 +2951,14 @@ "loose-envify": "^1.0.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webfontloader": { "version": "1.6.28", "resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz", diff --git a/package.json b/package.json index 8de24bf..7e309d7 100644 --- a/package.json +++ b/package.json @@ -18,13 +18,17 @@ "@mui/x-data-grid": "^6.19.4", "@mui/x-date-pickers": "^6.19.4", "@novu/notification-center": "^0.22.0", + "@types/formidable": "^3.4.5", "apexcharts": "^3.45.2", + "axios": "^1.6.7", + "busboy": "^1.6.0", "dayjs": "^1.11.10", "formidable": "^3.5.1", "jwt-decode": "^4.0.0", "mdi-material-ui": "^7.8.0", "mui-color-input": "^2.0.2", "next": "latest", + "node-fetch": "^3.3.2", "nprogress": "^0.2.0", "openapi-typescript-fetch": "^1.1.3", "react": "^18.2.0", diff --git a/pages/api/artist/requests/[requestId]/assets.tsx b/pages/api/artist/requests/[requestId]/assets.tsx new file mode 100644 index 0000000..3c297fc --- /dev/null +++ b/pages/api/artist/requests/[requestId]/assets.tsx @@ -0,0 +1,22 @@ +import { getAccessToken, withApiAuthRequired, getSession } from '@auth0/nextjs-auth0'; +import { IncomingForm } from 'formidable'; +import fs from 'fs/promises'; + +async function createBlobFromFile(path: string): Promise { + const file = await fs.readFile(path); + return new Blob([file]); +} +export default withApiAuthRequired(async function products(req, res) { + const { accessToken } = await getAccessToken(req, res); + const requestId = req.query.requestId; + const response = await fetch(process.env.NEXT_PUBLIC_API_URL+'/api/Requests/Artist/'+requestId+'/Assets', { + headers: { + "Authorization": `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }, + method: req.method + }); + let result = await response.json(); + res.status(200).json(result); +}); + diff --git a/pages/api/artist/requests/[requestId]/assets/[assetId].tsx b/pages/api/artist/requests/[requestId]/assets/[assetId].tsx new file mode 100644 index 0000000..ced6b6e --- /dev/null +++ b/pages/api/artist/requests/[requestId]/assets/[assetId].tsx @@ -0,0 +1,24 @@ +import { getAccessToken, withApiAuthRequired } from '@auth0/nextjs-auth0'; +import axios from 'axios'; +import fs from 'fs'; +import path from 'path'; + +export default withApiAuthRequired(async function references(req, res) { + const { accessToken } = await getAccessToken(req, res); + const requestId = req.query.requestId; + const assetId = req.query.assetId; + const response = await axios({ + method: 'get', + url: `${process.env.NEXT_PUBLIC_API_URL}/api/Requests/Artist/${requestId}/Assets/${assetId}`, + responseType: 'stream', + headers: { + + "Authorization": `Bearer ${accessToken}`, + } + }) + + res.setHeader('Content-Type', response.headers['content-type']); + + // Pipe the response stream directly to the response of the Next.js API Route + response.data.pipe(res); +}); diff --git a/pages/api/artist/requests/[requestId]/details.tsx b/pages/api/artist/requests/[requestId]/details.tsx new file mode 100644 index 0000000..59cebdd --- /dev/null +++ b/pages/api/artist/requests/[requestId]/details.tsx @@ -0,0 +1,16 @@ +import { getAccessToken, withApiAuthRequired, getSession } from '@auth0/nextjs-auth0'; + +export default withApiAuthRequired(async function requestDetails(req, res) { + const { accessToken } = await getAccessToken(req, res); + const requestId = req.query.requestId; + const response = await fetch(process.env.NEXT_PUBLIC_API_URL+'/api/Requests/Artist/'+requestId, { + headers: { + "Authorization": `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + }); + let result = await response.json(); + res.status(200).json(result); +}); + diff --git a/pages/api/artist/requests/[requestId]/newasset.tsx b/pages/api/artist/requests/[requestId]/newasset.tsx new file mode 100644 index 0000000..5ca6bfd --- /dev/null +++ b/pages/api/artist/requests/[requestId]/newasset.tsx @@ -0,0 +1,52 @@ +import { getAccessToken, withApiAuthRequired } from '@auth0/nextjs-auth0'; +import { IncomingForm } from 'formidable' +import fs from 'fs/promises'; + +export const config = { + api: { + bodyParser: false, + }, +}; +async function createBlobFromFile(path: string): Promise { + const file = await fs.readFile(path); + return new Blob([file]); +} + +export default withApiAuthRequired(async function handler(req, res) { + const { accessToken } = await getAccessToken(req, res); + + const form = new IncomingForm(); + const requestId = req.query.requestId; + + form.parse(req, async (err, fields, files) => { + if (err) { + console.error('Error parsing form:', err); + res.status(500).json({ error: 'Error parsing form' }); + return; + } + const file = files["newImage"]; // Assuming your file input field name is 'file' + try { + + const response = await fetch(process.env.NEXT_PUBLIC_API_URL+'/api/Requests/Artist/'+requestId+'/Assets', { + method: 'POST', + headers: { + "Authorization": `Bearer ${accessToken}`, + "Content-Type": " application/octet-stream" + }, + body: await createBlobFromFile(file[0].filepath) // Don't set Content-Type, FormData will handle it + }); + + (response) + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || 'Failed to upload file'); + } + + const responseData = await response.json(); + res.status(200).json(responseData); + } catch (error) { + console.error('Error uploading file:', error); + res.status(500).json({ error: 'Error uploading file' }); + } + }); +}); diff --git a/pages/api/artist/requests/[requestId]/references.tsx b/pages/api/artist/requests/[requestId]/references.tsx new file mode 100644 index 0000000..0c6b051 --- /dev/null +++ b/pages/api/artist/requests/[requestId]/references.tsx @@ -0,0 +1,22 @@ +import { getAccessToken, withApiAuthRequired, getSession } from '@auth0/nextjs-auth0'; +import { IncomingForm } from 'formidable'; +import fs from 'fs/promises'; + +async function createBlobFromFile(path: string): Promise { + const file = await fs.readFile(path); + return new Blob([file]); +} +export default withApiAuthRequired(async function products(req, res) { + const { accessToken } = await getAccessToken(req, res); + const requestId = req.query.requestId; + const response = await fetch(process.env.NEXT_PUBLIC_API_URL+'/api/Requests/Artist/'+requestId+'/References', { + headers: { + "Authorization": `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }, + method: req.method + }); + let result = await response.json(); + res.status(200).json(result); +}); + diff --git a/pages/api/artist/requests/[requestId]/references/[referenceId].tsx b/pages/api/artist/requests/[requestId]/references/[referenceId].tsx new file mode 100644 index 0000000..dbe88b4 --- /dev/null +++ b/pages/api/artist/requests/[requestId]/references/[referenceId].tsx @@ -0,0 +1,24 @@ +import { getAccessToken, withApiAuthRequired } from '@auth0/nextjs-auth0'; +import axios from 'axios'; +import fs from 'fs'; +import path from 'path'; + +export default withApiAuthRequired(async function references(req, res) { + const { accessToken } = await getAccessToken(req, res); + const requestId = req.query.requestId; + const referenceId = req.query.referenceId; + const response = await axios({ + method: 'get', + url: `${process.env.NEXT_PUBLIC_API_URL}/api/Requests/Artist/${requestId}/References/${referenceId}`, + responseType: 'stream', + headers: { + + "Authorization": `Bearer ${accessToken}`, + } + }) + + res.setHeader('Content-Type', response.headers['content-type']); + + // Pipe the response stream directly to the response of the Next.js API Route + response.data.pipe(res); +}); diff --git a/pages/api/box/newRequest.tsx b/pages/api/box/newRequest.tsx index f7fd12b..d3305ff 100644 --- a/pages/api/box/newRequest.tsx +++ b/pages/api/box/newRequest.tsx @@ -1,24 +1,34 @@ -import { useRouter } from 'next/router' import { getAccessToken } from '@auth0/nextjs-auth0'; +import fetch from 'node-fetch'; // Import node-fetch for making HTTP requests +export default async function handler(req, res) { + if (req.method === 'POST') { + const url = process.env.NEXT_PUBLIC_API_URL + `/api/Requests/Request`; + const { accessToken } = await getAccessToken(req, res); -export default async function handler(req, res ): Promise { - const { artistName } = req.query; - var url = process.env.NEXT_PUBLIC_API_URL+`/api/Requests/Request`; - const { accessToken } = await getAccessToken(req, res); - const response = await fetch(url,{ - method: 'POST', - headers: { + try { + const response = await fetch(url, { + method: 'POST', + headers: { + "Authorization": `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }, + body: req.body, // Pipe the incoming request directly to the outgoing request + }); - "Authorization": `Bearer ${accessToken}`, - 'Content-Type': 'application/json' - }, - body: req.body - }); - //console.log(response) - if (!response.ok) { - throw new Error('Failed to fetch seller'); + if (!response.ok) { + const errorData = await response.json(); + res.status(response.status).json(errorData); + return; + } + + const result = await response.json(); + res.status(200).json(result); + } catch (error) { + console.error('Error occurred during fetch:', error); + res.status(500).json({ error: 'An error occurred during the request' }); + } + } else { + res.status(405).json({ error: 'Method Not Allowed' }); } - let result = await response.json(); - res.status(200).json(result); -} \ No newline at end of file +} diff --git a/pages/api/requests/[requestId]/assets.tsx b/pages/api/requests/[requestId]/assets.tsx new file mode 100644 index 0000000..06c3c5c --- /dev/null +++ b/pages/api/requests/[requestId]/assets.tsx @@ -0,0 +1,22 @@ +import { getAccessToken, withApiAuthRequired, getSession } from '@auth0/nextjs-auth0'; +import { IncomingForm } from 'formidable'; +import fs from 'fs/promises'; + +async function createBlobFromFile(path: string): Promise { + const file = await fs.readFile(path); + return new Blob([file]); +} +export default withApiAuthRequired(async function products(req, res) { + const { accessToken } = await getAccessToken(req, res); + const requestId = req.query.requestId; + const response = await fetch(process.env.NEXT_PUBLIC_API_URL+'/api/Requests/Customer/'+requestId+'/Assets', { + headers: { + "Authorization": `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }, + method: req.method + }); + let result = await response.json(); + res.status(200).json(result); +}); + diff --git a/pages/api/requests/[requestId]/assets/[assetId].tsx b/pages/api/requests/[requestId]/assets/[assetId].tsx new file mode 100644 index 0000000..e6f81d9 --- /dev/null +++ b/pages/api/requests/[requestId]/assets/[assetId].tsx @@ -0,0 +1,24 @@ +import { getAccessToken, withApiAuthRequired } from '@auth0/nextjs-auth0'; +import axios from 'axios'; +import fs from 'fs'; +import path from 'path'; + +export default withApiAuthRequired(async function references(req, res) { + const { accessToken } = await getAccessToken(req, res); + const requestId = req.query.requestId; + const assetId = req.query.assetId; + const response = await axios({ + method: 'get', + url: `${process.env.NEXT_PUBLIC_API_URL}/api/Requests/Customer/${requestId}/Assets/${assetId}`, + responseType: 'stream', + headers: { + + "Authorization": `Bearer ${accessToken}`, + } + }) + + res.setHeader('Content-Type', response.headers['content-type']); + + // Pipe the response stream directly to the response of the Next.js API Route + response.data.pipe(res); +}); diff --git a/pages/api/requests/[requestId]/details.tsx b/pages/api/requests/[requestId]/details.tsx new file mode 100644 index 0000000..d85c0e8 --- /dev/null +++ b/pages/api/requests/[requestId]/details.tsx @@ -0,0 +1,16 @@ +import { getAccessToken, withApiAuthRequired, getSession } from '@auth0/nextjs-auth0'; + +export default withApiAuthRequired(async function requestDetails(req, res) { + const { accessToken } = await getAccessToken(req, res); + const requestId = req.query.requestId; + const response = await fetch(process.env.NEXT_PUBLIC_API_URL+'/api/Requests/Customer/'+requestId, { + headers: { + "Authorization": `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + }); + let result = await response.json(); + res.status(200).json(result); +}); + diff --git a/pages/api/requests/[requestId]/newreference.tsx b/pages/api/requests/[requestId]/newreference.tsx new file mode 100644 index 0000000..34a9e07 --- /dev/null +++ b/pages/api/requests/[requestId]/newreference.tsx @@ -0,0 +1,52 @@ +import { getAccessToken, withApiAuthRequired } from '@auth0/nextjs-auth0'; +import { IncomingForm } from 'formidable' +import fs from 'fs/promises'; + +export const config = { + api: { + bodyParser: false, + }, +}; +async function createBlobFromFile(path: string): Promise { + const file = await fs.readFile(path); + return new Blob([file]); +} + +export default withApiAuthRequired(async function handler(req, res) { + const { accessToken } = await getAccessToken(req, res); + + const form = new IncomingForm(); + const requestId = req.query.requestId; + + form.parse(req, async (err, fields, files) => { + if (err) { + console.error('Error parsing form:', err); + res.status(500).json({ error: 'Error parsing form' }); + return; + } + const file = files["newImage"]; // Assuming your file input field name is 'file' + try { + + const response = await fetch(process.env.NEXT_PUBLIC_API_URL+'/api/Requests/Customer/'+requestId+'/References', { + method: 'POST', + headers: { + "Authorization": `Bearer ${accessToken}`, + "Content-Type": " application/octet-stream" + }, + body: await createBlobFromFile(file[0].filepath) // Don't set Content-Type, FormData will handle it + }); + + (response) + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || 'Failed to upload file'); + } + + const responseData = await response.json(); + res.status(200).json(responseData); + } catch (error) { + console.error('Error uploading file:', error); + res.status(500).json({ error: 'Error uploading file' }); + } + }); +}); diff --git a/pages/api/requests/[requestId]/references.tsx b/pages/api/requests/[requestId]/references.tsx index febf661..ee1899c 100644 --- a/pages/api/requests/[requestId]/references.tsx +++ b/pages/api/requests/[requestId]/references.tsx @@ -1,32 +1,22 @@ import { getAccessToken, withApiAuthRequired, getSession } from '@auth0/nextjs-auth0'; +import { IncomingForm } from 'formidable'; +import fs from 'fs/promises'; +async function createBlobFromFile(path: string): Promise { + const file = await fs.readFile(path); + return new Blob([file]); +} export default withApiAuthRequired(async function products(req, res) { const { accessToken } = await getAccessToken(req, res); const requestId = req.query.requestId; - if(req.method == 'GET'){ - const response = await fetch(process.env.NEXT_PUBLIC_API_URL+'/api/Requests/Customer/'+requestId+'/Reference', { + const response = await fetch(process.env.NEXT_PUBLIC_API_URL+'/api/Requests/Customer/'+requestId+'/References', { headers: { "Authorization": `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, method: req.method }); - console.log(response) let result = await response.json(); res.status(200).json(result); - } - else{ - const response = await fetch(process.env.NEXT_PUBLIC_API_URL+'/api/Requests/Customer/'+requestId+'/Reference', { - headers: { - "Authorization": `Bearer ${accessToken}`, - 'Content-Type': 'application/json' - }, - method: req.method, - body: req.body - }); - console.log(response) - let result = await response.json(); - res.status(200).json(result); - } }); diff --git a/pages/api/requests/[requestId]/references/[referenceId].tsx b/pages/api/requests/[requestId]/references/[referenceId].tsx new file mode 100644 index 0000000..c04a27f --- /dev/null +++ b/pages/api/requests/[requestId]/references/[referenceId].tsx @@ -0,0 +1,24 @@ +import { getAccessToken, withApiAuthRequired } from '@auth0/nextjs-auth0'; +import axios from 'axios'; +import fs from 'fs'; +import path from 'path'; + +export default withApiAuthRequired(async function references(req, res) { + const { accessToken } = await getAccessToken(req, res); + const requestId = req.query.requestId; + const referenceId = req.query.referenceId; + const response = await axios({ + method: 'get', + url: `${process.env.NEXT_PUBLIC_API_URL}/api/Requests/Customer/${requestId}/References/${referenceId}`, + responseType: 'stream', + headers: { + + "Authorization": `Bearer ${accessToken}`, + } + }) + + res.setHeader('Content-Type', response.headers['content-type']); + + // Pipe the response stream directly to the response of the Next.js API Route + response.data.pipe(res); +}); diff --git a/pages/box/[artistName].tsx b/pages/box/[artistName].tsx index 681c11e..28e38a2 100644 --- a/pages/box/[artistName].tsx +++ b/pages/box/[artistName].tsx @@ -16,11 +16,12 @@ import DialogContentText from '@mui/material/DialogContentText'; import DialogActions from '@mui/material/DialogActions'; import CurrencyTextField from '@lupus-ai/mui-currency-textfield'; import TextField from '@mui/material/TextField'; -import ArtistPortfolio from "../../components/Old/artistPortfolio"; +import ArtistPortfolio from "../../components/dashboard/artist/portfolio"; import { RouterNetwork } from "mdi-material-ui"; import { useRouter } from "next/router"; import { profile } from "console"; import FileOpen from "@mui/icons-material/FileOpen"; +import Reviews from "../../components/dashboard/artist/reviews"; const Profile = () => { @@ -57,25 +58,29 @@ const Profile = () => { const handleClose = () => { setOpen(false); }; - const submitRequest = async (payload) => { - const formData = new FormData(); - formData.append("artistId", payload.artistId); - formData.append("message", payload.message); - formData.append("amount", payload.amount); - formData.append("file", payload.file); // Append the file to FormData - const requestResponse = await fetch('/api/box/newRequest', { - method: 'POST', - body: formData // Pass FormData containing the file - }); - - if(requestResponse.ok){ - router.push("/dashboard/requests") - } else { - alert("Error submitting request") + try { + const requestResponse = await fetch('/api/box/newRequest', { + method: 'POST', + body: JSON.stringify({ + artistId: profileData["id"], + message: payload.get('Message'), + amount: payload.get('Amount'), + }) + }); + if (requestResponse.ok) { + const requestResponseData = await requestResponse.json(); + router.push("/dashboard/requests/"+requestResponseData["id"]); + } else { + const errorData = await requestResponse.json(); + alert("Error submitting request: " + errorData.detail); + } + } catch (error) { + console.error('Error submitting request:', error); + alert("Error submitting request. Please try again later."); } - } + }; const getData = async () => { if(router.query.artistName!=null){ @@ -92,31 +97,6 @@ const Profile = () => { getData() }, [router.query.artistName]); - const columns: GridColDef[] = [ - { - field: 'message', - headerName: 'Review', - flex: 0.75 - }, - { - field: 'rating', - headerName: 'Rating', - flex: 0.25, - renderCell: (params: GridValueGetterParams) => ( - - ), - }, - ]; - - const rows = [ - { id: 1, message: 'Great work!', rating: 5 }, - { id: 2, message: 'BAD work!', rating: 1 }, - { id: 3, message: 'Okay work!', rating: 4 }, - { id: 4, message: 'Meh work!', rating: 2 }, - { id: 5, message: 'Great work!', rating: 5 }, - { id: 6, message: 'Mid work!', rating: 3 }, - { id: 7, message: 'HORRIBLE work!', rating: 1 }, - ]; return ( <> @@ -128,15 +108,7 @@ const Profile = () => { onSubmit: (event: React.FormEvent) => { event.preventDefault(); const formData = new FormData(event.currentTarget); - const email = formData.get("email"); - const file = formData.get("file"); // Get the file object - const payload = { - artistId: profileData["id"], - message: requestMessage, - amount: requestPrice, - file: file // Include the file in the payload - }; - submitRequest(payload); + submitRequest(formData); handleClose(); }, }} @@ -150,8 +122,8 @@ const Profile = () => { autoFocus required margin="dense" - id="name" - name="message" + id="Message" + name="Message" label="Request Message" type="message" fullWidth @@ -162,39 +134,17 @@ const Profile = () => { value={requestMessage} /> - - - @@ -264,19 +214,9 @@ const Profile = () => { REVIEWS HEADER - + {profileData!=null ? ( + + ):null} diff --git a/pages/dashboard/admin/requests.tsx b/pages/dashboard/admin/requests.tsx index a9c8389..f9011d2 100644 --- a/pages/dashboard/admin/requests.tsx +++ b/pages/dashboard/admin/requests.tsx @@ -8,7 +8,7 @@ import TextField from '@mui/material/TextField'; import Button from '@mui/material/Button'; import Switch from '@mui/material/Switch'; import Divider from '@mui/material/Divider'; -import ArtistRequest from "../../../components/ArtistRequest"; +import ArtistRequest from "../../../components/dashboard/admin/artistRequest"; const ArtistRequests = () => { const {user, isLoading} = useUser(); diff --git a/pages/dashboard/artist/artistsettings.tsx b/pages/dashboard/artist/artistsettings.tsx index fba5f36..d5f327c 100644 --- a/pages/dashboard/artist/artistsettings.tsx +++ b/pages/dashboard/artist/artistsettings.tsx @@ -2,13 +2,13 @@ import { useUser,withPageAuthRequired } from "@auth0/nextjs-auth0/client"; import { CircularProgress, Grid, IconButton, Tooltip, Typography } from "@mui/material"; import Card from "@mui/material/Card"; import CardContent from "@mui/material/CardContent"; -import EditableArtistPortfolio from "../../../components/editableArtistPortfolio"; +import EditableArtistPortfolio from "../../../components/dashboard/artist/editablePortfolio"; import { useEffect, useState } from "react"; import TextField from '@mui/material/TextField'; import Button from '@mui/material/Button'; import Switch from '@mui/material/Switch'; import Divider from '@mui/material/Divider'; -import ArtistRequest from "../../../components/ArtistRequest"; +import ArtistRequest from "../../../components/dashboard/admin/artistRequest"; import Box from '@mui/material/Box'; import { Save } from "@mui/icons-material"; diff --git a/pages/dashboard/artist/pagesettings.tsx b/pages/dashboard/artist/pagesettings.tsx index bb19287..f64b6c0 100644 --- a/pages/dashboard/artist/pagesettings.tsx +++ b/pages/dashboard/artist/pagesettings.tsx @@ -7,13 +7,13 @@ import CardMedia from '@mui/material/CardMedia'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import { useEffect, useState } from "react"; -import EditableArtistPortfolio from "../../../components/editableArtistPortfolio"; -import ArtistPortfolio from "../../../components/artistPortfolio"; +import EditableArtistPortfolio from "../../../components/dashboard/artist/editablePortfolio"; +import ArtistPortfolio from "../../../components/dashboard/artist/portfolio"; import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid'; import Rating from '@mui/material/Rating'; import CircularProgress from '@mui/material/CircularProgress'; import Box from '@mui/material/Box'; -import Reviews from "../../../components/reviews"; +import Reviews from "../../../components/dashboard/artist/reviews"; const Profile = () => { diff --git a/pages/dashboard/artist/requests.tsx b/pages/dashboard/artist/requests.tsx index 6994949..53fc5d7 100644 --- a/pages/dashboard/artist/requests.tsx +++ b/pages/dashboard/artist/requests.tsx @@ -30,22 +30,22 @@ export default function ServerPaginationGrid() { { field: 'status', headerName: 'Status', flex: 0.15, renderCell: (params) => { if(params.row.completed){ - return } label="Completed" variant="filled" color="success" /> + return } label="Completed" variant="outlined" color="success" /> } else if(params.row.paid){ - return } label="Paid" variant="filled" color="success" /> + return } label="Paid" variant="outlined" color="success" /> } else if(params.row.accepted && params.row.paid==false){ - return } label="Pending Payment" variant="filled" color="warning" /> + return } label="Pending Payment" variant="outlined" color="warning" /> } else if(params.row.accepted && params.row.paid){ - return } label="Accepted" variant="filled" color="info" /> + return } label="Accepted" variant="outlined" color="info" /> } else if(params.row.declined){ - return } label="Declined" variant="filled" color="error" /> + return } label="Declined" variant="outlined" color="error" /> } else{ - return } label="Pending" variant="filled" color="secondary" /> + return } label="Pending" variant="outlined" color="secondary" /> } } }, @@ -75,7 +75,7 @@ export default function ServerPaginationGrid() { const acceptRequest = async () => { let response = await fetch('/api/artist/requests/'+params.row["id"]+"/accept", { method: 'PUT' }) if(response.status === 200){ - router.reload() + router.push("/dashboard/artist/requests/"+params.row["id"]) } else{ alert("Error accepting request.") @@ -89,7 +89,7 @@ export default function ServerPaginationGrid() { const denyRequest = async () => { let response = await fetch('/api/artist/requests/'+params.row["id"]+"/deny", { method: 'PUT' }) if(response.status === 200){ - router.reload() + router.push("/dashboard/artist/requests/"+params.row["id"]) } else{ alert("Error accepting request.") @@ -99,7 +99,7 @@ export default function ServerPaginationGrid() { const completeRequest = async () => { let response = await fetch('/api/artist/requests/'+params.row["id"]+"/complete", { method: 'PUT' }) if(response.status === 200){ - router.reload() + router.push("/dashboard/artist/requests/"+params.row["id"]) } else{ alert("Error accepting request.") @@ -121,98 +121,7 @@ export default function ServerPaginationGrid() { formattedTime = date.toLocaleTimeString('en-US', { month: 'long', day: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); // Example format return (<> - - Request submitted on {formattedTime ?? ''} - - - - - - - - - - - - - Reference Images - - - - - - - - - - - - - - - - - - - - - - - - - - {(!params.row.declined && !params.row.accepted && !params.row.paid && !params.row.completed ? ( - } label="Pending" variant="filled" color="secondary" /> - ):null)} - {(params.row.declined ? ( - } label="Declined" variant="filled" color="error" /> - ):null)} - {(params.row.accepted ? ( - } label="Accepted" variant="filled" color="info" /> - ):null)} - {(params.row.paid ? ( - } label="Paid" variant="filled" color="success" /> - ):null)} - {(params.row.paid==false && params.row.accepted ? ( - } label="Pending Payment" variant="filled" color="warning" /> - ):null)} - {(params.row.completed ? ( - } label="Completed" variant="filled" color="success" /> - ):null)} - - - - - - - - - - - - - - - - - - - - - - + { router.push("/dashboard/artist/requests/"+params.row["id"])}} aria-label="accept" color="primary" > {((params.row.accepted==false && params.row.declined==false && params.row.completed==false) ? ( <> diff --git a/pages/dashboard/artist/requests/[requestId].tsx b/pages/dashboard/artist/requests/[requestId].tsx new file mode 100644 index 0000000..fb6a2c3 --- /dev/null +++ b/pages/dashboard/artist/requests/[requestId].tsx @@ -0,0 +1,310 @@ +import * as React from 'react'; +import { DataGrid } from '@mui/x-data-grid'; +import { GridColDef } from '@mui/x-data-grid'; +import TextField from '@mui/material/TextField'; +import { Button, CardHeader, CircularProgress, Stack, Typography } from '@mui/material'; +import CurrencyTextField from '@lupus-ai/mui-currency-textfield'; +import { DateField } from '@mui/x-date-pickers/DateField'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import Chip from '@mui/material/Chip'; +import {ArrowBack, Check, Close, Download, OpenInFull, OpenInNew, Refresh, Star, Upload } from '@mui/icons-material'; +import PriceCheckIcon from '@mui/icons-material/PriceCheck'; +import AssignmentTurnedInIcon from '@mui/icons-material/AssignmentTurnedIn'; +import AssignmentLateIcon from '@mui/icons-material/AssignmentLate'; +import ShoppingCartCheckoutIcon from '@mui/icons-material/ShoppingCartCheckout'; +import { IconButton } from '@mui/material'; +import Tooltip from '@mui/material/Tooltip'; +import { Card, CardContent } from '@mui/material'; +import Rating from '@mui/material/Rating'; +import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, FormControl, InputLabel, Box } from '@mui/material'; +import { Grid } from '@mui/material'; +import { useRouter } from 'next/router'; +import { withPageAuthRequired } from '@auth0/nextjs-auth0/client' +import { useState, useEffect } from 'react'; +import { Alert } from '@mui/material'; +import { ImageList } from '@mui/material'; +import AssetImage from '../../../../components/dashboard/artist/AssetImage' +import ReferenceImage from '../../../../components/dashboard/artist/ReferenceImage' +import { UploadBoxOutline } from 'mdi-material-ui'; + +const ArtistRequestDetails = () => { + const [references, setReferences] = useState([]); + const [assets, setAssets] = useState([]); + const [request, setRequest] = useState(null); + const router = useRouter(); + + const getData = async () => { + if(router.query.requestId!=null){ + const response = await fetch("/api/requests/"+router.query.requestId+"/details"); + const data = await response.json(); + setRequest(data); + setRating(data.requestRating) + setReview(data.reviewMessage) + + const requestResponse = await fetch("/api/artist/requests/"+router.query.requestId+"/references"); + const requestJson = await requestResponse.json(); + setReferences(requestJson); + + const assetResponse = await fetch("/api/artist/requests/"+router.query.requestId+"/assets"); + const assetJson = await assetResponse.json(); + setAssets(assetJson); + } + } + + const acceptRequest = async () => { + let response = await fetch('/api/artist/requests/'+request["id"]+"/accept", { method: 'PUT' }) + if(response.status === 200){ + router.reload() + } + else{ + alert("Error accepting request.") + } + } + + const viewRequest = async () => { + + } + + const denyRequest = async () => { + let response = await fetch('/api/artist/requests/'+request["id"]+"/deny", { method: 'PUT' }) + if(response.status === 200){ + router.reload() + } + else{ + alert("Error accepting request.") + } + } + + const completeRequest = async () => { + let response = await fetch('/api/artist/requests/'+request["id"]+"/complete", { method: 'PUT' }) + if(response.status === 200){ + router.reload() + } + else{ + alert("Error accepting request.") + } + } + + const handleReferenceUpload = async (event) =>{ + const file = event.target.files[0]; + const formData = new FormData(); + formData.append('newImage', file); + + fetch('/api/artist/requests/'+router.query.requestId+"/newasset", { + method: 'POST', + body: formData // Don't set Content-Type, FormData will handle it + }) + .then(response => response.json()) + .then(data => { + getData(); + }) + .catch(error => { + console.error('Error uploading file:', error); + // Handle error appropriately + }); + } + + + useEffect(() => { + getData() + }, [router.query.requestId]); + + const [open, setOpen] = React.useState(false); + const [rating, setRating] = React.useState(1); + const [review, setReview] = React.useState(""); + + const handlePay = async () => { + var paymentUrlRequest = await fetch('/api/requests/'+request.id+'/payment') + //console.log(paymentUrlRequest); + var paymentUrlJson = await paymentUrlRequest.json(); + var paymentUrl = paymentUrlJson.paymentUrl; + window.open(paymentUrl); + } + let formattedTime = "" + const date = new Date(request?.requestDate ?? ""); + formattedTime = date.toLocaleTimeString('en-US', { month: 'long', day: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); // Example format + + return (<> + {(request) ? ( + + + + + + + + + + + + + {(references.map((reference) => ( + + )))} + + + + + + + + + + + + + + + + + + + + + + + + + + + {(request.declined ? ( + + } label="Declined" variant="outlined" color="error" /> + + ):null)} + {(!request.declined && !request.accepted && !request.paid && !request.completed ? ( + + } label="Pending" variant="outlined" color="secondary" /> + + ):null)} + {(request.accepted && !request.completed ? ( + + } label="Accepted" variant="outlined" color="info" /> + + ):null)} + {(request.paid && request.accepted ? ( + + } label="Paid" variant="outlined" color="success" /> + + ):null)} + {(request.paid==false && request.accepted ? ( + + } label="Pending Payment" variant="outlined" color="warning" /> + + ):null)} + {(request.completed ? ( + + } label="Completed" variant="outlined" color="success" /> + + ):null)} + + + + + + + {router.push("/dashboard/artist/requests")}} color="primary"> + + + + + + + + } severity="info"> + Request submitted on {formattedTime} + + + + + + + + + + {request.paid ? ( + <> + + + The request has been paid for, start working on it! + + + + + + + {assets.length>0 ? ( + Your uploaded assets will appear below! + ): + ( + You have not uploaded any assets! + )} + + + + + + ):( + The request is not paid for do not work on the request! + )} + + + + {(assets.map((asset) => ( + + )))} + + + + + + + + + + ) + :( + <> + + Loading... + + + + + )} + + ); +}; + +// Protected route, checking user authentication client-side.(CSR) +export default withPageAuthRequired(ArtistRequestDetails); + + diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index ddc52dc..1018acd 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -18,19 +18,19 @@ import { withApiAuthRequired } from "@auth0/nextjs-auth0"; import { withPageAuthRequired } from '@auth0/nextjs-auth0/client' import Button from '@mui/material/Button' import { CardContent } from '@mui/material' -import Onboarding from '../../components/Onboarding' +import Onboarding from '../../components/dashboard/Onboarding' import { useState } from 'react' import { useEffect } from 'react' import router from 'next/router' import { isObject } from 'util' -import Orders from '../../components/Orders' +import Orders from '../../components/dashboard/customer/orders' import StatisticsCard from '../../views/dashboard/StatisticsCard' import ArtistStats from '../../components/ArtistStats' import { ArrowDownBox, BankTransfer, Cash, Clipboard, CubeOutline, StarOutline } from 'mdi-material-ui' import CircularProgress from '@mui/material/CircularProgress' import Tooltip from '@mui/material/Tooltip' import Box from '@mui/material/Box' -import { Fullscreen, OpenInBrowser, Settings, WebAsset } from '@mui/icons-material' +import { Fullscreen, List, OpenInBrowser, Settings, WebAsset } from '@mui/icons-material' @@ -103,8 +103,8 @@ const Dashboard = () => { - - {router.push("/dashboard/requests")}}> + + {router.push("/dashboard/requests")}}> @@ -133,14 +133,14 @@ const Dashboard = () => { )} - + {router.push("/dashboard/artist/reviews") }} size="large"> - + {router.push("/dashboard/artist/requests") }} size="large"> - + diff --git a/pages/dashboard/requests.tsx b/pages/dashboard/requests.tsx index 88d3d1f..8d27107 100644 --- a/pages/dashboard/requests.tsx +++ b/pages/dashboard/requests.tsx @@ -24,6 +24,7 @@ import { DownloadBox, StarOutline } from 'mdi-material-ui'; import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, FormControl, InputLabel, Box } from '@mui/material'; import { Grid } from '@mui/material'; import { useRouter } from 'next/router'; +import { request } from 'http'; export default function ServerPaginationGrid() { @@ -33,22 +34,22 @@ export default function ServerPaginationGrid() { { field: 'status', headerName: 'Status', flex: 0.15, renderCell: (params) => { if(params.row.completed){ - return } label="Completed" variant="filled" color="success" /> + return } label="Completed" variant="outlined" color="success" /> } else if(params.row.paid){ - return } label="Paid" variant="filled" color="success" /> + return } label="Paid" variant="outlined" color="success" /> } else if(params.row.accepted && params.row.paid==false){ - return } label="Pending Payment" variant="filled" color="warning" /> + return } label="Pending Payment" variant="outlined" color="warning" /> } else if(params.row.accepted && params.row.paid){ - return } label="Accepted" variant="filled" color="info" /> + return } label="Accepted" variant="outlined" color="info" /> } else if(params.row.declined){ - return } label="Declined" variant="filled" color="error" /> + return } label="Declined" variant="outlined" color="error" /> } else{ - return } label="Pending" variant="filled" color="secondary" /> + return } label="Pending" variant="outlined" color="secondary" /> } } }, @@ -74,214 +75,16 @@ export default function ServerPaginationGrid() { } }, { field: 'download', headerName: '', flex:0.1, renderCell: (params) =>{ - const acceptRequest = async () => { - let response = await fetch('/api/artist/requests/'+params.row["id"]+"/accept", { method: 'PUT' }) - if(response.status === 200){ - router.reload() - } - else{ - alert("Error accepting request.") - } - } - - const viewRequest = async () => { - - } - - const denyRequest = async () => { - let response = await fetch('/api/artist/requests/'+params.row["id"]+"/deny", { method: 'PUT' }) - if(response.status === 200){ - router.reload() - } - else{ - alert("Error accepting request.") - } - } - - const completeRequest = async () => { - let response = await fetch('/api/artist/requests/'+params.row["id"]+"/complete", { method: 'PUT' }) - if(response.status === 200){ - router.reload() - } - else{ - alert("Error accepting request.") - } - } + const handlePay = async () => { - var paymentUrlRequest = await fetch('/api/requests/'+params.row.id+'/payment') - //console.log(paymentUrlRequest); - var paymentUrlJson = await paymentUrlRequest.json(); - var paymentUrl = paymentUrlJson.paymentUrl; - window.open(paymentUrl); - } - const [open, setOpen] = React.useState(false); - const [rating, setRating] = React.useState(params.row.reviewRating); - const [review, setReview] = React.useState(params.row.reviewMessage); - const [alreadyReviewed, setAlreadyReviewed] = React.useState(params.row.reviewMessage != null && params.row.reviewMessage != ""); - const handleClickOpen = () => { - setOpen(true); - }; - - const handleClose = () => { - setOpen(false); - }; - - - const handleReviewChange = (event) => { - setReview(event.target.value); - } - - const handleRatingChange = async (event) => { - var rating = event.target.value; - var response = await fetch('/api/requests/'+params.row.id+'/review', { - method:"PUT", - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - rating: rating, - message: review - }) - }); - if(response.ok){ - router.push("/dashboard/requests") - } - else{ - alert("Could not submit review!") - } - } - - let formattedTime = "" - const date = new Date(params.row.requestDate); - formattedTime = date.toLocaleTimeString('en-US', { month: 'long', day: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); // Example format - + var paymentUrlRequest = await fetch('/api/requests/'+params.row.id+'/payment') + //console.log(paymentUrlRequest); + var paymentUrlJson = await paymentUrlRequest.json(); + var paymentUrl = paymentUrlJson.paymentUrl; + window.open(paymentUrl); + } return (<> - - Request submitted on {formattedTime ?? ''} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {(!params.row.declined && !params.row.accepted && !params.row.paid && !params.row.completed ? ( - } label="Pending" variant="filled" color="secondary" /> - ):null)} - - - {(params.row.declined ? ( - } label="Declined" variant="filled" color="error" /> - ):null)} - - - {(params.row.accepted ? ( - } label="Accepted" variant="filled" color="info" /> - ):null)} - - - {(params.row.paid && params.row.acccepted ? ( - - ):null)} - - - {(params.row.paid==false && params.row.accepted ? ( - } label="Pending Payment" variant="filled" color="warning" /> - ):null)} - - - {(params.row.completed ? ( - } label="Completed" variant="filled" color="success" /> - ):null)} - - - - - - {(params.row.completed) ? ( - - - - - - - - - ):null} - - - - - - - - - - - - - - - - - - - - - + { router.push("/dashboard/requests/"+params.row.id)}} aria-label="accept" color="primary"> {((params.row.accepted==true &¶ms.row.declined==false && params.row.paid==false) ? ( ): null diff --git a/pages/dashboard/requests/[requestId].tsx b/pages/dashboard/requests/[requestId].tsx index cbad4f9..fcebbbb 100644 --- a/pages/dashboard/requests/[requestId].tsx +++ b/pages/dashboard/requests/[requestId].tsx @@ -1,280 +1,317 @@ -import { withPageAuthRequired } from "@auth0/nextjs-auth0/client"; -import Grid from '@mui/material/Grid'; -import Card from '@mui/material/Card'; -import CardActions from '@mui/material/CardActions'; -import CardContent from '@mui/material/CardContent'; -import CardMedia from '@mui/material/CardMedia'; -import Button from '@mui/material/Button'; -import Typography from '@mui/material/Typography'; -import { useEffect, useState } from "react"; -import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid'; -import Rating from '@mui/material/Rating'; -import Dialog from '@mui/material/Dialog'; -import DialogTitle from '@mui/material/DialogTitle'; -import DialogContent from '@mui/material/DialogContent'; -import DialogContentText from '@mui/material/DialogContentText'; -import DialogActions from '@mui/material/DialogActions'; -import CurrencyTextField from '@lupus-ai/mui-currency-textfield'; +import * as React from 'react'; +import { DataGrid } from '@mui/x-data-grid'; +import { GridColDef } from '@mui/x-data-grid'; import TextField from '@mui/material/TextField'; -import ArtistPortfolio from "../../../components/Old/artistPortfolio"; -import { RouterNetwork } from "mdi-material-ui"; -import { useRouter } from "next/router"; -import { profile } from "console"; - -const Profile = () => { - - const [profileData, setArtistData] = useState(null); - const [description, setDescription] = useState(""); - const [guidelines, setGuidelines] = useState(""); - - const [requestMessage, setRequestMessage] = useState(""); - const [requestPrice, setRequestPrice] = useState(100.00); - - const handleRequestMessageChange = (event) => { - setRequestMessage(event.target.value); - } - - const handleRequestPriceChange = (event) => { - setRequestPrice(event.target.value); - } - +import { Alert, Button, CircularProgress, ImageList, ImageListItem, ListItem, Stack, Typography } from '@mui/material'; +import CurrencyTextField from '@lupus-ai/mui-currency-textfield'; +import { DateField } from '@mui/x-date-pickers/DateField'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import Chip from '@mui/material/Chip'; +import {ArrowBack, AssignmentLateOutlined, Check, Close, Download, Error, OpenInFull, OpenInNew, Refresh, Star, Upload, Warning } from '@mui/icons-material'; +import PriceCheckIcon from '@mui/icons-material/PriceCheck'; +import AssignmentTurnedInIcon from '@mui/icons-material/AssignmentTurnedIn'; +import AssignmentLateIcon from '@mui/icons-material/AssignmentLate'; +import ShoppingCartCheckoutIcon from '@mui/icons-material/ShoppingCartCheckout'; +import { IconButton } from '@mui/material'; +import Tooltip from '@mui/material/Tooltip'; +import { Card, CardContent } from '@mui/material'; +import Rating from '@mui/material/Rating'; +import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, FormControl, InputLabel, Box } from '@mui/material'; +import { Grid } from '@mui/material'; +import { useRouter } from 'next/router'; +import { withPageAuthRequired } from '@auth0/nextjs-auth0/client' +import { useState, useEffect } from 'react'; +import { UploadBoxOutline } from 'mdi-material-ui'; +import AssetImage from '../../../components/dashboard/artist/AssetImage' +import ReferenceImage from '../../../components/dashboard/artist/ReferenceImage' +import Paper from '@mui/material/Paper'; +const RequestDetails = () => { + const [request, setRequest] = useState(null); + const [references, setReferences] = useState([]); + const [assets, setAssets] = useState([]); const router = useRouter(); - const [open, setOpen] = useState(false); - const handleClickOpen = () => { - setOpen(true); - }; - - const handleClose = () => { - setOpen(false); - }; - - const submitRequest = async () => { - var payload = JSON.stringify({ - "artistId": profileData["id"], - "message": requestMessage, - "amount": requestPrice - }); - //console.log(payload) - const requestResponse = await fetch('/api/box/newRequest', { - method: 'POST', - body: payload - }) - if(requestResponse.ok){ - router.push("/dashboard/requests") - } - else{ - alert("Error submitting request") + const getData = async () => { + if(router.query.requestId!=null){ + const response = await fetch("/api/requests/"+router.query.requestId+"/details"); + const data = await response.json(); + setRequest(data); + setRating(data.reviewRating) + setReview(data.reviewMessage) + setAlreadyReviewed(data.reviewed) + + const requestResponse = await fetch("/api/requests/"+router.query.requestId+"/references"); + const requestJson = await requestResponse.json(); + setReferences(requestJson); + + const assetsResponse = await fetch("/api/requests/"+router.query.requestId+"/assets"); + const assetsJson = await assetsResponse.json(); + setAssets(assetsJson); } } - - const getData = async () => { - if(router.query.artistName!=null){ - const profileResponse = await fetch('/api/discovery/artist/'+router.query.artistName); - const sellerProfile = await profileResponse.json(); - setArtistData(sellerProfile); + + const handleReferenceUpload = async (event) =>{ + const file = event.target.files[0]; + const formData = new FormData(); + formData.append('newImage', file); - setDescription(sellerProfile["description"]); - setGuidelines(sellerProfile["requestGuidelines"]); - } + fetch('/api/requests/'+router.query.requestId+"/newreference", { + method: 'POST', + body: formData // Don't set Content-Type, FormData will handle it + }) + .then(response => response.json()) + .then(data => { + getData(); + }) + .catch(error => { + console.error('Error uploading file:', error); + // Handle error appropriately + }); } useEffect(() => { getData() - }, [router.query.artistName]); + }, [router.query.requestId]); - const columns: GridColDef[] = [ - { - field: 'message', - headerName: 'Review', - flex: 0.75 - }, - { - field: 'rating', - headerName: 'Rating', - flex: 0.25, - renderCell: (params: GridValueGetterParams) => ( - - ), - }, - ]; - - const rows = [ - { id: 1, message: 'Great work!', rating: 5 }, - { id: 2, message: 'BAD work!', rating: 1 }, - { id: 3, message: 'Okay work!', rating: 4 }, - { id: 4, message: 'Meh work!', rating: 2 }, - { id: 5, message: 'Great work!', rating: 5 }, - { id: 6, message: 'Mid work!', rating: 3 }, - { id: 7, message: 'HORRIBLE work!', rating: 1 }, - ]; - + const [open, setOpen] = React.useState(false); + const [rating, setRating] = React.useState(1); + const [review, setReview] = React.useState(""); + const [alreadyReviewed, setAlreadyReviewed] = React.useState(true); - return ( - <> - ) => { - event.preventDefault(); - const formData = new FormData(event.currentTarget); - const formJson = Object.fromEntries((formData as any).entries()); - const email = formJson.email; - ////console.log(email); - handleClose(); - }, - }} - > - New Request - - - Please read the guidelines of submitting a request before you proceed. You can do that by closing this popup and looking at the page behind. - - - - - - - - - + const handleReviewChange = (event) => { + setReview(event.target.value); + } - - - - - STORE HEADER - - - - - - - - - BIOGRAPHY HEADER - - {description} - - - - - - - - - - GUIDELINES HEADER - - {guidelines} - - - - - By clicking "Start New Request" you are agreeing to the terms above and to the terms of service. - - - [TERMS OF SERVICE] - - - - - - - - - - - - - - - REVIEWS HEADER - - - - - - - - - + const handleRatingChange = async (event) => { + var rating = event.target.value; + var response = await fetch('/api/requests/'+request.id+'/review', { + method:"PUT", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + rating: rating, + message: review + }) + }); + if(response.ok){ + router.reload(); + } + else{ + alert("Could not submit review!") + } + } + const handlePay = async () => { + var paymentUrlRequest = await fetch('/api/requests/'+request.id+'/payment') + //console.log(paymentUrlRequest); + var paymentUrlJson = await paymentUrlRequest.json(); + var paymentUrl = paymentUrlJson.paymentUrl; + window.open(paymentUrl); + } + let formattedTime = "" + const date = new Date(request?.requestDate ?? ""); + formattedTime = date.toLocaleTimeString('en-US', { month: 'long', day: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); // Example format + + return (<> + {(request) ? ( + + + + + + + + + + - - - - - - PORTFOLIO HEADER - - - - {profileData!=null ? ( - - ):null} - - - - - + + + + + + + + + + {request.accepted ? ( + You can no longer upload reference images, request is accepted! + ):( + (references.length > 0) ? ( + + There is a maximum of 10 reference images per request. + + ):( + You need to add reference images to your request! + ) + )} + + + + + + {(references.map((reference) => ( + + )))} + + + - - + + + + + + + + + + + + + + + + {(request.declined ? ( + + } label="Declined" variant="outlined" color="error" /> + + ):null)} + {(!request.declined && !request.accepted && !request.paid && !request.completed ? ( + + } label="Pending" variant="outlined" color="secondary" /> + + ):null)} + {(request.accepted && !request.completed ? ( + + } label="Accepted" variant="outlined" color="info" /> + + ):null)} + {(request.paid && request.accepted ? ( + + } label="Paid" variant="outlined" color="success" /> + + ):null)} + {(request.paid==false && request.accepted ? ( + + } label="Pending Payment" variant="outlined" color="warning" /> + + ):null)} + {(request.completed ? ( + + } label="Completed" variant="outlined" color="success" /> + + ):null)} + + + + + + + {router.push("/dashboard/requests")}} color="primary"> + + + + + + + + } severity="info"> + Request submitted on {formattedTime} + + + + + + + + + + {request.completed ? ( + Your success is complete and you can access your assets below! + ):( + When your request is completed your assets will appear below! + )} + + {request.completed ? ( + + + + + + + + + + + + + + + ): null} + + + {(assets.map((asset) => ( + + )))} + + + + + + - + + + ) + :( + <> + + Loading... + + + + )} + ); }; // Protected route, checking user authentication client-side.(CSR) -export default withPageAuthRequired(Profile); +export default withPageAuthRequired(RequestDetails);