mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-05 05:39:11 +00:00
Add a one-click action to export all conversations. Add a self-service delete account action to the settings page
This commit is contained in:
@@ -22,6 +22,10 @@ import {
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
AlertDialog, AlertDialogAction, AlertDialogCancel,
|
||||
AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
||||
|
||||
import {
|
||||
@@ -54,6 +58,8 @@ import {
|
||||
Brain,
|
||||
EyeSlash,
|
||||
Eye,
|
||||
Download,
|
||||
TrashSimple,
|
||||
} from "@phosphor-icons/react";
|
||||
|
||||
import Loading from "../components/loading/loading";
|
||||
@@ -63,6 +69,10 @@ import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/s
|
||||
import { AppSidebar } from "../components/appSidebar/appSidebar";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { KhojLogoType } from "../components/logo/khojLogo";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
|
||||
import JSZip from "jszip";
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
interface DropdownComponentProps {
|
||||
items: ModelOptions[];
|
||||
@@ -296,6 +306,10 @@ export default function SettingsView() {
|
||||
const [numberValidationState, setNumberValidationState] = useState<PhoneNumberValidationState>(
|
||||
PhoneNumberValidationState.Verified,
|
||||
);
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
const [exportProgress, setExportProgress] = useState(0);
|
||||
const [exportedConversations, setExportedConversations] = useState(0);
|
||||
const [totalConversations, setTotalConversations] = useState(0);
|
||||
const { toast } = useToast();
|
||||
const isMobileWidth = useIsMobileWidth();
|
||||
|
||||
@@ -311,8 +325,8 @@ export default function SettingsView() {
|
||||
initialUserConfig?.is_phone_number_verified
|
||||
? PhoneNumberValidationState.Verified
|
||||
: initialUserConfig?.phone_number
|
||||
? PhoneNumberValidationState.SendOTP
|
||||
: PhoneNumberValidationState.Setup,
|
||||
? PhoneNumberValidationState.SendOTP
|
||||
: PhoneNumberValidationState.Setup,
|
||||
);
|
||||
setName(initialUserConfig?.given_name);
|
||||
setNotionToken(initialUserConfig?.notion_token ?? null);
|
||||
@@ -538,6 +552,56 @@ export default function SettingsView() {
|
||||
}
|
||||
};
|
||||
|
||||
const exportChats = async () => {
|
||||
try {
|
||||
setIsExporting(true);
|
||||
|
||||
// Get total conversation count
|
||||
const statsResponse = await fetch('/api/chat/stats');
|
||||
const stats = await statsResponse.json();
|
||||
const total = stats.num_conversations;
|
||||
setTotalConversations(total);
|
||||
|
||||
// Create zip file
|
||||
const zip = new JSZip();
|
||||
const conversations = [];
|
||||
|
||||
// Fetch all conversations in batches of 10
|
||||
for (let page = 0; page * 10 < total; page++) {
|
||||
const response = await fetch(`/api/chat/export?page=${page}`);
|
||||
const data = await response.json();
|
||||
conversations.push(...data);
|
||||
|
||||
setExportedConversations((page + 1) * 10);
|
||||
setExportProgress(((page + 1) * 10 / total) * 100);
|
||||
}
|
||||
|
||||
// Add conversations to zip
|
||||
zip.file("conversations.json", JSON.stringify(conversations, null, 2));
|
||||
|
||||
// Generate and download zip
|
||||
const content = await zip.generateAsync({ type: "blob" });
|
||||
saveAs(content, "khoj-conversations.zip");
|
||||
|
||||
toast({
|
||||
title: "Export Complete",
|
||||
description: `Successfully exported ${conversations.length} conversations`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error exporting chats:", error);
|
||||
toast({
|
||||
title: "Export Failed",
|
||||
description: "Failed to export chats. Please try again.",
|
||||
variant: "destructive"
|
||||
});
|
||||
} finally {
|
||||
setIsExporting(false);
|
||||
setExportProgress(0);
|
||||
setExportedConversations(0);
|
||||
setTotalConversations(0);
|
||||
}
|
||||
};
|
||||
|
||||
const saveNotionToken = async () => {
|
||||
if (!notionToken) return;
|
||||
// Save Notion API key to server
|
||||
@@ -731,93 +795,93 @@ export default function SettingsView() {
|
||||
)) ||
|
||||
(userConfig.subscription_state ===
|
||||
"subscribed" && (
|
||||
<>
|
||||
<p className="text-xl text-primary/80">
|
||||
Futurist
|
||||
</p>
|
||||
<p className="text-gray-400">
|
||||
Subscription <b>renews</b> on{" "}
|
||||
<b>
|
||||
{
|
||||
userConfig.subscription_renewal_date
|
||||
}
|
||||
</b>
|
||||
</p>
|
||||
</>
|
||||
)) ||
|
||||
(userConfig.subscription_state ===
|
||||
"unsubscribed" && (
|
||||
<>
|
||||
<p className="text-xl">Futurist</p>
|
||||
<p className="text-gray-400">
|
||||
Subscription <b>ends</b> on{" "}
|
||||
<b>
|
||||
{
|
||||
userConfig.subscription_renewal_date
|
||||
}
|
||||
</b>
|
||||
</p>
|
||||
</>
|
||||
)) ||
|
||||
(userConfig.subscription_state ===
|
||||
"expired" && (
|
||||
<>
|
||||
<p className="text-xl">Humanist</p>
|
||||
{(userConfig.subscription_renewal_date && (
|
||||
<>
|
||||
<p className="text-xl text-primary/80">
|
||||
Futurist
|
||||
</p>
|
||||
<p className="text-gray-400">
|
||||
Subscription <b>expired</b>{" "}
|
||||
on{" "}
|
||||
Subscription <b>renews</b> on{" "}
|
||||
<b>
|
||||
{
|
||||
userConfig.subscription_renewal_date
|
||||
}
|
||||
</b>
|
||||
</p>
|
||||
)) || (
|
||||
</>
|
||||
)) ||
|
||||
(userConfig.subscription_state ===
|
||||
"unsubscribed" && (
|
||||
<>
|
||||
<p className="text-xl">Futurist</p>
|
||||
<p className="text-gray-400">
|
||||
Check{" "}
|
||||
<a
|
||||
href="https://khoj.dev/#pricing"
|
||||
target="_blank"
|
||||
>
|
||||
pricing page
|
||||
</a>{" "}
|
||||
to compare plans.
|
||||
Subscription <b>ends</b> on{" "}
|
||||
<b>
|
||||
{
|
||||
userConfig.subscription_renewal_date
|
||||
}
|
||||
</b>
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
)) ||
|
||||
(userConfig.subscription_state ===
|
||||
"expired" && (
|
||||
<>
|
||||
<p className="text-xl">Humanist</p>
|
||||
{(userConfig.subscription_renewal_date && (
|
||||
<p className="text-gray-400">
|
||||
Subscription <b>expired</b>{" "}
|
||||
on{" "}
|
||||
<b>
|
||||
{
|
||||
userConfig.subscription_renewal_date
|
||||
}
|
||||
</b>
|
||||
</p>
|
||||
)) || (
|
||||
<p className="text-gray-400">
|
||||
Check{" "}
|
||||
<a
|
||||
href="https://khoj.dev/#pricing"
|
||||
target="_blank"
|
||||
>
|
||||
pricing page
|
||||
</a>{" "}
|
||||
to compare plans.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-wrap gap-4">
|
||||
{(userConfig.subscription_state ==
|
||||
"subscribed" && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hover:text-red-400"
|
||||
onClick={() =>
|
||||
setSubscription("cancel")
|
||||
}
|
||||
>
|
||||
<ArrowCircleDown className="h-5 w-5 mr-2" />
|
||||
Unsubscribe
|
||||
</Button>
|
||||
)) ||
|
||||
(userConfig.subscription_state ==
|
||||
"unsubscribed" && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-primary/80 hover:text-primary"
|
||||
className="hover:text-red-400"
|
||||
onClick={() =>
|
||||
setSubscription("resubscribe")
|
||||
setSubscription("cancel")
|
||||
}
|
||||
>
|
||||
<ArrowCircleUp
|
||||
weight="bold"
|
||||
className="h-5 w-5 mr-2"
|
||||
/>
|
||||
Resubscribe
|
||||
<ArrowCircleDown className="h-5 w-5 mr-2" />
|
||||
Unsubscribe
|
||||
</Button>
|
||||
)) ||
|
||||
(userConfig.subscription_state ==
|
||||
"unsubscribed" && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-primary/80 hover:text-primary"
|
||||
onClick={() =>
|
||||
setSubscription("resubscribe")
|
||||
}
|
||||
>
|
||||
<ArrowCircleUp
|
||||
weight="bold"
|
||||
className="h-5 w-5 mr-2"
|
||||
/>
|
||||
Resubscribe
|
||||
</Button>
|
||||
)) ||
|
||||
(userConfig.subscription_enabled_trial_at && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -907,16 +971,16 @@ export default function SettingsView() {
|
||||
<Button variant="outline" size="sm">
|
||||
{(userConfig.enabled_content_source
|
||||
.github && (
|
||||
<>
|
||||
<Files className="h-5 w-5 inline mr-1" />
|
||||
Manage
|
||||
</>
|
||||
)) || (
|
||||
<>
|
||||
<Plugs className="h-5 w-5 inline mr-1" />
|
||||
Connect
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<Files className="h-5 w-5 inline mr-1" />
|
||||
Manage
|
||||
</>
|
||||
)) || (
|
||||
<>
|
||||
<Plugs className="h-5 w-5 inline mr-1" />
|
||||
Connect
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -958,8 +1022,8 @@ export default function SettingsView() {
|
||||
{
|
||||
/* Show connect to notion button if notion oauth url setup and user disconnected*/
|
||||
userConfig.notion_oauth_url &&
|
||||
!userConfig.enabled_content_source
|
||||
.notion ? (
|
||||
!userConfig.enabled_content_source
|
||||
.notion ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -973,39 +1037,39 @@ export default function SettingsView() {
|
||||
Connect
|
||||
</Button>
|
||||
) : /* Show sync button if user connected to notion and API key unchanged */
|
||||
userConfig.enabled_content_source.notion &&
|
||||
notionToken ===
|
||||
userConfig.notion_token ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
syncContent("notion")
|
||||
}
|
||||
>
|
||||
<ArrowsClockwise className="h-5 w-5 inline mr-1" />
|
||||
Sync
|
||||
</Button>
|
||||
) : /* Show set API key button notion oauth url not set setup */
|
||||
!userConfig.notion_oauth_url ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={saveNotionToken}
|
||||
disabled={
|
||||
notionToken ===
|
||||
userConfig.notion_token
|
||||
}
|
||||
>
|
||||
<FloppyDisk className="h-5 w-5 inline mr-1" />
|
||||
{(userConfig.enabled_content_source
|
||||
.notion &&
|
||||
"Update API Key") ||
|
||||
"Set API Key"}
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
userConfig.enabled_content_source.notion &&
|
||||
notionToken ===
|
||||
userConfig.notion_token ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
syncContent("notion")
|
||||
}
|
||||
>
|
||||
<ArrowsClockwise className="h-5 w-5 inline mr-1" />
|
||||
Sync
|
||||
</Button>
|
||||
) : /* Show set API key button notion oauth url not set setup */
|
||||
!userConfig.notion_oauth_url ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={saveNotionToken}
|
||||
disabled={
|
||||
notionToken ===
|
||||
userConfig.notion_token
|
||||
}
|
||||
>
|
||||
<FloppyDisk className="h-5 w-5 inline mr-1" />
|
||||
{(userConfig.enabled_content_source
|
||||
.notion &&
|
||||
"Update API Key") ||
|
||||
"Set API Key"}
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -1121,18 +1185,18 @@ export default function SettingsView() {
|
||||
Chat on Whatsapp
|
||||
{(numberValidationState ===
|
||||
PhoneNumberValidationState.Verified && (
|
||||
<CheckCircle
|
||||
weight="bold"
|
||||
className="h-4 w-4 ml-1 text-green-400"
|
||||
/>
|
||||
)) ||
|
||||
<CheckCircle
|
||||
weight="bold"
|
||||
className="h-4 w-4 ml-1 text-green-400"
|
||||
/>
|
||||
)) ||
|
||||
(numberValidationState !==
|
||||
PhoneNumberValidationState.Setup && (
|
||||
<ExclamationMark
|
||||
weight="bold"
|
||||
className="h-4 w-4 ml-1 text-yellow-400"
|
||||
/>
|
||||
))}
|
||||
<ExclamationMark
|
||||
weight="bold"
|
||||
className="h-4 w-4 ml-1 text-yellow-400"
|
||||
/>
|
||||
))}
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4">
|
||||
<p className="text-gray-400">
|
||||
@@ -1161,90 +1225,192 @@ export default function SettingsView() {
|
||||
/>
|
||||
{numberValidationState ===
|
||||
PhoneNumberValidationState.VerifyOTP && (
|
||||
<>
|
||||
<p>{`Enter the OTP sent to your number: ${phoneNumber}`}</p>
|
||||
<InputOTP
|
||||
autoFocus={true}
|
||||
maxLength={6}
|
||||
value={otp || ""}
|
||||
onChange={setOTP}
|
||||
onComplete={() =>
|
||||
setNumberValidationState(
|
||||
PhoneNumberValidationState.VerifyOTP,
|
||||
)
|
||||
}
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<p>{`Enter the OTP sent to your number: ${phoneNumber}`}</p>
|
||||
<InputOTP
|
||||
autoFocus={true}
|
||||
maxLength={6}
|
||||
value={otp || ""}
|
||||
onChange={setOTP}
|
||||
onComplete={() =>
|
||||
setNumberValidationState(
|
||||
PhoneNumberValidationState.VerifyOTP,
|
||||
)
|
||||
}
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-wrap gap-4">
|
||||
{(numberValidationState ===
|
||||
PhoneNumberValidationState.VerifyOTP && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={verifyOTP}
|
||||
>
|
||||
Verify
|
||||
</Button>
|
||||
)) || (
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={
|
||||
!phoneNumber ||
|
||||
(phoneNumber ===
|
||||
userConfig.phone_number &&
|
||||
numberValidationState ===
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={verifyOTP}
|
||||
>
|
||||
Verify
|
||||
</Button>
|
||||
)) || (
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={
|
||||
!phoneNumber ||
|
||||
(phoneNumber ===
|
||||
userConfig.phone_number &&
|
||||
numberValidationState ===
|
||||
PhoneNumberValidationState.Verified) ||
|
||||
!isValidPhoneNumber(phoneNumber)
|
||||
}
|
||||
onClick={sendOTP}
|
||||
>
|
||||
{!userConfig.phone_number ? (
|
||||
<>
|
||||
<Plugs className="inline mr-2" />
|
||||
Setup Whatsapp
|
||||
</>
|
||||
) : !phoneNumber ||
|
||||
(phoneNumber ===
|
||||
userConfig.phone_number &&
|
||||
numberValidationState ===
|
||||
PhoneNumberValidationState.Verified) ||
|
||||
!isValidPhoneNumber(phoneNumber) ? (
|
||||
<>
|
||||
<PlugsConnected className="inline mr-2 text-green-400" />
|
||||
Switch Number
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Send OTP{" "}
|
||||
<ArrowRight
|
||||
className="inline ml-2"
|
||||
weight="bold"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
!isValidPhoneNumber(phoneNumber)
|
||||
}
|
||||
onClick={sendOTP}
|
||||
>
|
||||
{!userConfig.phone_number ? (
|
||||
<>
|
||||
<Plugs className="inline mr-2" />
|
||||
Setup Whatsapp
|
||||
</>
|
||||
) : !phoneNumber ||
|
||||
(phoneNumber ===
|
||||
userConfig.phone_number &&
|
||||
numberValidationState ===
|
||||
PhoneNumberValidationState.Verified) ||
|
||||
!isValidPhoneNumber(phoneNumber) ? (
|
||||
<>
|
||||
<PlugsConnected className="inline mr-2 text-green-400" />
|
||||
Switch Number
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Send OTP{" "}
|
||||
<ArrowRight
|
||||
className="inline ml-2"
|
||||
weight="bold"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
{numberValidationState ===
|
||||
PhoneNumberValidationState.Verified && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => disconnectNumber()}
|
||||
>
|
||||
<CloudSlash className="h-5 w-5 mr-2" />
|
||||
Disconnect
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => disconnectNumber()}
|
||||
>
|
||||
<CloudSlash className="h-5 w-5 mr-2" />
|
||||
Disconnect
|
||||
</Button>
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section grid gap-8">
|
||||
<div id="clients" className="text-2xl">
|
||||
Account
|
||||
</div>
|
||||
<div className="cards flex flex-wrap gap-16">
|
||||
<Card className={cardClassName}>
|
||||
<CardHeader className="text-xl flex flex-row">
|
||||
<Download className="h-7 w-7 mr-2" />
|
||||
Export Data
|
||||
</CardHeader>
|
||||
<CardContent className="overflow-hidden">
|
||||
<p className="pb-4 text-gray-400">
|
||||
Download all your chat conversations
|
||||
</p>
|
||||
{exportProgress > 0 && (
|
||||
<div className="w-full mt-4">
|
||||
<Progress value={exportProgress} className="w-full" />
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
Exported {exportedConversations} of {totalConversations} conversations
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-wrap gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={exportChats}
|
||||
disabled={isExporting}
|
||||
>
|
||||
<Download className="h-5 w-5 mr-2" />
|
||||
{isExporting ? "Exporting..." : "Export Chats"}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card className={cardClassName}>
|
||||
<CardHeader className="text-xl flex flex-row">
|
||||
<TrashSimple className="h-7 w-7 mr-2 text-red-500" />
|
||||
Delete Account
|
||||
</CardHeader>
|
||||
<CardContent className="overflow-hidden">
|
||||
<p className="pb-4 text-gray-400">
|
||||
This will delete all your account data, including conversations, agents, and any assets you{"'"}ve generated. Be sure to export before you do this if you want to keep your information.
|
||||
</p>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-wrap gap-4">
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-red-500 hover:text-red-600 hover:bg-red-50"
|
||||
>
|
||||
<TrashSimple className="h-5 w-5 mr-2" />
|
||||
Delete Account
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action is irreversible. This will permanently delete your account
|
||||
and remove all your data from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="bg-red-500 hover:bg-red-600"
|
||||
onClick={async () => {
|
||||
try {
|
||||
const response = await fetch('/api/self', {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to delete account');
|
||||
|
||||
toast({
|
||||
title: "Account Deleted",
|
||||
description: "Your account has been successfully deleted.",
|
||||
});
|
||||
|
||||
// Redirect to home page after successful deletion
|
||||
window.location.href = "/";
|
||||
} catch (error) {
|
||||
console.error('Error deleting account:', error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to delete account. Please try again or contact support.",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TrashSimple className="h-5 w-5 mr-2" />
|
||||
Delete Account
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.6",
|
||||
"@radix-ui/themes": "^3.1.1",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -53,9 +54,11 @@
|
||||
"embla-carousel-react": "^8.5.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"framer-motion": "^12.0.6",
|
||||
"input-otp": "^1.2.4",
|
||||
"intl-tel-input": "^23.8.1",
|
||||
"jszip": "^3.10.1",
|
||||
"katex": "^0.16.21",
|
||||
"libphonenumber-js": "^1.11.4",
|
||||
"lucide-react": "^0.468.0",
|
||||
|
||||
@@ -1185,6 +1185,11 @@
|
||||
dependencies:
|
||||
dompurify "*"
|
||||
|
||||
"@types/file-saver@^2.0.7":
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.7.tgz#8dbb2f24bdc7486c54aa854eb414940bbd056f7d"
|
||||
integrity sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==
|
||||
|
||||
"@types/geojson@*":
|
||||
version "7946.0.16"
|
||||
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a"
|
||||
@@ -1770,6 +1775,11 @@ confbox@^0.2.1:
|
||||
resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.1.tgz#ae39f2c99699afa451d00206479f15f9a1208a8b"
|
||||
integrity sha512-hkT3yDPFbs95mNCy1+7qNKC6Pro+/ibzYxtM2iqEigpf0sVw+bg4Zh9/snjsBcf990vfIsg5+1U7VyiyBb3etg==
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
|
||||
|
||||
cose-base@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-1.0.3.tgz#650334b41b869578a543358b80cda7e0abe0a60a"
|
||||
@@ -2739,6 +2749,11 @@ file-entry-cache@^6.0.1:
|
||||
dependencies:
|
||||
flat-cache "^3.0.4"
|
||||
|
||||
file-saver@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
|
||||
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
|
||||
|
||||
fill-range@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
||||
@@ -3065,6 +3080,11 @@ ignore@^5.2.0:
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
|
||||
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
|
||||
|
||||
immediate@~3.0.5:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
|
||||
|
||||
import-fresh@^3.2.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf"
|
||||
@@ -3086,7 +3106,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2:
|
||||
inherits@2, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@@ -3343,6 +3363,11 @@ isarray@^2.0.5:
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
@@ -3427,6 +3452,16 @@ json5@^1.0.2:
|
||||
object.assign "^4.1.4"
|
||||
object.values "^1.1.6"
|
||||
|
||||
jszip@^3.10.1:
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
|
||||
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
|
||||
dependencies:
|
||||
lie "~3.3.0"
|
||||
pako "~1.0.2"
|
||||
readable-stream "~2.3.6"
|
||||
setimmediate "^1.0.5"
|
||||
|
||||
katex@^0.16.21, katex@^0.16.9:
|
||||
version "0.16.21"
|
||||
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.21.tgz#8f63c659e931b210139691f2cc7bb35166b792a3"
|
||||
@@ -3497,6 +3532,13 @@ libphonenumber-js@^1.11.4:
|
||||
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.12.5.tgz#8e6043a67112d4beedb8627b359a613f04d88fba"
|
||||
integrity sha512-DOjiaVjjSmap12ztyb4QgoFmUe/GbgnEXHu+R7iowk0lzDIjScvPAm8cK9RYTEobbRb0OPlwlZUGTTJPJg13Kw==
|
||||
|
||||
lie@~3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
|
||||
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
|
||||
dependencies:
|
||||
immediate "~3.0.5"
|
||||
|
||||
lilconfig@^3.0.0, lilconfig@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4"
|
||||
@@ -3960,6 +4002,11 @@ package-manager-detector@^0.2.8:
|
||||
dependencies:
|
||||
quansync "^0.2.7"
|
||||
|
||||
pako@~1.0.2:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
@@ -4160,6 +4207,11 @@ prettier@3.3.3:
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"
|
||||
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
@@ -4309,6 +4361,19 @@ read-cache@^1.0.0:
|
||||
dependencies:
|
||||
pify "^2.3.0"
|
||||
|
||||
readable-stream@~2.3.6:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
|
||||
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readdirp@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||
@@ -4433,6 +4498,11 @@ safe-array-concat@^1.1.3:
|
||||
has-symbols "^1.1.0"
|
||||
isarray "^2.0.5"
|
||||
|
||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-push-apply@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5"
|
||||
@@ -4503,6 +4573,11 @@ set-proto@^1.0.0:
|
||||
es-errors "^1.3.0"
|
||||
es-object-atoms "^1.0.0"
|
||||
|
||||
setimmediate@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
|
||||
|
||||
shadcn-ui@^0.9.0:
|
||||
version "0.9.5"
|
||||
resolved "https://registry.yarnpkg.com/shadcn-ui/-/shadcn-ui-0.9.5.tgz#b7f35b78f2c7fe0b71651fe542ed62f01795b4a6"
|
||||
@@ -4616,6 +4691,7 @@ string-argv@^0.3.2:
|
||||
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
||||
name string-width-cjs
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -4710,6 +4786,13 @@ string.prototype.trimstart@^1.0.8:
|
||||
define-properties "^1.2.1"
|
||||
es-object-atoms "^1.0.0"
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
@@ -5043,7 +5126,7 @@ use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc"
|
||||
integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==
|
||||
|
||||
util-deprecate@^1.0.2:
|
||||
util-deprecate@^1.0.2, util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
Reference in New Issue
Block a user