From ec0924bbfd88002559ef380d468790be28f5852e Mon Sep 17 00:00:00 2001 From: fcablik Date: Thu, 21 Dec 2023 16:28:23 +0100 Subject: [PATCH] removing old content + db models + migrations +added projects route + merged common styles into classlist and other FE fixes --- app/components/calendar-helpers.tsx | 156 ----- app/components/classlists.tsx | 33 +- app/components/image-sliders.tsx | 124 ---- app/components/modal-animation.tsx | 79 --- app/components/reservation-filters.tsx | 260 --------- app/components/search-bar.tsx | 71 --- app/routes/_base+/about.tsx | 3 - app/routes/_base+/index.tsx | 76 ++- app/routes/_base+/search.tsx | 186 ------ app/routes/admin+/brands+/$id.tsx | 231 -------- app/routes/admin+/brands+/$id_+/edit.tsx | 54 -- app/routes/admin+/brands+/createnew.tsx | 24 - app/routes/admin+/brands+/index.tsx | 161 ------ app/routes/admin+/dealers+/$id.tsx | 144 ----- app/routes/admin+/dealers+/$id_+/edit.tsx | 52 -- app/routes/admin+/dealers+/createnew.tsx | 24 - app/routes/admin+/dealers+/index.tsx | 70 --- app/routes/admin+/models+/$id.tsx | 324 ----------- app/routes/admin+/models+/$id_+/edit.tsx | 78 --- .../admin+/models+/$id_+/gallery.edit.tsx | 63 -- .../models+/$id_+/previewimages.edit.tsx | 65 --- app/routes/admin+/models+/createnew.tsx | 59 -- .../admin+/models+/facility+/$id.delete.tsx | 124 ---- .../admin+/models+/facility+/$id.edit.tsx | 42 -- .../admin+/models+/facility+/createnew.tsx | 22 - app/routes/admin+/models+/facility.tsx | 89 --- .../admin+/models+/gallery+/$id.delete.tsx | 116 ---- .../admin+/models+/gallery+/$id.edit.tsx | 48 -- app/routes/admin+/models+/gallery+/$id.tsx | 123 ---- .../admin+/models+/gallery+/createnew.tsx | 22 - app/routes/admin+/models+/gallery.tsx | 81 --- app/routes/admin+/models+/index.tsx | 166 ------ app/routes/admin+/users+/index.tsx | 138 ++--- app/routes/admin.tsx | 25 - app/routes/brands+/$url.tsx | 188 ------ app/routes/brands+/$url_+/$url.tsx | 215 ------- app/routes/brands+/index.tsx | 189 ------ app/routes/dealers+/$url.tsx | 92 --- app/routes/dealers+/index.tsx | 66 --- app/routes/portfolio.tsx | 39 +- app/routes/projects+/forcompanies.tsx | 15 + app/routes/projects+/freelance.tsx | 15 + app/routes/projects+/index.tsx | 25 + app/routes/projects.tsx | 111 ++++ app/routes/resources+/__brand-editor.tsx | 540 ------------------ app/routes/resources+/__dealer-editor.tsx | 494 ---------------- app/routes/resources+/__facility-editor.tsx | 194 ------- app/routes/resources+/__model-editor.tsx | 394 ------------- .../resources+/__model-gallery-selector.tsx | 245 -------- .../__model-preview-images-selector.tsx | 253 -------- .../resources+/__models-gallery-editor.tsx | 443 -------------- app/routes/resources+/_country-list.tsx | 197 ------- .../resources+/_similar-items-loader.tsx | 18 - .../resources+/brands-images.$imageId.tsx | 22 - .../carModels-gallery-images.$imageId.tsx | 22 - app/styles/customComponents.css | 6 +- app/utils/extended-theme.ts | 2 +- .../migration.sql | 128 ----- .../migration.sql | 2 - .../migration.sql | 25 - .../migration.sql | 31 - .../migration.sql | 26 - .../migration.sql | 26 - .../migration.sql | 27 - .../migration.sql | 28 - .../20231212124728_year_to_int/migration.sql | 28 - .../migration.sql | 5 - .../migration.sql | 29 - .../migration.sql | 14 +- prisma/schema.prisma | 112 ---- 70 files changed, 285 insertions(+), 7314 deletions(-) delete mode 100644 app/components/calendar-helpers.tsx delete mode 100644 app/components/image-sliders.tsx delete mode 100644 app/components/modal-animation.tsx delete mode 100644 app/components/reservation-filters.tsx delete mode 100644 app/components/search-bar.tsx delete mode 100644 app/routes/_base+/about.tsx delete mode 100644 app/routes/_base+/search.tsx delete mode 100644 app/routes/admin+/brands+/$id.tsx delete mode 100644 app/routes/admin+/brands+/$id_+/edit.tsx delete mode 100644 app/routes/admin+/brands+/createnew.tsx delete mode 100644 app/routes/admin+/brands+/index.tsx delete mode 100644 app/routes/admin+/dealers+/$id.tsx delete mode 100644 app/routes/admin+/dealers+/$id_+/edit.tsx delete mode 100644 app/routes/admin+/dealers+/createnew.tsx delete mode 100644 app/routes/admin+/dealers+/index.tsx delete mode 100644 app/routes/admin+/models+/$id.tsx delete mode 100644 app/routes/admin+/models+/$id_+/edit.tsx delete mode 100644 app/routes/admin+/models+/$id_+/gallery.edit.tsx delete mode 100644 app/routes/admin+/models+/$id_+/previewimages.edit.tsx delete mode 100644 app/routes/admin+/models+/createnew.tsx delete mode 100644 app/routes/admin+/models+/facility+/$id.delete.tsx delete mode 100644 app/routes/admin+/models+/facility+/$id.edit.tsx delete mode 100644 app/routes/admin+/models+/facility+/createnew.tsx delete mode 100644 app/routes/admin+/models+/facility.tsx delete mode 100644 app/routes/admin+/models+/gallery+/$id.delete.tsx delete mode 100644 app/routes/admin+/models+/gallery+/$id.edit.tsx delete mode 100644 app/routes/admin+/models+/gallery+/$id.tsx delete mode 100644 app/routes/admin+/models+/gallery+/createnew.tsx delete mode 100644 app/routes/admin+/models+/gallery.tsx delete mode 100644 app/routes/admin+/models+/index.tsx delete mode 100644 app/routes/brands+/$url.tsx delete mode 100644 app/routes/brands+/$url_+/$url.tsx delete mode 100644 app/routes/brands+/index.tsx delete mode 100644 app/routes/dealers+/$url.tsx delete mode 100644 app/routes/dealers+/index.tsx create mode 100644 app/routes/projects+/forcompanies.tsx create mode 100644 app/routes/projects+/freelance.tsx create mode 100644 app/routes/projects+/index.tsx create mode 100644 app/routes/projects.tsx delete mode 100644 app/routes/resources+/__brand-editor.tsx delete mode 100644 app/routes/resources+/__dealer-editor.tsx delete mode 100644 app/routes/resources+/__facility-editor.tsx delete mode 100644 app/routes/resources+/__model-editor.tsx delete mode 100644 app/routes/resources+/__model-gallery-selector.tsx delete mode 100644 app/routes/resources+/__model-preview-images-selector.tsx delete mode 100644 app/routes/resources+/__models-gallery-editor.tsx delete mode 100644 app/routes/resources+/_country-list.tsx delete mode 100644 app/routes/resources+/_similar-items-loader.tsx delete mode 100644 app/routes/resources+/brands-images.$imageId.tsx delete mode 100644 app/routes/resources+/carModels-gallery-images.$imageId.tsx delete mode 100644 prisma/migrations/20231208151344_init_with_custom_models_for_car_models/migration.sql delete mode 100644 prisma/migrations/20231212082435_car_brand_icon_logo/migration.sql delete mode 100644 prisma/migrations/20231212082952_logo_icon_car_brand/migration.sql delete mode 100644 prisma/migrations/20231212083433_car_models_preps/migration.sql delete mode 100644 prisma/migrations/20231212084029_car_models_and_brands_connection/migration.sql delete mode 100644 prisma/migrations/20231212104451_country_for_brand/migration.sql delete mode 100644 prisma/migrations/20231212104650_spelling_error/migration.sql delete mode 100644 prisma/migrations/20231212105208_car_models_expantion/migration.sql delete mode 100644 prisma/migrations/20231212124728_year_to_int/migration.sql delete mode 100644 prisma/migrations/20231212142454_car_models_foreign_key_and_indexing/migration.sql delete mode 100644 prisma/migrations/20231212151413_dealer_schema/migration.sql rename prisma/migrations/{20231212073816_car_brand_and_its_gallery => 20231221141742_removing_unnecessary_models}/migration.sql (56%) diff --git a/app/components/calendar-helpers.tsx b/app/components/calendar-helpers.tsx deleted file mode 100644 index 73a9425..0000000 --- a/app/components/calendar-helpers.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { - format, - getMonth, - getYear, - isSameMonth, - startOfMonth, -} from 'date-fns' -import { useEffect, useState } from 'react' -import { cn } from '#app/utils/misc.tsx' -import { Icon } from './ui/icon.tsx' - -export const generateCalendar = (firstDateOfMonth: Date): number[][] => { - const date = startOfMonth(firstDateOfMonth) - - const getDay = (date: Date) => { - let day = date.getDay() - if (day === 0) day = 7 - return day - 1 - } - - const monthCalendar: number[][] = [[]] - for (let i = 0; i < getDay(date); i++) { - monthCalendar[monthCalendar.length - 1].push(0) - } - - while (isSameMonth(date, firstDateOfMonth)) { - monthCalendar[monthCalendar.length - 1].push(date.getDate()) - - if (getDay(date) % 7 == 6) { - monthCalendar.push([]) - } - - date.setDate(date.getDate() + 1) - } - - if (getDay(date) != 0) { - for (let i = getDay(date); i < 7; i++) { - monthCalendar[monthCalendar.length - 1].push(0) - } - } - - return monthCalendar -} - -export function useCalendarsCommonLogic(monthsInPastAllowed?: boolean) { - const currentMonth = getMonth(new Date()) + 1 - const [selectedMonth, setSelectedMonth] = useState(currentMonth) - - const currentYear = getYear(new Date()) - const [selectedYear, setSelectedYear] = useState(currentYear) - - const [buttonDisabled, setButtonDisabled] = useState(false) - setTimeout(() => { - setButtonDisabled(false) - }, 2000) - - const handleIncrement = () => { - setSelectedMonth(prevMonth => { - const newMonth = prevMonth === 12 ? 1 : prevMonth + 1 - if (newMonth === 1) { - setSelectedYear(prevYear => prevYear + 1) - } - return newMonth - }) - } - - const handleDecrement = () => { - if (!monthsInPastAllowed) { - setButtonDisabled(true) - } - setSelectedMonth(prevMonth => { - const newMonth = prevMonth === 1 ? 12 : prevMonth - 1 - if (newMonth === 12) { - setSelectedYear(prevYear => prevYear - 1) - } - return newMonth - }) - } - - useEffect(() => {}, [selectedMonth, selectedYear]) // This runs whenever selectedMonth or selectedYear changes - - let selectedCurrentMonth = false - if (currentMonth === selectedMonth) { - selectedCurrentMonth = true - } - let selectedCurrentYear = false - if (currentYear === selectedYear) { - selectedCurrentYear = true - } - - const calendarNavigation = ( -
- - - -

- {format(new Date(2023, selectedMonth - 1, 1), 'LLLL')} - {selectedYear} -

-
- ) - - const daysTitles = ( -
-
Mo
-
Tu
-
We
-
Th
-
Fr
-
Sa
-
Su
-
- ) - - return { - currentMonth, - selectedMonth, - selectedYear, - calendarNavigation, - daysTitles, - } -} - -export function getDatesInBetween(startDate: Date, endDate: Date) { - const date = new Date(startDate.getTime()) - const dates = [] - - date.setDate(date.getDate() + 1) // exclude start date - while (date < endDate) { - // exclude end date - dates.push(new Date(date)) - date.setDate(date.getDate() + 1) - } - - return dates -} \ No newline at end of file diff --git a/app/components/classlists.tsx b/app/components/classlists.tsx index c5582b3..515bebf 100644 --- a/app/components/classlists.tsx +++ b/app/components/classlists.tsx @@ -1,25 +1,32 @@ -export const baseContainerWidthClassList = - 'md:max-w-[93%] lg:max-w-[90%] 2xl:max-w-[88%] 7xl:max-w-[1700px] md:mx-auto' - export const pagesContentContainerClassList = 'md:max-w-[93%] lg:max-w-[90%] 2xl:max-w-[88%] 7xl:max-w-[1600px] max-md:mx-3 md:mx-auto' -export const carModelsContentContainerClassList = 'md:max-w-[96%] mx-auto' - export const adminDetailBoxesClassList = 'shadow-admin-detail-box p-8 rounded-xl' export const frontendRoutesSpacingFromHeaderAndFooter = 'max-md:px-2 pt-16 md:pt-8 lg:pt-12 pb-24 md:pb-24 lg:pb-32' -export const offersAndServicesBoxesClassList = - 'max-sm:min-w-[85%] sm:max-lg:min-w-[45%] lg:w-1/3 p-4 bg-background rounded-xl border-2' +export const boxInnerContentBoxProps = + 'custom-box-in-box-sizes flex items-center mb-3 4xl:mb-4' + +export const boxProps = + 'flex flex-col rounded-3xl lg:rounded-6xl pt-4 px-4 md:px-5 md:pt-6' +export const darkBoxBgClassList = boxProps + ' bg-dark-gradient' +export const purpleBoxBgClassList = boxProps + ' bg-purple-box-gradient' + +export const darkBoxInnerContentBox = + boxInnerContentBoxProps + + ' bg-foreground/10 hover:bg-foreground hover:text-background cursor-pointer transition-colors duration-500 p-2 rounded-xl' + +export const purpleBoxInnerContentBox = + boxInnerContentBoxProps + + ' bg-highlight-dark/30 hover:bg-highlight-dark transition-colors duration-500 p-2 cursor-pointer rounded-xl' -export const offersAndServicesContainerClassList = - 'md:max-w-[88%] lg:max-w-[85%] 2xl:max-w-[83%] 6xl:max-w-[1400px] max-md:mx-3 md:mx-auto' +export const innerContentBoxTexts = + 'no-scrollbar overflow-scroll whitespace-nowrap' -export const destructiveModalWrapperClassList = - 'absolute left-1/2 top-20 z-3001 w-full max-w-1/3 -translate-x-1/2 rounded-xl border-4 border-destructive bg-white p-4' +export const boxInnerContentBoxInnerBox = + 'custom-box-in-box-in-box-sizes rounded-lg-to-xl bg-cover bg-contain' -export const deactivateModalWrapperClassList = - 'absolute left-1/2 top-20 z-3001 w-full max-w-1/3 -translate-x-1/2 rounded-xl border-4 border-destructive/80 bg-white p-4' +export const bigBoxTitle = 'capitalize text-center text-2xl font-semibold mb-6' diff --git a/app/components/image-sliders.tsx b/app/components/image-sliders.tsx deleted file mode 100644 index e700916..0000000 --- a/app/components/image-sliders.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { useState } from 'react' -import { cn, getCarModelsGalleryImgSrc } from '#app/utils/misc.tsx' -import { Icon } from './ui/icon.tsx' - -export function GallerySlider({ - images, - carModelSeo, - carModelTitle, -}: { - images: object - carModelSeo?: string - carModelTitle: string -}) { - const slides = Object.values(images).map(image => - getCarModelsGalleryImgSrc(image.id), - ) - const slidesAltTexts = Object.values(images).map(image => image.altText) - - const [currentIndex, setCurrentIndex] = useState(0) - const goToPrevious = () => { - const isFirstSlide = currentIndex === 0 - const newIndex = isFirstSlide ? slides.length - 1 : currentIndex - 1 - setCurrentIndex(newIndex) - } - const goToNext = () => { - const isLastSlide = currentIndex === slides.length - 1 - const newIndex = isLastSlide ? 0 : currentIndex + 1 - setCurrentIndex(newIndex) - } - - return ( -
-
- -
-
- -
- -
- {slides.map((slide, index) => ( - {slidesAltTexts[index] - ))} -
-
- ) -} - -export function CarModelPreviewImagesSlider({ - images, - carModelSeo, - carModelTitle, -}: { - images: object - carModelSeo?: string - carModelTitle: string -}) { - const slides = Object.values(images).map(image => - getCarModelsGalleryImgSrc(image.id), - ) - const slidesAltTexts = Object.values(images).map(image => image.altText) - - const [currentIndex, setCurrentIndex] = useState(0) - const goToPrevious = () => { - const isFirstSlide = currentIndex === 0 - const newIndex = isFirstSlide ? slides.length - 1 : currentIndex - 1 - setCurrentIndex(newIndex) - } - const goToNext = () => { - const isLastSlide = currentIndex === slides.length - 1 - const newIndex = isLastSlide ? 0 : currentIndex + 1 - setCurrentIndex(newIndex) - } - - return ( -
-
- -
-
- -
- -
- {slides.map((slide, index) => ( - {slidesAltTexts[index] - ))} -
-
-
- ) -} diff --git a/app/components/modal-animation.tsx b/app/components/modal-animation.tsx deleted file mode 100644 index f25057f..0000000 --- a/app/components/modal-animation.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useNavigate } from '@remix-run/react' - -// !this is still problematic on Safari/iOS devices when in lowe-batter-mode, otherwise works fine everywhere -export function useRedirectWithScrollToTop() { - const navigate = useNavigate() - - function scrollToTop(duration = 500) { - return new Promise(resolve => { - const start = window.scrollY - const end = 0 - const startTime = performance.now() - - function scroll() { - if (window.scrollY !== 0) { - const currentTime = performance.now() - const timeElapsed = currentTime - startTime - const scrollPosition = easeInOutQuad( - timeElapsed, - start, - end - start, - duration, - ) - window.scrollTo(0, scrollPosition) - - if (timeElapsed < duration) { - requestAnimationFrame(scroll) - } else { - resolve() - } - } else { - resolve() - } - } - - requestAnimationFrame(scroll) - }) - } - - function easeInOutQuad( - timeElapsed: number, - start: number, - endMinusStart: number, - duration: number, - ) { - timeElapsed /= duration / 2 - if (timeElapsed < 1) return (endMinusStart / 2) * timeElapsed * timeElapsed + start - timeElapsed-- - return (-endMinusStart / 2) * (timeElapsed * (timeElapsed - 2) - 1) + start - } - - const navigateAndScroll = async ( - navigateTo: string, // target path of redirect (string | back(-1)) - ) => { - await scrollToTop() - - if (navigateTo !== 'back') { - navigate(navigateTo) - } else { - navigate(-1) - } - } - - return navigateAndScroll -} - -export function useRedirectWithoutScrollToTop() { - const navigate = useNavigate() - const navigateAndScroll = ( - navigateTo: string, // target path of redirect (string | back(-1)) - ) => { - if (navigateTo !== 'back') { - navigate(navigateTo) - } else { - navigate(-1) - } - } - - return navigateAndScroll -} diff --git a/app/components/reservation-filters.tsx b/app/components/reservation-filters.tsx deleted file mode 100644 index b794e71..0000000 --- a/app/components/reservation-filters.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import { Form, useSearchParams, useSubmit } from '@remix-run/react' -import { format } from 'date-fns' -import { useEffect, useState } from 'react' -import { cn, useDebounce, useIsPending } from '#app/utils/misc.tsx' -import { generateCalendar, useCalendarsCommonLogic } from './calendar-helpers.tsx' -import { Button } from './ui/button.tsx' -import { Icon } from './ui/icon.tsx' -import { Input } from './ui/input.tsx' -import { Label } from './ui/label.tsx' -import { StatusButton } from './ui/status-button.tsx' - -export function FiltersWithSearchAndCalendar({ - actionUrl, - status, - // autoFocus = false, - autoSubmit = false, -}: { - actionUrl: 'admin/reservations' - status: 'idle' | 'pending' | 'success' | 'error' - // autoFocus?: boolean - autoSubmit?: boolean -}) { - const action = actionUrl - const [searchParams] = useSearchParams() - const submit = useSubmit() - const isSubmitting = useIsPending({ - formMethod: 'GET', - formAction: action, - }) - const handleFormChange = useDebounce((form: HTMLFormElement) => { - submit(form) - }, 400) - - const [showCalendar, setShowCalendar] = useState(false) - const [currentSearch, setCurrentSearch] = useState( - searchParams.get('search') ?? '', - ) - // handling live search param changes -(e.g. on change of searchParams by external Link from sidebar) - useEffect(() => { - setCurrentSearch(searchParams.get('search') ?? '') - }, [searchParams]) - - function toggleCalendarVisibility() { - setShowCalendar(prevVisible => !prevVisible) - } - function handleSelectedFilter(e: React.ChangeEvent) { - setCurrentSearch(e.target.value) - setShowCalendar(false) - } - function handleSelect(selectString: string) { - setCurrentSearch(selectString) - - if (!selectString.includes(certainDateSearchString)) { - setShowCalendar(false) - } - } - - // calendarSelections... (calendar from reservation-handlers-extensions.tsx) - const { calendarNavigation, daysTitles, selectedMonth, selectedYear } = - useCalendarsCommonLogic(true) //monthsInPastAllowed = true - - // handling the rendered section of all the dates - const displayedCalendarDate = new Date() - displayedCalendarDate.setMonth(selectedMonth - 1) - displayedCalendarDate.setFullYear(selectedYear) - const selectedMonthDates = generateCalendar(displayedCalendarDate) - - const highlightHoverClassList = - 'hover:bg-highlight hover:text-highlight-foreground' - - let certainDateSearch = '' - const certainDateSearchString = 'check-in-out-dates-' - if (currentSearch.includes(certainDateSearchString)) { - const dateOnly = currentSearch.split(certainDateSearchString) - certainDateSearch = dateOnly[1] - } - - const calendarDates = selectedMonthDates.map(week => - week.map((date, i) => { - const renderedFullDate = - date !== 0 - ? format( - new Date(selectedYear + '/' + selectedMonth + '/' + date), - 'yyyy/MM/dd', - ) - : '' - const dateHandler = 'check-in-out-dates-' + renderedFullDate - const selectedDateForSearch = - renderedFullDate === certainDateSearch ? true : false - - const dateButton = - date !== 0 ? ( - - ) : ( - - ) - - return dateButton - }), - ) - - return ( -
autoSubmit && handleFormChange(e.currentTarget)} - > -

Filters

- -
-
-
-
- - - - - - - - -
-
- - -
- - {!autoSubmit ? ( -
- - - Search - -
- ) : null} - - -
- - {/*
*/} - - -
-
-
{calendarNavigation}
- -
- {daysTitles} - -
- {calendarDates} -
-
-
-
- {/*
*/} -
-
- -
(Order: Newest first)
-
-
-
- ) -} diff --git a/app/components/search-bar.tsx b/app/components/search-bar.tsx deleted file mode 100644 index b8425fa..0000000 --- a/app/components/search-bar.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { Form, useSearchParams, useSubmit } from '@remix-run/react' -import { useId } from 'react' -import { useDebounce, useIsPending } from '#app/utils/misc.tsx' -import { Icon } from './ui/icon.tsx' -import { Input } from './ui/input.tsx' -import { Label } from './ui/label.tsx' -import { StatusButton } from './ui/status-button.tsx' - -export function SearchBar({ - actionUrl, - status, - autoFocus = false, - autoSubmit = false, - carModelUrl, -}: { - actionUrl: 'search' | 'brands' | 'admin/users' | 'pages' | 'carmodels' - status: 'idle' | 'pending' | 'success' | 'error' - autoFocus?: boolean - autoSubmit?: boolean - carModelUrl?: string -}) { - const id = useId() - const additionalActionRoute = carModelUrl ? '/' + carModelUrl : '' - const action = actionUrl + additionalActionRoute - - const [searchParams] = useSearchParams() - const submit = useSubmit() - const isSubmitting = useIsPending({ - formMethod: 'GET', - formAction: action, - }) - - const handleFormChange = useDebounce((form: HTMLFormElement) => { - submit(form) - }, 400) - - return ( -
autoSubmit && handleFormChange(e.currentTarget)} - > -
- - -
-
- - - Search - -
-
- ) -} diff --git a/app/routes/_base+/about.tsx b/app/routes/_base+/about.tsx deleted file mode 100644 index 55ef96d..0000000 --- a/app/routes/_base+/about.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function AboutRoute() { - return
About page
-} diff --git a/app/routes/_base+/index.tsx b/app/routes/_base+/index.tsx index d49e1fe..d528a5a 100644 --- a/app/routes/_base+/index.tsx +++ b/app/routes/_base+/index.tsx @@ -1,6 +1,15 @@ import { type DataFunctionArgs, type MetaFunction } from '@remix-run/node' -// import { LogoWochlife, LogoPhil } from '#app/components/logos.tsx' import { Link } from '@remix-run/react' +import { + bigBoxTitle, + boxInnerContentBoxInnerBox, + boxProps, + darkBoxBgClassList, + darkBoxInnerContentBox, + innerContentBoxTexts, + purpleBoxBgClassList, + purpleBoxInnerContentBox, +} from '#app/components/classlists.tsx' import { LogoPhil, LogoWochlife } from '#app/components/logos.tsx' import { Button } from '#app/components/ui/button.tsx' import { cn } from '#app/utils/misc.tsx' @@ -11,21 +20,6 @@ export async function loader({ request }: DataFunctionArgs) { return null } -//* global styling classes -// 2. boxes -const boxInnerContentBoxInnerBox = - 'custom-box-in-box-in-box-sizes rounded-lg-to-xl bg-cover bg-contain' -const bigBoxTitle = 'capitalize text-center text-2xl font-semibold mb-6' - -const boxInnerContentBoxProps = - 'custom-box-in-box-sizes flex items-center mb-3 4xl:mb-4' -const purpleBoxInnerContentBox = - boxInnerContentBoxProps + - ' bg-highlight-dark/30 hover:bg-highlight-dark transition-colors duration-500 p-2 cursor-pointer rounded-xl' -const darkBoxInnerContentBox = - boxInnerContentBoxProps + - ' bg-foreground/10 hover:bg-foreground hover:text-background cursor-pointer transition-colors duration-500 p-2 rounded-xl' - export default function Index() { //* styling classes // 1. grid, responsivness @@ -35,11 +29,6 @@ export default function Index() { const col2_col2 = 'custom-max-heights w-full lg:w-1/2 lg-to-xl-3:w-[55.83%] flex flex-col justify-between' - // 2. boxes - const boxProps = - 'flex flex-col rounded-3xl lg:rounded-6xl pt-4 px-4 md:px-5 md:pt-6' - const darkBoxBgClassList = boxProps + ' bg-dark-box-gradient' - const purpleBoxBgClassList = boxProps + ' bg-purple-box-gradient' const purpleBoxBgClassListSm = boxProps + ' bg-purple-box-gradient justify-between items-center pb-5 w-1/2 min-h-[175px] lg:h-[225px] lg-to-xl:h-[185px] 2xl:h-[195px] 4xl:h-[215px]' @@ -53,6 +42,7 @@ export default function Index() { // "Applications are not just a product to me, it's a form of an art, an expression. My apps show the world who You are. const aboutText = 'about' + const discoverText = 'discover' return (
@@ -102,33 +92,33 @@ export default function Index() { freelance -
+
@@ -142,7 +132,7 @@ export default function Index() { for companies -
+
@@ -191,7 +181,7 @@ export default function Index() {
@@ -203,9 +193,13 @@ export default function Index() { 'custom-projects-box-max-height h-full max-md:mt-6', )} > -

projects

+

+ + projects + +

-
+
-

{name}

-

- {description} -

+

{name}

+

{description}

) @@ -289,10 +281,8 @@ function ProjectsContentBox({ {!!imgSrc && imgSrc.length && }
-

{name}

-

- {description} -

+

{name}

+

{description}

) diff --git a/app/routes/_base+/search.tsx b/app/routes/_base+/search.tsx deleted file mode 100644 index ee84778..0000000 --- a/app/routes/_base+/search.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { - json, - type DataFunctionArgs, - // type MetaFunction, - redirect, -} from '@remix-run/node' -import { Link, Outlet, useLoaderData } from '@remix-run/react' -import { z } from 'zod' -import { - baseContainerWidthClassList, - frontendRoutesSpacingFromHeaderAndFooter, -} from '#app/components/classlists.tsx' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { ErrorList } from '#app/components/forms.tsx' -import { SearchBar } from '#app/components/search-bar.tsx' -import { Spacer } from '#app/components/spacer.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { cn, useDelayedIsPending } from '#app/utils/misc.tsx' - -const SearchResultSchema = z.object({ - id: z.string(), - url: z.string(), - title: z.string().nullable(), - type: z.enum(['CarBrand', 'CarModel', 'Dealer']), - carBrandTitle: z.string().nullable(), -}) - -const SearchResultsSchema = z.array(SearchResultSchema) - -export async function loader({ request, params }: DataFunctionArgs) { - const searchTerm = new URL(request.url).searchParams.get('search') - if (searchTerm === '') { - return redirect('/search') - } - - const like = `%${searchTerm ?? ''}%` - - const rawSearchResults = await prisma.$queryRaw` - SELECT - m.id, - m.url, - m.title, - 'CarModel' as type, - b.title as carBrandTitle - FROM CarModel m - JOIN CarBrand b ON m.carBrandId = b.id - WHERE m.title LIKE ${like} OR m.url LIKE ${like} - UNION - SELECT id, url, title, 'CarBrand' as type, NULL as carBrandTitle - FROM CarBrand - WHERE title LIKE ${like} - UNION - SELECT id, url, name as title, 'Dealer' as type, NULL as carBrandTitle - FROM Dealer - WHERE name LIKE ${like} - LIMIT 50 - ` - - const result = SearchResultsSchema.safeParse(rawSearchResults) - if (!result.success) { - return json({ status: 'error', error: result.error.message } as const, { - status: 400, - }) - } - - return json({ status: 'idle', searchResults: result.data } as const) -} - -export default function CarBrandUrlRoute() { - const data = useLoaderData() - - //TODO: implement search & query logic of load - const isPending = useDelayedIsPending({ - formMethod: 'GET', - formAction: '/search', - }) - if (data.status === 'error') { - console.error(data.error) - } - - return ( -
-
-

Search

- - -
- -
- -
- {data.status === 'idle' ? ( - data.searchResults.length ? ( -
    - {data.searchResults.map(result => ( -
  • - - - {result.title} - - -
  • - ))} -
- ) : ( -

No results found

- ) - ) : data.status === 'error' ? ( - - ) : null} -
-
- - -
- ) -} - -export function ErrorBoundary() { - return ( - ( -
-

- No carBrand with the carBrand url "{params.url}" exists -

- - - - - - -
- ), - }} - /> - ) -} - -// export const meta: MetaFunction = ({ data, params }) => { -// const displayName = data?.carBrand.title ?? params.url -// const seoContent = data?.carBrand.seo ?? params.title - -// return [ -// { title: `${displayName} | Wochlife` }, -// { -// name: 'description', -// content: seoContent, -// }, -// ] -// } diff --git a/app/routes/admin+/brands+/$id.tsx b/app/routes/admin+/brands+/$id.tsx deleted file mode 100644 index 41dc26a..0000000 --- a/app/routes/admin+/brands+/$id.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { - Link, - Outlet, - useFetcher, - useLoaderData, - useNavigate, -} from '@remix-run/react' -import { adminDetailBoxesClassList } from '#app/components/classlists.tsx' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { - cn, - invariantResponse, - getCarBrandImgSrc, -} from '#app/utils/misc.tsx' - -export async function loader({ params }: DataFunctionArgs) { - const carBrand = await prisma.carBrand.findUnique({ - where: { id: params.id }, - select: { - id: true, - url: true, - title: true, - description: true, - seo: true, - visibility: true, - images: { - select: { - id: true, - }, - }, - }, - }) - invariantResponse(carBrand, 'Not found', { status: 404 }) - - return json({ - carBrand, - isVisible: Boolean(carBrand.visibility), - }) -} - -export async function action({ request, params }: DataFunctionArgs) { - const form = await request.formData() - const isVisible = form.get('isVisible') === 'true' - - if (isVisible) { - await prisma.carBrand.update({ - where: { id: params.id }, - data: { visibility: true }, - select: { id: true }, - }) - } else { - await prisma.carBrand.update({ - where: { id: params.id }, - data: { visibility: false }, - select: { id: true }, - }) - } - return json({ status: 'success' }) -} - -export default function CarBrandIdRoute() { - const data = useLoaderData() - const carBrand = data.carBrand - - const visibilityFetcher = useFetcher() - const pendingVisible = visibilityFetcher.state !== 'idle' - const isVisible = pendingVisible - ? visibilityFetcher.formData?.get('isVisible') === 'true' - : data.isVisible - - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( - <> -
-
- - - - - -
-
- -
-
-

Title: {carBrand.title}

- -
-
-

- - id:  - - {carBrand.id} -

-

- - url:  - - /{carBrand.url} -

-
- -
-
-
- - visibility: - {' '} - - {carBrand.visibility ? 'visible' : 'hidden'} - -
-
- - - - - - - {carBrand.visibility ? ( - - - - ) : null} -
-
-
-
- -
-
-
-

Descriptions

- -

{carBrand.description}

- -

- - seo:  - - {carBrand.seo} -

-
-
- -
-
-

Images

- - {carBrand.images.length ? ( - <> -
- {carBrand.images.map(image => ( -
- {/* */} - - {/* */} -
- ))} -
- - ) : ( - <> - - You can add image in{' '} - - edit - {' '} - section. - - - )} -
-
-
-
- - - - ) -} - -export function ErrorBoundary() { - return ( - ( -
-

- No brand with the id "{params.id}" exists -

-
- ), - }} - /> - ) -} diff --git a/app/routes/admin+/brands+/$id_+/edit.tsx b/app/routes/admin+/brands+/$id_+/edit.tsx deleted file mode 100644 index fe0fc1d..0000000 --- a/app/routes/admin+/brands+/$id_+/edit.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { useLoaderData, useNavigate } from '@remix-run/react' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { invariantResponse } from '#app/utils/misc.tsx' -import { - CarBrandEditor, - action } from '../../../resources+/__brand-editor.tsx' - -export { action } - -export async function loader({ params }: DataFunctionArgs) { - const carBrand = await prisma.carBrand.findFirst({ - where: { - id: params.id, - }, - select: { - id: true, - url: true, - title: true, - countryOfOrigin: true, - description: true, - logoIcon: true, - seo: true, - images: true, - }, - }) - invariantResponse(carBrand, 'Not found', { status: 404 }) - - return json({ carBrand: carBrand }) -} - -export default function CarBrandEdit() { - const data = useLoaderData() - const navigate = useNavigate() - const goBack = () => navigate(-1) - - - return ( -
- -
- -
-

Editing: {data.carBrand.title}

- -
-
- ) -} diff --git a/app/routes/admin+/brands+/createnew.tsx b/app/routes/admin+/brands+/createnew.tsx deleted file mode 100644 index 19f0fef..0000000 --- a/app/routes/admin+/brands+/createnew.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useNavigate } from '@remix-run/react' -import { Button } from '#app/components/ui/button.tsx' -import { CarBrandEditor, action } from '../../resources+/__brand-editor.tsx' - -export { action } - -export default function CreateNewCarBrand() { - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( -
- -
- -
-

Create New CarModel

- -
-
- ) -} diff --git a/app/routes/admin+/brands+/index.tsx b/app/routes/admin+/brands+/index.tsx deleted file mode 100644 index 0a395b0..0000000 --- a/app/routes/admin+/brands+/index.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { type DataFunctionArgs, json } from '@remix-run/node' -import { Form, Link, useLoaderData } from '@remix-run/react' -import { Spacer } from '#app/components/spacer.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { Icon } from '#app/components/ui/icon.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { generateShortString, useDoubleCheckInsideMap } from '#app/utils/misc.tsx' - -export async function loader() { - const carBrands = await prisma.carBrand.findMany({ - select: { - id: true, - title: true, - url: true, - visibility: true, - }, - }) - if (!carBrands) { - throw new Response('not found', { status: 404 }) - } - return json({ carBrands }) -} - -export async function action({ request }: DataFunctionArgs) { - const form = await request.formData() - const carBrandId = form.get('carBrandId') as string - - if (carBrandId) { - await duplicateCarBrand(carBrandId) - } else { - return json({ status: 'error' }) - } - - return json({ status: 'success' }) -} - -async function duplicateCarBrand(carBrandId: string) { - const carBrand = await prisma.carBrand.findUnique({ - where: { id: carBrandId }, - }) - - if (!carBrand) { - throw new Error('CarBrand not found') - } - - const randomString = 'duplicated-' + carBrand.title + '-' + generateShortString(4) - await prisma.carBrand.create({ - data: { - ...carBrand, - id: undefined, //letting Prisma generate a new ID - url: randomString, - title: randomString, - description: randomString, - visibility: false, - }, - }) -} - -export default function AdminCarBrandsIndex() { - const data = useLoaderData() - const doubleCheckDuplicate = useDoubleCheckInsideMap() - - return ( -
-
-

- car brands overview -

-

Manage Your carBrands from here. πŸ€—

- -
- - - - - - - - - - - -
-
- - -
- {data.carBrands.map(carBrand => ( -
-
-
-
- {' '} - /{carBrand.url}{' '} -
-
{carBrand.title}
- - {carBrand.visibility ? ( -
- status:{' '} - - visible - -
- ) : ( -
- status:{' '} - - hidden - -
- )} -
- -
- - - - - {carBrand.visibility ? ( - - - - ) : null} - -
- - - -
-
-
-
- ))} -
-
- ) -} diff --git a/app/routes/admin+/dealers+/$id.tsx b/app/routes/admin+/dealers+/$id.tsx deleted file mode 100644 index 642134e..0000000 --- a/app/routes/admin+/dealers+/$id.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { Link, useLoaderData, useNavigate } from '@remix-run/react' -import { adminDetailBoxesClassList } from '#app/components/classlists.tsx' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { getDealerImgSrc, cn, invariantResponse } from '#app/utils/misc.tsx' - -export async function loader({ params }: DataFunctionArgs) { - const dealer = await prisma.dealer.findUnique({ - where: { id: params.id }, - select: { - id: true, - url: true, - name: true, - images: { - select: { - id: true, - altText: true, - }, - }, - }, - }) - invariantResponse(dealer, 'Not found', { status: 404 }) - return json({ - dealer, - }) -} - - -export default function AdminDealerIdRoute() { - const data = useLoaderData() - const dealer = data.dealer - - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( -
-
- - - - - -
-
- -
-
-

- Title: {dealer.name} -

- -
-

- - id:  - - {dealer.id} -

-

- - url:  - - /{dealer.url} -

-
-
-
- -
-
-
-

Gallery Images

- - {dealer.images.length ? ( - <> -
- {dealer.images.map(image => ( -
- {/* */} - {image.altText - {/* */} -
- ))} -
-
- - ...you can add more gallery images in the{' '} - - dealer's edit - {' '} - section. - -
- - ) : ( - <> - - You can add gallery images in the{' '} - - dealer's edit - {' '} - section. - - - )} -
-
-
-
- ) -} - -export function ErrorBoundary() { - return ( - ( -
-

- No dealer with the id "{params.id}" exists -

-
- ), - }} - /> - ) -} diff --git a/app/routes/admin+/dealers+/$id_+/edit.tsx b/app/routes/admin+/dealers+/$id_+/edit.tsx deleted file mode 100644 index 02c3a6a..0000000 --- a/app/routes/admin+/dealers+/$id_+/edit.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { useLoaderData, useNavigate } from '@remix-run/react' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { invariantResponse } from '#app/utils/misc.tsx' -import { DealerEditor, action } from '../../../resources+/__dealer-editor.tsx' - -export { action } - -export async function loader({ params }: DataFunctionArgs) { - const dealer = await prisma.dealer.findFirst({ - where: { - id: params.id, - }, - select: { - id: true, - url: true, - name: true, - state: true, - city: true, - address: true, - images: { - select: { - id: true, - altText: true, - }, - }, - }, - }) - invariantResponse(dealer, 'Not found', { status: 404 }) - return json({ dealer: dealer }) -} - -export default function DealerEdit() { - const data = useLoaderData() - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( -
- -
- -
-

Editing: {data.dealer.name}

- -
-
- ) -} diff --git a/app/routes/admin+/dealers+/createnew.tsx b/app/routes/admin+/dealers+/createnew.tsx deleted file mode 100644 index 3e4b34a..0000000 --- a/app/routes/admin+/dealers+/createnew.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useNavigate } from '@remix-run/react' -import { Button } from '#app/components/ui/button.tsx' -import { DealerEditor, action } from '../../resources+/__dealer-editor.tsx' - -export { action } - -export default function DealerCreateNewRoute() { - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( -
- -
- -
-

Create New Dealer

- -
-
- ) -} diff --git a/app/routes/admin+/dealers+/index.tsx b/app/routes/admin+/dealers+/index.tsx deleted file mode 100644 index 6231419..0000000 --- a/app/routes/admin+/dealers+/index.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { json } from '@remix-run/node' -import { Link, useLoaderData } from '@remix-run/react' -import { Spacer } from '#app/components/spacer.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' - -export async function loader() { - const dealers = await prisma.dealer.findMany({ - select: { - id: true, - name: true, - url: true, - }, - }) - if (!dealers) { - throw new Response('not found', { status: 404 }) - } - return json({ dealers }) -} - -export default function AdminDealersIndex() { - const data = useLoaderData() - - return ( -
-
-

- dealers overview -

-

Manage Your dealers from here. πŸ€—

- -
- - - - - - - -
-
- - -
- {data.dealers.map(dealer => ( -
-
-
-
- {' '} - /{dealer.url}{' '} -
-
{dealer.name}
-
- -
- - - -
-
-
- ))} -
-
- ) -} diff --git a/app/routes/admin+/models+/$id.tsx b/app/routes/admin+/models+/$id.tsx deleted file mode 100644 index 4029cbf..0000000 --- a/app/routes/admin+/models+/$id.tsx +++ /dev/null @@ -1,324 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { - Link, - Outlet, - useFetcher, - useLoaderData, - useNavigate, -} from '@remix-run/react' -import { adminDetailBoxesClassList } from '#app/components/classlists.tsx' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { Icon } from '#app/components/ui/icon.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { - cn, - invariantResponse, - getCarModelsGalleryImgSrc, -} from '#app/utils/misc.tsx' - -export async function loader({ params }: DataFunctionArgs) { - const carModel = await prisma.carModel.findUnique({ - where: { id: params.id }, - select: { - id: true, - url: true, - title: true, - description: true, - seo: true, - visibility: true, - carModelFacility: { - select: { - name: true, - iconName: true, - }, - }, - carModelGalleryImages: { - select: { - id: true, - }, - }, - carModelPreviewImages: { - select: { - id: true, - }, - }, - }, - }) - invariantResponse(carModel, 'Not found', { status: 404 }) - - return json({ - carModel, - isVisible: Boolean(carModel.visibility), - }) -} - -export async function action({ request, params }: DataFunctionArgs) { - const form = await request.formData() - const isVisible = form.get('isVisible') === 'true' - - if (isVisible) { - await prisma.carModel.update({ - where: { id: params.id }, - data: { visibility: true }, - select: { id: true }, - }) - } else { - await prisma.carModel.update({ - where: { id: params.id }, - data: { visibility: false }, - select: { id: true }, - }) - } - return json({ status: 'success' }) -} - -export default function CarModelIdRoute() { - const data = useLoaderData() - const carModel = data.carModel - - const visibilityFetcher = useFetcher() - const pendingVisible = visibilityFetcher.state !== 'idle' - const isVisible = pendingVisible - ? visibilityFetcher.formData?.get('isVisible') === 'true' - : data.isVisible - - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( - <> -
-
- - - - - -
-
- -
-
-

Title: {carModel.title}

- -
-
-

- - id:  - - {carModel.id} -

-

- - url:  - - /{carModel.url} -

-
- -
-
-
- - visibility: - {' '} - - {carModel.visibility ? 'visible' : 'hidden'} - -
-
- - - - - - - {carModel.visibility ? ( - - - - ) : null} -
-
-
-
- -
-
-
-

Descriptions

- -

{carModel.description}

- -

- - seo:  - - {carModel.seo} -

-
-
- -
-
-

Gallery Images

- - {carModel.carModelGalleryImages.length ? ( - <> -
- {carModel.carModelGalleryImages.map(image => ( -
- {/* */} - - {/* */} -
- ))} -
-
- - ...you can add more images from{' '} - - library - {' '} - section. - -
- - ) : ( - <> - - You can add gallery images from{' '} - - library - {' '} - section. - - - )} -
- -
-

Preview Images

- - {carModel.carModelPreviewImages.length ? ( - <> -
- {carModel.carModelPreviewImages.map(image => ( -
- {/* */} - - {/* */} -
- ))} -
-
- - ...you can add more images from{' '} - - library - {' '} - section. - -
- - ) : ( - <> - - You can add gallery images from{' '} - - library - {' '} - section. - - - )} -
-
- -
-
-

Facilities

- -
- {carModel.carModelFacility.length ? ( -
- {carModel.carModelFacility.map((facility, i) => ( -
-
{facility.name}
-
- {facility.iconName ? ( - - ) : ( - 'no icon' - )} -
-
- ))} -
- ) : ( - 'CarModel has no facilities selected.' - )} -
-
-
-
-
- - - - ) -} - -export function ErrorBoundary() { - return ( - ( -
-

- No carModel with the id "{params.id}" exists -

-
- ), - }} - /> - ) -} diff --git a/app/routes/admin+/models+/$id_+/edit.tsx b/app/routes/admin+/models+/$id_+/edit.tsx deleted file mode 100644 index 84bdd2c..0000000 --- a/app/routes/admin+/models+/$id_+/edit.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { useLoaderData, useNavigate } from '@remix-run/react' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { invariantResponse } from '#app/utils/misc.tsx' -import { - CarModelEditor, - action } from '../../../resources+/__model-editor.tsx' - -export { action } - -export async function loader({ params }: DataFunctionArgs) { - const carModel = await prisma.carModel.findFirst({ - where: { - id: params.id, - }, - select: { - id: true, - url: true, - title: true, - year: true, - description: true, - seo: true, - visibility: true, - carBrandId: true, - carModelFacility: { - select: { - id: true, - name: true, - iconName: true, - }, - }, - }, - }) - invariantResponse(carModel, 'Not found', { status: 404 }) - - const existingFacilities = await prisma.carModelFacility.findMany({ - select: { - id: true, - name: true, - iconName: true, - }, - }) - - const availableBrands = await prisma.carBrand.findMany({ - select: { - id: true, - }, - }) - - return json({ carModel: carModel, existingFacilities, availableBrands }) -} - -export default function CarModelEdit() { - const data = useLoaderData() - const navigate = useNavigate() - const goBack = () => navigate(-1) - - const availableBrands = data.availableBrands.map(brand=>brand.id) - - return ( -
- -
- -
-

Editing: {data.carModel.title}

- -
-
- ) -} diff --git a/app/routes/admin+/models+/$id_+/gallery.edit.tsx b/app/routes/admin+/models+/$id_+/gallery.edit.tsx deleted file mode 100644 index 9a734ff..0000000 --- a/app/routes/admin+/models+/$id_+/gallery.edit.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { useLoaderData, useNavigate } from '@remix-run/react' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { GallerySelector, action } from '../../../resources+/__model-gallery-selector.tsx' - -export { action } - -export async function loader({ params }: DataFunctionArgs) { - const carModel = await prisma.carModel.findFirst({ - where: { - id: params.id, - }, - select: { - id: true, - carModelFacility: { - select: { - id: true, - name: true, - iconName: true, - } - }, - carModelGalleryImages: { - select: { - id: true, - } - } - }, - }) - if (!carModel) { - throw new Response('not found', { status: 404 }) - } - - const galleries = await prisma.carModelsGallery.findMany({ - select: { - id: true, - name: true, - images: { - select: { - id: true, - } - }, - }, - }) - return json({ carModel: carModel, galleries }) -} - -export default function CarModelEdit() { - const data = useLoaderData() - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( -
- -
- - -
- ) -} diff --git a/app/routes/admin+/models+/$id_+/previewimages.edit.tsx b/app/routes/admin+/models+/$id_+/previewimages.edit.tsx deleted file mode 100644 index 567de8d..0000000 --- a/app/routes/admin+/models+/$id_+/previewimages.edit.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { useLoaderData, useNavigate } from '@remix-run/react' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { PreviewImagesSelector, action } from '../../../resources+/__model-preview-images-selector.tsx' - -export { action } - -export async function loader({ params }: DataFunctionArgs) { - const carModel = await prisma.carModel.findFirst({ - where: { - id: params.id, - }, - select: { - id: true, - carModelFacility: { - select: { - id: true, - name: true, - iconName: true, - } - }, - carModelPreviewImages: { - select: { - id: true, - } - } - }, - }) - if (!carModel) { - throw new Response('not found', { status: 404 }) - } - - const galleries = await prisma.carModelsGallery.findMany({ - select: { - id: true, - name: true, - images: { - select: { - id: true, - } - }, - }, - }) - return json({ carModel: carModel, galleries }) -} - -export default function CarModelEdit() { - const data = useLoaderData() - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( -
- -
- -
- -
-
- ) -} diff --git a/app/routes/admin+/models+/createnew.tsx b/app/routes/admin+/models+/createnew.tsx deleted file mode 100644 index a9c4f5c..0000000 --- a/app/routes/admin+/models+/createnew.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useLoaderData, useNavigate } from '@remix-run/react' -import { json } from '@remix-run/router' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { CarModelEditor, action } from '../../resources+/__model-editor.tsx' - -export async function loader() { - const existingFacilities = await prisma.carModelFacility.findMany({ - select: { - id: true, - name: true, - iconName: true, - }, - }) - - const availableBrands = await prisma.carBrand.findMany({ - select: { - id: true, - }, - }) - - return json({ existingFacilities, availableBrands }) -} - -export { action } - -export default function CreateNewCarModel() { - const data = useLoaderData<{ - existingFacilities: { - id: string - name: string - iconName: string - }[] - availableBrands: { - id: string - }[] - }>() - const navigate = useNavigate() - const goBack = () => navigate(-1) - - const availableBrands = data.availableBrands.map(brand=>brand.id) - - return ( -
- -
- -
-

Create New CarModel

- -
-
- ) -} diff --git a/app/routes/admin+/models+/facility+/$id.delete.tsx b/app/routes/admin+/models+/facility+/$id.delete.tsx deleted file mode 100644 index c0add89..0000000 --- a/app/routes/admin+/models+/facility+/$id.delete.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { useFetcher, useLoaderData, useNavigate } from '@remix-run/react' -import { destructiveModalWrapperClassList } from '#app/components/classlists.tsx' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { modalBackdropLightClassList } from '#app/components/modal-backdrop.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { Icon } from '#app/components/ui/icon.tsx' -import { StatusButton } from '#app/components/ui/status-button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { invariantResponse } from '#app/utils/misc.tsx' -import { createToastHeaders } from '#app/utils/toast.server.ts' - -export async function loader({ params }: DataFunctionArgs) { - const facility = await prisma.carModelFacility.findFirst({ - where: { - id: params.id, - }, - select: { - id: true, - name: true, - iconName: true, - }, - }) - - invariantResponse(facility, 'Not found', { status: 404 }) - return json({ facility }) -} - -export async function action({ request }: DataFunctionArgs) { - const formData = await request.formData() - invariantResponse( - formData.get('intent') === 'delete-facility', - 'Invalid intent', - ) - const facilityId = formData.get('facilityId') - invariantResponse(typeof facilityId === 'string', 'Invalid facilityId') - - const deletedFacility = await prisma.carModelFacility.delete({ - select: { name: true }, - where: { - id: facilityId, - }, - }) - - const toastHeaders = await createToastHeaders({ - title: 'Deleted!', - description: `The facility "${deletedFacility.name}" deleted. 🫑`, - }) - return json({ status: 'success' } as const, { headers: toastHeaders }) -} - -export default function FacilityDeletion() { - const data = useLoaderData() - const navigate = useNavigate() - const goBack = () => navigate(-1) - - const deleteFetcher = useFetcher() - - return ( - <> -
-
-
-

Deleting Facility

-
-
- {data.facility.iconName ? ( - - ) : ( - 'no icon' - )} -
- -

{data.facility.name}

-
- - - - - - - -
-
- - ) -} - -export function ErrorBoundary() { - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( - ( - <> -
-
-
-

- facility
with id: "{params.id}"
DELETED -

- -
-
- - ), - }} - /> - ) -} diff --git a/app/routes/admin+/models+/facility+/$id.edit.tsx b/app/routes/admin+/models+/facility+/$id.edit.tsx deleted file mode 100644 index 5316879..0000000 --- a/app/routes/admin+/models+/facility+/$id.edit.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { useLoaderData, useNavigate } from '@remix-run/react' -import { modalBackdropLightClassList } from '#app/components/modal-backdrop.tsx' -import { - FacilityEditor, - action, -} from '#app/routes/resources+/__facility-editor.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { invariantResponse } from '#app/utils/misc.tsx' - -export { action } - -export async function loader({ params }: DataFunctionArgs) { - const facility = await prisma.carModelFacility.findFirst({ - where: { - id: params.id, - }, - select: { - id: true, - name: true, - iconName: true, - }, - }) - - invariantResponse(facility, 'Not found', { status: 404 }) - return json({ facility }) -} - -export default function FacilityEdit() { - const data = useLoaderData() - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( - <> -
-
- -
- - ) -} diff --git a/app/routes/admin+/models+/facility+/createnew.tsx b/app/routes/admin+/models+/facility+/createnew.tsx deleted file mode 100644 index 063e205..0000000 --- a/app/routes/admin+/models+/facility+/createnew.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useNavigate } from '@remix-run/react' -import { modalBackdropLightClassList } from '#app/components/modal-backdrop.tsx' -import { - FacilityEditor, - action, -} from '#app/routes/resources+/__facility-editor.tsx' - -export { action } - -export default function CreateNewFacility() { - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( - <> -
-
- -
- - ) -} diff --git a/app/routes/admin+/models+/facility.tsx b/app/routes/admin+/models+/facility.tsx deleted file mode 100644 index 2ad9f9b..0000000 --- a/app/routes/admin+/models+/facility.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { json } from '@remix-run/node' -import { Link, Outlet, useLoaderData } from '@remix-run/react' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { Icon } from '#app/components/ui/icon.tsx' -import { action } from '#app/routes/resources+/__facility-editor.tsx' -import { prisma } from '#app/utils/db.server.ts' - -export { action } - -export async function loader() { - const facilities = await prisma.carModelFacility.findMany({ - select: { - id: true, - name: true, - iconName: true, - }, - }) - if (!facilities) { - throw new Response('not found', { status: 404 }) - } - return json({ facilities }) -} - -export default function FacilitiesRoute() { - const data = useLoaderData() - const reversedFacilities = data.facilities.slice().reverse() - - return ( -
- - -
-
create new facilities
- - - -
- - {data.facilities.length ? ( -
-
- existing facilities -
- -
- {/* // !reversed mapping */} - {reversedFacilities.map((facility, i) => ( -
-
{facility.name}
- -
- {facility.iconName ? ( - - ) : ( - <>No Icon - )} -
- -
- - - - - - - -
-
- ))} -
-
- ) : ( - <> -

no existing facilities, create your first

- - )} -
- ) -} - -export function ErrorBoundary() { - return -} diff --git a/app/routes/admin+/models+/gallery+/$id.delete.tsx b/app/routes/admin+/models+/gallery+/$id.delete.tsx deleted file mode 100644 index f97f0a9..0000000 --- a/app/routes/admin+/models+/gallery+/$id.delete.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { useFetcher, useLoaderData, useNavigate } from '@remix-run/react' -import { destructiveModalWrapperClassList } from '#app/components/classlists.tsx' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { modalBackdropLightClassList } from '#app/components/modal-backdrop.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { Icon } from '#app/components/ui/icon.tsx' -import { StatusButton } from '#app/components/ui/status-button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { invariantResponse } from '#app/utils/misc.tsx' -import { createToastHeaders } from '#app/utils/toast.server.ts' - -export async function loader({ params }: DataFunctionArgs) { - const carModelsGallery = await prisma.carModelsGallery.findFirst({ - where: { - id: params.id, - }, - select: { - id: true, - name: true, - }, - }) - if (!carModelsGallery) { - throw new Response('not found', { status: 404 }) - } - return json({ carModelsGallery }) -} - -export async function action({ request }: DataFunctionArgs) { - const formData = await request.formData() - invariantResponse( - formData.get('intent') === 'delete-carModelsGallery', - 'Invalid intent', - ) - const facilityId = formData.get('facilityId') - invariantResponse(typeof facilityId === 'string', 'Invalid facilityId') - - const deletedCarModelsGallery = await prisma.carModelsGallery.delete({ - select: { name: true }, - where: { - id: facilityId, - }, - }) - - const toastHeaders = await createToastHeaders({ - title: 'Deleted!', - description: `The carModelsGallery "${deletedCarModelsGallery.name}" deleted. 🫑`, - }) - return json({ status: 'success' } as const, { headers: toastHeaders }) -} - -export default function CarModelsGalleryDeletion() { - const data = useLoaderData() - const navigate = useNavigate() - const goBack = () => navigate(-1) - - const deleteFetcher = useFetcher() - - return ( - <> -
-
-
-

Deleting CarModelsGallery

-
-

{data.carModelsGallery.name}

-
- - - - - - - -
-
- - ) -} - -export function ErrorBoundary() { - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( - ( - <> -
-
-
-

- gallery
with id: "{params.id}"
DELETED -

- -
-
- - ), - }} - /> - ) -} diff --git a/app/routes/admin+/models+/gallery+/$id.edit.tsx b/app/routes/admin+/models+/gallery+/$id.edit.tsx deleted file mode 100644 index d78c748..0000000 --- a/app/routes/admin+/models+/gallery+/$id.edit.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { useLoaderData, useNavigate } from '@remix-run/react' -import { modalBackDropOnBackdropClassList } from '#app/components/modal-backdrop.tsx' -import { - CarModelsGalleryEditor, - action, -} from '#app/routes/resources+/__models-gallery-editor.tsx' - -import { prisma } from '#app/utils/db.server.ts' - -export { action } - -export async function loader({ params }: DataFunctionArgs) { - const carModelsGallery = await prisma.carModelsGallery.findFirst({ - where: { - id: params.id, - }, - select: { - id: true, - name: true, - images: { - select: { - id: true, - altText: true, - }, - }, - }, - }) - if (!carModelsGallery) { - throw new Response('not found', { status: 404 }) - } - return json({ carModelsGallery }) -} - -export default function CarModelsGalleryFolderEdit() { - const data = useLoaderData() - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( - <> -
-
- -
- - ) -} diff --git a/app/routes/admin+/models+/gallery+/$id.tsx b/app/routes/admin+/models+/gallery+/$id.tsx deleted file mode 100644 index 113bbe9..0000000 --- a/app/routes/admin+/models+/gallery+/$id.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { json, type DataFunctionArgs } from '@remix-run/node' -import { Link, Outlet, useLoaderData, useNavigate } from '@remix-run/react' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { modalBackdropClassList } from '#app/components/modal-backdrop.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { getCarModelsGalleryImgSrc, invariantResponse } from '#app/utils/misc.tsx' - -export async function loader({ params }: DataFunctionArgs) { - const gallery = await prisma.carModelsGallery.findUnique({ - where: { id: params.id }, - select: { - id: true, - name: true, - images: { - select: { - id: true, - altText: true, - }, - }, - }, - }) - - invariantResponse(gallery, 'Not found', { status: 404 }) - return json({ - gallery, - }) -} - -export default function CarModelIdRoute() { - const data = useLoaderData() - const gallery = data.gallery - - const navigate = useNavigate() - const goBack = () => navigate('/admin/models/gallery') - - return ( - <> -
-
-
-
-
- Gallery: {data.gallery.name} -
- - - - -
-
- -
-
-
-

Gallery Images

- - {gallery.images.length ? ( - <> -
- {gallery.images.map(image => ( -
- {/* */} - {image.altText - {/* */} -
- ))} -
-
- - ...you can add more gallery images in the{' '} - - gallery's edit - {' '} - section. - -
- - ) : ( - <> - - You can add gallery images in the{' '} - - gallery's edit - {' '} - section. - - - )} -
-
-
-
- - -
- - ) -} - -export function ErrorBoundary() { - return ( - ( -
-

- No gallery with the id "{params.id}" exists -

-
- ), - }} - /> - ) -} diff --git a/app/routes/admin+/models+/gallery+/createnew.tsx b/app/routes/admin+/models+/gallery+/createnew.tsx deleted file mode 100644 index c05f271..0000000 --- a/app/routes/admin+/models+/gallery+/createnew.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useNavigate } from '@remix-run/react' -import { modalBackdropLightClassList } from '#app/components/modal-backdrop.tsx' -import { - CarModelsGalleryEditor, - action, -} from '#app/routes/resources+/__models-gallery-editor.tsx' - -export { action } - -export default function CreateNewGalleryFolder() { - const navigate = useNavigate() - const goBack = () => navigate(-1) - - return ( - <> -
-
- -
- - ) -} diff --git a/app/routes/admin+/models+/gallery.tsx b/app/routes/admin+/models+/gallery.tsx deleted file mode 100644 index 05ab154..0000000 --- a/app/routes/admin+/models+/gallery.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { json } from '@remix-run/node' -import { Link, Outlet, useLoaderData } from '@remix-run/react' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { Icon } from '#app/components/ui/icon.tsx' -import { action } from '#app/routes/resources+/__models-gallery-editor.tsx' -import { prisma } from '#app/utils/db.server.ts' - -export { action } - -export async function loader() { - const carModelsGalleries = await prisma.carModelsGallery.findMany({ - select: { - id: true, - name: true, - }, - }) - if (!carModelsGalleries) { - throw new Response('not found', { status: 404 }) - } - return json({ carModelsGalleries }) -} - -export default function GalleriesRoute() { - const data = useLoaderData() - - return ( -
- - -
-
create new carModelsGalleries
- - - -
- - {data.carModelsGalleries.length ? ( -
-
- existing carModelsGalleries -
- -
- {data.carModelsGalleries.map((gallery, i) => ( -
-
{gallery.name}
- -
- - - - - - - -
-
- ))} -
-
- ) : ( - <> -

no existing gallerys, create your first

- - )} -
- ) -} - -export function ErrorBoundary() { - return -} diff --git a/app/routes/admin+/models+/index.tsx b/app/routes/admin+/models+/index.tsx deleted file mode 100644 index 0812453..0000000 --- a/app/routes/admin+/models+/index.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import { type DataFunctionArgs, json } from '@remix-run/node' -import { Form, Link, useLoaderData } from '@remix-run/react' -import { Spacer } from '#app/components/spacer.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { Icon } from '#app/components/ui/icon.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { generateShortString, useDoubleCheckInsideMap } from '#app/utils/misc.tsx' - -export async function loader() { - const carModels = await prisma.carModel.findMany({ - select: { - id: true, - title: true, - url: true, - visibility: true, - carBrand: { - select: { - url: true, - } - } - }, - }) - if (!carModels) { - throw new Response('not found', { status: 404 }) - } - return json({ carModels }) -} - -export async function action({ request }: DataFunctionArgs) { - const form = await request.formData() - const carModelId = form.get('carModelId') as string - - if (carModelId) { - await duplicateCarModel(carModelId) - } else { - return json({ status: 'error' }) - } - - return json({ status: 'success' }) -} - -async function duplicateCarModel(carModelId: string) { - const carModel = await prisma.carModel.findUnique({ - where: { id: carModelId }, - }) - - if (!carModel) { - throw new Error('CarModel not found') - } - - const randomString = 'duplicated-' + carModel.title + '-' + generateShortString(4) - await prisma.carModel.create({ - data: { - ...carModel, - id: undefined, //letting Prisma generate a new ID - url: randomString, - title: randomString, - description: randomString, - visibility: false, - }, - }) -} - -export default function AdminCarModelsIndex() { - const data = useLoaderData() - const doubleCheckDuplicate = useDoubleCheckInsideMap() - - return ( -
-
-

- car models overview -

-

Manage Your carModels from here. πŸ€—

- -
- - - - - - - - - - - -
-
- - -
- {data.carModels.map(carModel => ( -
-
-
-
- {' '} - /{carModel.url}{' '} -
-
{carModel.title}
- - {carModel.visibility ? ( -
- status:{' '} - - visible - -
- ) : ( -
- status:{' '} - - hidden - -
- )} -
- -
- - - - - {carModel.visibility ? ( - - - - ) : null} - -
- - - -
-
-
-
- ))} -
-
- ) -} diff --git a/app/routes/admin+/users+/index.tsx b/app/routes/admin+/users+/index.tsx index ee4daf1..cc11c52 100644 --- a/app/routes/admin+/users+/index.tsx +++ b/app/routes/admin+/users+/index.tsx @@ -1,110 +1,60 @@ -import { json, redirect, type DataFunctionArgs } from '@remix-run/node' +import { json } from '@remix-run/node' import { Link, useLoaderData } from '@remix-run/react' -import { z } from 'zod' import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { ErrorList } from '#app/components/forms.tsx' -import { SearchBar } from '#app/components/search-bar.tsx' import { prisma } from '#app/utils/db.server.ts' -import { cn, getUserImgSrc, useDelayedIsPending } from '#app/utils/misc.tsx' +import { getUserImgSrc } from '#app/utils/misc.tsx' -const UserSearchResultSchema = z.object({ - id: z.string(), - username: z.string(), - name: z.string().nullable(), - imageId: z.string().nullable(), -}) - -const UserSearchResultsSchema = z.array(UserSearchResultSchema) - -export async function loader({ request }: DataFunctionArgs) { - const searchTerm = new URL(request.url).searchParams.get('search') - if (searchTerm === '') { - return redirect('/admin/users') - } - - const like = `%${searchTerm ?? ''}%` - const rawUsers = await prisma.$queryRaw` - SELECT User.id, User.username, User.name, UserImage.id AS imageId - FROM User - LEFT JOIN UserImage ON User.id = UserImage.userId - WHERE User.username LIKE ${like} - OR User.name LIKE ${like} - ORDER BY ( - SELECT Note.updatedAt - FROM Note - WHERE Note.ownerId = User.id - ORDER BY Note.updatedAt DESC - LIMIT 1 - ) DESC - LIMIT 50 - ` - - const result = UserSearchResultsSchema.safeParse(rawUsers) - if (!result.success) { - return json({ status: 'error', error: result.error.message } as const, { - status: 400, - }) - } - return json({ status: 'idle', users: result.data } as const) +export async function loader() { + const users = await prisma.user.findMany({ + select: { + id: true, + username: true, + name: true, + image: { + select: { + id: true, + } + } + }, + }) + return json({ users }) } export default function UsersRoute() { const data = useLoaderData() - const isPending = useDelayedIsPending({ - formMethod: 'GET', - formAction: '/admin/users', - }) - - if (data.status === 'error') { - console.error(data.error) - } - return ( -
+

Wochlife Users

-
- -
-
- {data.status === 'idle' ? ( - data.users.length ? ( -
    - {data.users.map(user => ( -
  • - - {user.name - {user.name ? ( - - {user.name} - - ) : null} - - {user.username} + {data.users.length ? ( +
      + {data.users.map(user => ( +
    • + + {user.name + {user.name ? ( + + {user.name} - -
    • - ))} -
    - ) : ( -

    No users found

    - ) - ) : data.status === 'error' ? ( - - ) : null} + ) : null} + + {user.username} + + +
  • + ))} +
+ ) : ( +

No users found

+ )}
) diff --git a/app/routes/admin.tsx b/app/routes/admin.tsx index c2fc7cc..01822ab 100644 --- a/app/routes/admin.tsx +++ b/app/routes/admin.tsx @@ -91,14 +91,6 @@ export default function AdminRoute() { >
- - -
- - - - -
- - - () - const params = useParams() - - const isPending = useDelayedIsPending({ - formMethod: 'GET', - formAction: '/brands/' + params.url, - }) - if (data.status === 'error') { - console.error(data.error) - } - - return ( -
-
-

- {data.status === 'idle' && - data.carModels.length ? - {data.carModels[0].carBrandTitle} : No Models for {params.url} Found} -

- - -
- -
- -
- {data.status === 'idle' ? ( - data.carModels.length ? ( -
    - {data.carModels.map(carModel => ( -
  • - - {carModel.title && ( - - {carModel.title} - - )} - -
  • - ))} -
- ) : ( -

No models found

- ) - ) : data.status === 'error' ? ( - - ) : null} -
- - {/*
-
- {data.carModel.carModels.map( - (model, i) => - model.visibility && ( -
-
- -
-
{model.title}
-
- - - -
-
- ), - )} -
-
*/} -
- - -
- ) -} - -export function ErrorBoundary() { - return ( - ( -
-

- No carModel with the carModel url "{params.url}" exists -

- - - - - - -
- ), - }} - /> - ) -} - -// export const meta: MetaFunction = ({ data, params }) => { -// const displayName = data?.carModel.title ?? params.url -// const seoContent = data?.carModel.seo ?? params.title - -// return [ -// { title: `${displayName} | Wochlife` }, -// { -// name: 'description', -// content: seoContent, -// }, -// ] -// } diff --git a/app/routes/brands+/$url_+/$url.tsx b/app/routes/brands+/$url_+/$url.tsx deleted file mode 100644 index 1b3f761..0000000 --- a/app/routes/brands+/$url_+/$url.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import { json, type DataFunctionArgs, type MetaFunction } from '@remix-run/node' -import { Link, Outlet, useLoaderData } from '@remix-run/react' -import { baseContainerWidthClassList, frontendRoutesSpacingFromHeaderAndFooter } from '#app/components/classlists.tsx' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { - GallerySlider, - CarModelPreviewImagesSlider, -} from '#app/components/image-sliders.tsx' -import { Spacer } from '#app/components/spacer.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { Icon } from '#app/components/ui/icon.tsx' -import { SimilarItemsLoader } from '#app/routes/resources+/_similar-items-loader.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { cn, getCarModelsGalleryImgSrc } from '#app/utils/misc.tsx' -import { useOptionalUser } from '#app/utils/user.ts' - -export async function loader({ params }: DataFunctionArgs) { - const carModel = await prisma.carModel.findUnique({ - where: { url: params.url }, - select: { - id: true, - url: true, - title: true, - description: true, - seo: true, - visibility: true, - carModelFacility: { - select: { - id: true, - name: true, - iconName: true, - }, - }, - carModelGalleryImages: { - select: { - id: true, - altText: true, - }, - }, - carModelPreviewImages: { - select: { - id: true, - altText: true, - }, - }, - }, - }) - - if (!carModel || !carModel.visibility) { - throw new Response('not found', { status: 404 }) - } - - const carModelCurrentSeasonalPrices = await prisma.carModel.findUnique({ - where: { id: carModel.id }, - select: { - id: true, - }, - }) - - return json({ - carModel, - carModelCurrentSeasonalPrices, - }) -} - -export default function CarModelUrlRoute() { - const data = useLoaderData() - const isUserLoggedIn = useOptionalUser() - const carModel = data.carModel - const carModelGallery = carModel.carModelGalleryImages - const previewImages = carModel.carModelPreviewImages - const content = carModel.description ? carModel.description : '' - const carModelDescriptionHtml = content ? { __html: content } : null - - return ( -
- {previewImages.length ? ( -
- {previewImages.length > 1 ? ( - - ) : ( - <> - {previewImages[0].altText -
- - )} - -
-

CarModel {carModel.title}

-

carModel.shortDescription

- {isUserLoggedIn ? ( -
- - - -
- ) : null} -
-
- ) : null} - -
-
-

Facility

-
- {carModel.carModelFacility.length ? ( -
- {carModel.carModelFacility.map((facility, i) => ( -
-
{facility.name}
-
- {facility.iconName ? ( - - ) : ( - 'no icon' - )} -
-
- ))} -
- ) : ( - 'CarModel has no selected facilities, Contact us for more informations.' - )} -
-
- -
- {carModelDescriptionHtml ? ( -
-

CarModel details

- -
- ) : null} -
- - {carModelGallery.length ? ( - <> -

- Gallery -

- - {carModelGallery.length > 1 ? ( - - ) : ( - { - )} - - ) : null} -
- - - -
- ) -} - -export function ErrorBoundary() { - return ( - ( -
-

- No carModel with the carModel url "{params.url}" exists -

- - - - - - -
- ), - }} - /> - ) -} - -export const meta: MetaFunction = ({ data, params }) => { - const displayName = data?.carModel.title ?? params.url - const seoContent = data?.carModel.seo ?? params.title - - return [ - { title: `${displayName} | Wochlife` }, - { - name: 'description', - content: seoContent, - }, - ] -} diff --git a/app/routes/brands+/index.tsx b/app/routes/brands+/index.tsx deleted file mode 100644 index ad02d31..0000000 --- a/app/routes/brands+/index.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { - json, - type DataFunctionArgs, - // type MetaFunction, - redirect, -} from '@remix-run/node' -import { Link, Outlet, useLoaderData } from '@remix-run/react' -import { z } from 'zod' -import { - baseContainerWidthClassList, - frontendRoutesSpacingFromHeaderAndFooter, -} from '#app/components/classlists.tsx' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { ErrorList } from '#app/components/forms.tsx' -import { SearchBar } from '#app/components/search-bar.tsx' -import { Spacer } from '#app/components/spacer.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { cn, useDelayedIsPending } from '#app/utils/misc.tsx' - -const CarBrandSearchResultSchema = z.object({ - id: z.string(), - url: z.string(), - title: z.string().nullable(), -}) - -const CarBrandSearchResultsSchema = z.array(CarBrandSearchResultSchema) -export async function loader({ request }: DataFunctionArgs) { - const searchTerm = new URL(request.url).searchParams.get('search') - if (searchTerm === '') { - return redirect('/brands') - } - - const like = `%${searchTerm ?? ''}%` - const rawCarBrands = await prisma.$queryRaw` - SELECT CarBrand.id, CarBrand.url, CarBrand.title - FROM CarBrand - WHERE CarBrand.title LIKE ${like} - OR CarBrand.url LIKE ${like} - ORDER BY ( - SELECT Note.updatedAt - FROM Note - WHERE Note.ownerId = CarBrand.id - ORDER BY Note.updatedAt DESC - LIMIT 1 - ) DESC - LIMIT 50 - ` - - const result = CarBrandSearchResultsSchema.safeParse(rawCarBrands) - if (!result.success) { - return json({ status: 'error', error: result.error.message } as const, { - status: 400, - }) - } - return json({ status: 'idle', carBrands: result.data } as const) -} - -export default function CarBrandUrlRoute() { - const data = useLoaderData() - - //TODO: implement search & query logic of load - const isPending = useDelayedIsPending({ - formMethod: 'GET', - formAction: '/brands', - }) - if (data.status === 'error') { - console.error(data.error) - } - - return ( -
-
-

- Brands List -

- - -
- -
- -
- {data.status === 'idle' ? ( - data.carBrands.length ? ( -
    - {data.carBrands.map(carBrand => ( -
  • - - {carBrand.title && ( - - {carBrand.title} - - )} - -
  • - ))} -
- ) : ( -

No brands found

- ) - ) : data.status === 'error' ? ( - - ) : null} -
- - {/*
-
- {data.carBrand.carModels.map( - (model, i) => - model.visibility && ( -
-
- -
-
{model.title}
-
- - - -
-
- ), - )} -
-
*/} -
- - -
- ) -} - -export function ErrorBoundary() { - return ( - ( -
-

- No carBrand with the carBrand url "{params.url}" exists -

- - - - - - -
- ), - }} - /> - ) -} - -// export const meta: MetaFunction = ({ data, params }) => { -// const displayName = data?.carBrand.title ?? params.url -// const seoContent = data?.carBrand.seo ?? params.title - -// return [ -// { title: `${displayName} | Wochlife` }, -// { -// name: 'description', -// content: seoContent, -// }, -// ] -// } diff --git a/app/routes/dealers+/$url.tsx b/app/routes/dealers+/$url.tsx deleted file mode 100644 index 4ad95cc..0000000 --- a/app/routes/dealers+/$url.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { json, type DataFunctionArgs, type MetaFunction } from '@remix-run/node' -import { Link, useLoaderData } from '@remix-run/react' -import { frontendRoutesSpacingFromHeaderAndFooter } from '#app/components/classlists.tsx' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { Spacer } from '#app/components/spacer.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { useOptionalUser } from '#app/utils/user.ts' - -export async function loader({ params }: DataFunctionArgs) { - const dealer = await prisma.dealer.findUnique({ - where: { url: params.url }, - select: { - id: true, - url: true, - name: true, - images: { - select: { - id: true, - altText: true, - }, - }, - // previewImageId: true, - }, - }) - if (!dealer) { - throw new Response('not found', { status: 404 }) - } - return json({ dealer }) -} - -export default function DealerUrlRoute() { - const data = useLoaderData() - const isUserLoggedIn = useOptionalUser() - const dealer = data.dealer - - return ( -
-
-
-

{dealer.name}

- - {isUserLoggedIn ? ( -
- - - -
- ) : null} -
-
-
- ) -} - -export function ErrorBoundary() { - return ( - ( -
-

- No dealer with the dealer url "{params.url}" exists -

- - - - - - -
- ), - }} - /> - ) -} - -export const meta: MetaFunction = ({ data, params }) => { - const displayName = data?.dealer.name ?? params.url - - return [ - { title: `${displayName} | Wochlife` }, - { - name: 'content', - content: displayName, - }, - ] -} diff --git a/app/routes/dealers+/index.tsx b/app/routes/dealers+/index.tsx deleted file mode 100644 index 5e6af43..0000000 --- a/app/routes/dealers+/index.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { type DataFunctionArgs, json } from '@remix-run/node' -import { Link, useLoaderData } from '@remix-run/react' -import React from 'react' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { Spacer } from '#app/components/spacer.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { requireUserWithRole } from '#app/utils/permissions.ts' - -export async function loader({ request }: DataFunctionArgs) { - await requireUserWithRole(request, 'admin') // Temporary DEVelopment Phase - - const dealers = await prisma.dealer.findMany({ - select: { - id: true, - url: true, - name: true, - }, - }) - if (!dealers) { - throw new Response('not found', { status: 404 }) - } - return json({ dealers }) -} - -export default function DealersIndex() { - const data = useLoaderData() - - return ( -
-

Dealers

- - - {data.dealers.length ? ( -
-
- {data.dealers.map(dealer => ( -
-
- -
-
{dealer.name}
-
- - - -
-
- ))} -
-
- ) : ( -

Seems like no dealers are live at this moment. 😏

- )} -
- ) -} - -export function ErrorBoundary() { - return -} diff --git a/app/routes/portfolio.tsx b/app/routes/portfolio.tsx index 5bc021c..422e1f3 100644 --- a/app/routes/portfolio.tsx +++ b/app/routes/portfolio.tsx @@ -1,5 +1,12 @@ import { type DataFunctionArgs, type MetaFunction } from '@remix-run/node' import { NavLink, Outlet } from '@remix-run/react' +import { + bigBoxTitle, + boxInnerContentBoxInnerBox, + darkBoxBgClassList, + darkBoxInnerContentBox, + innerContentBoxTexts, +} from '#app/components/classlists.tsx' import { cn } from '#app/utils/misc.tsx' import { requireUserWithRole } from '#app/utils/permissions.ts' @@ -8,18 +15,6 @@ export async function loader({ request }: DataFunctionArgs) { return null } -//* global styling classes -// 2. boxes -const boxInnerContentBoxInnerBox = - 'custom-box-in-box-in-box-sizes rounded-lg-to-xl bg-cover bg-contain' -const bigBoxTitle = 'capitalize text-center text-2xl font-semibold mb-6' - -const boxInnerContentBoxProps = - 'custom-box-in-box-sizes flex items-center mb-3 4xl:mb-4' -const darkBoxInnerContentBox = - boxInnerContentBoxProps + - ' bg-foreground/10 hover:bg-foreground hover:text-background cursor-pointer transition-colors duration-500 p-2 rounded-xl' - export default function PortfolioRoute() { //* styling classes // 1. grid, responsivness @@ -27,16 +22,8 @@ export default function PortfolioRoute() { 'md-to-lg:w-[35%] lg:w-[32%] lg-to-xl:w-[29%] xl:w-[26.502%] max-w-[460px]' const col2 = 'w-full md-to-lg:w-[65%] lg:w-[68%] lg-to-xl:w-[71%] xl:w-[73.498%] flex flex-col' - - // 2. boxes - const boxProps = - 'flex flex-col rounded-3xl lg:rounded-6xl pt-4 px-4 md:px-5 md:pt-6' - const darkBoxBgClassList = boxProps + ' bg-dark-box-gradient' - - //* copy translations - return ( -
+

portfolio

@@ -53,7 +40,7 @@ export default function PortfolioRoute() { freelance -
+
{({ isActive }) => ( -
+
{({ isActive }) => (
-

{name}

-

- {description} -

+

{name}

+

{description}

) diff --git a/app/routes/projects+/forcompanies.tsx b/app/routes/projects+/forcompanies.tsx new file mode 100644 index 0000000..4c8fac7 --- /dev/null +++ b/app/routes/projects+/forcompanies.tsx @@ -0,0 +1,15 @@ +import { Outlet } from '@remix-run/react' + +export default function PortfolioForCompanies() { + return ( +
+

+ for companies +

+ +
+ +
+
+ ) +} diff --git a/app/routes/projects+/freelance.tsx b/app/routes/projects+/freelance.tsx new file mode 100644 index 0000000..4f8cef6 --- /dev/null +++ b/app/routes/projects+/freelance.tsx @@ -0,0 +1,15 @@ +import { Outlet } from '@remix-run/react' + +export default function PortfolioFreelance() { + return ( +
+

+ freelance +

+ +
+ +
+
+ ) +} diff --git a/app/routes/projects+/index.tsx b/app/routes/projects+/index.tsx new file mode 100644 index 0000000..0aaf108 --- /dev/null +++ b/app/routes/projects+/index.tsx @@ -0,0 +1,25 @@ +export default function PortfolioFreelance() { + return ( +
+

+ wochlife projects +

+ +
+
+
Web Dev
+ +
web dev content
+
+ +
+
+ streetwear art +
+ +
streetwear art content
+
+
+
+ ) +} diff --git a/app/routes/projects.tsx b/app/routes/projects.tsx new file mode 100644 index 0000000..711dada --- /dev/null +++ b/app/routes/projects.tsx @@ -0,0 +1,111 @@ +import { type DataFunctionArgs, type MetaFunction } from '@remix-run/node' +import { NavLink, Outlet } from '@remix-run/react' +import { + bigBoxTitle, + boxInnerContentBoxInnerBox, + darkBoxBgClassList, + innerContentBoxTexts, + purpleBoxBgClassList, + purpleBoxInnerContentBox, +} from '#app/components/classlists.tsx' +import { cn } from '#app/utils/misc.tsx' +import { requireUserWithRole } from '#app/utils/permissions.ts' + +export async function loader({ request }: DataFunctionArgs) { + await requireUserWithRole(request, 'admin') // Temporary DEVelopment Phase + return null +} + +export default function ProjectsRoute() { + //* styling classes + // 1. grid, responsivness + const col1 = + 'md-to-lg:w-[35%] lg:w-[32%] lg-to-xl:w-[29%] xl:w-[26.502%] max-w-[460px]' + const col2 = + 'w-full md-to-lg:w-[65%] lg:w-[68%] lg-to-xl:w-[71%] xl:w-[73.498%] flex flex-col' + + return ( +
+
+
+

projects

+ +
+ + {({ isActive }) => ( + + )} + + + {({ isActive }) => ( + + )} + + + {({ isActive }) => ( + + )} + +
+
+
+ +
+
+ +
+
+
+ ) +} + +function ProjectsContentBox({ + name, + description, + boxClass, + innerBoxClass, + imgSrc, +}: { + name: string + description: string + boxClass?: string + innerBoxClass?: string + imgSrc?: string +}) { + return ( +
+
+ {!!imgSrc && imgSrc.length && ( + + )} +
+
+

{name}

+

{description}

+
+
+ ) +} + +export const meta: MetaFunction = () => [{ title: 'Wochlife - Projects' }] diff --git a/app/routes/resources+/__brand-editor.tsx b/app/routes/resources+/__brand-editor.tsx deleted file mode 100644 index 68929da..0000000 --- a/app/routes/resources+/__brand-editor.tsx +++ /dev/null @@ -1,540 +0,0 @@ -import { - conform, - list, - useFieldList, - useFieldset, - useForm, - type FieldConfig, -} from '@conform-to/react' -import { getFieldsetConstraint, parse } from '@conform-to/zod' -import { createId as cuid } from '@paralleldrive/cuid2' -import { type CarBrand, type CarBrandImage } from '@prisma/client' -import { - unstable_createMemoryUploadHandler as createMemoryUploadHandler, - unstable_parseMultipartFormData as parseMultipartFormData, - json, - type DataFunctionArgs, - type SerializeFrom, -} from '@remix-run/node' -import { Form, useFetcher } from '@remix-run/react' -import { useRef, useState } from 'react' -import { toast } from 'sonner' -import { z } from 'zod' -import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx' -import { - ErrorList, - Field, - SelectBox, - TextareaField, -} from '#app/components/forms.tsx' -import { Button } from '#app/components/ui/button.tsx' -import { Icon } from '#app/components/ui/icon.tsx' -import { IconNames } from '#app/components/ui/icons/nameStrings.ts' -import { Label } from '#app/components/ui/label.tsx' -import { StatusButton } from '#app/components/ui/status-button.tsx' -import { Textarea } from '#app/components/ui/textarea.tsx' -import { prisma } from '#app/utils/db.server.ts' -import { cn, getCarBrandImgSrc } from '#app/utils/misc.tsx' -import { redirectWithToast } from '#app/utils/toast.server.ts' -import { countryList } from './_country-list.tsx' - -const urlMaxLength = 50 -const titleMinLength = 1 -const titleMaxLength = 100 -const descriptionMinLength = 1 -const descriptionMaxLength = 10000 -const seoMaxLength = 156 - -const MAX_UPLOAD_SIZE = 1024 * 1024 * 1 // 1MB - -const ImageFieldsetSchema = z.object({ - id: z.string().optional(), - file: z - .instanceof(File) - .optional() - .refine(file => { - return !file || file.size <= MAX_UPLOAD_SIZE - }, 'File size must be less than 1MB'), - altText: z.string().optional(), -}) - -type ImageFieldset = z.infer - -function imageHasFile( - image: ImageFieldset, -): image is ImageFieldset & { file: NonNullable } { - return Boolean(image.file?.size && image.file?.size > 0) -} - -function imageHasId( - image: ImageFieldset, -): image is ImageFieldset & { id: NonNullable } { - return image.id != null -} - -const maxImages = 1 - -const urlErrorRef = 'Invalid URL: Use only letters, numbers and dashes.' -const imgsErrorRef = - 'You have reached maximum number of images! (' + maxImages + ')' - -const CarBrandEditorSchema = z.object({ - id: z.string().optional(), - url: z - .string({ required_error: 'url is required' }) - .max(urlMaxLength) - // eslint-disable-next-line no-useless-escape - .regex(/^[a-zA-Z0-9\-]+$/, { - message: urlErrorRef, - }) - //allowing user to input both upper/lower, but always saving it in lowercase - .transform(value => value.toLowerCase()), - title: z.string().min(titleMinLength).max(titleMaxLength), - description: z.string().min(descriptionMinLength).max(descriptionMaxLength), - countryOfOrigin: z.string(), - logoIcon: z.string().optional(), - seo: z.string().max(seoMaxLength).optional(), - images: z.array(ImageFieldsetSchema).max(maxImages).optional(), -}) - -export async function action({ request, params }: DataFunctionArgs) { - const formData = await parseMultipartFormData( - request, - createMemoryUploadHandler({ maxPartSize: MAX_UPLOAD_SIZE }), - ) - - const submission = await parse(formData, { - schema: CarBrandEditorSchema.superRefine(async (data, ctx) => { - if (!data.id) return - - const carBrand = await prisma.carBrand.findUnique({ - select: { id: true }, - where: { id: data.id }, - }) - if (!carBrand) { - ctx.addIssue({ - code: 'custom', - message: 'CarBrand not found', - }) - } - }).transform(async ({ images = [], ...data }) => { - return { - ...data, - imageUpdates: await Promise.all( - images.filter(imageHasId).map(async i => { - if (imageHasFile(i)) { - return { - id: i.id, - altText: i.altText, - contentType: i.file.type, - blob: Buffer.from(await i.file.arrayBuffer()), - } - } else { - return { - id: i.id, - altText: i.altText, - } - } - }), - ), - newImages: await Promise.all( - images - .filter(imageHasFile) - .filter(i => !i.id) - .map(async image => { - return { - altText: image.altText, - contentType: image.file.type, - blob: Buffer.from(await image.file.arrayBuffer()), - } - }), - ), - } - }), - async: true, - }) - - if (submission.intent !== 'submit') { - return json({ status: 'idle', submission } as const) - } - - if (!submission.value) { - return json({ status: 'error', submission } as const, { status: 400 }) - } - - const { - id: carBrandId, - url, - title, - description, - countryOfOrigin, - logoIcon, - seo, - imageUpdates = [], - newImages = [], - } = submission.value - - const updatedCarBrand = await prisma.carBrand.upsert({ - select: { id: true, title: true }, - where: { id: carBrandId ?? '__new_carBrand__' }, - create: { - id: carBrandId ?? url, - url, // has to be unique!! //TODO: error handling unique constraint - title, - description, - countryOfOrigin, - logoIcon, - seo, - images: { create: newImages }, - }, - update: { - url, - title, - description, - countryOfOrigin, - logoIcon, - seo, - images: { - deleteMany: { id: { notIn: imageUpdates.map(i => i.id) } }, - updateMany: imageUpdates.map(updates => ({ - where: { id: updates.id }, - data: { ...updates, id: updates.blob ? cuid() : updates.id }, - })), - create: newImages, - }, - }, - }) - - let toastTitle - let toastDescription - if (params.id) { - toastTitle = 'CarBrand Edited!' - toastDescription = 'Your edits were saved. 😊' - } else { - toastTitle = 'CarBrand Created!' - toastDescription = `CarBrand: "${updatedCarBrand.title}" successfully created!` - } - - return redirectWithToast(`/admin/brands/${updatedCarBrand.id}`, { - type: 'success', - title: toastTitle, - description: toastDescription, - }) -} - -export function CarBrandEditor({ - carBrand, -}: { - carBrand?: SerializeFrom< - Pick< - CarBrand, - | 'id' - | 'url' - | 'title' - | 'description' - | 'logoIcon' - | 'seo' - | 'countryOfOrigin' - > & { - images: Array> - } - > -}) { - const carBrandFetcher = useFetcher() - const isPending = carBrandFetcher.state !== 'idle' - - const [form, fields] = useForm({ - id: 'carBrand-editor', - constraint: getFieldsetConstraint(CarBrandEditorSchema), - lastSubmission: carBrandFetcher.data?.submission, - onValidate({ formData }) { - const parsedData = parse(formData, { schema: CarBrandEditorSchema }) - const errorCheck = parsedData.error - - if (Object.entries(errorCheck).length) { - if ( - errorCheck.url && - errorCheck.url.filter(url => url.includes(urlErrorRef)) - ) { - toast.error(urlErrorRef) - } - - if (errorCheck.images) { - toast.error(imgsErrorRef) - } - } - - return parsedData - }, - defaultValue: { - url: carBrand?.url ?? '', - title: carBrand?.title ?? '', - description: carBrand?.description ?? '', - countryOfOrigin: carBrand?.countryOfOrigin ?? '', - logoIcon: carBrand?.logoIcon ?? '', - seo: carBrand?.seo ?? '', - images: carBrand?.images ?? [{}], - }, - }) - const imageList = useFieldList(form.ref, fields.images) - - const iconNames = IconNames() - - return ( -
-
- {/* - This hidden submit button is here to ensure that when the user hits - "enter" on an input field, the primary form function is submitted - rather than the first button in the form (which is delete/add image). - */} - - - - ))} - -
- - {imageList.length < maxImages ? ( - - ) : null} -
- - - -
- - - Submit - -
-
- ) -} - -function ImageChooser({ - config, -}: { - config: FieldConfig> -}) { - const ref = useRef(null) - const fields = useFieldset(ref, config) - const existingImage = Boolean(fields.id.defaultValue) - const [previewImage, setPreviewImage] = useState( - fields.id.defaultValue ? getCarBrandImgSrc(fields.id.defaultValue) : null, - ) - const [altText, setAltText] = useState(fields.altText.defaultValue ?? '') - - return ( -
-
-
-
- -
-
- -
-
-
- -