Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/herbatika/src/app/api/storefront-auth/_lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,14 @@ export const buildErrorResponse = async (response: Response) => {
)
}

export const isConflictStatus = (status: number) => status === 409

export const badRequest = (message: string) =>
NextResponse.json<ErrorPayload>({ message }, { status: 400 })

export const conflict = (message: string) =>
NextResponse.json<ErrorPayload>({ message }, { status: 409 })

export const serverError = (message: string, details?: unknown) =>
NextResponse.json<ErrorPayload>(
{
Expand Down
16 changes: 16 additions & 0 deletions apps/herbatika/src/app/api/storefront-auth/register/parse-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const asStringOrUndefined = (value: unknown) => {
if (typeof value !== "string") {
return
}

const trimmed = value.trim()
return trimmed.length > 0 ? trimmed : undefined
}

export const asRecordOrUndefined = (value: unknown) => {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return
}

return value as Record<string, unknown>
}
97 changes: 71 additions & 26 deletions apps/herbatika/src/app/api/storefront-auth/register/route.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import type { HttpTypes } from "@medusajs/types"
import { NextResponse } from "next/server"
import {
badRequest,
buildErrorResponse,
buildMedusaUrl,
conflict,
getPublishableHeaders,
isConflictStatus,
parseResponseJson,
serverError,
setSessionTokenCookie,
} from "../_lib"
import { asRecordOrUndefined, asStringOrUndefined } from "./parse-utils"
import {
createWholesaleCompanyRequest,
parseWholesaleRegistration,
} from "./wholesale"

type RegisterBody = {
email?: string
password?: string
first_name?: string
last_name?: string
wholesale?: unknown
}

type RegisterResponse = {
token: string
}

const asStringOrUndefined = (value: unknown) => {
if (typeof value !== "string") {
return
}

const trimmed = value.trim()
return trimmed.length > 0 ? trimmed : undefined
}

const isConflictStatus = (status: number) => status === 409

const createRegisterResponse = (token: string) => {
const response = NextResponse.json<RegisterResponse>(
{
Expand All @@ -44,7 +42,17 @@ const createRegisterResponse = (token: string) => {
}

const parseRegisterBody = async (request: Request) => {
const body = (await request.json()) as RegisterBody
const body = asRecordOrUndefined(await request.json()) as
| RegisterBody
| undefined

if (!body) {
return {
error: badRequest("Telo požiadavky musí byť platný JSON objekt."),
value: null,
}
}

const email = asStringOrUndefined(body.email)
const password = asStringOrUndefined(body.password)

Expand All @@ -55,13 +63,22 @@ const parseRegisterBody = async (request: Request) => {
}
}

const wholesale = parseWholesaleRegistration(body.wholesale)
if (wholesale.error) {
return {
error: wholesale.error,
value: null,
}
}

return {
error: null,
value: {
email,
password,
firstName: asStringOrUndefined(body.first_name),
lastName: asStringOrUndefined(body.last_name),
wholesale: wholesale.value,
},
}
}
Expand Down Expand Up @@ -92,7 +109,8 @@ export async function POST(request: Request) {
return parsedBody.error
}

const { email, firstName, lastName, password } = parsedBody.value
const { email, firstName, lastName, password, wholesale } =
parsedBody.value
const registerResponse = await fetch(
buildMedusaUrl("/auth/customer/emailpass/register"),
{
Expand All @@ -108,10 +126,17 @@ export async function POST(request: Request) {
}
)

if (!registerResponse.ok) {
const registerConflict = isConflictStatus(registerResponse.status)
if (!(registerResponse.ok || registerConflict)) {
return buildErrorResponse(registerResponse)
}

if (registerConflict && wholesale) {
return conflict(
"Účet s týmto e-mailom už existuje. Prihláste sa a požiadajte o VO účet cez podporu."
)
}

const loginResponse = await fetch(
buildMedusaUrl("/auth/customer/emailpass"),
{
Expand Down Expand Up @@ -143,6 +168,20 @@ export async function POST(request: Request) {
)
}

const customerProfile: HttpTypes.StoreCreateCustomer = {
email,
first_name: firstName,
last_name: lastName,
...(wholesale
? {
company_name: wholesale.companyName,
metadata: {
company_identifier: wholesale.companyIdentifier,
},
}
: {}),
}

const createCustomerResponse = await fetch(
buildMedusaUrl("/store/customers"),
{
Expand All @@ -152,25 +191,31 @@ export async function POST(request: Request) {
authorization: `Bearer ${loginToken}`,
...getPublishableHeaders(),
},
body: JSON.stringify({
email,
first_name: firstName,
last_name: lastName,
}),
body: JSON.stringify(customerProfile),
cache: "no-store",
}
)

if (
!(
createCustomerResponse.ok ||
isConflictStatus(createCustomerResponse.status)
)
) {
const customerConflict = isConflictStatus(createCustomerResponse.status)
if (!(createCustomerResponse.ok || customerConflict)) {
return buildErrorResponse(createCustomerResponse)
}

return createRegisterResponse(await refreshCustomerToken(loginToken))
const sessionToken = await refreshCustomerToken(loginToken)

if (wholesale) {
const companyError = await createWholesaleCompanyRequest({
email,
token: sessionToken,
wholesale,
})

if (companyError) {
return companyError
}
}

return createRegisterResponse(sessionToken)
} catch (error) {
if (error instanceof SyntaxError) {
return badRequest("Telo požiadavky musí byť platné JSON.")
Expand Down
144 changes: 144 additions & 0 deletions apps/herbatika/src/app/api/storefront-auth/register/wholesale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import type { NextResponse } from "next/server"
import { normalizeCountryCode } from "@/lib/forms/country-options"
import {
badRequest,
buildErrorResponse,
buildMedusaUrl,
getPublishableHeaders,
isConflictStatus,
} from "../_lib"
import { asRecordOrUndefined, asStringOrUndefined } from "./parse-utils"

export type ParsedWholesaleRegistration = {
companyName: string
companyIdentifier: string
currencyCode: string
billingAddress: {
address1: string
address2?: string
city: string
postalCode: string
countryCode: string
}
}

type WholesaleParseResult = {
error: NextResponse | null
value: ParsedWholesaleRegistration | null
}

export const parseWholesaleRegistration = (
value: unknown
): WholesaleParseResult => {
if (value === undefined || value === null) {
return { error: null, value: null }
}

const wholesale = asRecordOrUndefined(value)
if (!wholesale) {
return {
error: badRequest("Firemné údaje musia byť platný objekt."),
value: null,
}
}

const companyName = asStringOrUndefined(wholesale.company_name)
if (!companyName) {
return {
error: badRequest("Názov firmy je povinný."),
value: null,
}
}

const companyIdentifier = asStringOrUndefined(wholesale.company_identifier)
if (!companyIdentifier) {
return {
error: badRequest("IČO alebo firemný identifikátor je povinný."),
value: null,
}
}

const billingAddress = asRecordOrUndefined(wholesale.billing_address)
if (!billingAddress) {
return {
error: badRequest("Fakturačná adresa je povinná."),
value: null,
}
}

const address1 = asStringOrUndefined(billingAddress.address_1)
const city = asStringOrUndefined(billingAddress.city)
const postalCode = asStringOrUndefined(billingAddress.postal_code)
const rawCountryCode = asStringOrUndefined(billingAddress.country_code)

if (!(address1 && city && postalCode && rawCountryCode)) {
return {
error: badRequest("Fakturačná adresa je povinná."),
value: null,
}
}

const countryCode = normalizeCountryCode(rawCountryCode)
if (!countryCode) {
return {
error: badRequest("Vyberte platnú krajinu fakturačnej adresy."),
value: null,
}
}

return {
error: null,
value: {
companyName,
companyIdentifier,
currencyCode:
asStringOrUndefined(wholesale.currency_code)?.toUpperCase() ?? "EUR",
billingAddress: {
address1,
address2: asStringOrUndefined(billingAddress.address_2),
city,
postalCode,
countryCode: countryCode.toUpperCase(),
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
}
}

const createCompanyAddressLine = ({
address1,
address2,
}: ParsedWholesaleRegistration["billingAddress"]) =>
[address1, address2].filter(Boolean).join(", ")

export const createWholesaleCompanyRequest = async ({
email,
token,
wholesale,
}: {
email: string
token: string
wholesale: ParsedWholesaleRegistration
}) => {
const response = await fetch(buildMedusaUrl("/store/companies"), {
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${token}`,
...getPublishableHeaders(),
},
body: JSON.stringify({
name: wholesale.companyName,
email,
currency_code: wholesale.currencyCode.toLowerCase(),
address: createCompanyAddressLine(wholesale.billingAddress),
city: wholesale.billingAddress.city,
zip: wholesale.billingAddress.postalCode,
country: wholesale.billingAddress.countryCode.toLowerCase(),
}),
cache: "no-store",
Comment thread
greptile-apps[bot] marked this conversation as resolved.
})

return response.ok || isConflictStatus(response.status)
? null
: buildErrorResponse(response)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
1 change: 1 addition & 0 deletions apps/herbatika/src/components/auth-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function AuthControls({ mode, afterAuthHref }: AuthControlsProps) {

{mode === "register" && (
<RegisterForm
countryItems={controller.registerCountryItems}
defaultValues={controller.registerDefaultValues}
isBusy={controller.isBusy}
loginHref={controller.loginHref}
Expand Down
Loading
Loading