[FACTORY]: Init Commit
This commit is contained in:
commit
ad09e57c41
28
component_factory/README.md
Executable file
28
component_factory/README.md
Executable file
@ -0,0 +1,28 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Purpose
|
||||
|
||||
sandbox project to build the reactive components for the website
|
||||
|
||||
|
||||
|
5
component_factory/next-env.d.ts
vendored
Executable file
5
component_factory/next-env.d.ts
vendored
Executable file
@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
7
component_factory/next.config.ts
Executable file
7
component_factory/next.config.ts
Executable file
@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
29
component_factory/package.json
Executable file
29
component_factory/package.json
Executable file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "interfacemod_project",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"antd": "^5.26.7",
|
||||
"formik": "^2.4.6",
|
||||
"next": "15.4.5",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"redux": "^5.0.1",
|
||||
"sass": "^1.89.2",
|
||||
"yup": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
5
component_factory/postcss.config.mjs
Executable file
5
component_factory/postcss.config.mjs
Executable file
@ -0,0 +1,5 @@
|
||||
const config = {
|
||||
plugins: ["@tailwindcss/postcss"],
|
||||
};
|
||||
|
||||
export default config;
|
1
component_factory/public/file.svg
Executable file
1
component_factory/public/file.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 391 B |
1
component_factory/public/globe.svg
Executable file
1
component_factory/public/globe.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
After Width: | Height: | Size: 1.0 KiB |
1
component_factory/public/next.svg
Executable file
1
component_factory/public/next.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
component_factory/public/vercel.svg
Executable file
1
component_factory/public/vercel.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
After Width: | Height: | Size: 128 B |
1
component_factory/public/window.svg
Executable file
1
component_factory/public/window.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
After Width: | Height: | Size: 385 B |
38
component_factory/src/app/css/App.scss
Normal file
38
component_factory/src/app/css/App.scss
Normal file
@ -0,0 +1,38 @@
|
||||
body, html {
|
||||
font-family: "SFProDisplay", sans-serif !important;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background-color: #f4f3f3;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #979797;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #979797;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
z-index: -1;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
body, html {
|
||||
font-family: "SFProDisplay", sans-serif !important;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background-color: #f4f3f3;
|
||||
}
|
||||
|
89
component_factory/src/app/css/AvailabilityCalendar.scss
Normal file
89
component_factory/src/app/css/AvailabilityCalendar.scss
Normal file
@ -0,0 +1,89 @@
|
||||
@import 'variables.scss';
|
||||
|
||||
.calendar-container {
|
||||
margin: 0 calc((100% - 64px) / 7 / 2 * -1);
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 18px;
|
||||
}
|
||||
|
||||
&__table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
min-height: 350px;
|
||||
|
||||
.month-slot {
|
||||
height: 82px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: $grey;
|
||||
}
|
||||
}
|
||||
|
||||
& tbody {
|
||||
min-height: 350px;
|
||||
}
|
||||
|
||||
& tbody > tr > td {
|
||||
text-align: center;
|
||||
cursor: default;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.day-content {
|
||||
user-select: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&__title {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
|
||||
.ant-typography {
|
||||
line-height: 17px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__dot {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -7px;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: $blue;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.disabled-day {
|
||||
.day-content__title {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-day {
|
||||
.day-content__title {
|
||||
background-color: $blue;
|
||||
> span {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
151
component_factory/src/app/css/ImageUpload.scss
Normal file
151
component_factory/src/app/css/ImageUpload.scss
Normal file
@ -0,0 +1,151 @@
|
||||
@import './variables.scss';
|
||||
|
||||
.photo-upload-item {
|
||||
width: 100%;
|
||||
height: 252px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.ant-upload-picture-card-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ant-upload.ant-upload-select-picture-card {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
background-color: $white;
|
||||
border: 1px dashed $gray4;
|
||||
|
||||
.ant-upload {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-upload--has-data {
|
||||
.ant-upload {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.photo-preview-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&__hover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: $borderRadius;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
border-radius: $borderRadius;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: -12px;
|
||||
z-index: 99;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background-color: $white;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12) !important;
|
||||
padding: 0;
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.photo-upload-item {
|
||||
height: 163px;
|
||||
|
||||
.profile-upload {
|
||||
width: 163px !important;
|
||||
|
||||
.ant-upload {
|
||||
height: 163px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//LINK EDITOR PHOTO PICTURE CARD
|
||||
.link-editor-modal {
|
||||
.ant-upload-select-picture-card {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
background-color: #fff;
|
||||
border: 1px dashed #636363;
|
||||
border-radius: 8px;
|
||||
|
||||
}
|
||||
|
||||
.ant-upload.ant-upload-select-picture-card .ant-upload {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ant-upload.ant-upload-select-picture-card .ant-upload {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ant-upload.ant-upload-select-picture-card .ant-upload {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ant-upload-picture-card-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
68
component_factory/src/app/css/LinkEditorModal.scss
Normal file
68
component_factory/src/app/css/LinkEditorModal.scss
Normal file
@ -0,0 +1,68 @@
|
||||
@import 'variables.scss';
|
||||
|
||||
.link-editor {
|
||||
&__cover {
|
||||
width: 123px !important;
|
||||
height: 123px !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
@include modalActions;
|
||||
}
|
||||
|
||||
&-modal {
|
||||
.offer-form-item {
|
||||
.ant-row {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs {
|
||||
&-nav-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.is-edit {
|
||||
.ant-tabs-ink-bar-animated {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-tab {
|
||||
padding: 0 0 8px;
|
||||
border-bottom: 1px solid $gray12;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
|
||||
span {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
color: $default;
|
||||
|
||||
&.disabled {
|
||||
color: $gray6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&-active {
|
||||
border-bottom: 1px solid $blue;
|
||||
}
|
||||
}
|
||||
|
||||
&-ink-bar {
|
||||
bottom: 0 !important;
|
||||
background: $blue;
|
||||
width: 50% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
54
component_factory/src/app/css/ModuleOndemandVideo.scss
Normal file
54
component_factory/src/app/css/ModuleOndemandVideo.scss
Normal file
@ -0,0 +1,54 @@
|
||||
@import 'variables.scss';
|
||||
|
||||
.ant-alert-info {
|
||||
background: $grey;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.on-demand-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
overflow: auto;
|
||||
padding-bottom: 16px;
|
||||
|
||||
&::-webkit-scrollbar:horizontal {
|
||||
width: 5px !important;
|
||||
height: 5px !important;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: $gray2;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
&__container {
|
||||
flex-grow: 1;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
&__dropzone {
|
||||
width: 100%;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
&__item {
|
||||
width: 100%;
|
||||
transition: border-box 0.1s ease;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&--dragging {
|
||||
.border-box {
|
||||
box-shadow: $boxShadow2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
102
component_factory/src/app/css/ModuleShopify.scss
Normal file
102
component_factory/src/app/css/ModuleShopify.scss
Normal file
@ -0,0 +1,102 @@
|
||||
@import 'variables.scss';
|
||||
|
||||
.module-shopify__tooltip {
|
||||
border-radius: 8px;
|
||||
max-width: 367px;
|
||||
padding-top: 2px;
|
||||
|
||||
@include mobile {
|
||||
max-width: 339px;
|
||||
}
|
||||
|
||||
&.ant-tooltip-placement-bottomLeft {
|
||||
.ant-tooltip-arrow {
|
||||
left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tooltip {
|
||||
&-inner {
|
||||
background: $white;
|
||||
border: 1px solid $grey2;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
font-size: $small;
|
||||
color: $text;
|
||||
margin-left: -28px;
|
||||
|
||||
@include mobile {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
top: -10px;
|
||||
|
||||
&-content {
|
||||
box-shadow: none;
|
||||
border: 1px solid $grey2;
|
||||
border-bottom-width: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.shopify-module__alert {
|
||||
.ant-alert-message {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ant-alert-description {
|
||||
font-size: $xl-small;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.24px;
|
||||
}
|
||||
}
|
||||
|
||||
.collection-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar:horizontal {
|
||||
width: 5px !important;
|
||||
height: 5px !important;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: $gray2;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
&__container {
|
||||
flex-grow: 1;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
&__dropzone {
|
||||
width: 100%;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
&__item {
|
||||
margin-bottom: 20px;
|
||||
transition: border-box 0.1s ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&--dragging {
|
||||
.border-box {
|
||||
box-shadow: $boxShadow2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
component_factory/src/app/css/ProductEditorModal.scss
Normal file
22
component_factory/src/app/css/ProductEditorModal.scss
Normal file
@ -0,0 +1,22 @@
|
||||
@import 'variables.scss';
|
||||
|
||||
.product-editor {
|
||||
&-form {
|
||||
.ant-form-item-control-input {
|
||||
input,
|
||||
textarea {
|
||||
height: 52px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__cover {
|
||||
width: 123px !important;
|
||||
height: 123px !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
@include modalActions;
|
||||
}
|
||||
}
|
51
component_factory/src/app/css/base/button.scss
Normal file
51
component_factory/src/app/css/base/button.scss
Normal file
@ -0,0 +1,51 @@
|
||||
.link-editor-modal
|
||||
{
|
||||
button.ant-btn {
|
||||
height: 36px;
|
||||
box-shadow: none !important;
|
||||
text-shadow: none;
|
||||
border-radius: 123px;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
border: 2px solid #e0e0e0;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
button.ant-btn-primary {
|
||||
border: none;
|
||||
color: #121212;
|
||||
background: #121212;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
border-radius: 123px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
button.ant-btn:focus, button.ant-btn:hover {
|
||||
color: #121212 !important;
|
||||
border: 2px solid #e0e0e0 !important;
|
||||
background-color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
button.ant-btn-primary:focus, button.ant-btn-primary:hover {
|
||||
border: none !important;
|
||||
background: rgba(18, 18, 18, .8) !important;
|
||||
}
|
||||
|
||||
button.ant-btn.ant-btn-xl {
|
||||
height: 52px;
|
||||
border-radius: 123px;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
button.ant-btn-uppercase>span {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
button.ant-btn-primary .ant-typography, button.ant-btn-primary span {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
}
|
7
component_factory/src/app/css/base/display.scss
Normal file
7
component_factory/src/app/css/base/display.scss
Normal file
@ -0,0 +1,7 @@
|
||||
.link-editor-modal {
|
||||
|
||||
.d--block {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
}
|
36
component_factory/src/app/css/base/form.scss
Normal file
36
component_factory/src/app/css/base/form.scss
Normal file
@ -0,0 +1,36 @@
|
||||
.link-editor-modal {
|
||||
|
||||
.ant-form-item {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ant-form-item-control-input input, .ant-form-item-control-input textarea {
|
||||
height: 52px;
|
||||
padding-left: 14px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.ant-input:placeholder-shown {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.m__b--20 {
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
.m__t--8 {
|
||||
margin-top: 8px !important;
|
||||
}
|
||||
|
||||
.opacity--60 {
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
/**@media only screen and (max-width: 600px) {
|
||||
.offer-form-item .ant-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}**/
|
||||
}
|
41
component_factory/src/app/css/base/modal.scss
Normal file
41
component_factory/src/app/css/base/modal.scss
Normal file
@ -0,0 +1,41 @@
|
||||
.link-editor-modal
|
||||
{
|
||||
|
||||
.ant-modal-header {
|
||||
background-color: rgba(96, 229, 232, .02);
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ant-modal-content {
|
||||
border-radius: 16px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ant-modal-title {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.ant-modal-header .ant-modal-title {
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.ant-modal-header .ant-modal-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
|
||||
.ant-modal-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
|
||||
.ant-modal .ant-form-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
32
component_factory/src/app/css/base/spacing.scss
Normal file
32
component_factory/src/app/css/base/spacing.scss
Normal file
@ -0,0 +1,32 @@
|
||||
.link-editor-modal
|
||||
{
|
||||
|
||||
.m__t--24 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.m__y--32 {
|
||||
margin-top: 32px !important;
|
||||
margin-bottom: 32px !important;
|
||||
}
|
||||
|
||||
.m__b--4 {
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
.p__y--8 {
|
||||
padding-top: 8px !important;
|
||||
padding-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.p__x--8 {
|
||||
padding-left: 8px !important;
|
||||
padding-right: 8px !important;
|
||||
}
|
||||
|
||||
.m__t--16 {
|
||||
margin-top: 16px !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
3
component_factory/src/app/css/base/tabs.scss
Normal file
3
component_factory/src/app/css/base/tabs.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.link-editor-modal .ant-tabs-tab, .ant-tabs-tab:hover {
|
||||
color: #121212;
|
||||
}
|
36
component_factory/src/app/css/base/text.scss
Normal file
36
component_factory/src/app/css/base/text.scss
Normal file
@ -0,0 +1,36 @@
|
||||
.link-editor-modal {
|
||||
.text {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #121212;
|
||||
}
|
||||
|
||||
.text__align--center {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.text--semibold18.ant-typography {
|
||||
font-weight: 600 !important;
|
||||
font-size: 18px !important;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.text--regular14.ant-typography {
|
||||
font-weight: 400 !important;
|
||||
font-size: 14px !important;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.text--gray4, .text--gray4.ant-typography, .text--grey4, .text--grey4.ant-typography {
|
||||
color: #636363 !important;
|
||||
}
|
||||
|
||||
.leading-22 {
|
||||
line-height: 22px !important;
|
||||
}
|
||||
|
||||
.text--tundola, .text--tundola.ant-typography {
|
||||
color: #414141 !important;
|
||||
}
|
||||
}
|
23
component_factory/src/app/css/onboardingFlow.scss
Normal file
23
component_factory/src/app/css/onboardingFlow.scss
Normal file
@ -0,0 +1,23 @@
|
||||
:root {
|
||||
--ktc-color-background-brandbar-default: #000000 !important;
|
||||
--ktc-color-background-button-medium-primary-default: #323134 !important;
|
||||
--ktc-color-background-button-medium-primary-hover: #47474b !important;
|
||||
--ktc-color-background-button-medium-primary-active: #5d5d62 !important;
|
||||
--ktc-color-background-button-medium-primary-disabled: #a6a5aa !important;
|
||||
--ktc-color-background-progress-tracker-default: #f1f1f2 !important;
|
||||
--ktc-color-background-progress-tracker-track: #d5d5d7 !important;
|
||||
--ktc-color-text-progress-tracker-step-label: #8a898f !important;
|
||||
--ktc-color-icon-progress-tracker-step-icon-future: #d5d5d7 !important;
|
||||
--ktc-color-border-progress-tracker-step-icon-present: #8a898f !important;
|
||||
--ktc-color-background-slider-track: #d5d5d7 !important;
|
||||
--ktc-color-border-slider-knob-default: #747279 !important;
|
||||
--ktc-color-border-slider-knob-hover: #8a898f !important;
|
||||
--ktc-color-border-slider-knob-active: #9d9ca1 !important;
|
||||
--ktc-color-border-chip-filter-selected-default: #747279 !important;
|
||||
--ktc-color-background-chip-filter-selected-default: #f1f1f2 !important;
|
||||
--ktc-color-background-chip-filter-selected-hover: #d5d5d7 !important;
|
||||
--ktc-color-background-chip-filter-selected-active: #c2c2c5 !important;
|
||||
--kts-color-border-outline-default: #d5d5d7 !important;
|
||||
--kts-color-border-card-default: #d5d5d7 !important;
|
||||
--ktc-color-text-card-default: #323134 !important;
|
||||
}
|
190
component_factory/src/app/css/variables.scss
Normal file
190
component_factory/src/app/css/variables.scss
Normal file
@ -0,0 +1,190 @@
|
||||
$default: #121212;
|
||||
$dark: #121212;
|
||||
$darkSidebar: #1e1f20;
|
||||
$primary: #60e5e8;
|
||||
$blue: #257ffc;
|
||||
$blue2: #5e64ff;
|
||||
$pink: #ff07c9;
|
||||
$white: #ffffff;
|
||||
$border: #e9e9eb;
|
||||
$border2: #e5e5e5;
|
||||
$black: #000000;
|
||||
$black2: #121727;
|
||||
$gray: #e6e7e9;
|
||||
$gray2: #f9f9f9;
|
||||
$gray3: #e4e4e4;
|
||||
$grey: #f4f3f3;
|
||||
$grey2: #e6e6e6;
|
||||
$grey3: #e0e0e0;
|
||||
$grey4: #636363;
|
||||
$gray2: #e6e6e6;
|
||||
$gray3: #e0e0e0;
|
||||
$gray4: #636363;
|
||||
$gray5: #f5f5f5;
|
||||
$gray6: #cdcdcd;
|
||||
$gray7: #e6e6e6;
|
||||
$gray8: #d3d3d3;
|
||||
$gray9: #e9e9eb;
|
||||
$gray10: #f4f3f3;
|
||||
$gray11: #f1f1f1;
|
||||
$gray12: #f3f3f3;
|
||||
$red: #ff3c45;
|
||||
$secondaryRed: #fc0d1b;
|
||||
$inputBorder: #e4e4ed;
|
||||
$checked: linear-gradient(360deg, #76ba18 2.22%, #76ba18 100%);
|
||||
$button: linear-gradient(
|
||||
270deg,
|
||||
#17e391 21.47%,
|
||||
#58e5df 77.04%,
|
||||
#60e5e8 77.05%
|
||||
);
|
||||
$checkedBorder: #76ba18;
|
||||
$blueDodger: #5e64ff;
|
||||
$lavender: #cd5ce0;
|
||||
$yellow: #ffba00;
|
||||
$green: #76ba18;
|
||||
$violet: #ec8efc;
|
||||
$magenta: #cd5ce0;
|
||||
$anakiwa: #8feaff;
|
||||
$codGray: #141414;
|
||||
$darkGray: #1a1a1a;
|
||||
$orange: #ffa41c;
|
||||
$tundola: #414141;
|
||||
$mineShaft: #292929;
|
||||
$text: #121212;
|
||||
$borderRadius: 16px;
|
||||
$warning: #ffa41c;
|
||||
$success: #76ba18;
|
||||
$alto: #e0e0e0;
|
||||
$lightGreen: #50c32c;
|
||||
$secondaryGrey: #c4c4c4;
|
||||
$dusty_gray: #979797;
|
||||
$serenade: #fff6e8;
|
||||
$shark: #1e1f20;
|
||||
$error: #ff3c45;
|
||||
$grey5: #e0e0e0;
|
||||
$mercury: #e6e6e6;
|
||||
|
||||
$successBackground: #f1f8e8;
|
||||
|
||||
$xx-small: 10px;
|
||||
$x-small: 12px;
|
||||
$small: 14px;
|
||||
$xl-small: 15px;
|
||||
$medium1: 16px;
|
||||
$medium2: 18px;
|
||||
$normal: 20px;
|
||||
$normal1: 22px;
|
||||
$normal2: 24px;
|
||||
$large: 32px;
|
||||
$larger: 36px;
|
||||
$x-large: 40px;
|
||||
$xx-large: 56px;
|
||||
$xxl-large: 72px;
|
||||
|
||||
$regular: 400;
|
||||
$medium: 500;
|
||||
$semi-bold: 600;
|
||||
$bold: 700;
|
||||
$extra-bold: 800;
|
||||
|
||||
$container: 616px;
|
||||
|
||||
$mobile-width: 480px;
|
||||
$tablet-width: 768px;
|
||||
$desktop-width: 1024px;
|
||||
$small-height: 650px;
|
||||
$videoCallHeight: calc((50vw - 64px) * 720 / 1280);
|
||||
$fontGotham: 'Gotham Pro', sans-serif;
|
||||
$fontSFProDisplay: 'SFProDisplay', sans-serif;
|
||||
|
||||
$boxShadow:
|
||||
rgba(60, 64, 67, 0.3) 0 1px 2px 0,
|
||||
rgba(60, 64, 67, 0.15) 0 1px 3px 1px;
|
||||
|
||||
$boxShadow2: 0 0 10px rgba(0, 0, 0, 0.25);
|
||||
$boxShadow3: 0 0 15px rgba(0, 0, 0, 0.15);
|
||||
|
||||
$icons-url: 'https://komi-assets.s3.amazonaws.com/icons';
|
||||
|
||||
// KDS Colors
|
||||
$color-blue-100: #bbe1ff;
|
||||
$color-blue-800: #0f69e6;
|
||||
$color-accent-blue: $color-blue-800;
|
||||
$color-color-turquoise-200: #60e5e8;
|
||||
$color-overlay-turquoise-10: #60e5e81a;
|
||||
$color-overlay-turquoise-20: #60e5e833;
|
||||
|
||||
// :root {
|
||||
// --ant-primary-color: #10848b;
|
||||
// --ant-success-color: rgba(255, 255, 255, 0.1);
|
||||
// --ant-info-color: #ffffff;
|
||||
// }
|
||||
|
||||
@mixin mobile {
|
||||
@media (max-width: #{$mobile-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin md {
|
||||
@media (min-width: #{$mobile-width}) and (max-width: #{$tablet-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin tablet {
|
||||
@media (min-width: #{$tablet-width}) and (max-width: #{$desktop-width - 1px}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mobile-and-tablet {
|
||||
@media (max-width: #{$desktop-width - 1px}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin max-width($max-width) {
|
||||
@media (max-width: #{$max-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin desktop {
|
||||
@media (min-width: #{$desktop-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin smallHeight {
|
||||
@media (max-height: #{$small-height}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin modalActions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@include mobile {
|
||||
flex-direction: column-reverse;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
width: 123px;
|
||||
|
||||
&:nth-child(1) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
@include mobile {
|
||||
width: 100%;
|
||||
|
||||
&:nth-child(1) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
component_factory/src/app/favicon.ico
Executable file
BIN
component_factory/src/app/favicon.ico
Executable file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
26
component_factory/src/app/globals.css
Executable file
26
component_factory/src/app/globals.css
Executable file
@ -0,0 +1,26 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
34
component_factory/src/app/layout.tsx
Executable file
34
component_factory/src/app/layout.tsx
Executable file
@ -0,0 +1,34 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
15
component_factory/src/app/page.tsx
Executable file
15
component_factory/src/app/page.tsx
Executable file
@ -0,0 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
|
||||
export default function Page() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>Flat Interface Home Page</p>
|
||||
|
||||
<p>1: <a href="http://localhost:3000/simple_modal">Simple Modal</a></p>
|
||||
</>
|
||||
);
|
||||
}
|
463
component_factory/src/app/simple_modal/page.tsx
Normal file
463
component_factory/src/app/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/base/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>
|
||||
</>
|
||||
);
|
||||
}
|
176
component_factory/src/components/Form/FormItem.tsx
Normal file
176
component_factory/src/components/Form/FormItem.tsx
Normal file
@ -0,0 +1,176 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import React, { ChangeEvent } from "react";
|
||||
import { DatePicker, Form, Input, InputNumber, Select } from "antd";
|
||||
import { Text } from "@/components/Typography/Text";
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const { Option } = Select;
|
||||
|
||||
import { FormItemProps as $FormItemProps } from "antd/lib/form/FormItem";
|
||||
import { InputProps as $ItemProps } from "antd/lib/input";
|
||||
import { SelectProps as $SelectProps } from "antd/lib/select";
|
||||
import { FieldConfig, FieldProps, getIn } from "formik";
|
||||
import classNames from "classnames";
|
||||
|
||||
type SelectOption = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
export enum FieldType {
|
||||
input = "input",
|
||||
inputNumber = "input-number",
|
||||
textarea = "textarea",
|
||||
select = "select",
|
||||
time = "time",
|
||||
date = "date",
|
||||
}
|
||||
|
||||
export type FormItemProps = {
|
||||
children: React.ReactNode;
|
||||
} & {
|
||||
label: string;
|
||||
name: string;
|
||||
isInt?: boolean;
|
||||
description: string;
|
||||
className?: string;
|
||||
labelClassName?: string;
|
||||
descriptionClassName?: string;
|
||||
inputClassName?: string;
|
||||
formItemClassName?: string;
|
||||
submitCount: number;
|
||||
onChangeText?: (text: string) => void;
|
||||
type: FieldType;
|
||||
beforeSetValue?: <T>(value: T) => T;
|
||||
} & $FormItemProps &
|
||||
$ItemProps &
|
||||
$SelectProps<any> &
|
||||
FieldProps &
|
||||
Pick<FieldConfig, "validate">;
|
||||
|
||||
const CreateAntField =
|
||||
(AntComponent: any) =>
|
||||
({
|
||||
description,
|
||||
field,
|
||||
form,
|
||||
label,
|
||||
className,
|
||||
labelClassName,
|
||||
descriptionClassName,
|
||||
inputClassName,
|
||||
formItemClassName,
|
||||
selectOptions,
|
||||
submitCount,
|
||||
type,
|
||||
beforeSetValue,
|
||||
isInt,
|
||||
onChangeText,
|
||||
...props
|
||||
}: any) => {
|
||||
const touched = getIn(form.touched, field.name);
|
||||
const submitted = submitCount > 0;
|
||||
const hasError = getIn(form.errors, field.name);
|
||||
const submittedError = hasError && submitted;
|
||||
const touchedError = hasError && touched;
|
||||
|
||||
const onBlur = () => form.setFieldTouched(field.name, true);
|
||||
|
||||
const onInputChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
event &&
|
||||
event.target &&
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
typeof beforeSetValue === "function"
|
||||
? beforeSetValue(event.target.value)
|
||||
: event.target.value
|
||||
);
|
||||
onChangeText?.(event?.target?.value || "");
|
||||
};
|
||||
const onChange = (value: any) => {
|
||||
try {
|
||||
let text = value;
|
||||
|
||||
if (isInt) {
|
||||
if (!value) {
|
||||
text = 1;
|
||||
} else {
|
||||
const number = parseInt(value);
|
||||
text = number === NaN ? 1 : number;
|
||||
}
|
||||
}
|
||||
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
typeof beforeSetValue === "function" ? beforeSetValue(value) : text
|
||||
);
|
||||
onChangeText(text);
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const onTimeChange = (value: any) => {
|
||||
onBlur();
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
typeof beforeSetValue === "function" ? beforeSetValue(value) : value
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{label && (
|
||||
<Text
|
||||
className={classNames(
|
||||
"d--block leading-22",
|
||||
!description && "m__b--4",
|
||||
typeof label === "string" && "text--tundola",
|
||||
labelClassName
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
{description && (
|
||||
<Text
|
||||
className={classNames("d--block m__b--8", descriptionClassName)}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
<FormItem
|
||||
className={formItemClassName}
|
||||
help={submittedError || touchedError ? hasError : false}
|
||||
validateStatus={submittedError || touchedError ? "error" : "success"}
|
||||
>
|
||||
<AntComponent
|
||||
{...field}
|
||||
{...props}
|
||||
className={inputClassName}
|
||||
onBlur={[FieldType.time].includes(type) ? onTimeChange : onBlur}
|
||||
onChange={
|
||||
[
|
||||
FieldType.input,
|
||||
FieldType.textarea,
|
||||
FieldType.inputNumber,
|
||||
].includes(type)
|
||||
? onInputChange
|
||||
: onChange
|
||||
}
|
||||
>
|
||||
{selectOptions &&
|
||||
selectOptions.map((option: SelectOption, index: number) => (
|
||||
<Option key={index} value={option.value}>
|
||||
{option.label}
|
||||
</Option>
|
||||
))}
|
||||
</AntComponent>
|
||||
</FormItem>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export const AntSelect = CreateAntField(Select);
|
||||
export const AntInput = CreateAntField(Input);
|
||||
export const AntInputNumber = CreateAntField(InputNumber);
|
||||
export const AntTextArea = CreateAntField(Input.TextArea);
|
||||
export const AntDatePicker = CreateAntField(DatePicker);
|
||||
//export const AntTimePicker = CreateAntField(TimePickerDesktop);
|
202
component_factory/src/components/ImageUpload/ImageUpload.tsx
Normal file
202
component_factory/src/components/ImageUpload/ImageUpload.tsx
Normal file
@ -0,0 +1,202 @@
|
||||
import "../../app/css/ImageUpload.scss"
|
||||
import { Button, Row, Upload } from "antd";
|
||||
import { UploadFile } from "antd/lib/upload/interface";
|
||||
import CloseIcon from "@/icons/Closeicon";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
import { Paragraph } from "@/components/Typography/Paragraph";
|
||||
|
||||
// ✅ Dummy Fallbacks
|
||||
interface MediaUpload {
|
||||
id?: number;
|
||||
fileName?: string;
|
||||
url?: string;
|
||||
loading?: boolean;
|
||||
percent?: number;
|
||||
}
|
||||
|
||||
const AddIcon = () => <span style={{ fontSize: "32px" }}>+</span>;
|
||||
|
||||
const Progress = ({ value }: { value?: number }) => (
|
||||
<div className="progress-bar" style={{ textAlign: "center", padding: "20px" }}>
|
||||
Uploading... {value || 0}%
|
||||
</div>
|
||||
);
|
||||
|
||||
const ImageCrop = ({ children }: { children: React.ReactNode }) => <>{children}</>;
|
||||
|
||||
const resizeFile = async (file: File) => {
|
||||
return file; // mock
|
||||
};
|
||||
|
||||
const pushImageToS3 = async (
|
||||
file: UploadFile<any>,
|
||||
progressCallback: (percent: number) => void
|
||||
): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
let percent = 0;
|
||||
const interval = setInterval(() => {
|
||||
percent += 20;
|
||||
progressCallback(percent);
|
||||
if (percent >= 100) {
|
||||
clearInterval(interval);
|
||||
resolve("https://via.placeholder.com/369");
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
};
|
||||
|
||||
const onBeforeUpload = (file: File) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const API_URL = "https://example.com/api/upload";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
photoIndex?: number;
|
||||
photo: MediaUpload;
|
||||
onUploadSuccess: (file: MediaUpload) => void;
|
||||
onRemove: (file: MediaUpload) => void;
|
||||
children?: any;
|
||||
content?: React.ReactNode;
|
||||
dropAspect?: number;
|
||||
hideRemove?: boolean;
|
||||
hideUpdate?: boolean;
|
||||
replace?: boolean;
|
||||
}
|
||||
|
||||
const DropContainer: React.FC<any> = ({ dropAspect, children }) => {
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
const ImageUpload: React.FC<Props> = ({
|
||||
photoIndex = 1,
|
||||
photo,
|
||||
className,
|
||||
children,
|
||||
onUploadSuccess,
|
||||
onRemove,
|
||||
dropAspect,
|
||||
hideRemove,
|
||||
content,
|
||||
hideUpdate,
|
||||
replace,
|
||||
}) => {
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
const [photoUpload, setPhotoUpload] = useState<MediaUpload>();
|
||||
|
||||
useEffect(() => {
|
||||
if (photo) setPhotoUpload(photo);
|
||||
}, [photo]);
|
||||
|
||||
const removePhoto = () => {
|
||||
onRemove(photoUpload as MediaUpload);
|
||||
setPhotoUpload({});
|
||||
};
|
||||
|
||||
const handleChange = async (file: UploadFile<any>, index: number) => {
|
||||
let photo: MediaUpload = {};
|
||||
|
||||
if (file.status === "uploading") {
|
||||
photo.loading = true;
|
||||
photo.fileName = file.name;
|
||||
}
|
||||
|
||||
if (file.status === "done" || file.status === "error") {
|
||||
const transformFile = await resizeFile(file.originFileObj);
|
||||
file.originFileObj = transformFile;
|
||||
|
||||
const url = await pushImageToS3(file, (value: number) =>
|
||||
setPhotoUpload({ percent: value })
|
||||
);
|
||||
|
||||
photo = {
|
||||
id: index,
|
||||
fileName: file.name,
|
||||
url,
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
|
||||
onUploadSuccess({ ...photoUpload, ...photo });
|
||||
setPhotoUpload({ ...photoUpload, ...photo });
|
||||
};
|
||||
|
||||
const photoUploadClass = classNames({
|
||||
"photo-upload-item": true,
|
||||
[className || ""]: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={photoUploadClass}>
|
||||
<DropContainer dropAspect={dropAspect}>
|
||||
<span className="ant-upload-picture-card-wrapper">
|
||||
<div className="ant-upload ant-upload-select ant-upload-select-picture-card">
|
||||
<Upload
|
||||
className="ant-upload"
|
||||
style={photoUpload?.url && !replace ? { display: "block" } : undefined}
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
listType="picture-card"
|
||||
action={API_URL}
|
||||
showUploadList={false}
|
||||
beforeUpload={onBeforeUpload}
|
||||
onChange={({ file }) => handleChange(file, photoIndex)}
|
||||
>
|
||||
<span tabIndex={0} role="button" className="ant-upload">
|
||||
<input type="file" accept="image/png,image/jpeg,image/webp" style={{ display: "none" }} />
|
||||
{photoUpload?.url && !replace ? (
|
||||
|
||||
<svg
|
||||
width={32}
|
||||
height={32}
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
data-testid="media-upload--add"
|
||||
>
|
||||
<path
|
||||
d="M15.9997 26.6666C21.8907 26.6666 26.6663 21.891 26.6663 16C26.6663 10.1089 21.8907 5.33331 15.9997 5.33331C10.1086 5.33331 5.33301 10.1089 5.33301 16C5.33301 21.891 10.1086 26.6666 15.9997 26.6666Z"
|
||||
fill="#257FFC"
|
||||
stroke="#257FFC"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16 11.9999V19.9999"
|
||||
stroke="white"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M20 15.9999L12 15.9999"
|
||||
stroke="white"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
) : (
|
||||
|
||||
<img
|
||||
//src={photoUpload.url}
|
||||
alt="avatar"
|
||||
style={{ width: "100%" }}
|
||||
data-testid="media-upload--image-card"
|
||||
/>
|
||||
|
||||
)}
|
||||
</span>
|
||||
</Upload>
|
||||
</div>
|
||||
</span>
|
||||
</DropContainer>
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default ImageUpload;
|
22
component_factory/src/components/Modal/Modal.tsx
Normal file
22
component_factory/src/components/Modal/Modal.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import { Modal as AntdModal } from "antd";
|
||||
import { ModalProps } from "antd/lib/modal";
|
||||
import CloseIcon from "@/icons/Closeicon";
|
||||
|
||||
interface IProps extends ModalProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const Modal = ({ children, footer = false, ...props }: IProps) => {
|
||||
return (
|
||||
<AntdModal
|
||||
closeIcon={<CloseIcon role="close" width={24} height={24} />}
|
||||
footer={footer}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</AntdModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
18
component_factory/src/components/Typography/Link.tsx
Normal file
18
component_factory/src/components/Typography/Link.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import * as React from "react";
|
||||
import Typography from "antd/lib/typography";
|
||||
import { LinkProps as LinkAntdProps } from "antd/lib/typography/Link";
|
||||
import { TypographyPresets, presets } from "./Preset";
|
||||
const LinkAntd = Typography.Link;
|
||||
interface LinkProps extends LinkAntdProps {
|
||||
preset?: TypographyPresets;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Link = (props: LinkProps) => {
|
||||
const { preset, className, ...rest } = props;
|
||||
const namePreset = preset && presets[preset];
|
||||
const mainClass = `text text--primary ${
|
||||
namePreset ? namePreset : ""
|
||||
} ${className}`;
|
||||
return <LinkAntd className={mainClass} {...rest} />;
|
||||
};
|
17
component_factory/src/components/Typography/Paragraph.tsx
Normal file
17
component_factory/src/components/Typography/Paragraph.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import * as React from "react";
|
||||
import Typography from "antd/lib/typography";
|
||||
import { ParagraphProps as ParagraphAntdProps } from "antd/lib/typography/Paragraph";
|
||||
import { TypographyPresets, presets } from "./Preset";
|
||||
|
||||
const ParagraphAntd = Typography.Paragraph;
|
||||
export interface ParagraphProps extends ParagraphAntdProps {
|
||||
preset?: TypographyPresets;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Paragraph = (props: ParagraphProps) => {
|
||||
const { preset, className, ...rest } = props;
|
||||
const namePreset = preset && presets[preset];
|
||||
const mainClass = `text ${namePreset ? namePreset : ""} ${className}`;
|
||||
return <ParagraphAntd className={mainClass} {...rest} />;
|
||||
};
|
38
component_factory/src/components/Typography/Preset.ts
Normal file
38
component_factory/src/components/Typography/Preset.ts
Normal file
@ -0,0 +1,38 @@
|
||||
export const presets = {
|
||||
regular10: "text--regular10",
|
||||
regular12: "text--regular12",
|
||||
regular14: "text--regular14",
|
||||
regular15: "text--regular15",
|
||||
regular16: "text--regular16",
|
||||
regular18: "text--regular18",
|
||||
regular20: "text--regular20",
|
||||
regular22: "text--regular22",
|
||||
regular24: "text--regular24",
|
||||
medium12: "text--medium12",
|
||||
medium14: "text--medium14",
|
||||
medium16: "text--medium16",
|
||||
medium18: "text--medium18",
|
||||
medium20: "text--medium20",
|
||||
medium22: "text--medium22",
|
||||
medium24: "text--medium24",
|
||||
semibold12: "text--semibold12",
|
||||
semibold14: "text--semibold14",
|
||||
semibold16: "text--semibold16",
|
||||
semibold18: "text--semibold18",
|
||||
semibold20: "text--semibold20",
|
||||
semibold24: "text--semibold24",
|
||||
bold14: "text--bold14",
|
||||
bold16: "text--bold16",
|
||||
bold18: "text--bold18",
|
||||
bold20: "text--bold20",
|
||||
bold24: "text--bold24",
|
||||
bold28: "text--bold28",
|
||||
bold30: "text--bold30",
|
||||
bold32: "text--bold32",
|
||||
bold36: "text--bold36",
|
||||
bold40: "text--bold40",
|
||||
bold48: "text--bold48",
|
||||
bold56: "text--bold56",
|
||||
bold72: "text--bold72",
|
||||
};
|
||||
export type TypographyPresets = keyof typeof presets;
|
16
component_factory/src/components/Typography/Text.tsx
Normal file
16
component_factory/src/components/Typography/Text.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import * as React from "react";
|
||||
import Typography from "antd/lib/typography";
|
||||
import { TextProps as TextAntdProps } from "antd/lib/typography/Text";
|
||||
import { TypographyPresets, presets } from "./Preset";
|
||||
const TextAntd = Typography.Text;
|
||||
export interface TextProps extends TextAntdProps {
|
||||
preset?: TypographyPresets;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Text = (props: TextProps) => {
|
||||
const { preset, className, ...rest } = props;
|
||||
const namePreset = preset && presets[preset];
|
||||
const mainClass = `text ${namePreset ? namePreset : ""} ${className}`;
|
||||
return <TextAntd className={mainClass} {...rest} />;
|
||||
};
|
17
component_factory/src/components/Typography/Title.tsx
Normal file
17
component_factory/src/components/Typography/Title.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import * as React from "react";
|
||||
import Typography from "antd/lib/typography";
|
||||
import { TitleProps as TitleAntdProps } from "antd/lib/typography/Title";
|
||||
import { TypographyPresets, presets } from "./Preset";
|
||||
|
||||
const TitleAntd = Typography.Title;
|
||||
interface TitleProps extends TitleAntdProps {
|
||||
preset?: TypographyPresets;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Title = (props: TitleProps) => {
|
||||
const { preset, className, ...rest } = props;
|
||||
const namePreset = preset && presets[preset];
|
||||
const mainClass = `text title ${namePreset ? namePreset : ""} ${className}`;
|
||||
return <TitleAntd className={mainClass} {...rest} />;
|
||||
};
|
33
component_factory/src/icons/Closeicon.tsx
Normal file
33
component_factory/src/icons/Closeicon.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
interface CloseIconProps extends React.SVGProps<SVGSVGElement> {}
|
||||
|
||||
const CloseIcon: React.FC<CloseIconProps> = (props) => (
|
||||
<svg
|
||||
width={props.width || 24}
|
||||
height={props.height || 24}
|
||||
viewBox="0 0 28 28"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role={props.role || 'img'}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M14.0003 24.0007C19.5234 24.0007 24.0007 19.5234 24.0007 14.0003C24.0007 8.4773 19.5234 4 14.0003 4C8.4773 4 4 8.4773 4 14.0003C4 19.5234 8.4773 24.0007 14.0003 24.0007Z"
|
||||
fill="#121212"
|
||||
stroke="#121212"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M17.2811 10.7185L10.718 17.2816M17.2811 17.2816L10.718 10.7185"
|
||||
stroke="white"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default CloseIcon;
|
26
component_factory/src/icons/Closeoutlineicon.tsx
Normal file
26
component_factory/src/icons/Closeoutlineicon.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
|
||||
interface CloseOutlineIconProps extends React.SVGProps<SVGSVGElement> {}
|
||||
|
||||
const CloseOutlineIcon: React.FC<CloseOutlineIconProps> = (props) => (
|
||||
<svg
|
||||
width={props.width || 24}
|
||||
height={props.height || 24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role={props.role || 'img'}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M17 7L7 17M17 17L7 7"
|
||||
stroke="#020025"
|
||||
strokeWidth={2}
|
||||
strokeMiterlimit={10}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default CloseOutlineIcon;
|
28
component_factory/src/icons/Infoicon.tsx
Normal file
28
component_factory/src/icons/Infoicon.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
interface InfoIconProps extends React.SVGProps<SVGSVGElement> {}
|
||||
|
||||
const InfoIcon: React.FC<InfoIconProps> = (props) => (
|
||||
<svg
|
||||
width={props.width || 24}
|
||||
height={props.height || 24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role={props.role || 'img'}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z"
|
||||
stroke="#121212"
|
||||
strokeWidth="1.5"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
d="M13.8255 16.0795C13.8096 16.1421 13.7646 16.1932 13.7049 16.2178C13.1711 16.4378 11.7793 17.0322 10.9555 16.3064C10.8287 16.1986 10.7276 16.0639 10.6597 15.912C10.5917 15.7602 10.5585 15.595 10.5626 15.4287C10.6536 14.5279 10.8593 13.6425 11.1746 12.7938C11.261 12.5095 11.3137 12.216 11.3317 11.9194C11.3317 11.4533 11.1546 11.3288 10.6745 11.3288C10.5717 11.3349 10.4697 11.3483 10.3692 11.3686C10.19 11.4048 10.0088 11.246 10.0531 11.0687C10.0692 11.0044 10.1131 10.9505 10.1735 10.9233C10.6953 10.6886 11.2532 10.5435 11.8238 10.4944C12.6688 10.4944 13.2888 10.9159 13.2888 11.7177C13.2857 12.0268 13.2441 12.3344 13.1649 12.6333L12.6788 14.3526C12.5913 14.6571 12.4405 15.2828 12.5941 15.586C12.6355 15.6677 12.7209 15.714 12.8082 15.7419C12.9706 15.7938 13.1413 15.8172 13.3124 15.8108C13.3801 15.8082 13.4475 15.801 13.5139 15.7893C13.6843 15.7591 13.8681 15.9117 13.8255 16.0795ZM12.9965 9.33107C12.7989 9.33107 12.6058 9.27249 12.4415 9.16274C12.2773 9.05298 12.1492 8.89698 12.0736 8.71447C11.998 8.53195 11.9783 8.33112 12.0168 8.13736C12.0553 7.9436 12.1505 7.76562 12.2902 7.62593C12.4299 7.48624 12.6078 7.39111 12.8016 7.35257C12.9954 7.31403 13.1962 7.33381 13.3787 7.40941C13.5612 7.48501 13.7172 7.61303 13.827 7.77729C13.9367 7.94155 13.9953 8.13467 13.9953 8.33222C13.9953 8.59714 13.8901 8.8512 13.7028 9.03852C13.5154 9.22584 13.2614 9.33107 12.9965 9.33107Z"
|
||||
fill="#121212"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default InfoIcon;
|
@ -0,0 +1,64 @@
|
||||
export enum TalentProfileCollaboratorInvitationStatus {
|
||||
PENDING = "PENDING",
|
||||
EXPIRED = "EXPIRED",
|
||||
ACCEPTED = "ACCEPTED",
|
||||
}
|
||||
|
||||
export interface InviteCollaboratorRequest {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export type CancelInviteCollaboratorRequest = InviteCollaboratorRequest;
|
||||
|
||||
export interface RevokeInviteCollaboratorRequest {
|
||||
userId: number;
|
||||
email: string;
|
||||
}
|
||||
export interface ResendInviteCollaboratorRequest {
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface AcceptCollaboratorInvitationRequest {
|
||||
email: string;
|
||||
token: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
country: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface TalentCollaborator {
|
||||
id?: string;
|
||||
email?: string;
|
||||
status?: TalentProfileCollaboratorInvitationStatus;
|
||||
talentProfileCollaborator?: {
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
id?: string;
|
||||
permissions?: any;
|
||||
};
|
||||
user?: {
|
||||
avatar: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
id: string;
|
||||
};
|
||||
inviteLink?: string;
|
||||
}
|
||||
|
||||
export interface CollaboratorProfile {
|
||||
id: string;
|
||||
talentProfile: {
|
||||
id: string;
|
||||
avatar: string;
|
||||
displayName: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
user?: {
|
||||
id: number;
|
||||
username: string;
|
||||
};
|
||||
};
|
||||
}
|
@ -0,0 +1,443 @@
|
||||
import { Experience } from "redux/Experience/types";
|
||||
import { SocialProfileLinkTypes, UserProfile } from "redux/User/types";
|
||||
import {
|
||||
DataCaptureMethod,
|
||||
DataCaptureType,
|
||||
Theme,
|
||||
} from "@komi-app/components";
|
||||
|
||||
import {
|
||||
ModuleType,
|
||||
DataCaptureFieldType,
|
||||
MusicReleaseScope,
|
||||
MusicReleaseType,
|
||||
MusicReleaseMode,
|
||||
LegalsType,
|
||||
DataCaptureFormType,
|
||||
} from "@komi-app/shared-types";
|
||||
import { CommissionType } from "@komi-app/creator-ui";
|
||||
import { AffiliateProduct } from "@komi-app/affiliate-sdk";
|
||||
import { AffiliateNetworkType, AutomationMode } from "@komi-app/profiles-sdk";
|
||||
|
||||
export type TalentModuleMixItem =
|
||||
| ProductItem
|
||||
| LinkItem
|
||||
| MusicItem
|
||||
| OnDemandVideoItem
|
||||
| YoutubeVideoItem
|
||||
| ExperienceItem
|
||||
| PodcastItem
|
||||
| FanClubItem
|
||||
| BandsintownItem
|
||||
| ShopifyProductItem
|
||||
| ShopifyModuleItem
|
||||
| EventItem
|
||||
| DataCaptureFormItem
|
||||
| GroupItem;
|
||||
|
||||
export type ThirdPartyModuleType =
|
||||
| ModuleType.SHOP_MY_SHELF
|
||||
| ModuleType.SHOP_LIST
|
||||
| ModuleType.BANDSINTOWN
|
||||
| ModuleType.SEATED
|
||||
| ModuleType.YOUTUBE_COLLECTION
|
||||
| ModuleType.PODCAST_AUTOMATION;
|
||||
|
||||
// modules profile
|
||||
export interface CreateTalentProfileModuleRequest<T> {
|
||||
order: number;
|
||||
type: ModuleType;
|
||||
items: T;
|
||||
expand?: boolean;
|
||||
isEdit?: boolean;
|
||||
isCreate?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateTalentProfileModuleRequest<T> {
|
||||
id: string;
|
||||
order: number;
|
||||
type: ModuleType;
|
||||
items: T[];
|
||||
expand?: boolean;
|
||||
isEdit?: boolean;
|
||||
isCreate?: boolean;
|
||||
}
|
||||
|
||||
export interface ReorderTalentProfileModuleRequest {
|
||||
id: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface ReorderShopifyStoreRequest {
|
||||
shop: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface autoSaveProfileRequest {
|
||||
localizationId: string;
|
||||
localizationIds?: string[];
|
||||
profile: UserProfile;
|
||||
modules: TalentProfileModule<TalentModuleMixItem>[];
|
||||
updatedById?: number;
|
||||
digitalProductsFilesToUpload?: Record<string, File>;
|
||||
updateTalentId?: string;
|
||||
onNetworkError?: () => void;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
export interface publishProfileRequest {
|
||||
localizationId: string[];
|
||||
publishTalentId?: number;
|
||||
onNetworkError?: () => void;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
export interface DeleteLocalizationRequest {
|
||||
talentProfileId: string;
|
||||
localizationId: string;
|
||||
onNetworkError?: () => void;
|
||||
onSuccess?: () => void;
|
||||
onError?: () => void;
|
||||
}
|
||||
export interface requestOTPRequest {
|
||||
method: "phone" | "email";
|
||||
phone?: string;
|
||||
email?: string;
|
||||
userId?: number;
|
||||
}
|
||||
export interface TFAVerificationRequest {
|
||||
method: "phone" | "email";
|
||||
phone?: string;
|
||||
id?: string;
|
||||
otp?: string;
|
||||
}
|
||||
|
||||
export interface BaseItem {
|
||||
id?: string;
|
||||
order?: number;
|
||||
isUpdate?: boolean;
|
||||
isCreate?: boolean;
|
||||
isSuccess?: boolean;
|
||||
error?: any;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export interface LinkItem extends BaseItem {
|
||||
url?: string;
|
||||
title?: string;
|
||||
thumbnail?: string;
|
||||
specialOffer?: {
|
||||
storeUrl?: string;
|
||||
title?: string;
|
||||
thumbnail?: string;
|
||||
couponCode?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TextItem extends BaseItem {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface MediaGalleryItem extends BaseItem {
|
||||
src: string;
|
||||
thumbnailSrc?: string;
|
||||
contentType: string;
|
||||
originalFilename: string;
|
||||
caption?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface TipsLinkItem extends BaseItem {
|
||||
src: string;
|
||||
thumbnailSrc?: string;
|
||||
contentType: string;
|
||||
originalFilename: string;
|
||||
title?: string;
|
||||
url?: string;
|
||||
platform?: string;
|
||||
}
|
||||
|
||||
export type AffiliateItem = BaseItem &
|
||||
AffiliateProduct & {
|
||||
trackingUrl: string;
|
||||
affiliateNetwork?: AffiliateNetworkType;
|
||||
affiliateMetadata?: Record<string, any> & AffiliateMetadata;
|
||||
};
|
||||
|
||||
export interface AffiliateMetadata {
|
||||
commissionRate: number;
|
||||
commissionType: CommissionType;
|
||||
}
|
||||
|
||||
export interface BandsintownItem extends BaseItem {
|
||||
url?: string;
|
||||
metadata?: {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
export interface SeatedItem extends BaseItem {
|
||||
seatedId?: string;
|
||||
}
|
||||
export interface ShopMyShelfItem extends BaseItem {
|
||||
url?: string;
|
||||
}
|
||||
export interface ShopListItem extends BaseItem {
|
||||
url?: string;
|
||||
}
|
||||
export interface YoutubeItem extends BaseItem {
|
||||
url?: string;
|
||||
totalSelected?: number;
|
||||
sort?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface TikTokItem extends BaseItem {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface TikTokAutomationItem extends BaseItem {
|
||||
mode: AutomationMode;
|
||||
tiktokAccountId: string;
|
||||
links: TikTokItem[];
|
||||
}
|
||||
|
||||
export interface InstagramReelItem extends BaseItem {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface ProductItem extends BaseItem {
|
||||
url?: string;
|
||||
title?: string;
|
||||
thumbnail?: string;
|
||||
price?: number;
|
||||
currency?: string;
|
||||
}
|
||||
export interface EventItem extends BaseItem {
|
||||
eventDate?: string;
|
||||
venueName?: string;
|
||||
location?: string;
|
||||
ticketLink?: string;
|
||||
soldOut?: boolean;
|
||||
}
|
||||
|
||||
export interface ShopifyProductItem extends BaseItem {
|
||||
id?: string;
|
||||
shop?: string;
|
||||
itemIds?: string[];
|
||||
collectionId?: string;
|
||||
images: { src: string }[];
|
||||
title: string;
|
||||
variants: ShopifyVariant[];
|
||||
}
|
||||
|
||||
export interface ShopifyVariant {
|
||||
price: string;
|
||||
priceV2: {
|
||||
currencyCode: string;
|
||||
amount: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ShopifyModuleItem extends BaseItem {
|
||||
shop?: string;
|
||||
itemIds?: string[];
|
||||
collectionId?: string;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export interface MusicItemLink extends BaseItem {
|
||||
url?: string;
|
||||
isVisible?: boolean;
|
||||
isTemporary?: boolean;
|
||||
artistsToFollow?: MusicArtist[];
|
||||
type?: SocialProfileLinkTypes;
|
||||
}
|
||||
|
||||
export interface MusicArtist {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
export interface MusicItem extends BaseItem {
|
||||
id?: string;
|
||||
metadata: Record<string, any>;
|
||||
links: MusicItemLink[];
|
||||
order?: number;
|
||||
urlSlug?: string;
|
||||
type?: MusicReleaseType;
|
||||
releaseDate?: string;
|
||||
releaseType?: MusicReleaseMode;
|
||||
releaseScope?: MusicReleaseScope;
|
||||
timezone?: string;
|
||||
persistentId: string;
|
||||
}
|
||||
|
||||
export interface PodcastItemLink extends BaseItem {
|
||||
url?: string;
|
||||
type?: "SPOTIFY" | "STITCHER" | "APPLE_PODCAST" | "OVERCAST" | "AUDIBLE";
|
||||
}
|
||||
|
||||
export interface PodcastItem extends BaseItem {
|
||||
id?: string;
|
||||
metadata?: Record<string, any>;
|
||||
links: PodcastItemLink[];
|
||||
numberOfEpisodes?: number;
|
||||
activeLatestPodcast?: boolean;
|
||||
isDifferent?: boolean;
|
||||
customUrl?: string;
|
||||
}
|
||||
|
||||
export interface FanClubItem extends BaseItem {
|
||||
id?: string;
|
||||
thumbnail?: string;
|
||||
}
|
||||
|
||||
export interface OnDemandVideoItem extends Experience, BaseItem {
|
||||
experienceId?: string;
|
||||
}
|
||||
|
||||
export interface YoutubeVideoItem extends BaseItem {
|
||||
url?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface ExperienceItem extends Experience, BaseItem {
|
||||
experienceId?: string;
|
||||
}
|
||||
|
||||
export enum DATA_FORM_LAYOUT {
|
||||
left = "left",
|
||||
right = "right",
|
||||
}
|
||||
|
||||
export interface DataFormFieldItem {
|
||||
name: string;
|
||||
label: string;
|
||||
required: boolean;
|
||||
type: DataCaptureFieldType;
|
||||
legalType?: LegalsType;
|
||||
options: OptionItem[];
|
||||
text?: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface OptionItem {
|
||||
value?: string;
|
||||
label: string;
|
||||
checked: boolean;
|
||||
order: number;
|
||||
}
|
||||
|
||||
/*
|
||||
* <LEGACY DATA CAPTURE>
|
||||
*/
|
||||
export interface DataFormOptionItem extends OptionItem {
|
||||
edit?: boolean;
|
||||
}
|
||||
export interface DataCaptureFieldItem
|
||||
extends Omit<DataFormFieldItem, "options"> {
|
||||
edit?: boolean;
|
||||
options?: DataFormOptionItem[];
|
||||
}
|
||||
|
||||
export interface DataCaptureFormItem extends BaseItem {
|
||||
layout: DATA_FORM_LAYOUT;
|
||||
hasImage: boolean;
|
||||
enableImage?: boolean;
|
||||
image?: string;
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
form: {
|
||||
title?: string;
|
||||
subTitle?: string;
|
||||
name: string;
|
||||
fields: DataFormFieldItem[];
|
||||
};
|
||||
formType?: DataCaptureFormType;
|
||||
emailNotifications?: boolean;
|
||||
}
|
||||
/*
|
||||
* </LEGACY DATA CAPTURE>
|
||||
*/
|
||||
|
||||
/*
|
||||
* </NEW DATA CAPTURE>
|
||||
*/
|
||||
export interface DataCaptureActivationItemNew extends BaseItem {
|
||||
type: ModuleType;
|
||||
name: string;
|
||||
order: number;
|
||||
|
||||
secretCode?: string;
|
||||
secretLink?: string;
|
||||
imagePath?: string;
|
||||
isImageVisible: boolean;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
countdownExpiry?: number;
|
||||
countdownTimeZone?: string;
|
||||
methods: DataCaptureMethod[];
|
||||
theme?: Theme;
|
||||
moduleType: DataCaptureType;
|
||||
isPaused?: boolean;
|
||||
}
|
||||
/*
|
||||
* </NEW DATA CAPTURE>
|
||||
*/
|
||||
export type GroupItem = TalentProfileModule<TalentModuleMixItem>;
|
||||
|
||||
export interface TalentProfileModule<T> {
|
||||
id?: string;
|
||||
name: string;
|
||||
type: ModuleType | string;
|
||||
items: T[];
|
||||
order: number;
|
||||
expand?: boolean;
|
||||
isEdit?: boolean; // check state editing
|
||||
isUpdate?: boolean; // check to send dispatch action update
|
||||
isCreate?: boolean; // check to send dispatch action create
|
||||
isLoading?: boolean;
|
||||
talentProfileId?: string;
|
||||
localizationId?: string;
|
||||
groupId?: string | null;
|
||||
showTitle?: boolean;
|
||||
template?: boolean;
|
||||
visible: boolean;
|
||||
}
|
||||
export type TalentProfileModuleGroup<T> = TalentProfileModule<
|
||||
TalentProfileModule<T>
|
||||
>;
|
||||
|
||||
export class TalentProfileModuleObj<T> implements TalentProfileModule<T> {
|
||||
id?: string;
|
||||
name: string;
|
||||
type: ModuleType | string;
|
||||
items: T[];
|
||||
order: number;
|
||||
expand?: boolean;
|
||||
isEdit?: boolean;
|
||||
isUpdate?: boolean;
|
||||
isCreate?: boolean;
|
||||
isLoading?: boolean;
|
||||
visible: boolean;
|
||||
|
||||
constructor(profileModule?: TalentProfileModule<T>) {
|
||||
this.id = profileModule && profileModule.id ? profileModule.id : "";
|
||||
this.name = profileModule && profileModule.name ? profileModule.name : "";
|
||||
this.type = profileModule ? profileModule.type : "";
|
||||
this.items = profileModule ? profileModule.items : [];
|
||||
this.order = profileModule ? profileModule.order : -1;
|
||||
this.expand =
|
||||
profileModule && profileModule.expand ? profileModule.expand : true;
|
||||
this.isEdit =
|
||||
profileModule && profileModule.isEdit ? profileModule.isEdit : false;
|
||||
this.isUpdate =
|
||||
profileModule && profileModule.isUpdate ? profileModule.isUpdate : false;
|
||||
this.isCreate =
|
||||
profileModule && profileModule.isCreate ? profileModule.isCreate : false;
|
||||
this.isLoading =
|
||||
profileModule && profileModule.isLoading
|
||||
? profileModule.isLoading
|
||||
: false;
|
||||
this.visible =
|
||||
profileModule && profileModule.visible ? profileModule.visible : true;
|
||||
}
|
||||
}
|
251
component_factory/src/redux/Common/types.ts
Normal file
251
component_factory/src/redux/Common/types.ts
Normal file
@ -0,0 +1,251 @@
|
||||
import { GeneralApiProblem } from "../../services/api/apiProblem";
|
||||
import { TwilioError } from "twilio-video";
|
||||
import moment from "moment-timezone";
|
||||
import { type } from "os";
|
||||
import {
|
||||
BaseItem,
|
||||
TalentProfileModule,
|
||||
} from "models/talent/talent-profile-module.model";
|
||||
|
||||
export type ListQueryParams = {
|
||||
page: number;
|
||||
limit: number;
|
||||
sort?: string;
|
||||
filter?: string;
|
||||
};
|
||||
|
||||
export type Pagination<T> = {
|
||||
items: T;
|
||||
meta: {
|
||||
totalItems: number;
|
||||
itemCount: number;
|
||||
itemsPerPage: number;
|
||||
totalPages: number;
|
||||
currentPage: number;
|
||||
};
|
||||
links: {
|
||||
first: string;
|
||||
previous: string;
|
||||
next: string;
|
||||
last: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type Media = {
|
||||
src: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export interface BaseModuleProps {
|
||||
module: TalentProfileModule<BaseItem>;
|
||||
}
|
||||
|
||||
export type OkResponse<T> = {
|
||||
ok: any;
|
||||
response: T;
|
||||
};
|
||||
|
||||
export type Response<T> = GeneralApiProblem | OkResponse<T> | null;
|
||||
|
||||
export enum StatusType {
|
||||
ALL = "all",
|
||||
PENDING = "pending",
|
||||
APPROVED = "approved",
|
||||
REJECTED = "rejected",
|
||||
UNCOMPLETED_PROFILE = "uncompleted_profile",
|
||||
ACTIVATE = "activate",
|
||||
DEACTIVATE = "deactivate",
|
||||
}
|
||||
|
||||
export enum PaymentStatus {
|
||||
PENDING = "pending",
|
||||
SUCCEEDED = "succeeded",
|
||||
}
|
||||
|
||||
export type Callback = (...args: any[]) => void;
|
||||
|
||||
export type ErrorCallback = (error: TwilioError) => void;
|
||||
|
||||
export type MediaUpload = {
|
||||
id?: number;
|
||||
fileName?: string;
|
||||
loading?: boolean;
|
||||
url?: string | ArrayBuffer;
|
||||
poster?: string;
|
||||
duration?: number;
|
||||
percent?: number;
|
||||
size?: number;
|
||||
name?: string;
|
||||
isDeleted?: boolean;
|
||||
};
|
||||
|
||||
export interface IToken {
|
||||
exp: number;
|
||||
iat: number;
|
||||
roles: string[];
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export type TIME_ZONE = {
|
||||
tz: string;
|
||||
tx: string;
|
||||
sectionId?: number;
|
||||
};
|
||||
|
||||
export const TIME_ZONE_DATA: Array<{
|
||||
id: number;
|
||||
tx: string;
|
||||
data: Array<TIME_ZONE>;
|
||||
}> = [
|
||||
{
|
||||
id: 1,
|
||||
tx: "timeZone.currentTime",
|
||||
data: [{ tz: moment.tz.guess(), tx: moment.tz.guess(), sectionId: 1 }],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
tx: "timeZone.usaTime",
|
||||
data: [
|
||||
{
|
||||
tz: "Pacific/Honolulu",
|
||||
tx: "timeZone.hst",
|
||||
sectionId: 2,
|
||||
},
|
||||
{
|
||||
tz: "America/Anchorage",
|
||||
tx: "timeZone.akdt",
|
||||
sectionId: 3,
|
||||
},
|
||||
{
|
||||
tz: "America/Boise",
|
||||
tx: "timeZone.pdt",
|
||||
sectionId: 4,
|
||||
},
|
||||
{
|
||||
tz: "America/Cambridge_Bay",
|
||||
tx: "timeZone.mst",
|
||||
sectionId: 5,
|
||||
},
|
||||
{
|
||||
tz: "America/Bahia_Banderas",
|
||||
tx: "timeZone.mdt",
|
||||
sectionId: 6,
|
||||
},
|
||||
{
|
||||
tz: "America/Atikokan",
|
||||
tx: "timeZone.cdt",
|
||||
sectionId: 7,
|
||||
},
|
||||
{
|
||||
tz: "America/Anguilla",
|
||||
tx: "timeZone.edt",
|
||||
sectionId: 8,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
tx: "timeZone.europeTime",
|
||||
data: [
|
||||
{
|
||||
tz: "Europe/Rome",
|
||||
tx: "timeZone.bst",
|
||||
sectionId: 9,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Tallinn",
|
||||
tx: "timeZone.cest",
|
||||
sectionId: 10,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Berlin",
|
||||
tx: "timeZone.cet",
|
||||
sectionId: 11,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Simferopol",
|
||||
tx: "timeZone.eest",
|
||||
sectionId: 12,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Vilnius",
|
||||
tx: "timeZone.eet",
|
||||
sectionId: 13,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Kirov",
|
||||
tx: "timeZone.fet",
|
||||
sectionId: 14,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Saratov",
|
||||
tx: "timeZone.get",
|
||||
sectionId: 15,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Lisbon",
|
||||
tx: "timeZone.gmt",
|
||||
sectionId: 16,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Dublin",
|
||||
tx: "timeZone.ist",
|
||||
sectionId: 17,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Samara",
|
||||
tx: "timeZone.kuyt",
|
||||
sectionId: 18,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Minsk",
|
||||
tx: "timeZone.msd",
|
||||
sectionId: 19,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Minsk",
|
||||
tx: "timeZone.msk",
|
||||
sectionId: 20,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Ulyanovsk",
|
||||
tx: "timeZone.samt",
|
||||
sectionId: 21,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Istanbul",
|
||||
tx: "timeZone.trt",
|
||||
sectionId: 22,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Oslo",
|
||||
tx: "timeZone.west",
|
||||
sectionId: 23,
|
||||
},
|
||||
{
|
||||
tz: "Europe/Guernsey",
|
||||
tx: "timeZone.wet",
|
||||
sectionId: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export enum MessageType {
|
||||
Text = "Text",
|
||||
Tip = "Tip",
|
||||
}
|
||||
|
||||
export type ShopMyShelfProduct = {
|
||||
image?: string;
|
||||
original_image?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
url?: string;
|
||||
code?: string;
|
||||
};
|
||||
export type GetShopMyShelfCollectionRes = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
products: ShopMyShelfProduct[];
|
||||
};
|
96
component_factory/src/utils/notification.tsx
Normal file
96
component_factory/src/utils/notification.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import message, { ConfigOptions } from "antd/lib/message";
|
||||
import {
|
||||
NotificationMessage,
|
||||
NotificationIcon,
|
||||
} from "components/NotificationMessage";
|
||||
import { ErrorPublishNotification } from "components/ErrorPublishNotification";
|
||||
import classNames from "classnames";
|
||||
|
||||
export type NoticeType = "info" | "success" | "error" | "warning" | "loading";
|
||||
|
||||
const defaultConfig: ConfigOptions = {
|
||||
top: 5,
|
||||
duration: 5,
|
||||
maxCount: 5,
|
||||
};
|
||||
|
||||
type MessageParams = {
|
||||
message: string | ReactNode;
|
||||
key?: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
isOffDuration?: boolean;
|
||||
multiline?: boolean;
|
||||
};
|
||||
type MessagesParams = {
|
||||
key: string;
|
||||
messages: any[];
|
||||
isOffDuration?: boolean;
|
||||
};
|
||||
|
||||
class Notifiaction {
|
||||
constructor(config: ConfigOptions) {
|
||||
message.config(config);
|
||||
}
|
||||
|
||||
private notify(type: NoticeType, params: MessageParams) {
|
||||
const key =
|
||||
typeof params.message === "string" ? params.message : params.key;
|
||||
|
||||
message.open({
|
||||
key,
|
||||
type: type,
|
||||
className: classNames("noti-message", "noti-message--multiline"),
|
||||
icon: <NotificationIcon width={24} height={24} type={type} />,
|
||||
duration:
|
||||
params.duration === 0
|
||||
? 0
|
||||
: params.duration || defaultConfig.duration || 2,
|
||||
content: (
|
||||
<NotificationMessage
|
||||
message={params.message}
|
||||
onClose={() => message.destroy(key)}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
showErrorPublish(params: MessagesParams) {
|
||||
message.open({
|
||||
key: params.key,
|
||||
type: "error",
|
||||
className: classNames("noti-message", "noti-message--multiline"),
|
||||
icon: <NotificationIcon width={24} height={24} type={"error"} />,
|
||||
duration: undefined,
|
||||
content: (
|
||||
<ErrorPublishNotification
|
||||
messages={params.messages}
|
||||
onClose={() => message.destroy(params.key)}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
destroy(key: string) {
|
||||
message.destroy(key);
|
||||
}
|
||||
|
||||
error(params: MessageParams) {
|
||||
this.notify("error", params);
|
||||
}
|
||||
|
||||
warn(params: MessageParams) {
|
||||
this.notify("warning", params);
|
||||
}
|
||||
|
||||
success(params: MessageParams) {
|
||||
this.notify("success", params);
|
||||
}
|
||||
|
||||
info(params: MessageParams) {
|
||||
this.notify("info", params);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Notifiaction(defaultConfig);
|
27
component_factory/tsconfig.json
Executable file
27
component_factory/tsconfig.json
Executable file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
585
dev_notes/LinkEditorModal/SimpleModal_v0.js
Normal file
585
dev_notes/LinkEditorModal/SimpleModal_v0.js
Normal file
@ -0,0 +1,585 @@
|
||||
|
||||
|
||||
|
||||
//HANDLE CHARACTERS LABEL UNDER OFFER -> LOAD
|
||||
useEffect(() => {
|
||||
const setLinkEditorMethods = () => {
|
||||
|
||||
//SPECIAL DISCOUNT FORM INPUT
|
||||
const form = document.querySelector(".offer-form-item") as HTMLElement;
|
||||
|
||||
if(form) {
|
||||
const inputBox = form.getElementsByClassName('ant-form-item-has-success')[0] as HTMLElement;
|
||||
const hasError = form.querySelector(".ant-form-item-explain-error") as HTMLElement;
|
||||
|
||||
//Initial Check
|
||||
if (inputBox) {
|
||||
inputBox.style.marginBottom = hasError ? "24px" : "0px";
|
||||
}
|
||||
}
|
||||
|
||||
//FRONT SECOND INPUT
|
||||
const form2 = document.querySelector(".link-editor-form") as HTMLElement;
|
||||
if(form2) {
|
||||
const inputBox2 = form2.getElementsByClassName('ant-form-item-has-success')[1] as HTMLElement;
|
||||
|
||||
//Initial Check
|
||||
if(inputBox2) {
|
||||
inputBox2.style.marginBottom = "24px";
|
||||
}
|
||||
}
|
||||
|
||||
//console.log(form.getElementsByClassName('ant-form-item-has-success'));
|
||||
|
||||
|
||||
};
|
||||
|
||||
if (isModalOpen && linkType === LINK_TYPES.SPECIAL_OFFER) {
|
||||
// Wait briefly to allow DOM to mount
|
||||
const interval = setInterval(setLinkEditorMethods, 100);
|
||||
const timeout = setTimeout(() => clearInterval(interval), 2000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}
|
||||
|
||||
if (isModalOpen && linkType === LINK_TYPES.NORMAL) {
|
||||
// Wait briefly to allow DOM to mount
|
||||
const interval = setInterval(setLinkEditorMethods, 100);
|
||||
const timeout = setTimeout(() => clearInterval(interval), 2000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
}, [isModalOpen, linkType]); // 👈 include `linkType`
|
||||
|
||||
|
||||
=================================================================================================================
|
||||
|
||||
|
||||
#V3 INCLUDING MUTATION OBSERVERS [WORKING]
|
||||
useEffect(() => {
|
||||
const adjustDiscountLabelMargin = () => {
|
||||
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;
|
||||
|
||||
//Create a Mutation Observer to Observe This Element
|
||||
const observer = new MutationObserver(() => {
|
||||
const hasError = document.querySelector(".offer-form-item .ant-form-item-explain-error");
|
||||
//discountLabel.style.marginBottom = hasError ? "24px" : "0px";
|
||||
inputBox.style.marginBottom = hasError ? "24px" : "0px";
|
||||
});
|
||||
|
||||
observer.observe(form, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
//Initial Check
|
||||
if (inputBox) {
|
||||
console.log('FIRED')
|
||||
inputBox.style.marginBottom = hasError ? "24px" : "0px";
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
|
||||
};
|
||||
|
||||
if (isModalOpen && linkType === LINK_TYPES.SPECIAL_OFFER) {
|
||||
// Wait briefly to allow DOM to mount
|
||||
const interval = setInterval(adjustDiscountLabelMargin, 100);
|
||||
const timeout = setTimeout(() => clearInterval(interval), 2000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}
|
||||
}, [isModalOpen, linkType]); // 👈 include `linkType`
|
||||
|
||||
=====================================================================================================
|
||||
|
||||
//HANDLE CHARACTERS LABEL UNDER OFFER -> LOAD
|
||||
useEffect(() => {
|
||||
const adjustDiscountLabelMargin = () => {
|
||||
const form = document.querySelector(".offer-form-item .ant-form-item-has-success") as HTMLElement;
|
||||
const hasError = document.querySelector(".ant-form-item-explain-error") as HTMLElement;
|
||||
|
||||
if (form) {
|
||||
//console.log(form);
|
||||
form.style.marginBottom = hasError ? "24px" : "0px";
|
||||
}
|
||||
};
|
||||
|
||||
if (isModalOpen && linkType === LINK_TYPES.SPECIAL_OFFER) {
|
||||
// Wait briefly to allow DOM to mount
|
||||
const interval = setInterval(adjustDiscountLabelMargin, 100);
|
||||
const timeout = setTimeout(() => clearInterval(interval), 2000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}
|
||||
}, [isModalOpen, linkType]); // 👈 include `linkType`
|
||||
|
||||
|
||||
//USE MUTATION OBSERVER TO CHECK WHEN ERROR LABEL IS ACTIVATED -> OBSERVER
|
||||
useEffect(() => {
|
||||
|
||||
console.log('FIRED');
|
||||
|
||||
if (!(isModalOpen && linkType === LINK_TYPES.SPECIAL_OFFER)) return;
|
||||
|
||||
const formItem = document.querySelector(".offer-form-item") as HTMLElement;
|
||||
const discountLabel = document.querySelector(".offer-form-item .ant-form-item-has-success") as HTMLElement;
|
||||
|
||||
if (!formItem || !discountLabel) return;
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
const hasError = document.querySelector(".offer-form-item .ant-form-item-explain-error");
|
||||
discountLabel.style.marginBottom = hasError ? "24px" : "0px";
|
||||
});
|
||||
|
||||
observer.observe(formItem, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
// Initial check
|
||||
const hasError = document.querySelector(".offer-form-item .ant-form-item-has-success");
|
||||
discountLabel.style.marginBottom = hasError ? "24px" : "0px";
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [isModalOpen, linkType]);
|
||||
|
||||
|
||||
=======================================================================================
|
||||
|
||||
"use client";
|
||||
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
|
||||
import { Upload } from "antd";
|
||||
import { Col, Row } from "antd/lib/grid";
|
||||
|
||||
import "../css/base/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/onboardingFlow.scss"
|
||||
import "../css/LinkEditorModal.scss";
|
||||
import "../css/ProductEditorModal.scss";
|
||||
import "../css/ModuleOndemandVideo.scss";
|
||||
import "../css/ModuleShopify.scss"
|
||||
|
||||
import Modal from "@/components/Modal/Modal"; // Adjust this path as needed
|
||||
import Tabs from "antd/lib/tabs";
|
||||
import Button from "antd/lib/button";
|
||||
import Alert from "antd/lib/alert";
|
||||
import * as yup from "yup";
|
||||
import { Text } from "@/components/Typography/Text";
|
||||
import classNames from "classnames";
|
||||
import ImageUpload from "@/components/ImageUpload/ImageUpload";
|
||||
|
||||
import { Field, Form, Formik, FormikProps } from "formik";
|
||||
import { LinkItem } from "@/models/talent/talent-profile-module.model";
|
||||
|
||||
import { AntInput, FieldType } from "@/components/Form/FormItem";
|
||||
|
||||
//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 = Math.max((windowHeight - ( modalHeight + 120) / 2), 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]);
|
||||
|
||||
//HANDLE CHARACTERS LABEL UNDER OFFER -> LOAD
|
||||
useEffect(() => {
|
||||
|
||||
const adjustDiscountLabelMargin = () => {
|
||||
const form = document.querySelector(".offer-form-item .ant-form-item-has-success") as HTMLElement;
|
||||
const hasError = document.querySelector(".ant-form-item-explain-error") as HTMLElement;
|
||||
|
||||
if (form) {
|
||||
//console.log(form);
|
||||
form.style.marginBottom = hasError ? "24px" : "0px";
|
||||
}
|
||||
};
|
||||
|
||||
if (isModalOpen && linkType === LINK_TYPES.SPECIAL_OFFER) {
|
||||
// Wait briefly to allow DOM to mount
|
||||
const interval = setInterval(adjustDiscountLabelMargin, 100);
|
||||
const timeout = setTimeout(() => clearInterval(interval), 2000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}
|
||||
|
||||
if (isModalOpen && linkType === LINK_TYPES.NORMAL) {
|
||||
// Wait briefly to allow DOM to mount
|
||||
|
||||
console.log('FIRED')
|
||||
const form = document.querySelector(".link-editor-form") as HTMLElement;
|
||||
const inputBox = form.getElementsByClassName('ant-input-status-success')[1] as HTMLElement;
|
||||
|
||||
|
||||
if (form) {
|
||||
//console.log(form);
|
||||
inputBox.style.removeProperty("marginBottom");
|
||||
}
|
||||
}
|
||||
|
||||
}, [isModalOpen, linkType]); // 👈 include `linkType`
|
||||
|
||||
|
||||
|
||||
|
||||
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>
|
||||
<p>This is the modal content.</p>
|
||||
|
||||
</div>
|
||||
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
416
dev_notes/LinkEditorModal/SimpleModal_v1.js
Normal file
416
dev_notes/LinkEditorModal/SimpleModal_v1.js
Normal file
@ -0,0 +1,416 @@
|
||||
"use client";
|
||||
|
||||
import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
|
||||
|
||||
import { Upload } from "antd";
|
||||
import { Col, Row } from "antd/lib/grid";
|
||||
|
||||
import "../css/base/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/onboardingFlow.scss"
|
||||
import "../css/LinkEditorModal.scss";
|
||||
import "../css/ProductEditorModal.scss";
|
||||
import "../css/ModuleOndemandVideo.scss";
|
||||
import "../css/ModuleShopify.scss"
|
||||
|
||||
import Modal from "@/components/Modal/Modal"; // Adjust this path as needed
|
||||
import Tabs from "antd/lib/tabs";
|
||||
import Button from "antd/lib/button";
|
||||
import Alert from "antd/lib/alert";
|
||||
import * as yup from "yup";
|
||||
import { Text } from "@/components/Typography/Text";
|
||||
import classNames from "classnames";
|
||||
import ImageUpload from "@/components/ImageUpload/ImageUpload";
|
||||
|
||||
import { Field, Form, Formik, FormikProps } from "formik";
|
||||
import { LinkItem } from "@/models/talent/talent-profile-module.model";
|
||||
|
||||
import { AntInput, FieldType } from "@/components/Form/FormItem";
|
||||
|
||||
//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 = Math.max((windowHeight - ( modalHeight + 120) / 2), 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]);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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>
|
||||
<p>This is the modal content.</p>
|
||||
|
||||
</div>
|
||||
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
14
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/providers/auth.js
vendored
Normal file
14
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/providers/auth.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import { createContext, useContext } from 'react';
|
||||
import { createAuthTracker as createTracker } from '../trackers.js';
|
||||
export const AuthTrackingContext = createContext({
|
||||
createTracker: () => () => { }
|
||||
});
|
||||
export const useAuthTracking = () => useContext(AuthTrackingContext);
|
||||
const useAuthTrackingProvider = ({ children }) => ({
|
||||
children,
|
||||
createTracker
|
||||
});
|
||||
const AuthTrackingProviderView = ({ children, createTracker }) => (_jsx(AuthTrackingContext.Provider, { value: { createTracker }, children: children }));
|
||||
export const AuthTrackingProvider = props => AuthTrackingProviderView(useAuthTrackingProvider(props));
|
||||
export default AuthTrackingProvider;
|
21
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/providers/consumer.js
vendored
Normal file
21
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/providers/consumer.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import { createContext, useContext, useEffect } from 'react';
|
||||
import { ConsumerTrackerBuilder, createConsumerEventTracker as createTracker } from '../trackers/consumer.js';
|
||||
export const ConsumerTrackingContext = createContext({
|
||||
createTracker: () => () => { }
|
||||
});
|
||||
export const useConsumerTracking = () => useContext(ConsumerTrackingContext);
|
||||
function useConsumerTrackingProvider({ context, children }) {
|
||||
useEffect(() => {
|
||||
if (context) {
|
||||
ConsumerTrackerBuilder.context = context;
|
||||
}
|
||||
}, [context]);
|
||||
return {
|
||||
children,
|
||||
createTracker
|
||||
};
|
||||
}
|
||||
const ConsumerTrackingProviderView = ({ children, createTracker }) => (_jsx(ConsumerTrackingContext.Provider, { value: { createTracker }, children: children }));
|
||||
export const ConsumerTrackingProvider = props => ConsumerTrackingProviderView(useConsumerTrackingProvider(props));
|
||||
export default ConsumerTrackingProvider;
|
21
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/providers/creator.js
vendored
Normal file
21
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/providers/creator.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import { createContext, useContext, useEffect } from 'react';
|
||||
import { CreatorTrackerBuilder, createCreatorEventTracker as createTracker } from '../trackers/creator.js';
|
||||
export const CreatorTrackingContext = createContext({
|
||||
createTracker: () => () => { }
|
||||
});
|
||||
export const useCreatorTracking = () => useContext(CreatorTrackingContext);
|
||||
function useCreatorTrackingProvider({ context, children }) {
|
||||
useEffect(() => {
|
||||
if (context) {
|
||||
CreatorTrackerBuilder.context = context;
|
||||
}
|
||||
}, [context]);
|
||||
return {
|
||||
children,
|
||||
createTracker
|
||||
};
|
||||
}
|
||||
const CreatorTrackingProviderView = ({ children, createTracker }) => (_jsx(CreatorTrackingContext.Provider, { value: { createTracker }, children: children }));
|
||||
export const CreatorTrackingProvider = props => CreatorTrackingProviderView(useCreatorTrackingProvider(props));
|
||||
export default CreatorTrackingProvider;
|
32
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/trackers/auth.js
vendored
Normal file
32
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/trackers/auth.js
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
import { KOMI_USER_LOCATION, authInternalTracker, authTracking } from '../shared';
|
||||
import { getPlatform, track } from '../tracking';
|
||||
import { getUTMParams } from '../utils/utm';
|
||||
import Cookies from 'js-cookie';
|
||||
import platform from 'platform';
|
||||
export class AuthTrackerBuilder {
|
||||
}
|
||||
AuthTrackerBuilder.create = (event, ...trackingArgs) => {
|
||||
const { group, fn } = authTracking[event];
|
||||
return (...trackerArgs) => {
|
||||
var _a;
|
||||
let trackerProps = void 0;
|
||||
let options;
|
||||
const trackingProps = group instanceof Function
|
||||
?
|
||||
group(...trackingArgs)
|
||||
: void 0;
|
||||
if (!fn || !fn.length) {
|
||||
options = trackerArgs[0];
|
||||
}
|
||||
else {
|
||||
trackerProps = fn(trackerArgs[0]);
|
||||
options = trackerArgs[1];
|
||||
}
|
||||
const utmParams = getUTMParams();
|
||||
const internalProps = authInternalTracker(Object.assign({ height: (window === null || window === void 0 ? void 0 : window.innerHeight) || 0, location: Cookies.get(KOMI_USER_LOCATION), os: (_a = platform.os) === null || _a === void 0 ? void 0 : _a.toString(), platform: getPlatform(), referrer: document.referrer, width: (window === null || window === void 0 ? void 0 : window.innerWidth) || 0 }, utmParams));
|
||||
const data = Object.assign(Object.assign(Object.assign({}, internalProps), trackingProps), trackerProps);
|
||||
track(event, data, options);
|
||||
};
|
||||
};
|
||||
export default AuthTrackerBuilder;
|
||||
export const { create: createAuthTracker } = AuthTrackerBuilder;
|
72
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/tracking.js
vendored
Normal file
72
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/tracking.js
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { TrackingIntregrationType } from './shared.js';
|
||||
export function track(event, eventData, options) {
|
||||
var _a, _b, _c;
|
||||
const windowOrIFrame = getWindowOrIframe((_a = options === null || options === void 0 ? void 0 : options.source) !== null && _a !== void 0 ? _a : 'client');
|
||||
return (_b = windowOrIFrame === null || windowOrIFrame === void 0 ? void 0 : windowOrIFrame.analytics) === null || _b === void 0 ? void 0 : _b.track(event, eventData, {
|
||||
integrations: getSegmentIntegrations((_c = options === null || options === void 0 ? void 0 : options.integrations) !== null && _c !== void 0 ? _c : TrackingIntregrationType.ALL)
|
||||
});
|
||||
}
|
||||
export function identify(userId, traits, options) {
|
||||
var _a, _b;
|
||||
const windowOrIFrame = getWindowOrIframe((_a = options === null || options === void 0 ? void 0 : options.source) !== null && _a !== void 0 ? _a : 'client');
|
||||
return (_b = windowOrIFrame === null || windowOrIFrame === void 0 ? void 0 : windowOrIFrame.analytics) === null || _b === void 0 ? void 0 : _b.identify(String(userId), traits);
|
||||
}
|
||||
export function identifyTalentUser({ id, email }) {
|
||||
if (!id)
|
||||
return;
|
||||
return identify(id, { email }, { source: 'talent' });
|
||||
}
|
||||
export function getPlatform() {
|
||||
const uaParserResult = UAParser();
|
||||
const isMobile = uaParserResult.device.type === 'mobile';
|
||||
return isMobile ? 'Responsive' : 'Web';
|
||||
}
|
||||
export function addToDataLayer(event) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
;
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.dataLayer.push({ event: event });
|
||||
});
|
||||
}
|
||||
function getWindowOrIframe(source) {
|
||||
if (source === 'client' || source === 'auth-spa') {
|
||||
return window;
|
||||
}
|
||||
if (source === 'talent') {
|
||||
const talentPixel = document === null || document === void 0 ? void 0 : document.getElementById('talent-pixel');
|
||||
const talentPixelWindow = talentPixel === null || talentPixel === void 0 ? void 0 : talentPixel.contentWindow;
|
||||
return talentPixelWindow;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function getSegmentIntegrations(presetName) {
|
||||
switch (presetName) {
|
||||
case 'only-google':
|
||||
return {
|
||||
All: false,
|
||||
'Google Analytics': true,
|
||||
'Google Analytics 4': true,
|
||||
'Google Analytics Web': true
|
||||
};
|
||||
case 'all-except-google':
|
||||
return {
|
||||
All: true,
|
||||
'Google Analytics': false,
|
||||
'Google Analytics 4': false,
|
||||
'Google Analytics Web': false
|
||||
};
|
||||
case 'all':
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
48
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/utils/domains.js
vendored
Normal file
48
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/utils/domains.js
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
const defaultDomainNames = ['komi', 'localhost'];
|
||||
const defaultDomainEnvs = [
|
||||
'local',
|
||||
'develop',
|
||||
'staging',
|
||||
'preprod',
|
||||
'production'
|
||||
];
|
||||
const getLastDomainNameIndex = (url, domainNames = defaultDomainNames) => {
|
||||
const domainParts = getDomainParts(url);
|
||||
let lastDomainNameIndex = -1;
|
||||
for (let i = 0; i < domainParts.length; i++) {
|
||||
const domainPart = domainParts[i];
|
||||
if (domainNames.includes(domainPart)) {
|
||||
lastDomainNameIndex = i;
|
||||
}
|
||||
}
|
||||
return lastDomainNameIndex;
|
||||
};
|
||||
export const getDomainParts = (url) => {
|
||||
const { hostname } = new URL(url);
|
||||
return hostname.split('.');
|
||||
};
|
||||
export const getRootDomain = (url, domainNames = defaultDomainNames) => {
|
||||
const domainNameIndex = getLastDomainNameIndex(url, domainNames);
|
||||
if (domainNameIndex === -1) {
|
||||
throw new Error(`URL '${url}' does not contain a known domain name`);
|
||||
}
|
||||
const domainParts = getDomainParts(url);
|
||||
const rootDomainParts = domainParts.slice(domainNameIndex);
|
||||
return rootDomainParts.join('.');
|
||||
};
|
||||
export const getEnvRootDomain = (url, domainNames = defaultDomainNames, domainEnvs = defaultDomainEnvs) => {
|
||||
const domainNameIndex = getLastDomainNameIndex(url, domainNames);
|
||||
if (domainNameIndex === -1) {
|
||||
throw new Error(`URL '${url}' does not contain a known domain name`);
|
||||
}
|
||||
const domainParts = getDomainParts(url);
|
||||
const domainEnvIndex = domainNameIndex - 1;
|
||||
const isDomainEnvPresent = domainEnvIndex > -1 && domainEnvs.includes(domainParts[domainEnvIndex]);
|
||||
const rootDomainParts = domainParts.slice(isDomainEnvPresent ? domainEnvIndex : domainNameIndex);
|
||||
return rootDomainParts.join('.');
|
||||
};
|
||||
export const getRootDomainWithPort = (url, domainNames = defaultDomainNames) => {
|
||||
const { port } = new URL(url);
|
||||
const portAppendage = port.length === 0 ? '' : `:${port}`;
|
||||
return `${getRootDomain(url, domainNames)}${portAppendage}`;
|
||||
};
|
73
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/utils/utm.js
vendored
Normal file
73
interface_base/@komi-app/analytics-sdk/dist/esm/sdk/utils/utm.js
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import { getEnvRootDomain } from './domains.js';
|
||||
const UTM_PARAM_NAMES = [
|
||||
'utm_source',
|
||||
'utm_medium',
|
||||
'utm_campaign',
|
||||
'utm_term',
|
||||
'utm_content'
|
||||
];
|
||||
export function getUTMParams() {
|
||||
const fromSearch = getUTMParamsFromSearch();
|
||||
if (fromSearch) {
|
||||
setUTMParamsInCookies(fromSearch);
|
||||
return fromSearch;
|
||||
}
|
||||
const fromCookies = getUTMParamsFromCookies();
|
||||
return fromCookies;
|
||||
}
|
||||
export function copyUTMParamsFromSearchToCookies() {
|
||||
const fromSearch = getUTMParamsFromSearch();
|
||||
if (fromSearch) {
|
||||
setUTMParamsInCookies(fromSearch);
|
||||
}
|
||||
}
|
||||
function setUTMParamsInCookies(utmParams) {
|
||||
for (const paramName of UTM_PARAM_NAMES) {
|
||||
const paramValue = utmParams[paramName];
|
||||
if (paramValue) {
|
||||
Cookies.set(paramName, paramValue, {
|
||||
domain: getEnvRootDomain(window.location.href),
|
||||
expires: 9999,
|
||||
path: '/'
|
||||
});
|
||||
}
|
||||
else {
|
||||
Cookies.remove(paramName, {
|
||||
domain: getEnvRootDomain(window.location.href),
|
||||
path: '/'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function getUTMParamsFromCookies() {
|
||||
const result = {};
|
||||
for (const paramName of UTM_PARAM_NAMES) {
|
||||
const paramValue = Cookies.get(paramName);
|
||||
if (paramValue) {
|
||||
result[paramName] = paramValue;
|
||||
}
|
||||
}
|
||||
if (Object.keys(result).length) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function getUTMParamsFromSearch() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const result = {};
|
||||
for (const paramName of UTM_PARAM_NAMES) {
|
||||
const paramValue = searchParams.get(paramName);
|
||||
if (paramValue) {
|
||||
result[paramName] = paramValue;
|
||||
}
|
||||
}
|
||||
if (Object.keys(result).length) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
2
interface_base/@komi-app/analytics-sdk/dist/esm/shared/constants.js
vendored
Normal file
2
interface_base/@komi-app/analytics-sdk/dist/esm/shared/constants.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export const FAN_ID = 'FAN_ID';
|
||||
export const KOMI_USER_LOCATION = 'KOMI_USER_LOCATION';
|
11
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/events.js
vendored
Normal file
11
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/events.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
export var AuthEvent;
|
||||
(function (AuthEvent) {
|
||||
AuthEvent["ACCEPTED_COLLABORATION_INVITE"] = "accepted_collaboration_invite";
|
||||
AuthEvent["ACCOUNT_ACTIVATION_ATTEMPT"] = "Account activation attempt";
|
||||
AuthEvent["CLICK_RESEND_ACTIVATION_EMAIL"] = "Click re-send activation email";
|
||||
AuthEvent["PROFILE_CREATION_ATTEMPT"] = "Profile creation attempt";
|
||||
AuthEvent["SIGN_UP_ATTEMPT"] = "Sign up attempt";
|
||||
AuthEvent["SIGNUP"] = "Signup";
|
||||
AuthEvent["SUCCESSFULL_LOGIN"] = "Successfull Login";
|
||||
})(AuthEvent || (AuthEvent = {}));
|
||||
export default AuthEvent;
|
@ -0,0 +1,9 @@
|
||||
import TrackingEvent from '../../../tracking-event';
|
||||
import { authEventGroup } from '../groups';
|
||||
import { getCorrelationId } from '@komi-app/correlation';
|
||||
export const acceptedCollaborationInviteEvent = new TrackingEvent(authEventGroup, ({ acceptedInvite, email }) => ({
|
||||
accepted_invite: acceptedInvite,
|
||||
anonymousID: getCorrelationId(),
|
||||
email
|
||||
}));
|
||||
export default acceptedCollaborationInviteEvent;
|
@ -0,0 +1,9 @@
|
||||
import TrackingEvent from '../../../tracking-event';
|
||||
import { boolToYesNo } from '../../../utils/transformers';
|
||||
import { authEventGroup } from '../groups';
|
||||
export const accountActivationEvent = new TrackingEvent(authEventGroup, ({ success, status, sendNumber }) => ({
|
||||
'Is successful?': boolToYesNo(success),
|
||||
'Send number': sendNumber,
|
||||
'Verification status': status
|
||||
}));
|
||||
export default accountActivationEvent;
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event';
|
||||
import { authEventGroup } from '../groups';
|
||||
export const clickResendActivationEmailEvent = new TrackingEvent(authEventGroup, ({ sendNumber }) => ({
|
||||
'Send number': sendNumber
|
||||
}));
|
||||
export default clickResendActivationEmailEvent;
|
@ -0,0 +1,10 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import { boolToYesNo } from '../../../utils/transformers.js';
|
||||
import { authEventGroup } from '../groups.js';
|
||||
export const profileCreationAttemptEvent = new TrackingEvent(authEventGroup, ({ industry, success, isMobile, isMobileOnboardingExperimentEnabled }) => ({
|
||||
Industry: industry,
|
||||
'Is successful?': boolToYesNo(success),
|
||||
isMobile,
|
||||
isMobileOnboardingExperimentEnabled
|
||||
}));
|
||||
export default profileCreationAttemptEvent;
|
@ -0,0 +1,8 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import { boolToYesNo } from '../../../utils/transformers.js';
|
||||
import { authEventGroup } from '../groups.js';
|
||||
export const signupAttemptEvent = new TrackingEvent(authEventGroup, ({ method, success }) => ({
|
||||
'Is successful?': boolToYesNo(success),
|
||||
Method: method
|
||||
}));
|
||||
export default signupAttemptEvent;
|
9
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/events/signup.js
vendored
Normal file
9
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/events/signup.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import TrackingEvent from '../../../tracking-event';
|
||||
import { authEventGroup } from '../groups';
|
||||
export const signupEvent = new TrackingEvent(authEventGroup, ({ email, userFullname, userId, createdAt }) => ({
|
||||
email: email,
|
||||
Name: userFullname,
|
||||
'Registration Date': createdAt,
|
||||
'User id': userId
|
||||
}));
|
||||
export default signupEvent;
|
10
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/events/successfull-login.js
vendored
Normal file
10
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/events/successfull-login.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
import TrackingEvent from '../../../tracking-event';
|
||||
import { boolToYesNo } from '../../../utils/transformers';
|
||||
import { authEventGroup } from '../groups';
|
||||
export const successfullLoginEvent = new TrackingEvent(authEventGroup, ({ method, success, userFullname, userId }) => ({
|
||||
'Is successful?': boolToYesNo(success),
|
||||
'Login method': method,
|
||||
'Talent User Full Name': userFullname,
|
||||
'Talent User ID': userId
|
||||
}));
|
||||
export default successfullLoginEvent;
|
2
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/groups/auth.js
vendored
Normal file
2
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/groups/auth.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export const authEventGroup = () => { };
|
||||
export default authEventGroup;
|
1
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/provider.js
vendored
Normal file
1
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/provider.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
export const authInternalTracker = ({ location, platform, height, referrer, width, city, country, os, utm_campaign, utm_medium, utm_source }) => (Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ 'Operating System': os, Platform: platform, 'Referrer Domain': referrer, 'Screen Height': height, 'Screen Width': width }, (location && { Location: location })), (country && { Country: country })), (city && { City: city })), (utm_campaign && { 'UTM Campaign': utm_campaign })), (utm_medium && { 'UTM Medium': utm_medium })), (utm_source && { 'UTM Source': utm_source })));
|
18
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/tracking.js
vendored
Normal file
18
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/auth/tracking.js
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
import AuthEvent from './events.js';
|
||||
import acceptedCollaborationInviteEvent from './events/accepted-collaboration-invite.js';
|
||||
import accountActivationEvent from './events/account-activation-item.js';
|
||||
import clickResendActivationEmailEvent from './events/click-resend-activation-email.js';
|
||||
import profileCreationAttemptEvent from './events/profile-creation-attempt.js';
|
||||
import signUpAttemptEvent from './events/signup-attempt.js';
|
||||
import signupEvent from './events/signup.js';
|
||||
import successfullLoginEvent from './events/successfull-login.js';
|
||||
export const authTracking = {
|
||||
[AuthEvent.ACCEPTED_COLLABORATION_INVITE]: acceptedCollaborationInviteEvent,
|
||||
[AuthEvent.ACCOUNT_ACTIVATION_ATTEMPT]: accountActivationEvent,
|
||||
[AuthEvent.CLICK_RESEND_ACTIVATION_EMAIL]: clickResendActivationEmailEvent,
|
||||
[AuthEvent.PROFILE_CREATION_ATTEMPT]: profileCreationAttemptEvent,
|
||||
[AuthEvent.SIGNUP]: signupEvent,
|
||||
[AuthEvent.SIGN_UP_ATTEMPT]: signUpAttemptEvent,
|
||||
[AuthEvent.SUCCESSFULL_LOGIN]: successfullLoginEvent
|
||||
};
|
||||
export default authTracking;
|
45
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/consumer/events.js
vendored
Normal file
45
interface_base/@komi-app/analytics-sdk/dist/esm/shared/services/consumer/events.js
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
export var ConsumerEvent;
|
||||
(function (ConsumerEvent) {
|
||||
ConsumerEvent["ADD_PRODUCT_TO_CART"] = "Add product to cart";
|
||||
ConsumerEvent["CLICK_AFFILIATE_PRODUCT_ELEMENT"] = "Click Affiliate product element";
|
||||
ConsumerEvent["CLICK_BANDSINTOWN_RSVP_LINK"] = "Click Bandsintown RSVP link";
|
||||
ConsumerEvent["CLICK_BANDSINTOWN_TICKET_LINK"] = "Click Bandsintown ticket link";
|
||||
ConsumerEvent["CLICK_CUSTOM_EVENT_TICKET_LINK"] = "Click Custom Event ticket link";
|
||||
ConsumerEvent["CLICK_CUSTOM_LINK"] = "Click custom link";
|
||||
ConsumerEvent["CLICK_DATA_CAPTURE_FORM"] = "Click data capture form";
|
||||
ConsumerEvent["CLICK_MEDIA_GALLERY_ITEM"] = "Click media gallery item";
|
||||
ConsumerEvent["CLICK_MEDIA_GALLERY_LINK"] = "Click media gallery link";
|
||||
ConsumerEvent["CLICK_MUSIC_PROVIDER"] = "Click music provider";
|
||||
ConsumerEvent["CLICK_PLAY_FULL_PODCAST"] = "Click Play Full Podcast";
|
||||
ConsumerEvent["CLICK_PLAY_FULL_SONG"] = "Click Play Full Song";
|
||||
ConsumerEvent["CLICK_PODCAST_PROVIDER"] = "Click podcast provider";
|
||||
ConsumerEvent["CLICK_POWERED_BY_KOMI_BUTTON"] = "Click Powered by Komi button";
|
||||
ConsumerEvent["CLICK_PRE_SAVE_PROVIDER"] = "Click pre save provider";
|
||||
ConsumerEvent["CLICK_PRODUCT_LINK"] = "Click product link";
|
||||
ConsumerEvent["CLICK_SHOPIFY_PRODUCT_ELEMENT"] = "Click Shopify product element";
|
||||
ConsumerEvent["CLICK_SHOPLIST_PRODUCT"] = "Click Shoplist product";
|
||||
ConsumerEvent["CLICK_SHOP_MY_SHELF_PRODUCT"] = "Click Shop My Shelf product";
|
||||
ConsumerEvent["CLICK_SHORT_VIDEO_ITEM"] = "Click short video item";
|
||||
ConsumerEvent["CLICK_SPECIAL_OFFER_COPY_CODE"] = "Click Special Offer copy code";
|
||||
ConsumerEvent["CLICK_THE_SEATED_PROMOTED_LINK"] = "Click the Seated Promoted link";
|
||||
ConsumerEvent["CLICK_THE_SEATED_TICKET_LINK"] = "Click the Seated Ticket link";
|
||||
ConsumerEvent["EXPAND_TEXT_ITEM"] = "Expand text item";
|
||||
ConsumerEvent["ORDER_CONFIRMED"] = "Order confirmed";
|
||||
ConsumerEvent["PLAY_YOUTUBE_STREAM"] = "Play youtube stream";
|
||||
ConsumerEvent["PREVIEW_MUSIC"] = "Preview music";
|
||||
ConsumerEvent["PREVIEW_PODCAST"] = "Preview podcast";
|
||||
ConsumerEvent["START_WATCHING_SHORT_VIDEO"] = "Start watching short video";
|
||||
ConsumerEvent["SUBMIT_DATA_CAPTURE_FORM"] = "Submit data capture form";
|
||||
ConsumerEvent["SUCCESSFULL_PRE_SAVE"] = "Successfull pre-save";
|
||||
ConsumerEvent["VIEW_CART"] = "View cart";
|
||||
ConsumerEvent["VIEW_CHECKOUT_PAGE_SHOPIFY_PRODUCT"] = "View checkout page Shopify product";
|
||||
ConsumerEvent["VIEW_PAYMENT_DETAILS"] = "View payment details";
|
||||
ConsumerEvent["VIEW_PRODUCT_PAGE"] = "View product page";
|
||||
ConsumerEvent["VIEW_SHIPPING_DETAILS"] = "View shipping details";
|
||||
ConsumerEvent["VIEW_SHIPPING_METHOD"] = "View shipping method";
|
||||
ConsumerEvent["VIEW_TALENT_PROFILE"] = "View talent profile";
|
||||
ConsumerEvent["SELECT_DCM_METHOD"] = "Select DCM method";
|
||||
ConsumerEvent["COMPLETE_DCM_STEP"] = "Complete DCM step";
|
||||
ConsumerEvent["COMPLETE_DCM_SUBSCRIBE"] = "Complete DCM subscribe";
|
||||
})(ConsumerEvent || (ConsumerEvent = {}));
|
||||
export default ConsumerEvent;
|
@ -0,0 +1,9 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const addProductToCartEvent = new TrackingEvent(consumerElementEventGroup, ({ currency, from, type, price }) => ({
|
||||
'Added from': from,
|
||||
'Commerce type': type,
|
||||
Currency: currency,
|
||||
Price: price
|
||||
}));
|
||||
export default addProductToCartEvent;
|
@ -0,0 +1,8 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import { consumerModuleEventGroup } from '../groups.js';
|
||||
export const clickAffiliateProductElementEvent = new TrackingEvent(consumerModuleEventGroup, ({ currency, price, id, name }) => ({
|
||||
Currency: currency,
|
||||
'Element ID': id,
|
||||
'Element Name': name,
|
||||
Price: price
|
||||
}));
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickBandsintownRsvpLinkEvent = new TrackingEvent(consumerElementEventGroup, ({ venue }) => ({
|
||||
'Venue Name': venue
|
||||
}));
|
||||
export default clickBandsintownRsvpLinkEvent;
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickBandsintownTicketLinkEvent = new TrackingEvent(consumerElementEventGroup, ({ venue }) => ({
|
||||
'Venue Name': venue
|
||||
}));
|
||||
export default clickBandsintownTicketLinkEvent;
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickCustomEventTicketLinkEvent = new TrackingEvent(consumerElementEventGroup, ({ title }) => ({
|
||||
Title: title
|
||||
}));
|
||||
export default clickCustomEventTicketLinkEvent;
|
@ -0,0 +1,7 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickCustomLinkEvent = new TrackingEvent(consumerElementEventGroup, ({ title, type }) => ({
|
||||
'Link Type': type,
|
||||
Title: title
|
||||
}));
|
||||
export default clickCustomLinkEvent;
|
@ -0,0 +1,7 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerModuleEventGroup from '../groups/module.js';
|
||||
export const clickDataCaptureFormEvent = new TrackingEvent(consumerModuleEventGroup, ({ id, name }) => ({
|
||||
'Element ID': id,
|
||||
'Element Name': name
|
||||
}));
|
||||
export default clickDataCaptureFormEvent;
|
@ -0,0 +1,8 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerModuleEventGroup from '../groups/module.js';
|
||||
export const clickMediaGalleryItemEvent = new TrackingEvent(consumerModuleEventGroup, ({ id, url, type }) => ({
|
||||
'Element ID': id,
|
||||
'Element Name': type,
|
||||
'Element URL': url
|
||||
}));
|
||||
export default clickMediaGalleryItemEvent;
|
@ -0,0 +1,8 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerModuleEventGroup from '../groups/module.js';
|
||||
export const clickMediaGalleryLinkEvent = new TrackingEvent(consumerModuleEventGroup, ({ id, url, type }) => ({
|
||||
'Element ID': id,
|
||||
'Element Name': type,
|
||||
'Element URL': url
|
||||
}));
|
||||
export default clickMediaGalleryLinkEvent;
|
@ -0,0 +1,11 @@
|
||||
import { TrackingEvent } from '../../../tracking-event.js';
|
||||
import { boolToYesNo } from '../../../utils/transformers.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickMusicProviderEvent = new TrackingEvent(consumerElementEventGroup, ({ destination, url, automated, service, title }) => ({
|
||||
Destination: destination,
|
||||
'Is Automated': boolToYesNo(automated),
|
||||
'Service Platform': service,
|
||||
'Smart Link URL': url,
|
||||
Title: title
|
||||
}));
|
||||
export default clickMusicProviderEvent;
|
@ -0,0 +1,9 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import { boolToYesNo } from '../../../utils/transformers.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickPlayFullPodcastEvent = new TrackingEvent(consumerElementEventGroup, ({ service, title, automated }) => ({
|
||||
'Is Automated': boolToYesNo(automated),
|
||||
'Service Platform': service,
|
||||
Title: title
|
||||
}));
|
||||
export default clickPlayFullPodcastEvent;
|
@ -0,0 +1,7 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickPlayfullSongEvent = new TrackingEvent(consumerElementEventGroup, ({ service, title }) => ({
|
||||
'Service Platform': service,
|
||||
Title: title
|
||||
}));
|
||||
export default clickPlayfullSongEvent;
|
@ -0,0 +1,11 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import { boolToYesNo } from '../../../utils/transformers.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickPodcastProvider = new TrackingEvent(consumerElementEventGroup, ({ service, title, automated, destination, url }) => ({
|
||||
Destination: destination,
|
||||
'Is Automated': boolToYesNo(automated),
|
||||
'Service Platform': service,
|
||||
'Smart Link URL': url,
|
||||
Title: title
|
||||
}));
|
||||
export default clickPodcastProvider;
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerProfileEventGroup from '../groups/profile.js';
|
||||
export const clickPoweredByKomiEvent = new TrackingEvent(consumerProfileEventGroup, ({ content }) => ({
|
||||
content
|
||||
}));
|
||||
export default clickPoweredByKomiEvent;
|
@ -0,0 +1,9 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickPreSaveProviderEvent = new TrackingEvent(consumerElementEventGroup, ({ service, title, destination, url }) => ({
|
||||
Destination: destination,
|
||||
'Service Platform': service,
|
||||
'Smart Link URL': url,
|
||||
Title: title
|
||||
}));
|
||||
export default clickPreSaveProviderEvent;
|
@ -0,0 +1,4 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickProductLinkEvent = new TrackingEvent(consumerElementEventGroup);
|
||||
export default clickProductLinkEvent;
|
@ -0,0 +1,4 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickShopMyShelfProductEvent = new TrackingEvent(consumerElementEventGroup);
|
||||
export default clickShopMyShelfProductEvent;
|
@ -0,0 +1,7 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickShopifyProductElementEvent = new TrackingEvent(consumerElementEventGroup, ({ currency, price }) => ({
|
||||
Currency: currency,
|
||||
Price: price
|
||||
}));
|
||||
export default clickShopifyProductElementEvent;
|
@ -0,0 +1,4 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickShoplistProductEvent = new TrackingEvent(consumerElementEventGroup);
|
||||
export default clickShoplistProductEvent;
|
@ -0,0 +1,4 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerModuleEventGroup from '../groups/module.js';
|
||||
export const clickShortVideoItemEvent = new TrackingEvent(consumerModuleEventGroup, (props) => props);
|
||||
export default clickShortVideoItemEvent;
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickSpecialOfferCopyCodeEvent = new TrackingEvent(consumerElementEventGroup, ({ title }) => ({
|
||||
Title: title
|
||||
}));
|
||||
export default clickSpecialOfferCopyCodeEvent;
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickTheSeatedPromotedLinkEvent = new TrackingEvent(consumerElementEventGroup, ({ venue }) => ({
|
||||
'Venue Name': venue
|
||||
}));
|
||||
export default clickTheSeatedPromotedLinkEvent;
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const clickTheSeatedTicketLinkEvent = new TrackingEvent(consumerElementEventGroup, ({ venue }) => ({
|
||||
'Venue Name': venue
|
||||
}));
|
||||
export default clickTheSeatedTicketLinkEvent;
|
@ -0,0 +1,11 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import { boolToYesNo } from '../../../utils/transformers.js';
|
||||
import consumerModuleEventGroup from '../groups/module.js';
|
||||
export const completeDcmStep = new TrackingEvent(consumerModuleEventGroup, ({ status, stepType, isRequiredStep, currentStep, totalSteps }) => ({
|
||||
currentStep,
|
||||
isRequiredStep: boolToYesNo(isRequiredStep),
|
||||
status,
|
||||
stepType,
|
||||
totalSteps
|
||||
}));
|
||||
export default completeDcmStep;
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerModuleEventGroup from '../groups/module.js';
|
||||
export const completeDcmSubscribe = new TrackingEvent(consumerModuleEventGroup, ({ totalSteps }) => ({
|
||||
totalSteps
|
||||
}));
|
||||
export default completeDcmSubscribe;
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerModuleEventGroup from '../groups/module.js';
|
||||
export const expandTextItemEvent = new TrackingEvent(consumerModuleEventGroup, ({ id }) => ({
|
||||
'Element ID': id
|
||||
}));
|
||||
export default expandTextItemEvent;
|
@ -0,0 +1,8 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerProfileEventGroup from '../groups/profile.js';
|
||||
export const orderConfirmedEvent = new TrackingEvent(consumerProfileEventGroup, ({ currency, quantity, price }) => ({
|
||||
'Cart value': price,
|
||||
Currency: currency,
|
||||
'Products in cart': quantity
|
||||
}));
|
||||
export default orderConfirmedEvent;
|
@ -0,0 +1,4 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const playYoutubeStreamEvent = new TrackingEvent(consumerElementEventGroup);
|
||||
export default playYoutubeStreamEvent;
|
@ -0,0 +1,7 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const previewMusicEvent = new TrackingEvent(consumerElementEventGroup, ({ service, title }) => ({
|
||||
'Service Platform': service,
|
||||
Title: title
|
||||
}));
|
||||
export default previewMusicEvent;
|
@ -0,0 +1,9 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import { boolToYesNo } from '../../../utils/transformers.js';
|
||||
import consumerElementEventGroup from '../groups/element.js';
|
||||
export const previewPodcastEvent = new TrackingEvent(consumerElementEventGroup, ({ service, title, automated }) => ({
|
||||
'Is Automated': boolToYesNo(automated),
|
||||
'Service Platform': service,
|
||||
Title: title
|
||||
}));
|
||||
export default previewPodcastEvent;
|
@ -0,0 +1,6 @@
|
||||
import TrackingEvent from '../../../tracking-event.js';
|
||||
import consumerModuleEventGroup from '../groups/module.js';
|
||||
export const selectDcmMethod = new TrackingEvent(consumerModuleEventGroup, ({ methodType }) => ({
|
||||
methodType
|
||||
}));
|
||||
export default selectDcmMethod;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user