BannerRlsTester.tsx113 lines · main
1import { LOCAL_STORAGE_KEYS } from 'common'
2import { useParams } from 'common/hooks'
3import { AnimatePresence, motion } from 'framer-motion'
4import { Check, Loader2, Terminal } from 'lucide-react'
5import { useEffect, useState } from 'react'
6import { Badge, Button, cn } from 'ui'
7
8import { BannerCard } from '../BannerCard'
9import { useBannerStack } from '../BannerStackProvider'
10import { useFeaturePreviewModal } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
11import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
12
13const text = 'select * from colors'
14
15export const BannerRlsTester = () => {
16 const { ref } = useParams()
17 const { selectFeaturePreview } = useFeaturePreviewModal()
18
19 const [runQueryAnimate, setRunQueryAnimate] = useState(false)
20 const [showSummary, setShowSummary] = useState(false)
21
22 const { dismissBanner } = useBannerStack()
23 const [, setIsDismissed] = useLocalStorageQuery(
24 LOCAL_STORAGE_KEYS.RLS_TESTER_BANNER_DISMISSED(ref ?? ''),
25 false
26 )
27
28 useEffect(() => {
29 setTimeout(() => setRunQueryAnimate(true), 2400)
30 }, [])
31
32 useEffect(() => {
33 if (runQueryAnimate) {
34 setTimeout(() => setShowSummary(true), 1700)
35 }
36 }, [runQueryAnimate])
37
38 return (
39 <BannerCard
40 onDismiss={() => {
41 setIsDismissed(true)
42 dismissBanner('rls-tester-banner')
43 }}
44 >
45 <div className="flex flex-col gap-y-4">
46 <div className="flex flex-col gap-y-2 items-start">
47 <Badge variant="success" className="-ml-0.5 uppercase inline-flex items-center mb-2">
48 Preview
49 </Badge>
50
51 <div className={cn('transition-all bg-surface-100 w-full border rounded-md')}>
52 <div className="flex items-center gap-x-2 p-2">
53 {runQueryAnimate && !showSummary ? (
54 <Loader2 size={12} className="animate-spin" />
55 ) : (
56 <Terminal size={12} />
57 )}
58 <p
59 className="uppercase tracking-tight text-xs font-mono overflow-hidden whitespace-nowrap border-r-2 border-overlay"
60 style={{
61 width: `${text.length}ch`,
62 animation: `typewriter 2s steps(${text.length}) forwards, blink-caret 0.75s step-end infinite`,
63 }}
64 >
65 {text}
66 </p>
67 </div>
68
69 <AnimatePresence>
70 {showSummary && (
71 <motion.div
72 initial={{ height: 0 }}
73 animate={{ height: '50px' }}
74 exit={{ height: 0 }}
75 transition={{
76 type: 'spring',
77 stiffness: 420,
78 damping: 30,
79 mass: 0.4,
80 }}
81 className="border-t text-xs p-2"
82 >
83 <div className="flex items-center gap-x-2">
84 <Check size={12} strokeWidth={3} className="text-brand" />
85 <p>
86 Can access <code className="text-code-inline">public.colors</code>
87 </p>
88 </div>
89 <div>
90 <p className="text-foreground-light mt-0.5 ml-5">2 policies applied</p>
91 </div>
92 </motion.div>
93 )}
94 </AnimatePresence>
95 </div>
96 </div>
97 <div className="flex flex-col gap-y-1 mb-2">
98 <p className="text-sm font-medium">Row Level Security (RLS) Tester</p>
99 <p className="text-xs text-foreground-lighter text-balance">
100 Verify your RLS policies are correct by running queries as a specific user
101 </p>
102 </div>
103 <Button
104 type="default"
105 className="w-min"
106 onClick={() => selectFeaturePreview(LOCAL_STORAGE_KEYS.UI_PREVIEW_RLS_TESTER)}
107 >
108 Enable feature preview
109 </Button>
110 </div>
111 </BannerCard>
112 )
113}