Add org ACL, form grants, and Crest microsite v2 support.
Merge form submission reply mail with permission checks and grant APIs for scoped form access. Co-authored-by: Cursor <cursoragent@cursor.com>
12
.env.example
|
|
@ -99,6 +99,18 @@ MICROSITE_API_BASE_URL=http://localhost:3001/api
|
|||
# s3://{AWS_BUCKET}/{MICROSITE_V2_S3_PREFIX}/sites/{subdomain}/index.html
|
||||
# Dev fallback: open published HTML via CloudFront URL of index.html key directly.
|
||||
# Seed first template: npm run seed:microsite-v2-template
|
||||
#
|
||||
# Published-site target: s3 (default) | ec2. When ec2, rendered index.html +
|
||||
# config.json are uploaded via SFTP to {REMOTE_ROOT}/{subdomain}/ on the EC2 box.
|
||||
# Template HTML/schema/assets are still loaded from S3 regardless of this setting.
|
||||
# MICROSITE_V2_PUBLISH_TARGET=ec2
|
||||
# MICROSITE_V2_EC2_HOST=15.207.15.52
|
||||
# MICROSITE_V2_EC2_USER=ubuntu
|
||||
# MICROSITE_V2_EC2_PORT=22
|
||||
# Path to the SSH private key ON THE API SERVER (chmod 600). Not your laptop path.
|
||||
# MICROSITE_V2_EC2_KEY_PATH=/secure/path/VirtualEngage.pem
|
||||
# MICROSITE_V2_EC2_REMOTE_ROOT=/var/www/eventbuzz
|
||||
# MICROSITE_V2_EC2_CONNECT_TIMEOUT_MS=20000
|
||||
|
||||
# Comma-separated CORS origins (include console + public web app)
|
||||
# CORS_ORIGIN=https://console.eventbuzz.live,https://web.eventbuzz.live
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
/*!
|
||||
* Nivo Lightbox v1.3.1
|
||||
* http://dev7studios.com/nivo-lightbox
|
||||
*
|
||||
* Copyright 2013, Dev7studios
|
||||
* Free to use and abuse under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
.nivo-lightbox-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999999;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.nivo-lightbox-overlay.nivo-lightbox-open {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.nivo-lightbox-wrap {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
bottom: 10%;
|
||||
left: 10%;
|
||||
right: 10%;
|
||||
}
|
||||
.nivo-lightbox-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.nivo-lightbox-title-wrap {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 99999;
|
||||
text-align: center;
|
||||
}
|
||||
.nivo-lightbox-nav { display: none; }
|
||||
.nivo-lightbox-prev {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
}
|
||||
.nivo-lightbox-next {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
}
|
||||
.nivo-lightbox-close {
|
||||
position: absolute;
|
||||
top: 2%;
|
||||
right: 2%;
|
||||
}
|
||||
|
||||
.nivo-lightbox-image { text-align: center; }
|
||||
.nivo-lightbox-image img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.nivo-lightbox-content iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.nivo-lightbox-inline,
|
||||
.nivo-lightbox-ajax {
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
/* https://bugzilla.mozilla.org/show_bug.cgi?id=308801 */
|
||||
}
|
||||
.nivo-lightbox-error {
|
||||
display: table;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 1px #000;
|
||||
}
|
||||
.nivo-lightbox-error p {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Effects
|
||||
**********************************************/
|
||||
.nivo-lightbox-notouch .nivo-lightbox-effect-fade,
|
||||
.nivo-lightbox-notouch .nivo-lightbox-effect-fadeScale,
|
||||
.nivo-lightbox-notouch .nivo-lightbox-effect-slideLeft,
|
||||
.nivo-lightbox-notouch .nivo-lightbox-effect-slideRight,
|
||||
.nivo-lightbox-notouch .nivo-lightbox-effect-slideUp,
|
||||
.nivo-lightbox-notouch .nivo-lightbox-effect-slideDown,
|
||||
.nivo-lightbox-notouch .nivo-lightbox-effect-fall {
|
||||
-webkit-transition: all 0.2s ease-in-out;
|
||||
-moz-transition: all 0.2s ease-in-out;
|
||||
-ms-transition: all 0.2s ease-in-out;
|
||||
-o-transition: all 0.2s ease-in-out;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* fadeScale */
|
||||
.nivo-lightbox-effect-fadeScale .nivo-lightbox-wrap {
|
||||
-webkit-transition: all 0.3s;
|
||||
-moz-transition: all 0.3s;
|
||||
-ms-transition: all 0.3s;
|
||||
-o-transition: all 0.3s;
|
||||
transition: all 0.3s;
|
||||
-webkit-transform: scale(0.7);
|
||||
-moz-transform: scale(0.7);
|
||||
-ms-transform: scale(0.7);
|
||||
transform: scale(0.7);
|
||||
}
|
||||
.nivo-lightbox-effect-fadeScale.nivo-lightbox-open .nivo-lightbox-wrap {
|
||||
-webkit-transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/* slideLeft / slideRight / slideUp / slideDown */
|
||||
.nivo-lightbox-effect-slideLeft .nivo-lightbox-wrap,
|
||||
.nivo-lightbox-effect-slideRight .nivo-lightbox-wrap,
|
||||
.nivo-lightbox-effect-slideUp .nivo-lightbox-wrap,
|
||||
.nivo-lightbox-effect-slideDown .nivo-lightbox-wrap {
|
||||
-webkit-transition: all 0.3s cubic-bezier(0.25, 0.5, 0.5, 0.9);
|
||||
-moz-transition: all 0.3s cubic-bezier(0.25, 0.5, 0.5, 0.9);
|
||||
-ms-transition: all 0.3s cubic-bezier(0.25, 0.5, 0.5, 0.9);
|
||||
-o-transition: all 0.3s cubic-bezier(0.25, 0.5, 0.5, 0.9);
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.5, 0.5, 0.9);
|
||||
}
|
||||
.nivo-lightbox-effect-slideLeft .nivo-lightbox-wrap {
|
||||
-webkit-transform: translateX(-10%);
|
||||
-moz-transform: translateX(-10%);
|
||||
-ms-transform: translateX(-10%);
|
||||
transform: translateX(-10%);
|
||||
}
|
||||
.nivo-lightbox-effect-slideRight .nivo-lightbox-wrap {
|
||||
-webkit-transform: translateX(10%);
|
||||
-moz-transform: translateX(10%);
|
||||
-ms-transform: translateX(10%);
|
||||
transform: translateX(10%);
|
||||
}
|
||||
.nivo-lightbox-effect-slideLeft.nivo-lightbox-open .nivo-lightbox-wrap,
|
||||
.nivo-lightbox-effect-slideRight.nivo-lightbox-open .nivo-lightbox-wrap {
|
||||
-webkit-transform: translateX(0);
|
||||
-moz-transform: translateX(0);
|
||||
-ms-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
.nivo-lightbox-effect-slideDown .nivo-lightbox-wrap {
|
||||
-webkit-transform: translateY(-10%);
|
||||
-moz-transform: translateY(-10%);
|
||||
-ms-transform: translateY(-10%);
|
||||
transform: translateY(-10%);
|
||||
}
|
||||
.nivo-lightbox-effect-slideUp .nivo-lightbox-wrap {
|
||||
-webkit-transform: translateY(10%);
|
||||
-moz-transform: translateY(10%);
|
||||
-ms-transform: translateY(10%);
|
||||
transform: translateY(10%);
|
||||
}
|
||||
.nivo-lightbox-effect-slideUp.nivo-lightbox-open .nivo-lightbox-wrap,
|
||||
.nivo-lightbox-effect-slideDown.nivo-lightbox-open .nivo-lightbox-wrap {
|
||||
-webkit-transform: translateY(0);
|
||||
-moz-transform: translateY(0);
|
||||
-ms-transform: translateY(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* fall */
|
||||
.nivo-lightbox-body-effect-fall .nivo-lightbox-effect-fall {
|
||||
-webkit-perspective: 1000px;
|
||||
-moz-perspective: 1000px;
|
||||
perspective: 1000px;
|
||||
}
|
||||
.nivo-lightbox-effect-fall .nivo-lightbox-wrap {
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
-moz-transition: all 0.3s ease-out;
|
||||
-ms-transition: all 0.3s ease-out;
|
||||
-o-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
-webkit-transform: translateZ(300px);
|
||||
-moz-transform: translateZ(300px);
|
||||
-ms-transform: translateZ(300px);
|
||||
transform: translateZ(300px);
|
||||
}
|
||||
.nivo-lightbox-effect-fall.nivo-lightbox-open .nivo-lightbox-wrap {
|
||||
-webkit-transform: translateZ(0);
|
||||
-moz-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.icon-close{
|
||||
font-size: 24px;
|
||||
}
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
/* only small desktops */
|
||||
@media (min-width: 992px) and (max-width: 1199px) {
|
||||
#hero-area .contents .head-title {
|
||||
font-size: 40px;
|
||||
}
|
||||
.navbar-expand-lg .navbar-nav .nav-link {
|
||||
padding: 0 10px;
|
||||
}
|
||||
.single-intro-text h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
.counter-section .counter p {
|
||||
font-size: 14px;
|
||||
}
|
||||
.schedule .card .schedule-slot-time {
|
||||
padding: 38px 22px;
|
||||
}
|
||||
#pricing .price-block-wrapper .pricing-list {
|
||||
padding: 10px 35px 10px 45px;
|
||||
}
|
||||
.blog-item .descr h3 a {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* tablets */
|
||||
@media (max-width: 991px) {
|
||||
#count .count-wrapper {
|
||||
top: -20px;
|
||||
margin-top: -20px;
|
||||
margin-bottom: -24px;
|
||||
padding: 10px 8px;
|
||||
}
|
||||
|
||||
#clock.time-count {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.time-entry {
|
||||
float: none;
|
||||
width: auto;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0 4px;
|
||||
font-size: 11px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.time-entry span {
|
||||
font-size: 22px;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.time-entry b {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero-split {
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-item--register {
|
||||
margin-left: 0;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 16px;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-item--register .btn.btn-common {
|
||||
width: auto;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.page-title-section {
|
||||
padding: 40px 0;
|
||||
}
|
||||
.section-titile-bg {
|
||||
display: none;
|
||||
}
|
||||
.section-title {
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.about-item {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.accordion .header-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
.schedule .schedule-tab-title .nav-tabs .nav-link {
|
||||
width: 150px;
|
||||
}
|
||||
.schedule .card-header h4 {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
#pricing .price-block-wrapper .pricing-list {
|
||||
padding: 10px 35px 10px 45px;
|
||||
}
|
||||
#sponsors .spnsors-logo img {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
/* only small tablets */
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
#hero-area .contents .head-title {
|
||||
font-size: 36px;
|
||||
}
|
||||
#information-bar .info-bar-item {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.about-content .about-text h2 {
|
||||
font-size: 30px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.single-intro-text h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
.countdown-timer .heading-count h2 {
|
||||
font-size: 25px;
|
||||
}
|
||||
.schedule .card-header {
|
||||
padding: 20px 40px 35px 180px;
|
||||
}
|
||||
.schedule .card-header.schedule-card-header-static {
|
||||
padding: 20px 30px;
|
||||
}
|
||||
.schedule .schedule-session-body {
|
||||
padding: 20px 30px;
|
||||
}
|
||||
.schedule .card .schedule-slot-time {
|
||||
font-size: 14px;
|
||||
padding: 29px;
|
||||
max-width: 22%;
|
||||
}
|
||||
.schedule .schedule-tab-title .nav-tabs .nav-link {
|
||||
width: 150px;
|
||||
}
|
||||
.schedule .card-header h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
.counter-section .counter {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
#pricing .price-block-wrapper .pricing-list {
|
||||
padding: 10px 35px 10px 60px;
|
||||
}
|
||||
.about-item {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.accordion .header-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
#pricing .price-block-wrapper .pricing-list {
|
||||
padding: 10px 60px 10px 45px;
|
||||
}
|
||||
#sponsors .spnsors-logo {
|
||||
padding: 30px;
|
||||
}
|
||||
#event-up .event-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.footer-area h3 {
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
/* mobile or only mobile */
|
||||
@media (max-width: 767px) {
|
||||
.section-title {
|
||||
font-size: 22px;
|
||||
}
|
||||
.section-sub {
|
||||
font-size: 16px;
|
||||
}
|
||||
.hero-split-container {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
#hero-area .overlay::before {
|
||||
width: 100%;
|
||||
height: 70%;
|
||||
left: -0%;
|
||||
top: -10%;
|
||||
}
|
||||
|
||||
.hero-split {
|
||||
min-height: auto;
|
||||
padding: 100px 0 90px;
|
||||
}
|
||||
|
||||
.hero-split__media {
|
||||
margin-bottom: 24px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.hero-split__content {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.hero-split__media .hero-logo {
|
||||
max-height: min(220px, 40vw);
|
||||
}
|
||||
|
||||
#hero-area .hero-split__text .head-title,
|
||||
#hero-area .hero-split__text .hero-brand-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
#hero-area .hero-split__text .banner-info {
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#hero-area .hero-split__text .btn {
|
||||
margin: 0;
|
||||
}
|
||||
.video-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.schedule .schedule-session-row {
|
||||
flex-wrap: wrap;
|
||||
padding: 16px 20px;
|
||||
gap: 6px 10px;
|
||||
}
|
||||
.schedule .schedule-session-sep {
|
||||
display: none;
|
||||
}
|
||||
.schedule .schedule-session-row .schedule-session-body h4 {
|
||||
display: block;
|
||||
}
|
||||
.schedule .schedule-session-row .schedule-session-body .session-type {
|
||||
display: block;
|
||||
margin-left: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
#information-bar .info-bar-item {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.about-content .about-text h2 {
|
||||
font-size: 22px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.about-content ul li {
|
||||
font-size: 12px;
|
||||
}
|
||||
.time-entry {
|
||||
float: none;
|
||||
width: auto;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0 2px;
|
||||
font-size: 9px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.time-entry span {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.time-entry b {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#count .count-wrapper {
|
||||
top: -12px;
|
||||
margin-top: -12px;
|
||||
padding: 8px 4px;
|
||||
}
|
||||
.counter-section .counter {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.schedule .nav-tabs .nav-item {
|
||||
margin: 15px 0px;
|
||||
}
|
||||
.schedule .nav-tabs .nav-link {
|
||||
padding: 12px 10px;
|
||||
}
|
||||
.schedule .item-text h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
.schedule .card-body {
|
||||
padding: 15px;
|
||||
}
|
||||
.schedule .card .schedule-slot-time {
|
||||
display: flex;
|
||||
flex: 0 0 22%;
|
||||
max-width: 22%;
|
||||
font-size: 12px;
|
||||
padding: 20px 12px;
|
||||
}
|
||||
.schedule .card-header {
|
||||
padding: 30px;
|
||||
}
|
||||
.schedule .card-header.schedule-card-header-static {
|
||||
padding: 20px;
|
||||
}
|
||||
.schedule .schedule-session-body {
|
||||
padding: 20px;
|
||||
}
|
||||
.schedule .schedule-tab-title .nav-tabs .nav-link {
|
||||
width: 288px;
|
||||
}
|
||||
#event-up .event-item {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
#pricing .price-block-wrapper .pricing-list {
|
||||
padding: 10px 30px 10px 40px;
|
||||
}
|
||||
#sponsors .spnsors-logo {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.blog-item .descr h3 a {
|
||||
font-size: 18px;
|
||||
}
|
||||
.container-form .form-wrapper {
|
||||
padding: 20px;
|
||||
}
|
||||
.contact-item {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
#sponsors .spnsors-logo img {
|
||||
width: 80%;
|
||||
}
|
||||
#contact-text ul {
|
||||
text-align: left;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.subscribe-inner .subscribe-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.subscribe-inner .subscribe-title::before {
|
||||
left: 40%;
|
||||
}
|
||||
.subscribe-inner .sub-btn {
|
||||
width: 9rem;
|
||||
min-width: 5rem;
|
||||
padding: 18px 20px;
|
||||
}
|
||||
.footer-col-brand,
|
||||
.footer-col-links,
|
||||
.footer-col-social {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.footer-col-social {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 320px) and (max-width: 480px) {
|
||||
.btn {
|
||||
padding: 5px 7px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 560 KiB |
|
After Width: | Height: | Size: 560 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
|
@ -0,0 +1,55 @@
|
|||
$("#contactForm").validator().on("submit", function (event) {
|
||||
if (event.isDefaultPrevented()) {
|
||||
// handle the invalid form...
|
||||
formError();
|
||||
submitMSG(false, "Did you fill in the form properly?");
|
||||
} else {
|
||||
// everything looks good!
|
||||
event.preventDefault();
|
||||
submitForm();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function submitForm(){
|
||||
// Initiate Variables With Form Content
|
||||
var name = $("#name").val();
|
||||
var email = $("#email").val();
|
||||
var msg_subject = $("#msg_subject").val();
|
||||
var message = $("#message").val();
|
||||
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "assets/php/form-process.php",
|
||||
data: "name=" + name + "&email=" + email + "&msg_subject=" + msg_subject + "&message=" + message,
|
||||
success : function(text){
|
||||
if (text == "success"){
|
||||
formSuccess();
|
||||
} else {
|
||||
formError();
|
||||
submitMSG(false,text);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formSuccess(){
|
||||
$("#contactForm")[0].reset();
|
||||
submitMSG(true, "Message Submitted!")
|
||||
}
|
||||
|
||||
function formError(){
|
||||
$("#contactForm").removeClass().addClass('shake animated').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
|
||||
$(this).removeClass();
|
||||
});
|
||||
}
|
||||
|
||||
function submitMSG(valid, msg){
|
||||
if(valid){
|
||||
var msgClasses = "h3 text-left tada animated text-success";
|
||||
} else {
|
||||
var msgClasses = "h3 text-left text-danger";
|
||||
}
|
||||
$("#msgSubmit").removeClass().addClass(msgClasses).text(msg);
|
||||
}
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var formEl = null;
|
||||
var captchaToken = '';
|
||||
var captchaQuestionEl = null;
|
||||
var captchaInputEl = null;
|
||||
var captchaReloadBtn = null;
|
||||
var submitBtn = null;
|
||||
var msgEl = null;
|
||||
var currentConfig = null;
|
||||
var localCaptchaAnswer = null;
|
||||
var captchaMode = 'server';
|
||||
var fieldMap = {
|
||||
name: 'Name',
|
||||
email: 'Email',
|
||||
subject: 'Subject',
|
||||
message: 'Message'
|
||||
};
|
||||
|
||||
function apiBase(config) {
|
||||
var base = (config && config.apiBaseUrl) || '';
|
||||
return String(base).replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function parseJsonResponse(response) {
|
||||
return response.text().then(function (text) {
|
||||
var body = null;
|
||||
try {
|
||||
body = JSON.parse(text);
|
||||
} catch (error) {
|
||||
body = null;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
var message = (body && body.error && body.error.message) || text || 'Request failed';
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return body;
|
||||
});
|
||||
}
|
||||
|
||||
function showMessage(message, isError) {
|
||||
if (!msgEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
msgEl.classList.remove('hidden', 'text-success', 'text-danger');
|
||||
msgEl.classList.add(isError ? 'text-danger' : 'text-success');
|
||||
msgEl.textContent = message;
|
||||
}
|
||||
|
||||
function setSubmitting(isSubmitting) {
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = isSubmitting;
|
||||
}
|
||||
if (captchaInputEl) {
|
||||
captchaInputEl.disabled = isSubmitting;
|
||||
}
|
||||
if (captchaReloadBtn) {
|
||||
captchaReloadBtn.disabled = isSubmitting;
|
||||
}
|
||||
}
|
||||
|
||||
function loadLocalCaptcha() {
|
||||
var a = Math.floor(Math.random() * 9) + 1;
|
||||
var b = Math.floor(Math.random() * 9) + 1;
|
||||
|
||||
captchaMode = 'local';
|
||||
captchaToken = '';
|
||||
localCaptchaAnswer = String(a + b);
|
||||
|
||||
if (captchaQuestionEl) {
|
||||
captchaQuestionEl.textContent = a + ' + ' + b + ' = ?';
|
||||
}
|
||||
if (captchaInputEl) {
|
||||
captchaInputEl.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function loadCaptcha() {
|
||||
var base = apiBase(currentConfig);
|
||||
if (!base) {
|
||||
loadLocalCaptcha();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (captchaQuestionEl) {
|
||||
captchaQuestionEl.textContent = 'Loading…';
|
||||
}
|
||||
|
||||
return fetch(base + '/public/captcha/challenge', { cache: 'no-store' })
|
||||
.then(parseJsonResponse)
|
||||
.then(function (body) {
|
||||
var data = body && body.data ? body.data : body;
|
||||
captchaMode = 'server';
|
||||
localCaptchaAnswer = null;
|
||||
captchaToken = (data && data.captchaToken) || '';
|
||||
|
||||
if (!captchaToken) {
|
||||
loadLocalCaptcha();
|
||||
return;
|
||||
}
|
||||
|
||||
if (captchaQuestionEl) {
|
||||
captchaQuestionEl.textContent = (data && data.question) || 'Loading…';
|
||||
}
|
||||
if (captchaInputEl) {
|
||||
captchaInputEl.value = '';
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
loadLocalCaptcha();
|
||||
});
|
||||
}
|
||||
|
||||
function applyFieldMapFromDefinition(formDefinition) {
|
||||
var fields = (formDefinition && formDefinition.fields) || [];
|
||||
fields.forEach(function (field) {
|
||||
if (!field || !field.label) {
|
||||
return;
|
||||
}
|
||||
|
||||
var label = String(field.label).trim();
|
||||
var lower = label.toLowerCase();
|
||||
|
||||
if (lower === 'name' || lower.indexOf('name') >= 0) {
|
||||
fieldMap.name = label;
|
||||
} else if (field.type === 'email' || lower === 'email') {
|
||||
fieldMap.email = label;
|
||||
} else if (lower === 'subject') {
|
||||
fieldMap.subject = label;
|
||||
} else if (field.type === 'textarea' || lower === 'message') {
|
||||
fieldMap.message = label;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadFormDefinition(config, eventId) {
|
||||
var base = apiBase(config);
|
||||
var formId = config && config.formId;
|
||||
if (!base || !formId || !eventId) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return fetch(base + '/public/events/' + encodeURIComponent(eventId) + '/forms/' + encodeURIComponent(formId), {
|
||||
cache: 'no-store'
|
||||
})
|
||||
.then(parseJsonResponse)
|
||||
.then(function (body) {
|
||||
return (body && body.data) || body;
|
||||
})
|
||||
.catch(function () {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
function buildSubmitPayload() {
|
||||
var data = {};
|
||||
var nameInput = formEl.querySelector('[data-contact-field="name"]');
|
||||
var emailInput = formEl.querySelector('[data-contact-field="email"]');
|
||||
var subjectInput = formEl.querySelector('[data-contact-field="subject"]');
|
||||
var messageInput = formEl.querySelector('[data-contact-field="message"]');
|
||||
|
||||
data[fieldMap.name] = nameInput ? String(nameInput.value || '').trim() : '';
|
||||
data[fieldMap.email] = emailInput ? String(emailInput.value || '').trim() : '';
|
||||
data[fieldMap.subject] = subjectInput ? String(subjectInput.value || '').trim() : '';
|
||||
data[fieldMap.message] = messageInput ? String(messageInput.value || '').trim() : '';
|
||||
|
||||
var payload = {
|
||||
data: data,
|
||||
_gotcha: formEl.querySelector('[name="_gotcha"]') ? formEl.querySelector('[name="_gotcha"]').value : ''
|
||||
};
|
||||
|
||||
if (captchaMode === 'server' && captchaToken) {
|
||||
payload.captchaToken = captchaToken;
|
||||
payload.captchaAnswer = captchaInputEl ? String(captchaInputEl.value || '').trim() : '';
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
function validatePayload(payload) {
|
||||
if (!payload.data[fieldMap.name]) {
|
||||
return 'Please enter your name.';
|
||||
}
|
||||
if (!payload.data[fieldMap.email]) {
|
||||
return 'Please enter your email.';
|
||||
}
|
||||
|
||||
var captchaAnswer = captchaInputEl ? String(captchaInputEl.value || '').trim() : '';
|
||||
if (!captchaAnswer) {
|
||||
return 'Please solve the captcha.';
|
||||
}
|
||||
|
||||
if (captchaMode === 'local' && captchaAnswer !== localCaptchaAnswer) {
|
||||
return 'Incorrect captcha answer. Please try again.';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!currentConfig || !currentConfig.formId) {
|
||||
showMessage('Contact form is not configured.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
var base = apiBase(currentConfig);
|
||||
if (!base) {
|
||||
showMessage('Contact form API is not configured.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = buildSubmitPayload();
|
||||
var validationError = validatePayload(payload);
|
||||
if (validationError) {
|
||||
showMessage(validationError, true);
|
||||
formEl.classList.add('shake', 'animated');
|
||||
window.setTimeout(function () {
|
||||
formEl.classList.remove('shake', 'animated');
|
||||
}, 650);
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
showMessage('Sending…', false);
|
||||
|
||||
fetch(base + '/forms/' + encodeURIComponent(currentConfig.formId) + '/submit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(parseJsonResponse)
|
||||
.then(function () {
|
||||
var thankYou = (currentConfig && currentConfig.thankYouMessage)
|
||||
|| 'Thank you for contacting us, we will get back to you shortly!';
|
||||
formEl.reset();
|
||||
showMessage(thankYou, false);
|
||||
return loadCaptcha();
|
||||
})
|
||||
.catch(function (error) {
|
||||
showMessage(error.message || 'Could not send your message. Please try again.', true);
|
||||
return loadCaptcha();
|
||||
})
|
||||
.finally(function () {
|
||||
setSubmitting(false);
|
||||
});
|
||||
}
|
||||
|
||||
function bindForm() {
|
||||
if (!formEl || formEl.getAttribute('data-contact-bound') === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
formEl.setAttribute('data-contact-bound', 'true');
|
||||
captchaQuestionEl = formEl.querySelector('[data-contact-captcha-question]');
|
||||
captchaInputEl = formEl.querySelector('[data-contact-captcha-answer]');
|
||||
captchaReloadBtn = formEl.querySelector('[data-contact-captcha-reload]');
|
||||
submitBtn = formEl.querySelector('[type="submit"]');
|
||||
msgEl = document.getElementById('msgSubmit');
|
||||
|
||||
if (captchaReloadBtn) {
|
||||
captchaReloadBtn.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
loadCaptcha();
|
||||
});
|
||||
}
|
||||
|
||||
formEl.addEventListener('submit', handleSubmit);
|
||||
}
|
||||
|
||||
function init(contactConfig, siteConfig) {
|
||||
currentConfig = contactConfig || {};
|
||||
formEl = document.getElementById('contactForm');
|
||||
|
||||
if (!formEl || contactConfig.show === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
var eventId = (siteConfig && siteConfig.meta && siteConfig.meta.eventId) || contactConfig.eventId || '';
|
||||
formEl.setAttribute('data-event-id', eventId);
|
||||
formEl.setAttribute('data-form-id', currentConfig.formId || '');
|
||||
|
||||
bindForm();
|
||||
|
||||
loadFormDefinition(currentConfig, eventId).then(function (definition) {
|
||||
if (definition) {
|
||||
applyFieldMapFromDefinition(definition);
|
||||
if (definition.settings && definition.settings.thankYouMessage) {
|
||||
currentConfig.thankYouMessage = definition.settings.thankYouMessage;
|
||||
}
|
||||
}
|
||||
return loadCaptcha();
|
||||
});
|
||||
}
|
||||
|
||||
window.EventUpContactForm = {
|
||||
init: init
|
||||
};
|
||||
})();
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var fabEl = null;
|
||||
|
||||
function resolveFeedbackUrl(feedback, config) {
|
||||
if (feedback && feedback.url) {
|
||||
return feedback.url;
|
||||
}
|
||||
|
||||
var meta = (config && config.meta) || {};
|
||||
var eventId = meta.eventId;
|
||||
var formId = feedback && feedback.formId;
|
||||
var webBase = meta.webBaseUrl || 'https://web.eventbuzz.live';
|
||||
|
||||
if (eventId && formId) {
|
||||
return String(webBase).replace(/\/$/, '') + '/event/' + eventId + '/form/' + formId;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function openFeedback(feedback, config) {
|
||||
var url = resolveFeedbackUrl(feedback, config);
|
||||
if (!url) {
|
||||
console.warn('[feedback-fab] No feedback form URL configured.');
|
||||
return;
|
||||
}
|
||||
|
||||
var target = (feedback && feedback.target) || '_blank';
|
||||
|
||||
if (target === 'modal' && window.EventUpRegistrationModal && window.EventUpRegistrationModal.open) {
|
||||
window.EventUpRegistrationModal.open(url, (feedback && feedback.title) || 'Feedback');
|
||||
return;
|
||||
}
|
||||
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
|
||||
function init(feedback, config) {
|
||||
if (!feedback || feedback.show === false) {
|
||||
if (fabEl) {
|
||||
fabEl.style.display = 'none';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fabEl) {
|
||||
fabEl = document.createElement('button');
|
||||
fabEl.type = 'button';
|
||||
fabEl.className = 'eventup-feedback-fab';
|
||||
fabEl.setAttribute('data-feedback-fab', 'true');
|
||||
fabEl.innerHTML = '<span class="eventup-feedback-fab__text">Feedback</span>';
|
||||
document.body.appendChild(fabEl);
|
||||
}
|
||||
|
||||
fabEl.style.display = '';
|
||||
fabEl.setAttribute('aria-label', feedback.label || feedback.title || 'Feedback');
|
||||
|
||||
var label = fabEl.querySelector('.eventup-feedback-fab__text');
|
||||
if (label) {
|
||||
label.textContent = feedback.label || 'Feedback';
|
||||
}
|
||||
|
||||
if (fabEl.getAttribute('data-feedback-bound') === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
fabEl.setAttribute('data-feedback-bound', 'true');
|
||||
fabEl.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
openFeedback(feedback, config);
|
||||
});
|
||||
}
|
||||
|
||||
window.EventUpFeedbackFab = {
|
||||
init: init,
|
||||
open: openFeedback,
|
||||
resolveUrl: resolveFeedbackUrl
|
||||
};
|
||||
})();
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*!
|
||||
* The Final Countdown for jQuery v2.2.0 (http://hilios.github.io/jQuery.countdown/)
|
||||
* Copyright (c) 2016 Edson Hilios
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
!function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){"use strict";function b(a){if(a instanceof Date)return a;if(String(a).match(g))return String(a).match(/^[0-9]*$/)&&(a=Number(a)),String(a).match(/\-/)&&(a=String(a).replace(/\-/g,"/")),new Date(a);throw new Error("Couldn't cast `"+a+"` to a date object.")}function c(a){var b=a.toString().replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1");return new RegExp(b)}function d(a){return function(b){var d=b.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi);if(d)for(var f=0,g=d.length;f<g;++f){var h=d[f].match(/%(-|!)?([a-zA-Z]{1})(:[^;]+;)?/),j=c(h[0]),k=h[1]||"",l=h[3]||"",m=null;h=h[2],i.hasOwnProperty(h)&&(m=i[h],m=Number(a[m])),null!==m&&("!"===k&&(m=e(l,m)),""===k&&m<10&&(m="0"+m.toString()),b=b.replace(j,m.toString()))}return b=b.replace(/%%/,"%")}}function e(a,b){var c="s",d="";return a&&(a=a.replace(/(:|;|\s)/gi,"").split(/\,/),1===a.length?c=a[0]:(d=a[0],c=a[1])),Math.abs(b)>1?c:d}var f=[],g=[],h={precision:100,elapse:!1,defer:!1};g.push(/^[0-9]*$/.source),g.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/.source),g.push(/[0-9]{4}([\/\-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/.source),g=new RegExp(g.join("|"));var i={Y:"years",m:"months",n:"daysToMonth",d:"daysToWeek",w:"weeks",W:"weeksToMonth",H:"hours",M:"minutes",S:"seconds",D:"totalDays",I:"totalHours",N:"totalMinutes",T:"totalSeconds"},j=function(b,c,d){this.el=b,this.$el=a(b),this.interval=null,this.offset={},this.options=a.extend({},h),this.instanceNumber=f.length,f.push(this),this.$el.data("countdown-instance",this.instanceNumber),d&&("function"==typeof d?(this.$el.on("update.countdown",d),this.$el.on("stoped.countdown",d),this.$el.on("finish.countdown",d)):this.options=a.extend({},h,d)),this.setFinalDate(c),this.options.defer===!1&&this.start()};a.extend(j.prototype,{start:function(){null!==this.interval&&clearInterval(this.interval);var a=this;this.update(),this.interval=setInterval(function(){a.update.call(a)},this.options.precision)},stop:function(){clearInterval(this.interval),this.interval=null,this.dispatchEvent("stoped")},toggle:function(){this.interval?this.stop():this.start()},pause:function(){this.stop()},resume:function(){this.start()},remove:function(){this.stop.call(this),f[this.instanceNumber]=null,delete this.$el.data().countdownInstance},setFinalDate:function(a){this.finalDate=b(a)},update:function(){if(0===this.$el.closest("html").length)return void this.remove();var b,c=void 0!==a._data(this.el,"events"),d=new Date;b=this.finalDate.getTime()-d.getTime(),b=Math.ceil(b/1e3),b=!this.options.elapse&&b<0?0:Math.abs(b),this.totalSecsLeft!==b&&c&&(this.totalSecsLeft=b,this.elapsed=d>=this.finalDate,this.offset={seconds:this.totalSecsLeft%60,minutes:Math.floor(this.totalSecsLeft/60)%60,hours:Math.floor(this.totalSecsLeft/60/60)%24,days:Math.floor(this.totalSecsLeft/60/60/24)%7,daysToWeek:Math.floor(this.totalSecsLeft/60/60/24)%7,daysToMonth:Math.floor(this.totalSecsLeft/60/60/24%30.4368),weeks:Math.floor(this.totalSecsLeft/60/60/24/7),weeksToMonth:Math.floor(this.totalSecsLeft/60/60/24/7)%4,months:Math.floor(this.totalSecsLeft/60/60/24/30.4368),years:Math.abs(this.finalDate.getFullYear()-d.getFullYear()),totalDays:Math.floor(this.totalSecsLeft/60/60/24),totalHours:Math.floor(this.totalSecsLeft/60/60),totalMinutes:Math.floor(this.totalSecsLeft/60),totalSeconds:this.totalSecsLeft},this.options.elapse||0!==this.totalSecsLeft?this.dispatchEvent("update"):(this.stop(),this.dispatchEvent("finish")))},dispatchEvent:function(b){var c=a.Event(b+".countdown");c.finalDate=this.finalDate,c.elapsed=this.elapsed,c.offset=a.extend({},this.offset),c.strftime=d(this.offset),this.$el.trigger(c)}}),a.fn.countdown=function(){var b=Array.prototype.slice.call(arguments,0);return this.each(function(){var c=a(this).data("countdown-instance");if(void 0!==c){var d=f[c],e=b[0];j.prototype.hasOwnProperty(e)?d[e].apply(d,b.slice(1)):null===String(e).match(/^[$A-Z_][0-9A-Z_$]*$/i)?(d.setFinalDate.call(d,e),d.start()):a.error("Method %s does not exist on jQuery.countdown".replace(/\%s/gi,e))}else new j(this,b[0],b[1])})}});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/*!
|
||||
* jquery.counterup.js 1.0
|
||||
*
|
||||
* Copyright 2013, Benjamin Intal http://gambit.ph @bfintal
|
||||
* Released under the GPL v2 License
|
||||
*
|
||||
* Date: Nov 26, 2013
|
||||
*/(function(e){"use strict";e.fn.counterUp=function(t){var n=e.extend({time:400,delay:10},t);return this.each(function(){var t=e(this),r=n,i=function(){var e=[],n=r.time/r.delay,i=t.text(),s=/[0-9]+,[0-9]+/.test(i);i=i.replace(/,/g,"");var o=/^[0-9]+$/.test(i),u=/^[0-9]+\.[0-9]+$/.test(i),a=u?(i.split(".")[1]||[]).length:0;for(var f=n;f>=1;f--){var l=parseInt(i/n*f);u&&(l=parseFloat(i/n*f).toFixed(a));if(s)while(/(\d+)(\d{3})/.test(l.toString()))l=l.toString().replace(/(\d+)(\d{3})/,"$1,$2");e.unshift(l)}t.data("counterup-nums",e);t.text("0");var c=function(){t.text(t.data("counterup-nums").shift());if(t.data("counterup-nums").length)setTimeout(t.data("counterup-func"),r.delay);else{delete t.data("counterup-nums");t.data("counterup-nums",null);t.data("counterup-func",null)}};t.data("counterup-func",c);setTimeout(t.data("counterup-func"),r.delay)};t.waypoint(i,{offset:"100%",triggerOnce:!0})})}})(jQuery);
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
|
||||
*
|
||||
* Uses the built in easing capabilities added In jQuery 1.1
|
||||
* to offer multiple easing options
|
||||
*
|
||||
* TERMS OF USE - EASING EQUATIONS
|
||||
*
|
||||
* Open source under the BSD License.
|
||||
*
|
||||
* Copyright © 2001 Robert Penner
|
||||
* All rights reserved.
|
||||
*
|
||||
* TERMS OF USE - jQuery Easing
|
||||
*
|
||||
* Open source under the BSD License.
|
||||
*
|
||||
* Copyright © 2008 George McGinley Smith
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
* of conditions and the following disclaimer in the documentation and/or other materials
|
||||
* provided with the distribution.
|
||||
*
|
||||
* Neither the name of the author nor the names of contributors may be used to endorse
|
||||
* or promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
jQuery.easing.jswing=jQuery.easing.swing;jQuery.extend(jQuery.easing,{def:"easeOutQuad",swing:function(e,f,a,h,g){return jQuery.easing[jQuery.easing.def](e,f,a,h,g)},easeInQuad:function(e,f,a,h,g){return h*(f/=g)*f+a},easeOutQuad:function(e,f,a,h,g){return -h*(f/=g)*(f-2)+a},easeInOutQuad:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f+a}return -h/2*((--f)*(f-2)-1)+a},easeInCubic:function(e,f,a,h,g){return h*(f/=g)*f*f+a},easeOutCubic:function(e,f,a,h,g){return h*((f=f/g-1)*f*f+1)+a},easeInOutCubic:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f+a}return h/2*((f-=2)*f*f+2)+a},easeInQuart:function(e,f,a,h,g){return h*(f/=g)*f*f*f+a},easeOutQuart:function(e,f,a,h,g){return -h*((f=f/g-1)*f*f*f-1)+a},easeInOutQuart:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f*f+a}return -h/2*((f-=2)*f*f*f-2)+a},easeInQuint:function(e,f,a,h,g){return h*(f/=g)*f*f*f*f+a},easeOutQuint:function(e,f,a,h,g){return h*((f=f/g-1)*f*f*f*f+1)+a},easeInOutQuint:function(e,f,a,h,g){if((f/=g/2)<1){return h/2*f*f*f*f*f+a}return h/2*((f-=2)*f*f*f*f+2)+a},easeInSine:function(e,f,a,h,g){return -h*Math.cos(f/g*(Math.PI/2))+h+a},easeOutSine:function(e,f,a,h,g){return h*Math.sin(f/g*(Math.PI/2))+a},easeInOutSine:function(e,f,a,h,g){return -h/2*(Math.cos(Math.PI*f/g)-1)+a},easeInExpo:function(e,f,a,h,g){return(f==0)?a:h*Math.pow(2,10*(f/g-1))+a},easeOutExpo:function(e,f,a,h,g){return(f==g)?a+h:h*(-Math.pow(2,-10*f/g)+1)+a},easeInOutExpo:function(e,f,a,h,g){if(f==0){return a}if(f==g){return a+h}if((f/=g/2)<1){return h/2*Math.pow(2,10*(f-1))+a}return h/2*(-Math.pow(2,-10*--f)+2)+a},easeInCirc:function(e,f,a,h,g){return -h*(Math.sqrt(1-(f/=g)*f)-1)+a},easeOutCirc:function(e,f,a,h,g){return h*Math.sqrt(1-(f=f/g-1)*f)+a},easeInOutCirc:function(e,f,a,h,g){if((f/=g/2)<1){return -h/2*(Math.sqrt(1-f*f)-1)+a}return h/2*(Math.sqrt(1-(f-=2)*f)+1)+a},easeInElastic:function(f,h,e,l,k){var i=1.70158;var j=0;var g=l;if(h==0){return e}if((h/=k)==1){return e+l}if(!j){j=k*0.3}if(g<Math.abs(l)){g=l;var i=j/4}else{var i=j/(2*Math.PI)*Math.asin(l/g)}return -(g*Math.pow(2,10*(h-=1))*Math.sin((h*k-i)*(2*Math.PI)/j))+e},easeOutElastic:function(f,h,e,l,k){var i=1.70158;var j=0;var g=l;if(h==0){return e}if((h/=k)==1){return e+l}if(!j){j=k*0.3}if(g<Math.abs(l)){g=l;var i=j/4}else{var i=j/(2*Math.PI)*Math.asin(l/g)}return g*Math.pow(2,-10*h)*Math.sin((h*k-i)*(2*Math.PI)/j)+l+e},easeInOutElastic:function(f,h,e,l,k){var i=1.70158;var j=0;var g=l;if(h==0){return e}if((h/=k/2)==2){return e+l}if(!j){j=k*(0.3*1.5)}if(g<Math.abs(l)){g=l;var i=j/4}else{var i=j/(2*Math.PI)*Math.asin(l/g)}if(h<1){return -0.5*(g*Math.pow(2,10*(h-=1))*Math.sin((h*k-i)*(2*Math.PI)/j))+e}return g*Math.pow(2,-10*(h-=1))*Math.sin((h*k-i)*(2*Math.PI)/j)*0.5+l+e},easeInBack:function(e,f,a,i,h,g){if(g==undefined){g=1.70158}return i*(f/=h)*f*((g+1)*f-g)+a},easeOutBack:function(e,f,a,i,h,g){if(g==undefined){g=1.70158}return i*((f=f/h-1)*f*((g+1)*f+g)+1)+a},easeInOutBack:function(e,f,a,i,h,g){if(g==undefined){g=1.70158}if((f/=h/2)<1){return i/2*(f*f*(((g*=(1.525))+1)*f-g))+a}return i/2*((f-=2)*f*(((g*=(1.525))+1)*f+g)+2)+a},easeInBounce:function(e,f,a,h,g){return h-jQuery.easing.easeOutBounce(e,g-f,0,h,g)+a},easeOutBounce:function(e,f,a,h,g){if((f/=g)<(1/2.75)){return h*(7.5625*f*f)+a}else{if(f<(2/2.75)){return h*(7.5625*(f-=(1.5/2.75))*f+0.75)+a}else{if(f<(2.5/2.75)){return h*(7.5625*(f-=(2.25/2.75))*f+0.9375)+a}else{return h*(7.5625*(f-=(2.625/2.75))*f+0.984375)+a}}}},easeInOutBounce:function(e,f,a,h,g){if(f<g/2){return jQuery.easing.easeInBounce(e,f*2,0,h,g)*0.5+a}return jQuery.easing.easeOutBounce(e,f*2-g,0,h,g)*0.5+h*0.5+a}});
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* jQuery One Page Nav Plugin
|
||||
* http://github.com/davist11/jQuery-One-Page-Nav
|
||||
*
|
||||
* Copyright (c) 2010 Trevor Davis (http://trevordavis.net)
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* Uses the same license as jQuery, see:
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* @version 3.0.0
|
||||
*
|
||||
* Example usage:
|
||||
* $('#nav').onePageNav({
|
||||
* currentClass: 'current',
|
||||
* changeHash: false,
|
||||
* scrollSpeed: 750
|
||||
* });
|
||||
*/
|
||||
|
||||
;(function($, window, document, undefined){
|
||||
|
||||
// our plugin constructor
|
||||
var OnePageNav = function(elem, options){
|
||||
this.elem = elem;
|
||||
this.$elem = $(elem);
|
||||
this.options = options;
|
||||
this.metadata = this.$elem.data('plugin-options');
|
||||
this.$win = $(window);
|
||||
this.sections = {};
|
||||
this.didScroll = false;
|
||||
this.$doc = $(document);
|
||||
this.docHeight = this.$doc.height();
|
||||
};
|
||||
|
||||
// the plugin prototype
|
||||
OnePageNav.prototype = {
|
||||
defaults: {
|
||||
navItems: 'a',
|
||||
currentClass: 'current',
|
||||
changeHash: false,
|
||||
easing: 'swing',
|
||||
filter: '',
|
||||
scrollSpeed: 750,
|
||||
scrollThreshold: 0.5,
|
||||
begin: false,
|
||||
end: false,
|
||||
scrollChange: false
|
||||
},
|
||||
|
||||
init: function() {
|
||||
// Introduce defaults that can be extended either
|
||||
// globally or using an object literal.
|
||||
this.config = $.extend({}, this.defaults, this.options, this.metadata);
|
||||
|
||||
this.$nav = this.$elem.find(this.config.navItems);
|
||||
|
||||
//Filter any links out of the nav
|
||||
if(this.config.filter !== '') {
|
||||
this.$nav = this.$nav.filter(this.config.filter);
|
||||
}
|
||||
|
||||
//Handle clicks on the nav
|
||||
this.$nav.on('click.onePageNav', $.proxy(this.handleClick, this));
|
||||
|
||||
//Get the section positions
|
||||
this.getPositions();
|
||||
|
||||
//Handle scroll changes
|
||||
this.bindInterval();
|
||||
|
||||
//Update the positions on resize too
|
||||
this.$win.on('resize.onePageNav', $.proxy(this.getPositions, this));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
adjustNav: function(self, $parent) {
|
||||
self.$elem.find('.' + self.config.currentClass).removeClass(self.config.currentClass);
|
||||
$parent.addClass(self.config.currentClass);
|
||||
},
|
||||
|
||||
bindInterval: function() {
|
||||
var self = this;
|
||||
var docHeight;
|
||||
|
||||
self.$win.on('scroll.onePageNav', function() {
|
||||
self.didScroll = true;
|
||||
});
|
||||
|
||||
self.t = setInterval(function() {
|
||||
docHeight = self.$doc.height();
|
||||
|
||||
//If it was scrolled
|
||||
if(self.didScroll) {
|
||||
self.didScroll = false;
|
||||
self.scrollChange();
|
||||
}
|
||||
|
||||
//If the document height changes
|
||||
if(docHeight !== self.docHeight) {
|
||||
self.docHeight = docHeight;
|
||||
self.getPositions();
|
||||
}
|
||||
}, 250);
|
||||
},
|
||||
|
||||
getHash: function($link) {
|
||||
return $link.attr('href').split('#')[1];
|
||||
},
|
||||
|
||||
getPositions: function() {
|
||||
var self = this;
|
||||
var linkHref;
|
||||
var topPos;
|
||||
var $target;
|
||||
|
||||
self.$nav.each(function() {
|
||||
linkHref = self.getHash($(this));
|
||||
$target = $('#' + linkHref);
|
||||
|
||||
if($target.length) {
|
||||
topPos = $target.offset().top;
|
||||
self.sections[linkHref] = Math.round(topPos);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getSection: function(windowPos) {
|
||||
var returnValue = null;
|
||||
var windowHeight = Math.round(this.$win.height() * this.config.scrollThreshold);
|
||||
|
||||
for(var section in this.sections) {
|
||||
if((this.sections[section] - windowHeight) < windowPos) {
|
||||
returnValue = section;
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
},
|
||||
|
||||
handleClick: function(e) {
|
||||
var self = this;
|
||||
var $link = $(e.currentTarget);
|
||||
var $parent = $link.parent();
|
||||
var newLoc = '#' + self.getHash($link);
|
||||
|
||||
if(!$parent.hasClass(self.config.currentClass)) {
|
||||
//Start callback
|
||||
if(self.config.begin) {
|
||||
self.config.begin();
|
||||
}
|
||||
|
||||
//Change the highlighted nav item
|
||||
self.adjustNav(self, $parent);
|
||||
|
||||
//Removing the auto-adjust on scroll
|
||||
self.unbindInterval();
|
||||
|
||||
//Scroll to the correct position
|
||||
self.scrollTo(newLoc, function() {
|
||||
//Do we need to change the hash?
|
||||
if(self.config.changeHash) {
|
||||
window.location.hash = newLoc;
|
||||
}
|
||||
|
||||
//Add the auto-adjust on scroll back in
|
||||
self.bindInterval();
|
||||
|
||||
//End callback
|
||||
if(self.config.end) {
|
||||
self.config.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
scrollChange: function() {
|
||||
var windowTop = this.$win.scrollTop();
|
||||
var position = this.getSection(windowTop);
|
||||
var $parent;
|
||||
|
||||
//If the position is set
|
||||
if(position !== null) {
|
||||
$parent = this.$elem.find('a[href$="#' + position + '"]').parent();
|
||||
|
||||
//If it's not already the current section
|
||||
if(!$parent.hasClass(this.config.currentClass)) {
|
||||
//Change the highlighted nav item
|
||||
this.adjustNav(this, $parent);
|
||||
|
||||
//If there is a scrollChange callback
|
||||
if(this.config.scrollChange) {
|
||||
this.config.scrollChange($parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
scrollTo: function(target, callback) {
|
||||
var offset = $(target).offset().top;
|
||||
|
||||
$('html, body').animate({
|
||||
scrollTop: offset
|
||||
}, this.config.scrollSpeed, this.config.easing, callback);
|
||||
},
|
||||
|
||||
unbindInterval: function() {
|
||||
clearInterval(this.t);
|
||||
this.$win.unbind('scroll.onePageNav');
|
||||
}
|
||||
};
|
||||
|
||||
OnePageNav.defaults = OnePageNav.prototype.defaults;
|
||||
|
||||
$.fn.onePageNav = function(options) {
|
||||
return this.each(function() {
|
||||
new OnePageNav(this, options).init();
|
||||
});
|
||||
};
|
||||
|
||||
})( jQuery, window , document );
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
(function($) {
|
||||
|
||||
"use strict";
|
||||
|
||||
function updateNavbarSpacer() {
|
||||
var navbar = document.querySelector('.scrolling-navbar');
|
||||
var spacer = document.querySelector('.navbar-spacer');
|
||||
|
||||
if (!navbar || !spacer) {
|
||||
return;
|
||||
}
|
||||
|
||||
var height = Math.ceil(navbar.getBoundingClientRect().height);
|
||||
spacer.style.height = height + 'px';
|
||||
document.documentElement.style.setProperty('--site-header-height', height + 'px');
|
||||
}
|
||||
|
||||
window.updateNavbarSpacer = updateNavbarSpacer;
|
||||
|
||||
function initNavbarSpacer() {
|
||||
updateNavbarSpacer();
|
||||
|
||||
var navbar = document.querySelector('.scrolling-navbar');
|
||||
if (navbar && typeof ResizeObserver !== 'undefined') {
|
||||
var resizeObserver = new ResizeObserver(updateNavbarSpacer);
|
||||
resizeObserver.observe(navbar);
|
||||
}
|
||||
|
||||
var headerLogo = document.querySelector('[data-logo-placement="header"] [data-logo-image]');
|
||||
if (headerLogo) {
|
||||
if (headerLogo.complete) {
|
||||
updateNavbarSpacer();
|
||||
}
|
||||
headerLogo.addEventListener('load', updateNavbarSpacer);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initNavbarSpacer);
|
||||
} else {
|
||||
initNavbarSpacer();
|
||||
}
|
||||
|
||||
$(window).on('load', function() {
|
||||
|
||||
/*Page Loader active
|
||||
========================================================*/
|
||||
$('#preloader').fadeOut();
|
||||
|
||||
updateNavbarSpacer();
|
||||
|
||||
// Sticky Nav
|
||||
$(window).on('scroll', function() {
|
||||
var wasCollapsed = $('.scrolling-navbar').hasClass('top-nav-collapse');
|
||||
|
||||
if ($(window).scrollTop() > 200) {
|
||||
$('.scrolling-navbar').addClass('top-nav-collapse');
|
||||
} else {
|
||||
$('.scrolling-navbar').removeClass('top-nav-collapse');
|
||||
}
|
||||
|
||||
if (wasCollapsed !== $('.scrolling-navbar').hasClass('top-nav-collapse')) {
|
||||
requestAnimationFrame(updateNavbarSpacer);
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('resize', updateNavbarSpacer);
|
||||
|
||||
if (document.documentElement.classList.contains('site-config-loaded')) {
|
||||
updateNavbarSpacer();
|
||||
} else {
|
||||
var configObserver = new MutationObserver(function() {
|
||||
if (document.documentElement.classList.contains('site-config-loaded')) {
|
||||
updateNavbarSpacer();
|
||||
configObserver.disconnect();
|
||||
}
|
||||
});
|
||||
configObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
countdown timer — initialized from site.json via site-config.js
|
||||
========================================================================== */
|
||||
|
||||
/* Auto Close Responsive Navbar on Click
|
||||
========================================================*/
|
||||
function close_toggle() {
|
||||
if ($(window).width() <= 768) {
|
||||
$('.navbar-collapse a').on('click', function () {
|
||||
$('.navbar-collapse').collapse('hide');
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('.navbar .navbar-inverse a').off('click');
|
||||
}
|
||||
}
|
||||
close_toggle();
|
||||
$(window).resize(close_toggle);
|
||||
|
||||
/* WOW Scroll Spy
|
||||
========================================================*/
|
||||
var wow = new WOW({
|
||||
//disabled for mobile
|
||||
mobile: false
|
||||
});
|
||||
wow.init();
|
||||
|
||||
/* Nivo Lightbox
|
||||
========================================================*/
|
||||
$('.lightbox').nivoLightbox({
|
||||
effect: 'fadeScale',
|
||||
keyboardNav: true,
|
||||
});
|
||||
|
||||
// one page navigation
|
||||
$('.navbar-nav').onePageNav({
|
||||
currentClass: 'active'
|
||||
});
|
||||
|
||||
/* Counter (static placeholders only; event stats are animated in site-config.js) */
|
||||
$('.counterUp').not('[data-event-stats-list] .counterUp').counterUp({
|
||||
delay: 10,
|
||||
time: 1500
|
||||
});
|
||||
|
||||
/* Back Top Link active
|
||||
========================================================*/
|
||||
var offset = 200;
|
||||
var duration = 500;
|
||||
$(window).scroll(function() {
|
||||
if ($(this).scrollTop() > offset) {
|
||||
$('.back-to-top').fadeIn(400);
|
||||
} else {
|
||||
$('.back-to-top').fadeOut(400);
|
||||
}
|
||||
});
|
||||
|
||||
$('.back-to-top').on('click',function(event) {
|
||||
event.preventDefault();
|
||||
$('html, body').animate({
|
||||
scrollTop: 0
|
||||
}, 600);
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var popupEl = null;
|
||||
var autoCloseTimer = null;
|
||||
var popupBound = false;
|
||||
|
||||
function createPopup() {
|
||||
if (popupEl) {
|
||||
return popupEl;
|
||||
}
|
||||
|
||||
popupEl = document.createElement('div');
|
||||
popupEl.className = 'eventup-marketing-popup';
|
||||
popupEl.setAttribute('data-marketing-popup', '');
|
||||
popupEl.setAttribute('role', 'dialog');
|
||||
popupEl.setAttribute('aria-modal', 'true');
|
||||
popupEl.setAttribute('aria-hidden', 'true');
|
||||
popupEl.innerHTML =
|
||||
'<div class="eventup-marketing-popup__backdrop"></div>'
|
||||
+ '<div class="eventup-marketing-popup__card">'
|
||||
+ '<p class="eventup-marketing-popup__heading" data-marketing-heading>Introducing</p>'
|
||||
+ '<div class="eventup-marketing-popup__logo-wrap">'
|
||||
+ '<button type="button" class="eventup-marketing-popup__close" data-marketing-close aria-label="Close">'
|
||||
+ '<span aria-hidden="true">×</span>'
|
||||
+ '</button>'
|
||||
+ '<img class="eventup-marketing-popup__logo" data-marketing-logo src="" alt="">'
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
|
||||
document.body.appendChild(popupEl);
|
||||
bindPopupEvents(popupEl);
|
||||
return popupEl;
|
||||
}
|
||||
|
||||
function bindPopupEvents(popup) {
|
||||
if (!popup || popupBound) {
|
||||
return;
|
||||
}
|
||||
|
||||
popupBound = true;
|
||||
|
||||
var closeBtn = popup.querySelector('[data-marketing-close]');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', hidePopup);
|
||||
}
|
||||
|
||||
popup.addEventListener('click', function (event) {
|
||||
if (event.target === popup.querySelector('.eventup-marketing-popup__backdrop')) {
|
||||
hidePopup();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape' && popupEl && popupEl.classList.contains('is-visible')) {
|
||||
hidePopup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function hidePreloader() {
|
||||
var preloader = document.getElementById('preloader');
|
||||
if (!preloader) {
|
||||
return;
|
||||
}
|
||||
|
||||
preloader.style.display = 'none';
|
||||
preloader.style.visibility = 'hidden';
|
||||
preloader.style.opacity = '0';
|
||||
}
|
||||
|
||||
function hidePopup() {
|
||||
if (!popupEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoCloseTimer) {
|
||||
window.clearTimeout(autoCloseTimer);
|
||||
autoCloseTimer = null;
|
||||
}
|
||||
|
||||
popupEl.classList.remove('is-visible');
|
||||
popupEl.setAttribute('aria-hidden', 'true');
|
||||
document.body.classList.remove('eventup-marketing-popup-open');
|
||||
|
||||
window.setTimeout(function () {
|
||||
if (popupEl) {
|
||||
popupEl.style.display = 'none';
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
|
||||
function showPopup(config) {
|
||||
var popup = createPopup();
|
||||
var heading = popup.querySelector('[data-marketing-heading]');
|
||||
var logo = popup.querySelector('[data-marketing-logo]');
|
||||
var logoConfig = (config && config.logo) || {};
|
||||
var autoCloseMs = config && config.autoCloseDuration != null
|
||||
? Number(config.autoCloseDuration)
|
||||
: 3000;
|
||||
|
||||
if (heading) {
|
||||
heading.textContent = (config && config.heading) || 'Introducing';
|
||||
}
|
||||
|
||||
if (logo) {
|
||||
logo.src = logoConfig.url || 'assets/img/novel-logo.png';
|
||||
logo.alt = logoConfig.alt || 'NOVEL';
|
||||
}
|
||||
|
||||
hidePreloader();
|
||||
|
||||
popup.style.display = 'flex';
|
||||
popup.setAttribute('aria-hidden', 'false');
|
||||
document.body.classList.add('eventup-marketing-popup-open');
|
||||
|
||||
window.requestAnimationFrame(function () {
|
||||
popup.classList.add('is-visible');
|
||||
markShown();
|
||||
});
|
||||
|
||||
if (autoCloseTimer) {
|
||||
window.clearTimeout(autoCloseTimer);
|
||||
}
|
||||
|
||||
if (autoCloseMs > 0) {
|
||||
autoCloseTimer = window.setTimeout(hidePopup, autoCloseMs);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldShow(config) {
|
||||
if (!config || config.show === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.showOncePerSession !== false) {
|
||||
try {
|
||||
if (window.sessionStorage.getItem('eventup-marketing-popup-v2') === '1') {
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function markShown() {
|
||||
try {
|
||||
window.sessionStorage.setItem('eventup-marketing-popup-v2', '1');
|
||||
} catch (error) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function init(config) {
|
||||
if (!shouldShow(config)) {
|
||||
if (popupEl) {
|
||||
popupEl.style.display = 'none';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function launch() {
|
||||
window.setTimeout(function () {
|
||||
showPopup(config);
|
||||
}, 150);
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete') {
|
||||
launch();
|
||||
} else {
|
||||
window.addEventListener('load', launch);
|
||||
}
|
||||
}
|
||||
|
||||
window.EventUpMarketingPopup = {
|
||||
init: init,
|
||||
show: showPopup,
|
||||
hide: hidePopup
|
||||
};
|
||||
})();
|
||||
|
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* Nivo Lightbox v1.3.1
|
||||
* http://dev7studios.com/nivo-lightbox
|
||||
*
|
||||
* Copyright 2013, Dev7studios
|
||||
* Free to use and abuse under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
;(function($, window, document, undefined){
|
||||
|
||||
var pluginName = 'nivoLightbox',
|
||||
defaults = {
|
||||
effect: 'fade',
|
||||
theme: 'default',
|
||||
keyboardNav: true,
|
||||
clickImgToClose: false,
|
||||
clickOverlayToClose: true,
|
||||
onInit: function(){},
|
||||
beforeShowLightbox: function(){},
|
||||
afterShowLightbox: function(lightbox){},
|
||||
beforeHideLightbox: function(){},
|
||||
afterHideLightbox: function(){},
|
||||
beforePrev: function(element){},
|
||||
onPrev: function(element){},
|
||||
beforeNext: function(element){},
|
||||
onNext: function(element){},
|
||||
errorMessage: 'The requested content cannot be loaded. Please try again later.'
|
||||
};
|
||||
|
||||
function NivoLightbox(element, options){
|
||||
this.el = element;
|
||||
this.$el = $(this.el);
|
||||
|
||||
this.options = $.extend({}, defaults, options);
|
||||
|
||||
this._defaults = defaults;
|
||||
this._name = pluginName;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
NivoLightbox.prototype = {
|
||||
|
||||
init: function(){
|
||||
var $this = this;
|
||||
|
||||
// Need this so we don't use CSS transitions in mobile
|
||||
if(!$('html').hasClass('nivo-lightbox-notouch')) $('html').addClass('nivo-lightbox-notouch');
|
||||
if('ontouchstart' in document) $('html').removeClass('nivo-lightbox-notouch');
|
||||
|
||||
// Setup the click
|
||||
this.$el.on('click', function(e){
|
||||
$this.showLightbox(e);
|
||||
});
|
||||
|
||||
// keyboardNav
|
||||
if(this.options.keyboardNav){
|
||||
$('body').off('keyup').on('keyup', function(e){
|
||||
var code = (e.keyCode ? e.keyCode : e.which);
|
||||
// Escape
|
||||
if(code == 27) $this.destructLightbox();
|
||||
// Left
|
||||
if(code == 37) $('.nivo-lightbox-prev').trigger('click');
|
||||
// Right
|
||||
if(code == 39) $('.nivo-lightbox-next').trigger('click');
|
||||
});
|
||||
}
|
||||
|
||||
this.options.onInit.call(this);
|
||||
|
||||
},
|
||||
|
||||
showLightbox: function(e){
|
||||
var $this = this,
|
||||
currentLink = this.$el;
|
||||
|
||||
// Check content
|
||||
var check = this.checkContent(currentLink);
|
||||
if(!check) return;
|
||||
|
||||
e.preventDefault();
|
||||
this.options.beforeShowLightbox.call(this);
|
||||
var lightbox = this.constructLightbox();
|
||||
if(!lightbox) return;
|
||||
var content = lightbox.find('.nivo-lightbox-content');
|
||||
if(!content) return;
|
||||
|
||||
$('body').addClass('nivo-lightbox-body-effect-'+ this.options.effect);
|
||||
|
||||
this.processContent( content, currentLink );
|
||||
|
||||
// Nav
|
||||
if(this.$el.attr('data-lightbox-gallery')){
|
||||
var galleryItems = $('[data-lightbox-gallery="'+ this.$el.attr('data-lightbox-gallery') +'"]');
|
||||
|
||||
$('.nivo-lightbox-nav').show();
|
||||
|
||||
// Prev
|
||||
$('.nivo-lightbox-prev').off('click').on('click', function(e){
|
||||
e.preventDefault();
|
||||
var index = galleryItems.index(currentLink);
|
||||
currentLink = galleryItems.eq(index - 1);
|
||||
if(!$(currentLink).length) currentLink = galleryItems.last();
|
||||
$.when($this.options.beforePrev.call(this, [ currentLink ])).done(function(){
|
||||
$this.processContent(content, currentLink);
|
||||
$this.options.onPrev.call(this, [ currentLink ]);
|
||||
});
|
||||
});
|
||||
|
||||
// Next
|
||||
$('.nivo-lightbox-next').off('click').on('click', function(e){
|
||||
e.preventDefault();
|
||||
var index = galleryItems.index(currentLink);
|
||||
currentLink = galleryItems.eq(index + 1);
|
||||
if(!$(currentLink).length) currentLink = galleryItems.first();
|
||||
$.when($this.options.beforeNext.call(this, [ currentLink ])).done(function(){
|
||||
$this.processContent(content, currentLink);
|
||||
$this.options.onNext.call(this, [ currentLink ]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(function(){
|
||||
lightbox.addClass('nivo-lightbox-open');
|
||||
$this.options.afterShowLightbox.call(this, [ lightbox ]);
|
||||
}, 1); // For CSS transitions
|
||||
},
|
||||
|
||||
checkContent: function( link ) {
|
||||
var $this = this,
|
||||
href = link.attr('href'),
|
||||
video = href.match(/(youtube|youtube-nocookie|youtu|vimeo)\.(com|be)\/(watch\?v=([\w-]+)|([\w-]+))/);
|
||||
|
||||
if(href.match(/\.(jpeg|jpg|gif|png)$/i) !== null){
|
||||
return true;
|
||||
}
|
||||
// Video (Youtube/Vimeo)
|
||||
else if(video){
|
||||
return true;
|
||||
}
|
||||
// AJAX
|
||||
else if(link.attr('data-lightbox-type') == 'ajax'){
|
||||
return true;
|
||||
}
|
||||
// Inline HTML
|
||||
else if(href.substring(0, 1) == '#' && link.attr('data-lightbox-type') == 'inline'){
|
||||
return true;
|
||||
}
|
||||
// iFrame (default)
|
||||
else if(link.attr('data-lightbox-type') == 'iframe'){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
processContent: function(content, link){
|
||||
var $this = this,
|
||||
href = link.attr('href'),
|
||||
video = href.match(/(youtube|youtube-nocookie|youtu|vimeo)\.(com|be)\/(watch\?v=([\w-]+)|([\w-]+))/);
|
||||
|
||||
content.html('').addClass('nivo-lightbox-loading');
|
||||
|
||||
// Is HiDPI?
|
||||
if(this.isHidpi() && link.attr('data-lightbox-hidpi')){
|
||||
href = link.attr('data-lightbox-hidpi');
|
||||
}
|
||||
|
||||
// Image
|
||||
if(href.match(/\.(jpeg|jpg|gif|png)$/i) !== null){
|
||||
var img = $('<img>', { src: href, 'class': 'nivo-lightbox-image-display' });
|
||||
img.one('load', function() {
|
||||
var wrap = $('<div class="nivo-lightbox-image" />');
|
||||
wrap.append(img);
|
||||
content.html(wrap).removeClass('nivo-lightbox-loading');
|
||||
|
||||
// Vertically center images
|
||||
wrap.css({
|
||||
'line-height': $('.nivo-lightbox-content').height() +'px',
|
||||
'height': $('.nivo-lightbox-content').height() +'px' // For Firefox
|
||||
});
|
||||
$(window).resize(function() {
|
||||
wrap.css({
|
||||
'line-height': $('.nivo-lightbox-content').height() +'px',
|
||||
'height': $('.nivo-lightbox-content').height() +'px' // For Firefox
|
||||
});
|
||||
});
|
||||
}).each(function() {
|
||||
if(this.complete) $(this).load();
|
||||
});
|
||||
|
||||
img.error(function() {
|
||||
var wrap = $('<div class="nivo-lightbox-error"><p>'+ $this.options.errorMessage +'</p></div>');
|
||||
content.html(wrap).removeClass('nivo-lightbox-loading');
|
||||
});
|
||||
}
|
||||
// Video (Youtube/Vimeo)
|
||||
else if(video){
|
||||
var src = '',
|
||||
classTerm = 'nivo-lightbox-video';
|
||||
|
||||
if(video[1] == 'youtube'){
|
||||
src = '//www.youtube.com/embed/'+ video[4];
|
||||
classTerm = 'nivo-lightbox-youtube';
|
||||
}
|
||||
if(video[1] == 'youtube-nocookie'){
|
||||
src = href; //https://www.youtube-nocookie.com/embed/...
|
||||
classTerm = 'nivo-lightbox-youtube';
|
||||
}
|
||||
if(video[1] == 'youtu'){
|
||||
src = '//www.youtube.com/embed/'+ video[3];
|
||||
classTerm = 'nivo-lightbox-youtube';
|
||||
}
|
||||
if(video[1] == 'vimeo'){
|
||||
src = '//player.vimeo.com/video/'+ video[3];
|
||||
classTerm = 'nivo-lightbox-vimeo';
|
||||
}
|
||||
|
||||
if(src){
|
||||
var iframeVideo = $('<iframe>', {
|
||||
src: src,
|
||||
'class': classTerm,
|
||||
frameborder: 0,
|
||||
vspace: 0,
|
||||
hspace: 0,
|
||||
scrolling: 'auto'
|
||||
});
|
||||
content.html(iframeVideo);
|
||||
iframeVideo.load(function(){ content.removeClass('nivo-lightbox-loading'); });
|
||||
}
|
||||
}
|
||||
// AJAX
|
||||
else if(link.attr('data-lightbox-type') == 'ajax'){
|
||||
$.ajax({
|
||||
url: href,
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
var wrap = $('<div class="nivo-lightbox-ajax" />');
|
||||
wrap.append(data);
|
||||
content.html(wrap).removeClass('nivo-lightbox-loading');
|
||||
|
||||
// Vertically center html
|
||||
if(wrap.outerHeight() < content.height()){
|
||||
wrap.css({
|
||||
'position': 'relative',
|
||||
'top': '50%',
|
||||
'margin-top': -(wrap.outerHeight()/2) +'px'
|
||||
});
|
||||
}
|
||||
$(window).resize(function() {
|
||||
if(wrap.outerHeight() < content.height()){
|
||||
wrap.css({
|
||||
'position': 'relative',
|
||||
'top': '50%',
|
||||
'margin-top': -(wrap.outerHeight()/2) +'px'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function(){
|
||||
var wrap = $('<div class="nivo-lightbox-error"><p>'+ $this.options.errorMessage +'</p></div>');
|
||||
content.html(wrap).removeClass('nivo-lightbox-loading');
|
||||
}
|
||||
});
|
||||
}
|
||||
// Inline HTML
|
||||
else if(href.substring(0, 1) == '#' && link.attr('data-lightbox-type') == 'inline'){
|
||||
if($(href).length){
|
||||
var wrap = $('<div class="nivo-lightbox-inline" />');
|
||||
wrap.append($(href).clone().show());
|
||||
content.html(wrap).removeClass('nivo-lightbox-loading');
|
||||
|
||||
// Vertically center html
|
||||
if(wrap.outerHeight() < content.height()){
|
||||
wrap.css({
|
||||
'position': 'relative',
|
||||
'top': '50%',
|
||||
'margin-top': -(wrap.outerHeight()/2) +'px'
|
||||
});
|
||||
}
|
||||
$(window).resize(function() {
|
||||
if(wrap.outerHeight() < content.height()){
|
||||
wrap.css({
|
||||
'position': 'relative',
|
||||
'top': '50%',
|
||||
'margin-top': -(wrap.outerHeight()/2) +'px'
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var wrapError = $('<div class="nivo-lightbox-error"><p>'+ $this.options.errorMessage +'</p></div>');
|
||||
content.html(wrapError).removeClass('nivo-lightbox-loading');
|
||||
}
|
||||
}
|
||||
// iFrame (default)
|
||||
else if(link.attr('data-lightbox-type') == 'iframe'){
|
||||
var iframe = $('<iframe>', {
|
||||
src: href,
|
||||
'class': 'nivo-lightbox-item',
|
||||
frameborder: 0,
|
||||
vspace: 0,
|
||||
hspace: 0,
|
||||
scrolling: 'auto'
|
||||
});
|
||||
content.html(iframe);
|
||||
iframe.load(function(){ content.removeClass('nivo-lightbox-loading'); });
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the title
|
||||
if(link.attr('title')){
|
||||
var titleWrap = $('<span>', { 'class': 'nivo-lightbox-title' });
|
||||
titleWrap.text(link.attr('title'));
|
||||
$('.nivo-lightbox-title-wrap').html(titleWrap);
|
||||
} else {
|
||||
$('.nivo-lightbox-title-wrap').html('');
|
||||
}
|
||||
},
|
||||
|
||||
constructLightbox: function(){
|
||||
if($('.nivo-lightbox-overlay').length) return $('.nivo-lightbox-overlay');
|
||||
|
||||
var overlay = $('<div>', { 'class': 'nivo-lightbox-overlay nivo-lightbox-theme-'+ this.options.theme +' nivo-lightbox-effect-'+ this.options.effect });
|
||||
var wrap = $('<div>', { 'class': 'nivo-lightbox-wrap' });
|
||||
var content = $('<div>', { 'class': 'nivo-lightbox-content' });
|
||||
var nav = $('<a href="#" class="nivo-lightbox-nav nivo-lightbox-prev">Previous</a><a href="#" class="nivo-lightbox-nav nivo-lightbox-next">Next</a>');
|
||||
var close = $('<a href="#" class="nivo-lightbox-close" title="Close"><i class="icon-close"></i></a>');
|
||||
var title = $('<div>', { 'class': 'nivo-lightbox-title-wrap' });
|
||||
|
||||
var isMSIE = /*@cc_on!@*/0;
|
||||
if(isMSIE) overlay.addClass('nivo-lightbox-ie');
|
||||
|
||||
wrap.append(content);
|
||||
wrap.append(title);
|
||||
overlay.append(wrap);
|
||||
overlay.append(nav);
|
||||
overlay.append(close);
|
||||
$('body').append(overlay);
|
||||
|
||||
var $this = this;
|
||||
if($this.options.clickOverlayToClose){
|
||||
overlay.on('click', function(e){
|
||||
if(e.target === this || $(e.target).hasClass('nivo-lightbox-content') || $(e.target).hasClass('nivo-lightbox-image')){
|
||||
$this.destructLightbox();
|
||||
}
|
||||
});
|
||||
}
|
||||
if($this.options.clickImgToClose){
|
||||
overlay.on('click', function(e){
|
||||
if(e.target === this || $(e.target).hasClass('nivo-lightbox-image-display')){
|
||||
$this.destructLightbox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
close.on('click', function(e){
|
||||
e.preventDefault();
|
||||
$this.destructLightbox();
|
||||
});
|
||||
|
||||
return overlay;
|
||||
},
|
||||
|
||||
destructLightbox: function(){
|
||||
var $this = this;
|
||||
this.options.beforeHideLightbox.call(this);
|
||||
|
||||
$('.nivo-lightbox-overlay').removeClass('nivo-lightbox-open');
|
||||
$('.nivo-lightbox-nav').hide();
|
||||
$('body').removeClass('nivo-lightbox-body-effect-'+ $this.options.effect);
|
||||
|
||||
// For IE
|
||||
var isMSIE = /*@cc_on!@*/0;
|
||||
if(isMSIE){
|
||||
$('.nivo-lightbox-overlay iframe').attr("src", " ");
|
||||
$('.nivo-lightbox-overlay iframe').remove();
|
||||
}
|
||||
|
||||
// Remove click handlers
|
||||
$('.nivo-lightbox-prev').off('click');
|
||||
$('.nivo-lightbox-next').off('click');
|
||||
|
||||
// Empty content (for videos)
|
||||
$('.nivo-lightbox-content').empty();
|
||||
|
||||
this.options.afterHideLightbox.call(this);
|
||||
},
|
||||
|
||||
isHidpi: function(){
|
||||
var mediaQuery = "(-webkit-min-device-pixel-ratio: 1.5),\
|
||||
(min--moz-device-pixel-ratio: 1.5),\
|
||||
(-o-min-device-pixel-ratio: 3/2),\
|
||||
(min-resolution: 1.5dppx)";
|
||||
if(window.devicePixelRatio > 1) return true;
|
||||
if(window.matchMedia && window.matchMedia(mediaQuery).matches) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$.fn[pluginName] = function(options){
|
||||
return this.each(function(){
|
||||
if(!$.data(this, pluginName)){
|
||||
$.data(this, pluginName, new NivoLightbox(this, options));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
})(jQuery, window, document);
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var modalEl = null;
|
||||
var iframeEl = null;
|
||||
var titleEl = null;
|
||||
var modalReady = false;
|
||||
|
||||
function ensureModal() {
|
||||
if (modalEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
modalEl = document.createElement('div');
|
||||
modalEl.className = 'modal fade eventup-registration-modal';
|
||||
modalEl.id = 'eventupRegistrationModal';
|
||||
modalEl.setAttribute('tabindex', '-1');
|
||||
modalEl.setAttribute('role', 'dialog');
|
||||
modalEl.setAttribute('aria-labelledby', 'eventupRegistrationModalTitle');
|
||||
modalEl.setAttribute('aria-hidden', 'true');
|
||||
modalEl.innerHTML =
|
||||
'<div class="modal-dialog modal-dialog-centered modal-lg" role="document">' +
|
||||
'<div class="modal-content">' +
|
||||
'<div class="modal-header">' +
|
||||
'<h5 class="modal-title" id="eventupRegistrationModalTitle">Register</h5>' +
|
||||
'<button type="button" class="close" data-dismiss="modal" aria-label="Close">' +
|
||||
'<span aria-hidden="true">×</span>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'<div class="modal-body eventup-registration-modal__body">' +
|
||||
'<iframe class="eventup-registration-modal__frame" title="Event registration form" src="about:blank"></iframe>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
document.body.appendChild(modalEl);
|
||||
iframeEl = modalEl.querySelector('.eventup-registration-modal__frame');
|
||||
titleEl = modalEl.querySelector('#eventupRegistrationModalTitle');
|
||||
|
||||
if (window.jQuery) {
|
||||
window.jQuery(modalEl).on('hidden.bs.modal', function () {
|
||||
if (iframeEl) {
|
||||
iframeEl.src = 'about:blank';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
modalReady = true;
|
||||
}
|
||||
|
||||
function buildEmbedUrl(url) {
|
||||
if (!url) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
var parsed = new URL(url, window.location.href);
|
||||
parsed.searchParams.set('embed', '1');
|
||||
return parsed.toString();
|
||||
} catch (error) {
|
||||
var joiner = url.indexOf('?') >= 0 ? '&' : '?';
|
||||
return url + joiner + 'embed=1';
|
||||
}
|
||||
}
|
||||
|
||||
function resolveRegistrationUrl(cta, config) {
|
||||
if (cta && cta.url) {
|
||||
return cta.url;
|
||||
}
|
||||
|
||||
var meta = (config && config.meta) || {};
|
||||
var eventId = meta.eventId;
|
||||
var webBase = meta.webBaseUrl;
|
||||
|
||||
if (eventId && webBase) {
|
||||
return String(webBase).replace(/\/$/, '') + '/event/' + eventId + '/register';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function openModal(url, title) {
|
||||
ensureModal();
|
||||
|
||||
if (!url) {
|
||||
console.warn('[registration-modal] No registration URL configured.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (titleEl) {
|
||||
titleEl.textContent = title || 'Register';
|
||||
}
|
||||
|
||||
if (iframeEl) {
|
||||
iframeEl.src = buildEmbedUrl(url);
|
||||
}
|
||||
|
||||
if (window.jQuery) {
|
||||
window.jQuery(modalEl).modal('show');
|
||||
} else {
|
||||
modalEl.classList.add('show');
|
||||
modalEl.style.display = 'block';
|
||||
modalEl.removeAttribute('aria-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function bindLink(link, cta, config) {
|
||||
if (!link || link.getAttribute('data-registration-modal-bound') === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
link.setAttribute('data-registration-modal-bound', 'true');
|
||||
link.setAttribute('data-cta-target', 'modal');
|
||||
link.href = '#';
|
||||
|
||||
link.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
openModal(resolveRegistrationUrl(cta, config), cta && cta.text ? cta.text : 'Register');
|
||||
});
|
||||
}
|
||||
|
||||
window.EventUpRegistrationModal = {
|
||||
bindLink: bindLink,
|
||||
open: openModal,
|
||||
resolveUrl: resolveRegistrationUrl,
|
||||
isReady: function () {
|
||||
return modalReady;
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
function bindVideoTile(tile) {
|
||||
var video = tile.querySelector('.video-tile-player');
|
||||
var playBtn = tile.querySelector('.video-tile-play');
|
||||
|
||||
if (!video || !playBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
playBtn.addEventListener('click', function () {
|
||||
if (video.paused) {
|
||||
video.play();
|
||||
playBtn.innerHTML = '<i class="lni-pause"></i>';
|
||||
tile.classList.remove('is-paused');
|
||||
} else {
|
||||
video.pause();
|
||||
playBtn.innerHTML = '<i class="lni-play"></i>';
|
||||
tile.classList.add('is-paused');
|
||||
}
|
||||
});
|
||||
|
||||
video.addEventListener('ended', function () {
|
||||
video.pause();
|
||||
playBtn.innerHTML = '<i class="lni-play"></i>';
|
||||
tile.classList.add('is-paused');
|
||||
});
|
||||
}
|
||||
|
||||
function initVideoTiles() {
|
||||
var tiles = document.querySelectorAll('[data-video-tile]');
|
||||
|
||||
tiles.forEach(function (tile) {
|
||||
if (tile.getAttribute('data-video-bound') === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
tile.setAttribute('data-video-bound', 'true');
|
||||
bindVideoTile(tile);
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initVideoTiles);
|
||||
} else {
|
||||
initVideoTiles();
|
||||
}
|
||||
|
||||
document.addEventListener('site-config-loaded', initVideoTiles);
|
||||
})();
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
(function() {
|
||||
var Util,
|
||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
Util = (function() {
|
||||
function Util() {}
|
||||
|
||||
Util.prototype.extend = function(custom, defaults) {
|
||||
var key, value;
|
||||
for (key in custom) {
|
||||
value = custom[key];
|
||||
if (value != null) {
|
||||
defaults[key] = value;
|
||||
}
|
||||
}
|
||||
return defaults;
|
||||
};
|
||||
|
||||
Util.prototype.isMobile = function(agent) {
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(agent);
|
||||
};
|
||||
|
||||
return Util;
|
||||
|
||||
})();
|
||||
|
||||
this.WOW = (function() {
|
||||
WOW.prototype.defaults = {
|
||||
boxClass: 'wow',
|
||||
animateClass: 'animated',
|
||||
offset: 0,
|
||||
mobile: true
|
||||
};
|
||||
|
||||
function WOW(options) {
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
this.scrollCallback = __bind(this.scrollCallback, this);
|
||||
this.scrollHandler = __bind(this.scrollHandler, this);
|
||||
this.start = __bind(this.start, this);
|
||||
this.scrolled = true;
|
||||
this.config = this.util().extend(options, this.defaults);
|
||||
}
|
||||
|
||||
WOW.prototype.init = function() {
|
||||
var _ref;
|
||||
this.element = window.document.documentElement;
|
||||
this.boxes = this.element.getElementsByClassName(this.config.boxClass);
|
||||
if (this.boxes.length) {
|
||||
if (this.disabled()) {
|
||||
return this.resetStyle();
|
||||
} else {
|
||||
if ((_ref = document.readyState) === "interactive" || _ref === "complete") {
|
||||
return this.start();
|
||||
} else {
|
||||
return document.addEventListener('DOMContentLoaded', this.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WOW.prototype.start = function() {
|
||||
var box, _i, _len, _ref;
|
||||
_ref = this.boxes;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
box = _ref[_i];
|
||||
this.applyStyle(box, true);
|
||||
}
|
||||
window.addEventListener('scroll', this.scrollHandler, false);
|
||||
window.addEventListener('resize', this.scrollHandler, false);
|
||||
return this.interval = setInterval(this.scrollCallback, 50);
|
||||
};
|
||||
|
||||
WOW.prototype.stop = function() {
|
||||
window.removeEventListener('scroll', this.scrollHandler, false);
|
||||
window.removeEventListener('resize', this.scrollHandler, false);
|
||||
if (this.interval != null) {
|
||||
return clearInterval(this.interval);
|
||||
}
|
||||
};
|
||||
|
||||
WOW.prototype.show = function(box) {
|
||||
this.applyStyle(box);
|
||||
return box.className = "" + box.className + " " + this.config.animateClass;
|
||||
};
|
||||
|
||||
WOW.prototype.applyStyle = function(box, hidden) {
|
||||
var delay, duration, iteration;
|
||||
duration = box.getAttribute('data-wow-duration');
|
||||
delay = box.getAttribute('data-wow-delay');
|
||||
iteration = box.getAttribute('data-wow-iteration');
|
||||
return box.setAttribute('style', this.customStyle(hidden, duration, delay, iteration));
|
||||
};
|
||||
|
||||
WOW.prototype.resetStyle = function() {
|
||||
var box, _i, _len, _ref, _results;
|
||||
_ref = this.boxes;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
box = _ref[_i];
|
||||
_results.push(box.setAttribute('style', 'visibility: visible;'));
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
|
||||
WOW.prototype.customStyle = function(hidden, duration, delay, iteration) {
|
||||
var style;
|
||||
style = hidden ? "visibility: hidden; -webkit-animation-name: none; -moz-animation-name: none; animation-name: none;" : "visibility: visible;";
|
||||
if (duration) {
|
||||
style += "-webkit-animation-duration: " + duration + "; -moz-animation-duration: " + duration + "; animation-duration: " + duration + ";";
|
||||
}
|
||||
if (delay) {
|
||||
style += "-webkit-animation-delay: " + delay + "; -moz-animation-delay: " + delay + "; animation-delay: " + delay + ";";
|
||||
}
|
||||
if (iteration) {
|
||||
style += "-webkit-animation-iteration-count: " + iteration + "; -moz-animation-iteration-count: " + iteration + "; animation-iteration-count: " + iteration + ";";
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
WOW.prototype.scrollHandler = function() {
|
||||
return this.scrolled = true;
|
||||
};
|
||||
|
||||
WOW.prototype.scrollCallback = function() {
|
||||
var box;
|
||||
if (this.scrolled) {
|
||||
this.scrolled = false;
|
||||
this.boxes = (function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = this.boxes;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
box = _ref[_i];
|
||||
if (!(box)) {
|
||||
continue;
|
||||
}
|
||||
if (this.isVisible(box)) {
|
||||
this.show(box);
|
||||
continue;
|
||||
}
|
||||
_results.push(box);
|
||||
}
|
||||
return _results;
|
||||
}).call(this);
|
||||
if (!this.boxes.length) {
|
||||
return this.stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WOW.prototype.offsetTop = function(element) {
|
||||
var top;
|
||||
top = element.offsetTop;
|
||||
while (element = element.offsetParent) {
|
||||
top += element.offsetTop;
|
||||
}
|
||||
return top;
|
||||
};
|
||||
|
||||
WOW.prototype.isVisible = function(box) {
|
||||
var bottom, offset, top, viewBottom, viewTop;
|
||||
offset = box.getAttribute('data-wow-offset') || this.config.offset;
|
||||
viewTop = window.pageYOffset;
|
||||
viewBottom = viewTop + this.element.clientHeight - offset;
|
||||
top = this.offsetTop(box);
|
||||
bottom = top + box.clientHeight;
|
||||
return top <= viewBottom && bottom >= viewTop;
|
||||
};
|
||||
|
||||
WOW.prototype.util = function() {
|
||||
return this._util || (this._util = new Util());
|
||||
};
|
||||
|
||||
WOW.prototype.disabled = function() {
|
||||
return this.config.mobile === false && this.util().isMobile(navigator.userAgent);
|
||||
};
|
||||
|
||||
return WOW;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
||||
|
|
@ -0,0 +1,522 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>{{meta.title}}</title>
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="{{assetUrl assetsBaseUrl 'css/bootstrap.min.css'}}" >
|
||||
<!-- Icon -->
|
||||
<link rel="stylesheet" type="text/css" href="{{assetUrl assetsBaseUrl 'fonts/line-icons.css'}}">
|
||||
<!-- Nivo Lightbox -->
|
||||
<link rel="stylesheet" type="text/css" href="{{assetUrl assetsBaseUrl 'css/nivo-lightbox.css'}}" >
|
||||
<!-- Animate -->
|
||||
<link rel="stylesheet" type="text/css" href="{{assetUrl assetsBaseUrl 'css/animate.css'}}">
|
||||
<!-- Main Style -->
|
||||
<link rel="stylesheet" type="text/css" href="{{assetUrl assetsBaseUrl 'css/main.css?v=20260707e'}}">
|
||||
<!-- Responsive Style -->
|
||||
<link rel="stylesheet" type="text/css" href="{{assetUrl assetsBaseUrl 'css/responsive.css'}}">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--eventup-accent: {{theme.accent}};
|
||||
--eventup-accent-start: {{theme.accent}};
|
||||
--eventup-heading-color: {{#if theme.heading.color}}{{theme.heading.color}}{{else}}{{theme.headingColor}}{{/if}};
|
||||
}
|
||||
{{#if sections.hero.image.url}}
|
||||
#hero-area.hero-area-bg { background-image: url('{{resolveAsset assetsBaseUrl sections.hero.image.url}}'); }
|
||||
{{/if}}
|
||||
</style>
|
||||
<script id="eventup-microsite-config" type="application/json">{{json this}}</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Header Area wrapper Starts -->
|
||||
<nav class="navbar navbar-expand-lg bg-inverse fixed-top scrolling-navbar">
|
||||
<div class="container">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<a href="#" class="navbar-brand" data-site-component="logo" data-logo-placement="header">
|
||||
<img src="" alt="" data-logo-image>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<i class="lni-menu"></i>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav mr-auto w-100 justify-content-end">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="##header-wrap">
|
||||
Home
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="##about">
|
||||
About
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="##team">
|
||||
Speakers
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="##schedules">
|
||||
Schedule
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="##sponsors">
|
||||
Sponsors
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="##contact-map">
|
||||
Contact
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item--register" data-nav-cta>
|
||||
<a class="btn btn-common" href="#" data-nav-cta-link>Register Now</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="navbar-spacer" aria-hidden="true"></div>
|
||||
|
||||
<header id="header-wrap">
|
||||
<!-- Hero Area Start -->
|
||||
<div id="hero-area" class="hero-area-bg" data-site-component="hero">
|
||||
<div class="overlay"></div>
|
||||
<div class="container-fluid hero-split-container">
|
||||
<div class="row hero-split align-items-cente no-gutters">
|
||||
<div class="col-lg-6 col-md-6 col-12 hero-split__media">
|
||||
<div class="hero-logo-wrap" data-hero-logo hidden>
|
||||
<img class="hero-logo" data-hero-logo-image alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-12 hero-split__content">
|
||||
<div class="hero-split__text">
|
||||
<h2 class="head-title hero-brand-title" data-hero-field="event_name">Developers Conference</h2>
|
||||
<p class="banner-info" data-hero-field="date_venue">15, Oct 2020 - Maria Hall, NY, United states</p>
|
||||
<div class="banner-btn" data-hero-cta>
|
||||
<a href="##" class="btn btn-common" data-hero-cta-link>Get Ticket</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hero Area End -->
|
||||
|
||||
</header>
|
||||
<!-- Header Area wrapper End -->
|
||||
|
||||
<!-- Count Bar Start -->
|
||||
<section id="count" data-site-component="countdown">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10">
|
||||
<div class="count-wrapper text-center">
|
||||
<div class="time-countdown wow fadeInUp" data-wow-delay="0.2s">
|
||||
<div id="clock" class="time-count"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Count Bar End -->
|
||||
|
||||
<!-- About Section Start -->
|
||||
<section id="about" class="section-padding" data-site-component="about">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-12 col-xs-12" data-about-media-col hidden>
|
||||
<div class="about-media" data-about-media-slot></div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12 col-xs-12">
|
||||
<div class="about-content">
|
||||
<div>
|
||||
<div class="about-text">
|
||||
<h2 data-about-field="heading">About The Conference</h2>
|
||||
<div data-about-content>
|
||||
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod ncididunt ametfh consectetur dolore magna aliqua. Ut enim ad minim veniam dolor sitame magnaelit ate consectetur adipisicing. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- About Section End -->
|
||||
|
||||
<!-- Video Section Start -->
|
||||
<div class="ready-to-play" data-site-component="video">
|
||||
<div class="container">
|
||||
<div class="section-title-header text-center">
|
||||
<h2 class="section-title wow fadeInUp" data-wow-delay="0.2s" data-video-field="heading">Hear it from experts</h2>
|
||||
</div>
|
||||
<div class="video-grid" data-video-list></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Video Section End -->
|
||||
|
||||
<!-- Information Bar Start -->
|
||||
<section id="information-bar" data-site-component="information-bar">
|
||||
<div class="container">
|
||||
<div class="row inforation-wrapper" data-info-list>
|
||||
<div class="col-lg-3 col-md-6 col-xs-12">
|
||||
<div class="info-bar-item">
|
||||
<i class="lni-map-marker info-bar-item__icon"></i>
|
||||
<div class="info-bar-item__text">
|
||||
<b class="info-bar-item__label">Location</b>
|
||||
<span class="info-bar-item__value">Maria Hall, NY, USA</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-xs-12">
|
||||
<div class="info-bar-item">
|
||||
<i class="lni-calendar info-bar-item__icon"></i>
|
||||
<div class="info-bar-item__text">
|
||||
<b class="info-bar-item__label">Date & Time</b>
|
||||
<span class="info-bar-item__value">10am - 7pm, 15th Oct</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-xs-12">
|
||||
<div class="info-bar-item">
|
||||
<i class="lni-mic info-bar-item__icon"></i>
|
||||
<div class="info-bar-item__text">
|
||||
<b class="info-bar-item__label">Speakers</b>
|
||||
<span class="info-bar-item__value">25 Professionals</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-xs-12">
|
||||
<div class="info-bar-item">
|
||||
<i class="lni-user info-bar-item__icon"></i>
|
||||
<div class="info-bar-item__text">
|
||||
<b class="info-bar-item__label">Seats</b>
|
||||
<span class="info-bar-item__value">100 People</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Information Bar End -->
|
||||
|
||||
<!-- intro Section Start -->
|
||||
<section id="intro" class="intro section-padding" data-site-component="event_highlights">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="section-title-header text-center">
|
||||
<h2 class="section-title wow fadeInUp" data-wow-delay="0.2s" data-event-highlights-field="heading">Why You Should Join?</h2>
|
||||
<p class="wow fadeInDown" data-wow-delay="0.2s" data-event-highlights-subtitle>Lorem ipsum dolor sit amet, in quodsi vulputate pro. Ius illum vocent mediocritatem an <br> cule dicta iriure at phaedrum.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row intro-wrapper" data-event-highlights-list>
|
||||
<div class="col-lg-4 col-md-6 col-xs-12">
|
||||
<div class="single-intro-text mb-70">
|
||||
<i class="lni-microphone"></i>
|
||||
<h3>Great Speakers</h3>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Minus mollitia, excepturi.</p>
|
||||
<span class="count-number">01</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- intro Section End -->
|
||||
|
||||
<!-- Counter Area Start-->
|
||||
<section class="counter-section section-padding" data-site-component="event_stats">
|
||||
<div class="container">
|
||||
<div class="row" data-event-stats-list>
|
||||
<div class="col-lg-3 col-md-6 col-xs-12 work-counter-widget">
|
||||
<div class="counter">
|
||||
<div class="icon">
|
||||
<i class="lni-mic"></i>
|
||||
</div>
|
||||
<div class="counter-content">
|
||||
<div class="counterUp">42</div>
|
||||
<p>Speakers</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Counter Area End-->
|
||||
|
||||
<!-- Team Section Start -->
|
||||
<section id="team" class="section-padding text-center speakers" data-site-component="speakers">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="section-title-header text-center">
|
||||
<h2 class="section-title wow fadeInUp" data-wow-delay="0.2s" data-speakers-field="heading">Our Speakers</h2>
|
||||
<p class="wow fadeInDown" data-wow-delay="0.2s" data-speakers-subtitle>Lorem ipsum dolor sit amet, in quodsi vulputate pro.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 mb-5 text-center speakers-tabs-wrap is-hidden" data-speakers-tabs-wrap>
|
||||
<ul class="nav nav-tabs" id="speakersTab" role="tablist" data-speakers-tabs></ul>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="speakers-area row wow fadeInDown" data-wow-delay="0.3s">
|
||||
<div class="tab-content col-12 clearfix" id="speakersTabContent" data-speakers-content></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Team Section End -->
|
||||
|
||||
<!-- Schedule Section Start -->
|
||||
<section id="schedules" class="schedule section-padding" data-site-component="event_schedule">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="section-title-header text-center">
|
||||
<h2 class="section-title wow fadeInUp" data-wow-delay="0.2s" data-schedule-field="heading">Event Schedules</h2>
|
||||
<p class="wow fadeInDown" data-wow-delay="0.2s" data-schedule-subtitle>Lorem ipsum dolor sit amet, in quodsi vulputate pro. Ius illum vocent mediocritatem an <br> cule dicta iriure at phaedrum.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 mb-5 text-center schedule-date-display is-hidden" data-schedule-single-date-wrap>
|
||||
<h4 class="schedule-single-date" data-schedule-single-date></h4>
|
||||
</div>
|
||||
<div class="col-12 mb-5 text-center schedule-tabs-wrap is-hidden" data-schedule-tabs-wrap>
|
||||
<ul class="nav nav-tabs" id="scheduleTab" role="tablist" data-schedule-tabs></ul>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="schedule-area row wow fadeInDown" data-wow-delay="0.3s">
|
||||
<div class="schedule-tab-content col-12 clearfix">
|
||||
<div class="tab-content" id="scheduleTabContent" data-schedule-content></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Schedule Section End -->
|
||||
|
||||
<!-- About CREST Section Start -->
|
||||
<section id="about-crest" class="section-padding" data-site-component="about_crest">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-12 col-xs-12" data-about-media-col hidden>
|
||||
<div class="about-media" data-about-media-slot></div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12 col-xs-12">
|
||||
<div class="about-content">
|
||||
<div>
|
||||
<div class="about-text">
|
||||
<h2 data-about-crest-field="heading">About CREST</h2>
|
||||
<div data-about-crest-content></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- About CREST Section End -->
|
||||
|
||||
<!-- Blog Section Start -->
|
||||
<section id="blog" class="section-padding" data-site-component="latest_news">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="section-title-header text-center">
|
||||
<h2 class="section-title wow fadeInUp" data-wow-delay="0.2s" data-news-field="heading">Latest News</h2>
|
||||
<p class="wow fadeInDown" data-wow-delay="0.2s" data-news-subtitle>Lorem ipsum dolor sit amet, in quodsi vulputate pro.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" data-news-list></div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Blog Section End -->
|
||||
|
||||
<!-- Sponsors Section Start -->
|
||||
<section id="sponsors" class="section-padding" data-site-component="sponsors">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="section-title-header text-center">
|
||||
<h2 class="section-title wow fadeInUp" data-wow-delay="0.2s" data-sponsors-field="heading">Sponsors</h2>
|
||||
<p class="wow fadeInDown" data-wow-delay="0.2s" data-sponsors-subtitle>Lorem ipsum dolor sit amet, in quodsi vulputate pro.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sponsors-groups text-center wow fadeInDown" data-wow-delay="0.3s" data-sponsors-list></div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Sponsors Section End -->
|
||||
|
||||
<!-- Contact Us Section -->
|
||||
<section id="contact-map" class="section-padding" data-site-component="contact">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="section-title-header text-center">
|
||||
<h2 class="section-title wow fadeInUp" data-wow-delay="0.2s" data-contact-field="heading">Contact Us</h2>
|
||||
<p class="wow fadeInDown" data-wow-delay="0.2s" data-contact-field="subtitle" style="display: none;"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-4 col-md-12 col-xs-12">
|
||||
<div class="contact-details" data-contact-details hidden>
|
||||
<h4 class="contact-details-heading" data-contact-field="detailsHeading">Contact us:</h4>
|
||||
<ul class="contact-details-phones list-unstyled" data-contact-phones></ul>
|
||||
<p class="contact-details-address" data-contact-address></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8 col-md-12 col-xs-12">
|
||||
<div class="container-form wow fadeInLeft" data-wow-delay="0.2s">
|
||||
<div class="form-wrapper">
|
||||
<form role="form" method="post" id="contactForm" name="contact-form" novalidate>
|
||||
<div class="row">
|
||||
<div class="col-md-6 form-line">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" id="name" data-contact-field="name" name="name" placeholder="Name" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 form-line">
|
||||
<div class="form-group">
|
||||
<input type="email" class="form-control" id="email" data-contact-field="email" name="email" placeholder="Email" required>
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 form-line">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" id="msg_subject" data-contact-field="subject" name="subject" placeholder="Subject">
|
||||
<div class="help-block with-errors"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" rows="4" id="message" data-contact-field="message" name="message" placeholder="Message"></textarea>
|
||||
</div>
|
||||
<div class="form-group contact-captcha-group">
|
||||
<label class="contact-captcha-label" for="contactCaptcha">Security check</label>
|
||||
<div class="contact-captcha-row">
|
||||
<span class="contact-captcha-question" data-contact-captcha-question aria-live="polite">Loading…</span>
|
||||
<input type="text" class="form-control contact-captcha-input" id="contactCaptcha" data-contact-captcha-answer inputmode="numeric" autocomplete="off" placeholder="Answer" required>
|
||||
<button type="button" class="btn btn-common contact-captcha-reload" data-contact-captcha-reload aria-label="Reload captcha">↻</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eb-contact-honeypot" aria-hidden="true">
|
||||
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-submit">
|
||||
<button type="submit" class="btn btn-common" id="form-submit"><i class="fa fa-paper-plane" aria-hidden="true"></i> Send Message</button>
|
||||
<div id="msgSubmit" class="h3 text-center hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Contact Us Section End -->
|
||||
|
||||
<!-- Map Section Start -->
|
||||
<section id="google-map-area" data-site-component="map">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<iframe data-map-embed title="Event location map" style="border:0; height: 450px; width: 100%;" src="" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Map Section End -->
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10 col-md-12 col-xs-12">
|
||||
<div class="footer-otherinfo row align-items-start" data-site-component="footer">
|
||||
<div class="col-lg-4 col-md-12 footer-col footer-col-brand">
|
||||
<div class="footer-logo mb-3">
|
||||
<img src="" width="220" class="img-fluid" alt="" data-footer-logo>
|
||||
</div>
|
||||
<p class="footer-tagline" data-footer-tagline></p>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 footer-col footer-col-links" data-footer-links-wrap>
|
||||
<h6 class="footer-section-heading mb-4" data-footer-links-heading>Quick Links</h6>
|
||||
<ul class="footer-link-list list-unstyled" data-footer-link-list></ul>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 footer-col footer-col-social" data-footer-social-wrap>
|
||||
<h6 class="footer-section-heading mb-4" data-footer-social-heading>Follow us</h6>
|
||||
<ul class="footer-social-list list-unstyled" data-footer-social-list></ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="site-info">
|
||||
<p>Powered by <a href="https://eventbuzz.live" rel="nofollow" target="_blank">EventBuzz.live</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Go to Top Link -->
|
||||
<a href="##" class="back-to-top">
|
||||
<i class="lni-chevron-up"></i>
|
||||
</a>
|
||||
|
||||
<div id="preloader">
|
||||
<div class="sk-circle">
|
||||
<div class="sk-circle1 sk-child"></div>
|
||||
<div class="sk-circle2 sk-child"></div>
|
||||
<div class="sk-circle3 sk-child"></div>
|
||||
<div class="sk-circle4 sk-child"></div>
|
||||
<div class="sk-circle5 sk-child"></div>
|
||||
<div class="sk-circle6 sk-child"></div>
|
||||
<div class="sk-circle7 sk-child"></div>
|
||||
<div class="sk-circle8 sk-child"></div>
|
||||
<div class="sk-circle9 sk-child"></div>
|
||||
<div class="sk-circle10 sk-child"></div>
|
||||
<div class="sk-circle11 sk-child"></div>
|
||||
<div class="sk-circle12 sk-child"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/jquery-min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/popper.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/bootstrap.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/jquery.countdown.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/waypoints.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/jquery.counterup.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/jquery.nav.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/jquery.easing.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/wow.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/nivo-lightbox.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/video.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/registration-modal.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/feedback-fab.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/contact-form.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/marketing-popup.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/site-config.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/main.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/form-validator.min.js'}}"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,930 @@
|
|||
{
|
||||
"version": 1,
|
||||
"meta": {
|
||||
"title": "CREST NOVEL Summit",
|
||||
"eventId": ""
|
||||
},
|
||||
"theme": {
|
||||
"fields": [
|
||||
{
|
||||
"key": "accent",
|
||||
"type": "color",
|
||||
"label": "Accent color",
|
||||
"default": "#189A95"
|
||||
},
|
||||
{
|
||||
"key": "headingColor",
|
||||
"type": "color",
|
||||
"label": "Heading color",
|
||||
"default": "#015AAA"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sections": [
|
||||
{
|
||||
"id": "marketing_popup",
|
||||
"label": "Marketing popup",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "heading",
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"default": "Introducing"
|
||||
},
|
||||
{
|
||||
"key": "logo",
|
||||
"type": "image",
|
||||
"label": "Popup logo"
|
||||
},
|
||||
{
|
||||
"key": "media.type",
|
||||
"type": "select",
|
||||
"label": "Media",
|
||||
"default": "none",
|
||||
"options": [
|
||||
{
|
||||
"value": "none",
|
||||
"label": "None"
|
||||
},
|
||||
{
|
||||
"value": "image",
|
||||
"label": "Image"
|
||||
},
|
||||
{
|
||||
"value": "video",
|
||||
"label": "Video (upload)"
|
||||
},
|
||||
{
|
||||
"value": "youtube",
|
||||
"label": "YouTube video"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "media.image",
|
||||
"type": "image",
|
||||
"label": "Popup image",
|
||||
"showIf": {
|
||||
"field": "media.type",
|
||||
"equals": "image"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "media.video",
|
||||
"type": "video",
|
||||
"label": "Popup video",
|
||||
"showIf": {
|
||||
"field": "media.type",
|
||||
"equals": "video"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "media.youtube",
|
||||
"type": "url",
|
||||
"label": "YouTube URL",
|
||||
"placeholder": "https://www.youtube.com/watch?v=...",
|
||||
"showIf": {
|
||||
"field": "media.type",
|
||||
"equals": "youtube"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "autoCloseDuration",
|
||||
"type": "text",
|
||||
"label": "Auto-close after (ms)",
|
||||
"default": "3000"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "logo",
|
||||
"label": "Logos",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "header.url",
|
||||
"type": "image",
|
||||
"label": "Header logo"
|
||||
},
|
||||
{
|
||||
"key": "header.alt",
|
||||
"type": "text",
|
||||
"label": "Header logo alt",
|
||||
"default": "Event logo"
|
||||
},
|
||||
{
|
||||
"key": "header.maxHeight",
|
||||
"type": "text",
|
||||
"label": "Header logo max height (px)",
|
||||
"default": "56"
|
||||
},
|
||||
{
|
||||
"key": "hero.url",
|
||||
"type": "image",
|
||||
"label": "Hero logo"
|
||||
},
|
||||
{
|
||||
"key": "hero.alt",
|
||||
"type": "text",
|
||||
"label": "Hero logo alt",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "footer.url",
|
||||
"type": "image",
|
||||
"label": "Footer logo"
|
||||
},
|
||||
{
|
||||
"key": "footer.alt",
|
||||
"type": "text",
|
||||
"label": "Footer logo alt",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "popup.url",
|
||||
"type": "image",
|
||||
"label": "Marketing popup logo"
|
||||
},
|
||||
{
|
||||
"key": "popup.alt",
|
||||
"type": "text",
|
||||
"label": "Marketing popup logo alt",
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hero",
|
||||
"label": "Hero",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "event_name",
|
||||
"type": "text",
|
||||
"label": "Event name",
|
||||
"default": "Your Event Name"
|
||||
},
|
||||
{
|
||||
"key": "date_venue",
|
||||
"type": "text",
|
||||
"label": "Date & venue",
|
||||
"default": "Date and location"
|
||||
},
|
||||
{
|
||||
"key": "image.url",
|
||||
"type": "image",
|
||||
"label": "Hero background image"
|
||||
},
|
||||
{
|
||||
"key": "cta.text",
|
||||
"type": "text",
|
||||
"label": "CTA label",
|
||||
"default": "Register Now"
|
||||
},
|
||||
{
|
||||
"key": "cta.url",
|
||||
"type": "url",
|
||||
"label": "CTA URL",
|
||||
"default": "#"
|
||||
},
|
||||
{
|
||||
"key": "cta.target",
|
||||
"type": "select",
|
||||
"label": "CTA target",
|
||||
"default": "_blank",
|
||||
"options": [
|
||||
{
|
||||
"value": "_blank",
|
||||
"label": "New tab"
|
||||
},
|
||||
{
|
||||
"value": "modal",
|
||||
"label": "Modal (iframe)"
|
||||
},
|
||||
{
|
||||
"value": "_self",
|
||||
"label": "Same tab"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "countdown.show",
|
||||
"type": "boolean",
|
||||
"label": "Show countdown",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"key": "countdown.date",
|
||||
"type": "text",
|
||||
"label": "Countdown date (YYYY-MM-DD)",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "countdown.time",
|
||||
"type": "text",
|
||||
"label": "Countdown time (HH:mm)",
|
||||
"default": "12:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "about",
|
||||
"label": "About the event",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "heading",
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"default": "About the Event"
|
||||
},
|
||||
{
|
||||
"key": "content",
|
||||
"type": "richtext",
|
||||
"label": "Content",
|
||||
"default": "<p>Describe your event here.</p>"
|
||||
},
|
||||
{
|
||||
"key": "media.type",
|
||||
"type": "select",
|
||||
"label": "Media",
|
||||
"default": "none",
|
||||
"options": [
|
||||
{
|
||||
"value": "none",
|
||||
"label": "None"
|
||||
},
|
||||
{
|
||||
"value": "image",
|
||||
"label": "Image"
|
||||
},
|
||||
{
|
||||
"value": "video",
|
||||
"label": "Video (upload)"
|
||||
},
|
||||
{
|
||||
"value": "youtube",
|
||||
"label": "YouTube video"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "media.image",
|
||||
"type": "image",
|
||||
"label": "About image",
|
||||
"showIf": {
|
||||
"field": "media.type",
|
||||
"equals": "image"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "media.video",
|
||||
"type": "video",
|
||||
"label": "About video",
|
||||
"showIf": {
|
||||
"field": "media.type",
|
||||
"equals": "video"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "media.youtube",
|
||||
"type": "url",
|
||||
"label": "YouTube URL",
|
||||
"placeholder": "https://www.youtube.com/watch?v=...",
|
||||
"showIf": {
|
||||
"field": "media.type",
|
||||
"equals": "youtube"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "youtubeUrl",
|
||||
"type": "url",
|
||||
"label": "YouTube URL (legacy)",
|
||||
"placeholder": "https://www.youtube.com/watch?v=..."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "video",
|
||||
"label": "Expert videos",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "heading",
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"default": "Hear it from experts"
|
||||
},
|
||||
{
|
||||
"key": "items",
|
||||
"type": "array",
|
||||
"label": "Videos",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "url",
|
||||
"type": "video",
|
||||
"label": "Video URL"
|
||||
},
|
||||
{
|
||||
"key": "title",
|
||||
"type": "text",
|
||||
"label": "Title"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "informationBar",
|
||||
"label": "Information bar",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "items",
|
||||
"type": "array",
|
||||
"label": "Info items",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "icon",
|
||||
"type": "text",
|
||||
"label": "Icon class (LineIcons)",
|
||||
"default": "lni-map-marker"
|
||||
},
|
||||
{
|
||||
"key": "label",
|
||||
"type": "text",
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"key": "value",
|
||||
"type": "text",
|
||||
"label": "Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "event_highlights",
|
||||
"label": "Why join (highlights)",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "heading",
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"default": "Why You Should Join?"
|
||||
},
|
||||
{
|
||||
"key": "subtitle",
|
||||
"type": "textarea",
|
||||
"label": "Subtitle",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "items",
|
||||
"type": "array",
|
||||
"label": "Highlight cards",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "icon",
|
||||
"type": "text",
|
||||
"label": "Icon class",
|
||||
"default": "lni-microphone"
|
||||
},
|
||||
{
|
||||
"key": "title",
|
||||
"type": "text",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"key": "description",
|
||||
"type": "textarea",
|
||||
"label": "Description"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "event_stats",
|
||||
"label": "Stats counters",
|
||||
"show": false,
|
||||
"fields": [
|
||||
{
|
||||
"key": "items",
|
||||
"type": "array",
|
||||
"label": "Stats",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "icon",
|
||||
"type": "text",
|
||||
"label": "Icon class",
|
||||
"default": "lni-mic"
|
||||
},
|
||||
{
|
||||
"key": "value",
|
||||
"type": "text",
|
||||
"label": "Value"
|
||||
},
|
||||
{
|
||||
"key": "label",
|
||||
"type": "text",
|
||||
"label": "Label"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "speakers",
|
||||
"label": "Speakers",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "heading",
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"default": "Our Speakers"
|
||||
},
|
||||
{
|
||||
"key": "subtitle",
|
||||
"type": "textarea",
|
||||
"label": "Subtitle",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "tabs",
|
||||
"type": "array",
|
||||
"label": "Speaker tabs",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "label",
|
||||
"type": "text",
|
||||
"label": "Tab label",
|
||||
"default": "Speakers"
|
||||
},
|
||||
{
|
||||
"key": "items",
|
||||
"type": "array",
|
||||
"label": "Speakers in tab",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "name",
|
||||
"type": "text",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"key": "role",
|
||||
"type": "text",
|
||||
"label": "Role"
|
||||
},
|
||||
{
|
||||
"key": "image",
|
||||
"type": "image",
|
||||
"label": "Photo"
|
||||
},
|
||||
{
|
||||
"key": "url",
|
||||
"type": "url",
|
||||
"label": "Profile URL",
|
||||
"default": "#"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "items",
|
||||
"type": "array",
|
||||
"label": "Speakers (single tab fallback)",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "name",
|
||||
"type": "text",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"key": "role",
|
||||
"type": "text",
|
||||
"label": "Role"
|
||||
},
|
||||
{
|
||||
"key": "image",
|
||||
"type": "image",
|
||||
"label": "Photo"
|
||||
},
|
||||
{
|
||||
"key": "url",
|
||||
"type": "url",
|
||||
"label": "Profile URL",
|
||||
"default": "#"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "event_schedule",
|
||||
"label": "Schedule",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "heading",
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"default": "Event Schedule"
|
||||
},
|
||||
{
|
||||
"key": "subtitle",
|
||||
"type": "textarea",
|
||||
"label": "Subtitle",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "days",
|
||||
"type": "array",
|
||||
"label": "Days",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "label",
|
||||
"type": "text",
|
||||
"label": "Day label",
|
||||
"default": "Day 01"
|
||||
},
|
||||
{
|
||||
"key": "date",
|
||||
"type": "text",
|
||||
"label": "Date line",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "sessions",
|
||||
"type": "array",
|
||||
"label": "Sessions",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "time",
|
||||
"type": "text",
|
||||
"label": "Time"
|
||||
},
|
||||
{
|
||||
"key": "type",
|
||||
"type": "text",
|
||||
"label": "Type",
|
||||
"default": "Main programme"
|
||||
},
|
||||
{
|
||||
"key": "title",
|
||||
"type": "text",
|
||||
"label": "Title"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "about_crest",
|
||||
"label": "About CREST",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "heading",
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"default": "About CREST"
|
||||
},
|
||||
{
|
||||
"key": "content",
|
||||
"type": "richtext",
|
||||
"label": "Content",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "youtubeUrl",
|
||||
"type": "url",
|
||||
"label": "YouTube URL",
|
||||
"placeholder": "https://www.youtube.com/watch?v=..."
|
||||
},
|
||||
{
|
||||
"key": "cta.text",
|
||||
"type": "text",
|
||||
"label": "CTA text",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "cta.url",
|
||||
"type": "url",
|
||||
"label": "CTA URL",
|
||||
"default": "#"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "latest_news",
|
||||
"label": "Latest news",
|
||||
"show": false,
|
||||
"fields": [
|
||||
{
|
||||
"key": "heading",
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"default": "Latest News"
|
||||
},
|
||||
{
|
||||
"key": "subtitle",
|
||||
"type": "textarea",
|
||||
"label": "Subtitle",
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sponsors",
|
||||
"label": "Sponsors",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "heading",
|
||||
"type": "text",
|
||||
"label": "Heading (optional)",
|
||||
"default": "Sponsors"
|
||||
},
|
||||
{
|
||||
"key": "subtitle",
|
||||
"type": "textarea",
|
||||
"label": "Subtitle",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "hideHeading",
|
||||
"type": "boolean",
|
||||
"label": "Hide section heading",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"key": "hideGroupTitles",
|
||||
"type": "boolean",
|
||||
"label": "Hide sponsor tier titles",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"key": "groups",
|
||||
"type": "array",
|
||||
"label": "Sponsor groups",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "title",
|
||||
"type": "text",
|
||||
"label": "Tier title"
|
||||
},
|
||||
{
|
||||
"key": "tier",
|
||||
"type": "text",
|
||||
"label": "Tier id",
|
||||
"default": "other"
|
||||
},
|
||||
{
|
||||
"key": "items",
|
||||
"type": "array",
|
||||
"label": "Logos",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "image",
|
||||
"type": "image",
|
||||
"label": "Logo image"
|
||||
},
|
||||
{
|
||||
"key": "alt",
|
||||
"type": "text",
|
||||
"label": "Alt text"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "tierSizes.platinum",
|
||||
"type": "text",
|
||||
"label": "Platinum logo max height (px)",
|
||||
"default": "360"
|
||||
},
|
||||
{
|
||||
"key": "tierSizes.other",
|
||||
"type": "text",
|
||||
"label": "Other sponsor logo max height (px)",
|
||||
"default": "80"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "map",
|
||||
"label": "Map",
|
||||
"show": false,
|
||||
"fields": [
|
||||
{
|
||||
"key": "embedUrl",
|
||||
"type": "url",
|
||||
"label": "Google Maps embed URL"
|
||||
},
|
||||
{
|
||||
"key": "height",
|
||||
"type": "text",
|
||||
"label": "Iframe height (px)",
|
||||
"default": "450"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "contact",
|
||||
"label": "Contact",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "heading",
|
||||
"type": "text",
|
||||
"label": "Heading",
|
||||
"default": "Contact Us"
|
||||
},
|
||||
{
|
||||
"key": "subtitle",
|
||||
"type": "textarea",
|
||||
"label": "Subtitle",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "formId",
|
||||
"type": "text",
|
||||
"label": "EventBuzz form ID"
|
||||
},
|
||||
{
|
||||
"key": "apiBaseUrl",
|
||||
"type": "url",
|
||||
"label": "API base URL",
|
||||
"default": "https://api.eventbuzz.live/api"
|
||||
},
|
||||
{
|
||||
"key": "address",
|
||||
"type": "textarea",
|
||||
"label": "Address",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "phones",
|
||||
"type": "array",
|
||||
"label": "Phone numbers",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "label",
|
||||
"type": "text",
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"key": "number",
|
||||
"type": "text",
|
||||
"label": "Display number"
|
||||
},
|
||||
{
|
||||
"key": "tel",
|
||||
"type": "text",
|
||||
"label": "tel: href value"
|
||||
},
|
||||
{
|
||||
"key": "email",
|
||||
"type": "text",
|
||||
"label": "Email (optional)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "detailsHeading",
|
||||
"type": "text",
|
||||
"label": "Details heading",
|
||||
"default": "Contact us:"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "feedback",
|
||||
"label": "Feedback FAB",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "label",
|
||||
"type": "text",
|
||||
"label": "Button label",
|
||||
"default": "Feedback"
|
||||
},
|
||||
{
|
||||
"key": "url",
|
||||
"type": "url",
|
||||
"label": "Feedback form URL"
|
||||
},
|
||||
{
|
||||
"key": "target",
|
||||
"type": "select",
|
||||
"label": "Open in",
|
||||
"default": "_blank",
|
||||
"options": [
|
||||
{
|
||||
"value": "_blank",
|
||||
"label": "New tab"
|
||||
},
|
||||
{
|
||||
"value": "modal",
|
||||
"label": "Modal"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "footer",
|
||||
"label": "Footer",
|
||||
"show": true,
|
||||
"fields": [
|
||||
{
|
||||
"key": "tagline",
|
||||
"type": "text",
|
||||
"label": "Tagline",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"key": "linksHeading",
|
||||
"type": "text",
|
||||
"label": "Links heading",
|
||||
"default": "Quick Links"
|
||||
},
|
||||
{
|
||||
"key": "social.heading",
|
||||
"type": "text",
|
||||
"label": "Social heading",
|
||||
"default": "Follow us"
|
||||
},
|
||||
{
|
||||
"key": "links",
|
||||
"type": "array",
|
||||
"label": "Footer links",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "text",
|
||||
"type": "text",
|
||||
"label": "Text"
|
||||
},
|
||||
{
|
||||
"key": "url",
|
||||
"type": "url",
|
||||
"label": "URL"
|
||||
},
|
||||
{
|
||||
"key": "target",
|
||||
"type": "select",
|
||||
"label": "Target",
|
||||
"default": "_self",
|
||||
"options": [
|
||||
{
|
||||
"value": "modal",
|
||||
"label": "Modal"
|
||||
},
|
||||
{
|
||||
"value": "_blank",
|
||||
"label": "New tab"
|
||||
},
|
||||
{
|
||||
"value": "_self",
|
||||
"label": "Same tab"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "social.links",
|
||||
"type": "array",
|
||||
"label": "Social links",
|
||||
"itemFields": [
|
||||
{
|
||||
"key": "url",
|
||||
"type": "url",
|
||||
"label": "URL"
|
||||
},
|
||||
{
|
||||
"key": "icon",
|
||||
"type": "text",
|
||||
"label": "Icon class",
|
||||
"default": "lni-facebook-filled"
|
||||
},
|
||||
{
|
||||
"key": "label",
|
||||
"type": "text",
|
||||
"label": "Aria label"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -4,12 +4,12 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>{{meta.title}}</title>
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'assets/css/bootstrap.min.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'assets/fonts/line-icons.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'assets/css/nivo-lightbox.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'assets/css/animate.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'assets/css/main.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'assets/css/responsive.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'css/bootstrap.min.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'fonts/line-icons.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'css/nivo-lightbox.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'css/animate.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'css/main.css'}}">
|
||||
<link rel="stylesheet" href="{{assetUrl assetsBaseUrl 'css/responsive.css'}}">
|
||||
<style>
|
||||
:root {
|
||||
--eventup-accent: {{theme.accent}};
|
||||
|
|
@ -111,6 +111,15 @@
|
|||
<h2>{{sections.about.heading}}</h2>
|
||||
<div>{{{sections.about.content}}}</div>
|
||||
</div>
|
||||
{{#if (eq sections.about.media.type "image")}}{{#if sections.about.media.image}}
|
||||
<div class="eventup-section-media"><img src="{{resolveAsset assetsBaseUrl sections.about.media.image}}" alt="{{sections.about.heading}}"></div>
|
||||
{{/if}}{{/if}}
|
||||
{{#if (eq sections.about.media.type "video")}}{{#if sections.about.media.video}}
|
||||
<div class="eventup-section-media"><video controls preload="metadata" src="{{resolveAsset assetsBaseUrl sections.about.media.video}}"></video></div>
|
||||
{{/if}}{{/if}}
|
||||
{{#if (eq sections.about.media.type "youtube")}}{{#if sections.about.media.youtube}}
|
||||
<div class="eventup-section-media"><iframe src="{{youtubeEmbed sections.about.media.youtube}}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>
|
||||
{{/if}}{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -125,12 +134,17 @@
|
|||
<h2 class="section-title wow fadeInUp">{{sections.video.heading}}</h2>
|
||||
</div>
|
||||
<div class="video-grid row">
|
||||
{{#each sections.video.items}}
|
||||
<div class="col-md-6 mb-4">
|
||||
<video class="w-100" controls preload="metadata" src="{{resolveAsset @root.assetsBaseUrl this.url}}"></video>
|
||||
{{#if this.title}}<p class="mt-2 text-center">{{this.title}}</p>{{/if}}
|
||||
<div class="col-md-10 offset-md-1">
|
||||
{{#if (eq sections.video.media.type "image")}}{{#if sections.video.media.image}}
|
||||
<div class="eventup-section-media"><img src="{{resolveAsset assetsBaseUrl sections.video.media.image}}" alt="{{sections.video.heading}}"></div>
|
||||
{{/if}}{{/if}}
|
||||
{{#if (eq sections.video.media.type "video")}}{{#if sections.video.media.video}}
|
||||
<div class="eventup-section-media"><video controls preload="metadata" src="{{resolveAsset assetsBaseUrl sections.video.media.video}}"></video></div>
|
||||
{{/if}}{{/if}}
|
||||
{{#if (eq sections.video.media.type "youtube")}}{{#if sections.video.media.youtube}}
|
||||
<div class="eventup-section-media"><iframe src="{{youtubeEmbed sections.video.media.youtube}}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>
|
||||
{{/if}}{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -428,23 +442,23 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/jquery-min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/popper.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/bootstrap.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/jquery.countdown.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/waypoints.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/jquery.counterup.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/jquery.nav.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/jquery.easing.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/wow.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/nivo-lightbox.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/video.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/registration-modal.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/feedback-fab.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/contact-form.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/marketing-popup.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/main.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/form-validator.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'assets/js/eventup-ssr-bridge.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/jquery-min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/popper.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/bootstrap.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/jquery.countdown.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/waypoints.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/jquery.counterup.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/jquery.nav.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/jquery.easing.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/wow.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/nivo-lightbox.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/video.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/registration-modal.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/feedback-fab.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/contact-form.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/marketing-popup.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/main.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/form-validator.min.js'}}"></script>
|
||||
<script src="{{assetUrl assetsBaseUrl 'js/eventup-ssr-bridge.js'}}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,37 @@
|
|||
"fields": [
|
||||
{ "key": "heading", "type": "text", "label": "Heading", "default": "Introducing" },
|
||||
{ "key": "logo", "type": "image", "label": "Popup logo" },
|
||||
{
|
||||
"key": "media.type",
|
||||
"type": "select",
|
||||
"label": "Media",
|
||||
"default": "none",
|
||||
"options": [
|
||||
{ "value": "none", "label": "None" },
|
||||
{ "value": "image", "label": "Image" },
|
||||
{ "value": "video", "label": "Video (upload)" },
|
||||
{ "value": "youtube", "label": "YouTube video" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "media.image",
|
||||
"type": "image",
|
||||
"label": "Popup image",
|
||||
"showIf": { "field": "media.type", "equals": "image" }
|
||||
},
|
||||
{
|
||||
"key": "media.video",
|
||||
"type": "video",
|
||||
"label": "Popup video",
|
||||
"showIf": { "field": "media.type", "equals": "video" }
|
||||
},
|
||||
{
|
||||
"key": "media.youtube",
|
||||
"type": "url",
|
||||
"label": "YouTube URL",
|
||||
"placeholder": "https://www.youtube.com/watch?v=...",
|
||||
"showIf": { "field": "media.type", "equals": "youtube" }
|
||||
},
|
||||
{
|
||||
"key": "autoCloseDuration",
|
||||
"type": "text",
|
||||
|
|
@ -73,7 +104,37 @@
|
|||
"fields": [
|
||||
{ "key": "heading", "type": "text", "label": "Heading", "default": "About the Event" },
|
||||
{ "key": "content", "type": "richtext", "label": "Content", "default": "<p>Describe your event here.</p>" },
|
||||
{ "key": "youtubeUrl", "type": "url", "label": "YouTube embed URL (optional)" }
|
||||
{
|
||||
"key": "media.type",
|
||||
"type": "select",
|
||||
"label": "Media",
|
||||
"default": "none",
|
||||
"options": [
|
||||
{ "value": "none", "label": "None" },
|
||||
{ "value": "image", "label": "Image" },
|
||||
{ "value": "video", "label": "Video (upload)" },
|
||||
{ "value": "youtube", "label": "YouTube video" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "media.image",
|
||||
"type": "image",
|
||||
"label": "About image",
|
||||
"showIf": { "field": "media.type", "equals": "image" }
|
||||
},
|
||||
{
|
||||
"key": "media.video",
|
||||
"type": "video",
|
||||
"label": "About video",
|
||||
"showIf": { "field": "media.type", "equals": "video" }
|
||||
},
|
||||
{
|
||||
"key": "media.youtube",
|
||||
"type": "url",
|
||||
"label": "YouTube URL",
|
||||
"placeholder": "https://www.youtube.com/watch?v=...",
|
||||
"showIf": { "field": "media.type", "equals": "youtube" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -83,13 +144,35 @@
|
|||
"fields": [
|
||||
{ "key": "heading", "type": "text", "label": "Heading", "default": "Hear it from experts" },
|
||||
{
|
||||
"key": "items",
|
||||
"type": "array",
|
||||
"label": "Videos",
|
||||
"itemFields": [
|
||||
{ "key": "url", "type": "url", "label": "Video URL" },
|
||||
{ "key": "title", "type": "text", "label": "Title" }
|
||||
"key": "media.type",
|
||||
"type": "select",
|
||||
"label": "Media",
|
||||
"default": "none",
|
||||
"options": [
|
||||
{ "value": "none", "label": "None" },
|
||||
{ "value": "image", "label": "Image" },
|
||||
{ "value": "video", "label": "Video (upload)" },
|
||||
{ "value": "youtube", "label": "YouTube video" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "media.image",
|
||||
"type": "image",
|
||||
"label": "Image",
|
||||
"showIf": { "field": "media.type", "equals": "image" }
|
||||
},
|
||||
{
|
||||
"key": "media.video",
|
||||
"type": "video",
|
||||
"label": "Video",
|
||||
"showIf": { "field": "media.type", "equals": "video" }
|
||||
},
|
||||
{
|
||||
"key": "media.youtube",
|
||||
"type": "url",
|
||||
"label": "YouTube URL",
|
||||
"placeholder": "https://www.youtube.com/watch?v=...",
|
||||
"showIf": { "field": "media.type", "equals": "youtube" }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
"qrcode": "^1.5.4",
|
||||
"razorpay": "^2.9.6",
|
||||
"sharp": "^0.33.5",
|
||||
"ssh2-sftp-client": "^11.0.0",
|
||||
"winston": "^3.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -4870,6 +4871,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
|
|
@ -5187,6 +5197,15 @@
|
|||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
||||
|
|
@ -5329,6 +5348,15 @@
|
|||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/buildcheck": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz",
|
||||
"integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bullmq": {
|
||||
"version": "5.77.6",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.77.6.tgz",
|
||||
|
|
@ -5832,6 +5860,20 @@
|
|||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cpu-features": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
|
||||
"integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"buildcheck": "~0.0.6",
|
||||
"nan": "^2.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
|
|
@ -6279,6 +6321,12 @@
|
|||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/err-code": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
|
||||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||
|
|
@ -8962,6 +9010,13 @@
|
|||
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.28.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz",
|
||||
"integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
|
|
@ -9712,6 +9767,28 @@
|
|||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/promise-retry": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
|
||||
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"err-code": "^2.0.2",
|
||||
"retry": "^0.12.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/promise-retry/node_modules/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/prompts": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||
|
|
@ -10461,6 +10538,41 @@
|
|||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ssh2": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
|
||||
"integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"asn1": "^0.2.6",
|
||||
"bcrypt-pbkdf": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"cpu-features": "~0.0.10",
|
||||
"nan": "^2.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ssh2-sftp-client": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-11.0.0.tgz",
|
||||
"integrity": "sha512-lOjgNYtioYquhtgyHwPryFNhllkuENjvCKkUXo18w/Q4UpEffCnEUBfiOTlwFdKIhG1rhrOGnA6DeKPSF2CP6w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"concat-stream": "^2.0.0",
|
||||
"promise-retry": "^2.0.1",
|
||||
"ssh2": "^1.15.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://square.link/u/4g7sPflL"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-trace": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||
|
|
@ -10907,6 +11019,12 @@
|
|||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
|
|
|
|||
|
|
@ -14,8 +14,12 @@
|
|||
"backfill:session-join-codes": "node scripts/backfill-session-join-codes.js",
|
||||
"backfill:join-codes": "node scripts/backfill-join-codes.js",
|
||||
"backfill:tenant-organization": "node scripts/backfill-tenant-organization.js",
|
||||
"seed:org-roles": "node scripts/seed-org-roles.js",
|
||||
"backfill:form-acl": "node scripts/backfill-form-acl.js",
|
||||
"backfill:form-submission-files": "node scripts/backfill-form-submission-files.js",
|
||||
"seed:microsite-v2-template": "node scripts/seed-microsite-v2-template.js",
|
||||
"seed:crest-novel-template": "node scripts/seed-crest-novel-template.js",
|
||||
"setup:crest-novel-microsite": "node scripts/setup-crest-novel-microsite.js",
|
||||
"email-worker": "node src/workers/emailQueueWorker.js",
|
||||
"whatsapp-worker": "node src/workers/whatsappQueueWorker.js",
|
||||
"concierge-ingest-worker": "node ../chatbot/workers/ingest.js",
|
||||
|
|
@ -52,6 +56,7 @@
|
|||
"qrcode": "^1.5.4",
|
||||
"razorpay": "^2.9.6",
|
||||
"sharp": "^0.33.5",
|
||||
"ssh2-sftp-client": "^11.0.0",
|
||||
"winston": "^3.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Backfill Form.createdBy and defaultVisibility for ACL migration.
|
||||
* Run: cd backend && npm run backfill:form-acl
|
||||
*/
|
||||
const path = require('path');
|
||||
|
||||
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
||||
const mongoose = require('mongoose');
|
||||
const Form = require(path.join(__dirname, '..', 'src', 'models', 'Form'));
|
||||
|
||||
async function run() {
|
||||
const uri = process.env.MONGODB_URI;
|
||||
if (!uri) {
|
||||
console.error('MONGODB_URI is not set');
|
||||
process.exit(1);
|
||||
}
|
||||
await mongoose.connect(uri);
|
||||
|
||||
const r = await Form.updateMany(
|
||||
{ defaultVisibility: { $exists: false } },
|
||||
{ $set: { defaultVisibility: 'org' } },
|
||||
);
|
||||
console.log(`Set defaultVisibility=org on ${r.modifiedCount} form(s).`);
|
||||
console.log('Forms without createdBy remain null (creator bypass skipped for those).');
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,434 @@
|
|||
/**
|
||||
* Deep-copy an event (structure only — no submissions, participants, or responses)
|
||||
* into another account's organization.
|
||||
*
|
||||
* Run: node scripts/copy-event-to-account.js
|
||||
*/
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
||||
const mongoose = require('mongoose');
|
||||
const Event = require('../src/models/Event');
|
||||
const Form = require('../src/models/Form');
|
||||
const Track = require('../src/models/Track');
|
||||
const Session = require('../src/models/Session');
|
||||
const Speaker = require('../src/models/Speaker');
|
||||
const EventPage = require('../src/models/EventPage');
|
||||
const EventMicrosite = require('../src/models/EventMicrosite');
|
||||
const EventStaticMicrosite = require('../src/models/EventStaticMicrosite');
|
||||
const EventTicket = require('../src/models/EventTicket');
|
||||
const QuizPoll = require('../src/models/QuizPoll');
|
||||
const ParticipantList = require('../src/models/ParticipantList');
|
||||
const KnowledgeSource = require('../src/models/KnowledgeSource');
|
||||
const ContextChunk = require('../src/models/ContextChunk');
|
||||
const User = require('../src/models/User');
|
||||
const OrganizationUserMap = require('../src/models/OrganizationUserMap');
|
||||
const { syncQuizPollToFirebase } = require('../src/utils/quizPollFirebaseSync');
|
||||
|
||||
const SOURCE_EVENT_ID = '6a43e05fdc940a0d6e057f7b';
|
||||
const TARGET_EMAIL = 'anil.patil@stratawiz.com';
|
||||
|
||||
const CREATOR_EVENT_ROLE = 'Organizer';
|
||||
|
||||
function oid(v) {
|
||||
return v != null ? String(v) : '';
|
||||
}
|
||||
|
||||
function stripDoc(doc) {
|
||||
const o = { ...doc };
|
||||
delete o._id;
|
||||
delete o.__v;
|
||||
delete o.createdAt;
|
||||
delete o.updatedAt;
|
||||
return o;
|
||||
}
|
||||
|
||||
function buildIdMap() {
|
||||
const map = new Map();
|
||||
return {
|
||||
set(oldId, newId) {
|
||||
if (oldId != null && newId != null) map.set(oid(oldId), oid(newId));
|
||||
},
|
||||
get(oldId) {
|
||||
return map.get(oid(oldId));
|
||||
},
|
||||
has(oldId) {
|
||||
return map.has(oid(oldId));
|
||||
},
|
||||
remap(value) {
|
||||
if (value == null) return value;
|
||||
const s = oid(value);
|
||||
if (!mongoose.Types.ObjectId.isValid(s)) return value;
|
||||
const mapped = map.get(s);
|
||||
if (!mapped) return value;
|
||||
return mongoose.Types.ObjectId.isValid(mapped) ? new mongoose.Types.ObjectId(mapped) : mapped;
|
||||
},
|
||||
remapString(value) {
|
||||
if (value == null) return value;
|
||||
const s = String(value).trim();
|
||||
if (!mongoose.Types.ObjectId.isValid(s)) return value;
|
||||
return map.get(s) || value;
|
||||
},
|
||||
deep(obj) {
|
||||
if (obj == null) return obj;
|
||||
if (Array.isArray(obj)) return obj.map((item) => this.deep(item));
|
||||
if (obj instanceof mongoose.Types.ObjectId) return this.remap(obj);
|
||||
if (obj instanceof Date) return obj;
|
||||
if (typeof obj === 'string' && mongoose.Types.ObjectId.isValid(obj) && map.has(obj)) {
|
||||
return map.get(obj);
|
||||
}
|
||||
if (typeof obj === 'object') {
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(obj)) out[k] = this.deep(v);
|
||||
return out;
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function uniqueValue(base, exists) {
|
||||
const normalized = String(base || 'item')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9-]+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '');
|
||||
let candidate = normalized || 'copy';
|
||||
let i = 0;
|
||||
while (await exists(candidate)) {
|
||||
i += 1;
|
||||
candidate = `${normalized || 'copy'}-copy${i > 1 ? `-${i}` : ''}`;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
async function assignCreatorToEventStaff(creatorUserId, orgId, eventId) {
|
||||
if (!creatorUserId || !orgId || !eventId) return;
|
||||
const target = await User.findById(creatorUserId).select('organization role').lean();
|
||||
if (!target?.organization || oid(target.organization) !== oid(orgId)) return;
|
||||
|
||||
let map = await OrganizationUserMap.findOne({ user: creatorUserId, organization: orgId });
|
||||
if (!map) {
|
||||
map = await OrganizationUserMap.create({
|
||||
user: creatorUserId,
|
||||
organization: orgId,
|
||||
role: target.role || 'viewer',
|
||||
eventAccess: [],
|
||||
});
|
||||
}
|
||||
const eid = oid(eventId);
|
||||
if (map.eventAccess.some((e) => oid(e.event) === eid)) return;
|
||||
map.eventAccess.push({ event: eventId, sessionIds: [], eventRole: CREATOR_EVENT_ROLE });
|
||||
await map.save();
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const uri = process.env.MONGODB_URI;
|
||||
if (!uri) {
|
||||
console.error('MONGODB_URI is not set');
|
||||
process.exit(1);
|
||||
}
|
||||
await mongoose.connect(uri);
|
||||
|
||||
const sourceEvent = await Event.findById(SOURCE_EVENT_ID).lean();
|
||||
if (!sourceEvent) {
|
||||
console.error('Source event not found:', SOURCE_EVENT_ID);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const targetUser = await User.findOne({ email: TARGET_EMAIL }).lean();
|
||||
if (!targetUser) {
|
||||
console.error('Target user not found:', TARGET_EMAIL);
|
||||
process.exit(1);
|
||||
}
|
||||
const targetOrgId = targetUser.organization;
|
||||
if (!targetOrgId) {
|
||||
console.error('Target user has no organization');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const idMap = buildIdMap();
|
||||
const summary = {
|
||||
sourceEventId: SOURCE_EVENT_ID,
|
||||
sourceEventName: sourceEvent.name,
|
||||
targetEmail: TARGET_EMAIL,
|
||||
targetOrganizationId: oid(targetOrgId),
|
||||
copied: {},
|
||||
};
|
||||
|
||||
// --- Event shell ---
|
||||
const eventPayload = stripDoc(sourceEvent);
|
||||
eventPayload.organization = targetOrgId;
|
||||
eventPayload.status = 'draft';
|
||||
delete eventPayload.joinCode;
|
||||
if (eventPayload.metadata) {
|
||||
eventPayload.metadata = {
|
||||
...eventPayload.metadata,
|
||||
totalRevenue: 0,
|
||||
rating: 0,
|
||||
ratingCount: 0,
|
||||
};
|
||||
}
|
||||
if (eventPayload.ticketingManualPayment) {
|
||||
eventPayload.ticketingManualPayment = {
|
||||
...eventPayload.ticketingManualPayment,
|
||||
enablementTransaction: null,
|
||||
enablementFeePaidAt: eventPayload.ticketingManualPayment.enablementFeePaidAt || null,
|
||||
};
|
||||
}
|
||||
if (eventPayload.website?.folder) {
|
||||
eventPayload.website.folder = await uniqueValue(eventPayload.website.folder, async (slug) =>
|
||||
Boolean(await Event.findOne({ 'website.folder': slug }).select('_id').lean()),
|
||||
);
|
||||
}
|
||||
|
||||
const newEvent = await Event.create(eventPayload);
|
||||
summary.copied.eventId = oid(newEvent._id);
|
||||
|
||||
// --- Participant lists referenced by forms / tickets ---
|
||||
const sourceForms = await Form.find({ event: SOURCE_EVENT_ID }).lean();
|
||||
const sourceTickets = await EventTicket.find({ event: SOURCE_EVENT_ID }).lean();
|
||||
const listIds = new Set();
|
||||
for (const f of sourceForms) {
|
||||
const lid = f.settings?.onSubmitParticipantListId;
|
||||
if (lid) listIds.add(oid(lid));
|
||||
}
|
||||
for (const t of sourceTickets) {
|
||||
if (t.participantList) listIds.add(oid(t.participantList));
|
||||
}
|
||||
for (const listId of listIds) {
|
||||
const list = await ParticipantList.findById(listId).lean();
|
||||
if (!list) continue;
|
||||
const payload = stripDoc(list);
|
||||
payload.tenantOrganization = targetOrgId;
|
||||
payload.createdBy = targetUser._id;
|
||||
payload.participantIds = [];
|
||||
const created = await ParticipantList.create(payload);
|
||||
idMap.set(listId, created._id);
|
||||
}
|
||||
summary.copied.participantLists = listIds.size;
|
||||
|
||||
// --- Forms (no submissions) ---
|
||||
for (const form of sourceForms) {
|
||||
const payload = stripDoc(form);
|
||||
payload.tenantOrganization = targetOrgId;
|
||||
payload.event = newEvent._id;
|
||||
if (payload.settings?.onSubmitParticipantListId) {
|
||||
payload.settings.onSubmitParticipantListId = idMap.remap(payload.settings.onSubmitParticipantListId);
|
||||
}
|
||||
const created = await Form.create(payload);
|
||||
idMap.set(form._id, created._id);
|
||||
}
|
||||
summary.copied.forms = sourceForms.length;
|
||||
|
||||
// --- Speakers referenced by sessions ---
|
||||
const sourceSessions = await Session.find({ event: SOURCE_EVENT_ID }).lean();
|
||||
const speakerIds = new Set();
|
||||
for (const s of sourceSessions) {
|
||||
for (const sp of s.speakers || []) speakerIds.add(oid(sp));
|
||||
if (s.moderator) speakerIds.add(oid(s.moderator));
|
||||
}
|
||||
for (const speakerId of speakerIds) {
|
||||
const speaker = await Speaker.findById(speakerId).lean();
|
||||
if (!speaker) continue;
|
||||
const existing = await Speaker.findOne({ organization: targetOrgId, email: speaker.email }).lean();
|
||||
if (existing) {
|
||||
idMap.set(speakerId, existing._id);
|
||||
continue;
|
||||
}
|
||||
const payload = stripDoc(speaker);
|
||||
payload.organization = targetOrgId;
|
||||
payload.sessions = [];
|
||||
const created = await Speaker.create(payload);
|
||||
idMap.set(speakerId, created._id);
|
||||
}
|
||||
summary.copied.speakers = speakerIds.size;
|
||||
|
||||
// --- Tracks ---
|
||||
const sourceTracks = await Track.find({ event: SOURCE_EVENT_ID }).sort({ sortOrder: 1, createdAt: 1 }).lean();
|
||||
for (const track of sourceTracks) {
|
||||
const payload = stripDoc(track);
|
||||
payload.event = newEvent._id;
|
||||
delete payload.joinCode;
|
||||
const created = await Track.create(payload);
|
||||
idMap.set(track._id, created._id);
|
||||
}
|
||||
summary.copied.tracks = sourceTracks.length;
|
||||
|
||||
// --- Sessions ---
|
||||
for (const session of sourceSessions) {
|
||||
const payload = stripDoc(session);
|
||||
payload.event = newEvent._id;
|
||||
payload.track = idMap.remap(session.track);
|
||||
payload.speakers = (session.speakers || []).map((sp) => idMap.remap(sp)).filter(Boolean);
|
||||
payload.moderator = idMap.remap(session.moderator);
|
||||
payload.audienceFeedbackForm = idMap.remap(session.audienceFeedbackForm);
|
||||
payload.audienceRatingsForm = idMap.remap(session.audienceRatingsForm);
|
||||
delete payload.joinCode;
|
||||
const created = await Session.create(payload);
|
||||
idMap.set(session._id, created._id);
|
||||
}
|
||||
summary.copied.sessions = sourceSessions.length;
|
||||
|
||||
// Update speaker.session back-references
|
||||
for (const speakerId of speakerIds) {
|
||||
const newSpeakerId = idMap.get(speakerId);
|
||||
if (!newSpeakerId) continue;
|
||||
const sessionRefs = sourceSessions
|
||||
.filter((s) => (s.speakers || []).some((sp) => oid(sp) === speakerId) || oid(s.moderator) === speakerId)
|
||||
.map((s) => idMap.get(s._id))
|
||||
.filter(Boolean);
|
||||
if (sessionRefs.length) {
|
||||
await Speaker.findByIdAndUpdate(newSpeakerId, { $addToSet: { sessions: { $each: sessionRefs } } });
|
||||
}
|
||||
}
|
||||
|
||||
// --- Quiz / poll definitions (no responses) ---
|
||||
const sourceQuizzes = await QuizPoll.find({ event: SOURCE_EVENT_ID, deletedAt: null }).lean();
|
||||
for (const quiz of sourceQuizzes) {
|
||||
const payload = stripDoc(quiz);
|
||||
payload.event = newEvent._id;
|
||||
payload.session = idMap.remap(quiz.session);
|
||||
payload.settings = {
|
||||
...(payload.settings || {}),
|
||||
presentationToken: QuizPoll.generatePresentationToken(),
|
||||
isLive: false,
|
||||
liveQuestionId: null,
|
||||
};
|
||||
const created = await QuizPoll.create(payload);
|
||||
try {
|
||||
await syncQuizPollToFirebase(created);
|
||||
} catch (err) {
|
||||
console.warn('Firebase sync skipped for quiz', oid(created._id), err.message);
|
||||
}
|
||||
idMap.set(quiz._id, created._id);
|
||||
}
|
||||
summary.copied.quizzes = sourceQuizzes.length;
|
||||
|
||||
// --- Event pages ---
|
||||
const sourcePages = await EventPage.find({ event: SOURCE_EVENT_ID }).sort({ 'settings.menuOrder': 1 }).lean();
|
||||
for (const page of sourcePages) {
|
||||
const payload = stripDoc(page);
|
||||
payload.event = newEvent._id;
|
||||
payload.createdBy = targetUser._id;
|
||||
payload.slug = await uniqueValue(`${page.slug}-${oid(newEvent._id).slice(-6)}`, async (slug) =>
|
||||
Boolean(await EventPage.findOne({ slug }).select('_id').lean()),
|
||||
);
|
||||
payload.sections = idMap.deep(payload.sections || []);
|
||||
if (payload.content) payload.content = idMap.deep(payload.content);
|
||||
const created = await EventPage.create(payload);
|
||||
idMap.set(page._id, created._id);
|
||||
}
|
||||
summary.copied.eventPages = sourcePages.length;
|
||||
|
||||
// --- Event microsites ---
|
||||
const sourceMicrosites = await EventMicrosite.find({ event: SOURCE_EVENT_ID }).sort({ createdAt: 1 }).lean();
|
||||
for (const ms of sourceMicrosites) {
|
||||
const payload = stripDoc(ms);
|
||||
payload.event = newEvent._id;
|
||||
payload.createdBy = targetUser._id;
|
||||
payload.registrationFormId = idMap.remap(ms.registrationFormId);
|
||||
payload.sourcePageId = idMap.remap(ms.sourcePageId);
|
||||
if (payload.publishFolder) {
|
||||
payload.publishFolder = await uniqueValue(payload.publishFolder, async (slug) =>
|
||||
Boolean(await EventMicrosite.findOne({ publishFolder: slug }).select('_id').lean()),
|
||||
);
|
||||
}
|
||||
const created = await EventMicrosite.create(payload);
|
||||
idMap.set(ms._id, created._id);
|
||||
}
|
||||
summary.copied.eventMicrosites = sourceMicrosites.length;
|
||||
|
||||
// --- Static microsite v2 ---
|
||||
const sourceStatic = await EventStaticMicrosite.findOne({ event: SOURCE_EVENT_ID }).lean();
|
||||
if (sourceStatic) {
|
||||
const payload = stripDoc(sourceStatic);
|
||||
payload.event = newEvent._id;
|
||||
payload.organization = targetOrgId;
|
||||
payload.subdomain = await uniqueValue(sourceStatic.subdomain, async (sub) =>
|
||||
Boolean(await EventStaticMicrosite.findOne({ subdomain: sub }).select('_id').lean()),
|
||||
);
|
||||
payload.publishedHtmlUrl = null;
|
||||
payload.publishedHtmlS3Key = null;
|
||||
payload.publishedConfigS3Key = null;
|
||||
payload.publishedHtmlPath = null;
|
||||
payload.publishedConfigPath = null;
|
||||
payload.publishedAt = null;
|
||||
payload.publishedBy = null;
|
||||
payload.status = 'draft';
|
||||
payload.updatedBy = targetUser._id;
|
||||
payload.config = idMap.deep(payload.config || {});
|
||||
await EventStaticMicrosite.create(payload);
|
||||
summary.copied.staticMicrosite = payload.subdomain;
|
||||
}
|
||||
|
||||
// --- Tickets ---
|
||||
for (const ticket of sourceTickets) {
|
||||
const payload = stripDoc(ticket);
|
||||
payload.event = newEvent._id;
|
||||
payload.organization = targetOrgId;
|
||||
payload.registrationForm = idMap.remap(ticket.registrationForm);
|
||||
payload.participantList = idMap.remap(ticket.participantList);
|
||||
await EventTicket.create(payload);
|
||||
}
|
||||
summary.copied.tickets = sourceTickets.length;
|
||||
|
||||
// --- Knowledge sources + chunks (concierge) ---
|
||||
const sourceKnowledge = await KnowledgeSource.find({ eventId: SOURCE_EVENT_ID }).lean();
|
||||
for (const ks of sourceKnowledge) {
|
||||
const payload = stripDoc(ks);
|
||||
payload.eventId = newEvent._id;
|
||||
const createdKs = await KnowledgeSource.create(payload);
|
||||
idMap.set(ks._id, createdKs._id);
|
||||
|
||||
const chunks = await ContextChunk.find({ sourceId: ks._id }).lean();
|
||||
if (chunks.length) {
|
||||
await ContextChunk.insertMany(
|
||||
chunks.map((chunk) => {
|
||||
const c = stripDoc(chunk);
|
||||
c.eventId = newEvent._id;
|
||||
c.sourceId = createdKs._id;
|
||||
return c;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
summary.copied.knowledgeSources = sourceKnowledge.length;
|
||||
|
||||
// --- Remap website registration form on event ---
|
||||
if (newEvent.website?.registrationFormId) {
|
||||
newEvent.website.registrationFormId = idMap.remapString(newEvent.website.registrationFormId);
|
||||
newEvent.markModified('website');
|
||||
await newEvent.save();
|
||||
} else {
|
||||
const regForm = sourceForms.find((f) => f.type === 'registration');
|
||||
if (regForm && idMap.get(regForm._id)) {
|
||||
newEvent.website = newEvent.website || {};
|
||||
newEvent.website.registrationFormId = idMap.get(regForm._id);
|
||||
newEvent.markModified('website');
|
||||
await newEvent.save();
|
||||
}
|
||||
}
|
||||
|
||||
await assignCreatorToEventStaff(targetUser._id, targetOrgId, newEvent._id);
|
||||
|
||||
summary.newEventId = oid(newEvent._id);
|
||||
summary.newEventName = newEvent.name;
|
||||
summary.skipped = {
|
||||
formSubmissions: true,
|
||||
quizResponses: true,
|
||||
participants: true,
|
||||
attendance: true,
|
||||
payments: true,
|
||||
};
|
||||
|
||||
console.log('Event copied successfully');
|
||||
console.log(JSON.stringify(summary, null, 2));
|
||||
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,542 @@
|
|||
/**
|
||||
* Copy an event within the same organization with overrides (name, dates, venue).
|
||||
* Copies forms, agenda, microsite config, tickets, pages, etc. — no submissions/responses.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/copy-event-variant.js
|
||||
*/
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
||||
const mongoose = require('mongoose');
|
||||
const Event = require('../src/models/Event');
|
||||
const Form = require('../src/models/Form');
|
||||
const Track = require('../src/models/Track');
|
||||
const Session = require('../src/models/Session');
|
||||
const Speaker = require('../src/models/Speaker');
|
||||
const EventPage = require('../src/models/EventPage');
|
||||
const EventMicrosite = require('../src/models/EventMicrosite');
|
||||
const EventStaticMicrosite = require('../src/models/EventStaticMicrosite');
|
||||
const EventTicket = require('../src/models/EventTicket');
|
||||
const QuizPoll = require('../src/models/QuizPoll');
|
||||
const ParticipantList = require('../src/models/ParticipantList');
|
||||
const KnowledgeSource = require('../src/models/KnowledgeSource');
|
||||
const ContextChunk = require('../src/models/ContextChunk');
|
||||
const OrganizationUserMap = require('../src/models/OrganizationUserMap');
|
||||
const { syncQuizPollToFirebase } = require('../src/utils/quizPollFirebaseSync');
|
||||
|
||||
const SOURCE_EVENT_ID = '6a4cb75a7475c040cc6f2dd7';
|
||||
const PUBLIC_WEB_BASE = (process.env.PUBLIC_WEB_BASE || 'https://web.eventbuzz.live').replace(/\/$/, '');
|
||||
|
||||
const TARGETS = [
|
||||
{
|
||||
name: 'NOVEL Lung Health Summit - Kochi - 22nd & 23rd August 2026',
|
||||
startDate: new Date('2026-08-22T12:00:00.000Z'),
|
||||
endDate: new Date('2026-08-23T12:00:00.000Z'),
|
||||
location: 'Marriott, Kochi',
|
||||
subdomain: 'novel2026-kochi-22-23-aug',
|
||||
shortPlace: 'Kochi',
|
||||
dateLabel: '22nd & 23rd August 2026',
|
||||
dateShort: 'Aug 22–23, 2026',
|
||||
countdownDate: '2026-08-22',
|
||||
scheduleDay1: 'August 22, 2026',
|
||||
scheduleDay2: 'August 23, 2026',
|
||||
mapQuery: 'Marriott+Hotel+Kochi',
|
||||
sourceSessionBase: new Date('2026-08-08T00:00:00.000Z'),
|
||||
sessionBase: new Date('2026-08-22T00:00:00.000Z'),
|
||||
},
|
||||
{
|
||||
name: 'NOVEL Lung Health Summit - Dehradun - 19th & 20th September 2026',
|
||||
startDate: new Date('2026-09-19T12:00:00.000Z'),
|
||||
endDate: new Date('2026-09-20T12:00:00.000Z'),
|
||||
location: 'Hyatt Regency, Dehradun',
|
||||
subdomain: 'novel2026-dehradun-19-20-sep',
|
||||
shortPlace: 'Dehradun',
|
||||
dateLabel: '19th & 20th September 2026',
|
||||
dateShort: 'Sep 19–20, 2026',
|
||||
countdownDate: '2026-09-19',
|
||||
scheduleDay1: 'September 19, 2026',
|
||||
scheduleDay2: 'September 20, 2026',
|
||||
mapQuery: 'Hyatt+Regency+Dehradun',
|
||||
sourceSessionBase: new Date('2026-08-08T00:00:00.000Z'),
|
||||
sessionBase: new Date('2026-09-19T00:00:00.000Z'),
|
||||
},
|
||||
];
|
||||
|
||||
function oid(v) {
|
||||
return v != null ? String(v) : '';
|
||||
}
|
||||
|
||||
function stripDoc(doc) {
|
||||
const o = { ...doc };
|
||||
delete o._id;
|
||||
delete o.__v;
|
||||
delete o.createdAt;
|
||||
delete o.updatedAt;
|
||||
return o;
|
||||
}
|
||||
|
||||
function buildIdMap() {
|
||||
const map = new Map();
|
||||
return {
|
||||
set(oldId, newId) {
|
||||
if (oldId != null && newId != null) map.set(oid(oldId), oid(newId));
|
||||
},
|
||||
get(oldId) {
|
||||
return map.get(oid(oldId));
|
||||
},
|
||||
remap(value) {
|
||||
if (value == null) return value;
|
||||
const s = oid(value);
|
||||
if (!mongoose.Types.ObjectId.isValid(s)) return value;
|
||||
const mapped = map.get(s);
|
||||
if (!mapped) return value;
|
||||
return mongoose.Types.ObjectId.isValid(mapped) ? new mongoose.Types.ObjectId(mapped) : mapped;
|
||||
},
|
||||
remapString(value) {
|
||||
if (value == null) return value;
|
||||
const s = String(value).trim();
|
||||
if (!mongoose.Types.ObjectId.isValid(s)) return value;
|
||||
return map.get(s) || value;
|
||||
},
|
||||
deep(obj) {
|
||||
if (obj == null) return obj;
|
||||
if (Array.isArray(obj)) return obj.map((item) => this.deep(item));
|
||||
if (obj instanceof mongoose.Types.ObjectId) return this.remap(obj);
|
||||
if (obj instanceof Date) return obj;
|
||||
if (typeof obj === 'string' && mongoose.Types.ObjectId.isValid(obj) && map.has(obj)) {
|
||||
return map.get(obj);
|
||||
}
|
||||
if (typeof obj === 'object') {
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(obj)) out[k] = this.deep(v);
|
||||
return out;
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function uniqueValue(base, exists) {
|
||||
const normalized = String(base || 'item')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9-]+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '');
|
||||
let candidate = normalized || 'copy';
|
||||
let i = 0;
|
||||
while (await exists(candidate)) {
|
||||
i += 1;
|
||||
candidate = `${normalized || 'copy'}-copy${i > 1 ? `-${i}` : ''}`;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
function shiftSessionTime(date, sourceBase, targetBase) {
|
||||
if (!date) return date;
|
||||
const d = new Date(date);
|
||||
const offsetMs = targetBase.getTime() - sourceBase.getTime();
|
||||
return new Date(d.getTime() + offsetMs);
|
||||
}
|
||||
|
||||
function replaceStringsDeep(obj, replacements) {
|
||||
if (obj == null) return obj;
|
||||
if (typeof obj === 'string') {
|
||||
let out = obj;
|
||||
for (const [from, to] of replacements) {
|
||||
if (from) out = out.split(from).join(to);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (Array.isArray(obj)) return obj.map((item) => replaceStringsDeep(item, replacements));
|
||||
if (typeof obj === 'object') {
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(obj)) out[k] = replaceStringsDeep(v, replacements);
|
||||
return out;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function applyMicrositeOverrides(config, target, newEventId, idMap) {
|
||||
let next = idMap.deep(config || {});
|
||||
next.meta = next.meta || {};
|
||||
next.meta.title = target.name;
|
||||
next.meta.eventId = String(newEventId);
|
||||
|
||||
const replacements = [
|
||||
[SOURCE_EVENT_ID, String(newEventId)],
|
||||
['6a43e05fdc940a0d6e057f7b', String(newEventId)],
|
||||
['NOVEL Lung Health Summit - Goa - 7th & 8th August 2026', target.name],
|
||||
['NOVEL Lung Health Summit - Goa - 8th & 9th August 2026', target.name],
|
||||
['Goa - 7th & 8th August 2026', `${target.shortPlace} - ${target.dateLabel}`],
|
||||
['Goa - 8th & 9th August 2026', `${target.shortPlace} - ${target.dateLabel}`],
|
||||
['DoubleTree by Hilton Panjim, Goa', target.location],
|
||||
['Aug 7–8, 2026', target.dateShort],
|
||||
['Aug 8–9, 2026', target.dateShort],
|
||||
['2026-08-07', target.countdownDate],
|
||||
['2026-08-08', target.countdownDate === '2026-08-22' ? '2026-08-23' : '2026-09-20'],
|
||||
['August 7, 2026', target.scheduleDay1],
|
||||
['August 8, 2026', target.scheduleDay2],
|
||||
[
|
||||
'DoubleTree+by+Hilton+Goa+Panaji,+Chimbel,+Goa',
|
||||
target.mapQuery,
|
||||
],
|
||||
];
|
||||
|
||||
next = replaceStringsDeep(next, replacements);
|
||||
|
||||
const sections = next.sections || {};
|
||||
if (sections.hero) {
|
||||
sections.hero.date_venue = target.location;
|
||||
sections.hero.event_name = `${target.shortPlace} - ${target.dateLabel}`;
|
||||
if (sections.hero.countdown) {
|
||||
sections.hero.countdown.date = target.countdownDate;
|
||||
}
|
||||
}
|
||||
if (sections.informationBar?.items) {
|
||||
for (const item of sections.informationBar.items) {
|
||||
if (item.label === 'Location') item.value = target.location;
|
||||
if (item.label === 'Date & Time') {
|
||||
item.value = target.dateShort;
|
||||
if (item.calendar) {
|
||||
item.calendar.title = target.name;
|
||||
item.calendar.start = target.countdownDate;
|
||||
item.calendar.end =
|
||||
target.countdownDate === '2026-08-22' ? '2026-08-23' : '2026-09-20';
|
||||
item.calendar.location = target.location;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sections.map) {
|
||||
sections.map.embedUrl = `https://maps.google.com/maps?q=${target.mapQuery}&hl=en&z=15&output=embed`;
|
||||
}
|
||||
if (sections.event_schedule?.days) {
|
||||
const days = sections.event_schedule.days;
|
||||
if (days[0]) days[0].date = target.scheduleDay1;
|
||||
if (days[1]) days[1].date = target.scheduleDay2;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
async function copyEventAccess(sourceEventId, newEventId, orgId) {
|
||||
const maps = await OrganizationUserMap.find({ organization: orgId }).lean();
|
||||
let copied = 0;
|
||||
for (const map of maps) {
|
||||
const access = (map.eventAccess || []).find((e) => oid(e.event) === oid(sourceEventId));
|
||||
if (!access) continue;
|
||||
const doc = await OrganizationUserMap.findById(map._id);
|
||||
if (!doc) continue;
|
||||
const exists = doc.eventAccess.some((e) => oid(e.event) === oid(newEventId));
|
||||
if (exists) continue;
|
||||
doc.eventAccess.push({
|
||||
event: newEventId,
|
||||
sessionIds: [],
|
||||
eventRole: access.eventRole || 'Organizer',
|
||||
});
|
||||
await doc.save();
|
||||
copied += 1;
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
async function copyEventVariant(sourceEvent, target, summaryRoot) {
|
||||
const orgId = sourceEvent.organization;
|
||||
const idMap = buildIdMap();
|
||||
const summary = { ...summaryRoot, copied: {} };
|
||||
|
||||
const eventPayload = stripDoc(sourceEvent);
|
||||
eventPayload.organization = orgId;
|
||||
eventPayload.name = target.name;
|
||||
eventPayload.startDate = target.startDate;
|
||||
eventPayload.endDate = target.endDate;
|
||||
eventPayload.location = target.location;
|
||||
eventPayload.status = sourceEvent.status || 'draft';
|
||||
delete eventPayload.joinCode;
|
||||
if (eventPayload.metadata) {
|
||||
eventPayload.metadata = {
|
||||
...eventPayload.metadata,
|
||||
totalRevenue: 0,
|
||||
rating: 0,
|
||||
ratingCount: 0,
|
||||
};
|
||||
}
|
||||
if (eventPayload.ticketingManualPayment) {
|
||||
eventPayload.ticketingManualPayment = {
|
||||
...eventPayload.ticketingManualPayment,
|
||||
enablementTransaction: null,
|
||||
};
|
||||
}
|
||||
if (eventPayload.website?.folder) {
|
||||
eventPayload.website.folder = await uniqueValue(
|
||||
`${eventPayload.website.folder}-${target.shortPlace.toLowerCase()}`,
|
||||
async (slug) => Boolean(await Event.findOne({ 'website.folder': slug }).select('_id').lean()),
|
||||
);
|
||||
}
|
||||
|
||||
const newEvent = await Event.create(eventPayload);
|
||||
summary.copied.eventId = oid(newEvent._id);
|
||||
|
||||
const sourceForms = await Form.find({ event: SOURCE_EVENT_ID }).lean();
|
||||
const sourceTickets = await EventTicket.find({ event: SOURCE_EVENT_ID }).lean();
|
||||
const listIds = new Set();
|
||||
for (const f of sourceForms) {
|
||||
const lid = f.settings?.onSubmitParticipantListId;
|
||||
if (lid) listIds.add(oid(lid));
|
||||
}
|
||||
for (const t of sourceTickets) {
|
||||
if (t.participantList) listIds.add(oid(t.participantList));
|
||||
}
|
||||
for (const listId of listIds) {
|
||||
const list = await ParticipantList.findById(listId).lean();
|
||||
if (!list) continue;
|
||||
const payload = stripDoc(list);
|
||||
payload.tenantOrganization = orgId;
|
||||
payload.participantIds = [];
|
||||
const created = await ParticipantList.create(payload);
|
||||
idMap.set(listId, created._id);
|
||||
}
|
||||
summary.copied.participantLists = listIds.size;
|
||||
|
||||
for (const form of sourceForms) {
|
||||
const payload = stripDoc(form);
|
||||
payload.tenantOrganization = orgId;
|
||||
payload.event = newEvent._id;
|
||||
if (payload.settings?.onSubmitParticipantListId) {
|
||||
payload.settings.onSubmitParticipantListId = idMap.remap(payload.settings.onSubmitParticipantListId);
|
||||
}
|
||||
const created = await Form.create(payload);
|
||||
idMap.set(form._id, created._id);
|
||||
}
|
||||
summary.copied.forms = sourceForms.length;
|
||||
|
||||
const sourceSessions = await Session.find({ event: SOURCE_EVENT_ID }).lean();
|
||||
const speakerIds = new Set();
|
||||
for (const s of sourceSessions) {
|
||||
for (const sp of s.speakers || []) speakerIds.add(oid(sp));
|
||||
if (s.moderator) speakerIds.add(oid(s.moderator));
|
||||
}
|
||||
for (const speakerId of speakerIds) {
|
||||
idMap.set(speakerId, speakerId);
|
||||
}
|
||||
summary.copied.speakers = speakerIds.size;
|
||||
|
||||
const sourceTracks = await Track.find({ event: SOURCE_EVENT_ID }).sort({ sortOrder: 1, createdAt: 1 }).lean();
|
||||
for (const track of sourceTracks) {
|
||||
const payload = stripDoc(track);
|
||||
payload.event = newEvent._id;
|
||||
delete payload.joinCode;
|
||||
const created = await Track.create(payload);
|
||||
idMap.set(track._id, created._id);
|
||||
}
|
||||
summary.copied.tracks = sourceTracks.length;
|
||||
|
||||
for (const session of sourceSessions) {
|
||||
const payload = stripDoc(session);
|
||||
payload.event = newEvent._id;
|
||||
payload.track = idMap.remap(session.track);
|
||||
payload.speakers = (session.speakers || []).map((sp) => idMap.remap(sp)).filter(Boolean);
|
||||
payload.moderator = idMap.remap(session.moderator);
|
||||
payload.audienceFeedbackForm = idMap.remap(session.audienceFeedbackForm);
|
||||
payload.audienceRatingsForm = idMap.remap(session.audienceRatingsForm);
|
||||
payload.startTime = shiftSessionTime(session.startTime, target.sourceSessionBase, target.sessionBase);
|
||||
payload.endTime = shiftSessionTime(session.endTime, target.sourceSessionBase, target.sessionBase);
|
||||
delete payload.joinCode;
|
||||
const created = await Session.create(payload);
|
||||
idMap.set(session._id, created._id);
|
||||
}
|
||||
summary.copied.sessions = sourceSessions.length;
|
||||
|
||||
for (const speakerId of speakerIds) {
|
||||
const sessionRefs = sourceSessions
|
||||
.filter((s) => (s.speakers || []).some((sp) => oid(sp) === speakerId) || oid(s.moderator) === speakerId)
|
||||
.map((s) => idMap.get(s._id))
|
||||
.filter(Boolean);
|
||||
if (sessionRefs.length) {
|
||||
await Speaker.findByIdAndUpdate(speakerId, { $addToSet: { sessions: { $each: sessionRefs } } });
|
||||
}
|
||||
}
|
||||
|
||||
const sourceQuizzes = await QuizPoll.find({ event: SOURCE_EVENT_ID, deletedAt: null }).lean();
|
||||
for (const quiz of sourceQuizzes) {
|
||||
const payload = stripDoc(quiz);
|
||||
payload.event = newEvent._id;
|
||||
payload.session = idMap.remap(quiz.session);
|
||||
payload.settings = {
|
||||
...(payload.settings || {}),
|
||||
presentationToken: QuizPoll.generatePresentationToken(),
|
||||
isLive: false,
|
||||
liveQuestionId: null,
|
||||
};
|
||||
const created = await QuizPoll.create(payload);
|
||||
try {
|
||||
await syncQuizPollToFirebase(created);
|
||||
} catch (err) {
|
||||
console.warn('Firebase sync skipped for quiz', oid(created._id), err.message);
|
||||
}
|
||||
idMap.set(quiz._id, created._id);
|
||||
}
|
||||
summary.copied.quizzes = sourceQuizzes.length;
|
||||
|
||||
const sourcePages = await EventPage.find({ event: SOURCE_EVENT_ID }).sort({ 'settings.menuOrder': 1 }).lean();
|
||||
for (const page of sourcePages) {
|
||||
const payload = stripDoc(page);
|
||||
payload.event = newEvent._id;
|
||||
payload.slug = await uniqueValue(`${page.slug}-${target.shortPlace.toLowerCase()}`, async (slug) =>
|
||||
Boolean(await EventPage.findOne({ slug }).select('_id').lean()),
|
||||
);
|
||||
payload.sections = idMap.deep(payload.sections || []);
|
||||
if (payload.content) payload.content = idMap.deep(payload.content);
|
||||
const created = await EventPage.create(payload);
|
||||
idMap.set(page._id, created._id);
|
||||
}
|
||||
summary.copied.eventPages = sourcePages.length;
|
||||
|
||||
const sourceMicrosites = await EventMicrosite.find({ event: SOURCE_EVENT_ID }).sort({ createdAt: 1 }).lean();
|
||||
for (const ms of sourceMicrosites) {
|
||||
const payload = stripDoc(ms);
|
||||
payload.event = newEvent._id;
|
||||
payload.registrationFormId = idMap.remap(ms.registrationFormId);
|
||||
payload.sourcePageId = idMap.remap(ms.sourcePageId);
|
||||
if (payload.publishFolder) {
|
||||
payload.publishFolder = await uniqueValue(`${payload.publishFolder}-${target.shortPlace.toLowerCase()}`, async (slug) =>
|
||||
Boolean(await EventMicrosite.findOne({ publishFolder: slug }).select('_id').lean()),
|
||||
);
|
||||
}
|
||||
const created = await EventMicrosite.create(payload);
|
||||
idMap.set(ms._id, created._id);
|
||||
}
|
||||
summary.copied.eventMicrosites = sourceMicrosites.length;
|
||||
|
||||
const sourceStatic = await EventStaticMicrosite.findOne({ event: SOURCE_EVENT_ID }).lean();
|
||||
if (sourceStatic) {
|
||||
const payload = stripDoc(sourceStatic);
|
||||
payload.event = newEvent._id;
|
||||
payload.organization = orgId;
|
||||
payload.subdomain = await uniqueValue(target.subdomain, async (sub) =>
|
||||
Boolean(await EventStaticMicrosite.findOne({ subdomain: sub }).select('_id').lean()),
|
||||
);
|
||||
payload.publishedHtmlUrl = null;
|
||||
payload.publishedHtmlS3Key = null;
|
||||
payload.publishedConfigS3Key = null;
|
||||
payload.publishedHtmlPath = null;
|
||||
payload.publishedConfigPath = null;
|
||||
payload.publishedAt = null;
|
||||
payload.publishedBy = null;
|
||||
payload.status = 'draft';
|
||||
payload.config = applyMicrositeOverrides(
|
||||
sourceStatic.config,
|
||||
target,
|
||||
newEvent._id,
|
||||
idMap,
|
||||
);
|
||||
const regForm = sourceForms.find((f) => f.type === 'registration');
|
||||
const feedbackForm = sourceForms.find((f) => f.type === 'feedback');
|
||||
const newRegId = regForm ? idMap.get(regForm._id) : null;
|
||||
const newFeedbackId = feedbackForm ? idMap.get(feedbackForm._id) : null;
|
||||
if (newRegId && payload.config?.sections?.hero?.cta) {
|
||||
payload.config.sections.hero.cta.url = `${PUBLIC_WEB_BASE}/event/${newEvent._id}/form/${newRegId}`;
|
||||
}
|
||||
if (newFeedbackId && payload.config?.sections?.feedback) {
|
||||
payload.config.sections.feedback.url = `${PUBLIC_WEB_BASE}/event/${newEvent._id}/form/${newFeedbackId}`;
|
||||
}
|
||||
if (newRegId && payload.config?.sections?.footer?.links) {
|
||||
for (const link of payload.config.sections.footer.links) {
|
||||
if (link.text && /register/i.test(link.text)) {
|
||||
link.url = `${PUBLIC_WEB_BASE}/event/${newEvent._id}/form/${newRegId}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
await EventStaticMicrosite.create(payload);
|
||||
summary.copied.staticMicrosite = payload.subdomain;
|
||||
}
|
||||
|
||||
for (const ticket of sourceTickets) {
|
||||
const payload = stripDoc(ticket);
|
||||
payload.event = newEvent._id;
|
||||
payload.organization = orgId;
|
||||
payload.registrationForm = idMap.remap(ticket.registrationForm);
|
||||
payload.participantList = idMap.remap(ticket.participantList);
|
||||
await EventTicket.create(payload);
|
||||
}
|
||||
summary.copied.tickets = sourceTickets.length;
|
||||
|
||||
const sourceKnowledge = await KnowledgeSource.find({ eventId: SOURCE_EVENT_ID }).lean();
|
||||
for (const ks of sourceKnowledge) {
|
||||
const payload = stripDoc(ks);
|
||||
payload.eventId = newEvent._id;
|
||||
const createdKs = await KnowledgeSource.create(payload);
|
||||
idMap.set(ks._id, createdKs._id);
|
||||
const chunks = await ContextChunk.find({ sourceId: ks._id }).lean();
|
||||
if (chunks.length) {
|
||||
await ContextChunk.insertMany(
|
||||
chunks.map((chunk) => {
|
||||
const c = stripDoc(chunk);
|
||||
c.eventId = newEvent._id;
|
||||
c.sourceId = createdKs._id;
|
||||
return c;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
summary.copied.knowledgeSources = sourceKnowledge.length;
|
||||
|
||||
if (newEvent.website?.registrationFormId) {
|
||||
newEvent.website.registrationFormId = idMap.remapString(newEvent.website.registrationFormId);
|
||||
newEvent.markModified('website');
|
||||
await newEvent.save();
|
||||
} else {
|
||||
const regForm = sourceForms.find((f) => f.type === 'registration');
|
||||
if (regForm && idMap.get(regForm._id)) {
|
||||
newEvent.website = newEvent.website || {};
|
||||
newEvent.website.registrationFormId = idMap.get(regForm._id);
|
||||
newEvent.markModified('website');
|
||||
await newEvent.save();
|
||||
}
|
||||
}
|
||||
|
||||
summary.copied.eventAccessGrants = await copyEventAccess(SOURCE_EVENT_ID, newEvent._id, orgId);
|
||||
|
||||
summary.newEventId = oid(newEvent._id);
|
||||
summary.newEventName = newEvent.name;
|
||||
summary.location = target.location;
|
||||
summary.subdomain = summary.copied.staticMicrosite;
|
||||
return summary;
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const uri = process.env.MONGODB_URI;
|
||||
if (!uri) {
|
||||
console.error('MONGODB_URI is not set');
|
||||
process.exit(1);
|
||||
}
|
||||
await mongoose.connect(uri);
|
||||
|
||||
const sourceEvent = await Event.findById(SOURCE_EVENT_ID).lean();
|
||||
if (!sourceEvent) {
|
||||
console.error('Source event not found:', SOURCE_EVENT_ID);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const results = [];
|
||||
for (const target of TARGETS) {
|
||||
console.log(`\nCopying → ${target.name}...`);
|
||||
const summary = await copyEventVariant(sourceEvent, target, {
|
||||
sourceEventId: SOURCE_EVENT_ID,
|
||||
sourceEventName: sourceEvent.name,
|
||||
organizationId: oid(sourceEvent.organization),
|
||||
});
|
||||
results.push(summary);
|
||||
console.log(JSON.stringify(summary, null, 2));
|
||||
}
|
||||
|
||||
console.log('\nAll events created:');
|
||||
console.log(JSON.stringify(results, null, 2));
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Seed the CREST NOVEL microsite-v2 template.
|
||||
*
|
||||
* Usage: node scripts/seed-crest-novel-template.js [--user-id=<mongoId>] [--db-only]
|
||||
*/
|
||||
require('dotenv').config();
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const mongoose = require('mongoose');
|
||||
const { connectDB } = require('../src/config/database');
|
||||
const MicrositeV2Template = require('../src/models/MicrositeV2Template');
|
||||
const User = require('../src/models/User');
|
||||
const {
|
||||
uploadTemplateFile,
|
||||
uploadTemplateAsset,
|
||||
templateAssetsBaseUrl,
|
||||
} = require('../src/utils/s3MicrositeV2');
|
||||
const { parseTemplateSchema } = require('../src/utils/templateSchema');
|
||||
|
||||
const SLUG = 'crest-novel';
|
||||
const TEMPLATE_DIR = path.join(__dirname, '..', 'microsite-v2-templates', SLUG);
|
||||
const TEMPLATE_ASSETS = path.join(TEMPLATE_DIR, 'assets');
|
||||
const SOURCE_ASSETS = path.join(__dirname, '..', '..', 'microsites', 'crest-novel', 'assets');
|
||||
|
||||
async function findSeedUser(userIdArg) {
|
||||
if (userIdArg) {
|
||||
const u = await User.findById(userIdArg);
|
||||
if (u) return u;
|
||||
}
|
||||
const superAdmin = await User.findOne({ role: 'super_admin' }).sort({ createdAt: 1 });
|
||||
if (superAdmin) return superAdmin;
|
||||
throw new Error('No super_admin user found. Pass --user-id=<mongoId>');
|
||||
}
|
||||
|
||||
function walkDir(dir, base = dir) {
|
||||
const files = [];
|
||||
if (!fs.existsSync(dir)) return files;
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const full = path.join(dir, name);
|
||||
const stat = fs.statSync(full);
|
||||
if (stat.isDirectory()) {
|
||||
files.push(...walkDir(full, base));
|
||||
} else {
|
||||
files.push({ full, rel: path.relative(base, full).replace(/\\/g, '/') });
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function mimeFor(filePath) {
|
||||
if (filePath.endsWith('.css')) return 'text/css';
|
||||
if (filePath.endsWith('.js')) return 'application/javascript';
|
||||
if (filePath.endsWith('.woff')) return 'font/woff';
|
||||
if (filePath.endsWith('.woff2')) return 'font/woff2';
|
||||
if (filePath.endsWith('.ttf')) return 'font/ttf';
|
||||
if (filePath.endsWith('.eot')) return 'application/vnd.ms-fontobject';
|
||||
if (filePath.endsWith('.png')) return 'image/png';
|
||||
if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg';
|
||||
if (filePath.endsWith('.svg')) return 'image/svg+xml';
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
/** IcoMoon exports use filenames like LineIcons.woff?y2l643 — normalize for S3 keys. */
|
||||
function normalizeAssetRelPath(rel) {
|
||||
const base = String(rel).replace(/\\/g, '/');
|
||||
const fontsMatch = base.match(/^fonts\/LineIcons\.(woff2?|ttf|eot|svg)\?/i);
|
||||
if (fontsMatch) {
|
||||
return `fonts/LineIcons.${fontsMatch[1].toLowerCase()}`;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const dbOnly = process.argv.includes('--db-only');
|
||||
const userIdArg = (process.argv.find((a) => a.startsWith('--user-id=')) || '').split('=')[1];
|
||||
|
||||
await connectDB();
|
||||
const user = await findSeedUser(userIdArg);
|
||||
console.log(`Using user: ${user.email || user._id}`);
|
||||
|
||||
const htmlPath = path.join(TEMPLATE_DIR, 'template.html');
|
||||
const jsonPath = path.join(TEMPLATE_DIR, 'template.json');
|
||||
if (!fs.existsSync(htmlPath) || !fs.existsSync(jsonPath)) {
|
||||
throw new Error(`Missing template files in ${TEMPLATE_DIR}`);
|
||||
}
|
||||
|
||||
const htmlBuf = fs.readFileSync(htmlPath);
|
||||
const jsonBuf = fs.readFileSync(jsonPath);
|
||||
const schema = parseTemplateSchema(jsonBuf);
|
||||
|
||||
let htmlS3Key;
|
||||
let schemaS3Key;
|
||||
let thumbnailUrl;
|
||||
let assetsBaseUrl = templateAssetsBaseUrl(SLUG);
|
||||
|
||||
if (!dbOnly && process.env.AWS_BUCKET) {
|
||||
console.log('Uploading template.html...');
|
||||
const htmlUp = await uploadTemplateFile(SLUG, 'template.html', htmlBuf, 'text/html; charset=utf-8');
|
||||
htmlS3Key = htmlUp.s3Key;
|
||||
|
||||
console.log('Uploading template.json...');
|
||||
const jsonUp = await uploadTemplateFile(SLUG, 'template.json', jsonBuf, 'application/json');
|
||||
schemaS3Key = jsonUp.s3Key;
|
||||
|
||||
const thumbCandidates = [
|
||||
path.join(TEMPLATE_DIR, 'thumbnail.jpg'),
|
||||
path.join(TEMPLATE_DIR, 'thumbnail.png'),
|
||||
path.join(SOURCE_ASSETS, 'img', 'hero.jpg'),
|
||||
path.join(SOURCE_ASSETS, 'img', 'novel-logo.png'),
|
||||
];
|
||||
for (const tp of thumbCandidates) {
|
||||
if (fs.existsSync(tp)) {
|
||||
const ext = path.extname(tp).slice(1) || 'jpg';
|
||||
const thumbUp = await uploadTemplateFile(
|
||||
SLUG,
|
||||
`thumbnail.${ext}`,
|
||||
fs.readFileSync(tp),
|
||||
`image/${ext === 'jpg' ? 'jpeg' : ext}`,
|
||||
);
|
||||
thumbnailUrl = thumbUp.url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!thumbnailUrl) {
|
||||
const placeholder = Buffer.from(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="400" height="240"><rect fill="#015AAA" width="400" height="240"/><text x="50%" y="50%" fill="#fff" text-anchor="middle" dy=".3em" font-family="sans-serif" font-size="22">CREST NOVEL</text></svg>',
|
||||
);
|
||||
const thumbUp = await uploadTemplateFile(SLUG, 'thumbnail.svg', placeholder, 'image/svg+xml');
|
||||
thumbnailUrl = thumbUp.url;
|
||||
}
|
||||
|
||||
const assetRoots = [TEMPLATE_ASSETS, SOURCE_ASSETS];
|
||||
for (const root of assetRoots) {
|
||||
if (!fs.existsSync(root)) continue;
|
||||
console.log(`Uploading assets from ${root}...`);
|
||||
for (const { full, rel } of walkDir(root)) {
|
||||
const normalizedRel = normalizeAssetRelPath(rel);
|
||||
if (normalizedRel !== rel && fs.existsSync(path.join(root, normalizedRel))) {
|
||||
continue;
|
||||
}
|
||||
await uploadTemplateAsset(SLUG, normalizedRel, fs.readFileSync(full), mimeFor(normalizedRel));
|
||||
}
|
||||
}
|
||||
|
||||
assetsBaseUrl = templateAssetsBaseUrl(SLUG);
|
||||
console.log('S3 upload complete.');
|
||||
} else {
|
||||
console.log('Skipping S3 upload (--db-only or AWS_BUCKET not set).');
|
||||
}
|
||||
|
||||
const existing = await MicrositeV2Template.findOne({ slug: SLUG });
|
||||
const doc = {
|
||||
slug: SLUG,
|
||||
name: 'CREST NOVEL',
|
||||
description:
|
||||
'CREST / NOVEL lung health summit microsite — marketing popup, dual about sections, expert videos, tabbed speakers, schedule, sponsors, contact form, and feedback FAB.',
|
||||
thumbnailUrl: thumbnailUrl || '',
|
||||
htmlS3Key: htmlS3Key || '',
|
||||
schemaS3Key: schemaS3Key || '',
|
||||
schema,
|
||||
assetsBaseUrl,
|
||||
assetsS3Prefix: `templates/${SLUG}/assets`,
|
||||
isActive: Boolean(htmlS3Key && schemaS3Key),
|
||||
version: 1,
|
||||
createdBy: user._id,
|
||||
};
|
||||
|
||||
if (existing) {
|
||||
Object.assign(existing, doc);
|
||||
await existing.save();
|
||||
console.log(`Updated MicrositeV2Template: ${existing._id}`);
|
||||
} else {
|
||||
const created = await MicrositeV2Template.create(doc);
|
||||
console.log(`Created MicrositeV2Template: ${created._id}`);
|
||||
}
|
||||
|
||||
await mongoose.disconnect();
|
||||
console.log('Done.');
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Seed system roles per organisation and link OrganizationUserMap.customRole.
|
||||
* Run: cd backend && node scripts/seed-org-roles.js
|
||||
*/
|
||||
const path = require('path');
|
||||
|
||||
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
||||
const mongoose = require('mongoose');
|
||||
const Organization = require(path.join(__dirname, '..', 'src', 'models', 'Organization'));
|
||||
const OrganizationUserMap = require(path.join(__dirname, '..', 'src', 'models', 'OrganizationUserMap'));
|
||||
const Role = require(path.join(__dirname, '..', 'src', 'models', 'Role'));
|
||||
const {
|
||||
SYSTEM_ROLE_KEYS,
|
||||
SYSTEM_ROLE_PERMISSIONS,
|
||||
SYSTEM_ROLE_LABELS,
|
||||
} = require(path.join(__dirname, '..', 'src', 'config', 'systemRolePermissions'));
|
||||
|
||||
async function ensureSystemRolesForOrg(orgId) {
|
||||
const rolesByKey = {};
|
||||
for (const systemKey of SYSTEM_ROLE_KEYS) {
|
||||
let role = await Role.findOne({ organization: orgId, systemKey, isSystem: true });
|
||||
if (!role) {
|
||||
role = await Role.create({
|
||||
organization: orgId,
|
||||
name: SYSTEM_ROLE_LABELS[systemKey] || systemKey,
|
||||
description: `System role: ${systemKey}`,
|
||||
systemKey,
|
||||
isSystem: true,
|
||||
permissions: SYSTEM_ROLE_PERMISSIONS[systemKey] || [],
|
||||
});
|
||||
console.log(` Created system role ${systemKey} for org ${orgId}`);
|
||||
} else {
|
||||
role.permissions = SYSTEM_ROLE_PERMISSIONS[systemKey] || [];
|
||||
role.name = SYSTEM_ROLE_LABELS[systemKey] || systemKey;
|
||||
await role.save();
|
||||
}
|
||||
rolesByKey[systemKey] = role;
|
||||
}
|
||||
return rolesByKey;
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const uri = process.env.MONGODB_URI;
|
||||
if (!uri) {
|
||||
console.error('MONGODB_URI is not set');
|
||||
process.exit(1);
|
||||
}
|
||||
await mongoose.connect(uri);
|
||||
|
||||
const orgs = await Organization.find({}).select('_id name').lean();
|
||||
let mapsUpdated = 0;
|
||||
|
||||
for (const org of orgs) {
|
||||
console.log(`Org: ${org.name} (${org._id})`);
|
||||
const rolesByKey = await ensureSystemRolesForOrg(org._id);
|
||||
|
||||
const maps = await OrganizationUserMap.find({ organization: org._id });
|
||||
for (const map of maps) {
|
||||
const roleDoc = rolesByKey[map.role];
|
||||
if (roleDoc && String(map.customRole || '') !== String(roleDoc._id)) {
|
||||
map.customRole = roleDoc._id;
|
||||
await map.save();
|
||||
mapsUpdated += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Done. ${orgs.length} org(s), ${mapsUpdated} membership(s) linked to customRole.`);
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Configure static microsite v2 for an event using crest-novel template + site.json content.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/setup-crest-novel-microsite.js [--publish] [--event-id=<id>] [--subdomain=<sub>]
|
||||
*/
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
||||
const fs = require('fs');
|
||||
const mongoose = require('mongoose');
|
||||
const { connectDB } = require('../src/config/database');
|
||||
const Event = require('../src/models/Event');
|
||||
const Form = require('../src/models/Form');
|
||||
const MicrositeV2Template = require('../src/models/MicrositeV2Template');
|
||||
const EventStaticMicrosite = require('../src/models/EventStaticMicrosite');
|
||||
const User = require('../src/models/User');
|
||||
const { normalizeSubdomain, subdomainAvailability } = require('../src/utils/subdomainAvailability');
|
||||
const { uploadTemplateAsset } = require('../src/utils/s3MicrositeV2');
|
||||
const { publishStaticMicrosite } = require('../src/utils/publishStaticMicrosite');
|
||||
const { resolveConfigAssetUrls } = require('../src/utils/micrositeConfigAssets');
|
||||
|
||||
const DEFAULT_EVENT_ID = '6a4cb75a7475c040cc6f2dd7';
|
||||
const DEFAULT_SUBDOMAIN = 'novel2026-goa-8-9-aug';
|
||||
const TEMPLATE_SLUG = 'crest-novel';
|
||||
const SITE_JSON = path.join(__dirname, '..', '..', 'microsites', 'crest-novel', 'site.json');
|
||||
const PUBLIC_WEB_BASE = (process.env.PUBLIC_WEB_BASE || 'https://web.eventbuzz.live').replace(/\/$/, '');
|
||||
|
||||
function argValue(name, fallback) {
|
||||
const hit = process.argv.find((a) => a.startsWith(`--${name}=`));
|
||||
return hit ? hit.split('=').slice(1).join('=').trim() : fallback;
|
||||
}
|
||||
|
||||
function deepClone(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
function replaceEventIds(obj, oldId, newId) {
|
||||
if (obj == null) return obj;
|
||||
if (typeof obj === 'string') {
|
||||
return obj.split(oldId).join(newId);
|
||||
}
|
||||
if (Array.isArray(obj)) return obj.map((v) => replaceEventIds(v, oldId, newId));
|
||||
if (typeof obj === 'object') {
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(obj)) out[k] = replaceEventIds(v, oldId, newId);
|
||||
return out;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function siteJsonToConfig(site, eventId) {
|
||||
const theme = site.theme || {};
|
||||
const headingColor = theme.heading?.color || theme.headingColor || '#015AAA';
|
||||
return {
|
||||
meta: {
|
||||
title: site.meta?.title || 'NOVEL Lung Health Summit',
|
||||
eventId: String(eventId),
|
||||
},
|
||||
theme: {
|
||||
accent: theme.accent || '#189A95',
|
||||
headingColor,
|
||||
},
|
||||
sections: deepClone(site.components || {}),
|
||||
};
|
||||
}
|
||||
|
||||
async function findSeedUser() {
|
||||
const userIdArg = argValue('user-id', '');
|
||||
if (userIdArg) {
|
||||
const u = await User.findById(userIdArg);
|
||||
if (u) return u;
|
||||
}
|
||||
const platform = await User.findOne({ platform_admin: 'yes', isActive: { $ne: false } }).sort({ createdAt: 1 });
|
||||
if (platform) return platform;
|
||||
const superAdmin = await User.findOne({ role: 'super_admin' }).sort({ createdAt: 1 });
|
||||
if (superAdmin) return superAdmin;
|
||||
throw new Error('No user found for updatedBy/publish. Pass --user-id=<mongoId>');
|
||||
}
|
||||
|
||||
async function findRegistrationForm(eventId) {
|
||||
return Form.findOne({ event: eventId, type: 'registration', 'settings.isActive': { $ne: false } })
|
||||
.sort({ createdAt: 1 })
|
||||
.select('_id name')
|
||||
.lean();
|
||||
}
|
||||
|
||||
async function findFeedbackForm(eventId) {
|
||||
return Form.findOne({ event: eventId, type: 'feedback', 'settings.isActive': { $ne: false } })
|
||||
.sort({ createdAt: 1 })
|
||||
.select('_id name')
|
||||
.lean();
|
||||
}
|
||||
|
||||
function applyEventUrls(config, eventId, registrationFormId, feedbackFormId, eventDoc) {
|
||||
const regUrl = registrationFormId
|
||||
? `${PUBLIC_WEB_BASE}/event/${eventId}/form/${registrationFormId}`
|
||||
: `${PUBLIC_WEB_BASE}/event/${eventId}/register`;
|
||||
const feedbackUrl = feedbackFormId
|
||||
? `${PUBLIC_WEB_BASE}/event/${eventId}/form/${feedbackFormId}`
|
||||
: null;
|
||||
|
||||
const sections = config.sections || {};
|
||||
|
||||
if (sections.hero?.cta) {
|
||||
sections.hero.cta.url = regUrl;
|
||||
sections.hero.cta.target = '_blank';
|
||||
}
|
||||
if (sections.footer?.links && Array.isArray(sections.footer.links)) {
|
||||
for (const link of sections.footer.links) {
|
||||
if (link.text && /register/i.test(link.text)) link.url = regUrl;
|
||||
if (link.text && /feedback/i.test(link.text) && feedbackUrl) link.url = feedbackUrl;
|
||||
}
|
||||
}
|
||||
if (sections.feedback && feedbackUrl) {
|
||||
sections.feedback.url = feedbackUrl;
|
||||
}
|
||||
|
||||
if (eventDoc?.banner && sections.hero?.image) {
|
||||
sections.hero.image.url = eventDoc.banner;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
async function syncTemplateSiteConfigJs() {
|
||||
const siteConfigPath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'microsite-v2-templates',
|
||||
TEMPLATE_SLUG,
|
||||
'assets',
|
||||
'js',
|
||||
'site-config.js',
|
||||
);
|
||||
if (!fs.existsSync(siteConfigPath)) {
|
||||
throw new Error(`Missing site-config.js at ${siteConfigPath}`);
|
||||
}
|
||||
await uploadTemplateAsset(
|
||||
TEMPLATE_SLUG,
|
||||
'js/site-config.js',
|
||||
fs.readFileSync(siteConfigPath),
|
||||
'application/javascript',
|
||||
);
|
||||
console.log('Uploaded crest-novel site-config.js to template assets');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const eventId = argValue('event-id', DEFAULT_EVENT_ID);
|
||||
const subdomainRaw = argValue('subdomain', DEFAULT_SUBDOMAIN);
|
||||
const shouldPublish = process.argv.includes('--publish');
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(eventId)) {
|
||||
throw new Error(`Invalid event id: ${eventId}`);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(SITE_JSON)) {
|
||||
throw new Error(`Missing site.json at ${SITE_JSON}`);
|
||||
}
|
||||
|
||||
await connectDB();
|
||||
|
||||
const event = await Event.findById(eventId).lean();
|
||||
if (!event) throw new Error(`Event not found: ${eventId}`);
|
||||
|
||||
const template = await MicrositeV2Template.findOne({ slug: TEMPLATE_SLUG, isActive: true });
|
||||
if (!template) {
|
||||
throw new Error(`Active template "${TEMPLATE_SLUG}" not found. Run: npm run seed:crest-novel-template`);
|
||||
}
|
||||
|
||||
const user = await findSeedUser();
|
||||
const subdomain = normalizeSubdomain(subdomainRaw);
|
||||
if (!subdomain) throw new Error(`Invalid subdomain: ${subdomainRaw}`);
|
||||
|
||||
const existing = await EventStaticMicrosite.findOne({ event: eventId });
|
||||
const avail = await subdomainAvailability(subdomain, eventId, existing?._id?.toString());
|
||||
if (!avail.available) throw new Error(avail.message || 'Subdomain not available');
|
||||
|
||||
const site = JSON.parse(fs.readFileSync(SITE_JSON, 'utf8'));
|
||||
const oldEventId = site.meta?.eventId || '6a43e05fdc940a0d6e057f7b';
|
||||
|
||||
let config = siteJsonToConfig(site, eventId);
|
||||
config = replaceEventIds(config, oldEventId, eventId);
|
||||
|
||||
const registrationForm = await findRegistrationForm(eventId);
|
||||
const feedbackForm = await findFeedbackForm(eventId);
|
||||
config = applyEventUrls(config, eventId, registrationForm?._id, feedbackForm?._id, event);
|
||||
config.sections = resolveConfigAssetUrls(config.sections || {}, template.assetsBaseUrl);
|
||||
|
||||
await syncTemplateSiteConfigJs();
|
||||
|
||||
const payload = {
|
||||
event: event._id,
|
||||
organization: event.organization,
|
||||
subdomain,
|
||||
template: template._id,
|
||||
config,
|
||||
status: 'draft',
|
||||
updatedBy: user._id,
|
||||
};
|
||||
|
||||
let siteDoc;
|
||||
if (existing) {
|
||||
Object.assign(existing, payload);
|
||||
await existing.save();
|
||||
siteDoc = existing;
|
||||
console.log(`Updated EventStaticMicrosite: ${existing._id}`);
|
||||
} else {
|
||||
siteDoc = await EventStaticMicrosite.create(payload);
|
||||
console.log(`Created EventStaticMicrosite: ${siteDoc._id}`);
|
||||
}
|
||||
|
||||
console.log(`Event: ${event.name} (${eventId})`);
|
||||
console.log(`Template: ${template.name} (${template.slug})`);
|
||||
console.log(`Subdomain: ${subdomain}`);
|
||||
console.log(`Reg form: ${registrationForm?._id || '(none — using /register fallback)'}`);
|
||||
console.log(`Feedback: ${feedbackForm?._id || '(none)'}`);
|
||||
|
||||
if (shouldPublish) {
|
||||
console.log('Publishing...');
|
||||
const populated = await EventStaticMicrosite.findById(siteDoc._id).populate('template');
|
||||
const result = await publishStaticMicrosite({
|
||||
staticMicrosite: populated,
|
||||
template: populated.template,
|
||||
userId: user._id,
|
||||
});
|
||||
Object.assign(populated, result);
|
||||
await populated.save();
|
||||
console.log(`Published: ${result.publicUrl}`);
|
||||
} else {
|
||||
console.log('Saved as draft. Re-run with --publish to publish.');
|
||||
}
|
||||
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
/**
|
||||
* Align Goa 7–8 Aug microsite with Goa 8–9 Aug format and publish.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/setup-goa-7-8-microsite.js [--publish]
|
||||
*/
|
||||
const path = require('path');
|
||||
|
||||
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
||||
const mongoose = require('mongoose');
|
||||
const { connectDB } = require('../src/config/database');
|
||||
const Event = require('../src/models/Event');
|
||||
const Form = require('../src/models/Form');
|
||||
const EventStaticMicrosite = require('../src/models/EventStaticMicrosite');
|
||||
const MicrositeV2Template = require('../src/models/MicrositeV2Template');
|
||||
const User = require('../src/models/User');
|
||||
const { normalizeSubdomain, subdomainAvailability } = require('../src/utils/subdomainAvailability');
|
||||
const { publishStaticMicrosite } = require('../src/utils/publishStaticMicrosite');
|
||||
|
||||
const SOURCE_SUBDOMAIN = 'novel2026-goa-8-9-aug';
|
||||
const TARGET_EVENT_ID = '6a43e05fdc940a0d6e057f7b';
|
||||
const TARGET_SUBDOMAIN = 'novel2026-goa-7-8-aug';
|
||||
const SOURCE_EVENT_ID = '6a4cb75a7475c040cc6f2dd7';
|
||||
const PUBLIC_WEB_BASE = (process.env.PUBLIC_WEB_BASE || 'https://web.eventbuzz.live').replace(/\/$/, '');
|
||||
|
||||
const TARGET = {
|
||||
name: 'NOVEL Lung Health Summit - Goa - 7th & 8th August 2026',
|
||||
shortPlace: 'Goa',
|
||||
dateLabel: '7th & 8th August 2026',
|
||||
dateShort: 'Aug 7–8, 2026',
|
||||
countdownDate: '2026-08-07',
|
||||
scheduleDay1: 'August 7, 2026',
|
||||
scheduleDay2: 'August 8, 2026',
|
||||
location: 'DoubleTree by Hilton Panjim, Goa',
|
||||
mapQuery: 'DoubleTree+by+Hilton+Goa+Panaji,+Chimbel,+Goa',
|
||||
};
|
||||
|
||||
function deepClone(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
function replaceAllStrings(obj, replacements) {
|
||||
if (obj == null) return obj;
|
||||
if (typeof obj === 'string') {
|
||||
let out = obj;
|
||||
for (const [from, to] of replacements) {
|
||||
if (from) out = out.split(from).join(to);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (Array.isArray(obj)) return obj.map((item) => replaceAllStrings(item, replacements));
|
||||
if (typeof obj === 'object') {
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(obj)) out[k] = replaceAllStrings(v, replacements);
|
||||
return out;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
async function findForm(eventId, type) {
|
||||
return Form.findOne({ event: eventId, type, 'settings.isActive': { $ne: false } })
|
||||
.sort({ createdAt: 1 })
|
||||
.select('_id name')
|
||||
.lean();
|
||||
}
|
||||
|
||||
async function findContactForm(eventId) {
|
||||
return Form.findOne({ event: eventId, type: 'other', name: /contact/i, 'settings.isActive': { $ne: false } })
|
||||
.sort({ createdAt: 1 })
|
||||
.select('_id name')
|
||||
.lean();
|
||||
}
|
||||
|
||||
async function findPublishUser() {
|
||||
const platform = await User.findOne({ platform_admin: 'yes', isActive: { $ne: false } }).sort({ createdAt: 1 });
|
||||
if (platform) return platform;
|
||||
const superAdmin = await User.findOne({ role: 'super_admin' }).sort({ createdAt: 1 });
|
||||
if (superAdmin) return superAdmin;
|
||||
throw new Error('No user found for publish');
|
||||
}
|
||||
|
||||
function applyTargetOverrides(config, targetEventId, forms, heroBannerUrl) {
|
||||
const next = deepClone(config);
|
||||
next.meta = next.meta || {};
|
||||
next.meta.title = TARGET.name;
|
||||
next.meta.eventId = String(targetEventId);
|
||||
|
||||
const regUrl = forms.registration
|
||||
? `${PUBLIC_WEB_BASE}/event/${targetEventId}/form/${forms.registration._id}`
|
||||
: `${PUBLIC_WEB_BASE}/event/${targetEventId}/register`;
|
||||
const feedbackUrl = forms.feedback
|
||||
? `${PUBLIC_WEB_BASE}/event/${targetEventId}/form/${forms.feedback._id}`
|
||||
: null;
|
||||
|
||||
const sections = next.sections || {};
|
||||
|
||||
if (sections.hero) {
|
||||
sections.hero.event_name = `${TARGET.shortPlace} - ${TARGET.dateLabel}`;
|
||||
sections.hero.date_venue = TARGET.location;
|
||||
if (heroBannerUrl && sections.hero.image) {
|
||||
sections.hero.image.url = heroBannerUrl;
|
||||
}
|
||||
if (sections.hero.cta) {
|
||||
sections.hero.cta.url = regUrl;
|
||||
sections.hero.cta.target = '_blank';
|
||||
}
|
||||
if (sections.hero.countdown) {
|
||||
sections.hero.countdown.date = TARGET.countdownDate;
|
||||
}
|
||||
}
|
||||
|
||||
if (sections.informationBar?.items) {
|
||||
for (const item of sections.informationBar.items) {
|
||||
if (item.label === 'Location') item.value = TARGET.location;
|
||||
if (item.label === 'Date & Time') {
|
||||
item.value = TARGET.dateShort;
|
||||
if (item.calendar) {
|
||||
item.calendar.title = TARGET.name;
|
||||
item.calendar.start = TARGET.countdownDate;
|
||||
item.calendar.end = '2026-08-08';
|
||||
item.calendar.location = TARGET.location;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sections.event_schedule?.days) {
|
||||
if (sections.event_schedule.days[0]) sections.event_schedule.days[0].date = TARGET.scheduleDay1;
|
||||
if (sections.event_schedule.days[1]) sections.event_schedule.days[1].date = TARGET.scheduleDay2;
|
||||
}
|
||||
|
||||
if (sections.map) {
|
||||
sections.map.embedUrl = `https://maps.google.com/maps?q=${TARGET.mapQuery}&hl=en&z=15&output=embed`;
|
||||
}
|
||||
|
||||
if (sections.contact && forms.contact) {
|
||||
sections.contact.show = true;
|
||||
sections.contact.formId = String(forms.contact._id);
|
||||
sections.contact.apiBaseUrl = sections.contact.apiBaseUrl || 'https://api.eventbuzz.live/api';
|
||||
}
|
||||
|
||||
if (sections.feedback && feedbackUrl) {
|
||||
sections.feedback.url = feedbackUrl;
|
||||
}
|
||||
|
||||
if (sections.footer?.links) {
|
||||
for (const link of sections.footer.links) {
|
||||
if (link.text && /register/i.test(link.text)) {
|
||||
link.url = regUrl;
|
||||
link.target = '_blank';
|
||||
}
|
||||
if (link.text && /feedback/i.test(link.text) && feedbackUrl) {
|
||||
link.url = feedbackUrl;
|
||||
link.target = '_blank';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next.sections = sections;
|
||||
return next;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const shouldPublish = process.argv.includes('--publish');
|
||||
await connectDB();
|
||||
|
||||
const targetEvent = await Event.findById(TARGET_EVENT_ID).lean();
|
||||
if (!targetEvent) throw new Error(`Target event not found: ${TARGET_EVENT_ID}`);
|
||||
|
||||
const sourceSite = await EventStaticMicrosite.findOne({ subdomain: SOURCE_SUBDOMAIN }).populate('template').lean();
|
||||
if (!sourceSite) throw new Error(`Source microsite not found: ${SOURCE_SUBDOMAIN}`);
|
||||
|
||||
const existingTarget = await EventStaticMicrosite.findOne({ event: TARGET_EVENT_ID });
|
||||
const previousTarget = existingTarget ? deepClone(existingTarget.config) : null;
|
||||
const heroBannerUrl = previousTarget?.sections?.hero?.image?.url || null;
|
||||
|
||||
const subdomain = normalizeSubdomain(TARGET_SUBDOMAIN);
|
||||
const avail = await subdomainAvailability(subdomain, TARGET_EVENT_ID, existingTarget?._id?.toString());
|
||||
if (!avail.available) throw new Error(avail.message || 'Subdomain not available');
|
||||
|
||||
const [registration, feedback, contact] = await Promise.all([
|
||||
findForm(TARGET_EVENT_ID, 'registration'),
|
||||
findForm(TARGET_EVENT_ID, 'feedback'),
|
||||
findContactForm(TARGET_EVENT_ID),
|
||||
]);
|
||||
|
||||
const sourceForms = await Promise.all([
|
||||
findForm(SOURCE_EVENT_ID, 'registration'),
|
||||
findForm(SOURCE_EVENT_ID, 'feedback'),
|
||||
findContactForm(SOURCE_EVENT_ID),
|
||||
]);
|
||||
|
||||
const replacements = [
|
||||
[SOURCE_EVENT_ID, TARGET_EVENT_ID],
|
||||
[sourceForms[0]?._id ? String(sourceForms[0]._id) : '', registration?._id ? String(registration._id) : ''],
|
||||
[sourceForms[1]?._id ? String(sourceForms[1]._id) : '', feedback?._id ? String(feedback._id) : ''],
|
||||
['NOVEL Lung Health Summit - Goa - 8th & 9th August 2026', TARGET.name],
|
||||
['Goa - 8th & 9th August 2026', `${TARGET.shortPlace} - ${TARGET.dateLabel}`],
|
||||
['Aug 8–9, 2026', TARGET.dateShort],
|
||||
].filter(([from]) => from);
|
||||
|
||||
let config = replaceAllStrings(sourceSite.config, replacements);
|
||||
config = applyTargetOverrides(config, TARGET_EVENT_ID, { registration, feedback, contact }, heroBannerUrl);
|
||||
|
||||
const user = shouldPublish ? await findPublishUser() : null;
|
||||
const templateId = sourceSite.template?._id || sourceSite.template;
|
||||
|
||||
const payload = {
|
||||
event: targetEvent._id,
|
||||
organization: targetEvent.organization,
|
||||
subdomain,
|
||||
template: templateId,
|
||||
config,
|
||||
updatedBy: user?._id,
|
||||
};
|
||||
|
||||
let siteDoc;
|
||||
if (existingTarget) {
|
||||
Object.assign(existingTarget, payload);
|
||||
existingTarget.markModified('config');
|
||||
await existingTarget.save();
|
||||
siteDoc = existingTarget;
|
||||
console.log(`Updated microsite ${existingTarget._id}`);
|
||||
} else {
|
||||
siteDoc = await EventStaticMicrosite.create({ ...payload, status: 'draft' });
|
||||
console.log(`Created microsite ${siteDoc._id}`);
|
||||
}
|
||||
|
||||
console.log(`Event: ${targetEvent.name}`);
|
||||
console.log(`Subdomain: ${subdomain}`);
|
||||
console.log(`Template: ${sourceSite.template?.slug || templateId}`);
|
||||
console.log(`Reg form: ${registration?._id || '(none)'}`);
|
||||
console.log(`Feedback: ${feedback?._id || '(none)'}`);
|
||||
console.log(`Contact: ${contact?._id || '(none)'}`);
|
||||
|
||||
if (shouldPublish) {
|
||||
const populated = await EventStaticMicrosite.findById(siteDoc._id).populate('template');
|
||||
const result = await publishStaticMicrosite({
|
||||
staticMicrosite: populated,
|
||||
template: populated.template,
|
||||
userId: user._id,
|
||||
});
|
||||
Object.assign(populated, result);
|
||||
await populated.save();
|
||||
console.log(`Published: ${result.publicUrl}`);
|
||||
} else {
|
||||
console.log('Saved as draft. Re-run with --publish to publish.');
|
||||
}
|
||||
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* Set placeholder speaker / committee names on all Crest org static microsites.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/update-crest-microsite-speakers.js [--publish] [--org-id=<id>]
|
||||
*/
|
||||
const path = require('path');
|
||||
|
||||
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
|
||||
const mongoose = require('mongoose');
|
||||
const { connectDB } = require('../src/config/database');
|
||||
const EventStaticMicrosite = require('../src/models/EventStaticMicrosite');
|
||||
const Event = require('../src/models/Event');
|
||||
const MicrositeV2Template = require('../src/models/MicrositeV2Template');
|
||||
const User = require('../src/models/User');
|
||||
const { publishStaticMicrosite } = require('../src/utils/publishStaticMicrosite');
|
||||
|
||||
const DEFAULT_ORG_ID = '6a43dfeddc940a0d6e057eef';
|
||||
const TEAM_IMAGES = [
|
||||
'https://did94hhhcgq1d.cloudfront.net/eventbuzz/microsite-v2/templates/crest-novel/assets/img/team/team-01.jpg',
|
||||
'https://did94hhhcgq1d.cloudfront.net/eventbuzz/microsite-v2/templates/crest-novel/assets/img/team/team-02.jpg',
|
||||
'https://did94hhhcgq1d.cloudfront.net/eventbuzz/microsite-v2/templates/crest-novel/assets/img/team/team-03.jpg',
|
||||
'https://did94hhhcgq1d.cloudfront.net/eventbuzz/microsite-v2/templates/crest-novel/assets/img/team/team-04.jpg',
|
||||
];
|
||||
|
||||
function argValue(name, fallback) {
|
||||
const hit = process.argv.find((a) => a.startsWith(`--${name}=`));
|
||||
return hit ? hit.split('=').slice(1).join('=').trim() : fallback;
|
||||
}
|
||||
|
||||
function namePrefixForTab(label) {
|
||||
const normalized = String(label || '').trim().toLowerCase();
|
||||
if (normalized === 'speakers') return 'Speaker';
|
||||
if (normalized.includes('organis')) return 'Organising Committee';
|
||||
if (normalized.includes('scientific')) return 'Scientific Committee';
|
||||
return String(label || 'Member').trim() || 'Member';
|
||||
}
|
||||
|
||||
function placeholderItem(index, imageIndex = 0) {
|
||||
return {
|
||||
image: TEAM_IMAGES[imageIndex % TEAM_IMAGES.length],
|
||||
name: '',
|
||||
role: '',
|
||||
url: 'index.html#',
|
||||
};
|
||||
}
|
||||
|
||||
function renameTabItems(tab) {
|
||||
const prefix = namePrefixForTab(tab.label);
|
||||
const items = Array.isArray(tab.items) ? tab.items : [];
|
||||
return {
|
||||
...tab,
|
||||
items: items.map((item, index) => ({
|
||||
...item,
|
||||
name: `${prefix} ${index + 1}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function defaultSpeakersSection() {
|
||||
const counts = [
|
||||
{ label: 'Speakers', count: 4 },
|
||||
{ label: 'Organising Committee', count: 3 },
|
||||
{ label: 'Scientific Committee', count: 3 },
|
||||
];
|
||||
let imageIndex = 0;
|
||||
const tabs = counts.map(({ label, count }) => ({
|
||||
label,
|
||||
items: Array.from({ length: count }, () => {
|
||||
const item = placeholderItem(imageIndex, imageIndex);
|
||||
imageIndex += 1;
|
||||
return item;
|
||||
}),
|
||||
}));
|
||||
return {
|
||||
show: true,
|
||||
heading: 'Our Speakers',
|
||||
subtitle: 'Leading respiratory medicine experts and committee members from across India.',
|
||||
tabs: tabs.map(renameTabItems),
|
||||
};
|
||||
}
|
||||
|
||||
function applyPlaceholderNames(speakers) {
|
||||
if (!speakers || typeof speakers !== 'object') {
|
||||
return defaultSpeakersSection();
|
||||
}
|
||||
|
||||
const next = { ...speakers, show: true };
|
||||
|
||||
if (Array.isArray(next.tabs) && next.tabs.length) {
|
||||
next.tabs = next.tabs.map(renameTabItems);
|
||||
delete next.items;
|
||||
return next;
|
||||
}
|
||||
|
||||
if (Array.isArray(next.items) && next.items.length) {
|
||||
next.items = next.items.map((item, index) => ({
|
||||
...item,
|
||||
name: `Speaker ${index + 1}`,
|
||||
}));
|
||||
return next;
|
||||
}
|
||||
|
||||
return defaultSpeakersSection();
|
||||
}
|
||||
|
||||
async function findPublishUser() {
|
||||
const userIdArg = argValue('user-id', '');
|
||||
if (userIdArg) {
|
||||
const u = await User.findById(userIdArg);
|
||||
if (u) return u;
|
||||
}
|
||||
const platform = await User.findOne({ platform_admin: 'yes', isActive: { $ne: false } }).sort({ createdAt: 1 });
|
||||
if (platform) return platform;
|
||||
const superAdmin = await User.findOne({ role: 'super_admin' }).sort({ createdAt: 1 });
|
||||
if (superAdmin) return superAdmin;
|
||||
throw new Error('No user found for publish. Pass --user-id=<mongoId>');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const orgId = argValue('org-id', DEFAULT_ORG_ID);
|
||||
const shouldPublish = process.argv.includes('--publish');
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(orgId)) {
|
||||
throw new Error(`Invalid org id: ${orgId}`);
|
||||
}
|
||||
|
||||
await connectDB();
|
||||
|
||||
const sites = await EventStaticMicrosite.find({ organization: orgId })
|
||||
.populate('event', 'name')
|
||||
.populate('template')
|
||||
.sort({ subdomain: 1 });
|
||||
|
||||
if (!sites.length) {
|
||||
throw new Error(`No static microsites found for organization ${orgId}`);
|
||||
}
|
||||
|
||||
const user = shouldPublish ? await findPublishUser() : null;
|
||||
|
||||
for (const site of sites) {
|
||||
const config = site.config && typeof site.config === 'object' ? { ...site.config } : {};
|
||||
const sections = { ...(config.sections || {}) };
|
||||
sections.speakers = applyPlaceholderNames(sections.speakers);
|
||||
config.sections = sections;
|
||||
site.config = config;
|
||||
site.markModified('config');
|
||||
await site.save();
|
||||
|
||||
console.log(`Updated ${site.subdomain} (${site.event?.name || site.event})`);
|
||||
|
||||
if (shouldPublish) {
|
||||
const result = await publishStaticMicrosite({
|
||||
staticMicrosite: site,
|
||||
template: site.template,
|
||||
userId: user._id,
|
||||
});
|
||||
Object.assign(site, result);
|
||||
await site.save();
|
||||
console.log(` Published: ${result.publicUrl}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Done. ${sites.length} microsite(s) updated.${shouldPublish ? '' : ' Re-run with --publish to publish.'}`);
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -21,6 +21,7 @@ const mediaRoutes = require('./routes/media.routes');
|
|||
const contentCollectionRoutes = require('./routes/contentCollection.routes');
|
||||
const paymentRoutes = require('./routes/payment.routes');
|
||||
const organizationRoutes = require('./routes/organization.routes');
|
||||
const roleRoutes = require('./routes/role.routes');
|
||||
const subscriptionRoutes = require('./routes/subscription.routes');
|
||||
const platformAdminRoutes = require('./routes/platformAdmin.routes');
|
||||
const adminRequestRoutes = require('./routes/adminRequest.routes');
|
||||
|
|
@ -271,6 +272,7 @@ const createServer = async () => {
|
|||
server.route(contentCollectionRoutes);
|
||||
server.route(paymentRoutes);
|
||||
server.route(organizationRoutes);
|
||||
server.route(roleRoutes);
|
||||
server.route(subscriptionRoutes);
|
||||
server.route(platformAdminRoutes);
|
||||
server.route(adminRequestRoutes);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* ACL module registry — dashboard permission keys.
|
||||
* Row-level modules use ResourceGrant or eventAccess; module-only keys gate routes without row filters.
|
||||
*/
|
||||
const { COMBINED_PLAN_KEYS } = require('./planFeatureGroups');
|
||||
|
||||
const ACL_ACTIONS = ['read', 'write', 'delete'];
|
||||
|
||||
/** Row-level ACL in v1 */
|
||||
const ROW_LEVEL_MODULES = new Set(['events', 'tracks', 'sessions', 'forms']);
|
||||
|
||||
const ACL_MODULE_DEFS = {
|
||||
events: {
|
||||
key: 'events',
|
||||
label: 'Events',
|
||||
category: 'core',
|
||||
rowLevel: true,
|
||||
planFeatures: [],
|
||||
},
|
||||
tracks: {
|
||||
key: 'tracks',
|
||||
label: 'Tracks',
|
||||
category: 'agenda',
|
||||
rowLevel: true,
|
||||
planFeatures: ['agenda_builder', 'multi_track_agenda'],
|
||||
},
|
||||
sessions: {
|
||||
key: 'sessions',
|
||||
label: 'Sessions',
|
||||
category: 'agenda',
|
||||
rowLevel: true,
|
||||
planFeatures: ['agenda_builder'],
|
||||
},
|
||||
forms: {
|
||||
key: 'forms',
|
||||
label: 'Forms',
|
||||
category: 'forms',
|
||||
rowLevel: true,
|
||||
planFeatures: ['feedback_forms_and_surveys', 'registration_forms_with_custom_fields'],
|
||||
},
|
||||
user_management: {
|
||||
key: 'user_management',
|
||||
label: 'User Management',
|
||||
category: 'org',
|
||||
rowLevel: false,
|
||||
planFeatures: [],
|
||||
},
|
||||
settings: {
|
||||
key: 'settings',
|
||||
label: 'Organisation Settings',
|
||||
category: 'org',
|
||||
rowLevel: false,
|
||||
planFeatures: [],
|
||||
},
|
||||
billing: {
|
||||
key: 'billing',
|
||||
label: 'Billing & Payments',
|
||||
category: 'org',
|
||||
rowLevel: false,
|
||||
planFeatures: ['payment_gateway_integration', 'collect_payment_using_your_upi_qr'],
|
||||
},
|
||||
};
|
||||
|
||||
for (const key of COMBINED_PLAN_KEYS) {
|
||||
if (ACL_MODULE_DEFS[key]) continue;
|
||||
ACL_MODULE_DEFS[key] = {
|
||||
key,
|
||||
label: key.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
|
||||
category: 'feature',
|
||||
rowLevel: false,
|
||||
planFeatures: [key],
|
||||
};
|
||||
}
|
||||
|
||||
const ORDERED_ACL_MODULE_KEYS = [
|
||||
'events',
|
||||
'tracks',
|
||||
'sessions',
|
||||
'forms',
|
||||
'participants',
|
||||
'agenda_builder',
|
||||
'multi_track_agenda',
|
||||
'speaker_management',
|
||||
'content_management',
|
||||
'email_outreach_campaigns',
|
||||
'custom_badge_and_print_file_creation',
|
||||
'custom_template_for_wa_messaging',
|
||||
'live_polls',
|
||||
'multiple_quiz_modes',
|
||||
'qna_with_moderation',
|
||||
'ticketing_free_and_paid',
|
||||
'payment_gateway_integration',
|
||||
'user_management',
|
||||
'settings',
|
||||
'billing',
|
||||
...COMBINED_PLAN_KEYS.filter((k) => ![
|
||||
'events', 'tracks', 'sessions', 'forms', 'participants', 'agenda_builder',
|
||||
'multi_track_agenda', 'speaker_management', 'content_management',
|
||||
'email_outreach_campaigns', 'custom_badge_and_print_file_creation',
|
||||
'custom_template_for_wa_messaging', 'live_polls', 'multiple_quiz_modes',
|
||||
'qna_with_moderation', 'ticketing_free_and_paid', 'payment_gateway_integration',
|
||||
].includes(k)),
|
||||
];
|
||||
|
||||
function getModuleDef(moduleKey) {
|
||||
return ACL_MODULE_DEFS[moduleKey] || null;
|
||||
}
|
||||
|
||||
function isRowLevelModule(moduleKey) {
|
||||
return ROW_LEVEL_MODULES.has(moduleKey);
|
||||
}
|
||||
|
||||
function planFeaturesForModule(moduleKey) {
|
||||
const def = getModuleDef(moduleKey);
|
||||
if (!def) return [];
|
||||
return def.planFeatures || [];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ACL_ACTIONS,
|
||||
ROW_LEVEL_MODULES,
|
||||
ACL_MODULE_DEFS,
|
||||
ORDERED_ACL_MODULE_KEYS,
|
||||
getModuleDef,
|
||||
isRowLevelModule,
|
||||
planFeaturesForModule,
|
||||
};
|
||||