import React from 'react';
import superagent from 'superagent';
import _ from 'lodash';
import { fromUnixTime, formatDistanceToNow, format } from "date-fns";
import { formatInTimeZone } from 'date-fns-tz'
import ReactPlayer from 'react-player'
import {formatPrice} from '../Util/formatters';
import constants, { DEFAULT_TIMEZONE } from '../CloudApi/Constants.js';
import MoviesTVWrapper from './MoviesTVWrapper.jsx';
import MediaItemInfoPanel from './MediaItemInfoPanel.jsx';
import {
Container,
Segment,
Grid,
Header,
Image,
Icon,
Button,
Table,
Form,
Label,
Message,
Menu,
Tab,
Dropdown,
Radio,
Divider
} from 'semantic-ui-react'
export default class MediaProductEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
product: {
title: "",
description: "",
mediaItems: [],
mediaItemIds: [],
roomSettings: {},
entitlementClass: "",
featuredMediaItemId: "",
is3D: false,
locationCodes: null,
pricingProfileFilter: 0
},
locationSKUs: {},
allMediaItems: [],
mediaItemsOptions: [],
mediaProductTrailers: [],
trailerOptions: [],
messages: null,
entitlementClasses: [],
loadingShowings: false,
showings: [],
loadingMediaSessionEvents: false,
mediaSessionEvents: [],
};
}
async componentDidMount() {
this.setState({ loading: true });
const responses = await Promise.all([
this.reloadProduct(),
superagent.get(`/api/admin/media/items`).accept('json'),
superagent.get(`/api/admin/media/products?entitlementClass=FREE_FOR_ALL`).accept('json'),
superagent.get("/api/admin/media/schemas")
]);
const allMediaItems = responses[1].body;
const mediaItemsOptions = allMediaItems.map(nv => { return { "key": nv.id, "value": nv.id, "text": `${nv.title} (${nv.cdn})` } });
this.setState({ allMediaItems, mediaItemsOptions });
const mediaProductTrailers = responses[2].body;
const trailerOptions = mediaProductTrailers.map(mediaProduct => {
return {
"key": mediaProduct.bigMediaId,
"value": mediaProduct.bigMediaId,
"text": `${mediaProduct.title}`,
"image": { src: mediaProduct.marketingData["en-US"].unityPosterTexture && mediaProduct.marketingData["en-US"].unityPosterTexture["src"] }
}
});
this.setState({ mediaProductTrailers, trailerOptions });
const schemas = responses[3].body;
const entitlementClasses = Object.keys(schemas.EntitlementClass).map(k => { return { "key": k, "value": k, "text": k } });
const mediaTypes = Object.keys(schemas.MediaType).map(k => { return { "key": k, "value": k, "text": k } });
this.setState({ schemas, entitlementClasses, mediaTypes, loading: false });
}
async reloadProduct() {
if (this.props.match.params.productId) {
this.setState({ loading: true, messages: null });
const res = await superagent.get(`/api/admin/media/product/${this.props.match.params.productId}`).accept('json');
console.log(res.body);
this.setState({ product: res.body });
await Promise.all([
this.generateShowings(),
this.generateMediaSessionEvents()
]);
}
}
async onSaveProduct() {
this.setState({ loading: true });
try {
if (this.state.product.id) {
const res = await superagent.put(`/api/admin/media/product`).send(this.state.product);
console.log(res);
//this.setState({ loading: false });
} else {
const res = await superagent.post(`/api/admin/media/product`).send(this.state.product);
this.props.history.push({pathname: `/cinema/product/${res.body.id}`});
await this.reloadProduct();
}
this.setState({ messages: null, loading: false })
} catch (e) {
console.log(e.response.body);
this.setState({ messages: e.response.body, loading: false });
}
}
async onActivate(e) {
e.preventDefault();
const product = this.state.product;
const ACTIVATED = "ACTIVATED";
product.status = this.state.schemas.ProductStatus[ACTIVATED];
await this.onSaveProduct();
await this.reloadProduct();
this.setState({ loading: false });
}
async generateShowings() {
this.setState({ loadingShowings: true });
const res = await superagent.get(`/api/admin/media/showings/${this.state.product.synchronizationInterval}`).accept('json');
this.setState({ loadingShowings: false, showings: res.body });
}
async generateMediaSessionEvents() {
this.setState({ loadingMediaSessionEvents: true });
const res = await superagent.get(`/api/admin/media/events/${this.state.product.id}`).accept('json');
this.setState({ loadingMediaSessionEvents: false, mediaSessionEvents: res.body });
}
async onChange(e, { name, value }) {
const product = this.state.product;
product[name] = value;
this.setState({ product });
if (name === "synchronizationInterval") {
await this.generateShowings();
}
if (name === "featuredMediaItemId") {
await this.generateMediaSessionEvents();
}
}
onChangeCurrentChannelRoomSettings(e, { name, value }) {
if (this.state.product) {
const product = this.state.product;
if (!product.roomSettings) {
product.roomSettings = {};
}
product.roomSettings[name] = value;
console.log(product);
this.setState({ product });
}
}
onAddMediaItem(e) {
e.preventDefault();
let product = this.state.product;
product.mediaItems.push({
id: ""
});
this.setState({ product });
}
render() {
if (!this.state.product) {
return (
);
}
const existingProductInfo = (this.state.product.id) && (
ID: {this.state.product.id}
BigMediaId: {this.state.product.bigMediaId}
Created At: {(this.state.product.createdAt && this.state.product.createdAt._seconds) ? formatInTimeZone(fromUnixTime(this.state.product.createdAt._seconds), DEFAULT_TIMEZONE, 'PPPp z') : "Error"}
Status: {this.state.product.status}
Duration: {this.state.product.duration ? (this.state.product.duration / 1000.0) : 0} seconds
);
// Error messages.
let messages = null;
if (this.state.messages) {
if (_.isArray(this.state.messages)) {
messages = (
Errors:
{this.state.messages.map(message => (
{message.msg}
))}
);
} else {
messages = (
{this.state.messages}
);
}
}
const basicProductInfo = (
NOTE: This title and description is for internal use only!
See the marketing tab for customer facing information.
{(this.state.product.url) &&
}
);
const mediaItems = (
{(this.state.product.mediaItems.length > 0) && (
Item
Edit
)}
{this.state.product.mediaItems.map((mediaItem, mediaItemIndex) => {
let onMediaItemChanged = function (e, { name, value }) {
e.preventDefault();
let product = this.state.product;
product.mediaItemIds[mediaItemIndex] = value;
product.mediaItems[mediaItemIndex] = _.find(this.state.allMediaItems, { id: value });
this.setState({ product });
}
let onRemoveMediaItem = function (e) {
e.preventDefault();
let product = this.state.product;
product.mediaItemIds.splice(mediaItemIndex, 1);
product.mediaItems.splice(mediaItemIndex, 1);
this.setState({ product });
}
return (
#{mediaItemIndex + 1}
{mediaItem.cdn && }
Remove
)
})}
Add Media Item
)
const roomSettings = (
);
let featuredMediaItemOptions = [
{ "key": "empty", "value": "", "text": "No default selected" }
];
featuredMediaItemOptions = featuredMediaItemOptions.concat(this.state.product.mediaItems.map((mediaItem, mediaItemIndex) => {
return { "key": mediaItem.id, "value": mediaItem.id, "text": mediaItem.title }
}));
const movieNightsInfo = (
Showings Interval - show the movie every X minutes
Use this value to generate "showings" for the movie. A showing is a social movie watching session, where the movie is synchronized for everyone in the session.
{constants.SynchronizationIntervals.map(synchronizationInterval => {
return (
);
})}
Generated Showings (sample)
{this.state.showings.map(showing => {
return (
{formatInTimeZone(fromUnixTime(showing), DEFAULT_TIMEZONE, 'PPPp z')}
)
})}
Experimental!
Starting media item - the media item that starts on time.
Featured media item - the main media item that people paid for.
{featuredMediaItemOptions.map(mediaItemOption => {
return (
);
})}
{featuredMediaItemOptions.map(mediaItemOption => {
return (
);
})}
Generated Events
{this.state.mediaSessionEvents.map(mediaSessionEvent => {
return (
{mediaSessionEvent.eventType} (Timestamp: {mediaSessionEvent.timestampOffset / 1000.0} seconds)
)
})}
);
let marketingInfo =
;
if (this.state.product.marketingData) {
const locale = "en-US";
const selectedMarketingData = this.state.product.marketingData[locale];
const onChangeMarketingData = (e, { name, value }) => {
e.preventDefault();
if (this.state.product) {
const product = this.state.product;
product.marketingData[locale][name] = value;
this.setState({ product });
}
}
const onChangeMarketingDataArray = (e, { name, value }) => {
e.preventDefault();
if (this.state.product) {
const arrayValue = _.split(value, ", ");
const product = this.state.product;
product.marketingData[locale][name] = arrayValue;
this.setState({ product });
}
}
const onChangeImage = (e, { name, value }) => {
e.preventDefault();
if (this.state.product) {
const product = this.state.product;
product.marketingData[locale][name].src = value;
this.setState({ product });
}
}
const on3DChanged = function(e) {
e.preventDefault();
if (this.state.product) {
const product = this.state.product;
product.is3D = !product.is3D;
this.setState({ product });
}
}
marketingInfo = (
Is the customer buying 3D content?
This should only be checked if the "main feature" is 3D. If the main feature is 2D, then set this to false.
Locale Specific Marketing Info: {locale}
Unity poster dimensions (width x height): 170x253
Unity background image dimensions (width x height): 907x679 on a 1024x1024 image (example )
Unity screenshot image (width x height): 1920x800
{selectedMarketingData.unityPosterTexture &&
}
{selectedMarketingData.unityPosterTexture &&
}
);
}
// We have a different sku per location code.
const pricing = (this.state.product.stripeProductData && this.state.product.stripeProductData.skus) && (
Product ID: {this.state.product.stripeProductData.id}
Active?: {this.state.product.stripeProductData.active ? "yes" : "no"}
{!this.state.product.stripeProductData.active && (
Activate Product
)}
Adding new Countries & Prices
To add new countries, first select a pricing profile .
Then add one of the allowed countries. It will pre-fill the default currency and default price for that country, based on the profile.
{constants.PricingProfileOptions.map(profile => {
return (
);
})}
Country
Active?
Amount
{this.state.product.locationCodes.map((locationCode, locationCodeIndex) => {
const country = _.find(constants.CountryOptions, ["key", locationCode]);
const skuIndex = _.findIndex(this.state.product.stripeProductData.skus.data, (sku) => {
return (sku.attributes && sku.attributes.LocationCode && sku.attributes.LocationCode === locationCode);
});
const sku = this.state.product.stripeProductData.skus.data[skuIndex];
const currencyInfo = sku ? constants.Currencies[_.toUpper(sku.currency)] : null;
let pricingProfileCurrency, pricingProfilePrice = null;
// if the currency info hasn't been filled in, let's retreive the defaults from the Pricing Profiles
if(currencyInfo == null){
const pricingProfile = _.find(constants.PricingProfiles[this.state.product.pricingProfileFilter], ["key", locationCode]);
if(pricingProfile){
pricingProfileCurrency = pricingProfile.currency != null ? pricingProfile.currency : null;
pricingProfilePrice = pricingProfile.price != null ? pricingProfile.price : null;
}
}
const onChangeCurrency = async (e, { name, value }) => {
e.preventDefault();
const locationSKUs = this.state.locationSKUs;
if (!locationSKUs[locationCode]) {
locationSKUs[locationCode] = {};
}
locationSKUs[locationCode].selectedCurrency = value;
this.setState({ locationSKUs });
}
const onChangeCurrencyAmount = async (e, { name, value }) => {
e.preventDefault();
const locationSKUs = this.state.locationSKUs;
if (!locationSKUs[locationCode]) {
locationSKUs[locationCode] = {};
}
locationSKUs[locationCode].selectedCurrencyAmount = value;
this.setState({ locationSKUs });
}
const onAddPricing = async (e, { name, value }) => {
e.preventDefault();
const locationSKUs = this.state.locationSKUs;
if (locationSKUs[locationCode]) {
const payload = {
locationCode,
currency: locationSKUs[locationCode].selectedCurrency,
price: locationSKUs[locationCode].selectedCurrencyAmount,
}
try {
// HACK HACK HACK
//
// Save the product first, to update any location changes, then post the sku, then reload it!
await this.onSaveProduct();
this.setState({ loading: true });
const res = await superagent.post(`/api/admin/media/product/${this.state.product.id}/sku`).send(payload).accept('json');
await this.reloadProduct();
} catch (e) {
this.setState({ messages: e.response.body, loading: false });
}
this.setState({ loading: false });
}
}
const onRemovePricing = async (e) => {
e.preventDefault();
let product = this.state.product;
product.stripeProductData.skus.data.splice(skuIndex, 1);
this.setState({ loading: true, product });
try {
const res = await superagent.delete(`/api/admin/media/product/${this.state.product.id}/sku/${sku.id}`);
} catch (e) {
this.setState({ messages: e.response.body, loading: false });
}
this.setState({ loading: false });
}
if (sku) {
return (
Remove
{country && country.text}
{sku.active ? "Active" : "Inactive"}
{(currencyInfo && currencyInfo.icon) ? : }{sku.currency}
{formatPrice(sku.currency, sku.price)}
);
} else {
const locationSKU = this.state.locationSKUs[locationCode];
return (
{country && country.text}
Add Pricing
)
}
})}
);
const panes = [
{ menuItem: 'Details', render: () => {existingProductInfo} {basicProductInfo} },
{ menuItem: 'Media Items', render: () => {mediaItems} },
{ menuItem: 'Room Settings', render: () => {roomSettings} },
{ menuItem: 'Movie Nights', render: () => {movieNightsInfo} },
{ menuItem: 'Marketing', render: () => {marketingInfo} }
];
if (pricing) {
panes.push({ menuItem: 'Pricing', render: () => {pricing} });
}
const tabs = (
);
let activateButton = null;
if (this.state.product.id) {
if (this.state.product.status === "DRAFT") {
if (this.state.product.stripeProductData) {
if (!this.state.product.stripeProductData.active) {
activateButton = Activate Now! ;
}
} else {
activateButton = Activate Now! ;
}
}
}
let activationStatus =
if (this.state.product.id) {
if (this.state.product.mediaItemIds.length === 0) {
activationStatus = (
This product does not have any media items!
);
} else if (this.state.product.status === "DRAFT") {
activationStatus = (
This is a DRAFT. {activateButton}
);
} else if (this.state.product.status === "ACTIVATED") {
activationStatus = (
Activated! You should be able to see this in Bigscreen!
);
} else if (this.state.product.status === "DISABLED") {
activationStatus = (
This product is disabled
);
}
}
let stripeActivationStatus =
if (this.state.product.id && this.state.product.stripeProductData && this.state.product.stripeProductData.skus) {
if (this.state.product.stripeProductData.active) {
stripeActivationStatus = (
Active on Stripe
);
} else {
stripeActivationStatus = (
Not active on Stripe! See "Pricing" tab to activated on Stripe.
);
}
}
return (
{(!this.state.product.id) && }
{(this.state.product.id) && }
{this.state.product.title}
{messages && messages}
);
}
}