From ff88fc05fdcd276e8092d2a11f42f8bc1edadafe Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:12:11 +0200 Subject: [PATCH 01/31] feat: S3 storage LP Missing: * Graphs section --- src/assets/lang/en/cloud-object-storage.json | 168 ++++++++++++++++++ src/assets/lang/en/metatags-descriptions.json | 5 + src/assets/types/cloud-object-storage.ts | 79 ++++++++ .../cloud-object-storage/HeroSection.tsx | 33 ++++ .../HowMuchYouNeedSection.tsx | 75 ++++++++ .../PredictablePricingSection.tsx | 26 +++ .../cloud-object-storage/PriceCardSection.tsx | 70 ++++++++ .../WhyChooseInternxtSection.tsx | 93 ++++++++++ src/components/shared/Header.tsx | 5 +- src/pages/cloud-object-storage.tsx | 73 ++++++++ 10 files changed, 626 insertions(+), 1 deletion(-) create mode 100644 src/assets/lang/en/cloud-object-storage.json create mode 100644 src/assets/types/cloud-object-storage.ts create mode 100644 src/components/cloud-object-storage/HeroSection.tsx create mode 100644 src/components/cloud-object-storage/HowMuchYouNeedSection.tsx create mode 100644 src/components/cloud-object-storage/PredictablePricingSection.tsx create mode 100644 src/components/cloud-object-storage/PriceCardSection.tsx create mode 100644 src/components/cloud-object-storage/WhyChooseInternxtSection.tsx create mode 100644 src/pages/cloud-object-storage.tsx diff --git a/src/assets/lang/en/cloud-object-storage.json b/src/assets/lang/en/cloud-object-storage.json new file mode 100644 index 000000000..ca9deb48e --- /dev/null +++ b/src/assets/lang/en/cloud-object-storage.json @@ -0,0 +1,168 @@ +{ + "HeroSection": { + "title": { + "line1": "Internxt S3 ", + "line2": "compatible object storage" + }, + "description": "An efficient, scalable cloud storage solution for storing, accessing, and managing large-scale data.", + "cta": "Get started" + }, + "PredictablePricingSection": { + "title": "Simple, predictable pricing with zero fees and no API charges", + "description": "Avoid the hassle of unpredictable storage bills. Internxt offers a straightforward rate for storage capacity with no hidden fees.", + "info": [ + { + "title": "100% Hot", + "description": "Ultra fast and instantly accessible" + }, + { + "title": "Up to 80%", + "description": "More affordable than AWS" + }, + { + "title": "Zero fees", + "description": "Free data transfer" + } + ] + }, + "PriceCardSection": { + "title": "Internxt object cloud storage combines security and efficiency", + "description": "Internxt object storage is an affordable, high-performance solution revolutionizing online data storage, perfect for individuals or organizations needing reliable and secure infrastructure at a minimal cost.", + "cardText": { + "label": "Internxt S3", + "perTB": "Per TB/Billed monthly", + "price": "6.99", + "cta": "Select plan", + "whatsIncluded": { + "title": "What's included:", + "features": [ + "Billed monthly", + "Server-side encryption", + "AWS S3 and IAM API compatible", + "Increased speed", + "Flexible storage", + "Open source", + "GDPR compliant", + "Premium customer support" + ] + } + } + }, + "HowMuchYouNeedSection": { + "title": "How much storage do you need?", + "description": "Find out how much you can save with Internxt object storage. Use our cost calculator to see how we compare to hyperscalers.", + "pay-as-you-go": "Pay-as-you-go pricing", + "perMonth": "/per month", + "perYear": "/per year", + "storageAmount": "Storage amount", + "percentDownloadPerMonth": "Percent download per month", + "companies": ["Internxt", "Microsoft", "Amazon", "Google"] + }, + "WhyChooseInternxtSection": { + "title": "Why choose Internxt object storage?", + "description": "The benefits of object cloud storage with Internxt emphasize effective data management and security.", + "cards": [ + { + "title": "Zero ingress and egress fees", + "description": "Zero ingress and egress fees eliminate costs for transferring data into or out of storage, allowing for predictable budgeting and cost savings. This benefit is crucial for businesses with frequent data movement or large-scale data operations." + }, + { + "title": "Cost per GB", + "description": "With our pay-as-you-go model, you only pay for the storage you actually use. Object storage is designed to handle large volumes of unstructured data, which is typically less expensive per GB compared to structured data, which requires more complex management." + }, + { + "title": "Durability & Availability", + "description": "We enable redundant data storage that replicates and distributes data across multiple European servers. Data is protected from loss or corruption with technology that guarantees 99.999999999% (11 9s) of data durability for each object." + }, + { + "title": "Security", + "description": "With our object cloud storage, we use multiple security measures to protect your data. Data stored in isolated buckets is accessible only by you or those with permission and is end-to-end encrypted." + }, + { + "title": "Simple storage service", + "description": "Internxt provides object storage using the S3 protocol to simplify and manage data workflows, ensuring high availability and durability of data with the added security focus that Internxt prioritizes." + }, + { + "title": "Speed", + "description": "Object storage offers superior speed by allowing multiple read and write operations simultaneously and utilizing rich metadata for faster data retrieval. This allows for quicker access times and efficient handling of large volumes of data." + } + ], + "bannerText": { + "title": "Internxt object cloud storage", + "description": "Experience superior data management, speed, and efficiency", + "cta": "Get started" + } + }, + "FaqSection": { + "title": "Questions? We have answers", + "faq": [ + { + "question": "What is object storage?", + "answer": [ + "Object storage stores large amounts of data, like photos, videos, and emails, as individual “objects” with unique identifiers.", + "Unlike traditional file storage, which organizes data in a hierarchical file system, object storage stores data as objects in a flat address space, typically in a distributed storage infrastructure.", + "As a result, cloud object storage allows for easier scaling, faster access to individual items, and potentially lower costs depending on your storage needs." + ] + }, + { + "question": "What is a S3 protocol?", + "answer": [ + "The S3 protocol, or Simple Storage Service protocol, is a set of rules and standards that define how data is accessed, stored, and managed within Amazon S3, a popular cloud storage service provided by Amazon Web Services (AWS)." + ] + }, + { + "question": "How much object storage do I get?", + "answer": [ + "With Internxt cloud object storage, you can scale your storage to meet your needs. Our plans charge you monthly for each gigabyte-per-day-used you want to use. You can add or delete storage based on your needs." + ] + }, + { + "question": "Can I integrate Internxt object storage with existing applications and services?", + "answer": [ + "Yes, Internxt object storage is compatible with AWS S3 APIs, allowing seamless integration with a wide range of applications, tools, and services that support the S3 protocol.", + "S3 object storage allows users to upload, retrieve, and manage objects (files and data) using a simple web interface and APIs. The protocol ensures data integrity, security through access controls, and scalability to accommodate varying storage needs seamlessly across distributed servers." + ] + }, + { + "question": "Does Internxt have a minimum monthly storage policy?", + "answer": [ + "Internxt’s minimum monthly policy for object storage plans is 1TB. If stored objects are deleted before they have been stored with Internxt for 90 days, a time-deleted storage charge will be applied that is equal to the remaining days left on the monthly subscription.", + "This policy is the same as the minimum storage duration policy that AWS and other hyperscaler storage services charge." + ] + }, + { + "question": "Does Internxt have a monthly storage charge?", + "answer": [ + "Internxt has a monthly minimum charge associated with a 1TB monthly paid account. If you store less than 1TB, for example, the 4KB minimum, you will be charged for 1TB of storage." + ] + }, + { + "question": "What if I want to store small files? (Less than 4 KB)", + "answer": [ + "There is a minimum file size charge of 4KB. Storing a 4KB file will incur charges equivalent to a 1TB file. This policy resembles the minimum capacity charge per object policies found in some AWS storage." + ] + }, + { + "question": "What’s the cost of overwriting a file repeatedly without versioning?", + "answer": [ + "For example, if you upload a file named 'file.pdf' and overwrite it with a new version without changing the file name or using versioning the next day, the original file will transition from active storage to deleted storage.", + "This incurs charges for one day of active storage and 29 or 89 days of deleted storage, depending on your account's minimum storage retention policy. Additionally, charges will apply for storing the new version of 'file.pdf.'" + ] + }, + { + "question": "How do I contact support?", + "answer": [ + "If you have any questions, concerns, or feedback, you can contact our Customer Support Team via live chat or by emailing [hello@internxt.com](mailto:hello@internxt.com)." + ] + }, + { + "question": "What if I want to cancel my plan?", + "answer": [ + "If you delete objects from Internxt object storage before they have been stored for 90 days, you will still be charged as if those objects had been stored for the full 90 days.", + "This policy ensures a minimum charge period for stored data, regardless of how long the objects were actually stored before deletion.", + "If you want to cancel your object storage plan, email us at hello@internxt.com, and we’ll process the subscription cancellation." + ] + } + ] + } +} diff --git a/src/assets/lang/en/metatags-descriptions.json b/src/assets/lang/en/metatags-descriptions.json index dcf3a4ccb..36b019205 100644 --- a/src/assets/lang/en/metatags-descriptions.json +++ b/src/assets/lang/en/metatags-descriptions.json @@ -9,6 +9,11 @@ "title": "Internxt — Private, Encrypted Cloud Storage for Business", "description": "Prevent data loss with Internxt’s encrypted cloud storage for business—sign up to benefit from industry-leading cloud data protection." }, + { + "id": "cloud-object-storage", + "title": "Internxt — Object Storage", + "description": "Internxt provides private and affordable object storage for securely storing data using the S3 protocol to manage your storage needs effectively." + }, { "id": "drive", "title": "Internxt Drive – Private & Secure Cloud Storage", diff --git a/src/assets/types/cloud-object-storage.ts b/src/assets/types/cloud-object-storage.ts new file mode 100644 index 000000000..b65e116e3 --- /dev/null +++ b/src/assets/types/cloud-object-storage.ts @@ -0,0 +1,79 @@ +export interface CloudObjectStorageText { + HeroSection: HeroSection; + PredictablePricingSection: PredictablePricingSection; + PriceCardSection: PriceCardSection; + HowMuchYouNeedSection: HowMuchYouNeedSection; + WhyChooseInternxtSection: WhyChooseInternxtSection; + FaqSection: FAQSection; +} + +export interface FAQSection { + title: string; + faq: FAQ[]; +} + +export interface FAQ { + question: string; + answer: string[]; +} + +export interface HeroSection { + title: { + line1: string; + line2: string; + }; + description: string; + cta: string; +} + +export interface HowMuchYouNeedSection { + title: string; + description: string; + 'pay-as-you-go': string; + perMonth: string; + perYear: string; + storageAmount: string; + percentDownloadPerMonth: string; + companies: string[]; +} + +export interface PredictablePricingSection { + title: string; + description: string; + info: Info[]; +} + +export interface Info { + title: string; + description: string; +} + +export interface PriceCardSection { + title: string; + description: string; + cardText: CardText; +} + +export interface CardText { + label: string; + perTB: string; + price: string; + whatsIncluded: WhatsIncluded; + cta: string; +} + +export interface WhatsIncluded { + title: string; + features: string[]; +} + +export interface WhyChooseInternxtSection { + title: string; + description: string; + cards: Info[]; + bannerText: { + title: string; + description: string; + cta: string; + }; +} diff --git a/src/components/cloud-object-storage/HeroSection.tsx b/src/components/cloud-object-storage/HeroSection.tsx new file mode 100644 index 000000000..b4a8246bd --- /dev/null +++ b/src/components/cloud-object-storage/HeroSection.tsx @@ -0,0 +1,33 @@ +import { CloudObjectStorageText } from '@/assets/types/cloud-object-storage'; +import HeroSectionSafeArea from '../shared/HeroSectionSafeArea'; +import Header from '../shared/Header'; +import Button from '../shared/Button'; + +interface HeroSectionProps { + textContent: CloudObjectStorageText['HeroSection']; +} + +export const CloudObjectStorageHeroSection = ({ textContent }: HeroSectionProps): JSX.Element => ( +
+ +
+
+ {textContent.title.line1} + {textContent.title.line2} +
+

{textContent.description}

+
+
+
+
+); diff --git a/src/components/cloud-object-storage/HowMuchYouNeedSection.tsx b/src/components/cloud-object-storage/HowMuchYouNeedSection.tsx new file mode 100644 index 000000000..7ac12ccb4 --- /dev/null +++ b/src/components/cloud-object-storage/HowMuchYouNeedSection.tsx @@ -0,0 +1,75 @@ +import { CloudObjectStorageText } from '@/assets/types/cloud-object-storage'; +import { useState } from 'react'; +import { RangeSlider } from '../shared/RangeSlider'; + +interface HowMuchYouNeedSectionProps { + textContent: CloudObjectStorageText['HowMuchYouNeedSection']; +} + +export const HowMuchYouNeedSection = ({ textContent }: HowMuchYouNeedSectionProps): JSX.Element => { + const [monthlyPrice, setMonthlyPrice] = useState('3,495'); + const [yearlyPrice, setYearlyPrice] = useState('41,940'); + const [storageAmountValue, setStorageAmountValue] = useState(500); + const [percentDownloadValue, setPercentDownloadValue] = useState(2); + + function storageAmountValueLabelFormat(itemValue: number) { + setStorageAmountValue(itemValue); + return storageAmountValue; + } + + function percentDownloadValueLabelFormat(itemValue: number) { + setPercentDownloadValue(itemValue); + return percentDownloadValue; + } + + return ( +
+
+
+

{textContent.title}

+

{textContent.description}

+
+ + {/* Cards */} +
+
+ {/* Pricing */} +
+

{textContent['pay-as-you-go']}

+ {/* Monthly price */} +
+

${monthlyPrice}

+

{textContent.perMonth}

+
+ {/* Yearly price */} +
+

${yearlyPrice}

+

{textContent.perMonth}

+
+
+ + {/* Sliders */} +
+

{textContent.storageAmount}

+ {/* Storage amount slider */} +
+ +
+

{storageAmountValue}

+
+
+ {/* Yearly price */} +
+ +
+

{percentDownloadValue}

+
+
+
+
+ {/* Graphs (Comparison) */} +
+
+
+ ); +}; diff --git a/src/components/cloud-object-storage/PredictablePricingSection.tsx b/src/components/cloud-object-storage/PredictablePricingSection.tsx new file mode 100644 index 000000000..4e20715ff --- /dev/null +++ b/src/components/cloud-object-storage/PredictablePricingSection.tsx @@ -0,0 +1,26 @@ +import { CloudObjectStorageText } from '@/assets/types/cloud-object-storage'; + +interface PredictablePricingSectionProps { + textContent: CloudObjectStorageText['PredictablePricingSection']; +} + +export const PredictablePricingSection = ({ textContent }: PredictablePricingSectionProps): JSX.Element => { + return ( +
+
+
+

{textContent.title}

+

{textContent.description}

+
+
+ {textContent.info.map((card) => ( +
+

{card.title}

+

{card.description}

+
+ ))} +
+
+
+ ); +}; diff --git a/src/components/cloud-object-storage/PriceCardSection.tsx b/src/components/cloud-object-storage/PriceCardSection.tsx new file mode 100644 index 000000000..659f64dae --- /dev/null +++ b/src/components/cloud-object-storage/PriceCardSection.tsx @@ -0,0 +1,70 @@ +import { CloudObjectStorageText } from '@/assets/types/cloud-object-storage'; +import { getImage } from '@/lib/getImage'; +import Image from 'next/image'; +import Button from '../shared/Button'; + +interface PriceCardSectionProps { + textContent: CloudObjectStorageText['PriceCardSection']; +} + +export const CloudObjectStoragePriceCardSection = ({ textContent }: PriceCardSectionProps): JSX.Element => { + return ( +
+
+
+

{textContent.title}

+

{textContent.description}

+
+ +
+ Pay as you go + {/* Card */} +
+ {/* Fist part */} +
+
+

{textContent.cardText.label}

+
+
+

+ + {textContent.cardText.price} +

+

{textContent.cardText.perTB}

+
+
+ + {/* What's included */} +
+

{textContent.cardText.whatsIncluded.title}

+
+ {textContent.cardText.whatsIncluded.features.map((feature) => ( +
+ check icon +

{feature}

+
+ ))} +
+
+
+
+
+
+ ); +}; diff --git a/src/components/cloud-object-storage/WhyChooseInternxtSection.tsx b/src/components/cloud-object-storage/WhyChooseInternxtSection.tsx new file mode 100644 index 000000000..cda56cad6 --- /dev/null +++ b/src/components/cloud-object-storage/WhyChooseInternxtSection.tsx @@ -0,0 +1,93 @@ +import { CloudObjectStorageText } from '@/assets/types/cloud-object-storage'; +import { CardGroup } from '../shared/CardGroup'; +import { HandCoins, HardDrives, NumberCircleZero, ShieldPlus, SketchLogo, Speedometer } from '@phosphor-icons/react'; +import { getImage } from '@/lib/getImage'; +import Image from 'next/image'; +import Button from '../shared/Button'; + +interface CloudObjectStorageWhyChooseInternxtSectionProps { + textContent: CloudObjectStorageText['WhyChooseInternxtSection']; +} + +export const CloudObjectStorageWhyChooseInternxtSection = ({ + textContent, +}: CloudObjectStorageWhyChooseInternxtSectionProps): JSX.Element => { + const cards = [ + { + icon: NumberCircleZero, + title: textContent.cards[0].title, + description: textContent.cards[0].description, + }, + { + icon: HandCoins, + title: textContent.cards[1].title, + description: textContent.cards[1].description, + }, + { + icon: SketchLogo, + title: textContent.cards[2].title, + description: textContent.cards[2].description, + }, + { + icon: ShieldPlus, + title: textContent.cards[3].title, + description: textContent.cards[3].description, + }, + { + icon: HardDrives, + title: textContent.cards[4].title, + description: textContent.cards[4].description, + }, + { + icon: Speedometer, + title: textContent.cards[5].title, + description: textContent.cards[5].description, + }, + ]; + + return ( +
+
+
+

{textContent.title}

+

{textContent.description}

+
+ + + {/* !TODO: Extract banner to shared component */} + {/* Banner */} +
+
+ Internxt B2B CTA 1 +
+
+
+

{textContent.bannerText.title}

+

{textContent.bannerText.description}

+
+
+
+ Internxt B2B CTA 2 +
+
+
+
+ ); +}; diff --git a/src/components/shared/Header.tsx b/src/components/shared/Header.tsx index 200e1b3a2..0a7b5f82b 100644 --- a/src/components/shared/Header.tsx +++ b/src/components/shared/Header.tsx @@ -3,17 +3,20 @@ const Header = ({ maxWidth = 'max-w-[796px]', className, textHeightForDesk = 'sm:text-5xl', + withoutLeading, isToolsPage, }: { children: React.ReactNode; maxWidth?: string; className?: string; + withoutLeading?: boolean; isToolsPage?: boolean; textHeightForDesk?: string; }) => { + const leading = withoutLeading ? '' : 'sm:leading-tight'; return (

{children}

diff --git a/src/pages/cloud-object-storage.tsx b/src/pages/cloud-object-storage.tsx new file mode 100644 index 000000000..f816af5f9 --- /dev/null +++ b/src/pages/cloud-object-storage.tsx @@ -0,0 +1,73 @@ +import { CloudObjectStorageText } from '@/assets/types/cloud-object-storage'; +import { FooterText, MetatagsDescription, NavigationBarText } from '@/assets/types/layout/types'; +import { CloudObjectStorageHeroSection } from '@/components/cloud-object-storage/HeroSection'; +import { HowMuchYouNeedSection } from '@/components/cloud-object-storage/HowMuchYouNeedSection'; +import { PredictablePricingSection } from '@/components/cloud-object-storage/PredictablePricingSection'; +import { CloudObjectStoragePriceCardSection } from '@/components/cloud-object-storage/PriceCardSection'; +import { CloudObjectStorageWhyChooseInternxtSection } from '@/components/cloud-object-storage/WhyChooseInternxtSection'; +import Footer from '@/components/layout/footers/Footer'; +import Layout from '@/components/layout/Layout'; +import Navbar from '@/components/layout/navbars/Navbar'; +import FAQSection from '@/components/shared/sections/FaqSection'; +import { GetServerSidePropsContext } from 'next'; + +interface CloudObjectStorageProps { + metatagsDescription: MetatagsDescription[]; + navbarText: NavigationBarText; + textContent: CloudObjectStorageText; + footerText: FooterText; + locale: GetServerSidePropsContext['locale']; +} + +const CloudObjectStorage = ({ + metatagsDescription, + navbarText, + textContent, + footerText, + locale, +}: CloudObjectStorageProps): JSX.Element => { + const metatags = metatagsDescription.filter((metatag) => metatag.id === 'cloud-object-storage')[0]; + + const lang = locale as string; + + return ( + + + + + + + + + + + + + + + +