Merge branch 'main' into main
This commit is contained in:
commit
f4ecafad02
27
backend/access_token.py
Normal file
27
backend/access_token.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_access_token(access_code: str):
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
url = (
|
||||||
|
"https://backend.buildpicoapps.com/screenshot_to_code/validate_access_token"
|
||||||
|
)
|
||||||
|
data = json.dumps(
|
||||||
|
{
|
||||||
|
"access_code": access_code,
|
||||||
|
"secret": os.environ.get("PICO_BACKEND_SECRET"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
response = await client.post(url, content=data, headers=headers)
|
||||||
|
response_data = response.json()
|
||||||
|
|
||||||
|
if response_data["success"]:
|
||||||
|
print("Access token is valid.")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"Access token validation failed: {response_data['failure_reason']}")
|
||||||
|
return False
|
||||||
@ -1,6 +1,5 @@
|
|||||||
# Load environment variables first
|
# Load environment variables first
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@ -16,6 +15,7 @@ from mock import mock_completion
|
|||||||
from image_generation import create_alt_url_mapping, generate_images
|
from image_generation import create_alt_url_mapping, generate_images
|
||||||
from prompts import assemble_prompt
|
from prompts import assemble_prompt
|
||||||
from routes import screenshot
|
from routes import screenshot
|
||||||
|
from access_token import validate_access_token
|
||||||
|
|
||||||
app = FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
|
app = FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
|
||||||
|
|
||||||
@ -79,6 +79,20 @@ async def stream_code(websocket: WebSocket):
|
|||||||
|
|
||||||
# Get the OpenAI API key from the request. Fall back to environment variable if not provided.
|
# Get the OpenAI API key from the request. Fall back to environment variable if not provided.
|
||||||
# If neither is provided, we throw an error.
|
# If neither is provided, we throw an error.
|
||||||
|
openai_api_key = None
|
||||||
|
if "accessCode" in params and params["accessCode"]:
|
||||||
|
print("Access code - using platform API key")
|
||||||
|
if await validate_access_token(params["accessCode"]):
|
||||||
|
openai_api_key = os.environ.get("PLATFORM_OPENAI_API_KEY")
|
||||||
|
else:
|
||||||
|
await websocket.send_json(
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"value": "Invalid access code or you're out of credits. Please try again.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
else:
|
||||||
if params["openAiApiKey"]:
|
if params["openAiApiKey"]:
|
||||||
openai_api_key = params["openAiApiKey"]
|
openai_api_key = params["openAiApiKey"]
|
||||||
print("Using OpenAI API key from client-side settings dialog")
|
print("Using OpenAI API key from client-side settings dialog")
|
||||||
|
|||||||
@ -21,6 +21,34 @@
|
|||||||
<%- injectHead %>
|
<%- injectHead %>
|
||||||
|
|
||||||
<title>Screenshot to Code</title>
|
<title>Screenshot to Code</title>
|
||||||
|
|
||||||
|
<!-- Open Graph Meta Tags -->
|
||||||
|
<meta property="og:title" content="Screenshot to Code" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Convert any screenshot or design to clean code"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://screenshottocode.com/brand/twitter-summary-card.png"
|
||||||
|
/>
|
||||||
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="628" />
|
||||||
|
<meta property="og:url" content="https://screenshottocode.com" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<!-- Twitter Card tags -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:site" content="@picoapps" />
|
||||||
|
<!-- Keep in sync with og:title, og:description and og:image -->
|
||||||
|
<meta name="twitter:title" content="Screenshot to Code" />
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content="Convert any screenshot or design to clean code"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="twitter:image"
|
||||||
|
content="https://screenshottocode.com/brand/twitter-summary-card.png"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-separator": "^1.0.3",
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
|||||||
BIN
frontend/public/brand/twitter-summary-card.png
Normal file
BIN
frontend/public/brand/twitter-summary-card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
@ -51,12 +51,13 @@ function App() {
|
|||||||
isImageGenerationEnabled: true,
|
isImageGenerationEnabled: true,
|
||||||
editorTheme: EditorTheme.COBALT,
|
editorTheme: EditorTheme.COBALT,
|
||||||
isTermOfServiceAccepted: false,
|
isTermOfServiceAccepted: false,
|
||||||
|
accessCode: null,
|
||||||
},
|
},
|
||||||
"setting"
|
"setting"
|
||||||
);
|
);
|
||||||
const [outputSettings, setOutputSettings] = useState<OutputSettings>({
|
const [outputSettings, setOutputSettings] = useState<OutputSettings>({
|
||||||
css: CSSOption.TAILWIND,
|
css: CSSOption.TAILWIND,
|
||||||
js: JSFrameworkOption.VANILLA,
|
js: JSFrameworkOption.NO_FRAMEWORK,
|
||||||
});
|
});
|
||||||
const [shouldIncludeResultImage, setShouldIncludeResultImage] =
|
const [shouldIncludeResultImage, setShouldIncludeResultImage] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
@ -169,9 +170,8 @@ function App() {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mt-2 dark:bg-black dark:text-white">
|
<div className="mt-2 dark:bg-black dark:text-white">
|
||||||
{IS_RUNNING_ON_CLOUD && <PicoBadge />}
|
{IS_RUNNING_ON_CLOUD && <PicoBadge settings={settings} />}
|
||||||
{IS_RUNNING_ON_CLOUD && (
|
{IS_RUNNING_ON_CLOUD && (
|
||||||
<TermsOfServiceDialog
|
<TermsOfServiceDialog
|
||||||
open={!settings.isTermOfServiceAccepted}
|
open={!settings.isTermOfServiceAccepted}
|
||||||
@ -180,24 +180,23 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
<div className="lg:fixed lg:inset-y-0 lg:z-40 lg:flex lg:w-96 lg:flex-col">
|
<div className="lg:fixed lg:inset-y-0 lg:z-40 lg:flex lg:w-96 lg:flex-col">
|
||||||
<div className="flex grow flex-col gap-y-2 overflow-y-auto border-r border-gray-200 bg-white px-6 dark:bg-zinc-950 dark:text-white">
|
<div className="flex grow flex-col gap-y-2 overflow-y-auto border-r border-gray-200 bg-white px-6 dark:bg-zinc-950 dark:text-white">
|
||||||
<div className="flex items-center justify-between mt-10">
|
<div className="flex items-center justify-between mt-10 mb-2">
|
||||||
<h1 className="text-2xl ">Screenshot to Code</h1>
|
<h1 className="text-2xl ">Screenshot to Code</h1>
|
||||||
<SettingsDialog settings={settings} setSettings={setSettings} />
|
<SettingsDialog settings={settings} setSettings={setSettings} />
|
||||||
</div>
|
</div>
|
||||||
{appState === AppState.INITIAL && (
|
|
||||||
<h2 className="text-sm text-gray-500 mb-2">
|
|
||||||
Drag & drop a screenshot to get started.
|
|
||||||
</h2>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{appState === AppState.INITIAL && (
|
|
||||||
<OutputSettingsSection
|
<OutputSettingsSection
|
||||||
outputSettings={outputSettings}
|
outputSettings={outputSettings}
|
||||||
setOutputSettings={setOutputSettings}
|
setOutputSettings={setOutputSettings}
|
||||||
|
shouldDisableUpdates={
|
||||||
|
appState === AppState.CODING || appState === AppState.CODE_READY
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
{IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && <OnboardingNote />}
|
{IS_RUNNING_ON_CLOUD &&
|
||||||
|
!(settings.openAiApiKey || settings.accessCode) && (
|
||||||
|
<OnboardingNote />
|
||||||
|
)}
|
||||||
|
|
||||||
{(appState === AppState.CODING ||
|
{(appState === AppState.CODING ||
|
||||||
appState === AppState.CODE_READY) && (
|
appState === AppState.CODE_READY) && (
|
||||||
|
|||||||
@ -2,8 +2,15 @@ export function OnboardingNote() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-4 bg-green-700 p-2 rounded text-stone-200 text-sm">
|
<div className="flex flex-col space-y-4 bg-green-700 p-2 rounded text-stone-200 text-sm">
|
||||||
<span>
|
<span>
|
||||||
To use Screenshot to Code, you need an OpenAI API key with GPT4 vision
|
To use Screenshot to Code,{" "}
|
||||||
access.{" "}
|
<a
|
||||||
|
className="inline underline hover:opacity-70"
|
||||||
|
href="https://buy.stripe.com/8wM6sre70gBW1nqaEE"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
buy some credits (100 generations for $36)
|
||||||
|
</a>{" "}
|
||||||
|
or use your own OpenAI API key with GPT4 vision access.{" "}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/abi/screenshot-to-code/blob/main/Troubleshooting.md"
|
href="https://github.com/abi/screenshot-to-code/blob/main/Troubleshooting.md"
|
||||||
className="inline underline hover:opacity-70"
|
className="inline underline hover:opacity-70"
|
||||||
@ -11,18 +18,8 @@ export function OnboardingNote() {
|
|||||||
>
|
>
|
||||||
Follow these instructions to get yourself a key.
|
Follow these instructions to get yourself a key.
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
Then, paste it in the Settings dialog (gear icon above).
|
and paste it in the Settings dialog (gear icon above). Your key is only
|
||||||
</span>
|
stored in your browser. Never stored on our servers.
|
||||||
<span>
|
|
||||||
Your key is only stored in your browser. Never stored on our servers. If
|
|
||||||
you prefer, you can also run this app completely locally.{" "}
|
|
||||||
<a
|
|
||||||
href="https://github.com/abi/screenshot-to-code"
|
|
||||||
className="inline underline hover:opacity-70"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
See the Github project for instructions.
|
|
||||||
</a>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,14 +6,10 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "./ui/select";
|
} from "./ui/select";
|
||||||
import { CSSOption, JSFrameworkOption, OutputSettings } from "../types";
|
import { CSSOption, JSFrameworkOption, OutputSettings } from "../types";
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "./ui/accordion";
|
|
||||||
import { capitalize } from "../lib/utils";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { Label } from "@radix-ui/react-label";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Popover, PopoverTrigger, PopoverContent } from "./ui/popover";
|
||||||
|
|
||||||
function displayCSSOption(option: CSSOption) {
|
function displayCSSOption(option: CSSOption) {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
@ -26,6 +22,17 @@ function displayCSSOption(option: CSSOption) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function displayJSOption(option: JSFrameworkOption) {
|
||||||
|
switch (option) {
|
||||||
|
case JSFrameworkOption.REACT:
|
||||||
|
return "React";
|
||||||
|
case JSFrameworkOption.NO_FRAMEWORK:
|
||||||
|
return "No Framework";
|
||||||
|
default:
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function convertStringToCSSOption(option: string) {
|
function convertStringToCSSOption(option: string) {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case "tailwind":
|
case "tailwind":
|
||||||
@ -37,18 +44,57 @@ function convertStringToCSSOption(option: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateDisplayString(settings: OutputSettings) {
|
||||||
|
if (
|
||||||
|
settings.js === JSFrameworkOption.REACT &&
|
||||||
|
settings.css === CSSOption.TAILWIND
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Generating <span className="font-bold">React</span> +{" "}
|
||||||
|
<span className="font-bold">Tailwind</span> code
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
|
||||||
|
settings.css === CSSOption.TAILWIND
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div className="text-gray-800">
|
||||||
|
Generating <span className="font-bold">HTML</span> +{" "}
|
||||||
|
<span className="font-bold">Tailwind</span> code
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
|
||||||
|
settings.css === CSSOption.BOOTSTRAP
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Generating <span className="font-bold">HTML</span> +{" "}
|
||||||
|
<span className="font-bold">Bootstrap</span> code
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
outputSettings: OutputSettings;
|
outputSettings: OutputSettings;
|
||||||
setOutputSettings: React.Dispatch<React.SetStateAction<OutputSettings>>;
|
setOutputSettings: React.Dispatch<React.SetStateAction<OutputSettings>>;
|
||||||
|
shouldDisableUpdates?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) {
|
function OutputSettingsSection({
|
||||||
|
outputSettings,
|
||||||
|
setOutputSettings,
|
||||||
|
shouldDisableUpdates = false,
|
||||||
|
}: Props) {
|
||||||
const onCSSValueChange = (value: string) => {
|
const onCSSValueChange = (value: string) => {
|
||||||
setOutputSettings((prev) => {
|
setOutputSettings((prev) => {
|
||||||
if (prev.js === JSFrameworkOption.REACT) {
|
if (prev.js === JSFrameworkOption.REACT) {
|
||||||
if (value !== CSSOption.TAILWIND) {
|
if (value !== CSSOption.TAILWIND) {
|
||||||
toast.error(
|
toast.error(
|
||||||
"React only supports Tailwind CSS. Change JS framework to Vanilla to use Bootstrap."
|
'React only supports Tailwind CSS. Change JS framework to "No Framework" to use Bootstrap.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -79,48 +125,76 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion type="single" collapsible className="w-full">
|
<div className="flex flex-col gap-y-2 justify-between text-sm">
|
||||||
<AccordionItem value="item-1">
|
{generateDisplayString(outputSettings)}{" "}
|
||||||
<AccordionTrigger>
|
{!shouldDisableUpdates && (
|
||||||
<div className="flex gap-x-2">Output Settings </div>
|
<Popover>
|
||||||
</AccordionTrigger>
|
<PopoverTrigger asChild>
|
||||||
<AccordionContent className="gap-y-2 flex flex-col pt-2">
|
<Button variant="outline">Customize</Button>
|
||||||
<div className="flex justify-between items-center pr-2">
|
</PopoverTrigger>
|
||||||
<span className="text-sm">CSS</span>
|
<PopoverContent className="w-80 text-sm">
|
||||||
<Select value={outputSettings.css} onValueChange={onCSSValueChange}>
|
<div className="grid gap-4">
|
||||||
<SelectTrigger className="w-[180px]">
|
<div className="space-y-2">
|
||||||
{displayCSSOption(outputSettings.css)}
|
<h4 className="font-medium leading-none">Code Settings</h4>
|
||||||
</SelectTrigger>
|
<p className="text-muted-foreground">
|
||||||
<SelectContent>
|
Customize your code output
|
||||||
<SelectGroup>
|
</p>
|
||||||
<SelectItem value={CSSOption.TAILWIND}>Tailwind</SelectItem>
|
|
||||||
<SelectItem value={CSSOption.BOOTSTRAP}>Bootstrap</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center pr-2">
|
<div className="grid gap-2">
|
||||||
<span className="text-sm">JS Framework</span>
|
<div className="grid grid-cols-3 items-center gap-4">
|
||||||
|
<Label htmlFor="output-settings-js">JS</Label>
|
||||||
<Select
|
<Select
|
||||||
value={outputSettings.js}
|
value={outputSettings.js}
|
||||||
onValueChange={onJsFrameworkChange}
|
onValueChange={onJsFrameworkChange}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[180px]">
|
<SelectTrigger
|
||||||
{capitalize(outputSettings.js)}
|
className="col-span-2 h-8"
|
||||||
|
id="output-settings-js"
|
||||||
|
>
|
||||||
|
{displayJSOption(outputSettings.js)}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectItem value={JSFrameworkOption.VANILLA}>
|
<SelectItem value={JSFrameworkOption.NO_FRAMEWORK}>
|
||||||
Vanilla
|
No Framework
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={JSFrameworkOption.REACT}>
|
||||||
|
React
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value={JSFrameworkOption.REACT}>React</SelectItem>
|
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</AccordionContent>
|
<div className="grid grid-cols-3 items-center gap-4">
|
||||||
</AccordionItem>
|
<Label htmlFor="output-settings-css">CSS</Label>
|
||||||
</Accordion>
|
<Select
|
||||||
|
value={outputSettings.css}
|
||||||
|
onValueChange={onCSSValueChange}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className="col-span-2 h-8"
|
||||||
|
id="output-settings-css"
|
||||||
|
>
|
||||||
|
{displayCSSOption(outputSettings.css)}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value={CSSOption.TAILWIND}>
|
||||||
|
Tailwind
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={CSSOption.BOOTSTRAP}>
|
||||||
|
Bootstrap
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export function PicoBadge() {
|
import { Settings } from "../types";
|
||||||
|
|
||||||
|
export function PicoBadge({ settings }: { settings: Settings }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a
|
<a
|
||||||
@ -12,6 +14,7 @@ export function PicoBadge() {
|
|||||||
feature requests?
|
feature requests?
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
{!settings.accessCode && (
|
||||||
<a href="https://picoapps.xyz?ref=screenshot-to-code" target="_blank">
|
<a href="https://picoapps.xyz?ref=screenshot-to-code" target="_blank">
|
||||||
<div
|
<div
|
||||||
className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black
|
className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black
|
||||||
@ -20,6 +23,17 @@ export function PicoBadge() {
|
|||||||
an open source project by Pico
|
an open source project by Pico
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
|
{settings.accessCode && (
|
||||||
|
<a href="mailto:support@picoapps.xyz" target="_blank">
|
||||||
|
<div
|
||||||
|
className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black
|
||||||
|
bg-white px-4 text-xs py-3 cursor-pointer"
|
||||||
|
>
|
||||||
|
email support
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { Label } from "./ui/label";
|
|||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select";
|
||||||
import { capitalize } from "../lib/utils";
|
import { capitalize } from "../lib/utils";
|
||||||
|
import { IS_RUNNING_ON_CLOUD } from "../config";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
@ -38,6 +39,31 @@ function SettingsDialog({ settings, setSettings }: Props) {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="mb-4">Settings</DialogTitle>
|
<DialogTitle className="mb-4">Settings</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
{/* Access code */}
|
||||||
|
{IS_RUNNING_ON_CLOUD && (
|
||||||
|
<div className="flex flex-col space-y-4 bg-slate-300 p-4 rounded">
|
||||||
|
<Label htmlFor="access-code">
|
||||||
|
<div>Access Code</div>
|
||||||
|
<div className="font-light mt-1 leading-relaxed">
|
||||||
|
Buy an access code.
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
id="access-code"
|
||||||
|
placeholder="Enter your Screenshot to Code access code"
|
||||||
|
value={settings.accessCode || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSettings((s) => ({
|
||||||
|
...s,
|
||||||
|
accessCode: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Label htmlFor="image-generation">
|
<Label htmlFor="image-generation">
|
||||||
<div>DALL-E Placeholder Image Generation</div>
|
<div>DALL-E Placeholder Image Generation</div>
|
||||||
|
|||||||
29
frontend/src/components/ui/popover.tsx
Normal file
29
frontend/src/components/ui/popover.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
|
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||||
|
|
||||||
|
const PopoverContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||||
|
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
))
|
||||||
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent }
|
||||||
@ -9,7 +9,7 @@ export enum CSSOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum JSFrameworkOption {
|
export enum JSFrameworkOption {
|
||||||
VANILLA = "vanilla",
|
NO_FRAMEWORK = "vanilla",
|
||||||
REACT = "react",
|
REACT = "react",
|
||||||
VUE = "vue",
|
VUE = "vue",
|
||||||
}
|
}
|
||||||
@ -25,6 +25,7 @@ export interface Settings {
|
|||||||
isImageGenerationEnabled: boolean;
|
isImageGenerationEnabled: boolean;
|
||||||
editorTheme: EditorTheme;
|
editorTheme: EditorTheme;
|
||||||
isTermOfServiceAccepted: boolean; // Only relevant for hosted version
|
isTermOfServiceAccepted: boolean; // Only relevant for hosted version
|
||||||
|
accessCode: string | null; // Only relevant for hosted version
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppState {
|
export enum AppState {
|
||||||
|
|||||||
@ -805,6 +805,28 @@
|
|||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-primitive" "1.0.3"
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
|
||||||
|
"@radix-ui/react-popover@^1.0.7":
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.7.tgz#23eb7e3327330cb75ec7b4092d685398c1654e3c"
|
||||||
|
integrity sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/primitive" "1.0.1"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.1"
|
||||||
|
"@radix-ui/react-context" "1.0.1"
|
||||||
|
"@radix-ui/react-dismissable-layer" "1.0.5"
|
||||||
|
"@radix-ui/react-focus-guards" "1.0.1"
|
||||||
|
"@radix-ui/react-focus-scope" "1.0.4"
|
||||||
|
"@radix-ui/react-id" "1.0.1"
|
||||||
|
"@radix-ui/react-popper" "1.1.3"
|
||||||
|
"@radix-ui/react-portal" "1.0.4"
|
||||||
|
"@radix-ui/react-presence" "1.0.1"
|
||||||
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
"@radix-ui/react-slot" "1.0.2"
|
||||||
|
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||||
|
aria-hidden "^1.1.1"
|
||||||
|
react-remove-scroll "2.5.5"
|
||||||
|
|
||||||
"@radix-ui/react-popper@1.1.3":
|
"@radix-ui/react-popper@1.1.3":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user