diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index 4c59baab..75adbbc3 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -16,6 +16,7 @@ import {AE, DK, US} from 'country-flag-icons/react/3x2'; import {useEffect, useState} from 'react'; import {useTranslation} from 'react-i18next'; import type {FooterQuery, HeaderQuery} from 'storefrontapi.generated'; +import {useMobile} from '~/hooks/isMobile'; import {useRootLoaderData} from '~/root'; import classes from './Footer.module.css'; import logo from '/logo.avif'; @@ -26,6 +27,7 @@ export function Footer({ }: FooterQuery & {shop: HeaderQuery['shop']}) { const {t} = useTranslation(['footer']); const [currentPath, setCurrentPath] = useState(''); + const isMobile = useMobile(); useEffect(() => { // Check if the window object is available (client-side rendering) @@ -82,11 +84,9 @@ export function Footer({ - - - + {t('social_media')} - + {t('language')} - + © 2024 BySisters. All rights reserved. @@ -161,9 +165,13 @@ export function Footer({ function FooterMenu({menu}: {menu: FooterQuery['menu']}) { const {t} = useTranslation(['footer']); const {publicStoreDomain} = useRootLoaderData(); - + const isMobile = useMobile(); return ( - + {t('company')} {menu?.items .filter(({url}) => url !== null && url !== undefined) diff --git a/app/components/ModalAccount.tsx b/app/components/ModalAccount.tsx new file mode 100644 index 00000000..f4bb456f --- /dev/null +++ b/app/components/ModalAccount.tsx @@ -0,0 +1,111 @@ +import { + Blockquote, + Button, + Flex, + Image, + Modal, + rem, + Stack, + Text, + TextInput, + Title, +} from '@mantine/core'; +import {useFetcher} from '@remix-run/react'; +import {type CustomerDetailsQuery} from 'customer-accountapi.generated'; +import {useEffect, useState} from 'react'; +import {useMobile} from '~/hooks/isMobile'; +import {type ActionResponse} from '~/routes/account.profile'; + +export const ModalAccount = ({customer}: {customer?: CustomerDetailsQuery}) => { + const isMobile = useMobile(); + const fetcher = useFetcher(); + const [isModalOpen, setModalOpen] = useState(true); + + useEffect(() => { + if (fetcher.state === 'idle' && fetcher.data && !fetcher.data.error) { + setModalOpen(false); + } + }, [fetcher.state, fetcher.data]); + + // if user not logged in !customer + // if user already firstname + if (!customer || customer.customer.firstName) { + return null; + } + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + fetcher.submit(event.currentTarget, {method: 'post'}); + }; + + return ( + {}} shadow="0" centered> + + + + + + + + + Personlig information + + + For at gøre din oplevelse hos os mere personlig, bedes du venligst + oplyse dit fornavn og efternavn. + + + {fetcher.data?.error ? ( +
+ Fejl: +
+ {fetcher.data.error} +
+ ) : null} + + + + +
+
+
+ ); +}; diff --git a/app/components/Wrapper.module.css b/app/components/Wrapper.module.css index 7ee6a68b..71e35c0b 100644 --- a/app/components/Wrapper.module.css +++ b/app/components/Wrapper.module.css @@ -1,6 +1,6 @@ .padding { - padding-top: 30px; - padding-bottom: 30px; + padding-top: 25px; + padding-bottom: 25px; @media (min-width: $mantine-breakpoint-sm) { padding-top: 50px; @@ -9,8 +9,8 @@ } .margin { - margin-top: 30px; - margin-bottom: 30px; + margin-top: 25px; + margin-bottom: 25px; @media (min-width: $mantine-breakpoint-sm) { margin-top: 50px; diff --git a/app/components/account/AccountContent.tsx b/app/components/account/AccountContent.tsx index 788f7804..90d5eab0 100644 --- a/app/components/account/AccountContent.tsx +++ b/app/components/account/AccountContent.tsx @@ -2,7 +2,7 @@ import {Card} from '@mantine/core'; export function AccountContent({children}: {children: React.ReactNode}) { return ( - + {children} ); diff --git a/app/components/account/AccountTitle.tsx b/app/components/account/AccountTitle.tsx index ac4e289b..268d24a5 100644 --- a/app/components/account/AccountTitle.tsx +++ b/app/components/account/AccountTitle.tsx @@ -3,6 +3,7 @@ import { Box, Divider, Flex, + Group, ScrollArea, Title, type TitleProps, @@ -22,9 +23,9 @@ export function AccountTitle({ - + {linkBack ? ( {heading} - + {children ? ( @@ -56,7 +57,6 @@ export function AccountTitle({ ) : null} - ); } diff --git a/app/graphql/customer-account/CustomerDetailsQuery.ts b/app/graphql/customer-account/CustomerDetailsQuery.ts index 382d6972..b2934db5 100644 --- a/app/graphql/customer-account/CustomerDetailsQuery.ts +++ b/app/graphql/customer-account/CustomerDetailsQuery.ts @@ -4,6 +4,11 @@ export const CUSTOMER_FRAGMENT = `#graphql id firstName lastName + emailAddress { + emailAddress + marketingState + } + tags defaultAddress { ...Address } diff --git a/app/hooks/isMobile.ts b/app/hooks/isMobile.ts new file mode 100644 index 00000000..7bd040e8 --- /dev/null +++ b/app/hooks/isMobile.ts @@ -0,0 +1,6 @@ +import {useMediaQuery} from '@mantine/hooks'; + +export const useMobile = () => { + const isMobile = useMediaQuery('(max-width: 48em)'); + return isMobile; +}; diff --git a/app/i18n/@types/resources.d.ts b/app/i18n/@types/resources.d.ts index f1378917..d3b16694 100644 --- a/app/i18n/@types/resources.d.ts +++ b/app/i18n/@types/resources.d.ts @@ -1,4 +1,74 @@ interface Resources { + "account": { + "business_title": "Partner", + "business_text": "Gå til business", + "business_action": "Gå til business", + "orders_title": "Bookinger", + "orders_text": "Administrer dine tidligere og kommende bookinger.", + "profile_title": "Personlig oplysninger", + "profile_text": "Ændre din fornavn eller efternavn.", + "address_title": "Adresser", + "address_text": "Administrer dine adresser", + "partner_title": "Partner", + "partner_text": "Start din skønhedskarrier", + "partner_action": "Kom igang", + "profile": { + "title": "Personlige oplysninger", + "error": "Fejl", + "first_name": "Fornavn", + "last_name": "Efternavn", + "save": "Gem ændringer" + }, + "orders": { + "title": "Ordre", + "date": "Dato", + "payment": "Betaling", + "status": "Status", + "total": "Beløb", + "empty": "Du har ikke afgivet nogen ordre endnu", + "goto_frontpage": "Gå til forside", + "id": { + "title": "Ordre {{name}}", + "bought": "Købt", + "products": "Produkter", + "image": "Billed", + "description": "Beskrivelse", + "total": "Total", + "summary": "Summary", + "discount": "Rabat", + "subtotal": "Subtotal", + "tax": "Moms", + "shipping": "Forsendelse", + "no_shipping": "Ingen forsendelse", + "treatments": "Behandlinger", + "details": "Detaljer", + "visiting": "Hos <0>{{name}}", + "visiting_deleted": "Skønhedsprofil er slettet", + "time": "Tid", + "location": "Location", + "location_expenses": "Udgifterne bliver beregnet under købsprocessen." + } + }, + "address": { + "title": "Adresser", + "creating": "Opretter...", + "create": "Opret adresse", + "edit": "Ændre adresse", + "saving": "Gemmer...", + "save": "Gem", + "deleting": "Sletter...", + "delete": "Slet", + "form": { + "first_name": "Fornavn", + "last_name": "Efternavn", + "address": "Adresse", + "zip": "Postnummer", + "city": "By", + "phone": "Telefonnummer", + "default_address": "Indstil som standardadresse" + } + } + }, "book": { "complete_shipping_calculation": "Udgifterne bliver beregnet under købsprocessen", "complete_map": "Se adresse på google map", @@ -42,6 +112,7 @@ interface Resources { "english": "Engelsk", "arabic": "Arabisk", "login": "Log ind", + "logout": "Log ud", "monday": "Mandag", "tuesday": "Tirsdag", "wednesday": "Onsdag", @@ -76,12 +147,12 @@ interface Resources { "reset_filters": "Nulstil filtering" }, "index": { - "treatments_button": "Vis Kategorier", - "treatments_title": "Book unikke [oplevelser og skønhedsoplevelse]", + "treatments_button": "Vis skønhedsbehandlinger", + "treatments_title": "Book din [skønhedsbehandling] idag", "artists_button": "Vis skønhedseksperter", "artists_title": "Mød vores [talentfulde eksperter]", "left_button": "Find en skønhedsekspert", - "right_button": "Vis behandlinger", + "right_button": "Vis skønhedsbehandlinger", "subtitle": "Vores platform forbinder dig med talentfulde skønhedseksperter inden for alle aspekter af skønhed.", "title": "Find [skønhedseksperter] og book deres [behandlinger] direkte på vores platform" }, diff --git a/app/i18n/defaultConfig.ts b/app/i18n/defaultConfig.ts index 022b952e..22f6beca 100644 --- a/app/i18n/defaultConfig.ts +++ b/app/i18n/defaultConfig.ts @@ -1,5 +1,5 @@ export const i18nDefaultConfig = { - debug: true, + debug: false, react: { useSuspense: false, }, diff --git a/app/root.tsx b/app/root.tsx index 78edc99a..380a498d 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -29,11 +29,14 @@ import { type LoaderFunctionArgs, type SerializeFrom, } from '@shopify/remix-oxygen'; +import {type CustomerDetailsQuery} from 'customer-accountapi.generated'; import {useEffect, type ReactNode} from 'react'; import favicon from './assets/favicon.svg'; import {CustomAnalytics} from './components/CustomAnalytics'; import {LanguageDetector} from './components/LanguageDetector'; import {LayoutWrapper} from './components/LayoutWrapper'; +import {ModalAccount} from './components/ModalAccount'; +import {CUSTOMER_DETAILS_QUERY} from './graphql/customer-account/CustomerDetailsQuery'; import appStyles from './styles/app.css?url'; export const handle: Handle = { @@ -91,7 +94,12 @@ export async function loader({context}: LoaderFunctionArgs) { const {storefront, customerAccount, cart, env} = context; const publicStoreDomain = context.env.PUBLIC_STORE_DOMAIN; - const isLoggedInPromise = customerAccount.isLoggedIn(); + const isLoggedInPromise = await customerAccount.isLoggedIn(); + let customer: CustomerDetailsQuery | undefined = undefined; + if (isLoggedInPromise) { + const {data} = await context.customerAccount.query(CUSTOMER_DETAILS_QUERY); + customer = data; + } const cartPromise = cart.get(); // defer the footer query (below the fold) @@ -115,6 +123,7 @@ export async function loader({context}: LoaderFunctionArgs) { footer: footerPromise, header: await headerPromise, isLoggedIn: isLoggedInPromise, + customer, publicStoreDomain, shop: getShopAnalytics({ storefront, @@ -145,8 +154,14 @@ export function Layout({children}: {children: ReactNode}) { return ( @@ -163,9 +178,7 @@ export function Layout({children}: {children: ReactNode}) { - {!path.includes('/account/') && - !path.includes('/book/') && - data?.cart ? ( + {!path.includes('/book/') && data?.cart ? ( + diff --git a/app/routes/account._index.tsx b/app/routes/account._index.tsx new file mode 100644 index 00000000..3ec576ad --- /dev/null +++ b/app/routes/account._index.tsx @@ -0,0 +1,168 @@ +import { + Avatar, + Button, + Card, + Container, + Divider, + Flex, + Grid, + Group, + rem, + Stack, + Text, + Title, +} from '@mantine/core'; +import {Form, Link, useOutletContext} from '@remix-run/react'; +import { + IconAddressBook, + IconBusinessplan, + IconLogout, + IconShoppingBag, + IconUser, +} from '@tabler/icons-react'; +import {useTranslation} from 'react-i18next'; +import {AccountMenuLink} from '~/components/account/AccountMenuLink'; +import {useMobile} from '~/hooks/isMobile'; +import {type AccountOutlet} from './account'; + +export default function AccountIndex() { + const {t} = useTranslation(['account', 'global']); + const {customer} = useOutletContext(); + const isMobile = useMobile(); + + return ( + + + + + + +
+ + {customer.firstName} {customer.lastName} + + + {customer.emailAddress?.emailAddress} + +
+
+
+
+ + {customer.tags.includes('business') ? ( + + + + + + {t('business_title')} + + {t('business_text')} + + + + + ) : null} + + + + + + + {t('orders_title')} + + {t('orders_text')} + + + + + + + + + {t('profile_title')} + + {t('profile_text')} + + + + + + + + + + {t('address_title')} + + {t('address_text')} + + + + + {!customer.tags.includes('business') ? ( + + + + + + {t('partner_title')} + + {t('partner_text')} + + + + + ) : null} + +
+ + +
+ ) => { + e.preventDefault(); + const target = e.target as Element; + const form = target.closest('form'); + if (form) { + (form as HTMLFormElement).submit(); + } + }} + /> + +
+
+ ); +} diff --git a/app/routes/account.addresses.$id.edit.tsx b/app/routes/account.addresses.$id.edit.tsx index 6cc1b839..ef738f6b 100644 --- a/app/routes/account.addresses.$id.edit.tsx +++ b/app/routes/account.addresses.$id.edit.tsx @@ -1,9 +1,10 @@ -import {Button, Group} from '@mantine/core'; +import {Button, Container, Group, rem} from '@mantine/core'; import {useOutletContext, useParams, type MetaFunction} from '@remix-run/react'; import {parseGid} from '@shopify/hydrogen'; import type {CustomerAddressInput} from '@shopify/hydrogen/customer-account-api-types'; import { json, + redirect, type ActionFunctionArgs, type LoaderFunctionArgs, } from '@shopify/remix-oxygen'; @@ -11,6 +12,7 @@ import type { AddressFragment, CustomerFragment, } from 'customer-accountapi.generated'; +import {useTranslation} from 'react-i18next'; import {AccountContent} from '~/components/account/AccountContent'; import {AccountTitle} from '~/components/account/AccountTitle'; import { @@ -181,14 +183,7 @@ export async function action({request, context}: ActionFunctionArgs) { throw new Error('Customer address delete failed.'); } - return json( - {error: null, deletedAddress: addressId}, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); + return redirect('/account/addresses'); } catch (error: unknown) { if (error instanceof Error) { return json( @@ -250,6 +245,7 @@ export async function action({request, context}: ActionFunctionArgs) { } export default function Addresses() { + const {t} = useTranslation(['account'], {keyPrefix: 'address'}); const {customer} = useOutletContext<{customer: CustomerFragment}>(); const {defaultAddress, addresses} = customer; const {id} = useParams(); @@ -261,8 +257,8 @@ export default function Addresses() { } return ( - <> - + + - {stateForMethod('PUT') !== 'idle' ? 'Saving' : 'Save'} + {stateForMethod('PUT') !== 'idle' ? t('saving') : t('save')} )} - + ); } diff --git a/app/routes/account.addresses._index.tsx b/app/routes/account.addresses._index.tsx index dbd8b285..6e99dda8 100644 --- a/app/routes/account.addresses._index.tsx +++ b/app/routes/account.addresses._index.tsx @@ -1,4 +1,13 @@ -import {Button, Card, SimpleGrid, Stack, Text, Title} from '@mantine/core'; +import { + Button, + Card, + Container, + rem, + SimpleGrid, + Stack, + Text, + Title, +} from '@mantine/core'; import {Link, useOutletContext, type MetaFunction} from '@remix-run/react'; import {parseGid} from '@shopify/hydrogen'; import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; @@ -7,6 +16,7 @@ import type { AddressFragment, CustomerFragment, } from 'customer-accountapi.generated'; +import {useTranslation} from 'react-i18next'; import {AccountButton} from '~/components/account/AccountButton'; import {AccountContent} from '~/components/account/AccountContent'; import {AccountTitle} from '~/components/account/AccountTitle'; @@ -38,18 +48,19 @@ export async function loader({context}: LoaderFunctionArgs) { } export default function Addresses() { + const {t} = useTranslation(['account'], {keyPrefix: 'address'}); const {customer} = useOutletContext<{customer: CustomerFragment}>(); const {defaultAddress, addresses} = customer; return ( - <> - + + } data-testid="create-button" > - Opret adresse + {t('create')} @@ -58,7 +69,7 @@ export default function Addresses() { defaultAddress={defaultAddress} /> - + ); } @@ -66,6 +77,7 @@ function ExistingAddresses({ addresses, defaultAddress, }: Pick) { + const {t} = useTranslation(['account'], {keyPrefix: 'address'}); return ( {addresses.nodes.map((address) => ( @@ -80,7 +92,7 @@ function ExistingAddresses({ {address.zip} {address.city} - +
))} diff --git a/app/routes/account.addresses.create.tsx b/app/routes/account.addresses.create.tsx index 838433a2..865df3cb 100644 --- a/app/routes/account.addresses.create.tsx +++ b/app/routes/account.addresses.create.tsx @@ -1,9 +1,16 @@ -import {Button, Checkbox, Group, Stack, TextInput} from '@mantine/core'; +import { + Button, + Checkbox, + Container, + Group, + rem, + Stack, + TextInput, +} from '@mantine/core'; import { Form, useActionData, useNavigation, - useOutletContext, type MetaFunction, } from '@remix-run/react'; import type {CustomerAddressInput} from '@shopify/hydrogen/customer-account-api-types'; @@ -16,6 +23,7 @@ import type { AddressFragment, CustomerFragment, } from 'customer-accountapi.generated'; +import {useTranslation} from 'react-i18next'; import {AccountContent} from '~/components/account/AccountContent'; import {AccountTitle} from '~/components/account/AccountTitle'; import {CREATE_ADDRESS_MUTATION} from '~/graphql/customer-account/CustomerAddressMutations'; @@ -193,20 +201,20 @@ export async function action({request, context}: ActionFunctionArgs) { } export default function Addresses() { - const {customer} = useOutletContext<{customer: CustomerFragment}>(); - const {defaultAddress, addresses} = customer; - + const {t} = useTranslation(['account'], {keyPrefix: 'address'}); return ( - <> - + + - + ); } function NewAddressForm() { + const {t} = useTranslation(['account'], {keyPrefix: 'address'}); + const newAddress = { address1: '', address2: '', @@ -234,7 +242,7 @@ function NewAddressForm() { formMethod="POST" type="submit" > - {stateForMethod('POST') !== 'idle' ? 'Creating' : 'Create'} + {stateForMethod('POST') !== 'idle' ? t('creating') : t('create')} )} @@ -242,44 +250,6 @@ function NewAddressForm() { ); } -function ExistingAddresses({ - addresses, - defaultAddress, -}: Pick) { - return ( -
- Existing addresses - {addresses.nodes.map((address) => ( - - {({stateForMethod}) => ( -
- - -
- )} -
- ))} -
- ); -} - export function AddressForm({ addressId, address, @@ -295,6 +265,7 @@ export function AddressForm({ ) => ReturnType['state']; }) => React.ReactNode; }) { + const {t} = useTranslation(['account'], {keyPrefix: 'address.form'}); const {state, formMethod} = useNavigation(); const action = useActionData(); const error = action?.error?.[addressId]; @@ -305,36 +276,36 @@ export function AddressForm({ @@ -349,23 +320,23 @@ export function AddressForm({ /> @@ -391,7 +362,7 @@ export function AddressForm({ maxLength={2} /> - + + - Du er i gang med at registrere dig som selvstændig skønhedsekspert, - og vi er begejstrede for at have dig med på holdet. Ved at blive en - del af BySisters, træder du ind i et fællesskab, hvor passion for + Du er i gang med at starte din skønhedskarrier på vores platform, og + vi er begejstrede for at have dig med på holdet. Ved at blive en del + af BySisters, træder du ind i et fællesskab, hvor passion for skønhed og ekspertise mødes for at skabe unikke oplevelser for - kunderne.{' '} + kunderne. For at fuldføre din registrering, bedes du udfylde de nødvendige - oplysninger om dig selv. Vi ser frem til at se, hvordan du vil - berige vores fællesskab med din ekspertise og passion for skønhed. - Velkommen til bySisters – sammen skaber vi skønhed! + oplysninger om dig selv.
- + ); } diff --git a/app/routes/account.dashboard.tsx b/app/routes/account.dashboard.tsx deleted file mode 100644 index 9517dc21..00000000 --- a/app/routes/account.dashboard.tsx +++ /dev/null @@ -1,317 +0,0 @@ -import { - Accordion, - Alert, - Button, - Card, - Flex, - Grid, - rem, - RingProgress, - Stack, - Text, - ThemeIcon, - Title, -} from '@mantine/core'; -import {Link, useLoaderData, useOutletContext} from '@remix-run/react'; -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; -import {IconCheck, IconCircle, IconHeart} from '@tabler/icons-react'; -import {AccordionGuide} from '~/components/AccordionGuide'; -import {AccountContent} from '~/components/account/AccountContent'; -import {AccountTitle} from '~/components/account/AccountTitle'; -import {CUSTOMER_DETAILS_QUERY} from '~/graphql/customer-account/CustomerDetailsQuery'; -import {getBookingShopifyApi} from '~/lib/api/bookingShopifyApi'; -import type {CustomerStatus, User} from '~/lib/api/model'; -import {getCustomer} from '~/lib/get-customer'; -import {type AccountOutlet} from './account'; - -export async function loader({context}: LoaderFunctionArgs) { - const customerId = await getCustomer({context}); - - const {data, errors} = await context.customerAccount.query( - CUSTOMER_DETAILS_QUERY, - ); - - if (errors?.length || !data?.customer) { - throw new Error('Customer not found'); - } - - const status = await getBookingShopifyApi().customerStatus(customerId); - - return json( - { - status: status.payload, - }, - { - headers: { - 'Cache-Control': 'no-cache, no-store, must-revalidate', - }, - }, - ); -} - -export default function AccountIndex() { - const {customer, isBusiness, user} = useOutletContext(); - const {status} = useLoaderData(); - - const heading = `Velkommen, ${customer.firstName} ${customer.lastName}`; - - return ( - <> - - - - {isBusiness ? ( - - ) : ( - - )} - - - ); -} - -function BusinessAccount({ - status, - user, -}: { - status: CustomerStatus; - user?: User | null; -}) { - const totalCount = Object.keys(status).length; - let finishedCount = 0; - - for (const key in status) { - if ((status as any)[key] === true) { - finishedCount++; - } - } - - return ( - - - - - - - Kom i gang med BySisters - - Brug denne guide til at få din side op og køre. - - - {finishedCount}/{totalCount} - - } - sections={[ - {value: (finishedCount / totalCount) * 100, color: 'green'}, - ]} - /> - - - - - - Færdiggøre din profil - - - Udfyld alle felter under profil side. - - - - - - Opret en lokation - - - - Opret de steder, hvor du vil tilbyde dine ydelser fra, så dine - følgere har mulighed for at vælge den mest passende lokation,{' '} - - - - - - - Opret en vagtplan - - - - Opret din vagtplan, så dine følgere ved, hvornår de kan booke - din tid. - - - - - - - Tilføj en ydelse - - - - Tilføj dine ydelser, så dine følgere kan se, hvad du tilbyder, - og vælge det, de har brug for. - - - - - - - Upload dit billed - - - - Upload et billede af dig selv, så dine følgere kan sætte - ansigt på personen bag ydelserne, - - - - - - - - {!!user && !user?.active ? ( - - } - data-testid="business-notification" - > - - Du er nu tilmeldt som en business-konto på vores hjemmeside. - -
- - Mens vi gennemgår og aktiverer din profil, opfordres du til at - besøge og udfylde siderne for lokation, vagtplan, ydelser og - eventuelt uploade et billede, for at gøre din konto klar til - potentielle kunder. - -
-
- ) : null} -
- ); -} - -function BuyerAccount() { - return ( - - - Du er nu officielt oprettet og logget ind på dit personlige dashboard. - Herfra har du adgang til en række funktioner til at gøre din oplevelse - så glidende og behagelig som muligt. Du kan gennemse din{' '} - købshistorik, og se dine fremtidige - bookinger med skønhedseksperter.{' '} - - - - - Hvis du elsker skønhed og vil dele dine talenter med verden, har vi en - unik mulighed for dig.{' '} - - Du kan gratis konvertere din konto til en{' '} - businesskonto. Efterfølgende kan du - tilbyde dine ydelser på vores platform, tjene penge, udvide din - kundekreds og bygge dit brand. - - - Vi er her for at støtte dig på hvert skridt af vejen. Så hvis du har - spørgsmål eller brug for hjælp undervejs, tøv ikke med at{' '} - kontakte os.
-
- Velkommen ombord, og lad os sammen skabe skønhed! -
-
- -
-
- ); -} - -function IconCheckOrX(boolean: boolean, name: string) { - return boolean ? ( - - - - ) : ( - - - - ); -} diff --git a/app/routes/account.orders.$id.tsx b/app/routes/account.orders.$id.tsx index cc9c70ab..ea30924d 100644 --- a/app/routes/account.orders.$id.tsx +++ b/app/routes/account.orders.$id.tsx @@ -1,6 +1,7 @@ import { Badge, Card, + Container, Flex, rem, SimpleGrid, @@ -13,8 +14,7 @@ import {Link, useLoaderData, type MetaFunction} from '@remix-run/react'; import {flattenConnection, Image, Money, parseGid} from '@shopify/hydrogen'; import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import type {OrderLineItemFullFragment} from 'customer-accountapi.generated'; -import {format} from 'date-fns'; -import {da} from 'date-fns/locale'; +import {Trans, useTranslation} from 'react-i18next'; import {AccountContent} from '~/components/account/AccountContent'; import {AccountTitle} from '~/components/account/AccountTitle'; import {isEqualGid} from '~/data/isEqualGid'; @@ -25,6 +25,7 @@ import type { CustomerOrderGetResponse, CustomerOrderLineItem, } from '~/lib/api/model'; +import {useDate} from '~/lib/duration'; import {getCustomer} from '~/lib/get-customer'; export const meta: MetaFunction = ({data}) => { @@ -92,6 +93,9 @@ export async function loader({params, context, request}: LoaderFunctionArgs) { } export default function OrderRoute() { + const {t} = useTranslation(['account'], {keyPrefix: 'orders.id'}); + const {format} = useDate(); + const { treatmentOrder, order, @@ -110,16 +114,16 @@ export default function OrderRoute() { ); return ( - <> + Ordre {order.name}} + heading={<>{t('title', {name: order.name})}} /> - Købt {format(new Date(order.processedAt!), 'PPPP', {locale: da})} + {t('bought')} {format(new Date(order.processedAt!), 'PPPP')} {fulfillmentStatus.length > 0 ? fulfillmentStatus[0].status : '-'} @@ -135,14 +139,14 @@ export default function OrderRoute() { {productsInOrder.length > 0 ? ( - Produkter: + {t('products')}: - Billed: - Beskrivelse - Total + {t('image')}: + {t('description')} + {t('total')} @@ -157,13 +161,13 @@ export default function OrderRoute() { - Summary + {t('summary')} {((discountValue && discountValue.amount) || discountPercentage) && ( - Discounts + {t('discount')} {discountPercentage ? ( -{discountPercentage}% OFF @@ -174,22 +178,22 @@ export default function OrderRoute() { )} - Subtotal + {t('subtotal')} - Moms + {t('tax')} - Total + {t('total')} {productsInOrder.length > 0 ? ( - Forsendelse + {t('shipping')} {order?.shippingAddress ? ( <> @@ -208,21 +212,21 @@ export default function OrderRoute() { )} ) : ( - Ingen forsendelse + {t('no_shipping')} )} ) : null} - + ); } function OrderLineRow({lineItem}: {lineItem: OrderLineItemFullFragment}) { return ( - + {lineItem.image && (
@@ -251,20 +255,21 @@ function TreatmentTable({ treatmentOrder: CustomerOrder; lineItems: OrderLineItemFullFragment[]; }) { + const {t} = useTranslation(['account'], {keyPrefix: 'orders.id'}); if (treatmentOrder.line_items.length === 0) return null; return ( <> - Behandlinger + {t('treatments')}
- Billed: - Detaljer: - Total + {t('image')}: + {t('details')}: + {t('total')} @@ -295,9 +300,11 @@ function TreatmentLineRow({ treatmentLineItem: CustomerOrderLineItem; lineItem: OrderLineItemFullFragment; }) { + const {format} = useDate(); + const {t} = useTranslation(['account'], {keyPrefix: 'orders.id'}); return ( - + {lineItem?.image && (
@@ -309,36 +316,36 @@ function TreatmentLineRow({ {treatmentLineItem.title} {treatmentLineItem.user ? ( - - Hos: {' '} - - {treatmentLineItem.user?.fullname} - - + + {treatmentLineItem.user.fullname} + , + ]} + /> ) : ( - - Hos: Slettet skønhedsekspert - + t('visiting_deleted') )} - Tid:{' '} + {t('time')}:{' '} {format( new Date(treatmentLineItem.properties.from || ''), "EEEE 'd.' d'.' LLL 'kl 'HH:mm", - { - locale: da, - }, )} {treatmentLineItem.shipping ? ( - Location:{' '} + {t('location')}:{' '} {treatmentLineItem.shipping.destination.fullAddress} - Udgifterne bliver beregnet under købsprocessen. + {t('location_expenses')} {treatmentLineItem.shipping.cost.value}{' '} {treatmentLineItem.shipping.cost.currency} @@ -346,7 +353,7 @@ function TreatmentLineRow({ ) : ( <> - Location:{' '} + {t('location')}:{' '} {treatmentLineItem.location?.fullAddress} diff --git a/app/routes/account.orders._index.tsx b/app/routes/account.orders._index.tsx index 10970503..cd72439e 100644 --- a/app/routes/account.orders._index.tsx +++ b/app/routes/account.orders._index.tsx @@ -1,10 +1,10 @@ -import {Button, Card, Flex, Table, Title} from '@mantine/core'; +import {Button, Card, Container, Flex, rem, Table, Title} from '@mantine/core'; import {Link, useLoaderData, type MetaFunction} from '@remix-run/react'; import { - Money, - Pagination, flattenConnection, getPaginationVariables, + Money, + Pagination, } from '@shopify/hydrogen'; import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {IconHeartHandshake} from '@tabler/icons-react'; @@ -12,11 +12,11 @@ import type { CustomerOrdersFragment, OrderItemFragment, } from 'customer-accountapi.generated'; -import {format} from 'date-fns'; -import {da} from 'date-fns/locale'; +import {useTranslation} from 'react-i18next'; import {AccountContent} from '~/components/account/AccountContent'; import {AccountTitle} from '~/components/account/AccountTitle'; import {CUSTOMER_ORDERS_QUERY} from '~/graphql/customer-account/CustomerOrdersQuery'; +import {useDate} from '~/lib/duration'; export const meta: MetaFunction = () => { return [{title: 'Købshistorik'}]; @@ -51,26 +51,22 @@ export async function loader({request, context}: LoaderFunctionArgs) { } export default function Orders() { + const {t} = useTranslation(['account'], {keyPrefix: 'orders'}); const {customer} = useLoaderData<{customer: CustomerOrdersFragment}>(); const {orders} = customer; return ( - <> - - Orders ({orders.nodes.length}) - - } - /> + + - + ); } function OrdersTable({orders}: Pick) { + const {t} = useTranslation(['account', 'global'], {keyPrefix: 'orders'}); return orders?.nodes.length ? ( {({nodes, isLoading, PreviousLink, NextLink}) => { @@ -78,16 +74,16 @@ function OrdersTable({orders}: Pick) { <>
- Dato - Betaling - Status - Beløb + {t('date')} + {t('payment')} + {t('status')} + {t('total')} @@ -101,7 +97,7 @@ function OrdersTable({orders}: Pick) {
@@ -114,15 +110,16 @@ function OrdersTable({orders}: Pick) { } function EmptyOrders() { + const {t} = useTranslation(['account', 'global'], {keyPrefix: 'orders'}); return ( - Du har ikke afgivet nogen ordre endnu + {t('empty')} @@ -130,12 +127,11 @@ function EmptyOrders() { } function OrderItem({order}: {order: OrderItemFragment}) { + const {format} = useDate(); const fulfillmentStatus = flattenConnection(order.fulfillments)[0]?.status; return ( - - {format(new Date(order.processedAt), 'PPPP', {locale: da})} - + {format(new Date(order.processedAt), 'PPPP')} {order.financialStatus} {fulfillmentStatus &&

{fulfillmentStatus}

}
diff --git a/app/routes/account.profile.tsx b/app/routes/account.profile.tsx index 3412792c..83e4103b 100644 --- a/app/routes/account.profile.tsx +++ b/app/routes/account.profile.tsx @@ -1,4 +1,4 @@ -import {Blockquote, Stack, TextInput} from '@mantine/core'; +import {Blockquote, Container, rem, Stack, TextInput} from '@mantine/core'; import { Form, useActionData, @@ -12,6 +12,7 @@ import { type LoaderFunctionArgs, } from '@shopify/remix-oxygen'; import type {CustomerFragment} from 'customer-accountapi.generated'; +import {useTranslation} from 'react-i18next'; import {AccountContent} from '~/components/account/AccountContent'; import {AccountTitle} from '~/components/account/AccountTitle'; import {SubmitButton} from '~/components/form/SubmitButton'; @@ -71,7 +72,7 @@ export async function action({request, context}: ActionFunctionArgs) { } if (!data?.customerUpdate?.customer) { - throw new Error('Customer profile update failed.'); + throw new Error(data?.customerUpdate?.userErrors[0].message); } return json( @@ -99,18 +100,19 @@ export async function action({request, context}: ActionFunctionArgs) { } export default function AccountProfile() { + const {t} = useTranslation(['account'], {keyPrefix: 'profile'}); const account = useOutletContext<{customer: CustomerFragment}>(); const action = useActionData(); const customer = action?.customer ?? account?.customer; return ( - <> - + + {action?.error ? (
- Fejl: + {t('error')}:
{action.error}
@@ -119,26 +121,24 @@ export default function AccountProfile() {
- Gem ændringer + {t('save')}
- +
); } diff --git a/app/routes/account.tsx b/app/routes/account.tsx index 4c9970d5..bd6f763f 100644 --- a/app/routes/account.tsx +++ b/app/routes/account.tsx @@ -1,40 +1,25 @@ -import {AppShell, Flex, Text, UnstyledButton} from '@mantine/core'; -import {useDisclosure, useMediaQuery} from '@mantine/hooks'; import tiptapStyles from '@mantine/tiptap/styles.css?url'; import { - Link, Outlet, type ShouldRevalidateFunction, useLoaderData, } from '@remix-run/react'; -import {parseGid} from '@shopify/hydrogen'; -import { - json, - type LoaderFunctionArgs, - type SerializeFrom, -} from '@shopify/remix-oxygen'; -import { - IconAddressBook, - IconCalendarEvent, - IconClock, - IconHome, - IconLocation, - IconMenu2, - IconShoppingBag, - IconUser, -} from '@tabler/icons-react'; +import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {type CustomerFragment} from 'customer-accountapi.generated'; import {useEffect} from 'react'; import notify, {Toaster} from 'react-hot-toast'; import {getToast} from 'remix-toast'; -import {AccountMenu} from '~/components/AccountMenu'; import {CUSTOMER_DETAILS_QUERY} from '~/graphql/customer-account/CustomerDetailsQuery'; -import {getBookingShopifyApi} from '~/lib/api/bookingShopifyApi'; import {type User} from '~/lib/api/model'; export function links() { return [{rel: 'stylesheet', href: tiptapStyles}]; } + +export const handle: Handle = { + i18n: ['global', 'account', 'professions'], +}; + export type AccountOutlet = { customer: CustomerFragment; user?: User | null; @@ -67,25 +52,11 @@ export async function loader({context, request}: LoaderFunctionArgs) { throw new Error('Customer not found'); } - const {payload: userIsBusiness} = - await getBookingShopifyApi().customerIsBusiness( - parseGid(data.customer.id).id, - ); - - let user = undefined; - if (userIsBusiness.isBusiness) { - user = ( - await getBookingShopifyApi().customerGet(parseGid(data.customer.id).id) - ).payload; - } - const {toast} = await getToast(request); return json( { customer: data.customer, - isBusiness: userIsBusiness.isBusiness, - user, toast, }, { @@ -98,7 +69,7 @@ export async function loader({context, request}: LoaderFunctionArgs) { } export default function Acccount() { - const {customer, user, isBusiness, toast} = useLoaderData(); + const {customer, toast} = useLoaderData(); useEffect(() => { if (toast) { @@ -117,184 +88,13 @@ export default function Acccount() { }, [toast]); return ( - - {' '} + <> + - - - ); -} - -function AccountLayout({ - children, - ...props -}: { - children: React.ReactNode; -} & Awaited>) { - const [opened, {toggle, close}] = useDisclosure(false); - const isMobile = useMediaQuery('(max-width: 48em)'); - - return ( - - - - - - {children} - - - - {props.user ? ( - <> - - - - Lokationer - - - - - - Vagtplan - - - - - - Ydelser - - - - - - Kalendar - - - - ) : ( - <> - - - - Hjem - - - - - - Købshistorik - - - - - - Konto - - - - - - Adresser - - - - )} - - - - Menu - - - - - + ); } diff --git a/app/routes/account_._index.tsx b/app/routes/account_._index.tsx deleted file mode 100644 index 28b2c9cf..00000000 --- a/app/routes/account_._index.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import { - BackgroundImage, - Blockquote, - Button, - Flex, - Paper, - rem, - Stack, - Text, - TextInput, - Title, -} from '@mantine/core'; -import { - Form, - useActionData, - useLoaderData, - useNavigation, - type MetaFunction, -} from '@remix-run/react'; -import type {CustomerUpdateInput} from '@shopify/hydrogen/customer-account-api-types'; -import { - json, - redirect, - type ActionFunctionArgs, - type LoaderFunctionArgs, -} from '@shopify/remix-oxygen'; -import type {CustomerFragment} from 'customer-accountapi.generated'; -import {CUSTOMER_DETAILS_QUERY} from '~/graphql/customer-account/CustomerDetailsQuery'; -import {CUSTOMER_UPDATE_MUTATION} from '~/graphql/customer-account/CustomerUpdateMutation'; - -export type ActionResponse = { - error: string | null; - customer: CustomerFragment | null; -}; - -export const meta: MetaFunction = () => { - return [{title: 'Profile'}]; -}; - -export async function loader({context}: LoaderFunctionArgs) { - await context.customerAccount.handleAuthStatus(); - - const {data, errors} = await context.customerAccount.query( - CUSTOMER_DETAILS_QUERY, - ); - - if (errors?.length || !data?.customer) { - throw new Error('Customer not found'); - } - - if (data.customer.firstName && data.customer.lastName) { - return redirect('/account/dashboard'); - } - - return json(data, { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }); -} - -export async function action({request, context}: ActionFunctionArgs) { - const {customerAccount} = context; - - const form = await request.formData(); - - try { - const customer: CustomerUpdateInput = {}; - const validInputKeys = ['firstName', 'lastName'] as const; - for (const [key, value] of form.entries()) { - if (!validInputKeys.includes(key as any)) { - continue; - } - if (typeof value === 'string' && value.length) { - customer[key as (typeof validInputKeys)[number]] = value; - } - } - - // update customer and possibly password - const {data, errors} = await customerAccount.mutate( - CUSTOMER_UPDATE_MUTATION, - { - variables: { - customer, - }, - }, - ); - - if (errors?.length) { - throw new Error(errors[0].message); - } - - if (!data?.customerUpdate?.customer) { - throw new Error('Customer profile update failed.'); - } - - return json( - { - error: null, - customer: data?.customerUpdate?.customer, - }, - { - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); - } catch (error: any) { - return json( - {error: error.message, customer: null}, - { - status: 400, - headers: { - 'Set-Cookie': await context.session.commit(), - }, - }, - ); - } -} - -export default function AccountProfile() { - const loader = useLoaderData<{customer: CustomerFragment}>(); - const {state} = useNavigation(); - const action = useActionData(); - const customer = action?.customer ?? loader?.customer; - - return ( - - - - - Velkommen til vores platform - - - - For at gøre din oplevelse hos os mere personlig og effektiv, bedes - du venligst oplyse dit fornavn og efternavn.{' '} - - - {action?.error ? ( -
- Fejl: -
- {action.error} -
- ) : null} - - - - - - - -
-
-
- ); -} diff --git a/app/routes/account_.authorize.tsx b/app/routes/account_.authorize.tsx index f7a8723e..05a04f54 100644 --- a/app/routes/account_.authorize.tsx +++ b/app/routes/account_.authorize.tsx @@ -1,5 +1,6 @@ import type {LoaderFunctionArgs} from '@shopify/remix-oxygen'; export async function loader({context}: LoaderFunctionArgs) { + console.log('called this before redirect'); return context.customerAccount.authorize(); } diff --git a/app/routes/business._index.tsx b/app/routes/business._index.tsx new file mode 100644 index 00000000..f1dc4d5d --- /dev/null +++ b/app/routes/business._index.tsx @@ -0,0 +1,237 @@ +import { + Avatar, + Button, + Card, + Container, + Divider, + Flex, + Grid, + Group, + rem, + Stack, + Text, + Title, +} from '@mantine/core'; +import {Form, Link, useLoaderData, useOutletContext} from '@remix-run/react'; +import {json, type LoaderFunctionArgs} from '@remix-run/server-runtime'; +import { + IconBook, + IconClock, + IconCurrencyDollar, + IconGps, + IconHeart, + IconLogout, + IconParachute, + IconPlane, + IconShoppingBag, +} from '@tabler/icons-react'; +import {AccountMenuLink} from '~/components/account/AccountMenuLink'; +import {useMobile} from '~/hooks/isMobile'; +import {getBookingShopifyApi} from '~/lib/api/bookingShopifyApi'; +import {getCustomer} from '~/lib/get-customer'; +import {modifyImageUrl} from '~/lib/image'; +import {type AccountOutlet} from './account'; + +export async function loader({context}: LoaderFunctionArgs) { + const customerId = await getCustomer({context}); + + const status = await getBookingShopifyApi().customerStatus(customerId); + + return json({ + status: status.payload, + }); +} + +export default function AccountIndex() { + const {status} = useLoaderData(); + const {customer, user} = useOutletContext(); + const isMobile = useMobile(); + + return ( + + + + + + {user?.images?.profile?.url ? ( + + ) : ( + + )} +
+ + {customer.firstName} {customer.lastName} + + + {customer.emailAddress?.emailAddress} + +
+
+
+
+ + {customer.tags.includes('business') ? ( + + + + + + Konto + + Din personlig konto + + + + + ) : null} + + + + + + + Profil + + Opdater dine personlige oplysninger + + + + + + + + + Lokationer + + Administrer dine lokationer + + + + + + + + + Vagtplan + + Ændre din fornavn eller efternavn. + + + + + + + + + Ydelser + + Tilføj eller opdater dine ydelser. + + + + + + + + + Kalendar + + Administrer dine kunde bookinger + + + + + + + + + Ferie + + Planlæg og administrer dine ferier + + + + + + + + + Udbetalinger + + Administrer dine udbetalinger + + + +
+ + +
+ ) => { + e.preventDefault(); + const target = e.target as Element; + const form = target.closest('form'); + if (form) { + (form as HTMLFormElement).submit(); + } + }} + /> + +
+
+ ); +} diff --git a/app/routes/account.api.booked.tsx b/app/routes/business.api.booked.tsx similarity index 100% rename from app/routes/account.api.booked.tsx rename to app/routes/business.api.booked.tsx diff --git a/app/routes/account.api.bookings.tsx b/app/routes/business.api.bookings.tsx similarity index 100% rename from app/routes/account.api.bookings.tsx rename to app/routes/business.api.bookings.tsx diff --git a/app/routes/account.api.products.tsx b/app/routes/business.api.products.tsx similarity index 100% rename from app/routes/account.api.products.tsx rename to app/routes/business.api.products.tsx diff --git a/app/routes/account.booked.$blockedId.destroy.tsx b/app/routes/business.booked.$blockedId.destroy.tsx similarity index 100% rename from app/routes/account.booked.$blockedId.destroy.tsx rename to app/routes/business.booked.$blockedId.destroy.tsx diff --git a/app/routes/account.booked.create.tsx b/app/routes/business.booked.create.tsx similarity index 100% rename from app/routes/account.booked.create.tsx rename to app/routes/business.booked.create.tsx diff --git a/app/routes/account.booked.tsx b/app/routes/business.booked.tsx similarity index 92% rename from app/routes/account.booked.tsx rename to app/routes/business.booked.tsx index 3669c96a..d981e47d 100644 --- a/app/routes/account.booked.tsx +++ b/app/routes/business.booked.tsx @@ -1,4 +1,4 @@ -import {Button, Modal, ScrollArea, Table} from '@mantine/core'; +import {Button, Container, Modal, rem, ScrollArea, Table} from '@mantine/core'; import {useMediaQuery} from '@mantine/hooks'; import { Form, @@ -66,8 +66,8 @@ export default function AccountBookings() { )); return ( - <> - + + Tilføj ferie @@ -97,6 +97,6 @@ export default function AccountBookings() { - + ); } diff --git a/app/routes/account.bookings.$orderId.group.$groupId.tsx b/app/routes/business.bookings.$orderId.group.$groupId.tsx similarity index 100% rename from app/routes/account.bookings.$orderId.group.$groupId.tsx rename to app/routes/business.bookings.$orderId.group.$groupId.tsx diff --git a/app/routes/account.bookings.tsx b/app/routes/business.bookings.tsx similarity index 97% rename from app/routes/account.bookings.tsx rename to app/routes/business.bookings.tsx index 762a9aef..0db6df07 100644 --- a/app/routes/account.bookings.tsx +++ b/app/routes/business.bookings.tsx @@ -4,7 +4,7 @@ import dayGridPlugin from '@fullcalendar/daygrid'; import interactionPlugin from '@fullcalendar/interaction'; import FullCalendar from '@fullcalendar/react'; import timeGridPlugin from '@fullcalendar/timegrid'; -import {Modal} from '@mantine/core'; +import {Container, Modal, rem} from '@mantine/core'; import {useMediaQuery} from '@mantine/hooks'; import {Outlet, useNavigate, useOutlet} from '@remix-run/react'; import {AccountContent} from '~/components/account/AccountContent'; @@ -37,7 +37,7 @@ export default function AccountBookings() { }; return ( - <> + - + ); } diff --git a/app/routes/account.locations.$locationId._index.tsx b/app/routes/business.locations.$locationId._index.tsx similarity index 100% rename from app/routes/account.locations.$locationId._index.tsx rename to app/routes/business.locations.$locationId._index.tsx diff --git a/app/routes/account.locations.$locationId.destroy.tsx b/app/routes/business.locations.$locationId.destroy.tsx similarity index 100% rename from app/routes/account.locations.$locationId.destroy.tsx rename to app/routes/business.locations.$locationId.destroy.tsx diff --git a/app/routes/account.locations.$locationId.products.tsx b/app/routes/business.locations.$locationId.products.tsx similarity index 100% rename from app/routes/account.locations.$locationId.products.tsx rename to app/routes/business.locations.$locationId.products.tsx diff --git a/app/routes/account.locations.$locationId.tsx b/app/routes/business.locations.$locationId.tsx similarity index 91% rename from app/routes/account.locations.$locationId.tsx rename to app/routes/business.locations.$locationId.tsx index 28124a9c..1f7c67c7 100644 --- a/app/routes/account.locations.$locationId.tsx +++ b/app/routes/business.locations.$locationId.tsx @@ -4,7 +4,7 @@ import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {getBookingShopifyApi} from '~/lib/api/bookingShopifyApi'; import {getCustomer} from '~/lib/get-customer'; -import {Button} from '@mantine/core'; +import {Button, Container, rem} from '@mantine/core'; import {jsonWithSuccess} from 'remix-toast'; import {AccountButton} from '~/components/account/AccountButton'; import {AccountContent} from '~/components/account/AccountContent'; @@ -31,8 +31,8 @@ export default function AccountLocationsEdit() { const defaultValue = useLoaderData(); return ( - <> - + + Basic detaljer @@ -76,6 +76,6 @@ export default function AccountLocationsEdit() { - + ); } diff --git a/app/routes/account.locations._index.tsx b/app/routes/business.locations._index.tsx similarity index 97% rename from app/routes/account.locations._index.tsx rename to app/routes/business.locations._index.tsx index ee632f8b..91c7f9e9 100644 --- a/app/routes/account.locations._index.tsx +++ b/app/routes/business.locations._index.tsx @@ -1,6 +1,7 @@ import { Button, Card, + Container, Flex, rem, SimpleGrid, @@ -35,8 +36,8 @@ export default function AccountLocationsIndex() { const {locations} = useLoaderData(); return ( - <> - + + } @@ -81,7 +82,7 @@ export default function AccountLocationsIndex() { ))} - + ); } diff --git a/app/routes/account.locations.create.tsx b/app/routes/business.locations.create.tsx similarity index 96% rename from app/routes/account.locations.create.tsx rename to app/routes/business.locations.create.tsx index 782ff0f5..a773ef01 100644 --- a/app/routes/account.locations.create.tsx +++ b/app/routes/business.locations.create.tsx @@ -10,7 +10,7 @@ import {customerLocationCreateBody} from '~/lib/zod/bookingShopifyApi'; import {getFormProps, getInputProps, useForm} from '@conform-to/react'; import {parseWithZod} from '@conform-to/zod'; -import {Stack, TextInput} from '@mantine/core'; +import {Container, rem, Stack, TextInput} from '@mantine/core'; import {redirectWithSuccess} from 'remix-toast'; import {AccountContent} from '~/components/account/AccountContent'; import {AccountTitle} from '~/components/account/AccountTitle'; @@ -85,8 +85,11 @@ export default function Component() { }); return ( - <> - + +
@@ -206,6 +209,6 @@ export default function Component() {
- +
); } diff --git a/app/routes/account.locations.tsx b/app/routes/business.locations.tsx similarity index 100% rename from app/routes/account.locations.tsx rename to app/routes/business.locations.tsx diff --git a/app/routes/account.payouts.$id.tsx b/app/routes/business.payouts.$id.tsx similarity index 96% rename from app/routes/account.payouts.$id.tsx rename to app/routes/business.payouts.$id.tsx index f0953d88..3588effb 100644 --- a/app/routes/account.payouts.$id.tsx +++ b/app/routes/business.payouts.$id.tsx @@ -1,4 +1,4 @@ -import {Card, Stack, Table, Text} from '@mantine/core'; +import {Card, Container, rem, Stack, Table, Text} from '@mantine/core'; import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {getCustomer} from '~/lib/get-customer'; @@ -16,7 +16,7 @@ import type { CustomerPayoutLogResponse, Shipping, } from '~/lib/api/model'; -import {isMobilePay} from './account.payouts'; +import {isMobilePay} from './business.payouts'; export function isShipping( details: CustomerPayoutLogReferenceDocument, @@ -45,7 +45,7 @@ export default function AccountPayoutsId() { const data = useLoaderData(); return ( - <> + @@ -54,7 +54,7 @@ export default function AccountPayoutsId() { - + ); } diff --git a/app/routes/account.payouts._index.tsx b/app/routes/business.payouts._index.tsx similarity index 96% rename from app/routes/account.payouts._index.tsx rename to app/routes/business.payouts._index.tsx index eb9478d6..4d15aee6 100644 --- a/app/routes/account.payouts._index.tsx +++ b/app/routes/business.payouts._index.tsx @@ -2,6 +2,8 @@ import { Badge, Button, Card, + Container, + rem, SimpleGrid, Skeleton, Stack, @@ -22,7 +24,7 @@ import type { CustomerPayoutBalanceResponse, } from '~/lib/api/model'; import {getCustomer} from '~/lib/get-customer'; -import {isMobilePay} from './account.payouts'; +import {isMobilePay} from './business.payouts'; export async function loader({context}: LoaderFunctionArgs) { const customerId = await getCustomer({context}); @@ -43,8 +45,8 @@ export default function AccountPayoutsIndex() { const data = useLoaderData(); return ( - <> - + + @@ -114,7 +116,7 @@ export default function AccountPayoutsIndex() { - + ); } diff --git a/app/routes/account.payouts.create.tsx b/app/routes/business.payouts.create.tsx similarity index 97% rename from app/routes/account.payouts.create.tsx rename to app/routes/business.payouts.create.tsx index 394603e2..61acca67 100644 --- a/app/routes/account.payouts.create.tsx +++ b/app/routes/business.payouts.create.tsx @@ -1,8 +1,10 @@ import {parseWithZod} from '@conform-to/zod'; import { + Container, Flex, InputBase, Radio, + rem, Stack, Text, TextInput, @@ -26,7 +28,7 @@ import {SubmitButton} from '~/components/form/SubmitButton'; import {getBookingShopifyApi} from '~/lib/api/bookingShopifyApi'; import {CustomerPayoutAccountType} from '~/lib/api/model'; import {customerPayoutAccountCreateBody} from '~/lib/zod/bookingShopifyApi'; -import {isMobilePay} from './account.payouts'; +import {isMobilePay} from './business.payouts'; const schema = customerPayoutAccountCreateBody; @@ -95,7 +97,7 @@ export default function AccountPayoutsSettings() { const payoutType = useInputControl(fields.payoutType); return ( - <> + @@ -180,6 +182,6 @@ export default function AccountPayoutsSettings() { - + ); } diff --git a/app/routes/account.payouts.destroy.tsx b/app/routes/business.payouts.destroy.tsx similarity index 100% rename from app/routes/account.payouts.destroy.tsx rename to app/routes/business.payouts.destroy.tsx diff --git a/app/routes/account.payouts.request.tsx b/app/routes/business.payouts.request.tsx similarity index 100% rename from app/routes/account.payouts.request.tsx rename to app/routes/business.payouts.request.tsx diff --git a/app/routes/account.payouts.tsx b/app/routes/business.payouts.tsx similarity index 100% rename from app/routes/account.payouts.tsx rename to app/routes/business.payouts.tsx diff --git a/app/routes/account.public._index.tsx b/app/routes/business.public._index.tsx similarity index 100% rename from app/routes/account.public._index.tsx rename to app/routes/business.public._index.tsx diff --git a/app/routes/account.public.social.tsx b/app/routes/business.public.social.tsx similarity index 100% rename from app/routes/account.public.social.tsx rename to app/routes/business.public.social.tsx diff --git a/app/routes/account.public.theme.tsx b/app/routes/business.public.theme.tsx similarity index 100% rename from app/routes/account.public.theme.tsx rename to app/routes/business.public.theme.tsx diff --git a/app/routes/account.public.tsx b/app/routes/business.public.tsx similarity index 69% rename from app/routes/account.public.tsx rename to app/routes/business.public.tsx index ba85d176..f4830c9e 100644 --- a/app/routes/account.public.tsx +++ b/app/routes/business.public.tsx @@ -1,4 +1,4 @@ -import {Group} from '@mantine/core'; +import {Container, Group, rem} from '@mantine/core'; import {Outlet} from '@remix-run/react'; import {AccountButton} from '~/components/account/AccountButton'; @@ -7,19 +7,19 @@ import {AccountTitle} from '~/components/account/AccountTitle'; export default function AccountBusiness() { return ( - <> - + + Profil Social Media Tema - Skift billed + Skift billed - + ); } diff --git a/app/routes/account.upload.tsx b/app/routes/business.public.upload.tsx similarity index 68% rename from app/routes/account.upload.tsx rename to app/routes/business.public.upload.tsx index 421a0185..341f03ac 100644 --- a/app/routes/account.upload.tsx +++ b/app/routes/business.public.upload.tsx @@ -13,8 +13,6 @@ import { import {IconFileCv, IconInfoCircle} from '@tabler/icons-react'; import {useEffect, useRef, useState} from 'react'; import {redirectWithSuccess} from 'remix-toast'; -import {AccountContent} from '~/components/account/AccountContent'; -import {AccountTitle} from '~/components/account/AccountTitle'; import {getBookingShopifyApi} from '~/lib/api/bookingShopifyApi'; import {getCustomer} from '~/lib/get-customer'; @@ -116,53 +114,49 @@ export default function AccountUpload() { return ( <> - - - - {imageUploaded ? ( - } - > - Vi har modtaget dit billed, der går lidt tid før du ser dit billed - opdateret. - - ) : ( -
- - - } - name="file" - onChange={handleFileChange} - label="Vælge billed" - placeholder="Dit billed" - leftSectionPointerEvents="none" - /> - - - - - )} -
+ {imageUploaded ? ( + } + > + Vi har modtaget dit billed, der går lidt tid før du ser dit billed + opdateret. + + ) : ( +
+ + + } + name="file" + onChange={handleFileChange} + label="Vælge billed" + placeholder="Dit billed" + leftSectionPointerEvents="none" + /> + + + + + )} ); } diff --git a/app/routes/account.schedules.$scheduleHandle.destroy.tsx b/app/routes/business.schedules.$scheduleHandle.destroy.tsx similarity index 100% rename from app/routes/account.schedules.$scheduleHandle.destroy.tsx rename to app/routes/business.schedules.$scheduleHandle.destroy.tsx diff --git a/app/routes/account.schedules.$scheduleHandle.edit.tsx b/app/routes/business.schedules.$scheduleHandle.edit.tsx similarity index 100% rename from app/routes/account.schedules.$scheduleHandle.edit.tsx rename to app/routes/business.schedules.$scheduleHandle.edit.tsx diff --git a/app/routes/account.schedules.$scheduleHandle.tsx b/app/routes/business.schedules.$scheduleHandle.tsx similarity index 100% rename from app/routes/account.schedules.$scheduleHandle.tsx rename to app/routes/business.schedules.$scheduleHandle.tsx diff --git a/app/routes/account.schedules.create.tsx b/app/routes/business.schedules.create.tsx similarity index 100% rename from app/routes/account.schedules.create.tsx rename to app/routes/business.schedules.create.tsx diff --git a/app/routes/account.schedules.tsx b/app/routes/business.schedules.tsx similarity index 93% rename from app/routes/account.schedules.tsx rename to app/routes/business.schedules.tsx index 20216c31..f71e8156 100644 --- a/app/routes/account.schedules.tsx +++ b/app/routes/business.schedules.tsx @@ -1,5 +1,6 @@ import { Button, + Container, Divider, Flex, rem, @@ -23,7 +24,7 @@ import {AccountContent} from '~/components/account/AccountContent'; import {AccountTitle} from '~/components/account/AccountTitle'; import {getBookingShopifyApi} from '~/lib/api/bookingShopifyApi'; import {getCustomer} from '~/lib/get-customer'; -import {AccountSchedulesCreate} from './account.schedules.create'; +import {AccountSchedulesCreate} from './business.schedules.create'; export async function loader({context, request}: LoaderFunctionArgs) { const customerId = await getCustomer({context}); @@ -76,8 +77,8 @@ export default function AccountSchedulesIndex() { ) : null; return ( - <> - + + - + ); } diff --git a/app/routes/account.services.$productId._index.tsx b/app/routes/business.services.$productId._index.tsx similarity index 100% rename from app/routes/account.services.$productId._index.tsx rename to app/routes/business.services.$productId._index.tsx diff --git a/app/routes/account.services.$productId.configuration.tsx b/app/routes/business.services.$productId.configuration.tsx similarity index 100% rename from app/routes/account.services.$productId.configuration.tsx rename to app/routes/business.services.$productId.configuration.tsx diff --git a/app/routes/account.services.$productId.destroy.tsx b/app/routes/business.services.$productId.destroy.tsx similarity index 100% rename from app/routes/account.services.$productId.destroy.tsx rename to app/routes/business.services.$productId.destroy.tsx diff --git a/app/routes/account.services.$productId.options.$optionProductId.update.tsx b/app/routes/business.services.$productId.options.$optionProductId.update.tsx similarity index 100% rename from app/routes/account.services.$productId.options.$optionProductId.update.tsx rename to app/routes/business.services.$productId.options.$optionProductId.update.tsx diff --git a/app/routes/account.services.$productId.options.add.tsx b/app/routes/business.services.$productId.options.add.tsx similarity index 100% rename from app/routes/account.services.$productId.options.add.tsx rename to app/routes/business.services.$productId.options.add.tsx diff --git a/app/routes/account.services.$productId.options.destroy.tsx b/app/routes/business.services.$productId.options.destroy.tsx similarity index 100% rename from app/routes/account.services.$productId.options.destroy.tsx rename to app/routes/business.services.$productId.options.destroy.tsx diff --git a/app/routes/account.services.$productId.options.tsx b/app/routes/business.services.$productId.options.tsx similarity index 99% rename from app/routes/account.services.$productId.options.tsx rename to app/routes/business.services.$productId.options.tsx index a6eeff7d..8fbd006a 100644 --- a/app/routes/account.services.$productId.options.tsx +++ b/app/routes/business.services.$productId.options.tsx @@ -12,7 +12,7 @@ import { type SerializeFrom, } from '@shopify/remix-oxygen'; import {getCustomer} from '~/lib/get-customer'; -import {type loader as rootLoader} from './account.services.$productId'; +import {type loader as rootLoader} from './business.services.$productId'; import { Accordion, diff --git a/app/routes/account.services.$productId.tsx b/app/routes/business.services.$productId.tsx similarity index 93% rename from app/routes/account.services.$productId.tsx rename to app/routes/business.services.$productId.tsx index bde48242..2560a802 100644 --- a/app/routes/account.services.$productId.tsx +++ b/app/routes/business.services.$productId.tsx @@ -1,4 +1,4 @@ -import {Button} from '@mantine/core'; +import {Button, Container, rem} from '@mantine/core'; import {Form, Outlet, useLoaderData} from '@remix-run/react'; import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {AccountButton} from '~/components/account/AccountButton'; @@ -28,7 +28,7 @@ export default function EditAddress() { const {product} = useLoaderData(); return ( - <> + - + ); } diff --git a/app/routes/account.services._index.tsx b/app/routes/business.services._index.tsx similarity index 95% rename from app/routes/account.services._index.tsx rename to app/routes/business.services._index.tsx index bd38c3f3..8c5f5d59 100644 --- a/app/routes/account.services._index.tsx +++ b/app/routes/business.services._index.tsx @@ -2,6 +2,7 @@ import { ActionIcon, Button, Card, + Container, Divider, Flex, Grid, @@ -37,8 +38,8 @@ export default function AccountServicesIndex() { const {user} = useOutletContext(); return ( - <> - + + } @@ -76,10 +77,7 @@ export default function AccountServicesIndex() { {products.map((product) => { return ( - + )} - + ); } diff --git a/app/routes/account.services.create.tsx b/app/routes/business.services.create.tsx similarity index 98% rename from app/routes/account.services.create.tsx rename to app/routes/business.services.create.tsx index 5ac29839..8130dfaa 100644 --- a/app/routes/account.services.create.tsx +++ b/app/routes/business.services.create.tsx @@ -8,8 +8,10 @@ import { import {parseWithZod} from '@conform-to/zod'; import { + Container, Divider, Flex, + rem, Select, Stack, Switch, @@ -178,8 +180,8 @@ export default function AccountServicesCreate() { }; return ( - <> - + +
@@ -341,7 +343,7 @@ export default function AccountServicesCreate() {
- +
); } diff --git a/app/routes/account.services.tsx b/app/routes/business.services.tsx similarity index 88% rename from app/routes/account.services.tsx rename to app/routes/business.services.tsx index 82987e47..bb06a1bf 100644 --- a/app/routes/account.services.tsx +++ b/app/routes/business.services.tsx @@ -1,4 +1,4 @@ -import {Button, Flex, rem, ThemeIcon, Title} from '@mantine/core'; +import {Button, Container, Flex, rem, ThemeIcon, Title} from '@mantine/core'; import {Link, Outlet, useLoaderData, useOutletContext} from '@remix-run/react'; import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; @@ -29,8 +29,8 @@ export default function AccountServices() { if (!status.locations) { return ( - <> - + + @@ -52,13 +52,13 @@ export default function AccountServices() { - + ); } if (!status.schedules) { return ( - <> + @@ -77,7 +77,7 @@ export default function AccountServices() { - + ); } diff --git a/app/routes/business.tsx b/app/routes/business.tsx new file mode 100644 index 00000000..232df024 --- /dev/null +++ b/app/routes/business.tsx @@ -0,0 +1,106 @@ +import tiptapStyles from '@mantine/tiptap/styles.css?url'; +import { + Outlet, + useLoaderData, + type ShouldRevalidateFunction, +} from '@remix-run/react'; +import {parseGid} from '@shopify/hydrogen'; +import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type CustomerFragment} from 'customer-accountapi.generated'; +import {useEffect} from 'react'; +import notify, {Toaster} from 'react-hot-toast'; +import {getToast} from 'remix-toast'; +import {CUSTOMER_DETAILS_QUERY} from '~/graphql/customer-account/CustomerDetailsQuery'; +import {getBookingShopifyApi} from '~/lib/api/bookingShopifyApi'; +import {type User} from '~/lib/api/model'; + +export function links() { + return [{rel: 'stylesheet', href: tiptapStyles}]; +} +export type AccountOutlet = { + customer: CustomerFragment; + user?: User | null; + isBusiness: boolean; +}; + +export const shouldRevalidate: ShouldRevalidateFunction = ({ + formMethod, + currentUrl, + nextUrl, +}) => { + if (formMethod && formMethod !== 'GET') { + return true; + } + + // revalidate when manually revalidating via useRevalidator + if (currentUrl.toString() === nextUrl.toString()) { + return true; + } + + return false; +}; + +export async function loader({context, request}: LoaderFunctionArgs) { + const {data, errors} = await context.customerAccount.query( + CUSTOMER_DETAILS_QUERY, + ); + + if (errors?.length || !data?.customer) { + throw new Error('Customer not found'); + } + + if (!data.customer.tags.includes('business')) { + return redirect('/account'); + } + + const {payload} = await getBookingShopifyApi().customerGet( + parseGid(data.customer.id).id, + ); + + const {toast} = await getToast(request); + + return json( + { + customer: data.customer, + user: payload, + toast, + }, + { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + 'Set-Cookie': await context.session.commit(), + }, + }, + ); +} + +export default function Acccount() { + const {customer, user, toast} = useLoaderData(); + + useEffect(() => { + if (toast) { + switch (toast.type) { + case 'success': + notify.success(toast.message); + return; + case 'error': + notify.error(toast.message); + + return; + default: + return; + } + } + }, [toast]); + + return ( + <> + + + + ); +} diff --git a/app/routes/users.tsx b/app/routes/users.tsx index 791bbc7c..b62b2dae 100644 --- a/app/routes/users.tsx +++ b/app/routes/users.tsx @@ -56,7 +56,7 @@ import { } from '~/components/filters/LocationFilter'; import {ProfessionButton} from '~/components/ProfessionButton'; import {getTags} from '~/lib/tags'; -import {COLLECTION} from './account.services.create'; +import {COLLECTION} from './business.services.create'; import {PAGE_QUERY} from './pages.$handle'; export const handle: Handle = { diff --git a/customer-accountapi.generated.d.ts b/customer-accountapi.generated.d.ts index 433a1636..d078a0bb 100644 --- a/customer-accountapi.generated.d.ts +++ b/customer-accountapi.generated.d.ts @@ -68,8 +68,14 @@ export type CustomerAddressCreateMutation = { export type CustomerFragment = Pick< CustomerAccountAPI.Customer, - 'id' | 'firstName' | 'lastName' + 'id' | 'firstName' | 'lastName' | 'tags' > & { + emailAddress?: CustomerAccountAPI.Maybe< + Pick< + CustomerAccountAPI.CustomerEmailAddress, + 'emailAddress' | 'marketingState' + > + >; defaultAddress?: CustomerAccountAPI.Maybe< Pick< CustomerAccountAPI.CustomerAddress, @@ -131,8 +137,14 @@ export type CustomerDetailsQueryVariables = CustomerAccountAPI.Exact<{ export type CustomerDetailsQuery = { customer: Pick< CustomerAccountAPI.Customer, - 'id' | 'firstName' | 'lastName' + 'id' | 'firstName' | 'lastName' | 'tags' > & { + emailAddress?: CustomerAccountAPI.Maybe< + Pick< + CustomerAccountAPI.CustomerEmailAddress, + 'emailAddress' | 'marketingState' + > + >; defaultAddress?: CustomerAccountAPI.Maybe< Pick< CustomerAccountAPI.CustomerAddress, @@ -477,7 +489,7 @@ export type CustomerUpdateMutation = { }; interface GeneratedQueryTypes { - '#graphql\n query CustomerDetails {\n customer {\n ...Customer\n }\n }\n #graphql\n fragment Customer on Customer {\n id\n firstName\n lastName\n defaultAddress {\n ...Address\n }\n addresses(first: 6) {\n nodes {\n ...Address\n }\n }\n }\n fragment Address on CustomerAddress {\n id\n formatted\n firstName\n lastName\n company\n address1\n address2\n territoryCode\n zoneCode\n city\n zip\n phoneNumber\n }\n\n': { + '#graphql\n query CustomerDetails {\n customer {\n ...Customer\n }\n }\n #graphql\n fragment Customer on Customer {\n id\n firstName\n lastName\n emailAddress {\n emailAddress\n marketingState\n }\n tags\n defaultAddress {\n ...Address\n }\n addresses(first: 6) {\n nodes {\n ...Address\n }\n }\n }\n fragment Address on CustomerAddress {\n id\n formatted\n firstName\n lastName\n company\n address1\n address2\n territoryCode\n zoneCode\n city\n zip\n phoneNumber\n }\n\n': { return: CustomerDetailsQuery; variables: CustomerDetailsQueryVariables; }; diff --git a/public/locales/ar/account.json b/public/locales/ar/account.json new file mode 100644 index 00000000..44198e05 --- /dev/null +++ b/public/locales/ar/account.json @@ -0,0 +1,70 @@ +{ + "business_title": "شريك", + "business_text": "اذهب إلى العمل", + "business_action": "اذهب إلى العمل", + "orders_title": "الحجوزات", + "orders_text": "إدارة حجوزاتك السابقة والقادمة.", + "profile_title": "معلومات شخصية", + "profile_text": "قم بتغيير اسمك الأول أو الأخير.", + "address_title": "العناوين", + "address_text": "إدارة عناوينك", + "partner_title": "شريك", + "partner_text": "ابدأ مسيرتك في مجال الجمال", + "partner_action": "ابدأ الآن", + "profile": { + "title": "معلومات شخصية", + "error": "خطأ", + "first_name": "الاسم الأول", + "last_name": "الاسم الأخير", + "save": "حفظ التغييرات" + }, + "orders": { + "title": "الطلبات", + "date": "التاريخ", + "payment": "الدفع", + "status": "الحالة", + "total": "المبلغ", + "empty": "لم تقم بوضع أي طلبات حتى الآن", + "goto_frontpage": "اذهب إلى الصفحة الرئيسية", + "id": { + "title": "طلب {{name}}", + "bought": "تم الشراء", + "products": "المنتجات", + "image": "صورة", + "description": "الوصف", + "total": "المجموع", + "summary": "الملخص", + "discount": "الخصم", + "subtotal": "الإجمالي الفرعي", + "tax": "الضريبة", + "shipping": "الشحن", + "no_shipping": "لا يوجد شحن", + "treatments": "العلاجات", + "details": "التفاصيل", + "visiting": "زيارة <0>{{name}}", + "visiting_deleted": "تم حذف ملف الجمال", + "time": "الوقت", + "location": "الموقع", + "location_expenses": "سيتم حساب النفقات أثناء عملية الشراء." + } + }, + "address": { + "title": "العناوين", + "creating": "إنشاء...", + "create": "إنشاء عنوان", + "edit": "تعديل العنوان", + "saving": "حفظ...", + "save": "حفظ", + "deleting": "حذف...", + "delete": "حذف", + "form": { + "first_name": "الاسم الأول", + "last_name": "الاسم الأخير", + "address": "العنوان", + "zip": "الرمز البريدي", + "city": "المدينة", + "phone": "رقم الهاتف", + "default_address": "تعيين كعنوان افتراضي" + } + } +} diff --git a/public/locales/ar/global.json b/public/locales/ar/global.json index 05a17fb8..f3b70f7c 100644 --- a/public/locales/ar/global.json +++ b/public/locales/ar/global.json @@ -5,6 +5,7 @@ "english": "إنجليزي", "arabic": "عربي", "login": "تسجيل الدخول", + "logout": "تسجيل الخروج", "monday": "الإثنين", "tuesday": "الثلاثاء", "wednesday": "الأربعاء", diff --git a/public/locales/da/account.json b/public/locales/da/account.json new file mode 100644 index 00000000..8bbe42a8 --- /dev/null +++ b/public/locales/da/account.json @@ -0,0 +1,70 @@ +{ + "business_title": "Partner", + "business_text": "Gå til business", + "business_action": "Gå til business", + "orders_title": "Bookinger", + "orders_text": "Administrer dine tidligere og kommende bookinger.", + "profile_title": "Personlig oplysninger", + "profile_text": "Ændre din fornavn eller efternavn.", + "address_title": "Adresser", + "address_text": "Administrer dine adresser", + "partner_title": "Partner", + "partner_text": "Start din skønhedskarrier", + "partner_action": "Kom igang", + "profile": { + "title": "Personlige oplysninger", + "error": "Fejl", + "first_name": "Fornavn", + "last_name": "Efternavn", + "save": "Gem ændringer" + }, + "orders": { + "title": "Ordre", + "date": "Dato", + "payment": "Betaling", + "status": "Status", + "total": "Beløb", + "empty": "Du har ikke afgivet nogen ordre endnu", + "goto_frontpage": "Gå til forside", + "id": { + "title": "Ordre {{name}}", + "bought": "Købt", + "products": "Produkter", + "image": "Billed", + "description": "Beskrivelse", + "total": "Total", + "summary": "Summary", + "discount": "Rabat", + "subtotal": "Subtotal", + "tax": "Moms", + "shipping": "Forsendelse", + "no_shipping": "Ingen forsendelse", + "treatments": "Behandlinger", + "details": "Detaljer", + "visiting": "Hos <0>{{name}}", + "visiting_deleted": "Skønhedsprofil er slettet", + "time": "Tid", + "location": "Location", + "location_expenses": "Udgifterne bliver beregnet under købsprocessen." + } + }, + "address": { + "title": "Adresser", + "creating": "Opretter...", + "create": "Opret adresse", + "edit": "Ændre adresse", + "saving": "Gemmer...", + "save": "Gem", + "deleting": "Sletter...", + "delete": "Slet", + "form": { + "first_name": "Fornavn", + "last_name": "Efternavn", + "address": "Adresse", + "zip": "Postnummer", + "city": "By", + "phone": "Telefonnummer", + "default_address": "Indstil som standardadresse" + } + } +} diff --git a/public/locales/da/global.json b/public/locales/da/global.json index 50b73e63..435d8ab5 100644 --- a/public/locales/da/global.json +++ b/public/locales/da/global.json @@ -5,6 +5,7 @@ "english": "Engelsk", "arabic": "Arabisk", "login": "Log ind", + "logout": "Log ud", "monday": "Mandag", "tuesday": "Tirsdag", "wednesday": "Onsdag", diff --git a/public/locales/en/account.json b/public/locales/en/account.json new file mode 100644 index 00000000..e9e0dd1d --- /dev/null +++ b/public/locales/en/account.json @@ -0,0 +1,70 @@ +{ + "business_title": "Partner", + "business_text": "Go to business", + "business_action": "Go to business", + "orders_title": "Bookings", + "orders_text": "Manage your past and upcoming bookings.", + "profile_title": "Personal Information", + "profile_text": "Change your first or last name.", + "address_title": "Addresses", + "address_text": "Manage your addresses", + "partner_title": "Partner", + "partner_text": "Start your beauty career", + "partner_action": "Get started", + "profile": { + "title": "Personal Information", + "error": "Error", + "first_name": "First Name", + "last_name": "Last Name", + "save": "Save Changes" + }, + "orders": { + "title": "Orders", + "date": "Date", + "payment": "Payment", + "status": "Status", + "total": "Amount", + "empty": "You have not placed any orders yet", + "goto_frontpage": "Go to Frontpage", + "id": { + "title": "Order {{name}}", + "bought": "Bought", + "products": "Products", + "image": "Image", + "description": "Description", + "total": "Total", + "summary": "Summary", + "discount": "Discount", + "subtotal": "Subtotal", + "tax": "Tax", + "shipping": "Shipping", + "no_shipping": "No shipping", + "treatments": "Treatments", + "details": "Details", + "visiting": "Visiting <0>{{name}}", + "visiting_deleted": "Beauty profile deleted", + "time": "Time", + "location": "Location", + "location_expenses": "Expenses will be calculated during the purchase process." + } + }, + "address": { + "title": "Addresses", + "creating": "Creating...", + "create": "Create Address", + "edit": "Edit Address", + "saving": "Saving...", + "save": "Save", + "deleting": "Deleting...", + "delete": "Delete", + "form": { + "first_name": "First Name", + "last_name": "Last Name", + "address": "Address", + "zip": "Zip Code", + "city": "City", + "phone": "Phone Number", + "default_address": "Set as default address" + } + } +} diff --git a/public/locales/en/global.json b/public/locales/en/global.json index bf29c539..02709028 100644 --- a/public/locales/en/global.json +++ b/public/locales/en/global.json @@ -5,6 +5,7 @@ "english": "English", "arabic": "Arabic", "login": "Login", + "logout": "Log out", "monday": "Monday", "tuesday": "Tuesday", "wednesday": "Wednesday", diff --git a/storefrontapi.generated.d.ts b/storefrontapi.generated.d.ts index 7c78ff91..213a8c65 100644 --- a/storefrontapi.generated.d.ts +++ b/storefrontapi.generated.d.ts @@ -2808,148 +2808,6 @@ export type RecommendedTreatmentsQuery = { }; }; -export type ProductSearchSimpleFragment = Pick< - StorefrontAPI.Product, - 'id' | 'title' | 'handle' ->; - -export type ProductSearchQueryQueryVariables = StorefrontAPI.Exact<{ - collectionId: StorefrontAPI.Scalars['ID']['input']; - country?: StorefrontAPI.InputMaybe; - language?: StorefrontAPI.InputMaybe; - first?: StorefrontAPI.InputMaybe; -}>; - -export type ProductSearchQueryQuery = { - collection?: StorefrontAPI.Maybe<{ - products: { - nodes: Array>; - }; - }>; -}; - -export type ProductVariantIdsQueryVariables = StorefrontAPI.Exact<{ - country?: StorefrontAPI.InputMaybe; - language?: StorefrontAPI.InputMaybe; - variantId: - | Array - | StorefrontAPI.Scalars['ID']['input']; -}>; - -export type ProductVariantIdsQuery = { - nodes: Array< - StorefrontAPI.Maybe< - Pick< - StorefrontAPI.ProductVariant, - 'availableForSale' | 'id' | 'sku' | 'title' - > & { - compareAtPrice?: StorefrontAPI.Maybe< - Pick - >; - image?: StorefrontAPI.Maybe< - {__typename: 'Image'} & Pick< - StorefrontAPI.Image, - 'id' | 'url' | 'altText' | 'width' | 'height' - > - >; - price: Pick; - product: Pick; - selectedOptions: Array< - Pick - >; - unitPrice?: StorefrontAPI.Maybe< - Pick - >; - } - > - >; -}; - -export type ServicesOptionsTagProductFragment = Pick< - StorefrontAPI.Product, - 'id' | 'handle' | 'title' -> & { - options: Array>; - variants: { - nodes: Array< - Pick & { - price: Pick; - } - >; - }; -}; - -export type ServicesOptionsTagOptionsQueryQueryVariables = StorefrontAPI.Exact<{ - country?: StorefrontAPI.InputMaybe; - language?: StorefrontAPI.InputMaybe; - query: StorefrontAPI.Scalars['String']['input']; - first?: StorefrontAPI.InputMaybe; -}>; - -export type ServicesOptionsTagOptionsQueryQuery = { - products: { - nodes: Array< - Pick & { - options: Array>; - variants: { - nodes: Array< - Pick & { - price: Pick; - } - >; - }; - } - >; - }; -}; - -export type CategoryStorefrontFragment = Pick< - StorefrontAPI.Collection, - 'id' | 'title' -> & { - children?: StorefrontAPI.Maybe<{ - references?: StorefrontAPI.Maybe<{ - nodes: Array< - Pick & { - products: { - nodes: Array< - Pick - >; - }; - } - >; - }>; - }>; -}; - -export type CategoriesStorefrontQueryVariables = StorefrontAPI.Exact<{ - country?: StorefrontAPI.InputMaybe; - language?: StorefrontAPI.InputMaybe; -}>; - -export type CategoriesStorefrontQuery = { - collection?: StorefrontAPI.Maybe< - Pick & { - children?: StorefrontAPI.Maybe<{ - references?: StorefrontAPI.Maybe<{ - nodes: Array< - Pick & { - products: { - nodes: Array< - Pick< - StorefrontAPI.Product, - 'id' | 'title' | 'descriptionHtml' - > - >; - }; - } - >; - }>; - }>; - } - >; -}; - export type PredictiveArticleFragment = {__typename: 'Article'} & Pick< StorefrontAPI.Article, 'id' | 'title' | 'handle' | 'trackingParameters' @@ -3672,6 +3530,148 @@ export type PickMoreProductsQuery = { }; }; +export type ProductSearchSimpleFragment = Pick< + StorefrontAPI.Product, + 'id' | 'title' | 'handle' +>; + +export type ProductSearchQueryQueryVariables = StorefrontAPI.Exact<{ + collectionId: StorefrontAPI.Scalars['ID']['input']; + country?: StorefrontAPI.InputMaybe; + language?: StorefrontAPI.InputMaybe; + first?: StorefrontAPI.InputMaybe; +}>; + +export type ProductSearchQueryQuery = { + collection?: StorefrontAPI.Maybe<{ + products: { + nodes: Array>; + }; + }>; +}; + +export type ProductVariantIdsQueryVariables = StorefrontAPI.Exact<{ + country?: StorefrontAPI.InputMaybe; + language?: StorefrontAPI.InputMaybe; + variantId: + | Array + | StorefrontAPI.Scalars['ID']['input']; +}>; + +export type ProductVariantIdsQuery = { + nodes: Array< + StorefrontAPI.Maybe< + Pick< + StorefrontAPI.ProductVariant, + 'availableForSale' | 'id' | 'sku' | 'title' + > & { + compareAtPrice?: StorefrontAPI.Maybe< + Pick + >; + image?: StorefrontAPI.Maybe< + {__typename: 'Image'} & Pick< + StorefrontAPI.Image, + 'id' | 'url' | 'altText' | 'width' | 'height' + > + >; + price: Pick; + product: Pick; + selectedOptions: Array< + Pick + >; + unitPrice?: StorefrontAPI.Maybe< + Pick + >; + } + > + >; +}; + +export type ServicesOptionsTagProductFragment = Pick< + StorefrontAPI.Product, + 'id' | 'handle' | 'title' +> & { + options: Array>; + variants: { + nodes: Array< + Pick & { + price: Pick; + } + >; + }; +}; + +export type ServicesOptionsTagOptionsQueryQueryVariables = StorefrontAPI.Exact<{ + country?: StorefrontAPI.InputMaybe; + language?: StorefrontAPI.InputMaybe; + query: StorefrontAPI.Scalars['String']['input']; + first?: StorefrontAPI.InputMaybe; +}>; + +export type ServicesOptionsTagOptionsQueryQuery = { + products: { + nodes: Array< + Pick & { + options: Array>; + variants: { + nodes: Array< + Pick & { + price: Pick; + } + >; + }; + } + >; + }; +}; + +export type CategoryStorefrontFragment = Pick< + StorefrontAPI.Collection, + 'id' | 'title' +> & { + children?: StorefrontAPI.Maybe<{ + references?: StorefrontAPI.Maybe<{ + nodes: Array< + Pick & { + products: { + nodes: Array< + Pick + >; + }; + } + >; + }>; + }>; +}; + +export type CategoriesStorefrontQueryVariables = StorefrontAPI.Exact<{ + country?: StorefrontAPI.InputMaybe; + language?: StorefrontAPI.InputMaybe; +}>; + +export type CategoriesStorefrontQuery = { + collection?: StorefrontAPI.Maybe< + Pick & { + children?: StorefrontAPI.Maybe<{ + references?: StorefrontAPI.Maybe<{ + nodes: Array< + Pick & { + products: { + nodes: Array< + Pick< + StorefrontAPI.Product, + 'id' | 'title' | 'descriptionHtml' + > + >; + }; + } + >; + }>; + }>; + } + >; +}; + export type CategoriesCollectionProductUserFragment = Pick< StorefrontAPI.Metaobject, 'id' @@ -5990,22 +5990,6 @@ interface GeneratedQueryTypes { return: RecommendedTreatmentsQuery; variables: RecommendedTreatmentsQueryVariables; }; - '#graphql\n #graphql\n fragment ProductSearchSimple on Product {\n id\n title\n handle\n }\n\n query ProductSearchQuery(\n $collectionId: ID!\n $country: CountryCode\n $language: LanguageCode\n $first: Int\n ) @inContext(country: $country, language: $language) {\n collection(id: $collectionId) {\n products(first: $first) {\n nodes {\n ...ProductSearchSimple\n }\n }\n }\n }\n': { - return: ProductSearchQueryQuery; - variables: ProductSearchQueryQueryVariables; - }; - '#graphql\n query ProductVariantIds(\n $country: CountryCode\n $language: LanguageCode\n $variantId: [ID!]!\n ) @inContext(country: $country, language: $language) {\n nodes(ids: $variantId){\n ...on ProductVariant{\n ...ProductVariant\n }\n }\n }\n #graphql\n fragment ProductVariant on ProductVariant {\n availableForSale\n compareAtPrice {\n amount\n currencyCode\n }\n id\n image {\n __typename\n id\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n product {\n title\n handle\n }\n selectedOptions {\n name\n value\n }\n sku\n title\n unitPrice {\n amount\n currencyCode\n }\n }\n\n': { - return: ProductVariantIdsQuery; - variables: ProductVariantIdsQueryVariables; - }; - '#graphql\n #graphql\n fragment MoneyProductItem on MoneyV2 {\n amount\n currencyCode\n }\n\n fragment ServicesOptionsTagProduct on Product {\n id\n handle\n title\n options {\n name\n values\n }\n variants(first: 5) {\n nodes {\n id\n title\n price {\n ...MoneyProductItem\n }\n }\n }\n }\n\n query ServicesOptionsTagOptionsQuery(\n $country: CountryCode\n $language: LanguageCode\n $query: String!\n $first: Int\n ) @inContext(country: $country, language: $language) {\n products(first: $first, query: $query) {\n nodes {\n ...ServicesOptionsTagProduct\n }\n }\n }\n': { - return: ServicesOptionsTagOptionsQueryQuery; - variables: ServicesOptionsTagOptionsQueryQueryVariables; - }; - '#graphql\n #graphql\n fragment CategoryStorefront on Collection {\n id\n title\n children: metafield(key: "children", namespace: "booking") {\n references(first: 20) {\n nodes {\n ... on Collection {\n id\n title\n products(first: 30) {\n nodes {\n id\n title\n descriptionHtml\n }\n }\n }\n }\n }\n }\n }\n\n query CategoriesStorefront(\n $country: CountryCode\n $language: LanguageCode\n ) @inContext(country: $country, language: $language) {\n collection(handle: "alle-behandlinger") {\n ...CategoryStorefront\n }\n }\n': { - return: CategoriesStorefrontQuery; - variables: CategoriesStorefrontQueryVariables; - }; '#graphql\n fragment PredictiveArticle on Article {\n __typename\n id\n title\n handle\n image {\n url\n altText\n width\n height\n }\n trackingParameters\n }\n fragment PredictiveCollection on Collection {\n __typename\n id\n title\n handle\n image {\n url\n altText\n width\n height\n }\n trackingParameters\n }\n fragment PredictivePage on Page {\n __typename\n id\n title\n handle\n trackingParameters\n }\n fragment PredictiveProduct on Product {\n __typename\n id\n title\n handle\n trackingParameters\n variants(first: 1) {\n nodes {\n id\n image {\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n }\n }\n }\n fragment PredictiveQuery on SearchQuerySuggestion {\n __typename\n text\n styledText\n trackingParameters\n }\n query predictiveSearch(\n $country: CountryCode\n $language: LanguageCode\n $limit: Int!\n $limitScope: PredictiveSearchLimitScope!\n $searchTerm: String!\n $types: [PredictiveSearchType!]\n ) @inContext(country: $country, language: $language) {\n predictiveSearch(\n limit: $limit,\n limitScope: $limitScope,\n query: $searchTerm,\n types: $types,\n ) {\n articles {\n ...PredictiveArticle\n }\n collections {\n ...PredictiveCollection\n }\n pages {\n ...PredictivePage\n }\n products {\n ...PredictiveProduct\n }\n queries {\n ...PredictiveQuery\n }\n }\n }\n': { return: PredictiveSearchQuery; variables: PredictiveSearchQueryVariables; @@ -6030,6 +6014,22 @@ interface GeneratedQueryTypes { return: PickMoreProductsQuery; variables: PickMoreProductsQueryVariables; }; + '#graphql\n #graphql\n fragment ProductSearchSimple on Product {\n id\n title\n handle\n }\n\n query ProductSearchQuery(\n $collectionId: ID!\n $country: CountryCode\n $language: LanguageCode\n $first: Int\n ) @inContext(country: $country, language: $language) {\n collection(id: $collectionId) {\n products(first: $first) {\n nodes {\n ...ProductSearchSimple\n }\n }\n }\n }\n': { + return: ProductSearchQueryQuery; + variables: ProductSearchQueryQueryVariables; + }; + '#graphql\n query ProductVariantIds(\n $country: CountryCode\n $language: LanguageCode\n $variantId: [ID!]!\n ) @inContext(country: $country, language: $language) {\n nodes(ids: $variantId){\n ...on ProductVariant{\n ...ProductVariant\n }\n }\n }\n #graphql\n fragment ProductVariant on ProductVariant {\n availableForSale\n compareAtPrice {\n amount\n currencyCode\n }\n id\n image {\n __typename\n id\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n product {\n title\n handle\n }\n selectedOptions {\n name\n value\n }\n sku\n title\n unitPrice {\n amount\n currencyCode\n }\n }\n\n': { + return: ProductVariantIdsQuery; + variables: ProductVariantIdsQueryVariables; + }; + '#graphql\n #graphql\n fragment MoneyProductItem on MoneyV2 {\n amount\n currencyCode\n }\n\n fragment ServicesOptionsTagProduct on Product {\n id\n handle\n title\n options {\n name\n values\n }\n variants(first: 5) {\n nodes {\n id\n title\n price {\n ...MoneyProductItem\n }\n }\n }\n }\n\n query ServicesOptionsTagOptionsQuery(\n $country: CountryCode\n $language: LanguageCode\n $query: String!\n $first: Int\n ) @inContext(country: $country, language: $language) {\n products(first: $first, query: $query) {\n nodes {\n ...ServicesOptionsTagProduct\n }\n }\n }\n': { + return: ServicesOptionsTagOptionsQueryQuery; + variables: ServicesOptionsTagOptionsQueryQueryVariables; + }; + '#graphql\n #graphql\n fragment CategoryStorefront on Collection {\n id\n title\n children: metafield(key: "children", namespace: "booking") {\n references(first: 20) {\n nodes {\n ... on Collection {\n id\n title\n products(first: 30) {\n nodes {\n id\n title\n descriptionHtml\n }\n }\n }\n }\n }\n }\n }\n\n query CategoriesStorefront(\n $country: CountryCode\n $language: LanguageCode\n ) @inContext(country: $country, language: $language) {\n collection(handle: "alle-behandlinger") {\n ...CategoryStorefront\n }\n }\n': { + return: CategoriesStorefrontQuery; + variables: CategoriesStorefrontQueryVariables; + }; '#graphql\n #graphql\n #graphql\n #graphql\n fragment CategoriesCollectionProductUser on Metaobject {\n id\n image: field(key: "image") {\n reference {\n ... on MediaImage {\n image {\n width\n height\n url(transform: { maxHeight: 100, maxWidth: 100, crop: CENTER })\n }\n }\n }\n }\n }\n\n\n fragment CategoriesCollectionProduct on Product {\n id\n user: metafield(key: "user", namespace: "booking") {\n reference {\n ...CategoriesCollectionProductUser\n }\n }\n }\n\n #graphql\n fragment CategoriesCollectionFilter on Filter {\n id\n label\n values {\n count\n }\n }\n\n\n fragment CategoriesCollection on Product {\n id\n title\n descriptionHtml\n description\n productType\n handle\n vendor\n featuredImage {\n id\n altText\n url(transform: { maxHeight: 250, maxWidth: 250, crop: CENTER })\n width\n height\n }\n collection: metafield(key: "collection", namespace: "system") {\n reference {\n ... on Collection {\n products(first: 3, sortKey: RELEVANCE, filters: [{productMetafield: {namespace: "system", key: "default", value: "true"}}, {productMetafield: {namespace: "booking", key: "hide_from_profile", value: "false"}}, {productMetafield: {namespace: "system", key: "active",value: "true"}}]) {\n filters {\n ...CategoriesCollectionFilter\n }\n nodes {\n ...CategoriesCollectionProduct\n }\n }\n }\n }\n }\n }\n\n query categoriesCollection(\n $handle: String!\n $country: CountryCode\n $language: LanguageCode\n $first: Int\n $last: Int\n $startCursor: String\n $endCursor: String\n ) @inContext(country: $country, language: $language) {\n collection(handle: $handle) {\n id\n handle\n title\n description\n products(\n first: $first,\n last: $last,\n before: $startCursor,\n after: $endCursor,\n sortKey: TITLE\n ) {\n nodes {\n ...CategoriesCollection\n }\n pageInfo {\n hasPreviousPage\n hasNextPage\n endCursor\n startCursor\n }\n }\n }\n }\n': { return: CategoriesCollectionQuery; variables: CategoriesCollectionQueryVariables;