From 32a326c8b9edc0ee5c691ac89d92514abc0d8922 Mon Sep 17 00:00:00 2001 From: gofnnp Date: Fri, 25 Aug 2023 04:01:46 +0400 Subject: [PATCH] feat: add compatibility page --- src/api/api.ts | 6 +- src/api/resources/AICompatCategories.ts | 24 +++ src/api/resources/index.ts | 1 + src/components/Compatibility/index.tsx | 102 ++++++++++++ src/components/Compatibility/nameInput.tsx | 37 +++++ .../Compatibility/styles.module.css | 154 ++++++++++++++++++ src/components/DateTimePicker/DateInput.tsx | 4 +- src/components/DateTimePicker/DatePicker.tsx | 11 +- src/components/DateTimePicker/styles.css | 2 +- src/services/date/index.ts | 15 ++ 10 files changed, 348 insertions(+), 8 deletions(-) create mode 100644 src/api/resources/AICompatCategories.ts create mode 100644 src/components/Compatibility/index.tsx create mode 100644 src/components/Compatibility/nameInput.tsx create mode 100644 src/components/Compatibility/styles.module.css create mode 100644 src/services/date/index.ts diff --git a/src/api/api.ts b/src/api/api.ts index 5f0a6fb..1a97cff 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -13,7 +13,8 @@ import { SubscriptionCheckout, SubscriptionReceipts, SubscriptionStatus, - PaymentIntents + PaymentIntents, + AICompatCategories } from './resources' const api = { @@ -32,7 +33,8 @@ const api = { getSubscriptionStatus: createMethod(SubscriptionStatus.createRequest), getSubscriptionReceipt: createMethod(SubscriptionReceipts.createGetRequest), createSubscriptionReceipt: createMethod(SubscriptionReceipts.createRequest), - createPaymentIntent: createMethod(PaymentIntents.createRequest) + createPaymentIntent: createMethod(PaymentIntents.createRequest), + getAiCompatCategories: createMethod(AICompatCategories.createRequest) } export type ApiContextValue = typeof api diff --git a/src/api/resources/AICompatCategories.ts b/src/api/resources/AICompatCategories.ts new file mode 100644 index 0000000..6b607ed --- /dev/null +++ b/src/api/resources/AICompatCategories.ts @@ -0,0 +1,24 @@ +import routes from "@/routes"; +import { getBaseHeaders } from "../utils"; + +export interface Payload { + locale: string; +} + +export interface Response { + compat_categories: CompatCategory[] +} + +export interface CompatCategory { + id: number + name: string +} + +export const createRequest = ({ locale }: Payload): Request => { + const url = new URL(routes.server.compatCategories()); + const query = new URLSearchParams({ locale }); + + url.search = query.toString(); + + return new Request(url, { method: "GET", headers: getBaseHeaders() }); +}; diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index e13e829..8e2f524 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -12,3 +12,4 @@ export * as SubscriptionCheckout from './UserSubscriptionCheckout' export * as SubscriptionStatus from './UserSubscriptionStatus' export * as SubscriptionReceipts from './UserSubscriptionReceipts' export * as PaymentIntents from './UserPaymentIntents' +export * as AICompatCategories from './AICompatCategories' diff --git a/src/components/Compatibility/index.tsx b/src/components/Compatibility/index.tsx new file mode 100644 index 0000000..135efe0 --- /dev/null +++ b/src/components/Compatibility/index.tsx @@ -0,0 +1,102 @@ +import { useTranslation } from "react-i18next"; +import MainButton from "../MainButton"; +import Title from "../Title"; +import styles from "./styles.module.css"; +import { useCallback, useState } from "react"; +import NameInput from "./nameInput"; +import { DatePicker } from "../DateTimePicker"; +import { IDate, getDateAsString } from "@/services/date"; +import { AICompatCategories, useApi, useApiCall } from "@/api"; + +function CompatibilityPage(): JSX.Element { + const { t, i18n } = useTranslation(); + const [isDisabled, setIsDisabled] = useState(false); + const [name, setName] = useState(''); + const [date, setDate] = useState(''); + const [compatCategory, setCompatCategory] = useState(2); + const handleNext = () => console.log(name, date, compatCategory); + + const api = useApi() + const locale = i18n.language + const loadData = useCallback(() => { + return api.getAiCompatCategories({ locale }) + .then((resp: AICompatCategories.Response) => resp.compat_categories) + }, [api, locale]) + const { data } = useApiCall(loadData) + + const handleValidName = (name: string) => { + setIsDisabled(name === ''); + setName(name) + } + + const handleValidDate = (date: IDate | string) => { + setIsDisabled(date === ''); + setDate(date) + } + + const changeCompatCategory = (event: React.ChangeEvent) => { + setCompatCategory(parseInt(event.target.value)) + } + + + return ( +
+ + {t("compatibility")} + +
+ + {t("iAm")} + + + + + +
+
+ setIsDisabled(true)} + /> +
+
+ setIsDisabled(true)} + /> +
+
+ {data && data.length && ( +
+ { + data.map((item, index) => ( +
+ + +
+ )) + } +
+ )} + + {t("check")} + +
+
+ ); +} + +export default CompatibilityPage; diff --git a/src/components/Compatibility/nameInput.tsx b/src/components/Compatibility/nameInput.tsx new file mode 100644 index 0000000..df9d20c --- /dev/null +++ b/src/components/Compatibility/nameInput.tsx @@ -0,0 +1,37 @@ +import { useEffect, useState } from 'react' +import { FormField } from '@/types' +import styles from './styles.module.css' + +const isValidName = (name: string) => { + return name.length > 0 && name.length < 30 +} + +function NameInput(props: FormField): JSX.Element { + const { name, value, placeholder, onValid, onInvalid } = props + const [userName, setUserName] = useState(value) + const handleChange = (event: React.ChangeEvent) => { + setUserName(event.target.value) + } + + useEffect(() => { + if (isValidName(userName)) { + onValid(userName) + } else { + onInvalid() + } + }, [userName, onInvalid, onValid]) + + return ( +
+ +
+ ) +} + +export default NameInput diff --git a/src/components/Compatibility/styles.module.css b/src/components/Compatibility/styles.module.css new file mode 100644 index 0000000..3909856 --- /dev/null +++ b/src/components/Compatibility/styles.module.css @@ -0,0 +1,154 @@ +.page { + position: relative; + min-height: calc(100vh - 0px); + max-height: -webkit-fill-available; + flex: auto; + background-color: #121212; + align-items: flex-start; +} + +.content { + width: 100%; +} + +.title { + color: #fff; + font-weight: 500; + margin-top: 32px; +} + +.iam, +.plus { + color: #ea445a; +} + +.iam { + font-weight: 500; + margin-top: 32px; + margin-bottom: 0; + /* font-size: 24px; */ +} + +.plus { + font-size: 48px; + font-weight: 100; + margin-bottom: 0; +} + +.name-input-container { + display: flex; + flex-direction: row; + justify-content: center; +} + +.input-container__date-container { + margin-top: 16px; +} + +.name-input-container > input { + background-color: #595452; + color: #fff; + text-align: center; + padding: 8px 16px; + border-radius: 6px; + border: solid 2px #ea445a; + font-weight: 500; + font-size: 16px; +} + +.name-input-container > input:focus { + border-color: #066fde; + transition-delay: 0.1s; +} + +.date-input > h3 { + display: none; +} + +.date-input input { + background-color: #595452; + color: #fff; + text-align: center; + padding: 8px 16px; + border-radius: 6px; + border: solid 2px #ea445a; + font-weight: 500; + font-size: 16px; +} + +.date-input p { + text-align: center; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #83807e; +} + +.check-btn { + border-radius: 30px; + background-color: #ea445a; + max-width: 200px; + margin: 48px auto; +} + +.compatibility-categories { + display: flex; + flex-direction: column; + gap: 32px; + margin-top: 72px; +} + +.compatibility-categories__input--checked, +.compatibility-categories__input { + position: absolute; + left: -9999px; +} +.compatibility-categories__input--checked + label, +.compatibility-categories__input + label { + position: relative; + padding-left: 48px; + font-weight: 500; + cursor: pointer; + line-height: 24px; + display: inline-block; + color: #fff; +} +.compatibility-categories__input--checked + label:before, +.compatibility-categories__input + label:before { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 20px; + height: 20px; + border: 2px solid #ddd; + border-radius: 100%; +} +.compatibility-categories__input--checked + label:after, +.compatibility-categories__input + label:after { + content: ""; + width: 12px; + height: 12px; + background-image: url(/check-mark-1.png); + background-size: contain; + background-repeat: no-repeat; + position: absolute; + top: 8px; + left: 6px; + -webkit-transition: all 0.2s ease; + transition: all 0.2s ease; +} +.compatibility-categories__input + label:after { + opacity: 0; + -webkit-transform: scale(0); + transform: scale(0); +} +.compatibility-categories__input--checked + label:after { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); +} +.compatibility-categories__input--checked + label::before { + background-color: #ea445a; + border-color: #ea445a; +} diff --git a/src/components/DateTimePicker/DateInput.tsx b/src/components/DateTimePicker/DateInput.tsx index e37c4bf..60a2230 100644 --- a/src/components/DateTimePicker/DateInput.tsx +++ b/src/components/DateTimePicker/DateInput.tsx @@ -10,7 +10,7 @@ type DateInputProps = Omit, 'onValid' | 'onInvalid'> & } function DateInput(props: DateInputProps): JSX.Element { - const { label, placeholder, name, value = '', max, maxLength, onChange } = props + const { label, placeholder, name, inputClassName, value = '', max, maxLength, onChange } = props const validate = (value: number): boolean => value >= 0 && value <= max const handleChange = (e: React.ChangeEvent) => { const datePart = e.target.value ? parseInt(e.target.value, 10) : 0 @@ -20,7 +20,7 @@ function DateInput(props: DateInputProps): JSX.Element { onChange(datePart > 0 ? normalize(datePart, maxLength) : '') } return ( -
+

{label}

diff --git a/src/components/DateTimePicker/styles.css b/src/components/DateTimePicker/styles.css index 1626683..6e5fc6a 100644 --- a/src/components/DateTimePicker/styles.css +++ b/src/components/DateTimePicker/styles.css @@ -8,7 +8,7 @@ .date-picker__container { grid-gap: 12px; - background-color: #fff; + background-color: transparent; display: grid; gap: 12px; grid-template-columns: repeat(3,1fr); diff --git a/src/services/date/index.ts b/src/services/date/index.ts new file mode 100644 index 0000000..5150550 --- /dev/null +++ b/src/services/date/index.ts @@ -0,0 +1,15 @@ +export interface IDate { + year: string; + month: string; + day: string; +} + +export const getDateAsString = (date: Date | IDate | string): string => { + if (date instanceof Date) { + return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + } + if (typeof date === 'string') { + return date; + } + return `${date.year}-${date.month}-${date.day}`; +};