[FACTORY]: Init Commit

This commit is contained in:
SPACEBROWSER_DEV 2025-08-07 20:28:59 -04:00
commit ad09e57c41
2049 changed files with 197190 additions and 0 deletions

28
component_factory/README.md Executable file
View 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
View 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.

View 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
View 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"
}
}

View File

@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;

View 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

View 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

View 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

View 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

View 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

View 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;
}

View 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;
}
}
}
}
}

View 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%;
}
}

View 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;
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}

View 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;
}
}

View File

@ -0,0 +1,7 @@
.link-editor-modal {
.d--block {
display: block !important;
}
}

View 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;
}
}**/
}

View 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;
}
}

View 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;
}
}

View File

@ -0,0 +1,3 @@
.link-editor-modal .ant-tabs-tab, .ant-tabs-tab:hover {
color: #121212;
}

View 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;
}
}

View 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;
}

View 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;
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View 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;
}

View 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>
);
}

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

View File

@ -0,0 +1,463 @@
"use client";
import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import { Upload } from "antd";
import { Col, Row } from "antd/lib/grid";
import "../css/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 theyll be able to copy the discount code (if applicable) and directed to the website."
}
icon={<InfoIcon width={24} height={24} />}
showIcon
closeText={
<CloseOutlineIcon width={24} height={24} name="close-outline" />
}
onClose={() => {
setShowAlert(false);
localStorage.setItem(
"CLOSE_ALERT_ADD_SPECIAL_OFFER",
"true"
);
}}
/>
)}
</Tabs.TabPane>
)}
</Tabs>
<div>
{/* IMAGE UPLOAD BUTTON */}
<div className={isEdit ? "" : "m__y--32"}>
<Row align="middle" justify="center">
<Col>
<ImageUpload
className="product-editor__cover m__b--20"
photo={dummyPhoto}
onUploadSuccess={onUploadSuccess}
onRemove={onRemove}
dropAspect={1}
/>
</Col>
<Col className="m__b--4" span={24}>
<Text className="d--block text__align--center" preset="semibold18">
Thumbnail Photo
</Text>
</Col>
<Col span={24}>
<Text
className="d--block text__align--center text--grey4"
preset="regular14"
>
{/* //TODO */}
Use a size thats at least 369 x 369 pixels and 6MB or less
</Text>
</Col>
</Row>
</div>
<Formik
initialValues={initialValues as LinkEditorModalFormValues}
onSubmit={handleSubmit}
validationSchema={
linkType === LINK_TYPES.NORMAL
? validateLinkSchema
: validateSpecialOfferSchema
}
>
<>
<Form
className="link-editor-form"
>
{linkType === LINK_TYPES.NORMAL ? (
<>
<Field
component={AntInput}
name="url"
type={FieldType.input}
label="URL"
placeholder="https://example.com"
/>
<Field
component={AntInput}
name="title"
type={FieldType.input}
label="Title"
placeholder="Enter title of the link"
/>
</>
) : (
<>
<Field
component={AntInput}
name="specialOffer.storeUrl"
type={FieldType.input}
label="URL"
placeholder="https://example.com"
className="m__b--20"
/>
<Field
component={AntInput}
name="specialOffer.title"
type={FieldType.input}
label="What is the offer?"
placeholder="Add the offer information"
className="offer-form-item"
/>
<Text
id="discountLabel" className="d--block m__t--8 m__b--20 opacity--60" preset="regular14">{`0/50 characters`}</Text>
<Field
component={AntInput}
name="specialOffer.couponCode"
type={FieldType.input}
label="Discount code (optional)"
placeholder="Add the discount code"
/>
</>
)}
</Form>
<div className="link-editor__actions m__t--16">
<Button
className="ant-btn-xl ant-btn-uppercase"
>
Cancel
</Button>
<Button
className="ant-btn-xl ant-btn-uppercase"
type="primary"
>
Done
</Button>
</div>
</>
</Formik>
</div>
</Modal>
</>
);
}

View File

@ -0,0 +1,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);

View 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;

View 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;

View 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} />;
};

View 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} />;
};

View 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;

View 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} />;
};

View 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} />;
};

View 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;

View 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;

View 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;

View File

@ -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;
};
};
}

View File

@ -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;
}
}

View 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[];
};

View 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
View 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"]
}

View 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 theyll be able to copy the discount code (if applicable) and directed to the website."
}
icon={<InfoIcon width={24} height={24} />}
showIcon
closeText={
<CloseOutlineIcon width={24} height={24} name="close-outline" />
}
onClose={() => {
setShowAlert(false);
localStorage.setItem(
"CLOSE_ALERT_ADD_SPECIAL_OFFER",
"true"
);
}}
/>
)}
</Tabs.TabPane>
)}
</Tabs>
<div>
{/* IMAGE UPLOAD BUTTON */}
<div className={isEdit ? "" : "m__y--32"}>
<Row align="middle" justify="center">
<Col>
<ImageUpload
className="product-editor__cover m__b--20"
photo={dummyPhoto}
onUploadSuccess={onUploadSuccess}
onRemove={onRemove}
dropAspect={1}
/>
</Col>
<Col className="m__b--4" span={24}>
<Text className="d--block text__align--center" preset="semibold18">
Thumbnail Photo
</Text>
</Col>
<Col span={24}>
<Text
className="d--block text__align--center text--grey4"
preset="regular14"
>
{/* //TODO */}
Use a size thats at least 369 x 369 pixels and 6MB or less
</Text>
</Col>
</Row>
</div>
<Formik
initialValues={initialValues as LinkEditorModalFormValues}
onSubmit={handleSubmit}
validationSchema={
linkType === LINK_TYPES.NORMAL
? validateLinkSchema
: validateSpecialOfferSchema
}
>
<>
<Form
className="link-editor-form"
>
{linkType === LINK_TYPES.NORMAL ? (
<>
<Field
component={AntInput}
name="url"
type={FieldType.input}
label="URL"
placeholder="https://example.com"
/>
<Field
component={AntInput}
name="title"
type={FieldType.input}
label="Title"
placeholder="Enter title of the link"
/>
</>
) : (
<>
<Field
component={AntInput}
name="specialOffer.storeUrl"
type={FieldType.input}
label="URL"
placeholder="https://example.com"
className="m__b--20"
/>
<Field
component={AntInput}
name="specialOffer.title"
type={FieldType.input}
label="What is the offer?"
placeholder="Add the offer information"
className="offer-form-item"
/>
<Text
id="discountLabel" className="d--block m__t--8 m__b--20 opacity--60" preset="regular14">{`0/50 characters`}</Text>
<Field
component={AntInput}
name="specialOffer.couponCode"
type={FieldType.input}
label="Discount code (optional)"
placeholder="Add the discount code"
/>
</>
)}
</Form>
<div className="link-editor__actions m__t--16">
<Button
className="ant-btn-xl ant-btn-uppercase"
>
Cancel
</Button>
<Button
className="ant-btn-xl ant-btn-uppercase"
type="primary"
>
Done
</Button>
</div>
</>
</Formik>
<p>This is the modal content.</p>
</div>
</Modal>
</>
);
}

View 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 theyll be able to copy the discount code (if applicable) and directed to the website."
}
icon={<InfoIcon width={24} height={24} />}
showIcon
closeText={
<CloseOutlineIcon width={24} height={24} name="close-outline" />
}
onClose={() => {
setShowAlert(false);
localStorage.setItem(
"CLOSE_ALERT_ADD_SPECIAL_OFFER",
"true"
);
}}
/>
)}
</Tabs.TabPane>
)}
</Tabs>
<div>
{/* IMAGE UPLOAD BUTTON */}
<div className={isEdit ? "" : "m__y--32"}>
<Row align="middle" justify="center">
<Col>
<ImageUpload
className="product-editor__cover m__b--20"
photo={dummyPhoto}
onUploadSuccess={onUploadSuccess}
onRemove={onRemove}
dropAspect={1}
/>
</Col>
<Col className="m__b--4" span={24}>
<Text className="d--block text__align--center" preset="semibold18">
Thumbnail Photo
</Text>
</Col>
<Col span={24}>
<Text
className="d--block text__align--center text--grey4"
preset="regular14"
>
{/* //TODO */}
Use a size thats at least 369 x 369 pixels and 6MB or less
</Text>
</Col>
</Row>
</div>
<Formik
initialValues={initialValues as LinkEditorModalFormValues}
onSubmit={handleSubmit}
validationSchema={
linkType === LINK_TYPES.NORMAL
? validateLinkSchema
: validateSpecialOfferSchema
}
>
<>
<Form
className="link-editor-form"
>
{linkType === LINK_TYPES.NORMAL ? (
<>
<Field
component={AntInput}
name="url"
type={FieldType.input}
label="URL"
placeholder="https://example.com"
/>
<Field
component={AntInput}
name="title"
type={FieldType.input}
label="Title"
placeholder="Enter title of the link"
/>
</>
) : (
<>
<Field
component={AntInput}
name="specialOffer.storeUrl"
type={FieldType.input}
label="URL"
placeholder="https://example.com"
className="m__b--20"
/>
<Field
component={AntInput}
name="specialOffer.title"
type={FieldType.input}
label="What is the offer?"
placeholder="Add the offer information"
className="offer-form-item"
/>
<Text
id="discountLabel" className="d--block m__t--8 m__b--20 opacity--60" preset="regular14">{`0/50 characters`}</Text>
<Field
component={AntInput}
name="specialOffer.couponCode"
type={FieldType.input}
label="Discount code (optional)"
placeholder="Add the discount code"
/>
</>
)}
</Form>
<div className="link-editor__actions m__t--16">
<Button
className="ant-btn-xl ant-btn-uppercase"
>
Cancel
</Button>
<Button
className="ant-btn-xl ant-btn-uppercase"
type="primary"
>
Done
</Button>
</div>
</>
</Formik>
<p>This is the modal content.</p>
</div>
</Modal>
</>
);
}

View 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;

View 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;

View 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;

View 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;

View 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;
}
}

View 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}`;
};

View 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;
}
}

View File

@ -0,0 +1,2 @@
export const FAN_ID = 'FAN_ID';
export const KOMI_USER_LOCATION = 'KOMI_USER_LOCATION';

View 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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View 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;

View 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;

View File

@ -0,0 +1,2 @@
export const authEventGroup = () => { };
export default authEventGroup;

View 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 })));

View 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;

View 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;

View File

@ -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;

View File

@ -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
}));

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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