mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-12 17:57:50 +02:00
feat: add licensing handling
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useCallback, useState } from "react";
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { showErrorToast } from "@/lib/toast-utils";
|
||||
|
||||
interface CommercialTrialModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function CommercialTrialModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
}: CommercialTrialModalProps) {
|
||||
const [isAcknowledging, setIsAcknowledging] = useState(false);
|
||||
|
||||
const handleAcknowledge = useCallback(async () => {
|
||||
setIsAcknowledging(true);
|
||||
try {
|
||||
await invoke("acknowledge_trial_expiration");
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Failed to acknowledge trial expiration:", error);
|
||||
showErrorToast("Failed to save acknowledgment", {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Please try again",
|
||||
});
|
||||
} finally {
|
||||
setIsAcknowledging(false);
|
||||
}
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen}>
|
||||
<DialogContent
|
||||
className="sm:max-w-md"
|
||||
onEscapeKeyDown={(e) => e.preventDefault()}
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Commercial Trial Expired</DialogTitle>
|
||||
<DialogDescription>
|
||||
Your 2-week commercial trial period has ended.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
If you are using Donut Browser for business purposes, you need to
|
||||
purchase a commercial license to continue.
|
||||
</p>
|
||||
<p className="text-sm font-medium">
|
||||
Personal use remains free and unrestricted.
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Visit our website to learn more about commercial licensing options.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<LoadingButton
|
||||
onClick={handleAcknowledge}
|
||||
isLoading={isAcknowledging}
|
||||
>
|
||||
I Understand
|
||||
</LoadingButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -37,8 +37,10 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useCommercialTrial } from "@/hooks/use-commercial-trial";
|
||||
import type { PermissionType } from "@/hooks/use-permissions";
|
||||
import { usePermissions } from "@/hooks/use-permissions";
|
||||
import { useWayfernTerms } from "@/hooks/use-wayfern-terms";
|
||||
import {
|
||||
getThemeByColors,
|
||||
getThemeById,
|
||||
@@ -115,6 +117,10 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
isMicrophoneAccessGranted,
|
||||
isCameraAccessGranted,
|
||||
} = usePermissions();
|
||||
const { termsAccepted } = useWayfernTerms();
|
||||
const { trialStatus } = useCommercialTrial();
|
||||
const [mcpEnabled, setMcpEnabled] = useState(false);
|
||||
const [isMcpStarting, setIsMcpStarting] = useState(false);
|
||||
|
||||
const getPermissionIcon = useCallback((type: PermissionType) => {
|
||||
switch (type) {
|
||||
@@ -417,6 +423,16 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadMcpServerStatus = useCallback(async () => {
|
||||
try {
|
||||
const isRunning = await invoke<boolean>("get_mcp_server_status");
|
||||
setMcpEnabled(isRunning);
|
||||
} catch (error) {
|
||||
console.error("Failed to load MCP server status:", error);
|
||||
setMcpEnabled(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
// Restore original theme when closing without saving
|
||||
if (originalSettings.theme === "custom" && originalSettings.custom_theme) {
|
||||
@@ -455,6 +471,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
loadSettings().catch(console.error);
|
||||
checkDefaultBrowserStatus().catch(console.error);
|
||||
loadApiServerStatus().catch(console.error);
|
||||
loadMcpServerStatus().catch(console.error);
|
||||
|
||||
// Check if we're on macOS
|
||||
const userAgent = navigator.userAgent;
|
||||
@@ -481,6 +498,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
checkDefaultBrowserStatus,
|
||||
loadSettings,
|
||||
loadApiServerStatus,
|
||||
loadMcpServerStatus,
|
||||
]);
|
||||
|
||||
// Update permissions when the permission states change
|
||||
@@ -1047,6 +1065,100 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Commercial License Section */}
|
||||
<div className="space-y-4">
|
||||
<Label className="text-base font-medium">Commercial License</Label>
|
||||
|
||||
<div className="flex items-center justify-between p-3 rounded-md border bg-muted/40">
|
||||
{trialStatus?.type === "Active" ? (
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium">
|
||||
Trial: {trialStatus.days_remaining} days,{" "}
|
||||
{trialStatus.hours_remaining} hours remaining
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Commercial use is free during the trial period
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-orange-600">
|
||||
Trial expired
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Personal use remains free. Commercial use requires a
|
||||
license.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* MCP Server Section */}
|
||||
<div className="space-y-4">
|
||||
<Label className="text-base font-medium">MCP Server</Label>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="mcp-enabled"
|
||||
checked={mcpEnabled}
|
||||
disabled={!termsAccepted || isMcpStarting}
|
||||
onCheckedChange={async (checked: boolean) => {
|
||||
setIsMcpStarting(true);
|
||||
try {
|
||||
if (checked) {
|
||||
await invoke("start_mcp_server");
|
||||
setMcpEnabled(true);
|
||||
showSuccessToast("MCP server started");
|
||||
} else {
|
||||
await invoke("stop_mcp_server");
|
||||
setMcpEnabled(false);
|
||||
showSuccessToast("MCP server stopped");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to toggle MCP server:", e);
|
||||
showErrorToast("Failed to toggle MCP server", {
|
||||
description:
|
||||
e instanceof Error ? e.message : "Unknown error",
|
||||
});
|
||||
} finally {
|
||||
setIsMcpStarting(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="grid gap-1.5 leading-none">
|
||||
<Label
|
||||
htmlFor="mcp-enabled"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Enable MCP Server (Model Context Protocol)
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Allow AI assistants to control Wayfern and Camoufox browsers
|
||||
via MCP.
|
||||
{!termsAccepted && (
|
||||
<span className="ml-1 text-orange-600">
|
||||
(Accept terms first)
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{mcpEnabled && (
|
||||
<div className="p-3 space-y-2 text-xs rounded-md border bg-muted/40">
|
||||
<div className="font-medium">Available MCP Tools</div>
|
||||
<ul className="list-disc ml-5 space-y-0.5 text-muted-foreground">
|
||||
<li>list_profiles - List Wayfern/Camoufox profiles</li>
|
||||
<li>run_profile - Launch a browser profile</li>
|
||||
<li>kill_profile - Stop a running browser</li>
|
||||
<li>get_profile - Get profile details</li>
|
||||
<li>list_proxies - List configured proxies</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Advanced Section */}
|
||||
<div className="space-y-4">
|
||||
<Label className="text-base font-medium">Advanced</Label>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useCallback, useState } from "react";
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
|
||||
|
||||
interface WayfernTermsDialogProps {
|
||||
isOpen: boolean;
|
||||
onAccepted: () => void;
|
||||
}
|
||||
|
||||
export function WayfernTermsDialog({
|
||||
isOpen,
|
||||
onAccepted,
|
||||
}: WayfernTermsDialogProps) {
|
||||
const [isAccepting, setIsAccepting] = useState(false);
|
||||
|
||||
const handleAccept = useCallback(async () => {
|
||||
setIsAccepting(true);
|
||||
try {
|
||||
await invoke("accept_wayfern_terms");
|
||||
showSuccessToast("Terms accepted successfully");
|
||||
onAccepted();
|
||||
} catch (error) {
|
||||
console.error("Failed to accept terms:", error);
|
||||
showErrorToast("Failed to accept terms", {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Please try again",
|
||||
});
|
||||
} finally {
|
||||
setIsAccepting(false);
|
||||
}
|
||||
}, [onAccepted]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen}>
|
||||
<DialogContent
|
||||
className="sm:max-w-lg"
|
||||
onEscapeKeyDown={(e) => e.preventDefault()}
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Wayfern Terms and Conditions</DialogTitle>
|
||||
<DialogDescription>
|
||||
Before using Donut Browser, you must read and agree to Wayfern's
|
||||
Terms and Conditions.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Please review the Terms and Conditions at:
|
||||
</p>
|
||||
<a
|
||||
href="https://wayfern.com/terms-and-conditions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline text-sm font-medium block"
|
||||
>
|
||||
https://wayfern.com/terms-and-conditions
|
||||
</a>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
By clicking "I Accept", you agree to be bound by these terms.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<LoadingButton onClick={handleAccept} isLoading={isAccepting}>
|
||||
I Accept
|
||||
</LoadingButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user