├── .env.example ├── .eslintrc.cjs ├── .gitignore ├── components.json ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── vite.svg ├── src ├── App.css ├── App.jsx ├── assets │ └── react.svg ├── components │ ├── Widget.jsx │ └── ui │ │ ├── button.jsx │ │ ├── input.jsx │ │ ├── label.jsx │ │ ├── popover.jsx │ │ ├── separator.jsx │ │ └── textarea.jsx ├── index.css ├── index.jsx ├── lib │ └── utils.js ├── main.jsx ├── supabaseClient.js └── web-component.jsx ├── tailwind.config.js └── vite.config.js /.env.example: -------------------------------------------------------------------------------- 1 | VITE_SUPABASE_URL= 2 | VITE_SUPABASE_ANON_KEY= 3 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, es2020: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:react/recommended', 6 | 'plugin:react/jsx-runtime', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 10 | settings: { react: { version: '18.2' } }, 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': 'warn', 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env 26 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": false, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": [ 6 | "./src/*" 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "saas", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@radix-ui/react-label": "^2.1.0", 14 | "@radix-ui/react-popover": "^1.1.1", 15 | "@radix-ui/react-separator": "^1.1.0", 16 | "@radix-ui/react-slot": "^1.1.0", 17 | "@supabase/supabase-js": "^2.44.4", 18 | "class-variance-authority": "^0.7.0", 19 | "clsx": "^2.1.1", 20 | "lucide-react": "^0.403.0", 21 | "react": "^18.2.0", 22 | "react-dom": "^18.2.0", 23 | "tailwind-merge": "^2.4.0", 24 | "tailwindcss-animate": "^1.0.7" 25 | }, 26 | "devDependencies": { 27 | "@types/react": "^18.0.28", 28 | "@types/react-dom": "^18.0.11", 29 | "@vitejs/plugin-react": "^4.0.0", 30 | "autoprefixer": "^10.4.19", 31 | "eslint": "^8.38.0", 32 | "eslint-plugin-react": "^7.32.2", 33 | "eslint-plugin-react-hooks": "^4.6.0", 34 | "eslint-plugin-react-refresh": "^0.3.4", 35 | "postcss": "^8.4.39", 36 | "tailwindcss": "^3.4.4", 37 | "vite": "^4.3.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import reactLogo from "./assets/react.svg"; 3 | import viteLogo from "/vite.svg"; 4 | import "./App.css"; 5 | import { Widget } from "./components/Widget"; 6 | 7 | function App() { 8 | const [count, setCount] = useState(0); 9 | 10 | return ( 11 | <> 12 | 13 | 14 | ); 15 | } 16 | 17 | export default App; 18 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Widget.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "./ui/button"; 2 | import { Label } from "./ui/label"; 3 | import { Input } from "./ui/input"; 4 | import { Textarea } from "./ui/textarea"; 5 | import { Separator } from "@/components/ui/separator"; 6 | import { useState } from "react"; 7 | import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; 8 | import tailwindStyles from "../index.css?inline"; 9 | import supabase from "../supabaseClient"; 10 | 11 | export const Widget = ({ projectId }) => { 12 | const [rating, setRating] = useState(3); 13 | const [submitted, setSubmitted] = useState(false); 14 | 15 | const onSelectStar = (index) => { 16 | setRating(index + 1); 17 | }; 18 | 19 | const submit = async (e) => { 20 | e.preventDefault(); 21 | const form = e.target; 22 | const data = { 23 | p_project_id: projectId, 24 | p_user_name: form.name.value, 25 | p_user_email: form.email.value, 26 | p_message: form.feedback.value, 27 | p_rating: rating, 28 | }; 29 | const { data: returnedData, error } = await supabase.rpc("add_feedback", data); 30 | setSubmitted(true); 31 | console.log(returnedData); 32 | }; 33 | 34 | return ( 35 | <> 36 | 37 |
38 | 39 | 40 | 44 | 45 | 46 | 47 | {submitted ? ( 48 |
49 |

Thank you for your feedback!

50 |

51 | We appreciate your feedback. It helps us improve our product and provide better 52 | service to our customers. 53 |

54 |
55 | ) : ( 56 |
57 |

Send us your feedback

58 |
62 |
63 |
64 | 65 | 69 |
70 |
71 | 72 | 77 |
78 |
79 |
80 | 81 |