chore(release): v1.1.1\n\n- ProductForm: price fields for all supported coins\n- ProductCard: icon-based prices + QORT equivalent (2 decimals)\n- Exchange rate card: icon display, compact formatting, no background\n- Release notes and announcement for 1.1.1

This commit is contained in:
q-shop-release-bot
2025-08-23 06:40:37 -04:00
parent 37b3fc0427
commit d9c2db33a1
8 changed files with 117 additions and 64 deletions

View File

@@ -0,0 +1,27 @@
# QShop v1.1.1 — Release Notes
Release date: 20250823
## Summary
Minor UI/UX improvements and a fix to price entry for all supported coins.
## Changes
- Product pricing form
- Shows price fields for every coin your shop supports (hides others).
- Pre-fills existing prices when editing a product.
- Product card price display
- Uses PNG icons for all coins.
- For nonQORT selections, shows: `[COIN icon] amount [QORT icon] qortEquivalent`.
- QORT amounts rounded to 2 decimals; no letter tickers.
- Exchange rate card (sidebar)
- Removed background panel to prevent text overflow.
- Icon-based display with both directions:
- `[QORT icon] 1 = X [COIN icon]`
- `[COIN icon] 1 = Y [QORT icon]`
- Compact formatting (>=1 → 4 decimals; <1 4 significant digits).
- Removed the arrow/icons row below the subtitle.
## Notes
- This is a patch over v1.1.0 (which introduced BTC/LTC/DOGE/DGB/RVN support, Edit Shop improvements, and initial pricing UX updates).
- Build: `npm ci && npm run build`. Artifacts in `dist/`.

View File

@@ -71,6 +71,17 @@ Goal: Ship new coin support and UI fixes with a clean build and verified UX.
- Create tag `v1.1.0` and push to trigger workflow.
- Draft Gitea release and attach `dist.zip` (then publish to Qortal).
## v1.1.1 Patch Plan
- Show price inputs for all supported coins in ProductForm.
- Unify product card pricing with PNG icons + QORT equivalency (2decimals).
- Simplify Exchange Rate card visuals and format values compactly.
- Bump to 1.1.1, add release/announcement docs, tag and release.
## v1.1.1 Progress
- Implemented ProductForm price fields for all supported coins.
- Updated product cards and Exchange Rate card per spec.
- Added docs/RELEASE_NOTES_v1.1.1.md and docs/USER_ANNOUNCEMENT_v1.1.1.md.
## Open Questions
- Recreate Shop Data: ok to publish immediately from Edit modal, or should we gate behind an extra confirmation modal?
- Should `onPublishParamEdit` include `storeIdentifier` for edits, or remain excluded as now?
@@ -89,3 +100,6 @@ Goal: Ship new coin support and UI fixes with a clean build and verified UX.
- Bumped version to 1.1.0 in `package.json`.
- Added release notes and user announcement under `docs/`.
- Added Gitea workflow to build and archive `dist.zip` on tag.
- Product pricing form now shows price fields for all supported coins in the shop (hides nonsupported coins) and pre-fills values when editing.
- Product card pricing now uses PNG icons for all coins; for nonQORT selections the card shows both the selected coin amount and the QORT equivalent (QORT rounded to 2 decimals) using icons only.
- Exchange rate card updated: removed background, switched to icons, shows both directions with compact formatting (>=1 → 4 decimals; <1 4 significant digits), and removed the arrows row.

View File

@@ -0,0 +1,9 @@
# QShop v1.1.1 — Whats New
Quick polish update:
- Enter prices for every coin your shop supports (not just QORT/ARRR).
- Product prices now show coin icons everywhere, with a clear QORT equivalent.
- Exchange rate box is cleaner, uses icons, and shows both directions.
Thats it — smoother pricing and clearer info. Update to 1.1.1 and enjoy!

View File

@@ -1,7 +1,7 @@
{
"name": "q-shop",
"private": true,
"version": "1.1.0",
"version": "1.1.1",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -84,9 +84,6 @@ export const ProductForm: React.FC<ProductFormProps> = ({
const editProductQortPrice =
editProduct?.price?.find((item: Price) => item?.currency === "qort")
?.value || product.price;
const editProductARRRPrice =
editProduct?.price?.find((item: Price) => item?.currency === CoinFilter.arrr)
?.value || "";
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setProduct({
@@ -276,20 +273,26 @@ export const ProductForm: React.FC<ProductFormProps> = ({
onChangeFunc={handleProductPriceChange}
required={true}
/>
{currentStore?.supportedCoins?.includes(CoinFilter.arrr) && (
<CustomNumberField
name="arrr-price"
label="Price in ARRR"
variant={Variant.filled}
initialValue={editProductARRRPrice.toString()}
addIconButtons={false}
minValue={0}
maxValue={Number.MAX_SAFE_INTEGER}
allowDecimals={true}
onChangeFunc={(val)=>handleProductPriceChangeForeign(val, CoinFilter.arrr)}
required={false}
/>
)}
{(currentStore?.supportedCoins || [])
.filter((c) => c && c !== CoinFilter.qort)
.map((coin) => {
const initial = editProduct?.price?.find((p: Price) => p?.currency === coin)?.value;
return (
<CustomNumberField
key={coin}
name={`price-${coin}`}
label={`Price in ${coin}`}
variant={Variant.filled}
initialValue={(initial ?? "").toString()}
addIconButtons={false}
minValue={0}
maxValue={Number.MAX_SAFE_INTEGER}
allowDecimals={true}
onChangeFunc={(val) => handleProductPriceChangeForeign(val, coin)}
required={false}
/>
);
})}
<Box>
<FormControl fullWidth>

View File

@@ -4,7 +4,7 @@ import { RootState } from "../../../state/store";
import { Product } from "../../../state/features/storeSlice";
import { useDispatch, useSelector } from "react-redux";
import { setProductToCart } from "../../../state/features/cartSlice";
import { QortalSVG } from "../../../assets/svgs/QortalSVG";
import { coinPng } from "../../../constants/coin-icons";
import {
AddToCartButton,
ProductDescription,
@@ -16,7 +16,6 @@ import { CartSVG } from "../../../assets/svgs/CartSVG";
import { useNavigate } from "react-router-dom";
import { setNotification } from "../../../state/features/notificationsSlice";
import { AcceptedCoinRow } from "../Store/Store-styles";
import { ARRRSVG } from "../../../assets/svgs/ARRRSVG";
import { CoinFilter } from "../Store/Store";
function addEllipsis(str: string, limit: number) {
@@ -79,6 +78,11 @@ export const ProductCard: React.FC<ProductCardProps> = ({ product, exchangeRate,
}
}
const fmtQort = (n?: number) => {
if (n === null || n === undefined || isNaN(n as any)) return "";
return Number(n).toFixed(2);
};
return (
<StyledCard>
<CardMedia
@@ -109,27 +113,18 @@ export const ProductCard: React.FC<ProductCardProps> = ({ product, exchangeRate,
width: "100%",
}}
>
{filterCoin === CoinFilter.qort && (
{filterCoin === CoinFilter.qort ? (
<AcceptedCoinRow>
<QortalSVG
color={theme.palette.text.primary}
height={"23"}
width={"23"}
/>{" "}
{price}
<img src={coinPng('QORT') || ''} alt="QORT" width={23} height={23} />
{" "}{fmtQort(price as number)}
</AcceptedCoinRow>
)}
{filterCoin !== CoinFilter.qort && (
) : (
<AcceptedCoinRow>
{filterCoin === CoinFilter.arrr ? (
<ARRRSVG color={theme.palette.text.primary} height={"23"} width={"23"} />
) : (
<span style={{ fontWeight: 600 }}>{filterCoin}</span>
)}
<img src={coinPng(filterCoin) || ''} alt={filterCoin} width={23} height={23} />
{" "}{price}
{qortApprox ? (
<div style={{ fontSize: 12, opacity: 0.7, marginTop: 2 }}> {qortApprox} QORT</div>
) : null}
<span style={{ width: 12 }} />
<img src={coinPng('QORT') || ''} alt="QORT" width={23} height={23} />
{" "}{fmtQort(qortApprox ?? (priceQort as number))}
</AcceptedCoinRow>
)}
</ProductDescription>

View File

@@ -192,6 +192,13 @@ export const Store = () => {
null
);
const formatRate = (n: number): string => {
if (!isFinite(n)) return "";
const abs = Math.abs(n);
if (abs >= 1) return Number(n).toFixed(4);
return Number(n).toPrecision(4);
};
const storeToUse = useMemo(()=> {
return username === user?.name
? currentStore
@@ -793,30 +800,31 @@ const switchCoin = async ()=> {
</FormControl>
{coinToUse !== CoinFilter.qort && exchangeRate && (
<ExchangeRateCard>
<ExchangeRateRow>
<ExchangeRateTitle>1 QORT = {exchangeRate} {coinToUse}</ExchangeRateTitle>
</ExchangeRateRow>
<ExchangeRateRow>
<ExchangeRateTitle>
1 {String(coinToUse)} = {Number((1 / (exchangeRate || 1)).toFixed(8))} QORT
</ExchangeRateTitle>
</ExchangeRateRow>
<ExchangeRateRow>
<ExchangeRateSubTitle>
{`Rate calculated by recent trade portal trades`}
</ExchangeRateSubTitle>
</ExchangeRateRow>
<ExchangeRateRow style={{ gap: "10px" }}>
<AcceptedCoin src={coinPng('QORT') || ''} alt="QORT-logo" />
<CompareArrowsSVG
color={theme.palette.text.primary}
height={"32"}
width={"32"}
/>
<AcceptedCoin src={coinPng(String(coinToUse)) || ''} alt={`${coinToUse}-logo`} />
</ExchangeRateRow>
</ExchangeRateCard>
<ExchangeRateCard>
<ExchangeRateRow>
<ExchangeRateTitle>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
<AcceptedCoin src={coinPng('QORT') || ''} alt="QORT" />
1 = {formatRate(exchangeRate)}
<AcceptedCoin src={coinPng(String(coinToUse)) || ''} alt={`${coinToUse}`} />
</span>
</ExchangeRateTitle>
</ExchangeRateRow>
<ExchangeRateRow>
<ExchangeRateTitle>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
<AcceptedCoin src={coinPng(String(coinToUse)) || ''} alt={`${coinToUse}`} />
1 = {formatRate(1 / (exchangeRate || 1))}
<AcceptedCoin src={coinPng('QORT') || ''} alt="QORT" />
</span>
</ExchangeRateTitle>
</ExchangeRateRow>
<ExchangeRateRow>
<ExchangeRateSubTitle>
{`Rate calculated by recent trade portal trades`}
</ExchangeRateSubTitle>
</ExchangeRateRow>
</ExchangeRateCard>
)}
</FiltersSubContainer>
<FiltersTitle>

View File

@@ -306,9 +306,6 @@ export const ExchangeRateCard = styled(Box)(({ theme }) => ({
gap: "10px",
padding: "10px 15px",
width: "100%",
height: "200px",
backgroundColor: theme.palette.background.paper,
borderRadius: "10px",
marginTop: "10px",
}));