[FACTORY]: Sampling More Components For Isolation Testing

This commit is contained in:
SPACEBROWSER_DEV 2025-08-22 16:00:15 -04:00
parent 0301f3a0ab
commit 1acd94923b
22 changed files with 1972 additions and 0 deletions

View File

@ -13,6 +13,7 @@
"formik": "^2.4.6", "formik": "^2.4.6",
"next": "15.4.5", "next": "15.4.5",
"react": "19.1.0", "react": "19.1.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"redux": "^5.0.1", "redux": "^5.0.1",
"sass": "^1.89.2", "sass": "^1.89.2",

View File

@ -0,0 +1,20 @@
// File: InterfaceFactory/app/ShopifyProductCard/mockData.ts
export interface ShopifyProduct {
id: string;
title: string;
description: string;
image: string;
price: number;
available: boolean;
}
export const mockProduct: ShopifyProduct = {
id: 'prod_1',
title: 'Mock Shopify T-Shirt',
description: 'A stylish t-shirt made from 100% organic cotton. Perfect for developers.',
image: '/mock-images/shopify-tshirt.png', // You may need to add this image or use a placeholder URL
price: 29.99,
available: true,
};

View File

@ -0,0 +1,49 @@
// File: InterfaceFactory/app/ShopifyProductCard/page.tsx
'use client';
import React, { useEffect } from 'react';
import { ConfigProvider, App as AntdApp } from 'antd';
import enUS from 'antd/es/locale/en_US';
import ShopifyProductCard from '@/components/ShopifyProductCard/ShopifyProductCard';
import '@/app/css/ShopifyProductCard.scss';
import { mockProduct, ShopifyProduct } from './mockData';
// Minimal shape the card actually uses
type MinimalShopifyProductItem = {
id?: string;
title: string;
images: { src: string }[];
variants: { price: string }[];
available?: boolean;
};
const toShopifyProductItem = (p: ShopifyProduct): MinimalShopifyProductItem => ({
id: p.id,
title: p.title,
images: [{ src: p.image }], // ✅ card expects images[0].src
variants: [{ price: String(p.price) }],// ✅ card expects variants[0].price (string)
available: p.available,
});
export default function ShopifyProductCardPage() {
// If the card uses a portal, ensure it exists (safe no-op otherwise)
useEffect(() => {
if (!document.getElementById('root-portal')) {
const el = document.createElement('div');
el.id = 'root-portal';
document.body.appendChild(el);
}
}, []);
return (
<ConfigProvider locale={enUS}>
<AntdApp>
<div style={{ padding: 24, background: '#f9f9f9', minHeight: '100vh' }}>
{/* ✅ Provide the correct prop shape */}
<ShopifyProductCard item={toShopifyProductItem(mockProduct) as any} type={0} showPrice />
</div>
</AntdApp>
</ConfigProvider>
);
}

View File

@ -0,0 +1,36 @@
@import './variables.scss';
.fan-club-card {
&__title {
width: 100%;
max-width: 298px;
}
&__divider {
width: 100%;
height: 1px;
background: $gray5;
max-width: 252px;
}
&__cover {
width: 166px;
height: 166px;
border-radius: 50% !important;
margin-bottom: 0;
@include mobile {
width: 112px;
height: 112px;
}
.ant-upload {
border-radius: 50%;
}
img {
border-radius: 50% !important;
}
}
.btn-delete {
position: absolute;
top: 0;
right: 0;
}
}

View File

@ -0,0 +1,15 @@
.image-skeleton {
img {
object-fit: cover;
//object-position: top;
}
.avatar-profile__text {
font-weight: 500;
}
&--circle {
img {
border-radius: 100% !important;
}
}
}

View File

@ -0,0 +1,135 @@
@import './variables.scss';
@media (max-width: 768px) {
.module-editor__title--count {
position: absolute;
top: 24px;
margin-left: 0;
}
.module-editor__title--input {
font-size: 18px;
font-weight: 600;
height: 36px;
width: 100%;
}
}
.module-editor {
&__title {
min-width: 380px;
position: relative;
}
&__title--count {
position: absolute;
left: 0;
top: 28px;
color: $dark !important;
opacity: 0, 5;
margin-left: 65px;
}
&__title--input {
font-size: 20px;
font-weight: 500;
padding: 0;
caret-color: $default !important;
}
&__title--label {
line-height: 32px !important;
}
&__acitons {
border-radius: 16px;
border-right: none;
-webkit-border-radius: 16px;
-moz-border-radius: 16px;
-webkit-box-shadow: $boxShadow2;
-moz-box-shadow: $boxShadow2;
box-shadow: $boxShadow2;
.ant-list-header {
width: 212px;
height: 46px;
padding: 11px 20px;
}
.ant-list-item {
width: 212px;
height: 56px;
padding: 16px 20px;
cursor: pointer;
&:hover {
background-color: rgba(37, 127, 252, 0.05);
}
&:first-child {
border-radius: 16px 16px 0 0;
}
&:last-child {
border-radius: 0 0 16px 16px;
border-end-end-radius: 16px !important;
}
}
}
&__wrapper--with-bottom-actions {
.pannel-collapse .ant-collapse-item {
border-radius: 16px 16px 0 0 !important;
}
}
}
.module-editor-overlay {
padding-top: 0;
margin-top: -4px;
.ant-popover-inner {
border-radius: 8px;
}
.ant-popover-inner-content {
border-radius: 8px;
padding: 0;
}
}
.ant-select-dropdown {
z-index: 99999;
}
.ant-popover-arrow {
display: none;
}
.module-title-row {
flex-shrink: 1;
max-width: 100%;
min-width: 100px;
button {
width: 100%;
div {
width: 100%;
&.ant-typography {
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.module-editor-bottom-actions {
width: 100%;
display: flex;
justify-content: center;
border-radius: 0 0 16px 16px;
padding: 12px 20px;
background: var(--crt-sys-color-background-surface-lowest-default);
> button {
width: 100%;
}
}

View File

@ -0,0 +1,55 @@
@import 'variables.scss';
.popover-menu-actions {
border-radius: 16px;
border-right: none;
-webkit-border-radius: 16px;
-moz-border-radius: 16px;
-webkit-box-shadow: $boxShadow2;
-moz-box-shadow: $boxShadow2;
box-shadow: $boxShadow2;
.ant-list-item {
width: 204px;
height: 46px;
padding: 12px;
cursor: pointer;
&:hover {
background-color: rgba(37, 127, 252, 0.05);
}
&:first-child {
border-radius: 16px 16px 0 0;
}
&:last-child {
border-radius: 0 0 16px 16px !important;
}
&.title {
padding-bottom: 8px;
border-bottom: none;
&:hover {
background-color: transparent;
}
}
}
}
.popover-menu-overlay {
padding-top: 0;
margin-top: -4px;
.ant-popover-inner {
border-radius: 16px;
}
.ant-popover-inner-content {
border-radius: 16px;
padding: 0;
}
}
.ant-select-dropdown {
z-index: 99999;
}
.ant-popover-arrow {
display: none;
}

View File

@ -0,0 +1,24 @@
@import './variables.scss';
.progress {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.ant-progress {
&-inner {
width: 32px !important;
height: 32px !important;
svg path {
stroke-width: 10px !important;
}
}
}
.ant-typography {
font-size: 10px !important;
color: rgba($default, 0.5);
}
}

View File

@ -0,0 +1,62 @@
@import './variables';
.on-demand-delete-confirm-modal {
overflow: hidden;
}
.shopify-product-card {
overflow: hidden;
.price {
color: $grey4;
}
&__picture {
img,
.ant-skeleton-avatar {
width: 82px !important;
height: 82px !important;
object-fit: cover;
border-right: 1px solid $grey2;
}
}
&__info {
padding: 0 16px;
width: 334px;
&--content {
.ondemand-drag-btn {
padding-right: 0;
}
&__status {
font-size: 12px;
line-height: 20px;
padding: 0 6px;
color: #ffffff;
display: inline-block;
border-radius: 4px;
&--active {
background-color: $green;
}
&--draft {
background-color: $gray8;
}
&--unpublished {
background-color: $yellow;
}
}
}
}
&__title {
max-width: 224px;
}
&__empty {
width: 82px;
height: 82px;
background-color: $gray2;
border-right: 1px solid $grey2;
}
}

View File

@ -0,0 +1,69 @@
import { Skeleton, Avatar } from 'antd'
import React, { useEffect, useState } from 'react'
import '@/app/css/ImageSkeleton.scss'
interface IProps {
src: string | undefined
imageClassName?: string
skeletonClassName?: string
shape?: 'square' | 'circle'
size?: number | 'small' | 'large' | 'default' | undefined
imgWidth?: string | number
name?: string
}
const ImageSkeleton = ({
src,
imageClassName = '',
skeletonClassName = '',
shape = 'square',
size = 'large',
imgWidth = '24',
name
}: IProps) => {
const [isLoaded, setLoaded] = useState(false)
const [isError, setError] = useState(false)
const imgEl: any = React.useRef(null)
useEffect(() => {
if (imgEl.current?.complete) {
setLoaded(true)
}
}, [])
return (
<div className={`image-skeleton image-skeleton--${shape}`}>
{src && (
<img
ref={imgEl}
className={isLoaded ? imageClassName : 'd--none'}
src={src}
alt="avatar"
onLoad={() => setLoaded(true)}
onError={() => {
setLoaded(false)
setError(true)
}}
style={{
width: `${imgWidth}px`,
height: `${imgWidth}px`
}}
/>
)}
{isError && name ? (
<Avatar className="avatar-profile__text" shape={shape} size={size}>
NAME
</Avatar>
) : (
<Skeleton.Avatar
className={isLoaded && !!src ? 'd--none' : skeletonClassName}
active={!isLoaded && !!src}
shape={shape}
size={size}
></Skeleton.Avatar>
)}
</div>
)
}
export default ImageSkeleton

View File

@ -0,0 +1,100 @@
import React, { useCallback } from "react";
import { Button, List, Popover } from "antd";
import { Paragraph } from "@/components/Typography/Paragraph";
import { TypographyPresets } from "@/components/Typography/Preset";
import "@/app/css/ModuleEditor.scss"; // use styles from the original source file
export type PopoverMenuItem = {
key: string;
label: string;
onClick?: () => void;
className?: string;
itemClassName?: string;
labelPreset?: TypographyPresets;
};
interface ModuleEditorProps {
children: React.ReactNode;
data?: PopoverMenuItem[];
title?: string;
visible?: boolean; // controlled visibility (v4: visible, v5: open)
onChangeVisible?: (value: boolean) => void; // setter for visibility
}
const ModuleEditor = ({
children,
data = [],
title,
visible,
onChangeVisible,
}: ModuleEditorProps) => {
const handleClickItem = useCallback(
(item: PopoverMenuItem) => () => {
// mirror original: close first, then execute the action
onChangeVisible?.(false);
item.onClick?.();
},
[onChangeVisible]
);
const renderItems = () =>
data.map((item) => (
<List.Item
className={item.itemClassName}
key={item.key}
onClick={handleClickItem(item)}
>
<Paragraph
ellipsis
preset={item.labelPreset || "semibold14"}
className={item.className}
>
{item.label}
</Paragraph>
</List.Item>
));
// Support both antd v4 and v5 props (harmless on either)
const popProps: any = {
placement: "bottomRight", // original placement
overlayClassName: "module-editor-overlay", // original overlay class
trigger: ["click"], // original trigger
arrow: false, // v5 way to hide arrow
arrowContent: null, // v4 compatibility (noop on v5)
content: (
<List className="popover-menu-actions module-editor__acitons">
{title && (
<List.Item className="title">
<Paragraph className="opacity--06" preset="semibold14">
{title}
</Paragraph>
</List.Item>
)}
{renderItems()}
</List>
),
// controlled visibility
visible, // v4
onVisibleChange: onChangeVisible,
open: visible, // v5
onOpenChange: onChangeVisible,
};
return (
<Popover {...popProps}>
<Button
className="p--0 d--flex align__items--center height--24 min-h-auto"
type="link"
onClick={(e) => {
// keep scope-local toggle; match original stopPropagation behavior
e.stopPropagation();
onChangeVisible?.(true);
}}
>
{children}
</Button>
</Popover>
);
};
export default ModuleEditor;

View File

@ -0,0 +1,89 @@
import { Button, List, Popover } from "antd";
import { Paragraph } from "@/components/Typography/Paragraph";
import { TypographyPresets } from "@/components/Typography/Preset";
import * as React from "react";
import { useCallback } from "react";
import "@/app/css/PopoverMenu.scss";
export type PopoverMenuItem = {
key: string;
label: string;
onClick?: () => void;
className?: string;
itemClassName?: string;
labelPreset?: TypographyPresets;
};
interface PopoverMenuProps {
children: React.ReactNode;
data?: PopoverMenuItem[];
title?: string;
visible?: boolean;
onChangeVisible?: (value: boolean) => void;
}
const PopoverMenu = ({
children,
data = [],
title,
visible,
onChangeVisible,
}: PopoverMenuProps) => {
const handleClickItem = useCallback(
(item) => () => {
onChangeVisible?.(false);
item.onClick?.();
},
[data]
);
const renderItems = () => {
return data.map((item) => (
<List.Item
className={item.itemClassName}
key={item.key}
onClick={handleClickItem(item)}
>
<Paragraph
ellipsis={true}
preset={item.labelPreset || "semibold14"}
className={item.className}
>
{item.label}
</Paragraph>
</List.Item>
));
};
return (
<Popover
visible={visible}
placement="top"
overlayClassName="popover-menu-overlay"
arrowContent={null}
content={
<List className="popover-menu-actions ">
{title && (
<List.Item className="title">
<Paragraph className="opacity--06" preset="semibold14">
{title}
</Paragraph>
</List.Item>
)}
{renderItems()}
</List>
}
trigger={["click"]}
onVisibleChange={onChangeVisible}
>
<Button
className="p--0 d--flex align__items--center height--24 min-h-auto"
type="link"
onClick={() => onChangeVisible?.(true)}
>
{children}
</Button>
</Popover>
);
};
export default PopoverMenu;

View File

@ -0,0 +1,92 @@
import React, { useState } from 'react'
import { Button, Col, Row } from 'antd'
import ImageSkeleton from '@/components/ImageSkeleton/ImageSkeleton'
import { Text } from '@/components/Typography/Text'
import { ShopifyProductItem } from '@/models/talent/talent-profile-module.model'
interface Props {
index?: number
item: ShopifyProductItem
showPrice?: boolean
disableDragDrop?: boolean
type: number
onEdit?: (item: ShopifyProductItem) => void
onDelete?: (item: ShopifyProductItem) => void
active?: boolean
}
const PRODUCT_TYPE_INDIVIDUAL = 1
const ShopifyProductCard: React.FC<Props> = ({
item,
showPrice = true,
disableDragDrop = false,
type,
onDelete
}) => {
const variant = item?.variants?.reduce(function (prev, curr) {
return parseFloat(prev.price) < parseFloat(curr.price) ? prev : curr
})
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const onDeleteConfirm = () => {
typeof onDelete === 'function' && onDelete(item)
setShowDeleteConfirm(false)
}
return (
<React.Fragment>
{item && (
<div className="border-box d--flex shopify-product-card border__radius--8">
<div className="shopify-product-card__picture">
{item.images?.[0]?.src ? (
<ImageSkeleton src={item.images?.[0]?.src as string} />
) : (
<Row
align="middle"
justify="center"
className="shopify-product-card__empty"
>
</Row>
)}
</div>
<div className="shopify-product-card__info flex--1 d--flex flex__direction--column p__x--16 p__y--16">
<Row align="middle">
<Col flex="1">
<Text preset="semibold16">{item.title}</Text>
{showPrice && (
<div className="d--flex align__items--center justify__content--between m__t--4">
<Text preset="regular14" className="price">
</Text>
</div>
)}
</Col>
<Col
className={
type !== PRODUCT_TYPE_INDIVIDUAL ? undefined : 'd--none'
}
>
<Row wrap={false} gutter={16} align="middle">
<Col>
<Button
className="p--0 d--flex align__items--center height--24 min-h-auto"
type="link"
onClick={() => setShowDeleteConfirm(true)}
>
</Button>
</Col>
<Col>
</Col>
</Row>
</Col>
</Row>
</div>
</div>
)}
</React.Fragment>
)
}
export default ShopifyProductCard

View File

@ -0,0 +1,28 @@
'use client'
import React from 'react'
import { Progress as ProgressAntd, Typography } from 'antd'
import '@/app/css/Progress.scss'
const { Text } = Typography
interface IProps {
value: number | undefined
}
const Progress = ({ value }: IProps) => {
return (
<div className="progress">
<ProgressAntd
showInfo={false}
type="circle"
strokeColor={'#50c32c'}
percent={value}
status="active"
/>
<Text className="m__t--4">{`${value || 15}%`}</Text>
</div>
)
}
export default Progress

View File

@ -0,0 +1,463 @@
"use client";
import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import { Upload } from "antd";
import { Col, Row } from "antd/lib/grid";
import "../css/App.scss"
import "../css/base/spacing.scss"
import "../css/base/modal.scss"
import "../css/base/tabs.scss"
import "../css/base/text.scss"
import "../css/base/display.scss"
import "../css/base/form.scss"
import "../css/base/button.scss"
import "../css/onboardingFlow.scss"
import "../css/LinkEditorModal.scss";
import "../css/ProductEditorModal.scss";
import "../css/ModuleOndemandVideo.scss";
import "../css/ModuleShopify.scss"
import Tabs from "antd/lib/tabs";
import Button from "antd/lib/button";
import Alert from "antd/lib/alert";
import * as yup from "yup";
import classNames from "classnames";
import { Field, Form, Formik, FormikProps } from "formik";
import Modal from "@/components/Modal/Modal"; // Adjust this path as needed
import { Text } from "@/components/Typography/Text";
import ImageUpload from "@/components/ImageUpload/ImageUpload";
import { AntInput, FieldType } from "@/components/Form/FormItem";
import { LinkItem } from "@/models/talent/talent-profile-module.model";
//import icon
import InfoIcon from "@/icons/Infoicon";
import CloseOutlineIcon from "@/icons/Closeoutlineicon";
const initialProductValues: LinkItem = {
title: "",
url: "",
order: 0,
visible: true,
};
const initialSpecialOfferValues: LinkItem = {
title: "",
url: "",
order: 0,
specialOffer: {
thumbnail: "",
title: "",
storeUrl: "",
couponCode: "",
},
visible: true,
};
const LINK_TYPES = {
NORMAL: "NORMAL",
SPECIAL_OFFER: "SPECIAL_OFFER",
};
const Icon = ({ className, name, width, height }: { className?: string; name: string; width?: number; height?: number }) => {
return (
<span className={className} style={{ width, height }}>
[icon: {name}]
</span>
);
};
const dummyPhoto = {
url: "https://via.placeholder.com/369", // static test image
fileName: "placeholder.jpg",
loading: false,
};
const onUploadSuccess = (file: any) => {
console.log("Upload success:", file);
};
const onRemove = (file: any) => {
console.log("Removed:", file);
};
const validateLinkSchema = yup.object().shape({
title: yup.string().required("Please enter the link title"),
url: yup
.string()
.required("Please enter the URL")
.url("Please enter a valid URL"),
});
const validateSpecialOfferSchema = yup.object().shape({
specialOffer: yup.object().shape({
title: yup
.string()
.required("Please add the offer information")
.test("len", "Offer not more than 50 characters", (val) => {
if (val == undefined) {
return true;
}
return val.length == 0 || (val.length >= 1 && val.length <= 50);
}),
storeUrl: yup
.string()
.required("Please enter the URL")
.url("Please enter a valid URL"),
}),
});
export type LinkEditorModalFormValues = Pick<
LinkItem,
"thumbnail" | "url" | "title" | "specialOffer" | "order" | "visible"
>;
export default function Page() {
const [isModalOpen, setIsModalOpen] = useState(false);
// Dummy state vars to eliminate TS errors
const [linkType, setLinkType] = useState<string>(LINK_TYPES.NORMAL);
const [isEdit, setIsEdit] = useState(false);
const [showAlert, setShowAlert] = useState(true);
const [initialValues, setInitialValues] = useState<LinkEditorModalFormValues>();
function handleSubmit(values: LinkEditorModalFormValues, formikHelpers: FormikHelpers<LinkEditorModalFormValues>): void | Promise<any> {
throw new Error("Function not implemented.");
}
const dummyOptions = [
{ label: "Option A", value: "A" },
{ label: "Option B", value: "B" },
];
const [modalTop, setModalTop] = useState<number>(); // default safe gap
const modalRef = useRef<HTMLDivElement | null>(null);
let newTop: number;
//CUSTOM LOGIC: HANDLE THE TOP OF THE MODAL OF DIFFERENT PLATFORMS
useEffect(() => {
const updateModalTop = () => {
const modalEl = document.querySelector('.link-editor-modal') as HTMLElement;
if (!modalEl) return;
const modalHeight = modalEl.getBoundingClientRect().height;
const windowHeight = window.innerHeight;
const isMobile = window.innerWidth <= 768;
//PLATFORM LOGIC
if(!isMobile) {
if (modalHeight + 60 > windowHeight) {
newTop = 20;
setModalTop(newTop);
//console.log('DESKTOP OVERFLOW');
} else {
//newTop = Math.max((windowHeight - modalHeight) / 4, 30);
newTop = 20;
setModalTop(newTop);
//console.log('DESKTOP CENTERED');
}
}
else {
if (modalHeight + 60 > windowHeight) {
newTop = 20;
setModalTop(newTop);
//console.log('MOBILE OVERFLOW');
} else {
//newTop = 20;
//setModalTop(newTop);
newTop = Math.max( (windowHeight / 2) - (2.4 * modalHeight), 30);
setModalTop(newTop);
//modalEl.style.removeProperty('top');
//console.log('MOBILE CENTERED');
}
}
modalEl.style.top = `${newTop}px`; // 🔥 Direct DOM update
};
if (isModalOpen) {
setTimeout(updateModalTop, 100); // Wait for DOM to fully render
window.addEventListener("resize", updateModalTop);
}
return () => {
window.removeEventListener("resize", updateModalTop);
};
}, [isModalOpen, linkType]);
useLayoutEffect(() => {
const adjustMargin = () => {
if (!isModalOpen) return;
if (linkType === LINK_TYPES.SPECIAL_OFFER) {
//console.log('SPECIAL')
const form = document.querySelector(".offer-form-item") as HTMLElement;
const inputBox = form.getElementsByClassName('ant-form-item-has-success')[0] as HTMLElement;
const hasError = form.querySelector(".ant-form-item-explain-error") as HTMLElement;
if (inputBox) {
inputBox.style.marginBottom = hasError ? "24px" : "0px";
}
}
if (linkType === LINK_TYPES.NORMAL) {
//console.log('NORMAL')
const form = document.querySelector(".link-editor-form") as HTMLElement;
const inputBoxes = form?.getElementsByClassName("ant-form-item-has-success");
const inputBox = inputBoxes?.[1] as HTMLElement;
if (inputBox) {
inputBox.style.marginBottom = "24px";
}
}
};
// Delay just until after DOM mounts, but before paint
requestAnimationFrame(() => {
adjustMargin();
});
}, [linkType, isModalOpen]);
//DEALING WITH ERROR LABELS
const [errorActivated, setErrorActivated] = useState(false);
useEffect(() => {
if (!isModalOpen) return;
const formRoot = document.querySelector(".offer-form-item");
if (!formRoot) return;
const observer = new MutationObserver(() => {
const errorLabels = formRoot.querySelectorAll(".ant-form-item-explain-error");
// ✅ You can react to the presence of ANY error
if (errorLabels.length > 0) {
//console.log("🚨 Error label activated");
// Optional: do something here
setErrorActivated(true)
} else {
console.log("✅ No errors");
}
});
observer.observe(formRoot, {
childList: true,
subtree: true,
});
return () => observer.disconnect();
}, [isModalOpen, linkType]);
//TRIGGER FOR ACTIVATED ERROR
useEffect(() => {
if(errorActivated) {
console.log('ERROR ACTIVATED');
const form = document.querySelector(".offer-form-item") as HTMLElement;
const inputBox = form.getElementsByClassName('ant-form-item')[0] as HTMLElement;
if (inputBox) {
inputBox.style.marginBottom = "24px";
}
}
}, [errorActivated]);
return (
<>
{/** INLINE ROOT STYLING */}
<style
data-rc-order="append"
rc-util-key="-ant-1753973781591-0.9765637550520021-dynamic-theme"
dangerouslySetInnerHTML={{
__html:
"\n :root {\n --ant-primary-color: rgb(18, 18, 18);\n--ant-primary-color-disabled: #454545;\n--ant-primary-color-hover: #1f1f1f;\n--ant-primary-color-active: #000000;\n--ant-primary-color-outline: rgba(18, 18, 18, 0.2);\n--ant-primary-color-deprecated-bg: #454545;\n--ant-primary-color-deprecated-border: #2b2b2b;\n--ant-primary-1: #525252;\n--ant-primary-2: #454545;\n--ant-primary-3: #383838;\n--ant-primary-4: #2b2b2b;\n--ant-primary-5: #1f1f1f;\n--ant-primary-6: #121212;\n--ant-primary-7: #000000;\n--ant-primary-8: #000000;\n--ant-primary-9: #000000;\n--ant-primary-10: #000000;\n--ant-primary-color-deprecated-l-35: rgb(107, 107, 107);\n--ant-primary-color-deprecated-l-20: rgb(69, 69, 69);\n--ant-primary-color-deprecated-t-20: rgb(65, 65, 65);\n--ant-primary-color-deprecated-t-50: rgb(137, 137, 137);\n--ant-primary-color-deprecated-f-12: rgba(18, 18, 18, 0.12);\n--ant-primary-color-active-deprecated-f-30: rgba(82, 82, 82, 0.3);\n--ant-primary-color-active-deprecated-d-02: rgb(77, 77, 77);\n--ant-success-color: rgba(255, 255, 255, 0.1);\n--ant-success-color-disabled: #ffffff;\n--ant-success-color-hover: #ffffff;\n--ant-success-color-active: #b3b3b3;\n--ant-success-color-outline: rgba(255, 255, 255, 0.2);\n--ant-success-color-deprecated-bg: #ffffff;\n--ant-success-color-deprecated-border: #ffffff;\n--ant-info-color: rgb(255, 255, 255);\n--ant-info-color-disabled: #ffffff;\n--ant-info-color-hover: #ffffff;\n--ant-info-color-active: #b3b3b3;\n--ant-info-color-outline: rgba(255, 255, 255, 0.2);\n--ant-info-color-deprecated-bg: #ffffff;\n--ant-info-color-deprecated-border: #ffffff;\n }\n "
}}
/>
<p>1: <a href="http://localhost:3000">Home</a></p>
<button onClick={() => setIsModalOpen(true)}>Open Modal</button>
<Modal
open={isModalOpen}
width={664}
closable
style={{
marginLeft: "auto",
marginRight: "auto",
top: modalTop,
}}
className="link-editor-modal m__t--24"
title="Add New Link"
onCancel={() => setIsModalOpen(false)}
maskClosable={false}
ref={modalRef}
>
<Tabs
onChange={(activeKey) => setLinkType(activeKey)}
activeKey={linkType}
className={isEdit ? "is-edit" : ""}
>
{!isEdit && (
<Tabs.TabPane tab={<Text>Link</Text>} key={LINK_TYPES.NORMAL} />
)}
{!isEdit && (
<Tabs.TabPane
tab={<Text>Special offers</Text>}
key={LINK_TYPES.SPECIAL_OFFER}
>
{showAlert && (
<Alert
className={classNames(
"m__t--24 p__x--8 p__y--8 bg--darkGray shopify-module__alert"
)}
description={
"You can add special offer links and discount codes. When a customer clicks on the link, they will be presented with a popup where theyll be able to copy the discount code (if applicable) and directed to the website."
}
icon={<InfoIcon width={24} height={24} />}
showIcon
closeText={
<CloseOutlineIcon width={24} height={24} name="close-outline" />
}
onClose={() => {
setShowAlert(false);
localStorage.setItem(
"CLOSE_ALERT_ADD_SPECIAL_OFFER",
"true"
);
}}
/>
)}
</Tabs.TabPane>
)}
</Tabs>
<div>
{/* IMAGE UPLOAD BUTTON */}
<div className={isEdit ? "" : "m__y--32"}>
<Row align="middle" justify="center">
<Col>
<ImageUpload
className="product-editor__cover m__b--20"
photo={dummyPhoto}
onUploadSuccess={onUploadSuccess}
onRemove={onRemove}
dropAspect={1}
/>
</Col>
<Col className="m__b--4" span={24}>
<Text className="d--block text__align--center" preset="semibold18">
Thumbnail Photo
</Text>
</Col>
<Col span={24}>
<Text
className="d--block text__align--center text--grey4"
preset="regular14"
>
{/* //TODO */}
Use a size thats at least 369 x 369 pixels and 6MB or less
</Text>
</Col>
</Row>
</div>
<Formik
initialValues={initialValues as LinkEditorModalFormValues}
onSubmit={handleSubmit}
validationSchema={
linkType === LINK_TYPES.NORMAL
? validateLinkSchema
: validateSpecialOfferSchema
}
>
<>
<Form
className="link-editor-form"
>
{linkType === LINK_TYPES.NORMAL ? (
<>
<Field
component={AntInput}
name="url"
type={FieldType.input}
label="URL"
placeholder="https://example.com"
/>
<Field
component={AntInput}
name="title"
type={FieldType.input}
label="Title"
placeholder="Enter title of the link"
/>
</>
) : (
<>
<Field
component={AntInput}
name="specialOffer.storeUrl"
type={FieldType.input}
label="URL"
placeholder="https://example.com"
className="m__b--20"
/>
<Field
component={AntInput}
name="specialOffer.title"
type={FieldType.input}
label="What is the offer?"
placeholder="Add the offer information"
className="offer-form-item"
/>
<Text
id="discountLabel" className="d--block m__t--8 m__b--20 opacity--60" preset="regular14">{`0/50 characters`}</Text>
<Field
component={AntInput}
name="specialOffer.couponCode"
type={FieldType.input}
label="Discount code (optional)"
placeholder="Add the discount code"
/>
</>
)}
</Form>
<div className="link-editor__actions m__t--16">
<Button
className="ant-btn-xl ant-btn-uppercase"
>
Cancel
</Button>
<Button
className="ant-btn-xl ant-btn-uppercase"
type="primary"
>
Done
</Button>
</div>
</>
</Formik>
</div>
</Modal>
</>
);
}

View File

@ -0,0 +1,95 @@
'use client';
import React, { useState, useEffect } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Button from 'antd/lib/button';
import { Paragraph } from '@/components/Typography/Paragraph';
import { Text } from '@/components/Typography/Text';
// ⛔ Mock useWindowSize (since actual hook isn't available)
function useWindowSize() {
const [width, setWidth] = useState<number | undefined>(undefined);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
handleResize(); // Set initial
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return { width };
}
// ✅ Simulated store (mocked useProfileStore)
const useProfileStore = () => {
const [modules, setModules] = useState<any[]>([]);
const addModule = () => {
const newModule = {
id: Date.now(),
title: 'New Module',
description: 'Describe your module here',
};
setModules([...modules, newModule]);
};
return { modules, addModule };
};
export default function EmptyModulePage() {
const { modules, addModule } = useProfileStore();
const { width } = useWindowSize();
const isMobileStyleChangesEnabled = width !== undefined && width <= 768;
return (
<div className={isMobileStyleChangesEnabled ? 'p__t--12' : 'p__t--24'}>
{modules.length === 0 ? (
<Row
data-testid="module--empty"
gutter={[10, 10]}
className="text__align--center"
>
<Col span={24}>
</Col>
<Col span={24}>
<Text
data-testid="empty-module--caption"
preset="semibold18"
className="d--block m__b--4"
>
Nothing Here Yet
</Text>
<Paragraph
data-testid="empty-module--caption-cta"
className="opacity--60 leading-22"
>
Click the button below to get started
</Paragraph>
</Col>
<Col span={24}>
<Button type="link" onClick={addModule}>
<div className="d--flex justify__content--center align__items--center">
<Text preset="semibold18" className="text--blue m__l--4">
Add Module
</Text>
</div>
</Button>
</Col>
</Row>
) : (
<div className="p-8 max-w-xl mx-auto space-y-2">
<h2 className="text-xl font-bold">Modules:</h2>
<ul className="list-disc list-inside">
{modules.map((mod) => (
<li key={mod.id}>
<span className="font-medium">{mod.title}</span> - {mod.description}
</li>
))}
</ul>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,76 @@
"use client";
import { Col, Row } from 'antd'
import classNames from 'classnames'
import * as React from 'react'
import { useCallback, useEffect, useState } from 'react'
import { Paragraph } from '@/components/Typography/Paragraph'
import '../css/FanClubCard.scss'
interface FanClubCardProps {
title?: string
subTitle?: string
image?: string
onSaveFanClub?: (data: any) => void
}
const FanClubCard = ({
title,
subTitle,
image,
onSaveFanClub
}: FanClubCardProps) => {
const [photo, setPhoto] = useState<MediaUpload>({})
useEffect(() => {
setPhoto({ url: image })
}, [])
const uploadPhotoSuccess = useCallback(
(file: MediaUpload) => {
setPhoto(file)
const data = {
order: 0,
thumbnail: file.url
}
onSaveFanClub?.(data)
},
[onSaveFanClub]
)
const removePhoto = useCallback(() => {
setPhoto({})
const data = {
order: 0,
thumbnail: ''
}
onSaveFanClub?.(data)
}, [onSaveFanClub])
return (
<div className="fan-club-card">
<Row align="middle">
<Col flex={'1'}>
<Paragraph
preset={'bold32'}
className={classNames(
'fan-club-card__title m__b--8',
'leading-42'
)}
>
{title}
</Paragraph>
<Paragraph
className="m__b--8 opacity--80 fan-club-card__title"
preset={'regular18'}
>
{subTitle}
</Paragraph>
</Col>
<Col>
<p>HELLO</p>
</Col>
</Row>
</div>
)
}
export default FanClubCard

View File

@ -0,0 +1,41 @@
"use client";
import React, { useState } from "react";
import PopoverMenu, { PopoverMenuItem } from "@/components/PopoverMenu/ModuleEditor";
import { Button } from "antd";
export default function Page() {
const [visible, setVisible] = useState(false);
const menuItems: PopoverMenuItem[] = [
{
key: "1",
label: "Profile",
onClick: () => alert("Profile clicked"),
},
{
key: "2",
label: "Settings",
onClick: () => alert("Settings clicked"),
},
{
key: "3",
label: "Logout",
onClick: () => alert("Logout clicked"),
className: "text-red-500",
},
];
return (
<div style={{ padding: "40px" }}>
<h2>Testing PopoverMenu</h2>
<PopoverMenu
title="Menu"
data={menuItems}
visible={visible}
onChangeVisible={setVisible}
>
<Button type="primary">Open Menu</Button>
</PopoverMenu>
</div>
);
}

View File

@ -0,0 +1,59 @@
'use client'
import React from 'react'
import { Button, Col, Row } from 'antd'
import { Text } from '@/components/Typography/Text'
export interface IProps {
isReloading: boolean
close: () => void
reload: () => void
}
const empty = () => ({
title: 'Select Product Type',
content: (
<>
<Row gutter={[10, 10]} className="p__y--24">
<Col span={24} className="text__align--center">
</Col>
<Col span={24} className="text__align--center">
<Text preset="semibold18" className="d--block m__b--4">
No Products Found
</Text>
<Text>
You have no products in your Shopify Store. To add products to your
Komi profile, you need to add them to your Shopify store first. You
can click reload once your products are added to refresh this page.
</Text>
</Col>
</Row>
</>
),
footer: (
<Row justify="end" gutter={[16, 16]}>
<Col>
<Button
size="large"
block
onClick={close}
className="text__transform--uppercase letter__spacing--1"
>
Close
</Button>
</Col>
<Col>
<Button
className="text__transform--uppercase letter__spacing--1"
type="primary"
block
size="large"
>
Reload
</Button>
</Col>
</Row>
)
})
export default empty

View File

@ -0,0 +1,463 @@
"use client";
import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import { Upload } from "antd";
import { Col, Row } from "antd/lib/grid";
import "../css/App.scss"
import "../css/base/spacing.scss"
import "../css/base/modal.scss"
import "../css/base/tabs.scss"
import "../css/base/text.scss"
import "../css/base/display.scss"
import "../css/base/form.scss"
import "../css/base/button.scss"
import "../css/onboardingFlow.scss"
import "../css/LinkEditorModal.scss";
import "../css/ProductEditorModal.scss";
import "../css/ModuleOndemandVideo.scss";
import "../css/ModuleShopify.scss"
import Tabs from "antd/lib/tabs";
import Button from "antd/lib/button";
import Alert from "antd/lib/alert";
import * as yup from "yup";
import classNames from "classnames";
import { Field, Form, Formik, FormikProps } from "formik";
import Modal from "@/components/Modal/Modal"; // Adjust this path as needed
import { Text } from "@/components/Typography/Text";
import ImageUpload from "@/components/ImageUpload/ImageUpload";
import { AntInput, FieldType } from "@/components/Form/FormItem";
import { LinkItem } from "@/models/talent/talent-profile-module.model";
//import icon
import InfoIcon from "@/icons/Infoicon";
import CloseOutlineIcon from "@/icons/Closeoutlineicon";
const initialProductValues: LinkItem = {
title: "",
url: "",
order: 0,
visible: true,
};
const initialSpecialOfferValues: LinkItem = {
title: "",
url: "",
order: 0,
specialOffer: {
thumbnail: "",
title: "",
storeUrl: "",
couponCode: "",
},
visible: true,
};
const LINK_TYPES = {
NORMAL: "NORMAL",
SPECIAL_OFFER: "SPECIAL_OFFER",
};
const Icon = ({ className, name, width, height }: { className?: string; name: string; width?: number; height?: number }) => {
return (
<span className={className} style={{ width, height }}>
[icon: {name}]
</span>
);
};
const dummyPhoto = {
url: "https://via.placeholder.com/369", // static test image
fileName: "placeholder.jpg",
loading: false,
};
const onUploadSuccess = (file: any) => {
console.log("Upload success:", file);
};
const onRemove = (file: any) => {
console.log("Removed:", file);
};
const validateLinkSchema = yup.object().shape({
title: yup.string().required("Please enter the link title"),
url: yup
.string()
.required("Please enter the URL")
.url("Please enter a valid URL"),
});
const validateSpecialOfferSchema = yup.object().shape({
specialOffer: yup.object().shape({
title: yup
.string()
.required("Please add the offer information")
.test("len", "Offer not more than 50 characters", (val) => {
if (val == undefined) {
return true;
}
return val.length == 0 || (val.length >= 1 && val.length <= 50);
}),
storeUrl: yup
.string()
.required("Please enter the URL")
.url("Please enter a valid URL"),
}),
});
export type LinkEditorModalFormValues = Pick<
LinkItem,
"thumbnail" | "url" | "title" | "specialOffer" | "order" | "visible"
>;
export default function Page() {
const [isModalOpen, setIsModalOpen] = useState(false);
// Dummy state vars to eliminate TS errors
const [linkType, setLinkType] = useState<string>(LINK_TYPES.NORMAL);
const [isEdit, setIsEdit] = useState(false);
const [showAlert, setShowAlert] = useState(true);
const [initialValues, setInitialValues] = useState<LinkEditorModalFormValues>();
function handleSubmit(values: LinkEditorModalFormValues, formikHelpers: FormikHelpers<LinkEditorModalFormValues>): void | Promise<any> {
throw new Error("Function not implemented.");
}
const dummyOptions = [
{ label: "Option A", value: "A" },
{ label: "Option B", value: "B" },
];
const [modalTop, setModalTop] = useState<number>(); // default safe gap
const modalRef = useRef<HTMLDivElement | null>(null);
let newTop: number;
//CUSTOM LOGIC: HANDLE THE TOP OF THE MODAL OF DIFFERENT PLATFORMS
useEffect(() => {
const updateModalTop = () => {
const modalEl = document.querySelector('.link-editor-modal') as HTMLElement;
if (!modalEl) return;
const modalHeight = modalEl.getBoundingClientRect().height;
const windowHeight = window.innerHeight;
const isMobile = window.innerWidth <= 768;
//PLATFORM LOGIC
if(!isMobile) {
if (modalHeight + 60 > windowHeight) {
newTop = 20;
setModalTop(newTop);
//console.log('DESKTOP OVERFLOW');
} else {
//newTop = Math.max((windowHeight - modalHeight) / 4, 30);
newTop = 20;
setModalTop(newTop);
//console.log('DESKTOP CENTERED');
}
}
else {
if (modalHeight + 60 > windowHeight) {
newTop = 20;
setModalTop(newTop);
//console.log('MOBILE OVERFLOW');
} else {
//newTop = 20;
//setModalTop(newTop);
newTop = Math.max( (windowHeight / 2) - (2.4 * modalHeight), 30);
setModalTop(newTop);
//modalEl.style.removeProperty('top');
//console.log('MOBILE CENTERED');
}
}
modalEl.style.top = `${newTop}px`; // 🔥 Direct DOM update
};
if (isModalOpen) {
setTimeout(updateModalTop, 100); // Wait for DOM to fully render
window.addEventListener("resize", updateModalTop);
}
return () => {
window.removeEventListener("resize", updateModalTop);
};
}, [isModalOpen, linkType]);
useLayoutEffect(() => {
const adjustMargin = () => {
if (!isModalOpen) return;
if (linkType === LINK_TYPES.SPECIAL_OFFER) {
//console.log('SPECIAL')
const form = document.querySelector(".offer-form-item") as HTMLElement;
const inputBox = form.getElementsByClassName('ant-form-item-has-success')[0] as HTMLElement;
const hasError = form.querySelector(".ant-form-item-explain-error") as HTMLElement;
if (inputBox) {
inputBox.style.marginBottom = hasError ? "24px" : "0px";
}
}
if (linkType === LINK_TYPES.NORMAL) {
//console.log('NORMAL')
const form = document.querySelector(".link-editor-form") as HTMLElement;
const inputBoxes = form?.getElementsByClassName("ant-form-item-has-success");
const inputBox = inputBoxes?.[1] as HTMLElement;
if (inputBox) {
inputBox.style.marginBottom = "24px";
}
}
};
// Delay just until after DOM mounts, but before paint
requestAnimationFrame(() => {
adjustMargin();
});
}, [linkType, isModalOpen]);
//DEALING WITH ERROR LABELS
const [errorActivated, setErrorActivated] = useState(false);
useEffect(() => {
if (!isModalOpen) return;
const formRoot = document.querySelector(".offer-form-item");
if (!formRoot) return;
const observer = new MutationObserver(() => {
const errorLabels = formRoot.querySelectorAll(".ant-form-item-explain-error");
// ✅ You can react to the presence of ANY error
if (errorLabels.length > 0) {
//console.log("🚨 Error label activated");
// Optional: do something here
setErrorActivated(true)
} else {
console.log("✅ No errors");
}
});
observer.observe(formRoot, {
childList: true,
subtree: true,
});
return () => observer.disconnect();
}, [isModalOpen, linkType]);
//TRIGGER FOR ACTIVATED ERROR
useEffect(() => {
if(errorActivated) {
console.log('ERROR ACTIVATED');
const form = document.querySelector(".offer-form-item") as HTMLElement;
const inputBox = form.getElementsByClassName('ant-form-item')[0] as HTMLElement;
if (inputBox) {
inputBox.style.marginBottom = "24px";
}
}
}, [errorActivated]);
return (
<>
{/** INLINE ROOT STYLING */}
<style
data-rc-order="append"
rc-util-key="-ant-1753973781591-0.9765637550520021-dynamic-theme"
dangerouslySetInnerHTML={{
__html:
"\n :root {\n --ant-primary-color: rgb(18, 18, 18);\n--ant-primary-color-disabled: #454545;\n--ant-primary-color-hover: #1f1f1f;\n--ant-primary-color-active: #000000;\n--ant-primary-color-outline: rgba(18, 18, 18, 0.2);\n--ant-primary-color-deprecated-bg: #454545;\n--ant-primary-color-deprecated-border: #2b2b2b;\n--ant-primary-1: #525252;\n--ant-primary-2: #454545;\n--ant-primary-3: #383838;\n--ant-primary-4: #2b2b2b;\n--ant-primary-5: #1f1f1f;\n--ant-primary-6: #121212;\n--ant-primary-7: #000000;\n--ant-primary-8: #000000;\n--ant-primary-9: #000000;\n--ant-primary-10: #000000;\n--ant-primary-color-deprecated-l-35: rgb(107, 107, 107);\n--ant-primary-color-deprecated-l-20: rgb(69, 69, 69);\n--ant-primary-color-deprecated-t-20: rgb(65, 65, 65);\n--ant-primary-color-deprecated-t-50: rgb(137, 137, 137);\n--ant-primary-color-deprecated-f-12: rgba(18, 18, 18, 0.12);\n--ant-primary-color-active-deprecated-f-30: rgba(82, 82, 82, 0.3);\n--ant-primary-color-active-deprecated-d-02: rgb(77, 77, 77);\n--ant-success-color: rgba(255, 255, 255, 0.1);\n--ant-success-color-disabled: #ffffff;\n--ant-success-color-hover: #ffffff;\n--ant-success-color-active: #b3b3b3;\n--ant-success-color-outline: rgba(255, 255, 255, 0.2);\n--ant-success-color-deprecated-bg: #ffffff;\n--ant-success-color-deprecated-border: #ffffff;\n--ant-info-color: rgb(255, 255, 255);\n--ant-info-color-disabled: #ffffff;\n--ant-info-color-hover: #ffffff;\n--ant-info-color-active: #b3b3b3;\n--ant-info-color-outline: rgba(255, 255, 255, 0.2);\n--ant-info-color-deprecated-bg: #ffffff;\n--ant-info-color-deprecated-border: #ffffff;\n }\n "
}}
/>
<p>1: <a href="http://localhost:3000">Home</a></p>
<button onClick={() => setIsModalOpen(true)}>Open Modal</button>
<Modal
open={isModalOpen}
width={664}
closable
style={{
marginLeft: "auto",
marginRight: "auto",
top: modalTop,
}}
className="link-editor-modal m__t--24"
title="Add New Link"
onCancel={() => setIsModalOpen(false)}
maskClosable={false}
ref={modalRef}
>
<Tabs
onChange={(activeKey) => setLinkType(activeKey)}
activeKey={linkType}
className={isEdit ? "is-edit" : ""}
>
{!isEdit && (
<Tabs.TabPane tab={<Text>Link</Text>} key={LINK_TYPES.NORMAL} />
)}
{!isEdit && (
<Tabs.TabPane
tab={<Text>Special offers</Text>}
key={LINK_TYPES.SPECIAL_OFFER}
>
{showAlert && (
<Alert
className={classNames(
"m__t--24 p__x--8 p__y--8 bg--darkGray shopify-module__alert"
)}
description={
"You can add special offer links and discount codes. When a customer clicks on the link, they will be presented with a popup where theyll be able to copy the discount code (if applicable) and directed to the website."
}
icon={<InfoIcon width={24} height={24} />}
showIcon
closeText={
<CloseOutlineIcon width={24} height={24} name="close-outline" />
}
onClose={() => {
setShowAlert(false);
localStorage.setItem(
"CLOSE_ALERT_ADD_SPECIAL_OFFER",
"true"
);
}}
/>
)}
</Tabs.TabPane>
)}
</Tabs>
<div>
{/* IMAGE UPLOAD BUTTON */}
<div className={isEdit ? "" : "m__y--32"}>
<Row align="middle" justify="center">
<Col>
<ImageUpload
className="product-editor__cover m__b--20"
photo={dummyPhoto}
onUploadSuccess={onUploadSuccess}
onRemove={onRemove}
dropAspect={1}
/>
</Col>
<Col className="m__b--4" span={24}>
<Text className="d--block text__align--center" preset="semibold18">
Thumbnail Photo
</Text>
</Col>
<Col span={24}>
<Text
className="d--block text__align--center text--grey4"
preset="regular14"
>
{/* //TODO */}
Use a size thats at least 369 x 369 pixels and 6MB or less
</Text>
</Col>
</Row>
</div>
<Formik
initialValues={initialValues as LinkEditorModalFormValues}
onSubmit={handleSubmit}
validationSchema={
linkType === LINK_TYPES.NORMAL
? validateLinkSchema
: validateSpecialOfferSchema
}
>
<>
<Form
className="link-editor-form"
>
{linkType === LINK_TYPES.NORMAL ? (
<>
<Field
component={AntInput}
name="url"
type={FieldType.input}
label="URL"
placeholder="https://example.com"
/>
<Field
component={AntInput}
name="title"
type={FieldType.input}
label="Title"
placeholder="Enter title of the link"
/>
</>
) : (
<>
<Field
component={AntInput}
name="specialOffer.storeUrl"
type={FieldType.input}
label="URL"
placeholder="https://example.com"
className="m__b--20"
/>
<Field
component={AntInput}
name="specialOffer.title"
type={FieldType.input}
label="What is the offer?"
placeholder="Add the offer information"
className="offer-form-item"
/>
<Text
id="discountLabel" className="d--block m__t--8 m__b--20 opacity--60" preset="regular14">{`0/50 characters`}</Text>
<Field
component={AntInput}
name="specialOffer.couponCode"
type={FieldType.input}
label="Discount code (optional)"
placeholder="Add the discount code"
/>
</>
)}
</Form>
<div className="link-editor__actions m__t--16">
<Button
className="ant-btn-xl ant-btn-uppercase"
>
Cancel
</Button>
<Button
className="ant-btn-xl ant-btn-uppercase"
type="primary"
>
Done
</Button>
</div>
</>
</Formik>
</div>
</Modal>
</>
);
}