Opa @estevao_santos obrigado pelos comentários, havia esquecido de retornar aqui para mostrar como resolvi esse problema e até para servir com documentação para outros usuários.
Eu já tinha uma lógica semelhante ao do app que tinha uma barra de progresso com base no valor gasto, o que fiz para ter o preço correto?
1 - Dentro do manifest.json, eu criei o objeto separado por regiões e estados, para receber o valor mínimo para frete grátis:
"settingsSchema": {
"title": "Configuração | Minicart Valor Mínimo para Frete Grátis",
"type": "object",
"access": "public",
"bindingBounded": true,
"properties": {
"freeShippingRegions": {
"title": "Frete Grátis por Região",
"type": "object",
"properties": {
"Norte": {
"title": "Norte",
"type": "object",
"properties": {
"AC": {
"title": "Acre (AC)",
"type": "number"
},
"AP": {
"title": "Amapá (AP)",
"type": "number"
},
"AM": {
"title": "Amazonas (AM)",
"type": "number"
},
"PA": {
"title": "Pará (PA)",
"type": "number"
},
"RO": {
"title": "Rondônia (RO)",
"type": "number"
},
"RR": {
"title": "Roraima (RR)",
"type": "number"
},
"TO": {
"title": "Tocantins (TO)",
"type": "number"
}
}
},
"Nordeste": {
"title": "Nordeste",
"type": "object",
"properties": {
"AL": {
"title": "Alagoas (AL)",
"type": "number"
},
"BA": {
"title": "Bahia (BA)",
"type": "number"
},
"CE": {
"title": "Ceará (CE)",
"type": "number"
},
"MA": {
"title": "Maranhão (MA)",
"type": "number"
},
"PB": {
"title": "Paraíba (PB)",
"type": "number"
},
"PE": {
"title": "Pernambuco (PE)",
"type": "number"
},
"PI": {
"title": "Piauí (PI)",
"type": "number"
},
"RN": {
"title": "Rio Grande do Norte (RN)",
"type": "number"
},
"SE": {
"title": "Sergipe (SE)",
"type": "number"
}
}
},
"Centro-Oeste": {
"title": "Centro-Oeste",
"type": "object",
"properties": {
"DF": {
"title": "Distrito Federal (DF)",
"type": "number"
},
"GO": {
"title": "Goiás (GO)",
"type": "number"
},
"MT": {
"title": "Mato Grosso (MT)",
"type": "number"
},
"MS": {
"title": "Mato Grosso do Sul (MS)",
"type": "number"
}
}
},
"Sudeste": {
"title": "Sudeste",
"type": "object",
"properties": {
"ES": {
"title": "Espírito Santo (ES)",
"type": "number"
},
"MG": {
"title": "Minas Gerais (MG)",
"type": "number"
},
"RJ": {
"title": "Rio de Janeiro (RJ)",
"type": "number"
},
"SP": {
"title": "São Paulo (SP)",
"type": "number"
}
}
},
"Sul": {
"title": "Sul",
"type": "object",
"properties": {
"PR": {
"title": "Paraná (PR)",
"type": "number"
},
"RS": {
"title": "Rio Grande do Sul (RS)",
"type": "number"
},
"SC": {
"title": "Santa Catarina (SC)",
"type": "number"
}
}
}
}
}
}
},
2 - Criei uma query graphql que faz busca em vtex.apps-graphql:
query AppSettings {
publicSettingsForApp(app: "colocar_vendor_e_name_aqui", version: "x")
@context(provider: "vtex.apps-graphql") {
message
}
}
3 - usei o endpoint que é usado no checkout para busca o cep:
/api/checkout/pub/postal-code/BRA/CEP_AQUI
4 - Fiz o uso da api do checkout (attach) para adicionar o endereço após digitar o cep:
/api/checkout/pub/orderForm/${orderForm?.id}/attachments/shippingData
A implementação total ficou mais ou menos assim:
/* eslint-disable no-restricted-globals */
import React, { useState, useEffect } from 'react';
import { useOrderForm } from 'vtex.order-manager/OrderForm';
import { useCssHandles } from 'vtex.css-handles';
import { useQuery } from 'react-apollo';
import MinicartFreightBarHandles from './MinicartFreightBar.handles';
import SearchIcon from '../Icons/SearchIcon';
import AppSettings from './minicartbarSettings.graphql';
import formatMoney from '../../utils/formatMoney';
import styles from './MinicartFreeshipping.css';
const MinicartFreightBar = () => {
const [cep, setCep] = useState('');
const [state, setState] = useState('');
const [freeShippingAmount, setFreeShippingAmount] = useState(0);
const [isNewCep, setIsNewCep] = useState(false);
const minValue = freeShippingAmount;
const { orderForm } = useOrderForm();
const [remainingValue, setRemainigValue] = useState(0);
const [percentage, setPercentage] = useState(0);
const { handles } = useCssHandles(MinicartFreightBarHandles);
const { data } = useQuery(AppSettings, {
ssr: false,
fetchPolicy: 'no-cache',
});
useEffect(() => {
if (!orderForm) return;
if (orderForm?.shipping?.selectedAddress?.state && !isNewCep) {
setState(orderForm?.shipping?.selectedAddress?.state);
}
const minicartTotalValue = orderForm.value / 100;
setRemainigValue(minValue - minicartTotalValue);
if ((minicartTotalValue * 100) / Number(minValue) > 100)
return setPercentage(100);
setPercentage((minicartTotalValue * 100) / Number(minValue));
}, [isNewCep, minValue, orderForm]);
useEffect(() => {
if (!data?.publicSettingsForApp?.message) return;
const settings = JSON.parse(data.publicSettingsForApp.message);
const regions = settings.freeShippingRegions;
if (Object.keys(regions).length === 0) {
console.warn('No free shipping regions found');
return;
}
const states = Object?.keys(regions)?.reduce((acc, region) => {
const statesInRegion = regions[region];
Object?.keys(statesInRegion)?.forEach((statee) => {
acc[statee] = statesInRegion[statee];
});
return acc;
}, {} as { [state: string]: number });
const amount = states[state];
setFreeShippingAmount(amount);
}, [state, data]);
const handleChangeCep = (event: React.ChangeEvent<HTMLInputElement>) => {
let { value } = event.target;
value = value.replace(/\D/g, '');
if (value.length > 8) {
value = value.slice(0, 8);
}
if (value.length > 5) {
value = value.replace(/(\d{5})(\d)/, '$1-$2');
}
setCep(value);
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
try {
const response = await fetch(
`/api/checkout/pub/postal-code/BRA/${cep.replace(/\D/g, '')}`,
);
const dataCepInfos = await response.json();
if (dataCepInfos?.state?.length === 2) {
setIsNewCep(true);
setState(dataCepInfos?.state);
try {
const body = JSON.stringify({
clearAddressIfPostalCodeNotFound: false,
selectedAddresses: [
{
addressId: null,
addressType: 'search',
receiverName: '',
addressQuery: '',
...dataCepInfos,
isDisposable: true,
},
],
});
await fetch(
`/api/checkout/pub/orderForm/${orderForm?.id}/attachments/shippingData`,
{ body, method: 'POST' },
);
} catch (error) {
setIsNewCep(false);
console.error('Ocorreu um erro ao atualizar o orderform:', error);
}
}
} catch (error) {
setIsNewCep(false);
console.error('Ocorreu um erro ao buscar o endereço:', error);
}
};
return (
<>
<div className={handles.freightBarContainer}>
{remainingValue <= 0 ? (
<>
<div className={handles.animatedBarContainer}>
<div
style={{ width: `${percentage}%` }}
className={handles.animatedBar}
/>
</div>
<p className={handles.freightMessage}>
Parabéns, você ganhou <strong>FRETE GRÁTIS</strong>
</p>
</>
) : (
<div>
{isNaN(remainingValue) ? (
<p className={handles.freightMessage}>Digite um CEP válido</p>
) : (
<>
<div className={handles.animatedBarContainer}>
<div
style={{ width: `${percentage}%` }}
className={handles.animatedBar}
/>
</div>
<p className={handles.freightMessage}>
Faltam <strong>{formatMoney(remainingValue)}</strong> para{' '}
<strong>FRETE GRÁTIS</strong>
</p>
</>
)}
</div>
)}
</div>
<article className={styles.freeShippingCep__container}>
<section className={styles.freeShippingCep__textCep}>
<p>
Consulte seu CEP para <strong>FRETE GRÁTIS</strong>
</p>
</section>
<form
className={styles.freeShippingCep__content}
onSubmit={handleSubmit}
>
<input
type="text"
name="x-cep"
id="x-cep"
placeholder="Digite seu CEP"
className={styles.freeShippingCep__inputCep}
onChange={handleChangeCep}
value={cep}
/>
<button type="submit" className={styles.freeShippingCep__submit}>
<SearchIcon />
</button>
</form>
<section className={styles.freeShippingCep__DontKnowCep}>
<a
target="_blank"
href="http://buscacepinter.correios.com.br/app/endereco/index.php?t"
rel="noreferrer"
>
Não sei meu cep!
</a>
</section>
</article>
</>
);
};
export default MinicartFreightBar;
Sendo assim, dentro do admin, no meu app, consigo ajustar o valor do frete individualmente por estado:
E ao buscar no front, ele retorna os valores para que eu faça a conta:
Obrigado!!