first commit
This commit is contained in:
344
components/shop/Checkout.jsx
Normal file
344
components/shop/Checkout.jsx
Normal file
@ -0,0 +1,344 @@
|
||||
"use client";
|
||||
import { useContextElement } from "@/context/Context";
|
||||
import Link from "next/link";
|
||||
import DropdownSelect from "../common/DropdownSelect";
|
||||
export default function Checkout() {
|
||||
const {
|
||||
cartProducts,
|
||||
|
||||
totalPrice,
|
||||
} = useContextElement();
|
||||
|
||||
return (
|
||||
<div className="checkout-product tf-spacing-40">
|
||||
<div className="tf-container">
|
||||
<div className="row">
|
||||
<div className="col-md-8">
|
||||
<div className="left">
|
||||
<form
|
||||
className="form-checkout"
|
||||
onSubmit={(e) => e.preventDefault()}
|
||||
>
|
||||
<div className="mb_33">
|
||||
<h6 className="mb_12">Contact information</h6>
|
||||
<p className="text-body-2 mb_18">
|
||||
We’ll use this email to send you details and updates about
|
||||
your order
|
||||
</p>
|
||||
<fieldset>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
id="email"
|
||||
className="rounded-cycle"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div className="mb_34">
|
||||
<h6 className="mb_12">Billing address</h6>
|
||||
<p className="text-body-2 mb_18">
|
||||
Enter the billing address that matches your payment method
|
||||
</p>
|
||||
<div className="tf-select-tranform-lable mb_24">
|
||||
<span className="select-label">Country/Region</span>
|
||||
<select id="countrySelect" name="region-choose">
|
||||
<option />
|
||||
<option
|
||||
value="United States"
|
||||
data-provinces="[['Alabama','Alabama'],['Alaska','Alaska'],['American Samoa','American Samoa'],['Arizona','Arizona'],['Arkansas','Arkansas'],['Armed Forces Americas','Armed Forces Americas'],['Armed Forces Europe','Armed Forces Europe'],['Armed Forces Pacific','Armed Forces Pacific'],['California','California'],['Colorado','Colorado'],['Connecticut','Connecticut'],['Delaware','Delaware'],['District of Columbia','Washington DC'],['Federated States of Micronesia','Micronesia'],['Florida','Florida'],['Georgia','Georgia'],['Guam','Guam'],['Hawaii','Hawaii'],['Idaho','Idaho'],['Illinois','Illinois'],['Indiana','Indiana'],['Iowa','Iowa'],['Kansas','Kansas'],['Kentucky','Kentucky'],['Louisiana','Louisiana'],['Maine','Maine'],['Marshall Islands','Marshall Islands'],['Maryland','Maryland'],['Massachusetts','Massachusetts'],['Michigan','Michigan'],['Minnesota','Minnesota'],['Mississippi','Mississippi'],['Missouri','Missouri'],['Montana','Montana'],['Nebraska','Nebraska'],['Nevada','Nevada'],['New Hampshire','New Hampshire'],['New Jersey','New Jersey'],['New Mexico','New Mexico'],['New York','New York'],['North Carolina','North Carolina'],['North Dakota','North Dakota'],['Northern Mariana Islands','Northern Mariana Islands'],['Ohio','Ohio'],['Oklahoma','Oklahoma'],['Oregon','Oregon'],['Palau','Palau'],['Pennsylvania','Pennsylvania'],['Puerto Rico','Puerto Rico'],['Rhode Island','Rhode Island'],['South Carolina','South Carolina'],['South Dakota','South Dakota'],['Tennessee','Tennessee'],['Texas','Texas'],['Utah','Utah'],['Vermont','Vermont'],['Virgin Islands','U.S. Virgin Islands'],['Virginia','Virginia'],['Washington','Washington'],['West Virginia','West Virginia'],['Wisconsin','Wisconsin'],['Wyoming','Wyoming']]"
|
||||
>
|
||||
United States
|
||||
</option>
|
||||
<option
|
||||
value="Australia"
|
||||
data-provinces="[['Australian Capital Territory','Australian Capital Territory'],['New South Wales','New South Wales'],['Northern Territory','Northern Territory'],['Queensland','Queensland'],['South Australia','South Australia'],['Tasmania','Tasmania'],['Victoria','Victoria'],['Western Australia','Western Australia']]"
|
||||
>
|
||||
Australia
|
||||
</option>
|
||||
<option value="Austria" data-provinces="[]">
|
||||
Austria
|
||||
</option>
|
||||
<option value="Belgium" data-provinces="[]">
|
||||
Belgium
|
||||
</option>
|
||||
<option
|
||||
value="Canada"
|
||||
data-provinces="[['Alberta','Alberta'],['British Columbia','British Columbia'],['Manitoba','Manitoba'],['New Brunswick','New Brunswick'],['Newfoundland and Labrador','Newfoundland and Labrador'],['Northwest Territories','Northwest Territories'],['Nova Scotia','Nova Scotia'],['Nunavut','Nunavut'],['Ontario','Ontario'],['Prince Edward Island','Prince Edward Island'],['Quebec','Quebec'],['Saskatchewan','Saskatchewan'],['Yukon','Yukon']]"
|
||||
>
|
||||
Canada
|
||||
</option>
|
||||
<option value="Czech Republic" data-provinces="[]">
|
||||
Czechia
|
||||
</option>
|
||||
<option value="Denmark" data-provinces="[]">
|
||||
Denmark
|
||||
</option>
|
||||
<option value="Finland" data-provinces="[]">
|
||||
Finland
|
||||
</option>
|
||||
<option value="France" data-provinces="[]">
|
||||
France
|
||||
</option>
|
||||
<option value="Germany" data-provinces="[]">
|
||||
Germany
|
||||
</option>
|
||||
<option
|
||||
value="Hong Kong"
|
||||
data-provinces="[['Hong Kong Island','Hong Kong Island'],['Kowloon','Kowloon'],['New Territories','New Territories']]"
|
||||
>
|
||||
Hong Kong SAR
|
||||
</option>
|
||||
<option
|
||||
value="Ireland"
|
||||
data-provinces="[['Carlow','Carlow'],['Cavan','Cavan'],['Clare','Clare'],['Cork','Cork'],['Donegal','Donegal'],['Dublin','Dublin'],['Galway','Galway'],['Kerry','Kerry'],['Kildare','Kildare'],['Kilkenny','Kilkenny'],['Laois','Laois'],['Leitrim','Leitrim'],['Limerick','Limerick'],['Longford','Longford'],['Louth','Louth'],['Mayo','Mayo'],['Meath','Meath'],['Monaghan','Monaghan'],['Offaly','Offaly'],['Roscommon','Roscommon'],['Sligo','Sligo'],['Tipperary','Tipperary'],['Waterford','Waterford'],['Westmeath','Westmeath'],['Wexford','Wexford'],['Wicklow','Wicklow']]"
|
||||
>
|
||||
Ireland
|
||||
</option>
|
||||
<option value="Israel" data-provinces="[]">
|
||||
Israel
|
||||
</option>
|
||||
<option
|
||||
value="Italy"
|
||||
data-provinces="[['Agrigento','Agrigento'],['Alessandria','Alessandria'],['Ancona','Ancona'],['Aosta','Aosta Valley'],['Arezzo','Arezzo'],['Ascoli Piceno','Ascoli Piceno'],['Asti','Asti'],['Avellino','Avellino'],['Bari','Bari'],['Barletta-Andria-Trani','Barletta-Andria-Trani'],['Belluno','Belluno'],['Benevento','Benevento'],['Bergamo','Bergamo'],['Biella','Biella'],['Bologna','Bologna'],['Bolzano','South Tyrol'],['Brescia','Brescia'],['Brindisi','Brindisi'],['Cagliari','Cagliari'],['Caltanissetta','Caltanissetta'],['Campobasso','Campobasso'],['Carbonia-Iglesias','Carbonia-Iglesias'],['Caserta','Caserta'],['Catania','Catania'],['Catanzaro','Catanzaro'],['Chieti','Chieti'],['Como','Como'],['Cosenza','Cosenza'],['Cremona','Cremona'],['Crotone','Crotone'],['Cuneo','Cuneo'],['Enna','Enna'],['Fermo','Fermo'],['Ferrara','Ferrara'],['Firenze','Florence'],['Foggia','Foggia'],['Forlì-Cesena','Forlì-Cesena'],['Frosinone','Frosinone'],['Genova','Genoa'],['Gorizia','Gorizia'],['Grosseto','Grosseto'],['Imperia','Imperia'],['Isernia','Isernia'],['L'Aquila','L’Aquila'],['La Spezia','La Spezia'],['Latina','Latina'],['Lecce','Lecce'],['Lecco','Lecco'],['Livorno','Livorno'],['Lodi','Lodi'],['Lucca','Lucca'],['Macerata','Macerata'],['Mantova','Mantua'],['Massa-Carrara','Massa and Carrara'],['Matera','Matera'],['Medio Campidano','Medio Campidano'],['Messina','Messina'],['Milano','Milan'],['Modena','Modena'],['Monza e Brianza','Monza and Brianza'],['Napoli','Naples'],['Novara','Novara'],['Nuoro','Nuoro'],['Ogliastra','Ogliastra'],['Olbia-Tempio','Olbia-Tempio'],['Oristano','Oristano'],['Padova','Padua'],['Palermo','Palermo'],['Parma','Parma'],['Pavia','Pavia'],['Perugia','Perugia'],['Pesaro e Urbino','Pesaro and Urbino'],['Pescara','Pescara'],['Piacenza','Piacenza'],['Pisa','Pisa'],['Pistoia','Pistoia'],['Pordenone','Pordenone'],['Potenza','Potenza'],['Prato','Prato'],['Ragusa','Ragusa'],['Ravenna','Ravenna'],['Reggio Calabria','Reggio Calabria'],['Reggio Emilia','Reggio Emilia'],['Rieti','Rieti'],['Rimini','Rimini'],['Roma','Rome'],['Rovigo','Rovigo'],['Salerno','Salerno'],['Sassari','Sassari'],['Savona','Savona'],['Siena','Siena'],['Siracusa','Syracuse'],['Sondrio','Sondrio'],['Taranto','Taranto'],['Teramo','Teramo'],['Terni','Terni'],['Torino','Turin'],['Trapani','Trapani'],['Trento','Trentino'],['Treviso','Treviso'],['Trieste','Trieste'],['Udine','Udine'],['Varese','Varese'],['Venezia','Venice'],['Verbano-Cusio-Ossola','Verbano-Cusio-Ossola'],['Vercelli','Vercelli'],['Verona','Verona'],['Vibo Valentia','Vibo Valentia'],['Vicenza','Vicenza'],['Viterbo','Viterbo']]"
|
||||
>
|
||||
Italy
|
||||
</option>
|
||||
<option
|
||||
value="Japan"
|
||||
data-provinces="[['Aichi','Aichi'],['Akita','Akita'],['Aomori','Aomori'],['Chiba','Chiba'],['Ehime','Ehime'],['Fukui','Fukui'],['Fukuoka','Fukuoka'],['Fukushima','Fukushima'],['Gifu','Gifu'],['Gunma','Gunma'],['Hiroshima','Hiroshima'],['Hokkaidō','Hokkaido'],['Hyōgo','Hyogo'],['Ibaraki','Ibaraki'],['Ishikawa','Ishikawa'],['Iwate','Iwate'],['Kagawa','Kagawa'],['Kagoshima','Kagoshima'],['Kanagawa','Kanagawa'],['Kumamoto','Kumamoto'],['Kyōto','Kyoto'],['Kōchi','Kochi'],['Mie','Mie'],['Miyagi','Miyagi'],['Miyazaki','Miyazaki'],['Nagano','Nagano'],['Nagasaki','Nagasaki'],['Nara','Nara'],['Niigata','Niigata'],['Okayama','Okayama'],['Okinawa','Okinawa'],['Saga','Saga'],['Saitama','Saitama'],['Shiga','Shiga'],['Shimane','Shimane'],['Shizuoka','Shizuoka'],['Tochigi','Tochigi'],['Tokushima','Tokushima'],['Tottori','Tottori'],['Toyama','Toyama'],['Tōkyō','Tokyo'],['Wakayama','Wakayama'],['Yamagata','Yamagata'],['Yamaguchi','Yamaguchi'],['Yamanashi','Yamanashi'],['Ōita','Oita'],['Ōsaka','Osaka']]"
|
||||
>
|
||||
Japan
|
||||
</option>
|
||||
<option
|
||||
value="Malaysia"
|
||||
data-provinces="[['Johor','Johor'],['Kedah','Kedah'],['Kelantan','Kelantan'],['Kuala Lumpur','Kuala Lumpur'],['Labuan','Labuan'],['Melaka','Malacca'],['Negeri Sembilan','Negeri Sembilan'],['Pahang','Pahang'],['Penang','Penang'],['Perak','Perak'],['Perlis','Perlis'],['Putrajaya','Putrajaya'],['Sabah','Sabah'],['Sarawak','Sarawak'],['Selangor','Selangor'],['Terengganu','Terengganu']]"
|
||||
>
|
||||
Malaysia
|
||||
</option>
|
||||
<option value="Netherlands" data-provinces="[]">
|
||||
Netherlands
|
||||
</option>
|
||||
<option
|
||||
value="New Zealand"
|
||||
data-provinces="[['Auckland','Auckland'],['Bay of Plenty','Bay of Plenty'],['Canterbury','Canterbury'],['Chatham Islands','Chatham Islands'],['Gisborne','Gisborne'],['Hawke's Bay','Hawke’s Bay'],['Manawatu-Wanganui','Manawatū-Whanganui'],['Marlborough','Marlborough'],['Nelson','Nelson'],['Northland','Northland'],['Otago','Otago'],['Southland','Southland'],['Taranaki','Taranaki'],['Tasman','Tasman'],['Waikato','Waikato'],['Wellington','Wellington'],['West Coast','West Coast']]"
|
||||
>
|
||||
New Zealand
|
||||
</option>
|
||||
<option value="Norway" data-provinces="[]">
|
||||
Norway
|
||||
</option>
|
||||
<option value="Poland" data-provinces="[]">
|
||||
Poland
|
||||
</option>
|
||||
<option
|
||||
value="Portugal"
|
||||
data-provinces="[['Aveiro','Aveiro'],['Açores','Azores'],['Beja','Beja'],['Braga','Braga'],['Bragança','Bragança'],['Castelo Branco','Castelo Branco'],['Coimbra','Coimbra'],['Faro','Faro'],['Guarda','Guarda'],['Leiria','Leiria'],['Lisboa','Lisbon'],['Madeira','Madeira'],['Portalegre','Portalegre'],['Porto','Porto'],['Santarém','Santarém'],['Setúbal','Setúbal'],['Viana do Castelo','Viana do Castelo'],['Vila Real','Vila Real'],['Viseu','Viseu'],['Évora','Évora']]"
|
||||
>
|
||||
Portugal
|
||||
</option>
|
||||
<option value="Singapore" data-provinces="[]">
|
||||
Singapore
|
||||
</option>
|
||||
<option
|
||||
value="South Korea"
|
||||
data-provinces="[['Busan','Busan'],['Chungbuk','North Chungcheong'],['Chungnam','South Chungcheong'],['Daegu','Daegu'],['Daejeon','Daejeon'],['Gangwon','Gangwon'],['Gwangju','Gwangju City'],['Gyeongbuk','North Gyeongsang'],['Gyeonggi','Gyeonggi'],['Gyeongnam','South Gyeongsang'],['Incheon','Incheon'],['Jeju','Jeju'],['Jeonbuk','North Jeolla'],['Jeonnam','South Jeolla'],['Sejong','Sejong'],['Seoul','Seoul'],['Ulsan','Ulsan']]"
|
||||
>
|
||||
South Korea
|
||||
</option>
|
||||
<option
|
||||
value="Spain"
|
||||
data-provinces="[['A Coruña','A Coruña'],['Albacete','Albacete'],['Alicante','Alicante'],['Almería','Almería'],['Asturias','Asturias Province'],['Badajoz','Badajoz'],['Balears','Balears Province'],['Barcelona','Barcelona'],['Burgos','Burgos'],['Cantabria','Cantabria Province'],['Castellón','Castellón'],['Ceuta','Ceuta'],['Ciudad Real','Ciudad Real'],['Cuenca','Cuenca'],['Cáceres','Cáceres'],['Cádiz','Cádiz'],['Córdoba','Córdoba'],['Girona','Girona'],['Granada','Granada'],['Guadalajara','Guadalajara'],['Guipúzcoa','Gipuzkoa'],['Huelva','Huelva'],['Huesca','Huesca'],['Jaén','Jaén'],['La Rioja','La Rioja Province'],['Las Palmas','Las Palmas'],['León','León'],['Lleida','Lleida'],['Lugo','Lugo'],['Madrid','Madrid Province'],['Melilla','Melilla'],['Murcia','Murcia'],['Málaga','Málaga'],['Navarra','Navarra'],['Ourense','Ourense'],['Palencia','Palencia'],['Pontevedra','Pontevedra'],['Salamanca','Salamanca'],['Santa Cruz de Tenerife','Santa Cruz de Tenerife'],['Segovia','Segovia'],['Sevilla','Seville'],['Soria','Soria'],['Tarragona','Tarragona'],['Teruel','Teruel'],['Toledo','Toledo'],['Valencia','Valencia'],['Valladolid','Valladolid'],['Vizcaya','Biscay'],['Zamora','Zamora'],['Zaragoza','Zaragoza'],['Álava','Álava'],['Ávila','Ávila']]"
|
||||
>
|
||||
Spain
|
||||
</option>
|
||||
<option value="Sweden" data-provinces="[]">
|
||||
Sweden
|
||||
</option>
|
||||
<option value="Switzerland" data-provinces="[]">
|
||||
Switzerland
|
||||
</option>
|
||||
<option
|
||||
value="United Arab Emirates"
|
||||
data-provinces="[['Abu Dhabi','Abu Dhabi'],['Ajman','Ajman'],['Dubai','Dubai'],['Fujairah','Fujairah'],['Ras al-Khaimah','Ras al-Khaimah'],['Sharjah','Sharjah'],['Umm al-Quwain','Umm al-Quwain']]"
|
||||
>
|
||||
United Arab Emirates
|
||||
</option>
|
||||
<option
|
||||
value="United Kingdom"
|
||||
data-provinces="[['British Forces','British Forces'],['England','England'],['Northern Ireland','Northern Ireland'],['Scotland','Scotland'],['Wales','Wales']]"
|
||||
>
|
||||
United Kingdom
|
||||
</option>
|
||||
<option value="Vietnam" data-provinces="[]">
|
||||
Vietnam
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="grid-2 mb_24">
|
||||
<fieldset>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="First name"
|
||||
id="first-name"
|
||||
className="rounded-cycle"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Last name"
|
||||
id="last-name"
|
||||
className="rounded-cycle"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<fieldset className="mb_22">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Address"
|
||||
id="address"
|
||||
className="rounded-cycle"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
<a href="#" className="link text-body-2 mb_18">
|
||||
+ Add apartment, suit, etc.
|
||||
</a>
|
||||
<div className="grid-2 mb_24">
|
||||
<fieldset>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="City"
|
||||
id="city"
|
||||
className="rounded-cycle"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
<div className="tf-select-tranform-lable">
|
||||
<span className="select-label">State</span>
|
||||
<select name="region-choose" id="state">
|
||||
<option />
|
||||
<option value="California">California</option>
|
||||
<option value="Alabama">Alabam</option>
|
||||
<option value="Alaska">Alaska</option>
|
||||
<option value="Arizona">Arizona</option>
|
||||
<option value="Arkansas">Arkansas</option>
|
||||
<option value="Florida">Florida</option>
|
||||
<option value="Georgia">Georgia</option>
|
||||
<option value="Hawaii">Hawaii</option>
|
||||
<option value="Washington">Washington</option>
|
||||
<option value="Texas">Texas</option>
|
||||
<option value="Iowa">Iowa</option>
|
||||
<option value="Nevada">Nevada</option>
|
||||
<option value="Illinois">Illinois</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid-2">
|
||||
<fieldset>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Zip Code"
|
||||
id="zip-code"
|
||||
className="rounded-cycle"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="Phone (optional)"
|
||||
id="phone"
|
||||
className="rounded-cycle"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb_33">
|
||||
<h6 className="title mb_16">Payment options</h6>
|
||||
<div className="notification d-flex gap_16">
|
||||
<div className="icon">
|
||||
<i className="icon-info-circle-solid" />
|
||||
</div>
|
||||
<p className="text-body-2 text_mono-gray-6">
|
||||
There are no payment methods available. This may be an
|
||||
error on our side. Please contact us if you need any
|
||||
<br />
|
||||
help placing your order.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bot">
|
||||
<div className="align-items-center d-flex gap_8 mb_29">
|
||||
<fieldset className="fieldset-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="brand"
|
||||
className="tf-check"
|
||||
id="add"
|
||||
defaultChecked=""
|
||||
/>
|
||||
</fieldset>
|
||||
<label className="h6" htmlFor="add">
|
||||
Add a note to your order
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-body-2 mb_41">
|
||||
By proceeding with your purchase you agree to our Terms and
|
||||
Conditions and Privacy Policy
|
||||
</p>
|
||||
<div className="wrap-btn d-flex align-items-center justify-content-between">
|
||||
<Link
|
||||
href={`/cart`}
|
||||
className="link d-flex align-items-center gap_20 text-body-1"
|
||||
>
|
||||
<i className="icon-long-arrow-alt-left-solid" />
|
||||
Back to cart
|
||||
</Link>
|
||||
<a href="#" className="tf-btn btn-primary2 height-2">
|
||||
<span>Place Order</span>
|
||||
<span className="bg-effect" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<div className="cart-totals v2">
|
||||
<h2 className="title">Cart totals</h2>
|
||||
<div className="wrap-info">
|
||||
{cartProducts.map((product, i) => (
|
||||
<div key={i} className="info sub-heading d-flex">
|
||||
<div className="name">{product.title}</div>
|
||||
<span>x{product.quantity}</span>{" "}
|
||||
<div className="price sub-heading">
|
||||
${(product.price * product.quantity).toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<DropdownSelect
|
||||
defaultOption={"Add a coupon"}
|
||||
options={["coupon 10%", "coupon 20%"]}
|
||||
addtionalParentClass="h6 text_black px-0"
|
||||
/>
|
||||
<div className="sub-total d-flex justify-content-between align-items-center h6">
|
||||
<span>Discount</span>
|
||||
<span>-{totalPrice ? "$1.796" : 0}</span>
|
||||
</div>
|
||||
<div className="total h4 d-flex justify-content-between align-items-center">
|
||||
<span>Total</span>
|
||||
<span>${totalPrice ? (totalPrice - 1.796).toFixed(2) : 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
components/shop/CommentBox.jsx
Normal file
65
components/shop/CommentBox.jsx
Normal file
@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
|
||||
export default function CommentBox() {
|
||||
return (
|
||||
<form className="form-leave-comment" onSubmit={(e) => e.preventDefault()}>
|
||||
<div className="wrap">
|
||||
<fieldset className="">
|
||||
<textarea
|
||||
className=""
|
||||
rows={4}
|
||||
placeholder="Comment*"
|
||||
tabIndex={2}
|
||||
aria-required="true"
|
||||
required
|
||||
defaultValue={""}
|
||||
/>
|
||||
</fieldset>
|
||||
<div className="tf-grid-layout md-col-3">
|
||||
<fieldset className="">
|
||||
<input
|
||||
className=""
|
||||
type="text"
|
||||
placeholder="Name*"
|
||||
name="text"
|
||||
tabIndex={2}
|
||||
defaultValue=""
|
||||
aria-required="true"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset className="">
|
||||
<input
|
||||
className=""
|
||||
type="email"
|
||||
placeholder="Email*"
|
||||
name="email"
|
||||
tabIndex={2}
|
||||
defaultValue=""
|
||||
aria-required="true"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset className="">
|
||||
<input
|
||||
className=""
|
||||
type="text"
|
||||
placeholder="Website*"
|
||||
name="website"
|
||||
tabIndex={2}
|
||||
defaultValue=""
|
||||
aria-required="true"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div className="button-submit mt_32">
|
||||
<button className="tf-btn btn-primary2" type="submit">
|
||||
<span> Post Comment</span>
|
||||
<span className="bg-effect" />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
137
components/shop/FilteringOptions.jsx
Normal file
137
components/shop/FilteringOptions.jsx
Normal file
@ -0,0 +1,137 @@
|
||||
"use client";
|
||||
import Slider from "rc-slider";
|
||||
const brands = ["apple", "samsung", "xiaomi", "nokia", "sony", "oppo"];
|
||||
|
||||
export default function FilteringOptions({ allProps }) {
|
||||
return (
|
||||
<>
|
||||
{" "}
|
||||
<form className="form-search mb_30" onSubmit={(e) => e.preventDefault()}>
|
||||
<fieldset className="text">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
className="rounded-cycle"
|
||||
name="search"
|
||||
tabIndex={0}
|
||||
defaultValue=""
|
||||
aria-required="true"
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
<button className="" type="submit">
|
||||
<svg
|
||||
width={20}
|
||||
height={20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.875 1.875C8.43018 1.875 5.625 4.68018 5.625 8.125C5.625 9.62158 6.1499 10.9937 7.03125 12.0703L2.05078 17.0508L2.94922 17.9492L7.92969 12.9688C9.00635 13.8501 10.3784 14.375 11.875 14.375C15.3198 14.375 18.125 11.5698 18.125 8.125C18.125 4.68018 15.3198 1.875 11.875 1.875ZM11.875 3.125C14.6436 3.125 16.875 5.35645 16.875 8.125C16.875 10.8936 14.6436 13.125 11.875 13.125C9.10645 13.125 6.875 10.8936 6.875 8.125C6.875 5.35645 9.10645 3.125 11.875 3.125Z"
|
||||
fill="#5F697C"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
<div className="categories">
|
||||
<h6 className="title mb_26">Categories</h6>
|
||||
<ul className="list d-grid gap_9">
|
||||
<li className="active">
|
||||
<a href="#" className="link text-body-1">
|
||||
Phone, Tablet
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="link text-body-1">
|
||||
Sound
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="link text-body-1">
|
||||
Clock, Camera
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="link text-body-1">
|
||||
Household appliances
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="link text-body-1">
|
||||
Accessory
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="filter mb_42">
|
||||
<div className="heading d-flex align-items-center justify-content-between mb_50">
|
||||
<h6 className="">Fillters</h6>
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
allProps.clearFilter();
|
||||
}}
|
||||
className="btn-reset text_mono-gray-7 d-flex align-items-center fw-5 gap_4"
|
||||
>
|
||||
<i className="icon-sync-alt-solid" />
|
||||
Reset all
|
||||
</a>
|
||||
</div>
|
||||
<div className="brand wg-filter">
|
||||
<div className="box-fieldset-item">
|
||||
{brands.map((brand, index) => (
|
||||
<fieldset
|
||||
onClick={() => allProps.setBrands(brand)}
|
||||
className="fieldset-item"
|
||||
key={brand}
|
||||
>
|
||||
<input
|
||||
checked={allProps.brands.includes(brand)}
|
||||
type="checkbox"
|
||||
name="brand"
|
||||
readOnly
|
||||
className="tf-check"
|
||||
/>
|
||||
<label className="text_mono-gray text-body-1">
|
||||
{brand.charAt(0).toUpperCase() + brand.slice(1)}
|
||||
</label>
|
||||
</fieldset>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="wrap-range">
|
||||
<div id="range-price-1" className="range-price">
|
||||
<h6 className="title text_mono-gray mb_55">Price Range</h6>
|
||||
<div className="box-price-product mb_33">
|
||||
<div className="box-price-item min">
|
||||
<div className="price-val" id="price-min-value" data-currency="$">
|
||||
{allProps.price[0]}
|
||||
</div>
|
||||
</div>
|
||||
<div className="box-price-item max">
|
||||
<div className="price-val" id="price-max-value" data-currency="$">
|
||||
{allProps.price[1]}
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" className="btn-go">
|
||||
{" "}
|
||||
Go{" "}
|
||||
</a>
|
||||
</div>
|
||||
<div className="price-val-range" id="price-value-range">
|
||||
<Slider
|
||||
value={allProps.price}
|
||||
onChange={(price) => allProps.setPrice(price)}
|
||||
range
|
||||
min={0}
|
||||
max={700}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
115
components/shop/RelatedProducts.jsx
Normal file
115
components/shop/RelatedProducts.jsx
Normal file
@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
import { products } from "@/data/products";
|
||||
import React from "react";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Pagination } from "swiper/modules";
|
||||
import { useContextElement } from "@/context/Context";
|
||||
export default function RelatedProducts() {
|
||||
const { addProductToCart, isAddedToCartProducts } = useContextElement();
|
||||
|
||||
return (
|
||||
<div className="section-related tf-spacing-38">
|
||||
<div className="tf-container">
|
||||
<div className="heading-section mb_32 d-flex justify-content-between mb_104">
|
||||
<h3 className="">Related products</h3>
|
||||
<Link href={`/shop`} className="tf-btn btn-primary2">
|
||||
<span>See All</span>
|
||||
<span className="bg-effect" />
|
||||
</Link>
|
||||
</div>
|
||||
<Swiper
|
||||
className="swiper sw-layout"
|
||||
breakpoints={{
|
||||
0: { slidesPerView: 2 },
|
||||
575: {
|
||||
slidesPerView: 2,
|
||||
},
|
||||
768: {
|
||||
slidesPerView: 2,
|
||||
spaceBetween: 20,
|
||||
},
|
||||
992: {
|
||||
slidesPerView: 4,
|
||||
spaceBetween: 24,
|
||||
},
|
||||
}}
|
||||
spaceBetween={15}
|
||||
modules={[Pagination]}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
el: ".spd13",
|
||||
}}
|
||||
>
|
||||
{products.map((product, i) => (
|
||||
<SwiperSlide key={i} className="swiper-slide">
|
||||
<div className="product-item hover-image">
|
||||
<Link
|
||||
href={`/product-details/${product.id}`}
|
||||
className="img-style"
|
||||
>
|
||||
<Image
|
||||
className="lazyload"
|
||||
alt="item"
|
||||
src={product.imgSrc}
|
||||
width={product.width}
|
||||
height={product.height}
|
||||
/>
|
||||
</Link>
|
||||
<div className="content">
|
||||
<div className="info">
|
||||
<div className="text-body-1 product-name mb_8">
|
||||
<Link
|
||||
href={`/product-details/${product.id}`}
|
||||
className="link"
|
||||
>
|
||||
{product.title}
|
||||
</Link>
|
||||
</div>
|
||||
<h6 className="price d-flex gap_10">
|
||||
{product.oldPrice ? (
|
||||
<>
|
||||
<span className="new-price">
|
||||
$
|
||||
{product.price.toFixed(
|
||||
product.price % 1 === 0 ? 0 : 2
|
||||
)}
|
||||
</span>
|
||||
<span className="old-price sub-heading">
|
||||
$
|
||||
{product.oldPrice.toFixed(
|
||||
product.oldPrice % 1 === 0 ? 0 : 2
|
||||
)}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
`$${product.price.toFixed(
|
||||
product.price % 1 === 0 ? 0 : 2
|
||||
)}`
|
||||
)}
|
||||
</h6>
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
addProductToCart(product.id);
|
||||
}}
|
||||
className={`add-cart ${
|
||||
isAddedToCartProducts(product.id) ? "cart-added" : ""
|
||||
}`}
|
||||
>
|
||||
<i className="icon-shopping-cart-solid" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
|
||||
<div className="sw-dots sw-pagination-layout d-flex justify-content-center mt_22 spd13" />
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
472
components/shop/Shop.jsx
Normal file
472
components/shop/Shop.jsx
Normal file
@ -0,0 +1,472 @@
|
||||
"use client";
|
||||
import { products } from "@/data/products";
|
||||
import React, { useEffect, useReducer } from "react";
|
||||
import Image from "next/image";
|
||||
import FilteringOptions from "./FilteringOptions";
|
||||
import Link from "next/link";
|
||||
import { initialState, reducer } from "@/reducer/FilterReducer";
|
||||
import { useContextElement } from "@/context/Context";
|
||||
export default function Shop() {
|
||||
const { addProductToCart, isAddedToCartProducts } = useContextElement();
|
||||
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const {
|
||||
price,
|
||||
|
||||
brands,
|
||||
|
||||
filtered,
|
||||
sortingOption,
|
||||
sorted,
|
||||
|
||||
currentPage,
|
||||
itemPerPage,
|
||||
} = state;
|
||||
|
||||
const allProps = {
|
||||
...state,
|
||||
setPrice: (value) => dispatch({ type: "SET_PRICE", payload: value }),
|
||||
|
||||
setBrands: (newBrand) => {
|
||||
let newBrands = [...brands];
|
||||
if (newBrands.includes(newBrand)) {
|
||||
newBrands = newBrands.filter((brand) => brand != newBrand);
|
||||
} else {
|
||||
newBrands = [...newBrands, newBrand];
|
||||
}
|
||||
console.log(newBrands);
|
||||
dispatch({ type: "SET_BRANDS", payload: newBrands });
|
||||
},
|
||||
|
||||
setSortingOption: (value) =>
|
||||
dispatch({ type: "SET_SORTING_OPTION", payload: value }),
|
||||
|
||||
setCurrentPage: (value) =>
|
||||
dispatch({ type: "SET_CURRENT_PAGE", payload: value }),
|
||||
setItemPerPage: (value) => {
|
||||
dispatch({ type: "SET_CURRENT_PAGE", payload: 1 }),
|
||||
dispatch({ type: "SET_ITEM_PER_PAGE", payload: value });
|
||||
},
|
||||
clearFilter: () => {
|
||||
dispatch({ type: "CLEAR_FILTER" });
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let filteredArrays = [];
|
||||
|
||||
if (brands.length) {
|
||||
const filteredByBrands = [...products].filter((elm) =>
|
||||
brands.every((brand) => elm.filterBrands.includes(brand))
|
||||
);
|
||||
filteredArrays = [...filteredArrays, filteredByBrands];
|
||||
}
|
||||
|
||||
const filteredByPrice = [...products].filter(
|
||||
(elm) => elm.price >= price[0] && elm.price <= price[1]
|
||||
);
|
||||
filteredArrays = [...filteredArrays, filteredByPrice];
|
||||
|
||||
const commonItems = [...products].filter((item) =>
|
||||
filteredArrays.every((array) => array.includes(item))
|
||||
);
|
||||
dispatch({ type: "SET_FILTERED", payload: commonItems });
|
||||
}, [price, brands]);
|
||||
|
||||
useEffect(() => {
|
||||
if (sortingOption === "Price Ascending") {
|
||||
dispatch({
|
||||
type: "SET_SORTED",
|
||||
payload: [...filtered].sort((a, b) => a.price - b.price),
|
||||
});
|
||||
} else if (sortingOption === "Price Descending") {
|
||||
dispatch({
|
||||
type: "SET_SORTED",
|
||||
payload: [...filtered].sort((a, b) => b.price - a.price),
|
||||
});
|
||||
} else if (sortingOption === "Title Ascending") {
|
||||
dispatch({
|
||||
type: "SET_SORTED",
|
||||
payload: [...filtered].sort((a, b) => a.title.localeCompare(b.title)),
|
||||
});
|
||||
} else if (sortingOption === "Title Descending") {
|
||||
dispatch({
|
||||
type: "SET_SORTED",
|
||||
payload: [...filtered].sort((a, b) => b.title.localeCompare(a.title)),
|
||||
});
|
||||
} else {
|
||||
dispatch({ type: "SET_SORTED", payload: filtered });
|
||||
}
|
||||
dispatch({ type: "SET_CURRENT_PAGE", payload: 1 });
|
||||
}, [filtered, sortingOption]);
|
||||
return (
|
||||
<div className="tf-spacing-41">
|
||||
<div className="tf-container">
|
||||
<div className="wrap-shop">
|
||||
<div className="row">
|
||||
<div className="col-xl-8">
|
||||
<div className="left">
|
||||
<div className="top mb_92">
|
||||
<div className="text_mono-gray-8 text-body-1 result-count">
|
||||
Showing 1–6 of 8 results
|
||||
</div>
|
||||
<div className="wrap d-flex">
|
||||
<div className="wrap-sort">
|
||||
<div className="text_mono-gray-8 text-body-1">
|
||||
Sort by
|
||||
</div>
|
||||
<div
|
||||
className="tf-dropdown-sort"
|
||||
role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<div className="btn-select">
|
||||
<span className="text-body-3 text-sort-value">
|
||||
{sortingOption}
|
||||
</span>
|
||||
<span className="icon icon-angle-down-solid" />
|
||||
</div>
|
||||
<div className="dropdown-menu">
|
||||
{[
|
||||
"Sort by (Default)",
|
||||
"Title Ascending",
|
||||
"Title Descending",
|
||||
"Price Ascending",
|
||||
"Price Descending",
|
||||
].map((elm, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`select-item ${
|
||||
sortingOption == elm ? "active" : ""
|
||||
}`}
|
||||
onClick={() => allProps.setSortingOption(elm)}
|
||||
data-sort-value="best-selling"
|
||||
>
|
||||
<span className="text-body-3 text-value-item">
|
||||
{elm}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="wrap-layout">
|
||||
<ul
|
||||
className="nav-tab-filter group-layout"
|
||||
role="tablist"
|
||||
>
|
||||
<li className="nav-tab-item" role="presentation">
|
||||
<a
|
||||
href="#gridLayout"
|
||||
className="btn-layout grid nav-link-item active"
|
||||
data-bs-toggle="tab"
|
||||
>
|
||||
<svg
|
||||
width={22}
|
||||
height={22}
|
||||
viewBox="0 0 22 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0.611328 7.94444H21.3891M21.3891 14.6667H0.611328"
|
||||
stroke="black"
|
||||
/>
|
||||
<rect
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
width={21}
|
||||
height={21}
|
||||
stroke="black"
|
||||
/>
|
||||
<path d="M7.33398 0V20.7778" stroke="black" />
|
||||
<path d="M14.666 0V20.7778" stroke="black" />
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-tab-item" role="presentation">
|
||||
<a
|
||||
href="#listLayout"
|
||||
className="nav-link-item btn-layout list"
|
||||
data-bs-toggle="tab"
|
||||
>
|
||||
<svg
|
||||
width={22}
|
||||
height={22}
|
||||
viewBox="0 0 22 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.88867 4.8889H21.3887"
|
||||
stroke="black"
|
||||
/>
|
||||
<circle
|
||||
cx="1.22222"
|
||||
cy="4.88889"
|
||||
r="0.722222"
|
||||
fill="#D9D9D9"
|
||||
stroke="black"
|
||||
/>
|
||||
<path d="M4.88867 11H21.3887" stroke="black" />
|
||||
<path
|
||||
d="M1.94444 11C1.94444 11.3989 1.62109 11.7222 1.22222 11.7222C0.82335 11.7222 0.5 11.3989 0.5 11C0.5 10.6011 0.82335 10.2778 1.22222 10.2778C1.62109 10.2778 1.94444 10.6011 1.94444 11Z"
|
||||
fill="#D9D9D9"
|
||||
stroke="black"
|
||||
/>
|
||||
<path
|
||||
d="M4.88867 17.1111H21.3887"
|
||||
stroke="black"
|
||||
/>
|
||||
<path
|
||||
d="M1.94444 17.1111C1.94444 17.51 1.62109 17.8333 1.22222 17.8333C0.82335 17.8333 0.5 17.51 0.5 17.1111C0.5 16.7122 0.82335 16.3889 1.22222 16.3889C1.62109 16.3889 1.94444 16.7122 1.94444 17.1111Z"
|
||||
fill="#D9D9D9"
|
||||
stroke="black"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flat-animate-tab">
|
||||
<div className="tab-content">
|
||||
<div
|
||||
className="tab-pane active show"
|
||||
id="gridLayout"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div className="tf-grid-layout-2 md-col-3">
|
||||
{sorted.map((product) => (
|
||||
<div
|
||||
className="product-item hover-image"
|
||||
key={product.id}
|
||||
>
|
||||
<Link
|
||||
href={`/product-details/${product.id}`}
|
||||
className="img-style"
|
||||
>
|
||||
<Image
|
||||
className="lazyload"
|
||||
data-src={product.imgSrc}
|
||||
alt="item"
|
||||
src={product.imgSrc}
|
||||
width={product.width}
|
||||
height={product.height}
|
||||
/>
|
||||
</Link>
|
||||
<div className="content">
|
||||
<div className="info">
|
||||
<div className="text-body-1 product-name mb_8">
|
||||
<Link
|
||||
href={`/product-details/${product.id}`}
|
||||
className="link"
|
||||
>
|
||||
{product.title}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="h6 price d-flex gap_10">
|
||||
{product.oldPrice ? (
|
||||
<>
|
||||
<span className="new-price">
|
||||
$
|
||||
{product.price.toFixed(
|
||||
product.price % 1 === 0 ? 0 : 2
|
||||
)}
|
||||
</span>
|
||||
<span className="old-price sub-heading">
|
||||
$
|
||||
{product.oldPrice.toFixed(
|
||||
product.oldPrice % 1 === 0 ? 0 : 2
|
||||
)}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
`$${product.price.toFixed(
|
||||
product.price % 1 === 0 ? 0 : 2
|
||||
)}`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
addProductToCart(product.id);
|
||||
}}
|
||||
className={`add-cart ${
|
||||
isAddedToCartProducts(product.id)
|
||||
? "cart-added"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<i className="icon-shopping-cart-solid" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<ul className="wg-pagination d-flex justify-content-center gap_12 mt_58">
|
||||
<li>
|
||||
<a href="#">
|
||||
<i className="icon-angle-left-solid" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="active">
|
||||
1
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">...</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">4</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<i className="icon-angle-right-solid" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="tab-pane" id="listLayout" role="tabpanel">
|
||||
<div className="wrap-list d-flex gap-16 flex-direction-column">
|
||||
{sorted.map((product, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="product-item hover-image style-list"
|
||||
>
|
||||
<Link
|
||||
href={`/product-details/${product.id}`}
|
||||
className="img-style"
|
||||
>
|
||||
<Image
|
||||
className="lazyload"
|
||||
alt="item"
|
||||
src={product.imgSrc}
|
||||
width={product.width}
|
||||
height={product.height}
|
||||
/>
|
||||
</Link>
|
||||
<div className="content">
|
||||
<div className="info">
|
||||
<div className="text-body-1 product-name mb_8">
|
||||
<Link
|
||||
href={`/product-details/${product.id}`}
|
||||
className="link"
|
||||
>
|
||||
{product.title}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="h6 price d-flex gap_10">
|
||||
{product.oldPrice ? (
|
||||
<>
|
||||
<span className="new-price">
|
||||
$
|
||||
{product.price.toFixed(
|
||||
product.price % 1 === 0 ? 0 : 2
|
||||
)}
|
||||
</span>
|
||||
<span className="old-price sub-heading">
|
||||
$
|
||||
{product.oldPrice.toFixed(
|
||||
product.oldPrice % 1 === 0 ? 0 : 2
|
||||
)}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
`$${product.price.toFixed(
|
||||
product.price % 1 === 0 ? 0 : 2
|
||||
)}`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="list-product-btn">
|
||||
<a
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
addProductToCart(product.id);
|
||||
}}
|
||||
className="add-cart"
|
||||
href="#"
|
||||
>
|
||||
{isAddedToCartProducts(product.id)
|
||||
? "Already Added"
|
||||
: "Add to Cart"}
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="box-icon wishlist btn-icon-action"
|
||||
>
|
||||
<span className="icon icon-heart" />
|
||||
<span className="tooltip">Wishlist</span>
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="box-icon quickview tf-btn-loading"
|
||||
>
|
||||
<span className="icon icon-eye" />
|
||||
<span className="tooltip">Quick View</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<ul className="wg-pagination d-flex justify-content-center gap_12 mt_58">
|
||||
<li>
|
||||
<a href="#">
|
||||
<i className="icon-angle-left-solid" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="active">
|
||||
1
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">...</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">4</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<i className="icon-angle-right-solid" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-xl-4">
|
||||
<div className="right">
|
||||
<div className="sildebar-fiiler canvas-filter">
|
||||
<div className="canvas-wrapper">
|
||||
<div className="canvas-header d-flex d-xl-none">
|
||||
<h5 className="">
|
||||
<span className="icon icon-filter" />
|
||||
Filters
|
||||
</h5>
|
||||
<div className="close-filter">
|
||||
<span className="icon-times-solid" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="canvas-body">
|
||||
<FilteringOptions allProps={allProps} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
173
components/shop/ShopCart.jsx
Normal file
173
components/shop/ShopCart.jsx
Normal file
@ -0,0 +1,173 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useContextElement } from "@/context/Context";
|
||||
import DropdownSelect from "../common/DropdownSelect";
|
||||
export default function ShopCart() {
|
||||
const {
|
||||
cartProducts,
|
||||
setCartProducts,
|
||||
totalPrice,
|
||||
|
||||
updateQuantity,
|
||||
} = useContextElement();
|
||||
|
||||
const removeItem = (id) => {
|
||||
setCartProducts((pre) => [...pre.filter((elm) => elm.id != id)]);
|
||||
};
|
||||
return (
|
||||
<div className="cart-product tf-spacing-39">
|
||||
<div className="tf-container">
|
||||
<div className="row">
|
||||
<div className="col-md-8">
|
||||
<div className="left">
|
||||
<h2 className="title">Products</h2>
|
||||
<div className="tf-product-cart-wrap tf-cart-item">
|
||||
{cartProducts.length ? (
|
||||
<>
|
||||
{cartProducts.map((product, i) => (
|
||||
<div key={i} className="tf-cart-item_product file-delete">
|
||||
<div className="product-item style-cart hover-image">
|
||||
<fieldset className="fieldset-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="brand"
|
||||
className="tf-check"
|
||||
/>
|
||||
</fieldset>
|
||||
<Link
|
||||
href={`/product-details/${product.id}`}
|
||||
className="img-style"
|
||||
>
|
||||
<Image
|
||||
className="lazyload"
|
||||
alt="item"
|
||||
src={product.imgSrc}
|
||||
width={263}
|
||||
height={220}
|
||||
/>
|
||||
</Link>
|
||||
<div className="content">
|
||||
<div className="info">
|
||||
<div className="h5 product-name line-clamp-2 mb_16">
|
||||
<Link
|
||||
href={`/product-details/${product.id}`}
|
||||
className="link"
|
||||
>
|
||||
{product.title}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="price d-flex gap_10 mb_33">
|
||||
<span className="new-price h5">
|
||||
${product.price.toFixed(2)}
|
||||
</span>
|
||||
{product.oldPrice && (
|
||||
<span className="old-price h6">
|
||||
${product.oldPrice.toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="tf-product-info-quantity">
|
||||
<div className="wg-quantity">
|
||||
<span
|
||||
className="btn-quantity btn-decrease"
|
||||
onClick={() =>
|
||||
updateQuantity(
|
||||
product.id,
|
||||
product.quantity - 1
|
||||
)
|
||||
}
|
||||
>
|
||||
-
|
||||
</span>
|
||||
<input
|
||||
className="quantity-product"
|
||||
type="text"
|
||||
name="number"
|
||||
readOnly
|
||||
value={product.quantity}
|
||||
/>
|
||||
<span
|
||||
className="btn-quantity btn-increase"
|
||||
onClick={() =>
|
||||
updateQuantity(
|
||||
product.id,
|
||||
product.quantity + 1
|
||||
)
|
||||
}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => removeItem(product.id)}
|
||||
className="remove-cart"
|
||||
>
|
||||
<span className="remove icon-times-solid" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div className="">
|
||||
<div className="col-4">
|
||||
Your Cart is empty. Start adding favorite products to
|
||||
cart!{" "}
|
||||
</div>
|
||||
<Link
|
||||
className="tf-btn mt-4 mb-3 text-white"
|
||||
style={{ width: "fit-content" }}
|
||||
href="/shop"
|
||||
>
|
||||
Explore Products
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<div className="cart-totals">
|
||||
<h2 className="title">Cart totals</h2>
|
||||
{cartProducts.map((product, i) => (
|
||||
<div key={i} className="info sub-heading d-flex">
|
||||
<div className="name">{product.title}</div>
|
||||
<span>x{product.quantity}</span>{" "}
|
||||
<div className="price sub-heading">
|
||||
${(product.price * product.quantity).toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<DropdownSelect
|
||||
defaultOption={"Add a coupon"}
|
||||
options={["coupon 10%", "coupon 20%"]}
|
||||
addtionalParentClass="h6 text_black px-0"
|
||||
/>
|
||||
<div className="sub-total d-flex justify-content-between align-items-center h6">
|
||||
<span>Subtotal</span>
|
||||
<span>${totalPrice.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="total h4 d-flex justify-content-between align-items-center">
|
||||
<span>Total</span>
|
||||
<span>${totalPrice.toFixed(2)}</span>
|
||||
</div>
|
||||
<Link
|
||||
href={`/checkout`}
|
||||
className="tf-btn w-full btn-primary2 height-3"
|
||||
>
|
||||
<span className="text-body-1">Check Out</span>
|
||||
<span className="bg-effect" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
299
components/shop/ShopDescription.jsx
Normal file
299
components/shop/ShopDescription.jsx
Normal file
@ -0,0 +1,299 @@
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
import CommentBox from "./CommentBox";
|
||||
import { useState } from "react";
|
||||
export default function ShopDescription() {
|
||||
const [activeTab, setActiveTab] = useState(1);
|
||||
return (
|
||||
<div className="tf-container">
|
||||
<div className="widget-tabs style-1">
|
||||
<ul className="widget-menu-tab overflow-x-auto">
|
||||
<li
|
||||
className={`item-title h5 ${activeTab == 1 ? "active" : ""}`}
|
||||
onClick={() => setActiveTab(1)}
|
||||
>
|
||||
<span className="text-whitespace">Description</span>
|
||||
</li>
|
||||
<li
|
||||
className={`item-title h5 ${activeTab == 2 ? "active" : ""}`}
|
||||
onClick={() => setActiveTab(2)}
|
||||
>
|
||||
<span className="text-whitespace">Reviews (3)</span>
|
||||
</li>
|
||||
<li
|
||||
className={`item-title h5 ${activeTab == 3 ? "active" : ""}`}
|
||||
onClick={() => setActiveTab(3)}
|
||||
>
|
||||
<span className="text-whitespace">Additinal Information</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="widget-content-tab">
|
||||
<div
|
||||
className={`widget-content-inner ${
|
||||
activeTab == 1 ? "active" : ""
|
||||
} `}
|
||||
>
|
||||
<div className="tab-description d-grid gap_29">
|
||||
<p className="text-body-1 text_mono-gray-8">
|
||||
The 24-inch iMac M3 2023 has an impressively slim design.
|
||||
Despite being slim, the iMac is also equipped with a diverse
|
||||
connection port system such as Thunderbolt. In terms of color,
|
||||
the 24-inch iMac M3 2023 impresses with seven different color
|
||||
versions for users to choose from: Blue, Green, Pink, Silver,
|
||||
Yellow, Orange and Purple.
|
||||
</p>
|
||||
<p className="text-body-1 text_mono-gray-8">
|
||||
The iMac M3 2023 is equipped with a 24-inch screen with thin
|
||||
screen borders for a wide display space. The screen is equipped
|
||||
with an impressive high resolution of upto 4.5K and a brightness
|
||||
of 500 nits for super sharp display. The device screen is also
|
||||
equipped with a wide color gamut P3, so the details displayed on
|
||||
the screen are extremely vivid
|
||||
</p>
|
||||
<p className="text-body-1 text_mono-gray-8">
|
||||
The 24-inch iMac M3 2023 is equipped with the best camera system
|
||||
compared to previous generations of iMac introduced by Apple.
|
||||
Along with that is an integrated image processor, a processor
|
||||
with the ability to analyze and improve pixels at 1 trillion
|
||||
calculations per second. Thanks to that, the 24-inch iMac M3
|
||||
2023 brings impressive video recording capabilities. In addition
|
||||
to the sharp camera, the 24-inch iMac M3 2023 is also equipped
|
||||
with a microphone that supports effective sound recording.
|
||||
Thanks to that, users can record podcasts or livestreams without
|
||||
the need for supporting equipment. In particular, the microphone
|
||||
on the device is also designed with echo reduction and
|
||||
beamforming technology to effectively reduce ambient noise
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`widget-content-inner ${
|
||||
activeTab == 2 ? "active" : ""
|
||||
} `}
|
||||
>
|
||||
<div className="tab-reviews write-cancel-review-wrap">
|
||||
<div className="tab-reviews-heading">
|
||||
<div className="top">
|
||||
<div className="text-center">
|
||||
<div className="number text-display">4.9</div>
|
||||
<div className="ratings justify-content-center">
|
||||
<i className="icon icon-star-solid" />
|
||||
<i className="icon icon-star-solid" />
|
||||
<i className="icon icon-star-solid" />
|
||||
<i className="icon icon-star-solid" />
|
||||
<i className="icon icon-star-solid" />
|
||||
</div>
|
||||
<p className="text_mono-gray-8">(168 Ratings)</p>
|
||||
</div>
|
||||
<div className="rating-score">
|
||||
<div className="item">
|
||||
<div className="number-1 text-body-2 text_mono-gray">
|
||||
5
|
||||
</div>
|
||||
<i className="icon icon-star-solid" />
|
||||
<div className="line-bg">
|
||||
<div style={{ width: "94.67%" }} />
|
||||
</div>
|
||||
<div className="number-2 text-body-2 text_mono-gray">
|
||||
59
|
||||
</div>
|
||||
</div>
|
||||
<div className="item">
|
||||
<div className="number-1 text-body-2 text_mono-gray">
|
||||
4
|
||||
</div>
|
||||
<i className="icon icon-star-solid" />
|
||||
<div className="line-bg">
|
||||
<div style={{ width: "60%" }} />
|
||||
</div>
|
||||
<div className="number-2 text-body-2 text_mono-gray">
|
||||
46
|
||||
</div>
|
||||
</div>
|
||||
<div className="item">
|
||||
<div className="number-1 text-body-2 text_mono-gray">
|
||||
3
|
||||
</div>
|
||||
<i className="icon icon-star-solid" />
|
||||
<div className="line-bg">
|
||||
<div style={{ width: "0%" }} />
|
||||
</div>
|
||||
<div className="number-2 text-body-2 text_mono-gray">
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
<div className="item">
|
||||
<div className="number-1 text-body-2 text_mono-gray">
|
||||
2
|
||||
</div>
|
||||
<i className="icon icon-star-solid" />
|
||||
<div className="line-bg">
|
||||
<div style={{ width: "0%" }} />
|
||||
</div>
|
||||
<div className="number-2 text-body-2 text_mono-gray">
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
<div className="item">
|
||||
<div className="number-1 text-body-2 text_mono-gray">
|
||||
1
|
||||
</div>
|
||||
<i className="icon icon-star-solid" />
|
||||
<div className="line-bg">
|
||||
<div style={{ width: "0%" }} />
|
||||
</div>
|
||||
<div className="number-2 text-body-2 text_mono-gray">
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="reply-comment style-2">
|
||||
<div className="reply-comment-heading mb_82">
|
||||
<h2>3 Comments</h2>
|
||||
</div>
|
||||
<div className="wrap-comment">
|
||||
<div className="reply-comment-wrap">
|
||||
<div className="reply-comment-item">
|
||||
<div className="heading mb_15">
|
||||
<div className="box-user">
|
||||
<div className="avatar">
|
||||
<Image
|
||||
alt="avatar"
|
||||
src="/images/avatar/avatar-2.jpg"
|
||||
width={119}
|
||||
height={119}
|
||||
/>
|
||||
</div>
|
||||
<div className="content">
|
||||
<h5>Kobbie Dao</h5>
|
||||
<p className="text-body-1 text_mono-gray-5">
|
||||
September 30, 2023 / 9:30 AM
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" className="tf-btn btn-primary2">
|
||||
<span>Reply</span>
|
||||
<span className="bg-effect" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="comment text-body-1 text_black">
|
||||
As a web designer myself, I couldn't agree more with the
|
||||
sentiment that great design should be felt and
|
||||
remembered long after the screen is turned off.
|
||||
</div>
|
||||
</div>
|
||||
<div className="reply-comment-item type-reply">
|
||||
<div className="heading mb_35">
|
||||
<div className="box-user">
|
||||
<div className="avatar">
|
||||
<Image
|
||||
alt="avatar"
|
||||
src="/images/avatar/avatar-3.jpg"
|
||||
width={119}
|
||||
height={119}
|
||||
/>
|
||||
</div>
|
||||
<div className="content">
|
||||
<h5 className="text_black">Zalatan</h5>
|
||||
<p className="text-body-1 text_mono-gray-5">
|
||||
September 30, 2023 / 9:30 AM
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" className="tf-btn btn-primary2">
|
||||
<span>Reply</span>
|
||||
<span className="bg-effect" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="comment text-body-1 text_black">
|
||||
Aute mi ut suspendisse velit leo, vel risus ac. Amet dui
|
||||
dignissim fermentum malesuada auctor volutpat,
|
||||
vestibulum ipsum nulla.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="reply-comment-wrap">
|
||||
<div className="reply-comment-item">
|
||||
<div className="heading mb_15">
|
||||
<div className="box-user">
|
||||
<div className="avatar">
|
||||
<Image
|
||||
alt="avatar"
|
||||
src="/images/avatar/avatar-4.jpg"
|
||||
width={119}
|
||||
height={119}
|
||||
/>
|
||||
</div>
|
||||
<div className="content">
|
||||
<h5 className="text_black">Bellingham</h5>
|
||||
<p className="text-body-1 text_mono-gray-5">
|
||||
September 30, 2023 / 9:30 AM
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" className="tf-btn btn-primary2">
|
||||
<span>Reply</span>
|
||||
<span className="bg-effect" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="comment text-body-1 text_black">
|
||||
Aute mi ut suspendisse velit leo, vel risus ac. Amet dui
|
||||
dignissim fermentum malesuada auctor volutpat,
|
||||
vestibulum ipsum nulla.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="leave-comment">
|
||||
<div className="heading-title mb_56">
|
||||
<h2 className="mb_18">Leave a Reply</h2>
|
||||
<p className="text-body-1 text_mono-gray letter-spacing-1">
|
||||
Your email address will not be published. Require field
|
||||
are marked <span>*</span>
|
||||
</p>
|
||||
</div>
|
||||
<CommentBox />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`widget-content-inner ${
|
||||
activeTab == 3 ? "active" : ""
|
||||
} `}
|
||||
>
|
||||
<div className="tab-description d-grid gap_12">
|
||||
<p className="text-body-1 text_mono-gray-8">
|
||||
With the 2024 version, the Macbook Air is upgraded to a
|
||||
15.3-inch screen with Liquid Retina resolution (2880 x 1864)
|
||||
that reproduces detailed image quality, millions of pixels and a
|
||||
wide spectrum P3 color range of up to 1 billion colors. Allows
|
||||
you to perfectly perform graphic design tasks with strict color
|
||||
requirements.
|
||||
</p>
|
||||
<p className="text-body-1 text_mono-gray-8">
|
||||
500 nits brightness with True Tone Technology regulates vision
|
||||
and vision extremely well, quickly adapting to ambient light
|
||||
conditions to display the sharpest content from text to video.
|
||||
</p>
|
||||
<p className="text-body-1 text_mono-gray-8">
|
||||
MacBook Air 15 inch M3 2024 8-core CPU - Screen
|
||||
</p>
|
||||
<p className="text-body-1 text_mono-gray-8">
|
||||
Multi-dimensional surround sound system Dolby Atmos, Spatial
|
||||
Audio combined with a high-quality linear speaker system brings
|
||||
a complete and realistic entertainment experience with vivid
|
||||
sound quality, clear bass, and sound amplification. Loud allows
|
||||
you to immerse yourself in melodious music or vivid movies,
|
||||
detailed sound is also extremely beneficial when participating
|
||||
in video post-production editing and sound effects.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
371
components/shop/ShopDetails.jsx
Normal file
371
components/shop/ShopDetails.jsx
Normal file
@ -0,0 +1,371 @@
|
||||
"use client";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import Drift from "drift-zoom";
|
||||
import PhotoSwipeLightbox from "photoswipe/lightbox";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { FreeMode, Thumbs } from "swiper/modules";
|
||||
import { useContextElement } from "@/context/Context";
|
||||
const colorOptions = [
|
||||
{
|
||||
id: "blue-sapphire",
|
||||
label: "Blue Sapphire",
|
||||
price: null,
|
||||
defaultChecked: false,
|
||||
},
|
||||
{
|
||||
id: "seafoam-green",
|
||||
label: "Seafoam Green",
|
||||
price: "79.99",
|
||||
defaultChecked: true,
|
||||
},
|
||||
{
|
||||
id: "dusty-rose",
|
||||
label: "Dusty Rose",
|
||||
price: "89.99",
|
||||
defaultChecked: false,
|
||||
},
|
||||
{
|
||||
id: "light-gray",
|
||||
label: "Light Gray",
|
||||
price: "59.99",
|
||||
defaultChecked: false,
|
||||
},
|
||||
];
|
||||
export default function ShopDetails({ product }) {
|
||||
const { addProductToCart, isAddedToCartProducts } = useContextElement();
|
||||
const [value, setValue] = useState(1);
|
||||
const slides = [
|
||||
{
|
||||
color: "blue-sapphire",
|
||||
image: product.imgSrc,
|
||||
},
|
||||
{
|
||||
color: "dusty-rose",
|
||||
image: "/images/shop/thumbs-main-2.jpg",
|
||||
},
|
||||
{
|
||||
color: "light-gray",
|
||||
image: "/images/shop/thumbs-main-3.jpg",
|
||||
},
|
||||
{
|
||||
color: "seafoam-green",
|
||||
image: "/images/shop/thumbs-main-4.jpg",
|
||||
},
|
||||
];
|
||||
const [activeColor, setActiveColor] = useState(
|
||||
colorOptions.find((opt) => opt.defaultChecked)?.id || ""
|
||||
);
|
||||
const [swiperThumb, setSwiperThumb] = useState(null);
|
||||
const lightboxRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize PhotoSwipeLightbox
|
||||
const lightbox = new PhotoSwipeLightbox({
|
||||
gallery: "#gallery-swiper-started",
|
||||
children: ".item",
|
||||
pswpModule: () => import("photoswipe"),
|
||||
});
|
||||
|
||||
lightbox.init();
|
||||
|
||||
// Store the lightbox instance in the ref for later use
|
||||
lightboxRef.current = lightbox;
|
||||
|
||||
// Cleanup: destroy the lightbox when the component unmounts
|
||||
return () => {
|
||||
lightbox.destroy();
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
// Function to initialize Drift
|
||||
// Function to check window width
|
||||
const checkWindowSize = () => window.innerWidth >= 1200;
|
||||
|
||||
// Only proceed if window is wide enough
|
||||
if (!checkWindowSize()) return;
|
||||
|
||||
const imageZoom = () => {
|
||||
const driftAll = document.querySelectorAll(".tf-image-zoom");
|
||||
const pane = document.querySelector(".tf-zoom-main");
|
||||
|
||||
driftAll.forEach((el) => {
|
||||
new Drift(el, {
|
||||
zoomFactor: 2,
|
||||
paneContainer: pane,
|
||||
inlinePane: false,
|
||||
handleTouch: false,
|
||||
hoverBoundingBox: true,
|
||||
containInline: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
imageZoom();
|
||||
const zoomElements = document.querySelectorAll(".tf-image-zoom");
|
||||
|
||||
const handleMouseOver = (event) => {
|
||||
const parent = event.target.closest(".section-image-zoom");
|
||||
if (parent) {
|
||||
parent.classList.add("zoom-active");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = (event) => {
|
||||
const parent = event.target.closest(".section-image-zoom");
|
||||
if (parent) {
|
||||
parent.classList.remove("zoom-active");
|
||||
}
|
||||
};
|
||||
|
||||
zoomElements.forEach((element) => {
|
||||
element.addEventListener("mouseover", handleMouseOver);
|
||||
element.addEventListener("mouseleave", handleMouseLeave);
|
||||
});
|
||||
|
||||
// Cleanup event listeners on component unmount
|
||||
return () => {
|
||||
zoomElements.forEach((element) => {
|
||||
element.removeEventListener("mouseover", handleMouseOver);
|
||||
element.removeEventListener("mouseleave", handleMouseLeave);
|
||||
});
|
||||
};
|
||||
}, []); // Empty dependency array to run only once on mount
|
||||
return (
|
||||
<div className="tf-spacing-1">
|
||||
<div className="tf-container">
|
||||
<div className="shop-details">
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="left">
|
||||
<div className="tf-product-media-wrap">
|
||||
<div className="thumbs-slider">
|
||||
<Swiper
|
||||
dir="ltr"
|
||||
className="swiper tf-product-media-main mb_20"
|
||||
id="gallery-swiper-started"
|
||||
modules={[Thumbs]}
|
||||
thumbs={{ swiper: swiperThumb }}
|
||||
>
|
||||
{slides.map((slide, index) => (
|
||||
<SwiperSlide
|
||||
key={index}
|
||||
className="swiper-slide"
|
||||
data-color={slide.color}
|
||||
>
|
||||
<a
|
||||
href={slide.image}
|
||||
target="_blank"
|
||||
className="item"
|
||||
data-pswp-width="400px"
|
||||
data-pswp-height="400px"
|
||||
>
|
||||
<Image
|
||||
className="tf-image-zoom lazyload"
|
||||
data-zoom={slide.image}
|
||||
data-src={slide.image}
|
||||
alt=""
|
||||
src={slide.image}
|
||||
width={836}
|
||||
height={718}
|
||||
/>
|
||||
</a>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<Swiper
|
||||
dir="ltr"
|
||||
modules={[Thumbs, FreeMode]}
|
||||
onSwiper={setSwiperThumb}
|
||||
className="swiper tf-product-media-thumbs other-image-zoom"
|
||||
{...{
|
||||
spaceBetween: 24,
|
||||
slidesPerView: "auto",
|
||||
freeMode: true,
|
||||
watchSlidesProgress: true,
|
||||
observer: true,
|
||||
observeParents: true,
|
||||
}}
|
||||
>
|
||||
{slides.map((elm, i) => (
|
||||
<SwiperSlide
|
||||
key={i}
|
||||
className="swiper-slide stagger-item"
|
||||
>
|
||||
<div className="item">
|
||||
<Image
|
||||
className="lazyload"
|
||||
alt=""
|
||||
src={elm.image}
|
||||
width={191}
|
||||
height={160}
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="right">
|
||||
<div className="tf-product-info-wrap position-relative">
|
||||
<div className="tf-zoom-main" />
|
||||
<div className="tf-product-info-list other-image-zoom">
|
||||
<div className="tf-product-info-heading">
|
||||
<div className="tf-product-info-name mb_24">
|
||||
<h3 className="name mb_11">{product.title}</h3>
|
||||
<div className="sub">
|
||||
<div className="tf-product-info-rate">
|
||||
<div className="ratings d-flex gap_8">
|
||||
<i className="icon-star-solid" />
|
||||
<i className="icon-star-solid" />
|
||||
<i className="icon-star-solid" />
|
||||
<i className="icon-star-half-alt-solid" />
|
||||
<i className="icon-star1" />
|
||||
</div>
|
||||
<div className="text text-body-2 text_color-text-2 fw-5">
|
||||
3 Review
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tf-product-info-desc mb_12">
|
||||
<div className="tf-product-info-price">
|
||||
<h2 className="price-on-sale">
|
||||
${product.price.toFixed(2)}
|
||||
</h2>
|
||||
{product.oldPrice && (
|
||||
<h3 className="compare-at-price text_mono-gray-5">
|
||||
${product.oldPrice.toFixed(2)}
|
||||
</h3>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tf-product-info-choose-option gap-19">
|
||||
<div className="variant-picker-item mb_58">
|
||||
<div className="variant-picker-label text-body-2 text_color-text-2 mb_6">
|
||||
Select Color
|
||||
</div>
|
||||
<div className="variant-picker-values">
|
||||
{colorOptions.map((color) => {
|
||||
const inputId = `values-${color.id}`;
|
||||
const isActive = activeColor === color.id;
|
||||
|
||||
return (
|
||||
<React.Fragment key={color.id}>
|
||||
<input
|
||||
id={inputId}
|
||||
type="radio"
|
||||
name="color1"
|
||||
checked={isActive}
|
||||
onChange={() => setActiveColor(color.id)}
|
||||
/>
|
||||
<label
|
||||
className={`hover-tooltip tooltip-bot radius-60 color-btn ${
|
||||
isActive ? "active" : ""
|
||||
}`}
|
||||
htmlFor={inputId}
|
||||
data-value={color.id}
|
||||
data-color={color.id}
|
||||
{...(color.price
|
||||
? { "data-price": color.price }
|
||||
: {})}
|
||||
>
|
||||
<span
|
||||
className={`btn-checkbox bg-color-${color.id}`}
|
||||
/>
|
||||
<span className="tooltip">{color.label}</span>
|
||||
</label>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className="list mb_57">
|
||||
<li className="text-body-2">
|
||||
Condition: Machine Body. + 143W Power Supply
|
||||
</li>
|
||||
<li className="text-body-2">
|
||||
Display : 24" 4.5K (4480x2520) , 500 nits , wide color
|
||||
P3
|
||||
</li>
|
||||
<li className="text-body-2">
|
||||
CPU: Apple M3 8-Core CPU 4.0Ghz
|
||||
</li>
|
||||
<li className="text-body-2">
|
||||
GPU: 8-Core GPU ,16-Core Neural Engine
|
||||
</li>
|
||||
<li className="text-body-2">
|
||||
RAM: 8GB of onboard " Unified "
|
||||
</li>
|
||||
<li className="text-body-2">Storage: 256GB PCIe</li>
|
||||
<li className="text-body-2">
|
||||
Connect : x2 Thunderbolt 3 | USB 4 ports , Jack 3.5,
|
||||
Weight : 4,43kg
|
||||
</li>
|
||||
<li className="text-body-2">
|
||||
Accessory : Mouse,Keyboard, Power Adapter, Cable C to
|
||||
lightning.
|
||||
</li>
|
||||
</ul>
|
||||
<div className="d-flex tf-product-wrap-btn gap_12 mb_23">
|
||||
<div className="tf-product-info-quantity d-flex align-items-center">
|
||||
<div className="title text-body-2 text_mono-gray-8">
|
||||
QTY
|
||||
</div>
|
||||
<div className="wg-quantity">
|
||||
<span
|
||||
onClick={() =>
|
||||
setValue((pre) => (pre == 1 ? 1 : pre - 1))
|
||||
}
|
||||
className="btn-quantity btn-decrease"
|
||||
>
|
||||
-
|
||||
</span>
|
||||
<input
|
||||
className="quantity-product"
|
||||
type="text"
|
||||
name="number"
|
||||
readOnly
|
||||
value={value}
|
||||
/>
|
||||
<span
|
||||
onClick={() => setValue((pre) => pre + 1)}
|
||||
className="btn-quantity btn-increase"
|
||||
>
|
||||
+
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tf-product-info-by-btn d-flex align-items-center w-full">
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
addProductToCart(product.id, value);
|
||||
}}
|
||||
className="tf-btn w-full show-shopping-cart"
|
||||
>
|
||||
<span className="text-body-2 fw-4">
|
||||
{isAddedToCartProducts(product.id)
|
||||
? "Already Added"
|
||||
: "Add To Cart"}
|
||||
</span>
|
||||
<span className="bg-effect" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user