feat(admin-sdk,admin-bundler,admin-shared,medusa): Restructure admin packages (#8988)

**What**
- Renames /admin-next -> /admin
- Renames @medusajs/admin-sdk -> @medusajs/admin-bundler
- Creates a new package called @medusajs/admin-sdk that will hold all tooling relevant to creating admin extensions. This is currently `defineRouteConfig` and `defineWidgetConfig`, but will eventually also export methods for adding custom fields, register translation, etc. 
  - cc: @shahednasser we should update the examples in the docs so these functions are imported from `@medusajs/admin-sdk`. People will also need to install the package in their project, as it's no longer a transient dependency.
  - cc: @olivermrbl we might want to publish a changelog when this is merged, as it is a breaking change, and will require people to import the `defineXConfig` from the new package instead of `@medusajs/admin-shared`.
- Updates CODEOWNERS so /admin packages does not require a review from the UI team.
This commit is contained in:
Kasper Fabricius Kristensen
2024-09-04 19:00:25 +00:00
committed by GitHub
parent beaa851302
commit 0fe1201435
1440 changed files with 122 additions and 86 deletions
@@ -0,0 +1,97 @@
import { HttpTypes } from "@medusajs/types"
import { countries, getCountryByIso2 } from "./data/countries"
export const isSameAddress = (
a?: HttpTypes.AdminOrderAddress | null,
b?: HttpTypes.AdminOrderAddress | null
) => {
if (!a || !b) {
return false
}
return (
a.first_name === b.first_name &&
a.last_name === b.last_name &&
a.address_1 === b.address_1 &&
a.address_2 === b.address_2 &&
a.city === b.city &&
a.postal_code === b.postal_code &&
a.province === b.province &&
a.country_code === b.country_code
)
}
export const getFormattedAddress = ({
address,
}: {
address?: HttpTypes.AdminOrderAddress | null
}) => {
if (!address) {
return []
}
const {
first_name,
last_name,
company,
address_1,
address_2,
city,
postal_code,
province,
country,
country_code,
} = address
const name = [first_name, last_name].filter(Boolean).join(" ")
const formattedAddress: string[] = []
if (name) {
formattedAddress.push(name)
}
if (company) {
formattedAddress.push(company)
}
if (address_1) {
formattedAddress.push(address_1)
}
if (address_2) {
formattedAddress.push(address_2)
}
const cityProvincePostal = [city, province, postal_code]
.filter(Boolean)
.join(" ")
if (cityProvincePostal) {
formattedAddress.push(cityProvincePostal)
}
if (country) {
formattedAddress.push(country.display_name!)
} else if (country_code) {
const country = getCountryByIso2(country_code)
if (country) {
formattedAddress.push(country.display_name)
} else {
formattedAddress.push(country_code.toUpperCase())
}
}
return formattedAddress
}
export const getFormattedCountry = (countryCode: string | null | undefined) => {
if (!countryCode) {
return ""
}
const country = countries.find((c) => c.iso_2 === countryCode)
return country ? country.display_name : countryCode
}
@@ -0,0 +1,6 @@
/**
* Helper function to cast a z.union([z.number(), z.string()]) to a number
*/
export const castNumber = (number: number | string) => {
return typeof number === "string" ? Number(number.replace(",", ".")) : number
}
@@ -0,0 +1,15 @@
import Medusa from "@medusajs/js-sdk"
export const backendUrl = __BACKEND_URL__ ?? "http://localhost:9000"
export const sdk = new Medusa({
baseUrl: backendUrl,
auth: {
type: "session",
},
})
// useful when you want to call the BE from the console and try things out quickly
if (typeof window !== "undefined") {
;(window as any).__sdk = sdk
}
@@ -0,0 +1 @@
export * from "./client"
@@ -0,0 +1,32 @@
/**
* Pick properties from an object and copy them to a new object
* @param obj
* @param keys
*/
export function pick(obj: Record<string, any>, keys: string[]) {
const ret: Record<string, any> = {}
keys.forEach((k) => {
if (k in obj) {
ret[k] = obj[k]
}
})
return ret
}
/**
* Remove properties that are `null` or `undefined` from the object.
* @param obj
*/
export function cleanNonValues(obj: Record<string, any>) {
const ret: Record<string, any> = {}
for (const key in obj) {
if (obj[key] !== null && typeof obj[key] !== "undefined") {
ret[key] = obj[key]
}
}
return ret
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,734 @@
/** This file is auto-generated. Do not modify it manually. */
export type CurrencyInfo = {
code: string
name: string
symbol_native: string
decimal_digits: number
}
export const currencies: Record<string, CurrencyInfo> = {
USD: {
code: "USD",
name: "US Dollar",
symbol_native: "$",
decimal_digits: 2,
},
CAD: {
code: "CAD",
name: "Canadian Dollar",
symbol_native: "$",
decimal_digits: 2,
},
EUR: {
code: "EUR",
name: "Euro",
symbol_native: "€",
decimal_digits: 2,
},
AED: {
code: "AED",
name: "United Arab Emirates Dirham",
symbol_native: "د.إ.",
decimal_digits: 2,
},
AFN: {
code: "AFN",
name: "Afghan Afghani",
symbol_native: "؋",
decimal_digits: 0,
},
ALL: {
code: "ALL",
name: "Albanian Lek",
symbol_native: "Lek",
decimal_digits: 0,
},
AMD: {
code: "AMD",
name: "Armenian Dram",
symbol_native: "դր.",
decimal_digits: 0,
},
ARS: {
code: "ARS",
name: "Argentine Peso",
symbol_native: "$",
decimal_digits: 2,
},
AUD: {
code: "AUD",
name: "Australian Dollar",
symbol_native: "$",
decimal_digits: 2,
},
AZN: {
code: "AZN",
name: "Azerbaijani Manat",
symbol_native: "ман.",
decimal_digits: 2,
},
BAM: {
code: "BAM",
name: "Bosnia-Herzegovina Convertible Mark",
symbol_native: "KM",
decimal_digits: 2,
},
BDT: {
code: "BDT",
name: "Bangladeshi Taka",
symbol_native: "৳",
decimal_digits: 2,
},
BGN: {
code: "BGN",
name: "Bulgarian Lev",
symbol_native: "лв.",
decimal_digits: 2,
},
BHD: {
code: "BHD",
name: "Bahraini Dinar",
symbol_native: "د.ب.",
decimal_digits: 3,
},
BIF: {
code: "BIF",
name: "Burundian Franc",
symbol_native: "FBu",
decimal_digits: 0,
},
BND: {
code: "BND",
name: "Brunei Dollar",
symbol_native: "$",
decimal_digits: 2,
},
BOB: {
code: "BOB",
name: "Bolivian Boliviano",
symbol_native: "Bs",
decimal_digits: 2,
},
BRL: {
code: "BRL",
name: "Brazilian Real",
symbol_native: "R$",
decimal_digits: 2,
},
BWP: {
code: "BWP",
name: "Botswanan Pula",
symbol_native: "P",
decimal_digits: 2,
},
BYN: {
code: "BYN",
name: "Belarusian Ruble",
symbol_native: "руб.",
decimal_digits: 2,
},
BZD: {
code: "BZD",
name: "Belize Dollar",
symbol_native: "$",
decimal_digits: 2,
},
CDF: {
code: "CDF",
name: "Congolese Franc",
symbol_native: "FrCD",
decimal_digits: 2,
},
CHF: {
code: "CHF",
name: "Swiss Franc",
symbol_native: "CHF",
decimal_digits: 2,
},
CLP: {
code: "CLP",
name: "Chilean Peso",
symbol_native: "$",
decimal_digits: 0,
},
CNY: {
code: "CNY",
name: "Chinese Yuan",
symbol_native: "CN¥",
decimal_digits: 2,
},
COP: {
code: "COP",
name: "Colombian Peso",
symbol_native: "$",
decimal_digits: 0,
},
CRC: {
code: "CRC",
name: "Costa Rican Colón",
symbol_native: "₡",
decimal_digits: 0,
},
CVE: {
code: "CVE",
name: "Cape Verdean Escudo",
symbol_native: "CV$",
decimal_digits: 2,
},
CZK: {
code: "CZK",
name: "Czech Republic Koruna",
symbol_native: "Kč",
decimal_digits: 2,
},
DJF: {
code: "DJF",
name: "Djiboutian Franc",
symbol_native: "Fdj",
decimal_digits: 0,
},
DKK: {
code: "DKK",
name: "Danish Krone",
symbol_native: "kr",
decimal_digits: 2,
},
DOP: {
code: "DOP",
name: "Dominican Peso",
symbol_native: "RD$",
decimal_digits: 2,
},
DZD: {
code: "DZD",
name: "Algerian Dinar",
symbol_native: "د.ج.",
decimal_digits: 2,
},
EEK: {
code: "EEK",
name: "Estonian Kroon",
symbol_native: "kr",
decimal_digits: 2,
},
EGP: {
code: "EGP",
name: "Egyptian Pound",
symbol_native: "ج.م.",
decimal_digits: 2,
},
ERN: {
code: "ERN",
name: "Eritrean Nakfa",
symbol_native: "Nfk",
decimal_digits: 2,
},
ETB: {
code: "ETB",
name: "Ethiopian Birr",
symbol_native: "Br",
decimal_digits: 2,
},
GBP: {
code: "GBP",
name: "British Pound Sterling",
symbol_native: "£",
decimal_digits: 2,
},
GEL: {
code: "GEL",
name: "Georgian Lari",
symbol_native: "GEL",
decimal_digits: 2,
},
GHS: {
code: "GHS",
name: "Ghanaian Cedi",
symbol_native: "GH₵",
decimal_digits: 2,
},
GNF: {
code: "GNF",
name: "Guinean Franc",
symbol_native: "FG",
decimal_digits: 0,
},
GTQ: {
code: "GTQ",
name: "Guatemalan Quetzal",
symbol_native: "Q",
decimal_digits: 2,
},
HKD: {
code: "HKD",
name: "Hong Kong Dollar",
symbol_native: "$",
decimal_digits: 2,
},
HNL: {
code: "HNL",
name: "Honduran Lempira",
symbol_native: "L",
decimal_digits: 2,
},
HRK: {
code: "HRK",
name: "Croatian Kuna",
symbol_native: "kn",
decimal_digits: 2,
},
HUF: {
code: "HUF",
name: "Hungarian Forint",
symbol_native: "Ft",
decimal_digits: 0,
},
IDR: {
code: "IDR",
name: "Indonesian Rupiah",
symbol_native: "Rp",
decimal_digits: 0,
},
ILS: {
code: "ILS",
name: "Israeli New Sheqel",
symbol_native: "₪",
decimal_digits: 2,
},
INR: {
code: "INR",
name: "Indian Rupee",
symbol_native: "₹",
decimal_digits: 2,
},
IQD: {
code: "IQD",
name: "Iraqi Dinar",
symbol_native: "د.ع.",
decimal_digits: 0,
},
IRR: {
code: "IRR",
name: "Iranian Rial",
symbol_native: "﷼",
decimal_digits: 0,
},
ISK: {
code: "ISK",
name: "Icelandic Króna",
symbol_native: "kr",
decimal_digits: 0,
},
JMD: {
code: "JMD",
name: "Jamaican Dollar",
symbol_native: "$",
decimal_digits: 2,
},
JOD: {
code: "JOD",
name: "Jordanian Dinar",
symbol_native: "د.أ.",
decimal_digits: 3,
},
JPY: {
code: "JPY",
name: "Japanese Yen",
symbol_native: "¥",
decimal_digits: 0,
},
KES: {
code: "KES",
name: "Kenyan Shilling",
symbol_native: "Ksh",
decimal_digits: 2,
},
KHR: {
code: "KHR",
name: "Cambodian Riel",
symbol_native: "៛",
decimal_digits: 2,
},
KMF: {
code: "KMF",
name: "Comorian Franc",
symbol_native: "FC",
decimal_digits: 0,
},
KRW: {
code: "KRW",
name: "South Korean Won",
symbol_native: "₩",
decimal_digits: 0,
},
KWD: {
code: "KWD",
name: "Kuwaiti Dinar",
symbol_native: "د.ك.",
decimal_digits: 3,
},
KZT: {
code: "KZT",
name: "Kazakhstani Tenge",
symbol_native: "тңг.",
decimal_digits: 2,
},
LBP: {
code: "LBP",
name: "Lebanese Pound",
symbol_native: "ل.ل.",
decimal_digits: 0,
},
LKR: {
code: "LKR",
name: "Sri Lankan Rupee",
symbol_native: "SL Re",
decimal_digits: 2,
},
LTL: {
code: "LTL",
name: "Lithuanian Litas",
symbol_native: "Lt",
decimal_digits: 2,
},
LVL: {
code: "LVL",
name: "Latvian Lats",
symbol_native: "Ls",
decimal_digits: 2,
},
LYD: {
code: "LYD",
name: "Libyan Dinar",
symbol_native: "د.ل.",
decimal_digits: 3,
},
MAD: {
code: "MAD",
name: "Moroccan Dirham",
symbol_native: "د.م.",
decimal_digits: 2,
},
MDL: {
code: "MDL",
name: "Moldovan Leu",
symbol_native: "MDL",
decimal_digits: 2,
},
MGA: {
code: "MGA",
name: "Malagasy Ariary",
symbol_native: "MGA",
decimal_digits: 0,
},
MKD: {
code: "MKD",
name: "Macedonian Denar",
symbol_native: "MKD",
decimal_digits: 2,
},
MMK: {
code: "MMK",
name: "Myanma Kyat",
symbol_native: "K",
decimal_digits: 0,
},
MNT: {
code: "MNT",
name: "Mongolian Tugrig",
symbol_native: "₮",
decimal_digits: 0,
},
MOP: {
code: "MOP",
name: "Macanese Pataca",
symbol_native: "MOP$",
decimal_digits: 2,
},
MUR: {
code: "MUR",
name: "Mauritian Rupee",
symbol_native: "MURs",
decimal_digits: 0,
},
MXN: {
code: "MXN",
name: "Mexican Peso",
symbol_native: "$",
decimal_digits: 2,
},
MYR: {
code: "MYR",
name: "Malaysian Ringgit",
symbol_native: "RM",
decimal_digits: 2,
},
MZN: {
code: "MZN",
name: "Mozambican Metical",
symbol_native: "MTn",
decimal_digits: 2,
},
NAD: {
code: "NAD",
name: "Namibian Dollar",
symbol_native: "N$",
decimal_digits: 2,
},
NGN: {
code: "NGN",
name: "Nigerian Naira",
symbol_native: "₦",
decimal_digits: 2,
},
NIO: {
code: "NIO",
name: "Nicaraguan Córdoba",
symbol_native: "C$",
decimal_digits: 2,
},
NOK: {
code: "NOK",
name: "Norwegian Krone",
symbol_native: "kr",
decimal_digits: 2,
},
NPR: {
code: "NPR",
name: "Nepalese Rupee",
symbol_native: "नेरू",
decimal_digits: 2,
},
NZD: {
code: "NZD",
name: "New Zealand Dollar",
symbol_native: "$",
decimal_digits: 2,
},
OMR: {
code: "OMR",
name: "Omani Rial",
symbol_native: "ر.ع.",
decimal_digits: 3,
},
PAB: {
code: "PAB",
name: "Panamanian Balboa",
symbol_native: "B/.",
decimal_digits: 2,
},
PEN: {
code: "PEN",
name: "Peruvian Nuevo Sol",
symbol_native: "S/.",
decimal_digits: 2,
},
PHP: {
code: "PHP",
name: "Philippine Peso",
symbol_native: "₱",
decimal_digits: 2,
},
PKR: {
code: "PKR",
name: "Pakistani Rupee",
symbol_native: "₨",
decimal_digits: 0,
},
PLN: {
code: "PLN",
name: "Polish Zloty",
symbol_native: "zł",
decimal_digits: 2,
},
PYG: {
code: "PYG",
name: "Paraguayan Guarani",
symbol_native: "₲",
decimal_digits: 0,
},
QAR: {
code: "QAR",
name: "Qatari Rial",
symbol_native: "ر.ق.",
decimal_digits: 2,
},
RON: {
code: "RON",
name: "Romanian Leu",
symbol_native: "RON",
decimal_digits: 2,
},
RSD: {
code: "RSD",
name: "Serbian Dinar",
symbol_native: "дин.",
decimal_digits: 0,
},
RUB: {
code: "RUB",
name: "Russian Ruble",
symbol_native: "₽.",
decimal_digits: 2,
},
RWF: {
code: "RWF",
name: "Rwandan Franc",
symbol_native: "FR",
decimal_digits: 0,
},
SAR: {
code: "SAR",
name: "Saudi Riyal",
symbol_native: "ر.س.",
decimal_digits: 2,
},
SDG: {
code: "SDG",
name: "Sudanese Pound",
symbol_native: "SDG",
decimal_digits: 2,
},
SEK: {
code: "SEK",
name: "Swedish Krona",
symbol_native: "kr",
decimal_digits: 2,
},
SGD: {
code: "SGD",
name: "Singapore Dollar",
symbol_native: "$",
decimal_digits: 2,
},
SOS: {
code: "SOS",
name: "Somali Shilling",
symbol_native: "Ssh",
decimal_digits: 0,
},
SYP: {
code: "SYP",
name: "Syrian Pound",
symbol_native: "ل.س.",
decimal_digits: 0,
},
THB: {
code: "THB",
name: "Thai Baht",
symbol_native: "฿",
decimal_digits: 2,
},
TND: {
code: "TND",
name: "Tunisian Dinar",
symbol_native: "د.ت.",
decimal_digits: 3,
},
TOP: {
code: "TOP",
name: "Tongan Paʻanga",
symbol_native: "T$",
decimal_digits: 2,
},
TRY: {
code: "TRY",
name: "Turkish Lira",
symbol_native: "TL",
decimal_digits: 2,
},
TTD: {
code: "TTD",
name: "Trinidad and Tobago Dollar",
symbol_native: "$",
decimal_digits: 2,
},
TWD: {
code: "TWD",
name: "New Taiwan Dollar",
symbol_native: "NT$",
decimal_digits: 2,
},
TZS: {
code: "TZS",
name: "Tanzanian Shilling",
symbol_native: "TSh",
decimal_digits: 0,
},
UAH: {
code: "UAH",
name: "Ukrainian Hryvnia",
symbol_native: "₴",
decimal_digits: 2,
},
UGX: {
code: "UGX",
name: "Ugandan Shilling",
symbol_native: "USh",
decimal_digits: 0,
},
UYU: {
code: "UYU",
name: "Uruguayan Peso",
symbol_native: "$",
decimal_digits: 2,
},
UZS: {
code: "UZS",
name: "Uzbekistan Som",
symbol_native: "UZS",
decimal_digits: 0,
},
VEF: {
code: "VEF",
name: "Venezuelan Bolívar",
symbol_native: "Bs.F.",
decimal_digits: 2,
},
VND: {
code: "VND",
name: "Vietnamese Dong",
symbol_native: "₫",
decimal_digits: 0,
},
XAF: {
code: "XAF",
name: "CFA Franc BEAC",
symbol_native: "FCFA",
decimal_digits: 0,
},
XOF: {
code: "XOF",
name: "CFA Franc BCEAO",
symbol_native: "CFA",
decimal_digits: 0,
},
YER: {
code: "YER",
name: "Yemeni Rial",
symbol_native: "ر.ي.",
decimal_digits: 0,
},
ZAR: {
code: "ZAR",
name: "South African Rand",
symbol_native: "R",
decimal_digits: 2,
},
ZMK: {
code: "ZMK",
name: "Zambian Kwacha",
symbol_native: "ZK",
decimal_digits: 0,
},
ZWL: {
code: "ZWL",
name: "Zimbabwean Dollar",
symbol_native: "ZWL$",
decimal_digits: 0,
},
}
export function getCurrencySymbol(code: string) {
return currencies[code.toUpperCase()].symbol_native
}
@@ -0,0 +1,57 @@
import { RouteObject } from "react-router-dom"
import { ErrorBoundary } from "../components/utilities/error-boundary"
/**
* Used to test if a route is a settings route.
*/
export const settingsRouteRegex = /^\/settings\//
export const createRouteMap = (
routes: { path: string; Component: () => JSX.Element }[],
ignore?: string
): RouteObject[] => {
const root: RouteObject[] = []
const addRoute = (
pathSegments: string[],
Component: () => JSX.Element,
currentLevel: RouteObject[]
) => {
if (!pathSegments.length) {
return
}
const [currentSegment, ...remainingSegments] = pathSegments
let route = currentLevel.find((r) => r.path === currentSegment)
if (!route) {
route = { path: currentSegment, children: [] }
currentLevel.push(route)
}
if (remainingSegments.length === 0) {
route.children ||= []
route.children.push({
path: "",
ErrorBoundary: ErrorBoundary,
async lazy() {
return { Component }
},
})
} else {
route.children ||= []
addRoute(remainingSegments, Component, route.children)
}
}
routes.forEach(({ path, Component }) => {
// Remove the ignore segment from the path if it is provided
const cleanedPath = ignore
? path.replace(ignore, "").replace(/^\/+/, "")
: path.replace(/^\/+/, "")
const pathSegments = cleanedPath.split("/").filter(Boolean)
addRoute(pathSegments, Component, root)
})
return root
}
@@ -0,0 +1,69 @@
import { castNumber } from "./cast-number"
export function transformNullableFormValue<T>(
value: T,
nullify = true
): T | undefined | null {
if (typeof value === "string" && value.trim() === "") {
return nullify ? null : undefined
}
if (Array.isArray(value) && value.length === 0) {
return nullify ? null : undefined
}
return value
}
type Nullable<T> = { [K in keyof T]: T[K] | null }
type Optional<T> = { [K in keyof T]: T[K] | undefined }
export function transformNullableFormData<
T extends Record<string, unknown>,
K extends boolean = true
>(data: T, nullify: K = true as K): K extends true ? Nullable<T> : Optional<T> {
return Object.entries(data).reduce((acc, [key, value]) => {
return {
...acc,
[key]: transformNullableFormValue(value, nullify),
}
}, {} as K extends true ? Nullable<T> : Optional<T>)
}
export function transformNullableFormNumber<K extends boolean = true>(
value?: string | number,
nullify: K = true as K
): K extends true ? number | null : number | undefined {
if (
typeof value === "undefined" ||
(typeof value === "string" && value.trim() === "")
) {
return (nullify ? null : undefined) as K extends true
? number | null
: number | undefined
}
if (typeof value === "string") {
return castNumber(value)
}
return value
}
type NullableNumbers = Record<string, number | null>
type OptionalNumbers = Record<string, number | undefined>
export function transformNullableFormNumbers<
T extends Record<string, string | number | undefined>,
K extends boolean = true
>(
data: T,
nullify: K = true as K
): K extends true ? NullableNumbers : OptionalNumbers {
return Object.entries(data).reduce((acc, [key, value]) => {
return {
...acc,
[key]: transformNullableFormNumber(value, nullify),
}
}, {} as K extends true ? NullableNumbers : OptionalNumbers)
}
@@ -0,0 +1,7 @@
export const formatCurrency = (amount: number, currency: string) => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,
signDisplay: "auto",
}).format(amount)
}
@@ -0,0 +1,18 @@
/**
* Providers only have an ID to identify them. This function formats the ID
* into a human-readable string.
*
* Format example: pp_stripe-blik_dkk
*
* @param id - The ID of the provider
* @returns A formatted string
*/
export const formatProvider = (id: string) => {
const [_, name, type] = id.split("_")
return (
name
.split("-")
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
.join(" ") + (type ? ` (${type.toUpperCase()})` : "")
)
}
@@ -0,0 +1,5 @@
import { FetchError } from "@medusajs/js-sdk"
export const isFetchError = (error: any): error is FetchError => {
return error instanceof FetchError
}
@@ -0,0 +1,53 @@
import { currencies } from "./data/currencies"
export const getDecimalDigits = (currency: string) => {
return currencies[currency.toUpperCase()]?.decimal_digits ?? 0
}
/**
* Returns a formatted amount based on the currency code using the browser's locale
* @param amount - The amount to format
* @param currencyCode - The currency code to format the amount in
* @returns - The formatted amount
*
* @example
* getFormattedAmount(10, "usd") // '$10.00' if the browser's locale is en-US
* getFormattedAmount(10, "usd") // '10,00 $' if the browser's locale is fr-FR
*/
export const getLocaleAmount = (amount: number, currencyCode: string) => {
const formatter = new Intl.NumberFormat([], {
style: "currency",
currencyDisplay: "narrowSymbol",
currency: currencyCode,
})
return formatter.format(amount)
}
export const getNativeSymbol = (currencyCode: string) => {
const formatted = new Intl.NumberFormat([], {
style: "currency",
currency: currencyCode,
currencyDisplay: "narrowSymbol",
}).format(0)
return formatted.replace(/\d/g, "").replace(/[.,]/g, "").trim()
}
/**
* In some cases we want to display the amount with the currency code and symbol,
* in the format of "symbol amount currencyCode". This breaks from the
* user's locale and is only used in cases where we want to display the
* currency code and symbol explicitly, e.g. for totals.
*/
export const getStylizedAmount = (amount: number, currencyCode: string) => {
const symbol = getNativeSymbol(currencyCode)
const decimalDigits = getDecimalDigits(currencyCode)
const total = amount.toLocaleString(undefined, {
minimumFractionDigits: decimalDigits,
maximumFractionDigits: decimalDigits,
})
return `${symbol} ${total} ${currencyCode.toUpperCase()}`
}
@@ -0,0 +1,58 @@
import { TFunction } from "i18next"
export const getOrderPaymentStatus = (
t: TFunction<"translation">,
status: string
) => {
const [label, color] = {
not_paid: [t("orders.payment.status.notPaid"), "red"],
authorized: [t("orders.payment.status.authorized"), "orange"],
partially_authorized: [
t("orders.payment.status.partiallyAuthorized"),
"red",
],
awaiting: [t("orders.payment.status.awaiting"), "orange"],
captured: [t("orders.payment.status.captured"), "green"],
refunded: [t("orders.payment.status.refunded"), "green"],
partially_refunded: [
t("orders.payment.status.partiallyRefunded"),
"orange",
],
partially_captured: [
t("orders.payment.status.partiallyCaptured"),
"orange",
],
canceled: [t("orders.payment.status.canceled"), "red"],
requires_action: [t("orders.payment.status.requiresAction"), "orange"],
}[status]
return { label, color }
}
export const getOrderFulfillmentStatus = (
t: TFunction<"translation">,
status: string
) => {
const [label, color] = {
not_fulfilled: [t("orders.fulfillment.status.notFulfilled"), "red"],
partially_fulfilled: [
t("orders.fulfillment.status.partiallyFulfilled"),
"orange",
],
fulfilled: [t("orders.fulfillment.status.fulfilled"), "green"],
partially_shipped: [
t("orders.fulfillment.status.partiallyShipped"),
"orange",
],
shipped: [t("orders.fulfillment.status.shipped"), "green"],
partially_returned: [
t("orders.fulfillment.status.partiallyReturned"),
"orange",
],
returned: [t("orders.fulfillment.status.returned"), "green"],
canceled: [t("orders.fulfillment.status.canceled"), "red"],
requires_action: [t("orders.fulfillment.status.requiresAction"), "orange"],
}[status] as [string, "red" | "orange" | "green"]
return { label, color }
}
@@ -0,0 +1,5 @@
import { OrderLineItemDTO } from "@medusajs/types"
export const getFulfillableQuantity = (item: OrderLineItemDTO) => {
return item.quantity - item.detail.fulfilled_quantity
}
@@ -0,0 +1,21 @@
import { AdminPaymentCollection } from "@medusajs/types"
export const getTotalCaptured = (
paymentCollections: AdminPaymentCollection[]
) =>
paymentCollections.reduce((acc, paymentCollection) => {
acc =
acc +
((paymentCollection.captured_amount as number) -
(paymentCollection.refunded_amount as number))
return acc
}, 0)
export const getTotalPending = (paymentCollections: AdminPaymentCollection[]) =>
paymentCollections.reduce((acc, paymentCollection) => {
acc +=
(paymentCollection.amount as number) -
(paymentCollection.captured_amount as number)
return acc
}, 0)
@@ -0,0 +1,27 @@
const formatter = new Intl.NumberFormat([], {
style: "percent",
minimumFractionDigits: 2,
})
/**
* Formats a number as a percentage
* @param value - The value to format
* @param isPercentageValue - Whether the value is already a percentage value (where `0` is 0%, `0.5` is 50%, `0.75` is 75%, etc). Defaults to false
* @returns The formatted percentage in the form of a localized string
*
* @example
* formatPercentage(0.5, true) // "50%"
* formatPercentage(50) // "50%"
*/
export const formatPercentage = (
value?: number | null,
isPercentageValue = false
) => {
let val = value || 0
if (!isPercentageValue) {
val = val / 100
}
return formatter.format(val)
}
@@ -0,0 +1,31 @@
import { PromotionDTO } from "@medusajs/types"
export enum PromotionStatus {
SCHEDULED = "SCHEDULED",
EXPIRED = "EXPIRED",
ACTIVE = "ACTIVE",
DISABLED = "DISABLED",
}
export const getPromotionStatus = (promotion: PromotionDTO) => {
const date = new Date()
const campaign = promotion.campaign
if (!campaign) {
return PromotionStatus.ACTIVE
}
if (new Date(campaign.starts_at!) > date) {
return PromotionStatus.SCHEDULED
}
const campaignBudget = campaign.budget
const overBudget =
campaignBudget && campaignBudget.used! > campaignBudget.limit!
if ((campaign.ends_at && new Date(campaign.ends_at) < date) || overBudget) {
return PromotionStatus.EXPIRED
}
return PromotionStatus.ACTIVE
}
@@ -0,0 +1,13 @@
import { QueryClient } from "@tanstack/react-query"
export const MEDUSA_BACKEND_URL = __BACKEND_URL__ ?? "http://localhost:9000"
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
staleTime: 90000,
retry: 1,
},
},
})
@@ -0,0 +1,54 @@
import { QueryKey, UseQueryOptions } from "@tanstack/react-query"
export type TQueryKey<TKey, TListQuery = any, TDetailQuery = string> = {
all: readonly [TKey]
lists: () => readonly [...TQueryKey<TKey>["all"], "list"]
list: (
query?: TListQuery
) => readonly [
...ReturnType<TQueryKey<TKey>["lists"]>,
{ query: TListQuery | undefined },
]
details: () => readonly [...TQueryKey<TKey>["all"], "detail"]
detail: (
id: TDetailQuery,
query?: TListQuery
) => readonly [
...ReturnType<TQueryKey<TKey>["details"]>,
TDetailQuery,
{ query: TListQuery | undefined },
]
}
export type UseQueryOptionsWrapper<
// Return type of queryFn
TQueryFn = unknown,
// Type thrown in case the queryFn rejects
E = Error,
// Query key type
TQueryKey extends QueryKey = QueryKey,
> = Omit<
UseQueryOptions<TQueryFn, E, TQueryFn, TQueryKey>,
"queryKey" | "queryFn"
>
export const queryKeysFactory = <
T,
TListQueryType = any,
TDetailQueryType = string,
>(
globalKey: T
) => {
const queryKeyFactory: TQueryKey<T, TListQueryType, TDetailQueryType> = {
all: [globalKey],
lists: () => [...queryKeyFactory.all, "list"],
list: (query?: TListQueryType) => [...queryKeyFactory.lists(), { query }],
details: () => [...queryKeyFactory.all, "detail"],
detail: (id: TDetailQueryType, query?: TListQueryType) => [
...queryKeyFactory.details(),
id,
{ query },
],
}
return queryKeyFactory
}
+17
View File
@@ -0,0 +1,17 @@
import { AdminOrderLineItem } from "@medusajs/types"
export function getReturnableQuantity(item: AdminOrderLineItem): number {
const {
shipped_quantity,
return_received_quantity,
return_dismissed_quantity,
return_requested_quantity,
} = item.detail
return (
shipped_quantity -
(return_received_quantity +
return_requested_quantity +
return_dismissed_quantity)
)
}
@@ -0,0 +1,36 @@
import i18n from "i18next"
import { z } from "zod"
export const AddressSchema = z.object({
first_name: z.string().min(1),
last_name: z.string().min(1),
company: z.string().optional(),
address_1: z.string().min(1),
address_2: z.string().optional(),
city: z.string().min(1),
postal_code: z.string().min(1),
province: z.string().optional(),
country_code: z.string().min(1),
phone: z.string().optional(),
})
export const EmailSchema = z.object({
email: z.string().email(),
})
export const TransferOwnershipSchema = z
.object({
current_owner_id: z.string().min(1),
new_owner_id: z
.string()
.min(1, i18n.t("transferOwnership.validation.required")),
})
.superRefine((data, ctx) => {
if (data.current_owner_id === data.new_owner_id) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["new_owner_id"],
message: i18n.t("transferOwnership.validation.mustBeDifferent"),
})
}
})
@@ -0,0 +1,19 @@
import { HttpTypes } from "@medusajs/types"
export function isReturnOption(shippingOption: HttpTypes.AdminShippingOption) {
return !!shippingOption.rules?.find(
(r) =>
r.attribute === "is_return" && r.value === "true" && r.operator === "eq"
)
}
export function isOptionEnabledInStore(
shippingOption: HttpTypes.AdminShippingOption
) {
return !!shippingOption.rules?.find(
(r) =>
r.attribute === "enabled_in_store" &&
r.value === "true" &&
r.operator === "eq"
)
}
@@ -0,0 +1,2 @@
export const MEDUSA_STOREFRONT_URL =
__STOREFRONT_URL__ ?? "http://localhost:8000"
@@ -0,0 +1,47 @@
import i18next from "i18next"
import { z } from "zod"
import { castNumber } from "./cast-number"
/**
* Validates that an optional value is an integer.
*/
export const optionalInt = z
.union([z.string(), z.number()])
.optional()
.refine(
(value) => {
if (value === "" || value === undefined) {
return true
}
return Number.isInteger(castNumber(value))
},
{
message: i18next.t("validation.mustBeInt"),
}
)
.refine(
(value) => {
if (value === "" || value === undefined) {
return true
}
return castNumber(value) >= 0
},
{
message: i18next.t("validation.mustBePositive"),
}
)
/**
* Schema for metadata form.
*/
export const metadataFormSchema = z.array(
z.object({
key: z.string(),
value: z.unknown(),
isInitial: z.boolean().optional(),
isDeleted: z.boolean().optional(),
isIgnored: z.boolean().optional(),
})
)