fix: dashboard improvements and query speed improvements
This commit is contained in:
82
apps/start/src/components/organization/feedback-prompt.tsx
Normal file
82
apps/start/src/components/organization/feedback-prompt.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { PromptCard } from '@/components/organization/prompt-card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useAppContext } from '@/hooks/use-app-context';
|
||||
import { useCookieStore } from '@/hooks/use-cookie-store';
|
||||
import { op } from '@/utils/op';
|
||||
import { MessageSquareIcon } from 'lucide-react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
const THIRTY_DAYS_IN_SECONDS = 60 * 60 * 24 * 30;
|
||||
|
||||
export default function FeedbackPrompt() {
|
||||
const { isSelfHosted } = useAppContext();
|
||||
const [feedbackPromptSeen, setFeedbackPromptSeen] = useCookieStore(
|
||||
'feedback-prompt-seen',
|
||||
'',
|
||||
{ maxAge: THIRTY_DAYS_IN_SECONDS },
|
||||
);
|
||||
|
||||
const shouldShow = useMemo(() => {
|
||||
if (isSelfHosted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!feedbackPromptSeen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const lastSeenDate = new Date(feedbackPromptSeen);
|
||||
const now = new Date();
|
||||
const daysSinceLastSeen =
|
||||
(now.getTime() - lastSeenDate.getTime()) / (1000 * 60 * 60 * 24);
|
||||
|
||||
return daysSinceLastSeen >= 30;
|
||||
} catch {
|
||||
// If date parsing fails, show the prompt
|
||||
return true;
|
||||
}
|
||||
}, [isSelfHosted, feedbackPromptSeen]);
|
||||
|
||||
const handleGiveFeedback = () => {
|
||||
// Open userjot widget
|
||||
if (typeof window !== 'undefined' && 'uj' in window) {
|
||||
(window.uj as any).showWidget();
|
||||
}
|
||||
// Set cookie with current timestamp
|
||||
setFeedbackPromptSeen(new Date().toISOString());
|
||||
op.track('feedback_prompt_button_clicked');
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
// Set cookie with current timestamp when closed
|
||||
setFeedbackPromptSeen(new Date().toISOString());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShow) {
|
||||
op.track('feedback_prompt_viewed');
|
||||
}
|
||||
}, [shouldShow]);
|
||||
|
||||
return (
|
||||
<PromptCard
|
||||
title="Share Your Feedback"
|
||||
subtitle="Help us improve OpenPanel with your insights"
|
||||
onClose={handleClose}
|
||||
show={shouldShow}
|
||||
gradientColor="rgb(59 130 246)"
|
||||
>
|
||||
<div className="px-6 col gap-4">
|
||||
<p className="text-sm text-foreground leading-normal">
|
||||
Your feedback helps us build features you actually need. Share your
|
||||
thoughts, report bugs, or suggest improvements
|
||||
</p>
|
||||
|
||||
<Button className="self-start" onClick={handleGiveFeedback}>
|
||||
Give Feedback
|
||||
</Button>
|
||||
</div>
|
||||
</PromptCard>
|
||||
);
|
||||
}
|
||||
66
apps/start/src/components/organization/prompt-card.tsx
Normal file
66
apps/start/src/components/organization/prompt-card.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { XIcon } from 'lucide-react';
|
||||
|
||||
interface PromptCardProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
onClose: () => void;
|
||||
children: React.ReactNode;
|
||||
gradientColor?: string;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
export function PromptCard({
|
||||
title,
|
||||
subtitle,
|
||||
onClose,
|
||||
children,
|
||||
gradientColor = 'rgb(16 185 129)',
|
||||
show,
|
||||
}: PromptCardProps) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{show && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 100, scale: 0.95 }}
|
||||
animate={{ opacity: 1, x: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, x: 100, scale: 0.95 }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 300,
|
||||
damping: 30,
|
||||
}}
|
||||
className="fixed bottom-0 right-0 z-50 p-4 max-w-sm"
|
||||
>
|
||||
<div className="bg-card border rounded-lg shadow-[0_0_100px_50px_rgba(20,20,20,1)] col gap-6 py-6 overflow-hidden">
|
||||
<div className="relative px-6 col gap-1">
|
||||
<div
|
||||
className="absolute -bottom-10 -right-10 h-64 w-64 rounded-full opacity-30 blur-3xl pointer-events-none"
|
||||
style={{
|
||||
background: `radial-gradient(circle, ${gradientColor} 0%, transparent 70%)`,
|
||||
}}
|
||||
/>
|
||||
<div className="row items-center justify-between">
|
||||
<h2 className="text-xl font-semibold max-w-[200px] leading-snug">
|
||||
{title}
|
||||
</h2>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-full"
|
||||
onClick={onClose}
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{subtitle}</p>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Button, LinkButton } from '@/components/ui/button';
|
||||
import { PromptCard } from '@/components/organization/prompt-card';
|
||||
import { LinkButton } from '@/components/ui/button';
|
||||
import { useAppContext } from '@/hooks/use-app-context';
|
||||
import { useCookieStore } from '@/hooks/use-cookie-store';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import {
|
||||
AwardIcon,
|
||||
HeartIcon,
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
MessageCircleIcon,
|
||||
RocketIcon,
|
||||
SparklesIcon,
|
||||
XIcon,
|
||||
ZapIcon,
|
||||
} from 'lucide-react';
|
||||
|
||||
@@ -78,70 +77,43 @@ export default function SupporterPrompt() {
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{!supporterPromptClosed && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 100, scale: 0.95 }}
|
||||
animate={{ opacity: 1, x: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, x: 100, scale: 0.95 }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 300,
|
||||
damping: 30,
|
||||
}}
|
||||
className="fixed bottom-0 right-0 z-50 p-4 max-w-md"
|
||||
<PromptCard
|
||||
title="Support OpenPanel"
|
||||
subtitle="Help us build the future of open analytics"
|
||||
onClose={() => setSupporterPromptClosed(true)}
|
||||
show={!supporterPromptClosed}
|
||||
gradientColor="rgb(16 185 129)"
|
||||
>
|
||||
<div className="col gap-3 px-6">
|
||||
{PERKS.map((perk) => (
|
||||
<PerkPoint
|
||||
key={perk.text}
|
||||
icon={perk.icon}
|
||||
text={perk.text}
|
||||
description={perk.description}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="px-6">
|
||||
<LinkButton
|
||||
className="w-full"
|
||||
href="https://buy.polar.sh/polar_cl_Az1CruNFzQB2bYdMOZmGHqTevW317knWqV44W1FqZmV"
|
||||
>
|
||||
<div className="bg-card border p-6 rounded-lg shadow-lg col gap-4">
|
||||
<div>
|
||||
<div className="row items-center justify-between">
|
||||
<h2 className="text-xl font-semibold">Support OpenPanel</h2>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-full"
|
||||
onClick={() => setSupporterPromptClosed(true)}
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Help us build the future of open analytics
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="col gap-3">
|
||||
{PERKS.map((perk) => (
|
||||
<PerkPoint
|
||||
key={perk.text}
|
||||
icon={perk.icon}
|
||||
text={perk.text}
|
||||
description={perk.description}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="pt-2">
|
||||
<LinkButton
|
||||
className="w-full"
|
||||
href="https://buy.polar.sh/polar_cl_Az1CruNFzQB2bYdMOZmGHqTevW317knWqV44W1FqZmV"
|
||||
>
|
||||
Become a Supporter
|
||||
</LinkButton>
|
||||
<p className="text-xs text-muted-foreground text-center mt-4">
|
||||
Starting at $20/month • Cancel anytime •{' '}
|
||||
<a
|
||||
href="https://openpanel.dev/supporter"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-primary underline-offset-4 hover:underline"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
Become a Supporter
|
||||
</LinkButton>
|
||||
<p className="text-xs text-muted-foreground text-center mt-4">
|
||||
Starting at $20/month • Cancel anytime •{' '}
|
||||
<a
|
||||
href="https://openpanel.dev/supporter"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-primary underline-offset-4 hover:underline"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</PromptCard>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user