diff --git a/web/common/toolkit.tsx b/web/common/toolkit.tsx index 0313e954b..722f2b0fc 100644 --- a/web/common/toolkit.tsx +++ b/web/common/toolkit.tsx @@ -5,14 +5,28 @@ import { LayoutDetect, Products, Collaborate, - Slogan -} from 'sections/bridged'; + Slogan, +} from "sections/bridged"; + +import { FreePlan, PlanList, FeatureList, Faq } from "sections/pricing"; export const BridgedSection = [ { content: () => }, - { content: (isMobile) => }, + { + content: isMobile => ( + + ), + }, { content: () => }, { content: () => }, { content: () => }, { content: () => }, -]; \ No newline at end of file +]; + +export const PricingSection = [ + { content: () => }, + { content: () => }, + { content: () => }, + { content: () => }, + { content: () => }, +]; diff --git a/web/components/feature-choice/index.tsx b/web/components/feature-choice/index.tsx new file mode 100644 index 000000000..7abcbccd3 --- /dev/null +++ b/web/components/feature-choice/index.tsx @@ -0,0 +1,84 @@ +import React, { useState } from "react"; +import { Flex, Text } from "rebass"; +import styled from "@emotion/styled"; +import Icon from "components/icon"; + +interface FeatureChoiceProps { + titleList: string; + featureData: any; +} + +const FeatureChoice: React.FC = props => { + const { titleList, featureData } = props; + const [isOpen, setIsOpen] = useState([]); + + const handleFeatureClick = (idx: number) => { + setIsOpen(d => { + if (d.indexOf(idx) != -1) { + d.splice(d.indexOf(idx), 1); + return [...d.filter((item, index) => d.indexOf(item) === index)]; + } else { + return [...d.filter((item, index) => d.indexOf(item) === index), idx]; + } + }); + }; + + return ( + + + + {titleList}{" "} + {titleList === "Extra Usage" && ( + + )} + + {featureData.map((item, ix) => ( + handleFeatureClick(ix)} + style={{ cursor: "pointer" }} + mb="36px" + key={item.id} + > + + {item.title} + + + {item.feature.map((i, idx) => ( + + {isOpen.includes(ix) && ( + + {i.name} + + )} + + ))} + + + ))} + + + View all + + + + ); +}; + +const Item = styled(Flex)` + &:last-child { + margin-bottom: 0px; + } +`; + +const Feature = styled(Flex)` + &:first-of-type { + margin-top: 24px; + } + + &:last-child { + margin-bottom: 0px; + } +`; + +export default FeatureChoice; diff --git a/web/components/icon/icons.tsx b/web/components/icon/icons.tsx index af04d2642..f6eadbc6f 100644 --- a/web/components/icon/icons.tsx +++ b/web/components/icon/icons.tsx @@ -27,6 +27,12 @@ export interface IconList { play: IconListProps; gradient: IconListProps; close: IconListProps; + question: IconListProps; + plus: IconListProps; + faqClose: IconListProps; + okaySign: IconListProps; + questionMark: IconListProps; + check: IconListProps; } const icons: IconList = { @@ -325,109 +331,366 @@ const icons: IconList = { mockIcon: { width: 24, height: 24, - svg: - - + svg: ( + + + + ), }, headerClose: { width: 32, height: 32, - svg: - - - + svg: ( + + + + + ), }, lock: { width: 16, height: 16, - svg: - - + svg: ( + + + + ), }, loading: { width: 84, height: 84, - svg: - - - - - - - - + svg: ( + + + + + + + + + + ), }, home: { width: 42, height: 42, - svg: - - + svg: ( + + + + ), }, graph: { width: 42, height: 42, - svg: - - + svg: ( + + + + ), }, play: { width: 42, height: 42, - svg: - - - - - - - - - - - - - + svg: ( + + + + + + + + + + + + + + + ), }, gradient: { width: 2080, height: 1765, - svg: - - - - + svg: ( + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + ), }, close: { width: 19, height: 19, - svg: - - - - } + svg: ( + + + + + ), + }, + question: { + width: 18, + height: 18, + svg: ( + + + + ), + }, + plus: { + width: 14, + height: 14, + svg: ( + + + + ), + }, + faqClose: { + width: 32, + height: 32, + svg: ( + + + + + ), + }, + okaySign: { + width: 24, + height: 24, + svg: ( + + + + + ), + }, + questionMark: { + width: 24, + height: 24, + svg: ( + + + + ), + }, + check: { + width: 17, + height: 13, + svg: ( + + + + ), + }, }; export default icons; diff --git a/web/components/question/index.tsx b/web/components/question/index.tsx new file mode 100644 index 000000000..628ce805b --- /dev/null +++ b/web/components/question/index.tsx @@ -0,0 +1,63 @@ +import React, { useState } from "react"; +import styled from "@emotion/styled"; +import { Flex, Text } from "rebass"; +import Icon from "components/icon"; + +interface QuestionProps { + list: { + title: string; + desc?: string; + }; +} + +const Question: React.FC = ({ list }) => { + const [isOpen, setIsOpen] = useState(false); + + const handleIconClick = () => { + setIsOpen(!isOpen); + }; + + return ( + + + {list.title} + + + + {isOpen && ( + + {list.desc} + + )} + + ); +}; + +export default Question; + +const Title = styled(Text)` + font-size: 20px; + letter-spacing: 0em; + text-align: left; +`; + + +const Desc = styled(Flex)` + font-size: 20px; + line-height: 133%; + + color: #3d3d3d; + + letter-spacing: 0em; + text-align: left; +`; diff --git a/web/components/start-now/index.tsx b/web/components/start-now/index.tsx new file mode 100644 index 000000000..c6631c50f --- /dev/null +++ b/web/components/start-now/index.tsx @@ -0,0 +1,113 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { Flex, Text, Heading, Button } from "rebass"; +import Icon from "components/icon"; +import { media } from "utils/styled/media"; +import { ThemeInterface } from "utils/styled/theme"; + +const descList = [ + { + title: "Code export including Flutter, React and more", + }, + { + title: "Private git integration", + }, + { + title: "Design linting", + }, + { + title: "Unlimited public projects", + }, + { + title: "Up to 5000 Objects", + }, +]; + +const StartNow: React.FC = () => { + return ( + + + + {descList.map((item, ix) => ( + + + {item.title} + + ))} + + + + + $0 + /mo + + + No credit card required + + + ); +}; + +export default StartNow; + +const Card = styled(Flex)` + box-shadow: 0px 4px 64px 12px rgba(0, 0, 0, 0.08); + border-radius: 8px; + + ${props => media("0px", (props.theme as ThemeInterface).breakpoints[0])} { + height: 686px; + } +`; + +const LeftWrapper = styled(Flex)` + ${props => media("0px", (props.theme as ThemeInterface).breakpoints[0])} { + height: 50%; + } +`; + +const RightWrapper = styled(Flex)` + ${props => media("0px", (props.theme as ThemeInterface).breakpoints[0])} { + height: 50%; + } +`; + +const Desc = styled(Flex)` + letter-spacing: 0em; + text-align: left; +`; + +const NoCredit = styled(Flex)` + letter-spacing: 0em; + text-align: left; +`; diff --git a/web/pages/pricing/index.tsx b/web/pages/pricing/index.tsx index c494a7c0b..3a1bac127 100644 --- a/web/pages/pricing/index.tsx +++ b/web/pages/pricing/index.tsx @@ -1,21 +1,6 @@ +import { PricingSection } from "common/toolkit"; import React from "react"; + export default function PricingPage() { - return ( - <> -
- -

Bridged is free to use

-

- Free for dreamers. Bridged is Free for now, we don't take any forms - of payment for now. Feel free to create your awesome products with - it. -

-
-
- - ); -} \ No newline at end of file + return PricingSection.map(item => item.content()); +} diff --git a/web/sections/pricing/faq/index.tsx b/web/sections/pricing/faq/index.tsx new file mode 100644 index 000000000..3e9b6e9f5 --- /dev/null +++ b/web/sections/pricing/faq/index.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { Flex, Heading } from "rebass"; +import SectionLayout from "layout/section"; +import Question from "components/question"; +import BlankArea from "components/blank-area"; + +const questionList = [ + { + title: "How do Bridged make money?", + }, + { + title: "What are the limitations with free plan?", + }, + { + title: "I cannot see images anymore. What happened?", + desc: + "Your image is uploaded and hosted on bridged cloud for 24 hours for development mode. If you enable publishing option for the screen / component you selected, all the resources will be long-lived. long-lived resource hosting is only available for paid plan. for free plan, we only support 24 hours temp hosting.", + }, + { + title: "How does the standard extra cloud usage fee calculated?", + }, + { + title: "Does bridged have explicit enterprise support plan?", + }, +]; + +const Faq: React.FC = () => { + return ( + + + FAQs + + {questionList.map((item, ix) => ( + + ))} + + + + + ); +}; + +export default Faq; + +const FreeText = styled(Heading)` + font-size: 48px; + color: #000000; + + letter-spacing: 0em; +`; + +const Wrapper = styled(Flex)` + margin-top: 62px; +`; diff --git a/web/sections/pricing/feature-list/desktop-view/index.tsx b/web/sections/pricing/feature-list/desktop-view/index.tsx new file mode 100644 index 000000000..5075708b2 --- /dev/null +++ b/web/sections/pricing/feature-list/desktop-view/index.tsx @@ -0,0 +1,107 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { Flex, Text, Heading } from "rebass"; +import Icon from "components/icon"; +import SectionLayout from "layout/section"; + +interface FeatureProps { + data: { + id: number; + title: string; + feature: { + id: number; + name: string; + price: string[]; + }[]; + }[]; +} + +const FeatureListDesktopView: React.FC = ({ data }) => { + return ( + + +
+ + Features + + + Free + Team + + Extra usage + + + +
+ + + {data.map((item, ix) => ( + + + {item.title} + + + {item.feature.map((item, ix) => ( + + + {item.name} + + {item.price[0]} + {item.price[1]} + {item.price[2]} + + ))} + + + ))} + + View all + + + +
+
+ ); +}; + +const Header = styled(Flex)` + border-bottom: 1px solid #f8f8f8; +`; + +const Content = styled(Flex)` + font-weight: normal; + font-size: 18px; + text-align: center; + justify-content: center; + color: #7e7e7e; +`; + +const ContentsWrapper = styled(Flex)` + &:last-child { + margin-bottom: 0px; + } +`; + +export default FeatureListDesktopView; diff --git a/web/sections/pricing/feature-list/index.tsx b/web/sections/pricing/feature-list/index.tsx new file mode 100644 index 000000000..c5e98771e --- /dev/null +++ b/web/sections/pricing/feature-list/index.tsx @@ -0,0 +1,131 @@ +import React from "react"; +import styled from "@emotion/styled"; +import FeatureListMobileView from "./mobile-view"; +import FeatureListDesktopView from "./desktop-view"; +import { Flex } from "rebass"; +import { DesktopView } from "utils/styled/styles"; +import { media } from "utils/styled/media"; +import { ThemeInterface } from "utils/styled/theme"; + +const featureDataList = [ + { + id: 1, + title: "Authentication", + feature: [ + { + id: 1, + name: "Phone Auth - US, Canada, and India (by requests)", + price: ["100 / Mo", "50,000 / Mo", "$1 / 2K"], + }, + { + id: 2, + name: "Phone Auth - All other countries", + price: ["100 / Mo", "50,000 / Mo", "$1 / 2K"], + }, + { + id: 3, + name: "Other Authentication services", + price: ["Free", "Free", "Free"], + }, + ], + }, + { + id: 2, + title: "Asset Storage", + feature: [ + { + id: 1, + name: "Storage Capacity", + price: ["0.5 GiB", "30 GiB", "$0.023/GiB"], + }, + { + id: 2, + name: "Read Access", + price: ["$999/GB", "$999/GB", "$999/GB"], + }, + { + id: 3, + name: "Multiple buckets per project", + price: ["X", "$999/GB", "Free"], + }, + ], + }, + { + id: 3, + title: "Database", + feature: [ + { + id: 1, + name: "feature", + price: ["$999/GB", "$999/GB", "$999/GB"], + }, + { + id: 2, + name: "feature", + price: ["$999/GB", "$999/GB", "$999/GB"], + }, + ], + }, + { + id: 4, + title: "Capacity - Objects", + feature: [ + { + id: 1, + name: "objects such as design screen, component, or layer.", + price: ["5000", "100,000", "$1/1M"], + }, + { + id: 2, + name: "feature", + price: ["$999/GB", "$999/GB", "$999/GB"], + }, + ], + }, + { + id: 5, + title: "Code blocks", + feature: [ + { + id: 1, + name: "code blocks", + price: ["100,000", "Unlimited", "X"], + }, + { + id: 2, + name: "feature", + price: ["$999/GB", "$999/GB", "$999/GB"], + }, + ], + }, +]; + +const FeatureList: React.FC = () => { + return ( + + + + + + + + + + ); +}; + +const Mobile = styled(Flex)` + display: none; + + ${props => media("0px", (props.theme as ThemeInterface).breakpoints[0])} { + display: flex; + width: 100%; + position: relative; + } +`; + +export default FeatureList; diff --git a/web/sections/pricing/feature-list/mobile-view/index.tsx b/web/sections/pricing/feature-list/mobile-view/index.tsx new file mode 100644 index 000000000..9855dd881 --- /dev/null +++ b/web/sections/pricing/feature-list/mobile-view/index.tsx @@ -0,0 +1,39 @@ +import React, { useState } from "react"; +import styled from "@emotion/styled"; +import { Flex, Text, Heading } from "rebass"; +import Icon from "components/icon"; +import SectionLayout from "layout/section"; +import FeatureChoice from "components/feature-choice"; + +interface FeatureProps { + data: any; +} + +const choice = ["Free", "Team", "Extra Usage"]; + +const FeatureListMobileView: React.FC = ({ data }) => { + return ( + + + Features + + + {choice.map(i => ( + + ))} + + + ); +}; + +const Wrapper = styled(Flex)` + &:last-child { + margin-bottom: 0px; + } +`; + +export default FeatureListMobileView; diff --git a/web/sections/pricing/free-plan/index.tsx b/web/sections/pricing/free-plan/index.tsx new file mode 100644 index 000000000..3b2e0398d --- /dev/null +++ b/web/sections/pricing/free-plan/index.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import styled from "@emotion/styled"; +import { Flex, Heading, Text } from "rebass"; +import SectionLayout from "layout/section"; +import StartNow from "components/start-now"; +import BlankArea from "components/blank-area"; + +const FreePlan = () => { + return ( + + + + Free for dreamers + + Unlock your possibility, express your ideas faster then ever. Bridged + is free forever. + + + + + + ); +}; + +export default FreePlan; + +const FreeText = styled(Heading)` + font-size: 72px; + color: #000000; + + text-align: center; + letter-spacing: -0.025em; +`; + +const Desc = styled(Text)` + font-size: 24px; + line-height: 34px; + letter-spacing: 0em; + text-align: center; + + color: #707070; +`; diff --git a/web/sections/pricing/index.ts b/web/sections/pricing/index.ts new file mode 100644 index 000000000..48943fcb4 --- /dev/null +++ b/web/sections/pricing/index.ts @@ -0,0 +1,6 @@ +import FreePlan from "./free-plan"; +import PlanList from "./plan-list"; +import Faq from "./faq"; +import FeatureList from "./feature-list"; + +export { FreePlan, PlanList, Faq, FeatureList }; diff --git a/web/sections/pricing/plan-list/index.tsx b/web/sections/pricing/plan-list/index.tsx new file mode 100644 index 000000000..24f50d2bc --- /dev/null +++ b/web/sections/pricing/plan-list/index.tsx @@ -0,0 +1,281 @@ +import React, { useCallback } from "react"; +import styled from "@emotion/styled"; +import { Flex, Heading, Text, Button } from "rebass"; +import SectionLayout from "layout/section"; +import BlankArea from "components/blank-area"; +import Icon from "components/icon"; +import { media } from "utils/styled/media"; +import { ThemeInterface } from "utils/styled/theme"; +import { usePopupContext } from "utils/context/PopupContext"; + +const forYouTitleList = [ + { + title: "Private git integration", + }, + { + title: "Design linting", + }, + { + title: "0.5GB Asset Storage", + }, + { + title: "Code export", + }, + { + title: "Unlimited public projects", + }, + { + title: "Up to 5000 Objects", + }, + { + title: "1M Code blocks", + }, +]; + +const forTeamTitleList = [ + { + title: "Unlimited Long-lived hosting", + }, + { + title: "Unlimited Private projects", + }, + { + title: "30GB Asset Storage", + }, + { + title: "Code generation with full-engine capability", + }, + { + title: "Unlimited Projects", + }, + { + title: "Custom Domain", + }, + { + title: "1M Cloud objects", + }, + { + title: "Unlimited Code blocks", + }, +]; + +const PlanList: React.FC = () => { + const { addPopup, removePopup } = usePopupContext(); + + const handleClickQuestionMark = useCallback(() => { + addPopup({ + title: "", + element: ( + + removePopup()} + style={{ cursor: "pointer" }} + /> + + + What are the limitations of free plan? + + + To build an enterprise level application, you’ll need a paid plan. + Paid plan includes extra default storage and unlimited projects + count. Also cloud objects such as translation token can be stored + up to 1 million. The extra usage will be charged as Standard Cloud + Fee. + + + + ), + showOnlyBody: true, + height: "50vw", + }); + }, []); + + return ( + + + Pay as you grow + + Start small, pay when you’re ready. + + + + + + For you + + + + $0 + + {forYouTitleList.map((item, ix) => ( + + + {item.title} + + ))} + + + + + + + For your team + + + $24 + + + per seats/mo + + + {forTeamTitleList.map((item, ix) => ( + + + {item.title} + + ))} + + + + + + + ); +}; + +export default PlanList; + +const FreePlanPopup = styled(Flex)` + flex-direction: column; + background-color: #ffffff; + border-radius: 8px; +`; + +const Title = styled(Heading)` + font-weight: bold; + font-size: 56px; + line-height: 86.5%; + + text-align: center; + letter-spacing: -0.025em; + + color: #000000; +`; + +const Desc = styled(Text)` + font-weight: normal; + font-size: 24px; + line-height: 141%; + text-align: center; + + color: #686868; +`; + +const Wrapper = styled(Flex)` + ${props => media("0px", (props.theme as ThemeInterface).breakpoints[0])} { + height: 1128px; + } +`; + +const ForYou = styled(Flex)` + ${props => media("0px", (props.theme as ThemeInterface).breakpoints[0])} { + width: 90%; + height: 527px; + } + border: 1px solid #f7f7f7; + border-radius: 8px; +`; + +const CardWrapper = styled(Flex)` + ${props => media("0px", (props.theme as ThemeInterface).breakpoints[0])} { + height: 95%; + } +`; + +const CardTitle = styled(Flex)` + font-size: 24px; + line-height: 135%; + + color: #000000; +`; + +const ForTeam = styled(Flex)` + ${props => media("0px", (props.theme as ThemeInterface).breakpoints[0])} { + width: 90%; + height: 577px; + } + + box-shadow: 0px 4px 128px 32px rgba(0, 0, 0, 0.08); + border-radius: 8px; +`;