From a1962f6aca951cf3b2656b52c0db4f0e3d0791a6 Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 10 Jul 2026 07:58:46 +0300 Subject: [PATCH 1/6] feat(branding): use the OLITT logo on checkout and the browser tab Point the checkout header logo at olitt-logo.svg and add app/icon.svg as the favicon, removing the Spree favicon.ico. Add a checkout layout test asserting the OLITT logo renders and no Spree logo remains. --- .../(checkout)/__tests__/layout.test.tsx | 37 ++++++++++++++++++ .../[country]/[locale]/(checkout)/layout.tsx | 4 +- src/app/favicon.ico | Bin 25931 -> 0 bytes src/app/icon.svg | 11 ++++++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/app/[country]/[locale]/(checkout)/__tests__/layout.test.tsx delete mode 100644 src/app/favicon.ico create mode 100644 src/app/icon.svg diff --git a/src/app/[country]/[locale]/(checkout)/__tests__/layout.test.tsx b/src/app/[country]/[locale]/(checkout)/__tests__/layout.test.tsx new file mode 100644 index 00000000..df1338e6 --- /dev/null +++ b/src/app/[country]/[locale]/(checkout)/__tests__/layout.test.tsx @@ -0,0 +1,37 @@ +import { render, screen } from "@testing-library/react"; +import type { ReactNode } from "react"; +import { describe, expect, it, vi } from "vitest"; + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/contexts/CheckoutContext", () => ({ + CheckoutProvider: ({ children }: { children: ReactNode }) => children, + CheckoutSummary: () => null, +})); + +vi.mock("@/contexts/TenantContext", () => ({ + useTenantConfig: () => ({ storeName: "Test Store" }), +})); + +import CheckoutLayout from "../layout"; + +describe("CheckoutLayout branding", () => { + it("renders the OLITT logo and never the Spree logo", () => { + render( + +
checkout body
+
, + ); + + const logos = screen.getAllByRole("img"); + expect(logos.length).toBeGreaterThan(0); + for (const logo of logos) { + expect(logo).toHaveAttribute("src", "/olitt-logo.svg"); + } + expect( + logos.some((logo) => logo.getAttribute("src")?.includes("spree")), + ).toBe(false); + }); +}); diff --git a/src/app/[country]/[locale]/(checkout)/layout.tsx b/src/app/[country]/[locale]/(checkout)/layout.tsx index e727b428..51108ae3 100644 --- a/src/app/[country]/[locale]/(checkout)/layout.tsx +++ b/src/app/[country]/[locale]/(checkout)/layout.tsx @@ -23,7 +23,7 @@ function CheckoutHeader() { const tenantConfig = useTenantConfig(); const branding = resolveTenantBranding(tenantConfig, { name: tenantConfig.storeName ?? "Store", - logoUrl: "/spree.png", + logoUrl: "/olitt-logo.svg", }); const navigation = resolveTenantNavigation(tenantConfig); @@ -31,7 +31,7 @@ function CheckoutHeader() {
{branding.name%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m diff --git a/src/app/icon.svg b/src/app/icon.svg new file mode 100644 index 00000000..0e8c2c4e --- /dev/null +++ b/src/app/icon.svg @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file From 132de20ff25733095c42ba309e99df61e83f5ffa Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 10 Jul 2026 08:07:25 +0300 Subject: [PATCH 2/6] fix(home): populate featured products via getProducts Fetch homepage featured products through getProducts and drop the unused locale and country props threaded to the old fetch path. --- .../[country]/[locale]/(storefront)/page.tsx | 2 -- src/components/home/FeaturedProductsSection.tsx | 11 +---------- .../page-builder/PageSectionsRenderer.tsx | 4 ---- src/components/products/FeaturedProducts.tsx | 17 +++++------------ 4 files changed, 6 insertions(+), 28 deletions(-) diff --git a/src/app/[country]/[locale]/(storefront)/page.tsx b/src/app/[country]/[locale]/(storefront)/page.tsx index cd434d1b..02052253 100644 --- a/src/app/[country]/[locale]/(storefront)/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/page.tsx @@ -105,9 +105,7 @@ export default async function HomePage({ ); diff --git a/src/components/home/FeaturedProductsSection.tsx b/src/components/home/FeaturedProductsSection.tsx index 95408d71..971846fc 100644 --- a/src/components/home/FeaturedProductsSection.tsx +++ b/src/components/home/FeaturedProductsSection.tsx @@ -18,16 +18,12 @@ function CarouselSkeleton() { interface FeaturedProductsSectionProps { basePath: string; - locale: string; - country: string; currency?: string; section: HomepageFeaturedProductsSectionConfig; } export async function FeaturedProductsSection({ basePath, - locale, - country, currency, section, }: FeaturedProductsSectionProps) { @@ -79,12 +75,7 @@ export async function FeaturedProductsSection({
}> - +
diff --git a/src/components/page-builder/PageSectionsRenderer.tsx b/src/components/page-builder/PageSectionsRenderer.tsx index bb316d1e..cdb9b56f 100644 --- a/src/components/page-builder/PageSectionsRenderer.tsx +++ b/src/components/page-builder/PageSectionsRenderer.tsx @@ -17,8 +17,6 @@ interface PageSectionsRendererProps { export function PageSectionsRenderer({ sections, basePath, - locale, - country, currency, keyPrefix = "section", }: PageSectionsRendererProps) { @@ -39,9 +37,7 @@ export function PageSectionsRenderer({ ); diff --git a/src/components/products/FeaturedProducts.tsx b/src/components/products/FeaturedProducts.tsx index ac989146..ab16c713 100644 --- a/src/components/products/FeaturedProducts.tsx +++ b/src/components/products/FeaturedProducts.tsx @@ -1,8 +1,7 @@ import dynamic from "next/dynamic"; import { ProductCardSkeleton } from "@/components/products/ProductCardSkeleton"; import { PRODUCT_CARD_FIELDS } from "@/lib/data/cached"; -import { cachedListProducts } from "@/lib/data/products"; -import { getAccessToken } from "@/lib/spree"; +import { getProducts } from "@/lib/data/products"; const LazyProductCarousel = dynamic( () => @@ -22,23 +21,17 @@ const LazyProductCarousel = dynamic( interface FeaturedProductsProps { basePath: string; - locale: string; - country: string; currency?: string; } export async function FeaturedProducts({ basePath, - locale, - country, currency, }: FeaturedProductsProps) { - const userToken = await getAccessToken(); - const productsResponse = await cachedListProducts( - { limit: 8, fields: PRODUCT_CARD_FIELDS }, - { locale, country }, - userToken, - ); + const productsResponse = await getProducts({ + limit: 10, + fields: PRODUCT_CARD_FIELDS, + }); return ( Date: Fri, 10 Jul 2026 09:07:01 +0300 Subject: [PATCH 3/6] refactor(page-builder): drop unused locale/country props and de-brittle logo test PageSectionsRenderer and DynamicPageRenderer never read locale or country yet required them, forcing every caller to thread the values through. Remove the dead props from both components and all call sites. In the checkout branding test, select the logo images by their accessible name instead of asserting on every image, so an unrelated image added to the layout later cannot break it. --- .../[locale]/(checkout)/__tests__/layout.test.tsx | 6 ++---- .../[country]/[locale]/(checkout)/checkout/[id]/page.tsx | 4 ---- .../[locale]/(storefront)/pages/[...slug]/page.tsx | 8 +------- .../[locale]/(storefront)/products/[slug]/page.tsx | 4 ---- src/components/page-builder/DynamicPageRenderer.tsx | 6 ------ src/components/page-builder/PageSectionsRenderer.tsx | 2 -- 6 files changed, 3 insertions(+), 27 deletions(-) diff --git a/src/app/[country]/[locale]/(checkout)/__tests__/layout.test.tsx b/src/app/[country]/[locale]/(checkout)/__tests__/layout.test.tsx index df1338e6..6a13027d 100644 --- a/src/app/[country]/[locale]/(checkout)/__tests__/layout.test.tsx +++ b/src/app/[country]/[locale]/(checkout)/__tests__/layout.test.tsx @@ -25,13 +25,11 @@ describe("CheckoutLayout branding", () => { , ); - const logos = screen.getAllByRole("img"); + const logos = screen.getAllByRole("img", { name: "Test Store" }); expect(logos.length).toBeGreaterThan(0); for (const logo of logos) { expect(logo).toHaveAttribute("src", "/olitt-logo.svg"); + expect(logo.getAttribute("src")).not.toContain("spree"); } - expect( - logos.some((logo) => logo.getAttribute("src")?.includes("spree")), - ).toBe(false); }); }); diff --git a/src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx b/src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx index a7243d97..61ff1413 100644 --- a/src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx +++ b/src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx @@ -86,8 +86,6 @@ async function CheckoutDataLoader({ params }: CheckoutPageProps) { @@ -101,8 +99,6 @@ async function CheckoutDataLoader({ params }: CheckoutPageProps) { diff --git a/src/app/[country]/[locale]/(storefront)/pages/[...slug]/page.tsx b/src/app/[country]/[locale]/(storefront)/pages/[...slug]/page.tsx index 50722937..19e8f662 100644 --- a/src/app/[country]/[locale]/(storefront)/pages/[...slug]/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/pages/[...slug]/page.tsx @@ -58,12 +58,6 @@ export default async function DynamicStorePage({ } return ( - + ); } diff --git a/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx b/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx index 7224c88f..1d5d717a 100644 --- a/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx @@ -107,8 +107,6 @@ export default async function ProductPage({ @@ -118,8 +116,6 @@ export default async function ProductPage({ diff --git a/src/components/page-builder/DynamicPageRenderer.tsx b/src/components/page-builder/DynamicPageRenderer.tsx index acc61120..12b26fa5 100644 --- a/src/components/page-builder/DynamicPageRenderer.tsx +++ b/src/components/page-builder/DynamicPageRenderer.tsx @@ -4,24 +4,18 @@ import type { DynamicPageConfig } from "@/lib/page-builder"; interface DynamicPageRendererProps { page: DynamicPageConfig; basePath: string; - locale: string; - country: string; currency?: string; } export function DynamicPageRenderer({ page, basePath, - locale, - country, currency, }: DynamicPageRendererProps) { return ( diff --git a/src/components/page-builder/PageSectionsRenderer.tsx b/src/components/page-builder/PageSectionsRenderer.tsx index cdb9b56f..0a37a29a 100644 --- a/src/components/page-builder/PageSectionsRenderer.tsx +++ b/src/components/page-builder/PageSectionsRenderer.tsx @@ -8,8 +8,6 @@ import type { DynamicPageSectionConfig } from "@/lib/page-builder"; interface PageSectionsRendererProps { sections: DynamicPageSectionConfig[]; basePath: string; - locale: string; - country: string; currency?: string; keyPrefix?: string; } From 54cb2978706b90069d715d26e7405d110f4e0846 Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 10 Jul 2026 09:07:04 +0300 Subject: [PATCH 4/6] refactor(branding): serve the OLITT favicon from metadata Point the app icon at the existing public/olitt-logo.svg through the root layout metadata instead of shipping a second copy at src/app/icon.svg. Removes the duplicate asset while keeping the OLITT mark in the browser tab. --- src/app/icon.svg | 11 ----------- src/app/layout.tsx | 1 + 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 src/app/icon.svg diff --git a/src/app/icon.svg b/src/app/icon.svg deleted file mode 100644 index 0e8c2c4e..00000000 --- a/src/app/icon.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 69277513..68c1d825 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -35,6 +35,7 @@ export const metadata: Metadata = { default: rootStoreName, }, description: getStoreDescription(), + icons: { icon: "/olitt-logo.svg" }, }; export default async function RootLayout({ From 5ca397c556f0fbe41217456e97245ae02c82ef69 Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 10 Jul 2026 15:22:28 +0300 Subject: [PATCH 5/6] fix(homepage): scope theme colors to the sections that use them Set the design-system color variables on each themed homepage section so descendants resolve against the section theme, and bind the outline button, product card and carousel colors to those variables. The variables are set on the section element, leaving pages outside the homepage unaffected. Apply the accent pair only when the generator supplies its validated label color. --- .../home/FeaturedProductsSection.tsx | 7 +-- src/components/home/FeaturesSection.tsx | 7 +-- src/components/home/HeroSection.tsx | 7 +-- src/components/products/ProductCard.tsx | 12 ++--- src/components/products/ProductCarousel.tsx | 4 +- src/components/ui/button.tsx | 2 +- src/lib/homepage/section-theme.ts | 44 +++++++++++++++++++ src/lib/homepage/types.ts | 1 + 8 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 src/lib/homepage/section-theme.ts diff --git a/src/components/home/FeaturedProductsSection.tsx b/src/components/home/FeaturedProductsSection.tsx index 971846fc..ba52e97f 100644 --- a/src/components/home/FeaturedProductsSection.tsx +++ b/src/components/home/FeaturedProductsSection.tsx @@ -1,10 +1,10 @@ import Link from "next/link"; -import type { CSSProperties } from "react"; import { Suspense } from "react"; import { FeaturedProducts } from "@/components/products/FeaturedProducts"; import { ProductCardSkeleton } from "@/components/products/ProductCardSkeleton"; import { Button } from "@/components/ui/button"; import type { HomepageFeaturedProductsSectionConfig } from "@/lib/homepage"; +import { buildSectionThemeVars } from "@/lib/homepage/section-theme"; function CarouselSkeleton() { return ( @@ -28,10 +28,7 @@ export async function FeaturedProductsSection({ section, }: FeaturedProductsSectionProps) { const theme = section.theme ?? {}; - const sectionStyle: CSSProperties = { - backgroundColor: theme.background, - color: theme.foreground, - }; + const sectionStyle = buildSectionThemeVars(theme); const mutedTextColor = theme.mutedForeground ?? "#64748b"; const borderColor = theme.borderColor ?? "#cbd5e1"; diff --git a/src/components/home/FeaturesSection.tsx b/src/components/home/FeaturesSection.tsx index 08b51efb..2ccbfeae 100644 --- a/src/components/home/FeaturesSection.tsx +++ b/src/components/home/FeaturesSection.tsx @@ -1,6 +1,6 @@ import { ShieldCheck, ShoppingBag, Sparkles, Truck } from "lucide-react"; -import type { CSSProperties } from "react"; import type { HomepageFeaturesSectionConfig } from "@/lib/homepage"; +import { buildSectionThemeVars } from "@/lib/homepage/section-theme"; interface FeaturesSectionProps { section: HomepageFeaturesSectionConfig; @@ -21,10 +21,7 @@ function getFeatureIcon(iconName?: string) { export async function FeaturesSection({ section }: FeaturesSectionProps) { const theme = section.theme ?? {}; - const sectionStyle: CSSProperties = { - backgroundColor: theme.background, - color: theme.foreground, - }; + const sectionStyle = buildSectionThemeVars(theme); const mutedTextColor = theme.mutedForeground ?? "#6b7280"; const cardBackground = theme.cardBackground ?? "#ffffff"; const borderColor = theme.borderColor ?? "#e5e7eb"; diff --git a/src/components/home/HeroSection.tsx b/src/components/home/HeroSection.tsx index 1dba2f59..6e37fce2 100644 --- a/src/components/home/HeroSection.tsx +++ b/src/components/home/HeroSection.tsx @@ -1,12 +1,12 @@ import { ArrowRight, Play } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; -import type { CSSProperties } from "react"; import { Button } from "@/components/ui/button"; import type { HomepageActionIcon, HomepageHeroSectionConfig, } from "@/lib/homepage"; +import { buildSectionThemeVars } from "@/lib/homepage/section-theme"; interface HeroSectionProps { basePath: string; @@ -36,10 +36,7 @@ function getActionIcon(icon?: HomepageActionIcon) { export async function HeroSection({ basePath, section }: HeroSectionProps) { const theme = section.theme ?? {}; - const sectionStyle: CSSProperties = { - backgroundColor: theme.background, - color: theme.foreground, - }; + const sectionStyle = buildSectionThemeVars(theme); const mutedTextColor = theme.mutedForeground ?? "#6b7280"; const cardBackground = theme.cardBackground ?? "#ffffff"; const borderColor = theme.borderColor ?? "#e5e7eb"; diff --git a/src/components/products/ProductCard.tsx b/src/components/products/ProductCard.tsx index 4ad0406a..e5a94cd6 100644 --- a/src/components/products/ProductCard.tsx +++ b/src/components/products/ProductCard.tsx @@ -66,7 +66,7 @@ export const ProductCard = memo(function ProductCard({ onClick={handleClick} > {/* Image */} -
+
-

+

{product.name}

{displayPrice && ( - + {displayPrice} )} {onSale && strikethroughPrice && ( - + {strikethroughPrice} )}
{!product.purchasable && ( - {t("outOfStock")} + + {t("outOfStock")} + )}
diff --git a/src/components/products/ProductCarousel.tsx b/src/components/products/ProductCarousel.tsx index 0b050657..d887d7e7 100644 --- a/src/components/products/ProductCarousel.tsx +++ b/src/components/products/ProductCarousel.tsx @@ -20,7 +20,7 @@ interface ProductCarouselProps { } const NAV_BUTTON_BASE = - "absolute top-1/2 -translate-y-1/2 z-10 w-10 h-10 flex items-center justify-center cursor-pointer rounded-lg bg-white border border-gray-300 text-gray-600 hover:bg-gray-100 hover:text-gray-900 transition-colors"; + "absolute top-1/2 -translate-y-1/2 z-10 w-10 h-10 flex items-center justify-center cursor-pointer rounded-lg bg-background border border-border text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"; export function ProductCarousel({ products, @@ -49,7 +49,7 @@ export function ProductCarousel({ if (products.length === 0) { return (
-

{t("noProductsFound")}

+

{t("noProductsFound")}

); } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 3464538f..8119db1e 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -11,7 +11,7 @@ const buttonVariants = cva( variant: { default: "bg-primary text-primary-foreground hover:bg-primary/80", outline: - "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + "border-border bg-background text-foreground hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", ghost: diff --git a/src/lib/homepage/section-theme.ts b/src/lib/homepage/section-theme.ts new file mode 100644 index 00000000..c0d463aa --- /dev/null +++ b/src/lib/homepage/section-theme.ts @@ -0,0 +1,44 @@ +import type { CSSProperties } from "react"; +import type { HomepageThemeConfig } from "./types"; + +type ThemeVars = CSSProperties & Record<`--${string}`, string | undefined>; + +/** + * Build the inline style for a themed homepage section. + * + * Paints the section and scopes the design-system color variables to it, so + * descendants styled with utility classes such as `bg-background` or + * `text-foreground` resolve against the section's theme instead of the page + * defaults. The variables are set on the section element, so pages and + * components outside the section are unaffected. + * + * The accent pair is applied only when the theme carries both `accent` and + * `accentForeground`: an accent without its generator-validated label color + * would leave button labels on the default ink, which can fail contrast. + */ +export function buildSectionThemeVars(theme: HomepageThemeConfig): ThemeVars { + const hasReadableAccent = Boolean(theme.accent && theme.accentForeground); + const vars: ThemeVars = { + backgroundColor: theme.background, + color: theme.foreground, + "--background": theme.background, + "--foreground": theme.foreground, + "--card": theme.cardBackground, + "--card-foreground": theme.foreground, + "--muted": theme.cardBackground, + "--muted-foreground": theme.mutedForeground, + "--border": theme.borderColor, + "--input": theme.borderColor, + "--primary": hasReadableAccent ? theme.accent : undefined, + "--primary-foreground": hasReadableAccent + ? theme.accentForeground + : undefined, + "--ring": theme.accent, + }; + + for (const key of Object.keys(vars) as (keyof ThemeVars)[]) { + if (vars[key] === undefined) delete vars[key]; + } + + return vars; +} diff --git a/src/lib/homepage/types.ts b/src/lib/homepage/types.ts index e6d2d1dc..d9dcbbd7 100644 --- a/src/lib/homepage/types.ts +++ b/src/lib/homepage/types.ts @@ -17,6 +17,7 @@ export interface HomepageThemeConfig { background?: string; foreground?: string; accent?: string; + accentForeground?: string; mutedForeground?: string; cardBackground?: string; borderColor?: string; From beb8e4923ef8a90daacdbbc30b370e9a12e15de3 Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 10 Jul 2026 15:49:51 +0300 Subject: [PATCH 6/6] fix(homepage): Render checkout on the neutral default palette Render checkout on the neutral default palette instead of the tenant background, so a dark generated theme cannot make the payment form illegible. --- .../[country]/[locale]/(checkout)/layout.tsx | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/app/[country]/[locale]/(checkout)/layout.tsx b/src/app/[country]/[locale]/(checkout)/layout.tsx index 51108ae3..2396a818 100644 --- a/src/app/[country]/[locale]/(checkout)/layout.tsx +++ b/src/app/[country]/[locale]/(checkout)/layout.tsx @@ -138,18 +138,9 @@ interface CheckoutLayoutProps { function CheckoutLayoutContent({ children }: CheckoutLayoutProps) { return ( -
+
{/* Mobile header */} -
+
@@ -175,13 +166,7 @@ function CheckoutLayoutContent({ children }: CheckoutLayoutProps) {
{/* Desktop summary sidebar — Shopify: light gray bg with left border */} -
+