mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 12:24:18 +00:00
Merge branch 'common' of github.com:KieIO/grocery-vercel-commerce into m5-datnguyen
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -35,3 +35,5 @@ yarn-error.log*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
.eslintrc
|
||||
|
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"schema": {
|
||||
"https://buybutton.store/graphql": {
|
||||
"headers": {
|
||||
"Authorization": "Bearer xzy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"./framework/bigcommerce/api/**/*.ts": {
|
||||
"noRequire": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"generates": {
|
||||
"./framework/bigcommerce/schema.d.ts": {
|
||||
"plugins": ["typescript", "typescript-operations"]
|
||||
},
|
||||
"./framework/bigcommerce/schema.graphql": {
|
||||
"plugins": ["schema-ast"]
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"afterAllFileWrite": ["prettier --write"]
|
||||
}
|
||||
}
|
3
next-env.d.ts
vendored
3
next-env.d.ts
vendored
@@ -1,3 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
|
@@ -13,6 +13,16 @@ const isVendure = provider === 'vendure'
|
||||
|
||||
module.exports = withCommerceConfig({
|
||||
commerce,
|
||||
typescript: { // todo: remove it
|
||||
// !! WARN !!
|
||||
// Dangerously allow production builds to successfully complete even if
|
||||
// your project has type errors.
|
||||
// !! WARN !!
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
images: {
|
||||
// todo: replace domains for images
|
||||
domains: ['user-images.githubusercontent.com'],
|
||||
|
14
pages/blogs.tsx
Normal file
14
pages/blogs.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Layout } from 'src/components/common';
|
||||
import { BlogsList, FeaturedCardBlog, BlogHeading, BlogBreadCrumb } from 'src/components/modules/blogs';
|
||||
|
||||
export default function BlogsPage() {
|
||||
return(
|
||||
<>
|
||||
<BlogBreadCrumb />
|
||||
<BlogHeading />
|
||||
<FeaturedCardBlog />
|
||||
<BlogsList />
|
||||
</>
|
||||
)
|
||||
}
|
||||
BlogsPage.Layout = Layout
|
@@ -13,7 +13,7 @@ export default function Home() {
|
||||
<HomeRecipe />
|
||||
<HomeSubscribe />
|
||||
|
||||
{/* // todo: uncomment */}
|
||||
{/* // todo: uncomment
|
||||
{/* <ModalCreateUserInfo/> */}
|
||||
</>
|
||||
)
|
||||
|
12
pages/privacy-policy.tsx
Normal file
12
pages/privacy-policy.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Layout } from "src/components/common"
|
||||
import { DeliveryAndPolicyContent, DeliveryAndPolicyBreadCrumb } from "src/components/modules/delivery-policy"
|
||||
|
||||
export default function DeliveryAndPolicyPage () {
|
||||
return (
|
||||
<>
|
||||
<DeliveryAndPolicyBreadCrumb />
|
||||
<DeliveryAndPolicyContent />
|
||||
</>
|
||||
)
|
||||
}
|
||||
DeliveryAndPolicyPage.Layout = Layout
|
BIN
public/assets/images/author.png
Normal file
BIN
public/assets/images/author.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
public/assets/images/image20.png
Normal file
BIN
public/assets/images/image20.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 406 KiB |
BIN
public/assets/images/image21.png
Normal file
BIN
public/assets/images/image21.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 180 KiB |
BIN
public/assets/images/image22.png
Normal file
BIN
public/assets/images/image22.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 187 KiB |
BIN
public/assets/images/image23.png
Normal file
BIN
public/assets/images/image23.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 198 KiB |
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
interface BreadcrumbSeparatorProps {
|
||||
children?: React.ReactNode
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const BreadcrumbSeparator = ({ children }: BreadcrumbSeparatorProps) => {
|
||||
|
@@ -3,7 +3,6 @@
|
||||
.cardBlogWarpper {
|
||||
@apply inline-flex flex-col justify-start;
|
||||
max-width: 39.2rem;
|
||||
min-height: 34.4rem;
|
||||
.image {
|
||||
width: 100%;
|
||||
max-height: 22rem;
|
||||
@@ -11,6 +10,9 @@
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
img{
|
||||
border-radius: 2.4rem;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
padding: 1.6rem 0.8rem 0.4rem 0.8rem;
|
||||
|
@@ -52,7 +52,8 @@
|
||||
}
|
||||
}
|
||||
.contentContainer {
|
||||
@apply hidden pb-16;
|
||||
@apply hidden pb-16 whitespace-pre-line;
|
||||
padding-top: 1.6rem;
|
||||
}
|
||||
@keyframes ContentAnimationIn {
|
||||
0% {
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import s from './CollapseChild.module.scss'
|
||||
import { useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import CollapseContent from './CollapseContent/CollapseContent'
|
||||
|
||||
interface CollapseProps{
|
||||
title?: string,
|
||||
content: Array<string>,
|
||||
content: string,
|
||||
isToggle?: boolean,
|
||||
}
|
||||
const CollapseChild = ({title, content, isToggle=false}: CollapseProps) => {
|
||||
@@ -26,9 +25,7 @@ const CollapseChild = ({title, content, isToggle=false}: CollapseProps) => {
|
||||
<div className={s.toggle}></div>
|
||||
</div>
|
||||
<div className={s.contentContainer}>
|
||||
{
|
||||
content.map(item => <CollapseContent key={item} content={item} />)
|
||||
}
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@@ -1,3 +0,0 @@
|
||||
.content {
|
||||
margin-top: 1.6rem;
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
import s from './CollapseContent.module.scss'
|
||||
|
||||
interface CollapseContentProps{
|
||||
content: string
|
||||
}
|
||||
|
||||
const CollapseContent = ({content}: CollapseContentProps) => {
|
||||
return (
|
||||
<div className={s.content}>
|
||||
{content}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CollapseContent
|
@@ -1,15 +1,15 @@
|
||||
import CollapseChild from './CollapseChild/CollapseChild'
|
||||
|
||||
interface CollapseCommonProps{
|
||||
data: {title: string, content: Array<string>}[],
|
||||
data: {title: string, content: string}[],
|
||||
}
|
||||
|
||||
const CollapseCommon = ({data}: CollapseCommonProps) => {
|
||||
return (
|
||||
<section>
|
||||
{
|
||||
data.map(item =>
|
||||
<CollapseChild key={item.title} title={item.title} content={item.content}/>
|
||||
data.map((item,index) =>
|
||||
<CollapseChild key={`${item.title}-${index}`} title={item.title} content={item.content} />
|
||||
)
|
||||
}
|
||||
</section>
|
||||
|
@@ -48,11 +48,11 @@ const FOOTER_COLUMNS = [
|
||||
},
|
||||
{
|
||||
name: 'Privacy Policy',
|
||||
link: ROUTE.TERM_CONDITION,
|
||||
link: ROUTE.PRIVACY_POLICY,
|
||||
},
|
||||
{
|
||||
name: 'Blog',
|
||||
link: ROUTE.TERM_CONDITION,
|
||||
link: ROUTE.BLOGS,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
|
||||
@import '../../../styles/utilities';
|
||||
.tabWapper{
|
||||
@apply flex flex-col w-full;
|
||||
|
@@ -2,29 +2,28 @@ import React, {
|
||||
Children,
|
||||
PropsWithChildren,
|
||||
ReactElement,
|
||||
RefObject,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
cloneElement,
|
||||
} from 'react'
|
||||
import s from './TabCommon.module.scss'
|
||||
} from 'react'
|
||||
import s from './TabCommon.module.scss'
|
||||
|
||||
import TabItem from './components/TabItem/TabItem'
|
||||
import { TabPaneProps } from './components/TabPane/TabPane'
|
||||
import classNames from 'classnames'
|
||||
import TabItem from './components/TabItem/TabItem'
|
||||
import { TabPaneProps } from './components/TabPane/TabPane'
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface TabCommonProps {
|
||||
interface TabCommonProps {
|
||||
defaultActiveTab?: number
|
||||
children: React.ReactNode
|
||||
center?:boolean
|
||||
}
|
||||
}
|
||||
|
||||
const TabCommon = ({
|
||||
const TabCommon = ({
|
||||
defaultActiveTab = 0,
|
||||
children,
|
||||
center
|
||||
}: TabCommonProps) => {
|
||||
}: TabCommonProps) => {
|
||||
const [active, setActive] = useState(0)
|
||||
const slider = useRef<HTMLDivElement>(null)
|
||||
const headerRef = useRef<HTMLUListElement>(null)
|
||||
@@ -81,6 +80,6 @@ const TabCommon = ({
|
||||
}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TabCommon
|
||||
export default TabCommon
|
||||
|
22
src/components/common/TabCommon/TabItem/TabItem.module.scss
Normal file
22
src/components/common/TabCommon/TabItem/TabItem.module.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
@import '../../../../styles/utilities';
|
||||
|
||||
.tabItem {
|
||||
margin-right: 4.8rem;
|
||||
padding-top: 1.6rem;
|
||||
padding-bottom: 1.6rem;
|
||||
|
||||
&:hover {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.tabItemActive {
|
||||
@apply font-bold;
|
||||
margin-right: 4.8rem;
|
||||
padding-top: 1.6rem;
|
||||
padding-bottom: 1.6rem;
|
||||
|
||||
&:hover {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
}
|
19
src/components/common/TabCommon/TabItem/TabItem.tsx
Normal file
19
src/components/common/TabCommon/TabItem/TabItem.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react"
|
||||
import s from './TabItem.module.scss'
|
||||
|
||||
interface TabItemProps {
|
||||
active: boolean;
|
||||
children: string;
|
||||
onClick: (tabIndex: number, tabPane?: string) => void;
|
||||
}
|
||||
|
||||
const TabItem = ({ active = false, children, onClick } : TabItemProps) => {
|
||||
|
||||
return (
|
||||
<span onClick={onClick} className={active ? s.tabItemActive : s.tabItem} >
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabItem;
|
@@ -11,4 +11,3 @@
|
||||
@apply font-bold;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,9 @@
|
||||
import classNames from 'classnames'
|
||||
<<<<<<< HEAD
|
||||
import React, { RefObject, useRef } from 'react'
|
||||
=======
|
||||
import React from 'react'
|
||||
>>>>>>> 88f90912429447f6ae7bafa77484465965e0ee13
|
||||
import s from './TabItem.module.scss'
|
||||
|
||||
interface TabItemProps {
|
||||
@@ -21,7 +25,6 @@ const TabItem = ({
|
||||
return (
|
||||
<span
|
||||
onClick={handleClick}
|
||||
// className={active ? s.tabItemActive : s.tabItem}
|
||||
className={classNames(s.tabItem, {[s.tabItemActive]:active})}
|
||||
>
|
||||
{children}
|
||||
@@ -29,4 +32,8 @@ const TabItem = ({
|
||||
)
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
export default TabItem
|
||||
=======
|
||||
export default TabItem
|
||||
>>>>>>> 88f90912429447f6ae7bafa77484465965e0ee13
|
||||
|
@@ -36,6 +36,7 @@ export { default as CardItemCheckout} from './CardItemCheckout/CardItemCheckout'
|
||||
export { default as CardBlog} from './CardBlog/CardBlog'
|
||||
export { default as RelevantBlogPosts} from './RelevantBlogPosts/RelevantBlogPosts'
|
||||
export { default as CollapseCommon} from './CollapseCommon/CollapseCommon'
|
||||
export { default as BreadcrumbCommon } from './BreadcrumbCommon/BreadcrumbCommon'
|
||||
export { default as ImgWithLink} from './ImgWithLink/ImgWithLink'
|
||||
export { default as RecipeDetail} from './RecipeDetail/RecipeDetail'
|
||||
export { default as DrawerCommon} from './DrawerCommon/DrawerCommon'
|
||||
|
@@ -1,5 +1,27 @@
|
||||
@import '../../../../styles/utilities';
|
||||
|
||||
.accountNavigation {
|
||||
@apply spacing-horizontal;
|
||||
@apply flex;
|
||||
width: 100%;
|
||||
|
||||
.slider {
|
||||
@apply inline-block;
|
||||
width: 0.2rem;
|
||||
height: 4.8rem;
|
||||
border-radius: 3px;
|
||||
background-color: var(--primary);
|
||||
position: absolute;
|
||||
left: 11.2rem;
|
||||
transition: all .2s linear;
|
||||
}
|
||||
|
||||
.tabList {
|
||||
margin-top: 3.8rem;
|
||||
margin-right: 12.4rem;
|
||||
}
|
||||
|
||||
.tabBody {
|
||||
margin-top: -4.7rem;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@@ -1,65 +1,67 @@
|
||||
import React, { useState, useRef, RefObject, useEffect } from "react"
|
||||
import React, { useRef, useEffect, Children, ReactElement, PropsWithChildren, useState, cloneElement } from "react"
|
||||
import s from './AccountNavigation.module.scss'
|
||||
|
||||
import AccountNavigationItem from './components/AccountNavigationItem'
|
||||
import AccountNavigationItem from './components/AccountNavigationItem/AccountNavigationItem'
|
||||
import {TabPaneProps} from '../../../common/TabCommon/components/TabPane/TabPane'
|
||||
|
||||
interface AccountNavigationProps {
|
||||
|
||||
defaultActiveIndex: number;
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const AccountNavigation = ({ } : AccountNavigationProps) => {
|
||||
const active = "active", unActive = "";
|
||||
const AccountNavigation = ({ defaultActiveIndex, children } : AccountNavigationProps) => {
|
||||
const [active, setActive] = useState(defaultActiveIndex)
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const headerRef = useRef<HTMLUListElement>(null)
|
||||
|
||||
const [item1Active, setItem1Active] = useState(unActive);
|
||||
const [item2Active, setItem2Active] = useState(active);
|
||||
const [item3Active, setItem3Active] = useState(unActive);
|
||||
|
||||
const item1 = useRef<HTMLDivElement>(null);
|
||||
const item2 = useRef<HTMLDivElement>(null);
|
||||
const item3 = useRef<HTMLDivElement>(null);
|
||||
const slider = useRef<HTMLDivElement>(null);
|
||||
|
||||
function slide(ref: RefObject<HTMLDivElement>) {
|
||||
const top = ref.current.offsetTop;
|
||||
slider.current.style.top = top.toString()+"px";
|
||||
const onTabClick = (index: number) => {
|
||||
setActive(index)
|
||||
}
|
||||
|
||||
function toggleItem1():void {
|
||||
setItem1Active(active)
|
||||
function slide(index: number) {
|
||||
const active = headerRef.current?.children.item(index)?.getBoundingClientRect()
|
||||
const header = headerRef.current?.getBoundingClientRect()
|
||||
const current = sliderRef.current
|
||||
|
||||
setItem2Active(unActive)
|
||||
setItem3Active(unActive)
|
||||
slide(item1);
|
||||
if (current && active && header) {
|
||||
const top = active.top;
|
||||
current.style.top = top.toString()+"px";
|
||||
}
|
||||
function toggleItem2():void {
|
||||
setItem2Active(active)
|
||||
|
||||
setItem1Active(unActive)
|
||||
setItem3Active(unActive)
|
||||
slide(item2);
|
||||
}
|
||||
function toggleItem3():void {
|
||||
setItem3Active(active)
|
||||
|
||||
setItem1Active(unActive)
|
||||
setItem2Active(unActive)
|
||||
slide(item3);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
slide(item2);
|
||||
}, [])
|
||||
slide(active);
|
||||
}, [active])
|
||||
|
||||
return (
|
||||
<section className={s.accountNavigation}>
|
||||
<div ref={item1}>
|
||||
<AccountNavigationItem onClick={toggleItem1} active={item1Active}>Customer Information</AccountNavigationItem>
|
||||
</div>
|
||||
<div ref={item2}>
|
||||
<AccountNavigationItem onClick={toggleItem2} active={item2Active}>Your Orders</AccountNavigationItem>
|
||||
</div>
|
||||
<div ref={item3}>
|
||||
<AccountNavigationItem onClick={toggleItem3} active={item3Active}>Favourites</AccountNavigationItem>
|
||||
<ul className={s.tabList} ref={headerRef}>
|
||||
{
|
||||
Children.map(children, (tab, index) => {
|
||||
let item = tab as ReactElement<PropsWithChildren<TabPaneProps>>
|
||||
return (
|
||||
<li key={item.props.tabName}>
|
||||
<AccountNavigationItem
|
||||
active={active === index}
|
||||
onClick={onTabClick}
|
||||
tabIndex={index}
|
||||
>
|
||||
{item.props.tabName}
|
||||
</AccountNavigationItem>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
<div ref={sliderRef} className={s.slider}></div>
|
||||
</ul>
|
||||
|
||||
<div className={s.tabBody}>
|
||||
{
|
||||
Children.map(children, (tab, index) => {
|
||||
let item = tab as ReactElement<PropsWithChildren<TabPaneProps>>
|
||||
return cloneElement(item, { active: index === active });
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<div ref={slider} className={s.slider}></div>
|
||||
</section>
|
||||
|
@@ -1,7 +1,6 @@
|
||||
@import '../../../../../styles/utilities';
|
||||
@import '../../../../../../styles/utilities';
|
||||
|
||||
.accountNavigationItem {
|
||||
@apply bg-gray;
|
||||
width: 28rem;
|
||||
padding: 1.2rem 0 1.2rem 1.6rem;
|
||||
margin-bottom: 1.2rem;
|
||||
@@ -14,6 +13,5 @@
|
||||
&.active {
|
||||
background-color: #FBFBFB;
|
||||
border-radius: 0 1.6rem 1.6rem 0;
|
||||
border-left: 2px solid var(--primary);
|
||||
}
|
||||
}
|
@@ -4,15 +4,19 @@ import s from './AccountNavigationItem.module.scss'
|
||||
|
||||
interface AccountNavigationItemProps {
|
||||
children?: string;
|
||||
active?: string;
|
||||
target?: string;
|
||||
onClick: () => void;
|
||||
active?: boolean;
|
||||
tabIndex: number
|
||||
onClick: (index: number) => void;
|
||||
}
|
||||
|
||||
const AccountNavigationItem = ({ children, active="", onClick } : AccountNavigationItemProps) => {
|
||||
const AccountNavigationItem = ({ children, active, tabIndex, onClick } : AccountNavigationItemProps) => {
|
||||
|
||||
const handleClick = () => {
|
||||
onClick(tabIndex)
|
||||
}
|
||||
return (
|
||||
<div onClick={onClick} className={classNames(s.accountNavigationItem, {
|
||||
[s[active]]:active
|
||||
<div onClick={handleClick} className={classNames(s.accountNavigationItem, {
|
||||
[s.active]:active
|
||||
})}>
|
||||
{children}
|
||||
</div>
|
||||
|
@@ -0,0 +1,26 @@
|
||||
@import '../../../../styles/utilities';
|
||||
|
||||
.accountPage {
|
||||
@apply spacing-horizontal;
|
||||
background-color: #F5F4F2;
|
||||
margin-top: -3.2rem;
|
||||
padding-top: 3.2rem;
|
||||
padding-bottom: 3.2rem;
|
||||
|
||||
@screen md {
|
||||
padding-left: 3.2rem;
|
||||
padding-right: 3.2rem;
|
||||
}
|
||||
|
||||
@screen xl {
|
||||
@apply spacing-horizontal
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 1.2rem;
|
||||
|
||||
@screen md {
|
||||
margin-bottom: 3.8rem;
|
||||
}
|
||||
}
|
||||
}
|
86
src/components/modules/account/AccountPage/AccountPage.tsx
Normal file
86
src/components/modules/account/AccountPage/AccountPage.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React, { useState } from "react"
|
||||
import s from './AccountPage.module.scss'
|
||||
|
||||
import AccountNavigation from '../AccountNavigation/AccountNavigation'
|
||||
import HeadingCommon from '../../../common/HeadingCommon/HeadingCommon'
|
||||
import AccountInfomation from "./components/AccountInfomation/AccountInfomation"
|
||||
import OrderInfomation from './components/OrderInformation/OrderInformation'
|
||||
import EditInfoModal from './components/EditInfoModal/EditInfoModal'
|
||||
import TabPane from "src/components/common/TabCommon/components/TabPane/TabPane"
|
||||
|
||||
const waiting = [
|
||||
{
|
||||
id: "NO 123456",
|
||||
products: ["Tomato", "Fish", "Pork", "Onion"],
|
||||
totalPrice : 1000
|
||||
}
|
||||
]
|
||||
|
||||
const delivering = [
|
||||
{
|
||||
id: "NO 123456",
|
||||
products: ["Tomato", "Fish", "Pork", "Onion", "Tomato", "Fish", "Pork", "Onion"],
|
||||
totalPrice : 1000
|
||||
}
|
||||
]
|
||||
|
||||
const delivered = [
|
||||
{
|
||||
id: "NO 123456",
|
||||
products: ["Tomato", "Fish", "Pork", "Onion", "Tomato", "Fish", "Pork", "Onion"],
|
||||
totalPrice : 1000
|
||||
}
|
||||
]
|
||||
|
||||
let account = {
|
||||
name: "vu duong",
|
||||
email: "vuduong@gmail.com",
|
||||
address: "234 Dien Bien Phu Bis, Dakao ward",
|
||||
state: "District 1",
|
||||
city: "HCMC",
|
||||
postalCode: "700000",
|
||||
phoneNumber: "(+84) 937 937 195"
|
||||
}
|
||||
|
||||
interface AccountPageProps {
|
||||
defaultActiveContent?: "info" | "orders" | "favorites"
|
||||
}
|
||||
|
||||
const AccountPage = ({defaultActiveContent="orders"} : AccountPageProps) => {
|
||||
|
||||
const [activeTab] = useState(defaultActiveContent==="info" ? 0 : defaultActiveContent==="orders" ? 1 : 2)
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
|
||||
function showModal() {
|
||||
setModalVisible(true);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
setModalVisible(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className={s.accountPage}>
|
||||
<div className={s.header}>
|
||||
<HeadingCommon>Account</HeadingCommon>
|
||||
</div>
|
||||
|
||||
<AccountNavigation defaultActiveIndex={activeTab}>
|
||||
<TabPane tabName="Customer Information">
|
||||
<AccountInfomation account={account} onClick={showModal} />
|
||||
</TabPane>
|
||||
<TabPane tabName="Your Orders">
|
||||
<OrderInfomation waiting={waiting} delivering={delivering} delivered={delivered} />
|
||||
</TabPane>
|
||||
<TabPane tabName="Favourite">
|
||||
{/* <FavoriteProduct active={activeTab === 2} favProducts={favProducts} /> */}
|
||||
</TabPane>
|
||||
</AccountNavigation>
|
||||
</section>
|
||||
<EditInfoModal accountInfo={account} closeModal={closeModal} visible={modalVisible} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
BIN
src/components/modules/account/AccountPage/assets/avatar.png
Normal file
BIN
src/components/modules/account/AccountPage/assets/avatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,67 @@
|
||||
@import '../../../../../../styles/utilities';
|
||||
|
||||
.accountInfomation {
|
||||
@apply flex justify-center items-center;
|
||||
text-align: center;
|
||||
margin-top: 1.6rem;
|
||||
|
||||
@screen md {
|
||||
@apply block;
|
||||
text-align: left;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: 22rem;
|
||||
width: 22rem;
|
||||
border-radius: 50%;
|
||||
margin: auto;
|
||||
margin-bottom: 4rem;
|
||||
|
||||
|
||||
@screen md {
|
||||
margin-left: 0
|
||||
}
|
||||
}
|
||||
|
||||
.accountName {
|
||||
@apply heading-3 font-heading;
|
||||
}
|
||||
|
||||
.horizontalSeparator{
|
||||
border: 1px solid var(--disabled);
|
||||
max-width: 39.2rem;
|
||||
min-width: 30rem;
|
||||
margin-top: 2.4rem;
|
||||
margin-bottom: 2.4rem;
|
||||
}
|
||||
|
||||
.shippingInfo {
|
||||
@apply heading-3 font-heading;
|
||||
}
|
||||
|
||||
.accountAddress {
|
||||
max-width: 31rem;
|
||||
min-width: none;
|
||||
}
|
||||
|
||||
.editInfoBtn {
|
||||
@apply text-center font-bold custom-border-radius;
|
||||
margin: auto;
|
||||
margin-top: 2.4rem;
|
||||
margin-bottom: 2.4rem;
|
||||
padding: .8rem 1.6rem;
|
||||
color: #141414;
|
||||
border: 1px solid #141414;
|
||||
max-width: 8.8rem;
|
||||
|
||||
&:hover {
|
||||
@apply cursor-pointer;
|
||||
background-color: #FBFBFB;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
import React from "react"
|
||||
import s from './AccountInfomation.module.scss'
|
||||
|
||||
import Image from "next/image"
|
||||
import avatar from '../../assets/avatar.png';
|
||||
|
||||
interface AccountProps {
|
||||
name: string, email: string, address: string, state: string, city: string, postalCode: string, phoneNumber: string
|
||||
}
|
||||
|
||||
interface AccountInfomationProps {
|
||||
account: AccountProps;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const AccountInfomation = ({ account, onClick } : AccountInfomationProps) => {
|
||||
|
||||
// need to handle call back when edit account information
|
||||
|
||||
const showEditForm = () => onClick()
|
||||
|
||||
return (
|
||||
<section className={s.accountInfomation}>
|
||||
{
|
||||
<div>
|
||||
<div className={s.avatar}>
|
||||
<Image src={avatar} alt="avatar" />
|
||||
</div>
|
||||
|
||||
<div className={s.accountName}>
|
||||
{account.name}
|
||||
</div>
|
||||
<div className={s.accountEmail}>
|
||||
{account.email}
|
||||
</div>
|
||||
|
||||
<div className={s.horizontalSeparator}></div>
|
||||
|
||||
<div className={s.shippingInfo}>Shipping Infomation</div>
|
||||
|
||||
<div className={s.accountAddress}>
|
||||
{account.address + `, ${account.state}, ${account.city}, ${account.postalCode}`}
|
||||
</div>
|
||||
|
||||
<div className={s.accountPhoneNumber}>
|
||||
{account.phoneNumber}
|
||||
</div>
|
||||
|
||||
<div onClick={showEditForm} className={s.editInfoBtn}>Edit</div>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountInfomation
|
@@ -0,0 +1,81 @@
|
||||
@import '../../../../../../styles/utilities';
|
||||
|
||||
.editInfoModal {
|
||||
.input {
|
||||
@apply bg-white;
|
||||
margin-bottom: 1.6rem;
|
||||
width: 100%;
|
||||
border: 2px solid #EBEBEB;
|
||||
border-radius: .8rem;
|
||||
padding: 1.6rem;
|
||||
}
|
||||
|
||||
.inputDisable {
|
||||
margin-bottom: 1.6rem;
|
||||
width: 100%;
|
||||
border: 2px solid #EBEBEB;
|
||||
border-radius: .8rem;
|
||||
padding: 1.6rem;
|
||||
background-color: #EBEBEB;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
|
||||
.inputStateWrapper {
|
||||
@apply bg-white;
|
||||
margin-bottom: 1.6rem;
|
||||
margin-right: 1.6rem;
|
||||
border: 2px solid #EBEBEB;
|
||||
border-radius: .8rem;
|
||||
padding: 1.6rem;
|
||||
|
||||
.inputState {
|
||||
@apply bg-white cursor-pointer;
|
||||
border: white;
|
||||
}
|
||||
}
|
||||
|
||||
.inputPostalCode {
|
||||
@apply bg-white;
|
||||
margin-bottom: 1.6rem;
|
||||
border: 2px solid #EBEBEB;
|
||||
border-radius: .8rem;
|
||||
padding: 1.6rem;
|
||||
}
|
||||
|
||||
.inputPhoneNumber {
|
||||
@apply bg-white;
|
||||
margin-bottom: 4rem;
|
||||
width: 100%;
|
||||
border: 2px solid #EBEBEB;
|
||||
border-radius: .8rem;
|
||||
padding: 1.6rem;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
@apply flex;
|
||||
|
||||
.buttonCancel {
|
||||
@apply bg-white text-center font-bold custom-border-radius-lg;
|
||||
color: #141414;
|
||||
border: 1px solid #141414;
|
||||
padding: 1.6rem;
|
||||
margin-right: 1.6rem;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonSave {
|
||||
@apply text-center font-bold custom-border-radius-lg;
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
padding: 1.6rem;
|
||||
width: 100%;
|
||||
&:hover {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
import classNames from "classnames"
|
||||
import React, { useState } from "react"
|
||||
import s from './EditInfoModal.module.scss'
|
||||
|
||||
import {ModalCommon, MenuDropdown} from '../../../../../common'
|
||||
|
||||
import {ButtonCommon} from '../../../../../common'
|
||||
|
||||
interface EditInfoModalProps {
|
||||
accountInfo: {name: string, email: string, address: string, state: string, city: string, postalCode: string, phoneNumber: string};
|
||||
visible: boolean;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoModalProps) => {
|
||||
|
||||
const [name, setName] = useState(accountInfo.name);
|
||||
const [email, setEmail] = useState(accountInfo.email);
|
||||
const [address, setAddress] = useState(accountInfo.address);
|
||||
const [state, setState] = useState(accountInfo.state);
|
||||
const [city, setCity] = useState(accountInfo.city);
|
||||
const [postalCode, setPostalCode] = useState(accountInfo.postalCode);
|
||||
const [phoneNumber, setPhoneNumber] = useState(accountInfo.phoneNumber);
|
||||
|
||||
function saveInfo() {
|
||||
console.log("saved !!!");
|
||||
|
||||
closeModal();
|
||||
}
|
||||
|
||||
const states = [
|
||||
{name: "D1", onClick: () => {setState("D1")}},
|
||||
{name: "D2", onClick: () => {setState("D2")}},
|
||||
{name: "D3", onClick: () => {setState("D3")}}
|
||||
]
|
||||
|
||||
return (
|
||||
<ModalCommon onClose={closeModal} visible={visible} title="Edit Infomation">
|
||||
<section className={s.editInfoModal}>
|
||||
<div>
|
||||
<input className={s.input} type="text" name="name" placeholder="Name"
|
||||
value={name} onChange={e => {setName(e.target.value)}} />
|
||||
</div>
|
||||
<div>
|
||||
<input className={s.inputDisable} type="text" name="email" placeholder="Email"
|
||||
value={email} onChange={e => {setEmail(e.target.value)}} />
|
||||
</div>
|
||||
<div>
|
||||
<input className={s.input} type="text" name="address" placeholder="Address"
|
||||
value={address} onChange={e => {setAddress(e.target.value)}}/>
|
||||
</div>
|
||||
<div>
|
||||
<input className={s.input} type="text" name="city" placeholder="City"
|
||||
value={city} onChange={e => {setCity(e.target.value)}} />
|
||||
</div>
|
||||
|
||||
<div className="flex">
|
||||
<div className={s.inputStateWrapper}>
|
||||
<MenuDropdown options={states} isHasArrow={true} >
|
||||
<input className={s.inputState} type="text" name="state" placeholder="State"
|
||||
value={state} disabled />
|
||||
</MenuDropdown>
|
||||
</div>
|
||||
|
||||
<input className={s.inputPostalCode} type="text" name="postalCode" placeholder="Postal code"
|
||||
value={postalCode} onChange={e => {setPostalCode(e.target.value)}} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input className={s.inputPhoneNumber} type="text" name="phoneNumber" placeholder="Phone number"
|
||||
value={phoneNumber} onChange={e => {setPhoneNumber(e.target.value)}} />
|
||||
</div>
|
||||
|
||||
<div className={s.buttons}>
|
||||
<div onClick={closeModal} className={s.buttonCancel}>Cancel</div>
|
||||
<div onClick={saveInfo} className={s.buttonSave}>Save</div>
|
||||
</div>
|
||||
</section>
|
||||
</ModalCommon>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditInfoModal
|
@@ -0,0 +1,16 @@
|
||||
@import '../../../../../../styles/utilities';
|
||||
|
||||
.orderInformation {
|
||||
.title {
|
||||
@apply heading-3 font-heading;
|
||||
margin-top: 1.6rem;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
margin-top: 3.2rem;
|
||||
|
||||
.blank {
|
||||
margin-bottom: 2.4rem;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
import React from "react"
|
||||
import s from './OrderInformation.module.scss'
|
||||
|
||||
import { TabCommon } from '../../../../../common'
|
||||
import TabPane from 'src/components/common/TabCommon/components/TabPane/TabPane'
|
||||
import DeliveryItem from '../../../DeliveryItem/DeliveryItem'
|
||||
|
||||
|
||||
interface OrderInformationProps {
|
||||
waiting: {id: string, products: string[], totalPrice: number}[],
|
||||
delivering: {id: string, products: string[], totalPrice: number}[],
|
||||
delivered: {id: string, products: string[], totalPrice: number}[],
|
||||
// active?: boolean
|
||||
}
|
||||
|
||||
const OrderInformation = ({ waiting, delivering, delivered} : OrderInformationProps) => {
|
||||
|
||||
return (
|
||||
<section className={s.orderInformation}>
|
||||
{
|
||||
<div>
|
||||
<div className={s.title}>Order Information</div>
|
||||
|
||||
<div className={s.tabs}>
|
||||
<TabCommon>
|
||||
<TabPane tabName={"Wait for Comfirmation"} >
|
||||
<div className={s.blank}></div>
|
||||
{
|
||||
waiting.map((order, i) => {
|
||||
return (
|
||||
<DeliveryItem key={order.id} id={order.id} status="waiting" products={order.products} totalPrice={order.totalPrice} />
|
||||
)
|
||||
})
|
||||
}
|
||||
</TabPane>
|
||||
|
||||
<TabPane tabName={"Delivering"}>
|
||||
<div className={s.blank}></div>
|
||||
{
|
||||
delivering.map((order, i) => {
|
||||
return (
|
||||
<DeliveryItem key={order.id} id={order.id} status="delivering" products={order.products} totalPrice={order.totalPrice} />
|
||||
)
|
||||
})
|
||||
}
|
||||
</TabPane>
|
||||
|
||||
<TabPane tabName={"Delivered"}>
|
||||
<div className={s.blank}></div>
|
||||
{
|
||||
delivered.map((order, i) => {
|
||||
return (
|
||||
<DeliveryItem key={order.id} id={order.id} status="delivered" products={order.products} totalPrice={order.totalPrice} />
|
||||
)
|
||||
})
|
||||
}
|
||||
</TabPane>
|
||||
</TabCommon>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrderInformation
|
@@ -0,0 +1,26 @@
|
||||
@import '../../../../styles/utilities';
|
||||
|
||||
.deliveryItem {
|
||||
@apply flex bg-white items-center custom-border-radius;
|
||||
margin-bottom: 1.6rem;
|
||||
border: 1px solid var(--primary)
|
||||
}
|
||||
|
||||
.separator {
|
||||
border-left: 2px dashed #EBEBEB;
|
||||
max-height: 9.2rem;
|
||||
min-height: 8.6rem;
|
||||
|
||||
margin-left: .6rem;
|
||||
margin-right: .6rem;
|
||||
|
||||
@screen md {
|
||||
margin-left: .8rem;
|
||||
margin-right: .8rem;
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
margin-left: 2.4rem;
|
||||
margin-right: 2.4rem;
|
||||
}
|
||||
}
|
30
src/components/modules/account/DeliveryItem/DeliveryItem.tsx
Normal file
30
src/components/modules/account/DeliveryItem/DeliveryItem.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react"
|
||||
import s from './DeliveryItem.module.scss'
|
||||
|
||||
import IdAndStatus from './components/IdAndStatus/IdAndStatus'
|
||||
import Products from './components/Products/Products'
|
||||
import TotalPrice from './components/TotalPrice/TotalPrice'
|
||||
import ReOrder from './components/ReOrder/ReOrder'
|
||||
|
||||
|
||||
interface DeliveryItemProps {
|
||||
id: string;
|
||||
status: "waiting" | "delivering" | "delivered";
|
||||
products: string[];
|
||||
totalPrice: number;
|
||||
reOrderLink?: string;
|
||||
}
|
||||
|
||||
const DeliveryItem = ({ id, status, products, totalPrice, reOrderLink } : DeliveryItemProps) => {
|
||||
return (
|
||||
<section className={s.deliveryItem}>
|
||||
<IdAndStatus id={id} status={status} />
|
||||
<div className={s.separator}></div>
|
||||
<Products products={products} />
|
||||
<TotalPrice totalPrice={totalPrice} />
|
||||
<ReOrder show={status==="delivered" ? true : false} href={reOrderLink} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeliveryItem
|
@@ -0,0 +1,75 @@
|
||||
@import '../../../../../../styles/utilities';
|
||||
|
||||
.idAndStatus {
|
||||
@apply items-center;
|
||||
padding: 2.4rem 0 2.4rem 1rem;
|
||||
|
||||
@screen md {
|
||||
padding: 2.4rem 0 2.4rem 1.2rem;
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
padding: 2.4rem 0 2.4rem 2.4rem;
|
||||
}
|
||||
|
||||
.id {
|
||||
@apply font-bold;
|
||||
margin-bottom: .8rem;
|
||||
}
|
||||
|
||||
.deliveryStatus {
|
||||
@apply font-bold text-white;
|
||||
font-size: 1.2rem;
|
||||
line-height: 2rem;
|
||||
padding: 0 .8rem;
|
||||
border-radius: 0.5rem;
|
||||
width: fit-content;
|
||||
|
||||
&.waiting {
|
||||
background-color: #D9A645;
|
||||
}
|
||||
&.delivering {
|
||||
background-color: var(--info-dark);
|
||||
}
|
||||
&.delivered {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
}
|
||||
}@import '../../../../../../styles/utilities';
|
||||
|
||||
.idAndStatus {
|
||||
@apply items-center;
|
||||
padding: 2.4rem 0 2.4rem 1rem;
|
||||
|
||||
@screen md {
|
||||
padding: 2.4rem 0 2.4rem 1.2rem;
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
padding: 2.4rem 0 2.4rem 2.4rem;
|
||||
}
|
||||
|
||||
.id {
|
||||
@apply font-bold;
|
||||
margin-bottom: .8rem;
|
||||
}
|
||||
|
||||
.deliveryStatus {
|
||||
@apply font-bold text-white;
|
||||
font-size: 1.2rem;
|
||||
line-height: 2rem;
|
||||
padding: 0 .8rem;
|
||||
border-radius: 0.5rem;
|
||||
width: fit-content;
|
||||
|
||||
&.waiting {
|
||||
background-color: #D9A645;
|
||||
}
|
||||
&.delivering {
|
||||
background-color: var(--info-dark);
|
||||
}
|
||||
&.delivered {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
import classNames from "classnames"
|
||||
import React from "react"
|
||||
import s from './IdAndStatus.module.scss'
|
||||
|
||||
|
||||
interface IdAndStatusProps {
|
||||
id?: string;
|
||||
status: "waiting" | "delivering" | "delivered";
|
||||
}
|
||||
|
||||
const IdAndStatus = ({ id, status="waiting" } : IdAndStatusProps) => {
|
||||
return (
|
||||
<div className={s.idAndStatus}>
|
||||
<div className={s.id}>
|
||||
{id}
|
||||
</div>
|
||||
<div className={classNames(s.deliveryStatus, {
|
||||
[s[status]]: status
|
||||
})}> {status}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IdAndStatus
|
@@ -0,0 +1,12 @@
|
||||
@import '../../../../../../styles/utilities';
|
||||
|
||||
.products {
|
||||
margin-top: .8rem;
|
||||
max-width: 32%;
|
||||
min-width: none;
|
||||
|
||||
@screen lg {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
import React from "react"
|
||||
import s from './Products.module.scss'
|
||||
|
||||
interface ProductsProps {
|
||||
products: string[];
|
||||
}
|
||||
|
||||
const Products = ({ products } : ProductsProps) => {
|
||||
|
||||
function toString(products:string[]): string {
|
||||
let strProducts = "";
|
||||
products.map((prod, i) => {
|
||||
if (i === 0) {
|
||||
strProducts += prod;
|
||||
} else {
|
||||
strProducts += `, ${prod}`
|
||||
}
|
||||
});
|
||||
return strProducts;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.products}>
|
||||
{toString(products)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
@@ -0,0 +1,27 @@
|
||||
@import '../../../../../../styles/utilities';
|
||||
|
||||
.reOrder {
|
||||
@apply text-white custom-border-radius hidden font-bold;
|
||||
padding: .4rem .6rem;
|
||||
margin-right: 1rem;
|
||||
background-color: var(--primary);
|
||||
|
||||
@screen md {
|
||||
padding: .4rem .6rem;
|
||||
margin-right: 1.2rem;
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
padding: .8rem 1.2rem;
|
||||
margin-right: 2.4rem;
|
||||
}
|
||||
|
||||
&.show {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
import classNames from "classnames"
|
||||
import React from "react"
|
||||
import s from './ReOrder.module.scss'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface ReOrderProps {
|
||||
show: boolean;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
const ReOrder = ({ show=false, href="#" } : ReOrderProps) => {
|
||||
return (
|
||||
<div className={classNames(s.reOrder, {
|
||||
[s.show]: show
|
||||
})}>
|
||||
<Link href={href}>
|
||||
<a>Re-Order</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReOrder
|
@@ -0,0 +1,26 @@
|
||||
@import '../../../../../../styles/utilities';
|
||||
|
||||
.totalPrice {
|
||||
margin-left: auto;
|
||||
margin-right: 1rem;
|
||||
|
||||
@screen md {
|
||||
margin-right: 1.2rem;
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
margin-right: 2.4rem;
|
||||
}
|
||||
|
||||
.price {
|
||||
@apply font-bold ;
|
||||
|
||||
@screen md {
|
||||
@apply topline
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
@apply sub-headline;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
import React from "react"
|
||||
import s from './TotalPrice.module.scss'
|
||||
|
||||
|
||||
interface TotalPriceProps {
|
||||
totalPrice: number;
|
||||
}
|
||||
|
||||
const TotalPrice = ({ totalPrice } : TotalPriceProps) => {
|
||||
return (
|
||||
<section className={s.totalPrice}>
|
||||
<div className="text-right">Total</div>
|
||||
<div className={s.price}>Rp {totalPrice}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default TotalPrice
|
3
src/components/modules/account/index.ts
Normal file
3
src/components/modules/account/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as AccountNavigation } from './AccountNavigation/AccountNavigation'
|
||||
export { default as DeliveryItem } from './DeliveryItem/DeliveryItem'
|
||||
export { default as AccountPage } from './AccountPage/AccountPage'
|
@@ -0,0 +1,7 @@
|
||||
@import "../../../../styles/utilities";
|
||||
.breadCrumbWrapper {
|
||||
@apply py-12 spacing-horizontal;
|
||||
@screen lg {
|
||||
padding-left: 3.2rem;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { BreadcrumbCommon } from "src/components/common"
|
||||
import s from './BlogBreadCrumb.module.scss'
|
||||
|
||||
const BLOG_DATA = [
|
||||
{link: "/blogs", name: "Blog"},
|
||||
];
|
||||
|
||||
const BlogBreadCrumb = () => {
|
||||
return (
|
||||
<section className={s.breadCrumbWrapper}>
|
||||
<BreadcrumbCommon crumbs={BLOG_DATA} showHomePage={true}/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogBreadCrumb
|
@@ -0,0 +1,9 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.headingWrapper {
|
||||
@apply flex spacing-horizontal-left pb-16 justify-center;
|
||||
.heading{
|
||||
max-width: 121.6rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
18
src/components/modules/blogs/BlogHeading/BlogHeading.tsx
Normal file
18
src/components/modules/blogs/BlogHeading/BlogHeading.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { HeadingCommon } from "src/components/common"
|
||||
import s from './BlogHeading.module.scss'
|
||||
|
||||
interface BlogHeadingProps {
|
||||
children?: React.ReactNode,
|
||||
heading?: string,
|
||||
}
|
||||
|
||||
const BlogHeading = ({heading = "BLOG"}: BlogHeadingProps) => {
|
||||
return (
|
||||
<section className={s.headingWrapper}>
|
||||
<div className={s.heading}>
|
||||
<HeadingCommon>{heading}</HeadingCommon>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default BlogHeading
|
23
src/components/modules/blogs/BlogsList/BlogsList.module.scss
Normal file
23
src/components/modules/blogs/BlogsList/BlogsList.module.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.wrapper {
|
||||
@apply flex flex-col spacing-horizontal items-center;
|
||||
padding-bottom: 16.8rem;
|
||||
.list {
|
||||
@apply grid grid-cols-1 gap-8;
|
||||
max-width: 121.6rem;
|
||||
@screen md {
|
||||
@apply grid-cols-2;
|
||||
}
|
||||
@screen lg {
|
||||
@apply grid-cols-3;
|
||||
}
|
||||
}
|
||||
.card {
|
||||
@apply pb-16;
|
||||
}
|
||||
.pagination {
|
||||
@apply flex justify-center items-center ;
|
||||
padding-top: 0.8rem;
|
||||
}
|
||||
}
|
157
src/components/modules/blogs/BlogsList/BlogsList.tsx
Normal file
157
src/components/modules/blogs/BlogsList/BlogsList.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import React, { useState } from 'react'
|
||||
import CardBlog, { BlogCardProps } from 'src/components/common/CardBlog/CardBlog'
|
||||
import PaginationCommon from 'src/components/common/PaginationCommon/PaginationCommon'
|
||||
import s from "./BlogsList.module.scss"
|
||||
import { DEFAULT_BLOG_PAGE_SIZE } from 'src/utils/constanst.utils'
|
||||
import image15 from '../../../../../public/assets/images/image15.png'
|
||||
import image16 from '../../../../../public/assets/images/image16.png'
|
||||
import image17 from '../../../../../public/assets/images/image17.png'
|
||||
import image21 from '../../../../../public/assets/images/image21.png'
|
||||
import image22 from '../../../../../public/assets/images/image22.png'
|
||||
import image23 from '../../../../../public/assets/images/image23.png'
|
||||
|
||||
interface BlogsListProps {
|
||||
data?: BlogCardProps[],
|
||||
}
|
||||
|
||||
const BLOGSLIST_DATA = [
|
||||
{
|
||||
imageSrc: image15.src,
|
||||
title: "1",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image16.src,
|
||||
title: "2",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image17.src,
|
||||
title: "3",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image21.src,
|
||||
title: "4",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image22.src,
|
||||
title: "5",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image23.src,
|
||||
title: "6",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image15.src,
|
||||
title: "7",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image16.src,
|
||||
title: "8",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image17.src,
|
||||
title: "9",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image23.src,
|
||||
title: "10",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image21.src,
|
||||
title: "11",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image22.src,
|
||||
title: "12",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image15.src,
|
||||
title: "13",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image16.src,
|
||||
title: "14",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image17.src,
|
||||
title: "15",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image21.src,
|
||||
title: "16",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image23.src,
|
||||
title: "17",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: image22.src,
|
||||
title: "18",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
const BlogsList = ({ data = BLOGSLIST_DATA }:BlogsListProps) => {
|
||||
const [currentPage, setCurrentPage] = useState(0)
|
||||
const onPageChange = (page:number) => {
|
||||
setCurrentPage(page)
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className={s.wrapper}>
|
||||
<div className={s.list}>
|
||||
{
|
||||
data.slice(currentPage*DEFAULT_BLOG_PAGE_SIZE,(currentPage+1)*DEFAULT_BLOG_PAGE_SIZE).map((product,index)=> {
|
||||
return(
|
||||
<div className={s.card} key={`${product.title}-${index}`}>
|
||||
<CardBlog {...product} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<div className={s.pagination}>
|
||||
<PaginationCommon total={data.length} pageSize={DEFAULT_BLOG_PAGE_SIZE} onChange={onPageChange}/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogsList
|
@@ -0,0 +1,34 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.featuredCard {
|
||||
@apply flex flex-row justify-center spacing-horizontal pb-16;
|
||||
.featuredCardWrapper {
|
||||
@apply flex flex-col;
|
||||
@screen lg {
|
||||
@apply flex-row justify-between;
|
||||
}
|
||||
max-width: 121.6rem;
|
||||
}
|
||||
}
|
||||
.left {
|
||||
max-width: 59.8rem;
|
||||
img {
|
||||
border-radius: 2.4rem;
|
||||
}
|
||||
}
|
||||
.right {
|
||||
flex-shrink: 3;
|
||||
@screen lg {
|
||||
margin-left: 6.4rem;
|
||||
}
|
||||
}
|
||||
.titleWrapper {
|
||||
@apply flex flex-col items-start font-heading heading-3;
|
||||
margin-bottom: 1.6rem;
|
||||
.title {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
margin-top: 1.6rem
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
import s from './FeaturedCardBlog.module.scss'
|
||||
import { Author, DateTime } from 'src/components/common'
|
||||
import Image from "next/image";
|
||||
import image20 from '../../../../../public/assets/images/image20.png'
|
||||
import author from '../../../../../public/assets/images/author.png'
|
||||
|
||||
interface FeaturedCardBlogProps{
|
||||
title?: string,
|
||||
content?: string,
|
||||
imgSrc?: any,
|
||||
imgAuthor?: any,
|
||||
date?: string,
|
||||
authorName?: string,
|
||||
}
|
||||
|
||||
const FEATURED_DATA = {
|
||||
title: "Flammekueche with green asparagus",
|
||||
content: "Traditionally, the Flammekueche is made with rapeseed oil, which, contrary to popular belief, is indeed an oil that can be cooked hot and is not limited to seasoning. It is important to vary the oils in the kitchen to take advantage of the benefits of each. Rapeseed oil is an oil rich in omega 3 which participate in the proper functioning of the cardiovascular system as well as in vitamins E which contributes to the protection of cells against oxidative stress. In short, oils are your friends 😉",
|
||||
imgSrc: image20,
|
||||
imgAuthor: author.src,
|
||||
date: "APRIL 30, 2021",
|
||||
author: "Alessandro Del Piero"
|
||||
}
|
||||
|
||||
const FeaturedCardBlog = ({
|
||||
title = FEATURED_DATA.title,
|
||||
content = FEATURED_DATA.content,
|
||||
imgSrc = FEATURED_DATA.imgSrc,
|
||||
imgAuthor = FEATURED_DATA.imgAuthor,
|
||||
date = FEATURED_DATA.date,
|
||||
authorName = FEATURED_DATA.author
|
||||
}: FeaturedCardBlogProps) => {
|
||||
return (
|
||||
<section className={s.featuredCard}>
|
||||
<div className={s.featuredCardWrapper}>
|
||||
<div className={s.left}>
|
||||
<Image src={imgSrc} alt="image feature card"/>
|
||||
</div>
|
||||
<div className={s.right}>
|
||||
<div className={s.titleWrapper}>
|
||||
<DateTime date={date}/>
|
||||
<a className={s.title}>{title}</a>
|
||||
</div>
|
||||
<Author name={authorName} image={imgAuthor}/>
|
||||
<div className={s.content}>{content}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeaturedCardBlog
|
4
src/components/modules/blogs/index.tsx
Normal file
4
src/components/modules/blogs/index.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as FeaturedCardBlog } from './FeaturedCardBlog/FeaturedCardBlog'
|
||||
export { default as BlogsList } from './BlogsList/BlogsList'
|
||||
export { default as BlogHeading } from './BlogHeading/BlogHeading'
|
||||
export { default as BlogBreadCrumb } from './BlogBreadcrumb/BlogBreadcrumb'
|
@@ -0,0 +1,8 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.breadCrumb {
|
||||
@apply absolute z-10 pt-12 spacing-horizontal;
|
||||
@screen lg{
|
||||
padding-left: 3.2rem;
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
import { BreadcrumbCommon } from 'src/components/common'
|
||||
import s from './DeliveryAndPolicyBreadCrumb.module.scss'
|
||||
|
||||
const CRUMB_DATA = [
|
||||
{
|
||||
link: "/delivery-policy",
|
||||
name: "Delivery And Policy"
|
||||
}
|
||||
]
|
||||
const DeliveryAndPolicyBreadCrumb = () => {
|
||||
return (
|
||||
<section className={s.breadCrumb}>
|
||||
<BreadcrumbCommon crumbs={CRUMB_DATA}/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default DeliveryAndPolicyBreadCrumb
|
@@ -0,0 +1,26 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.wrapper {
|
||||
@apply flex justify-center pt-20 pb-28 spacing-horizontal;
|
||||
.deliveryAndPolicyContentWrapper {
|
||||
max-width: 80.4rem;
|
||||
min-height: 4rem;
|
||||
.titleWrapper {
|
||||
@apply flex flex-col items-start;
|
||||
.date {
|
||||
@apply inline flex flex-row;
|
||||
margin-bottom: 0.4rem;
|
||||
.update {
|
||||
@apply uppercase leading-8;
|
||||
color:var(--text-label);
|
||||
font-size: 1.2rem;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
@apply pb-16 whitespace-pre-line;
|
||||
padding-top: 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
import { CollapseCommon, DateTime, HeadingCommon } from 'src/components/common'
|
||||
import s from './DeliveryAndPolicyContent.module.scss'
|
||||
|
||||
interface DeliveryAndPolicyContentProps{
|
||||
title?: string,
|
||||
date?: string,
|
||||
content?: string,
|
||||
}
|
||||
const HEADER_CONTENT =
|
||||
`When you’re trying to eat healthier but want something more substantial than a leafy green salad, broccoli salad is there for you. I love the crunch and heft of broccoli, especially when it’s cut up into bite size spoonable pieces.
|
||||
|
||||
Some people aren’t into raw broccoli, but I love it! I always go for the raw broccoli on those vegetable platters that seem to be at every potluck/party you go to.
|
||||
|
||||
This is a simple broccoli salad: you have the bulk of it, raw broccoli; crunchy red onions for a bit of acidity and raw crunch, craisins for sweetness, almonds for a nutty counter point; and a sweet and tangy soy-rice vinegar-sesame dressing.`;
|
||||
|
||||
const DELIVERYANDPOLICY_DATA = [
|
||||
{
|
||||
title: "This is a subtitle",
|
||||
content: `When you’re trying to eat healthier but want something more substantial than a leafy green salad, broccoli salad is there for you. I love the crunch and heft of broccoli, especially when it’s cut up into bite size spoonable pieces.
|
||||
|
||||
Some people aren’t into raw broccoli, but I love it! I always go for the raw broccoli on those vegetable platters that seem to be at every potluck/party you go to.
|
||||
|
||||
This is a simple broccoli salad: you have the bulk of it, raw broccoli; crunchy red onions for a bit of acidity and raw crunch, craisins for sweetness, almonds for a nutty counter point; and a sweet and tangy soy-rice vinegar-sesame dressing.`,
|
||||
},
|
||||
{
|
||||
title: "This is a subtitle",
|
||||
content: `When you’re trying to eat healthier but want something more substantial than a leafy green salad, broccoli salad is there for you. I love the crunch and heft of broccoli, especially when it’s cut up into bite size spoonable pieces.
|
||||
|
||||
Some people aren’t into raw broccoli, but I love it! I always go for the raw broccoli on those vegetable platters that seem to be at every potluck/party you go to.
|
||||
|
||||
This is a simple broccoli salad: you have the bulk of it, raw broccoli; crunchy red onions for a bit of acidity and raw crunch, craisins for sweetness, almonds for a nutty counter point; and a sweet and tangy soy-rice vinegar-sesame dressing.`,
|
||||
},
|
||||
{
|
||||
title: "This is a subtitle",
|
||||
content: `When you’re trying to eat healthier but want something more substantial than a leafy green salad, broccoli salad is there for you. I love the crunch and heft of broccoli, especially when it’s cut up into bite size spoonable pieces.
|
||||
|
||||
Some people aren’t into raw broccoli, but I love it! I always go for the raw broccoli on those vegetable platters that seem to be at every potluck/party you go to.
|
||||
|
||||
This is a simple broccoli salad: you have the bulk of it, raw broccoli; crunchy red onions for a bit of acidity and raw crunch, craisins for sweetness, almonds for a nutty counter point; and a sweet and tangy soy-rice vinegar-sesame dressing.`,
|
||||
},
|
||||
{
|
||||
title: "This is a subtitle",
|
||||
content: `When you’re trying to eat healthier but want something more substantial than a leafy green salad, broccoli salad is there for you. I love the crunch and heft of broccoli, especially when it’s cut up into bite size spoonable pieces.
|
||||
|
||||
Some people aren’t into raw broccoli, but I love it! I always go for the raw broccoli on those vegetable platters that seem to be at every potluck/party you go to.
|
||||
|
||||
This is a simple broccoli salad: you have the bulk of it, raw broccoli; crunchy red onions for a bit of acidity and raw crunch, craisins for sweetness, almonds for a nutty counter point; and a sweet and tangy soy-rice vinegar-sesame dressing.`,
|
||||
},
|
||||
{
|
||||
title: "This is a subtitle",
|
||||
content: `When you’re trying to eat healthier but want something more substantial than a leafy green salad, broccoli salad is there for you. I love the crunch and heft of broccoli, especially when it’s cut up into bite size spoonable pieces.
|
||||
|
||||
Some people aren’t into raw broccoli, but I love it! I always go for the raw broccoli on those vegetable platters that seem to be at every potluck/party you go to.
|
||||
|
||||
This is a simple broccoli salad: you have the bulk of it, raw broccoli; crunchy red onions for a bit of acidity and raw crunch, craisins for sweetness, almonds for a nutty counter point; and a sweet and tangy soy-rice vinegar-sesame dressing.`,
|
||||
},
|
||||
]
|
||||
|
||||
const DeliveryAndPolicyContent = ( { title="Delivery & Policy", date="APRIL 30, 2021", content = HEADER_CONTENT } : DeliveryAndPolicyContentProps) => {
|
||||
return (
|
||||
<section className={s.wrapper}>
|
||||
|
||||
<div className={s.deliveryAndPolicyContentWrapper}>
|
||||
<div className={s.titleWrapper}>
|
||||
<div className={s.date}>
|
||||
<div className={s.update}>LASTEST UPDATED: </div>
|
||||
<DateTime date={date} />
|
||||
</div>
|
||||
<HeadingCommon>{title}</HeadingCommon>
|
||||
</div>
|
||||
<div className={s.content}>
|
||||
{content}
|
||||
</div>
|
||||
<CollapseCommon data={DELIVERYANDPOLICY_DATA} />
|
||||
</div>
|
||||
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeliveryAndPolicyContent
|
2
src/components/modules/delivery-policy/index.tsx
Normal file
2
src/components/modules/delivery-policy/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as DeliveryAndPolicyContent } from './DeliveryAndPolicyContent/DeliveryAndPolicyContent'
|
||||
export { default as DeliveryAndPolicyBreadCrumb } from './DeliveryAndPolicyBreadCrumb/DeliveryAndPolicyBreadCrumb'
|
@@ -116,3 +116,4 @@ export const FEATURED = [
|
||||
},
|
||||
]
|
||||
|
||||
export const DEFAULT_BLOG_PAGE_SIZE=6;
|
||||
|
@@ -12,7 +12,6 @@
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noUnusedLocals": true,
|
||||
@@ -26,7 +25,8 @@
|
||||
"@commerce/*": ["framework/commerce/*"],
|
||||
"@framework": ["framework/vendure"],
|
||||
"@framework/*": ["framework/vendure/*"]
|
||||
}
|
||||
},
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
||||
"exclude": [
|
||||
|
Reference in New Issue
Block a user