mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-11 12:23:20 +00:00
consistent feature locking with lock icon
This commit is contained in:
1 parent
f8bf7c0710
commit
0f0874b20e
4 files changed
+174
-99
No files matched your search
@@ -5,27 +5,26 @@
|
||||
background-color: $panel-inner;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
box-shadow: none !important;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba($panel, 0.8);
|
||||
background-color: rgba($panel-inner, 0.6);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: rgba($white, 0.8);
|
||||
font-size: 16px;
|
||||
color: rgba($white, 0.9);
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
pointer-events: all;
|
||||
border: 1px solid rgba($primary, 0.4);
|
||||
border: 2px solid rgba($primary, 0.4);
|
||||
border-radius: 0.5rem;
|
||||
|
||||
span {
|
||||
text-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ interface PremiumLockProps {
|
||||
session?: any;
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
showCtaButton?: boolean;
|
||||
lockPosition?: "top" | "center";
|
||||
plural?: boolean;
|
||||
}
|
||||
|
||||
export default function PremiumLock({
|
||||
@@ -21,6 +23,8 @@ export default function PremiumLock({
|
||||
sessionSubscriptionsWrapper,
|
||||
showCtaButton = false,
|
||||
large = false,
|
||||
lockPosition = "center",
|
||||
plural = false,
|
||||
}: PremiumLockProps) {
|
||||
const hasAccess = () => {
|
||||
switch (requiredPlan) {
|
||||
@@ -50,40 +54,92 @@ export default function PremiumLock({
|
||||
children
|
||||
) : (
|
||||
<Panel className="fy-premium-lock rounded px-3 py-4">
|
||||
{children}
|
||||
<div className="overlay">
|
||||
<div className="d-flex flex-column align-items-center gap-2 text-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faLock}
|
||||
className={`me-2 ${large ? "fs-4" : "fs-5"}`}
|
||||
/>
|
||||
{requiredPlan === "any" ? (
|
||||
<span className={`${large ? "lead fw-medium" : ""}`}>
|
||||
This feature requires a{" "}
|
||||
<Link
|
||||
to="/pricing"
|
||||
className={`${large ? "lead fw-medium" : ""}`}
|
||||
>
|
||||
subscription plan.
|
||||
</Link>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
This feature requires a{" "}
|
||||
<Link to="/pricing">{requiredPlan} subscription</Link>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{showCtaButton && (
|
||||
<Button
|
||||
variant="primary"
|
||||
label="Upgrade your account"
|
||||
icon={faUp}
|
||||
to="/pricing"
|
||||
className="mt-2"
|
||||
{lockPosition === "center" && children}
|
||||
{lockPosition === "top" && (
|
||||
<div className="mt-5 mb-0">{children}</div>
|
||||
)}
|
||||
<div
|
||||
className={`overlay ${
|
||||
lockPosition === "top"
|
||||
? "align-items-start justify-content-start pt-3"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{lockPosition === "center" ? (
|
||||
<div className="d-flex flex-column align-items-center gap-2 text-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faLock}
|
||||
className={`me-2 ${large ? "fs-4" : "fs-5"}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{requiredPlan === "any" ? (
|
||||
<span className={`${large ? "lead fw-medium" : ""}`}>
|
||||
{plural
|
||||
? "These features require a"
|
||||
: "This feature requires a"}{" "}
|
||||
<Link
|
||||
to="/pricing"
|
||||
className={`${large ? "lead fw-medium" : ""}`}
|
||||
>
|
||||
subscription plan
|
||||
</Link>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
{plural
|
||||
? "These features require a"
|
||||
: "This feature requires a"}{" "}
|
||||
<Link to="/pricing">{requiredPlan} plan</Link>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{showCtaButton && (
|
||||
<Button
|
||||
variant="primary"
|
||||
label="Upgrade your account"
|
||||
icon={faUp}
|
||||
to="/pricing"
|
||||
className="mt-2"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex align-items-center gap-2 text-start justify-content-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faLock}
|
||||
className={`me-1 ${large ? "fs-4" : "fs-7"}`}
|
||||
/>
|
||||
{requiredPlan === "any" ? (
|
||||
<span className={`${large ? "lead fw-medium" : ""}`}>
|
||||
{plural
|
||||
? "These features require a"
|
||||
: "This feature requires a"}{" "}
|
||||
<Link
|
||||
to="/pricing"
|
||||
className={`${large ? "lead fw-medium" : ""}`}
|
||||
>
|
||||
subscription plan
|
||||
</Link>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
<Link className="fw-medium" to="/pricing">
|
||||
Upgrade to {requiredPlan}
|
||||
</Link>{" "}
|
||||
{plural ? "to use these features" : "to use this feature"}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{showCtaButton && (
|
||||
<Button
|
||||
variant="primary"
|
||||
label="Upgrade your account"
|
||||
icon={faUp}
|
||||
to="/pricing"
|
||||
className="mt-2"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Panel>
|
||||
)}
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
faLock,
|
||||
faPlus,
|
||||
faSparkles,
|
||||
faStars,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import "./LivePortrait.scss";
|
||||
import ThumbnailMediaPicker from "./ThumbnailMediaPicker";
|
||||
@@ -53,6 +52,7 @@ import MotionEntityInput from "./MotionEntityInput";
|
||||
import OutputThumbnailImage from "./OutputThumbnailImage";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { JobState } from "@storyteller/components/src/jobs/JobStates";
|
||||
import PremiumLock from "components/PremiumLock";
|
||||
|
||||
interface LivePortraitProps {
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
@@ -110,7 +110,6 @@ export default function LivePortrait({
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
const hasPremium = sessionSubscriptionsWrapper.hasPaidFeatures();
|
||||
const [generatedVideoSrc, setGeneratedVideoSrc] = useState("");
|
||||
const [sourceTokens, setSourceTokens] = useState<string[]>([
|
||||
...PRECOMPUTED_SOURCE_TOKENS,
|
||||
@@ -992,31 +991,40 @@ export default function LivePortrait({
|
||||
</div>
|
||||
|
||||
<div className="d-flex flex-column gap-2">
|
||||
<div className="d-flex gap-3">
|
||||
<Checkbox
|
||||
disabled={!hasPremium}
|
||||
label={"Make Private"}
|
||||
onChange={() => {
|
||||
setVisibility(prevVisibility =>
|
||||
prevVisibility === "private" ? "public" : "private"
|
||||
);
|
||||
}}
|
||||
checked={visibility === "private"}
|
||||
/>
|
||||
<PremiumLock
|
||||
sessionSubscriptionsWrapper={sessionSubscriptionsWrapper}
|
||||
lockPosition="top"
|
||||
requiredPlan="pro"
|
||||
plural={true}
|
||||
>
|
||||
<div className="d-flex gap-3">
|
||||
<Checkbox
|
||||
label={"Make Private"}
|
||||
onChange={() => {
|
||||
setVisibility(prevVisibility =>
|
||||
prevVisibility === "private"
|
||||
? "public"
|
||||
: "private"
|
||||
);
|
||||
}}
|
||||
checked={visibility === "private"}
|
||||
className="mb-0"
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
disabled={!hasPremium}
|
||||
label={"Remove Watermark"}
|
||||
onChange={() => {
|
||||
setRemoveWatermark(
|
||||
prevRemoveWatermark => !prevRemoveWatermark
|
||||
);
|
||||
}}
|
||||
checked={removeWatermark}
|
||||
/>
|
||||
</div>
|
||||
<Checkbox
|
||||
label={"Remove Watermark"}
|
||||
onChange={() => {
|
||||
setRemoveWatermark(
|
||||
prevRemoveWatermark => !prevRemoveWatermark
|
||||
);
|
||||
}}
|
||||
checked={removeWatermark}
|
||||
className="mb-0"
|
||||
/>
|
||||
</div>
|
||||
</PremiumLock>
|
||||
|
||||
{!hasPremium && (
|
||||
{/* {!hasPremium && (
|
||||
<div className="d-flex">
|
||||
<Button
|
||||
variant="link"
|
||||
@@ -1025,7 +1033,7 @@ export default function LivePortrait({
|
||||
to="/pricing"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
EnqueueVSTResponse,
|
||||
} from "@storyteller/components/src/api/workflows/EnqueueVST";
|
||||
import { Prompt } from "@storyteller/components/src/api/prompts/GetPrompts";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import { Link, useHistory, useParams } from "react-router-dom";
|
||||
import { STYLE_OPTIONS, STYLES_BY_KEY } from "common/StyleOptions";
|
||||
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
|
||||
import { StyleSelectionButton } from "./StyleSelection/StyleSelectionButton";
|
||||
@@ -30,7 +30,7 @@ import { isMobile } from "react-device-detect";
|
||||
import { AITools } from "components/marketing";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faLock } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Link } from "react-router-dom";
|
||||
import PremiumLock from "components/PremiumLock";
|
||||
|
||||
export default function StyleVideo() {
|
||||
const { mediaToken: pageMediaToken } = useParams<{ mediaToken: string }>();
|
||||
@@ -52,9 +52,6 @@ export default function StyleVideo() {
|
||||
const { open, modalOpen } = useModal();
|
||||
const history = useHistory();
|
||||
const [enableSignUpBlock] = useState(false);
|
||||
const proOrElite =
|
||||
sessionSubscriptions?.hasActiveProSubscription() ||
|
||||
sessionSubscriptions?.hasActiveEliteSubscription();
|
||||
|
||||
const openStyleSelection = () =>
|
||||
open({
|
||||
@@ -105,9 +102,9 @@ export default function StyleVideo() {
|
||||
} else {
|
||||
// @ts-ignore
|
||||
window.dataLayer.push({
|
||||
"event": "enqueue_failure",
|
||||
"page": "/style-video",
|
||||
"user_id": "$user_id"
|
||||
event: "enqueue_failure",
|
||||
page: "/style-video",
|
||||
user_id: "$user_id",
|
||||
});
|
||||
console.log("Failed to enqueue job", res);
|
||||
}
|
||||
@@ -120,10 +117,14 @@ export default function StyleVideo() {
|
||||
|
||||
const lengthOptions = [
|
||||
{ label: "3 seconds", value: 3000 },
|
||||
{ disabled: !proOrElite, label: "5 seconds", value: 5000 },
|
||||
{ disabled: !proOrElite, label: "7 seconds", value: 7000 },
|
||||
{ label: "5 seconds", value: 5000 },
|
||||
{ label: "7 seconds", value: 7000 },
|
||||
];
|
||||
|
||||
const proOrElite =
|
||||
sessionSubscriptions?.hasActiveProSubscription() ||
|
||||
sessionSubscriptions?.hasActiveEliteSubscription();
|
||||
|
||||
const onPromptUpdate = (prompt: Prompt | null) => {
|
||||
promptSet(prompt?.maybe_positive_prompt || "");
|
||||
negativePromptSet(prompt?.maybe_negative_prompt || "");
|
||||
@@ -487,26 +488,37 @@ export default function StyleVideo() {
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
<SegmentButtons
|
||||
{...{
|
||||
className: "fy-style-video-length mb-1",
|
||||
label: "Video Duration",
|
||||
onChange: ({ target }: { target: any }) => {
|
||||
lengthSet(target.value);
|
||||
},
|
||||
options: lengthOptions,
|
||||
value: length,
|
||||
highlight: true,
|
||||
}}
|
||||
/>
|
||||
<Link
|
||||
{...{
|
||||
className: "fs-7 lh-1",
|
||||
to: "/pricing",
|
||||
}}
|
||||
>
|
||||
Subscribe to Pro or Elite for 5 or 7 second videos
|
||||
</Link>
|
||||
{sessionSubscriptions && (
|
||||
<PremiumLock
|
||||
sessionSubscriptionsWrapper={sessionSubscriptions}
|
||||
requiredPlan="pro"
|
||||
lockPosition="top"
|
||||
>
|
||||
<SegmentButtons
|
||||
{...{
|
||||
className: "fy-style-video-length",
|
||||
label: "Video Duration",
|
||||
onChange: ({ target }: { target: any }) => {
|
||||
lengthSet(target.value);
|
||||
},
|
||||
options: lengthOptions,
|
||||
value: length,
|
||||
highlight: true,
|
||||
}}
|
||||
/>
|
||||
</PremiumLock>
|
||||
)}
|
||||
|
||||
{!proOrElite && (
|
||||
<Link
|
||||
{...{
|
||||
className: "d-flex fs-7 lh-1 pt-3",
|
||||
to: "/pricing",
|
||||
}}
|
||||
>
|
||||
Subscribe to Pro or Elite for 5 or 7 second videos
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
Reference in new issue
Block a user