[FACTORY]: Sampling More Components For Isolation Testing
This commit is contained in:
parent
0301f3a0ab
commit
1acd94923b
@ -13,6 +13,7 @@
|
||||
"formik": "^2.4.6",
|
||||
"next": "15.4.5",
|
||||
"react": "19.1.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-dom": "19.1.0",
|
||||
"redux": "^5.0.1",
|
||||
"sass": "^1.89.2",
|
||||
|
0
component_factory/src/app/simple_modal/page.tsx → component_factory/src/app/LinkEditorModal/page.tsx
Normal file → Executable file
0
component_factory/src/app/simple_modal/page.tsx → component_factory/src/app/LinkEditorModal/page.tsx
Normal file → Executable file
20
component_factory/src/app/ShopifyProductCard/mockData.ts
Normal file
20
component_factory/src/app/ShopifyProductCard/mockData.ts
Normal 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,
|
||||
};
|
||||
|
49
component_factory/src/app/ShopifyProductCard/page.tsx
Normal file
49
component_factory/src/app/ShopifyProductCard/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
36
component_factory/src/app/css/FanClubCard.scss
Normal file
36
component_factory/src/app/css/FanClubCard.scss
Normal 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;
|
||||
}
|
||||
}
|
15
component_factory/src/app/css/ImageSkeleton.scss
Normal file
15
component_factory/src/app/css/ImageSkeleton.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
135
component_factory/src/app/css/ModuleEditor.scss
Normal file
135
component_factory/src/app/css/ModuleEditor.scss
Normal 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%;
|
||||
}
|
||||
}
|
55
component_factory/src/app/css/PopoverMenu.scss
Normal file
55
component_factory/src/app/css/PopoverMenu.scss
Normal 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;
|
||||
}
|
24
component_factory/src/app/css/Progress.scss
Normal file
24
component_factory/src/app/css/Progress.scss
Normal 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);
|
||||
}
|
||||
}
|
62
component_factory/src/app/css/ShopifyProductCard.scss
Normal file
62
component_factory/src/app/css/ShopifyProductCard.scss
Normal 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;
|
||||
}
|
||||
}
|
@ -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
|
100
component_factory/src/components/PopoverMenu/ModuleEditor.tsx
Normal file
100
component_factory/src/components/PopoverMenu/ModuleEditor.tsx
Normal 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;
|
89
component_factory/src/components/PopoverMenu/PopoverMenu.tsx
Normal file
89
component_factory/src/components/PopoverMenu/PopoverMenu.tsx
Normal 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;
|
@ -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
|
28
interface_components/Component_ProgressBar/page.tsx
Normal file
28
interface_components/Component_ProgressBar/page.tsx
Normal 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
|
463
interface_components_final/LinkEditorModal/page.tsx
Executable file
463
interface_components_final/LinkEditorModal/page.tsx
Executable 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 they’ll 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 that’s 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>
|
||||
</>
|
||||
);
|
||||
}
|
95
interface_factory_pages/EmptyModule/page.tsx
Normal file
95
interface_factory_pages/EmptyModule/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
76
interface_factory_pages/fanclubcard/page.tsx
Normal file
76
interface_factory_pages/fanclubcard/page.tsx
Normal 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
|
41
interface_factory_pages/popover/page.tsx
Normal file
41
interface_factory_pages/popover/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
59
interface_factory_pages/scratchbuild/page.tsx
Normal file
59
interface_factory_pages/scratchbuild/page.tsx
Normal 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
|
463
interface_factory_pages/simple_modal/page.tsx
Normal file
463
interface_factory_pages/simple_modal/page.tsx
Normal 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 they’ll 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 that’s 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>
|
||||
</>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user