SPACEBROWSER_INTERFACE/interface_components_final/LinkEditorModal/page.tsx

464 lines
15 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
</>
);
}