BannerRlsTester.tsx113 lines · main
| 1 | import { LOCAL_STORAGE_KEYS } from 'common' |
| 2 | import { useParams } from 'common/hooks' |
| 3 | import { AnimatePresence, motion } from 'framer-motion' |
| 4 | import { Check, Loader2, Terminal } from 'lucide-react' |
| 5 | import { useEffect, useState } from 'react' |
| 6 | import { Badge, Button, cn } from 'ui' |
| 7 | |
| 8 | import { BannerCard } from '../BannerCard' |
| 9 | import { useBannerStack } from '../BannerStackProvider' |
| 10 | import { useFeaturePreviewModal } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext' |
| 11 | import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' |
| 12 | |
| 13 | const text = 'select * from colors' |
| 14 | |
| 15 | export 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 | } |