diff --git a/app/dashboard/(admin)/[listKey]/[id]/page.js b/app/dashboard/(admin)/[listKey]/[id]/page.js index 5ef129e..b570794 100644 --- a/app/dashboard/(admin)/[listKey]/[id]/page.js +++ b/app/dashboard/(admin)/[listKey]/[id]/page.js @@ -1,5 +1,5 @@ "use client"; -import { ItemPage } from "@keystone/screens/ItemPage"; +import { ItemPage } from "@keystone/screens"; export default ItemPage; diff --git a/app/dashboard/(admin)/[listKey]/create/page.js b/app/dashboard/(admin)/[listKey]/create/page.js index c6a16d3..676c581 100644 --- a/app/dashboard/(admin)/[listKey]/create/page.js +++ b/app/dashboard/(admin)/[listKey]/create/page.js @@ -1,5 +1,5 @@ "use client"; -import { CreateItemPage } from "@keystone/screens/CreateItemPage"; +import { CreateItemPage } from "@keystone/screens"; export default CreateItemPage; diff --git a/app/dashboard/(admin)/[listKey]/page.js b/app/dashboard/(admin)/[listKey]/page.js index 3d6add0..bd94918 100644 --- a/app/dashboard/(admin)/[listKey]/page.js +++ b/app/dashboard/(admin)/[listKey]/page.js @@ -1,5 +1,5 @@ "use client"; -import { ListPage } from "@keystone/screens/ListPage"; +import { ListPage } from "@keystone/screens"; export default ListPage; diff --git a/app/dashboard/(admin)/layout.js b/app/dashboard/(admin)/layout.js index f60e87a..c9d91d4 100644 --- a/app/dashboard/(admin)/layout.js +++ b/app/dashboard/(admin)/layout.js @@ -1,6 +1,6 @@ "use client"; -import { AdminLayout } from "@keystone/components/AdminLayout"; +import { DashboardLayout } from "@keystone/screens"; export default function Layout({ children }) { - return {children}; + return {children}; } diff --git a/app/dashboard/(admin)/oms/channels/(components)/ChannelPlatforms.js b/app/dashboard/(admin)/oms/channels/(components)/ChannelPlatforms.js new file mode 100644 index 0000000..87884a1 --- /dev/null +++ b/app/dashboard/(admin)/oms/channels/(components)/ChannelPlatforms.js @@ -0,0 +1,198 @@ +"use client"; + +import React, { useState } from "react"; +import { useQuery, gql } from "@keystone-6/core/admin-ui/apollo"; +import { EllipsisVertical, Plus } from "lucide-react"; +import { Skeleton } from "@ui/skeleton"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; + +import { Badge } from "@ui/badge"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuPortal, + DropdownMenuTrigger, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/dropdown-menu-depracated"; +import { CreatePlatform } from "./CreatePlatform"; + +export const CHANNEL_PLATFORMS_QUERY = gql` + query ( + $where: ChannelPlatformWhereInput + $take: Int! + $skip: Int! + $orderBy: [ChannelPlatformOrderByInput!] + ) { + items: channelPlatforms( + where: $where + take: $take + skip: $skip + orderBy: $orderBy + ) { + id + name + createPurchaseFunction + searchProductsFunction + getProductFunction + getWebhooksFunction + deleteWebhookFunction + createWebhookFunction + cancelPurchaseWebhookHandler + createTrackingWebhookHandler + oAuthFunction + oAuthCallbackFunction + appKey + appSecret + createdAt + updatedAt + __typename + } + count: channelPlatformsCount(where: $where) + } +`; + +const ChannelPlatformsContent = ({ data, openDrawer, showAll }) => { + if (!data || !data.items) return null; + + const platformItems = [...data.items]; + + return platformItems + .slice(0, showAll ? platformItems.length : 6) + .map((platform, index) => ( +
+ + +
+ )); +}; + +const useChannelPlatformsQuery = () => { + return useQuery(CHANNEL_PLATFORMS_QUERY, { + variables: { + where: { OR: [] }, + take: 50, + skip: 0, + }, + }); +}; + +export const ChannelPlatforms = ({ openDrawer }) => { + const { data, loading, error } = useChannelPlatformsQuery(); + const [showAll, setShowAll] = useState(true); + + if (loading) { + return ( +
+ {Array(6) + .fill(0) + .map((_, index) => ( + + ))} +
+ ); + } + + if (error) return

Error loading platforms: {error.message}

; + + return ( + + ); +}; + +export const ChannelPlatformsMobile = ({ openDrawer }) => { + const { data, loading, error, refetch } = useChannelPlatformsQuery(); + + if (loading) { + return ( +
+ {Array(6) + .fill(0) + .map((_, index) => ( + + ))} +
+ ); + } + + if (error) return

Error loading platforms: {error.message}

; + + return ( + + + {data.items.length && ( + + )} + + + + + + + ) : ( + + ) + } + /> + + + {data.items.map((platform) => ( + openDrawer(platform.id, "ChannelPlatform")} + className="block w-full text-left px-4 py-2 text-sm" + > + {platform.name} + + ))} + + + + ); +}; diff --git a/app/dashboard/(admin)/oms/channels/(components)/Channels.js b/app/dashboard/(admin)/oms/channels/(components)/Channels.js new file mode 100644 index 0000000..7f9bc13 --- /dev/null +++ b/app/dashboard/(admin)/oms/channels/(components)/Channels.js @@ -0,0 +1,297 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { gql } from "@apollo/client"; +import { Skeleton } from "@ui/skeleton"; +import { Badge } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; +import { Webhooks } from "./Webhooks"; +import { Links } from "./Links"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs"; +import { SearchOrders } from "./SearchOrders"; +import { + ChevronDownIcon, + Circle, + EllipsisVertical, + Square, + Triangle, + Webhook, +} from "lucide-react"; +import { + TicketIcon, + Square2StackIcon, + LinkIcon, +} from "@heroicons/react/16/solid"; +import { + format, + parseISO, + differenceInMinutes, + differenceInHours, +} from "date-fns"; + +export const CHANNELS_QUERY = gql` + query ( + $where: ChannelWhereInput + $take: Int! + $skip: Int! + $orderBy: [ChannelOrderByInput!] + ) { + items: channels( + where: $where + take: $take + skip: $skip + orderBy: $orderBy + ) { + id + name + platform { + name + } + links(orderBy: [{ rank: asc }]) { + id + rank + channel { + id + name + } + filters + } + channelItemsCount + linksCount + createdAt + updatedAt + } + count: channelsCount(where: $where) + } +`; + +function formatDate(dateString) { + const date = parseISO(dateString); + const now = new Date(); + const minutesDifference = differenceInMinutes(now, date); + const hoursDifference = differenceInHours(now, date); + + if (minutesDifference < 60) { + return `${minutesDifference} minutes ago`; + } else if (hoursDifference < 24) { + return `${hoursDifference} hours ago`; + } else { + return format(date, "PPP"); + } +} + +export const Channels = ({ openDrawer, selectedPlatform }) => { + const { data, loading, error, refetch } = useQuery(CHANNELS_QUERY, { + variables: { + where: selectedPlatform + ? { platform: { id: { equals: selectedPlatform } } } + : { OR: [] }, + take: 50, + skip: 0, + }, + }); + + useEffect(() => { + refetch(); + }, [selectedPlatform]); + + const [showAll, setShowAll] = useState(false); + + if (loading) { + return ( +
+ {Array(6).fill(0).map((_, index) => ( +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ ))} +
+ ); + } + + if (error) return

Error loading channels: {error.message}

; + + const channelItems = data.items; + + return ( +
+ {!showAll && channelItems.length > 6 && ( +
+ )} + {channelItems.length ? ( +
+ {channelItems + .slice(0, showAll ? channelItems.length : 6) + .map((channel) => ( +
+
+
+
+ + {channel.name.slice(0, 2)} + +
+
+
+

{channel.name}

+
+ +
+ {channel.platform ? ( +
+
+
+
+ {channel.platform.name} +
+ ) : ( +
+
+
+
+ Platform not connected +
+ )} + +

+ Last updated {formatDate(channel.updatedAt)} +

+
+
+ +
+ + + + + + + {/* + */} + + + +
+ +

+

+
+ Create platform webhooks to keep Openship in sync +
+
+ +

+
+ {/* +

+

+ +
+

+
*/} + + + +
+
+
+
+ ))} +
+ ) : ( +
+
+
+ + + +
+ + + No Channels found + + + Get started by creating a new one. + +
+
+ )} + + {!showAll && channelItems.length > 6 && ( +
+ +
+ )} +
+ ); +}; diff --git a/app/dashboard/(admin)/oms/channels/(components)/CreateChannel.js b/app/dashboard/(admin)/oms/channels/(components)/CreateChannel.js new file mode 100644 index 0000000..f0464d8 --- /dev/null +++ b/app/dashboard/(admin)/oms/channels/(components)/CreateChannel.js @@ -0,0 +1,285 @@ +import React, { useMemo, useState } from "react"; +import { useCreateItem } from "@keystone/utils/useCreateItem"; +import { useList } from "@keystone/keystoneProvider"; +import { Button } from "@ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@ui/dialog"; +import { Fields } from "@keystone/themes/Tailwind/atlas/components/Fields"; +import { useQuery, gql } from "@keystone-6/core/admin-ui/apollo"; +import { GraphQLErrorNotice } from "@keystone/themes/Tailwind/atlas/components/GraphQLErrorNotice"; +import { CHANNEL_PLATFORMS_QUERY } from "./ChannelPlatforms"; +import { CHANNELS_QUERY } from "./Channels"; + +const GET_CHANNEL_PLATFORM_DETAILS = gql` + query ($id: ID!) { + channelPlatform(where: { id: $id }) { + id + name + appKey + appSecret + callbackUrl + oAuthFunction + oAuthCallbackFunction + } + } +`; + +export function getFilteredProps(props, modifications, defaultCollapse) { + const fieldKeysToShow = modifications.map((mod) => mod.key); + const breakGroups = modifications.reduce((acc, mod) => { + if (mod.breakGroup) { + acc.push(mod.breakGroup); + } + return acc; + }, []); + + const newFieldModes = { ...props.fieldModes }; + + Object.keys(props.fields).forEach((key) => { + if (!fieldKeysToShow.includes(key)) { + newFieldModes[key] = "hidden"; + } else { + newFieldModes[key] = props.fieldModes[key] || "edit"; + } + }); + + const updatedFields = Object.keys(props.fields).reduce((obj, key) => { + const modification = modifications.find((mod) => mod.key === key); + if (modification) { + obj[key] = { + ...props.fields[key], + fieldMeta: { + ...props.fields[key].fieldMeta, + ...modification.fieldMeta, + }, + }; + } else { + obj[key] = props.fields[key]; + } + return obj; + }, {}); + + const reorderedFields = modifications.reduce((obj, mod) => { + obj[mod.key] = updatedFields[mod.key]; + return obj; + }, {}); + + const updatedGroups = props.groups.map((group) => { + if (breakGroups.includes(group.label)) { + return { + ...group, + fields: group.fields.filter( + (field) => !fieldKeysToShow.includes(field.path) + ), + }; + } + return { + ...group, + collapsed: defaultCollapse, + }; + }); + + return { + ...props, + fields: reorderedFields, + fieldModes: newFieldModes, + groups: updatedGroups, + }; +} + +export function CreateChannel() { + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const list = useList("Channel"); + const { create, props, state, error } = useCreateItem(list); + const { refetch } = useQuery(CHANNELS_QUERY, { + variables: { + where: { OR: [] }, + take: 50, + skip: 0, + }, + }); + + const handleDialogClose = () => { + setIsDialogOpen(false); + }; + + const platformId = props.value.platform?.value?.value?.id; + + const filteredProps = useMemo(() => { + const modifications = [ + { key: "platform", fieldMeta: { hideButtons: true } }, + ]; + return getFilteredProps(props, modifications); + }, [props]); + + return ( + + + + + + + Create Channel + + Select a platform and fill in the necessary fields + + + {error && ( + + )} + + + {platformId && } + + + + + + {platformId && ( + + )} + + + + ); +} + +function TriggerButton({ setIsDialogOpen }) { + const { data, loading, error } = useQuery(CHANNEL_PLATFORMS_QUERY, { + variables: { + where: { OR: [] }, + take: 50, + skip: 0, + }, + }); + + return ( + + ); +} + +export function FilteredFields({ platformId, props }) { + const { data, loading, error } = useQuery(GET_CHANNEL_PLATFORM_DETAILS, { + variables: { id: platformId }, + }); + + if (loading) return

Loading...

; + if (error) return

Error loading platform data.

; + + const platformData = data?.channelPlatform; + + let modifications = []; + + if (platformData) { + if ( + platformData.appKey && + platformData.appSecret && + platformData.oAuthFunction && + platformData.oAuthCallbackFunction + ) { + modifications = [{ key: "domain", breakGroup: "Credentials" }]; + } else { + modifications = [ + { key: "name" }, + { key: "domain", breakGroup: "Credentials" }, + { key: "accessToken", breakGroup: "Credentials" }, + ]; + } + } + + const filteredProps = getFilteredProps(props, modifications); + + if (!filteredProps.fields) return null; + + return ( +
+ +
+ ); +} + +export function CreateChannelButton({ + platformId, + handleChannelCreation, + refetch, + props, + state, + setIsDialogOpen, +}) { + const { data, loading, error } = useQuery(GET_CHANNEL_PLATFORM_DETAILS, { + variables: { id: platformId }, + }); + + if (loading) return ; + if (error) return ; + + const platformData = data?.channelPlatform; + + const handleClick = async () => { + if ( + platformData?.appKey && + platformData?.appSecret && + platformData?.oAuthFunction && + platformData?.oAuthCallbackFunction + ) { + const { oauth, scopes } = await import( + `../../../../../../channelAdapters/${platformData.oAuthFunction}` + ); + + const config = { + apiKey: platformData.appKey, + apiSecret: platformData.appSecret, + redirectUri: platformData.callbackUrl, + scopes: scopes(), + }; + + const domain = props.value.domain?.value?.inner?.value; + oauth(domain, config); + } else { + const item = await handleChannelCreation(); + if (item) { + refetch(); + setIsDialogOpen(false); + } + } + }; + + return ( + + ); +} diff --git a/app/dashboard/(admin)/oms/channels/(components)/CreatePlatform.js b/app/dashboard/(admin)/oms/channels/(components)/CreatePlatform.js new file mode 100644 index 0000000..e13c6ce --- /dev/null +++ b/app/dashboard/(admin)/oms/channels/(components)/CreatePlatform.js @@ -0,0 +1,227 @@ +"use client"; +import React, { useMemo, useState } from "react"; +import { useCreateItem } from "@keystone/utils/useCreateItem"; +import { useKeystone, useList } from "@keystone/keystoneProvider"; +import { Button } from "@ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@ui/dialog"; +import { Fields } from "@keystone/themes/Tailwind/atlas/components/Fields"; +import { GraphQLErrorNotice } from "@keystone/themes/Tailwind/atlas/components/GraphQLErrorNotice"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@ui/select"; +import { Label } from "@ui/label"; +import { channelAdapters as adapters } from "../../../../../../channelAdapters"; +import { getFilteredProps } from "./CreateChannel"; +import { Badge } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; + +export const channelAdapters = { + ...adapters, + Medusa: "soon", + Magento: "soon", + Stripe: "soon", +}; + +export function CreatePlatform({ refetch, trigger }) { + const [selectedPlatform, setSelectedPlatform] = useState(undefined); + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const list = useList("ChannelPlatform"); + const { create, props, state, error } = useCreateItem(list); + const { createViewFieldModes } = useKeystone(); + + const keysToUpdateCustom = [ + "name", + "createPurchaseFunction", + "getWebhooksFunction", + "deleteWebhookFunction", + "createWebhookFunction", + "searchProductsFunction", + "getProductFunction", + "cancelPurchaseWebhookHandler", + "createTrackingWebhookHandler", + "oAuthFunction", + "oAuthCallbackFunction", + "appKey", + "appSecret", + ]; + + const keysToUpdateTemplate = ["name", "appKey", "appSecret"]; + + const handlePlatformActivation = async () => { + const item = await create(); + if (item) { + refetch(); + clearFunctionFields(); + setIsDialogOpen(false); + } + }; + + const handleTemplateSelection = (value) => { + setSelectedPlatform(value); + + if (value === "custom") { + clearFunctionFields(); + } else { + props.onChange((oldValues) => { + const newValues = { ...oldValues }; + keysToUpdateCustom + .filter((key) => !["appKey", "appSecret"].includes(key)) + .forEach((key) => { + newValues[key] = { + ...oldValues[key], + value: { + ...oldValues[key].value, + inner: { + ...oldValues[key].value.inner, + value: + key === "name" + ? value.charAt(0).toUpperCase() + value.slice(1) + : value, + }, + }, + }; + }); + return newValues; + }); + } + }; + + const clearFunctionFields = () => { + const clearedFields = keysToUpdateCustom.reduce((acc, key) => { + acc[key] = { + ...props.value[key], + value: { + ...props.value[key].value, + inner: { + ...props.value[key].value.inner, + value: "", + }, + }, + }; + return acc; + }, {}); + + props.onChange((prev) => ({ ...prev, ...clearedFields })); + }; + + const handleDialogClose = () => { + setSelectedPlatform(null); // Reset selected platform + clearFunctionFields(); // Clear all fields + setIsDialogOpen(false); // Close dialog + }; + + const filteredProps = useMemo(() => { + const fieldKeysToShow = + selectedPlatform === "custom" ? keysToUpdateCustom : keysToUpdateTemplate; + + const modifications = fieldKeysToShow.map((key) => ({ key })); + + return getFilteredProps(props, [...modifications], true); + }, [props, selectedPlatform]); + + return ( + + {trigger} + + + + Create Channel Platform + + {selectedPlatform === "custom" + ? "Create a custom platform from scratch by providing the necessary fields" + : "Create a platform based on an existing template"} + + + +
+ + +
+ + {selectedPlatform && ( +
+ {error && ( + + )} + {createViewFieldModes.state === "error" && ( + + )} + {createViewFieldModes.state === "loading" && ( +
+ )} + +
+ )} + + + + + + +
+ ); +} diff --git a/app/dashboard/(admin)/oms/channels/(components)/Links.js b/app/dashboard/(admin)/oms/channels/(components)/Links.js new file mode 100644 index 0000000..1fe16ff --- /dev/null +++ b/app/dashboard/(admin)/oms/channels/(components)/Links.js @@ -0,0 +1,812 @@ +import React, { useState, useMemo, useEffect } from "react"; +import { useCreateItem } from "@keystone/utils/useCreateItem"; +import { gql, useMutation, useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { useList } from "@keystone/keystoneProvider"; +import { + ArrowRight, + Edit, + Edit2, + ListFilter, + Plus, + Trash2, + XIcon, +} from "lucide-react"; +import { Button } from "@ui/button"; +import { Badge } from "@ui/badge"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue, +} from "@ui/select"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@ui/dropdown-menu-depracated"; +import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover"; +import { Label } from "@ui/label"; +import { useToasts } from "@keystone/screens"; +import { cn } from "@keystone/utils/cn"; +import { ReactSortable } from "react-sortablejs"; +import { Pencil1Icon } from "@radix-ui/react-icons"; +import { RiFilterLine } from "@remixicon/react"; + +const GET_CHANNELS = gql` + query GetChannels($where: ChannelWhereInput, $take: Int, $skip: Int) { + items: channels(where: $where, take: $take, skip: $skip) { + id + name + } + } +`; + +export const CreateLinkButton = ({ channelId, refetch }) => { + const list = useList("Link"); + const { createWithData, state, error } = useCreateItem(list); + const { + data, + loading, + error: queryError, + } = useQuery(GET_CHANNELS, { + variables: { where: {}, take: 50, skip: 0 }, + }); + + const [isCreating, setIsCreating] = useState(false); + + const handleCreateLink = async (channelId) => { + setIsCreating(true); + try { + const item = await createWithData({ + data: { + channel: { connect: { id: channelId } }, + channel: { connect: { id: channelId } }, + }, + }); + if (item) { + refetch(); + } + } finally { + setIsCreating(false); + } + }; + + return ( + + +
+ +
+
+ + Select Channel to Link + + + {loading && Loading...} + {queryError && ( + Error loading channels + )} + {data?.items?.map((channel) => ( + handleCreateLink(channel.id)} + disabled={isCreating} + > + {channel.name} + + ))} + + +
+ ); +}; + +const areOrdersEqual = (links1, links2) => { + if (links1.length !== links2.length) return false; + return links1.every((link, index) => link.id === links2[index].id); +}; + +export const Links = ({ + channelId, + links: initialLinks, + refetch, + isLoading, + editItem, +}) => { + const [links, setLinks] = useState([]); + const [selectedLinkId, setSelectedLinkId] = useState(null); + const [isUpdating, setIsUpdating] = useState(false); + const [isDeletingFilter, setIsDeletingFilter] = useState(false); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [error, setError] = useState(null); + + const hasOrderChanged = !areOrdersEqual(initialLinks, links); + + const updateMutation = gql` + mutation ($data: LinkUpdateInput!, $id: ID!) { + item: updateLink(where: { id: $id }, data: $data) { + id + filters + } + } + `; + const [update] = useMutation(updateMutation); + + const updateLinksMutation = gql` + mutation UpdateLinks($data: [LinkUpdateArgs!]!) { + updateLinks(data: $data) { + id + rank + } + } + `; + const [updateLinks] = useMutation(updateLinksMutation); + + const deleteMutation = gql` + mutation ($id: ID!) { + deleteLink(where: { id: $id }) { + id + } + } + `; + const [deleteLink] = useMutation(deleteMutation); + + const toasts = useToasts(); + const list = useList("Order"); + + useEffect(() => { + const simplifiedLinks = initialLinks.map((link) => ({ + id: link.id, + name: link.channel ? link.channel.name : "Unnamed", + filtersCount: link.filters?.length || 0, + rank: link.rank, + filters: link.filters || [], + })); + setLinks(simplifiedLinks); + }, [initialLinks]); + + const handleFilterSubmit = (state) => { + setIsUpdating(true); + setError(null); + const selectedLink = links.find((link) => link.id === selectedLinkId); + const updatedFilters = [...(selectedLink.filters || [])]; + + const existingFilterIndex = updatedFilters.findIndex( + (filter) => + filter.field === state.fieldPath && filter.type === state.filterType + ); + + if (existingFilterIndex !== -1) { + updatedFilters[existingFilterIndex] = { + type: state.filterType, + field: state.fieldPath, + value: state.filterValue, + }; + } else { + updatedFilters.push({ + type: state.filterType, + field: state.fieldPath, + value: state.filterValue, + }); + } + + update({ + variables: { data: { filters: updatedFilters }, id: selectedLinkId }, + }) + .then((result) => { + const updatedLink = result.data.item; + setLinks((prevLinks) => + prevLinks.map((link) => + link.id === updatedLink.id + ? { + ...link, + filters: updatedLink.filters, + filtersCount: updatedLink.filters.length, + } + : link + ) + ); + toasts.addToast({ + tone: "positive", + title: "Filters updated successfully", + }); + setIsPopoverOpen(false); + }) + .catch((err) => { + setError(err.message); + toasts.addToast({ + title: "Failed to update filters", + tone: "negative", + message: err.message, + }); + }) + .finally(() => { + setIsUpdating(false); + }); + }; + + const deleteFilter = (linkId, field, type) => { + setIsDeletingFilter(true); + setError(null); + const link = links.find((link) => link.id === linkId); + if (!link || !link.filters) { + setError("Link or filters not found"); + setIsDeletingFilter(false); + return; + } + const updatedFilters = link.filters.filter( + (filter) => !(filter.field === field && filter.type === type) + ); + + update({ + variables: { data: { filters: updatedFilters }, id: linkId }, + }) + .then((result) => { + const updatedLink = result.data.item; + setLinks((prevLinks) => + prevLinks.map((link) => + link.id === updatedLink.id + ? { + ...link, + filters: updatedLink.filters, + filtersCount: updatedLink.filters.length, + } + : link + ) + ); + toasts.addToast({ + tone: "positive", + title: "Filter deleted successfully", + }); + }) + .catch((err) => { + setError(err.message); + toasts.addToast({ + title: "Failed to delete filter", + tone: "negative", + message: err.message, + }); + }) + .finally(() => { + setIsDeletingFilter(false); + }); + }; + + const handleDeleteLink = async (linkId) => { + try { + await deleteLink({ variables: { id: linkId } }); + setLinks(links.filter((link) => link.id !== linkId)); + toasts.addToast({ + tone: "positive", + title: "Link deleted successfully", + }); + } catch (err) { + setError(err.message); + toasts.addToast({ + title: "Failed to delete link", + tone: "negative", + message: err.message, + }); + } + }; + + const handleSaveOrder = async () => { + setIsUpdating(true); + try { + const updateData = links.map((link, index) => ({ + where: { id: link.id }, + data: { rank: index + 1 }, + })); + + await updateLinks({ variables: { data: updateData } }); + + toasts.addToast({ + tone: "positive", + title: "Link order updated successfully", + }); + refetch(); + } catch (err) { + setError(err.message); + toasts.addToast({ + title: "Failed to update link order", + tone: "negative", + message: err.message, + }); + } finally { + setIsUpdating(false); + } + }; + + if (isLoading) { + return
Loading links...
; + } + + return ( +
+ {error &&
{error}
} +
+
+
+
+ +
+ Create links to channels based on filters +
+
+ {/* {JSON.stringify({ initialLinks })} */} + {hasOrderChanged ? ( + + ) : ( + + )} +
+ {/* + {state.map((item) => ( +
{item.name}
+ ))} +
*/} + {links.length > 0 && ( + handleLinkOrderChange(newState)} + className="flex flex-col mt-3 gap-2" + > + {links.map((link) => ( + setSelectedLinkId(link.id)} + // onDelete={() => handleDeleteLink(link.id)} + editItem={() => editItem(link.id, "Link")} + /> + ))} + + )} +
+
+ + {selectedLinkId && ( +
+
+
+
+ +
+ Orders matching these filters will be processed by this + channel +
+
+
+ + + + + + + + +
+
+ link.id === selectedLinkId).filters + } + list={list} + linkId={selectedLinkId} + deleteFilter={deleteFilter} + handleFilterSubmit={handleFilterSubmit} + isUpdating={isUpdating} + isDeletingFilter={isDeletingFilter} + /> +
+
+ )} +
+ ); +}; + +export const Link = ({ link, isSelected, onSelect, editItem }) => { + return ( +
+
+
+ + {link.rank} + + {link.name} +
+
+ + + + + {link.filtersCount || 0} + +
+
+
+ ); +}; + +function FilterList({ + filters, + list, + linkId, + deleteFilter, + handleFilterSubmit, + isUpdating, + isDeletingFilter, +}) { + return ( +
+ {filters?.map((filter, index) => ( + + ))} +
+ ); +} + +function FilterPill({ + filter, + field, + linkId, + deleteFilter, + handleFilterSubmit, + isUpdating, + isDeletingFilter, + list, +}) { + const [isOpen, setIsOpen] = useState(false); + + const onRemove = () => { + deleteFilter(linkId, filter.field, filter.type); + }; + + const filterType = field.controller.filter.types[filter.type]; + const filterLabel = filterType ? filterType.label : filter.type; + + const formattedValue = field.controller.filter.format + ? field.controller.filter.format({ + label: filterLabel, + type: filter.type, + value: filter.value, + }) + : `${filterLabel} ${filter.value}`; + + const readableFilter = `${field.label} ${formattedValue}`; + + const handleSubmit = async (state) => { + await handleFilterSubmit(state); + setIsOpen(false); + }; + + return ( + + +
+ {readableFilter} +
+ { + e.stopPropagation(); + onRemove(); + }} + disabled={isDeletingFilter} + > + + +
+
+
+ + + +
+ ); +} + +function AddFilterContent({ list, listKey, handleFilterSubmit, isUpdating }) { + const [state, setState] = useState({ + fieldPath: null, + filterType: null, + filterValue: null, + }); + + const fieldsWithFilters = useMemo(() => { + const fieldsWithFilters = {}; + Object.keys(list.fields).forEach((fieldPath) => { + const field = list.fields[fieldPath]; + if (field.controller.filter) { + fieldsWithFilters[fieldPath] = field; + } + }); + return fieldsWithFilters; + }, [list.fields]); + + const handleSelectFieldPath = (fieldPath) => { + setState({ + fieldPath, + filterType: null, + filterValue: null, + }); + }; + + const handleSelectFilterType = (filterType) => { + setState((prevState) => ({ + ...prevState, + filterType, + filterValue: + fieldsWithFilters[prevState.fieldPath]?.controller.filter.types[ + filterType + ]?.initialValue ?? null, + })); + }; + + return ( +
+
+
+ + +
+ + {state.fieldPath && ( +
+ + +
+ )} + + {state.fieldPath && + state.filterType && + fieldsWithFilters[state.fieldPath] && + (() => { + const { Filter } = + fieldsWithFilters[state.fieldPath].controller.filter; + if (Filter) + return ( +
+ + { + setState((state) => ({ + ...state, + filterValue: value, + })); + }} + /> +
+ ); + })()} +
+ +
+ ); +} + +function UpdateFilterContent({ list, filter, handleFilterSubmit, isUpdating }) { + const [state, setState] = useState({ + fieldPath: filter.field, + filterType: filter.type, + filterValue: filter.value, + }); + + const fieldsWithFilters = useMemo(() => { + const fieldsWithFilters = {}; + Object.keys(list.fields).forEach((fieldPath) => { + const field = list.fields[fieldPath]; + if (field.controller.filter) { + fieldsWithFilters[fieldPath] = field; + } + }); + return fieldsWithFilters; + }, [list.fields]); + + const handleSelectFieldPath = (fieldPath) => { + setState({ + fieldPath, + filterType: null, + filterValue: null, + }); + }; + + const handleSelectFilterType = (filterType) => { + setState((prevState) => ({ + ...prevState, + filterType, + filterValue: + fieldsWithFilters[prevState.fieldPath]?.controller.filter.types[ + filterType + ]?.initialValue ?? null, + })); + }; + + return ( +
+
+
+ + +
+ + {state.fieldPath && ( +
+ + +
+ )} + + {state.fieldPath && + state.filterType && + fieldsWithFilters[state.fieldPath] && + (() => { + const { Filter } = + fieldsWithFilters[state.fieldPath].controller.filter; + if (Filter) + return ( +
+ + { + setState((state) => ({ + ...state, + filterValue: value, + })); + }} + /> +
+ ); + })()} +
+ +
+ ); +} diff --git a/app/dashboard/(admin)/oms/channels/(components)/PlatformCard.js b/app/dashboard/(admin)/oms/channels/(components)/PlatformCard.js new file mode 100644 index 0000000..eedfded --- /dev/null +++ b/app/dashboard/(admin)/oms/channels/(components)/PlatformCard.js @@ -0,0 +1,94 @@ +import React, { useState } from "react"; +import { useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { EllipsisVertical, Plus } from "lucide-react"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; +import { + Badge, + BadgeButton, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { Skeleton } from "@ui/skeleton"; +import { CreatePlatform } from "./CreatePlatform"; +import { CHANNEL_PLATFORMS_QUERY } from "./ChannelPlatforms"; + +export const PlatformCard = ({ openDrawer, setSelectedPlatform }) => { + const [selectedPlatformId, setSelectedPlatformId] = useState(null); + const { data, loading, error, refetch } = useQuery(CHANNEL_PLATFORMS_QUERY, { + variables: { where: { OR: [] }, take: 50, skip: 0 }, + }); + + if (loading) { + return ( +
+

Platforms

+
+ {[1, 2, 3].map((i) => ( + + ))} +
+
+ ); + } + + if (error) return
Error loading platforms: {error.message}
; + + const platforms = data?.items || []; + + const handlePlatformClick = (platformId) => { + if (selectedPlatformId === platformId) { + setSelectedPlatformId(null); + setSelectedPlatform(null); + } else { + setSelectedPlatformId(platformId); + setSelectedPlatform(platformId); + } + }; + + return ( +
+

+ Platforms +

+
+ + // Create + // + // + + } + /> + {platforms.map((platform) => ( + handlePlatformClick(platform.id)} + > + {platform.name} + + + ))} +
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/channels/(components)/SearchOrders.js b/app/dashboard/(admin)/oms/channels/(components)/SearchOrders.js new file mode 100644 index 0000000..48c94a0 --- /dev/null +++ b/app/dashboard/(admin)/oms/channels/(components)/SearchOrders.js @@ -0,0 +1,189 @@ +import React, { useState } from "react"; +import { useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { gql } from "@apollo/client"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; +import { BadgeButton } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { Input } from "@ui/input"; +import { ArrowLeft, ArrowRight, Search } from "lucide-react"; + +export const CHANNEL_ORDERS_QUERY = gql` + query CHANNEL_ORDERS_QUERY( + $skip: Int + $take: Int + $where: OrderWhereInput + $cartItemsWhere: CartItemWhereInput + ) { + orders(orderBy: [], take: $take, skip: $skip, where: $where) { + id + orderId + orderName + email + firstName + lastName + streetAddress1 + streetAddress2 + city + state + zip + orderError + cartItems(where: $cartItemsWhere) { + id + name + image + price + quantity + productId + variantId + purchaseId + url + error + order { + id + } + channel { + id + name + } + } + shop { + name + domain + accessToken + } + currency + totalPrice + subTotalPrice + totalDiscount + totalTax + status + createdAt + } + } +`; + +export const SearchOrders = ({ + channelId, + searchEntry: initialSearchEntry, + pageSize, +}) => { + const [searchEntry, setSearchEntry] = useState(initialSearchEntry); + const [skip, setSkip] = useState(0); + + const { data, error, loading } = useQuery(CHANNEL_ORDERS_QUERY, { + variables: { + where: { + cartItems: { + some: { channel: { id: { equals: channelId } } }, + }, + OR: [ + { orderName: { contains: searchEntry, mode: "insensitive" } }, + { firstName: { contains: searchEntry, mode: "insensitive" } }, + { lastName: { contains: searchEntry, mode: "insensitive" } }, + { streetAddress1: { contains: searchEntry, mode: "insensitive" } }, + { streetAddress2: { contains: searchEntry, mode: "insensitive" } }, + { city: { contains: searchEntry, mode: "insensitive" } }, + { state: { contains: searchEntry, mode: "insensitive" } }, + { zip: { contains: searchEntry, mode: "insensitive" } }, + ], + }, + cartItemsWhere: { channel: { id: { equals: channelId } } }, + skip: skip, + take: pageSize, + }, + }); + + const handleSearch = () => { + setSkip(0); + }; + + const handleNextPage = () => { + setSkip(skip + pageSize); + }; + + const handlePreviousPage = () => { + setSkip(Math.max(0, skip - pageSize)); + }; + + if (loading) return

Loading...

; + if (error) return

Error: {error.message}

; + + const orders = data?.orders || []; + + return ( +
+
+
+ + +
+
+ setSearchEntry(e.target.value)} + onKeyPress={(e) => { + if (e.key === "Enter") { + handleSearch(); + } + }} + /> +
+ + Search + +
+
+
+ {orders.map((order) => ( +
+

{order.orderName}

+

+ {order.firstName} {order.lastName} +

+

+ {order.streetAddress1} {order.streetAddress2} +

+

+ {order.city}, {order.state} {order.zip} +

+

Created: {new Date(order.createdAt).toLocaleString()}

+

+ Total: {order.currency} {order.totalPrice} +

+

Status: {order.status}

+

Cart Items:

+
    + {order.cartItems.map((item) => ( +
  • + {item.name} - Quantity: {item.quantity}, Price: {order.currency}{" "} + {item.price} +
  • + ))} +
+
+ ))} + {orders.length === 0 && ( +

No orders found.

+ )} +
+ ); +}; diff --git a/app/dashboard/(admin)/oms/channels/(components)/Webhooks.js b/app/dashboard/(admin)/oms/channels/(components)/Webhooks.js new file mode 100644 index 0000000..d827bcf --- /dev/null +++ b/app/dashboard/(admin)/oms/channels/(components)/Webhooks.js @@ -0,0 +1,261 @@ +import React, { useState } from "react"; +import { useMutation, gql, useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; +import { + Badge, + BadgeButton, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { useToasts } from "@keystone/screens"; +import { InfoCircledIcon } from "@radix-ui/react-icons"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/tooltip"; +import { RiLoader2Fill } from "@remixicon/react"; +import { Plus } from "lucide-react"; +import { GraphQLErrorNotice } from "@keystone/themes/Tailwind/atlas/components/GraphQLErrorNotice"; + +const CREATE_CHANNEL_WEBHOOK = gql` + mutation CreateChannelWebhook( + $channelId: ID! + $topic: String! + $endpoint: String! + ) { + createChannelWebhook( + channelId: $channelId + topic: $topic + endpoint: $endpoint + ) { + success + error + webhookId + } + } +`; + +const DELETE_CHANNEL_WEBHOOK = gql` + mutation DeleteChannelWebhook($channelId: ID!, $webhookId: ID!) { + deleteChannelWebhook(channelId: $channelId, webhookId: $webhookId) { + success + error + } + } +`; + +const GET_CHANNEL_WEBHOOKS = gql` + query GetChannelWebhooks($channelId: ID!) { + getChannelWebhooks(channelId: $channelId) { + id + callbackUrl + topic + } + } +`; + +const RECOMMENDED_WEBHOOKS = [ + { + callbackUrl: "/api/handlers/channel/cancel-purchase/[channelId]", + topic: "ORDER_CANCELLED", + description: + "When a purchase order is cancelled by this channel, enabling this will notify Openship to mark the cart item as cancelled and move the order to PENDING for reprocessing.", + }, + { + callbackUrl: "/api/handlers/channel/create-tracking/[channelId]", + topic: "TRACKING_CREATED", + description: + "When a purchase order is fulfilled by this channel, enabling this will notify Openship to add the tracking to the order and shop.", + }, +]; + +const WebhookItem = ({ webhook, refetch, channelId }) => { + const [deleteWebhook] = useMutation(DELETE_CHANNEL_WEBHOOK); + const [loading, setLoading] = useState(false); + const toasts = useToasts(); + + const handleDelete = async () => { + setLoading(true); + await deleteWebhook({ + variables: { + channelId, + webhookId: webhook.id, + }, + }) + .then(({ errors }) => { + const error = errors?.find( + (x) => x.path === undefined || x.path?.length === 1 + ); + if (error) { + toasts.addToast({ + title: "Failed to delete webhook", + tone: "negative", + message: error.message, + }); + } else { + toasts.addToast({ + tone: "positive", + title: "Webhook deleted successfully", + }); + } + }) + .catch((err) => { + toasts.addToast({ + title: "Failed to delete webhook", + tone: "negative", + message: err.message, + }); + }); + refetch(); + setLoading(false); + }; + + return ( +
+
+ {webhook.topic} + + {loading ? "Deleting..." : "Delete"} + +
+
+ Callback URL: + {webhook.callbackUrl} +
+
+ ); +}; + +const RecommendedWebhookItem = ({ webhook, refetch, channelId }) => { + const [createWebhook, { loading, error }] = useMutation( + CREATE_CHANNEL_WEBHOOK + ); + const toasts = useToasts(); + + const handleCreate = async () => { + await createWebhook({ + variables: { + channelId, + topic: webhook.topic, + endpoint: webhook.callbackUrl.replace("[channelId]", channelId), + }, + }) + .then(({ errors }) => { + const error = errors?.find( + (x) => x.path === undefined || x.path?.length === 1 + ); + if (error) { + toasts.addToast({ + title: "Failed to create webhook", + tone: "negative", + message: error.message, + }); + } else { + toasts.addToast({ + tone: "positive", + title: "Webhook created successfully", + }); + } + }) + .catch((err) => { + toasts.addToast({ + title: "Failed to create webhook", + tone: "negative", + message: err.message, + }); + }); + refetch(); + }; + + return ( +
+
+
+ + {webhook.topic} + + + + + + +

{webhook.description}

+
+
+
+
+
+
+ ); +}; + +export const Webhooks = ({ channelId }) => { + const { data, loading, error, refetch } = useQuery(GET_CHANNEL_WEBHOOKS, { + variables: { channelId }, + }); + + if (loading) { + return
Loading webhooks...
; + } + + if (error) { + return ( +
+ + Error loading webhooks: {error?.message} + +
+ ); + } + + const webhooks = data.getChannelWebhooks; + + return ( +
+
+ {webhooks.map((webhook) => ( + + ))} +
+ +
+ {RECOMMENDED_WEBHOOKS.map((webhook) => { + const existingWebhook = webhooks.find( + (w) => + w.topic === webhook.topic && + w.callbackUrl === webhook.callbackUrl.replace("[channelId]", channelId) + ); + return !existingWebhook ? ( + + ) : null; + })} +
+
+ ); +}; \ No newline at end of file diff --git a/app/dashboard/(admin)/oms/channels/page.js b/app/dashboard/(admin)/oms/channels/page.js new file mode 100644 index 0000000..ff74537 --- /dev/null +++ b/app/dashboard/(admin)/oms/channels/page.js @@ -0,0 +1,42 @@ +"use client"; + +import React, { useState } from "react"; +import { Channels } from "./(components)/Channels"; +import { CreateChannel } from "./(components)/CreateChannel"; +import { PlatformCard } from "./(components)/PlatformCard"; +import { useDrawer } from "@keystone/themes/Tailwind/atlas/components/Modals/drawer-context"; + +const ChannelsPage = () => { + const { openEditDrawer } = useDrawer(); + const [selectedPlatform, setSelectedPlatform] = useState(null); + + return ( +
+
+
+

Channels

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ ); +}; + +export default ChannelsPage; \ No newline at end of file diff --git a/app/dashboard/(admin)/oms/matches/(components)/MatchCard.js b/app/dashboard/(admin)/oms/matches/(components)/MatchCard.js new file mode 100644 index 0000000..038b2f2 --- /dev/null +++ b/app/dashboard/(admin)/oms/matches/(components)/MatchCard.js @@ -0,0 +1,364 @@ +import React, { useState } from "react"; +import { + Loader2, + MoreHorizontal, + ChevronRight, + ChevronsUpDown, + AlertTriangle, + Check, + ArrowRight, + ArrowUp, + ArrowDown, + ArrowUpDown, +} from "lucide-react"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@ui/collapsible"; +import { Button } from "@ui/button"; +import { Badge, BadgeButton } from "@ui/badge"; +import { Separator } from "@ui/separator"; +import { useDrawer } from "@keystone/themes/Tailwind/atlas/components/Modals/drawer-context"; + +export const MatchCard = ({ + match, + onMatchAction, + handleAcceptPriceChange, + handleSyncInventory, + updateChannelItemLoading, + updateShopProductLoading, + renderButtons, +}) => { + const { openEditDrawer } = useDrawer(); + + return ( +
+ handleSyncInventory(match)} + updateShopProductLoading={updateShopProductLoading} + renderButtons={renderButtons} + > + + + + + +
+ ); +}; + +const MatchHeader = ({ + match, + children, + defaultOpen = false, + isLoading = false, + openEditDrawer, + handleSyncInventory, + updateShopProductLoading, + renderButtons, // Add this prop +}) => { + const [isOpen, setIsOpen] = useState(defaultOpen); + const { inventoryNeedsToBeSynced } = match; + + return ( + +
+ + + +
+ {inventoryNeedsToBeSynced?.syncEligible && ( + + )} + + {isLoading && ( + + )} + + + {renderButtons && renderButtons()} +
+
+ {children} +
+ ); +}; + +const InventorySyncButton = ({ + syncEligible, + sourceQuantity, + targetQuantity, + onSyncInventory, + isLoading, +}) => { + if (!syncEligible) return null; + + if (sourceQuantity === targetQuantity) { + return ( + + + Inventory Synced + + ); + } + + return ( + + + Inventory Needs Sync + + + ); +}; + +const ProductDetailsCollapsible = ({ + items, + title, + defaultOpen = true, + openEditDrawer, + onAcceptPriceChange, + updateChannelItemLoading, +}) => { + const [isOpen, setIsOpen] = React.useState(defaultOpen); + const isShopProduct = title === "Shop Product"; + + return ( + + + + + + {items.map((item, index) => ( +
+ {item.externalDetails.error ? ( +
+ + + +
+
+ {item.shop?.name || item.channel?.name} +
+

+ {item.productId} | {item.variantId} +

+

+ QTY: {item.quantity} +

+
+
+ ) : ( +
+
+ {item.externalDetails?.image && ( + {item.externalDetails?.title} + )} +
+
+ {item.shop?.name || item.channel?.name} +
+ + {item.externalDetails?.title || + "Details could not be fetched"} + +

+ {item.productId} | {item.variantId} +

+ {item.quantity > 1 ? ( +
+

+ $ + {( + parseFloat(item.externalDetails?.price) * + item.quantity + ).toFixed(2)} +

+

+ (${parseFloat(item.externalDetails?.price).toFixed(2)}{" "} + x {item.quantity}) + {item.price && + item.price !== item.externalDetails?.price && ( + + (was ${parseFloat(item.price).toFixed(2)}) + + )} +

+
+ ) : ( +

+ ${parseFloat(item.externalDetails?.price).toFixed(2)} + {item.price && + item.price !== item.externalDetails?.price && ( + + (was ${parseFloat(item.price).toFixed(2)}) + + )} +

+ )} +

+ INVENTORY:{" "} + {item.externalDetails?.inventory !== null + ? item.externalDetails?.inventory >= 1e9 + ? `${(item.externalDetails?.inventory / 1e9).toFixed( + 1 + )}B` + : item.externalDetails?.inventory >= 1e6 + ? `${(item.externalDetails?.inventory / 1e6).toFixed( + 1 + )}M` + : item.externalDetails?.inventory >= 1e3 + ? `${(item.externalDetails?.inventory / 1e3).toFixed( + 1 + )}k` + : item.externalDetails?.inventory.toString() + : "N/A"} +

+
+
+ +
+ {!isShopProduct && item.priceChanged !== 0 && ( +
+ 0 ? "red" : "green"} + className="flex flex-wrap gap-2 items-center border text-xs font-medium tracking-wide uppercase py-0.5 shadow-xs" + > + {item.priceChanged > 0 ? ( + <> + + + Price Went Up $ + {Math.abs(item.priceChanged).toFixed(2)} + + + ) : ( + <> + + + Price Went Down $ + {Math.abs(item.priceChanged).toFixed(2)} + + + )} + + +
+ )} + +
+ +
+
+
+ )} +
+ ))} +
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/matches/(components)/MatchDetailsDialog.js b/app/dashboard/(admin)/oms/matches/(components)/MatchDetailsDialog.js new file mode 100644 index 0000000..ecee0f2 --- /dev/null +++ b/app/dashboard/(admin)/oms/matches/(components)/MatchDetailsDialog.js @@ -0,0 +1,328 @@ +import React, { useState, useMemo, useEffect } from "react"; +import { useKeystone, useList } from "@keystone/keystoneProvider"; +import { Button } from "@ui/button"; +import { Fields } from "@keystone/themes/Tailwind/atlas/components/Fields"; +import { GraphQLErrorNotice } from "@keystone/themes/Tailwind/atlas/components/GraphQLErrorNotice"; +import { useCreateItem } from "@keystone/utils/useCreateItem"; +import { useQuery, gql, useApolloClient } from "@apollo/client"; +import { X, ChevronDown } from "lucide-react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { cn } from "@keystone/utils/cn"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@ui/select"; +import * as SelectPrimitive from "@radix-ui/react-select"; + +import { getFilteredProps } from "../../shops/(components)/CreateShop"; +import { LineItemSelect } from "../../shops/(components)/LineItemSelect"; +import { CartItemSelect } from "../../shops/(components)/CartItemSelect"; + +const SHOPS_QUERY = gql` + query GetShops { + shops { + id + name + } + } +`; + +const CHANNELS_QUERY = gql` + query GetChannels { + channels { + id + name + } + } +`; + +const SECTIONS = { + input: { + label: "Shop Products", + description: "Shop products in the match", + fields: ["input"], + component: "LineItemSelect", + }, + output: { + label: "Channel Products", + description: "Channel products in the match", + fields: ["output"], + component: "CartItemSelect", + }, +}; + +const MatchDialog = DialogPrimitive.Root; +const MatchDialogTrigger = DialogPrimitive.Trigger; + +const MatchDialogContent = React.forwardRef( + ({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + + ) +); +MatchDialogContent.displayName = DialogPrimitive.Content.displayName; + +export function MatchDetailsDialog({ isOpen, onClose, match, shopId }) { + const list = useList("Match"); + const client = useApolloClient(); + + const { create, createWithData, props, state, error } = useCreateItem(list); + const { createViewFieldModes } = useKeystone(); + const [isInitialized, setIsInitialized] = useState(false); + const [activeTab, setActiveTab] = useState("input"); + const [localState, setLocalState] = useState({ + lineItems: [], + cartItems: [], + }); + + const { data: shopsData } = useQuery(SHOPS_QUERY); + const { data: channelsData } = useQuery(CHANNELS_QUERY); + + const shops = shopsData?.shops || []; + const channels = channelsData?.channels || []; + + const filteredProps = useMemo(() => { + const modifications = Object.values(SECTIONS) + .flatMap((section) => section.fields) + .map((key) => ({ key })); + return getFilteredProps(props, modifications, true); + }, [props]); + + const handleSave = async () => { + const formData = {}; + + // Process lineItems + if (localState.lineItems.length > 0) { + formData.input = { + create: localState.lineItems.map((item) => ({ + quantity: parseInt(item.quantity), + productId: item.productId, + variantId: item.variantId, + shop: item.shop ? { connect: { id: item.shop.id } } : undefined, + })), + }; + } + + formData.output = { + create: localState.cartItems.map((item) => ({ + price: item.price, + quantity: parseInt(item.quantity), + productId: item.productId, + variantId: item.variantId, + channel: item.channel + ? { connect: { id: item.channel.id } } + : undefined, + })), + }; + + try { + const item = await createWithData({ data: formData }); + if (item) { + onClose(); + await client.refetchQueries({ + include: "active", + }); + } + } catch (error) { + console.error("Error creating match:", error); + } + }; + + useEffect(() => { + if (match && !isInitialized) { + props.onChange((oldValues) => { + const newValues = { ...oldValues }; + + // Set shop value first + if (shopId && newValues.shop) { + newValues.shop = { + ...oldValues.shop, + kind: "value", + value: { + id: null, + kind: "one", + value: { + id: shopId, + label: shops.find((shop) => shop.id === shopId)?.name || "", + data: { __typename: "Shop" }, + }, + initialValue: null, + }, + }; + } + + if (match) { + Object.keys(match).forEach((key) => { + if (newValues[key] && key !== "shop") { + newValues[key] = { + ...oldValues[key], + value: { + ...oldValues[key].value, + inner: { + ...oldValues[key].value.inner, + value: match[key], + }, + }, + }; + } + }); + } + + return newValues; + }); + setLocalState({ + lineItems: match.lineItems || [], + cartItems: match.cartItems || [], + }); + setIsInitialized(true); + } + }, [match, props, isInitialized]); + + return ( + + + + + +
+

Create Match

+

+ View and edit match information before creation +

+
+
+ {/* Mobile dropdown */} +
+ +
+ {/* Desktop sidebar */} +
+ {Object.entries(SECTIONS).map(([key, { label, description }]) => ( + + ))} +
+
+
+ {error && ( + + )} + {createViewFieldModes.state === "error" && ( + + )} + {createViewFieldModes.state === "loading" && ( +
Loading update form...
+ )} + {activeTab === "input" ? ( + + ) : activeTab === "output" ? ( + + ) : ( + ({ key })), + true + )} + // {...props} + /> + )} +
+
+
+
+ + +
+
+
+ ); +} diff --git a/app/dashboard/(admin)/oms/matches/(components)/MatchList.js b/app/dashboard/(admin)/oms/matches/(components)/MatchList.js new file mode 100644 index 0000000..edac20f --- /dev/null +++ b/app/dashboard/(admin)/oms/matches/(components)/MatchList.js @@ -0,0 +1,848 @@ +import React, { useState, useMemo } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { makeDataGetter } from "@keystone-6/core/admin-ui/utils"; +import { useList } from "@keystone/keystoneProvider"; +import { useFilter } from "@keystone/utils/useFilter"; +import { useFilters } from "@keystone/utils/useFilters"; +import { useSelectedFields } from "@keystone/utils/useSelectedFields"; +import { useSort } from "@keystone/utils/useSort"; +import { Link } from "next-view-transitions"; +import { + Search, + ArrowUpDown, + Filter, + PlusIcon, + SquareArrowRight, + Triangle, + Circle, + Square, + ChevronRight, + ChevronsUpDown, + AlertTriangle, + Box, + ArrowDown, + ArrowUp, + ArrowRight, + Check, + MoreVertical, + MoreHorizontal, +} from "lucide-react"; +import { FilterAdd } from "@keystone/themes/Tailwind/atlas/components/FilterAdd"; +import { FilterList } from "@keystone/themes/Tailwind/atlas/components/FilterList"; +import { SortSelection } from "@keystone/themes/Tailwind/atlas/components/SortSelection"; +import { + Pagination, + PaginationDropdown, + PaginationNavigation, + PaginationStats, +} from "@keystone/themes/Tailwind/atlas/components/Pagination"; +import { CreateButtonLink } from "@keystone/themes/Tailwind/atlas/components/CreateButtonLink"; +import { Input } from "@ui/input"; +import { Button } from "@ui/button"; +import { Badge, BadgeButton } from "@ui/badge"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from "@ui/breadcrumb"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@ui/collapsible"; +import { Separator } from "@ui/separator"; +import { LoadingIcon } from "@keystone/screens"; +import { + Dropdown, + DropdownButton, + DropdownMenu, + DropdownItem, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/dropdown-menu"; +import { useDrawer } from "@keystone/themes/Tailwind/atlas/components/Modals/drawer-context"; +import { useUpdateItem } from "@keystone/themes/Tailwind/atlas/components/EditItemDrawer"; +import { gql, useMutation, useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { MatchCard } from "./MatchCard"; + +const listMetaGraphqlQuery = gql` + query ($listKey: String!) { + keystone { + adminMeta { + list(key: $listKey) { + hideDelete + hideCreate + fields { + path + isOrderable + isFilterable + listView { + fieldMode + } + } + } + } + } + } +`; + +const UPDATE_SHOP_PRODUCT_MUTATION = gql` + mutation UpdateShopProduct( + $shopId: ID! + $variantId: ID! + $productId: ID! + $inventoryDelta: Int + ) { + updateShopProduct( + shopId: $shopId + variantId: $variantId + productId: $productId + inventoryDelta: $inventoryDelta + ) { + error + success + updatedVariant { + inventory + } + } + } +`; + +export const MatchList = ({ onMatchAction, showCreate }) => { + const listKey = "Match"; + const list = useList(listKey); + const { push } = useRouter(); + const searchParams = useSearchParams(); + + const query = Object.fromEntries(searchParams); + + const currentPage = parseInt(query.page) || 1; + const pageSize = parseInt(query.pageSize) || list.pageSize; + + const [searchString, setSearchString] = useState(query.search || ""); + + const metaQuery = useQuery(listMetaGraphqlQuery, { variables: { listKey } }); + + const { listViewFieldModesByField, filterableFields, orderableFields } = + useMemo(() => { + const listViewFieldModesByField = {}; + const orderableFields = new Set(); + const filterableFields = new Set(); + for (const field of metaQuery.data?.keystone.adminMeta.list?.fields || + []) { + listViewFieldModesByField[field.path] = field.listView.fieldMode; + if (field.isOrderable) orderableFields.add(field.path); + if (field.isFilterable) filterableFields.add(field.path); + } + return { listViewFieldModesByField, orderableFields, filterableFields }; + }, [metaQuery.data?.keystone.adminMeta.list?.fields]); + + const sort = useSort(list, orderableFields); + const filters = useFilters(list, filterableFields); + const searchFields = Object.keys(list.fields).filter( + (key) => list.fields[key].search + ); + const search = useFilter(searchString, list, searchFields); + + let selectedFields = useSelectedFields(list, listViewFieldModesByField); + + const { data, error, refetch } = useQuery( + useMemo(() => { + let selectedGqlFields = ` + id + input { + id + quantity + productId + variantId + lineItemId + externalDetails { + title + image + price + productLink + inventory + inventoryTracked + error + } + shop { + id + name + } + } + output { + id + quantity + productId + variantId + lineItemId + priceChanged + externalDetails { + title + image + price + productLink + inventory + inventoryTracked + error + } + price + channel { + id + name + } + } + createdAt + outputPriceChanged + inventoryNeedsToBeSynced { + syncEligible + sourceQuantity + targetQuantity + } + `; + + return gql` + query ($where: ${list.gqlNames.whereInputName}, $take: Int!, $skip: Int!, $orderBy: [${list.gqlNames.listOrderName}!]) { + items: ${list.gqlNames.listQueryName}(where: $where, take: $take, skip: $skip, orderBy: $orderBy) { + ${selectedGqlFields} + } + count: ${list.gqlNames.listQueryCountName}(where: $where) + } + `; + }, [list, selectedFields]), + { + fetchPolicy: "cache-and-network", + errorPolicy: "all", + variables: { + where: { ...filters.where, ...search }, + take: pageSize, + skip: (currentPage - 1) * pageSize, + orderBy: sort + ? [{ [sort.field]: sort.direction.toLowerCase() }] + : undefined, + }, + } + ); + + const dataGetter = makeDataGetter(data, error?.graphQLErrors); + + const handleSearch = (e) => { + e.preventDefault(); + const newSearchParams = new URLSearchParams(searchParams); + newSearchParams.set("search", searchString); + push(`?${newSearchParams.toString()}`); + }; + + const { + handleUpdate: updateChannelItem, + updateLoading: updateChannelItemLoading, + } = useUpdateItem("ChannelItem"); + const [updateShopProduct, { loading: updateShopProductLoading }] = + useMutation(UPDATE_SHOP_PRODUCT_MUTATION); + + const handleAcceptPriceChange = async (channelItemId, newPrice) => { + try { + await updateChannelItem(channelItemId, { price: newPrice }); + refetch(); + } catch (error) { + console.error("Error updating channel item price:", error); + } + }; + + const handleSyncInventory = async (match) => { + if (!match.inventoryNeedsToBeSynced?.syncEligible) { + console.error("Inventory sync is not eligible for this match"); + return; + } + + const input = match.input[0]; + const { shop, productId, variantId } = input; + const { sourceQuantity, targetQuantity } = match.inventoryNeedsToBeSynced; + const inventoryDelta = targetQuantity - sourceQuantity; + + try { + await updateShopProduct({ + variables: { + shopId: shop.id, + productId, + variantId, + inventoryDelta, + }, + }); + refetch(); + } catch (error) { + console.error("Error updating shop product inventory:", error); + } + }; + + const renderMatchList = () => { + if (!data) return
Loading...
; + if (error) return
Error: {error.message}
; + + return ( +
+ {dataGetter.get("items").data.map((match) => ( + + ))} +
+ ); + }; + + return ( + <> + {metaQuery.error ? ( + "Error..." + ) : metaQuery.data ? ( +
+ {/* + + + + Dashboard + + + + {list.label} + + +
+
+

{list.label}

+

+ {list.description || + `Create and manage ${list.label.toLowerCase()}`} +

+
+ {data?.count || searchString || filters.filters.length ? ( +
+ {showCreate && } +
+ ) : null} +
*/} +
+
+
+ +
+ setSearchString(e.target.value)} + placeholder={`Search by ${ + searchFields.length + ? searchFields.join(", ").toLowerCase() + : "ID" + }`} + /> +
+
+
+ +
+
+ + + { + const currentSearchParams = new URLSearchParams( + searchParams.toString() + ); + if (newSort) { + currentSearchParams.set( + "sortBy", + `${newSort.direction === "DESC" ? "-" : ""}${ + newSort.field + }` + ); + } else { + currentSearchParams.delete("sortBy"); + } + push(`?${currentSearchParams.toString()}`); + }} + dropdownTrigger={ + + } + /> + + + FILTER + + } + /> +
+
+ + {filters.filters.length > 0 && ( +
+
+ + + Filters + + +
+
+
+ +
+
+
+ )} + +
+ +
+ + {data?.items?.length ? ( + renderMatchList() + ) : ( +
+
+
+ + + +
+ {searchString || filters.filters.length ? ( + <> + + No {list.label}{" "} + + + Found{" "} + {searchString + ? `matching your search` + : `matching your filters`}{" "} + + + + ) : ( + <> + + No {list.label} + + + Get started by creating a new one.{" "} + + {showCreate && } + + )} +
+
+ )} +
+
+ ) : ( + + )} + + ); +}; + +// export const MatchCard = ({ +// match, +// onMatchAction, +// handleAcceptPriceChange, +// handleSyncInventory, +// updateChannelItemLoading, +// updateShopProductLoading, +// }) => { +// const { openEditDrawer } = useDrawer(); + +// return ( +//
+// handleSyncInventory(match)} +// updateShopProductLoading={updateShopProductLoading} +// > +// +// +// +// +// +//
+// ); +// }; + +// const MatchHeader = ({ +// match, +// children, +// defaultOpen = false, +// isLoading = false, +// openEditDrawer, +// handleSyncInventory, +// updateShopProductLoading, +// renderButtons, // Add this prop +// }) => { +// const [isOpen, setIsOpen] = useState(defaultOpen); +// const { inventoryNeedsToBeSynced } = match; + +// return ( +// +//
+// +// +// +//
+// {inventoryNeedsToBeSynced?.syncEligible && ( +// +// )} + +// {match.outputPriceChanged === true && ( +// +// )} +// {isLoading && ( +// +// )} + +// +//
+//
+// {children} +//
+// ); +// }; + +// const InventorySyncButton = ({ +// syncEligible, +// sourceQuantity, +// targetQuantity, +// onSyncInventory, +// isLoading, +// }) => { +// if (!syncEligible) return null; + +// if (sourceQuantity === targetQuantity) { +// return ( +// +// +// Inventory Synced +// +// ); +// } + +// return ( +// +// +// Inventory Needs Sync +// +// +// ); +// }; + +// const ProductDetailsCollapsible = ({ +// items, +// title, +// defaultOpen = true, +// openEditDrawer, +// onAcceptPriceChange, +// updateChannelItemLoading, +// }) => { +// const [isOpen, setIsOpen] = React.useState(defaultOpen); +// const isShopProduct = title === "Shop Product"; + +// return ( +// +// +// +// +// +// {items.map((item, index) => ( +//
+// {item.externalDetails.error ? ( +//
+// +// +// +//
+//
+// {item.shop?.name || item.channel?.name} +//
+//

+// {item.productId} | {item.variantId} +//

+//

+// QTY: {item.quantity} +//

+//
+//
+// ) : ( +//
+//
+// {item.externalDetails?.title} +//
+//
+// {item.shop?.name || item.channel?.name} +//
+// +// {item.externalDetails?.title || +// "Details could not be fetched"} +// +//

+// {item.productId} | {item.variantId} +//

+// {item.quantity > 1 ? ( +//
+//

+// $ +// {( +// parseFloat(item.externalDetails?.price) * +// item.quantity +// ).toFixed(2)} +//

+//

+// (${parseFloat(item.externalDetails?.price).toFixed(2)}{" "} +// x {item.quantity}) +// {item.price && +// item.price !== item.externalDetails?.price && ( +// +// (was ${parseFloat(item.price).toFixed(2)}) +// +// )} +//

+//
+// ) : ( +//

+// ${parseFloat(item.externalDetails?.price).toFixed(2)} +// {item.price && +// item.price !== item.externalDetails?.price && ( +// +// (was ${parseFloat(item.price).toFixed(2)}) +// +// )} +//

+// )} +//

+// INVENTORY:{" "} +// {item.externalDetails?.inventory !== null +// ? item.externalDetails?.inventory >= 1e9 +// ? `${(item.externalDetails?.inventory / 1e9).toFixed( +// 1 +// )}B` +// : item.externalDetails?.inventory >= 1e6 +// ? `${(item.externalDetails?.inventory / 1e6).toFixed( +// 1 +// )}M` +// : item.externalDetails?.inventory >= 1e3 +// ? `${(item.externalDetails?.inventory / 1e3).toFixed( +// 1 +// )}k` +// : item.externalDetails?.inventory.toString() +// : "N/A"} +//

+//
+//
+ +//
+// {!isShopProduct && item.priceChanged !== 0 && ( +//
+// 0 ? "red" : "green"} +// className="flex gap-2 items-center border text-xs font-medium tracking-wide uppercase py-0.5 shadow-xs" +// > +// {item.priceChanged > 0 ? ( +// <> +// +// +// Price Went Up $ +// {Math.abs(item.priceChanged).toFixed(2)} +// +// +// ) : ( +// <> +// +// +// Price Went Down $ +// {Math.abs(item.priceChanged).toFixed(2)} +// +// +// )} +// +// +//
+// )} + +//
+// +//
+//
+//
+// )} +//
+// ))} +//
+//
+// ); +// }; + +export default MatchList; diff --git a/app/dashboard/(admin)/oms/matches/(components)/ShowMatchesButton.js b/app/dashboard/(admin)/oms/matches/(components)/ShowMatchesButton.js new file mode 100644 index 0000000..eb4dae9 --- /dev/null +++ b/app/dashboard/(admin)/oms/matches/(components)/ShowMatchesButton.js @@ -0,0 +1,161 @@ +import React from "react"; +import { useQuery, gql } from "@apollo/client"; +import { Button } from "@ui/button"; +import { + Sheet, + SheetContent, + SheetTrigger, + SheetHeader, + SheetTitle, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/sheet"; +import { MatchCard } from "./MatchCard"; +import { Badge } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { Card } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/card"; + +const GET_MATCHES_COUNT = gql` + query GetMatchesCount($where: MatchWhereInput!) { + matchesCount(where: $where) + } +`; + +const GET_MATCHES = gql` + query GetMatches($where: MatchWhereInput!) { + matches(where: $where) { + id + input { + id + quantity + productId + variantId + externalDetails { + title + image + price + inventory + } + shop { + id + name + } + } + output { + id + quantity + productId + variantId + externalDetails { + title + image + price + inventory + } + channel { + id + name + } + priceChanged + } + outputPriceChanged + inventoryNeedsToBeSynced { + syncEligible + sourceQuantity + targetQuantity + } + } + } +`; + +export const ShowMatchesButton = ({ product, onMatchAction }) => { + const whereClause = { + AND: [ + { + input: { + some: { + productId: { equals: product.productId }, + variantId: { equals: product.variantId }, + }, + }, + }, + ], + }; + + const { data: countData, loading: countLoading } = useQuery( + GET_MATCHES_COUNT, + { + variables: { where: whereClause }, + } + ); + + const { + data: matchesData, + loading: matchesLoading, + refetch, + } = useQuery(GET_MATCHES, { + variables: { where: whereClause }, + }); + + const handleSheetOpen = () => { + refetch(); + }; + + return ( + + + + + + + Item Matches + +
+ {product.image && ( + {product.title} + )} +
+
{product.title}
+
+ {product.productId} | {product.variantId} +
+
${product.price}
+
+
+
+
+
+ {matchesData?.matches?.length} match + {matchesData?.matches?.length > 1 ? "es" : ""} found +
+
+ {matchesLoading ? ( +
Loading matches...
+ ) : matchesData?.matches?.length > 0 ? ( + matchesData.matches.map((match) => ( + + )) + ) : ( + <>No Matches Found + )} +
+
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/matches/(components)/SyncInventoryDialog.js b/app/dashboard/(admin)/oms/matches/(components)/SyncInventoryDialog.js new file mode 100644 index 0000000..e6777d9 --- /dev/null +++ b/app/dashboard/(admin)/oms/matches/(components)/SyncInventoryDialog.js @@ -0,0 +1,361 @@ +"use client"; + +import React, { useState, useMemo, useEffect } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogTrigger, +} from "@ui/dialog"; +import { Button, buttonVariants } from "@ui/button"; +import { Input } from "@ui/input"; +import { Badge, BadgeButton } from "@ui/badge"; +import { Plus, X, ChevronRight } from "lucide-react"; +import { cn } from "@keystone/utils/cn"; +import { RiLoader2Fill } from "@remixicon/react"; +import { + gql, + useApolloClient, + useMutation, + useQuery, +} from "@keystone-6/core/admin-ui/apollo"; +import { MatchCard } from "./MatchCard"; + +const FETCH_FILTERED_MATCHES_QUERY = gql` + query FETCH_FILTERED_MATCHES_QUERY { + getFilteredMatches { + id + input { + id + quantity + productId + variantId + lineItemId + externalDetails { + title + image + price + productLink + inventory + inventoryTracked + error + } + shop { + id + name + } + } + output { + id + quantity + productId + variantId + lineItemId + externalDetails { + title + image + price + productLink + inventory + inventoryTracked + error + } + price + priceChanged + channel { + id + name + } + } + createdAt + outputPriceChanged + inventoryNeedsToBeSynced { + syncEligible + sourceQuantity + targetQuantity + } + } + } +`; + +const UPDATE_SHOP_PRODUCT_MUTATION = gql` + mutation UpdateShopProduct( + $shopId: ID! + $variantId: ID! + $productId: ID! + $inventoryDelta: Int + ) { + updateShopProduct( + shopId: $shopId + variantId: $variantId + productId: $productId + inventoryDelta: $inventoryDelta + ) { + error + success + updatedVariant { + inventory + } + } + } +`; + +export const SyncInventoryDialog = () => { + const [isOpen, setIsOpen] = useState(false); + const [searchEntry, setSearchEntry] = useState(""); + const [selectedMatches, setSelectedMatches] = useState([]); + const [removedMatches, setRemovedMatches] = useState([]); + const [isSyncing, setIsSyncing] = useState(false); + const [syncingMatchIds, setSyncingMatchIds] = useState(new Set()); + + const { data, loading, error } = useQuery(FETCH_FILTERED_MATCHES_QUERY); + const [updateShopProduct] = useMutation(UPDATE_SHOP_PRODUCT_MUTATION); + const client = useApolloClient(); + + const filteredMatches = useMemo(() => { + if (!data?.getFilteredMatches) return []; + return data.getFilteredMatches.filter((match) => + match.input[0].externalDetails.title + .toLowerCase() + .includes(searchEntry.toLowerCase()) + ); + }, [data, searchEntry]); + + const syncableMatches = useMemo(() => { + return filteredMatches.filter( + (match) => match.inventoryNeedsToBeSynced.syncEligible + ); + }, [filteredMatches]); + + useEffect(() => { + if (data?.getFilteredMatches) { + setSelectedMatches(data.getFilteredMatches.map((match) => match.id)); + } + }, [data]); + + const handleRemoveMatch = (matchId) => { + setSelectedMatches((prev) => prev.filter((id) => id !== matchId)); + setRemovedMatches((prev) => [...prev, matchId]); + }; + + const handleAddMatch = (matchId) => { + setSelectedMatches((prev) => [...prev, matchId]); + setRemovedMatches((prev) => prev.filter((id) => id !== matchId)); + }; + + const handleSyncInventory = async () => { + setIsSyncing(true); + const syncingIds = new Set(syncingMatchIds); + + for (const match of filteredMatches) { + if (selectedMatches.includes(match.id)) { + const input = match.input[0]; + const output = match.output[0]; + syncingIds.add(match.id); + setSyncingMatchIds(new Set(syncingIds)); + + try { + await updateShopProduct({ + variables: { + shopId: input.shop.id, + variantId: input.variantId, + productId: input.productId, + inventoryDelta: + output.externalDetails.inventory - + input.externalDetails.inventory, + }, + }); + } catch (err) { + console.error( + `Error updating shop product for match ${match.id}:`, + err + ); + } + } + } + await client.refetchQueries({ + include: "active", + }); + setSyncingMatchIds(new Set()); + setIsSyncing(false); + setIsOpen(false); + }; + + const handleSearch = () => { + const searchResults = filteredMatches.map((match) => match.id); + setSelectedMatches(searchResults); + setRemovedMatches( + data.getFilteredMatches + .filter((match) => !searchResults.includes(match.id)) + .map((match) => match.id) + ); + }; + + return ( + + + + + + + Sync Inventory + +
+
+ setSearchEntry(e.target.value)} + onKeyPress={(e) => { + if (e.key === "Enter") { + handleSearch(); + } + }} + /> +
+ + Search + +
+
+
+
+
+ +
+
+ +
+
+
+ + Matches to Sync + +
+ + {selectedMatches.length} MATCH + {selectedMatches.length !== 1 ? "ES" : ""} + +
+
+
+
+
+
+ {filteredMatches + .filter((match) => selectedMatches.includes(match.id)) + .map((match) => ( +
+ ( +
+ handleRemoveMatch(match.id)} + className="border p-1.5 rounded-md shadow-sm" + > + + +
+ )} + updateShopProductLoading={syncingMatchIds.has(match.id)} + /> +
+ ))} +
+
+ +
+ +
+
+ +
+
+
+ + Removed Matches + +
+ + {removedMatches.length} MATCH + {removedMatches.length !== 1 ? "ES" : ""} + +
+
+
+
+
+
+ {filteredMatches + .filter((match) => removedMatches.includes(match.id)) + .map((match) => ( +
+ ( + handleAddMatch(match.id)} + className="border p-1.5 rounded-md shadow-sm" + > + + + )} + /> +
+ ))} +
+
+
+ + + + +
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/matches/page.js b/app/dashboard/(admin)/oms/matches/page.js new file mode 100644 index 0000000..41cb8ad --- /dev/null +++ b/app/dashboard/(admin)/oms/matches/page.js @@ -0,0 +1,456 @@ +"use client"; + +import React, { useState, useEffect, useMemo } from "react"; +import { useQuery, useMutation, gql, useApolloClient } from "@apollo/client"; +import { Input } from "@ui/input"; +import { Button } from "@ui/button"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs"; +import { Badge, BadgeButton } from "@ui/badge"; +import { Search, ArrowUpDown } from "lucide-react"; +import { Drawer, DrawerContent, DrawerTrigger } from "@ui/drawer"; +import { MultiSelect } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/multi-select"; +import { SyncInventoryDialog } from "./(components)/SyncInventoryDialog"; +import { MatchList } from "./(components)/MatchList"; +import { ShowMatchesButton } from "./(components)/ShowMatchesButton"; +import { MatchDetailsDialog } from "./(components)/MatchDetailsDialog"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/breadcrumb"; +import { Link } from "next-view-transitions"; +import { cn } from "@keystone/utils/cn"; +import { buttonVariants } from "@ui/button"; +import { ChevronRight } from "lucide-react"; +import { + CircleStackIcon, + Square2StackIcon, + Square3Stack3DIcon, +} from "@heroicons/react/16/solid"; + +const SEARCH_SHOP_PRODUCTS = gql` + query SearchShopProducts($shopId: ID!, $searchEntry: String) { + searchShopProducts(shopId: $shopId, searchEntry: $searchEntry) { + image + title + productId + variantId + price + availableForSale + productLink + inventory + inventoryTracked + } + } +`; + +const SEARCH_CHANNEL_PRODUCTS = gql` + query SearchChannelProducts($channelId: ID!, $searchEntry: String) { + searchChannelProducts(channelId: $channelId, searchEntry: $searchEntry) { + image + title + productId + variantId + price + availableForSale + productLink + inventory + inventoryTracked + } + } +`; + +const ALL_SHOPS_QUERY = gql` + query ALL_SHOPS_QUERY { + shops { + id + name + } + } +`; + +const ALL_CHANNELS_QUERY = gql` + query ALL_CHANNELS_QUERY { + channels { + id + name + } + } +`; + +const SYNC_INVENTORY = gql` + mutation SyncInventory($ids: [ID!]!) { + syncInventory(ids: $ids) { + id + } + } +`; + +const MatchesPage = () => { + const [searchString, setSearchString] = useState(""); + const [selectedTab, setSelectedTab] = useState("shop"); + const [selectedShopIds, setSelectedShopIds] = useState([]); + const [selectedChannelIds, setSelectedChannelIds] = useState([]); + const [selectedProduct, setSelectedProduct] = useState(null); + const [sortField, setSortField] = useState("title"); + const [sortDirection, setSortDirection] = useState("asc"); + + const { data: shopsData } = useQuery(ALL_SHOPS_QUERY); + const { data: channelsData } = useQuery(ALL_CHANNELS_QUERY); + + const [syncInventory] = useMutation(SYNC_INVENTORY); + const client = useApolloClient(); + + // Use useEffect to set all shops and channels as selected when data is loaded + useEffect(() => { + if (shopsData?.shops) { + setSelectedShopIds(shopsData.shops.map((shop) => shop.id)); + } + }, [shopsData]); + + useEffect(() => { + if (channelsData?.channels) { + setSelectedChannelIds(channelsData.channels.map((channel) => channel.id)); + } + }, [channelsData]); + + const handleSearch = (e) => { + if (e.key === "Enter") { + setSearchString(e.target.value); + } + }; + + const handleSort = (field) => { + if (field === sortField) { + setSortDirection(sortDirection === "asc" ? "desc" : "asc"); + } else { + setSortField(field); + setSortDirection("asc"); + } + }; + + const sortProducts = (products) => { + return [...products].sort((a, b) => { + if (a[sortField] < b[sortField]) return sortDirection === "asc" ? -1 : 1; + if (a[sortField] > b[sortField]) return sortDirection === "asc" ? 1 : -1; + return 0; + }); + }; + + const renderProductList = (products) => { + const sortedProducts = sortProducts(products); + return sortedProducts.map((product) => ( +
+ {product.image && ( + {product.title} + )} +
+
{product.title}
+
+ {product.productId} | {product.variantId} +
+
${product.price}
+
+ +
+ )); + }; + + const ShopSummary = ({ shopId }) => { + const [isOpen, setIsOpen] = useState(false); + const shop = shopsData?.shops.find((s) => s.id === shopId); + + const { data: shopProductsData, loading: shopProductsLoading } = useQuery( + SEARCH_SHOP_PRODUCTS, + { + variables: { shopId, searchEntry: searchString }, + skip: !isOpen, + } + ); + + const handleToggle = () => { + setIsOpen(!isOpen); + }; + + return ( +
+ { + e.preventDefault(); + handleToggle(); + }} + className="list-none outline-none cursor-pointer" + > +
+
+ +
+
+ + {shop?.name} + +
+
+
+ {isOpen && ( +
+ {shopProductsLoading ? ( +
Loading...
+ ) : shopProductsData?.searchShopProducts?.length > 0 ? ( + renderProductList(shopProductsData.searchShopProducts) + ) : ( +
+ + No Products Found + +
+ )} +
+ )} +
+ ); + }; + + const ChannelSummary = ({ channelId }) => { + const [isOpen, setIsOpen] = useState(false); + const channel = channelsData?.channels.find((c) => c.id === channelId); + + const { data: channelProductsData, loading: channelProductsLoading } = + useQuery(SEARCH_CHANNEL_PRODUCTS, { + variables: { channelId, searchEntry: searchString }, + skip: !isOpen, + }); + + const handleToggle = () => { + setIsOpen(!isOpen); + }; + + return ( +
+ { + e.preventDefault(); + handleToggle(); + }} + className="list-none outline-none cursor-pointer" + > +
+
+ +
+ +
+ + {channel?.name} + +
+
+
+ {isOpen && ( +
+ {channelProductsLoading ? ( +
Loading...
+ ) : channelProductsData?.searchChannelProducts?.length > 0 ? ( + renderProductList(channelProductsData.searchChannelProducts) + ) : ( +
+ + No Products Found + +
+ )} +
+ )} +
+ ); + }; + + const handleMatchAction = async (action, matchId) => { + try { + switch (action) { + case "sync": + await syncInventory({ variables: { ids: [matchId] } }); + // Refetch queries to update the UI + await client.refetchQueries({ + include: [SEARCH_SHOP_PRODUCTS, SEARCH_CHANNEL_PRODUCTS], + }); + break; + case "edit": + // Implement edit logic here + console.log("Edit match:", matchId); + break; + case "delete": + // Implement delete logic here + console.log("Delete match:", matchId); + break; + default: + console.log("Unknown action:", action); + } + } catch (error) { + console.error(`Error performing ${action}:`, error); + } + }; + + return ( +
+ + + + + Dashboard + + + + Matches + + + +
+
+

Matches

+

+ Manage and sync inventory matches across shops and channels +

+
+
+ + +
+
+ + + + + + Shop Products + + + + Channel Products + + + + Matches + + + +
+
+ + setSearchString(e.target.value)} + /> +
+
+ ({ + label: shop.name, + value: shop.id, + })) || [] + } + onValueChange={setSelectedShopIds} + defaultValue={selectedShopIds} + placeholder={ + + Select shop... + + } + className="text-base mb-4 bg-background" + /> + +
+ {selectedShopIds.map((shopId) => ( + + ))} +
+
+ +
+
+ + setSearchString(e.target.value)} + /> +
+
+ ({ + label: channel.name, + value: channel.id, + })) || [] + } + onValueChange={setSelectedChannelIds} + defaultValue={selectedChannelIds} + placeholder={ + + Select channel... + + } + className="text-base mb-4" + /> + +
+ {selectedChannelIds.map((channelId) => ( + + ))} +
+
+ + + +
+
+ ); +}; + +export default MatchesPage; diff --git a/app/dashboard/(admin)/oms/orders/(components)/CartItem.js b/app/dashboard/(admin)/oms/orders/(components)/CartItem.js new file mode 100644 index 0000000..f3b7ab9 --- /dev/null +++ b/app/dashboard/(admin)/oms/orders/(components)/CartItem.js @@ -0,0 +1,200 @@ +import React, { useState } from "react"; +import { + useDeleteItem, + useUpdateItem, +} from "@keystone/themes/Tailwind/atlas/components/EditItemDrawer"; +import { + Badge, + BadgeButton, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; +import { + ChevronLeft, + ChevronRight, + X, + ArrowRight, + MoreHorizontal, + AlertTriangle, +} from "lucide-react"; +import { RiLoader2Fill } from "@remixicon/react"; + +export const CartItem = ({ + item, + isCartItem, + openEditDrawer, + removeEditItemButton, +}) => { + const { handleDelete, deleteLoading } = useDeleteItem( + isCartItem ? "CartItem" : "LineItem" + ); + const { handleUpdate, updateLoading } = useUpdateItem( + isCartItem ? "CartItem" : "LineItem" + ); + const [quantity, setQuantity] = useState(item.quantity); + + const handleQuantityChange = async (newQuantity) => { + setQuantity(newQuantity); + await handleUpdate(item.id, { quantity: newQuantity }); + }; + + const handleDeleteItem = async () => { + await handleDelete(item.id); + }; + + const handleAcceptError = async () => { + await handleUpdate(item.id, { + error: "", + }); + }; + + return ( +
+ {item.image && ( + {item.name} + )} +
+
+ {item.channel?.name} +
+ {item.name} +
+ {item.productId} | {item.variantId} +
+ {quantity > 1 ? ( +
+

+ ${(parseFloat(item.price) * quantity).toFixed(2)} +

+

+ (${parseFloat(item.price).toFixed(2)} x {quantity}) +

+
+ ) : ( +

+ ${parseFloat(item.price).toFixed(2)} +

+ )} +
+
+
+ {isCartItem && !item.purchaseId && ( + <> +
+ {(updateLoading || deleteLoading) && ( +
+ + + + + )} + {item.purchaseId && ( + + {item.purchaseId} + + )} + {!removeEditItemButton && ( + + )} +
+ {item.error && ( + +
+ + Error: {item.error} +
+ +
+ )} + + {isCartItem && item.url && ( +
+ +
+ )} +
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/orders/(components)/ChannelSearchAccordion.js b/app/dashboard/(admin)/oms/orders/(components)/ChannelSearchAccordion.js new file mode 100644 index 0000000..ac3cda6 --- /dev/null +++ b/app/dashboard/(admin)/oms/orders/(components)/ChannelSearchAccordion.js @@ -0,0 +1,230 @@ +// components/ChannelSearchAccordion.js +import React, { useState } from "react"; +import { gql, useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { + Collapsible, + CollapsibleTrigger, + CollapsibleContent, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/collapsible"; +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/select"; +import { Input } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/input"; +import { BadgeButton } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { ChevronsUpDown, ChevronLeft, ChevronRight, Plus } from "lucide-react"; + +const SEARCH_CHANNEL_PRODUCTS = gql` + query SearchChannelProducts($channelId: ID!, $searchEntry: String) { + searchChannelProducts(channelId: $channelId, searchEntry: $searchEntry) { + image + title + productId + variantId + price + availableForSale + productLink + inventory + inventoryTracked + error + } + } +`; + +export const ChannelSearchAccordion = ({ channels, onAddItem }) => { + const [searchEntry, setSearchEntry] = useState(""); + const [selectedChannelId, setSelectedChannelId] = useState(""); + const [isOpen, setIsOpen] = useState(false); + const [quantities, setQuantities] = useState({}); + const [localQuantities, setLocalQuantities] = useState({}); + + const { data, loading, error } = useQuery(SEARCH_CHANNEL_PRODUCTS, { + variables: { channelId: selectedChannelId, searchEntry }, + skip: !selectedChannelId || !searchEntry, + }); + + const handleSearch = (e) => { + if (e.key === "Enter") { + setSearchEntry(e.target.value); + } + }; + + const handleQuantityChange = (productId, newQuantity) => { + setQuantities({ ...quantities, [productId]: newQuantity }); + setLocalQuantities({ ...localQuantities, [productId]: newQuantity }); + }; + + const handleQuantityBlur = (productId) => { + const quantity = localQuantities[productId] || quantities[productId] || 1; + setQuantities({ ...quantities, [productId]: Math.max(1, quantity) }); + setLocalQuantities({ + ...localQuantities, + [productId]: Math.max(1, quantity), + }); + }; + + const handleQuantityKeyDown = (e, productId) => { + if (e.key === "Enter") { + handleQuantityBlur(productId); + } + }; + + const handleAddItem = (product) => { + const quantity = quantities[product.productId] || 1; + onAddItem({ ...product, quantity }, selectedChannelId); + setQuantities({ ...quantities, [product.productId]: 1 }); + setLocalQuantities({ ...localQuantities, [product.productId]: 1 }); + }; + + return ( + + + + + + + setSearchEntry(e.target.value)} + onKeyPress={handleSearch} + /> + {loading &&
Loading...
} + {error &&
Error: {error.message}
} + {data?.searchChannelProducts && ( +
+ {data.searchChannelProducts.map((product) => ( +
+
+ {product.image && ( + {product.title} + )} +
+
+
+ {product.title} +
+
+ {product.productId} | {product.variantId} +
+ {(quantities[product.productId] || 1) > 1 ? ( +
+

+ $ + {( + parseFloat(product.price) * + (quantities[product.productId] || 1) + ).toFixed(2)} +

+

+ (${parseFloat(product.price).toFixed(2)} x{" "} + {quantities[product.productId] || 1}) +

+
+ ) : ( +

+ ${parseFloat(product.price).toFixed(2)} +

+ )} +
+
+
+ + handleQuantityChange( + product.productId, + Math.max(1, (quantities[product.productId] || 1) - 1) + ) + } + className="border px-1" + > + + + + setLocalQuantities({ + ...localQuantities, + [product.productId]: e.target.value, + }) + } + onBlur={() => handleQuantityBlur(product.productId)} + onKeyDown={(e) => + handleQuantityKeyDown(e, product.productId) + } + /> + + handleQuantityChange( + product.productId, + (quantities[product.productId] || 1) + 1 + ) + } + className="border px-1" + > + + +
+ handleAddItem(product)} + color="indigo" + className="border px-1" + > + + +
+
+ ))} +
+ )} +
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/orders/(components)/ItemPagination.js b/app/dashboard/(admin)/oms/orders/(components)/ItemPagination.js new file mode 100644 index 0000000..991d641 --- /dev/null +++ b/app/dashboard/(admin)/oms/orders/(components)/ItemPagination.js @@ -0,0 +1,99 @@ +import React, { useState } from "react"; +import { ArrowLeft, ArrowRight } from "lucide-react"; + +export function ItemPagination({ + currentPage, + totalItems, + itemsPerPage, + onPageChange, + isCartItem, +}) { + const [currentPageInput, setCurrentPageInput] = useState(currentPage.toString()); + const totalPages = Math.ceil(totalItems / itemsPerPage); + + if (totalItems <= 5) { + return null; + } + + const handlePageChange = (newPage) => { + const page = Math.max(1, Math.min(totalPages, Number(newPage))); + onPageChange(page); + setCurrentPageInput(page.toString()); + }; + + const handleInputChange = (e) => { + const newValue = e.target.value; + if (newValue === "" || /^\d+$/.test(newValue)) { + setCurrentPageInput(newValue); + } + }; + + const handleInputBlur = () => { + if (currentPageInput === "") { + setCurrentPageInput(currentPage.toString()); + } else { + handlePageChange(currentPageInput); + } + }; + + const colorClasses = isCartItem + ? "bg-white border-emerald-200 text-emerald-500 hover:bg-emerald-100 hover:text-emerald-700 focus:ring-emerald-700 dark:bg-emerald-950 dark:border-emerald-900 dark:text-emerald-300 dark:hover:text-white dark:hover:bg-emerald-700 dark:focus:ring-emerald-500" + : "bg-white border-blue-200 text-blue-500 hover:bg-blue-100 hover:text-blue-700 focus:ring-blue-700 dark:bg-blue-950 dark:border-blue-900 dark:text-blue-300 dark:hover:text-white dark:hover:bg-blue-700 dark:focus:ring-blue-500"; + + return ( +
+ +
+ { + if (e.key === "Enter") { + handleInputBlur(); + } + }} + /> + + / {totalPages} + +
+ +
+ ); +} + +export function ItemPaginationStats({ currentPage, totalItems, itemsPerPage }) { + const start = (currentPage - 1) * itemsPerPage + 1; + const end = Math.min(currentPage * itemsPerPage, totalItems); + + return ( +
+ Showing {start} - {end} of {totalItems} items +
+ ); +} diff --git a/app/dashboard/(admin)/oms/orders/(components)/OrderDetailsComponent.js b/app/dashboard/(admin)/oms/orders/(components)/OrderDetailsComponent.js new file mode 100644 index 0000000..1ac6e8a --- /dev/null +++ b/app/dashboard/(admin)/oms/orders/(components)/OrderDetailsComponent.js @@ -0,0 +1,252 @@ +import React from "react"; +import { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/accordion"; +import { Badge } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { + Dropdown, + DropdownButton, + DropdownMenu, + DropdownItem, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/dropdown-menu"; +import { RiLoader2Fill } from "@remixicon/react"; +import { ProductDetailsCollapsible } from "./ProductDetailsCollapsible"; +import { ChannelSearchAccordion } from "./ChannelSearchAccordion"; +import { + PencilSquareIcon, + Square2StackIcon, + TicketIcon, + TrashIcon, +} from "@heroicons/react/16/solid"; +import { ChevronDown, MoreVertical, SaveIcon } from "lucide-react"; +import { + useDeleteItem, + useUpdateItem, +} from "@keystone/themes/Tailwind/atlas/components/EditItemDrawer"; +import { AlertTriangle } from "lucide-react"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; +import { DeleteButton } from "@keystone/themes/Tailwind/atlas/components/EditItemDrawer"; +import { useList } from "@keystone/keystoneProvider"; +import { useApolloClient } from "@keystone-6/core/admin-ui/apollo"; + +export const OrderDetailsComponent = ({ + order, + shopId, + onOrderAction, + openEditDrawer, + channels, + loadingActions, + removeEditItemButton, + renderButtons +}) => { + const list = useList('Order'); + const client = useApolloClient(); + + const handleAddToCart = (product, channelId) => { + onOrderAction("addToCart", order.id, { + channelId, + image: product.image, + name: product.title, + price: product.price, + productId: product.productId, + variantId: product.variantId, + quantity: product.quantity || 1, + }); + }; + + const { handleUpdate, updateLoading } = useUpdateItem("Order"); + + const handleAcceptError = async () => { + await handleUpdate(order.id, { + orderError: "", + }); + }; + + const orderButtons = [ + { + buttonText: "GET MATCH", + color: "green", + icon: , + onClick: () => onOrderAction("getMatch", order.id), + }, + { + buttonText: "SAVE MATCH", + color: "teal", + icon: , + onClick: () => onOrderAction("saveMatch", order.id), + }, + { + buttonText: "PLACE ORDER", + color: "cyan", + icon: , + onClick: () => onOrderAction("placeOrder", order.id), + }, + { + buttonText: "EDIT ORDER", + color: "blue", + icon: , + onClick: () => openEditDrawer(order.id, "Order"), + }, + // "DELETE ORDER" button removed from here + ]; + + const currentAction = Object.entries(loadingActions).find( + ([_, value]) => value[order.id] + )?.[0]; + + const getLoadingText = (action) => { + switch (action) { + case "getMatch": + return "Getting Match"; + case "saveMatch": + return "Saving Match"; + case "placeOrder": + return "Placing Order"; + case "deleteOrder": + return "Deleting Order"; + case "addToCart": + return "Adding to cart"; + default: + return "Loading"; + } + }; + + const handleDeleteComplete = async () => { + await client.refetchQueries({ include: "active" }); + // Add any additional logic needed after deletion + }; + + return ( + + +
+
+
+ + {order.orderName} + + {/* {order.readyToProcess && ( + + {order.readyToProcess} + + )} */} + + {order.date} + + + + {order.shop?.name} + +
+
+

+ {order.firstName} {order.lastName} +

+

{order.streetAddress1}

+ {order.streetAddress2 &&

{order.streetAddress2}

} +

+ {order.city}, {order.state} {order.zip} +

+
+
+
+
+ {currentAction && ( + + + {getLoadingText(currentAction)} + + )} + {!removeEditItemButton && ( + + + + + + {orderButtons.map((button) => ( + + {button.icon} + {button.buttonText} + + ))} + {/* Manually add the DELETE ORDER button at the bottom */} + {/* + + + DELETE ORDER + + */} + + + )} + + + + + + {renderButtons && renderButtons()} + +
+ {order.orderError && ( + + + Error: {order.orderError} + + + )} +
+
+ +
+ + +
+ +
+
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/orders/(components)/ProcessOrdersDialog.js b/app/dashboard/(admin)/oms/orders/(components)/ProcessOrdersDialog.js new file mode 100644 index 0000000..fa40e85 --- /dev/null +++ b/app/dashboard/(admin)/oms/orders/(components)/ProcessOrdersDialog.js @@ -0,0 +1,246 @@ +import React, { useState, useMemo, useEffect } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@ui/dialog"; +import { Button, buttonVariants } from "@ui/button"; +import { Input } from "@ui/input"; +import { Badge, BadgeButton } from "@ui/badge"; +import { Plus, X, ChevronRight, ChevronDown } from "lucide-react"; +import { OrderDetailsComponent } from "./OrderDetailsComponent"; +import { cn } from "@keystone/utils/cn"; +import { RiLoader2Fill } from "@remixicon/react"; + +export const ProcessOrdersDialog = ({ + isOpen, + onClose, + orders, + onProcessOrders, + processingOrders, +}) => { + const [searchEntry, setSearchEntry] = useState(""); + const [selectedOrders, setSelectedOrders] = useState([]); + const [removedOrders, setRemovedOrders] = useState([]); + + const ordersWithCart = orders; + + useEffect(() => { + setSelectedOrders(ordersWithCart.map((order) => order.id)); + }, [ordersWithCart]); + + const filteredOrders = useMemo(() => { + return ordersWithCart.filter((order) => + order.orderName.toLowerCase().includes(searchEntry.toLowerCase()) + ); + }, [ordersWithCart, searchEntry]); + + const handleRemoveOrder = (orderId) => { + setSelectedOrders((prev) => prev.filter((id) => id !== orderId)); + setRemovedOrders((prev) => [...prev, orderId]); + }; + + const handleAddOrder = (orderId) => { + setSelectedOrders((prev) => [...prev, orderId]); + setRemovedOrders((prev) => prev.filter((id) => id !== orderId)); + }; + + const handleProcessOrders = () => { + onProcessOrders(selectedOrders); + }; + + const handleSearch = () => { + const searchResults = ordersWithCart.filter((order) => + order.orderName.toLowerCase().includes(searchEntry.toLowerCase()) + ); + setSelectedOrders(searchResults.map((order) => order.id)); + setRemovedOrders( + ordersWithCart + .filter((order) => !searchResults.includes(order)) + .map((order) => order.id) + ); + }; + + return ( + + + + Process Orders + +
+
+ setSearchEntry(e.target.value)} + onKeyPress={(e) => { + if (e.key === "Enter") { + handleSearch(); + } + }} + /> +
+ + Search + +
+
+
+
+
+ +
+
+ +
+
+
+ + Orders to be Processed + +
+ + {selectedOrders.length} ORDER + {selectedOrders.length !== 1 ? "S" : ""} + +
+
+
+
+
+
+ {filteredOrders.map( + (order) => + selectedOrders.includes(order.id) && ( +
+ {}} + openEditDrawer={() => {}} + channels={[]} + loadingActions={{}} + isProcessDialog={true} + removeEditItemButton + renderButtons={() => ( +
+ {processingOrders.includes(order.id) && ( + + + Processing + + )} + handleRemoveOrder(order.id)} + className="border p-1" + > + + +
+ )} + /> +
+ ) + )} +
+
+ +
+ +
+
+ +
+
+
+ + Removed Orders + +
+ + {removedOrders.length} ORDER + {removedOrders.length !== 1 ? "S" : ""} + +
+
+
+
+
+
+ {ordersWithCart + .filter((order) => removedOrders.includes(order.id)) + .map((order) => ( +
+ {}} + openEditDrawer={() => {}} + channels={[]} + loadingActions={{}} + isProcessDialog={true} + removeEditItemButton + renderButtons={() => ( + handleAddOrder(order.id)} + className="border p-1" + > + + + )} + /> +
+ ))} +
+
+
+ + + + +
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/orders/(components)/ProductDetailsCollapsible.js b/app/dashboard/(admin)/oms/orders/(components)/ProductDetailsCollapsible.js new file mode 100644 index 0000000..968550c --- /dev/null +++ b/app/dashboard/(admin)/oms/orders/(components)/ProductDetailsCollapsible.js @@ -0,0 +1,181 @@ +// components/ProductDetailsCollapsible.js +import React, { useState } from "react"; +import { useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { + Collapsible, + CollapsibleTrigger, + CollapsibleContent, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/collapsible"; + +import { + ChevronsUpDown, + ChevronLeft, + ChevronRight, + X, + ArrowRight, + MoreHorizontal, +} from "lucide-react"; + +import { ItemPagination, ItemPaginationStats } from "./ItemPagination"; +import { gql } from "@keystone-6/core/admin-ui/apollo"; +import { Skeleton } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/skeleton"; + +import { CartItem } from "./CartItem"; + +export const CART_ITEMS_QUERY = gql` + query CART_ITEMS_QUERY($orderId: ID!, $take: Int!, $skip: Int!) { + cartItems( + where: { order: { id: { equals: $orderId } } } + take: $take + skip: $skip + orderBy: [{ updatedAt: desc }] + ) { + id + name + image + price + quantity + productId + variantId + purchaseId + url + error + channel { + id + name + } + } + cartItemsCount(where: { order: { id: { equals: $orderId } } }) + } +`; + +export const LINE_ITEMS_QUERY = gql` + query LINE_ITEMS_QUERY($orderId: ID!, $take: Int!, $skip: Int!) { + lineItems( + where: { order: { id: { equals: $orderId } } } + take: $take + skip: $skip + orderBy: [{ updatedAt: desc }] + ) { + id + name + quantity + price + image + productId + variantId + } + lineItemsCount(where: { order: { id: { equals: $orderId } } }) + } +`; + +export const ProductDetailsCollapsible = ({ + orderId, + title, + defaultOpen = true, + openEditDrawer, + removeEditItemButton, + totalItems, +}) => { + const [isOpen, setIsOpen] = useState(defaultOpen); + const [currentPage, setCurrentPage] = useState(1); + const isCartItem = title === "Cart Item"; + const itemsPerPage = 5; + + const query = isCartItem ? CART_ITEMS_QUERY : LINE_ITEMS_QUERY; + const { data, loading, error, refetch } = useQuery(query, { + variables: { + orderId, + take: itemsPerPage, + skip: (currentPage - 1) * itemsPerPage, + }, + skip: !isOpen, + }); + + const items = isCartItem ? data?.cartItems : data?.lineItems; + const itemsCount = isCartItem ? data?.cartItemsCount : data?.lineItemsCount; + + const triggerClassName = `flex items-center rounded-sm shadow-sm uppercase tracking-wide border max-w-fit gap-2 text-nowrap pl-2.5 pr-1 py-[3px] text-sm font-medium ${ + isCartItem + ? "text-emerald-500 bg-white border-emerald-200 hover:bg-emerald-100 hover:text-emerald-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-emerald-700 dark:bg-emerald-950 dark:border-emerald-900 dark:text-emerald-300 dark:hover:text-white dark:hover:bg-emerald-700 dark:focus:ring-blue-500 dark:focus:text-white" + : "text-blue-500 bg-white border-blue-200 hover:bg-blue-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-blue-950 dark:border-blue-900 dark:text-blue-300 dark:hover:text-white dark:hover:bg-blue-700 dark:focus:ring-blue-500 dark:focus:text-white" + }`; + + const renderSkeletonItem = () => ( +
+ +
+ + + +
+
+ + +
+
+ ); + + return ( + +
+ + + + {isOpen && totalItems > 5 && ( + + )} +
+ + {isOpen && ( + <> + {totalItems > 5 && ( + + )} + {loading ? ( + Array.from({ length: itemsPerPage }).map((_, index) => ( +
{renderSkeletonItem()}
+ )) + ) : error ? ( +
+ Error loading items: {error.message} +
+ ) : ( + items?.map((item, index) => ( + + )) + )} + + )} +
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/orders/(components)/StatusBadge.js b/app/dashboard/(admin)/oms/orders/(components)/StatusBadge.js new file mode 100644 index 0000000..6f383ad --- /dev/null +++ b/app/dashboard/(admin)/oms/orders/(components)/StatusBadge.js @@ -0,0 +1,20 @@ +// components/StatusBadge.js +import React from 'react'; +import { Badge } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; + +export const StatusBadge = ({ status, selectedStatus, count, onClick }) => { + return ( + onClick(status)} + > + {status} + + {count} + + + ); +}; \ No newline at end of file diff --git a/app/dashboard/(admin)/oms/orders/(components)/StatusShopFilter.js b/app/dashboard/(admin)/oms/orders/(components)/StatusShopFilter.js new file mode 100644 index 0000000..c7f9e40 --- /dev/null +++ b/app/dashboard/(admin)/oms/orders/(components)/StatusShopFilter.js @@ -0,0 +1,176 @@ +// components/StatusShopFilter.js +import React, { useEffect } from "react"; +import { useRouter, usePathname, useSearchParams } from "next/navigation"; +import { Badge } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { StatusBadge } from "./StatusBadge"; +import { RelationshipSelect } from "@keystone/themes/Tailwind/atlas/components/RelationshipSelect"; +import { useList } from "@keystone/keystoneProvider"; +import { gql, useQuery } from "@keystone-6/core/admin-ui/apollo"; + +export const StatusShopFilter = ({ statuses, orderCounts }) => { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const selectedStatus = searchParams + .get("!status_is_i") + ?.replace(/^"|"$/g, ""); + const selectedShop = + searchParams.get("!shop_matches")?.replace(/^"|"$/g, "") || "ALL"; + + useEffect(() => { + const params = new URLSearchParams(searchParams); + if (!params.get("!status_is_i")) { + params.set("!status_is_i", `"PENDING"`); + router.push(`${pathname}?${params.toString()}`, { shallow: true }); + } + }, []); + + const handleStatusChange = (status) => { + const params = new URLSearchParams(searchParams); + if (status === selectedStatus) { + params.delete("!status_is_i"); + } else { + params.set("!status_is_i", `"${status}"`); + } + router.push(`${pathname}?${params.toString()}`); + }; + + const handleShopChange = (newValue) => { + const params = new URLSearchParams(searchParams); + if (newValue !== "ALL") { + params.set("!shop_matches", `"${newValue}"`); + } else { + params.delete("!shop_matches"); + } + router.push(`${pathname}?${params.toString()}`); + }; + + const foreignList = useList("Shop"); + + // const shopSelectState = { + // kind: "many", + // value: selectedShop === "ALL" ? [] : [{ id: selectedShop }], + // onChange: (newItems) => { + // handleShopChange(newItems.length > 0 ? newItems[0].id : "ALL"); + // }, + // }; + + const { filterValues, loading } = useRelationshipFilterValues({ + value: searchParams.get("!shop_matches")?.replace(/^"|"$/g, ""), + list: foreignList, + }); + const state = { + kind: "many", + value: filterValues, + onChange: (newItems) => { + const params = new URLSearchParams(searchParams); + if (newItems.length > 0) { + params.set("!shop_matches", `"${newItems.map((item) => item.id).join(",")}"`); + } else { + params.delete("!shop_matches"); + } + router.push(`${pathname}?${params.toString()}`); + }, + }; + + return ( +
+
+

+ Status +

+
+ {statuses.map((status) => ( + + ))} +
+
+
+

+ Filter by shop +

+ {/*
+ handleShopChange("ALL")} + > + All Shops + + {shops.map((shop) => ( + handleShopChange(shop.id)} + > + {shop.name} + + ))} +
*/} + +
+
+ ); +}; + +function useRelationshipFilterValues({ value, list }) { + const foreignIds = getForeignIds(value); + const where = { id: { in: foreignIds } }; + + const query = gql` + query FOREIGNLIST_QUERY($where: ${list.gqlNames.whereInputName}!) { + items: ${list.gqlNames.listQueryName}(where: $where) { + id + ${list.labelField} + } + } + `; + + const { data, loading } = useQuery(query, { + variables: { + where, + }, + }); + + return { + filterValues: + data?.items?.map((item) => { + return { + id: item.id, + label: item[list.labelField] || item.id, + }; + }) || foreignIds.map((f) => ({ label: f, id: f })), + loading: loading, + }; +} + +function getForeignIds(value) { + if (typeof value === "string" && value.length > 0) { + return value.split(","); + } + return []; +} diff --git a/app/dashboard/(admin)/oms/orders/page.js b/app/dashboard/(admin)/oms/orders/page.js new file mode 100644 index 0000000..ec1a676 --- /dev/null +++ b/app/dashboard/(admin)/oms/orders/page.js @@ -0,0 +1,803 @@ +"use client"; +import React, { useState, useEffect, useMemo } from "react"; +import { useRouter, usePathname, useSearchParams } from "next/navigation"; +import { + useQuery, + useMutation, + useApolloClient, + gql, +} from "@keystone-6/core/admin-ui/apollo"; +import { makeDataGetter } from "@keystone-6/core/admin-ui/utils"; +import { useList } from "@keystone/keystoneProvider"; +import { useFilter } from "@keystone/utils/useFilter"; +import { useFilters } from "@keystone/utils/useFilters"; +import { useQueryParamsFromLocalStorage } from "@keystone/utils/useQueryParamsFromLocalStorage"; +import { useSelectedFields } from "@keystone/utils/useSelectedFields"; +import { useSort } from "@keystone/utils/useSort"; +import { Link } from "next-view-transitions"; +import { useToasts } from "@keystone/screens"; +import { useDrawer } from "@keystone/themes/Tailwind/atlas/components/Modals/drawer-context"; +import { OrderDetailsComponent } from "./(components)/OrderDetailsComponent"; +import { StatusShopFilter } from "./(components)/StatusShopFilter"; +import { ProcessOrdersDialog } from "./(components)/ProcessOrdersDialog"; +import { OrderDetailsDialog } from "../shops/(components)/OrderDetailsDialog"; +import { SearchOrders } from "../shops/(components)/SearchOrders"; +import { CreateButtonLink } from "@keystone/themes/Tailwind/atlas/components/CreateButtonLink"; +import { FilterAdd } from "@keystone/themes/Tailwind/atlas/components/FilterAdd"; +import { FilterList } from "@keystone/themes/Tailwind/atlas/components/FilterList"; +import { SortSelection } from "@keystone/themes/Tailwind/atlas/components/SortSelection"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; +import { LoadingIcon } from "@keystone/themes/Tailwind/atlas/components/LoadingIcon"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/breadcrumb"; +import { + Pagination, + PaginationDropdown, + PaginationNavigation, + PaginationStats, +} from "@keystone/themes/Tailwind/atlas/components/Pagination"; +import { Input } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/input"; +import { + Badge, + BadgeButton, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@ui/dialog"; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/tabs"; +import { + ArrowUpDown, + Circle, + Search, + Square, + SquareArrowRight, + Triangle, + PlusCircleIcon, + PlusIcon as PlusIcon2, + ChevronDown, + SearchIcon, + Filter, + ArrowRight, + ChevronRight, +} from "lucide-react"; +import { + Dropdown, + DropdownButton, + DropdownMenu, + DropdownItem, +} from "@ui/dropdown-menu"; +import { + ArrowPathRoundedSquareIcon, + PlusIcon, +} from "@heroicons/react/16/solid"; + +const PLACE_ORDERS = gql` + mutation PLACE_ORDERS($ids: [ID!]!) { + placeOrders(ids: $ids) { + orderId + } + } +`; + +const ADDMATCHTOCART_MUTATION = gql` + mutation ADDMATCHTOCART_MUTATION($orderId: ID!) { + addMatchToCart(orderId: $orderId) { + id + } + } +`; + +const ADD_TO_CART_MUTATION = gql` + mutation ADD_TO_CART_MUTATION( + $channelId: ID + $image: String + $name: String + $price: String + $productId: String + $variantId: String + $quantity: String + $orderId: ID + ) { + addToCart( + channelId: $channelId + image: $image + name: $name + price: $price + productId: $productId + variantId: $variantId + quantity: $quantity + orderId: $orderId + ) { + id + orderId + orderName + } + } +`; + +const MATCHORDER_MUTATION = gql` + mutation MATCHORDER_MUTATION($orderId: ID!) { + matchOrder(orderId: $orderId) { + id + input { + id + quantity + productId + variantId + } + output { + id + quantity + productId + variantId + } + } + } +`; + +const listMetaGraphqlQuery = gql` + query ($listKey: String!) { + keystone { + adminMeta { + list(key: $listKey) { + hideDelete + hideCreate + fields { + path + isOrderable + isFilterable + listView { + fieldMode + } + } + } + } + } + } +`; + +const ALL_SHOPS_QUERY = gql` + query ALL_SHOPS_QUERY { + shops { + id + name + } + } +`; + +const ORDERSCOUNT_QUERY = gql` + query ORDERSCOUNT_QUERY($shop: ShopWhereInput) { + pendingCount: ordersCount( + where: { status: { equals: "PENDING" }, shop: $shop } + ) + inprocessCount: ordersCount( + where: { status: { equals: "INPROCESS" }, shop: $shop } + ) + awaitingCount: ordersCount( + where: { status: { equals: "AWAITING" }, shop: $shop } + ) + backorderedCount: ordersCount( + where: { status: { equals: "BACKORDERED" }, shop: $shop } + ) + cancelledCount: ordersCount( + where: { status: { equals: "CANCELLED" }, shop: $shop } + ) + completeCount: ordersCount( + where: { status: { equals: "COMPLETE" }, shop: $shop } + ) + } +`; + +const CHANNELS_QUERY = gql` + query GetChannels { + channels { + id + name + } + } +`; + +const ORDERS_QUERY = gql` + query ORDERS_QUERY( + $where: OrderWhereInput + $take: Int! + $skip: Int! + $orderBy: [OrderOrderByInput!] + ) { + items: orders(where: $where, take: $take, skip: $skip, orderBy: $orderBy) { + id + orderId + orderLink + orderName + email + firstName + lastName + streetAddress1 + streetAddress2 + city + state + zip + orderError + cartItemsCount + lineItemsCount + shop { + id + name + domain + accessToken + } + currency + totalPrice + subTotalPrice + totalDiscount + totalTax + status + createdAt + readyToProcess + } + count: ordersCount(where: $where) + } +`; + +export const OrderPage = () => { + const client = useApolloClient(); + const listKey = "Order"; + const list = useList(listKey); + const { openEditDrawer } = useDrawer(); + const { push } = useRouter(); + const searchParams = useSearchParams(); + const query = Object.fromEntries(searchParams.entries()); + const { resetToDefaults } = useQueryParamsFromLocalStorage(listKey); + const currentPage = parseInt(query.page) || 1; + const pageSize = parseInt(query.pageSize) || list.pageSize; + const metaQuery = useQuery(listMetaGraphqlQuery, { variables: { listKey } }); + const [placeOrders] = useMutation(PLACE_ORDERS); + const [addMatchToCart] = useMutation(ADDMATCHTOCART_MUTATION); + const [addToCart] = useMutation(ADD_TO_CART_MUTATION); + const [matchOrder] = useMutation(MATCHORDER_MUTATION); + const [loadingActions, setLoadingActions] = useState({}); + const [isCreateOrderDialogOpen, setIsCreateOrderDialogOpen] = useState(false); + const [selectedOrder, setSelectedOrder] = useState(null); + const toasts = useToasts(); + const [isProcessOrdersDialogOpen, setIsProcessOrdersDialogOpen] = + useState(false); + const [processingOrders, setProcessingOrders] = useState([]); + + const { data: channelsData } = useQuery(CHANNELS_QUERY); + + const channels = channelsData?.channels || []; + + const selectedShop = + searchParams.get("!shop_matches")?.replace(/^"|"$/g, "") || null; + + const statuses = [ + "PENDING", + "INPROCESS", + "AWAITING", + "BACKORDERED", + "CANCELLED", + "COMPLETE", + ]; + const { data: shopsData } = useQuery(ALL_SHOPS_QUERY); + + const { listViewFieldModesByField, filterableFields, orderableFields } = + useMemo(() => { + const listViewFieldModesByField = {}; + const orderableFields = new Set(); + const filterableFields = new Set(); + for (const field of metaQuery.data?.keystone.adminMeta.list?.fields || + []) { + listViewFieldModesByField[field.path] = field.listView.fieldMode; + if (field.isOrderable) orderableFields.add(field.path); + if (field.isFilterable) filterableFields.add(field.path); + } + return { listViewFieldModesByField, orderableFields, filterableFields }; + }, [metaQuery.data?.keystone.adminMeta.list?.fields]); + + const sort = useSort(list, orderableFields); + const filters = useFilters(list, filterableFields); + const searchFields = Object.keys(list.fields).filter( + (key) => list.fields[key].search + ); + const searchLabels = searchFields.map((key) => list.fields[key].label); + const searchParam = typeof query.search === "string" ? query.search : ""; + const [searchString, updateSearchString] = useState(searchParam); + const search = useFilter(searchParam, list, searchFields); + + const updateSearch = (value) => { + const { search, ...queries } = query; + const newQueryString = new URLSearchParams(queries).toString(); + if (value.trim()) { + const searchQuery = `search=${encodeURIComponent(value)}`; + const queryString = newQueryString + ? `${newQueryString}&${searchQuery}` + : searchQuery; + push(`?${queryString}`); + } else { + push(`?${newQueryString}`); + } + }; + + let selectedFields = useSelectedFields(list, listViewFieldModesByField); + + const { data: orderCounts, refetch: refetchOrderCounts } = useQuery( + ORDERSCOUNT_QUERY, + { + variables: { + ...(selectedShop ? { shop: filters.where.shop } : {}), + }, + fetchPolicy: "cache-and-network", + errorPolicy: "all", + } + ); + + let { data, error, refetch } = useQuery(ORDERS_QUERY, { + fetchPolicy: "cache-and-network", + errorPolicy: "all", + skip: !metaQuery.data, + variables: { + where: { ...filters.where, ...search }, + take: pageSize, + skip: (currentPage - 1) * pageSize, + orderBy: sort + ? [{ [sort.field]: sort.direction.toLowerCase() }] + : undefined, + }, + }); + + const dataGetter = makeDataGetter(data, error?.graphQLErrors); + + const [selectedItemsState, setSelectedItems] = useState(() => ({ + itemsFromServer: undefined, + selectedItems: new Set(), + })); + + if (data && data.items && selectedItemsState.itemsFromServer !== data.items) { + const newSelectedItems = new Set(); + data.items.forEach((item) => { + if (selectedItemsState.selectedItems.has(item.id)) { + newSelectedItems.add(item.id); + } + }); + setSelectedItems({ + itemsFromServer: data.items, + selectedItems: newSelectedItems, + }); + } + + const showCreate = + !(metaQuery.data?.keystone.adminMeta.list?.hideCreate ?? true) || null; + + const handleOrderAction = async (action, orderId, additionalData) => { + setLoadingActions((prev) => ({ ...prev, [action]: { [orderId]: true } })); + try { + switch (action) { + case "getMatch": + await addMatchToCart({ variables: { orderId } }); + toasts.addToast({ + title: "Match added to cart", + tone: "positive", + }); + break; + case "saveMatch": + await matchOrder({ variables: { orderId } }); + toasts.addToast({ + title: "Match saved", + tone: "positive", + }); + break; + case "placeOrder": + await placeOrders({ variables: { ids: [orderId] } }); + toasts.addToast({ + title: "Order placed successfully", + tone: "positive", + }); + break; + case "addToCart": + const { + channelId, + image, + name, + price, + productId, + variantId, + quantity, + } = additionalData; + await addToCart({ + variables: { + channelId, + image, + name, + price, + productId, + variantId, + quantity: quantity.toString(), + orderId, + }, + }); + toasts.addToast({ + title: "Item added to cart", + tone: "positive", + }); + break; + case "editOrder": + openEditDrawer(orderId, "Order"); + break; + case "deleteOrder": + // Implement delete order logic here + toasts.addToast({ + title: "Order deleted", + tone: "positive", + }); + break; + default: + console.log("Unknown action:", action); + } + + await client.refetchQueries({ + include: "active", + }); + } catch (error) { + console.error(`Error performing ${action}:`, error); + toasts.addToast({ + title: `Error performing ${action}`, + tone: "negative", + message: error.message, + }); + } finally { + setLoadingActions((prev) => ({ + ...prev, + [action]: { [orderId]: false }, + })); + } + }; + + const handleCreateOrder = (type) => { + if (type === "scratch") { + setSelectedOrder("scratch"); + } else { + setIsCreateOrderDialogOpen(true); + } + }; + + const handleOrderSelect = (order) => { + setSelectedOrder(order); + setIsCreateOrderDialogOpen(false); + }; + + const handleProcessAll = async () => { + setIsProcessOrdersDialogOpen(true); + }; + + const processSelectedOrders = async (selectedOrderIds) => { + setLoadingActions((prev) => ({ ...prev, processAll: true })); + setProcessingOrders(selectedOrderIds); + try { + await placeOrders({ variables: { ids: selectedOrderIds } }); + toasts.addToast({ + title: "Orders processed successfully", + tone: "positive", + }); + // await refetch(); + // await refetchOrderCounts(); + + await client.refetchQueries({ + include: "active", + }); + } catch (error) { + console.error("Error processing orders:", error); + toasts.addToast({ + title: "Error processing orders", + tone: "negative", + message: error.message, + }); + } finally { + setLoadingActions((prev) => ({ ...prev, processAll: false })); + setProcessingOrders([]); + setIsProcessOrdersDialogOpen(false); + } + }; + + const qualifyingOrders = useMemo(() => { + return ( + data?.items?.filter((order) => order.readyToProcess === "READY") || [] + ); + }, [data]); + + return ( + <> + {metaQuery.error ? ( + "Error..." + ) : data && metaQuery.data ? ( +
+ + + + + Dashboard + + + + {list.label} + + + +
+
+

+ {list.label} +

+

+ {list.description || + `Create and manage ${list.label.toLowerCase()}`} +

+
+
+ + + + Create Order + + + handleCreateOrder("scratch")} + className="text-muted-foreground flex gap-2 font-medium tracking-wide uppercase" + > + + From Scratch + + setIsCreateOrderDialogOpen(true)} + className="text-muted-foreground flex gap-2 font-medium tracking-wide uppercase" + > + + From Existing Orders + + + +
+
+ +
+
+
+ +
{ + e.preventDefault(); + updateSearch(searchString); + }} + > + updateSearchString(e.target.value)} + placeholder={`Search by ${ + searchLabels.length + ? searchLabels.join(", ").toLowerCase() + : "ID" + }`} + /> +
+
+
+ +
+
+ + + + + SORT + + } + /> + + + FILTER + + } + /> +
+
+ + {filters.filters.length > 0 && ( +
+
+ + + Filters + + +
+
+
+ +
+
+
+ )} + +
+ +
+ + {data?.items?.length ? ( + <> +
+ {dataGetter.get("items").data.map((order) => ( + // <> + // {JSON.stringify(order)} + // + + ))} +
+ + ) : ( +
+
+
+ + + +
+ {query.search || filters.filters.length ? ( + <> + + No {list.label} {" "} + + + Found{" "} + {searchParam + ? `matching your search` + : `matching your filters`}{" "} + + + + ) : ( + <> + + No {list.label} + + + Get started by creating a new one.{" "} + + {showCreate && } + + )} +
+
+ )} +
+ + + + + Search Existing Orders + + Search for an existing order to edit or view details. + + +
+ +
+
+
+ + setSelectedOrder(null)} + order={selectedOrder === "scratch" ? null : selectedOrder} + shopId={selectedOrder?.shop?.id} + /> + setIsProcessOrdersDialogOpen(false)} + orders={qualifyingOrders} + // orders={Array(20) + // .fill() + // .flatMap(() => data?.items || [])} + onProcessOrders={processSelectedOrders} + processingOrders={processingOrders} + /> +
+ ) : ( + + )} + + ); +}; + +export default OrderPage; diff --git a/app/dashboard/(admin)/oms/shops/(components)/CartItemField.js b/app/dashboard/(admin)/oms/shops/(components)/CartItemField.js new file mode 100644 index 0000000..b18d684 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/CartItemField.js @@ -0,0 +1,125 @@ +import React, { useState, useEffect } from "react"; +import { BadgeButton } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { ChevronLeft, ChevronRight, X } from "lucide-react"; + +export const CartItemField = ({ + title, + name, + image, + price, + quantity, + productId, + variantId, + channel, + index, + onRemove, + onQuantityChange, + isInvalid, +}) => { + const [localQuantity, setLocalQuantity] = useState(quantity.toString()); + + const handleQuantityInputChange = (e) => { + setLocalQuantity(e.target.value); + }; + + const handleQuantityUpdate = () => { + const newQuantity = Math.max(1, parseInt(localQuantity) || 1); + onQuantityChange(newQuantity); + setLocalQuantity(newQuantity.toString()); + }; + + const handleQuantityBlur = () => { + handleQuantityUpdate(); + }; + + const handleQuantityKeyDown = (e) => { + if (e.key === "Enter") { + handleQuantityUpdate(); + } + }; + + useEffect(() => { + setLocalQuantity(quantity.toString()); + }, [quantity]); + + return ( +
+
+ {title} +
+ {/* {JSON.stringify({ + title, + name, + image, + price, + quantity, + productId, + variantId, + })} */} +
+
+ {channel ? channel.name : "Unknown Channel"} +
+
{title || name}
+
+ {productId} | {variantId} +
+ +
+
+ ${(price * quantity).toFixed(2)} + {quantity > 1 && ( + + (${price} x {quantity}) + + )} +
+
+ onQuantityChange(Math.max(1, quantity - 1))} + className="px-1" + > + + + + + onQuantityChange(quantity + 1)} + className="px-1" + > + + + + + +
+
+
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/shops/(components)/CartItemSelect.js b/app/dashboard/(admin)/oms/shops/(components)/CartItemSelect.js new file mode 100644 index 0000000..7c83347 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/CartItemSelect.js @@ -0,0 +1,342 @@ +import React, { useEffect, useState } from "react"; +import { useQuery, gql } from "@apollo/client"; +import { Input } from "@ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@ui/select"; +import { Button } from "@ui/button"; +import { CartItemField } from "./CartItemField"; +import { ChevronLeft, ChevronRight, Plus } from "lucide-react"; +import { + Badge, + BadgeButton, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; + +const SEARCH_CHANNEL_PRODUCTS = gql` + query SearchChannelProducts($channelId: ID!, $searchEntry: String) { + searchChannelProducts(channelId: $channelId, searchEntry: $searchEntry) { + image + title + productId + variantId + price + availableForSale + productLink + inventory + inventoryTracked + error + } + } +`; + +export const CartItemSelect = ({ channels, localState, setLocalState }) => { + const [searchEntry, setSearchEntry] = useState(""); + const [selectedChannelId, setSelectedChannelId] = useState(""); + const [quantities, setQuantities] = useState({}); + const [localQuantities, setLocalQuantities] = useState({}); + const [invalidChannels, setInvalidChannels] = useState([]); + + const { data, loading, error } = useQuery(SEARCH_CHANNEL_PRODUCTS, { + variables: { channelId: selectedChannelId, searchEntry }, + skip: !selectedChannelId || !searchEntry, + }); + + const filterValidCartItems = (cartItems) => { + return cartItems.filter((item) => + channels.some((channel) => channel.id === item.channel?.id) + ); + }; + + useEffect(() => { + const invalidChannelIds = localState.cartItems + .filter( + (item) => !channels.some((channel) => channel.id === item.channel?.id) + ) + .map((item) => item.channel?.id); + setInvalidChannels(invalidChannelIds); + }, [localState.cartItems, channels]); + + const handleSearch = (e) => { + if (e.key === "Enter") { + setSearchEntry(e.target.value); + } + }; + + const handleAddItem = (product) => { + const quantity = quantities[product.productId] || 1; + setLocalState((prevState) => { + const existingItemIndex = prevState.cartItems.findIndex( + (item) => + item.productId === product.productId && + item.variantId === product.variantId && + item.channel?.id === selectedChannelId + ); + + if (existingItemIndex !== -1) { + return { + ...prevState, + cartItems: prevState.cartItems.map((item, index) => + index === existingItemIndex + ? { ...item, quantity: item.quantity + quantity } + : item + ), + }; + } else { + return { + ...prevState, + cartItems: [ + ...prevState.cartItems, + { + ...product, + quantity, + channel: channels.find((c) => c.id === selectedChannelId), + }, + ], + }; + } + }); + setQuantities({ ...quantities, [product.productId]: 1 }); + setLocalQuantities({ ...localQuantities, [product.productId]: 1 }); + }; + + const handleQuantityChange = (productId, newQuantity) => { + setQuantities({ ...quantities, [productId]: newQuantity }); + setLocalQuantities({ ...localQuantities, [productId]: newQuantity }); + }; + + const handleQuantityBlur = (productId) => { + const quantity = localQuantities[productId] || quantities[productId] || 1; + setQuantities({ ...quantities, [productId]: Math.max(1, quantity) }); + setLocalQuantities({ + ...localQuantities, + [productId]: Math.max(1, quantity), + }); + }; + + const handleQuantityKeyDown = (e, productId) => { + if (e.key === "Enter") { + handleQuantityBlur(productId); + } + }; + + const validCartItems = filterValidCartItems(localState.cartItems); + const invalidCartItems = localState.cartItems.filter( + (item) => !channels.some((channel) => channel.id === item.channel?.id) + ); + + return ( +
+
+
+ Cart Items +
+ {invalidCartItems.length > 0 && ( +
+ +
+ + MISSING CHANNELS + + + Some cart items have channels that no longer exist. Please + review and update these items. If this order is created, these + cart items will not be created. + + {invalidCartItems.map((item, index) => ( +
+ {item.image && ( +
+ {item.title} +
+ )} +
+
+ {item.channel?.name || "Unknown Channel"} +
+
{item.name}
+
+ {item.productId} | {item.variantId} +
+
+ ${(item.price * item.quantity).toFixed(2)} +
+
+
+ ))} +
+
+
+ )} + {validCartItems.length > 0 ? ( +
+ {validCartItems.map((item, index) => ( + { + setLocalState((prevState) => ({ + ...prevState, + cartItems: prevState.cartItems.filter( + (_, i) => i !== index + ), + })); + }} + onQuantityChange={(newQuantity) => { + setLocalState((prevState) => ({ + ...prevState, + cartItems: prevState.cartItems.map((cartItem, i) => + i === index + ? { ...cartItem, quantity: newQuantity } + : cartItem + ), + })); + }} + /> + ))} +
+ ) : ( +
+ None added +
+ )} +
+ + + {loading &&
Loading...
} + {error &&
Error: {error.message}
} + {data?.searchChannelProducts && ( +
+ {data.searchChannelProducts.map((product) => ( +
+
+ {product.title} +
+
+
{product.title}
+
+ {product.productId} | {product.variantId} +
+ +
+
+ $ + {( + parseFloat(product.price) * + (quantities[product.productId] || 1) + ).toFixed(2)} + {(quantities[product.productId] || 1) > 1 && ( + + (${parseFloat(product.price).toFixed(2)} x{" "} + {quantities[product.productId] || 1}) + + )} +
+
+ + handleQuantityChange( + product.productId, + Math.max( + 1, + (quantities[product.productId] || 1) - 1 + ) + ) + } + className="px-1" + > + + + + + handleQuantityInputChange( + product.productId, + e.target.value + ) + } + onBlur={() => handleQuantityBlur(product.productId)} + onKeyDown={(e) => + handleQuantityKeyDown(e, product.productId) + } + /> + + handleQuantityChange( + product.productId, + (quantities[product.productId] || 1) + 1 + ) + } + className="px-1" + > + + + handleAddItem(product)} + className="px-1" + > + + +
+
+
+
+ ))} +
+ )} +
+
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/shops/(components)/CreatePlatform.js b/app/dashboard/(admin)/oms/shops/(components)/CreatePlatform.js new file mode 100644 index 0000000..4282634 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/CreatePlatform.js @@ -0,0 +1,231 @@ +"use client"; +import React, { useMemo, useState } from "react"; +import { useCreateItem } from "@keystone/utils/useCreateItem"; +import { useKeystone, useList } from "@keystone/keystoneProvider"; +import { Button } from "@ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@ui/dialog"; +import { Fields } from "@keystone/themes/Tailwind/atlas/components/Fields"; +import { GraphQLErrorNotice } from "@keystone/themes/Tailwind/atlas/components/GraphQLErrorNotice"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@ui/select"; +import { Label } from "@ui/label"; +import { shopAdapters as adapters } from "../../../../../../shopAdapters"; +import { getFilteredProps } from "./CreateShop"; +import { Badge } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; + +export const shopAdapters = { + ...adapters, + Medusa: "soon", + Magento: "soon", + Stripe: "soon", +}; + +export function CreatePlatform({ refetch, trigger }) { + const [selectedPlatform, setSelectedPlatform] = useState(undefined); + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const list = useList("ShopPlatform"); + const { create, props, state, error } = useCreateItem(list); + const { createViewFieldModes } = useKeystone(); + + const keysToUpdateCustom = [ + "name", + "orderLinkFunction", + "updateProductFunction", + "getWebhooksFunction", + "deleteWebhookFunction", + "createWebhookFunction", + "searchProductsFunction", + "getProductFunction", + "searchOrdersFunction", + "addTrackingFunction", + "addCartToPlatformOrderFunction", + "oAuthFunction", + "oAuthCallbackFunction", + "cancelOrderWebhookHandler", + "createOrderWebhookHandler", + "appKey", + "appSecret", + ]; + + const keysToUpdateTemplate = ["name", "appKey", "appSecret"]; + + const handlePlatformActivation = async () => { + const item = await create(); + if (item) { + refetch(); + clearFunctionFields(); + setIsDialogOpen(false); + } + }; + + const handleTemplateSelection = (value) => { + setSelectedPlatform(value); + + if (value === "custom") { + clearFunctionFields(); + } else { + props.onChange((oldValues) => { + const newValues = { ...oldValues }; + keysToUpdateCustom + .filter((key) => !["appKey", "appSecret"].includes(key)) + .forEach((key) => { + newValues[key] = { + ...oldValues[key], + value: { + ...oldValues[key].value, + inner: { + ...oldValues[key].value.inner, + value: + key === "name" + ? value.charAt(0).toUpperCase() + value.slice(1) + : value, + }, + }, + }; + }); + return newValues; + }); + } + }; + + const clearFunctionFields = () => { + const clearedFields = keysToUpdateCustom.reduce((acc, key) => { + acc[key] = { + ...props.value[key], + value: { + ...props.value[key].value, + inner: { + ...props.value[key].value.inner, + value: "", + }, + }, + }; + return acc; + }, {}); + + props.onChange((prev) => ({ ...prev, ...clearedFields })); + }; + + const handleDialogClose = () => { + setSelectedPlatform(null); // Reset selected platform + clearFunctionFields(); // Clear all fields + setIsDialogOpen(false); // Close dialog + }; + + const filteredProps = useMemo(() => { + const fieldKeysToShow = + selectedPlatform === "custom" ? keysToUpdateCustom : keysToUpdateTemplate; + + const modifications = fieldKeysToShow.map((key) => ({ key })); + + return getFilteredProps(props, [...modifications], true); + }, [props, selectedPlatform]); + + return ( + + {trigger} + + + + Create Shop Platform + + {selectedPlatform === "custom" + ? "Create a custom platform from scratch by providing the necessary fields" + : "Create a platform based on an existing template"} + + + +
+ + +
+ + {selectedPlatform && ( +
+ {error && ( + + )} + {createViewFieldModes.state === "error" && ( + + )} + {createViewFieldModes.state === "loading" && ( +
+ )} + +
+ )} + + + + + + +
+ ); +} diff --git a/app/dashboard/(admin)/oms/shops/(components)/CreateShop.js b/app/dashboard/(admin)/oms/shops/(components)/CreateShop.js new file mode 100644 index 0000000..5b3ac94 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/CreateShop.js @@ -0,0 +1,285 @@ +import React, { useMemo, useState } from "react"; +import { useCreateItem } from "@keystone/utils/useCreateItem"; +import { useList } from "@keystone/keystoneProvider"; +import { Button } from "@ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@ui/dialog"; +import { Fields } from "@keystone/themes/Tailwind/atlas/components/Fields"; +import { useQuery, gql } from "@keystone-6/core/admin-ui/apollo"; +import { GraphQLErrorNotice } from "@keystone/themes/Tailwind/atlas/components/GraphQLErrorNotice"; +import { SHOP_PLATFORMS_QUERY } from "./ShopPlatforms"; +import { SHOPS_QUERY } from "./Shops"; + +const GET_SHOP_PLATFORM_DETAILS = gql` + query ($id: ID!) { + shopPlatform(where: { id: $id }) { + id + name + appKey + appSecret + callbackUrl + oAuthFunction + oAuthCallbackFunction + } + } +`; + +export function getFilteredProps(props, modifications, defaultCollapse) { + const fieldKeysToShow = modifications.map((mod) => mod.key); + const breakGroups = modifications.reduce((acc, mod) => { + if (mod.breakGroup) { + acc.push(mod.breakGroup); + } + return acc; + }, []); + + const newFieldModes = { ...props.fieldModes }; + + Object.keys(props.fields).forEach((key) => { + if (!fieldKeysToShow.includes(key)) { + newFieldModes[key] = "hidden"; + } else { + newFieldModes[key] = props.fieldModes[key] || "edit"; + } + }); + + const updatedFields = Object.keys(props.fields).reduce((obj, key) => { + const modification = modifications.find((mod) => mod.key === key); + if (modification) { + obj[key] = { + ...props.fields[key], + fieldMeta: { + ...props.fields[key].fieldMeta, + ...modification.fieldMeta, + }, + }; + } else { + obj[key] = props.fields[key]; + } + return obj; + }, {}); + + const reorderedFields = modifications.reduce((obj, mod) => { + obj[mod.key] = updatedFields[mod.key]; + return obj; + }, {}); + + const updatedGroups = props.groups.map((group) => { + if (breakGroups.includes(group.label)) { + return { + ...group, + fields: group.fields.filter( + (field) => !fieldKeysToShow.includes(field.path) + ), + }; + } + return { + ...group, + collapsed: defaultCollapse, + }; + }); + + return { + ...props, + fields: reorderedFields, + fieldModes: newFieldModes, + groups: updatedGroups, + }; +} + +export function CreateShop() { + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const list = useList("Shop"); + const { create, props, state, error } = useCreateItem(list); + const { refetch } = useQuery(SHOPS_QUERY, { + variables: { + where: { OR: [] }, + take: 50, + skip: 0, + }, + }); + + const handleDialogClose = () => { + setIsDialogOpen(false); + }; + + const platformId = props.value.platform?.value?.value?.id; + + const filteredProps = useMemo(() => { + const modifications = [ + { key: "platform", fieldMeta: { hideButtons: true } }, + ]; + return getFilteredProps(props, modifications); + }, [props]); + + return ( + + + + + + + Create Shop + + Select a platform and fill in the necessary fields + + + {error && ( + + )} + + + {platformId && } + + + + + + {platformId && ( + + )} + + + + ); +} + +function TriggerButton({ setIsDialogOpen }) { + const { data, loading, error } = useQuery(SHOP_PLATFORMS_QUERY, { + variables: { + where: { OR: [] }, + take: 50, + skip: 0, + }, + }); + + return ( + + ); +} + +export function FilteredFields({ platformId, props }) { + const { data, loading, error } = useQuery(GET_SHOP_PLATFORM_DETAILS, { + variables: { id: platformId }, + }); + + if (loading) return

Loading...

; + if (error) return

Error loading platform data.

; + + const platformData = data?.shopPlatform; + + let modifications = []; + + if (platformData) { + if ( + platformData.appKey && + platformData.appSecret && + platformData.oAuthFunction && + platformData.oAuthCallbackFunction + ) { + modifications = [{ key: "domain", breakGroup: "Credentials" }]; + } else { + modifications = [ + { key: "name" }, + { key: "domain", breakGroup: "Credentials" }, + { key: "accessToken", breakGroup: "Credentials" }, + ]; + } + } + + const filteredProps = getFilteredProps(props, modifications); + + if (!filteredProps.fields) return null; + + return ( +
+ +
+ ); +} + +export function CreateShopButton({ + platformId, + handleShopCreation, + refetch, + props, + state, + setIsDialogOpen, +}) { + const { data, loading, error } = useQuery(GET_SHOP_PLATFORM_DETAILS, { + variables: { id: platformId }, + }); + + if (loading) return ; + if (error) return ; + + const platformData = data?.shopPlatform; + + const handleClick = async () => { + if ( + platformData?.appKey && + platformData?.appSecret && + platformData?.oAuthFunction && + platformData?.oAuthCallbackFunction + ) { + const { oauth, scopes } = await import( + `../../../../../../shopAdapters/${platformData.oAuthFunction}` + ); + + const config = { + apiKey: platformData.appKey, + apiSecret: platformData.appSecret, + redirectUri: platformData.callbackUrl, + scopes: scopes(), + }; + + const domain = props.value.domain?.value?.inner?.value; + oauth(domain, config); + } else { + const item = await handleShopCreation(); + if (item) { + refetch(); + setIsDialogOpen(false); + } + } + }; + + return ( + + ); +} diff --git a/app/dashboard/(admin)/oms/shops/(components)/LineItemField.js b/app/dashboard/(admin)/oms/shops/(components)/LineItemField.js new file mode 100644 index 0000000..66efc2a --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/LineItemField.js @@ -0,0 +1,114 @@ +import React, { useState, useEffect } from "react"; +import { BadgeButton } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { + ArrowLeft, + ArrowRight, + ChevronLeft, + ChevronRight, + Minus, + Plus, + X, +} from "lucide-react"; + +export const LineItemField = ({ + title, + name, + image, + price, + quantity, + productId, + variantId, + index, + onRemove, + onQuantityChange, +}) => { + const [localQuantity, setLocalQuantity] = useState(quantity.toString()); + + const handleQuantityInputChange = (e) => { + setLocalQuantity(e.target.value); + }; + + const handleQuantityUpdate = () => { + const newQuantity = Math.max(1, parseInt(localQuantity) || 1); + onQuantityChange(newQuantity); + setLocalQuantity(newQuantity.toString()); + }; + + const handleQuantityBlur = () => { + handleQuantityUpdate(); + }; + + const handleQuantityKeyDown = (e) => { + if (e.key === "Enter") { + handleQuantityUpdate(); + } + }; + + useEffect(() => { + setLocalQuantity(quantity.toString()); + }, [quantity]); + + return ( +
+
+ {title} +
+
+
{title || name}
+
+ {productId} | {variantId} +
+
+
+ ${(price * quantity).toFixed(2)} + {quantity > 1 && ( + + (${price} x {quantity}) + + )} +
+
+ onQuantityChange(Math.max(1, quantity - 1))} + className="px-1" + > + + + + + onQuantityChange(quantity + 1)} + className="px-1" + > + + + + + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/app/dashboard/(admin)/oms/shops/(components)/LineItemSelect.js b/app/dashboard/(admin)/oms/shops/(components)/LineItemSelect.js new file mode 100644 index 0000000..f6df7c5 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/LineItemSelect.js @@ -0,0 +1,288 @@ +import React, { useState } from "react"; +import { useQuery, gql } from "@apollo/client"; +import { Input } from "@ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@ui/select"; +import { Button } from "@ui/button"; +import { LineItemField } from "./LineItemField"; +import { ChevronLeft, ChevronRight, Plus } from "lucide-react"; +import { BadgeButton } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; + +const SEARCH_SHOP_PRODUCTS = gql` + query SearchShopProducts($shopId: ID!, $searchEntry: String) { + searchShopProducts(shopId: $shopId, searchEntry: $searchEntry) { + image + title + productId + variantId + price + availableForSale + productLink + inventory + inventoryTracked + } + } +`; + +export const LineItemSelect = ({ + shops, + localState, + setLocalState, + shopId, +}) => { + const [searchEntry, setSearchEntry] = useState(""); + const [selectedShopId, setSelectedShopId] = useState(shopId || ""); + const [quantities, setQuantities] = useState({}); + const [localQuantities, setLocalQuantities] = useState({}); + + const { data, loading, error } = useQuery(SEARCH_SHOP_PRODUCTS, { + variables: { shopId: selectedShopId, searchEntry }, + skip: !selectedShopId || !searchEntry, + }); + + const handleSearch = (e) => { + if (e.key === "Enter") { + setSearchEntry(e.target.value); + } + }; + + const handleAddItem = (product) => { + const quantity = quantities[product.productId] || 1; + setLocalState((prevState) => { + const existingItemIndex = prevState.lineItems.findIndex( + (item) => + item.productId === product.productId && + item.variantId === product.variantId + ); + + if (existingItemIndex !== -1) { + // If the item already exists, create a new array with the updated item + return { + ...prevState, + lineItems: prevState.lineItems.map((item, index) => + index === existingItemIndex + ? { ...item, quantity: item.quantity + quantity } + : item + ), + }; + } else { + // If it's a new item, add it to the lineItems + return { + ...prevState, + lineItems: [ + ...prevState.lineItems, + { + ...product, + quantity, + shop: shops.find((s) => s.id === selectedShopId), + }, + ], + }; + } + }); + setQuantities({ ...quantities, [product.productId]: 1 }); + setLocalQuantities({ ...localQuantities, [product.productId]: 1 }); + }; + + const handleQuantityChange = (productId, newQuantity) => { + setQuantities({ ...quantities, [productId]: newQuantity }); + setLocalQuantities({ ...localQuantities, [productId]: newQuantity }); + }; + + const handleQuantityBlur = (productId) => { + const quantity = localQuantities[productId] || quantities[productId] || 1; + setQuantities({ ...quantities, [productId]: Math.max(1, quantity) }); + setLocalQuantities({ + ...localQuantities, + [productId]: Math.max(1, quantity), + }); + }; + + const handleQuantityKeyDown = (e, productId) => { + if (e.key === "Enter") { + handleQuantityBlur(productId); + } + }; + + return ( +
+
+
+ Line Items +
+ {localState.lineItems.length > 0 ? ( +
+ {localState.lineItems.map((item, index) => ( + { + setLocalState((prevState) => ({ + ...prevState, + lineItems: prevState.lineItems.filter( + (_, i) => i !== index + ), + })); + }} + onQuantityChange={(newQuantity) => { + setLocalState((prevState) => ({ + ...prevState, + lineItems: prevState.lineItems.map((lineItem, i) => + i === index + ? { ...lineItem, quantity: newQuantity } + : lineItem + ), + })); + }} + /> + ))} +
+ ) : ( +
+ None added +
+ )} +
+ + + {loading &&
Loading...
} + {error &&
Error: {error.message}
} + {data?.searchShopProducts && ( +
+ {data.searchShopProducts.map((product) => ( +
+
+ {product.image && ( + {product.title} + )} +
+
+
{product.title}
+
+ {product.productId} | {product.variantId} +
+ +
+
+ $ + {( + parseFloat(product.price) * + (quantities[product.productId] || 1) + ).toFixed(2)} + {(quantities[product.productId] || 1) > 1 && ( + + (${parseFloat(product.price).toFixed(2)} x{" "} + {quantities[product.productId] || 1}) + + )} +
+
+ + handleQuantityChange( + product.productId, + Math.max( + 1, + (quantities[product.productId] || 1) - 1 + ) + ) + } + className="px-1" + > + + + + + setLocalQuantities({ + ...localQuantities, + [product.productId]: e.target.value, + }) + } + onBlur={() => handleQuantityBlur(product.productId)} + onKeyDown={(e) => + handleQuantityKeyDown(e, product.productId) + } + /> + + handleQuantityChange( + product.productId, + (quantities[product.productId] || 1) + 1 + ) + } + className="px-1" + > + + + handleAddItem(product)} + className="px-1" + > + + +
+
+
+
+ ))} +
+ )} +
+
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/shops/(components)/Links.js b/app/dashboard/(admin)/oms/shops/(components)/Links.js new file mode 100644 index 0000000..5a033eb --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/Links.js @@ -0,0 +1,826 @@ +import React, { useState, useMemo, useEffect } from "react"; +import { useCreateItem } from "@keystone/utils/useCreateItem"; +import { gql, useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { useList } from "@keystone/keystoneProvider"; +import { useUpdateItem, useDeleteItem } from "@keystone/themes/Tailwind/atlas/components/EditItemDrawer"; +import { + ArrowRight, + Edit, + Edit2, + ListFilter, + Plus, + Trash2, + XIcon, + ChevronDown, +} from "lucide-react"; +import { Button } from "@ui/button"; +import { Badge } from "@ui/badge"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue, +} from "@ui/select"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@ui/dropdown-menu-depracated"; +import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover"; +import { Label } from "@ui/label"; +import { useToasts } from "@keystone/screens"; +import { cn } from "@keystone/utils/cn"; +import { ReactSortable } from "react-sortablejs"; +import { Pencil1Icon } from "@radix-ui/react-icons"; +import { RiFilterLine } from "@remixicon/react"; + +const GET_CHANNELS = gql` + query GetChannels($where: ChannelWhereInput, $take: Int, $skip: Int) { + items: channels(where: $where, take: $take, skip: $skip) { + id + name + } + } +`; + +export const CreateLinkButton = ({ shopId, refetch }) => { + const list = useList("Link"); + const { createWithData, state, error } = useCreateItem(list); + const { + data, + loading, + error: queryError, + } = useQuery(GET_CHANNELS, { + variables: { where: {}, take: 50, skip: 0 }, + }); + + const [isCreating, setIsCreating] = useState(false); + + const handleCreateLink = async (channelId) => { + setIsCreating(true); + try { + const item = await createWithData({ + data: { + shop: { connect: { id: shopId } }, + channel: { connect: { id: channelId } }, + }, + }); + if (item) { + refetch(); + } + } finally { + setIsCreating(false); + } + }; + + return ( + + +
+ +
+
+ + Select Channel to Link + + + {loading && Loading...} + {queryError && ( + Error loading channels + )} + {data?.items?.map((channel) => ( + handleCreateLink(channel.id)} + disabled={isCreating} + > + {channel.name} + + ))} + + +
+ ); +}; + +const areOrdersEqual = (links1, links2) => { + if (links1.length !== links2.length) return false; + return links1.every((link, index) => link.id === links2[index].id); +}; + +export const Links = ({ + shopId, + links: initialLinks, + refetch, + isLoading, + editItem, + linkMode, +}) => { + const [links, setLinks] = useState([]); + const [selectedLinkId, setSelectedLinkId] = useState(null); + const [isUpdating, setIsUpdating] = useState(false); + const [isDeletingFilter, setIsDeletingFilter] = useState(false); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [error, setError] = useState(null); + + const hasOrderChanged = !areOrdersEqual(initialLinks, links); + + const { handleUpdate: updateLink, updateLoading } = useUpdateItem("Link"); + const { handleDelete: deleteLink, deleteLoading } = useDeleteItem("Link"); + const { handleUpdate: updateShop, updateLoading: updateShopLoading } = useUpdateItem("Shop"); + + const toasts = useToasts(); + const list = useList("Order"); + + const handleLinkModeChange = async (newMode) => { + try { + await updateShop(shopId, { linkMode: newMode }); + toasts.addToast({ + tone: "positive", + title: "Link mode updated successfully", + }); + } catch (err) { + setError(err.message); + toasts.addToast({ + title: "Failed to update link mode", + tone: "negative", + message: err.message, + }); + } + }; + + useEffect(() => { + const simplifiedLinks = initialLinks.map((link) => ({ + id: link.id, + name: link.channel ? link.channel.name : "Unnamed", + filtersCount: link.filters?.length || 0, + rank: link.rank, + filters: link.filters || [], + })); + setLinks(simplifiedLinks); + }, [initialLinks]); + + const handleFilterSubmit = (state) => { + setIsUpdating(true); + setError(null); + const selectedLink = links.find((link) => link.id === selectedLinkId); + const updatedFilters = [...(selectedLink.filters || [])]; + + const existingFilterIndex = updatedFilters.findIndex( + (filter) => + filter.field === state.fieldPath && filter.type === state.filterType + ); + + if (existingFilterIndex !== -1) { + updatedFilters[existingFilterIndex] = { + type: state.filterType, + field: state.fieldPath, + value: state.filterValue, + }; + } else { + updatedFilters.push({ + type: state.filterType, + field: state.fieldPath, + value: state.filterValue, + }); + } + + updateLink(selectedLinkId, { filters: updatedFilters }) + .then(() => { + setLinks((prevLinks) => + prevLinks.map((link) => + link.id === selectedLinkId + ? { + ...link, + filters: updatedFilters, + filtersCount: updatedFilters.length, + } + : link + ) + ); + toasts.addToast({ + tone: "positive", + title: "Filters updated successfully", + }); + setIsPopoverOpen(false); + }) + .catch((err) => { + setError(err.message); + toasts.addToast({ + title: "Failed to update filters", + tone: "negative", + message: err.message, + }); + }) + .finally(() => { + setIsUpdating(false); + }); + }; + + const deleteFilter = (linkId, field, type) => { + setIsDeletingFilter(true); + setError(null); + const link = links.find((link) => link.id === linkId); + if (!link || !link.filters) { + setError("Link or filters not found"); + setIsDeletingFilter(false); + return; + } + const updatedFilters = link.filters.filter( + (filter) => !(filter.field === field && filter.type === type) + ); + + updateLink(linkId, { filters: updatedFilters }) + .then(() => { + setLinks((prevLinks) => + prevLinks.map((link) => + link.id === linkId + ? { + ...link, + filters: updatedFilters, + filtersCount: updatedFilters.length, + } + : link + ) + ); + toasts.addToast({ + tone: "positive", + title: "Filter deleted successfully", + }); + }) + .catch((err) => { + setError(err.message); + toasts.addToast({ + title: "Failed to delete filter", + tone: "negative", + message: err.message, + }); + }) + .finally(() => { + setIsDeletingFilter(false); + }); + }; + + const handleDeleteLink = async (linkId) => { + try { + await deleteLink(linkId); + setLinks(links.filter((link) => link.id !== linkId)); + toasts.addToast({ + tone: "positive", + title: "Link deleted successfully", + }); + } catch (err) { + setError(err.message); + toasts.addToast({ + title: "Failed to delete link", + tone: "negative", + message: err.message, + }); + } + }; + + const handleSaveOrder = async () => { + setIsUpdating(true); + try { + const updatePromises = links.map((link, index) => + updateLink(link.id, { rank: index + 1 }) + ); + await Promise.all(updatePromises); + + toasts.addToast({ + tone: "positive", + title: "Link order updated successfully", + }); + refetch(); + } catch (err) { + setError(err.message); + toasts.addToast({ + title: "Failed to update link order", + tone: "negative", + message: err.message, + }); + } finally { + setIsUpdating(false); + } + }; + + if (isLoading) { + return
Loading links...
; + } + + return ( +
+ {error &&
{error}
} +
+
+
+
+ +
+ Create links to channels based on filters +
+
+
+ + + + + + handleLinkModeChange('sequential')}> + Sequential + + handleLinkModeChange('simultaneous')}> + Simultaneous + + + + {hasOrderChanged ? ( + + ) : ( + + )} +
+
+ {links.length > 0 && ( + + {links.map((link) => ( + setSelectedLinkId(link.id)} + editItem={() => editItem(link.id, "Link")} + onDelete={() => handleDeleteLink(link.id)} + isDeleting={deleteLoading} + /> + ))} + + )} +
+
+ + {selectedLinkId && ( +
+
+
+
+ +
+ Orders matching these filters will be processed by this + channel +
+
+
+ + + + + + + + +
+
+ link.id === selectedLinkId).filters + } + list={list} + linkId={selectedLinkId} + deleteFilter={deleteFilter} + handleFilterSubmit={handleFilterSubmit} + isUpdating={updateLoading} + isDeletingFilter={isDeletingFilter} + /> +
+
+ )} +
+ ); +}; + +export const Link = ({ linkMode, link, isSelected, onSelect, editItem, onDelete, isDeleting }) => { + return ( +
+
+
+ + {linkMode === 'sequential' ? link.rank : '1'} + + {link.name} +
+
+ + + + + {link.filtersCount || 0} filter{link.filtersCount !== 1 && "s"} + + + + +
+
+
+ ); +}; + +function FilterList({ + filters, + list, + linkId, + deleteFilter, + handleFilterSubmit, + isUpdating, + isDeletingFilter, +}) { + return ( +
+ {filters?.map((filter, index) => ( + + ))} +
+ ); +} + +function FilterPill({ + filter, + field, + linkId, + deleteFilter, + handleFilterSubmit, + isUpdating, + isDeletingFilter, + list, +}) { + const [isOpen, setIsOpen] = useState(false); + + const onRemove = () => { + deleteFilter(linkId, filter.field, filter.type); + }; + + const filterType = field.controller.filter.types[filter.type]; + const filterLabel = filterType ? filterType.label : filter.type; + + const formattedValue = field.controller.filter.format + ? field.controller.filter.format({ + label: filterLabel, + type: filter.type, + value: filter.value, + }) + : `${filterLabel} ${filter.value}`; + + const readableFilter = `${field.label} ${formattedValue}`; + + const handleSubmit = async (state) => { + await handleFilterSubmit(state); + setIsOpen(false); + }; + + return ( + + +
+ {readableFilter} +
+ { + e.stopPropagation(); + onRemove(); + }} + disabled={isDeletingFilter} + > + + +
+
+
+ + + +
+ ); +} + +function AddFilterContent({ list, listKey, handleFilterSubmit, isUpdating }) { + const [state, setState] = useState({ + fieldPath: null, + filterType: null, + filterValue: null, + }); + + const fieldsWithFilters = useMemo(() => { + const fieldsWithFilters = {}; + Object.keys(list.fields).forEach((fieldPath) => { + const field = list.fields[fieldPath]; + if (field.controller.filter) { + fieldsWithFilters[fieldPath] = field; + } + }); + return fieldsWithFilters; + }, [list.fields]); + + const handleSelectFieldPath = (fieldPath) => { + setState({ + fieldPath, + filterType: null, + filterValue: null, + }); + }; + + const handleSelectFilterType = (filterType) => { + setState((prevState) => ({ + ...prevState, + filterType, + filterValue: + fieldsWithFilters[prevState.fieldPath]?.controller.filter.types[ + filterType + ]?.initialValue ?? null, + })); + }; + + return ( +
+
+
+ + +
+ + {state.fieldPath && ( +
+ + +
+ )} + + {state.fieldPath && + state.filterType && + fieldsWithFilters[state.fieldPath] && + (() => { + const { Filter } = + fieldsWithFilters[state.fieldPath].controller.filter; + if (Filter) + return ( +
+ + { + setState((state) => ({ + ...state, + filterValue: value, + })); + }} + /> +
+ ); + })()} +
+ +
+ ); +} + +function UpdateFilterContent({ list, filter, handleFilterSubmit, isUpdating }) { + const [state, setState] = useState({ + fieldPath: filter.field, + filterType: filter.type, + filterValue: filter.value, + }); + + const fieldsWithFilters = useMemo(() => { + const fieldsWithFilters = {}; + Object.keys(list.fields).forEach((fieldPath) => { + const field = list.fields[fieldPath]; + if (field.controller.filter) { + fieldsWithFilters[fieldPath] = field; + } + }); + return fieldsWithFilters; + }, [list.fields]); + + const handleSelectFieldPath = (fieldPath) => { + setState({ + fieldPath, + filterType: null, + filterValue: null, + }); + }; + + const handleSelectFilterType = (filterType) => { + setState((prevState) => ({ + ...prevState, + filterType, + filterValue: + fieldsWithFilters[prevState.fieldPath]?.controller.filter.types[ + filterType + ]?.initialValue ?? null, + })); + }; + + return ( +
+
+
+ + +
+ + {state.fieldPath && ( +
+ + +
+ )} + + {state.fieldPath && + state.filterType && + fieldsWithFilters[state.fieldPath] && + (() => { + const { Filter } = + fieldsWithFilters[state.fieldPath].controller.filter; + if (Filter) + return ( +
+ + { + setState((state) => ({ + ...state, + filterValue: value, + })); + }} + /> +
+ ); + })()} +
+ +
+ ); +} diff --git a/app/dashboard/(admin)/oms/shops/(components)/OrderDetailsDialog.js b/app/dashboard/(admin)/oms/shops/(components)/OrderDetailsDialog.js new file mode 100644 index 0000000..8cb2cf4 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/OrderDetailsDialog.js @@ -0,0 +1,433 @@ +import React, { useState, useMemo, useEffect, useRef } from "react"; +import { useKeystone, useList } from "@keystone/keystoneProvider"; +import { Button } from "@ui/button"; +import { Fields } from "@keystone/themes/Tailwind/atlas/components/Fields"; +import { GraphQLErrorNotice } from "@keystone/themes/Tailwind/atlas/components/GraphQLErrorNotice"; +import { getFilteredProps } from "./CreateShop"; +import { useCreateItem } from "@keystone/utils/useCreateItem"; +import { useQuery, gql, useApolloClient } from "@apollo/client"; +import { + ChevronDownIcon, + X, + Check, + ChevronDown, + ChevronUp, + Plus, + Minus, +} from "lucide-react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { cn } from "@keystone/utils/cn"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@ui/select"; +import * as SelectPrimitive from "@radix-ui/react-select"; + +import { LineItemSelect } from "./LineItemSelect"; +import { CartItemSelect } from "./CartItemSelect"; + +const SHOPS_QUERY = gql` + query GetShops { + shops { + id + name + } + } +`; + +const CHANNELS_QUERY = gql` + query GetChannels { + channels { + id + name + } + } +`; + +const SECTIONS = { + orderDetails: { + label: "Order Details", + description: "Basic order information and status", + fields: [ + "orderId", + "orderName", + "shop", + "status", + "currency", + "totalPrice", + "subTotalPrice", + "totalDiscount", + "totalTax", + "shippingMethod", + ], + }, + customerInfo: { + label: "Customer Info", + description: "Customer contact details", + fields: ["email", "firstName", "lastName", "phoneNumber"], + }, + shippingAddress: { + label: "Shipping Address", + description: "Delivery location information", + fields: [ + "streetAddress1", + "streetAddress2", + "city", + "state", + "zip", + "country", + ], + }, + additionalInfo: { + label: "Additional Info", + description: "Extra details and order notes", + fields: ["note", "orderError", "locationId"], + }, + lineItems: { + label: "Line Items", + description: "Individual products in the order", + fields: ["lineItems"], + component: "LineItemSelect", + }, + cartItems: { + label: "Cart Items", + description: "Items in the customer's cart", + fields: ["cartItems"], + component: "CartItemSelect", + }, + orderActions: { + label: "Order Actions", + description: "Manage and process the order", + fields: ["linkOrder", "matchOrder", "processOrder"], + }, +}; + +const OrderDialog = DialogPrimitive.Root; +const OrderDialogTrigger = DialogPrimitive.Trigger; + +const OrderDialogContent = React.forwardRef( + ({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + + ) +); +OrderDialogContent.displayName = DialogPrimitive.Content.displayName; + +export function OrderDetailsDialog({ isOpen, onClose, order, shopId }) { + const list = useList("Order"); + const client = useApolloClient(); + + const { create, createWithData, props, state, error } = useCreateItem(list); + const { createViewFieldModes } = useKeystone(); + const [isInitialized, setIsInitialized] = useState(false); + const [activeTab, setActiveTab] = useState("orderDetails"); + const [localState, setLocalState] = useState({ + lineItems: [], + cartItems: [], + }); + + const { data: shopsData } = useQuery(SHOPS_QUERY); + const { data: channelsData } = useQuery(CHANNELS_QUERY); + + const shops = shopsData?.shops || []; + const channels = channelsData?.channels || []; + + const filteredProps = useMemo(() => { + const modifications = Object.values(SECTIONS) + .flatMap((section) => section.fields) + .map((key) => ({ key })); + return getFilteredProps(props, modifications, true); + }, [props]); + + const filterValidCartItems = (cartItems) => { + return cartItems.filter((item) => + channels.some((channel) => channel.id === item.channel?.id) + ); + }; + + const handleSave = async () => { + const formData = {}; + + // Process props.value + Object.entries(props.value).forEach(([key, value]) => { + if ( + value.value?.inner?.value !== null && + value.value?.inner?.value !== "" && + value.value?.inner?.value !== undefined + ) { + formData[key] = value.value.inner.value; + } + }); + + // Special handling for shop + if (props.value.shop?.value?.value?.id) { + formData.shop = { connect: { id: props.value.shop.value.value.id } }; + } + + // Process lineItems + if (localState.lineItems.length > 0) { + formData.lineItems = { + create: localState.lineItems.map((item) => ({ + name: item.title || item.name, + image: item.image, + price: item.price, + quantity: parseInt(item.quantity), + productId: item.productId, + variantId: item.variantId, + lineItemId: item.lineItemId, + })), + }; + } + + // Process cartItems + const validCartItems = localState.cartItems.filter((item) => + channels.some((channel) => channel.id === item.channel?.id) + ); + + if (validCartItems.length > 0) { + formData.cartItems = { + create: validCartItems.map((item) => ({ + name: item.title || item.name, + image: item.image, + price: item.price, + quantity: parseInt(item.quantity), + productId: item.productId, + variantId: item.variantId, + channel: item.channel + ? { connect: { id: item.channel.id } } + : undefined, + })), + }; + } else { + // If there are no valid cart items, ensure cartItems is not included in formData + delete formData.cartItems; + } + + // Connect the shop + // if (shopId) { + // formData.shop = { connect: { id: shopId } }; + // } + + try { + const item = await createWithData({ data: formData }); + if (item) { + onClose(); + // await client.refetchQueries({ + // include: "active", + // }); + await client.refetchQueries({ + include: "active", + + }); + } + } catch (error) { + console.error("Error creating order:", error); + } + }; + + useEffect(() => { + if (order && !isInitialized) { + props.onChange((oldValues) => { + const newValues = { ...oldValues }; + + // Set shop value first + if (shopId && newValues.shop) { + newValues.shop = { + ...oldValues.shop, + kind: "value", + value: { + id: null, + kind: "one", + value: { + id: shopId, + label: shops.find((shop) => shop.id === shopId)?.name || "", + data: { __typename: "Shop" }, + }, + initialValue: null, + }, + }; + } + + if (order) { + Object.keys(order).forEach((key) => { + if (newValues[key] && key !== "shop") { + newValues[key] = { + ...oldValues[key], + value: { + ...oldValues[key].value, + inner: { + ...oldValues[key].value.inner, + value: order[key], + }, + }, + }; + } + }); + } + // Ensure status is set to PENDING + newValues.status = { + ...oldValues.status, + value: { + ...oldValues.status?.value, + inner: { + ...oldValues.status?.value?.inner, + value: "PENDING", + }, + }, + }; + + return newValues; + }); + setLocalState({ + lineItems: order.lineItems || [], + cartItems: order.cartItems || [], + }); + setIsInitialized(true); + } + }, [order, props, isInitialized]); + + return ( + + +
+

Create Order

+

+ View and edit order information before creation +

+
+
+ {/* Mobile dropdown */} +
+ +
+ {/* Desktop sidebar */} +
+ {Object.entries(SECTIONS).map(([key, { label, description }]) => ( + + ))} +
+
+
+ {error && ( + + )} + {createViewFieldModes.state === "error" && ( + + )} + {createViewFieldModes.state === "loading" && ( +
Loading update form...
+ )} + {/* {JSON.stringify(props.value.shop)} */} + {activeTab === "lineItems" ? ( + + ) : activeTab === "cartItems" ? ( + + ) : ( + ({ key })), + true + )} + /> + )} +
+
+
+
+ + +
+
+
+ ); +} diff --git a/app/dashboard/(admin)/oms/shops/(components)/PlatformCard.js b/app/dashboard/(admin)/oms/shops/(components)/PlatformCard.js new file mode 100644 index 0000000..b7987c2 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/PlatformCard.js @@ -0,0 +1,85 @@ +import React, { useState } from "react"; +import { useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { EllipsisVertical, Plus } from "lucide-react"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; +import { + Badge, + BadgeButton, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { Skeleton } from "@ui/skeleton"; +import { CreatePlatform } from "./CreatePlatform"; +import { SHOP_PLATFORMS_QUERY } from "./ShopPlatforms"; + +export const PlatformCard = ({ openDrawer, setSelectedPlatform }) => { + const [selectedPlatformId, setSelectedPlatformId] = useState(null); + const { data, loading, error, refetch } = useQuery(SHOP_PLATFORMS_QUERY, { + variables: { where: { OR: [] }, take: 50, skip: 0 }, + }); + + if (loading) { + return ( +
+

Platforms

+
+ {[1, 2, 3].map((i) => ( + + ))} +
+
+ ); + } + + if (error) return
Error loading platforms: {error.message}
; + const platforms = data?.items || []; + + const handlePlatformClick = (platformId) => { + if (selectedPlatformId === platformId) { + setSelectedPlatformId(null); + setSelectedPlatform(null); + } else { + setSelectedPlatformId(platformId); + setSelectedPlatform(platformId); + } + }; + + return ( +
+

+ Platforms +

+
+ + + + } + /> + + {platforms.map((platform) => ( + handlePlatformClick(platform.id)} + > + {platform.name} + + + ))} +
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/shops/(components)/SearchOrders.js b/app/dashboard/(admin)/oms/shops/(components)/SearchOrders.js new file mode 100644 index 0000000..a99a9a9 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/SearchOrders.js @@ -0,0 +1,515 @@ +import React, { useState } from "react"; +import { useQuery } from "@apollo/client"; +import { gql } from "@apollo/client"; +import { Button } from "@ui/button"; +import { Skeleton } from "@ui/skeleton"; +import { Badge, BadgeButton } from "@ui/badge"; +import { + ArrowLeft, + ArrowRight, + ChevronRight, + CirclePlus, + ChevronDown, + ArrowRight as ArrowRightIcon, + MoreHorizontal, +} from "lucide-react"; +import { Input } from "@ui/input"; +import { cn } from "@keystone/utils/cn"; +import { buttonVariants } from "@ui/button"; +import { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent, +} from "@ui/accordion"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@ui/collapsible"; + +const SEARCH_SHOP_ORDERS = gql` + query SearchShopOrders( + $shopId: ID! + $searchEntry: String + $take: Int! + $skip: Int + $after: String + ) { + searchShopOrders( + shopId: $shopId + searchEntry: $searchEntry + take: $take + skip: $skip + after: $after + ) { + orders { + orderId + orderName + link + date + firstName + lastName + streetAddress1 + streetAddress2 + city + state + zip + country + email + cartItems { + productId + variantId + quantity + price + name + image + channel { + id + name + } + } + lineItems { + name + quantity + price + image + productId + variantId + lineItemId + } + fulfillments { + company + number + url + } + note + totalPrice + cursor + } + hasNextPage + } + } +`; + +export function SearchOrders({ + shops, + shopId: initialShopId, + pageSize = 10, + onOrderSelect, +}) { + const [searchEntry, setSearchEntry] = useState(""); + const [activeSearch, setActiveSearch] = useState(""); + const [skip, setSkip] = useState(0); + const [after, setAfter] = useState(null); + const [selectedShopId, setSelectedShopId] = useState(initialShopId || "ALL"); + + const handleSearch = () => { + setSkip(0); + setAfter(null); + setActiveSearch(searchEntry); + }; + + const handleNextPage = (newSkip, newAfter) => { + setSkip(newSkip); + setAfter(newAfter); + }; + + const handlePreviousPage = (newSkip) => { + setSkip(newSkip); + setAfter(null); + }; + + const handleShopChange = (shopId) => { + setSelectedShopId(shopId); + setSkip(0); + setAfter(null); + setActiveSearch(""); + setSearchEntry(""); + }; + + return ( +
+
+
+
+ + +
+
+ setSearchEntry(e.target.value)} + onKeyPress={(e) => { + if (e.key === "Enter") { + handleSearch(); + } + }} + /> +
+ + Search + +
+
+
+ {shops && ( +
+ handleShopChange("ALL")} + > + All Shops + + {shops.map((shop) => ( + handleShopChange(shop.id)} + > + {shop.name} + + ))} +
+ )} +
+
+ {selectedShopId === "ALL" ? ( + + ) : ( + + )} +
+
+ ); +} + +function AllShopsAccordion({ + shops, + searchEntry, + skip, + after, + pageSize, + onNextPage, + onPreviousPage, + onOrderSelect, +}) { + return ( +
+ {shops.map((shop) => ( + + ))} +
+ ); +} + +function ShopCollapsible({ + shop, + searchEntry, + skip, + after, + pageSize, + onNextPage, + onPreviousPage, + onOrderSelect, +}) { + const [isOpen, setIsOpen] = useState(false); + + return ( +
+ { + e.preventDefault(); + setIsOpen(!isOpen); + }} + className="list-none outline-none cursor-pointer" + > +
+
+ +
+
+ {shop.name} + {/* + Search Results + */} +
+
+
+
+ +
+
+ ); +} + +function OrdersContent({ + shopId, + searchEntry, + skip, + after, + pageSize, + onNextPage, + onPreviousPage, + onOrderSelect, +}) { + const { data, loading, error } = useQuery(SEARCH_SHOP_ORDERS, { + variables: { shopId, searchEntry, take: pageSize, skip, after }, + fetchPolicy: "network-only", + skip: !shopId, + }); + + if (!shopId) return null; + if (loading) return ; + if (error) + return ( +
+ + Error loading orders: {error?.message} + +
+ ); + + const { orders, hasNextPage } = data?.searchShopOrders || { + orders: [], + hasNextPage: false, + }; + + return ( +
+
+ {orders.map((order) => ( + + onOrderSelect && onOrderSelect({ ...order, shop: { id: shopId } }) + } + isSearchResult={true} + /> + ))} +
+
+ ); +} + +const OrderDetailsComponent = ({ order, shopId, onSelect, isSearchResult }) => { + return ( + + +
+
+
+ + {order.orderName} + + + {order.date} + +
+
+

+ {order.firstName} {order.lastName} +

+

{order.streetAddress1}

+ {order.streetAddress2 &&

{order.streetAddress2}

} +

+ {order.city}, {order.state} {order.zip} +

+
+
+
+ onSelect(order)} + > + {" "} + {isSearchResult ? "SELECT" : "CREATE"} + + + + + + +
+
+ +
+ + {order.cartItems && order.cartItems.length > 0 && ( + + // <>{JSON.stringify(order.cartItems)} + )} +
+
+
+
+ ); +}; + +const ProductDetailsCollapsible = ({ items, title, defaultOpen = true }) => { + const [isOpen, setIsOpen] = useState(defaultOpen); + const isCartItem = title === "Cart Item"; + + return ( + + + + + + {items.map((item, index) => ( +
+
+ {item.image && ( + {item.name} + )} +
+
+ {item.channel?.name} +
+ {item.name} +
+ {item.productId} | {item.variantId} +
+ {item.quantity > 1 ? ( +
+

+ ${(parseFloat(item.price) * item.quantity).toFixed(2)} +

+

+ (${parseFloat(item.price).toFixed(2)} x {item.quantity}) +

+
+ ) : ( +

+ ${parseFloat(item.price).toFixed(2)} +

+ )} + {item.purchaseId && ( + + {item.purchaseId} + + )} +
+
+ {isCartItem && item.url && ( + + )} +
+
+
+ ))} +
+
+ ); +}; + +export default SearchOrders; diff --git a/app/dashboard/(admin)/oms/shops/(components)/ShopPlatforms.js b/app/dashboard/(admin)/oms/shops/(components)/ShopPlatforms.js new file mode 100644 index 0000000..b149807 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/ShopPlatforms.js @@ -0,0 +1,197 @@ +"use client"; + +import React, { useState } from "react"; +import { useQuery, gql } from "@keystone-6/core/admin-ui/apollo"; +import { EllipsisVertical, Plus } from "lucide-react"; +import { Skeleton } from "@ui/skeleton"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; + +import { Badge } from "@ui/badge"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuPortal, + DropdownMenuTrigger, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/dropdown-menu-depracated"; +import { CreatePlatform } from "./CreatePlatform"; + +export const SHOP_PLATFORMS_QUERY = gql` + query ( + $where: ShopPlatformWhereInput + $take: Int! + $skip: Int! + $orderBy: [ShopPlatformOrderByInput!] + ) { + items: shopPlatforms(where: $where, take: $take, skip: $skip, orderBy: $orderBy) { + id + name + orderLinkFunction + updateProductFunction + getWebhooksFunction + deleteWebhookFunction + createWebhookFunction + searchProductsFunction + getProductFunction + searchOrdersFunction + addTrackingFunction + addCartToPlatformOrderFunction + cancelOrderWebhookHandler + createOrderWebhookHandler + oAuthFunction + oAuthCallbackFunction + appKey + appSecret + createdAt + updatedAt + __typename + } + count: shopPlatformsCount(where: $where) + } +`; + +const ShopPlatformsContent = ({ data, openDrawer, showAll }) => { + if (!data || !data.items) return null; + + const platformItems = [...data.items]; + + return platformItems + .slice(0, showAll ? platformItems.length : 6) + .map((platform, index) => ( +
+ + +
+ )); +}; + +const useShopPlatformsQuery = () => { + return useQuery(SHOP_PLATFORMS_QUERY, { + variables: { + where: { OR: [] }, + take: 50, + skip: 0, + }, + }); +}; + +export const ShopPlatforms = ({ openDrawer }) => { + const { data, loading, error } = useShopPlatformsQuery(); + const [showAll, setShowAll] = useState(true); + + if (loading) { + return ( +
+ {Array(6) + .fill(0) + .map((_, index) => ( + + ))} +
+ ); + } + + if (error) return

Error loading platforms: {error.message}

; + + return ( + + ); +}; + +export const ShopPlatformsMobile = ({ openDrawer }) => { + const { data, loading, error, refetch } = useShopPlatformsQuery(); + + if (loading) { + return ( +
+ {Array(6) + .fill(0) + .map((_, index) => ( + + ))} +
+ ); + } + + if (error) return

Error loading platforms: {error.message}

; + + return ( + + + {data.items.length && ( + + )} + + + + + + + ) : ( + + ) + } + /> + + + {data.items.map((platform) => ( + openDrawer(platform.id, "ShopPlatform")} + className="block w-full text-left px-4 py-2 text-sm" + > + {platform.name} + + ))} + + + + ); +}; diff --git a/app/dashboard/(admin)/oms/shops/(components)/Shops.js b/app/dashboard/(admin)/oms/shops/(components)/Shops.js new file mode 100644 index 0000000..a5111fb --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/Shops.js @@ -0,0 +1,307 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { useQuery, gql } from "@keystone-6/core/admin-ui/apollo"; +import { + ChevronDownIcon, + Circle, + EllipsisVertical, + Square, + Triangle, + Webhook, +} from "lucide-react"; +import { + DescriptionDetails, + DescriptionList, + DescriptionTerm, +} from "@ui/description-list"; +import { + differenceInHours, + format, + parseISO, + differenceInMinutes, +} from "date-fns"; +import { Skeleton } from "@ui/skeleton"; +import { Badge } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; +import { Webhooks } from "./Webhooks"; +import { Avatar } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/avatar"; +import { Squircle } from "@squircle-js/react"; +import { + Cog6ToothIcon, + Cog8ToothIcon, + CogIcon, + LinkIcon, + Square2StackIcon, + Square3Stack3DIcon, + TicketIcon, +} from "@heroicons/react/16/solid"; +import { Links } from "./Links"; +import { RiCalculatorLine, RiMapPin2Line } from "@remixicon/react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs"; +import { SearchOrders } from "./SearchOrders"; + +export const SHOPS_QUERY = gql` + query ( + $where: ShopWhereInput + $take: Int! + $skip: Int! + $orderBy: [ShopOrderByInput!] + ) { + items: shops(where: $where, take: $take, skip: $skip, orderBy: $orderBy) { + id + name + platform { + name + } + linkMode + links(orderBy: [{ rank: asc }]) { + id + rank + channel { + id + name + } + filters + } + ordersCount + shopItemsCount + linksCount + createdAt + updatedAt + } + count: shopsCount(where: $where) + } +`; + +function formatDate(dateString) { + const date = parseISO(dateString); + const now = new Date(); + const minutesDifference = differenceInMinutes(now, date); + const hoursDifference = differenceInHours(now, date); + + if (minutesDifference < 60) { + return `${minutesDifference} minutes ago`; + } else if (hoursDifference < 24) { + return `${hoursDifference} hours ago`; + } else { + return format(date, "PPP"); + } +} + +export const Shops = ({ openDrawer, selectedPlatform }) => { + const { data, loading, error, refetch } = useQuery(SHOPS_QUERY, { + variables: { + where: selectedPlatform + ? { platform: { id: { equals: selectedPlatform } } } + : { OR: [] }, + take: 50, + skip: 0, + }, + }); + + useEffect(() => { + refetch(); + }, [selectedPlatform]); + + const [showAll, setShowAll] = useState(false); + + if (loading) { + return ( +
+ {Array(6).fill(0).map((_, index) => ( +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ ))} +
+ ); + } + + if (error) return

Error loading shops: {error.message}

; + + const shopItems = data.items; + + return ( +
+ {!showAll && shopItems.length > 6 && ( +
+ )} + {shopItems.length ? ( +
+ {shopItems.slice(0, showAll ? shopItems.length : 6).map((shop) => ( +
+
+
+
+ + {shop.name.slice(0, 2)} + +
+
+
+

{shop.name}

+
+ +
+ {shop.platform ? ( +
+
+
+
+ {shop.platform.name} +
+ ) : ( +
+
+
+
+ Platform not connected +
+ )} + +

+ Last updated {formatDate(shop.updatedAt)} +

+
+
+ +
+ + + + + + + + + + + +
+ +

+

+
+ Create platform webhooks to keep Openship in sync +
+
+ +

+
+ +

+

+ +
+

+
+ +
+ {/* Adjust the height as needed */} + +
+
+
+
+
+
+ ))} +
+ ) : ( +
+
+
+ + + +
+ + + No Shops found + + + Get started by creating a new one. + +
+
+ )} + + {!showAll && shopItems.length > 6 && ( +
+ +
+ )} +
+ ); +}; diff --git a/app/dashboard/(admin)/oms/shops/(components)/Webhooks.js b/app/dashboard/(admin)/oms/shops/(components)/Webhooks.js new file mode 100644 index 0000000..9b1f1c1 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/(components)/Webhooks.js @@ -0,0 +1,260 @@ +import React, { useState } from "react"; +import { useMutation, gql, useQuery } from "@keystone-6/core/admin-ui/apollo"; +import { Button } from "@keystone/themes/Tailwind/atlas/primitives/default/ui/button"; +import { + Badge, + BadgeButton, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/badge"; +import { useToasts } from "@keystone/screens"; +import { InfoCircledIcon } from "@radix-ui/react-icons"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@keystone/themes/Tailwind/atlas/primitives/default/ui/tooltip"; +import { RiLoader2Fill } from "@remixicon/react"; +import { Plus } from "lucide-react"; +import { GraphQLErrorNotice } from "@keystone/themes/Tailwind/atlas/components/GraphQLErrorNotice"; + +const CREATE_SHOP_WEBHOOK = gql` + mutation CreateShopWebhook( + $shopId: ID! + $topic: String! + $endpoint: String! + ) { + createShopWebhook(shopId: $shopId, topic: $topic, endpoint: $endpoint) { + success + error + webhookId + } + } +`; + +const DELETE_SHOP_WEBHOOK = gql` + mutation DeleteShopWebhook($shopId: ID!, $webhookId: ID!) { + deleteShopWebhook(shopId: $shopId, webhookId: $webhookId) { + success + error + } + } +`; + +const GET_SHOP_WEBHOOKS = gql` + query GetShopWebhooks($shopId: ID!) { + getShopWebhooks(shopId: $shopId) { + id + callbackUrl + topic + } + } +`; + +const RECOMMENDED_WEBHOOKS = [ + { + callbackUrl: "/api/handlers/shop/create-order/[shopId]", + topic: "ORDER_CREATED", + description: + "When an order is created on this shop, Openship will create the order to be fulfilled.", + }, + { + callbackUrl: "/api/handlers/shop/cancel-order/[shopId]", + topic: "ORDER_CANCELLED", + description: + "When an order is cancelled on this shop, Openship will mark the order status cancelled", + }, + { + callbackUrl: "/api/handlers/shop/cancel-order/[shopId]", + topic: "ORDER_CHARGEBACKED", + description: + "When an order is chargebacked on this shop, Openship will mark the order status cancelled", + }, +]; + +const WebhookItem = ({ webhook, refetch, shopId }) => { + const [deleteWebhook] = useMutation(DELETE_SHOP_WEBHOOK); + const [loading, setLoading] = useState(false); + + const handleDelete = async () => { + setLoading(true); + await deleteWebhook({ + variables: { + shopId, + webhookId: webhook.id, + }, + }) + .then(({ errors }) => { + const error = errors?.find( + (x) => x.path === undefined || x.path?.length === 1 + ); + if (error) { + toasts.addToast({ + title: "Failed to delete webhook", + tone: "negative", + message: error.message, + }); + } else { + toasts.addToast({ + tone: "positive", + title: "Webhook deleted successfully", + }); + } + }) + .catch((err) => { + toasts.addToast({ + title: "Failed to delete webhook", + tone: "negative", + message: err.message, + }); + }); + refetch(); + setLoading(false); + }; + + return ( +
+
+ {webhook.topic} + + {loading ? "Deleting..." : "Delete"} + +
+
+ Callback URL: + {webhook.callbackUrl} +
+
+ ); +}; + +const RecommendedWebhookItem = ({ webhook, refetch, shopId }) => { + const [createWebhook, { loading, error }] = useMutation(CREATE_SHOP_WEBHOOK); + const toasts = useToasts(); + + const handleCreate = async () => { + await createWebhook({ + variables: { + shopId, + topic: webhook.topic, + endpoint: webhook.callbackUrl.replace("[shopId]", shopId), + }, + }) + .then(({ errors }) => { + const error = errors?.find( + (x) => x.path === undefined || x.path?.length === 1 + ); + if (error) { + toasts.addToast({ + title: "Failed to create webhook", + tone: "negative", + message: error.message, + }); + } else { + toasts.addToast({ + tone: "positive", + title: "Webhook created successfully", + }); + } + }) + .catch((err) => { + toasts.addToast({ + title: "Failed to create webhook", + tone: "negative", + message: err.message, + }); + }); + refetch(); + }; + + return ( +
+
+
+ + {webhook.topic} + + + + + + +

{webhook.description}

+
+
+
+
+
+
+ ); +}; + +export const Webhooks = ({ shopId }) => { + const { data, loading, error, refetch } = useQuery(GET_SHOP_WEBHOOKS, { + variables: { shopId }, + }); + + if (loading) { + return
Loading webhooks...
; + } + + if (error) { + return ( +
+ + Error loading webhooks: {error?.message} + +
+ ); + } + + const webhooks = data.getShopWebhooks; + + return ( +
+
+ {webhooks.map((webhook) => ( + + ))} +
+ +
+ {RECOMMENDED_WEBHOOKS.map((webhook) => { + const existingWebhook = webhooks.find( + (w) => + w.topic === webhook.topic && + w.callbackUrl === webhook.callbackUrl.replace("[shopId]", shopId) + ); + return !existingWebhook ? ( + + ) : null; + })} +
+
+ ); +}; diff --git a/app/dashboard/(admin)/oms/shops/page.js b/app/dashboard/(admin)/oms/shops/page.js new file mode 100644 index 0000000..46ce4b6 --- /dev/null +++ b/app/dashboard/(admin)/oms/shops/page.js @@ -0,0 +1,42 @@ +"use client"; + +import React, { useState } from "react"; +import { Shops } from "./(components)/Shops"; +import { CreateShop } from "./(components)/CreateShop"; +import { PlatformCard } from "./(components)/PlatformCard"; +import { useDrawer } from "@keystone/themes/Tailwind/atlas/components/Modals/drawer-context"; + +const ShopsPage = () => { + const { openEditDrawer } = useDrawer(); + const [selectedPlatform, setSelectedPlatform] = useState(null); + + return ( +
+
+
+

Shops

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ ); +}; + +export default ShopsPage; \ No newline at end of file diff --git a/app/dashboard/(admin)/page.js b/app/dashboard/(admin)/page.js index 4f58886..aacbb7a 100644 --- a/app/dashboard/(admin)/page.js +++ b/app/dashboard/(admin)/page.js @@ -1,5 +1,5 @@ "use client"; -import { HomePage } from "@keystone/screens/HomePage"; +import { HomePage } from "@keystone/screens"; export default HomePage; diff --git a/app/dashboard/init/page.js b/app/dashboard/init/page.js index 69bbfa5..faeb85c 100644 --- a/app/dashboard/init/page.js +++ b/app/dashboard/init/page.js @@ -1,6 +1,5 @@ "use client"; -import { InitPage } from "@keystone/screens/InitPage"; -import { SignInPage } from "@keystone/screens/SignInPage"; +import { InitPage } from "@keystone/screens"; -export default InitPage \ No newline at end of file +export default InitPage; diff --git a/app/dashboard/layout.js b/app/dashboard/layout.js index a485567..41dda3b 100644 --- a/app/dashboard/layout.js +++ b/app/dashboard/layout.js @@ -1,28 +1,7 @@ "use client"; -import { DrawerProvider } from "@keystone/components/Modals"; -import { ToastProvider } from "@keystone/components/Toast"; -import { UIProvider } from "@keystone/components/UIProvider"; -import { KeystoneProvider } from "@keystone/keystoneProvider"; -import { AppProgressBar as ProgressBar } from "next-nprogress-bar"; -// import './globals.css' +import { OuterLayout } from "@keystone/screens"; -export default function RootLayout({ children }) { - return ( - - - - - {children} - - - - - - ); +export default function Layout({ children }) { + return {children}; } diff --git a/app/dashboard/no-access/page.js b/app/dashboard/no-access/page.js index b881f4c..637d752 100644 --- a/app/dashboard/no-access/page.js +++ b/app/dashboard/no-access/page.js @@ -1,5 +1,5 @@ "use client"; -import { NoAccessPage } from "@keystone/screens/NoAccessPage"; +import { NoAccessPage } from "@keystone/screens"; -export default NoAccessPage \ No newline at end of file +export default NoAccessPage; diff --git a/app/dashboard/reset/page.js b/app/dashboard/reset/page.js new file mode 100644 index 0000000..fc9c300 --- /dev/null +++ b/app/dashboard/reset/page.js @@ -0,0 +1,5 @@ +"use client"; + +import { ResetPage } from "@keystone/screens"; + +export default ResetPage; diff --git a/app/dashboard/signin/page.js b/app/dashboard/signin/page.js index bfcce60..fb250be 100644 --- a/app/dashboard/signin/page.js +++ b/app/dashboard/signin/page.js @@ -1,5 +1,5 @@ "use client"; -import { SignInPage } from "@keystone/screens/SignInPage"; +import { SignInPage } from "@keystone/screens"; -export default SignInPage \ No newline at end of file +export default SignInPage; diff --git a/app/dashboard/signup/page.js b/app/dashboard/signup/page.js new file mode 100644 index 0000000..d053dbd --- /dev/null +++ b/app/dashboard/signup/page.js @@ -0,0 +1,5 @@ +"use client"; + +import { SignUpPage } from "@keystone/screens"; + +export default SignUpPage; diff --git a/channelAdapters/bigcommerce.js b/channelAdapters/bigcommerce.js new file mode 100644 index 0000000..bf411cd --- /dev/null +++ b/channelAdapters/bigcommerce.js @@ -0,0 +1,384 @@ + +// Function to search products in BigCommerce +export async function searchProducts({ domain, accessToken, searchEntry }) { + const products = []; + + // Helper function to format product data + function formatProductData(variant, product) { + return { + image: variant.image_url || product.images[0]?.url_thumbnail || "", + title: `${product.name} ${ + variant.option_values?.length > 0 + ? `(${variant.option_values.map((o) => o.label).join(", ")})` + : "" + }`, + productId: product.id.toString(), + variantId: variant.id.toString(), + price: variant.price?.toString() || product.price?.toString() || "0", + availableForSale: product.availability === "available", + inventory: variant.inventory_level, + inventoryTracked: variant.inventory_tracking !== "none", + }; + } + + const includeFields = "images,variants"; + + const queryParams = new URLSearchParams({ + include: includeFields, + }); + if (searchEntry) queryParams.set("name:like", searchEntry); + + const response = await fetch( + `https://api.bigcommerce.com/stores/${domain}/v3/catalog/products?${queryParams.toString()}`, + { + method: "GET", + headers: { + "X-Auth-Token": accessToken, + "Content-Type": "application/json", + }, + } + ); + const responseData = await response.json(); + + responseData.data.forEach((product) => { + product.variants.forEach((variant) => { + products.push(formatProductData(variant, product)); + }); + }); + + return { products }; +} + +// Function to get a specific product by variantId and productId in BigCommerce +export async function getProduct({ + domain, + accessToken, + variantId, + productId, +}) { + // Helper function to format product data + function formatProductData(variant, product) { + return { + image: variant.image_url || product.images[0]?.url_thumbnail || "", + title: `${product.name} ${ + variant.option_values?.length > 0 + ? `(${variant.option_values.map((o) => o.label).join(", ")})` + : "" + }`, + productId: product.id.toString(), + variantId: variant.id.toString(), + price: variant.price?.toString() || product.price?.toString() || "0", + availableForSale: product.availability === "available", + inventory: variant.inventory_level, + inventoryTracked: variant.inventory_tracking !== "none", + }; + } + + const includeFields = "images,variants"; + + const variantResponse = await fetch( + `https://api.bigcommerce.com/stores/${domain}/v3/catalog/products/${productId}/variants/${variantId}`, + { + method: "GET", + headers: { + "X-Auth-Token": accessToken, + "Content-Type": "application/json", + }, + } + ); + const variant = await variantResponse.json(); + + if (!variant || !variant.data) { + throw new Error("Variant not found from BigCommerce"); + } + + const productResponse = await fetch( + `https://api.bigcommerce.com/stores/${domain}/v3/catalog/products/${productId}?include=${includeFields}`, + { + method: "GET", + headers: { + "X-Auth-Token": accessToken, + "Content-Type": "application/json", + }, + } + ); + const product = await productResponse.json(); + + if (!product || !product.data) { + throw new Error("Product not found from BigCommerce"); + } + + const formattedProduct = formatProductData(variant.data, product.data); + + return formattedProduct; +} + +// Create Purchase +export async function createPurchase({ + domain, + accessToken, + cartItems, + email, + address, + orderId, +}) { + const shopItems = cartItems.map(({ productId, quantity }) => ({ + product_id: productId, + quantity, + })); + + const response = await fetch( + `https://api.bigcommerce.com/stores/${domain}/v2/orders`, + { + method: "POST", + headers: { + "X-Auth-Token": accessToken, + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ + billing_address: { + first_name: address.firstName, + last_name: address.lastName, + street_1: address.streetAddress1, + city: address.city, + state: address.state, + zip: address.zip, + country: "United States", // Adapt country code as needed + country_iso2: "US", + email, + }, + shipping_addresses: [ + { + first_name: address.firstName, + last_name: address.lastName, + street_1: address.streetAddress1, + street_2: address.streetAddress2, + city: address.city, + state: address.state, + zip: address.zip, + country: "United States", // Adapt country code as needed + country_iso2: "US", + email, + shipping_method: "Free Shipping", // Adapt shipping method as needed + }, + ], + status_id: 11, // Completed status + staff_notes: `Openship order placed (Order ID: ${orderId})`, + products: shopItems, + customer_id: 0, // Guest customer + }), + } + ); + + const data = await response.json(); + + if (data[0]?.status >= 400) { + throw new Error(`Error creating order: ${data[0].title}`); + } + + return { + purchaseId: data.id.toString(), + }; +} + +// Create Webhook +export async function createWebhook({ domain, accessToken, topic, endpoint }) { + const mapTopic = { + ORDER_CREATED: "store/order/created", + ORDER_CANCELLED: "store/order/archived", + ORDER_CHARGEBACKED: "store/order/refund/created", + TRACKING_CREATED: "store/shipment/created", + }; + + if (!mapTopic[topic]) { + throw new Error("Topic not mapped yet"); + } + + const response = await fetch( + `https://api.bigcommerce.com/stores/${domain}/v3/hooks`, + { + method: "POST", + headers: { + "X-Auth-Token": accessToken, + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ + scope: mapTopic[topic], + destination: `${process.env.FRONTEND_URL}${endpoint}`, + is_active: true, + events_history_enabled: true, + headers: { + custom: "JSON", + }, + }), + } + ); + + const data = await response.json(); + + if (data.title) { + throw new Error(`Error creating webhook: ${data.title}`); + } + + return { success: "Webhook created", webhookId: data.id }; +} + +// Delete Webhook +export async function deleteWebhook({ domain, accessToken, webhookId }) { + const response = await fetch( + `https://api.bigcommerce.com/stores/${domain}/v3/hooks/${webhookId}`, + { + method: "DELETE", + headers: { + "X-Auth-Token": accessToken, + "Content-Type": "application/json", + Accept: "application/json", + }, + } + ); + + const data = await response.json(); + + if (data.title) { + throw new Error(`Error deleting webhook: ${data.title}`); + } + + return { success: "Webhook deleted" }; +} + +// Get Webhooks +export async function getWebhooks({ domain, accessToken }) { + const mapTopic = { + "store/order/created": "ORDER_CREATED", + "store/order/archived": "ORDER_CANCELLED", + "store/order/refund/created": "ORDER_CHARGEBACKED", + "store/shipment/created": "TRACKING_CREATED", + }; + + const response = await fetch( + `https://api.bigcommerce.com/stores/${domain}/v3/hooks`, + { + method: "GET", + headers: { + "X-Auth-Token": accessToken, + "Content-Type": "application/json", + Accept: "application/json", + }, + } + ); + + const { data } = await response.json(); + + const webhooks = data.map(({ id, destination, created_at, scope }) => ({ + id, + createdAt: created_at, + callbackUrl: destination.replace(process.env.FRONTEND_URL, ""), + topic: mapTopic[scope], + includeFields: [], + })); + + return { webhooks }; +} + +// BigCommerce OAuth function +export function oauth(storeHash, config) { + const clientId = config.clientId; + const redirectUri = config.redirectUri; + const scopes = config.scopes; + + const authUrl = `https://login.bigcommerce.com/oauth2/authorize?client_id=${clientId}&response_type=code&scope=${scopes.join(" ")}&redirect_uri=${redirectUri}&context=stores/${storeHash}`; + + window.location.href = authUrl; // Redirect using window.location.href +} + +// BigCommerce callback function +export async function callback(query, { appKey, appSecret, redirectUri }) { + const accessTokenRequestUrl = "https://login.bigcommerce.com/oauth2/token"; + const accessTokenPayload = { + client_id: appKey, + client_secret: appSecret, + redirect_uri: redirectUri, + grant_type: "authorization_code", + ...query, + }; + + const response = await fetch(accessTokenRequestUrl, { + method: "POST", + body: JSON.stringify(accessTokenPayload), + headers: { + "Content-Type": "application/json", + }, + }); + + const responseBody = await response.json(); + const accessToken = responseBody.access_token; + const storeHash = responseBody.context.split("/")[1]; + + return { storeHash, accessToken }; +} + +export async function createTrackingWebhookHandler(req, res) { + const { id, orderId } = req.body.data; + const { producer } = req.body; + if (!id || !orderId) { + return { error: true }; + } + const foundCartItems = await keystoneContext.sudo().query.CartItem.findMany({ + where: { + purchaseId: { equals: orderId.toString() }, + channel: { domain: { equals: producer.split("/")[1] } }, + }, + query: "id quantity channel { domain accessToken }", + }); + + if (!foundCartItems[0]?.channel?.domain || !foundCartItems[0]?.channel?.accessToken) { + return { error: true }; + } + + const response = await fetch( + `https://api.bigcommerce.com/stores/${foundCartItems[0]?.channel?.domain}/v2/orders/${orderId}/shipments/${id}`, + { + method: "GET", + headers: { + "X-Auth-Token": foundCartItems[0]?.channel?.accessToken, + "Content-Type": "application/json", + Accept: "application/json", + }, + } + ); + + const data = await response.json(); + return { + purchaseId: orderId.toString(), + trackingNumber: data.tracking_number, + trackingCompany: getShippingCarrier(data.tracking_number), + domain: producer.split("/")[1], + }; +} + +export async function cancelPurchaseWebhookHandler(req, res) { + const { data, producer } = req.body; + if (!data?.orderId) { + return res.status(400).json({ error: "Missing fields needed to cancel cart item" }); + } + return data.orderId.toString(); +} + +function getShippingCarrier(trackingNumber) { + // Define your carrier logic here + if (/^[\d]{20,22}$/.test(trackingNumber)) { + return "USPS"; + } + if (/^(\d{12}|\d{15})$/.test(trackingNumber)) { + return "FedEx"; + } + if (/^1Z[\dA-Z]{16}$/i.test(trackingNumber)) { + return "UPS"; + } + if (/^([0-9]{10}|[0-9]{12})$/.test(trackingNumber)) { + return "DHL"; + } + return "Other"; +} diff --git a/channelAdapters/index.js b/channelAdapters/index.js new file mode 100644 index 0000000..2f37bed --- /dev/null +++ b/channelAdapters/index.js @@ -0,0 +1,5 @@ +export const channelAdapters = { + shopify: () => import("./shopify"), + bigcommerce: () => import("./bigcommerce"), + woocommerce: () => import("./woocommerce"), +}; diff --git a/channelAdapters/shopify.js b/channelAdapters/shopify.js new file mode 100644 index 0000000..bad048d --- /dev/null +++ b/channelAdapters/shopify.js @@ -0,0 +1,534 @@ +import { GraphQLClient, gql } from "graphql-request"; +import ShopifyToken from "shopify-token"; + +// Add this constant at the top of the file +const REQUIRED_SCOPES = [ + "read_products", + "write_orders", + "read_fulfillments", + "write_fulfillments", + "read_customers", + "write_customers", + "read_shipping", + "write_draft_orders", + "read_draft_orders", + "read_price_rules", + "write_price_rules", + "read_inventory", + "write_inventory", + "read_locations", + "write_merchant_managed_fulfillment_orders", + "read_merchant_managed_fulfillment_orders", +]; + +// Function to search products +export async function searchProducts({ domain, accessToken, searchEntry }) { + const shopifyClient = new GraphQLClient( + `https://${domain}/admin/api/graphql.json`, + { + headers: { + "X-Shopify-Access-Token": accessToken, + }, + } + ); + + const gqlQuery = gql` + query SearchProducts($query: String) { + productVariants(first: 15, query: $query) { + edges { + node { + id + availableForSale + image { + originalSrc + } + price + title + product { + id + handle + title + images(first: 1) { + edges { + node { + originalSrc + } + } + } + } + inventoryQuantity + inventoryPolicy + } + } + } + } + `; + + const { productVariants } = await shopifyClient.request(gqlQuery, { + query: searchEntry, + }); + + if (productVariants.edges.length < 1) { + throw new Error("No products found from Shopify"); + } + + + const products = productVariants.edges.map(({ node }) => ({ + image: + node.image?.originalSrc || node.product.images.edges[0]?.node.originalSrc, + title: `${node.product.title} - ${node.title}`, + productId: node.product.id.split("/").pop(), + variantId: node.id.split("/").pop(), + price: node.price, + availableForSale: node.availableForSale, + inventory: node.inventoryQuantity, + inventoryTracked: node.inventoryPolicy !== "deny", + productLink: `https://${domain}/products/${node.product.handle}`, + })); + + return { products }; +} + +// Function to get a specific product by variantId and productId +export async function getProduct({ + domain, + accessToken, + variantId, + productId, +}) { + const shopifyClient = new GraphQLClient( + `https://${domain}/admin/api/graphql.json`, + { + headers: { + "X-Shopify-Access-Token": accessToken, + }, + } + ); + + const gqlQuery = gql` + query GetProduct($variantId: ID!) { + productVariant(id: $variantId) { + id + availableForSale + image { + originalSrc + } + price + title + product { + id + handle + title + images(first: 1) { + edges { + node { + originalSrc + } + } + } + } + inventoryQuantity + inventoryPolicy + } + } + `; + + const { productVariant } = await shopifyClient.request(gqlQuery, { + variantId: `gid://shopify/ProductVariant/${variantId}`, + productId: `gid://shopify/Product/${productId}`, + }); + + if (!productVariant) { + throw new Error("Product not found from Shopify"); + } + + + const product = { + image: + productVariant.image?.originalSrc || + productVariant.product.images.edges[0]?.node.originalSrc, + title: `${productVariant.product.title} - ${productVariant.title}`, + productId: productVariant.product.id.split("/").pop(), + variantId: productVariant.id.split("/").pop(), + price: productVariant.price, + availableForSale: productVariant.availableForSale, + inventory: productVariant.inventoryQuantity, + inventoryTracked: productVariant.inventoryPolicy !== "deny", + productLink: `https://${domain}/products/${productVariant.product.handle}`, + }; + + return { product }; +} + +// Create Purchase +export async function createPurchase({ + domain, + accessToken, + cartItems, + email, + address, + orderId, +}) { + const shopifyClient = new GraphQLClient( + `https://${domain}/admin/api/graphql.json`, + { + headers: { + "X-Shopify-Access-Token": accessToken, + }, + } + ); + + const shopItems = cartItems.map(({ variantId, quantity }) => ({ + variantId: `gid://shopify/ProductVariant/${variantId}`, + quantity, + })); + + const input = { + email, + note: "Openship order placed", + shippingAddress: { + firstName: address.firstName, + lastName: address.lastName, + address1: address.streetAddress1, + address2: address.streetAddress2, + city: address.city, + province: address.state, + zip: address.zip, + countryCode: "US", // Adapt country code as needed + }, + lineItems: shopItems, + customAttributes: [{ key: "openship_order_id", value: orderId }], + shippingLine: { + title: "Free shipping", // Adapt shipping title/price as needed + price: "0", + }, + }; + + const { + draftOrderCreate: { draftOrder, userErrors }, + } = await shopifyClient.request( + gql` + mutation draftOrderCreate($input: DraftOrderInput!) { + draftOrderCreate(input: $input) { + draftOrder { + id + } + userErrors { + field + message + } + } + } + `, + { input } + ); + + if (userErrors.length > 0) { + throw new Error(`Error creating draft order: ${userErrors[0].message}`); + } + + const { draftOrderComplete } = await shopifyClient.request( + gql` + mutation draftOrderComplete($id: ID!, $paymentPending: Boolean) { + draftOrderComplete(id: $id, paymentPending: $paymentPending) { + draftOrder { + order { + id + } + } + userErrors { + field + message + } + } + } + `, + { + id: draftOrder.id, + paymentPending: false, + } + ); + + if (draftOrderComplete.userErrors.length > 0) { + throw new Error( + `Error completing draft order: ${draftOrderComplete.userErrors[0].message}` + ); + } + + const orderRes = await fetch( + `https:// + domain + }/admin/api/2020-04/orders/${draftOrderComplete.draftOrder.order.id + .split("/") + .pop()}.json`, + { + headers: { + "X-Shopify-Access-Token": accessToken, + }, + } + ); + + const { + order: { order_status_url }, + } = await orderRes.json(); + + return { + purchaseId: draftOrderComplete.draftOrder.order.id.split("/").pop(), + url: order_status_url, + }; +} + +// Create Webhook +export async function createWebhook({ domain, accessToken, topic, endpoint }) { + const mapTopic = { + ORDER_CREATED: "ORDERS_CREATE", + ORDER_CANCELLED: "ORDERS_CANCELLED", + ORDER_CHARGEBACKED: "DISPUTES_CREATE", + TRACKING_CREATED: "FULFILLMENTS_CREATE", + }; + + if (!mapTopic[topic]) { + throw new Error("Topic not mapped yet"); + } + + const shopifyClient = new GraphQLClient( + `https://${domain}/admin/api/graphql.json`, + { + headers: { + "X-Shopify-Access-Token": accessToken, + }, + } + ); + + const { + webhookSubscriptionCreate: { userErrors, webhookSubscription }, + } = await shopifyClient.request( + gql` + mutation ( + $topic: WebhookSubscriptionTopic! + $webhookSubscription: WebhookSubscriptionInput! + ) { + webhookSubscriptionCreate( + topic: $topic + webhookSubscription: $webhookSubscription + ) { + userErrors { + field + message + } + webhookSubscription { + id + } + } + } + `, + { + topic: mapTopic[topic], + webhookSubscription: { + callbackUrl: `${process.env.FRONTEND_URL}${endpoint}`, + format: "JSON", + }, + } + ); + + + if (userErrors.length > 0) { + return { error: `Error creating webhook: ${userErrors[0].message}` }; + } + + return { success: "Webhook created", webhookId: webhookSubscription.id }; +} + +// Delete Webhook +export async function deleteWebhook({ domain, accessToken, webhookId }) { + const shopifyClient = new GraphQLClient( + `https://${domain}/admin/api/graphql.json`, + { + headers: { + "X-Shopify-Access-Token": accessToken, + }, + } + ); + + const { + webhookSubscriptionDelete: { userErrors, deletedWebhookSubscriptionId }, + } = await shopifyClient.request( + gql` + mutation webhookSubscriptionDelete($id: ID!) { + webhookSubscriptionDelete(id: $id) { + deletedWebhookSubscriptionId + userErrors { + field + message + } + } + } + `, + { + id: webhookId, + } + ); + + if (userErrors.length > 0) { + throw new Error(`Error deleting webhook: ${userErrors[0].message}`); + } + + return { success: "Webhook deleted" }; +} + +// Get Webhooks +export async function getWebhooks({ domain, accessToken }) { + const mapTopic = { + ORDERS_CREATE: "ORDER_CREATED", + ORDERS_CANCELLED: "ORDER_CANCELLED", + DISPUTES_CREATE: "ORDER_CHARGEBACKED", + FULFILLMENTS_CREATE: "TRACKING_CREATED", + }; + + const shopifyClient = new GraphQLClient( + `https://${domain}/admin/api/graphql.json`, + { + headers: { + "X-Shopify-Access-Token": accessToken, + }, + } + ); + + const data = await shopifyClient.request( + gql` + query { + webhookSubscriptions(first: 10) { + edges { + node { + id + callbackUrl + createdAt + topic + includeFields + } + } + } + } + ` + ); + + return { + webhooks: data?.webhookSubscriptions.edges.map(({ node }) => ({ + ...node, + callbackUrl: node.callbackUrl.replace(process.env.FRONTEND_URL, ""), + topic: mapTopic[node.topic], + })), + }; +} + +// Shopify OAuth function +export function oauth(domain, config) { + const redirectUri = config.redirectUri; + const scopes = config.scopes; + + const authUrl = `https://${domain}/admin/oauth/authorize?client_id=${ + config.apiKey + }&scope=${scopes.join(",")}&redirect_uri=${redirectUri}`; + + window.location.href = authUrl; +} + +export async function callback( + query, + { appKey, appSecret, redirectUri, scopes } +) { + const { shop, hmac, code, host, timestamp } = query; + + async function getToken() { + if (!hmac || !timestamp) { + return { + status: 422, + error: "Unprocessable Entity: hmac or timestamp not found", + }; + } + + const shopifyToken = new ShopifyToken({ + redirectUri: redirectUri, + sharedSecret: appSecret, + apiKey: appKey, + scopes: scopes, + accessMode: "offline", + timeout: 10000, + }); + + if (!shopifyToken.verifyHmac(query)) { + console.error("Error validating hmac"); + throw new Error("Error validating hmac"); + } + + if (!code) { + // Fetch access token directly if 'code' is not present + try { + const response = await fetch( + `https://${shop}/admin/oauth/access_token`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + client_id: appKey, + client_secret: appSecret, + }), + } + ); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error || "Error fetching access token"); + } + + return data.access_token; + } catch (error) { + console.error("Error fetching access token directly", error); + throw new Error("Error fetching access token directly"); + } + } else { + // Use the code to get the access token + try { + const data = await shopifyToken.getAccessToken(shop, code); + return data.access_token; + } catch (error) { + console.error("Error getting access token with code", error); + throw new Error("Error getting access token with code"); + } + } + } + + try { + const accessToken = await getToken(); + return accessToken; + } catch (error) { + return { + status: 500, + error: error.message, + }; + } +} + +export async function createTrackingWebhookHandler(req, res) { + const { tracking_numbers, tracking_company, order_id } = req.body; + if (!tracking_numbers?.length || !tracking_company || !order_id) { + return { error: true }; + } + return { + purchaseId: order_id.toString(), + trackingNumber: tracking_numbers[0], + trackingCompany: tracking_company, + }; +} + +export async function cancelPurchaseWebhookHandler(req, res) { + const { id } = req.body; + if (!id) { + return res + .status(400) + .json({ error: "Missing fields needed to cancel cart item" }); + } + return id.toString(); +} diff --git a/channelAdapters/woocommerce.js b/channelAdapters/woocommerce.js new file mode 100644 index 0000000..7b12642 --- /dev/null +++ b/channelAdapters/woocommerce.js @@ -0,0 +1,273 @@ + +// Function to search products in WooCommerce +export async function searchProducts({ domain, accessToken, searchEntry }) { + const response = await fetch( + `${domain}/wp-json/wc/v3/products?search=${searchEntry}`, + { + headers: { + Authorization: "Basic " + btoa(accessToken), + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.message || "Unable to fetch products."); + } + + const allProducts = await response.json(); + + const products = []; + for (const { + id, + name, + price, + images, + variations, + purchasable, + } of allProducts) { + if (variations.length === 0) { + products.push({ + image: images[0].src, + title: name, + productId: id.toString(), + variantId: "0", + price, + availableForSale: purchasable && true, + }); + } else { + const variantResponse = await fetch( + `${domain}/wp-json/wc/v3/products/${id}/variations`, + { + headers: { + Authorization: "Basic " + btoa(accessToken), + "Content-Type": "application/json", + }, + } + ); + + const allVariants = await variantResponse.json(); + + for (const variant of allVariants) { + products.push({ + image: variant.image.src, + title: name, + productId: id.toString(), + variantId: variant.id.toString(), + price: variant.price, + availableForSale: variant.purchasable && true, + }); + } + } + } + + return { products }; +} + +// Function to get a specific product by variantId and productId in WooCommerce +export async function getProduct({ domain, accessToken, variantId, productId }) { + const response = await fetch( + `${domain}/wp-json/wc/v3/products/${productId}/variations/${variantId}`, + { + headers: { + Authorization: "Basic " + btoa(accessToken), + "Content-Type": "application/json", + }, + } + ); + const product = await response.json(); + + if (!product) { + throw new Error("Product not found"); + } + + return { + image: product.image.src, + title: product.name, + productId: productId.toString(), + variantId: variantId.toString(), + price: product.price, + availableForSale: product.purchasable && true, + }; +} + + +// Create Purchase +export async function createPurchase({ + domain, + accessToken, + email, + cartItems, + address, +}) { + const { + firstName, + lastName, + streetAddress1, + streetAddress2, + city, + state, + zip, + country, + } = address; + + const line_items = cartItems.map((item) => ({ + product_id: item.productId, + variation_id: item.variantId, + quantity: item.quantity, + })); + + const shipping = { + first_name: firstName, + last_name: lastName, + address_1: streetAddress1, + address_2: streetAddress2, + city, + state, + postcode: zip, + country, + }; + + const orderData = { + line_items, + shipping, + status: "completed", // You might want to adjust the status based on your workflow + }; + + const createOrderResponse = await fetch(`${domain}/wp-json/wc/v3/orders`, { + method: "POST", + headers: { + Authorization: "Basic " + btoa(accessToken), + "Content-Type": "application/json", + }, + body: JSON.stringify(orderData), + }); + + const orderJson = await createOrderResponse.json(); + + if (createOrderResponse.status >= 400) { + throw new Error(`Order creation failed: ${orderJson.message}`); + } + + return { + purchaseId: orderJson.id.toString(), + url: `${domain}/wp-admin/post.php?post=${orderJson.id}&action=edit`, + }; +} + +// Create Webhook +export async function createWebhook({ domain, accessToken, topic, endpoint }) { + const mapTopic = { + ORDER_CREATED: "woocommerce_created_order", + ORDER_CANCELLED: "woocommerce_order_status_cancelled", + ORDER_CHARGEBACKED: "woocommerce_payment_complete", + TRACKING_CREATED: "woocommerce_order_tracking_number_added", + }; + + if (!mapTopic[topic]) { + throw new Error("Topic not mapped yet"); + } + + const response = await fetch(`${domain}/wp-json/wc/v3/webhooks`, { + method: "POST", + headers: { + Authorization: `Bearer ${req.body.accessToken}`, + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ + name: `${topic} webhook`, + topic: mapTopic[topic], + delivery_url: `${process.env.FRONTEND_URL}${endpoint}`, + status: "active", + secret: "", + }), + }); + + const data = await response.json(); + + if (data.message) { + throw new Error(`Error creating webhook: ${data.message}`); + } + + return { success: "Webhook created" }; +} + +// Delete Webhook +export async function deleteWebhook({ domain, accessToken, webhookId }) { + const response = await fetch( + `${domain}/wp-json/wc/v3/webhooks/${webhookId}`, + { + method: "DELETE", + headers: { + Authorization: `Bearer ${btoa(accessToken)}`, + "Content-Type": "application/json", + Accept: "application/json", + }, + } + ); + + const data = await response.json(); + + if (data.message) { + throw new Error(`Error deleting webhook: ${data.message}`); + } + + return { success: "Webhook deleted" }; +} + +// Get Webhooks +export async function getWebhooks({ domain, accessToken }) { + const mapTopic = { + ORDER_CREATED: "woocommerce_checkout_order_processed", + ORDER_CANCELLED: "woocommerce_order_status_cancelled", + ORDER_CHARGEBACKED: "woocommerce_refund_created", + TRACKING_CREATED: "woocommerce_order_status_changed", + }; + + const response = await fetch(`${domain}/wp-json/wc/v3/webhooks`, { + method: "GET", + headers: { + Authorization: `Bearer ${btoa(accessToken)}`, + "Content-Type": "application/json", + Accept: "application/json", + }, + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || "Unable to fetch webhooks."); + } + + const webhooks = data.map(({ id, delivery_url, created_at, topic }) => ({ + id, + createdAt: created_at, + callbackUrl: delivery_url.replace(process.env.FRONTEND_URL, ""), + topic: mapTopic[topic], + includeFields: [], + })); + + return { webhooks }; +} + +export async function createTrackingWebhookHandler(req, res) { + const { shippingOrder } = req.body; + if (!shippingOrder?.trackingNumber?.length || !shippingOrder?.carrier?.name || !shippingOrder?.purchaseOrder) { + return { error: true }; + } + return { + purchaseId: shippingOrder.purchaseOrder, + trackingNumber: shippingOrder.trackingNumber[0], + trackingCompany: shippingOrder.carrier.name, + }; +} + +export async function cancelPurchaseWebhookHandler(req, res) { + const { id } = req.body; + if (!id) { + return res.status(400).json({ error: "Missing fields needed to cancel cart item" }); + } + return id.toString(); +} diff --git a/components/AppShell.js b/components/AppShell.js deleted file mode 100644 index c9b798a..0000000 --- a/components/AppShell.js +++ /dev/null @@ -1,1199 +0,0 @@ -import React, { useState } from "react"; -import { - createStyles, - Navbar, - UnstyledButton, - Tooltip, - Title, - Box, - Badge, - Group, - ActionIcon, - useMantineColorScheme, - useMantineTheme, - Divider, - Breadcrumbs, - Menu, - Skeleton, - Stack, - Popper, - Center, - Paper, - Code, - Button, - Text, - Burger, - Transition, -} from "@mantine/core"; - -import { - GlobeIcon, - IssueReopenedIcon, - StackIcon, - ContainerIcon, - SunIcon, - MoonIcon, - PackageIcon, - PlusIcon, - SignOutIcon, - KeyIcon, - CopyIcon, - SyncIcon, - CheckIcon, - ChevronDownIcon, -} from "@primer/octicons-react"; -import { LogoIconSVG } from "@svg"; -import { useRouter } from "next/router"; -import Link from "next/link"; -import { SHOPS_QUERY } from "@graphql/shops"; -import { CHANNELS_QUERY } from "@graphql/channels"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import useSWR from "swr"; -import { CreateShopView } from "./CreateViews/CreateShopView"; -import { CreateChannelView } from "./CreateViews/CreateChannelView"; -import { CreateOrderView } from "./CreateViews/CreateOrderView"; -import { CreateMatchView } from "./CreateViews/CreateMatchView"; -import request, { gql } from "graphql-request"; -import { useClipboard, useClickOutside } from "@mantine/hooks"; -import { useNotifications } from "@mantine/notifications"; -import { useModals } from "@mantine/modals"; -import { useSharedState } from "keystone/lib/useSharedState"; - -const HEADER_SIZE = 50; - -const useStyles = createStyles((theme, { opened }) => ({ - wrapper: { - display: "flex", - flex: 1, - }, - - aside: { - flex: "0 0 60px", - backgroundColor: - theme.colorScheme === "dark" ? theme.colors.dark[7] : theme.white, - display: "flex", - flexDirection: "column", - alignItems: "center", - borderRight: `1px solid ${ - theme.colorScheme === "dark" ? theme.colors.dark[7] : theme.colors.gray[3] - }`, - }, - - main: { - flex: 1, - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[6] - : theme.colors.gray[0], - }, - - mainLink: { - width: 44, - height: 44, - borderRadius: theme.radius.md, - display: "flex", - alignItems: "center", - justifyContent: "center", - color: - theme.colorScheme === "dark" - ? theme.colors.dark[0] - : theme.colors.gray[7], - - "&:hover": { - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.colors.gray[0], - }, - }, - - mainLinkActive: { - "&, &:hover": { - backgroundColor: - theme.colorScheme === "dark" - ? theme.fn.rgba(theme.colors[theme.primaryColor][9], 0.25) - : theme.colors[theme.primaryColor][0], - color: - theme.colors[theme.primaryColor][theme.colorScheme === "dark" ? 4 : 7], - }, - }, - - title: { - boxSizing: "border-box", - fontFamily: `Greycliff CF, ${theme.fontFamily}`, - marginBottom: theme.spacing.xl, - backgroundColor: - theme.colorScheme === "dark" ? theme.colors.dark[7] : theme.white, - padding: theme.spacing.md, - paddingTop: 18, - height: 60, - borderBottom: `1px solid ${ - theme.colorScheme === "dark" ? theme.colors.dark[7] : theme.colors.gray[3] - }`, - }, - - logo: { - boxSizing: "border-box", - width: "100%", - display: "flex", - justifyContent: "center", - height: 60, - paddingTop: theme.spacing.md, - borderBottom: `1px solid ${ - theme.colorScheme === "dark" ? theme.colors.dark[7] : theme.colors.gray[3] - }`, - marginBottom: theme.spacing.xl, - }, - - link: { - boxSizing: "border-box", - display: "flex", - alignItems: "center", - textDecoration: "none", - borderTopRightRadius: theme.radius.sm, - borderBottomRightRadius: theme.radius.sm, - color: - theme.colorScheme === "dark" - ? theme.colors.dark[0] - : theme.colors.gray[7], - padding: `0 ${theme.spacing.md}px`, - marginTop: 2, - fontSize: theme.fontSizes.sm, - marginRight: theme.spacing.md, - height: 44, - lineHeight: "44px", - textTransform: "uppercase", - fontWeight: 700, - letterSpacing: 0.4, - cursor: "pointer", - "&:hover": { - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.colors.gray[1], - color: theme.colorScheme === "dark" ? theme.white : theme.black, - }, - }, - - linkActive: { - "&, &:hover": { - borderLeftColor: - theme.colors[theme.primaryColor][theme.colorScheme === "dark" ? 7 : 5], - backgroundImage: - theme.colorScheme === "dark" - ? `linear-gradient(to right, #1A1B1E, ${theme.colors.dark[6]})` - : `linear-gradient(to right, #fff, ${theme.colors.blue[2]})`, - color: theme.colors.blue[6], - border: `1px solid ${ - theme.colors[theme.colorScheme === "dark" ? "dark" : "blue"][ - theme.colorScheme === "dark" ? 5 : 1 - ] - }`, - borderLeft: "none", - }, - }, - - mobileLink: { - display: "block", - lineHeight: 1, - padding: "8px 12px", - borderRadius: theme.radius.sm, - textDecoration: "none", - color: - theme.colorScheme === "dark" - ? theme.colors.dark[0] - : theme.colors.gray[7], - fontSize: theme.fontSizes.sm, - textTransform: "uppercase", - fontWeight: 700, - letterSpacing: 0.3, - "&:hover": { - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[6] - : theme.colors.gray[2], - }, - - [theme.fn.smallerThan("sm")]: { - borderRadius: 0, - padding: theme.spacing.md, - }, - }, - - mobileLinkActive: { - "&, &:hover": { - backgroundColor: theme.fn.variant({ - variant: "light", - color: theme.primaryColor, - }).background, - color: theme.fn.variant({ variant: "light", color: theme.primaryColor }) - .color, - }, - }, - - dropdown: { - position: "absolute", - top: HEADER_SIZE, - left: 0, - right: 0, - zIndex: 0, - borderTopRightRadius: 0, - borderTopLeftRadius: 0, - borderTopWidth: 0, - overflow: "hidden", - - [theme.fn.largerThan("sm")]: { - display: "none", - }, - }, -})); - -export const mainLinksMockdata = [ - { icon: GlobeIcon, label: "Orders", href: "/" }, - { icon: ContainerIcon, label: "Products", href: "/products" }, - { icon: PackageIcon, label: "Shops", href: "/shops" }, - { icon: StackIcon, label: "Channels", href: "/channels" }, -]; - -export function AppShell({ - loadingTabs, - data, - activeTab, - setActiveTab, - children, -}) { - const [navMode, setNavMode] = useState(null); - const [showModal, setShowModal] = useSharedState("createModal", false); - const router = useRouter(); - - const toggle = (value) => { - if (value === navMode) { - setNavMode(null); - } else { - setNavMode(value); - } - }; - - const ref = useClickOutside(() => setNavMode(null)); - - const { classes, cx } = useStyles({}); - - const { data: shopData } = useSWR(SHOPS_QUERY, gqlFetcher); - - const { data: channelData, error } = useSWR(CHANNELS_QUERY, gqlFetcher); - - const theme = useMantineTheme(); - - const { toggleColorScheme } = useMantineColorScheme(); - - const createModals = { - Order: ( - - ), - Shop: , - Channel: ( - - ), - Match: ( - - ), - }; - - const active = mainLinksMockdata.find( - (item) => item.href === router?.pathname - ); - - const mainLinks = mainLinksMockdata.map((link) => ( - - - setActive(link.label)} - component="a" - className={cx(classes.mainLink, { - [classes.mainLinkActive]: router?.pathname === link.href, - })} - my={3} - > - - - - - )); - - const links = data?.map(({ label, count }, index) => ( - { - event.preventDefault(); - setActiveTab(index); - }} - key={index} - > - {label} - {(count || count === 0) && {count}} - - )); - - const NavigationMap = { - primary: mainLinksMockdata.map((link, index) => ( - - - - {link.label} - - - )), - secondary: data.map((link, index) => ( - { - setActiveTab(index); - setNavMode(null); - }} - > - {link.label} - {(link?.count || link.count === 0) && ( - - {link.count} - - )} - - )), - }; - - const signOut = async () => { - await request( - "/api/graphql", - gql` - mutation { - endSession - } - ` - ); - router.push("/signin"); - }; - - return ( - - {createModals[showModal]} - - -
-
- - - -
- {mainLinks} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {theme.colorScheme === "dark" ? : } - - - -
- {(data || loadingTabs) && ( -
- - {active?.label} - - - - {links} - {loadingTabs && ( - - - - - - )} -
- )} -
-
- - - -
- -
- - {/* - - {data && data[activeTab] && ( - - )} - */} - - {/* - - */} - {/* - {data && data[activeTab] && ( - - )} */} - - { - // setNavMode("primary"); - toggle("primary"); - }} - sx={{ - border: `1px solid ${ - theme.colors.gray[theme.colorScheme === "dark" ? 9 : 2] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - fontSize: "13px", - background: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.fn.lighten(theme.colors.blueGray[0], 0.5), - color: - theme.colorScheme === "dark" - ? theme.colors.dark[0] - : theme.colors.blueGray[6], - }} - > - - - {data && data[activeTab] && ( - - )} - - {(styles) => ( - - {NavigationMap[navMode] && NavigationMap[navMode]} - - )} - - - - - - toggleColorScheme()} - size={28} - color={theme.colorScheme === "dark" ? "gray" : "dark"} - radius="sm" - sx={{ - border: `1px solid ${ - theme.colors.dark[theme.colorScheme === "dark" ? 1 : 8] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - }} - > - {theme.colorScheme === "dark" ? : } - - -
- {/* - {data && data[activeTab] && ( - - )} - */} -
-
- - {children} - -
- ); -} - -function CreateModalButton({ - shopData, - setShowModal, - position = "right", - buttonColor, - buttonBorder, - buttonShadow, -}) { - const theme = useMantineTheme(); - - return ( - - - - } - styles={{ - item: { - color: - theme.colorScheme === "dark" - ? theme.colors.blueGray[2] - : theme.colors.blueGray[7], - textTransform: "uppercase", - fontWeight: 600, - letterSpacing: 0.4, - }, - }} - position={position} - > - Create a new - {[ - { mode: "Order", enabled: shopData?.shops.length > 0 }, - { mode: "Shop" }, - { mode: "Channel" }, - { mode: "Match" }, - ].map(({ mode, enabled = true }) => ( - setShowModal(mode)} - disabled={!enabled} - > - {mode} - - ))} - - ); -} - -function KeyPopper({ - shopData, - setShowModal, - position = "right", - buttonColor, - buttonBorder, -}) { - const [referenceElement, setReferenceElement] = useState(null); - const [visible, setVisible] = useState(false); - const theme = useMantineTheme(); - const clipboard = useClipboard({ timeout: 1000 }); - const notifications = useNotifications(); - const modals = useModals(); - - const { data, error, mutate } = useSWR( - gql` - query KEY_QUERY { - apiKeys { - id - } - } - `, - gqlFetcher - ); - - async function createKey() { - return await request( - "/api/graphql", - gql` - mutation { - createapiKey(data: {}) { - id - } - } - ` - ) - .then(async ({ createapiKey }) => { - await mutate(({ apiKeys }) => { - return { - apiKeys: [createapiKey], - }; - }, false); - notifications.showNotification({ - title: `API Key has been generated.`, - // message: JSON.stringify(data), - }); - }) - .catch((error) => { - // setLoading(false); - notifications.showNotification({ - title: error.response.errors[0].extensions.code, - message: error.response.errors[0].message, - color: "red", - }); - }); - } - - return ( - - setVisible((m) => !m)} - > - - - - - - {data?.apiKeys[0] ? ( - - - - {data.apiKeys[0].id} - clipboard.copy(data.apiKeys[0].id)} - > - {clipboard.copied ? ( - - ) : ( - - )} - - - - - - modals.openConfirmModal({ - title: ( - - Regenerate Key - - ), - centered: true, - children: ( - - Are you sure you want to regenerate this key? This - action will invalidate your previous key and any - applications using the previous key will need to be - updated. - - ), - labels: { - confirm: "Regenerate Key", - cancel: "No don't regenerate it", - }, - confirmProps: { color: "red" }, - // onCancel: () => console.log("Cancel"), - onConfirm: createKey, - }) - } - > - - - - ) : ( - - )} - - {/* setVisible((m) => !m)} - > - - */} - - - - ); -} diff --git a/components/ChannelGrid/dangerous/index.js b/components/ChannelGrid/dangerous/index.js deleted file mode 100644 index 52122db..0000000 --- a/components/ChannelGrid/dangerous/index.js +++ /dev/null @@ -1,157 +0,0 @@ -import React, { useState } from "react"; -import { - Text, - useMantineTheme, - Group, - Box, - ActionIcon, - Input, - Button, - Loader, - Badge, - Stack, -} from "@mantine/core"; -import { FlameIcon, XIcon } from "@primer/octicons-react"; -import { - CHANNELS_QUERY, - CREATE_CHANNEL_METAFIELD_MUTATION, - DELETE_CHANNEL_MUTATION, -} from "@graphql/channels"; -import { useNotifications } from "@mantine/notifications"; -import useSWR from "swr"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import request from "graphql-request"; -import { useModals } from "@mantine/modals"; - -export const Dangerous = ({ channelId, name }) => { - const [loading, setLoading] = useState(false); - const { mutate: mutateChannels } = useSWR(CHANNELS_QUERY, gqlFetcher); - - const theme = useMantineTheme(); - const notifications = useNotifications(); - const modals = useModals(); - - const openDeleteModal = () => - modals.openConfirmModal({ - title: ( - - Delete {name} - - ), - closeOnConfirm: false, - centered: true, - children: ( - - Are you sure you want to delete this channel? This action is - destructive and will delete all data associated with this channel - including orders, links, matches, etc. - - ), - labels: { confirm: "Delete channel", cancel: "No don't delete it" }, - confirmProps: { color: "red", loading }, - // onCancel: () => console.log("Cancel"), - onConfirm: async () => - await request("/api/graphql", DELETE_CHANNEL_MUTATION, { - id: channelId, - }) - .then(async () => { - await mutateChannels(({ channels }) => { - const newData = []; - for (const item of channels) { - if (item.id !== channelId) { - newData.push(item); - } - } - return { - channels: newData, - }; - }, false); - modals.closeAll(); - notifications.showNotification({ - title: `Channel has been deleted.`, - // message: JSON.stringify(data), - }); - }) - .catch((error) => { - setLoading(false); - notifications.showNotification({ - title: error.response.errors[0].extensions.code, - message: error.response.errors[0].message, - color: "red", - }); - }), - }); - - return ( - <> - {/* - Delete channel - - - - - - - Deleting the channel will permanently delete all data connected to - this shop including orders, links, matches, etc. This action is - irreversible. - - - - - */} - - - - - ); -}; diff --git a/components/ChannelGrid/details/EditDetails.js b/components/ChannelGrid/details/EditDetails.js deleted file mode 100644 index f683d03..0000000 --- a/components/ChannelGrid/details/EditDetails.js +++ /dev/null @@ -1,186 +0,0 @@ -import React, { useState } from "react"; -import { - Text, - useMantineTheme, - Button, - Group, - Loader, - Input, - ActionIcon, -} from "@mantine/core"; -import useSWR from "swr"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import { PencilIcon, XIcon } from "@primer/octicons-react"; -import request from "graphql-request"; -import { useNotifications } from "@mantine/notifications"; -import { CHANNELS_QUERY, UPDATE_CHANNEL_MUTATION } from "@graphql/channels"; - -export const EditDetails = ({ detail, channelId }) => { - const theme = useMantineTheme(); - const notifications = useNotifications(); - const [editMode, setEditMode] = useState(false); - const [loading, setLoading] = useState(false); - const [value, setValue] = useState(detail.value); - const { mutate: mutateChannels } = useSWR(CHANNELS_QUERY, gqlFetcher); - - return editMode ? ( - - setValue(event.target.value)} - styles={{ - wrapper: { - width: "100%", - }, - input: { - minHeight: 0, - height: 22, - lineHeight: 1, - }, - }} - autoFocus - type={detail.type} - /> - - - - - - ) : ( - - {detail.type === "password" ? ( - - ) : ( - - {detail.value} - - )} - - - ); -}; diff --git a/components/ChannelGrid/details/index.js b/components/ChannelGrid/details/index.js deleted file mode 100644 index 1e95df1..0000000 --- a/components/ChannelGrid/details/index.js +++ /dev/null @@ -1,89 +0,0 @@ -import React from "react"; -import { Paper, Text, useMantineTheme, Group, Box, Stack } from "@mantine/core"; -import { EditDetails } from "./EditDetails"; - -export const Details = ({ channelId, name, type, domain, accessToken }) => { - const theme = useMantineTheme(); - const details = [ - { - label: "Domain", - value: domain, - edit: true, - functionValue: "domain", - }, - { - label: "Access Token", - value: accessToken, - edit: true, - functionValue: "accessToken", - type: "password", - }, - { - label: "Type", - value: type, - transform: "uppercase", - }, - ]; - - return ( - - - - Channel Details - - - Edit details - - - - {details.map((detail) => ( - - - - {detail.label} - - - - {detail.edit ? ( - - ) : ( - - {detail.value} - - )} - - - ))} - - ); -}; diff --git a/components/ChannelGrid/endpoints/EditEndpoint.js b/components/ChannelGrid/endpoints/EditEndpoint.js deleted file mode 100644 index 85f56eb..0000000 --- a/components/ChannelGrid/endpoints/EditEndpoint.js +++ /dev/null @@ -1,161 +0,0 @@ -import React, { useState } from "react"; -import { - Text, - useMantineTheme, - Button, - Group, - Badge, - TextInput, - Collapse as Coll, - Box, - Stack, -} from "@mantine/core"; -import useSWR from "swr"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import request from "graphql-request"; -import { useNotifications } from "@mantine/notifications"; -import { CHANNELS_QUERY, UPDATE_CHANNEL_MUTATION } from "@graphql/channels"; - -export const EditEndpoint = ({ detail, channelId }) => { - const notifications = useNotifications(); - const theme = useMantineTheme(); - const [opened, setOpen] = useState(false); - const [loading, setLoading] = useState(false); - const [endpoint, setEndpoint] = useState(detail.value); - const { mutate: mutateChannels } = useSWR(CHANNELS_QUERY, gqlFetcher); - - - return ( - - - - - {detail.label} - - - {detail.description} - - - - setOpen((o) => !o)} - sx={{ - border: `1px solid ${ - theme.colors[detail.value ? "blue" : "green"][ - theme.colorScheme === "dark" ? 9 : 1 - ] - }`, - cursor: "pointer", - }} - > - {detail.value ? "Active" : "Activate"} - - - - -
{ - event.preventDefault(); - if (endpoint !== detail.value) { - setLoading(true); - let data = {}; - data[`${detail.functionValue}`] = endpoint; - await request("/api/graphql", UPDATE_CHANNEL_MUTATION, { - id: channelId, - data, - }) - .then(async ({ updateChannel }) => { - setLoading(false); - await mutateChannels(({ channels }) => { - const newData = []; - for (const item of channels) { - if (item.id === updateChannel.id) { - newData.push(updateChannel); - } else { - newData.push(item); - } - } - return { - channels: newData, - }; - }, false); - notifications.showNotification({ - title: `Endpoint has been updated.`, - // message: JSON.stringify(data), - }); - setOpen(false); - }) - .catch((error) => { - setLoading(false); - notifications.showNotification({ - title: error.response.errors[0].extensions.code, - message: error.response.errors[0].message, - color: "red", - }); - }); - } - }} - > - : } - rightSectionWidth={loading ? 26 : 70} - pb="sm" - px="sm" - id="endpoint" - spellcheck="false" - - rightSection={ - - } - size="sm" - value={endpoint} - onChange={(event) => setEndpoint(event.target.value)} - /> - -
-
- ); -}; diff --git a/components/ChannelGrid/endpoints/index.js b/components/ChannelGrid/endpoints/index.js deleted file mode 100644 index db6b15e..0000000 --- a/components/ChannelGrid/endpoints/index.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from "react"; -import { Group, Paper, Stack, Text, useMantineTheme } from "@mantine/core"; -import { useNotifications } from "@mantine/notifications"; -import { EditEndpoint } from "./EditEndpoint"; - -export const Endpoints = ({ - channelId, - searchProductsEndpoint, - createPurchaseEndpoint, -}) => { - const theme = useMantineTheme(); - const notifications = useNotifications(); - - const functions = [ - { - label: "Search Products", - description: "Endpoint to search channel products", - value: searchProductsEndpoint, - functionValue: "searchProductsEndpoint", - }, - { - label: "Create Purchase", - description: "Endpoint to create channel purchase orders", - value: createPurchaseEndpoint, - functionValue: "createPurchaseEndpoint", - }, - ]; - - return ( - - - - Endpoints - - - Control how Openship interacts with your channel - - - {functions.map((detail) => ( - - ))} - - ); -}; diff --git a/components/ChannelGrid/index.js b/components/ChannelGrid/index.js deleted file mode 100644 index 6d4c2ca..0000000 --- a/components/ChannelGrid/index.js +++ /dev/null @@ -1,271 +0,0 @@ -import React, { useState } from "react"; -import { - Container, - useMantineTheme, - Group, - Stack, - Tabs, - Text, - ThemeIcon, - Button, - Input, - Loader, -} from "@mantine/core"; -import { Details } from "./details"; -import { Endpoints } from "./endpoints"; -import { Orders } from "./orders"; -import { Metafields } from "./metafields"; -import { Dangerous } from "./dangerous"; -import { Webhooks } from "./webhooks"; -import Avatar from "boring-avatars"; -import { - AlertIcon, - ChecklistIcon, - CodeIcon, - EllipsisIcon, - GlobeIcon, - LinkIcon, - WebhookIcon, -} from "@primer/octicons-react"; -import { CHANNELS_QUERY, UPDATE_CHANNEL_MUTATION } from "@graphql/channels"; -import { useHover } from "@mantine/hooks"; -import useSWR from "swr"; -import { useNotifications } from "@mantine/notifications"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import request from "graphql-request"; - -export function ChannelGrid({ - id, - name, - type, - domain, - accessToken, - searchProductsEndpoint, - createPurchaseEndpoint, - getWebhooksEndpoint, - createWebhookEndpoint, - deleteWebhookEndpoint, - metafields, -}) { - const theme = useMantineTheme(); - - return ( - - - - }> -
- - }> - - - }> - - - }> - - - }> - - - } color="red"> - - - - - ); -} - -const EditName = ({ name, id }) => { - const theme = useMantineTheme(); - - const { hovered, ref } = useHover(); - const [editMode, setEditMode] = useState(false); - const [value, setValue] = useState(name); - const [loading, setLoading] = useState(false); - const { mutate: mutateChannels } = useSWR(CHANNELS_QUERY, gqlFetcher); - const notifications = useNotifications(); - - return ( - - - - - {editMode ? ( - - setValue(event.target.value)} - styles={{ - wrapper: { - width: `${2 + value.length * 0.5}rem`, - }, - input: { - minHeight: 0, - height: 31, - lineHeight: 1, - fontWeight: 600, - fontSize: 20, - }, - }} - ml={-2} - autoFocus - /> - - - - - - ) : ( - - {name} - - )} - {hovered && !editMode && ( - - )} - - ); -}; diff --git a/components/ChannelGrid/metafields/EditMetafield.js b/components/ChannelGrid/metafields/EditMetafield.js deleted file mode 100644 index a16b4c5..0000000 --- a/components/ChannelGrid/metafields/EditMetafield.js +++ /dev/null @@ -1,307 +0,0 @@ -import React, { useState } from "react"; -import { - Text, - useMantineTheme, - Button, - Group, - Loader, - Input, - ActionIcon, - Box, -} from "@mantine/core"; -import useSWR from "swr"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import { PencilIcon, TrashIcon, XIcon } from "@primer/octicons-react"; -import request from "graphql-request"; -import { useNotifications } from "@mantine/notifications"; -import { - CHANNELS_QUERY, - DELETE_CHANNEL_METAFIELD_MUTATION, - UPDATE_CHANNEL_METAFIELD_MUTATION, -} from "@graphql/channels"; - -export const EditMetafield = ({ metafield, channelId }) => { - const theme = useMantineTheme(); - const notifications = useNotifications(); - const [editMode, setEditMode] = useState(false); - const [loading, setLoading] = useState(false); - const [value, setValue] = useState(metafield.value); - const [key, setKey] = useState(metafield.key); - const { mutate: mutateChannels } = useSWR(CHANNELS_QUERY, gqlFetcher); - - return editMode ? ( - - - {/* - {customInput.key} - */} - setKey(event.target.value)} - styles={{ - wrapper: { - width: "100%", - }, - input: { - minHeight: 0, - height: 22, - fontWeight: 600, - color: - theme.colors.blueGray[theme.colorScheme === "dark" ? 4 : 5], - }, - }} - ml={-2} - /> - - - setValue(event.target.value)} - styles={{ - wrapper: { - width: "100%", - }, - input: { - minHeight: 0, - height: 22, - }, - }} - autoFocus - /> - - - - - - - - ) : ( - - - - {metafield.key} - - - - - - {metafield.value} - - - {loading && } - - - - - - - ); -}; diff --git a/components/ChannelGrid/metafields/index.js b/components/ChannelGrid/metafields/index.js deleted file mode 100644 index a830ac5..0000000 --- a/components/ChannelGrid/metafields/index.js +++ /dev/null @@ -1,230 +0,0 @@ -import React, { useState } from "react"; -import { - Paper, - Text, - useMantineTheme, - Group, - Box, - ActionIcon, - Collapse, - Input, - Button, - Loader, - Stack, -} from "@mantine/core"; -import { EditMetafield } from "./EditMetafield"; -import { PlusIcon, XIcon } from "@primer/octicons-react"; -import { - CHANNELS_QUERY, - CREATE_CHANNEL_METAFIELD_MUTATION, -} from "@graphql/channels"; -import { useNotifications } from "@mantine/notifications"; -import useSWR from "swr"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import request from "graphql-request"; - -export const Metafields = ({ channelId, metafields }) => { - const theme = useMantineTheme(); - const [opened, setOpen] = useState(false); - - return ( - - - - - Metafields - - - Create channel-specific fields - - - setOpen(!opened)} - color="green" - size={28} - radius="sm" - ml="auto" - sx={{ - border: `1px solid ${ - theme.colors.green[theme.colorScheme === "dark" ? 9 : 1] - }`, - }} - mr={2} - > - - - - - - - {metafields.map((metafield) => ( - - ))} - - ); -}; - -const CreateMetafield = ({ channelId, setOpen, opened }) => { - const theme = useMantineTheme(); - const notifications = useNotifications(); - const [loading, setLoading] = useState(false); - const [value, setValue] = useState(""); - const [key, setKey] = useState(""); - const { mutate: mutateChannels } = useSWR(CHANNELS_QUERY, gqlFetcher); - - return ( - - - setKey(event.target.value)} - styles={{ - wrapper: { - width: "100%", - }, - input: { - minHeight: 0, - height: 22, - fontWeight: 600, - color: - theme.colors.blueGray[theme.colorScheme === "dark" ? 4 : 5], - }, - }} - ml={-2} - autoFocus={opened} - /> - - - setValue(event.target.value)} - styles={{ - wrapper: { - width: "100%", - }, - input: { - minHeight: 0, - height: 22, - }, - }} - /> - - {loading && } - - - - - - ); -}; diff --git a/components/ChannelGrid/orders/index.js b/components/ChannelGrid/orders/index.js deleted file mode 100644 index 54cb1b1..0000000 --- a/components/ChannelGrid/orders/index.js +++ /dev/null @@ -1,280 +0,0 @@ -import React, { useState } from "react"; -import { - Skeleton, - Paper, - Text, - Box, - useMantineTheme, - Divider, - Button, - Group, - Input, - Stack, -} from "@mantine/core"; -import { CHANNEL_ORDERS_QUERY } from "@graphql/orders"; -import useSWR from "swr"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import { Collapse } from "@primitives/collapse"; -import { Order } from "@primitives/order"; -import { CartItem } from "@primitives/cartItem"; -import { ArrowRightIcon, SearchIcon } from "@primer/octicons-react"; - -export const Orders = ({ channelId }) => { - const theme = useMantineTheme(); - const [searchEntry, setSearchEntry] = useState(""); - - const { - data, - error, - mutate: mutateOrders, - } = useSWR( - [ - CHANNEL_ORDERS_QUERY, - JSON.stringify({ - where: { - cartItems: { - some: { channel: { id: { equals: channelId } } }, - }, - OR: [ - { orderName: { contains: searchEntry, mode: "insensitive" } }, - { first_name: { contains: searchEntry, mode: "insensitive" } }, - { last_name: { contains: searchEntry, mode: "insensitive" } }, - { streetAddress1: { contains: searchEntry, mode: "insensitive" } }, - { streetAddress2: { contains: searchEntry, mode: "insensitive" } }, - { city: { contains: searchEntry, mode: "insensitive" } }, - { state: { contains: searchEntry, mode: "insensitive" } }, - { zip: { contains: searchEntry, mode: "insensitive" } }, - ], - }, - cartItemsWhere: { channel: { id: { equals: channelId } } }, - // skip: parseInt(skip), - take: 5, - }), - ], - gqlFetcher - ); - if (error) return
Failed to load
; - - return ( - - - - Orders - - - Search orders from Openship - - - - - } - size="md" - onKeyPress={(e) => { - if (e.key === "Enter") { - setSearchEntry(e.target.value); - } - }} - /> - - {!data && ( - <> - - - - - )} - {data?.orders.length === 0 && ( - - No orders found - - )} - {data?.orders?.map((order) => { - const { - id, - orderId, - shop, - orderName, - first_name, - last_name, - streetAddress1, - streetAddress2, - city, - state, - zip, - createdAt, - cartItems, - lineItems, - orderError, - } = order; - return ( - - ( - <> - - - {" "} - {first_name} {last_name} -
- {streetAddress1} {streetAddress2} -
- {city} - {state && ", "} - {state} {zip} - - } - /> -
- {open && ( - <> - {cartItems?.length > 0 && ( - <> - - - - Cart Items - - {cartItems.map( - ({ - id: cartId, - name, - quantity, - price, - image, - productId, - variantId, - purchaseId, - lineItemId, - channel, - url: cartURL, - error, - status, - }) => ( - - - {cartURL && ( - - )} - - } - /> - - ) - )} - - - )} - - )} - - )} - /> -
- ); - })} -
- ); -}; diff --git a/components/ChannelGrid/webhooks/index.js b/components/ChannelGrid/webhooks/index.js deleted file mode 100644 index 810a679..0000000 --- a/components/ChannelGrid/webhooks/index.js +++ /dev/null @@ -1,466 +0,0 @@ -import React, { useState } from "react"; -import { - Paper, - Text, - useMantineTheme, - Group, - Box, - ActionIcon, - Collapse, - Input, - Button, - Loader, - Table, - Code, - Divider, - TextInput, - Stack, - Tooltip, -} from "@mantine/core"; -import { - GearIcon, - InfoIcon, - PlusIcon, - TrashIcon, - XIcon, -} from "@primer/octicons-react"; -import { CHANNELS_QUERY, UPDATE_CHANNEL_MUTATION } from "@graphql/channels"; -import { useNotifications } from "@mantine/notifications"; -import useSWR from "swr"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import request from "graphql-request"; - -export const Webhooks = ({ - channelId, - type, - domain, - accessToken, - getWebhooksEndpoint, - createWebhookEndpoint, - deleteWebhookEndpoint, -}) => { - const theme = useMantineTheme(); - const [opened, setOpen] = useState(false); - const [deleteLoading, setDeleteLoading] = useState(null); - const [createLoading, setCreateLoading] = useState(null); - const notifications = useNotifications(); - - const showToggle = ({ - opened, - getWebhooksEndpoint, - createWebhookEndpoint, - deleteWebhookEndpoint, - }) => { - if ( - !getWebhooksEndpoint || - !createWebhookEndpoint || - !deleteWebhookEndpoint - ) { - return true; - } - if (opened) { - return opened; - } - return false; - }; - - const params = new URLSearchParams({ - accessToken, - domain, - }).toString(); - - const url = `${getWebhooksEndpoint}?${params}`; - - const { data, error, mutate } = useSWR(url); - const existingWebhooks = [...(data?.webhooks ? data.webhooks : [])]; - - const recommendWebhooks = [ - { - callbackUrl: `/api/triggers/cancel-purchase/${type}`, - topic: "ORDER_CANCELLED", - description: - "When a purchase order is cancelled by the channel, Openship will mark the cart item as cancelled and move the order to PENDING to be processed again", - }, - { - callbackUrl: `/api/triggers/create-tracking/${type}`, - topic: "TRACKING_CREATED", - description: - "When a purchase order is fulfilled by the channel, Openship will add this tracking to the order and the shop", - }, - ].filter((item) => { - return ( - existingWebhooks.filter((existItem) => { - return ( - existItem.topic == item.topic && - existItem.callbackUrl == item.callbackUrl - ); - }).length == 0 - ); - }); - - const platformWebhookEndpoints = [ - { - label: "Get webhooks endpoint", - value: getWebhooksEndpoint, - functionValue: "getWebhooksEndpoint", - }, - { - label: "Create webhook endpoint", - value: createWebhookEndpoint, - functionValue: "createWebhookEndpoint", - }, - { - label: "Delete webhook endpoint", - value: deleteWebhookEndpoint, - functionValue: "deleteWebhookEndpoint", - }, - ]; - - return ( - - - - - Webhooks - - - Create platform webhooks to keep Openship in sync - - - {/* setOpen(!opened)} - color="green" - size={28} - radius="sm" - ml="auto" - sx={{ - border: `1px solid ${ - theme.colors.green[theme.colorScheme === "dark" ? 9 : 1] - }`, - }} - mr={2} - > - - */} - {getWebhooksEndpoint && createWebhookEndpoint && deleteWebhookEndpoint && ( - setOpen(!opened)} - color="gray" - size={28} - radius="sm" - ml="auto" - sx={{ - border: `1px solid ${ - theme.colors.green[theme.colorScheme === "dark" ? 9 : 1] - }`, - }} - mr={2} - > - - - )} - - - {/* */} - - - Webhook configuration - - {platformWebhookEndpoints.map((setting) => ( - - ))} - - - {getWebhooksEndpoint && createWebhookEndpoint && deleteWebhookEndpoint && ( - - - - - - - - - - {[...existingWebhooks, ...recommendWebhooks].map((webhook) => ( - - - - - - ))} - -
TopicURLActions
- - {webhook.topic} - {webhook.description && ( - - - - - - )} - - - - {webhook.callbackUrl} - - - {webhook.id ? ( - { - event.preventDefault(); - setDeleteLoading(webhook.id); - const res = await fetch(`${deleteWebhookEndpoint}`, { - body: JSON.stringify({ - accessToken, - domain, - webhookId: webhook.id, - }), - headers: { - "Content-Type": "application/json", - }, - method: "POST", - }); - - const { error, success } = await res.json(); - setDeleteLoading(null); - - if (error) { - setDeleteLoading(null); - notifications.showNotification({ - title: `Webhook could not be deleted.`, - message: error, - color: "red", - }); - } else { - setDeleteLoading(null); - await mutate(); - notifications.showNotification({ - title: `Webhook has been deleted.`, - // message: JSON.stringify(data), - }); - } - }} - color="red" - size={20} - radius="sm" - ml="auto" - sx={{ - border: `1px solid ${ - theme.colors.red[theme.colorScheme === "dark" ? 9 : 1] - }`, - }} - mr={2} - loading={webhook.id === deleteLoading} - > - - - ) : ( - { - // setOpen(true); - // setCallbackUrl(webhook.callbackUrl); - // setTopic(webhook.topic); - // }} - onClick={async (event) => { - event.preventDefault(); - setCreateLoading(webhook.id); - const res = await fetch(`${createWebhookEndpoint}`, { - body: JSON.stringify({ - accessToken, - domain, - endpoint: webhook.callbackUrl, - topic: webhook.topic, - }), - headers: { - "Content-Type": "application/json", - }, - method: "POST", - }); - - const { error, success } = await res.json(); - setCreateLoading(null); - - if (error) { - setCreateLoading(null); - notifications.showNotification({ - title: `Webhook could not be created.`, - message: error, - color: "red", - }); - } else { - setCreateLoading(null); - await mutate(); - notifications.showNotification({ - title: `Webhook has been created.`, - // message: JSON.stringify(data), - }); - } - }} - color="blue" - size={20} - radius="sm" - ml="auto" - sx={{ - border: `1px solid ${ - theme.colors.blue[ - theme.colorScheme === "dark" ? 9 : 1 - ] - }`, - }} - mr={2} - loading={webhook.id === createLoading} - > - - - )} -
- )} -
- ); -}; - -function EditWebhookSettings({ setting, channelId, setOpen }) { - const notifications = useNotifications(); - const [loading, setLoading] = useState(false); - const [endpoint, setEndpoint] = useState(setting.value); - const { mutate: mutateChannels } = useSWR(CHANNELS_QUERY, gqlFetcher); - - return ( -
{ - event.preventDefault(); - if (endpoint !== setting.value) { - setLoading(true); - let data = {}; - data[`${setting.functionValue}`] = endpoint; - await request("/api/graphql", UPDATE_CHANNEL_MUTATION, { - id: channelId, - data, - }) - .then(async ({ updateChannel }) => { - setLoading(false); - await mutateChannels(({ channels }) => { - const newData = []; - for (const item of channels) { - if (item.id === updateChannel.id) { - newData.push(updateChannel); - } else { - newData.push(item); - } - } - return { - channels: newData, - }; - }, false); - notifications.showNotification({ - title: `Endpoint has been updated.`, - // message: JSON.stringify(data), - }); - // setOpen(false); - }) - .catch((error) => { - setLoading(false); - notifications.showNotification({ - title: error.response.errors[0].extensions.code, - message: error.response.errors[0].message, - color: "red", - }); - }); - } - }} - > - : } - rightSectionWidth={loading ? 26 : 70} - pb="sm" - px="sm" - id="endpoint" - spellcheck="false" - rightSection={ - - } - size="sm" - value={endpoint} - onChange={(event) => setEndpoint(event.target.value)} - /> - - ); -} diff --git a/components/CreateItemDrawer.js b/components/CreateItemDrawer.js deleted file mode 100644 index df9823e..0000000 --- a/components/CreateItemDrawer.js +++ /dev/null @@ -1,164 +0,0 @@ -import { useCallback, useMemo, useState } from "react" -import isDeepEqual from "fast-deep-equal" -import { Box } from "@keystone-ui/core" -import { Drawer } from "@keystone-ui/modals" -import { useToasts } from "@keystone-ui/toast" -import { LoadingDots } from "@keystone-ui/loading" - -import { gql, useMutation } from "../apollo" -import { useKeystone, useList } from "../context" - -import { Fields } from "../utils/Fields" -import { usePreventNavigation } from "../utils/usePreventNavigation" -import { GraphQLErrorNotice } from "./GraphQLErrorNotice" - -export function CreateItemDrawer({ listKey, onClose, onCreate }) { - const { createViewFieldModes } = useKeystone() - const list = useList(listKey) - - const toasts = useToasts() - - const [ - createItem, - { loading, error, data: returnedData } - ] = useMutation(gql`mutation($data: ${list.gqlNames.createInputName}!) { - item: ${list.gqlNames.createMutationName}(data: $data) { - id - label: ${list.labelField} - } -}`) - - const [value, setValue] = useState(() => { - const value = {} - Object.keys(list.fields).forEach(fieldPath => { - value[fieldPath] = { - kind: "value", - value: list.fields[fieldPath].controller.defaultValue - } - }) - return value - }) - - const invalidFields = useMemo(() => { - const invalidFields = new Set() - - Object.keys(value).forEach(fieldPath => { - const val = value[fieldPath].value - - const validateFn = list.fields[fieldPath].controller.validate - if (validateFn) { - const result = validateFn(val) - if (result === false) { - invalidFields.add(fieldPath) - } - } - }) - return invalidFields - }, [list, value]) - - const [forceValidation, setForceValidation] = useState(false) - - const data = {} - Object.keys(list.fields).forEach(fieldPath => { - const { controller } = list.fields[fieldPath] - const serialized = controller.serialize(value[fieldPath].value) - if ( - !isDeepEqual(serialized, controller.serialize(controller.defaultValue)) - ) { - Object.assign(data, serialized) - } - }) - - const shouldPreventNavigation = - !returnedData?.item && Object.keys(data).length !== 0 - - usePreventNavigation(shouldPreventNavigation) - - return ( - { - const newForceValidation = invalidFields.size !== 0 - setForceValidation(newForceValidation) - - if (newForceValidation) return - - createItem({ - variables: { - data - } - }) - .then(({ data }) => { - const label = data.item.label || data.item.id - onCreate({ id: data.item.id, label }) - toasts.addToast({ - title: label, - message: "Created Successfully", - tone: "positive" - }) - }) - .catch(() => {}) - } - }, - cancel: { - label: "Cancel", - action: () => { - if ( - !shouldPreventNavigation || - window.confirm( - "There are unsaved changes, are you sure you want to exit?" - ) - ) { - onClose() - } - } - } - }} - > - {createViewFieldModes.state === "error" && ( - - )} - {createViewFieldModes.state === "loading" && ( - - )} - {error && ( - - )} - - { - setValue(oldValues => getNewValue(oldValues)) - }, [])} - /> - - - ) -} \ No newline at end of file diff --git a/components/CreateViews/CreateChannelView/ChannelForm.js b/components/CreateViews/CreateChannelView/ChannelForm.js deleted file mode 100644 index a39e7be..0000000 --- a/components/CreateViews/CreateChannelView/ChannelForm.js +++ /dev/null @@ -1,161 +0,0 @@ -import { formList, useForm } from "@mantine/form"; -import { - TextInput, - Button, - useMantineTheme, - Box, - Paper, - Text, - Stack, - Code, -} from "@mantine/core"; - -export const ChannelForm = ({ - label, - fields, - metafields, - handleSubmit, - buttonText, - loading, -}) => { - const theme = useMantineTheme(); - - const arrayToObject = (array, key) => - array.reduce( - (obj, item) => ({ - ...obj, - [item[key]]: "", - }), - {} - ); - - const form = useForm({ - initialValues: { - ...arrayToObject(fields, "name"), - ...(metafields && { - metafields: formList( - metafields.map(({ name }) => ({ key: name, value: "" })) - ), - }), - }, - }); - - return ( -
- {fields.map( - ({ - name, - title, - placeholder, - rightSection, - rightSectionWidth = 140, - }) => ( - - ) - )} - {metafields && ( - - - - {label} fields - - - {metafields.map( - ({ name, title, placeholder, rightSection }, index) => ( - - ) - )} - - )} - - - - - ); -}; diff --git a/components/CreateViews/CreateChannelView/index.js b/components/CreateViews/CreateChannelView/index.js deleted file mode 100644 index 1a796c6..0000000 --- a/components/CreateViews/CreateChannelView/index.js +++ /dev/null @@ -1,263 +0,0 @@ -import { useState } from "react"; -import { Text, useMantineTheme, Box, Select, Modal } from "@mantine/core"; -import { mutate } from "swr"; -import { request } from "graphql-request"; -import { CHANNELS_QUERY, CREATE_CHANNEL_MUTATION } from "@graphql/channels"; -import { useNotifications } from "@mantine/notifications"; -import { useRouter } from "next/router"; -import { ChannelForm } from "./ChannelForm"; - -//hardcoded endpoints fetched through state type -const endpoints = { - shopify: { - searchProductsEndpoint: "/api/search-products/shopify", - createPurchaseEndpoint: "/api/create-purchase/shopify", - getWebhooksEndpoint: "/api/get-webhooks/shopify", - createWebhookEndpoint: "/api/create-webhook/shopify", - deleteWebhookEndpoint: "/api/delete-webhook/shopify", - }, - stockandtrace: { - searchProductsEndpoint: "/api/search-products/stockandtrace", - createPurchaseEndpoint: "/api/create-purchase/stockandtrace", - getWebhooksEndpoint: "/api/get-webhooks/shopify", - createWebhookEndpoint: "/api/create-webhook/shopify", - deleteWebhookEndpoint: "/api/delete-webhook/shopify", - }, - demo: { - domain: "https://openship-channel.vercel.app", - accessToken: "supersecret", - searchProductsEndpoint: - "https://openship-channel.vercel.app/api/search-products", - createPurchaseEndpoint: - "https://openship-channel.vercel.app/api/create-purchase", - }, -}; - -export const CreateChannelView = ({ showModal, setShowModal }) => { - const notifications = useNotifications(); - const router = useRouter(); - const theme = useMantineTheme(); - const [type, setType] = useState("shopify"); - const [loading, setLoading] = useState(false); - - const createChannel = async (values) => { - setLoading(true); - - const res = await request("/api/graphql", CREATE_CHANNEL_MUTATION, { - data: { - ...values, - }, - }) - .then(async () => { - await mutate(CHANNELS_QUERY); - - notifications.showNotification({ - title: `Channel has been added.`, - }); - setShowModal(false); - }) - .catch((error) => { - setLoading(false); - notifications.showNotification({ - title: error.response.errors[0].extensions.code, - message: error.response.errors[0].message, - color: "red", - }); - }); - }; - - const ChannelForms = { - shopify: { - label: "Shopify", - fields: [ - { - title: "URL", - name: "shop", - placeholder: "centralbikeshop", - rightSection: ".myshopify.com", - }, - ], - handleSubmit: (values) => - router.push(`/api/o-auth/channel/shopify?shop=${values.shop}`), - buttonText: "Connect Shopify", - }, - shopifycustom: { - label: "Shopify Custom", - fields: [ - { title: "Name", name: "name", placeholder: "Central Bike Shop" }, - { title: "Domain", name: "domain", placeholder: "centralbikeshop.com" }, - { - title: "Access Token", - name: "accessToken", - placeholder: "supersecret", - }, - ], - handleSubmit: (values) => - createChannel({ - type: "shopify", - ...values, - ...endpoints.shopify, - }), - }, - bigcommerce: { - label: "Big Commerce", - fields: [ - { - title: "URL", - name: "shop", - placeholder: "centralbikeshop", - rightSection: ".mybigcommerce.com", - rightSectionWidth: 190, - }, - ], - handleSubmit: (values) => - router.push(`/api/o-auth/channel/bigcommerce?shop=${values.shop}`), - buttonText: "Connect BigCommerce", - }, - stockandtrace: { - label: "Stock & Trace", - fields: [ - { title: "Name", name: "name", placeholder: "Central Bike Shop" }, - { - title: "Domain", - name: "domain", - placeholder: "centralbikeshop.com", - }, - { - title: "Access Token", - name: "accessToken", - placeholder: "supersecret", - }, - ], - metafields: [ - { title: "Warehouse", name: "Warehouse", placeholder: "WH99" }, - { title: "Account", name: "Account", placeholder: "9837443" }, - { title: "Ship To", name: "Shipto", placeholder: "Supplier" }, - ], - handleSubmit: ({ metafields, ...values }) => - createChannel({ - type: "stockandtrace", - metafields: { create: metafields }, - ...values, - //hardcoded values - ...endpoints.stockandtrace, - }), - }, - custom: { - label: "Custom", - fields: [ - { title: "Name", name: "name", placeholder: "Central Bike Shop" }, - { title: "Domain", name: "domain", placeholder: "centralbikeshop.com" }, - { - title: "Access Token", - name: "accessToken", - placeholder: "supersecret", - }, - ], - handleSubmit: (values) => - createChannel({ - type: "custom", - ...values, - }), - }, - demo: { - label: "Demo", - fields: [ - { title: "Name", name: "name", placeholder: "Central Bike Shop" }, - ], - handleSubmit: (values) => - createChannel({ - type: "demo", - ...values, - ...endpoints.demo, - }), - }, - }; - - return ( - setShowModal(false)} - // size="xl" - title={ - - Create Channel - - } - > - } - variant="unstyled" - pt={4} - pb={6} - pl={12} - styles={{ - input: { height: 22, lineHeight: 0 }, - wrapper: { - background: - theme.colorScheme === "dark" ? theme.colors.gray[8] : "#fff", - }, - rightSection: { marginRight: 8 }, - }} - onKeyPress={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - setSearchEntry(e.target.value); - setOpened(true); - } - }} - rightSectionWidth={100} - rightSection={{rightSection}} - /> - - - - - Results - - - - - - {data?.products?.map( - ({ title, image, price, productId, variantId }) => ( - { - form.addListItem("channelProducts", { - title, - image, - price, - quantity: 1, - productId, - variantId, - channel: { - id: channel.id, - name: channel.name, - }, - }); - setOpened(false); - }} - sx={{ cursor: "pointer" }} - > - - - - -
- {title} - - {productId} | {variantId} - -
-
-
-
- ) - )} -
-
-
- {/* 0 && opened} - referenceElement={referenceElement} - transition="pop-top-left" - transitionDuration={200} - position="bottom" - // placement="bottom-start" - arrowStyle={{ - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.colors.gray[1], - }} - modifiers={[ - // { - // name: "preventOverflow", - // enabled: false, - // }, - // { - // name: "flip", - // enabled: dropdownPosition === "flip", - // }, - { - // @ts-ignore - name: "sameWidth", - enabled: true, - phase: "beforeWrite", - requires: ["computeStyles"], - fn: ({ state }) => { - // eslint-disable-next-line no-param-reassign - state.styles.popper.width = `${state.rects.reference.width}px`; - }, - effect: ({ state }) => { - // eslint-disable-next-line no-param-reassign - state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`; - }, - }, - // { - // // @ts-ignore - // name: "directionControl", - // enabled: true, - // phase: "main", - // fn: ({ state }) => { - // if (previousPlacement.current !== state.placement) { - // previousPlacement.current = state.placement; - - // const nextDirection = - // state.placement === "top" ? "column-reverse" : "column"; - - // if (direction !== nextDirection && switchDirectionOnFlip) { - // onDirectionChange && onDirectionChange(nextDirection); - // } - // } - // }, - // }, - {}, - ]} - > - - - {data?.products?.map( - ({ title, image, price, productId, variantId }) => ( - - form.addListItem("cartItems", { - title, - image, - price, - quantity: 1, - productId, - variantId, - channel, - }) - } - sx={{ cursor: "pointer" }} - > - - - - -
- {title} - - {productId} | {variantId} - -
-
-
-
- ) - )} -
-
-
*/} - - ); -} -function CartItemField({ - id, - image, - channel, - title, - productId, - variantId, - price, - quantity, - form, - index, -}) { - const handlers = useRef(); - const theme = useMantineTheme(); - - return ( - - - - - - - {channel?.name} - - {title} - - {productId} | {variantId} - - - - - ${(price * quantity).toFixed(2)} - - {quantity > 1 && ( - - - (${price} x {quantity}) - - - )} - - - - handlers.current.decrement()} - // disabled={value === min} - // className={classes.control} - onMouseDown={(event) => event.preventDefault()} - sx={{ - // backgroundColor: - // theme.colorScheme === "dark" - // ? theme.colors.dark[7] - // : theme.white, - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - > - - - - - handlers.current.increment()} - // disabled={value === min} - // className={classes.control} - onMouseDown={(event) => event.preventDefault()} - sx={{ - // backgroundColor: - // theme.colorScheme === "dark" - // ? theme.colors.dark[7] - // : theme.white, - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - > - - - - form.removeListItem("channelProducts", index) - } - onMouseDown={(event) => event.preventDefault()} - sx={{ - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.red[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - ml={6} - > - - - - - - - - - - ); -} diff --git a/components/CreateViews/CreateMatchView/ChannelSelect.js b/components/CreateViews/CreateMatchView/ChannelSelect.js deleted file mode 100644 index 4bca1b5..0000000 --- a/components/CreateViews/CreateMatchView/ChannelSelect.js +++ /dev/null @@ -1,123 +0,0 @@ -import { Select, useMantineTheme } from "@mantine/core"; - -export function ChannelSelect({ channels, type = "lg", ...props }) { - const theme = useMantineTheme(); - - const styles = { - sm: { - root: { - position: "relative", - }, - - input: { - fontWeight: 600, - color: - theme.colorScheme === "light" - ? theme.colors.indigo[7] - : theme.colors.dark[0], - height: 28, - minHeight: 28, - paddingLeft: 13, - border: `1px solid ${ - theme.colors.blueGray[theme.colorScheme === "dark" ? 7 : 2] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - // fontSize: "16px !important", - background: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.fn.lighten(theme.colors.blueGray[0], 0.5), - "&:focus, &:focus-within": { - outline: "none", - borderColor: `${ - theme.colors[theme.primaryColor][ - theme.colorScheme === "dark" ? 8 : 5 - ] - } !important`, - }, - }, - - required: { - display: "none", - // ":before": { marginLeft: "auto", content: '" required"' }, - }, - - error: { - fontSize: 14, - }, - item: { - fontWeight: 600, - marginTop: 3, - }, - }, - lg: { - root: { - position: "relative", - }, - - input: { - fontWeight: 600, - color: - theme.colorScheme === "light" - ? theme.colors.blue[7] - : theme.colors.dark[0], - height: "auto", - paddingTop: 18, - paddingLeft: 13, - border: `1px solid ${ - theme.colors.blueGray[theme.colorScheme === "dark" ? 7 : 2] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - // fontSize: "16px !important", - background: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.fn.lighten(theme.colors.blueGray[0], 0.5), - "&:focus, &:focus-within": { - outline: "none", - borderColor: `${ - theme.colors[theme.primaryColor][ - theme.colorScheme === "dark" ? 8 : 5 - ] - } !important`, - }, - }, - - required: { - display: "none", - // ":before": { marginLeft: "auto", content: '" required"' }, - }, - - error: { - fontSize: 14, - }, - - label: { - position: "absolute", - pointerEvents: "none", - color: theme.colors.blueGray[theme.colorScheme === "dark" ? 2 : 6], - fontSize: theme.fontSizes.xs, - paddingLeft: 14, - paddingTop: 6, - zIndex: 1, - }, - item: { - fontWeight: 600, - marginTop: 3, - }, - }, - }; - - return ( - } - variant="unstyled" - pt={4} - pb={6} - pl={12} - styles={{ - input: { height: 22, lineHeight: 0 }, - wrapper: { - background: - theme.colorScheme === "dark" ? theme.colors.gray[8] : "#fff", - }, - rightSection: { marginRight: 8 }, - }} - onKeyPress={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - setSearchEntry(e.target.value); - setOpened(true); - } - }} - rightSectionWidth={100} - rightSection={{rightSection}} - /> - - - - - Results - - - - - - {data?.products?.map( - ({ title, image, price, productId, variantId }) => ( - { - form.addListItem("shopProducts", { - title, - image, - price, - quantity: 1, - productId, - variantId, - shop: { - id: shop.id, - name: shop.name, - }, - }); - setOpened(false); - }} - sx={{ cursor: "pointer" }} - > - - - - -
- {title} - - {productId} | {variantId} - -
-
-
-
- ) - )} -
-
-
- {form.values.shopProducts.length > 0 && ( - - )} - {/* 0 && opened} - referenceElement={referenceElement} - transition="pop-top-left" - transitionDuration={200} - position="bottom" - // placement="bottom-start" - arrowStyle={{ - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.colors.gray[1], - }} - modifiers={[ - { - // @ts-ignore - name: "sameWidth", - enabled: true, - phase: "beforeWrite", - requires: ["computeStyles"], - fn: ({ state }) => { - // eslint-disable-next-line no-param-reassign - state.styles.popper.width = `${state.rects.reference.width}px`; - }, - effect: ({ state }) => { - // eslint-disable-next-line no-param-reassign - state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`; - }, - }, - {}, - ]} - > - - - {data?.products?.map( - ({ title, image, price, productId, variantId }) => ( - { - form.addListItem("lineItems", { - title, - image, - price, - quantity: 1, - productId, - variantId, - }); - setOpened(false); - }} - sx={{ cursor: "pointer" }} - > - - - - -
- {title} - - {productId} | {variantId} - -
-
-
-
- ) - )} -
-
-
*/} - - ); -} - -function LineItemField({ - image, - title, - productId, - variantId, - price, - quantity, - form, - index, -}) { - const handlers = useRef(); - const theme = useMantineTheme(); - - return ( - - - - - - - {title} - - {productId} | {variantId} - - - - - ${(price * quantity).toFixed(2)} - - {quantity > 1 && ( - - - (${price} x {quantity}) - - - )} - - - - handlers.current.decrement()} - // disabled={value === min} - // className={classes.control} - onMouseDown={(event) => event.preventDefault()} - sx={{ - // backgroundColor: - // theme.colorScheme === "dark" - // ? theme.colors.dark[7] - // : theme.white, - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - > - - - - - handlers.current.increment()} - // disabled={value === min} - // className={classes.control} - onMouseDown={(event) => event.preventDefault()} - sx={{ - // backgroundColor: - // theme.colorScheme === "dark" - // ? theme.colors.dark[7] - // : theme.white, - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - > - - - form.removeListItem("shopProducts", index)} - onMouseDown={(event) => event.preventDefault()} - sx={{ - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.red[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - ml={6} - > - - - - - - - - - - ); -} diff --git a/components/CreateViews/CreateMatchView/ShopSelect.js b/components/CreateViews/CreateMatchView/ShopSelect.js deleted file mode 100644 index 3a08613..0000000 --- a/components/CreateViews/CreateMatchView/ShopSelect.js +++ /dev/null @@ -1,123 +0,0 @@ -import { Select, useMantineTheme } from "@mantine/core"; - -export function ShopSelect({ shops, type = "lg", ...props }) { - const theme = useMantineTheme(); - - const styles = { - sm: { - root: { - position: "relative", - }, - - input: { - fontWeight: 600, - color: - theme.colorScheme === "light" - ? theme.colors.cyan[7] - : theme.colors.dark[0], - height: 28, - minHeight: 28, - paddingLeft: 13, - border: `1px solid ${ - theme.colors.blueGray[theme.colorScheme === "dark" ? 7 : 2] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - // fontSize: "16px !important", - background: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.fn.lighten(theme.colors.blueGray[0], 0.5), - "&:focus, &:focus-within": { - outline: "none", - borderColor: `${ - theme.colors[theme.primaryColor][ - theme.colorScheme === "dark" ? 8 : 5 - ] - } !important`, - }, - }, - - required: { - display: "none", - // ":before": { marginLeft: "auto", content: '" required"' }, - }, - - error: { - fontSize: 14, - }, - item: { - fontWeight: 600, - marginTop: 3, - }, - }, - lg: { - root: { - position: "relative", - }, - - input: { - fontWeight: 600, - color: - theme.colorScheme === "light" - ? theme.colors.cyan[7] - : theme.colors.dark[0], - height: "auto", - paddingTop: 18, - paddingLeft: 13, - border: `1px solid ${ - theme.colors.blueGray[theme.colorScheme === "dark" ? 7 : 2] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - // fontSize: "16px !important", - background: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.fn.lighten(theme.colors.blueGray[0], 0.5), - "&:focus, &:focus-within": { - outline: "none", - borderColor: `${ - theme.colors[theme.primaryColor][ - theme.colorScheme === "dark" ? 8 : 5 - ] - } !important`, - }, - }, - - required: { - display: "none", - // ":before": { marginLeft: "auto", content: '" required"' }, - }, - - error: { - fontSize: 14, - }, - - label: { - position: "absolute", - pointerEvents: "none", - color: theme.colors.blueGray[theme.colorScheme === "dark" ? 2 : 6], - fontSize: theme.fontSizes.xs, - paddingLeft: 14, - paddingTop: 6, - zIndex: 1, - }, - item: { - fontWeight: 600, - marginTop: 3, - }, - }, - }; - - return ( - } - variant="unstyled" - pt={4} - pb={6} - pl={12} - styles={{ - input: { height: 22, lineHeight: 0 }, - wrapper: { - background: - theme.colorScheme === "dark" ? theme.colors.gray[8] : "#fff", - }, - rightSection: { marginRight: 8 }, - }} - onKeyPress={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - setSearchEntry(e.target.value); - setOpened(true); - } - }} - rightSectionWidth={100} - rightSection={{rightSection}} - /> - - 0 && opened} - referenceElement={referenceElement} - transition="pop-top-left" - transitionDuration={200} - position="bottom" - // placement="bottom-start" - arrowStyle={{ - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.colors.gray[1], - }} - modifiers={[ - // { - // name: "preventOverflow", - // enabled: false, - // }, - // { - // name: "flip", - // enabled: dropdownPosition === "flip", - // }, - { - // @ts-ignore - name: "sameWidth", - enabled: true, - phase: "beforeWrite", - requires: ["computeStyles"], - fn: ({ state }) => { - // eslint-disable-next-line no-param-reassign - state.styles.popper.width = `${state.rects.reference.width}px`; - }, - effect: ({ state }) => { - // eslint-disable-next-line no-param-reassign - state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`; - }, - }, - // { - // // @ts-ignore - // name: "directionControl", - // enabled: true, - // phase: "main", - // fn: ({ state }) => { - // if (previousPlacement.current !== state.placement) { - // previousPlacement.current = state.placement; - - // const nextDirection = - // state.placement === "top" ? "column-reverse" : "column"; - - // if (direction !== nextDirection && switchDirectionOnFlip) { - // onDirectionChange && onDirectionChange(nextDirection); - // } - // } - // }, - // }, - {}, - ]} - > - - - {data?.products?.map( - ({ title, image, price, productId, variantId }) => ( - - form.addListItem("cartItems", { - title, - image, - price, - quantity: 1, - productId, - variantId, - channel, - }) - } - sx={{ cursor: "pointer" }} - > - - - - -
- {title} - - {productId} | {variantId} - -
-
-
-
- ) - )} -
-
-
- - ); -} -function CartItemField({ - id, - image, - channel, - title, - productId, - variantId, - price, - quantity, - form, - index, -}) { - const handlers = useRef(); - const theme = useMantineTheme(); - - return ( - - - - - - - {channel.name} - - {title} - - {productId} | {variantId} - - - - - ${(price * quantity).toFixed(2)} - - {quantity > 1 && ( - - - (${price} x {quantity}) - - - )} - - - - handlers.current.decrement()} - // disabled={value === min} - // className={classes.control} - onMouseDown={(event) => event.preventDefault()} - sx={{ - // backgroundColor: - // theme.colorScheme === "dark" - // ? theme.colors.dark[7] - // : theme.white, - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - > - - - - - handlers.current.increment()} - // disabled={value === min} - // className={classes.control} - onMouseDown={(event) => event.preventDefault()} - sx={{ - // backgroundColor: - // theme.colorScheme === "dark" - // ? theme.colors.dark[7] - // : theme.white, - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - > - - - form.removeListItem("cartItems", index)} - onMouseDown={(event) => event.preventDefault()} - sx={{ - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.red[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - ml={6} - > - - - - - - - - - - ); -} diff --git a/components/CreateViews/CreateOrderView/ChannelSelect.js b/components/CreateViews/CreateOrderView/ChannelSelect.js deleted file mode 100644 index 14ae596..0000000 --- a/components/CreateViews/CreateOrderView/ChannelSelect.js +++ /dev/null @@ -1,123 +0,0 @@ -import { Select, useMantineTheme } from "@mantine/core"; - -export function ChannelSelect({ channels, type = "lg", ...props }) { - const theme = useMantineTheme(); - - const styles = { - sm: { - root: { - position: "relative", - }, - - input: { - fontWeight: 600, - color: - theme.colorScheme === "light" - ? theme.colors.blue[7] - : theme.colors.dark[0], - height: 28, - minHeight: 28, - paddingLeft: 13, - border: `1px solid ${ - theme.colors.blueGray[theme.colorScheme === "dark" ? 7 : 2] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - // fontSize: "16px !important", - background: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.fn.lighten(theme.colors.blueGray[0], 0.5), - "&:focus, &:focus-within": { - outline: "none", - borderColor: `${ - theme.colors[theme.primaryColor][ - theme.colorScheme === "dark" ? 8 : 5 - ] - } !important`, - }, - }, - - required: { - display: "none", - // ":before": { marginLeft: "auto", content: '" required"' }, - }, - - error: { - fontSize: 14, - }, - item: { - fontWeight: 600, - marginTop: 3, - }, - }, - lg: { - root: { - position: "relative", - }, - - input: { - fontWeight: 600, - color: - theme.colorScheme === "light" - ? theme.colors.blue[7] - : theme.colors.dark[0], - height: "auto", - paddingTop: 18, - paddingLeft: 13, - border: `1px solid ${ - theme.colors.blueGray[theme.colorScheme === "dark" ? 7 : 2] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - // fontSize: "16px !important", - background: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.fn.lighten(theme.colors.blueGray[0], 0.5), - "&:focus, &:focus-within": { - outline: "none", - borderColor: `${ - theme.colors[theme.primaryColor][ - theme.colorScheme === "dark" ? 8 : 5 - ] - } !important`, - }, - }, - - required: { - display: "none", - // ":before": { marginLeft: "auto", content: '" required"' }, - }, - - error: { - fontSize: 14, - }, - - label: { - position: "absolute", - pointerEvents: "none", - color: theme.colors.blueGray[theme.colorScheme === "dark" ? 2 : 6], - fontSize: theme.fontSizes.xs, - paddingLeft: 14, - paddingTop: 6, - zIndex: 1, - }, - item: { - fontWeight: 600, - marginTop: 3, - }, - }, - }; - - return ( - } - variant="unstyled" - pt={4} - pb={6} - pl={12} - styles={{ - input: { height: 22, lineHeight: 0 }, - wrapper: { - background: - theme.colorScheme === "dark" ? theme.colors.gray[8] : "#fff", - }, - }} - onKeyPress={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - setSearchEntry(e.target.value); - setOpened(true); - } - }} - /> - - 0 && opened} - referenceElement={referenceElement} - transition="pop-top-left" - transitionDuration={200} - position="bottom" - // placement="bottom-start" - arrowStyle={{ - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.colors.gray[1], - }} - modifiers={[ - { - // @ts-ignore - name: "sameWidth", - enabled: true, - phase: "beforeWrite", - requires: ["computeStyles"], - fn: ({ state }) => { - // eslint-disable-next-line no-param-reassign - state.styles.popper.width = `${state.rects.reference.width}px`; - }, - effect: ({ state }) => { - // eslint-disable-next-line no-param-reassign - state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`; - }, - }, - {}, - ]} - > - - - {data?.products?.map( - ({ title, image, price, productId, variantId }) => ( - { - form.addListItem("lineItems", { - title, - image, - price, - quantity: 1, - productId, - variantId, - }); - setOpened(false); - }} - sx={{ cursor: "pointer" }} - > - - - - -
- {title} - - {productId} | {variantId} - -
-
-
-
- ) - )} -
-
-
- - ); -} -function LineItemField({ - image, - title, - productId, - variantId, - price, - quantity, - form, - index, -}) { - const handlers = useRef(); - const theme = useMantineTheme(); - - return ( - - - - - - - {title} - - {productId} | {variantId} - - - - - ${(price * quantity).toFixed(2)} - - {quantity > 1 && ( - - - (${price} x {quantity}) - - - )} - - - - handlers.current.decrement()} - // disabled={value === min} - // className={classes.control} - onMouseDown={(event) => event.preventDefault()} - sx={{ - // backgroundColor: - // theme.colorScheme === "dark" - // ? theme.colors.dark[7] - // : theme.white, - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - > - - - - - handlers.current.increment()} - // disabled={value === min} - // className={classes.control} - onMouseDown={(event) => event.preventDefault()} - sx={{ - // backgroundColor: - // theme.colorScheme === "dark" - // ? theme.colors.dark[7] - // : theme.white, - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - > - - - form.removeListItem("lineItems", index)} - onMouseDown={(event) => event.preventDefault()} - sx={{ - border: `1px solid ${ - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.red[3] - }`, - - "&:disabled": { - borderColor: - theme.colorScheme === "dark" - ? "transparent" - : theme.colors.gray[3], - opacity: 0.8, - backgroundColor: "transparent", - }, - }} - ml={6} - > - - - - - - - - - - ); -} diff --git a/components/CreateViews/CreateOrderView/ShopSelect.js b/components/CreateViews/CreateOrderView/ShopSelect.js deleted file mode 100644 index 3a08613..0000000 --- a/components/CreateViews/CreateOrderView/ShopSelect.js +++ /dev/null @@ -1,123 +0,0 @@ -import { Select, useMantineTheme } from "@mantine/core"; - -export function ShopSelect({ shops, type = "lg", ...props }) { - const theme = useMantineTheme(); - - const styles = { - sm: { - root: { - position: "relative", - }, - - input: { - fontWeight: 600, - color: - theme.colorScheme === "light" - ? theme.colors.cyan[7] - : theme.colors.dark[0], - height: 28, - minHeight: 28, - paddingLeft: 13, - border: `1px solid ${ - theme.colors.blueGray[theme.colorScheme === "dark" ? 7 : 2] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - // fontSize: "16px !important", - background: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.fn.lighten(theme.colors.blueGray[0], 0.5), - "&:focus, &:focus-within": { - outline: "none", - borderColor: `${ - theme.colors[theme.primaryColor][ - theme.colorScheme === "dark" ? 8 : 5 - ] - } !important`, - }, - }, - - required: { - display: "none", - // ":before": { marginLeft: "auto", content: '" required"' }, - }, - - error: { - fontSize: 14, - }, - item: { - fontWeight: 600, - marginTop: 3, - }, - }, - lg: { - root: { - position: "relative", - }, - - input: { - fontWeight: 600, - color: - theme.colorScheme === "light" - ? theme.colors.cyan[7] - : theme.colors.dark[0], - height: "auto", - paddingTop: 18, - paddingLeft: 13, - border: `1px solid ${ - theme.colors.blueGray[theme.colorScheme === "dark" ? 7 : 2] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - // fontSize: "16px !important", - background: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.fn.lighten(theme.colors.blueGray[0], 0.5), - "&:focus, &:focus-within": { - outline: "none", - borderColor: `${ - theme.colors[theme.primaryColor][ - theme.colorScheme === "dark" ? 8 : 5 - ] - } !important`, - }, - }, - - required: { - display: "none", - // ":before": { marginLeft: "auto", content: '" required"' }, - }, - - error: { - fontSize: 14, - }, - - label: { - position: "absolute", - pointerEvents: "none", - color: theme.colors.blueGray[theme.colorScheme === "dark" ? 2 : 6], - fontSize: theme.fontSizes.xs, - paddingLeft: 14, - paddingTop: 6, - zIndex: 1, - }, - item: { - fontWeight: 600, - marginTop: 3, - }, - }, - }; - - return ( - } - {...form.getInputProps("status")} - /> - {shopData?.shops.length > 0 && ( - { - form.setFieldValue("lineItems", formList([])); - form.getInputProps("shopId").onChange(event); - }} - error={form.getInputProps("shopId").error} - shops={shopData?.shops} - form={form} - mt="md" - size="md" - label="Shop" - /> - )} - - {shop && shop.searchProductsEndpoint ? ( - - ) : ( - - - - - - )} - {channel && channel.searchProductsEndpoint && ( - 0 && ( - { - form.getInputProps("channelId").onChange(event); - }} - error={form.getInputProps("channelId").error} - channels={channelData?.channels} - form={form} - type="sm" - size="xs" - rightSection={ - - } - /> - ) - } - /> - )} - { - setType(value); - }} - data={Object.keys(ShopForms).map((key) => ({ - value: key, - label: ShopForms[key].label, - }))} - styles={{ - root: { - position: "relative", - }, - - input: { - fontWeight: 600, - color: - theme.colorScheme === "light" - ? theme.colors.cyan[7] - : theme.colors.dark[0], - height: "auto", - paddingTop: 18, - paddingLeft: 13, - border: `1px solid ${ - theme.colors.blueGray[theme.colorScheme === "dark" ? 7 : 2] - }`, - boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)", - // fontSize: "16px !important", - textTransform: "uppercase", - background: - theme.colorScheme === "dark" - ? theme.colors.dark[5] - : theme.fn.lighten(theme.colors.blueGray[0], 0.5), - "&:focus, &:focus-within": { - outline: "none", - borderColor: `${ - theme.colors[theme.primaryColor][ - theme.colorScheme === "dark" ? 8 : 5 - ] - } !important`, - }, - }, - - required: { - display: "none", - // ":before": { marginLeft: "auto", content: '" required"' }, - }, - - error: { - fontSize: 14, - }, - - label: { - position: "absolute", - pointerEvents: "none", - color: theme.colors.blueGray[theme.colorScheme === "dark" ? 2 : 6], - fontSize: theme.fontSizes.xs, - paddingLeft: 14, - paddingTop: 6, - zIndex: 1, - }, - item: { - fontWeight: 600, - marginTop: 3, - textTransform: "uppercase", - }, - }} - size="md" - /> - {ShopForms[type].component} - -
- ); -}; diff --git a/components/OSOrders/editCartItem/index.js b/components/OSOrders/editCartItem/index.js deleted file mode 100644 index 1770a9a..0000000 --- a/components/OSOrders/editCartItem/index.js +++ /dev/null @@ -1,152 +0,0 @@ -import React, { useState } from "react"; -import { useForm } from "@mantine/hooks"; -import { - TextInput, - Button, - Text, - LoadingOverlay, - Drawer, - useMantineTheme, - Box, -} from "@mantine/core"; -import { mutate } from "swr"; -import { ORDER_COUNT_QUERY } from "@graphql/orders"; -import { UPDATE_CARTITEM } from "@graphql/cartItems"; -import { request } from "graphql-request"; -import { useNotifications } from "@mantine/notifications"; - -export function EditCartItem({ isOpen, onClose, cartItem, mutateOrders }) { - const notifications = useNotifications(); - - const [loading, setLoading] = useState(false); - - const theme = useMantineTheme(); - - const { name, error, purchaseId, url } = cartItem; - - const form = useForm({ - initialValues: { - name, - error, - purchaseId, - url, - }, - }); - - const handleSubmit = async ({ name, error, purchaseId, url }) => { - setLoading(true); - // setError(null); - - const res = await request("/api/graphql", UPDATE_CARTITEM, { - id: cartItem.id, - data: { - name, - error, - purchaseId, - url, - // checkOrder: true, - }, - }) - .then(async () => { - await mutateOrders(); - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "PENDING" } } }), - ]); - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "INPROCESS" } } }), - ]); - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "AWAITING" } } }), - ]); - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "BACKORDERED" } } }), - ]); - notifications.showNotification({ - title: `Order has been updated.`, - }); - onClose(); - }) - .catch((error) => { - setLoading(false); - notifications.showNotification({ - title: error.response.errors[0].extensions.code, - message: error.response.errors[0].message, - color: "red", - }); - }); - }; - - return ( - - Edit Cart Item - - } - padding="xl" - size="lg" - position="right" - > -
- - - - - - - - - - -
- ); -} diff --git a/components/OSOrders/editOrder/index.js b/components/OSOrders/editOrder/index.js deleted file mode 100644 index fd2b095..0000000 --- a/components/OSOrders/editOrder/index.js +++ /dev/null @@ -1,539 +0,0 @@ -import React, { useState } from "react"; -import { - TextInput, - Button, - Text, - Group, - Drawer, - useMantineTheme, - Box, - Select, - Modal, - Tooltip, -} from "@mantine/core"; -import useSWR, { mutate } from "swr"; -import { ORDER_COUNT_QUERY, UPDATE_ORDER } from "@graphql/orders"; -import { request } from "graphql-request"; -import { useNotifications } from "@mantine/notifications"; -import { CartItemSelect } from "@components/CreateViews/CreateOrderView/CartItemSelect"; -import { ChannelSelect } from "@components/CreateViews/CreateOrderView/ChannelSelect"; -import { LineItemSelect } from "@components/CreateViews/CreateOrderView/LineItemSelect"; -import { ShopSelect } from "@components/CreateViews/CreateOrderView/ShopSelect"; -import { SHOPS_QUERY } from "@graphql/shops"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import { CHANNELS_QUERY } from "@graphql/channels"; -import { useForm, formList } from "@mantine/form"; -import { ChevronDownIcon } from "@primer/octicons-react"; -import isPlainObject from "lodash.isplainobject"; -import isEqual from "lodash.isequal"; -import reduce from "lodash.reduce"; -import { - CREATE_CARTITEM, - DELETE_CARTITEM_MUTATION, - UPDATE_CARTITEM, -} from "@graphql/cartItems"; -import { - CREATE_LINEITEM, - DELETE_LINEITEM_MUTATION, - UPDATE_LINEITEM, -} from "@graphql/lineItems"; - -export function EditOrder({ isOpen, onClose, order, mutateOrders }) { - const notifications = useNotifications(); - - const { data: shopData } = useSWR(SHOPS_QUERY, gqlFetcher); - - const { data: channelData, error } = useSWR(CHANNELS_QUERY, gqlFetcher); - - const [loading, setLoading] = useState(false); - - const theme = useMantineTheme(); - - const { - status, - first_name, - last_name, - email, - streetAddress1, - streetAddress2, - city, - state, - zip, - country, - lineItems, - cartItems, - } = order; - - const initialValues = { - status, - first_name, - last_name, - email, - streetAddress1, - streetAddress2, - city, - state, - zip, - country, - shopId: order?.shop?.id ?? shopData?.shops[0]?.id, - channelId: channelData?.channels[0]?.id, - lineItems: formList( - lineItems?.map(({ name, ...item }) => ({ - title: name, - ...item, - })) ?? [] - ), - cartItems: formList( - cartItems?.map(({ name, ...item }) => ({ - title: name, - ...item, - })) ?? [] - ), - }; - - const form = useForm({ - initialValues, - }); - - const shop = shopData?.shops.find((shop) => shop.id === form.values.shopId); - const channel = channelData?.channels.find( - (channel) => channel.id === form.values.channelId - ); - - const diff = function (obj1, obj2) { - return reduce( - obj1, - function (result, value, key) { - if (isPlainObject(value)) { - result[key] = diff(value, obj2[key]); - } else if (!isEqual(value, obj2[key])) { - result[key] = value; - } - return result; - }, - {} - ); - }; - - function getDifference(array1, array2) { - return array1.filter((object1) => { - return !array2.some((object2) => { - return object1.id === object2.id; - }); - }); - } - - const handleSubmit = async (values) => { - setLoading(true); - console.log(values); - - const { lineItems, cartItems, channelId, ...updatedValues } = diff( - values, - initialValues - ); - - console.log({ updatedValues }); - try { - if (lineItems) { - const lineItemsToDelete = getDifference( - initialValues.lineItems, - form.values.lineItems - ); - if (lineItemsToDelete) { - for (const { - id, - order: lineOrder, - channel, - title, - ...restItem - } of lineItemsToDelete) { - if (id) { - await request("/api/graphql", DELETE_LINEITEM_MUTATION, { - id, - }); - } - } - } - for (const { - id, - order: lineOrder, - title, - quantity, - ...restItem - } of lineItems) { - if (id) { - console.log("if called", id); - await request("/api/graphql", UPDATE_LINEITEM, { - id, - data: { - quantity, - }, - }); - } else { - console.log("else called", restItem); - await request("/api/graphql", CREATE_LINEITEM, { - data: { - ...restItem, - name: title, - quantity, - order: { connect: { id: order.id } }, - }, - }); - } - } - } - if (cartItems) { - const cartItemsToDelete = getDifference( - initialValues.cartItems, - form.values.cartItems - ); - if (cartItemsToDelete) { - for (const { - id, - order: cartOrder, - channel, - title, - ...restItem - } of cartItemsToDelete) { - if (id) { - await request("/api/graphql", DELETE_CARTITEM_MUTATION, { - id, - }); - } - } - } - for (const { - id, - order: cartOrder, - channel, - title, - quantity, - ...restItem - } of cartItems) { - if (id) { - console.log("if called", id); - await request("/api/graphql", UPDATE_CARTITEM, { - id, - data: { - quantity, - }, - }); - } else { - console.log("else called", restItem); - await request("/api/graphql", CREATE_CARTITEM, { - data: { - ...restItem, - name: title, - quantity, - order: { connect: { id: order.id } }, - channel: { connect: { id: channel.id } }, - }, - }); - } - } - } - if (Object.keys(updatedValues).length > 0) { - const { shopId, ...restUpdated } = updatedValues; - const res = await request("/api/graphql", UPDATE_ORDER, { - id: order.id, - data: { - ...(shopId && { shop: { connect: { id: shopId } } }), - ...restUpdated, - }, - }); - } - - if (updatedValues.status) { - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "PENDING" } } }), - ]); - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "INPROCESS" } } }), - ]); - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "AWAITING" } } }), - ]); - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "BACKORDERED" } } }), - ]); - } - console.log("called"); - const hello = await mutateOrders(); - console.log({ hello }); - notifications.showNotification({ - title: `Order has been updated.`, - }); - onClose(); - } catch (error) { - setLoading(false); - notifications.showNotification({ - title: error.response.errors[0].extensions.code, - message: error.response.errors[0].message, - color: "red", - }); - } - }; - - return ( - - Edit Order - - } - styles={{ - inner: { padding: 20 }, - // modal: { height: "100%" }, - // body: { minHeight: "100%" }, - }} - > -
- - - - - - - - - - - {/* {error && ( - - {error} - - )} */} - - - } - onKeyPress={(e) => { - if (e.key === "Enter") { - setSearchEntry(e.target.value); - } - }} - /> - - - {!data && } - {error && Something went wrong. Please try again later.} - - - - - ); -}; diff --git a/components/OSOrders/osOrderList.js b/components/OSOrders/osOrderList.js deleted file mode 100644 index 899d1c0..0000000 --- a/components/OSOrders/osOrderList.js +++ /dev/null @@ -1,959 +0,0 @@ -import React, { useState } from "react"; -import { useNotifications } from "@mantine/notifications"; -import { - Box, - Button, - Menu, - useMantineTheme, - Loader, - ActionIcon, - Divider, - Text, - Group, - Skeleton, -} from "@mantine/core"; -import { mutate } from "swr"; -import { Order } from "@primitives/order"; -import { LineItem } from "@primitives/lineItem"; -import { CartItem } from "@primitives/cartItem"; -import { QuantityCounter } from "@primitives/quantityCounter"; -import { Collapse } from "@primitives/collapse"; -import { ErrorTooltip } from "@primitives/errorTooltip"; -import { - GlobeIcon, - KebabHorizontalIcon, - AlertFillIcon, - ArrowRightIcon, - ChevronDownIcon, - XIcon, -} from "@primer/octicons-react"; -import { EditOrder } from "./editOrder"; -import { EditCartItem } from "./editCartItem"; -import { PreviousCarts } from "./previousCarts"; -import { EditProduct } from "../ProductSearch/editProduct"; -import { - PLACE_ORDERS, - ORDER_COUNT_QUERY, - UPDATE_ORDER, - DELETE_ORDER, - ADD_TO_CART_MUTATION, -} from "@graphql/orders"; -import { ADDMATCHTOCART_MUTATION, MATCHORDER_MUTATION } from "@graphql/matches"; -import { DELETE_CARTITEM_MUTATION, UPDATE_CARTITEM } from "@graphql/cartItems"; -import request from "graphql-request"; -import { ChannelProductSearch } from "@components/ProductSearch"; - -export function OsOrderList({ - data, - mutateOrders, - orderCountMutate, - status, - orderPerPage, -}) { - const theme = useMantineTheme(); - - const [editProduct, setEditProduct] = useState(null); - const [selectedOrder, setSelectedOrder] = useState(null); - const [editOrder, setEditOrder] = useState(null); - const [editCartItem, setEditCartItem] = useState(null); - const [previousCarts, setPreviousCarts] = useState(null); - const [loading, setLoading] = useState(null); - const [loadingColor, setLoadingColor] = useState(null); - const [loadingID, setLoadingID] = useState(null); - - const editOrderDetails = data?.orders?.filter( - ({ id }) => id === editOrder - )[0]; - - const previousCartsDetails = data?.orders?.filter( - ({ id }) => id === previousCarts - )[0]; - - const editCartItemDetails = - editOrder && - editCartItem && - data?.orders - ?.filter(({ id }) => id === editOrder)[0] - ?.cartItems.filter(({ id }) => id === editCartItem)[0]; - - const notifications = useNotifications(); - - function updateOrderCache(updatedOrder) { - return mutateOrders(({ orders }) => { - const newData = []; - for (const item of orders) { - if (item.id === updatedOrder.id) { - newData.push(updatedOrder); - } else { - newData.push(item); - } - } - return { - orders: newData, - }; - }, false); - } - - const fetchGQL = async ({ query, values, success, callback }) => { - const res = await request("/api/graphql", query, values) - .then(async (data) => { - callback && callback({ returnedData: data }); - success && - notifications.showNotification({ - title: success, - }); - }) - .catch((error) => { - notifications.showNotification({ - title: error.response?.errors[0].extensions.code, - message: error.response?.errors[0].message, - color: "red", - }); - }); - - // try { - // const data = await request("/api/graphql", query, values); - // if (callback) callback({ returnedData: data }); - // if (success) - // notifications.showNotification({ - // title: success, - // }); - // } catch (error) { - // console.error(error); - - // notifications.showNotification({ - // title: error.message.split(":")[0], - // message: error.message.split(":")[1], - // }); - // } - }; - - const placeOrder = async ({ orderId }) => { - const res = await request("/api/graphql", PLACE_ORDERS, { - ids: [orderId], - }) - .then(async () => { - mutateOrders(); - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "PENDING" } } }), - ]); - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "INPROCESS" } } }), - ]); - await mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: "AWAITING" } } }), - ]); - notifications.showNotification({ - title: success, - }); - }) - .catch((error) => { - error.response && - notifications.showNotification({ - title: error.response.errors[0].extensions.code, - message: error.response.errors[0].message, - color: "red", - }); - }); - }; - - const deleteOrderErrors = async ({ orderId }) => { - await fetchGQL({ - values: { - id: orderId, - data: { orderError: "" }, - }, - query: UPDATE_ORDER, - success: "Error deleted", - callback: ({ returnedData }) => { - updateOrderCache(returnedData.updateOrder); - }, - }); - }; - - const deleteCartItemErrors = async ({ orderId }) => { - const res = await request("/api/graphql", UPDATE_ORDER, { - id: orderId, - data: { orderError: null }, - }) - .then(async (data) => { - mutateOrders(({ orders }) => { - const newData = []; - for (const item of orders) { - if (item.id === returnedData.id) { - newData.push(returnedData); - } else { - newData.push(item); - } - } - return { orders: newData }; - }, false); - }) - .catch((error) => { - notifications.showNotification({ - title: error.response.errors[0].extensions.code, - message: error.response.errors[0].message, - color: "red", - }); - }); - }; - - const orderButtons = [ - { - buttonText: "GET MATCH", - color: "green", - icon: , - onClick: async ({ orderId }) => { - setLoadingColor("green"); - setLoading("Getting Match"); - setLoadingID(orderId); - await fetchGQL({ - values: { orderId }, - query: ADDMATCHTOCART_MUTATION, - success: "Match has been added", - callback: ({ returnedData }) => { - updateOrderCache(returnedData.addMatchToCart); - }, - }); - setLoadingColor(null); - setLoading(null); - setLoadingID(null); - }, - }, - { - buttonText: "SAVE MATCH", - color: "teal", - icon: , - onClick: async ({ orderId }) => { - setLoadingColor("cyan"); - setLoading("Saving Match"); - setLoadingID(orderId); - await fetchGQL({ - values: { orderId }, - query: MATCHORDER_MUTATION, - success: "Cart has been matched", - }); - setLoadingColor(null); - setLoading(null); - setLoadingID(null); - }, - }, - { - buttonText: "PLACE ORDER", - color: "cyan", - icon: , - onClick: async ({ orderId }) => { - setLoadingColor("blue"); - setLoading("Processing"); - setLoadingID(orderId); - await placeOrder({ orderId }); - setLoadingColor(null); - setLoading(null); - setLoadingID(null); - }, - }, - { - buttonText: "EDIT ORDER", - color: "blue", - icon: , - onClick: ({ orderId }) => { - setEditOrder(orderId); - }, - }, - // { - // buttonText: "SHOW PREVIOUS CARTS", - // color: "grape", - // icon: , - // onClick: ({ orderId }) => { - // setPreviousCarts(orderId); - // }, - // }, - { - buttonText: "DELETE ORDER", - color: "red", - icon: , - onClick: async ({ orderId }) => { - setLoadingColor("red"); - setLoading("Deleting"); - setLoadingID(orderId); - // await fetchGQL({ - // values: { id: orderId }, - // query: DELETE_CARTITEM, - // }); - // await fetchGQL({ - // values: { id: orderId }, - // query: DELETE_CARTITEM, - // }); - await fetchGQL({ - values: { id: orderId }, - query: DELETE_ORDER, - success: "Order has been deleted", - callback: () => { - mutateOrders(); - orderCountMutate(); - mutate([ - ORDER_COUNT_QUERY, - JSON.stringify({ where: { status: { equals: status } } }), - ]); - }, - }); - setLoadingColor(null); - setLoading(null); - setLoadingID(null); - }, - }, - ]; - - const lineItemButtons = [ - { - buttonText: "Edit Product", - color: "lightBlue", - icon: , - onClick: ({ product }) => { - setEditProduct(product); - }, - }, - ]; - - const cartItemButtons = [ - { - buttonText: "EDIT CART ITEM", - color: "blue", - onClick: ({ orderId, cartItemId }) => { - setEditOrder(orderId); - setEditCartItem(cartItemId); - }, - }, - ]; - - return ( - - {editProduct && ( - setEditProduct(null)} - /> - )} - {editOrderDetails && !editCartItemDetails && ( - setEditOrder(null)} - order={editOrderDetails} - mutateOrders={mutateOrders} - /> - )} - {editCartItemDetails && ( - { - setEditOrder(null); - setEditCartItem(null); - }} - cartItem={editCartItemDetails} - mutateOrders={mutateOrders} - /> - )} - {previousCartsDetails && ( - setPreviousCarts(null)} - order={previousCartsDetails} - mutateOrders={mutateOrders} - /> - )} - {!data && ( - <> - - - - - )} - {data?.orders?.map((order) => { - const { - id, - orderId, - shop, - orderName, - first_name, - last_name, - streetAddress1, - streetAddress2, - city, - state, - zip, - createdAt, - cartItems, - lineItems, - orderError, - } = order; - return ( - - ( - <> - - setSelectedOrder ? setSelectedOrder(id) : null - } - > - - {" "} - {first_name} {last_name} -
- {streetAddress1} {streetAddress2} -
- {city} - {state && ", "} - {state} {zip} - - } - buttons={ - <> - {loading && loadingID === id && ( - - )} - - - - } - styles={{ - item: { - color: - theme.colorScheme === "dark" - ? theme.colors.blueGray[2] - : theme.colors.blueGray[7], - textTransform: "uppercase", - fontWeight: 600, - letterSpacing: 0.4, - }, - }} - > - Order Actions - {orderButtons.map( - ({ - color, - icon, - endpoint, - buttonText, - success, - onClick, - }) => ( - onClick({ orderId: id })} - color={color} - // sx={{ color: theme.colors[color] }} - // icon={icon} - > - {buttonText} - - ) - )} - - - } - error={ - orderError && ( - <> - deleteOrderErrors({ orderId: id })} - label={orderError} - buttonText="1 ERROR" - /> - - ) - } - /> -
- {open && ( - <> - - - - Line Items - - {lineItems.map( - ({ - id: lineId, - name, - quantity, - price, - image, - productId, - variantId, - lineItemId, - }) => ( - - - - } - styles={{ - item: { - color: - theme.colorScheme === "dark" - ? theme.colors.blueGray[2] - : theme.colors.blueGray[7], - textTransform: "uppercase", - fontWeight: 600, - letterSpacing: 0.4, - }, - }} - > - Actions - {lineItemButtons.map( - ({ color, buttonText, onClick, icon }) => ( - - onClick({ - product: { - productId, - variantId, - price, - title: name, - image, - domain: shop.domain, - accessToken: shop.accessToken, - updateProductEndpoint: - shop.updateProductEndpoint, - }, - }) - } - // sx={{ color: theme.colors[color] }} - // icon={icon} - > - {buttonText} - - ) - )} - - } - /> - ) - )} - - {cartItems?.length > 0 && ( - <> - - - - Cart Items - - {cartItems.map( - ({ - id: cartId, - name, - quantity, - price, - image, - productId, - variantId, - purchaseId, - lineItemId, - channel, - url: cartURL, - error, - status, - }) => ( - - - {cartURL ? ( - - ) : ( - <> - - fetchGQL({ - values: { - id: cartId, - data: { - quantity: parseInt(e), - }, - }, - query: UPDATE_CARTITEM, - success: - "Item has been updated", - callback: ({ - returnedData, - }) => - updateOrderCache( - returnedData - .updateCartItem.order - ), - }) - } - /> - {/* */} - - fetchGQL({ - values: { id: cartId }, - query: - DELETE_CARTITEM_MUTATION, - success: - "Item has been deleted", - callback: ({ - returnedData, - }) => { - const filteredOrder = { - ...order, - cartItems: - order.cartItems.filter( - (c) => - c.id !== - returnedData - .deleteCartItem.id - ), - }; - updateOrderCache( - filteredOrder - ); - }, - }) - } - > - - - - )} - - - - - } - styles={{ - item: { - color: - theme.colorScheme === "dark" - ? theme.colors.blueGray[2] - : theme.colors.blueGray[7], - textTransform: "uppercase", - fontWeight: 600, - letterSpacing: 0.4, - }, - }} - > - Actions - {cartItemButtons.map( - ({ - color, - endpoint, - buttonText, - success, - onClick, - }) => ( - - onClick({ - orderId: id, - cartItemId: cartId, - }) - } - // sx={{ color: theme.colors[color] }} - // icon={icon} - > - {buttonText} - - ) - )} - - - } - error={ - error && ( - - fetchGQL({ - values: { - id: cartId, - data: { error: "" }, - }, - query: UPDATE_CARTITEM, - callback: ({ returnedData }) => - updateOrderCache( - returnedData.updateCartItem - .order - ), - }) - } - label={error} - buttonText={ - - } - /> - ) - } - /> - - ) - )} - - - )} - - {status === "PENDING" && ( - <> - - - ( - <> - - - Channel Search - - setOpen(!open)} - sx={{ marginLeft: "auto" }} - aria-label="show line-items" - > - - - {/* */} - - {open && ( - - - fetchGQL({ - values: { - orderId: id, - ...values, - }, - query: ADD_TO_CART_MUTATION, - success: "Item has been added", - callback: ({ returnedData }) => - updateOrderCache( - returnedData.addToCart - ), - }) - } - /> - - )} - - )} - /> - - - )} - - )} - - )} - /> -
- ); - })} -
- ); -} diff --git a/components/OSOrders/previousCarts/CartList.js b/components/OSOrders/previousCarts/CartList.js deleted file mode 100644 index e5e5da6..0000000 --- a/components/OSOrders/previousCarts/CartList.js +++ /dev/null @@ -1,111 +0,0 @@ -import { Box, Skeleton, Group } from "@mantine/core"; -import useSWR from "swr"; -import { CartItem } from "@primitives/cartItem"; - -const objectsEqual = (o1, o2) => - typeof o1 === "object" && Object.keys(o1).length > 0 - ? Object.keys(o1).length === Object.keys(o2).length && - Object.keys(o1).every((p) => objectsEqual(o1[p], o2[p])) - : o1 === o2; - -const arraysEqual = (a1, a2) => - a1.length === a2.length && a1.every((o, idx) => objectsEqual(o, a2[idx])); - -const compare = (a, b) => { - const bandA = a.name.toUpperCase(); - const bandB = b.name.toUpperCase(); - - let comparison = 0; - if (bandA > bandB) { - comparison = 1; - } else if (bandA < bandB) { - comparison = -1; - } - return comparison; -}; - -export function CartList({ - shopId, - accessToken, - domain, - searchEntry, - shopName, - searchProductsEndpoint, -}) { - const params = new URLSearchParams({ - accessToken, - domain, - searchEntry: searchEntry.map(({ name }) => name), - shopName, - }).toString(); - - const url = `${searchProductsEndpoint}?${params}`; - - const { data, error } = useSWR(url, { - revalidateOnFocus: false, - shouldRetryOnError: false, - }); - - if (error) { - return Something went wrong. Please try again later.; - } - - if (!data) return ; - - return ( - - {data?.orders - ?.filter( - (order) => - order.cart !== null && - arraysEqual( - order.lineItems - .map(({ name, quantity }) => ({ - name, - quantity, - })) - .sort(compare), - searchEntry.sort(compare) - ) - ) - .map(({ cartItems }) => ( - - {cartItems?.length > 0 && ( - - {cartItems.map( - ({ - id: cartId, - name, - quantity, - price, - image, - productId, - variantId, - purchaseId, - lineItemId, - channel, - url, - error, - }) => ( - - - - ) - )} - - )} - - ))} - - ); -} diff --git a/components/OSOrders/previousCarts/index.js b/components/OSOrders/previousCarts/index.js deleted file mode 100644 index 5422c8d..0000000 --- a/components/OSOrders/previousCarts/index.js +++ /dev/null @@ -1,37 +0,0 @@ -import { Box, Drawer } from "@mantine/core"; - -import { CartList } from "./CartList"; - -export const PreviousCarts = ({ isOpen, onClose, order, mutateOrders }) => { - const searchEntry = order.lineItems.map(({ name, quantity }) => ({ - name, - quantity, - })); - - return ( - - Edit Order - - } - padding="xl" - size="lg" - position="right" - > - - {order.shop.searchOrdersEndpoint} - - - - ); -}; diff --git a/components/Patterns.js b/components/Patterns.js deleted file mode 100644 index ea09633..0000000 --- a/components/Patterns.js +++ /dev/null @@ -1,167 +0,0 @@ -export const patterns = ({ FILLCOLOR, FILLOPACITY }) => ({ - jigsaw: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='192' viewBox='0 0 192 192'%3E%3Cpath fill='FILLCOLOR' fill-opacity='FILLOPACITY' d='M192 15v2a11 11 0 0 0-11 11c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H145v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11 13 13 0 1 1 .02 26 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43a6.1 6.1 0 0 0-3.03 4.87V143h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 181 164a11 11 0 0 0 11 11v2a13 13 0 0 1-13-13 12 12 0 0 1 3.11-7.53l2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84a6.1 6.1 0 0 0-4.87-3.03H145v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 124 181a11 11 0 0 0-11 11h-2a13 13 0 0 1 13-13c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43a6.1 6.1 0 0 0 3.03-4.87V145h-35.02a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 107 124a11 11 0 0 0-22 0c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H49v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11A13 13 0 0 1 81 192h-2a11 11 0 0 0-11-11c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V145H11.98a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 0 1 0 177v-2a11 11 0 0 0 11-11c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H47v-35.02a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 28 109a13 13 0 1 1 0-26c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43A6.1 6.1 0 0 0 47 84.02V49H11.98a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 11 28 11 11 0 0 0 0 17v-2a13 13 0 0 1 13 13c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84A6.1 6.1 0 0 0 11.98 47H47V11.98a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 68 11 11 11 0 0 0 79 0h2a13 13 0 0 1-13 13 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43A6.1 6.1 0 0 0 49 11.98V47h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 85 68a11 11 0 0 0 22 0c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H143V11.98a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 124 13a13 13 0 0 1-13-13h2a11 11 0 0 0 11 11c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V47h35.02a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 179 28a13 13 0 0 1 13-13zM84.02 143a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 83 124a13 13 0 1 1 26 0c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84a6.1 6.1 0 0 0 4.87 3.03H143v-35.02a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 164 107a11 11 0 0 0 0-22c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V49h-35.02a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 1 1 83 68a12 12 0 0 1 3.11-7.53l2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84A6.1 6.1 0 0 0 84.02 49H49v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 28 85a11 11 0 0 0 0 22c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V143h35.02z'%3E%3C/path%3E%3C/svg%3E\")", - overcast: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80' width='80' height='80'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M0 0h80v80H0V0zm20 20v40h40V20H20zm20 35a15 15 0 1 1 0-30 15 15 0 0 1 0 30z' opacity='.5'%3E%3C/path%3E%3Cpath d='M15 15h50l-5 5H20v40l-5 5V15zm0 50h50V15L80 0v80H0l15-15zm32.07-32.07l3.54-3.54A15 15 0 0 1 29.4 50.6l3.53-3.53a10 10 0 1 0 14.14-14.14zM32.93 47.07a10 10 0 1 1 14.14-14.14L32.93 47.07z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E\")", - "formal-invitation": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='18' viewBox='0 0 100 18'%3E%3Cpath fill='FILLCOLOR' fill-opacity='FILLOPACITY' d='M61.82 18c3.47-1.45 6.86-3.78 11.3-7.34C78 6.76 80.34 5.1 83.87 3.42 88.56 1.16 93.75 0 100 0v6.16C98.76 6.05 97.43 6 96 6c-9.59 0-14.23 2.23-23.13 9.34-1.28 1.03-2.39 1.9-3.4 2.66h-7.65zm-23.64 0H22.52c-1-.76-2.1-1.63-3.4-2.66C11.57 9.3 7.08 6.78 0 6.16V0c6.25 0 11.44 1.16 16.14 3.42 3.53 1.7 5.87 3.35 10.73 7.24 4.45 3.56 7.84 5.9 11.31 7.34zM61.82 0h7.66a39.57 39.57 0 0 1-7.34 4.58C57.44 6.84 52.25 8 46 8S34.56 6.84 29.86 4.58A39.57 39.57 0 0 1 22.52 0h15.66C41.65 1.44 45.21 2 50 2c4.8 0 8.35-.56 11.82-2z'%3E%3C/path%3E%3C/svg%3E\")", - topography: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='600' viewBox='0 0 600 600'%3E%3Cpath fill='%231e293b' fill-opacity='0.4' d='M600 325.1v-1.17c-6.5 3.83-13.06 7.64-14.68 8.64-10.6 6.56-18.57 12.56-24.68 19.09-5.58 5.95-12.44 10.06-22.42 14.15-1.45.6-2.96 1.2-4.83 1.9l-4.75 1.82c-9.78 3.75-14.8 6.27-18.98 10.1-4.23 3.88-9.65 6.6-16.77 8.84-1.95.6-3.99 1.17-6.47 1.8l-6.14 1.53c-5.29 1.35-8.3 2.37-10.54 3.78-3.08 1.92-6.63 3.26-12.74 5.03a384.1 384.1 0 0 1-4.82 1.36c-2.04.58-3.6 1.04-5.17 1.52a110.03 110.03 0 0 0-11.2 4.05c-2.7 1.15-5.5 3.93-8.78 8.4a157.68 157.68 0 0 0-6.15 9.2c-5.75 9.07-7.58 11.74-10.24 14.51a50.97 50.97 0 0 1-4.6 4.22c-2.33 1.9-10.39 7.54-11.81 8.74a14.68 14.68 0 0 0-3.67 4.15c-1.24 2.3-1.9 4.57-2.78 8.87-2.17 10.61-3.52 14.81-8.2 22.1-4.07 6.33-6.8 9.88-9.83 12.99-.47.48-.95.96-1.5 1.48l-3.75 3.56c-1.67 1.6-3.18 3.12-4.86 4.9a42.44 42.44 0 0 0-9.89 16.94c-2.5 8.13-2.72 15.47-1.76 27.22.47 5.82.51 6.36.51 8.18 0 10.51.12 17.53.63 25.78.24 4.05.56 7.8.97 11.22h.9c-1.13-9.58-1.5-21.83-1.5-37 0-1.86-.04-2.4-.52-8.26-.94-11.63-.72-18.87 1.73-26.85a41.44 41.44 0 0 1 9.65-16.55c1.67-1.76 3.18-3.27 4.83-4.85.63-.6 3.13-2.96 3.75-3.57a71.6 71.6 0 0 0 1.52-1.5c3.09-3.16 5.86-6.76 9.96-13.15 4.77-7.42 6.15-11.71 8.34-22.44.86-4.21 1.5-6.4 2.68-8.6.68-1.25 1.79-2.48 3.43-3.86 1.38-1.15 9.43-6.8 11.8-8.72 1.71-1.4 3.26-2.81 4.7-4.3 2.72-2.85 4.56-5.54 10.36-14.67a156.9 156.9 0 0 1 6.1-9.15c3.2-4.33 5.9-7.01 8.37-8.07 3.5-1.5 7.06-2.77 11.1-4.02a233.84 233.84 0 0 1 7.6-2.2l2.38-.67c6.19-1.79 9.81-3.16 12.98-5.15 2.14-1.33 5.08-2.33 10.27-3.65l6.14-1.53c2.5-.63 4.55-1.2 6.52-1.82 7.24-2.27 12.79-5.06 17.15-9.05 4.05-3.72 9-6.2 18.66-9.9l4.75-1.82c1.87-.72 3.39-1.31 4.85-1.91 10.1-4.15 17.07-8.32 22.76-14.4 6.05-6.45 13.95-12.4 24.49-18.92 1.56-.96 7.82-4.6 14.15-8.33v-64.58c-4 8.15-8.52 14.85-12.7 17.9-2.51 1.82-5.38 4.02-9.04 6.92a1063.87 1063.87 0 0 0-6.23 4.98l-1.27 1.02a2309.25 2309.25 0 0 1-4.87 3.9c-7.55 6-12.9 10.05-17.61 13.19-3.1 2.06-3.86 2.78-8.06 7.13-5.84 6.07-11.72 8.62-29.15 10.95-11.3 1.5-20.04 4.91-30.75 11.07-1.65.94-7.27 4.27-6.97 4.1-2.7 1.58-4.69 2.69-6.64 3.66-5.63 2.8-10.47 4.17-15.71 4.17-17.13 0-41.44 11.51-51.63 22.83-12.05 13.4-31.42 27.7-45.25 31.16-7.4 1.85-11.85 7.05-14.04 14.69-1.26 4.4-1.58 8.28-1.58 13.82 0 .82.01.98.24 3.63.45 5.18.35 8.72-.77 13.26-1.53 6.2-4.89 12.6-10.59 19.43-13.87 16.65-22.88 46.58-22.88 71.68 0 2.39.02 4.26.06 8.75.12 10.8.1 15.8-.22 21.95-.56 11.18-2.09 20.73-5 29.3h-1.05c2.94-8.56 4.49-18.12 5.05-29.35.31-6.13.34-11.1.22-21.9-.04-4.48-.06-6.36-.06-8.75 0-25.32 9.07-55.47 23.12-72.32 5.6-6.72 8.88-12.99 10.38-19.03 1.09-4.4 1.18-7.85.74-12.93-.23-2.7-.24-2.86-.24-3.72 0-5.62.32-9.57 1.62-14.1 2.28-7.95 6.97-13.44 14.76-15.39 13.6-3.4 32.82-17.59 44.75-30.84C409 360.14 433.58 348.5 451 348.5c5.07 0 9.77-1.33 15.26-4.07 1.93-.96 3.9-2.05 6.58-3.62-.3.18 5.33-3.16 6.98-4.11 10.82-6.21 19.66-9.67 31.11-11.2 17.23-2.3 22.9-4.75 28.57-10.64 4.25-4.41 5.04-5.16 8.22-7.28 4.68-3.11 10.01-7.14 17.55-13.14a1113.33 1113.33 0 0 0 4.86-3.89l1.28-1.02a4668.54 4668.54 0 0 1 6.23-4.98c3.67-2.9 6.55-5.12 9.07-6.95 4.37-3.19 9.16-10.56 13.29-19.4v66.9zm0-116.23c-.62.01-1.27.06-1.95.13-6.13.63-13.83 3.45-21.83 7.45-3.64 1.82-8.46 2.67-14.17 2.71-4.7.04-9.72-.47-14.73-1.33-1.7-.3-3.26-.61-4.67-.93a31.55 31.55 0 0 0-3.55-.57 273.4 273.4 0 0 0-16.66-.88c-10.42-.16-17.2.74-17.97 2.73-.38.97.6 2.55 3.03 4.87 1.01.97 2.22 2.03 4.04 3.55a1746.07 1746.07 0 0 0 4.79 4.02c1.39 1.2 3.1 1.92 5.5 2.5.7.16.86.2 2.64.54 3.53.7 5.03 1.25 6.15 2.63 1.41 1.76 1.4 4.54-.15 8.88-2.44 6.83-5.72 10.05-10.19 10.33-3.63.23-7.6-1.29-14.52-5.06-4.53-2.47-6.82-7.3-8.32-15.26-.17-.87-.32-1.78-.5-2.86l-.43-2.76c-1.05-6.58-1.9-9.2-3.73-10.11-.81-.4-1.59-.74-2.36-1-2.27-.77-4.6-1.02-8.1-.92-2.29.07-14.7 1-13.77.93-20.55 1.37-28.8 5.05-37.09 14.99a133.07 133.07 0 0 0-4.25 5.44l-2.3 3.09-2.51 3.32c-4.1 5.36-7.06 8.48-10.39 11.12-.65.52-1.33 1.04-2.13 1.62l-4.11 2.94a106.8 106.8 0 0 0-5.16 3.99c-4.55 3.74-9.74 8.6-16.25 15.38-8.25 8.58-11.78 13.54-11.7 15.95.07 1.65 1.64 2.11 6.79 2.38 1.61.09 2.15.12 2.98.2 2.95.24 5.09.73 6.81 1.68 7.48 4.15 11.63 7.26 13.95 11.58 3.3 6.15.8 12.88-8.89 20.26-8.28 6.3-11.1 10.37-11.31 14.96-.06 1.17 0 1.93.26 4.43.69 6.47.25 10.65-2.8 17.42a44.23 44.23 0 0 1-4.16 7.53c-2.82 3.97-5.47 5.74-10.6 7.69-.43.16-3.34 1.23-4.27 1.59-1.8.68-3.38 1.36-5.01 2.14-4.18 2-8.4 4.6-13.1 8.24-8.44 6.51-13.23 14.56-15.98 25.06-1.1 4.2-1.55 6.81-2.8 15.21-1.26 8.6-2.17 12.64-4.08 16.55-2.1 4.28-11.93 26.59-12.97 28.88a382.7 382.7 0 0 1-6.37 13.41c-4.07 8.11-7.61 14.07-10.73 17.81-5.38 6.46-8.98 14.37-13.77 28.42a810.14 810.14 0 0 0-1.89 5.6c-1.8 5.35-2.96 8.6-4.26 11.85-6.13 15.32-25.43 26.31-46.46 26.31-11.2 0-20.58-2.74-31.02-8.55-5.6-3.13-4.55-2.42-22.26-14.54-14.33-9.8-17.7-10.73-20.47-6.9-.37.5-1.81 2.74-1.83 2.77a52.24 52.24 0 0 1-4.94 5.9c-.73.79-5.52 5.87-6.97 7.45-2.38 2.6-4.3 4.81-5.98 6.93a45.6 45.6 0 0 0-5.08 7.66c-1.29 2.57-1.9 5.25-2.66 10.6a997.6 997.6 0 0 1-.46 3.18h-1l.47-3.32c.77-5.45 1.4-8.2 2.75-10.9a46.54 46.54 0 0 1 5.2-7.84c1.7-2.14 3.63-4.38 6.03-6.98 1.45-1.59 6.24-6.68 6.96-7.46a51.58 51.58 0 0 0 4.84-5.78s1.47-2.26 1.86-2.8c3.25-4.5 7.08-3.44 21.84 6.67 17.67 12.08 16.62 11.38 22.19 14.48 10.3 5.73 19.5 8.43 30.53 8.43 20.65 0 39.57-10.77 45.54-25.69a219.7 219.7 0 0 0 4.24-11.8 6752.32 6752.32 0 0 0 1.88-5.6c4.83-14.16 8.47-22.14 13.96-28.73 3.05-3.66 6.56-9.57 10.6-17.61 1.97-3.93 4.04-8.31 6.35-13.38 1.03-2.28 10.88-24.61 12.98-28.91 1.85-3.79 2.75-7.76 4-16.25 1.24-8.44 1.7-11.07 2.81-15.32 2.8-10.7 7.71-18.94 16.33-25.6a73.18 73.18 0 0 1 13.29-8.35c1.66-.8 3.27-1.48 5.08-2.18.94-.36 3.86-1.43 4.28-1.59 4.95-1.88 7.44-3.55 10.14-7.33 1.35-1.9 2.68-4.3 4.06-7.37 2.97-6.58 3.39-10.59 2.72-16.9a27.13 27.13 0 0 1-.27-4.58c.22-4.94 3.21-9.24 11.7-15.7 9.33-7.11 11.66-13.34 8.62-19-2.2-4.09-6.25-7.12-13.55-11.17-1.57-.88-3.6-1.33-6.42-1.57-.8-.07-1.34-.1-2.95-.19-5.77-.3-7.63-.85-7.72-3.34-.1-2.81 3.5-7.87 11.97-16.69 6.53-6.8 11.75-11.69 16.33-15.45 1.79-1.47 3.42-2.72 5.2-4.03l4.12-2.94c.79-.58 1.46-1.08 2.1-1.59 3.26-2.6 6.16-5.65 10.21-10.94a383.2 383.2 0 0 0 2.5-3.32l2.31-3.09c1.8-2.39 3.04-4 4.29-5.48 8.47-10.17 16.98-13.96 37.27-15.3-.44.02 12-.9 14.32-.98 3.62-.1 6.05.16 8.46.98.8.27 1.62.62 2.47 1.04 2.27 1.14 3.17 3.87 4.27 10.85l.44 2.76c.17 1.07.33 1.97.5 2.83 1.44 7.69 3.62 12.29 7.8 14.57 6.76 3.68 10.6 5.15 13.99 4.94 4-.25 6.99-3.17 9.3-9.67 1.45-4.04 1.46-6.49.32-7.92-.9-1.12-2.28-1.62-5.57-2.27a55.8 55.8 0 0 1-2.67-.55c-2.54-.6-4.39-1.4-5.93-2.71a252.63 252.63 0 0 0-4.78-4.01 84.35 84.35 0 0 1-4.08-3.6c-2.73-2.6-3.86-4.43-3.28-5.95 1.02-2.64 7.82-3.54 18.93-3.37a230.56 230.56 0 0 1 16.73.88c2.76.39 3.2.49 3.68.6 1.4.3 2.95.62 4.62.91a82.9 82.9 0 0 0 14.56 1.32c5.56-.04 10.24-.86 13.73-2.6 8.1-4.05 15.89-6.9 22.17-7.56.7-.07 1.4-.11 2.05-.13v1zm0-100.94v1.5c-8.62 16.05-17.27 29.55-23.65 35.92-3.19 3.2-7.62 4.9-13.54 5.56-4.45.48-8.28.4-19.18-.2-9.91-.55-15.32-.44-20.52.78a84.05 84.05 0 0 1-15 2.11l-2.25.14c-12.49.75-19.37 1.78-32.72 5.74-4.5 1.33-9.27 2.49-14.3 3.48a246.27 246.27 0 0 1-32.6 3.97c-7.56.45-13.21.57-20.24.57-5.4 0-11.9 1.61-18 5.18-8.3 4.87-15.06 12.87-19.53 24.5a68.57 68.57 0 0 1-4.56 9.8c-3.6 6.2-6.92 8.99-13.38 12.18l-4.03 1.96a64.48 64.48 0 0 0-15.16 10.25c-8.2 7.33-13.72 16.63-22.54 35.6l-2.08 4.49c-7.3 15.7-11.5 23.3-17.35 29.87-7.7 8.66-20.25 14.42-40.31 20.08-4.37 1.23-19.04 5.08-19.24 5.13-6.92 1.87-11.68 3.34-15.63 4.92-10.55 4.22-18.71 10.52-36.38 26.52l-1.7 1.54c-8.58 7.76-13.41 11.9-18.81 15.88-3.95 2.9-8 5.67-12.97 8.91-2.06 1.34-10.3 6.6-12.33 7.94-11.52 7.5-18.53 13.04-24.62 20.08a62.01 62.01 0 0 0-6.44 8.85c-4.13 6.91-6.27 13.15-9.2 25.11l-1.54 6.26c-.6 2.45-1.15 4.54-1.72 6.58-2.97 10.7-6.9 17.36-14.78 26.91L69.6 491a148.51 148.51 0 0 0-4.19 5.3 23.9 23.9 0 0 0-3.44 6.28c-1.16 3.23-1.52 5.9-1.87 11.94-.58 10.05-1.42 15.04-4.63 22.67-1.57 3.72-5.66 14.02-6.41 15.8a73.46 73.46 0 0 1-3.57 7.4c-2.88 5.14-6.71 10.12-13.12 16.95-5.96 6.36-8.87 10.9-10.61 16a56.88 56.88 0 0 0-1.38 4.82l-.46 1.84h-1.03l.52-2.08c.52-2.09.92-3.49 1.4-4.9 1.8-5.25 4.78-9.9 10.84-16.36 6.35-6.78 10.13-11.7 12.97-16.77a72.5 72.5 0 0 0 3.52-7.29c.75-1.76 4.84-12.06 6.4-15.8 3.17-7.5 3.99-12.4 4.56-22.33.35-6.14.72-8.88 1.93-12.23a24.9 24.9 0 0 1 3.58-6.54c1.27-1.7 2.6-3.37 4.22-5.34l4.11-4.95c7.8-9.46 11.66-16 14.59-26.54.56-2.04 1.1-4.12 1.71-6.56l1.53-6.26c2.96-12.04 5.13-18.36 9.32-25.39 1.84-3.08 4-6.05 6.54-8.99 6.17-7.12 13.24-12.7 24.83-20.26 2.05-1.33 10.28-6.6 12.33-7.94 4.96-3.22 9-5.98 12.92-8.87 5.37-3.95 10.19-8.08 18.74-15.82l1.7-1.54c17.76-16.09 25.98-22.43 36.67-26.7 4-1.6 8.8-3.09 15.75-4.96.21-.06 14.87-3.9 19.22-5.13 19.9-5.61 32.32-11.31 39.85-19.78 5.76-6.48 9.93-14.02 17.18-29.64l2.09-4.5c8.87-19.07 14.44-28.46 22.77-35.9a65.48 65.48 0 0 1 15.38-10.4l4.04-1.97c6.3-3.1 9.47-5.77 12.96-11.77a67.6 67.6 0 0 0 4.48-9.67c4.56-11.84 11.47-20.02 19.97-25 6.25-3.66 12.93-5.32 18.5-5.32 7.01 0 12.65-.12 20.17-.57a245.3 245.3 0 0 0 32.47-3.96c5-.98 9.75-2.13 14.22-3.45 13.43-3.98 20.38-5.02 32.94-5.78l2.24-.14c5.76-.37 9.8-.9 14.85-2.09 5.31-1.25 10.79-1.35 22.6-.7 9.04.5 12.84.58 17.21.1 5.71-.62 9.94-2.26 12.95-5.26 6.44-6.45 15.3-20.37 24.35-36.72zm0 450.21c-1.28-4.6-2.2-10.55-3.33-20.25l-.24-2.04-.23-2.03c-1.82-15.7-3.07-21.98-5.55-24.47-2.46-2.46-3.04-5.03-2.52-8.64.1-.6.18-1.1.39-2.15.69-3.54.77-5.04.08-6.84-.91-2.38-3.31-4.41-7.79-6.26-5.08-2.09-6.52-4.84-4.89-8.44.66-1.45 1.79-3.02 3.52-5.01 1.04-1.2 5.48-5.96 5.08-5.53 6.15-6.7 8.98-11.34 8.98-16.48a15.2 15.2 0 0 1 6.5-12.89v1.26a14.17 14.17 0 0 0-5.5 11.63c0 5.47-2.93 10.29-9.24 17.16.38-.42-4.04 4.33-5.07 5.5-1.67 1.93-2.75 3.43-3.36 4.77-1.37 3.04-.23 5.22 4.36 7.1 4.71 1.95 7.32 4.16 8.34 6.83.78 2.04.7 3.67-.03 7.4-.2 1.03-.3 1.51-.38 2.09-.48 3.33.03 5.59 2.23 7.8 2.74 2.74 3.98 8.96 5.84 25.06l.24 2.03.23 2.04c.82 7.01 1.53 12.06 2.34 16.03v4.33zm0-62.16c-1.4-3.13-4.43-9.9-4.95-11.17-1.02-2.53-1.25-3.8-.91-5.18.2-.84 2.05-4.68 2.32-5.33a70.79 70.79 0 0 0 3.54-11.2v3.99a62.82 62.82 0 0 1-2.62 7.6c-.31.75-2.09 4.46-2.27 5.18-.28 1.12-.08 2.22.87 4.57.41 1.02 2.5 5.7 4.02 9.09v2.45zm0-85.09c-1.65 1.66-3.66 2.9-6.4 4.13-.25.1-13.97 5.47-20.4 8.43-9.35 4.32-16.7 5.9-23.03 5.25-5.08-.53-9.02-2.25-14.77-5.92l-3.2-2.07a77.4 77.4 0 0 0-5.44-3.27c-4.05-2.18-3.25-5.8 1.47-10.47 3.71-3.68 9.6-7.93 18.73-13.8l4.46-2.82c17.95-11.33 18.22-11.5 22.27-14.74 11.25-9 19.69-14.02 26.31-15.1v1.02c-6.37 1.1-14.62 6-25.69 14.86-4.1 3.28-4.34 3.44-22.36 14.8a652.4 652.4 0 0 0-4.45 2.83c-9.07 5.83-14.92 10.05-18.57 13.66-4.31 4.28-4.95 7.13-1.7 8.88 1.7.91 3.29 1.88 5.5 3.3l3.2 2.08c5.64 3.59 9.45 5.25 14.34 5.76 6.13.64 13.32-.9 22.52-5.15 6.46-2.98 20.18-8.35 20.4-8.44 3.04-1.37 5.1-2.71 6.81-4.69v1.47zm0-41.37v1c-6.56.26-12.11 3.13-19.71 9.08l-4.63 3.68a51.87 51.87 0 0 1-4.4 3.14c-.82.52-5.51 3.33-6.22 3.76-3.31 2-6.15 3.8-8.87 5.6a112.61 112.61 0 0 0-8.16 5.92c-4.61 3.72-7.4 6.9-7.97 9.35-.63 2.67 1.48 4.53 7.05 5.46 10.7 1.78 20.92-.05 30.45-4.65a61.96 61.96 0 0 0 17.1-12.2 41.8 41.8 0 0 0 5.36-7.42v1.92a38.94 38.94 0 0 1-4.64 6.19 62.95 62.95 0 0 1-17.39 12.41c-9.7 4.68-20.13 6.55-31.05 4.73-6.06-1-8.65-3.29-7.85-6.67.64-2.74 3.53-6.05 8.31-9.9 2.35-1.9 5.1-3.88 8.24-5.97 2.73-1.82 5.58-3.61 8.9-5.62.72-.44 5.4-3.24 6.22-3.75 1.26-.8 2.6-1.76 4.3-3.09.8-.62 3.9-3.1 4.63-3.67 7.77-6.1 13.49-9.04 20.33-9.3zm0-154.6v1c-1.75-.24-4.3.23-7.82 1.55-10.01 3.75-13.8 5.07-19.15 6.76-1.78.56-2.63.83-3.87 1.24-1.48.5-3.16.76-6.74 1.16a1550.34 1550.34 0 0 0-2.64.3c-7.8.94-11.28 2.47-11.28 6.07 0 4.45 2.89 13.18 7.96 25.81a57.34 57.34 0 0 1 2.33 7.6 258.32 258.32 0 0 1 .84 3.46c1.86 7.62 3.17 10.71 5.56 11.67 2.21.88 4.7.6 7.47-.72 3.48-1.69 7.22-4.94 11.2-9.47 1.52-1.7 2.97-3.49 4.59-5.57l3.16-4.1c2.59-3.23 6.07-12.21 8.39-20.23v3.45c-2.29 7.2-5.27 14.5-7.61 17.41-.44.55-2.67 3.46-3.15 4.09-1.63 2.1-3.1 3.9-4.62 5.62-4.08 4.61-7.9 7.94-11.53 9.7-2.99 1.44-5.77 1.75-8.28.74-2.84-1.13-4.2-4.34-6.15-12.35a2097.48 2097.48 0 0 1-.84-3.46c-.8-3.2-1.47-5.45-2.28-7.46-5.14-12.8-8.04-21.55-8.04-26.19 0-4.37 3.84-6.06 12.16-7.07a160.9 160.9 0 0 1 2.65-.3c3.5-.39 5.15-.64 6.53-1.1 1.26-.42 2.1-.7 3.88-1.26 5.34-1.68 9.11-3 19.1-6.74 3.53-1.32 6.22-1.84 8.18-1.61zM0 292c10.13-11.31 18.13-23.2 23.07-35.39 3.3-8.14 6.09-16.12 10.81-30.55l1.59-4.84c6.53-19.94 10.11-29.82 14.77-39.56 6.07-12.72 12.55-21.18 20.27-25.54 6.66-3.76 10.2-7.86 12.22-13.15a46.6 46.6 0 0 0 1.86-6.58c1.23-5.2 2.05-7.59 3.93-10.36 2.45-3.62 6.27-6.53 12.1-8.96 15.78-6.58 16.73-7.04 18.05-9.01.65-.98.83-2.15.74-4.51-.03-.73-.23-3.82-.24-4A93.8 93.8 0 0 1 119 94c0-10.04.18-11.37 2.37-13.15.52-.42 1.13-.8 2.07-1.3.27-.14 2.18-1.12 2.84-1.48a68.4 68.4 0 0 0 9.12-5.87c2.06-1.54 2.64-2.14 8.01-7.93 3.78-4.09 6.21-6.36 8.96-8.12 3.64-2.33 7.2-3.12 10.9-2.11 4.4 1.2 10.81 2 18.78 2.46 6.9.4 12.9.5 21.95.5 4.87 0 8.97.47 15.4 1.57 7.77 1.33 9.3 1.54 12.38 1.54 4.05 0 7.43-.88 10.68-2.95 5.06-3.22 8.11-4.67 11.2-5.2 3.62-.64 4.77-.46 16.55 2.06 17.26 3.7 30.85 1.36 41.06-9.7 5.1-5.53 5.48-8.9 3.48-14.8-.83-2.42-1.03-3.1-1.17-4.3-.29-2.52.5-4.71 2.71-6.93 2.65-2.65 4.72-9.17 6.22-18.29h2.03c-1.56 9.71-3.77 16.65-6.83 19.7-1.79 1.8-2.36 3.39-2.14 5.28.11 1 .3 1.63 1.07 3.9 2.22 6.53 1.76 10.66-3.9 16.8-10.77 11.66-25.07 14.13-42.95 10.3-11.42-2.45-12.55-2.62-15.78-2.06-2.77.48-5.62 1.84-10.47 4.92a20.93 20.93 0 0 1-11.76 3.27c-3.25 0-4.81-.22-12.73-1.57C212.74 59.46 208.73 59 204 59c-9.1 0-15.11-.1-22.07-.5-8.09-.47-14.62-1.29-19.2-2.54-5.62-1.53-10.17 1.38-17.85 9.66-5.5 5.94-6.08 6.53-8.28 8.18a70.38 70.38 0 0 1-9.38 6.03c-.68.37-2.58 1.35-2.84 1.49-.84.44-1.35.76-1.75 1.08C121.16 83.6 121 84.8 121 94c0 1.85.06 3.54.17 5.44 0 .17.2 3.28.24 4.03.1 2.75-.13 4.29-1.08 5.71-1.67 2.5-2.27 2.8-18.95 9.74-5.48 2.29-8.99 4.96-11.2 8.24-1.71 2.51-2.47 4.73-3.64 9.7-.83 3.5-1.21 4.92-1.94 6.83-2.18 5.73-6.05 10.19-13.1 14.18-7.3 4.12-13.55 12.28-19.46 24.66-4.6 9.64-8.17 19.46-14.67 39.32l-1.58 4.84c-4.75 14.47-7.54 22.48-10.86 30.69-5.28 13.01-13.95 25.65-24.93 37.6v-2.97zm0 78v-.5l1-.01c6.32 0 7.47 5.2 4.6 13.36a60.36 60.36 0 0 1-5.6 11.3v-1.92a57.76 57.76 0 0 0 4.65-9.72c2.69-7.6 1.71-12.02-3.65-12.02-.34 0-.67 0-1 .02v-46.59a340.96 340.96 0 0 0 13.71-8.34c13.66-9.46 29.79-37.6 29.79-53.59 0-18.1 21.57-72.64 32.23-79.42 12.71-8.09 32.24-27.96 35.8-37.75 1.93-5.3 5.5-7.27 14.42-9.37 6.15-1.44 8.64-2.42 10.67-4.79 1.5-1.74 2.72-4.79 4.33-10.3.23-.78 1.9-6.68 2.43-8.46 3.62-12.08 7.3-18.49 13.47-20.39 2.5-.76 3.03-.98 9.74-3.7 7.49-3.03 11.97-4.43 17.12-4.92 6.75-.65 13.13.75 19.55 4.67 5.43 3.32 12.19 4.72 20.17 4.56 6.03-.12 12.2-1.07 19.83-2.8 1.82-.4 7.38-1.74 8.26-1.94 2.69-.6 4.34-.89 5.48-.89 4.97 0 8.93-.05 14.2-.27 7.9-.32 15.56-.92 22.75-1.88 8.5-1.14 15.9-2.73 21.88-4.82 18.9-6.62 32.64-18.3 33.67-27.59.29-2.56.4-2.96 2.79-11.11 2.33-7.95 3.21-12.93 2.72-18.23-.2-2.24-.69-4.38-1.48-6.42-1.5-3.92-2.63-9.4-3.43-16.18h.9c.77 6.47 1.89 11.72 3.47 15.82a24.93 24.93 0 0 1 1.54 6.69c.5 5.46-.4 10.54-2.77 18.6-2.36 8.06-2.47 8.47-2.74 10.95-1.09 9.75-15.1 21.68-34.33 28.41-6.06 2.12-13.52 3.72-22.09 4.87-7.22.96-14.92 1.57-22.83 1.89-5.3.21-9.27.27-14.25.27-1.04 0-2.64.27-5.26.87-.87.2-6.43 1.53-8.26 1.94-7.68 1.73-13.92 2.7-20.03 2.82-8.15.17-15.1-1.27-20.71-4.7-6.23-3.81-12.4-5.16-18.93-4.54-5.04.48-9.44 1.86-16.84 4.86-6.75 2.74-7.29 2.95-9.82 3.73-5.73 1.76-9.28 7.96-12.81 19.72-.53 1.77-2.2 7.66-2.43 8.46-1.66 5.65-2.91 8.78-4.53 10.67-2.22 2.58-4.84 3.62-12.01 5.3-7.8 1.83-11.13 3.66-12.9 8.54-3.65 10.04-23.32 30.06-36.2 38.25C65.94 190 44.5 244.2 44.5 262c0 16.34-16.3 44.78-30.22 54.41-2.14 1.48-8.24 5.12-14.28 8.68v-1.16 46.09zm0-173.7v-1.11c7.42-3.82 14.55-10.23 21.84-18.98 3.8-4.56 14.21-18.78 15.79-20.55 1.8-2.04 4.06-3.96 7.42-6.45 1.08-.8 4.92-3.57 5.49-3.99 9.36-6.85 14-11.96 15.98-19.36.8-2.98 1.54-6.78 2.46-12.3.23-1.44 2-12.46 2.56-15.79 2.87-16.77 5.73-26.79 10.07-32.1C92.46 52.43 101.5 38.13 101.5 33c0-2.54.34-3.35 6.05-15.71.68-1.49 1.25-2.74 1.77-3.93 2.5-5.75 3.9-10.04 4.14-13.36h1c-.23 3.48-1.66 7.87-4.23 13.76-.52 1.2-1.09 2.45-1.78 3.95-5.54 12.01-5.95 12.99-5.95 15.29 0 5.47-9.09 19.84-20.11 33.31-4.2 5.12-7.03 15.06-9.86 31.64-.57 3.33-2.33 14.33-2.57 15.78-.92 5.56-1.67 9.38-2.48 12.4-2.05 7.68-6.82 12.93-16.35 19.91l-5.49 3.98c-3.3 2.45-5.51 4.34-7.27 6.31-1.53 1.73-11.94 15.93-15.76 20.53-7.52 9.02-14.88 15.6-22.61 19.46zm0 361.83v-4.33c.48 2.36 1 4.35 1.6 6.15 2 6.03 4.6 8.26 8.19 6.59C28.76 557.69 43.5 542.4 43.5 527c0-16.2 6.37-31.99 17.1-46.3 1.88-2.5 3.66-4.4 5.53-6 .73-.62 1.45-1.18 2.3-1.8l2-1.43c3.68-2.68 5.32-5.28 7.08-12.59.75-3.07 1.38-5.02 4.2-13.26l.63-1.88c3.24-9.58 4.56-14.97 4.17-18.65-.48-4.43-3.8-5.23-11.3-1.64a81.12 81.12 0 0 1-9.15 3.7c-13.89 4.67-26.96 5.8-42.66 5.42l-1.95-.05-1.45-.02a39.8 39.8 0 0 0-15.05 2.96A21.81 21.81 0 0 0 0 438.37v-1.26a23.55 23.55 0 0 1 4.55-2.57 40.77 40.77 0 0 1 16.92-3.02l1.95.05c15.6.38 28.57-.75 42.32-5.37a80.12 80.12 0 0 0 9.04-3.65c8.04-3.84 12.16-2.85 12.72 2.43.42 3.89-.92 9.34-4.21 19.08l-.64 1.88c-2.8 8.2-3.43 10.15-4.16 13.18-1.82 7.52-3.59 10.34-7.47 13.16l-2 1.43c-.84.6-1.54 1.15-2.25 1.75a35.45 35.45 0 0 0-5.37 5.84c-10.61 14.15-16.9 29.74-16.9 45.7 0 15.88-15 31.45-34.29 40.45-4.3 2.01-7.39-.66-9.56-7.18-.23-.68-.44-1.39-.65-2.13zm0-62.16v-2.45l1.46 3.27c2.1 4.8 3.46 10.33 4.26 16.77.66 5.3.84 9.3 1.04 18.5.2 9.32.5 12.75 1.63 15.05 1.28 2.6 3.67 2.35 8.29-1.5 17.14-14.3 21.82-22.9 21.82-38.62 0-7.17 1.1-12.39 3.7-17.68 2.27-4.67 3.65-6.62 13.4-19.62a69.8 69.8 0 0 1 7.6-8.79 44.76 44.76 0 0 1 3.54-3.06c.38-.3.64-.52.89-.74a10.47 10.47 0 0 0 2.63-3.32 35.78 35.78 0 0 0 2.26-5.94l.37-1.2.36-1.15c.29-.91.48-1.55.66-2.16.45-1.53.74-2.68.91-3.66.38-2.2.12-3.49-.85-4.15-2.35-1.61-9.28-.24-23.8 4.94-9.54 3.4-16.12 4.17-27.85 4.26-7.71.06-10.43.4-13.25 2.12-3.48 2.12-5.84 6.4-7.58 14.26-.5 2.2-.99 4.19-1.49 5.98v-3.98l.51-2.22c1.8-8.1 4.28-12.6 8.04-14.9 3.04-1.85 5.86-2.2 13.77-2.26 11.61-.09 18.1-.84 27.51-4.2 14.93-5.32 21.95-6.71 24.7-4.83 1.38.94 1.71 2.6 1.28 5.15a33.69 33.69 0 0 1-.94 3.78l-.66 2.17-.36 1.15-.37 1.2a36.64 36.64 0 0 1-2.33 6.1c-.8 1.53-1.61 2.52-2.86 3.61l-.92.77-1.02.83c-.9.74-1.65 1.4-2.47 2.18a68.84 68.84 0 0 0-7.48 8.66c-9.7 12.93-11.07 14.87-13.31 19.46-2.52 5.15-3.59 10.22-3.59 17.24 0 16.04-4.82 24.91-22.18 39.38-5.04 4.2-8.18 4.55-9.83 1.18-1.22-2.5-1.52-5.94-1.73-15.47-.2-9.16-.38-13.15-1.03-18.4-.79-6.34-2.12-11.8-4.19-16.49L0 495.98zM379.27 0h1.04l1.5 5.26c3.28 11.56 4.89 19.33 5.26 27.8.49 11.01-1.52 21.26-6.63 31.17-7.8 15.13-20.47 26.5-36.22 34.1-12.38 5.96-26.12 9.17-36.22 9.17-6.84 0-17.24 1.38-37.27 4.62l-2.27.37c-24.5 3.99-31.65 5-37.46 5-3.49 0-4.08-.08-19.54-2.8-3.56-.64-6.32-1.1-9-1.5-20.23-2.96-31-1.2-31.96 7.86-.1.85-.18 1.72-.29 2.81l-.27 2.73c-1.1 10.9-2.02 15.73-4.31 19.96-2.9 5.34-7.77 7.95-15.63 7.95-10.2 0-12.92.6-15.5 3.17.52-.51-5.03 5.85-8.16 8.7-2.75 2.5-14.32 12.55-15.77 13.83a341.27 341.27 0 0 0-6.54 5.92c-6.97 6.49-11.81 11.76-14.6 16.15-5.92 9.3-10.48 18.04-11.69 24.08-1.66 8.3 3.67 9.54 19.02 1.21a626.23 626.23 0 0 1 44.54-21.9c3.5-1.56 14.04-6.2 15.68-6.95 5.05-2.25 8.3-3.8 10.78-5.15l1.95-1.07 2.18-1.18c1.76-.94 3.38-1.76 5-2.55 18.1-8.72 34.48-10.46 50.33-1.2 22.89 13.34 38.28 37.02 38.28 56.44 0 19.12-.73 25.13-5.18 33.2a45.32 45.32 0 0 1-4.94 7.12c-6.47 7.77-11.81 16.2-12.76 21.27-1.2 6.34 4.69 7.03 20.17-.05 13.31-6.08 22.4-14.95 28.5-26.32a80.51 80.51 0 0 0 6.1-15.13c.9-2.98 3.17-11.65 3.41-12.48a29.02 29.02 0 0 1 1.75-4.83c7.47-14.93 21.09-30.5 36.25-37.24 7.61-3.38 13-9.65 19.4-20.79.84-1.48 4.26-7.64 5.14-9.17 3.52-6.1 6.22-9.7 9.37-11.98 10.15-7.4 28.7-11.1 50.29-11.1 7.52 0 16.54-1.24 27.51-3.58a420.1 420.1 0 0 0 14.96-3.52c-1.3.33 15.54-3.98 19.42-4.89 14.15-3.33 41.07-5.01 64.11-5.01 17.36 0 27.82-9.23 38.53-38.67 6.62-18.21 6.62-26.37 2.69-34.35l-1.18-2.37A13.36 13.36 0 0 1 587.5 58c0-4.03 0-4.01 2.5-24.56.46-3.73.8-6.74 1.12-9.64.9-8.45 1.38-15.2 1.38-20.8 0-.94-.02-1.94-.04-3h1c.03 1.06.04 2.06.04 3 0 5.65-.48 12.43-1.39 20.9-.3 2.91-.66 5.93-1.11 9.66-2.5 20.45-2.5 20.47-2.5 24.44 0 1.97.45 3.57 1.45 5.68.24.51 1.16 2.35 1.17 2.36 4.06 8.24 4.06 16.68-2.65 35.13-10.84 29.8-21.63 39.33-39.47 39.33-22.96 0-49.83 1.68-63.89 4.99-3.86.9-20.69 5.2-19.4 4.88a421.05 421.05 0 0 1-14.99 3.53c-11.04 2.35-20.11 3.6-27.72 3.6-21.4 0-39.76 3.67-49.7 10.9-3 2.19-5.64 5.7-9.1 11.68-.87 1.52-4.29 7.68-5.14 9.17-6.49 11.3-12 17.71-19.86 21.2-14.9 6.63-28.38 22.03-35.75 36.77a28.17 28.17 0 0 0-1.69 4.67c-.23.8-2.5 9.49-3.4 12.5a81.48 81.48 0 0 1-6.19 15.3c-6.2 11.56-15.44 20.58-28.96 26.76-16.1 7.36-23 6.55-21.58-1.04 1-5.29 6.4-13.83 12.99-21.73a44.33 44.33 0 0 0 4.82-6.96c4.35-7.88 5.06-13.77 5.06-32.72 0-19.04-15.19-42.4-37.72-55.55-15.57-9.08-31.62-7.38-49.45 1.21a132.9 132.9 0 0 0-7.14 3.71l-1.95 1.07a158.83 158.83 0 0 1-10.85 5.19c-1.65.74-12.18 5.38-15.69 6.95a625.25 625.25 0 0 0-44.46 21.86c-15.95 8.66-22.37 7.16-20.48-2.29 1.24-6.2 5.83-15.02 11.82-24.42 2.85-4.48 7.74-9.8 14.77-16.34 1.98-1.85 4.12-3.79 6.56-5.94 1.46-1.29 13.02-11.33 15.75-13.82 3.09-2.8 8.6-9.14 8.14-8.67 2.82-2.82 5.75-3.46 16.2-3.46 7.5 0 12.04-2.43 14.75-7.42 2.2-4.07 3.11-8.84 4.2-19.59l.26-2.73.3-2.81c.56-5.42 4.47-8.5 11.23-9.6 5.44-.88 12.51-.51 21.86.86 2.7.4 5.47.86 9.04 1.49 15.33 2.7 15.96 2.8 19.36 2.8 5.73 0 12.9-1.03 37.3-5l2.27-.36c20.1-3.26 30.52-4.64 37.43-4.64 9.95 0 23.54-3.18 35.78-9.08 15.57-7.5 28.09-18.73 35.78-33.65 5.02-9.75 7-19.82 6.51-30.67-.37-8.37-1.96-16.08-5.23-27.57L379.27 0zm13.68 0h1.02c.78 3.9 1.92 8.7 3.51 14.88 3.63 14.05 3.06 27.03-.75 38.77a61 61 0 0 1-11.35 20.68 138.36 138.36 0 0 1-19.32 18.77c-11.32 9.02-23.36 15.49-35.95 18.39a258.63 258.63 0 0 1-22.57 4.07c-3.17.44-6.36.85-10.3 1.32l-9.39 1.12c-11.53 1.41-17.45 2.55-21.64 4.46-9.28 4.21-28.35 6.04-49.21 6.04-1.37 0-2.8-.12-4.3-.35-2.62-.41-5-1.03-9.14-2.29-7.34-2.21-9.63-2.75-12.63-2.56-3.9.23-6.63 2.29-8.47 6.89-1.86 4.66-2.42 7.53-3.34 14.98-1.1 8.98-2.87 12.12-9.97 14.3a40.12 40.12 0 0 0-6.8 2.66c-.63.33-1.16.64-1.76 1.02l-1.34.86c-1.9 1.14-3.86 1.49-9.25 1.49-3.2 0-8.83-.55-9.51-.39-1.22.28-.75-.14-7.14 6.24-1.5 1.5-3.49 3.18-6.32 5.37-1.52 1.18-7.16 5.43-7.94 6.03-4.96 3.78-8.33 6.6-11.06 9.38-4.88 4.98-6.85 9.15-5.56 12.7 1.34 3.67 4.07 4.42 8.9 2.82a55.72 55.72 0 0 0 7.77-3.48c1.5-.77 7.78-4.13 9.37-4.96a116.8 116.8 0 0 1 12.31-5.68 162.2 162.2 0 0 0 11.04-4.84c2.04-.97 10.74-5.16 13-6.22 4.41-2.1 8.1-3.78 11.65-5.29 17.14-7.3 29.32-9.9 37.67-6.65l5.43 2.1c2.3.88 4.17 1.62 6.02 2.38a150.9 150.9 0 0 1 13.07 6c18.34 9.63 30.35 22.13 34.79 39.87 6.96 27.85 3.6 45.53-8.08 62.4-3.97 5.75-3.52 9.2.06 8.97 4.14-.28 10.21-4.95 15.11-12.52 3.1-4.8 5.1-10.45 8.05-21.53l1.69-6.35c.66-2.47 1.24-4.52 1.83-6.5 4.93-16.56 11-27.28 21.56-34.76 7.15-5.06 23.73-15.5 25.48-16.75 6.74-4.81 10.53-9.44 14.34-18 7.74-17.44 21.09-24.34 44.47-24.34 9.36 0 17.91-1.13 29.53-3.49a624.86 624.86 0 0 0 6.2-1.28c2.4-.5 4.07-.84 5.66-1.13 4.03-.74 7.04-1.1 9.61-1.1 4.44 0 9.39-1 31.39-5.99l2.95-.66c16.34-3.67 25.64-5.35 31.66-5.35 1.54 0 2.4.01 6.4.1 7.8.15 12.27.13 17.33-.2 16.41-1.06 26.73-5.36 29.8-14.56a87.1 87.1 0 0 1 3.55-8.83c-.15.31 2.29-4.96 2.9-6.38 5.38-12.3 5.57-21.92-1.44-39.44a86.4 86.4 0 0 1-5.26-20.72c-1.61-11.98-1.38-23.14.1-40.35l.2-2.12h1l-.2 2.2c-1.48 17.15-1.7 28.24-.11 40.14a85.4 85.4 0 0 0 5.2 20.47c7.1 17.78 6.91 27.67 1.43 40.22-.62 1.43-3.06 6.72-2.91 6.4a86.17 86.17 0 0 0-3.52 8.73c-3.23 9.72-13.9 14.15-30.68 15.24-5.1.33-9.58.35-17.42.2-3.98-.09-4.84-.1-6.37-.1-5.91 0-15.18 1.67-31.44 5.32l-2.95.67c-22.16 5.02-27.05 6.01-31.61 6.01-2.5 0-5.45.36-9.43 1.09-1.58.29-3.25.62-5.64 1.11a4894.21 4894.21 0 0 0-6.2 1.29c-11.68 2.37-20.3 3.51-29.73 3.51-23.02 0-36 6.71-43.53 23.66-3.9 8.8-7.82 13.58-14.7 18.5-1.78 1.27-18.36 11.7-25.48 16.75-10.34 7.32-16.3 17.87-21.19 34.23-.58 1.96-1.15 4-1.82 6.47l-1.69 6.35c-2.98 11.18-5 16.9-8.17 21.81-5.05 7.81-11.37 12.68-15.89 12.98-4.7.31-5.3-4.23-.94-10.53 11.52-16.64 14.82-34.03 7.92-61.6-4.35-17.42-16.16-29.72-34.27-39.22-4-2.1-8.2-4-12.99-5.97-1.84-.75-3.7-1.49-6-2.38l-5.43-2.08c-8.03-3.12-20.02-.58-36.92 6.63-3.52 1.5-7.21 3.19-11.61 5.27l-13 6.22c-4.71 2.22-8.16 3.75-11.11 4.88a115.87 115.87 0 0 0-12.21 5.63c-1.58.83-7.86 4.18-9.37 4.96a56.55 56.55 0 0 1-7.9 3.54c-5.3 1.75-8.62.85-10.17-3.43-1.46-4.02.66-8.5 5.8-13.74 2.75-2.82 6.16-5.66 11.15-9.48.79-.6 6.43-4.85 7.94-6.02a66.96 66.96 0 0 0 6.23-5.28c6.74-6.74 6.1-6.16 7.61-6.51.87-.2 6.69.36 9.74.36 5.22 0 7.03-.32 8.74-1.35l1.31-.84c.62-.4 1.18-.72 1.84-1.07a41.07 41.07 0 0 1 6.96-2.72c6.64-2.04 8.22-4.84 9.28-13.47.93-7.53 1.5-10.47 3.4-15.24 1.99-4.95 5.04-7.26 9.34-7.51 3.17-.2 5.5.35 12.97 2.6a63.54 63.54 0 0 0 9.02 2.26c1.45.22 2.83.34 4.14.34 20.71 0 39.7-1.82 48.8-5.96 4.32-1.96 10.29-3.1 21.93-4.53l9.4-1.12c3.92-.48 7.11-.88 10.27-1.32 8.16-1.14 15.4-2.43 22.49-4.06 12.42-2.86 24.33-9.26 35.55-18.2a137.4 137.4 0 0 0 19.18-18.64 60.02 60.02 0 0 0 11.15-20.32c3.76-11.57 4.32-24.36.75-38.23A284.86 284.86 0 0 1 392.95 0zM506.7 0h1.26c-.5.66-.9 1.18-1.17 1.51-3.95 4.96-6.9 7.92-9.82 9.57A10.02 10.02 0 0 1 492 12.5c-2.38 0-4.24.67-6.71 2.21l-2.65 1.71c-4.38 2.8-8.01 4.08-13.64 4.08-5.6 0-9.99-1.26-16.08-4.05a202.63 202.63 0 0 1-2.3-1.06l-2.18-.98c-1.6-.7-2.92-1.17-4.17-1.48a13.42 13.42 0 0 0-3.27-.43c-2.3 0-4.3-.68-11-3.37l-1.56-.62c-5-1.97-8.1-2.82-10.52-2.66-2.93.2-4.42 2.03-4.42 6.15 0 20.76-5.21 50.42-12.15 57.35-7.58 7.59-26.55 23.7-34.06 29.06-13.16 9.4-31.17 20.2-44.11 25.06a106.87 106.87 0 0 1-13.32 4.03c-3.28.78-6.6 1.43-11.25 2.24-.53.1-8.8 1.5-11.5 1.99-4.86.87-9.3 1.74-14 2.76-20.62 4.48-25.07 5.01-38.11 5.01-2.49 0-2.9-.07-14.05-2-2.42-.42-4.31-.73-6.15-1-8.11-1.19-13.83-1.36-17.64-.2-4.54 1.4-5.93 4.65-3.7 10.52 2.02 5.28 4.84 8.61 8.84 10.74 3.26 1.74 6.75 2.6 13.82 3.71 9.42 1.48 10.94 1.75 15.5 2.92a78.2 78.2 0 0 1 18.62 7.37c8.3 4.58 14.58 11.5 19.98 20.89 2.73 4.73 9.46 19.33 10.54 21.19 3.4 5.85 6.26 6.63 10.89 2 4.95-4.94 10.35-8.37 21.13-14.06.47-.25 2.06-1.1 2.12-1.12 7.98-4.21 11.92-6.51 15.87-9.54 5.11-3.9 8.66-8.1 10.77-13.11 8.52-20.24 20.75-33.31 32.46-33.31l5.5.03c10.53.08 17.35.02 24.9-.31 13.66-.62 23.78-2.09 29.39-4.67 5.85-2.7 13.42-5.49 24.18-9.02 3.46-1.14 6.29-2.05 12.7-4.1 7.7-2.45 11.08-3.54 15.17-4.9a1059.43 1059.43 0 0 1 11.33-3.72c3.67-1.2 5.96-2 8.03-2.78a59.88 59.88 0 0 0 6.66-2.94c1.87-.98 3.76-2.1 5.86-3.5 3.48-2.33 6.15-3.13 12.04-4.13l1.15-.2c5.71-1.01 9-2.3 12.76-5.63 7.82-6.96 8.58-23.18 3.84-44.52-1.7-7.67-2.1-19.28-1.57-35.47A837.22 837.22 0 0 1 546.76 0h1l-.15 3.06c-.32 6.42-.53 11.02-.68 15.62-.51 16.1-.12 27.65 1.56 35.21 4.82 21.68 4.04 38.2-4.16 45.48-3.91 3.48-7.37 4.84-13.24 5.87l-1.16.2c-5.76.99-8.32 1.75-11.65 3.98a63.73 63.73 0 0 1-5.96 3.56 60.86 60.86 0 0 1-6.77 2.99c-2.09.79-4.39 1.58-8.07 2.79a5398.31 5398.31 0 0 1-11.32 3.71c-4.1 1.37-7.48 2.46-15.18 4.92-6.42 2.04-9.24 2.95-12.7 4.08-10.73 3.53-18.27 6.3-24.07 8.98-5.76 2.66-15.97 4.14-29.77 4.77-7.56.33-14.4.39-24.95.31l-5.49-.03c-11.19 0-23.16 12.79-31.54 32.7-2.19 5.19-5.84 9.52-11.08 13.52-4.02 3.07-7.99 5.39-16.01 9.62l-2.12 1.12c-10.7 5.65-16.04 9.04-20.9 13.9-5.14 5.14-8.75 4.15-12.45-2.22-1.12-1.92-7.85-16.5-10.54-21.2-5.33-9.24-11.48-16.02-19.6-20.5a77.2 77.2 0 0 0-18.4-7.28c-4.5-1.17-6.02-1.43-15.4-2.9-7.17-1.12-10.74-2-14.13-3.81-4.22-2.25-7.2-5.77-9.3-11.27-2.43-6.39-.78-10.26 4.34-11.83 4-1.22 9.82-1.05 18.08.17 1.84.27 3.74.58 6.17 1 11.02 1.9 11.48 1.98 13.88 1.98 12.96 0 17.35-.52 37.9-4.99 4.71-1.02 9.16-1.9 14.03-2.77 2.71-.48 10.98-1.9 11.5-1.98 4.64-.81 7.95-1.46 11.2-2.23 4.55-1.07 8.76-2.34 13.2-4 12.83-4.81 30.79-15.59 43.88-24.94 7.47-5.33 26.4-21.4 33.94-28.94C407.3 61.98 412.5 32.49 412.5 12c0-4.61 1.86-6.9 5.35-7.15 2.63-.18 5.8.7 10.96 2.73l1.56.62c6.53 2.62 8.53 3.3 10.63 3.3 1.14 0 2.3.16 3.5.46 1.32.33 2.68.82 4.34 1.53a90.97 90.97 0 0 1 3.34 1.52l1.15.54c5.98 2.73 10.23 3.95 15.67 3.95 5.41 0 8.87-1.21 13.1-3.92.2-.13 2.1-1.38 2.66-1.72 2.62-1.63 4.64-2.36 7.24-2.36 1.47 0 2.94-.43 4.47-1.3 2.78-1.56 5.67-4.45 9.54-9.31l.7-.89zM324.54 600h-2.03c.49-2.96.91-6.2 1.28-9.66.44-4.1.76-8.25.98-12.21.08-1.39.14-2.65-.35-7.29-.47-1.94-.93-4.14-1.36-6.54-2.01-11.26-2.66-22.9-1.14-33.78a60.76 60.76 0 0 1 5.18-17.95 70.78 70.78 0 0 1 12.6-18.22c3.38-3.6 5.53-5.5 11.83-10.79 4.5-3.78 6.35-5.56 7.52-7.5.64-1.07.95-2.06.95-3.06 0-1.75 0-1.74-.75-9.23-.36-3.7-.57-6.3-.68-8.96-.5-12.1 1.62-19.6 8.11-21.76 15.9-5.3 25.89-12.1 33.45-25.54C409.6 390.65 425.85 376 436 376c12.36 0 20-1.96 29.41-8.8 6.76-4.92 9.5-6.6 12.47-7.46 2.22-.64 3.8-.74 9.12-.74 1.86 0 3.53-.83 5.57-2.62 1.08-.96 5.11-5.12 5.6-5.6 6.04-5.85 11.98-8.78 20.83-8.78 2.45 0 4.54.04 7.32.12 7.51.23 8.87.17 11.27-.7 3.03-1.1 5.53-3.03 14.75-11.17 8-7.06 10.72-8.92 22.87-16.47 1.44-.9 2.59-1.63 3.69-2.37a69.45 69.45 0 0 0 9.46-7.5c4.12-3.88 8.02-7.85 11.64-11.9v2.98a201.58 201.58 0 0 1-10.27 10.38c-3.18 3-6.2 5.35-9.72 7.7-1.12.76-2.28 1.5-3.75 2.4-12.05 7.5-14.71 9.32-22.6 16.28-9.46 8.35-12.01 10.32-15.39 11.55-2.74 1-4.19 1.06-12.01.82-2.76-.08-4.83-.12-7.26-.12-8.27 0-13.75 2.7-19.43 8.22-.44.43-4.52 4.64-5.68 5.66-2.37 2.09-4.46 3.12-6.89 3.12-5.1 0-6.6.1-8.56.66-2.67.78-5.29 2.37-11.85 7.15-9.8 7.13-17.85 9.19-30.59 9.19-9.22 0-24.96 14.2-34.13 30.49-7.84 13.94-18.24 21.02-34.55 26.46-5.31 1.77-7.21 8.51-6.75 19.78.1 2.6.31 5.19.68 8.84.75 7.62.75 7.58.75 9.43 0 1.38-.42 2.73-1.24 4.09-1.33 2.2-3.26 4.07-7.94 8-6.25 5.24-8.36 7.12-11.67 10.63a68.8 68.8 0 0 0-12.25 17.71 58.8 58.8 0 0 0-5 17.36c-1.49 10.66-.85 22.09 1.13 33.15.43 2.37.88 4.53 1.33 6.44.16.66.3 1.25.6 4.06a249.3 249.3 0 0 1-1.17 16.12c-.37 3.37-.78 6.53-1.25 9.44zm-13.4 0h-1.05l.12-.28c3.07-7.16 4.29-11.83 4.29-18.72 0-3.57-.07-4.93-.76-15.65-.77-12.04-1-19.64-.55-28.3.58-11.5 2.4-22.1 5.81-32.16 1.3-3.8 2.8-7.5 4.55-11.1 3.46-7.14 6.83-12.39 10.42-16.6a59.02 59.02 0 0 1 4.35-4.56c.43-.4 3-2.8 3.67-3.45 5.72-5.6 7.51-11.52 7.51-29.18 0-18.84 2.9-23.77 15.82-28.24 1.09-.37 1.92-.67 2.77-.98a51.3 51.3 0 0 0 6.1-2.7c4.95-2.6 9.64-6.22 14.44-11.42 25.5-27.63 37.15-35.16 56.37-35.16 8.28 0 14.54-1.95 22-6.3 1.78-1.03 13.82-8.82 18.16-11.27 2.83-1.59 5.66-3.03 8.63-4.39 7.92-3.6 13.97-4.45 26.6-4.8 7.53-.2 10.7-.49 14.26-1.58 4.55-1.4 8.06-4 10.93-8.43 2.2-3.41 6.85-7.08 14.66-12.06 1.61-1.03 3.27-2.05 5.65-3.5 9.53-5.85 11.56-7.13 14.81-9.57 5.34-4 9.3-8.37 13.68-14.77a204.2 204.2 0 0 0 5.62-8.75v1.9c-1.97 3.17-3.4 5.38-4.8 7.42-4.42 6.48-8.46 10.92-13.9 15-3.29 2.46-5.32 3.75-14.89 9.61a375.06 375.06 0 0 0-5.63 3.5c-7.7 4.9-12.26 8.52-14.36 11.76-3 4.63-6.7 7.39-11.48 8.85-3.68 1.12-6.9 1.42-14.53 1.63-12.5.34-18.44 1.18-26.2 4.7a111.08 111.08 0 0 0-8.56 4.35c-4.3 2.43-16.34 10.22-18.15 11.27-7.6 4.43-14.03 6.43-22.5 6.43-18.87 0-30.3 7.4-55.63 34.84-4.88 5.28-9.67 8.97-14.7 11.62-2 1.05-4 1.92-6.23 2.75-.86.32-1.7.62-5.37 1.87-5.08 1.76-7.44 3.25-9.28 6.37-2.23 3.78-3.29 9.94-3.29 20.05 0 17.9-1.87 24.07-7.8 29.89-.69.67-3.27 3.06-3.69 3.46a58.04 58.04 0 0 0-4.28 4.49c-3.53 4.14-6.86 9.32-10.28 16.38a95.19 95.19 0 0 0-4.5 10.99c-3.38 9.97-5.18 20.48-5.76 31.9-.44 8.6-.22 16.17.55 28.17.69 10.76.76 12.12.76 15.72 0 6.35-1.02 10.87-4.35 19zm25.08 0h-1c-.04-4.73.06-9.39.28-15.02.26-6.41-.4-11.79-2.53-24.37l-.31-1.86c-2.12-12.55-2.76-19.35-1.97-26.47 1.03-9.25 4.75-16.68 12-22.67 22.04-18.2 29.81-30.18 29.81-44.61 0-2.6-.3-4.81-.98-8.17-.97-4.79-1.1-5.68-.97-7.57.2-2.56 1.27-4.7 3.56-6.72 2.67-2.35 7.05-4.6 13.72-7.01 9.72-3.5 15.52-9.18 24.3-21.57l1.78-2.5c4.48-6.33 7.1-9.63 10.43-12.78 4.31-4.07 8.98-6.77 14.54-8.17 13.3-3.32 20.37-5.47 25.34-7.64a49.5 49.5 0 0 0 5.28-2.7c1.1-.65 1.75-1.04 4.24-2.6 2.7-1.68 5.22-2.08 11.38-2.28 5.44-.18 7.9-.43 10.97-1.41a21.47 21.47 0 0 0 9.54-6.22c4.87-5.3 10.03-7.61 17.79-8.9 1.07-.18 1.88-.3 3.86-.58 6.9-.97 9.94-1.69 13.48-3.62 4.5-2.45 6.79-4.44 23.46-19.68l3.14-2.85c9.65-8.71 16.12-13.83 21.42-16.48 4.25-2.12 7.6-4.69 11.22-8.6v1.45c-3.42 3.57-6.69 6-10.78 8.05-5.18 2.59-11.61 7.67-21.2 16.32l-3.12 2.85c-16.8 15.35-19.05 17.3-23.66 19.82-3.68 2-6.8 2.75-13.82 3.73-1.97.28-2.78.4-3.84.57-7.56 1.26-12.52 3.48-17.21 8.6a22.47 22.47 0 0 1-9.97 6.5c-3.2 1-5.72 1.27-11.25 1.45-5.98.2-8.39.57-10.89 2.13a144 144 0 0 1-4.25 2.61 50.48 50.48 0 0 1-5.39 2.75c-5.04 2.2-12.15 4.37-25.5 7.7-9.74 2.44-15.26 7.65-24.4 20.56l-1.77 2.5c-8.9 12.54-14.82 18.34-24.78 21.93-6.57 2.36-10.85 4.57-13.4 6.82-2.1 1.86-3.05 3.74-3.22 6.04-.13 1.76 0 2.63.95 7.3.7 3.42 1 5.7 1 8.37 0 14.79-7.93 27-30.18 45.39-7.03 5.8-10.64 13-11.64 22-.78 7-.14 13.73 1.96 26.2l.32 1.85c2.15 12.65 2.8 18.07 2.54 24.58-.22 5.57-.32 10.2-.28 14.98zM95.9 600h-2.04c.68-3.82 1.14-8.8 1.61-15.98.2-3.11.27-4.06.39-5.6 1.3-17.54 4.04-27.14 11.5-33.2 4.65-3.77 7.22-8.92 8.67-16 .51-2.52.7-3.87 1.33-9.17.66-5.5 1.16-8.06 2.24-10.36 1.45-3.09 3.82-4.69 7.39-4.69 14.28 0 38.48 9.12 53.6 20.2 8.66 6.35 21.26 13.32 31.74 17.11 13.03 4.71 21.89 4.41 24.75-1.73 1.7-3.64 1.92-4.11 2.65-5.77 2.93-6.67 4.69-12.2 5.25-17.5.23-2.17.24-4.23.02-6.2-.32-2.75-1.42-4.55-4.08-7.35l-1.32-1.37a30.59 30.59 0 0 1-2.41-2.79 30.37 30.37 0 0 1-2.5-4.07l-1.13-2.14c-1.62-3.1-2.68-4.6-4.12-5.56-5.26-3.5-14.8-5.5-28.55-6.83a272.42 272.42 0 0 0-9.04-.71l-2.18-.17c-9.57-.73-15.12-1.56-19.06-3.2C156.57 471.07 136 450.5 136 440c0-5.34 1.74-9.53 5.47-14.13 1.98-2.44 11.12-11.71 12.79-13.54 4.52-4.97 10.16-9.54 17.68-14.66 2.8-1.9 14.78-9.6 17.49-11.49a50.54 50.54 0 0 0 6.34-5.43c1.53-1.5 6.96-7.13 7.12-7.3 7.18-7.3 12.7-11.56 19.74-14.38 3.36-1.34 8.13-2.79 17.45-5.38a9577.18 9577.18 0 0 1 11.78-3.28 602.6 602.6 0 0 0 12.67-3.7c20.4-6.24 34-12.08 40.79-18.44 8.74-8.2 11.78-13.84 15.73-26.02 2.02-6.22 3.09-9.04 5.07-12.72 9.54-17.71 28.71-39.37 43.5-45.45C383.77 238.25 389 232.34 389 226c0-2.89 2.73-8.4 6.83-13.73 4.76-6.2 10.65-11.36 16.75-14.18 12.5-5.77 33.5-10.09 47.42-10.09 5.32 0 9.83-1.5 16.42-4.89 9.2-4.71 10.1-5.11 13.58-5.11 10.42 0 32.06-2.55 45.76-5.97l3.88-.98 3.47-.89c2.6-.66 4.33-1.08 5.93-1.43 3.9-.86 6.76-1.23 9.58-1.17 2.74.06 5.47.52 8.67 1.48 4.56 1.37 13.71-.9 22.87-5.68a68.07 68.07 0 0 0 9.84-6.2v2.4c-11.09 8.14-25.76 13.66-33.29 11.4a29.72 29.72 0 0 0-8.13-1.4c-2.63-.05-5.36.3-9.11 1.12a238 238 0 0 0-9.33 2.3l-3.9.99C522.38 177.43 500.58 180 490 180c-2.99 0-3.91.4-12.67 4.89-6.85 3.51-11.61 5.11-17.33 5.11-13.65 0-34.35 4.26-46.58 9.9-5.78 2.67-11.42 7.62-16 13.58-3.85 5.02-6.42 10.2-6.42 12.52 0 7.27-5.8 13.82-20.62 19.92-14.27 5.88-33.16 27.21-42.5 44.55-1.9 3.55-2.95 6.28-4.93 12.4-4.05 12.47-7.23 18.39-16.27 26.86-7.08 6.64-20.87 12.57-41.57 18.89a604.52 604.52 0 0 1-12.7 3.71 1495.1 1495.1 0 0 1-11.8 3.28c-9.24 2.58-13.97 4.01-17.24 5.32-6.73 2.69-12.05 6.8-19.05 13.92-.15.15-5.6 5.8-7.15 7.32a52.4 52.4 0 0 1-6.6 5.65c-2.74 1.92-14.75 9.63-17.5 11.5-7.4 5.04-12.94 9.52-17.33 14.35-1.72 1.9-10.8 11.11-12.71 13.46-3.47 4.26-5.03 8.03-5.03 12.87 0 9.5 20 29.5 33.38 35.08 3.67 1.53 9.1 2.34 18.45 3.05a586.23 586.23 0 0 0 4.34.32c3.24.23 5.07.37 6.93.55 14.08 1.37 23.82 3.4 29.45 7.17 1.82 1.2 3.02 2.91 4.8 6.29l1.11 2.13a28.55 28.55 0 0 0 2.34 3.81c.62.83 1.3 1.6 2.26 2.61.23.24 1.1 1.16 1.32 1.37 2.93 3.09 4.24 5.23 4.61 8.5.24 2.12.23 4.33-.01 6.64-.59 5.55-2.4 11.25-5.41 18.1-.74 1.67-.96 2.15-2.66 5.8-3.49 7.47-13.33 7.8-27.25 2.77-10.67-3.86-23.43-10.92-32.25-17.38C164.62 515.96 140.82 507 127 507c-5 0-6.4 3.02-7.64 13.29a99.03 99.03 0 0 1-1.36 9.33c-1.53 7.5-4.3 13.04-9.37 17.16-6.87 5.58-9.5 14.78-10.77 31.8-.11 1.52-.18 2.47-.38 5.57-.46 7.01-.91 11.99-1.57 15.85zm8.05 0h-1.02c.29-1.41.58-2.94.9-4.59l1.05-5.62c2.5-13.3 4.2-19.92 6.68-24.05 1.7-2.84 3.68-5.5 8.05-11.03 8.21-10.36 10.88-14.55 10.88-18.71l-.02-1.69c-.02-1.78-.02-2.7.02-3.77.21-5.05 1.47-8.2 4.64-9.4 3.92-1.5 10.39.44 20.12 6.43 9.56 5.88 17.53 10.7 25.91 15.66 1.31.78 14.27 8.41 17.67 10.45a714.21 714.21 0 0 1 6.42 3.9c13.82 8.5 38.94 5.05 46.3-7.83 3.6-6.28 4.54-8.52 7.78-17.32a82.3 82.3 0 0 1 1.18-3.07 42.27 42.27 0 0 1 4.06-7.64c9.33-13.98 14.92-26.1 14.92-36.72 0-3.66.75-6.62 3.36-14.85.52-1.64.83-2.66 1.15-3.73 3.64-12.23 3.04-19.12-4.29-24a23.1 23.1 0 0 0-9.98-3.78c-7.2-.93-14.49 1.17-23.91 5.88-1.55.78-6.64 3.44-7.6 3.93a62.6 62.6 0 0 0-4.14 2.3l-4.4 2.66c-11.62 6.92-20.4 9.18-32.81 6.08-3.32-.84-6.24-1.4-13.1-2.64-13.25-2.39-18.7-3.75-23.33-6.46-6.23-3.67-7.46-9.02-2.88-16.65A93.1 93.1 0 0 1 172 415.42a157 157 0 0 1 8.32-7.66c-.07.05 6.16-5.3 7.82-6.77a85.12 85.12 0 0 0 6.5-6.33c7.7-8.46 12.78-13.36 20.08-18.57 9.94-7.1 21.4-12.36 35.18-15.58 37.03-8.64 51-12.7 58.83-17.93 8.6-5.73 21.3-24.77 36.84-54.81 5.22-10.1 12.27-18.4 21.13-25.71 5.13-4.24 9.56-7.25 17.55-12.23 7.42-4.62 9.62-6.14 11.38-8.16a21.15 21.15 0 0 0 2.95-4.87c.61-1.3 2.87-6.47 3-6.77 1.36-3 2.56-5.4 3.95-7.73 6.53-10.97 16.03-18 31.4-20.8 12.73-2.3 19.85-2.7 29.68-2.3 3.25.13 4.13.16 5.6.14 5.15-.07 9.71-1.04 16.61-3.8 20.74-8.3 38.75-12.04 59.19-12.04 3.05 0 6.03.15 10.48.48l2.09.16c12.45.96 18.08.96 25.34-.63a49.65 49.65 0 0 0 14.09-5.45v1.15a50.52 50.52 0 0 1-13.88 5.28c-7.38 1.61-13.08 1.61-25.63.65l-2.08-.16c-4.43-.33-7.39-.48-10.41-.48-20.3 0-38.2 3.72-58.81 11.96-7.01 2.8-11.7 3.8-16.97 3.88-1.5.02-2.39-.01-5.66-.14-9.76-.4-16.8-.01-29.47 2.3-15.06 2.73-24.32 9.58-30.71 20.31a72.8 72.8 0 0 0-3.9 7.63c-.12.28-2.39 5.47-3.01 6.79a22 22 0 0 1-3.1 5.1c-1.86 2.13-4.07 3.66-11.6 8.35-7.95 4.96-12.35 7.95-17.44 12.15-8.76 7.23-15.73 15.43-20.89 25.4-15.61 30.2-28.36 49.32-37.16 55.19-7.98 5.32-21.97 9.39-59.17 18.07-13.65 3.18-24.98 8.39-34.82 15.42-7.22 5.16-12.27 10.01-19.92 18.43a86.07 86.07 0 0 1-6.57 6.4c-1.67 1.48-7.91 6.83-7.84 6.77-3.27 2.84-5.8 5.16-8.26 7.62a92.1 92.1 0 0 0-14.27 18.13c-4.3 7.16-3.22 11.89 2.53 15.26 4.47 2.63 9.88 3.99 23.24 6.39a185.7 185.7 0 0 1 12.92 2.6c12.11 3.03 20.64.84 32.06-5.96l4.4-2.65c1.66-1 2.96-1.73 4.2-2.35.95-.48 6.04-3.14 7.6-3.92 9.59-4.8 17.04-6.94 24.49-5.98a24.1 24.1 0 0 1 10.4 3.93c7.82 5.21 8.45 12.52 4.7 25.13-.32 1.07-.64 2.1-1.16 3.74-2.57 8.12-3.31 11.04-3.31 14.55 0 10.88-5.66 23.14-15.08 37.28a41.28 41.28 0 0 0-3.97 7.46c-.37.9-.73 1.82-1.18 3.04-3.25 8.85-4.21 11.13-7.84 17.47-7.67 13.42-33.43 16.95-47.7 8.18a578.4 578.4 0 0 0-6.4-3.89c-3.4-2.04-16.36-9.67-17.67-10.45-8.38-4.97-16.36-9.78-25.92-15.66-9.5-5.85-15.7-7.7-19.24-6.36-2.68 1.02-3.8 3.82-4 8.51a61.12 61.12 0 0 0-.02 3.72l.02 1.7c0 4.5-2.69 8.73-11.52 19.87-3.92 4.95-5.87 7.59-7.55 10.39-2.39 3.97-4.08 10.56-6.56 23.72l-1.05 5.62-.86 4.4zm10.5 0h-1c.03-.34.04-.68.04-1 0-12.39 8.48-33.57 19.16-43.37a26.18 26.18 0 0 0 3.67-4.17 35.8 35.8 0 0 0 2.88-4.9c.36-.72 1.75-3.66 2.1-4.36 3.22-6.29 6.84-6.54 16.97.39 1.34.9 6.07 4.16 6.4 4.38 2.62 1.8 4.67 3.2 6.7 4.56 5.03 3.39 9.37 6.2 13.51 8.7 14.33 8.67 25.49 13.27 34.11 13.27 16.86 0 32.71-5.95 39.6-14.8 1.59-2.04 3.2-5.17 5.06-9.63.8-1.92 1.64-4.06 2.67-6.8l2.74-7.33c4.66-12.44 7.76-19.06 11.56-23.27 7.9-8.79 14.87-36 14.87-52.67 0-1.9.17-3.11 1.02-8.27.37-2.2.58-3.6.74-5.07.63-5.51.21-9.46-1.68-12.39-4.6-7.1-19.7-9.23-38.46-4.78a100.57 100.57 0 0 0-18.94 6.3c-5.17 2.37-17.11 9.74-16.5 9.4-6.72 3.64-12.97 4.15-24.8 1.3-29.55-7.14-30.43-8.62-15.26-26.81 17.44-20.93 47.12-46.18 56.38-46.18 9.92 0 53.84-11.98 65.78-17.95 9.46-4.73 24.32-21.18 36.82-37.85.71-.95 13.5-21.6 19.2-29.6 9.35-13.13 18.22-22.55 26.95-27.53 7.29-4.17 13.16-10.28 18.8-18.73 1.93-2.9 10.52-17.65 12.73-20.41 1.54-1.93 3-3.21 4.52-3.89 14.07-6.25 24.22-9.04 39.2-9.04h29c4.05 0 7.36-.4 22.93-2.5l4.3-.57c9.92-1.3 16.57-1.93 21.77-1.93 1.66 0 2.95.01 6.03.04 18.61.19 28.55-.48 44.86-4.03 3.1-.67 6.13-1.78 9.11-3.31v1.12a37.96 37.96 0 0 1-8.9 3.17c-16.4 3.56-26.4 4.24-45.08 4.05-3.08-.03-4.36-.04-6.02-.04-5.15 0-11.76.63-21.64 1.92l-4.3.58c-15.64 2.11-18.94 2.5-23.06 2.5h-29c-14.81 0-24.84 2.75-38.8 8.96-1.34.6-2.69 1.78-4.14 3.6-2.16 2.68-10.72 17.39-12.68 20.33-5.72 8.57-11.7 14.8-19.13 19.04-8.57 4.9-17.36 14.23-26.63 27.24-5.68 7.97-18.47 28.64-19.22 29.63-12.6 16.8-27.52 33.32-37.18 38.15-12.06 6.03-56.14 18.05-66.22 18.05-8.82 0-38.39 25.15-55.62 45.82-14.6 17.52-14.19 18.21 14.74 25.2 11.6 2.8 17.6 2.3 24.09-1.2-.67.35 11.31-7.03 16.56-9.44 5.41-2.48 11.6-4.59 19.11-6.37 19.13-4.53 34.65-2.35 39.54 5.22 2.05 3.17 2.48 7.32 1.84 13.04a96.34 96.34 0 0 1-.75 5.13c-.84 5.08-1.01 6.29-1.01 8.1 0 16.9-7.03 44.33-15.13 53.33-3.68 4.09-6.76 10.65-11.37 22.96-.35.93-2.2 5.94-2.73 7.33-1.04 2.76-1.88 4.9-2.68 6.84-1.9 4.53-3.55 7.73-5.2 9.85-7.1 9.13-23.25 15.19-40.39 15.19-8.86 0-20.15-4.65-34.63-13.42-4.15-2.51-8.5-5.32-13.55-8.72a861.54 861.54 0 0 1-6.71-4.56l-6.4-4.39c-9.68-6.63-12.61-6.42-15.5-.75-.35.68-1.74 3.62-2.1 4.35a36.77 36.77 0 0 1-2.96 5.03c-1.12 1.57-2.37 3-3.81 4.33-10.47 9.6-18.84 30.51-18.84 42.63l-.03 1zm-29.65 0h-1.1c1.17-2.52 1.79-5.2 1.79-8 0-20 4.83-42.04 12.15-49.35 5.17-5.18 7.77-8.38 9.9-12.74 2.64-5.41 3.95-12 3.95-20.91 0-6.82 1.14-11.59 3.37-15.07 1.74-2.7 3.6-4.21 8.91-7.52a31.64 31.64 0 0 0 3.9-2.79c4.61-3.96 6.58-6.2 7.72-9.41 1.43-4.02.93-9.04-1.86-16.02a68.98 68.98 0 0 0-3.99-8.07l-.93-1.7a75.47 75.47 0 0 1-2.64-5c-5.16-10.71-3.77-18.9 7.68-29.78a204 204 0 0 1 26.81-21.55c3.96-2.69 16.8-10.8 19.24-12.5 1.99-1.4 4.33-3.3 7.77-6.3-.02 0 7.23-6.39 9.47-8.3 4.97-4.26 9.09-7.5 13.05-10.15 4.72-3.15 8.97-5.28 12.87-6.32 12.78-3.41 15.6-4.18 21.77-5.97 12.55-3.64 21.96-6.9 28.14-10a45.47 45.47 0 0 1 7.47-2.79c8.66-2.66 12.02-4.1 16.97-8.1 6.78-5.46 13.07-14.25 19.33-27.87 15.97-34.77 19.08-39.39 32.15-49.19 3.14-2.36 6.37-4.1 11.43-6.4l2.33-1.04c11.93-5.35 16.87-8.93 21.1-17.38 1.88-3.77 2.48-6.29 3.37-12.27.78-5.19 1.48-7.56 3.53-10.25 2.57-3.4 7.03-6.27 14.36-9.01 3.37-1.26 7.36-2.5 12.05-3.73 16.33-4.3 25.28-5.36 39.6-5.81 6.9-.22 9.5-.56 12.66-2 1.19-.54 2.36-1.23 3.58-2.11 3.7-2.7 8.14-4.54 13.24-5.67 5.71-1.27 10.69-1.54 18.7-1.45l2.35.02c2.82 0 6.8-1 19.7-4.69 10.83-3.08 15.95-4.31 19.3-4.31.82 0 1.9.13 3.55.41l5.01.9c9.82 1.68 17.44 1.89 25.15-.21 7.98-2.18 14.8-6.77 20.29-14.24V147c-5.47 7.04-12.21 11.42-20.03 13.55-7.88 2.15-15.63 1.94-25.58.23l-5-.9c-1.6-.26-2.64-.39-3.39-.39-3.2 0-8.32 1.22-19.74 4.48-12.35 3.53-16.3 4.52-19.26 4.52l-2.36-.02c-7.94-.1-12.85.17-18.47 1.42-4.97 1.11-9.3 2.9-12.88 5.5a21.4 21.4 0 0 1-3.75 2.22c-3.32 1.5-6 1.87-13.04 2.09-14.25.44-23.13 1.5-39.37 5.77a125.56 125.56 0 0 0-11.95 3.7c-7.17 2.7-11.49 5.46-13.93 8.68-1.9 2.52-2.58 4.76-3.33 9.8-.9 6.08-1.53 8.68-3.47 12.56a30.6 30.6 0 0 1-9.66 11.45c-3.12 2.26-5.95 3.73-11.93 6.4l-2.31 1.04c-5.01 2.27-8.18 3.99-11.25 6.29-12.9 9.68-15.93 14.17-31.85 48.8-6.31 13.76-12.7 22.68-19.6 28.25-5.08 4.1-8.53 5.57-17.3 8.27a44.64 44.64 0 0 0-7.33 2.73c-6.24 3.12-15.7 6.4-28.3 10.06a867.4 867.4 0 0 1-21.8 5.97c-3.77 1.01-7.93 3.1-12.56 6.19a137.35 137.35 0 0 0-12.95 10.07c-2.24 1.92-9.48 8.3-9.48 8.3a98.2 98.2 0 0 1-7.84 6.37c-2.46 1.72-15.32 9.83-19.26 12.5a203 203 0 0 0-26.69 21.45c-11.13 10.58-12.43 18.3-7.47 28.63a74.52 74.52 0 0 0 2.62 4.95l.94 1.7a69.84 69.84 0 0 1 4.03 8.17c2.88 7.2 3.4 12.46 1.89 16.73-1.22 3.43-3.28 5.77-8.02 9.84-1.14.97-2.32 1.8-5.3 3.67-3.92 2.45-5.69 3.89-7.31 6.42-2.13 3.3-3.22 7.89-3.22 14.53 0 9.05-1.34 15.79-4.05 21.34-2.19 4.49-4.85 7.77-10.1 13.01-7.07 7.07-11.85 28.9-11.85 48.65 0 2.8-.58 5.48-1.7 8zm282.54 0h-1.01l-1.1-5.8c-3.08-16.26-4.05-26.2-2.74-37.26.7-5.8.77-9.68.55-15.3-.18-4.45-.17-5.68.19-7.63.78-4.3 3.44-8.53 10.39-16.34 9.07-10.2 12.26-15.41 19.8-30.15 1.35-2.64 2.33-4.47 3.38-6.3.9-1.58 1.82-3.06 2.77-4.5 3.14-4.7 7.03-8.42 16.84-16.81 11.22-9.6 15.5-13.86 18.13-19.13.7-1.4 1.3-2.8 1.93-4.4a206 206 0 0 0 1.49-4.05c3.63-9.94 8.01-13.93 22.9-17.81 4.99-1.3 20.55-5.13 21.38-5.34 16.19-4.1 25.33-7.36 33.48-12.6 5.86-3.77 5.84-3.76 27.66-16.53l2.6-1.52c10.23-6 17.1-10.2 22.73-13.95a149.3 149.3 0 0 0 8.8-6.3 723.7 723.7 0 0 0 6.37-5.08A87.74 87.74 0 0 1 600 342.95v1.12a85.76 85.76 0 0 0-15.49 9.9c.18-.14-4.76 3.84-6.38 5.1a150.3 150.3 0 0 1-8.85 6.35c-5.65 3.76-12.53 7.96-22.78 13.97l-2.6 1.53c-21.8 12.75-21.78 12.74-27.63 16.5-8.27 5.32-17.49 8.61-33.78 12.73-.83.21-16.39 4.04-21.36 5.33-8.03 2.1-13.15 4.5-16.45 7.5-2.66 2.42-4 4.86-5.77 9.7l-1.5 4.07a51.12 51.12 0 0 1-1.96 4.47c-2.72 5.45-7.04 9.75-18.38 19.45-9.73 8.32-13.6 12.02-16.65 16.6a77.18 77.18 0 0 0-2.74 4.45c-1.05 1.81-2.01 3.63-3.35 6.25-7.58 14.81-10.82 20.08-19.96 30.36-6.83 7.7-9.4 11.78-10.15 15.86-.34 1.85-.34 3.04-.17 7.4.22 5.68.14 9.6-.55 15.47-1.3 10.92-.34 20.79 2.73 36.95l1.12 5.99zm-76.59 0h-2.1l1.39-4.3c1.04-3.3 1.93-6.78 2.68-10.4 2.65-12.73 3.27-23.63 3.27-41.3 0-5.71-1.86-9.75-4.13-9.75-2.94 0-6.96 5.61-10.93 17.08C271.14 579.68 258.3 593 238 593c-22.42 0-29.26-1.35-48.42-10.09a87.69 87.69 0 0 1-9.42-5.04c-2.95-1.8-12.78-8.57-14.84-9.72-4.2-2.36-7-2.71-9.72-.99-.63.4-1.26.91-1.9 1.55a57.69 57.69 0 0 1-4.31 3.86 147.88 147.88 0 0 1-3.06 2.44l-1 .8C137.01 582.43 134 587.18 134 597c0 1.02-.02 2.01-.07 3h-2c.05-.99.07-1.98.07-3 0-10.52 3.33-15.78 12.09-22.76a265.61 265.61 0 0 1 2-1.6c.83-.64 1.43-1.13 2.03-1.61a55.76 55.76 0 0 0 4.17-3.74c.74-.73 1.48-1.34 2.24-1.82 3.47-2.2 7-1.75 11.77.93 2.15 1.21 12.03 8 14.9 9.76a85.7 85.7 0 0 0 9.22 4.93C209.29 589.7 215.85 591 238 591c19.25 0 31.49-12.7 41.06-40.33 4.24-12.25 8.66-18.42 12.81-18.42 3.8 0 6.13 5.06 6.13 11.75 0 17.8-.63 28.8-3.3 41.7-.77 3.7-1.68 7.23-2.75 10.6-.4 1.3-.8 2.53-1.19 3.7zm-149.25 0l.5-.94a160.1 160.1 0 0 0 6.53-13.26c2.73-6.29 5.78-9.64 9.24-10.52 3.74-.95 7.15.74 12.56 5.13 5.43 4.4 6.07 4.86 7.73 5.1 1.6.22 4.28 1.14 8.86 2.95 1.3.5 10.78 4.35 13.85 5.55 3.07 1.2 5.85 2.25 8.49 3.18 3.1 1.1 5.98 2.04 8.65 2.81h-3.45c-1.76-.56-3.6-1.18-5.54-1.87a281.2 281.2 0 0 1-8.51-3.19c-3.08-1.2-12.57-5.04-13.86-5.55-4.5-1.78-7.15-2.68-8.63-2.9-1.94-.27-2.53-.7-8.22-5.3-5.17-4.2-8.36-5.78-11.69-4.94-3.1.78-5.94 3.92-8.56 9.95a161 161 0 0 1-6.82 13.8h-1.13zm112.89 0a30.34 30.34 0 0 0 11.27-6.27c1.55-1.36 3.32-3.46 5.34-6.29 1.05-1.46 2.15-3.1 3.41-5.04a349.73 349.73 0 0 0 2.5-3.9l.47-.75.93-1.47a89.17 89.17 0 0 1 3.25-4.86c1.05-1.43 1.82-2.23 2.44-2.46 1.02-.37 1.49.48 1.49 2.04l.01 2.11c.05 6.91-.08 11.32-.7 16.33a48.4 48.4 0 0 1-2.38 10.56h-1.07a46.47 46.47 0 0 0 2.45-10.68c.62-4.96.75-9.33.7-16.2l-.01-2.12c0-.97-.08-1.12-.15-1.1-.36.14-1.05.85-1.97 2.1a88.44 88.44 0 0 0-3.22 4.82l-.92 1.46-.48.75a1268.1 1268.1 0 0 1-2.5 3.92c-1.26 1.95-2.38 3.6-3.44 5.08-2.06 2.88-3.87 5.04-5.5 6.45a30.87 30.87 0 0 1-8.94 5.52h-2.98zm-183.72 0H69.3c3.37-3.43 5.19-8.33 5.19-15 0-18.6-.04-17.35 1.02-20.77.6-1.93 1.5-3.74 3.27-6.63.42-.7 4.92-7.8 6.78-10.86 3.04-4.97 11.04-16.5 12.21-18.56 3.48-6.08 4.72-12.06 4.72-24.18 0-7.85 2.5-14.2 8.1-23.44l2.84-4.63a72.67 72.67 0 0 0 2.49-4.4c1.62-3.15 2.48-5.78 2.62-8.28.2-3.78-1.3-7.29-4.9-10.9-5.13-5.12-8.6-5.43-11.2-1.85-2.12 2.92-3.48 7.74-5.06 16.47-.2 1.03-.82 4.6-.82 4.57-.83 4.67-1.4 7.33-2.1 9.6-1.35 4.42-3.7 7.61-8.36 12.26l-3.26 3.2c-6.38 6.39-9.68 11.51-11.36 19.5l-1.16 5.52c-.87 4.1-1.56 7.04-2.33 9.94-3.67 13.74-9.65 25.97-22.59 44.72-7.68 11.14-11.05 18.87-10.92 23.72h-1c-.12-5.16 3.35-13.05 11.1-24.28 12.87-18.67 18.8-30.8 22.44-44.42.77-2.88 1.45-5.8 2.32-9.89l1.16-5.51c1.73-8.22 5.13-13.5 11.64-20 .63-.64 2.84-2.8 3.25-3.21 4.57-4.54 6.82-7.62 8.12-11.84a81.58 81.58 0 0 0 2.07-9.48l.81-4.57c1.62-8.9 3-13.8 5.24-16.89 3-4.15 7.2-3.78 12.71 1.74 3.8 3.8 5.42 7.58 5.2 11.66-.15 2.66-1.05 5.41-2.73 8.68a73.6 73.6 0 0 1-2.52 4.46l-2.84 4.63c-5.52 9.1-7.96 15.3-7.96 22.92 0 12.28-1.28 18.43-4.85 24.68-1.2 2.1-9.21 13.65-12.22 18.58-1.87 3.06-6.37 10.18-6.78 10.86-1.73 2.82-2.6 4.57-3.17 6.4-1.02 3.28-.98 2.1-.98 20.48 0 6.52-1.7 11.44-4.82 15zM310.09 0h1.06c-.37.9-.77 1.83-1.2 2.82-3.9 9.06-5.45 15.15-5.45 25.18 0 7.64-2.1 11.6-6.64 13.05-3.46 1.1-5.72.98-17.57-.43-11.55-1.36-19.17-1.58-28.16-.14-6.24 2.49-25.91 7.02-32.13 7.02-11.15 0-36.76-2.88-54.12-7.01a22.08 22.08 0 0 0-16.95 2.48c-4.05 2.33-7.09 5.03-13.9 11.97-6.28 6.39-9.53 9.23-13.8 11.5-7.09 3.79-11.22 7.65-13.4 12.27-1.82 3.85-2.33 7.84-2.33 15.29 0 4.4-2.65 6.69-9.45 9.74.1-.05-2.97 1.31-3.84 1.71-8.78 4.06-12.71 8.29-12.71 16.55 0 12.52-4.86 19.22-17.34 27.96l-4.56 3.14c-1.9 1.3-3.3 2.3-4.67 3.3-.92.68-1.79 1.34-2.62 2-7.16 5.62-11 14.54-15.56 33.28-.63 2.57-3.3 14-4.07 17.14a350.44 350.44 0 0 1-5.2 19.33c-1.37 4.5-4.5 15.07-4.96 16.53-1.05 3.4-1.64 4.94-2.46 6.32-.82 1.4-6.85 9.08-12.64 18.27L0 277.98v-1.9l4.58-7.35a270.8 270.8 0 0 1 12.61-18.23c-.3.5 1.35-2.8 2.38-6.12.45-1.44 3.58-12.01 4.95-16.53 1.83-6.03 3.44-12.09 5.19-19.27.76-3.13 3.44-14.56 4.06-17.14 4.62-18.95 8.52-28.02 15.92-33.83.84-.67 1.72-1.33 2.65-2.01 1.38-1.02 2.8-2.01 4.7-3.32l4.54-3.14C73.83 140.57 78.5 134.13 78.5 122c0-8.74 4.2-13.26 13.29-17.45.88-.41 3.96-1.77 3.85-1.73 6.46-2.9 8.86-4.97 8.86-8.82 0-7.6.53-11.7 2.42-15.71 2.29-4.84 6.57-8.85 13.84-12.73 4.15-2.21 7.35-5 14.15-11.93 6.28-6.4 9.36-9.13 13.52-11.53a23.07 23.07 0 0 1 17.69-2.59c17.27 4.12 42.8 6.99 53.88 6.99 6.1 0 25.73-4.53 31.92-7 9.12-1.46 16.83-1.25 28.49.13 11.63 1.38 13.9 1.5 17.15.47 4.06-1.3 5.94-4.85 5.94-12.1 0-10.1 1.56-16.3 6.6-28zm25.12 0h1c.05 5.62.26 11.48.65 19.4.47 9.7.64 14.57.64 21.6 0 9.81-4.68 17.46-13.1 23.16-6.53 4.43-14.94 7.46-24.33 9.33-3.74.54-9.42.56-22.68.23-6.74-.17-9.35-.22-12.39-.22-2.77 0-4.97.43-7.63 1.36-.88.3-4.55 1.74-5.58 2.11-6.55 2.35-13.59 3.53-24.79 3.53-8.1 0-13.58-1.38-22.46-4.9l-3.18-1.25c-12.55-4.87-21.27-5.15-37.18 1.12-11.15 4.39-18.13 9.2-22.28 14.81-3.15 4.26-4.33 7.8-5.94 15.8-1.22 6.09-1.93 8.74-3.5 12.13-1.65 3.53-3.97 5.81-7.07 7.22-2.33 1.07-4.35 1.5-9.32 2.19-9.04 1.27-12.77 3.09-15.61 9.58-3.71 8.48-7.72 13.87-14.22 19.76-2.4 2.18-13.14 11.02-15.91 13.42-8.2 7.1-13.85 17.37-18.7 31.97a258.81 258.81 0 0 0-3.27 10.7c-.01.05-2.26 7.97-2.88 10.1-8.49 28.85-17.88 52.95-26.13 61.2-2.8 2.8-5.06 5.64-10.4 12.96-3.4 4.68-6.23 8.25-8.95 11.1v-1.55c2.74-2.98 5.73-6.82 9.48-11.97 4.03-5.52 6.32-8.4 9.17-11.24 8.07-8.08 17.44-32.14 25.87-60.8.62-2.1 2.86-10.03 2.88-10.08 1.21-4.24 2.21-7.53 3.28-10.74 4.9-14.75 10.63-25.16 19-32.4 2.78-2.42 13.5-11.25 15.89-13.4 6.4-5.8 10.32-11.09 13.97-19.43 1.68-3.83 4.05-6.31 7.2-7.86 2.4-1.17 4.64-1.67 9.53-2.36 4.54-.63 6.5-1.05 8.7-2.06 2.89-1.31 5.03-3.42 6.58-6.73 1.53-3.3 2.23-5.9 3.43-11.9 1.64-8.14 2.85-11.79 6.11-16.2 4.28-5.79 11.41-10.7 22.73-15.16 16.15-6.36 25.13-6.07 37.9-1.11l3.19 1.26c8.77 3.47 14.13 4.82 22.09 4.82 11.09 0 18.02-1.16 24.46-3.47 1-.36 4.68-1.8 5.58-2.11A22.5 22.5 0 0 1 265 72.5c3.05 0 5.67.05 14.07.26 11.53.29 17.2.27 20.83-.25 9.25-1.85 17.54-4.83 23.94-9.17C332 57.8 336.5 50.46 336.5 41c0-7-.17-11.86-.7-22.7-.35-7.26-.55-12.83-.59-18.3zM93.87 0h2.04c-.7 4-1.61 6.82-3.03 9.47-2.33 4.38-2.85 5.75-5.26 13.03a40.46 40.46 0 0 1-1.94 5.03c-2.24 4.66-5.92 8.8-13.07 14.26-8.01 6.13-14.27 16.55-20.03 31.55-2.4 6.23-8.75 25.63-9.64 28.01-2.69 7.16-6.56 12.7-15.63 23.68l-2.68 3.24c-6.02 7.34-9.35 12.07-11.72 17.15-2.3 4.94-7.12 9.9-12.91 14.15v-2.4c5.14-3.94 9.1-8.3 11.1-12.6 2.46-5.27 5.87-10.1 11.98-17.56l2.68-3.26c8.94-10.8 12.72-16.22 15.3-23.1.88-2.33 7.24-21.74 9.65-28.03 5.89-15.31 12.3-26 20.68-32.41 6.92-5.3 10.4-9.2 12.48-13.55.65-1.35 1.16-2.7 1.85-4.79 2.45-7.4 3-8.83 5.4-13.34A27.68 27.68 0 0 0 93.87 0zm9.07 0h1.02c-1.66 8.3-2.91 12.67-4.54 15.26a59.14 59.14 0 0 0-4.1 8.21c-1.27 3-2.44 6.2-3.5 9.4-.38 1.12-.7 2.16-2.41 5.39a251.48 251.48 0 0 0-12.81 13.3c-3.48 3.96-5.95 7.27-7.15 9.66-.95 1.9-2.06 5.99-3.61 12.97-.64 2.9-3.65 17.15-4.51 21.07-3.63 16.45-6.63 26.69-9.9 32-7.66 12.45-10.64 15.71-37.08 41.1A69.78 69.78 0 0 1 0 179.21v-1.15a69.39 69.39 0 0 0 13.65-10.42c26.4-25.33 29.32-28.55 36.92-40.9 3.2-5.18 6.18-15.37 9.78-31.7.86-3.91 3.87-18.16 4.51-21.06 1.57-7.09 2.7-11.2 3.7-13.2 1.24-2.5 3.76-5.86 7.29-9.89.9-1.03 1.86-2.1 2.86-3.18 2.4-2.6 4.96-5.22 7.53-7.76.9-.88 1.73-1.7 3.37-3.4a129.02 129.02 0 0 1 4.78-13.46 60.07 60.07 0 0 1 4.19-8.35c1.52-2.44 2.74-6.71 4.36-14.74zM83.71 0h1.1c-2.09 4.74-6.03 8.92-11.42 12.3-7.2 4.52-16.5 7.2-24.39 7.2-8.9 0-11.8 7-11.74 21.52 0 1.7.04 3.17.12 5.99.1 3.3.12 4.45.12 5.99 0 5.73-.76 11.3-2.01 16.5a66.67 66.67 0 0 1-2.15 6.97 2597.76 2597.76 0 0 1-7 15.86A4270.8 4270.8 0 0 1 6.44 136.2 54.64 54.64 0 0 1 0 147v-1.65a54.87 54.87 0 0 0 5.55-9.57A4269.82 4269.82 0 0 0 30.7 79.97c.53-1.2.99-2.23 2.44-5.9A69.23 69.23 0 0 0 36.5 53c0-1.52-.03-2.66-.12-5.95-.08-2.83-.12-4.31-.12-6.01-.03-6.79.53-11.62 2.07-15.34 1.94-4.68 5.39-7.19 10.67-7.19 7.7 0 16.81-2.63 23.86-7.05C77.93 8.27 81.66 4.38 83.7 0zm282.63 0h1.01c1.86 10.02 2.18 12.67 2.32 18.3a123.43 123.43 0 0 1 .37 27.83c-.96 8.78-3.1 16.01-6.63 21.15-11.34 16.5-39.8 29.22-66.41 29.22-5.09 0-10.47.28-16.31.83a413.8 413.8 0 0 0-24.37 3.16c-21.56 3.26-27.66 4.01-36.32 4.01-6.92 0-12.2-1.05-21.69-3.9l-2.78-.83c-1.39-.41-2.54-.74-3.65-1.02-8-2.05-14.22-2.04-21.7.72a16.32 16.32 0 0 0-9.17 8.18c-1.6 3.05-2.5 6.06-4.02 12.83-1.5 6.64-2.34 9.52-3.99 12.64a16.16 16.16 0 0 1-9.85 8.36 104.8 104.8 0 0 0-9.5 3.42c-6.55 2.8-10.1 5.57-13.8 10.47-1.33 1.75-1.03 1.3-5.43 7.9-1.98 2.97-4.66 5.8-8.48 9.14-2.01 1.76-10.71 8.83-12.88 10.7-7.37 6.35-12.58 12.14-16.63 19.14-4.22 7.3-7.8 18.3-11.28 33.26-.87 3.73-1.72 7.64-2.64 12.14l-1.18 5.8-1.09 5.45c-1.8 8.96-2.77 13.28-3.77 16.26-6.8 20.44-17.26 42.16-27.13 51.2-5.11 4.7-8.1 7.07-11.1 8.86-.9.54-1.84 1.04-2.92 1.57-.44.22-9.6 4.4-14.1 6.66l-1.22.62v-1.13l.78-.39c4.52-2.26 13.67-6.44 14.1-6.65a41.19 41.19 0 0 0 2.84-1.54c2.94-1.75 5.88-4.09 10.94-8.73 9.71-8.9 20.1-30.51 26.87-50.79.97-2.92 1.94-7.22 3.73-16.13l1.1-5.46a490.5 490.5 0 0 1 3.82-17.96c3.5-15.06 7.1-26.14 11.39-33.54 4.11-7.11 9.4-12.98 16.83-19.4 2.19-1.88 10.88-8.95 12.88-10.7 3.77-3.28 6.39-6.05 8.3-8.93 4.43-6.64 4.12-6.18 5.47-7.96 3.8-5.03 7.5-7.91 14.21-10.78 2.61-1.12 5.74-2.24 9.59-3.46a15.17 15.17 0 0 0 9.27-7.86c1.59-3.02 2.42-5.85 4.03-12.99 1.41-6.27 2.32-9.33 3.98-12.48a17.31 17.31 0 0 1 9.7-8.66c7.7-2.83 14.1-2.84 22.3-.75 1.12.29 2.28.61 3.68 1.03l3.73 1.11c8.47 2.54 13.66 3.58 20.46 3.58 8.59 0 14.67-.75 36.18-4a414.64 414.64 0 0 1 24.41-3.17c5.88-.54 11.29-.83 16.41-.83 26.3 0 54.45-12.58 65.59-28.78 3.42-4.98 5.5-12.06 6.46-20.7.84-7.74.73-16.02.02-23.9a136.2 136.2 0 0 0-.57-5.12c0-4.47-.3-6.94-2.16-17zM18.88 0h1.03C18 7.57 17.15 10.18 14.46 16.2c-1.95 4.37-2.67 9.19-2.42 14.89.2 4.33.71 7.7 2.28 16.13 1.09 5.88 1.57 8.77 1.94 12.2.96 8.9.24 16.08-2.8 22.79A463.4 463.4 0 0 1 0 109.43v-2.12a465 465 0 0 0 12.54-25.52c2.97-6.52 3.67-13.53 2.72-22.27-.36-3.4-.84-6.26-1.93-12.12-1.57-8.47-2.1-11.88-2.29-16.27-.26-5.84.48-10.81 2.5-15.33 2.64-5.9 3.48-8.47 5.34-15.8zm280.47 0a70.78 70.78 0 0 1-4.91 11.24c-2.56 4.7-4.01 8.45-4.86 11.98l-.4 1.8-.28 1.45a5.28 5.28 0 0 1-.74 2.07c-.74 1.03-1.93 1.28-5.13 1.25.92 0-9.85-.29-15.03-.29-10.2 0-18.45.82-29.46 2.56-16.87 2.66-17.73 2.77-23.66 2.52a42.57 42.57 0 0 1-8-1.09c-17.7-4.16-46.18-5.86-54.72-3.01-2.72.9-5.88 2.8-9.52 5.59a112.37 112.37 0 0 0-6.54 5.48c-1.4 1.25-9.17 8.5-10.78 9.84-1.45 1.2-8.18 7.42-8.85 8.02a114.65 114.65 0 0 1-4.55 3.9c-4.99 4.03-8.9 6.2-11.92 6.2-3.52.05-4.32 0-5.14-.4-1.13-.56-1.5-1.72-1.13-3.57.74-3.63 4.47-10.84 12.84-24.8 5.69-9.48 9.42-18 11.78-26.2 1.45-5.04 1.94-7.4 2.97-14.54h1.01c-1.05 7.3-1.54 9.7-3.01 14.82-2.39 8.28-6.16 16.89-11.9 26.44-8.3 13.84-12 21.01-12.7 24.48-.3 1.45-.08 2.14.59 2.47.6.3 1.35.35 3.48.3 3.92 0 7.69-2.1 12.5-5.98 1.4-1.13 2.87-2.39 4.51-3.86.66-.59 7.41-6.83 8.88-8.05 1.59-1.33 9.34-8.55 10.75-9.82 2.4-2.15 4.55-3.96 6.6-5.53 3.72-2.85 6.97-4.8 9.81-5.74 8.76-2.92 37.41-1.22 55.27 2.99 2.57.6 5.14.95 7.81 1.06 5.84.25 6.7.14 23.47-2.51 11.05-1.75 19.36-2.57 29.6-2.57 5.2 0 15.99.3 15.05.29 2.87.03 3.84-.17 4.3-.83.23-.32.4-.8.58-1.7l.28-1.43.4-1.85c.88-3.6 2.36-7.44 4.96-12.22 1.87-3.43 3.44-7 4.73-10.76h1.06zm-8.59 0c-5.91 17.94-9.55 22-19.76 22-4.5 0-10.22.32-28.69 1.5l-1.53.1c-15.6.99-23.47 1.4-28.78 1.4-5.35 0-13.24-.96-28.86-3.28l-1.54-.23C163.18 18.75 157.47 18 153 18c-4.45 0-7.3 1.01-10.96 3.34-.1.06-1.8 1.17-2.3 1.47-2.43 1.5-4.32 2.19-6.74 2.19-2.8 0-4.11-1.46-4.11-4.22 0-1.04.16-2.29.5-4.1.16-.82.9-4.4 1.07-5.32.8-4.11 1.3-7.68 1.47-11.36h2c-.17 3.82-.68 7.5-1.5 11.75-.19.94-.92 4.5-1.07 5.31a21.04 21.04 0 0 0-.47 3.72c0 1.7.46 2.22 2.11 2.22 1.99 0 3.55-.57 5.7-1.9.47-.28 2.15-1.37 2.26-1.44C144.92 17.14 148.12 16 153 16c4.62 0 10.3.74 28.9 3.51l1.53.23C198.93 22.04 206.8 23 212 23c5.25 0 13.11-.41 28.65-1.4l1.54-.1C260.73 20.32 266.43 20 271 20c8.95 0 12.15-3.4 17.66-20h2.1zM141.51 0h1.13c-2.06 3.86-2.63 5.1-2.77 6.19-.15 1.12.42 1.64 2.32 1.96 1.8.3 3.85.35 10.81.35 6.02 0 13 .56 21.35 1.62 3.95.5 8.03 1.1 13.13 1.89 24 3.7 22.5 3.49 26.83 3.49 24.02 0 51.83-2.24 60.45-6.94 2.88-1.57 5.05-4.49 6.6-8.56h1.07c-1.64 4.47-3.98 7.69-7.2 9.44-8.83 4.82-36.67 7.06-60.92 7.06-4.41 0-2.84.22-26.98-3.5-5.1-.8-9.17-1.38-13.1-1.88-8.31-1.06-15.26-1.62-21.23-1.62-7.04 0-9.1-.05-10.97-.37-2.38-.4-3.38-1.32-3.15-3.07.16-1.22.69-2.41 2.63-6.06zm76.4 0c5.69 1.64 10.37 2.5 14.09 2.5 9.59 0 16.7-.71 22.4-2.5h2.98C251.12 2.53 243.2 3.5 232 3.5c-4.5 0-10.32-1.21-17.53-3.5h3.45zM70.69 0c-2.87 3.27-6.95 5.39-12.02 6.53-3.98.89-7.5 1.08-12.92 1A97.24 97.24 0 0 0 44 7.5c-5.37 0-8.86-1.24-10.1-4.97A8.6 8.6 0 0 1 33.5 0h.99c.02.82.14 1.56.36 2.22C35.91 5.39 39.02 6.5 44 6.5l1.76.02c5.35.09 8.8-.1 12.69-.97C62.95 4.54 66.63 2.74 69.3 0h1.37zM0 207.87c7.31-.16 11.5 3.33 11.5 11.13 0 11.41-5.05 28.35-11.5 41.5v-2.3c5.93-12.72 10.5-28.47 10.5-39.2 0-7.18-3.7-10.3-10.5-10.13v-1zm0 7.05c1.23.14 2.18.58 2.87 1.31 1.4 1.48 1.6 3.72 1.16 7.58l-.16 1.3A28.93 28.93 0 0 0 3.5 229c0 3.2-1.48 9.52-3.5 15.9v-3.45c1.49-5.13 2.5-9.87 2.5-12.45 0-.98.08-1.75.37-4.02l.16-1.29c.42-3.56.24-5.59-.88-6.77-.5-.53-1.21-.87-2.15-1v-1zM0 410.9v-1.47a21.67 21.67 0 0 0 2.97-4.7c1.32-2.7 2.68-6.28 4.56-11.89 7.85-23.55 7.83-26.6.25-30.4-2.25-1.12-4.8-1.43-7.78-.91v-1.02a13.1 13.1 0 0 1 8.22 1.04c8.24 4.12 8.26 7.6.25 31.6-1.88 5.66-3.25 9.27-4.6 12.02A20.82 20.82 0 0 1 0 410.9zM33.64 452c1.68 0 3.04-.23 8.34-1.31l2.38-.47c8.26-1.57 12.72-1.3 14.53 2.33 1.38 2.75-.47 5.86-4.75 9.68a75.6 75.6 0 0 1-5.08 4.07c-.94.7-4.89 3.59-5.79 4.27-1.86 1.4-2.97 2.37-3.47 3.03a19.08 19.08 0 0 0-2.89 5.5c.07-.2-4.02 13.65-6.96 22.22-2.7 7.85-5.56 10.72-8.82 8.59-2.11-1.4-3.66-4.24-6.6-11.03-1.98-4.62-2.5-5.76-3.4-7.4-4.55-8.18-3.9-23.9-.05-32.87a9.6 9.6 0 0 1 6.98-5.96c2.59-.66 4.86-.75 11.78-.67l3.8.02zm0 2c-1.13 0-2.09 0-3.82-.02-12.07-.13-14.83.57-16.9 5.41-3.63 8.47-4.26 23.55-.05 31.12.96 1.73 1.48 2.88 3.5 7.58 2.72 6.3 4.24 9.08 5.86 10.14 1.64 1.08 3.5-.8 5.82-7.55a682.9 682.9 0 0 0 6.97-22.24 21.03 21.03 0 0 1 3.18-6.04c.65-.87 1.85-1.9 3.86-3.43.92-.7 4.87-3.57 5.8-4.27 2.02-1.5 3.6-2.77 4.95-3.97 3.63-3.23 5.09-5.7 4.3-7.28-1.21-2.42-5.07-2.65-12.38-1.27l-2.35.47c-5.49 1.11-6.86 1.35-8.74 1.35zm345.63 146c-3.45-12.26-3.77-14.13-3.77-19 0-3.33-.13-6.27-.43-11.34-.63-10.33-.65-13.5.26-17.07 1.21-4.74 4.21-7.1 9.67-7.1h26c4.08 0 5.19 1.85 5.93 7.11.1.79.13.97.19 1.32.84 5.35 2.8 7.58 8.88 7.58 3.64 0 5.54.4 6.43 1.37.76.83.76 1.44.36 3.93-.85 5.26.5 8.85 7.5 13.8 6.32 4.45 11.63 5.36 16.55 3.37 3.8-1.54 6.73-4.16 11.92-10l1.1-1.23 1.09-1.23a75.6 75.6 0 0 1 2.7-2.86 35.81 35.81 0 0 1 9.57-6.73c1.52-.76 1.72-.86 5.66-2.63 6.1-2.73 9.01-4.5 11.74-7.62 2.63-3 4.67-4.85 6.7-6.04 3.18-1.85 5.46-2.13 13.68-2.13 5.98 0 10.56-4.32 18-14.99l2.82-4.03c1.06-1.5 1.94-2.7 2.79-3.79 7.87-10.12 19.38-10.4 30.74.96 5.54 5.53 10.17 19.43 13.64 38.51 2.5 13.75 4.18 29.46 4.47 39.84h-1c-.3-10.32-1.96-25.97-4.45-39.66-3.43-18.87-8.02-32.65-13.36-37.99-10.95-10.95-21.76-10.68-29.26-1.04-.83 1.07-1.7 2.26-2.75 3.75l-2.81 4.02c-7.65 10.95-12.38 15.42-18.83 15.42-8.04 0-10.21.26-13.17 2-1.92 1.12-3.9 2.9-6.45 5.83-2.86 3.26-5.87 5.09-12.09 7.88a103.35 103.35 0 0 0-5.62 2.6 34.84 34.84 0 0 0-9.32 6.54 74.67 74.67 0 0 0-3.75 4.05l-1.1 1.24c-5.28 5.95-8.29 8.64-12.28 10.25-5.26 2.13-10.92 1.17-17.5-3.48-7.33-5.17-8.82-9.15-7.92-14.77.34-2.12.34-2.6-.1-3.1-.64-.69-2.34-1.04-5.7-1.04-6.63 0-8.96-2.63-9.87-8.42l-.2-1.34c-.67-4.82-1.53-6.24-4.93-6.24h-26c-5 0-7.6 2.04-8.7 6.34-.88 3.43-.85 6.57-.23 16.76a177 177 0 0 1 .43 11.4c0 4.78.32 6.63 3.81 19h-1.04zm13.68 0c-1.31-6.58-1.61-10.71-1.36-14.84.04-.7.1-1.44.18-2.38l.23-2.56c.34-3.81.5-6.97.5-11.22 0-4.94 1.46-7.76 4.21-8.42 2.38-.58 5.56.54 9.2 3 6.64 4.52 13.99 13.07 16.55 19.23 4.77 11.44 14.12 15.69 33.54 15.69 8.6 0 14.32-2.35 20.67-7.88 1.45-1.26 15.06-15 21-20 7.21-6.07 11.77-7.59 20.62-8.32 5.52-.45 7.98-.9 11.44-2.36 4.58-1.95 9.36-5.48 14.9-11.29 7.43-7.76 13.25-8.92 17.47-4.3 3.32 3.63 5.46 10.58 6.82 20.24.73 5.17.94 7.74 1.58 17.38.25 3.75.17 5.32-.92 18.03h-1c1.09-12.7 1.17-14.28.92-17.97-.64-9.6-.85-12.16-1.57-17.3-1.33-9.47-3.43-16.27-6.56-19.7-3.76-4.11-8.93-3.08-16 4.32-5.65 5.9-10.54 9.5-15.25 11.5-3.58 1.53-6.13 1.99-11.6 2.44-8.8.72-13.17 2.18-20.2 8.1-5.9 4.96-19.5 18.7-21 19.99-6.52 5.68-12.47 8.12-21.32 8.12-19.78 0-29.5-4.42-34.46-16.3-2.49-5.97-9.71-14.38-16.2-18.79-3.42-2.32-6.36-3.35-8.4-2.86-2.2.53-3.44 2.92-3.44 7.45 0 4.28-.16 7.47-.5 11.31l-.23 2.56c-.09.93-.14 1.65-.19 2.35-.24 4.08.06 8.18 1.39 14.78h-1.02zm113.75 0c2.52-3.26 8.93-11.79 10.9-14.3 5.48-6.98 13.05-12.38 19.4-13.94 7.01-1.71 11.5 1.45 11.5 9.24 0 4.02-.04 5.16-.74 19h-1c.7-13.85.74-15 .74-19 0-7.12-3.86-9.83-10.26-8.26-6.11 1.5-13.5 6.77-18.85 13.57-1.86 2.36-7.65 10.07-10.43 13.69h-1.26zm-9.86-338.96c3.44 2.71 7 5.1 11.44 7.75 1.06.64 8.42 4.9 10.35 6.1 11.27 7 15 13.35 12.35 25.33-1.45 6.52-4.53 11.1-9.39 14.44-3.83 2.63-8.07 4.26-16.08 6.56-11.97 3.45-13.68 3.99-18.82 6.28a60.18 60.18 0 0 0-7.81 4.18c-11.11 7.07-19.1 7.7-27.96 3.28-3.56-1.77-17.2-11-17.2-11.01a101.77 101.77 0 0 0-5.2-3.07c-16.04-8.83-34.27-24.16-34.52-31.85-.11-3.46 1.99-6.57 6.28-10.26 1.03-.9 2.18-1.81 3.68-2.95.72-.55 3.38-2.56 3.94-3 4.47-3.4 7.18-5.79 9.32-8.45 11.12-13.82 26.55-28.68 34.36-32.28 12.06-5.54 19.84-5.77 27.37.12 3.25 2.54 5.65 6.54 8.58 13.35.29.65 2.3 5.45 2.88 6.74 1.62 3.65 2.9 5.8 4.24 6.94.72.6 1.45 1.2 2.2 1.8zm-3.49-.28c-1.63-1.39-3.03-3.74-4.77-7.65-.58-1.3-2.6-6.12-2.88-6.76-2.81-6.5-5.08-10.3-7.98-12.56-6.83-5.35-13.85-5.15-25.3.12-7.45 3.42-22.7 18.12-33.64 31.72-2.27 2.82-5.08 5.3-9.67 8.79l-3.94 2.98a79.98 79.98 0 0 0-3.59 2.88c-3.87 3.33-5.67 6-5.58 8.69.21 6.64 18.14 21.72 33.48 30.15 1.76.97 3.5 2 5.3 3.13.12.08 13.61 9.22 17.03 10.92 8.22 4.1 15.46 3.52 26-3.18a62.17 62.17 0 0 1 8.07-4.31c5.25-2.35 7-2.9 19.08-6.38 7.8-2.24 11.9-3.82 15.5-6.3 4.44-3.04 7.23-7.18 8.56-13.22 2.44-11.02-.83-16.6-11.45-23.2-1.9-1.18-9.23-5.42-10.32-6.08-4.5-2.69-8.13-5.12-11.64-7.9-.77-.6-1.52-1.21-2.26-1.84zM87.72 241.6c4.3-2.98 7.88-5 12.14-6.95.84-.4 1.73-.78 2.78-1.24l4.37-1.88a164.3 164.3 0 0 0 17.74-8.96 320.67 320.67 0 0 1 27.87-14.5c4.22-1.95 21.89-9.84 21.17-9.52 19.17-8.62 28.1-6.93 49.5 8.05 7.91 5.54 13.24 13.25 16.45 22.66 3.02 8.83 3.76 16.51 3.76 27.75 0 8.32-.66 12.95-3.68 18.97-4.18 8.36-12.3 16.14-25.58 23.47-24.45 13.49-38.83 27.55-52.83 47.84-8.83 12.8-47.76 44.21-65.16 54.15C75.04 413.55 48.89 423.5 31 423.5c-10.05 0-14.67-4.78-14.76-13.37-.07-6.32 2.06-13.73 6.3-24.32 2.95-7.37 2.02-12.9-2.16-22.29-3.19-7.17-3.88-9.14-3.88-12.52 0-3.35 1.87-6.9 5.52-11.07 2.61-3 3.5-3.83 11.9-11.5 5.09-4.66 8.08-7.6 10.7-10.75 9.46-11.36 12.62-19.47 17.9-44.78 3.12-15.05 6.63-20.28 15.12-25.25.8-.47 3.95-2.25 4.7-2.68a76.66 76.66 0 0 0 5.38-3.38zm.56.82a77.63 77.63 0 0 1-5.44 3.43l-4.7 2.67c-8.23 4.82-11.57 9.81-14.65 24.6-5.3 25.45-8.51 33.7-18.1 45.21-2.66 3.19-5.68 6.16-10.8 10.84-8.36 7.64-9.24 8.48-11.82 11.42-3.5 4.01-5.27 7.36-5.27 10.42 0 3.18.68 5.1 3.8 12.12 4.27 9.6 5.24 15.37 2.16 23.07-4.18 10.47-6.29 17.78-6.22 23.93.08 8.06 4.26 12.38 13.76 12.38 17.67 0 43.68-9.9 64.75-21.93 17.28-9.88 56.1-41.2 64.84-53.85 14.08-20.42 28.57-34.59 53.17-48.16 13.12-7.23 21.09-14.87 25.17-23.03 2.92-5.86 3.57-10.35 3.57-18.53 0-11.13-.74-18.73-3.7-27.43-3.15-9.22-8.36-16.75-16.09-22.16-21.13-14.8-29.7-16.42-48.5-7.95.7-.32-16.96 7.56-21.17 9.5-1.7.8-3.3 1.55-4.86 2.3a319.68 319.68 0 0 0-22.93 12.17 165.3 165.3 0 0 1-17.85 9.01l-4.37 1.88c-1.04.45-1.92.84-2.76 1.23a74.56 74.56 0 0 0-11.99 6.86zm-7.6 12.2c7.7-6.25 12.3-8.17 23.68-11.27 6.12-1.67 9.12-2.95 12.31-5.72 3.8-3.3 7.47-4.52 15.86-6.1 2.75-.52 3.67-.7 5.06-1.02 5.48-1.24 9.48-2.93 13.1-5.89 10.42-8.53 25.4-14.11 36.31-14.11 5.33 0 16.77 7.58 25.74 17.16 10.73 11.46 15.96 23.27 12.73 32.5-3.18 9.1-11.39 18.57-23.03 27.86-8.44 6.73-18.36 13-25.22 16.43-3.72 1.86-6.59 4.88-9.77 9.99-.69 1.1-11.1 20.25-16.03 27.83-5.62 8.65-15.4 17.36-30.23 27.96a552.58 552.58 0 0 1-9.2 6.42c-.13.09-6.81 4.65-8.6 5.89-6.47 4.46-10.35 7.35-13.05 9.83-11.64 10.67-37.14 15.54-43.7 8.98-1.96-1.96-2.2-4.06-1.95-10.52.37-9.42-.5-14.5-4.95-20.51a34.09 34.09 0 0 0-7.04-6.92c-3.93-2.95-6.07-6.11-6.56-9.49-.97-6.61 3.87-13.06 14.17-21.69 1.58-1.32 6.67-5.44 7.09-5.78a48.03 48.03 0 0 0 5.23-4.77c4.1-4.63 5.85-9.55 7.8-20.07a501.52 501.52 0 0 0 .8-4.37c.33-1.87.6-3.3.88-4.73.74-3.78 1.5-7.18 2.4-10.63 1-3.78 1.38-5.5 2.36-10.37.6-3.02.93-4.21 1.56-5.47 1.22-2.45 1.27-2.5 12.25-11.42zm.64.78c-10.77 8.74-10.88 8.84-12 11.08-.58 1.16-.88 2.3-1.47 5.22-.98 4.89-1.36 6.63-2.37 10.44-.9 3.43-1.65 6.8-2.39 10.56a339.79 339.79 0 0 0-1.29 6.95l-.39 2.15c-1.98 10.68-3.77 15.74-8.04 20.54a48.77 48.77 0 0 1-5.34 4.88c-.42.34-5.5 4.47-7.07 5.78-10.04 8.4-14.72 14.65-13.83 20.78.45 3.1 2.44 6.03 6.17 8.83 3 2.25 5.39 4.62 7.24 7.12 4.63 6.24 5.52 11.52 5.15 21.15-.25 6.14-.01 8.1 1.66 9.78 6.1 6.1 31.02 1.33 42.31-9.02 2.75-2.52 6.66-5.43 13.16-9.92l8.6-5.89c3.63-2.48 6.45-4.44 9.19-6.4 14.73-10.54 24.44-19.18 29.97-27.7 4.9-7.54 15.31-26.68 16.02-27.8 3.27-5.26 6.26-8.41 10.18-10.37 6.79-3.4 16.65-9.63 25.03-16.32 11.52-9.18 19.61-18.53 22.72-27.4 3.07-8.78-2.02-20.27-12.52-31.49-8.8-9.4-20.04-16.84-25.01-16.84-10.67 0-25.43 5.5-35.68 13.89-3.76 3.07-7.9 4.81-13.5 6.09-1.41.32-2.35.5-5.11 1.02-8.21 1.55-11.76 2.73-15.38 5.88-3.34 2.9-6.45 4.22-12.7 5.92-11.26 3.07-15.75 4.94-23.31 11.09zM212 251.85c0 7.56-.6 10.92-2.6 14.3-1.1 1.84-7.66 10.05-8.6 11.3-5.96 7.94-9.33 10.28-17.26 13.76-1.34.58-2.2 1-3.03 1.5-.55.33-1.2.66-2 1.02-.71.33-4.46 1.9-5.52 2.39-6.05 2.78-8.99 5.8-8.99 10.73 0 10.97-18.95 36.12-34.51 44.87-8.18 4.6-21.3 9.36-32.78 11.86-13.33 2.9-22.49 2.48-24.62-2.32-1.32-2.97-4.4-4.26-11.98-5.81l-.6-.12c-4.84-.99-6.94-1.55-9.03-2.64-2.92-1.5-4.48-3.7-4.48-6.84 0-2.74 1.08-5.77 3.25-9.67.85-1.53 1.82-3.13 3.23-5.35-.16.25 2.83-4.4 3.67-5.76 6.69-10.7 9.85-18.5 9.85-27.22 0-18.41 11.22-33.37 27.5-42.86 5.22-3.05 9.23-3.31 15.2-2.12 5.04 1 6.05.9 7.43-1.52 4.5-7.85 7.04-9.5 15.87-9.5 3.93 0 6.97-.98 10.47-3.16 1.56-.97 8.67-6.17 10.99-7.68 9.2-5.98 11.34-7 25.2-11.95 6.95-2.48 15.18 1.28 22.33 9.12 6.55 7.19 11.01 16.61 11.01 23.67zm-2 0c0-6.5-4.25-15.48-10.49-22.32-6.67-7.32-14.16-10.74-20.17-8.59-13.73 4.9-15.73 5.85-24.8 11.75-2.24 1.46-9.37 6.68-11.01 7.7-3.8 2.36-7.2 3.46-11.53 3.46-8.08 0-9.98 1.23-14.13 8.5-1.1 1.91-2.51 2.88-4.35 3.09-1.3.14-1.9.05-5.22-.61-5.53-1.1-9.07-.88-13.8 1.88-15.72 9.17-26.5 23.55-26.5 41.14 0 9.2-3.28 17.29-10.15 28.28l-3.68 5.77c-1.39 2.19-2.35 3.77-3.17 5.25-2.02 3.63-3 6.38-3 8.7 0 4.19 2.87 5.67 11.9 7.52l.61.12c8.27 1.7 11.7 3.13 13.4 6.95 3.17 7.14 36 0 54.6-10.46 14.98-8.43 33.49-32.99 33.49-43.13 0-5.9 3.47-9.48 10.16-12.55 1.1-.5 4.85-2.08 5.52-2.38.74-.34 1.32-.64 1.8-.93.92-.55 1.85-1 3.25-1.62 7.65-3.35 10.75-5.5 16.47-13.12 1.02-1.36 7.47-9.42 8.47-11.11 1.79-3.01 2.33-6.06 2.33-13.3zm-37.18-22.4c.15-.1 2.4-1.51 2.95-1.84.96-.57 1.7-.94 2.43-1.17 2.57-.83 5.06-.1 11.04 3.12 14.86 8 19.43 22.87 9.18 38.71-4.04 6.24-9.37 9-18.72 11.11-.85.2-1.2.27-3.13.68-6.04 1.29-8.78 2.08-11.6 3.65-3.63 2.02-6.09 4.98-7.5 9.44-7.87 24.93-19.72 43.34-36.28 50.31-16.45 6.93-21.13 8.53-27.98 8.89-4.94.25-9.8-.65-15.4-2.89a44.45 44.45 0 0 1-5.64-2.6c-4.02-2.33-5.14-4.74-4.5-9.31.3-2.13 3.77-15.53 4.84-20.65.63-3.05 1.19-6.14 1.75-9.69a464.04 464.04 0 0 0 1.35-8.9c1.42-9.41 2.5-14.27 4.49-18.65 2.46-5.43 6.13-9.03 11.72-11.13 6.59-2.47 10.54-3.1 18.03-3.53 4.75-.27 6.68-.64 9-2.05.61-.37 1.22-.81 1.82-1.33a30.61 30.61 0 0 0 3.37-3.4c.59-.69 2.38-2.9 2.63-3.19 3.36-4 6.3-5.53 12.33-5.53 3.94 0 5.9-.92 8.18-3.36-.17.18 2.75-3.14 3.85-4.22a30.95 30.95 0 0 1 6.79-5c1.5-.83 3.15-1.62 4.99-2.38a64.92 64.92 0 0 0 10.01-5.1zm-14.52 8.34a29.95 29.95 0 0 0-6.57 4.84 116.68 116.68 0 0 0-3.82 4.2c-2.46 2.63-4.68 3.67-8.91 3.67-5.72 0-8.39 1.39-11.57 5.17-.23.28-2.03 2.5-2.63 3.2a31.6 31.6 0 0 1-3.47 3.51c-.65.55-1.3 1.03-1.96 1.43-2.5 1.51-4.55 1.9-9.47 2.19-7.39.42-11.25 1.04-17.72 3.47-5.34 2-8.82 5.4-11.17 10.6-1.93 4.27-3 9.07-4.41 18.39l-.65 4.34-.7 4.57c-.57 3.56-1.12 6.67-1.76 9.73-1.08 5.18-4.54 18.53-4.83 20.59-.59 4.17.35 6.18 4.01 8.3 1.35.77 3.1 1.58 5.52 2.55 5.46 2.18 10.18 3.05 14.97 2.8 6.69-.34 11.32-1.93 27.65-8.8 16.21-6.83 27.92-25.01 35.71-49.7 1.49-4.7 4.12-7.86 7.97-10 2.93-1.63 5.74-2.45 11.87-3.76 1.92-.4 2.28-.49 3.12-.68 9.12-2.06 14.24-4.7 18.1-10.67 9.92-15.34 5.55-29.55-8.82-37.29-5.75-3.1-8.03-3.76-10.25-3.05-.65.2-1.33.54-2.23 1.08-.55.32-2.77 1.72-2.93 1.82a65.91 65.91 0 0 1-10.16 5.17c-1.8.75-3.42 1.52-4.89 2.33zm-42.39 32.72c16.15-2.87 26.36-.97 32.47 6.16 5.08 5.93 1.13 21.42-5.93 35.55-4.79 9.58-10.6 16.21-23.16 25.19-14.15 10.1-35.5 12.2-40.71 3.85-1.86-2.97-2.1-8.14-1.06-15.73.78-5.68 1.86-10.71 4.73-22.98l.12-.51c1.59-6.8 2.37-10.31 3.14-14.14 1.45-7.25 3.74-11.47 7.26-13.74 2.81-1.8 5.53-2.28 12.33-2.62 5.33-.27 7.56-.46 10.81-1.03zm.18.98c-3.3.59-5.56.78-10.94 1.05-6.62.33-9.23.78-11.84 2.46-3.25 2.1-5.42 6.09-6.82 13.1-.77 3.84-1.56 7.35-3.15 14.17l-.12.5c-2.86 12.24-3.93 17.26-4.7 22.9-1.03 7.36-.79 12.36.9 15.07 4.82 7.7 25.54 5.67 39.29-4.15 12.43-8.88 18.13-15.39 22.84-24.81 6.86-13.72 10.75-29 6.07-34.45-5.84-6.81-15.7-8.65-31.53-5.84zM132 276.5c7.12 0 10.66 3.08 11.25 8.7.42 4.02-.43 8.14-2.77 15.94-2.56 8.52-18.36 25.38-27.2 31.28-7.01 4.67-20.02 5.67-26.57.99-3.99-2.85-3.53-12.08.02-26.46.68-2.75 1.47-5.65 2.37-8.76a412.6 412.6 0 0 1 3.05-10.14l.37-1.2c1.48-4.8 5.1-7.75 10.73-9.27 4.4-1.2 9.54-1.5 17.48-1.33l3.89.1c3.87.11 5.42.15 7.38.15zm0 1c-1.97 0-3.53-.04-7.41-.15l-3.88-.1c-7.85-.17-12.92.13-17.2 1.3-5.32 1.43-8.67 4.16-10.03 8.6a1277.83 1277.83 0 0 1-1.6 5.21c-.68 2.2-1.27 4.17-1.82 6.1-.9 3.1-1.68 5.99-2.36 8.73-3.43 13.88-3.87 22.93-.4 25.4 6.17 4.42 18.73 3.45 25.42-1 8.66-5.78 24.33-22.49 26.8-30.73 2.3-7.67 3.14-11.71 2.73-15.56-.53-5.1-3.64-7.8-10.25-7.8zm-17.79 7a31.3 31.3 0 0 1 8.57 1.4c5.42 1.78 8.72 5.03 8.72 10.1 0 9.59-9.51 17.2-22.34 21.47-9.82 3.28-13.62-1.79-11.66-16.54.84-6.28 3.82-10.67 8.24-13.46a20.38 20.38 0 0 1 8.47-2.97zm-.6 1.08a19.39 19.39 0 0 0-7.34 2.73c-4.18 2.64-6.98 6.78-7.77 12.76-1.89 14.11 1.36 18.45 10.34 15.46C121.3 312.37 130.5 305 130.5 296c0-4.56-2.98-7.5-8.03-9.15a28.05 28.05 0 0 0-8.2-1.35c-.13 0-.35.03-.66.08zm80.87-23.45c-2.72 9.8-14.93 9.86-26.72 3.3-10.17-5.64-13.8-17.98-5-22.87a66.53 66.53 0 0 0 4.48-2.7l2.03-1.3a50.15 50.15 0 0 1 3.92-2.3c4.73-2.43 8.82-2.8 14-.72 9.16 3.66 10.98 13.33 7.3 26.6zm-20.83-24.98a49.26 49.26 0 0 0-3.84 2.25l-2.03 1.3c-.84.53-1.5.95-2.16 1.35-.82.5-1.6.96-2.38 1.39-7.94 4.4-4.59 15.8 5 21.12 11.31 6.29 22.8 6.23 25.28-2.7 3.57-12.83 1.85-21.97-6.7-25.4-4.9-1.95-8.69-1.62-13.17.7zm17.85 12.15c0 5.7-2.44 9-6.64 9.96-3.3.76-7.56-.05-11.08-1.81l-1.89-.94c-.67-.34-1.18-.62-1.63-.88-4.07-2.38-4.13-4.97.34-10.93 6.8-9.06 20.9-7.16 20.9 4.6zm-1 0c0-5.3-2.87-8.55-7.32-9.16-4.23-.57-8.99 1.44-11.78 5.16-4.15 5.54-4.1 7.44-.64 9.47.44.25.93.51 1.59.85l1.87.93c3.34 1.67 7.36 2.44 10.42 1.74 3.73-.86 5.86-3.74 5.86-9zM387 530.3c0-12.8 2.44-16.74 18.48-29.77a56.8 56.8 0 0 1 7.61-5.2c2.6-1.5 5.33-2.82 8.5-4.18 1.24-.53 2.48-1.05 4.1-1.7l3.92-1.57c9.4-3.83 13.74-6.7 16.62-12.05 1.2-2.22 2.21-4.4 3.23-6.83a148.57 148.57 0 0 0 1.54-3.84l.3-.74.56-1.44c3.2-8.02 6.05-12.08 12.7-16.5a35.26 35.26 0 0 0 4.96-4 46.36 46.36 0 0 0 3.88-4.29c.27-.34 2.55-3.2 3.2-3.98 3.48-4.15 6.51-5.9 11.51-5.9 3.08 0 5.62-.63 9.57-2.1 5.42-2.02 6.53-2.34 8.96-2.2 2.53.13 4.85 1.26 7.18 3.59 1.3 1.3 5.55 5.83 6.52 6.78 5.06 5 9.44 6.92 17.77 6.92a197.5 197.5 0 0 1 12.08.45c15.93.87 21.94.57 25.28-2.21 6.91-5.77 11.64-2.73 11.64 7.76 0 10.73-8.6 20-19 20-4.8 0-8.32 1.43-9.34 3.67-1.12 2.48.68 6.15 5.98 10.57 13.6 11.33 11.24 20.76-7.64 20.76a21.91 21.91 0 0 0-14.6 5.24c-3.28 2.71-5.8 5.86-9.85 11.82l-1.52 2.25c-3.1 4.57-5.01 7.1-7.32 9.4-6.21 6.21-9.3 7.64-13.05 6.89l-1-.23a10.82 10.82 0 0 0-2.66-.37c-1.6 0-2.41.67-8.18 6.22-4.85 4.67-8.07 6.78-11.82 6.78-1.33 0-3.46 1.15-6.45 3.45-1.27.98-2.68 2.14-4.5 3.7l-4.92 4.29a181.11 181.11 0 0 1-4.54 3.82c-9.33 7.56-15.63 10.2-20.21 6.52-2.7-2.15-4.14-4.51-4.63-7.26-.37-2.04-.26-3.63.29-7.3.87-5.85.65-8.42-1.83-11.6-2.32-2.98-2.96-3.22-3.77-2.39-.25.26-1.35 1.63-1.61 1.94-2.21 2.5-4.85 3.57-9 2.82-4.6-.84-5.57-4.11-4.72-10.09l.24-1.56c.6-3.66.68-4.93.25-5.8-.44-.86-1.9-.94-5.23.4l-.74.29c-13.78 5.54-15.26 6.09-19.43 6.67-6.03.84-9.31-1.6-9.31-7.9zm2 0c0 5 2.14 6.6 7.04 5.92 3.91-.55 5.43-1.1 18.95-6.55l.75-.3c4.17-1.66 6.7-1.54 7.76.58.71 1.43.62 2.76-.06 7l-.24 1.53c-.72 5.04-.06 7.27 3.09 7.84 3.43.62 5.38-.17 7.15-2.18.2-.23 1.34-1.66 1.68-2 1.9-1.96 3.82-1.25 6.78 2.55 2.9 3.74 3.17 6.77 2.22 13.12-1 6.75-.52 9.4 3.62 12.71 3.49 2.8 9.1.45 17.7-6.51 1.35-1.1 2.75-2.28 4.49-3.78l4.93-4.3c1.84-1.58 3.27-2.76 4.58-3.77 3.34-2.56 5.74-3.86 7.67-3.86 3.04 0 5.95-1.9 10.43-6.22l2.46-2.39c.94-.89 1.67-1.56 2.37-2.13 1.81-1.49 3.3-2.26 4.74-2.26 1.03 0 1.81.13 3.1.42.7.16.71.17.96.21 2.96.6 5.45-.55 11.23-6.33 2.2-2.2 4.06-4.65 7.09-9.11l1.52-2.25c4.15-6.11 6.76-9.37 10.22-12.24a23.9 23.9 0 0 1 15.88-5.7c16.87 0 18.62-7.01 6.36-17.23-5.9-4.92-8.12-9.41-6.52-12.93 1.42-3.12 5.67-4.84 11.16-4.84 9.25 0 17-8.34 17-18 0-8.94-2.88-10.79-8.36-6.23-3.94 3.28-9.98 3.59-26.67 2.68l-1.02-.06c-5.09-.27-7.99-.39-10.95-.39-8.88 0-13.76-2.14-19.18-7.5-1-.98-5.26-5.53-6.53-6.79-1.99-1.99-3.86-2.9-5.87-3-2.03-.12-3.06.18-8.15 2.07-4.15 1.55-6.9 2.22-10.27 2.22-4.33 0-6.84 1.46-9.98 5.2-.63.74-2.89 3.6-3.18 3.95a48.29 48.29 0 0 1-4.04 4.46 37.26 37.26 0 0 1-5.24 4.23c-6.26 4.17-8.9 7.91-11.95 15.58l-.57 1.43-.28.74a531.5 531.5 0 0 1-1.56 3.88 77.49 77.49 0 0 1-3.32 7c-3.16 5.88-7.82 8.97-17.63 12.96l-3.92 1.58c-1.6.64-2.84 1.15-4.05 1.67a79.2 79.2 0 0 0-8.3 4.08 54.8 54.8 0 0 0-7.35 5.02C391.12 514.78 389 518.21 389 530.31zm133.22-79.76c3.06 1.53 6.54 2.02 10.68 1.7 2.53-.2 4.91-.62 8.8-1.49 5.36-1.19 6.33-1.38 8.33-1.54 2.78-.23 4.82.17 6.29 1.4 1.58 1.31 1.96 2.72 1.26 4.22-.66 1.38-1.05 1.74-5.05 5.07-3.53 2.93-5.03 4.83-5.03 7.09 0 7.3 1.29 10.02 7.83 15.62 3.86 3.3 5.93 6.84 5.28 9.62-.75 3.25-4.96 5.02-12.61 5.02-7.18 0-12.7 4.61-20.03 14.68-.5.7-3.96 5.57-4.94 6.87a38.89 38.89 0 0 1-4.72 5.5c-1.06.98-2.09 1.7-3.1 2.15-2.85 1.26-5.05 1.57-9.83 1.74-7.66.27-10.87 1.45-14.98 7.1-1.58 2.17-3.11 4-4.68 5.6a42.87 42.87 0 0 1-8.65 6.69c-.15.08-10.69 6.19-14.8 8.83-3.76 2.42-6.45 2.04-8.22-.77-1.28-2.03-1.9-4.54-2.87-10.35-.84-5.08-1.27-7.08-2.06-8.93-.97-2.3-2.21-3.24-4.02-2.88-6.2 1.24-8.95 1.39-10.98.2-2.37-1.4-3.13-4.62-2.62-10.73.16-1.96-1.04-2.87-3.76-3.04-2.24-.13-4.9.2-9.94 1.12l-.69.12c-7.97 1.45-10.72 1.72-12.72.73-2.91-1.43-1.6-5.27 4.23-12.21 5.48-6.53 10.6-10.81 15.76-13.53 3.74-1.97 5.94-2.65 12.16-4.1 7.29-1.72 10.4-3.51 14.04-9.31 2.96-4.75 10.74-18.62 12.14-20.84 3.59-5.67 6.8-9.1 11.05-11.34 2.6-1.38 4.72-2.82 9.17-6.07l1.38-1.01c7.85-5.72 12.3-7.98 17.68-7.98 4.22 0 6.49 1.36 9.13 4.77.34.43 1.67 2.22 2 2.67.85 1.09 1.6 1.98 2.45 2.83a24.29 24.29 0 0 0 6.64 4.78zm-.44.9c-2.8-1.4-5-3.03-6.92-4.97-.87-.9-1.65-1.81-2.51-2.93-.35-.46-1.68-2.25-2.01-2.67-2.47-3.18-4.46-4.38-8.34-4.38-5.09 0-9.4 2.2-17.09 7.78l-1.38 1.01c-4.49 3.29-6.63 4.74-9.3 6.15-4.06 2.15-7.16 5.45-10.66 11-1.39 2.19-9.16 16.05-12.15 20.82-3.79 6.07-7.13 7.98-14.66 9.75-6.13 1.45-8.27 2.1-11.92 4.02-5.04 2.66-10.05 6.86-15.46 13.3-5.43 6.46-6.53 9.69-4.55 10.66 1.7.84 4.48.57 12.1-.81l.7-.13c5.12-.93 7.82-1.27 10.17-1.12 3.21.2 4.92 1.48 4.7 4.11-.48 5.76.2 8.64 2.13 9.78 1.73 1.02 4.34.88 10.27-.31 2.35-.47 4 .78 5.14 3.47.83 1.95 1.27 4 2.07 8.8l.06.36c.94 5.65 1.55 8.11 2.72 9.98 1.46 2.3 3.52 2.6 6.84.46 4.14-2.66 14.69-8.77 14.81-8.85a41.9 41.9 0 0 0 8.46-6.54 47.89 47.89 0 0 0 4.6-5.48c4.32-5.95 7.81-7.23 15.74-7.5 4.66-.17 6.76-.47 9.46-1.67.9-.4 1.85-1.06 2.84-1.96a38.03 38.03 0 0 0 4.6-5.36c.96-1.3 4.4-6.16 4.93-6.87 7.5-10.31 13.22-15.09 20.83-15.09 7.24 0 11.02-1.6 11.64-4.24.54-2.32-1.36-5.55-4.97-8.64-6.75-5.79-8.17-8.79-8.17-16.38 0-2.67 1.64-4.74 5.39-7.86 3.8-3.17 4.23-3.56 4.78-4.73.5-1.06.25-1.99-.99-3.03-2.23-1.85-4.72-1.65-13.76.36-3.93.87-6.35 1.3-8.94 1.5-4.3.34-7.97-.18-11.2-1.8zm-28-3.9c5.65-2.82 8.96-2.2 12.9 1.37.56.5 2.6 2.47 3.02 2.87 4.2 3.89 8.07 5.71 14.3 5.71 11.37 0 14 1.41 16.1 8.09.26.83 1.35 4.6 1.66 5.62.8 2.63 1.64 5.03 2.7 7.6 2.13 5.17 2.64 8.32 1.72 10.24-.77 1.61-2.1 2.18-5.37 2.79-2.32.43-2.8.53-3.85.85-1.85.58-3.35 1.4-4.6 2.66-1 1-2.02 2.13-3.31 3.66-.6.71-2.91 3.5-3.46 4.14-7.2 8.54-12.43 12.35-19.59 12.35-3.76 0-6.95 1.28-10.59 4-1.84 1.37-11.62 10.31-15.22 13.06a73.09 73.09 0 0 1-8.95 5.88c-4.58 2.54-7.35 3.22-8.98 2.23-1.32-.8-1.65-2.07-1.94-5.5a52.53 52.53 0 0 0-.16-1.81c-.54-4.73-2.24-6.86-7.16-6.86-7.11 0-8.85-1.23-9.73-5.41-.96-4.61-2.1-6.7-6.55-9.67-3.97-2.65-4.31-5.42-1.52-8.22 2-2 4.63-3.5 11.35-6.87 6.61-3.3 9.2-4.8 11.1-6.68a39.09 39.09 0 0 0 5.3-6.48c.98-1.5 1.83-3.04 2.88-5.13l2.12-4.3c.91-1.83 1.72-3.37 2.61-4.98 5.74-10.32 10.37-14.78 23.22-21.2zm-22.34 21.7c-.89 1.59-1.69 3.12-2.6 4.94l-2.11 4.3a52.9 52.9 0 0 1-2.94 5.23 40.08 40.08 0 0 1-5.44 6.63c-2 2-4.62 3.51-11.35 6.87-6.6 3.3-9.2 4.8-11.1 6.69-2.33 2.34-2.08 4.37 1.38 6.67 4.7 3.14 5.96 5.46 6.97 10.3.78 3.7 2.09 4.62 8.75 4.62 5.5 0 7.57 2.57 8.15 7.75.06.5.09.82.17 1.84.25 3.06.55 4.17 1.46 4.72 1.2.74 3.69.13 7.98-2.25a72.09 72.09 0 0 0 8.82-5.8c3.55-2.7 13.34-11.65 15.24-13.07 3.79-2.83 7.18-4.19 11.18-4.19 6.77 0 11.8-3.67 18.83-12l3.45-4.13a60.07 60.07 0 0 1 3.37-3.72 11.72 11.72 0 0 1 5.01-2.91c1.1-.34 1.6-.45 3.97-.89 2.95-.55 4.07-1.02 4.65-2.23.76-1.59.28-4.5-1.74-9.43a84.46 84.46 0 0 1-2.74-7.69c-.31-1.03-1.4-4.8-1.66-5.61-1.95-6.2-4.16-7.39-15.14-7.39-6.5 0-10.61-1.93-14.98-5.98-.44-.4-2.46-2.37-3.01-2.86-3.65-3.3-6.52-3.85-11.79-1.21-12.67 6.33-17.15 10.65-22.78 20.8zm55.86 11.93c-2.98 6.45-16.78 15.26-26.74 15.26-5.33 0-7.56-2.98-7.11-7.86.32-3.48 2.1-7.91 3.93-10.61l1.52-2.32a44.95 44.95 0 0 1 1.88-2.7c3.66-4.8 7.85-7.45 13.62-7.45 9.06 0 15.75 9.52 12.9 15.68zm-.9-.42c2.52-5.47-3.65-14.26-12-14.26-5.4 0-9.33 2.48-12.82 7.06-.6.8-1.17 1.6-1.85 2.64 0 0-1.2 1.87-1.52 2.33-1.74 2.57-3.46 6.85-3.77 10.14-.4 4.33 1.43 6.77 6.12 6.77 9.57 0 23.02-8.58 25.83-14.68zm-69.67 20.74c2.08.18 4.44.81 5.88 1.8 2.12 1.47 2.2 3.6-.26 6.05-5.14 5.15-12.85 4.34-12.85-1.35 0-4.66 3.14-6.84 7.23-6.5zm-.09 1c-3.56-.3-6.14 1.5-6.14 5.5 0 4.58 6.53 5.26 11.15.65 2.03-2.04 1.98-3.43.4-4.52-1.27-.88-3.48-1.47-5.4-1.63zm29.59-225.95c4.64 2.35 17.27 8.24 19.39 9.43a24.14 24.14 0 0 1 7.05 5.64 45.03 45.03 0 0 1 3.75 5.2c2.4 3.78.04 7.66-6.2 11.63-4.97 3.16-12.18 6.3-21.95 9.82-4.84 1.74-19.63 6.68-21.1 7.2-6.59 2.33-14.85.1-25.14-5.86-3.93-2.27-8-5-12.94-8.54-2.23-1.61-9.5-6.99-10.7-7.85a81.21 81.21 0 0 0-8.63-5.7c-4.82-2.6-4.45-6.64.17-12.13 3.27-3.88 4.17-4.67 18.1-16.33a230.2 230.2 0 0 0 8.89-7.74 95.2 95.2 0 0 0 4.72-4.66c5.08-5.43 9.8-6.49 14.97-3.92 2.24 1.1 4.53 2.85 7.43 5.52 1.48 1.37 6.94 6.72 7.98 7.7 5.2 4.91 9.46 8.2 14.2 10.6zm-.46.9c-4.85-2.45-9.18-5.79-14.44-10.76-1.05-1-6.5-6.34-7.97-7.69-2.83-2.61-5.06-4.3-7.2-5.37-4.75-2.36-9-1.4-13.8 3.71a96.18 96.18 0 0 1-4.76 4.71c-2.48 2.3-5.16 4.62-8.92 7.77-13.86 11.6-14.77 12.4-17.98 16.21-4.28 5.08-4.58 8.4-.46 10.61 2.23 1.2 4.9 2.99 8.74 5.77 1.2.87 8.47 6.24 10.7 7.85a154.8 154.8 0 0 0 12.85 8.49c10.06 5.82 18.07 7.98 24.3 5.78 1.48-.52 16.27-5.47 21.1-7.2 9.7-3.5 16.86-6.61 21.75-9.72 5.84-3.71 7.9-7.1 5.9-10.26a44.09 44.09 0 0 0-3.67-5.08 23.16 23.16 0 0 0-6.78-5.42c-2.08-1.16-14.68-7.05-19.36-9.4zm-38.83 8.05c3.11-.37 5.7-.13 8.4.7 2.15.66 2.74.93 8.64 3.77 4.75 2.29 8.39 3.86 13.19 5.56 8.38 2.97 11.32 6.23 8.83 9.76-2.08 2.94-8.04 5.92-17.84 9.18-8.45 2.82-15.48 2.35-21.43-.9-4.65-2.55-8.33-6.5-12.15-12.3-2.9-4.41-2.73-8.2.16-11.06 2.48-2.45 6.87-4.07 12.2-4.7zm.12 1c-5.13.6-9.33 2.16-11.62 4.42-2.53 2.5-2.68 5.77-.02 9.8 3.73 5.68 7.3 9.51 11.8 11.97 5.7 3.11 12.43 3.57 20.62.84 9.59-3.2 15.44-6.12 17.34-8.82 1.94-2.75-.5-5.45-8.35-8.24-4.84-1.72-8.5-3.3-13.28-5.6-5.84-2.81-6.42-3.07-8.5-3.71a18.42 18.42 0 0 0-8-.66zM202.5 500.38c0 4.78-1.45 7.56-4.43 8.93-2.29 1.05-4.55 1.23-10.79 1.2l-1.78-.01c-9.19 0-17-7.65-17-15.5 0-7.59 10.6-10.51 19.74-5.44 2.78 1.55 4.21 1.94 8.57 2.75 4.44.83 5.69 2.27 5.69 8.07zm-1 0c0-5.3-.9-6.34-4.88-7.08-4.45-.83-5.96-1.25-8.86-2.86-8.57-4.76-18.26-2.1-18.26 4.56 0 7.3 7.36 14.5 16 14.5h1.79c6.06.04 8.26-.14 10.36-1.1 2.6-1.2 3.85-3.6 3.85-8.02zm33.33-117.85c3.71-1.31 8.7-2.7 16.1-4.55 2.58-.65 16.53-4.04 20.56-5.05 19.59-4.93 31.55-8.9 38.23-13.35 14.93-9.95 36.87-33.88 43.83-47.8 2.25-4.5 4.65-6.38 7.68-6.25 1.26.06 2.61.45 4.32 1.2a50.81 50.81 0 0 1 3.54 1.7l1.26.63c4.78 2.34 8.38 3.44 12.65 3.44 7.2 0 10.01 3.07 8.35 7.91-1.4 4.06-5.92 8.91-11.1 12.02-8.3 4.98-11.75 17.3-11.75 33.57 0 3.59-1.37 6.28-3.98 8.36-1.98 1.58-4.2 2.6-8.47 4.16l-1.02.37c-4.85 1.75-6.98 2.77-8.68 4.46-5.09 5.1-12.54 7.15-20.35 7.15-1.38 0-2.47.92-3.99 3.1-.29.41-1.32 1.95-1.47 2.18-2.68 3.92-4.93 5.72-8.54 5.72-7.84 0-10.74.93-21.76 6.94-5.18 2.82-8.8 3.58-14.66 3.68-.26 0-.47 0-.92.02-4.82.06-7.12.3-10.51 1.34a73.43 73.43 0 0 0-8.89 3.56c-2.17 1-10.53 5.01-10.23 4.87-7.79 3.7-13.32 5.98-18.9 7.57-12.41 3.55-18.58 2.24-27.42-4.07-2.58-1.85-2.72-4.43-.83-7.62 1.45-2.45 3.9-5.09 8.08-8.97l1.78-1.64c3.92-3.6 4.48-4.11 5.9-5.53 2.32-2.32 3.12-3.5 5.48-7.63 1.93-3.36 3.37-5.11 6.27-7.06 2.3-1.54 5.34-2.98 9.44-4.43zm.34.94c-4.03 1.42-7 2.83-9.22 4.32-2.75 1.85-4.1 3.49-5.96 6.73-2.4 4.2-3.24 5.44-5.64 7.83-1.43 1.44-2 1.96-5.94 5.57l-1.77 1.63c-4.1 3.82-6.52 6.41-7.9 8.75-1.65 2.79-1.54 4.8.55 6.3 8.6 6.14 14.46 7.38 26.57 3.92 5.5-1.57 11-3.84 18.74-7.51-.3.14 8.06-3.88 10.24-4.88a74.3 74.3 0 0 1 9.01-3.6c3.51-1.09 5.89-1.33 10.8-1.4h.91c5.72-.1 9.18-.83 14.2-3.57 11.16-6.08 14.2-7.06 22.24-7.06 3.19 0 5.2-1.6 7.71-5.28l1.48-2.2c1.7-2.43 3-3.52 4.81-3.52 7.57 0 14.78-2 19.65-6.85 1.83-1.84 4.04-2.9 9.04-4.7l1.02-.37c8.6-3.13 11.79-5.67 11.79-11.58 0-16.6 3.53-29.2 12.24-34.43 5-3 9.35-7.67 10.66-11.48 1.42-4.13-.83-6.59-7.4-6.59-4.45 0-8.19-1.14-13.09-3.54-7.52-3.67-6.78-3.34-8.72-3.43-2.58-.1-4.65 1.52-6.74 5.7-7.04 14.07-29.1 38.14-44.17 48.19-6.81 4.54-18.84 8.52-38.55 13.48-4.03 1.02-17.98 4.4-20.56 5.05-7.37 1.84-12.33 3.23-16 4.52zM252 387.5c2.08 0 4-.2 7.25-.69 5.22-.77 6.64-.9 8.46-.5 2.52.56 3.79 2.35 3.79 5.69 0 4.05-2.27 7.29-6.62 10.11-3.24 2.1-6.53 3.53-14.15 6.4l-.27.1-2.28.86c-3.04 1.16-5.27 2.52-9.33 5.43l-.8.57c-8.19 5.88-13.35 8.03-23.05 8.03-4.98 0-6.88-2.03-5.75-5.62.87-2.81 3.58-6.56 7.8-11.13 1.26-1.37 2.64-2.8 4.15-4.3 3.17-3.14 11.25-10.61 11.45-10.8.46-.47.93-.89 1.4-1.26 3.38-2.71 5.77-3.08 14.18-2.93 1.65.03 2.63.04 3.77.04zm0 1c-1.15 0-2.13-.01-3.79-.04-8.18-.14-10.4.2-13.54 2.71-.44.35-.88.74-1.32 1.18-.2.21-8.3 7.69-11.45 10.82a134.6 134.6 0 0 0-4.12 4.26c-4.12 4.47-6.76 8.12-7.58 10.75-.9 2.88.45 4.32 4.8 4.32 9.46 0 14.44-2.07 22.46-7.84l.8-.57c4.13-2.96 6.42-4.36 9.56-5.56l2.3-.86.25-.1c7.55-2.84 10.8-4.25 13.97-6.3 4.08-2.65 6.16-5.6 6.16-9.27 0-2.89-.97-4.26-3-4.7-1.65-.37-3.05-.25-8.1.5-3.3.5-5.26.7-7.4.7zm112.47-45.34c-1.88 5.44-1.98 6.76-.98 12.76 1.18 7.06-1.38 16.58-5.49 16.58a16.89 16.89 0 0 0-1.51.07l-.64.04c-2.86.18-4.83.17-6.94-.17-6.55-1.06-10.41-5.14-10.41-13.44 0-13.9 2.14-19.69 8.13-26.33a21.9 21.9 0 0 0 2.52-3.75c.59-1.03 2.78-5.13 2.72-5.01 4.44-8.14 7.71-11.53 12.25-10.4 1.17.3 2.2.77 3.58 1.59l1.39.84a20 20 0 0 0 3.1 1.6c.7.27 1.8.32 4.75.26l.72-.01c3.16-.05 4.78.08 5.83.66 1.61.89 1.2 2.56-1.14 4.9a215.9 215.9 0 0 1-3.86 3.76c-10.6 10.1-12.75 12.4-14.02 16.05zm-.94-.32c1.34-3.9 3.46-6.17 14.27-16.46 1.55-1.47 2.73-2.62 3.85-3.73 1.94-1.95 2.17-2.88 1.35-3.33-.82-.45-2.37-.58-5.32-.53l-.72.01c-3.14.06-4.26.02-5.14-.34-1.06-.41-1.97-.9-3.25-1.67l-1.38-.83a12.1 12.1 0 0 0-3.31-1.47c-3.88-.97-6.92 2.17-11.13 9.9.07-.13-2.14 3.98-2.73 5.02a22.71 22.71 0 0 1-2.65 3.92c-5.81 6.47-7.87 12-7.87 25.67 0 7.79 3.48 11.47 9.57 12.45 2.01.33 3.92.34 6.71.16a371.33 371.33 0 0 0 1.23-.07c.42-.03.73-.04.99-.04 3.2 0 5.6-8.9 4.5-15.42-1.02-6.16-.91-7.64 1.03-13.24zm-9.26 12.42c.58.52 2.5 1.9 2.55 1.93 1.96 1.57 2.04 3.31.01 6.36-3.74 5.64-8.83 3.09-8.83-4.55 0-3.81.51-5.67 2.07-6.02 1.18-.26 2 .3 4.2 2.28zm-1.34 1.48c-1.5-1.35-2.23-1.85-2.43-1.8-.17.03-.5 1.23-.5 4.06 0 5.87 2.67 7.21 5.17 3.45 1.5-2.26 1.47-2.84.4-3.7.03.03-1.95-1.4-2.64-2zm222.9-130.19c2.2-1.1 3.67-1.66 5.88-2.36l.28-.09a48.92 48.92 0 0 0 8.79-3.55c4.17-2.08 6.35-1.88 6.96.84.44 2 .2 4.01-1.25 12.7-2.27 13.62-9.16 26.14-21.17 36.3-4.3 3.63-7.41 4.39-9.75 2.44-1.88-1.57-3.1-4.57-4.61-10.48-.3-1.15-1.43-5.83-1.72-6.96a114.18 114.18 0 0 0-2.71-9.22c-2.4-6.82-3.03-10.78-2.1-12.94.77-1.83 2.08-2.24 5.6-2.45 1.49-.09 2.09-.14 2.97-.28l1.95-.33c.72-.12 1.22-.2 1.68-.29 1.1-.2 1.92-.38 2.71-.6 1.7-.49 3.42-1.2 6.49-2.73zm.44.9c-3.11 1.54-4.88 2.29-6.65 2.79-.84.23-1.69.42-2.81.63a108.77 108.77 0 0 1-3.81.63c-.77.13-1.39.19-2.92.28-3.13.18-4.17.51-4.74 1.85-.78 1.84-.2 5.62 2.13 12.2a115.12 115.12 0 0 1 2.74 9.31l1.72 6.96c1.46 5.7 2.62 8.58 4.28 9.96 1.87 1.56 4.49.93 8.47-2.44 11.82-10 18.6-22.3 20.83-35.7 1.4-8.45 1.65-10.51 1.25-12.31-.41-1.87-1.86-2-5.54-.16a49.87 49.87 0 0 1-8.93 3.6l-.28.1a35.4 35.4 0 0 0-5.74 2.3zm-4.5 6.58c1.37-.32 2.5-.75 3.9-1.42.35-.18 2.57-1.31 3.32-1.67 1.5-.71 2.97-1.31 4.7-1.89 2.7-.9 4.64-.77 5.88.4.98.94 1.34 2.26 1.41 4.18.02.4.02.7.02 1.37 0 5.63-4.63 16.88-11.34 22.75-4.34 3.8-7.31 4.67-9.92 2.52-2.06-1.7-3.5-4.65-6.67-12.91-1.86-4.83-2.05-8.1-.68-10.2 1.12-1.7 2.9-2.36 5.83-2.7l1.26-.12c1.19-.12 1.75-.19 2.3-.31zm-2.1 2.3l-1.22.12c-2.4.27-3.7.76-4.39 1.81-.93 1.43-.78 4.1.87 8.38 3.02 7.84 4.41 10.71 6.08 12.09 1.63 1.34 3.64.75 7.33-2.48C584.6 250.77 589 240.08 589 235c0-.64 0-.93-.02-1.29-.05-1.44-.3-2.33-.79-2.8-.6-.57-1.8-.65-3.87.04a37.95 37.95 0 0 0-4.47 1.8c-.72.34-2.93 1.47-3.32 1.66a19.54 19.54 0 0 1-4.3 1.56c-.66.16-1.28.24-2.56.36zm-227.73-88.98c-1.59 4.3-3.54 7.25-7.14 11.4l-2.6 2.97a67.02 67.02 0 0 0-2.63 3.23 46.4 46.4 0 0 0-4.68 7.5c-2.85 5.7-7.14 10.18-12.85 13.89-4.25 2.76-8.25 4.62-15.67 7.59-11.01 4.4-16.43 1.26-27.22-16.4-2.86-4.69-8.8-8.63-17.98-12.66-3-1.33-12.88-5.24-14.43-5.92-4.96-2.18-7.04-3.72-6.42-5.85.67-2.32 5.3-4.05 15.48-6.08 16.63-3.32 26.93-3.82 39.93-3.02 7.9.49 9.67.5 12.74-.26 1.99-.48 3.92-1.3 6-2.6l2.79-1.71c9.86-6.14 12.94-7.96 17.3-9.9 6.03-2.71 10.57-3.32 13.94-1.4 7.2 4.12 7.68 7.7 3.44 19.22zm-1.88-.7c3.95-10.7 3.6-13.26-2.56-16.78-2.66-1.52-6.62-.99-12.12 1.48-4.24 1.9-7.3 3.7-17.07 9.77l-2.79 1.73a22.6 22.6 0 0 1-6.57 2.84c-3.36.81-5.22.8-13.34.3-12.84-.78-22.97-.29-39.41 3-4.9.97-8.45 1.88-10.79 2.75-2.03.76-3.04 1.45-3.17 1.91-.16.57 1.48 1.79 5.3 3.46 1.5.67 11.39 4.58 14.44 5.93 9.52 4.19 15.74 8.3 18.87 13.44 10.35 16.93 14.87 19.56 24.78 15.6 7.3-2.93 11.21-4.75 15.33-7.42 5.42-3.53 9.47-7.75 12.15-13.1 1.44-2.9 3.02-5.4 4.86-7.82a68.95 68.95 0 0 1 2.72-3.33l2.6-2.97c3.46-3.99 5.28-6.75 6.77-10.79zm-6.64-.39c-7.94 12.8-18.53 21.75-33.3 25.23-7.82 1.83-12.47-.79-13.12-5.93-.55-4.45 2.29-9.06 6-9.06 3.02 0 5.6-1.68 15.38-9.16 1.47-1.12 2.57-1.96 3.66-2.74 4.4-3.2 7.77-5.17 10.82-6.08 5.57-1.67 9.33-2.15 11.35-1.22 2.5 1.14 2.22 4.13-.79 8.96zm-.84-.52c2.72-4.4 2.94-6.74 1.21-7.53-1.71-.79-5.32-.33-10.65 1.27-2.9.87-6.2 2.79-10.51 5.92-1.08.79-2.18 1.62-3.65 2.74-10.08 7.72-12.62 9.36-15.98 9.36-3.02 0-5.5 4.02-5 7.94.56 4.5 4.62 6.78 11.89 5.07 14.48-3.4 24.86-12.18 32.69-24.77zM461.17 33.53c13.88 4.96 20.75 4.96 31.62.01 3.02-1.37 5.47-2.94 11-6.82 5.57-3.92 8.05-5.51 11.14-6.92 4.14-1.88 7.78-2.38 11.22-1.28 3.92 1.26 6.2 12.3 6.78 28.45.5 14.2-.52 28.93-2.46 34.2-1.82 4.93-5.86 8.17-11.51 10.02A41.7 41.7 0 0 1 506 93.01c-5.79 0-9 2.4-12.2 7.64-.37.59-1.55 2.6-1.71 2.87-1.75 2.9-3.05 4.33-4.93 4.95-.94.32-2.07.83-3.87 1.74l-2.43 1.23c-1.03.53-1.87.94-2.7 1.34-6.43 3.1-11.73 4.72-17.16 4.72-5.71 0-10.04 2.09-14.02 5.92-1.16 1.11-4.2 4.53-4.63 4.94-2.54 2.44-5.93 4.24-10.85 6.1-1.4.52-5.98 2.13-6.25 2.22l-2.06.78c-.89.36-1.78.63-2.7.81-5.55 1.14-11.14-.54-17.98-4.42-1.27-.73-5.13-3.06-5.76-3.42-2.05-1.16-4.12-1.53-9.09-1.9l-1.73-.15c-4.78-.4-7.68-1.14-10.22-2.97-5-3.61-6.77-7.76-5.65-12.33 1.33-5.42 6.5-11.02 14.85-17.28a169.2 169.2 0 0 1 6.5-4.61c-.33.23 4.33-2.92 5.3-3.6 2.73-1.91 4.8-3.9 12.75-12.04l1.09-1.1c3.49-3.56 5.89-5.89 8.12-7.83 2.9-2.5 4.72-5.95 7.5-13.05l.63-1.61c2.7-6.92 4.28-10 6.87-12.33 1.42-1.28 6.68-6.54 7.93-7.5 3.98-3 8.01-2.73 19.57 1.4zm-.34.94c-11.26-4.02-15-4.28-18.62-1.53-1.19.9-6.4 6.11-7.88 7.43-2.42 2.18-3.96 5.19-6.6 11.95l-.63 1.61c-2.83 7.26-4.72 10.8-7.77 13.45a141.85 141.85 0 0 0-9.16 8.87c-8.02 8.2-10.08 10.2-12.88 12.16-.99.69-5.65 3.84-5.31 3.6-2.5 1.71-4.52 3.13-6.47 4.59-8.17 6.13-13.23 11.6-14.48 16.72-1.02 4.15.58 7.9 5.26 11.27 2.36 1.7 5.11 2.4 9.72 2.8l1.73.13c5.12.4 7.28.78 9.5 2.05.65.36 4.5 2.7 5.76 3.4 6.66 3.78 12.04 5.4 17.29 4.32.86-.17 1.7-.42 2.52-.75a67 67 0 0 1 2.1-.8c.28-.1 4.86-1.7 6.24-2.22 4.8-1.8 8.08-3.56 10.5-5.88.4-.38 3.44-3.8 4.63-4.94 4.16-4 8.72-6.2 14.72-6.2 5.25 0 10.42-1.59 16.73-4.62.82-.4 1.65-.8 2.68-1.33.12-.06 1.93-.99 2.43-1.23 1.84-.93 3-1.46 4-1.8 1.6-.52 2.76-1.82 4.39-4.52l1.7-2.88c3.39-5.5 6.87-8.11 13.07-8.11 4.45 0 8.73-.49 12.64-1.77 5.4-1.76 9.2-4.8 10.9-9.41 1.87-5.11 2.9-19.75 2.39-33.83-.56-15.53-2.81-26.48-6.08-27.52-3.18-1.02-6.57-.55-10.5 1.23-3.02 1.37-5.47 2.94-11 6.83-5.57 3.92-8.05 5.5-11.14 6.92-11.13 5.05-18.26 5.05-32.38.01zM475 55c5.38 0 7.55-.21 9.72-.96 1.26-.43 9.95-4.8 14.88-6.96 1.9-.82 3.56-2.44 6.6-6.04 2.56-3.04 3.19-3.75 4.4-4.84 3.7-3.35 7.07-3.28 10.22 1.23 6.23 8.9 5.61 15.94.07 27.02a71.26 71.26 0 0 0-2.5 5.48c-.32.8-1 2.7-1.09 2.9-.17.45-.34.81-.54 1.17-.63 1.14-1.56 2.21-4.05 4.7-2.4 2.4-5.16 3.27-11.68 4.33-1.81.3-2.2.36-3 .51-6.02 1.1-9.6 2.69-12.24 6.07-3.57 4.59-7.9 7.48-14.98 10.74-.55.24-1.1.5-1.8.8l-1.78.8a60.08 60.08 0 0 0-7.7 3.9c-2.57 1.6-4.79 2.35-9.42 3.46-8.58 2.06-12.28 3.76-17.37 9.36-5.12 5.64-10.17 7.64-16.63 6.7-5.36-.79-10.63-3.01-23.56-9.48-6.3-3.15-6.43-7.78-1.5-13.56 3.38-3.94 3.52-4.06 19.4-16.44 8.12-6.33 12.97-10.57 16.63-14.88 2.53-2.98 4.2-5.73 4.96-8.3 5.5-18.3 12.5-21.98 22.78-15.56 1.95 1.22 6.61 4.55 7.18 4.9 3.36 2.15 6.52 2.95 13 2.95zm0 2c-6.84 0-10.37-.89-14.08-3.26-.63-.4-5.27-3.71-7.16-4.9-9.05-5.65-14.66-2.7-19.8 14.45-.86 2.87-2.67 5.85-5.35 9.01-3.78 4.45-8.7 8.75-16.94 15.17-15.66 12.21-15.86 12.38-19.1 16.16-4.17 4.9-4.09 8 .88 10.48 12.71 6.35 17.89 8.54 22.94 9.28 5.78.84 10.18-.9 14.87-6.06 5.42-5.96 9.45-7.82 18.38-9.96 4.43-1.07 6.5-1.76 8.83-3.22a61.7 61.7 0 0 1 7.94-4.02l1.78-.8 1.78-.8c6.82-3.13 10.91-5.87 14.24-10.14 3-3.87 7-5.64 13.46-6.82.83-.15 1.21-.21 3.04-.51 6.1-1 8.6-1.78 10.58-3.77 2.36-2.36 3.21-3.34 3.72-4.26.15-.27.29-.56.44-.94.06-.15.75-2.06 1.09-2.9.64-1.6 1.45-3.4 2.57-5.64 5.24-10.49 5.8-16.8.07-24.98-2.4-3.44-4.37-3.48-7.24-.89-1.11 1-1.73 1.7-4.22 4.65-3.24 3.85-5.04 5.59-7.32 6.59-4.82 2.1-13.62 6.53-15.03 7.01-2.44.84-4.79 1.07-10.37 1.07zm-12.7 8.6c5.47 3.9 10.34 3.72 18.23.88 5.39-1.94 5.92-2.1 7.7-2.1 2.5-.01 4.21 1.36 5.24 4.46 1.66 4.98-2.32 8.52-12.3 12.68-2.7 1.13-16.25 6.18-20 7.73-7.86 3.24-13.93 6.42-18.87 10.15-13.02 9.84-18.36 11.93-23.71 9.68a24.67 24.67 0 0 1-3.62-1.98l-1.99-1.28a90.4 90.4 0 0 0-2.24-1.4c-3.33-2-2.82-4.28.85-7.34 1.35-1.13 10.66-7.61 13.53-9.91 7.1-5.69 11.91-11.47 14.41-18.34 3.07-8.45 4.89-12.1 6.8-13.39 1.73-1.16 3.36-.53 6.18 1.9.63.56 3.4 3.08 4.11 3.7 1.93 1.7 3.71 3.15 5.67 4.55zm-.6.8c-1.98-1.42-3.79-2.88-5.74-4.6-.73-.64-3.48-3.16-4.1-3.7-2.5-2.16-3.75-2.65-4.97-1.83-1.66 1.11-3.44 4.7-6.42 12.9-2.57 7.07-7.5 12.99-14.72 18.78-2.91 2.33-12.21 8.8-13.52 9.9-3.22 2.68-3.56 4.17-.97 5.72l2.26 1.4 1.99 1.28c1.47.93 2.48 1.5 3.47 1.91 4.9 2.07 9.96.07 22.72-9.56 5.02-3.79 11.15-7 19.1-10.28 3.76-1.55 17.3-6.6 20-7.72 9.5-3.97 13.14-7.2 11.73-11.44-.9-2.71-2.25-3.8-4.3-3.79-1.6 0-2.15.17-7.36 2.05-8.17 2.94-13.34 3.14-19.16-1.01z'%3E%3C/path%3E%3C/svg%3E\")", - texture: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='FILLCOLOR' fill-opacity='FILLOPACITY' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E\")", - jupiter: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='52' height='52' viewBox='0 0 52 52'%3E%3Cpath fill='FILLCOLOR' fill-opacity='FILLOPACITY' d='M0 17.83V0h17.83a3 3 0 0 1-5.66 2H5.9A5 5 0 0 1 2 5.9v6.27a3 3 0 0 1-2 5.66zm0 18.34a3 3 0 0 1 2 5.66v6.27A5 5 0 0 1 5.9 52h6.27a3 3 0 0 1 5.66 0H0V36.17zM36.17 52a3 3 0 0 1 5.66 0h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 0 1 0-5.66V52H36.17zM0 31.93v-9.78a5 5 0 0 1 3.8.72l4.43-4.43a3 3 0 1 1 1.42 1.41L5.2 24.28a5 5 0 0 1 0 5.52l4.44 4.43a3 3 0 1 1-1.42 1.42L3.8 31.2a5 5 0 0 1-3.8.72zm52-14.1a3 3 0 0 1 0-5.66V5.9A5 5 0 0 1 48.1 2h-6.27a3 3 0 0 1-5.66-2H52v17.83zm0 14.1a4.97 4.97 0 0 1-1.72-.72l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1 0-5.52l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43c.53-.35 1.12-.6 1.72-.72v9.78zM22.15 0h9.78a5 5 0 0 1-.72 3.8l4.44 4.43a3 3 0 1 1-1.42 1.42L29.8 5.2a5 5 0 0 1-5.52 0l-4.43 4.44a3 3 0 1 1-1.41-1.42l4.43-4.43a5 5 0 0 1-.72-3.8zm0 52c.13-.6.37-1.19.72-1.72l-4.43-4.43a3 3 0 1 1 1.41-1.41l4.43 4.43a5 5 0 0 1 5.52 0l4.43-4.43a3 3 0 1 1 1.42 1.41l-4.44 4.43c.36.53.6 1.12.72 1.72h-9.78zm9.75-24a5 5 0 0 1-3.9 3.9v6.27a3 3 0 1 1-2 0V31.9a5 5 0 0 1-3.9-3.9h-6.27a3 3 0 1 1 0-2h6.27a5 5 0 0 1 3.9-3.9v-6.27a3 3 0 1 1 2 0v6.27a5 5 0 0 1 3.9 3.9h6.27a3 3 0 1 1 0 2H31.9z'%3E%3C/path%3E%3C/svg%3E\")", - architect: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='199' viewBox='0 0 100 199'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M0 199V0h1v1.99L100 199h-1.12L1 4.22V199H0zM100 2h-.12l-1-2H100v2z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E\")", - cutout: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M12 0h18v6h6v6h6v18h-6v6h-6v6H12v-6H6v-6H0V12h6V6h6V0zm12 6h-6v6h-6v6H6v6h6v6h6v6h6v-6h6v-6h6v-6h-6v-6h-6V6zm-6 12h6v6h-6v-6zm24 24h6v6h-6v-6z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E\")", - hideout: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M0 38.59l2.83-2.83 1.41 1.41L1.41 40H0v-1.41zM0 1.4l2.83 2.83 1.41-1.41L1.41 0H0v1.41zM38.59 40l-2.83-2.83 1.41-1.41L40 38.59V40h-1.41zM40 1.41l-2.83 2.83-1.41-1.41L38.59 0H40v1.41zM20 18.6l2.83-2.83 1.41 1.41L21.41 20l2.83 2.83-1.41 1.41L20 21.41l-2.83 2.83-1.41-1.41L18.59 20l-2.83-2.83 1.41-1.41L20 18.59z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "graph-paper": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - yyy: "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='96' viewBox='0 0 60 96'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M36 10a6 6 0 0 1 12 0v12a6 6 0 0 1-6 6 6 6 0 0 0-6 6 6 6 0 0 1-12 0 6 6 0 0 0-6-6 6 6 0 0 1-6-6V10a6 6 0 1 1 12 0 6 6 0 0 0 12 0zm24 78a6 6 0 0 1-6-6 6 6 0 0 0-6-6 6 6 0 0 1-6-6V58a6 6 0 1 1 12 0 6 6 0 0 0 6 6v24zM0 88V64a6 6 0 0 0 6-6 6 6 0 0 1 12 0v12a6 6 0 0 1-6 6 6 6 0 0 0-6 6 6 6 0 0 1-6 6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - squares: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='Artboard-5' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='nonzero'%3E%3Cpath d='M6 18h12V6H6v12zM4 4h16v16H4V4z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "falling-triangles": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='72' viewBox='0 0 36 72'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M2 6h12L8 18 2 6zm18 36h12l-6 12-6-12z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "piano-man": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='70' height='46' viewBox='0 0 70 46'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpolygon points='68 44 62 44 62 46 56 46 56 44 52 44 52 46 46 46 46 44 40 44 40 46 38 46 38 44 32 44 32 46 26 46 26 44 22 44 22 46 16 46 16 44 12 44 12 46 6 46 6 44 0 44 0 42 8 42 8 28 6 28 6 0 12 0 12 28 10 28 10 42 18 42 18 28 16 28 16 0 22 0 22 28 20 28 20 42 28 42 28 28 26 28 26 0 32 0 32 28 30 28 30 42 38 42 38 0 40 0 40 42 48 42 48 28 46 28 46 0 52 0 52 28 50 28 50 42 58 42 58 28 56 28 56 0 62 0 62 28 60 28 60 42 68 42 68 0 70 0 70 46 68 46'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "pie-factory": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60' viewBox='0 0 60 60'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='nonzero'%3E%3Cpath d='M29 58.58l7.38-7.39A30.95 30.95 0 0 1 29 37.84a30.95 30.95 0 0 1-7.38 13.36l7.37 7.38zm1.4 1.41l.01.01h-2.84l-7.37-7.38A30.95 30.95 0 0 1 6.84 60H0v-1.02a28.9 28.9 0 0 0 18.79-7.78L0 32.41v-4.84L18.78 8.79A28.9 28.9 0 0 0 0 1.02V0h6.84a30.95 30.95 0 0 1 13.35 7.38L27.57 0h2.84l7.39 7.38A30.95 30.95 0 0 1 51.16 0H60v27.58-.01V60h-8.84a30.95 30.95 0 0 1-13.37-7.4L30.4 60zM29 1.41l-7.4 7.38A30.95 30.95 0 0 1 29 22.16 30.95 30.95 0 0 1 36.38 8.8L29 1.4zM58 1A28.9 28.9 0 0 0 39.2 8.8L58 27.58V1.02zm-20.2 9.2A28.9 28.9 0 0 0 30.02 29h26.56L37.8 10.21zM30.02 31a28.9 28.9 0 0 0 7.77 18.79l18.79-18.79H30.02zm9.18 20.2A28.9 28.9 0 0 0 58 59V32.4L39.2 51.19zm-19-1.4a28.9 28.9 0 0 0 7.78-18.8H1.41l18.8 18.8zm7.78-20.8A28.9 28.9 0 0 0 20.2 10.2L1.41 29h26.57z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - dominos: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='126' height='84' viewBox='0 0 126 84'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M126 83v1H0v-2h40V42H0v-2h40V0h2v40h40V0h2v40h40V0h2v83zm-2-1V42H84v40h40zM82 42H42v40h40V42zm-50-6a4 4 0 1 1 0-8 4 4 0 0 1 0 8zM8 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm96 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm-42 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm30-12a4 4 0 1 1 0-8 4 4 0 0 1 0 8zM20 54a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm12 24a4 4 0 1 1 0-8 4 4 0 0 1 0 8zM8 54a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm24 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8zM8 78a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm12 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm54 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8zM50 54a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm24 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8zM50 78a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm54-12a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm12 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8zM92 54a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm24 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8zM92 78a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm24-42a4 4 0 1 1 0-8 4 4 0 0 1 0 8z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - hexagons: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='49' viewBox='0 0 28 49'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='hexagons' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='nonzero'%3E%3Cpath d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "charlie-brown": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='12' viewBox='0 0 20 12'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='charlie-brown' fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M9.8 12L0 2.2V.8l10 10 10-10v1.4L10.2 12h-.4zm-4 0L0 6.2V4.8L7.2 12H5.8zm8.4 0L20 6.2V4.8L12.8 12h1.4zM9.8 0l.2.2.2-.2h-.4zm-4 0L10 4.2 14.2 0h-1.4L10 2.8 7.2 0H5.8z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - autumn: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='88' height='24' viewBox='0 0 88 24'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='autumn' fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M10 0l30 15 2 1V2.18A10 10 0 0 0 41.76 0H39.7a8 8 0 0 1 .3 2.18v10.58L14.47 0H10zm31.76 24a10 10 0 0 0-5.29-6.76L4 1 2 0v13.82a10 10 0 0 0 5.53 8.94L10 24h4.47l-6.05-3.02A8 8 0 0 1 4 13.82V3.24l31.58 15.78A8 8 0 0 1 39.7 24h2.06zM78 24l2.47-1.24A10 10 0 0 0 86 13.82V0l-2 1-32.47 16.24A10 10 0 0 0 46.24 24h2.06a8 8 0 0 1 4.12-4.98L84 3.24v10.58a8 8 0 0 1-4.42 7.16L73.53 24H78zm0-24L48 15l-2 1V2.18A10 10 0 0 1 46.24 0h2.06a8 8 0 0 0-.3 2.18v10.58L73.53 0H78z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - temple: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='152' height='152' viewBox='0 0 152 152'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='temple' fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M152 150v2H0v-2h28v-8H8v-20H0v-2h8V80h42v20h20v42H30v8h90v-8H80v-42h20V80h42v40h8V30h-8v40h-42V50H80V8h40V0h2v8h20v20h8V0h2v150zm-2 0v-28h-8v20h-20v8h28zM82 30v18h18V30H82zm20 18h20v20h18V30h-20V10H82v18h20v20zm0 2v18h18V50h-18zm20-22h18V10h-18v18zm-54 92v-18H50v18h18zm-20-18H28V82H10v38h20v20h38v-18H48v-20zm0-2V82H30v18h18zm-20 22H10v18h18v-18zm54 0v18h38v-20h20V82h-18v20h-20v20H82zm18-20H82v18h18v-18zm2-2h18V82h-18v18zm20 40v-18h18v18h-18zM30 0h-2v8H8v20H0v2h8v40h42V50h20V8H30V0zm20 48h18V30H50v18zm18-20H48v20H28v20H10V30h20V10h38v18zM30 50h18v18H30V50zm-2-40H10v18h18V10z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "stamp-collection": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='77' height='107' viewBox='0 0 77 107'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='stamp-collection' fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M46 101a5 5 0 0 1 5 5h5a5 5 0 0 1 10 0h5a5 5 0 0 1 5-5v-5a5 5 0 0 1 0-10v-5a5 5 0 0 1 0-10v-5a5 5 0 0 1 0-10v-5a5 5 0 0 1 0-10v-5a5 5 0 0 1 0-10v-5a5 5 0 0 1 0-10V6a5 5 0 0 1-5-5h-5a5 5 0 0 1-10 0h-5a5 5 0 0 1-10 0h-5a5 5 0 0 1-10 0h-5a5 5 0 0 1-10 0H6a5 5 0 0 1-5 5v5a5 5 0 0 1 0 10v5a5 5 0 0 1 0 10v5a5 5 0 0 1 0 10v5a5 5 0 0 1 0 10v5a5 5 0 0 1 0 10v5a5 5 0 0 1 0 10v5a5 5 0 0 1 5 5h5a5 5 0 0 1 10 0h5a5 5 0 0 1 10 0h5a5 5 0 0 1 5-5zm15-2a7 7 0 0 0-6.71 5h-1.58a7 7 0 0 0-13.42 0h-1.58a7 7 0 0 0-13.42 0h-1.58a7 7 0 0 0-13.42 0H7.71A7.01 7.01 0 0 0 3 99.29v-1.58a7 7 0 0 0 0-13.42v-1.58a7 7 0 0 0 0-13.42v-1.58a7 7 0 0 0 0-13.42v-1.58a7 7 0 0 0 0-13.42v-1.58a7 7 0 0 0 0-13.42v-1.58A7 7 0 0 0 3 9.29V7.71A7.02 7.02 0 0 0 7.71 3h1.58a7 7 0 0 0 13.42 0h1.58a7 7 0 0 0 13.42 0h1.58a7 7 0 0 0 13.42 0h1.58a7 7 0 0 0 13.42 0h1.58A7.02 7.02 0 0 0 74 7.71v1.58a7 7 0 0 0 0 13.42v1.58a7 7 0 0 0 0 13.42v1.58a7 7 0 0 0 0 13.42v1.58a7 7 0 0 0 0 13.42v1.58a7 7 0 0 0 0 13.42v1.58a7 7 0 0 0 0 13.42v1.58a7.01 7.01 0 0 0-4.71 4.71h-1.58A7 7 0 0 0 61 99zM12 12h53v83H12V12zm51 81H14V14h49v79z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "death-star": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='105' viewBox='0 0 80 105'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='death-star' fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M20 10a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V10zm15 35a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V45zM20 75a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V75zm30-65a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V10zm0 65a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V75zM35 10a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V10zM5 45a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V45zm0-35a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V10zm60 35a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V45zm0-35a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V10z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "church-on-sunday": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='church-on-sunday' fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M77.17 0H80v2.83l-.1.1A39.9 39.9 0 0 1 74.64 20a39.9 39.9 0 0 1 5.24 17.06l.11.11v2.89c-.01 6.9-1.8 13.79-5.35 19.94A39.96 39.96 0 0 1 80 79.94V80h-2.83L66.84 69.66a39.83 39.83 0 0 1-24.1 10.25l.09.09h-5.66l.1-.1c-8.7-.58-17.22-4-24.1-10.23L2.82 80H0V79.94c.01-6.9 1.8-13.8 5.35-19.94A39.96 39.96 0 0 1 0 40.06V37.17l.1-.1A39.9 39.9 0 0 1 5.36 20 39.9 39.9 0 0 1 .1 2.94L0 2.83V0h2.83l-.1.1a39.83 39.83 0 0 1 24.1 10.24L37.18 0H40c0 6.92-1.78 13.83-5.35 20A39.96 39.96 0 0 1 40 40c0-6.92 1.78-13.83 5.35-20A39.96 39.96 0 0 1 40 0h2.83l10.33 10.34A39.83 39.83 0 0 1 77.26.09L77.17 0zm.77 77.94c-.3-5.52-1.8-11-4.49-16a40.18 40.18 0 0 1-5.17 6.34l9.66 9.66zm-12.52-9.7l-6.83-6.83-5.46 5.46-1.41 1.41-9.66 9.66c8.4-.45 16.69-3.68 23.36-9.7zm-23.07 6.58l7.99-7.98a40.05 40.05 0 0 1-3.79-4.9 37.88 37.88 0 0 0-4.2 12.88zM47.68 60a37.98 37.98 0 0 0 4.07 5.42L57.17 60l-5.42-5.42A38 38 0 0 0 47.68 60zm2.66-6.84a40.06 40.06 0 0 0-3.79 4.9 37.88 37.88 0 0 1-4.2-12.88l7.99 7.98zm1.38-1.44l1.41 1.41 5.46 5.46 6.83-6.84a37.85 37.85 0 0 0-23.36-9.7l9.66 9.67zM60 60l6.87 6.87A38.1 38.1 0 0 0 72.32 60a38.11 38.11 0 0 0-5.45-6.87L60 60zm-14.65 0a39.9 39.9 0 0 0-5.24 17.06l-.11.11-.1-.1A39.9 39.9 0 0 0 34.64 60a39.9 39.9 0 0 0 5.24-17.06l.11-.11.1.1A39.9 39.9 0 0 0 45.36 60zm9.23-48.25a37.85 37.85 0 0 1 23.36-9.7l-9.66 9.67-1.41 1.41-5.46 5.46-6.83-6.84zm13.67 13.67L62.83 20l5.42-5.42A38 38 0 0 1 72.32 20a37.98 37.98 0 0 1-4.07 5.42zm5.2-3.47a40.05 40.05 0 0 1-3.79 4.89l7.99 7.98c-.61-4.45-2.01-8.82-4.2-12.87zm-6.58 4.92l1.41 1.41 9.66 9.66a37.85 37.85 0 0 1-23.36-9.7l6.83-6.83 5.46 5.46zM53.13 13.13L60 20l-6.87 6.87A38.11 38.11 0 0 1 47.68 20a38.1 38.1 0 0 1 5.45-6.87zm-1.41-1.41l-9.66-9.66c.3 5.52 1.8 11 4.49 16a40.18 40.18 0 0 1 5.17-6.34zm-9.66 26.22c.3-5.52 1.8-11 4.49-16a40.18 40.18 0 0 0 5.17 6.34l-9.66 9.66zm26.22 13.78l9.66-9.66c-.3 5.52-1.8 11-4.49 16a40.18 40.18 0 0 0-5.17-6.34zm8.98-11.81L66.84 50.34a39.83 39.83 0 0 0-24.1-10.25l10.42-10.43a39.83 39.83 0 0 0 24.1 10.25zm-7.6-26.75a40.06 40.06 0 0 1 3.79 4.9 37.88 37.88 0 0 0 4.2-12.88l-7.99 7.98zm-31.72 28.9c-8.4.45-16.69 3.68-23.36 9.7l6.83 6.83 5.46-5.46 1.41-1.41 9.66-9.66zM22.83 60l5.42 5.42c1.54-1.7 2.9-3.52 4.07-5.42a38 38 0 0 0-4.07-5.42L22.83 60zm5.45 8.28l-1.41-1.41-5.46-5.46-6.83 6.84a37.85 37.85 0 0 0 23.36 9.7l-9.66-9.67zm9.37 6.54l-7.99-7.98a40.05 40.05 0 0 0 3.79-4.9 37.88 37.88 0 0 1 4.2 12.88zM20 60l-6.87-6.87A38.11 38.11 0 0 0 7.68 60a38.11 38.11 0 0 0 5.45 6.87L20 60zm17.26-19.9L26.84 29.65a39.83 39.83 0 0 1-24.1 10.25l10.42 10.43a39.83 39.83 0 0 1 24.1-10.25zm-35.2 1.96l9.66 9.66a40.18 40.18 0 0 0-5.17 6.33c-2.7-5-4.2-10.47-4.5-16zm4.49 19.89c-2.7 5-4.2 10.47-4.5 16l9.67-9.67a40.18 40.18 0 0 1-5.17-6.33zm31.1-16.77c-.61 4.45-2.01 8.82-4.2 12.87a40.06 40.06 0 0 0-3.79-4.89l7.99-7.98zm-4.2-23.23c2.7 5 4.2 10.47 4.5 16l-9.67-9.67c1.97-1.97 3.7-4.1 5.17-6.33zm-14.86-.54l6.83 6.84a37.85 37.85 0 0 1-23.36 9.7l9.66-9.67 1.41-1.41 5.46-5.46zm-8.25 5.43l-7.99 7.98c.61-4.45 2.01-8.82 4.2-12.87a40.04 40.04 0 0 0 3.79 4.89zm1.41-1.42A37.99 37.99 0 0 1 7.68 20a38 38 0 0 1 4.07-5.42L17.17 20l-5.42 5.42zm-5.2-7.37a40.04 40.04 0 0 1 3.79-4.89L2.35 5.18c.61 4.45 2.01 8.82 4.2 12.87zm6.58-4.92l-1.41-1.41-9.66-9.66a37.85 37.85 0 0 1 23.36 9.7l-6.83 6.83-5.46-5.46zm13.74 13.74L20 20l6.87-6.87A38.1 38.1 0 0 1 32.32 20a38.1 38.1 0 0 1-5.45 6.87zm6.58-8.82a40.18 40.18 0 0 0-5.17-6.33l9.66-9.66c-.3 5.52-1.8 11-4.49 16z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "i-like-food": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260' viewBox='0 0 260 260'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M24.37 16c.2.65.39 1.32.54 2H21.17l1.17 2.34.45.9-.24.11V28a5 5 0 0 1-2.23 8.94l-.02.06a8 8 0 0 1-7.75 6h-20a8 8 0 0 1-7.74-6l-.02-.06A5 5 0 0 1-17.45 28v-6.76l-.79-1.58-.44-.9.9-.44.63-.32H-20a23.01 23.01 0 0 1 44.37-2zm-36.82 2a1 1 0 0 0-.44.1l-3.1 1.56.89 1.79 1.31-.66a3 3 0 0 1 2.69 0l2.2 1.1a1 1 0 0 0 .9 0l2.21-1.1a3 3 0 0 1 2.69 0l2.2 1.1a1 1 0 0 0 .9 0l2.21-1.1a3 3 0 0 1 2.69 0l2.2 1.1a1 1 0 0 0 .86.02l2.88-1.27a3 3 0 0 1 2.43 0l2.88 1.27a1 1 0 0 0 .85-.02l3.1-1.55-.89-1.79-1.42.71a3 3 0 0 1-2.56.06l-2.77-1.23a1 1 0 0 0-.4-.09h-.01a1 1 0 0 0-.4.09l-2.78 1.23a3 3 0 0 1-2.56-.06l-2.3-1.15a1 1 0 0 0-.45-.11h-.01a1 1 0 0 0-.44.1L.9 19.22a3 3 0 0 1-2.69 0l-2.2-1.1a1 1 0 0 0-.45-.11h-.01a1 1 0 0 0-.44.1l-2.21 1.11a3 3 0 0 1-2.69 0l-2.2-1.1a1 1 0 0 0-.45-.11h-.01zm0-2h-4.9a21.01 21.01 0 0 1 39.61 0h-2.09l-.06-.13-.26.13h-32.31zm30.35 7.68l1.36-.68h1.3v2h-36v-1.15l.34-.17 1.36-.68h2.59l1.36.68a3 3 0 0 0 2.69 0l1.36-.68h2.59l1.36.68a3 3 0 0 0 2.69 0L2.26 23h2.59l1.36.68a3 3 0 0 0 2.56.06l1.67-.74h3.23l1.67.74a3 3 0 0 0 2.56-.06zM-13.82 27l16.37 4.91L18.93 27h-32.75zm-.63 2h.34l16.66 5 16.67-5h.33a3 3 0 1 1 0 6h-34a3 3 0 1 1 0-6zm1.35 8a6 6 0 0 0 5.65 4h20a6 6 0 0 0 5.66-4H-13.1z'/%3E%3Cpath id='path6_fill-copy' d='M284.37 16c.2.65.39 1.32.54 2H281.17l1.17 2.34.45.9-.24.11V28a5 5 0 0 1-2.23 8.94l-.02.06a8 8 0 0 1-7.75 6h-20a8 8 0 0 1-7.74-6l-.02-.06a5 5 0 0 1-2.24-8.94v-6.76l-.79-1.58-.44-.9.9-.44.63-.32H240a23.01 23.01 0 0 1 44.37-2zm-36.82 2a1 1 0 0 0-.44.1l-3.1 1.56.89 1.79 1.31-.66a3 3 0 0 1 2.69 0l2.2 1.1a1 1 0 0 0 .9 0l2.21-1.1a3 3 0 0 1 2.69 0l2.2 1.1a1 1 0 0 0 .9 0l2.21-1.1a3 3 0 0 1 2.69 0l2.2 1.1a1 1 0 0 0 .86.02l2.88-1.27a3 3 0 0 1 2.43 0l2.88 1.27a1 1 0 0 0 .85-.02l3.1-1.55-.89-1.79-1.42.71a3 3 0 0 1-2.56.06l-2.77-1.23a1 1 0 0 0-.4-.09h-.01a1 1 0 0 0-.4.09l-2.78 1.23a3 3 0 0 1-2.56-.06l-2.3-1.15a1 1 0 0 0-.45-.11h-.01a1 1 0 0 0-.44.1l-2.21 1.11a3 3 0 0 1-2.69 0l-2.2-1.1a1 1 0 0 0-.45-.11h-.01a1 1 0 0 0-.44.1l-2.21 1.11a3 3 0 0 1-2.69 0l-2.2-1.1a1 1 0 0 0-.45-.11h-.01zm0-2h-4.9a21.01 21.01 0 0 1 39.61 0h-2.09l-.06-.13-.26.13h-32.31zm30.35 7.68l1.36-.68h1.3v2h-36v-1.15l.34-.17 1.36-.68h2.59l1.36.68a3 3 0 0 0 2.69 0l1.36-.68h2.59l1.36.68a3 3 0 0 0 2.69 0l1.36-.68h2.59l1.36.68a3 3 0 0 0 2.56.06l1.67-.74h3.23l1.67.74a3 3 0 0 0 2.56-.06zM246.18 27l16.37 4.91L278.93 27h-32.75zm-.63 2h.34l16.66 5 16.67-5h.33a3 3 0 1 1 0 6h-34a3 3 0 1 1 0-6zm1.35 8a6 6 0 0 0 5.65 4h20a6 6 0 0 0 5.66-4H246.9z'/%3E%3Cpath d='M159.5 21.02A9 9 0 0 0 151 15h-42a9 9 0 0 0-8.5 6.02 6 6 0 0 0 .02 11.96A8.99 8.99 0 0 0 109 45h42a9 9 0 0 0 8.48-12.02 6 6 0 0 0 .02-11.96zM151 17h-42a7 7 0 0 0-6.33 4h54.66a7 7 0 0 0-6.33-4zm-9.34 26a8.98 8.98 0 0 0 3.34-7h-2a7 7 0 0 1-7 7h-4.34a8.98 8.98 0 0 0 3.34-7h-2a7 7 0 0 1-7 7h-4.34a8.98 8.98 0 0 0 3.34-7h-2a7 7 0 0 1-7 7h-7a7 7 0 1 1 0-14h42a7 7 0 1 1 0 14h-9.34zM109 27a9 9 0 0 0-7.48 4H101a4 4 0 1 1 0-8h58a4 4 0 0 1 0 8h-.52a9 9 0 0 0-7.48-4h-42z'/%3E%3Cpath d='M39 115a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm6-8a6 6 0 1 1-12 0 6 6 0 0 1 12 0zm-3-29v-2h8v-6H40a4 4 0 0 0-4 4v10H22l-1.33 4-.67 2h2.19L26 130h26l3.81-40H58l-.67-2L56 84H42v-6zm-4-4v10h2V74h8v-2h-8a2 2 0 0 0-2 2zm2 12h14.56l.67 2H22.77l.67-2H40zm13.8 4H24.2l3.62 38h22.36l3.62-38z'/%3E%3Cpath d='M129 92h-6v4h-6v4h-6v14h-3l.24 2 3.76 32h36l3.76-32 .24-2h-3v-14h-6v-4h-6v-4h-8zm18 22v-12h-4v4h3v8h1zm-3 0v-6h-4v6h4zm-6 6v-16h-4v19.17c1.6-.7 2.97-1.8 4-3.17zm-6 3.8V100h-4v23.8a10.04 10.04 0 0 0 4 0zm-6-.63V104h-4v16a10.04 10.04 0 0 0 4 3.17zm-6-9.17v-6h-4v6h4zm-6 0v-8h3v-4h-4v12h1zm27-12v-4h-4v4h3v4h1v-4zm-6 0v-8h-4v4h3v4h1zm-6-4v-4h-4v8h1v-4h3zm-6 4v-4h-4v8h1v-4h3zm7 24a12 12 0 0 0 11.83-10h7.92l-3.53 30h-32.44l-3.53-30h7.92A12 12 0 0 0 130 126z'/%3E%3Cpath d='M212 86v2h-4v-2h4zm4 0h-2v2h2v-2zm-20 0v.1a5 5 0 0 0-.56 9.65l.06.25 1.12 4.48a2 2 0 0 0 1.94 1.52h.01l7.02 24.55a2 2 0 0 0 1.92 1.45h4.98a2 2 0 0 0 1.92-1.45l7.02-24.55a2 2 0 0 0 1.95-1.52L224.5 96l.06-.25a5 5 0 0 0-.56-9.65V86a14 14 0 0 0-28 0zm4 0h6v2h-9a3 3 0 1 0 0 6H223a3 3 0 1 0 0-6H220v-2h2a12 12 0 1 0-24 0h2zm-1.44 14l-1-4h24.88l-1 4h-22.88zm8.95 26l-6.86-24h18.7l-6.86 24h-4.98zM150 242a22 22 0 1 0 0-44 22 22 0 0 0 0 44zm24-22a24 24 0 1 1-48 0 24 24 0 0 1 48 0zm-28.38 17.73l2.04-.87a6 6 0 0 1 4.68 0l2.04.87a2 2 0 0 0 2.5-.82l1.14-1.9a6 6 0 0 1 3.79-2.75l2.15-.5a2 2 0 0 0 1.54-2.12l-.19-2.2a6 6 0 0 1 1.45-4.46l1.45-1.67a2 2 0 0 0 0-2.62l-1.45-1.67a6 6 0 0 1-1.45-4.46l.2-2.2a2 2 0 0 0-1.55-2.13l-2.15-.5a6 6 0 0 1-3.8-2.75l-1.13-1.9a2 2 0 0 0-2.5-.8l-2.04.86a6 6 0 0 1-4.68 0l-2.04-.87a2 2 0 0 0-2.5.82l-1.14 1.9a6 6 0 0 1-3.79 2.75l-2.15.5a2 2 0 0 0-1.54 2.12l.19 2.2a6 6 0 0 1-1.45 4.46l-1.45 1.67a2 2 0 0 0 0 2.62l1.45 1.67a6 6 0 0 1 1.45 4.46l-.2 2.2a2 2 0 0 0 1.55 2.13l2.15.5a6 6 0 0 1 3.8 2.75l1.13 1.9a2 2 0 0 0 2.5.8zm2.82.97a4 4 0 0 1 3.12 0l2.04.87a4 4 0 0 0 4.99-1.62l1.14-1.9a4 4 0 0 1 2.53-1.84l2.15-.5a4 4 0 0 0 3.09-4.24l-.2-2.2a4 4 0 0 1 .97-2.98l1.45-1.67a4 4 0 0 0 0-5.24l-1.45-1.67a4 4 0 0 1-.97-2.97l.2-2.2a4 4 0 0 0-3.09-4.25l-2.15-.5a4 4 0 0 1-2.53-1.84l-1.14-1.9a4 4 0 0 0-5-1.62l-2.03.87a4 4 0 0 1-3.12 0l-2.04-.87a4 4 0 0 0-4.99 1.62l-1.14 1.9a4 4 0 0 1-2.53 1.84l-2.15.5a4 4 0 0 0-3.09 4.24l.2 2.2a4 4 0 0 1-.97 2.98l-1.45 1.67a4 4 0 0 0 0 5.24l1.45 1.67a4 4 0 0 1 .97 2.97l-.2 2.2a4 4 0 0 0 3.09 4.25l2.15.5a4 4 0 0 1 2.53 1.84l1.14 1.9a4 4 0 0 0 5 1.62l2.03-.87zM152 207a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm6 2a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm-11 1a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm-6 0a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm3-5a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm-8 8a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm3 6a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm0 6a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm4 7a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm5-2a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm5 4a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm4-6a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm6-4a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm-4-3a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm4-3a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm-5-4a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm-24 6a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm16 5a5 5 0 1 0 0-10 5 5 0 0 0 0 10zm7-5a7 7 0 1 1-14 0 7 7 0 0 1 14 0zm86-29a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm19 9a1 1 0 0 1 1-1h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-1-1zm-14 5a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm-25 1a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm5 4a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm9 0a1 1 0 0 1 1-1h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-1-1zm15 1a1 1 0 0 1 1-1h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-1-1zm12-2a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm-11-14a1 1 0 0 1 1-1h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-1-1zm-19 0a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm6 5a1 1 0 0 1 1-1h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-1-1zm-25 15c0-.47.01-.94.03-1.4a5 5 0 0 1-1.7-8 3.99 3.99 0 0 1 1.88-5.18 5 5 0 0 1 3.4-6.22 3 3 0 0 1 1.46-1.05 5 5 0 0 1 7.76-3.27A30.86 30.86 0 0 1 246 184c6.79 0 13.06 2.18 18.17 5.88a5 5 0 0 1 7.76 3.27 3 3 0 0 1 1.47 1.05 5 5 0 0 1 3.4 6.22 4 4 0 0 1 1.87 5.18 4.98 4.98 0 0 1-1.7 8c.02.46.03.93.03 1.4v1h-62v-1zm.83-7.17a30.9 30.9 0 0 0-.62 3.57 3 3 0 0 1-.61-4.2c.37.28.78.49 1.23.63zm1.49-4.61c-.36.87-.68 1.76-.96 2.68a2 2 0 0 1-.21-3.71c.33.4.73.75 1.17 1.03zm2.32-4.54c-.54.86-1.03 1.76-1.49 2.68a3 3 0 0 1-.07-4.67 3 3 0 0 0 1.56 1.99zm1.14-1.7c.35-.5.72-.98 1.1-1.46a1 1 0 1 0-1.1 1.45zm5.34-5.77c-1.03.86-2 1.79-2.9 2.77a3 3 0 0 0-1.11-.77 3 3 0 0 1 4-2zm42.66 2.77c-.9-.98-1.87-1.9-2.9-2.77a3 3 0 0 1 4.01 2 3 3 0 0 0-1.1.77zm1.34 1.54c.38.48.75.96 1.1 1.45a1 1 0 1 0-1.1-1.45zm3.73 5.84c-.46-.92-.95-1.82-1.5-2.68a3 3 0 0 0 1.57-1.99 3 3 0 0 1-.07 4.67zm1.8 4.53c-.29-.9-.6-1.8-.97-2.67.44-.28.84-.63 1.17-1.03a2 2 0 0 1-.2 3.7zm1.14 5.51c-.14-1.21-.35-2.4-.62-3.57.45-.14.86-.35 1.23-.63a2.99 2.99 0 0 1-.6 4.2zM275 214a29 29 0 0 0-57.97 0h57.96zM72.33 198.12c-.21-.32-.34-.7-.34-1.12v-12h-2v12a4.01 4.01 0 0 0 7.09 2.54c.57-.69.91-1.57.91-2.54v-12h-2v12a1.99 1.99 0 0 1-2 2 2 2 0 0 1-1.66-.88zM75 176c.38 0 .74-.04 1.1-.12a4 4 0 0 0 6.19 2.4A13.94 13.94 0 0 1 84 185v24a6 6 0 0 1-6 6h-3v9a5 5 0 1 1-10 0v-9h-3a6 6 0 0 1-6-6v-24a14 14 0 0 1 14-14 5 5 0 0 0 5 5zm-17 15v12a1.99 1.99 0 0 0 1.22 1.84 2 2 0 0 0 2.44-.72c.21-.32.34-.7.34-1.12v-12h2v12a3.98 3.98 0 0 1-5.35 3.77 3.98 3.98 0 0 1-.65-.3V209a4 4 0 0 0 4 4h16a4 4 0 0 0 4-4v-24c.01-1.53-.23-2.88-.72-4.17-.43.1-.87.16-1.28.17a6 6 0 0 1-5.2-3 7 7 0 0 1-6.47-4.88A12 12 0 0 0 58 185v6zm9 24v9a3 3 0 1 0 6 0v-9h-6z'/%3E%3Cpath d='M-17 191a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm19 9a1 1 0 0 1 1-1h2a1 1 0 0 1 0 2H3a1 1 0 0 1-1-1zm-14 5a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm-25 1a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm5 4a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm9 0a1 1 0 0 1 1-1h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-1-1zm15 1a1 1 0 0 1 1-1h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-1-1zm12-2a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2H4zm-11-14a1 1 0 0 1 1-1h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-1-1zm-19 0a1 1 0 0 0 0 2h2a1 1 0 0 0 0-2h-2zm6 5a1 1 0 0 1 1-1h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-1-1zm-25 15c0-.47.01-.94.03-1.4a5 5 0 0 1-1.7-8 3.99 3.99 0 0 1 1.88-5.18 5 5 0 0 1 3.4-6.22 3 3 0 0 1 1.46-1.05 5 5 0 0 1 7.76-3.27A30.86 30.86 0 0 1-14 184c6.79 0 13.06 2.18 18.17 5.88a5 5 0 0 1 7.76 3.27 3 3 0 0 1 1.47 1.05 5 5 0 0 1 3.4 6.22 4 4 0 0 1 1.87 5.18 4.98 4.98 0 0 1-1.7 8c.02.46.03.93.03 1.4v1h-62v-1zm.83-7.17a30.9 30.9 0 0 0-.62 3.57 3 3 0 0 1-.61-4.2c.37.28.78.49 1.23.63zm1.49-4.61c-.36.87-.68 1.76-.96 2.68a2 2 0 0 1-.21-3.71c.33.4.73.75 1.17 1.03zm2.32-4.54c-.54.86-1.03 1.76-1.49 2.68a3 3 0 0 1-.07-4.67 3 3 0 0 0 1.56 1.99zm1.14-1.7c.35-.5.72-.98 1.1-1.46a1 1 0 1 0-1.1 1.45zm5.34-5.77c-1.03.86-2 1.79-2.9 2.77a3 3 0 0 0-1.11-.77 3 3 0 0 1 4-2zm42.66 2.77c-.9-.98-1.87-1.9-2.9-2.77a3 3 0 0 1 4.01 2 3 3 0 0 0-1.1.77zm1.34 1.54c.38.48.75.96 1.1 1.45a1 1 0 1 0-1.1-1.45zm3.73 5.84c-.46-.92-.95-1.82-1.5-2.68a3 3 0 0 0 1.57-1.99 3 3 0 0 1-.07 4.67zm1.8 4.53c-.29-.9-.6-1.8-.97-2.67.44-.28.84-.63 1.17-1.03a2 2 0 0 1-.2 3.7zm1.14 5.51c-.14-1.21-.35-2.4-.62-3.57.45-.14.86-.35 1.23-.63a2.99 2.99 0 0 1-.6 4.2zM15 214a29 29 0 0 0-57.97 0h57.96z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "overlapping-hexagons": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='50' height='40' viewBox='0 0 50 40'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M40 10L36.67 0h-2.11l3.33 10H20l-2.28 6.84L12.11 0H10l6.67 20H10l-2.28 6.84L2.11 10 5.44 0h-2.1L0 10l6.67 20-3.34 10h2.11l2.28-6.84L10 40h20l2.28-6.84L34.56 40h2.1l-3.33-10H40l2.28-6.84L47.89 40H50l-6.67-20L50 0h-2.1l-5.62 16.84L40 10zm1.23 10l-2.28-6.84L34 28h4.56l2.67-8zm-10.67 8l-2-6h-9.12l2 6h9.12zm-12.84-4.84L12.77 38h15.79l2.67-8H20l-2.28-6.84zM18.77 20H30l2.28 6.84L37.23 12H21.44l-2.67 8zm-7.33 2H16l-4.95 14.84L8.77 30l2.67-8z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "four-point-stars": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpolygon fill-rule='evenodd' points='8 4 12 6 8 8 6 12 4 8 0 6 4 4 6 0 8 4'/%3E%3C/g%3E%3C/svg%3E\")", - bamboo: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='32' viewBox='0 0 16 32'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M0 24h4v2H0v-2zm0 4h6v2H0v-2zm0-8h2v2H0v-2zM0 0h4v2H0V0zm0 4h2v2H0V4zm16 20h-6v2h6v-2zm0 4H8v2h8v-2zm0-8h-4v2h4v-2zm0-20h-6v2h6V0zm0 4h-4v2h4V4zm-2 12h2v2h-2v-2zm0-8h2v2h-2V8zM2 8h10v2H2V8zm0 8h10v2H2v-2zm-2-4h14v2H0v-2zm4-8h6v2H4V4zm0 16h6v2H4v-2zM6 0h2v2H6V0zm0 24h2v2H6v-2z'/%3E%3C/g%3E%3C/svg%3E\")", - "bathroom-floor": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M0 0h40v40H0V0zm40 40h40v40H40V40zm0-40h2l-2 2V0zm0 4l4-4h2l-6 6V4zm0 4l8-8h2L40 10V8zm0 4L52 0h2L40 14v-2zm0 4L56 0h2L40 18v-2zm0 4L60 0h2L40 22v-2zm0 4L64 0h2L40 26v-2zm0 4L68 0h2L40 30v-2zm0 4L72 0h2L40 34v-2zm0 4L76 0h2L40 38v-2zm0 4L80 0v2L42 40h-2zm4 0L80 4v2L46 40h-2zm4 0L80 8v2L50 40h-2zm4 0l28-28v2L54 40h-2zm4 0l24-24v2L58 40h-2zm4 0l20-20v2L62 40h-2zm4 0l16-16v2L66 40h-2zm4 0l12-12v2L70 40h-2zm4 0l8-8v2l-6 6h-2zm4 0l4-4v2l-2 2h-2z'/%3E%3C/g%3E%3C/svg%3E\")", - "cork-screw": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='16' viewBox='0 0 20 16'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M0 .04C2.6.22 4.99 1.1 7 2.5A13.94 13.94 0 0 1 15 0h4c.34 0 .67.01 1 .04v2A12 12 0 0 0 7.17 12h5.12A7 7 0 0 1 20 7.07V14a5 5 0 0 0-3-4.58A5 5 0 0 0 14 14H0V7.07c.86.12 1.67.4 2.4.81.75-1.52 1.76-2.9 2.98-4.05C3.79 2.83 1.96 2.2 0 2.04v-2z'/%3E%3C/g%3E%3C/svg%3E\")", - "happy-intersection": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='88' height='88' viewBox='0 0 88 88'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M29.42 29.41c.36-.36.58-.85.58-1.4V0h-4v26H0v4h28c.55 0 1.05-.22 1.41-.58h.01zm0 29.18c.36.36.58.86.58 1.4V88h-4V62H0v-4h28c.56 0 1.05.22 1.41.58zm29.16 0c-.36.36-.58.85-.58 1.4V88h4V62h26v-4H60c-.55 0-1.05.22-1.41.58h-.01zM62 26V0h-4v28c0 .55.22 1.05.58 1.41.37.37.86.59 1.41.59H88v-4H62zM18 36c0-1.1.9-2 2-2h10a2 2 0 1 1 0 4H20a2 2 0 0 1-2-2zm0 16c0-1.1.9-2 2-2h10a2 2 0 1 1 0 4H20a2 2 0 0 1-2-2zm16-26a2 2 0 0 1 2-2 2 2 0 0 1 2 2v4a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-4zm16 0a2 2 0 0 1 2-2 2 2 0 0 1 2 2v4a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-4zM34 58a2 2 0 0 1 2-2 2 2 0 0 1 2 2v4a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-4zm16 0a2 2 0 0 1 2-2 2 2 0 0 1 2 2v4a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-4zM34 78a2 2 0 0 1 2-2 2 2 0 0 1 2 2v6a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-6zm16 0a2 2 0 0 1 2-2 2 2 0 0 1 2 2v6a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-6zM34 4a2 2 0 0 1 2-2 2 2 0 0 1 2 2v6a2 2 0 0 1-2 2 2 2 0 0 1-2-2V4zm16 0a2 2 0 0 1 2-2 2 2 0 0 1 2 2v6a2 2 0 0 1-2 2 2 2 0 0 1-2-2V4zm-8 82a2 2 0 1 1 4 0v2h-4v-2zm0-68a2 2 0 1 1 4 0v10a2 2 0 1 1-4 0V18zM66 4a2 2 0 1 1 4 0v8a2 2 0 1 1-4 0V4zm0 72a2 2 0 1 1 4 0v8a2 2 0 1 1-4 0v-8zm-48 0a2 2 0 1 1 4 0v8a2 2 0 1 1-4 0v-8zm0-72a2 2 0 1 1 4 0v8a2 2 0 1 1-4 0V4zm24-4h4v2a2 2 0 1 1-4 0V0zm0 60a2 2 0 1 1 4 0v10a2 2 0 1 1-4 0V60zm14-24c0-1.1.9-2 2-2h10a2 2 0 1 1 0 4H58a2 2 0 0 1-2-2zm0 16c0-1.1.9-2 2-2h10a2 2 0 1 1 0 4H58a2 2 0 0 1-2-2zm-28-6a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm8 26a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm16 0a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM36 20a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm16 0a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm-8-8a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 68a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm16-34a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm16-12a2 2 0 1 0 0 4 6 6 0 1 1 0 12 2 2 0 1 0 0 4 10 10 0 1 0 0-20zm-64 0a2 2 0 1 1 0 4 6 6 0 1 0 0 12 2 2 0 1 1 0 4 10 10 0 1 1 0-20zm56-12a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 48a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm-48 0a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0-48a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm24 32a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-4a6 6 0 1 0 0-12 6 6 0 0 0 0 12zm36-36a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-4a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM10 44c0-1.1.9-2 2-2h8a2 2 0 1 1 0 4h-8a2 2 0 0 1-2-2zm56 0c0-1.1.9-2 2-2h8a2 2 0 1 1 0 4h-8a2 2 0 0 1-2-2zm8 24c0-1.1.9-2 2-2h8a2 2 0 1 1 0 4h-8a2 2 0 0 1-2-2zM3 68c0-1.1.9-2 2-2h8a2 2 0 1 1 0 4H5a2 2 0 0 1-2-2zm0-48c0-1.1.9-2 2-2h8a2 2 0 1 1 0 4H5a2 2 0 0 1-2-2zm71 0c0-1.1.9-2 2-2h8a2 2 0 1 1 0 4h-8a2 2 0 0 1-2-2zm6 66a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-4a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM8 86a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-4a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0-68A6 6 0 1 1 8 2a6 6 0 0 1 0 12zm0-4a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm36 36a2 2 0 1 0 0-4 2 2 0 0 0 0 4z'/%3E%3C/g%3E%3C/svg%3E\")", - kiwi: "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='34' height='44' viewBox='0 0 34 44'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M1 6.2C.72 5.55.38 4.94 0 4.36v13.28c.38-.58.72-1.2 1-1.84A12.04 12.04 0 0 0 7.2 22 12.04 12.04 0 0 0 1 28.2c-.28-.65-.62-1.26-1-1.84v13.28c.38-.58.72-1.2 1-1.84A12.04 12.04 0 0 0 7.2 44h21.6a12.05 12.05 0 0 0 5.2-4.36V26.36A12.05 12.05 0 0 0 28.8 22a12.05 12.05 0 0 0 5.2-4.36V4.36A12.05 12.05 0 0 0 28.8 0H7.2A12.04 12.04 0 0 0 1 6.2zM17.36 23H12a10 10 0 1 0 0 20h5.36a11.99 11.99 0 0 1 0-20zm1.28-2H24a10 10 0 1 0 0-20h-5.36a11.99 11.99 0 0 1 0 20zM12 1a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 14a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-3.46-2a2 2 0 1 0-3.47 2 2 2 0 0 0 3.47-2zm0-4a2 2 0 1 0-3.47-2 2 2 0 0 0 3.47 2zM12 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3.46 2a2 2 0 1 0 3.47-2 2 2 0 0 0-3.47 2zm0 4a2 2 0 1 0 3.47 2 2 2 0 0 0-3.47-2zM24 43a10 10 0 1 0 0-20 10 10 0 0 0 0 20zm0-14a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3.46 2a2 2 0 1 0 3.47-2 2 2 0 0 0-3.47 2zm0 4a2 2 0 1 0 3.47 2 2 2 0 0 0-3.47-2zM24 37a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-3.46-2a2 2 0 1 0-3.47 2 2 2 0 0 0 3.47-2zm0-4a2 2 0 1 0-3.47-2 2 2 0 0 0 3.47 2z'/%3E%3C/g%3E%3C/svg%3E\")", - lips: "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='112' height='92' viewBox='0 0 112 92'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M72 10H40L16 20H0v8h16l24-14h32l24 14h16v-8H96L72 10zm0-8H40L16 4H0v8h16l24-6h32l24 6h16V4H96L72 2zm0 84H40l-24-6H0v8h16l24 2h32l24-2h16v-8H96l-24 6zm0-8H40L16 64H0v8h16l24 10h32l24-10h16v-8H96L72 78zm0-12H40L16 56H0v4h16l24 14h32l24-14h16v-4H96L72 66zm0-16H40l-24-2H0v4h16l24 6h32l24-6h16v-4H96l-24 2zm0-16H40l-24 6H0v4h16l24-2h32l24 2h16v-4H96l-24-6zm0-16H40L16 32H0v4h16l24-10h32l24 10h16v-4H96L72 18z'/%3E%3C/g%3E%3C/svg%3E\")", - lisbon: - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M41 37.59V25h-2v12.59l-8.9-8.9-1.41 1.41 8.9 8.9H25v2h12.59l-8.9 8.9 1.41 1.41 8.9-8.9V55h2V42.41l8.9 8.9 1.41-1.41-8.9-8.9H55v-2H42.41l8.9-8.9-1.41-1.41-8.9 8.9zM1 1h2v2H1V1zm0 4h2v2H1V5zm0 4h2v2H1V9zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm0 4h2v2H1v-2zm4 0h2v2H5v-2zm4 0h2v2H9v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zM5 1h2v2H5V1zm4 0h2v2H9V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm4 0h2v2h-2V1zm0 4h2v2h-2V5zm0 4h2v2h-2V9zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zm0 4h2v2h-2v-2zM5 5h70v70H5V5zm2 68h66V7H7v66zM9 9h62v62H9V9zm2 60h58V11H11v58zm2-39.6V13h16.4A29.1 29.1 0 0 0 13 29.4zM15 15v6.67A31.17 31.17 0 0 1 21.67 15H15zm-2 52V50.6A29.1 29.1 0 0 0 29.4 67H13zm2-8.67V65h6.67A31.17 31.17 0 0 1 15 58.33zM67 67H50.6A29.1 29.1 0 0 0 67 50.6V67zm-8.67-2H65v-6.67A31.17 31.17 0 0 1 58.33 65zM67 13v16.4A29.1 29.1 0 0 0 50.6 13H67zm-2 8.67V15h-6.67A31.17 31.17 0 0 1 65 21.67zM39 13h2v2h-2v-2zm7.02.66l1.93.52-.51 1.93-1.94-.52.52-1.93zm6.61 2.46l1.74 1-1 1.73-1.74-1 1-1.73zm5.75 4.08l1.42 1.42-1.42 1.4-1.4-1.4 1.4-1.42zm4.5 5.43l1 1.74-1.73 1-1-1.74 1.73-1zm2.94 6.42l.52 1.93-1.93.52-.52-1.94 1.93-.51zM67 39v2h-2v-2h2zm-.66 7.02l-.52 1.93-1.93-.51.52-1.94 1.93.52zm-2.46 6.61l-1 1.74-1.73-1 1-1.74 1.73 1zm-4.08 5.75l-1.42 1.42-1.4-1.42 1.4-1.4 1.42 1.4zm-5.43 4.5l-1.74 1-1-1.73 1.74-1 1 1.73zM41 67h-2v-2h2v2zm6.95-1.18l-1.93.52-.52-1.93 1.94-.52.51 1.93zm-13.97.52l-1.93-.52.51-1.93 1.94.52-.52 1.93zm-6.61-2.46l-1.74-1 1-1.73 1.74 1-1 1.73zm-5.75-4.08l-1.42-1.42 1.42-1.4 1.4 1.4-1.4 1.42zm-4.5-5.43l-1-1.74 1.73-1 1 1.74-1.73 1zm-2.94-6.42l-.52-1.93 1.93-.52.52 1.94-1.93.51zM13 41v-2h2v2h-2zm.66-7.02l.52-1.93 1.93.51-.52 1.94-1.93-.52zm2.46-6.61l1-1.74 1.73 1-1 1.74-1.73-1zm4.08-5.75l1.42-1.42 1.4 1.42-1.4 1.4-1.42-1.4zm5.43-4.5l1.74-1 1 1.73-1.74 1-1-1.73zm6.42-2.94l1.93-.52.52 1.93-1.94.52-.51-1.93zM40 63a23 23 0 1 1 0-46 23 23 0 0 1 0 46zm0-2a21 21 0 1 0 0-42 21 21 0 0 0 0 42zm0-2a19 19 0 1 1 0-38 19 19 0 0 1 0 38zm0-2a17 17 0 1 0 0-34 17 17 0 0 0 0 34z'/%3E%3C/g%3E%3C/svg%3E\")", - "random-shapes": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M11 0l5 20H6l5-20zm42 31a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM0 72h40v4H0v-4zm0-8h31v4H0v-4zm20-16h20v4H20v-4zM0 56h40v4H0v-4zm63-25a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm10 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM53 41a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm10 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm10 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-30 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-28-8a5 5 0 0 0-10 0h10zm10 0a5 5 0 0 1-10 0h10zM56 5a5 5 0 0 0-10 0h10zm10 0a5 5 0 0 1-10 0h10zm-3 46a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm10 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM21 0l5 20H16l5-20zm43 64v-4h-4v4h-4v4h4v4h4v-4h4v-4h-4zM36 13h4v4h-4v-4zm4 4h4v4h-4v-4zm-4 4h4v4h-4v-4zm8-8h4v4h-4v-4z'/%3E%3C/g%3E%3C/svg%3E\")", - "steel-beams": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='42' height='58' viewBox='0 0 42 58'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M12 18h12v18h6v4H18V22h-6v-4zm-6-2v-4H0V0h36v6h6v36h-6v4h6v12H6v-6H0V16h6zM34 2H2v8h24v24h8V2zM6 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm8 0a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm8 0a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm8 0a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM2 50h32v-8H10V18H2v32zm28-6a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-8 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-8 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-8 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm0-8a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm0-8a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm0-8a2 2 0 1 0 0 4 2 2 0 0 0 0-4z'/%3E%3C/g%3E%3C/svg%3E\")", - "tiny-checkers": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M0 0h4v4H0V0zm4 4h4v4H4V4z'/%3E%3C/g%3E%3C/svg%3E\")", - "x-equals": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath fill-rule='evenodd' d='M5 3.59L1.46.05.05 1.46 3.59 5 .05 8.54l1.41 1.41L5 6.41l3.54 3.54 1.41-1.41L6.41 5l3.54-3.54L8.54.05 5 3.59zM17 2h24v2H17V2zm0 4h24v2H17V6zM2 17h2v24H2V17zm4 0h2v24H6V17z'/%3E%3C/g%3E%3C/svg%3E\")", - "anchors-away": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80' width='80' height='80'%3E%3Cpath fill='FILLCOLOR' fill-opacity='FILLOPACITY' d='M14 16H9v-2h5V9.87a4 4 0 1 1 2 0V14h5v2h-5v15.95A10 10 0 0 0 23.66 27l-3.46-2 8.2-2.2-2.9 5a12 12 0 0 1-21 0l-2.89-5 8.2 2.2-3.47 2A10 10 0 0 0 14 31.95V16zm40 40h-5v-2h5v-4.13a4 4 0 1 1 2 0V54h5v2h-5v15.95A10 10 0 0 0 63.66 67l-3.47-2 8.2-2.2-2.88 5a12 12 0 0 1-21.02 0l-2.88-5 8.2 2.2-3.47 2A10 10 0 0 0 54 71.95V56zm-39 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm40-40a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM15 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm40 40a2 2 0 1 0 0-4 2 2 0 0 0 0 4z'%3E%3C/path%3E%3C/svg%3E\")", - "bevel-circle": - "url(\"data:image/svg+xml,%3Csvg width='38' height='38' viewBox='0 0 38 38' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='bevel-circle' fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M10.414 29l-8 8h33.172l-8-8H10.414zM9 27.586l-8 8V2.414l8 8v17.172zM10.414 9l-8-8h33.172l-8 8H10.414zM29 10.414l8-8v33.172l-8-8V10.414zM11 11h16v16H11V11zm8 14c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zm0-2c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zM0 0h38v38H0V0z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "brick-wall": - "url(\"data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='brick-wall' fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M0 0h42v44H0V0zm1 1h40v20H1V1zM0 23h20v20H0V23zm22 0h20v20H22V23z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "fancy-rectangles": - "url(\"data:image/svg+xml,%3Csvg width='60' height='48' viewBox='0 0 60 48' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='fancy-rectangles' fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M6 12h6v12H6V12zm12 0h6v12h-6V12zm6-12h6v12h-6V0zM12 0h6v12h-6V0zm0 24h6v12h-6V24zM0 0h6v12H0V0zm6 36h6v12H6V36zm12 0h6v12h-6V36zm12-12h6v12h-6V24zM42 0h6v12h-6V0zm-6 12h6v12h-6V12zm12 0h6v12h-6V12zM36 36h6v12h-6V36zm12 0h6v12h-6V36zm-6-12h6v12h-6V24zm12 0h6v12h-6V24z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "heavy-rain": - "url(\"data:image/svg+xml,%3Csvg width='12' height='24' viewBox='0 0 12 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M2 0h2v12H2V0zm1 20c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM9 8c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zm-1 4h2v12H8V12z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "overlapping-circles": - "url(\"data:image/svg+xml,%3Csvg width='80' height='80' viewBox='0 0 80 80' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M50 50c0-5.523 4.477-10 10-10s10 4.477 10 10-4.477 10-10 10c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10zM10 10c0-5.523 4.477-10 10-10s10 4.477 10 10-4.477 10-10 10c0 5.523-4.477 10-10 10S0 25.523 0 20s4.477-10 10-10zm10 8c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm40 40c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - plus: "url(\"data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "rounded-plus-connected": - "url(\"data:image/svg+xml,%3Csvg width='84' height='84' viewBox='0 0 84 84' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M84 23c-4.417 0-8-3.584-8-7.998V8h-7.002C64.58 8 61 4.42 61 0H23c0 4.417-3.584 8-7.998 8H8v7.002C8 19.42 4.42 23 0 23v38c4.417 0 8 3.584 8 7.998V76h7.002C19.42 76 23 79.58 23 84h38c0-4.417 3.584-8 7.998-8H76v-7.002C76 64.58 79.58 61 84 61V23zM59.05 83H43V66.95c5.054-.5 9-4.764 9-9.948V52h5.002c5.18 0 9.446-3.947 9.95-9H83v16.05c-5.054.5-9 4.764-9 9.948V74h-5.002c-5.18 0-9.446 3.947-9.95 9zm-34.1 0H41V66.95c-5.053-.502-9-4.768-9-9.948V52h-5.002c-5.184 0-9.447-3.946-9.95-9H1v16.05c5.053.502 9 4.768 9 9.948V74h5.002c5.184 0 9.447 3.946 9.95 9zm0-82H41v16.05c-5.054.5-9 4.764-9 9.948V32h-5.002c-5.18 0-9.446 3.947-9.95 9H1V24.95c5.054-.5 9-4.764 9-9.948V10h5.002c5.18 0 9.446-3.947 9.95-9zm34.1 0H43v16.05c5.053.502 9 4.768 9 9.948V32h5.002c5.184 0 9.447 3.946 9.95 9H83V24.95c-5.053-.502-9-4.768-9-9.948V10h-5.002c-5.184 0-9.447-3.946-9.95-9zM50 50v7.002C50 61.42 46.42 65 42 65c-4.417 0-8-3.584-8-7.998V50h-7.002C22.58 50 19 46.42 19 42c0-4.417 3.584-8 7.998-8H34v-7.002C34 22.58 37.58 19 42 19c4.417 0 8 3.584 8 7.998V34h7.002C61.42 34 65 37.58 65 42c0 4.417-3.584 8-7.998 8H50z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - "volcano-lamp": - "url(\"data:image/svg+xml,%3Csvg width='48' height='32' viewBox='0 0 48 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M27 32c0-3.314 2.686-6 6-6 5.523 0 10-4.477 10-10S38.523 6 33 6c-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 6.627 0 12 5.373 12 12s-5.373 12-12 12c-2.21 0-4 1.79-4 4h-2zm-6 0c0-3.314-2.686-6-6-6-5.523 0-10-4.477-10-10S9.477 6 15 6c3.314 0 6-2.686 6-6h-2c0 2.21-1.79 4-4 4C8.373 4 3 9.373 3 16s5.373 12 12 12c2.21 0 4 1.79 4 4h2z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - wiggle: - "url(\"data:image/svg+xml,%3Csvg width='52' height='26' viewBox='0 0 52 26' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY'%3E%3Cpath d='M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")", - bubbles: - "url(\"data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - cage: "url(\"data:image/svg+xml,%3Csvg width='32' height='26' viewBox='0 0 32 26' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14 0v3.994C14 7.864 10.858 11 7 11c-3.866 0-7-3.138-7-7.006V0h2v4.005C2 6.765 4.24 9 7 9c2.756 0 5-2.236 5-4.995V0h2zm0 26v-5.994C14 16.138 10.866 13 7 13c-3.858 0-7 3.137-7 7.006V26h2v-6.005C2 17.235 4.244 15 7 15c2.76 0 5 2.236 5 4.995V26h2zm2-18.994C16 3.136 19.142 0 23 0c3.866 0 7 3.138 7 7.006v9.988C30 20.864 26.858 24 23 24c-3.866 0-7-3.138-7-7.006V7.006zm2-.01C18 4.235 20.244 2 23 2c2.76 0 5 2.236 5 4.995v10.01C28 19.765 25.756 22 23 22c-2.76 0-5-2.236-5-4.995V6.995z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - connections: - "url(\"data:image/svg+xml,%3Csvg width='36' height='36' viewBox='0 0 36 36' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M36 0H0v36h36V0zM15.126 2H2v13.126c.367.094.714.24 1.032.428L15.554 3.032c-.188-.318-.334-.665-.428-1.032zM18 4.874V18H4.874c-.094-.367-.24-.714-.428-1.032L16.968 4.446c.318.188.665.334 1.032.428zM22.874 2h11.712L20 16.586V4.874c1.406-.362 2.512-1.468 2.874-2.874zm10.252 18H20v13.126c.367.094.714.24 1.032.428l12.522-12.522c-.188-.318-.334-.665-.428-1.032zM36 22.874V36H22.874c-.094-.367-.24-.714-.428-1.032l12.522-12.522c.318.188.665.334 1.032.428zm0-7.748V3.414L21.414 18h11.712c.362-1.406 1.468-2.512 2.874-2.874zm-18 18V21.414L3.414 36h11.712c.362-1.406 1.468-2.512 2.874-2.874zM4.874 20h11.712L2 34.586V22.874c1.406-.362 2.512-1.468 2.874-2.874z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - current: - "url(\"data:image/svg+xml,%3Csvg width='76' height='18' viewBox='0 0 76 18' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M32 18c-2.43-1.824-4-4.73-4-8 0-4.418-3.582-8-8-8H0V0h20c5.523 0 10 4.477 10 10 0 4.418 3.582 8 8 8h20c4.418 0 8-3.582 8-8 0-5.523 4.477-10 10-10v2c-4.418 0-8 3.582-8 8 0 3.27-1.57 6.176-4 8H32zM64 0c-1.67 1.256-3.748 2-6 2H38c-2.252 0-4.33-.744-6-2h32z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "diagonal-stripes": - "url(\"data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'%3E%3Cpath d='M0 40L40 0H20L0 20M40 40V20L20 40'/%3E%3C/g%3E%3C/svg%3E\")", - "flipped-diamonds": - "url(\"data:image/svg+xml,%3Csvg width='16' height='20' viewBox='0 0 16 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'%3E%3Cpath d='M8 0v20L0 10M16 0v10L8 0M16 10v10H8'/%3E%3C/g%3E%3C/svg%3E\")", - "floating-cogs": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='360' height='360' viewBox='0 0 360 360'%3E%3Cpath fill='FILLCOLOR' fill-opacity='FILLOPACITY' d='M0 85.02l4.62-4.27a49.09 49.09 0 0 0 7.33 3.74l-1.2 10.24 2.66.87 5.05-9c2.62.65 5.34 1.08 8.12 1.28L28.6 98h2.8l2.02-10.12c2.74-.2 5.46-.62 8.12-1.28l5.05 8.99 2.66-.86-1.2-10.24c2.55-1.03 5-2.29 7.33-3.74l7.58 7 2.26-1.65-4.3-9.38a48.3 48.3 0 0 0 5.8-5.8l9.38 4.3 1.65-2.26-7-7.58a49.09 49.09 0 0 0 3.74-7.33l10.24 1.2.87-2.66-9-5.05a48.07 48.07 0 0 0 1.28-8.12L88 41.4v-2.8l-10.12-2.02c-.2-2.74-.62-5.46-1.28-8.12l8.99-5.05-.86-2.66-10.24 1.2c-1.03-2.55-2.29-5-3.74-7.33l7-7.58-1.65-2.26-9.38 4.3a48.3 48.3 0 0 0-5.8-5.8L62.42 0h2.16l-1.25 2.72a50.31 50.31 0 0 1 3.95 3.95l9.5-4.36 3.52 4.85-7.08 7.68c.94 1.6 1.79 3.27 2.54 4.98l10.38-1.21 1.85 5.7-9.11 5.12c.39 1.8.68 3.65.87 5.52L90 37v6l-10.25 2.05a49.9 49.9 0 0 1-.87 5.52l9.11 5.12-1.85 5.7-10.38-1.21c-.75 1.7-1.6 3.37-2.54 4.98l7.08 7.68-3.52 4.85-9.5-4.36a50.31 50.31 0 0 1-3.95 3.95l4.36 9.5-4.85 3.52-7.68-7.08c-1.6.94-3.27 1.79-4.98 2.54l1.21 10.38-5.7 1.85-5.12-9.11c-1.8.39-3.65.68-5.52.87L33 100h-6l-2.05-10.25a49.9 49.9 0 0 1-5.52-.87l-5.12 9.11-5.7-1.85 1.21-10.38c-1.7-.75-3.37-1.6-4.98-2.54L0 87.68v-2.66zM0 52.7V27.3l8.38 4.84a22.96 22.96 0 0 0 0 15.72L0 52.7zm0-39.16A39.91 39.91 0 0 1 26 .2v17.15a22.98 22.98 0 0 0-13.62 7.86L0 18.06v-4.52zm0 52.92v-4.52l12.38-7.15A22.98 22.98 0 0 0 26 62.65V79.8A39.91 39.91 0 0 1 0 66.46zM34 79.8V62.65a22.98 22.98 0 0 0 13.62-7.86l14.85 8.58A39.97 39.97 0 0 1 34 79.8zm32.48-23.36l-14.86-8.58a22.96 22.96 0 0 0 0-15.72l14.86-8.58A39.86 39.86 0 0 1 70 40a39.9 39.9 0 0 1-3.52 16.44zm-4.01-39.8L47.62 25.2A22.98 22.98 0 0 0 34 17.35V.2a39.97 39.97 0 0 1 28.47 16.43v.01zM0 50.38l5.98-3.45a25.01 25.01 0 0 1 0-13.88L0 29.6v20.78zm.5-34.35l11.48 6.63c3.27-3.4 7.44-5.8 12.02-6.94V2.47A37.96 37.96 0 0 0 .5 16.04v-.01zm0 47.92A37.96 37.96 0 0 0 24 77.53V64.28a24.97 24.97 0 0 1-12.02-6.95L.5 63.96v-.01zM36 77.53a37.96 37.96 0 0 0 23.5-13.57l-11.48-6.63A24.97 24.97 0 0 1 36 64.28v13.25zm29.5-23.96a37.91 37.91 0 0 0 0-27.14l-11.48 6.63a25.01 25.01 0 0 1 0 13.88l11.49 6.63h-.01zm-6-37.53A37.96 37.96 0 0 0 36 2.47v13.25c4.66 1.15 8.8 3.6 12.02 6.95l11.48-6.63zM30 54a14 14 0 1 1 0-28 14 14 0 0 1 0 28zm0-2a12 12 0 1 0 0-24 12 12 0 0 0 0 24zm0-2a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm77.47 45.17l-1.62-5.97 5.67-2.06 2.61 5.64c1.09-.25 2.2-.44 3.33-.58l.52-6.2h6.04l.52 6.2c1.13.14 2.24.33 3.33.58l2.6-5.64 5.68 2.06-1.62 5.97c1.02.51 2 1.07 2.95 1.69l4.35-4.38 4.62 3.88-3.53 5c.8.84 1.53 1.71 2.23 2.62l5.52-2.6 3.02 5.23-4.98 3.46c.46 1.06.86 2.14 1.2 3.25l6.02-.54 1.05 5.94-5.84 1.54c.07 1.16.07 2.32 0 3.48l5.84 1.54-1.05 5.94-6.02-.54c-.34 1.1-.74 2.2-1.2 3.25l4.98 3.46-3.02 5.22-5.52-2.6c-.7.92-1.44 1.8-2.23 2.62l3.53 5-4.62 3.89-4.35-4.38a30.2 30.2 0 0 1-2.95 1.69l1.62 5.97-5.67 2.06-2.61-5.64c-1.09.25-2.2.44-3.33.58l-.52 6.2h-6.04l-.52-6.2a30.27 30.27 0 0 1-3.33-.58l-2.6 5.64-5.68-2.06 1.62-5.97c-1.01-.5-2-1.07-2.95-1.69l-4.35 4.38-4.62-3.88 3.53-5a32.5 32.5 0 0 1-2.23-2.62l-5.52 2.6-3.02-5.23 4.98-3.46a29.66 29.66 0 0 1-1.2-3.25l-6.02.54-1.05-5.94 5.84-1.54a30.28 30.28 0 0 1 0-3.48l-5.84-1.54 1.05-5.94 6.02.54c.34-1.1.74-2.2 1.2-3.25l-4.98-3.46 3.02-5.22 5.52 2.6c.7-.92 1.44-1.8 2.23-2.62l-3.53-5 4.62-3.89 4.35 4.38a30.2 30.2 0 0 1 2.95-1.69zm15.2-1.12l-.5-6.05h-2.34l-.5 6.05c-2.18.13-4.3.5-6.32 1.1l-2.54-5.5-2.2.8 1.6 5.85a27.97 27.97 0 0 0-5.56 3.21l-4.27-4.3-1.79 1.5 3.5 4.95a28.14 28.14 0 0 0-4.12 4.92l-5.5-2.59-1.16 2.02 4.98 3.46a27.8 27.8 0 0 0-2.2 6.03l-6.03-.55-.4 2.3 5.86 1.54a28.3 28.3 0 0 0 0 6.42l-5.87 1.55.4 2.3 6.05-.56a27.8 27.8 0 0 0 2.2 6.03l-5 3.47 1.17 2.02 5.49-2.59a28.14 28.14 0 0 0 4.12 4.92l-3.5 4.96 1.79 1.5 4.27-4.31a27.97 27.97 0 0 0 5.56 3.21l-1.6 5.85 2.2.8 2.54-5.5c2.02.6 4.14.97 6.32 1.1l.5 6.05h2.34l.5-6.05c2.18-.13 4.3-.5 6.32-1.1l2.54 5.5 2.2-.8-1.6-5.85a27.97 27.97 0 0 0 5.56-3.21l4.27 4.3 1.79-1.5-3.5-4.95a28.14 28.14 0 0 0 4.12-4.92l5.5 2.59 1.16-2.02-4.98-3.46a27.8 27.8 0 0 0 2.2-6.03l6.03.55.4-2.3-5.86-1.54a28.3 28.3 0 0 0 0-6.42l5.87-1.55-.4-2.3-6.05.56a27.8 27.8 0 0 0-2.2-6.03l4.99-3.46-1.17-2.02-5.49 2.59a28.14 28.14 0 0 0-4.12-4.92l3.5-4.96-1.79-1.5-4.27 4.31a27.97 27.97 0 0 0-5.56-3.21l1.6-5.85-2.2-.8-2.54 5.5c-2.02-.6-4.14-.97-6.32-1.1l.01-.01zM121 128a8 8 0 1 1 0-16 8 8 0 0 1 0 16zm0-2a6 6 0 1 0 0-12 6 6 0 0 0 0 12zm0-18a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm8.49 3.51a5 5 0 1 1 6.95-7.2 5 5 0 0 1-6.95 7.2zM133 120a5 5 0 1 1 10 0 5 5 0 0 1-10 0zm-3.51 8.49a5 5 0 1 1 7.2 6.95 5 5 0 0 1-7.2-6.95zM121 132a5 5 0 1 1 0 10 5 5 0 0 1 0-10zm-8.49-3.51a5 5 0 1 1-6.95 7.2 5 5 0 0 1 6.95-7.2zM109 120a5 5 0 1 1-10 0 5 5 0 0 1 10 0zm3.51-8.49a5 5 0 1 1-7.2-6.95 5 5 0 0 1 7.2 6.95zM121 106a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm9.9 4.1a3 3 0 1 0 4.39-4.09 3 3 0 0 0-4.39 4.09zm4.1 9.9a3 3 0 1 0 6 0 3 3 0 0 0-6 0zm-4.1 9.9a3 3 0 1 0 4.09 4.39 3 3 0 0 0-4.09-4.39zM121 134a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm-9.9-4.1a3 3 0 1 0-4.39 4.09 3 3 0 0 0 4.39-4.09zM107 120a3 3 0 1 0-6 0 3 3 0 0 0 6 0zm4.1-9.9a3 3 0 1 0-4.09-4.39 3 3 0 0 0 4.09 4.39zm129.42-6.95v.01c.87.07 1.74.17 2.6.3l1.5-3.91 1.94-3.64 3.89.97v4.13l-.5 4.13c.83.28 1.64.59 2.44.93l2.42-3.43 2.76-3.07 3.54 1.88-1 4-1.49 3.89c.73.47 1.45.97 2.15 1.49l3.19-2.76 3.42-2.3 2.97 2.67-1.93 3.65-2.38 3.4c.6.64 1.2 1.3 1.76 1.99l3.68-1.94 3.85-1.48 2.29 3.28-2.7 3.11-3.12 2.82c.43.76.84 1.53 1.22 2.32l4.04-1 4.1-.5 1.43 3.73-3.37 2.37-3.7 1.98c.23.84.44 1.68.62 2.54l4.17.01 4.1.5.48 3.97-3.85 1.48-4.06 1.02c.03.87.03 1.75 0 2.62l4.06 1.02 3.85 1.48-.48 3.97-4.1.51h-4.17c-.18.86-.39 1.71-.63 2.54l3.7 1.98 3.38 2.37-1.43 3.73-4.1-.5-4.04-1c-.38.79-.79 1.56-1.22 2.32l3.13 2.82 2.7 3.11-2.3 3.28-3.85-1.48-3.68-1.95a37 37 0 0 1-1.76 2l2.38 3.41 1.93 3.64-2.97 2.67-3.42-2.3-3.19-2.76a40.1 40.1 0 0 1-2.15 1.48l1.48 3.9 1 4-3.53 1.88-2.76-3.07-2.42-3.43c-.8.33-1.61.65-2.45.93l.5 4.13v4.13l-3.88.97-1.94-3.65-1.5-3.9c-.86.13-1.73.23-2.6.31L240 187l-1 4h-4l-1-4-.52-4.16a37.6 37.6 0 0 1-2.6-.3l-1.5 3.91-1.94 3.64-3.89-.97v-4.13l.5-4.13c-.83-.28-1.64-.59-2.44-.93l-2.42 3.43-2.76 3.07-3.54-1.88 1-4 1.49-3.89c-.74-.47-1.45-.97-2.15-1.49l-3.19 2.76-3.42 2.3-2.97-2.67 1.93-3.65 2.38-3.4c-.61-.65-1.2-1.31-1.76-1.99l-3.68 1.94-3.85 1.48-2.29-3.28 2.7-3.11 3.12-2.82c-.43-.76-.84-1.53-1.22-2.32l-4.04 1-4.1.5-1.43-3.73 3.37-2.37 3.7-1.98c-.23-.84-.44-1.68-.62-2.54l-4.17-.01-4.1-.5-.48-3.97 3.85-1.48 4.06-1.02c-.03-.87-.03-1.75 0-2.62l-4.06-1.02-3.85-1.48.48-3.97 4.1-.51h4.17c.18-.86.39-1.71.63-2.54l-3.7-1.98-3.38-2.37 1.43-3.73 4.1.5 4.04 1c.38-.79.79-1.56 1.22-2.32l-3.13-2.82-2.7-3.11 2.3-3.28 3.85 1.48 3.68 1.95a37 37 0 0 1 1.76-2l-2.38-3.41-1.93-3.64 2.97-2.67 3.42 2.3 3.19 2.76c.7-.52 1.41-1.02 2.15-1.48l-1.48-3.9-1-4 3.53-1.88 2.76 3.07 2.42 3.43c.8-.33 1.61-.65 2.45-.93l-.5-4.13v-4.13l3.88-.97 1.94 3.65 1.5 3.9c.86-.13 1.73-.23 2.6-.31L234 99l1-4h4l1 4 .52 4.15zm-14.3 3.4c-1.83.54-3.6 1.21-5.3 2l-3.5-4.97-1.38-1.53-.88.47.5 2 2.16 5.67a38.09 38.09 0 0 0-4.66 3.22l-4.61-4-1.71-1.15-.75.67.97 1.82 3.47 4.98a38.22 38.22 0 0 0-3.79 4.28l-5.37-2.84-1.92-.74-.57.82 1.35 1.56 4.52 4.09a37.9 37.9 0 0 0-2.64 5l-5.89-1.45-2.04-.25-.36.94 1.69 1.18 5.36 2.87a37.74 37.74 0 0 0-1.35 5.5l-6.08.01-2.04.25-.12 1 1.92.73 5.9 1.5a38.54 38.54 0 0 0 0 5.65l-5.9 1.49-1.92.74.12.99 2.04.25 6.08.01c.31 1.86.77 3.7 1.35 5.5l-5.36 2.87-1.7 1.18.37.94 2.04-.25 5.9-1.46a37.9 37.9 0 0 0 2.63 5.01l-4.52 4.1-1.35 1.55.57.82 1.92-.74 5.37-2.84a38.22 38.22 0 0 0 3.8 4.28l-3.48 4.98-.97 1.82.75.67 1.7-1.15 4.62-4a38.09 38.09 0 0 0 4.66 3.22l-2.17 5.67-.5 2 .89.47 1.38-1.53 3.5-4.98c1.7.8 3.47 1.47 5.3 2l-.73 6.04v2.06l.97.24.97-1.82 2.2-5.68c1.83.36 3.7.6 5.62.68L236 187l.5 2h1l.5-2 .75-6.04a38.2 38.2 0 0 0 5.62-.68l2.2 5.68.97 1.82.97-.24v-2.06l-.73-6.03c1.83-.54 3.6-1.21 5.3-2l3.5 4.97 1.38 1.53.88-.47-.5-2-2.16-5.67a38.09 38.09 0 0 0 4.66-3.22l4.61 4 1.71 1.15.75-.67-.97-1.82-3.47-4.98a38.22 38.22 0 0 0 3.79-4.28l5.37 2.84 1.92.74.57-.82-1.35-1.56-4.52-4.09c1-1.6 1.88-3.27 2.64-5l5.89 1.45 2.04.25.36-.94-1.69-1.18-5.36-2.87a37.4 37.4 0 0 0 1.35-5.5l6.08-.01 2.04-.25.12-1-1.92-.73-5.9-1.5c.14-1.88.14-3.77 0-5.65l5.9-1.49 1.92-.74-.12-.99-2.04-.25-6.08-.01a37.4 37.4 0 0 0-1.35-5.5l5.36-2.87 1.7-1.18-.37-.94-2.04.25-5.9 1.46a37.9 37.9 0 0 0-2.63-5.01l4.52-4.1 1.35-1.55-.57-.82-1.92.74-5.37 2.84a38.22 38.22 0 0 0-3.8-4.28l3.48-4.98.97-1.82-.75-.67-1.7 1.15-4.62 4a38.09 38.09 0 0 0-4.66-3.22l2.17-5.67.5-2-.89-.47-1.38 1.53-3.5 4.98c-1.7-.8-3.47-1.47-5.3-2l.73-6.04v-2.06l-.97-.24-.97 1.82-2.2 5.68c-1.83-.36-3.7-.6-5.62-.68L238 99l-.5-2h-1l-.5 2-.75 6.04c-1.92.09-3.8.32-5.62.68l-2.2-5.68-.97-1.82-.97.24v2.06l.73 6.03zm-5.85 5.65A34.82 34.82 0 0 1 236 108v6a28.8 28.8 0 0 0-12.63 3.39l-3-5.2v.01zm2.8.83l1 1.74a30.8 30.8 0 0 1 9.83-2.63v-2.01a32.8 32.8 0 0 0-10.83 2.9zm-4.53.17l3 5.2a29.12 29.12 0 0 0-9.24 9.24l-5.2-3a35.18 35.18 0 0 1 11.44-11.44zm-.67 2.84a33.19 33.19 0 0 0-7.93 7.93l1.74 1a31.18 31.18 0 0 1 7.2-7.2l-1.01-1.73zm-11.77 10.33h-.01l5.2 3A28.8 28.8 0 0 0 208 142h-6a34.82 34.82 0 0 1 4.2-15.63zm.83 2.8a32.8 32.8 0 0 0-2.9 10.83h2.01a30.8 30.8 0 0 1 2.63-9.83l-1.74-1zM202.01 144h6.01c.15 4.41 1.3 8.73 3.38 12.63l-5.2 3a34.82 34.82 0 0 1-4.19-15.63zm2.12 2a32.8 32.8 0 0 0 2.9 10.84l1.74-1a30.8 30.8 0 0 1-2.63-9.84h-2.01zm3.07 15.36l5.2-3c2.34 3.74 5.5 6.9 9.24 9.24l-3 5.2a35.18 35.18 0 0 1-11.44-11.44zm2.84.67a33.19 33.19 0 0 0 7.93 7.93l1-1.74a31.18 31.18 0 0 1-7.2-7.2l-1.73 1.01zm10.33 11.77v.01l3-5.2A28.85 28.85 0 0 0 236 172v6a34.82 34.82 0 0 1-15.63-4.2zm2.8-.83a32.8 32.8 0 0 0 10.83 2.9v-2.01a30.8 30.8 0 0 1-9.83-2.63l-1 1.74zm14.83 5.02v-6.01c4.41-.15 8.73-1.3 12.63-3.38l3 5.2a34.82 34.82 0 0 1-15.63 4.19zm2-2.12a32.8 32.8 0 0 0 10.84-2.9l-1-1.74a30.8 30.8 0 0 1-9.84 2.63v2.01zm15.36-3.07l-3-5.2c3.74-2.34 6.9-5.5 9.24-9.24l5.2 3a35.18 35.18 0 0 1-11.44 11.44zm.67-2.84a33.19 33.19 0 0 0 7.93-7.93l-1.74-1a31.18 31.18 0 0 1-7.2 7.2l1.01 1.73zm11.77-10.33h.01l-5.2-3A28.85 28.85 0 0 0 266 144h6a34.82 34.82 0 0 1-4.2 15.63zm-.83-2.8a32.8 32.8 0 0 0 2.9-10.83h-2.01a30.8 30.8 0 0 1-2.63 9.83l1.74 1zm5.02-14.83h-6.01a28.85 28.85 0 0 0-3.38-12.63l5.2-3a34.82 34.82 0 0 1 4.19 15.63zm-2.12-2a32.8 32.8 0 0 0-2.9-10.84l-1.74 1a30.8 30.8 0 0 1 2.63 9.84h2.01zm-3.07-15.36l-5.2 3a29.12 29.12 0 0 0-9.24-9.24l3-5.2a35.18 35.18 0 0 1 11.44 11.44zm-2.84-.67a33.19 33.19 0 0 0-7.93-7.93l-1 1.74a31.18 31.18 0 0 1 7.2 7.2l1.73-1.01zM238 108a34.82 34.82 0 0 1 15.63 4.19l-3 5.2a28.85 28.85 0 0 0-12.63-3.38V108zm12.84 5.02a32.8 32.8 0 0 0-10.84-2.9v2.01a30.8 30.8 0 0 1 9.83 2.63l1-1.74h.01zM237 156a13 13 0 1 1 0-26 13 13 0 0 1 0 26zm0-2a11 11 0 1 0 0-22 11 11 0 0 0 0 22zM137.54 0h56.92l-.74 1.03c.57.7 1.12 1.4 1.64 2.14l7.75-2.9 2 3.46-6.38 5.25c.37.82.72 1.65 1.03 2.5l8.22-.8 1.04 3.86-7.52 3.43c.15.88.26 1.77.35 2.67L210 22v4l-8.15 1.36c-.09.9-.2 1.8-.35 2.67l7.52 3.43-1.04 3.86-8.22-.8c-.31.85-.66 1.68-1.03 2.5l6.38 5.25-2 3.46-7.75-2.9c-.52.74-1.07 1.45-1.64 2.14l4.8 6.73-2.82 2.83-6.73-4.8c-.7.56-1.4 1.11-2.14 1.63l2.9 7.75-3.46 2-5.25-6.38c-.82.37-1.65.72-2.5 1.03l.8 8.22-3.86 1.04-3.43-7.52c-.88.15-1.77.26-2.67.35L168 68h-4l-1.36-8.15c-.9-.09-1.8-.2-2.67-.35l-3.43 7.52-3.86-1.04.8-8.22c-.85-.31-1.68-.66-2.5-1.03l-5.25 6.38-3.46-2 2.9-7.75a36.15 36.15 0 0 1-2.14-1.64l-6.73 4.8-2.83-2.82 4.8-6.73c-.56-.7-1.11-1.4-1.63-2.14l-7.75 2.9-2-3.46 6.38-5.25c-.37-.82-.72-1.65-1.03-2.5l-8.22.8-1.04-3.86 7.52-3.43c-.15-.88-.26-1.77-.35-2.67L122 26v-4l8.15-1.36c.09-.9.2-1.8.35-2.67l-7.52-3.43 1.04-3.86 8.22.8c.31-.85.66-1.68 1.03-2.5l-6.38-5.25 2-3.46 7.75 2.9c.52-.74 1.07-1.45 1.64-2.14L137.54 0zm2.43 0l.83 1.17a34.14 34.14 0 0 0-3.38 4.4l-7.63-2.86-.33.58 6.29 5.18a33.79 33.79 0 0 0-2.13 5.12l-8.1-.78-.18.64 7.42 3.37a34.02 34.02 0 0 0-.72 5.5L124 23.68v.66l8.04 1.34c.1 1.88.33 3.72.72 5.5l-7.42 3.38.18.64 8.1-.78a33.88 33.88 0 0 0 2.13 5.12l-6.29 5.18.33.58 7.63-2.86c1 1.56 2.14 3.03 3.38 4.4l-4.73 6.63.47.47 6.63-4.73a34.14 34.14 0 0 0 4.4 3.38l-2.86 7.63.58.33 5.18-6.29c1.63.84 3.35 1.56 5.12 2.13l-.78 8.1.64.18 3.37-7.42c1.79.39 3.63.63 5.5.72l1.35 8.04h.66l1.34-8.04c1.88-.1 3.72-.33 5.5-.72l3.38 7.42.64-.18-.78-8.1a33.88 33.88 0 0 0 5.12-2.13l5.18 6.29.58-.33-2.86-7.63c1.56-1 3.03-2.14 4.4-3.38l6.63 4.73.47-.47-4.73-6.63a34.14 34.14 0 0 0 3.38-4.4l7.63 2.86.33-.58-6.29-5.18a33.79 33.79 0 0 0 2.13-5.12l8.1.78.18-.64-7.42-3.37c.39-1.79.63-3.63.72-5.5l8.04-1.35v-.66l-8.04-1.34c-.1-1.88-.33-3.72-.72-5.5l7.42-3.38-.18-.64-8.1.78a33.79 33.79 0 0 0-2.13-5.12l6.29-5.18-.33-.58-7.63 2.86c-1-1.56-2.14-3.03-3.38-4.4l.83-1.17h-52.06V0zm-2.82 27h14.15A15.02 15.02 0 0 0 163 38.7v14.15A29.01 29.01 0 0 1 137.15 27zm12.57-27H163v9.3A15.02 15.02 0 0 0 151.3 21h-14.15a28.99 28.99 0 0 1 12.57-21zM169 52.85V38.7A15.02 15.02 0 0 0 180.7 27h14.15A29.01 29.01 0 0 1 169 52.85zM182.28 0a28.99 28.99 0 0 1 12.57 21H180.7A15.02 15.02 0 0 0 169 9.3V0h13.28zm-42.82 29A27.03 27.03 0 0 0 161 50.54V40.25A17.04 17.04 0 0 1 149.75 29h-10.29zm14.16-29a27.04 27.04 0 0 0-14.16 19h10.29A17.04 17.04 0 0 1 161 7.75V0h-7.38zM171 50.54A27.03 27.03 0 0 0 192.54 29h-10.29A17.04 17.04 0 0 1 171 40.25v10.29zM178.38 0H171v7.75A17.04 17.04 0 0 1 182.25 19h10.29a27.04 27.04 0 0 0-14.16-19zM166 34a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm-39.51 176.15l-10.67-7.95 6-10.4 12.23 5.27a23.97 23.97 0 0 1 8.4-4.86L144 177h12l1.55 13.21a23.97 23.97 0 0 1 8.4 4.86l12.23-5.27 6 10.4-10.67 7.95a24 24 0 0 1 0 9.7l10.67 7.95-6 10.4-12.23-5.27a23.97 23.97 0 0 1-8.4 4.86L156 249h-12l-1.55-13.21a23.97 23.97 0 0 1-8.4-4.86l-12.23 5.27-6-10.4 10.67-7.95a24.1 24.1 0 0 1 0-9.7zm29.25-16.4l-1.5-12.75h-8.48l-1.5 12.76c-3.75 1-7.1 2.99-9.79 5.65l-11.8-5.08-4.23 7.34 10.3 7.68c-.98 3.7-.98 7.6 0 11.3l-10.3 7.68 4.23 7.34 11.8-5.08a22.1 22.1 0 0 0 9.8 5.65l1.5 12.76h8.47l1.5-12.76c3.75-1 7.1-2.99 9.79-5.65l11.8 5.08 4.23-7.34-10.3-7.68c.98-3.7.98-7.6 0-11.3l10.3-7.68-4.23-7.34-11.8 5.08a21.98 21.98 0 0 0-9.8-5.65l.01-.01zM150 225a12 12 0 1 1 0-24 12 12 0 0 1 0 24zm0-2a10 10 0 1 0 0-20 10 10 0 0 0 0 20zm3.53 67.72l4.26.07.51 1.93-3.65 2.19c.11.63.2 1.27.25 1.92L159 298v2l-4.1 1.17c-.05.65-.14 1.29-.25 1.92l3.65 2.2-.51 1.92-4.26.07c-.22.61-.47 1.21-.74 1.8l2.96 3.05-1 1.74-4.13-1.04a24.1 24.1 0 0 1-1.18 1.54l2.07 3.72-1.42 1.42-3.72-2.07c-.5.41-1.01.8-1.54 1.18l1.04 4.13-1.74 1-3.05-2.96c-.59.27-1.19.52-1.8.74l-.07 4.26-1.93.51-2.19-3.65c-.63.11-1.27.2-1.92.25L132 327h-2l-1.17-4.1c-.65-.05-1.29-.14-1.92-.25l-2.2 3.65-1.92-.51-.07-4.26c-.61-.22-1.21-.47-1.8-.74l-3.05 2.96-1.74-1 1.04-4.13a24.1 24.1 0 0 1-1.54-1.18l-3.72 2.07-1.42-1.42 2.07-3.72c-.41-.5-.8-1.01-1.18-1.54l-4.13 1.04-1-1.74 2.96-3.05c-.27-.59-.52-1.19-.74-1.8l-4.26-.07-.51-1.93 3.65-2.19c-.11-.63-.2-1.27-.25-1.92L103 300v-2l4.1-1.17c.05-.65.14-1.29.25-1.92l-3.65-2.2.51-1.92 4.26-.07c.22-.61.47-1.21.74-1.8l-2.96-3.05 1-1.74 4.13 1.04c.38-.53.77-1.04 1.18-1.54l-2.07-3.72 1.42-1.42 3.72 2.07c.5-.41 1.01-.8 1.54-1.18l-1.04-4.13 1.74-1 3.05 2.96c.59-.27 1.19-.52 1.8-.74l.07-4.26 1.93-.51 2.19 3.65c.63-.11 1.27-.2 1.92-.25L130 271h2l1.17 4.1c.65.05 1.29.14 1.92.25l2.2-3.65 1.92.51.07 4.26c.61.22 1.21.47 1.8.74l3.05-2.96 1.74 1-1.04 4.13c.53.38 1.04.77 1.54 1.18l3.72-2.07 1.42 1.42-2.07 3.72c.41.5.8 1.01 1.18 1.54l4.13-1.04 1 1.74-2.96 3.05c.27.59.52 1.19.74 1.8zM109 299a22 22 0 1 0 44 0 22 22 0 0 0-44 0zm27.11-10.86l-3 5.22a6 6 0 0 0-4.21 0l-3.01-5.22a11.95 11.95 0 0 1 10.22 0zm1.74 1a12 12 0 0 1 5.1 8.86h-6.01a6.01 6.01 0 0 0-2.1-3.64l3-5.22h.01zm-13.7 0l3.02 5.22a6.01 6.01 0 0 0-2.1 3.64h-6.03a12 12 0 0 1 5.11-8.86zm-5.1 10.86h6.01a6.01 6.01 0 0 0 2.1 3.64l-3 5.22a12 12 0 0 1-5.12-8.86h.01zm6.84 9.86l3-5.22a6 6 0 0 0 4.21 0l3.01 5.22a11.95 11.95 0 0 1-10.22 0zm11.96-1l-3.02-5.22a6.01 6.01 0 0 0 2.1-3.64h6.03a12 12 0 0 1-5.11 8.86zm-4.68-19.62a10.04 10.04 0 0 0-4.34 0l1.05 1.82c.74-.1 1.5-.1 2.24 0l1.05-1.82zm5.2 3l-1.05 1.82c.46.59.84 1.24 1.12 1.94h2.1a9.99 9.99 0 0 0-2.17-3.76zm-14.74 0a9.99 9.99 0 0 0-2.17 3.76h2.1c.28-.7.66-1.35 1.12-1.94l-1.05-1.82zm-2.17 9.76a9.99 9.99 0 0 0 2.17 3.76l1.05-1.82a8.01 8.01 0 0 1-1.12-1.94h-2.1zm7.37 6.76c1.43.32 2.91.32 4.34 0l-1.05-1.82c-.74.1-1.5.1-2.24 0l-1.05 1.82zm9.54-3a9.99 9.99 0 0 0 2.17-3.76h-2.1c-.28.7-.66 1.35-1.12 1.94l1.05 1.82zM127 299a4 4 0 1 1 8 0 4 4 0 0 1-8 0zm2 0a2 2 0 1 0 4 0 2 2 0 0 0-4 0zm15 0a4 4 0 1 1 8 0 4 4 0 0 1-8 0zm-6.5 11.26a4 4 0 1 1 4 6.93 4 4 0 0 1-4-6.93zm-13 0a4 4 0 1 1-4 6.93 4 4 0 0 1 4-6.93zM118 299a4 4 0 1 1-8 0 4 4 0 0 1 8 0zm6.5-11.26a4 4 0 1 1-4-6.93 4 4 0 0 1 4 6.93zm13 0a4 4 0 1 1 4-6.93 4 4 0 0 1-4 6.93zM146 299a2 2 0 1 0 4 0 2 2 0 0 0-4 0zm-7.5 12.99a2 2 0 1 0 1.66 3.64 2 2 0 0 0-1.66-3.64zm-15 0a2 2 0 1 0-2.15 3.38 2 2 0 0 0 2.15-3.38zM116 299a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm7.5-12.99a2 2 0 1 0-1.66-3.64 2 2 0 0 0 1.66 3.64zm15 0a2 2 0 1 0 2.15-3.38 2 2 0 0 0-2.15 3.38zm103.8-61.7l-.8-8.22 5.8-1.55 3.42 7.52c2.26-.43 4.57-.74 6.92-.9L259 213h6l1.36 8.16c2.35.16 4.66.47 6.92.9l3.42-7.52 5.8 1.55-.8 8.22c2.21.77 4.37 1.66 6.45 2.68l5.25-6.38 5.2 3-2.9 7.74a60.25 60.25 0 0 1 5.53 4.25l6.73-4.8 4.24 4.24-4.8 6.73a60.25 60.25 0 0 1 4.25 5.53l7.74-2.9 3 5.2-6.38 5.25a59.62 59.62 0 0 1 2.68 6.45l8.22-.8 1.55 5.8-7.52 3.42c.43 2.26.74 4.57.9 6.92L330 278v6l-8.16 1.36a60.03 60.03 0 0 1-.9 6.92l7.52 3.42-1.55 5.8-8.22-.8a59.62 59.62 0 0 1-2.68 6.45l6.38 5.25-3 5.2-7.74-2.9a60.25 60.25 0 0 1-4.25 5.53l4.8 6.73-4.24 4.24-6.73-4.8a60.25 60.25 0 0 1-5.53 4.25l2.9 7.74-5.2 3-5.25-6.38a59.62 59.62 0 0 1-6.45 2.68l.8 8.22-5.8 1.55-3.42-7.52c-2.26.43-4.57.74-6.92.9L265 349h-6l-1.36-8.16a60.03 60.03 0 0 1-6.92-.9l-3.42 7.52-5.8-1.55.8-8.22a59.62 59.62 0 0 1-6.45-2.68l-5.25 6.38-5.2-3 2.9-7.74a60.25 60.25 0 0 1-5.53-4.25l-6.73 4.8-4.24-4.24 4.8-6.73a60.25 60.25 0 0 1-4.25-5.53l-7.74 2.9-3-5.2 6.38-5.25a59.62 59.62 0 0 1-2.68-6.45l-8.22.8-1.55-5.8 7.52-3.42c-.43-2.29-.73-4.6-.9-6.92L194 284v-6l8.16-1.36c.16-2.35.47-4.66.9-6.92l-7.52-3.42 1.55-5.8 8.22.8c.77-2.2 1.66-4.35 2.68-6.45l-6.38-5.25 3-5.2 7.74 2.9a60.25 60.25 0 0 1 4.25-5.53l-4.8-6.73 4.24-4.24 6.73 4.8a60.25 60.25 0 0 1 5.53-4.25l-2.9-7.74 5.2-3 5.25 6.38a59.62 59.62 0 0 1 6.45-2.68zm2.12 1.4c-3.15 1-6.19 2.27-9.08 3.77l-5.19-6.3-2.3 1.33 2.86 7.65a58.24 58.24 0 0 0-7.79 5.98l-6.65-4.75-1.88 1.88 4.75 6.65a58.24 58.24 0 0 0-5.98 7.79l-7.65-2.86-1.33 2.3 6.3 5.2a57.64 57.64 0 0 0-3.77 9.07l-8.12-.79-.69 2.58 7.43 3.38a58 58 0 0 0-1.27 9.73l-8.06 1.35v2.66l8.06 1.35c.15 3.32.58 6.58 1.27 9.73l-7.43 3.38.7 2.58 8.11-.79c1 3.15 2.27 6.19 3.77 9.08l-6.3 5.19 1.33 2.3 7.65-2.86a58.24 58.24 0 0 0 5.98 7.79l-4.75 6.65 1.88 1.88 6.65-4.75a60.3 60.3 0 0 0 7.79 5.98l-2.86 7.65 2.3 1.33 5.2-6.3a56.99 56.99 0 0 0 9.07 3.77l-.79 8.12 2.58.69 3.38-7.43c3.15.69 6.4 1.12 9.73 1.27l1.35 8.06h2.66l1.35-8.06c3.32-.15 6.58-.58 9.73-1.27l3.38 7.43 2.58-.7-.79-8.11c3.15-1 6.19-2.27 9.08-3.77l5.19 6.3 2.3-1.33-2.86-7.65a58.24 58.24 0 0 0 7.79-5.98l6.65 4.75 1.88-1.88-4.75-6.65a60.3 60.3 0 0 0 5.98-7.79l7.65 2.86 1.33-2.3-6.3-5.2a56.99 56.99 0 0 0 3.77-9.07l8.12.79.69-2.58-7.43-3.38a58 58 0 0 0 1.27-9.73l8.06-1.35v-2.66l-8.06-1.35a58.04 58.04 0 0 0-1.27-9.73l7.43-3.38-.7-2.58-8.11.79c-1-3.15-2.27-6.19-3.77-9.08l6.3-5.19-1.33-2.3-7.65 2.86a58.24 58.24 0 0 0-5.98-7.79l4.75-6.65-1.88-1.88-6.65 4.75a58.24 58.24 0 0 0-7.79-5.98l2.86-7.65-2.3-1.33-5.2 6.3a57.64 57.64 0 0 0-9.07-3.77l.79-8.12-2.58-.69-3.38 7.43a58 58 0 0 0-9.73-1.27l-1.35-8.06h-2.66l-1.35 8.06c-3.32.15-6.58.58-9.73 1.27l-3.38-7.43-2.58.7.79 8.11zm4.58 50.1a13.96 13.96 0 0 0 0 10.39l-33.88 19.55A52.77 52.77 0 0 1 209 281c0-8.94 2.21-17.37 6.12-24.75L249 275.8v.01zm2-3.47l-33.87-19.56A52.97 52.97 0 0 1 260 228.04v39.1a13.99 13.99 0 0 0-9 5.2zm0 17.32a13.99 13.99 0 0 0 9 5.2v39.1a52.97 52.97 0 0 1-42.87-24.74L251 289.66zm13 5.2a13.99 13.99 0 0 0 9-5.2l33.87 19.56A52.97 52.97 0 0 1 264 333.96v-39.1zm11-8.66a13.96 13.96 0 0 0 0-10.4l33.88-19.55A52.77 52.77 0 0 1 315 281c0 8.94-2.21 17.37-6.12 24.75L275 286.2zm-2-13.86a13.99 13.99 0 0 0-9-5.2v-39.1a52.97 52.97 0 0 1 42.87 24.74L273 272.34zm-57.04-13.3A50.8 50.8 0 0 0 211 281a50.8 50.8 0 0 0 4.96 21.96l30.62-17.68c-.78-2.8-.78-5.76 0-8.56l-30.62-17.68zm4-6.93l30.62 17.68a16.08 16.08 0 0 1 7.42-4.29v-35.35a50.96 50.96 0 0 0-38.04 21.96zm0 57.78A50.96 50.96 0 0 0 258 331.85V296.5a15.98 15.98 0 0 1-7.42-4.29l-30.62 17.68zM266 331.85a50.96 50.96 0 0 0 38.04-21.96l-30.62-17.68a16.08 16.08 0 0 1-7.42 4.29v35.35zm42.04-28.89A50.8 50.8 0 0 0 313 281a50.8 50.8 0 0 0-4.96-21.96l-30.62 17.68c.78 2.8.78 5.76 0 8.56l30.62 17.68zm-4-50.85A50.96 50.96 0 0 0 266 230.15v35.35c2.86.74 5.41 2.25 7.42 4.29l30.62-17.68zM262 290a9 9 0 1 1 0-18 9 9 0 0 1 0 18zm0-2a7 7 0 1 0 0-14 7 7 0 0 0 0 14zM0 242.64l2.76.4 4.75 2.27a38.2 38.2 0 0 1 2.85-3.4l-3.06-4.28-1.69-5.11 3.07-2.58 4.74 2.55 3.69 3.76a37.96 37.96 0 0 1 3.84-2.22l-1.42-5.07.17-5.38 3.76-1.37 3.6 4.02 2.17 4.79c1.42-.34 2.88-.6 4.37-.77L34 225l2-5h4l2 5 .4 5.25c1.49.17 2.95.43 4.37.77l2.18-4.8 3.59-4 3.76 1.36.17 5.38-1.42 5.07c1.33.67 2.6 1.41 3.84 2.22l3.69-3.76 4.74-2.55 3.07 2.58-1.69 5.11-3.06 4.29a38.2 38.2 0 0 1 2.85 3.4l4.75-2.28 5.33-.77 2 3.46-3.33 4.23-4.34 2.98c.59 1.36 1.1 2.75 1.52 4.17l5.23-.52 5.27 1.1.7 3.94-4.58 2.84-5.1 1.31a38.6 38.6 0 0 1 0 4.44l5.1 1.3 4.58 2.85-.7 3.93-5.27 1.1-5.23-.5a36.3 36.3 0 0 1-1.52 4.16l4.34 2.98 3.33 4.23-2 3.46-5.33-.77-4.75-2.27a38.2 38.2 0 0 1-2.85 3.4l3.06 4.28 1.69 5.11-3.07 2.58-4.74-2.55-3.69-3.76a37.96 37.96 0 0 1-3.84 2.22l1.42 5.07-.17 5.38-3.76 1.37-3.6-4.02-2.17-4.79c-1.42.34-2.88.6-4.37.77L42 311l-2 5h-4l-2-5-.4-5.25a37.87 37.87 0 0 1-4.37-.77l-2.18 4.8-3.59 4-3.76-1.36-.17-5.38 1.42-5.07c-1.32-.66-2.6-1.4-3.84-2.22l-3.69 3.76-4.74 2.55-3.07-2.58 1.69-5.11 3.06-4.29a38.2 38.2 0 0 1-2.85-3.4l-4.75 2.28-2.76.4v-8.17l3.1-2.13a37.72 37.72 0 0 1-1.52-4.17l-1.58.16v-8.82l.06-.01a38.6 38.6 0 0 1 0-4.44l-.06-.01v-8.82l1.58.16c.43-1.43.94-2.82 1.52-4.17L0 250.8v-8.17.01zm0 1.87v3.89l5.62 3.84a35.74 35.74 0 0 0-2.55 7.02l-3.07-.3v4.75l2.2.56a36.42 36.42 0 0 0 0 7.46l-2.2.56v4.75l3.07-.3a35.2 35.2 0 0 0 2.55 7.02L0 287.6v3.89l1.76-.26 6.41-3.07c1.4 2.06 3 3.98 4.8 5.71l-4.14 5.78-1.01 3.07 1.22 1.03 2.85-1.52 4.98-5.08c2 1.45 4.16 2.7 6.45 3.73l-1.9 6.84.1 3.23 1.5.55 2.15-2.4 2.94-6.48a35.9 35.9 0 0 0 7.34 1.3L36 311l1.2 3h1.6l1.2-3 .55-7.09a35.9 35.9 0 0 0 7.34-1.29l2.94 6.47 2.15 2.4 1.5-.54.1-3.23-1.9-6.84a35.96 35.96 0 0 0 6.45-3.73l4.98 5.08 2.85 1.52 1.22-1.03-1-3.07-4.15-5.78a35.8 35.8 0 0 0 4.8-5.7l6.4 3.06 3.2.46.8-1.38-2-2.54-5.85-4.01c1.1-2.24 1.95-4.6 2.55-7.02l7.07.7 3.16-.66.28-1.58-2.75-1.7-6.88-1.77c.26-2.48.26-4.98 0-7.46l6.88-1.77 2.75-1.7-.28-1.58-3.16-.66-7.07.7a35.74 35.74 0 0 0-2.55-7.02l5.86-4 2-2.55-.8-1.38-3.2.46-6.41 3.07c-1.4-2.06-3-3.98-4.8-5.71l4.14-5.78 1.01-3.07-1.22-1.03-2.85 1.52-4.98 5.08c-2-1.45-4.16-2.7-6.45-3.73l1.9-6.84-.1-3.23-1.5-.55-2.15 2.4-2.94 6.48a35.9 35.9 0 0 0-7.34-1.3L40 225l-1.2-3h-1.6l-1.2 3-.55 7.09c-2.48.17-4.94.6-7.34 1.29l-2.94-6.47-2.15-2.4-1.5.54-.1 3.23 1.9 6.84a35.96 35.96 0 0 0-6.45 3.73l-4.98-5.08-2.85-1.52-1.22 1.03 1 3.07 4.15 5.78a36.18 36.18 0 0 0-4.8 5.7l-6.4-3.06L0 244.5v.01zM38 272a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0-26a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm24 24a4 4 0 1 1 8 0 4 4 0 0 1-8 0zm-24 24a4 4 0 1 1 0 8 4 4 0 0 1 0-8zm-24-24a4 4 0 1 1-8 0 4 4 0 0 1 8 0zm24-26a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm26 26a2 2 0 1 0 4 0 2 2 0 0 0-4 0zm-26 26a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-26-26a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm3.37 22.63a12 12 0 1 1 16.17-17.74 12 12 0 0 1-16.17 17.74zm0-45.26a12 12 0 1 1 17.74 16.17 12 12 0 0 1-17.74-16.17zm45.26 0a12 12 0 1 1-16.17 17.74 12 12 0 0 1 16.17-17.74zm0 45.26a12 12 0 1 1-17.74-16.17 12 12 0 0 1 17.74 16.17zm-15.56-29.7a10 10 0 1 0 14.39-13.9 10 10 0 0 0-14.39 13.9zm0 14.14a10 10 0 1 0 13.9 14.39 10 10 0 0 0-13.9-14.39zm-14.14 0a10 10 0 1 0-14.39 13.9 10 10 0 0 0 14.39-13.9zm0-14.14a10 10 0 1 0-13.9-14.39 10 10 0 0 0 13.9 14.39zm230.9-245.4l-.08-4.18 1.93-.52 2.04 3.67c1.07-.2 2.16-.35 3.26-.43L270 10h2l1.02 4.07c1.1.08 2.2.22 3.26.43l2.04-3.67 1.93.52-.07 4.19a27 27 0 0 1 3.04 1.26l2.91-3.01 1.74 1-1.16 4.03c.91.62 1.78 1.29 2.61 2l3.6-2.15 1.41 1.41-2.16 3.6c.72.83 1.4 1.7 2 2.6l4.04-1.15 1 1.74-3.01 2.91c.48.98.9 2 1.26 3.04l4.2-.07.5 1.93-3.66 2.04c.2 1.07.35 2.16.43 3.26L303 41v2l-4.07 1.02a26.9 26.9 0 0 1-.43 3.26l3.67 2.04-.52 1.93-4.19-.07a27.82 27.82 0 0 1-1.26 3.04l3.01 2.91-1 1.74-4.03-1.16c-.62.91-1.29 1.78-2 2.61l2.15 3.6-1.41 1.41-3.6-2.16c-.83.72-1.7 1.4-2.6 2l1.15 4.04-1.74 1-2.91-3.01a27 27 0 0 1-3.04 1.26l.07 4.2-1.93.5-2.04-3.66c-1.07.2-2.16.35-3.26.43L272 74h-2l-1.02-4.07a26.9 26.9 0 0 1-3.26-.43l-2.04 3.67-1.93-.52.07-4.19a27.82 27.82 0 0 1-3.04-1.26l-2.91 3.01-1.74-1 1.16-4.03c-.9-.62-1.78-1.29-2.61-2l-3.6 2.15-1.41-1.41 2.16-3.6c-.72-.83-1.4-1.7-2-2.6l-4.04 1.15-1-1.74 3.01-2.91a27 27 0 0 1-1.26-3.04l-4.2.07-.5-1.93 3.66-2.04c-.2-1.07-.35-2.16-.43-3.26L239 43v-2l4.07-1.02c.08-1.1.22-2.2.43-3.26l-3.67-2.04.52-1.93 4.19.07a27 27 0 0 1 1.26-3.04l-3.01-2.91 1-1.74 4.03 1.16c.62-.91 1.29-1.78 2-2.61l-2.15-3.6 1.41-1.41 3.6 2.16c.83-.72 1.7-1.4 2.6-2l-1.15-4.04 1.74-1 2.91 3.01a27 27 0 0 1 3.04-1.26l.01-.01zM271 68a26 26 0 1 0 0-52 26 26 0 0 0 0 52zm0-9a17 17 0 1 1 0-34 17 17 0 0 1 0 34zm0-2a15 15 0 1 0 0-30 15 15 0 0 0 0 30zm0-8a7 7 0 1 1 0-14 7 7 0 0 1 0 14zm0-2a5 5 0 1 0 0-10 5 5 0 0 0 0 10zm0-14a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm9 9a2 2 0 1 1 4 0 2 2 0 0 1-4 0zm-9 9a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm-9-9a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm47.93 53.79l-1.8-3.91 1.63-1.18 3.15 2.92c.4-.17.82-.3 1.25-.4L315 89h2l.84 4.21c.43.1.85.24 1.25.4l3.15-2.9 1.62 1.17-1.8 3.9c.3.33.55.69.78 1.06l4.26-.5.62 1.9-3.75 2.1c.04.44.04.87 0 1.31l3.75 2.1-.62 1.9-4.26-.5c-.23.38-.49.74-.77 1.06l1.8 3.91-1.63 1.18-3.15-2.92c-.4.17-.82.3-1.25.4L317 113h-2l-.84-4.21c-.43-.1-.85-.24-1.25-.4l-3.15 2.9-1.62-1.17 1.8-3.9a8.03 8.03 0 0 1-.78-1.06l-4.26.5-.62-1.9 3.75-2.1a8.1 8.1 0 0 1 0-1.31l-3.75-2.1.62-1.9 4.26.5c.23-.38.49-.74.77-1.06zM316 106a5 5 0 1 0 0-10 5 5 0 0 0 0 10zM75.73 179.2l-.6-2.1 1.74-1 1.51 1.57a9.93 9.93 0 0 1 2.1-.55L81 175h2l.53 2.12c.72.1 1.42.3 2.09.55l1.51-1.56 1.74 1-.6 2.1c.56.45 1.07.96 1.52 1.52l2.1-.6 1 1.74-1.56 1.51c.25.67.44 1.37.55 2.1L94 186v2l-2.12.53a9.9 9.9 0 0 1-.55 2.09l1.56 1.51-1 1.74-2.1-.6a9.93 9.93 0 0 1-1.52 1.52l.6 2.1-1.74 1-1.51-1.56c-.67.25-1.37.44-2.1.55L83 199h-2l-.53-2.12c-.71-.1-1.42-.3-2.09-.55l-1.51 1.56-1.74-1 .6-2.1a9.93 9.93 0 0 1-1.52-1.52l-2.1.6-1-1.74 1.56-1.51a9.93 9.93 0 0 1-.55-2.1L70 188v-2l2.12-.53c.1-.72.3-1.42.55-2.09l-1.56-1.51 1-1.74 2.1.6c.45-.56.96-1.07 1.52-1.52v-.01zm2.15.94a8.04 8.04 0 0 0-2.74 2.74l-.14.25a7.96 7.96 0 0 0 0 7.74l.14.25a8.04 8.04 0 0 0 2.74 2.74l.25.14a7.96 7.96 0 0 0 7.74 0l.25-.14a8.04 8.04 0 0 0 2.74-2.74l.14-.25a7.96 7.96 0 0 0 0-7.74l-.14-.25a8.04 8.04 0 0 0-2.74-2.74l-.25-.14a7.96 7.96 0 0 0-7.74 0l-.25.14zM82 193a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm278 3.18l-3.8 5.6-7.18-3.51 2.6-8.07a32.15 32.15 0 0 1-3.07-2.46l-7.27 4.35-5.04-6.22 5.82-6.26c-.64-1.13-1.2-2.3-1.7-3.52l-8.45.73-1.8-7.8 7.95-3.07a32.5 32.5 0 0 1 0-3.9l-7.95-3.07 1.8-7.8 8.45.73a31.7 31.7 0 0 1 1.7-3.52l-5.82-6.26 5.04-6.22 7.27 4.35c.97-.88 2-1.7 3.07-2.46l-2.6-8.07 7.19-3.5 3.79 5.59v64.36zm0-3.53v-57.3l-4.46-6.58-4.1 2 2.53 7.87a30.14 30.14 0 0 0-5.13 4.1l-7.08-4.24-2.88 3.55 5.65 6.09a29.87 29.87 0 0 0-2.82 5.86l-8.24-.7-1.03 4.46 7.73 2.99a30.34 30.34 0 0 0 0 6.5l-7.73 3 1.03 4.45 8.24-.7a29.87 29.87 0 0 0 2.82 5.86l-5.65 6.1 2.88 3.54 7.08-4.23a30.14 30.14 0 0 0 5.13 4.09l-2.54 7.86 4.11 2 4.46-6.57zm0-51.57v5.71l-3.56-3.8a24.94 24.94 0 0 1 3.56-1.91zm0 22.68l-14.17 6.64c-2.5-9.5.77-19.57 8.38-25.78l5.79 10.5v8.64zm0 23.16a25.08 25.08 0 0 1-13.32-13.9l13.32-2.55v16.45zm0-43.64l-.39.2.39.4v-.6zm0 18.29v-2.35l-6.3-11.44a22.93 22.93 0 0 0-6.43 19.76l12.73-5.97zm0 23.15v-12.23l-10.47 2.01A23.1 23.1 0 0 0 360 182.72zM0 129.82l1 1.46a31.8 31.8 0 0 1 3.8-.86L6 122h8l1.2 8.42c1.3.21 2.57.5 3.8.86l4.8-7.06 7.18 3.51-2.6 8.07c1.07.76 2.1 1.58 3.07 2.46l7.27-4.35 5.04 6.22-5.82 6.26c.64 1.13 1.2 2.3 1.7 3.52l8.45-.73 1.8 7.8-7.95 3.07c.08 1.3.08 2.6 0 3.9l7.95 3.07-1.8 7.8-8.45-.73a33.5 33.5 0 0 1-1.7 3.52l5.82 6.26-5.04 6.22-7.27-4.35c-.97.88-2 1.7-3.07 2.46l2.6 8.07-7.19 3.5-4.78-7.05c-1.24.36-2.51.65-3.8.86L14 202H6l-1.2-8.42a31.8 31.8 0 0 1-3.8-.86l-1 1.46v-64.36zm0 3.53v57.3l.2-.29c2.02.7 4.15 1.2 6.34 1.44l1.17 8.2h4.58l1.17-8.2c2.2-.25 4.32-.74 6.35-1.44l4.65 6.87 4.1-2-2.53-7.87a30.14 30.14 0 0 0 5.13-4.1l7.08 4.24 2.88-3.55-5.65-6.09c1.14-1.83 2.1-3.8 2.82-5.86l8.24.7 1.03-4.46-7.73-2.99a30.7 30.7 0 0 0 0-6.5l7.73-3-1.03-4.45-8.24.7a29.87 29.87 0 0 0-2.82-5.86l5.65-6.1-2.88-3.54-7.08 4.23a30.14 30.14 0 0 0-5.13-4.09l2.54-7.86-4.11-2-4.65 6.86a29.82 29.82 0 0 0-6.35-1.44l-1.17-8.2H7.7l-1.17 8.2c-2.2.25-4.32.74-6.35 1.44l-.19-.29H0zm34.17 35.05l-16.26-7.62a7.94 7.94 0 0 0-.8-2.44l8.68-15.72a24.95 24.95 0 0 1 8.38 25.78zm-.85 2.63a25.01 25.01 0 0 1-21.94 15.93l2.23-17.82a8.3 8.3 0 0 0 2.07-1.5l17.64 3.39zM0 139.08A24.92 24.92 0 0 1 10 137c5 0 9.65 1.47 13.56 4l-12.28 13.1a8.06 8.06 0 0 0-2.56 0L0 144.8v-5.72zm0 22.68v-8.65l2.88 5.23c-.4.77-.66 1.59-.79 2.44l-2.09.98zm0 23.16v-16.45l4.32-.83c.6.6 1.3 1.11 2.07 1.5l2.23 17.82c-2.97-.16-5.9-.85-8.62-2.04zM10 156a6 6 0 1 1 0 12 6 6 0 0 1 0-12zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8zM0 141.28v.6l9.48 10.13c.35-.02.7-.02 1.04 0l9.87-10.54A22.9 22.9 0 0 0 10 139c-3.58 0-6.98.82-10 2.28zm0 18.29l.34-.16c.09-.34.2-.67.32-.99l-.66-1.2v2.35zm0 23.15c1.97.95 4.1 1.63 6.34 1.99l-1.8-14.33a11.6 11.6 0 0 1-.83-.6l-3.71.7v12.24zm13.66 1.99a23.03 23.03 0 0 0 16.8-12.21l-14.17-2.72c-.27.21-.55.42-.84.6l-1.79 14.33zm19.07-19.17a22.93 22.93 0 0 0-6.42-19.75l-6.97 12.63c.12.32.23.65.32.99l13.07 6.13zM137.54 360l-4.07-5.7 2.83-2.83 6.73 4.8c.7-.56 1.4-1.11 2.14-1.63l-2.9-7.75 3.46-2 5.25 6.38c.82-.37 1.65-.72 2.5-1.03l-.8-8.22 3.86-1.04 3.43 7.52c.88-.15 1.77-.26 2.67-.35L164 340h4l1.36 8.15c.9.09 1.8.2 2.67.35l3.43-7.52 3.86 1.04-.8 8.22c.85.31 1.68.66 2.5 1.03l5.25-6.38 3.46 2-2.9 7.75c.74.52 1.45 1.07 2.14 1.64l6.73-4.8 2.83 2.82-4.07 5.7h-56.92zm2.43 0h52.06l3.9-5.46-.47-.47-6.63 4.73a34.14 34.14 0 0 0-4.4-3.38l2.86-7.63-.58-.33-5.18 6.29a33.79 33.79 0 0 0-5.12-2.13l.78-8.1-.64-.18-3.37 7.42a34.02 34.02 0 0 0-5.5-.72l-1.35-8.04h-.66l-1.34 8.04c-1.88.1-3.72.33-5.5.72l-3.38-7.42-.64.18.78 8.1a33.88 33.88 0 0 0-5.12 2.13l-5.18-6.29-.58.33 2.86 7.63c-1.56 1-3.03 2.14-4.4 3.38l-6.63-4.73-.47.47 3.9 5.46zm9.75 0a28.83 28.83 0 0 1 13.28-4.85V360h-13.28zm32.56 0H169v-4.85c4.9.5 9.42 2.22 13.28 4.85zm-28.66 0H161v-2.54a26.8 26.8 0 0 0-7.38 2.54zm24.76 0a26.8 26.8 0 0 0-7.38-2.54V360h7.38zM358.79 0h-1.21l1.5 3.28a48.3 48.3 0 0 0-5.8 5.8l-9.38-4.3-1.65 2.26 7 7.58a47.84 47.84 0 0 0-3.74 7.33l-10.24-1.2-.86 2.66 8.99 5.05a47.91 47.91 0 0 0-1.28 8.12L332 38.6v2.8l10.12 2.02c.2 2.78.63 5.5 1.28 8.12l-9 5.05.87 2.66 10.24-1.2c1.04 2.54 2.29 5 3.74 7.33l-7 7.58 1.65 2.26 9.38-4.3a48.3 48.3 0 0 0 5.8 5.8l-4.3 9.38 2.26 1.65 2.96-2.73v2.66l-2.84 2.62-4.85-3.52 4.36-9.5a50.31 50.31 0 0 1-3.95-3.95l-9.5 4.36-3.52-4.85 7.08-7.68a49.83 49.83 0 0 1-2.54-4.98l-10.38 1.21-1.85-5.7 9.11-5.12a49.9 49.9 0 0 1-.87-5.52L330 43v-6l10.25-2.05c.19-1.87.48-3.72.87-5.52l-9.11-5.12 1.85-5.7 10.38 1.21c.75-1.71 1.6-3.37 2.54-4.98l-7.08-7.68 3.52-4.85 9.5 4.36a50.31 50.31 0 0 1 3.95-3.95L355.42 0h3.37zM360 52.7l-6.48 3.74A39.86 39.86 0 0 1 350 40a39.9 39.9 0 0 1 3.52-16.44L360 27.3v25.4zm0-39.16v4.52l-2.47-1.43c.77-1.07 1.6-2.1 2.47-3.09zm0 52.92c-.87-.99-1.7-2.02-2.47-3.1l2.47-1.42v4.52zm0-16.07V29.61l-5.5-3.18a37.91 37.91 0 0 0 0 27.14l5.5-3.18zM62.42 360h2.16l3.11-6.78-4.85-3.52-7.68 7.08a49.83 49.83 0 0 0-4.98-2.54l1.21-10.38-5.7-1.85-5.12 9.11a49.9 49.9 0 0 0-5.52-.87L33 340h-6l-2.05 10.25c-1.85.19-3.7.48-5.52.87l-5.12-9.11-5.7 1.85 1.21 10.38c-1.71.75-3.37 1.6-4.98 2.54L0 352.32v5.17-2.5l4.62 4.26a47.84 47.84 0 0 1 7.33-3.74l-1.2-10.24 2.66-.86 5.05 8.99a47.91 47.91 0 0 1 8.12-1.28L28.6 342h2.8l2.02 10.12c2.78.2 5.5.63 8.12 1.28l5.05-9 2.66.87-1.2 10.24c2.54 1.04 5 2.29 7.33 3.74l7.58-7 2.26 1.65-2.8 6.1zM360 244.51l-1.44-.2-.8 1.38 2 2.54.24.17v-3.89zm0 14.45l-4-.4-3.16.66-.28 1.58 2.75 1.7 4.69 1.2v-4.74zm0 13.33l-4.7 1.2-2.74 1.71.28 1.58 3.16.66 4-.4v-4.75zm0 15.31l-.24.17-2 2.54.8 1.38 1.44-.2v-3.89zm0 5.76l-2.57.37-2-3.46 3.33-4.23 1.24-.85v8.17zm0-14.31l-3.65.36-5.27-1.1-.7-3.94 4.58-2.84 5.04-1.3v8.82zm0-13.28l-5.04-1.3-4.58-2.84.7-3.93 5.27-1.1 3.65.35v8.82zm0-14.96l-1.24-.85-3.33-4.23 2-3.46 2.57.37v8.17zm0 101.5V360h-4.58l-3.11-6.78 4.85-3.52 2.84 2.62v-.01zm0 2.67l-2.96-2.73-2.26 1.65 2.8 6.1H360v-5.02z'%3E%3C/path%3E%3C/svg%3E\")", - glamorous: - "url(\"data:image/svg+xml,%3Csvg width='180' height='180' viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M81.28 88H68.413l19.298 19.298L81.28 88zm2.107 0h13.226L90 107.838 83.387 88zm15.334 0h12.866l-19.298 19.298L98.72 88zm-32.927-2.207L73.586 78h32.827l.5.5 7.294 7.293L115.414 87l-24.707 24.707-.707.707L64.586 87l1.207-1.207zm2.62.207L74 80.414 79.586 86H68.414zm16 0L90 80.414 95.586 86H84.414zm16 0L106 80.414 111.586 86h-11.172zm-8-6h11.173L98 85.586 92.414 80zM82 85.586L87.586 80H76.414L82 85.586zM17.414 0L.707 16.707 0 17.414V0h17.414zM4.28 0L0 12.838V0h4.28zm10.306 0L2.288 12.298 6.388 0h8.198zM180 17.414L162.586 0H180v17.414zM165.414 0l12.298 12.298L173.612 0h-8.198zM180 12.838L175.72 0H180v12.838zM0 163h16.413l.5.5 7.294 7.293L25.414 172l-8 8H0v-17zm0 10h6.613l-2.334 7H0v-7zm14.586 7l7-7H8.72l-2.333 7h8.2zM0 165.414L5.586 171H0v-5.586zM10.414 171L16 165.414 21.586 171H10.414zm-8-6h11.172L8 170.586 2.414 165zM180 163h-16.413l-7.794 7.793-1.207 1.207 8 8H180v-17zm-14.586 17l-7-7h12.865l2.333 7h-8.2zM180 173h-6.613l2.334 7H180v-7zm-21.586-2l5.586-5.586 5.586 5.586h-11.172zM180 165.414L174.414 171H180v-5.586zm-8 5.172l5.586-5.586h-11.172l5.586 5.586zM152.933 25.653l1.414 1.414-33.94 33.942-1.416-1.416 33.943-33.94zm1.414 127.28l-1.414 1.414-33.942-33.94 1.416-1.416 33.94 33.943zm-127.28 1.414l-1.414-1.414 33.94-33.942 1.416 1.416-33.943 33.94zm-1.414-127.28l1.414-1.414 33.942 33.94-1.416 1.416-33.94-33.943zM0 85c2.21 0 4 1.79 4 4s-1.79 4-4 4v-8zm180 0c-2.21 0-4 1.79-4 4s1.79 4 4 4v-8zM94 0c0 2.21-1.79 4-4 4s-4-1.79-4-4h8zm0 180c0-2.21-1.79-4-4-4s-4 1.79-4 4h8z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - houndstooth: - "url(\"data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ctitle%3Ehoundstooth%3C/title%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'%3E%3Cpath d='M0 18h6l6-6v6h6l-6 6H0M24 18v6h-6M24 0l-6 6h-6l6-6M12 0v6L0 18v-6l6-6H0V0'/%3E%3C/g%3E%3C/svg%3E\")", - leaf: "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 40' width='80' height='40'%3E%3Cpath fill='FILLCOLOR' fill-opacity='FILLOPACITY' d='M0 40a19.96 19.96 0 0 1 5.9-14.11 20.17 20.17 0 0 1 19.44-5.2A20 20 0 0 1 20.2 40H0zM65.32.75A20.02 20.02 0 0 1 40.8 25.26 20.02 20.02 0 0 1 65.32.76zM.07 0h20.1l-.08.07A20.02 20.02 0 0 1 .75 5.25 20.08 20.08 0 0 1 .07 0zm1.94 40h2.53l4.26-4.24v-9.78A17.96 17.96 0 0 0 2 40zm5.38 0h9.8a17.98 17.98 0 0 0 6.67-16.42L7.4 40zm3.43-15.42v9.17l11.62-11.59c-3.97-.5-8.08.3-11.62 2.42zm32.86-.78A18 18 0 0 0 63.85 3.63L43.68 23.8zm7.2-19.17v9.15L62.43 2.22c-3.96-.5-8.05.3-11.57 2.4zm-3.49 2.72c-4.1 4.1-5.81 9.69-5.13 15.03l6.61-6.6V6.02c-.51.41-1 .85-1.48 1.33zM17.18 0H7.42L3.64 3.78A18 18 0 0 0 17.18 0zM2.08 0c-.01.8.04 1.58.14 2.37L4.59 0H2.07z'%3E%3C/path%3E%3C/svg%3E\")", - "lines-in-motion": - "url(\"data:image/svg+xml,%3Csvg width='120' height='120' viewBox='0 0 120 120' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9 0h2v20H9V0zm25.134.84l1.732 1-10 17.32-1.732-1 10-17.32zm-20 20l1.732 1-10 17.32-1.732-1 10-17.32zM58.16 4.134l1 1.732-17.32 10-1-1.732 17.32-10zm-40 40l1 1.732-17.32 10-1-1.732 17.32-10zM80 9v2H60V9h20zM20 69v2H0v-2h20zm79.32-55l-1 1.732-17.32-10L82 4l17.32 10zm-80 80l-1 1.732-17.32-10L2 84l17.32 10zm96.546-75.84l-1.732 1-10-17.32 1.732-1 10 17.32zm-100 100l-1.732 1-10-17.32 1.732-1 10 17.32zM38.16 24.134l1 1.732-17.32 10-1-1.732 17.32-10zM60 29v2H40v-2h20zm19.32 5l-1 1.732-17.32-10L62 24l17.32 10zm16.546 4.16l-1.732 1-10-17.32 1.732-1 10 17.32zM111 40h-2V20h2v20zm3.134.84l1.732 1-10 17.32-1.732-1 10-17.32zM40 49v2H20v-2h20zm19.32 5l-1 1.732-17.32-10L42 44l17.32 10zm16.546 4.16l-1.732 1-10-17.32 1.732-1 10 17.32zM91 60h-2V40h2v20zm3.134.84l1.732 1-10 17.32-1.732-1 10-17.32zm24.026 3.294l1 1.732-17.32 10-1-1.732 17.32-10zM39.32 74l-1 1.732-17.32-10L22 64l17.32 10zm16.546 4.16l-1.732 1-10-17.32 1.732-1 10 17.32zM71 80h-2V60h2v20zm3.134.84l1.732 1-10 17.32-1.732-1 10-17.32zm24.026 3.294l1 1.732-17.32 10-1-1.732 17.32-10zM120 89v2h-20v-2h20zm-84.134 9.16l-1.732 1-10-17.32 1.732-1 10 17.32zM51 100h-2V80h2v20zm3.134.84l1.732 1-10 17.32-1.732-1 10-17.32zm24.026 3.294l1 1.732-17.32 10-1-1.732 17.32-10zM100 109v2H80v-2h20zm19.32 5l-1 1.732-17.32-10 1-1.732 17.32 10zM31 120h-2v-20h2v20z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - moroccan: - "url(\"data:image/svg+xml,%3Csvg width='80' height='88' viewBox='0 0 80 88' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M22 21.91V26h-2c-9.94 0-18 8.06-18 18 0 9.943 8.058 18 18 18h2v4.09c8.012.722 14.785 5.738 18 12.73 3.212-6.99 9.983-12.008 18-12.73V62h2c9.94 0 18-8.06 18-18 0-9.943-8.058-18-18-18h-2v-4.09c-8.012-.722-14.785-5.738-18-12.73-3.212 6.99-9.983 12.008-18 12.73zM54 58v4.696c-5.574 1.316-10.455 4.428-14 8.69-3.545-4.262-8.426-7.374-14-8.69V58h-5.993C12.27 58 6 51.734 6 44c0-7.732 6.275-14 14.007-14H26v-4.696c5.574-1.316 10.455-4.428 14-8.69 3.545 4.262 8.426 7.374 14 8.69V30h5.993C67.73 30 74 36.266 74 44c0 7.732-6.275 14-14.007 14H54zM42 88c0-9.94 8.06-18 18-18h2v-4.09c8.016-.722 14.787-5.738 18-12.73v7.434c-3.545 4.262-8.426 7.374-14 8.69V74h-5.993C52.275 74 46 80.268 46 88h-4zm-4 0c0-9.943-8.058-18-18-18h-2v-4.09c-8.012-.722-14.785-5.738-18-12.73v7.434c3.545 4.262 8.426 7.374 14 8.69V74h5.993C27.73 74 34 80.266 34 88h4zm4-88c0 9.943 8.058 18 18 18h2v4.09c8.012.722 14.785 5.738 18 12.73v-7.434c-3.545-4.262-8.426-7.374-14-8.69V14h-5.993C52.27 14 46 7.734 46 0h-4zM0 34.82c3.213-6.992 9.984-12.008 18-12.73V18h2c9.94 0 18-8.06 18-18h-4c0 7.732-6.275 14-14.007 14H14v4.696c-5.574 1.316-10.455 4.428-14 8.69v7.433z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "morphing-diamonds": - "url(\"data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M54.627 0l.83.828-1.415 1.415L51.8 0h2.827zM5.373 0l-.83.828L5.96 2.243 8.2 0H5.374zM48.97 0l3.657 3.657-1.414 1.414L46.143 0h2.828zM11.03 0L7.372 3.657 8.787 5.07 13.857 0H11.03zm32.284 0L49.8 6.485 48.384 7.9l-7.9-7.9h2.83zM16.686 0L10.2 6.485 11.616 7.9l7.9-7.9h-2.83zm20.97 0l9.315 9.314-1.414 1.414L34.828 0h2.83zM22.344 0L13.03 9.314l1.414 1.414L25.172 0h-2.83zM32 0l12.142 12.142-1.414 1.414L30 .828 17.272 13.556l-1.414-1.414L28 0h4zM.284 0l28 28-1.414 1.414L0 2.544V0h.284zM0 5.373l25.456 25.455-1.414 1.415L0 8.2V5.374zm0 5.656l22.627 22.627-1.414 1.414L0 13.86v-2.83zm0 5.656l19.8 19.8-1.415 1.413L0 19.514v-2.83zm0 5.657l16.97 16.97-1.414 1.415L0 25.172v-2.83zM0 28l14.142 14.142-1.414 1.414L0 30.828V28zm0 5.657L11.314 44.97 9.9 46.386l-9.9-9.9v-2.828zm0 5.657L8.485 47.8 7.07 49.212 0 42.143v-2.83zm0 5.657l5.657 5.657-1.414 1.415L0 47.8v-2.83zm0 5.657l2.828 2.83-1.414 1.413L0 53.456v-2.83zM54.627 60L30 35.373 5.373 60H8.2L30 38.2 51.8 60h2.827zm-5.656 0L30 41.03 11.03 60h2.828L30 43.858 46.142 60h2.83zm-5.656 0L30 46.686 16.686 60h2.83L30 49.515 40.485 60h2.83zm-5.657 0L30 52.343 22.343 60h2.83L30 55.172 34.828 60h2.83zM32 60l-2-2-2 2h4zM59.716 0l-28 28 1.414 1.414L60 2.544V0h-.284zM60 5.373L34.544 30.828l1.414 1.415L60 8.2V5.374zm0 5.656L37.373 33.656l1.414 1.414L60 13.86v-2.83zm0 5.656l-19.8 19.8 1.415 1.413L60 19.514v-2.83zm0 5.657l-16.97 16.97 1.414 1.415L60 25.172v-2.83zM60 28L45.858 42.142l1.414 1.414L60 30.828V28zm0 5.657L48.686 44.97l1.415 1.415 9.9-9.9v-2.828zm0 5.657L51.515 47.8l1.414 1.413 7.07-7.07v-2.83zm0 5.657l-5.657 5.657 1.414 1.415L60 47.8v-2.83zm0 5.657l-2.828 2.83 1.414 1.413L60 53.456v-2.83zM39.9 16.385l1.414-1.414L30 3.658 18.686 14.97l1.415 1.415 9.9-9.9 9.9 9.9zm-2.83 2.828l1.415-1.414L30 9.313 21.515 17.8l1.414 1.413 7.07-7.07 7.07 7.07zm-2.827 2.83l1.414-1.416L30 14.97l-5.657 5.657 1.414 1.415L30 17.8l4.243 4.242zm-2.83 2.827l1.415-1.414L30 20.626l-2.828 2.83 1.414 1.414L30 23.456l1.414 1.414zM56.87 59.414L58.284 58 30 29.716 1.716 58l1.414 1.414L30 32.544l26.87 26.87z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - rails: - "url(\"data:image/svg+xml,%3Csvg width='20' height='10' viewBox='0 0 20 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16 6H6v4H4V6H2V4h2V0h2v4h10V0h2v4h2v2h-2v4h-2V6z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - rain: "url(\"data:image/svg+xml,%3Csvg width='12' height='16' viewBox='0 0 12 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 .99C4 .445 4.444 0 5 0c.552 0 1 .45 1 .99v4.02C6 5.555 5.556 6 5 6c-.552 0-1-.45-1-.99V.99zm6 8c0-.546.444-.99 1-.99.552 0 1 .45 1 .99v4.02c0 .546-.444.99-1 .99-.552 0-1-.45-1-.99V8.99z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - skulls: - "url(\"data:image/svg+xml,%3Csvg width='180' height='180' viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M82.42 180h-1.415L0 98.995v-2.827L6.167 90 0 83.833V81.004L81.005 0h2.827L90 6.167 96.167 0H98.996L180 81.005v2.827L173.833 90 180 96.167V98.996L98.995 180h-2.827L90 173.833 83.833 180H82.42zm0-1.414L1.413 97.58 8.994 90l-7.58-7.58L82.42 1.413 90 8.994l7.58-7.58 81.006 81.005-7.58 7.58 7.58 7.58-81.005 81.006-7.58-7.58-7.58 7.58zM175.196 0h-25.832c1.033 2.924 2.616 5.59 4.625 7.868C152.145 9.682 151 12.208 151 15c0 5.523 4.477 10 10 10 1.657 0 3 1.343 3 3v4h16V0h-4.803c.51.883.803 1.907.803 3 0 3.314-2.686 6-6 6s-6-2.686-6-6c0-1.093.292-2.117.803-3h10.394-13.685C161.18.938 161 1.948 161 3v4c-4.418 0-8 3.582-8 8s3.582 8 8 8c2.76 0 5 2.24 5 5v2h4v-4h2v4h4v-4h2v4h2V0h-4.803zm-15.783 0c-.27.954-.414 1.96-.414 3v2.2c-1.25.254-2.414.74-3.447 1.412-1.716-1.93-3.098-4.164-4.054-6.612h7.914zM180 17h-3l2.143-10H180v10zm-30.635 163c-.884-2.502-1.365-5.195-1.365-8 0-13.255 10.748-24 23.99-24H180v32h-30.635zm12.147 0c.5-1.416 1.345-2.67 2.434-3.66l-1.345-1.48c-1.498 1.364-2.62 3.136-3.186 5.14H151.5c-.97-2.48-1.5-5.177-1.5-8 0-12.15 9.84-22 22-22h8v30h-18.488zm13.685 0c-1.037-1.793-2.976-3-5.197-3-2.22 0-4.16 1.207-5.197 3h10.394zM0 148h8.01C21.26 148 32 158.742 32 172c0 2.805-.48 5.498-1.366 8H0v-32zm0 2h8c12.15 0 22 9.847 22 22 0 2.822-.53 5.52-1.5 8h-7.914c-.567-2.004-1.688-3.776-3.187-5.14l-1.346 1.48c1.09.99 1.933 2.244 2.434 3.66H0v-30zm15.197 30c-1.037-1.793-2.976-3-5.197-3-2.22 0-4.16 1.207-5.197 3h10.394zM0 32h16v-4c0-1.657 1.343-3 3-3 5.523 0 10-4.477 10-10 0-2.794-1.145-5.32-2.992-7.134C28.018 5.586 29.6 2.924 30.634 0H0v32zm0-2h2v-4h2v4h4v-4h2v4h4v-2c0-2.76 2.24-5 5-5 4.418 0 8-3.582 8-8s-3.582-8-8-8V3c0-1.052-.18-2.062-.512-3H0v30zM28.5 0c-.954 2.448-2.335 4.683-4.05 6.613-1.035-.672-2.2-1.16-3.45-1.413V3c0-1.04-.144-2.046-.414-3H28.5zM0 17h3L.857 7H0v10zM15.197 0c.51.883.803 1.907.803 3 0 3.314-2.686 6-6 6S4 6.314 4 3c0-1.093.292-2.117.803-3h10.394zM109 115c-1.657 0-3 1.343-3 3v4H74v-4c0-1.657-1.343-3-3-3-5.523 0-10-4.477-10-10 0-2.793 1.145-5.318 2.99-7.132C60.262 93.638 58 88.084 58 82c0-13.255 10.748-24 23.99-24h16.02C111.26 58 122 68.742 122 82c0 6.082-2.263 11.636-5.992 15.866C117.855 99.68 119 102.206 119 105c0 5.523-4.477 10-10 10zm0-2c-2.76 0-5 2.24-5 5v2h-4v-4h-2v4h-4v-4h-2v4h-4v-4h-2v4h-4v-4h-2v4h-4v-2c0-2.76-2.24-5-5-5-4.418 0-8-3.582-8-8s3.582-8 8-8v-4c0-2.64 1.136-5.013 2.946-6.66L72.6 84.86C70.39 86.874 69 89.775 69 93v2.2c-1.25.254-2.414.74-3.447 1.412C62.098 92.727 60 87.61 60 82c0-12.15 9.84-22 22-22h16c12.15 0 22 9.847 22 22 0 5.61-2.097 10.728-5.55 14.613-1.035-.672-2.2-1.16-3.45-1.413V93c0-3.226-1.39-6.127-3.6-8.14l-1.346 1.48C107.864 87.987 109 90.36 109 93v4c4.418 0 8 3.582 8 8s-3.582 8-8 8zM90.857 97L93 107h-6l2.143-10h1.714zM80 99c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zm20 0c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "squares-in-squares": - "url(\"data:image/svg+xml,%3Csvg width='70' height='70' viewBox='0 0 70 70' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'%3E%3Cpath d='M0 0h35v35H0V0zm5 5h25v25H5V5zm5 5h15v15H10V10zm5 5h5v5h-5v-5zM40 5h25v25H40V5zm5 5h15v15H45V10zm5 5h5v5h-5v-5zM70 35H35v35h35V35zm-5 5H40v25h25V40zm-5 5H45v15h15V45zm-5 5h-5v5h5v-5zM30 40H5v25h25V40zm-5 5H10v15h15V45zm-5 5h-5v5h5v-5z'/%3E%3C/g%3E%3C/svg%3E\")", - stripes: - "url(\"data:image/svg+xml,%3Csvg width='40' height='1' viewBox='0 0 40 1' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h20v1H0z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "tic-tac-toe": - "url(\"data:image/svg+xml,%3Csvg width='64' height='64' viewBox='0 0 64 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 16c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zm33.414-6l5.95-5.95L45.95.636 40 6.586 34.05.636 32.636 2.05 38.586 8l-5.95 5.95 1.414 1.414L40 9.414l5.95 5.95 1.414-1.414L41.414 8zM40 48c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zM9.414 40l5.95-5.95-1.414-1.414L8 38.586l-5.95-5.95L.636 34.05 6.586 40l-5.95 5.95 1.414 1.414L8 41.414l5.95 5.95 1.414-1.414L9.414 40z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "zig-zag": - "url(\"data:image/svg+xml,%3Csvg width='40' height='12' viewBox='0 0 40 12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 6.172L6.172 0h5.656L0 11.828V6.172zm40 5.656L28.172 0h5.656L40 6.172v5.656zM6.172 12l12-12h3.656l12 12h-5.656L20 3.828 11.828 12H6.172zm12 0L20 10.172 21.828 12h-3.656z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - aztec: - "url(\"data:image/svg+xml,%3Csvg width='32' height='64' viewBox='0 0 32 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 28h20V16h-4v8H4V4h28v28h-4V8H8v12h4v-8h12v20H0v-4zm12 8h20v4H16v24H0v-4h12V36zm16 12h-4v12h8v4H20V44h12v12h-4v-8zM0 36h8v20H0v-4h4V40H0v-4z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "bank-note": - "url(\"data:image/svg+xml,%3Csvg width='100' height='20' viewBox='0 0 100 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21.184 20c.357-.13.72-.264 1.088-.402l1.768-.661C33.64 15.347 39.647 14 50 14c10.271 0 15.362 1.222 24.629 4.928.955.383 1.869.74 2.75 1.072h6.225c-2.51-.73-5.139-1.691-8.233-2.928C65.888 13.278 60.562 12 50 12c-10.626 0-16.855 1.397-26.66 5.063l-1.767.662c-2.475.923-4.66 1.674-6.724 2.275h6.335zm0-20C13.258 2.892 8.077 4 0 4V2c5.744 0 9.951-.574 14.85-2h6.334zM77.38 0C85.239 2.966 90.502 4 100 4V2c-6.842 0-11.386-.542-16.396-2h-6.225zM0 14c8.44 0 13.718-1.21 22.272-4.402l1.768-.661C33.64 5.347 39.647 4 50 4c10.271 0 15.362 1.222 24.629 4.928C84.112 12.722 89.438 14 100 14v-2c-10.271 0-15.362-1.222-24.629-4.928C65.888 3.278 60.562 2 50 2 39.374 2 33.145 3.397 23.34 7.063l-1.767.662C13.223 10.84 8.163 12 0 12v2z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - boxes: - "url(\"data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h20L0 20z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "circles-squares": - "url(\"data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h20v20H0V0zm10 17a7 7 0 1 0 0-14 7 7 0 0 0 0 14zm20 0a7 7 0 1 0 0-14 7 7 0 0 0 0 14zM10 37a7 7 0 1 0 0-14 7 7 0 0 0 0 14zm10-17h20v20H20V20zm10 17a7 7 0 1 0 0-14 7 7 0 0 0 0 14z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "circuit-board": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 304 304' width='304' height='304'%3E%3Cpath fill='FILLCOLOR' fill-opacity='FILLOPACITY' d='M44.1 224a5 5 0 1 1 0 2H0v-2h44.1zm160 48a5 5 0 1 1 0 2H82v-2h122.1zm57.8-46a5 5 0 1 1 0-2H304v2h-42.1zm0 16a5 5 0 1 1 0-2H304v2h-42.1zm6.2-114a5 5 0 1 1 0 2h-86.2a5 5 0 1 1 0-2h86.2zm-256-48a5 5 0 1 1 0 2H0v-2h12.1zm185.8 34a5 5 0 1 1 0-2h86.2a5 5 0 1 1 0 2h-86.2zM258 12.1a5 5 0 1 1-2 0V0h2v12.1zm-64 208a5 5 0 1 1-2 0v-54.2a5 5 0 1 1 2 0v54.2zm48-198.2V80h62v2h-64V21.9a5 5 0 1 1 2 0zm16 16V64h46v2h-48V37.9a5 5 0 1 1 2 0zm-128 96V208h16v12.1a5 5 0 1 1-2 0V210h-16v-76.1a5 5 0 1 1 2 0zm-5.9-21.9a5 5 0 1 1 0 2H114v48H85.9a5 5 0 1 1 0-2H112v-48h12.1zm-6.2 130a5 5 0 1 1 0-2H176v-74.1a5 5 0 1 1 2 0V242h-60.1zm-16-64a5 5 0 1 1 0-2H114v48h10.1a5 5 0 1 1 0 2H112v-48h-10.1zM66 284.1a5 5 0 1 1-2 0V274H50v30h-2v-32h18v12.1zM236.1 176a5 5 0 1 1 0 2H226v94h48v32h-2v-30h-48v-98h12.1zm25.8-30a5 5 0 1 1 0-2H274v44.1a5 5 0 1 1-2 0V146h-10.1zm-64 96a5 5 0 1 1 0-2H208v-80h16v-14h-42.1a5 5 0 1 1 0-2H226v18h-16v80h-12.1zm86.2-210a5 5 0 1 1 0 2H272V0h2v32h10.1zM98 101.9V146H53.9a5 5 0 1 1 0-2H96v-42.1a5 5 0 1 1 2 0zM53.9 34a5 5 0 1 1 0-2H80V0h2v34H53.9zm60.1 3.9V66H82v64H69.9a5 5 0 1 1 0-2H80V64h32V37.9a5 5 0 1 1 2 0zM101.9 82a5 5 0 1 1 0-2H128V37.9a5 5 0 1 1 2 0V82h-28.1zm16-64a5 5 0 1 1 0-2H146v44.1a5 5 0 1 1-2 0V18h-26.1zm102.2 270a5 5 0 1 1 0 2H98v14h-2v-16h124.1zM242 149.9V160h16v34h-16v62h48v48h-2v-46h-48v-66h16v-30h-16v-12.1a5 5 0 1 1 2 0zM53.9 18a5 5 0 1 1 0-2H64V2H48V0h18v18H53.9zm112 32a5 5 0 1 1 0-2H192V0h50v2h-48v48h-28.1zm-48-48a5 5 0 0 1-9.8-2h2.07a3 3 0 1 0 5.66 0H178v34h-18V21.9a5 5 0 1 1 2 0V32h14V2h-58.1zm0 96a5 5 0 1 1 0-2H137l32-32h39V21.9a5 5 0 1 1 2 0V66h-40.17l-32 32H117.9zm28.1 90.1a5 5 0 1 1-2 0v-76.51L175.59 80H224V21.9a5 5 0 1 1 2 0V82h-49.59L146 112.41v75.69zm16 32a5 5 0 1 1-2 0v-99.51L184.59 96H300.1a5 5 0 0 1 3.9-3.9v2.07a3 3 0 0 0 0 5.66v2.07a5 5 0 0 1-3.9-3.9H185.41L162 121.41v98.69zm-144-64a5 5 0 1 1-2 0v-3.51l48-48V48h32V0h2v50H66v55.41l-48 48v2.69zM50 53.9v43.51l-48 48V208h26.1a5 5 0 1 1 0 2H0v-65.41l48-48V53.9a5 5 0 1 1 2 0zm-16 16V89.41l-34 34v-2.82l32-32V69.9a5 5 0 1 1 2 0zM12.1 32a5 5 0 1 1 0 2H9.41L0 43.41V40.6L8.59 32h3.51zm265.8 18a5 5 0 1 1 0-2h18.69l7.41-7.41v2.82L297.41 50H277.9zm-16 160a5 5 0 1 1 0-2H288v-71.41l16-16v2.82l-14 14V210h-28.1zm-208 32a5 5 0 1 1 0-2H64v-22.59L40.59 194H21.9a5 5 0 1 1 0-2H41.41L66 216.59V242H53.9zm150.2 14a5 5 0 1 1 0 2H96v-56.6L56.6 162H37.9a5 5 0 1 1 0-2h19.5L98 200.6V256h106.1zm-150.2 2a5 5 0 1 1 0-2H80v-46.59L48.59 178H21.9a5 5 0 1 1 0-2H49.41L82 208.59V258H53.9zM34 39.8v1.61L9.41 66H0v-2h8.59L32 40.59V0h2v39.8zM2 300.1a5 5 0 0 1 3.9 3.9H3.83A3 3 0 0 0 0 302.17V256h18v48h-2v-46H2v42.1zM34 241v63h-2v-62H0v-2h34v1zM17 18H0v-2h16V0h2v18h-1zm273-2h14v2h-16V0h2v16zm-32 273v15h-2v-14h-14v14h-2v-16h18v1zM0 92.1A5.02 5.02 0 0 1 6 97a5 5 0 0 1-6 4.9v-2.07a3 3 0 1 0 0-5.66V92.1zM80 272h2v32h-2v-32zm37.9 32h-2.07a3 3 0 0 0-5.66 0h-2.07a5 5 0 0 1 9.8 0zM5.9 0A5.02 5.02 0 0 1 0 5.9V3.83A3 3 0 0 0 3.83 0H5.9zm294.2 0h2.07A3 3 0 0 0 304 3.83V5.9a5 5 0 0 1-3.9-5.9zm3.9 300.1v2.07a3 3 0 0 0-1.83 1.83h-2.07a5 5 0 0 1 3.9-3.9zM97 100a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-48 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 48a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 96a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-144a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-96 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm96 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-32 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM49 36a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-32 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM33 68a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-48a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 240a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm80-176a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 48a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm112 176a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-16 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM17 180a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-32a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16 0a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM17 84a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm32 64a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm16-16a3 3 0 1 0 0-6 3 3 0 0 0 0 6z'%3E%3C/path%3E%3C/svg%3E\")", - curtain: - "url(\"data:image/svg+xml,%3Csvg width='44' height='12' viewBox='0 0 44 12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 12v-2L0 0v10l4 2h16zm18 0l4-2V0L22 10v2h16zM20 0v8L4 0h16zm18 0L22 8V0h16z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "diagonal-lines": - "url(\"data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E\")", - "endless-clouds": - "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 56 28' width='56' height='28'%3E%3Cpath fill='FILLCOLOR' fill-opacity='FILLOPACITY' d='M56 26v2h-7.75c2.3-1.27 4.94-2 7.75-2zm-26 2a2 2 0 1 0-4 0h-4.09A25.98 25.98 0 0 0 0 16v-2c.67 0 1.34.02 2 .07V14a2 2 0 0 0-2-2v-2a4 4 0 0 1 3.98 3.6 28.09 28.09 0 0 1 2.8-3.86A8 8 0 0 0 0 6V4a9.99 9.99 0 0 1 8.17 4.23c.94-.95 1.96-1.83 3.03-2.63A13.98 13.98 0 0 0 0 0h7.75c2 1.1 3.73 2.63 5.1 4.45 1.12-.72 2.3-1.37 3.53-1.93A20.1 20.1 0 0 0 14.28 0h2.7c.45.56.88 1.14 1.29 1.74 1.3-.48 2.63-.87 4-1.15-.11-.2-.23-.4-.36-.59H26v.07a28.4 28.4 0 0 1 4 0V0h4.09l-.37.59c1.38.28 2.72.67 4.01 1.15.4-.6.84-1.18 1.3-1.74h2.69a20.1 20.1 0 0 0-2.1 2.52c1.23.56 2.41 1.2 3.54 1.93A16.08 16.08 0 0 1 48.25 0H56c-4.58 0-8.65 2.2-11.2 5.6 1.07.8 2.09 1.68 3.03 2.63A9.99 9.99 0 0 1 56 4v2a8 8 0 0 0-6.77 3.74c1.03 1.2 1.97 2.5 2.79 3.86A4 4 0 0 1 56 10v2a2 2 0 0 0-2 2.07 28.4 28.4 0 0 1 2-.07v2c-9.2 0-17.3 4.78-21.91 12H30zM7.75 28H0v-2c2.81 0 5.46.73 7.75 2zM56 20v2c-5.6 0-10.65 2.3-14.28 6h-2.7c4.04-4.89 10.15-8 16.98-8zm-39.03 8h-2.69C10.65 24.3 5.6 22 0 22v-2c6.83 0 12.94 3.11 16.97 8zm15.01-.4a28.09 28.09 0 0 1 2.8-3.86 8 8 0 0 0-13.55 0c1.03 1.2 1.97 2.5 2.79 3.86a4 4 0 0 1 7.96 0zm14.29-11.86c1.3-.48 2.63-.87 4-1.15a25.99 25.99 0 0 0-44.55 0c1.38.28 2.72.67 4.01 1.15a21.98 21.98 0 0 1 36.54 0zm-5.43 2.71c1.13-.72 2.3-1.37 3.54-1.93a19.98 19.98 0 0 0-32.76 0c1.23.56 2.41 1.2 3.54 1.93a15.98 15.98 0 0 1 25.68 0zm-4.67 3.78c.94-.95 1.96-1.83 3.03-2.63a13.98 13.98 0 0 0-22.4 0c1.07.8 2.09 1.68 3.03 2.63a9.99 9.99 0 0 1 16.34 0z'%3E%3C/path%3E%3C/svg%3E\")", - eyes: "url(\"data:image/svg+xml,%3Csvg width='20' height='12' viewBox='0 0 20 12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 12c0-.622-.095-1.221-.27-1.785A5.982 5.982 0 0 0 10 12c1.67 0 3.182-.683 4.27-1.785A5.998 5.998 0 0 0 14 12h2a4 4 0 0 1 4-4V6c-1.67 0-3.182.683-4.27 1.785C15.905 7.22 16 6.622 16 6c0-.622-.095-1.221-.27-1.785A5.982 5.982 0 0 0 20 6V4a4 4 0 0 1-4-4h-2c0 .622.095 1.221.27 1.785A5.982 5.982 0 0 0 10 0C8.33 0 6.818.683 5.73 1.785 5.905 1.22 6 .622 6 0H4a4 4 0 0 1-4 4v2c1.67 0 3.182.683 4.27 1.785A5.998 5.998 0 0 1 4 6c0-.622.095-1.221.27-1.785A5.982 5.982 0 0 1 0 6v2a4 4 0 0 1 4 4h2zm-4 0a2 2 0 0 0-2-2v2h2zm16 0a2 2 0 0 1 2-2v2h-2zM0 2a2 2 0 0 0 2-2H0v2zm20 0a2 2 0 0 1-2-2h2v2zm-10 8a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "floor-tile": - "url(\"data:image/svg+xml,%3Csvg width='30' height='30' viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 10h10v10H0V10zM10 0h10v10H10V0z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - groovy: - "url(\"data:image/svg+xml,%3Csvg width='24' height='40' viewBox='0 0 24 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 40c5.523 0 10-4.477 10-10V0C4.477 0 0 4.477 0 10v30zm22 0c-5.523 0-10-4.477-10-10V0c5.523 0 10 4.477 10 10v30z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "intersecting-circles": - "url(\"data:image/svg+xml,%3Csvg width='30' height='30' viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15 0C6.716 0 0 6.716 0 15c8.284 0 15-6.716 15-15zM0 15c0 8.284 6.716 15 15 15 0-8.284-6.716-15-15-15zm30 0c0-8.284-6.716-15-15-15 0 8.284 6.716 15 15 15zm0 0c0 8.284-6.716 15-15 15 0-8.284 6.716-15 15-15z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - melt: "url(\"data:image/svg+xml,%3Csvg width='24' height='20' viewBox='0 0 24 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 18c0-1.105.887-2 1.998-2 1.104 0 2-.895 2.002-1.994V14v6h-4v-2zM0 13.998C0 12.895.888 12 2 12c1.105 0 2 .888 2 2 0 1.105.888 2 2 2 1.105 0 2 .888 2 2v2H0v-6.002zm16 4.004A1.994 1.994 0 0 1 14 20c-1.105 0-2-.887-2-1.998v-4.004A1.994 1.994 0 0 0 10 12c-1.105 0-2-.888-2-2 0-1.105-.888-2-2-2-1.105 0-2-.887-2-1.998V1.998A1.994 1.994 0 0 0 2 0a2 2 0 0 0-2 2V0h8v2c0 1.105.888 2 2 2 1.105 0 2 .888 2 2 0 1.105.888 2 2 2 1.105 0 2-.888 2-2 0-1.105.888-2 2-2 1.105 0 2-.888 2-2V0h4v6.002A1.994 1.994 0 0 1 22 8c-1.105 0-2 .888-2 2 0 1.105-.888 2-2 2-1.105 0-2 .887-2 1.998v4.004z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "overlapping-diamonds": - "url(\"data:image/svg+xml,%3Csvg width='48' height='64' viewBox='0 0 48 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M48 28v-4L36 12 24 24 12 12 0 24v4l4 4-4 4v4l12 12 12-12 12 12 12-12v-4l-4-4 4-4zM8 32l-6-6 10-10 10 10-6 6 6 6-10 10L2 38l6-6zm12 0l4-4 4 4-4 4-4-4zm12 0l-6-6 10-10 10 10-6 6 6 6-10 10-10-10 6-6zM0 16L10 6 4 0h4l4 4 4-4h4l-6 6 10 10L34 6l-6-6h4l4 4 4-4h4l-6 6 10 10v4L36 8 24 20 12 8 0 20v-4zm0 32l10 10-6 6h4l4-4 4 4h4l-6-6 10-10 10 10-6 6h4l4-4 4 4h4l-6-6 10-10v-4L36 56 24 44 12 56 0 44v4z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "parkay-floor": - "url(\"data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 20.5V18H0v-2h20v-2H0v-2h20v-2H0V8h20V6H0V4h20V2H0V0h22v20h2V0h2v20h2V0h2v20h2V0h2v20h2V0h2v20h2v2H20v-1.5zM0 20h2v20H0V20zm4 0h2v20H4V20zm4 0h2v20H8V20zm4 0h2v20h-2V20zm4 0h2v20h-2V20zm4 4h20v2H20v-2zm0 4h20v2H20v-2zm0 4h20v2H20v-2zm0 4h20v2H20v-2z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "pixel-dots": - "url(\"data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h16v2h-6v6h6v8H8v-6H2v6H0V0zm4 4h2v2H4V4zm8 8h2v2h-2v-2zm-8 0h2v2H4v-2zm8-8h2v2h-2V4z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "polka-dots": - "url(\"data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'%3E%3Ccircle cx='3' cy='3' r='3'/%3E%3Ccircle cx='13' cy='13' r='3'/%3E%3C/g%3E%3C/svg%3E\")", - signal: - "url(\"data:image/svg+xml,%3Csvg width='84' height='48' viewBox='0 0 84 48' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h12v6H0V0zm28 8h12v6H28V8zm14-8h12v6H42V0zm14 0h12v6H56V0zm0 8h12v6H56V8zM42 8h12v6H42V8zm0 16h12v6H42v-6zm14-8h12v6H56v-6zm14 0h12v6H70v-6zm0-16h12v6H70V0zM28 32h12v6H28v-6zM14 16h12v6H14v-6zM0 24h12v6H0v-6zm0 8h12v6H0v-6zm14 0h12v6H14v-6zm14 8h12v6H28v-6zm-14 0h12v6H14v-6zm28 0h12v6H42v-6zm14-8h12v6H56v-6zm0-8h12v6H56v-6zm14 8h12v6H70v-6zm0 8h12v6H70v-6zM14 24h12v6H14v-6zm14-8h12v6H28v-6zM14 8h12v6H14V8zM0 8h12v6H0V8z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - "slanted-stars": - "url(\"data:image/svg+xml,%3Csvg width='30' height='30' viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 15l15 15H0V15zM15 0l15 15V0H15z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", - wallpaper: - "url(\"data:image/svg+xml,%3Csvg width='84' height='16' viewBox='0 0 84 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M78 7V4h-2v3h-3v2h3v3h2V9h3V7h-3zM30 7V4h-2v3h-3v2h3v3h2V9h3V7h-3zM10 0h2v16h-2V0zm6 0h4v16h-4V0zM2 0h4v16H2V0zm50 0h2v16h-2V0zM38 0h2v16h-2V0zm28 0h2v16h-2V0zm-8 0h6v16h-6V0zM42 0h6v16h-6V0z' fill='FILLCOLOR' fill-opacity='FILLOPACITY' fill-rule='evenodd'/%3E%3C/svg%3E\")", -}); diff --git a/components/ProductSearch/ChannelProductSearch.js b/components/ProductSearch/ChannelProductSearch.js deleted file mode 100644 index 58a12fb..0000000 --- a/components/ProductSearch/ChannelProductSearch.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from "react"; -import useSWR from "swr"; -import { gqlFetcher } from "keystone/lib/gqlFetcher"; -import { useSharedState } from "keystone/lib/useSharedState"; -import { CHANNELS_QUERY } from "@graphql/channels"; -import { ProductSearch } from "./Search"; - -export function ChannelProductSearch({ - swrKey, - disabled, - addToCart, - atcText = "Add To Cart", -}) { - const { data, error } = useSWR(CHANNELS_QUERY, gqlFetcher); - - const [searchEntry, setSearchEntry] = useSharedState( - `${swrKey}channelSearchEntry`, - "" - ); - - return data?.channels ? ( - - ) : ( - <> - ); -} diff --git a/components/ProductSearch/ProductList.js b/components/ProductSearch/ProductList.js deleted file mode 100644 index eedf181..0000000 --- a/components/ProductSearch/ProductList.js +++ /dev/null @@ -1,85 +0,0 @@ -import { Box, Center, Skeleton } from "@mantine/core"; -import useSWR from "swr"; - -import { Product } from "@primitives/product"; - -export function ProductList({ - optionId, - optionName, - accessToken, - domain, - searchProductsEndpoint, - updateProductEndpoint, - disabled, - addToCart, - searchEntry, - atcText, - buttons, -}) { - const params = new URLSearchParams({ - accessToken, - domain, - searchEntry, - }).toString(); - - const url = `${searchProductsEndpoint}?${params}`; - - const fetcher = async (url) => { - try { - const res = await fetch(url); - return res.json(); - } catch (e) { - throw e.message; - } - }; - - const { data, error } = useSWR(url, fetcher); - - if (error) { - return
{error}
; - } - - if (!data) - return ( - <> - - - ); - - console.log({ data }); - return ( - - {data?.products?.map( - ({ - productId, - variantId, - title, - image, - price, - availableForSale, - productLink, - }) => ( - - ) - )} - - ); -} diff --git a/components/ProductSearch/Search.js b/components/ProductSearch/Search.js deleted file mode 100644 index dcb515d..0000000 --- a/components/ProductSearch/Search.js +++ /dev/null @@ -1,234 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { - Input, - Box, - Group, - Button, - useMantineTheme, - Skeleton, - Text, - Paper, -} from "@mantine/core"; -import { Option } from "@primitives/option"; -import { ProductList } from "./ProductList"; -import { - ArrowLeftIcon, - ArrowRightIcon, - SearchIcon, -} from "@primer/octicons-react"; -import { useSharedState } from "keystone/lib/useSharedState"; -import Link from "next/link"; - -const addFirstURL = { - Channel: "/channels", - Shop: "/shops", -}; - -export function ProductSearch({ - title, - disabled, - addToCart, - options = [], - optionName = "Channel", - color, - atcText, - buttons, - searchEntry, - setSearchEntry, -}) { - const theme = useMantineTheme(); - // const [selectedOption, setSelectedOption] = useState(options[0].id); - const [selectedOption, setSelectedOption] = useSharedState( - `${title}Options`, - "" - ); - - const OptionSelect = ({ options }) => { - if (!options) - return ( - - - {optionName}S - - - - - - - - ); - if (options.length === 0) - return ( - - - - - - ); - return ( -