├── .dockerignore ├── .env.example ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── app ├── actions │ ├── Ai.ts │ ├── createProject.ts │ ├── deleteFeedback.ts │ ├── deleteProject.ts │ ├── getFeedback.ts │ └── getProject.ts ├── api │ ├── auth │ │ └── [...nextauth] │ │ │ └── route.ts │ ├── feedback │ │ └── route.ts │ └── user │ │ └── route.ts ├── docs │ └── page.tsx ├── favicon.ico ├── feedbacks │ └── [id] │ │ └── page.tsx ├── globals.css ├── layout.tsx ├── page.tsx ├── privacy │ └── page.tsx ├── projects │ └── page.tsx ├── provider.tsx ├── signin │ └── page.tsx └── terms │ └── page.tsx ├── components.json ├── components ├── Accordion.tsx ├── Features.tsx ├── Footer.tsx ├── Hero.tsx ├── HowItWork.tsx ├── MarqueeDemo.tsx ├── MarqueeFeedback.tsx ├── NavLink.tsx ├── Navigaton.tsx ├── Project-Card.tsx ├── Provider │ └── LenisProvider.tsx ├── ThemeToggle.tsx ├── create-project-dialog.tsx ├── embedCode.tsx ├── theme-provider.tsx └── ui │ ├── Clip-loader.tsx │ ├── FeedbackWidget.tsx │ ├── accordion.tsx │ ├── badge.tsx │ ├── button.tsx │ ├── card.tsx │ ├── checkbox.tsx │ ├── code-block.tsx │ ├── dialog.tsx │ ├── input.tsx │ ├── label.tsx │ ├── marquee.tsx │ ├── meteors.tsx │ ├── select.tsx │ ├── table.tsx │ ├── tabs.tsx │ ├── text-reveal.tsx │ └── textarea.tsx ├── eslint.config.mjs ├── lib ├── auth.ts ├── db.ts ├── utils.ts └── utils.tsx ├── middelware.ts ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── prisma ├── migrations │ ├── 20250105090005_auth │ │ └── migration.sql │ ├── 20250105174211_auth │ │ └── migration.sql │ └── migration_lock.toml └── schema.prisma ├── tailwind.config.ts └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL="postgresql://postgres:mysecretpassword@localhost:5432/postgres?sslmode=disable" 2 | NEXTAUTH_URL=http://localhost:3000 3 | NEXTAUTH_SECRET=password_nextauth 4 | GOOGLE_ID = 5 | GOOGLE_SECRET = 6 | GOOGLE_API_KEY= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [1.0.0] - 2025-01-14 6 | 7 | ### Added 8 | - **Initial Release**: Complete implementation of FeedWall with the following features: 9 | - **Feedback Widget**: Embed feedback widgets into user projects. 10 | - **Feedback Management**: View, sort, and filter feedback by name, date, and rating. 11 | - **AI-Powered Summaries**: AI-generated pros and cons for feedback. 12 | - **Showcase & Embed Code**: Select feedback to showcase with an embed code. 13 | - **Frontend**: Built with Next.js, TailwindCSS. 14 | - **Backend**: Managed with Next.js API, Prisma, and PostgreSQL. 15 | - **Deployment**: Deployed on Vercel. 16 | 17 | ### Changed 18 | - Improved mobile responsiveness using TailwindCSS. 19 | - Optimized feedback loading and sorting for faster access. 20 | - Enhanced AI-driven feedback summarization. 21 | 22 | ## Future Updates 23 | 24 | - **Integration of New Features**: Adding new feedback sorting algorithms (Planned). 25 | - **Enhanced AI Feedback Summaries**: Improved feedback summarization (Planned). 26 | 27 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | pankajams1234@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing to Feed-Wall 3 | 4 | We welcome contributions to **Feed-Wall** and appreciate the effort made by every contributor. If you're interested in improving the project, here's how you can get involved: 5 | 6 | ## Table of Contents 7 | - [How to Contribute](#how-to-contribute) 8 | - [Contributing Guidelines](#contributing-guidelines) 9 | - [Suggested Contributions](#suggested-contributions) 10 | - [Code of Conduct](#code-of-conduct) 11 | 12 | ## How to Contribute 13 | 14 | ### Fork the Repository 15 | 1. Fork the repository by clicking the "Fork" button on the [FlowPay GitHub page](https://github.com/PankajKumardev/Flowpay). 16 | 2. Clone your forked repository to your local machine: 17 | ```bash 18 | git clone https://github.com/pankajkumardev/feedwall.git 19 | cd ui-unify 20 | ``` 21 | 22 | ### Set Up Your Development Environment 23 | 1. Install dependencies: 24 | ```bash 25 | npm install 26 | ``` 27 | 2. Set up environment variables by copying the .env.example file to .env and filling in the necessary details. 28 | 4. **Initialize the database:** 29 | ```bash 30 | npx prisma generate 31 | npx prisma migrate dev 32 | ``` 33 | 5. Start the development server: 34 | ```bash 35 | npm run dev 36 | ``` 37 | 38 | ### Create a New Branch 39 | 1. Create a new branch to work on your feature or fix: 40 | ```bash 41 | git checkout -b feature/your-feature-name 42 | ``` 43 | 44 | ### Make Changes 45 | - Write your code and make sure it adheres to the coding standards of this project. 46 | - Test your changes locally and ensure everything works as expected. 47 | 48 | ### Commit and Push Changes 49 | 1. Add your changes: 50 | ```bash 51 | git add . 52 | ``` 53 | 2. Commit your changes with a clear and descriptive message: 54 | ```bash 55 | git commit -m "Add your commit message here" 56 | ``` 57 | 3. Push your changes: 58 | ```bash 59 | git push origin feature/your-feature-name 60 | ``` 61 | 62 | ### Open a Pull Request 63 | - Open a pull request to the `main` branch of this repository. Provide a clear description of the changes you’ve made and any additional information. 64 | 65 | --- 66 | 67 | ## Contributing Guidelines 68 | 69 | ### Code Standards 70 | - Follow best practices for writing clean, readable, and maintainable code. 71 | - Use **ESLint** and **Prettier** to ensure consistent code formatting. Check if the project’s `.eslint.json` and `.prettierrc` configurations are in place. 72 | 73 | ### Commit Messages 74 | - Write **clear** and **concise** commit messages. Follow the [Conventional Commits](https://www.conventionalcommits.org/) style guide if possible. 75 | 76 | ### Pull Request Reviews 77 | - Be open to feedback and make requested changes before merging your pull request. 78 | 79 | --- 80 | 81 | ## Suggested Contributions 82 | 83 | We appreciate contributions in the following areas: 84 | 85 | - **Bug Fixes**: Help fix any issues found in the project. Check the [open issues](https://github.com/PankajKumardev/feedwall/issues) for details. 86 | - **Features**: Add new features to enhance the functionality of FeedWall. 87 | - For example, improving feedback analysis or adding new feedback sorting options. 88 | - Integration with additional third-party services. 89 | - **UI/UX Enhancements**: Improve the user interface, accessibility, and design of the feedback widget and user dashboard. 90 | - **Documentation**: Help improve the documentation by fixing typos, improving readability, or adding missing information. 91 | 92 | --- 93 | 94 | 95 | ## Code of Conduct 96 | 97 | Please be respectful and follow the [Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/) while contributing to this project. 98 | 99 | --- 100 | 101 | Thank you for contributing to Feed-Wall! Your help is truly appreciated! 🚀 102 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20.12.0-alpine3.19 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package* . 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | CMD ["npm", "run", "start"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Pankaj Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # FeedWall 3 | 4 | 5 | ![image](https://github.com/user-attachments/assets/eaa0dbc7-c2ac-4236-ae0d-2f9cf7d65e30) 6 | 7 | 8 | ![Stars](https://img.shields.io/github/stars/PankajKumardev/feedwall?style=social) ![Forks](https://img.shields.io/github/forks/PankajKumardev/feedwall?style=social) ![Issues](https://img.shields.io/github/issues/PankajKumardev/feedwall) 9 | 10 | ## 🌟 Overview 11 | 12 | FeedWall allows users to embed a feedback widget into their projects and receive feedback directly from their site visitors. The feedback is stored in a central database, and users can view, sort, and analyze feedback easily. With AI-powered insights, users can get a summarized view of the pros and cons of their feedback, and even select specific feedback to showcase with an embed code. 13 | 14 | --- 15 | 16 | ## 🚀 Features 17 | 18 | ### 📝 Feedback Widgets 19 | 20 | - Embed customizable feedback widgets into any project. 21 | - Collect and display feedback from visitors directly on your website. 22 | 23 | ### 🔍 Feedback Management 24 | 25 | - View, sort, and filter feedback by name, date, and rating. 26 | - Download feedback in CSV format for offline analysis. 27 | 28 | ### 🤖 AI-Powered Insights 29 | 30 | - Get AI-generated summaries of feedback, highlighting pros and cons. 31 | - Select specific feedback (1-6 entries) to showcase with an embed code. 32 | 33 | ### 🌐 Deployment 34 | 35 | - Hosted on **Vercel**. 36 | 37 | --- 38 | 39 | ## 💻 Tech Stack 40 | 41 | | **Category** | **Technology** | 42 | | -------------- | ----------------------------------- | 43 | | Frontend | Next.js, TailwindCSS | 44 | | Backend | Next.js API, Prisma, PostgreSQL | 45 | | AI Integration | Gemini API | 46 | | Tools | TypeScript, Zod, Docker | 47 | | Authentication | NextAuth | 48 | | Deployment | Vercel | 49 | 50 | --- 51 | 52 | ## 📥 Installation 53 | 54 | 1. Clone the repository: 55 | ```bash 56 | git clone https://github.com/PankajKumardev/feedwall.git 57 | cd feedwall 58 | ``` 59 | 2. Install dependencies: 60 | ```bash 61 | npm install 62 | ``` 63 | 3. Set up environment variables: 64 | - Copy `.env.example` to `.env`. 65 | - Add required keys. 66 | 4. **Initialize the database:** 67 | ```bash 68 | npx prisma generate 69 | npx prisma migrate dev 70 | ``` 71 | 5. Start the development server: 72 | ```bash 73 | npm run dev 74 | ``` 75 | 76 | --- 77 | 78 | ## 🤝 Contribution Guidelines 79 | 80 | ### 🌱 How to Get Involved 81 | 82 | 1. **Fork the repository** by clicking the "Fork" button. 83 | 2. **Clone your fork:** 84 | ```bash 85 | git clone https://github.com/pankajkumardev/feedwall.git 86 | ``` 87 | 3. **Create a new branch:** 88 | ```bash 89 | git checkout -b feature/ 90 | ``` 91 | 4. **Make changes** and commit: 92 | ```bash 93 | git add . 94 | git commit -m "Your descriptive commit message" 95 | ``` 96 | 5. **Push changes:** 97 | ```bash 98 | git push origin 99 | ``` 100 | 6. Open a pull request. 101 | 102 | ### 📌 Suggested Contributions 103 | 104 | - Enhance widget customization features. 105 | - Add more AI-driven feedback analysis options. 106 | - Improve feedback sorting and filtering mechanisms. 107 | 108 | --- 109 | 110 | ## 🌟 Stargazers & Forkers 111 | 112 | We appreciate your support! 🌟🍴 113 | 114 | [![Stargazers](https://img.shields.io/github/stars/PankajKumardev/feedwall)](https://github.com/PankajKumardev/feedwall/stargazers) [![Forks](https://img.shields.io/github/forks/PankajKumardev/feedwall)](https://github.com/PankajKumardev/feedwall/network/members) 115 | 116 | --- 117 | 118 | ## 🛡 License 119 | 120 | FeedWall is available under the MIT License. Feel free to use and modify responsibly. 121 | 122 | --- 123 | 124 | ## 📖 Changelog 125 | 126 | Refer to [`CHANGELOG.md`](https://github.com/PankajKumardev/feedwall/blob/main/CHANGELOG.md) for updates. 127 | 128 | --- 129 | 130 | ## 📬 Contact 131 | 132 | For queries or collaborations: 133 | 134 | - Email: [pankajams1234@gmail.com](mailto:pankajams1234@gmail.com) 135 | - LinkedIn: [Pankaj Kumar](https://www.linkedin.com/in/pankajkumardev0/) 136 | - Twitter: [@pankajkumar_dev](https://x.com/pankajkumar_dev) 137 | 138 | 139 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /app/actions/Ai.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import { GoogleGenerativeAI } from '@google/generative-ai'; 4 | 5 | const googleApiKey = process.env.GOOGLE_API_KEY; 6 | if (!googleApiKey) { 7 | throw new Error('GOOGLE_API_KEY is not defined'); 8 | } 9 | 10 | const genAI = new GoogleGenerativeAI(googleApiKey); 11 | 12 | interface Feedback { 13 | id: number; 14 | name: string; 15 | email: string; 16 | createdAt: Date; 17 | feedback: string; 18 | rating: number; 19 | projectid: number; 20 | } 21 | 22 | export const AISummary = async ( 23 | feedbacks: Feedback[] | null 24 | ): Promise => { 25 | if (!feedbacks || feedbacks.length === 0) { 26 | throw new Error('No feedbacks provided'); 27 | } 28 | 29 | const prompt = 30 | 'Create a summary of feedbacks with pros and cons in points. Here are the feedbacks: ' + 31 | feedbacks.map((feedback) => feedback.feedback).join(' '); 32 | 33 | console.log(feedbacks); 34 | 35 | const model = genAI.getGenerativeModel({ 36 | model: 'gemini-1.5-flash', 37 | }); 38 | try { 39 | const result = await model.generateContent(prompt); 40 | const responseText = result.response.text 41 | ? await result.response.text() 42 | : JSON.stringify(result.response); 43 | 44 | if (!responseText) { 45 | throw new Error('No response text generated'); 46 | } 47 | return responseText; 48 | } catch (err) { 49 | console.error(err); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /app/actions/createProject.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import prisma from '@/lib/db'; 4 | import { getServerSession } from 'next-auth'; 5 | import { z } from 'zod'; 6 | 7 | const schema = z.object({ 8 | name: z.string(), 9 | description: z.string(), 10 | url: z.string().url(), 11 | }); 12 | 13 | export const createProject = async ( 14 | name: string, 15 | description: string, 16 | url: string 17 | ) => { 18 | const session = await getServerSession(); 19 | console.log('Session:', session); // Add this line to log the session object 20 | if (!session?.user) { 21 | throw new Error('You must be logged in to create a project'); 22 | } 23 | const userId = session.user.email; 24 | 25 | try { 26 | const findUser = await prisma.user.findUnique({ 27 | where: { 28 | email: userId as string, 29 | }, 30 | }); 31 | if (!findUser) { 32 | throw new Error('User not found'); 33 | } 34 | const parse = schema.safeParse({ name, description, url }); 35 | if (!parse.success) { 36 | throw new Error('Invalid input'); 37 | } 38 | const project = await prisma.project.create({ 39 | data: { 40 | name: parse.data.name, 41 | description: parse.data.description, 42 | url: parse.data.url, 43 | userId: findUser.id, 44 | }, 45 | }); 46 | if (!project) { 47 | throw new Error('Project not created'); 48 | } 49 | 50 | return project; 51 | } catch (error) { 52 | console.error(error); 53 | throw new Error('An error occurred while creating the project'); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /app/actions/deleteFeedback.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import prisma from '@/lib/db'; 4 | 5 | export const deleteFeedback = async (id: number) => { 6 | try { 7 | await prisma.feedback.delete({ 8 | where: { 9 | id: id, 10 | }, 11 | }); 12 | } catch (error) { 13 | console.error(error); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /app/actions/deleteProject.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import prisma from '@/lib/db'; 4 | import { getServerSession } from 'next-auth'; 5 | 6 | export const deleteProject = async (id: number) => { 7 | try { 8 | const session = await getServerSession(); 9 | if (!session?.user) { 10 | throw new Error('You must be logged in to delete a project'); 11 | } 12 | const userId = session.user.email; 13 | if (!userId) { 14 | throw new Error('User not found'); 15 | } 16 | const findUser = await prisma.user.findUnique({ 17 | where: { 18 | email: userId as string, 19 | }, 20 | }); 21 | if (!findUser) { 22 | throw new Error('User not found'); 23 | } 24 | 25 | const project = await prisma.project.delete({ 26 | where: { 27 | id: id, 28 | userId: findUser.id, 29 | }, 30 | }); 31 | if (!project) { 32 | throw new Error('Project not found'); 33 | } 34 | } catch (error) { 35 | console.error(error); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /app/actions/getFeedback.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | import prisma from '@/lib/db'; 3 | 4 | export default async function getFeedbacks(id: number) { 5 | try { 6 | const feedbacks = await prisma.feedback.findMany({ 7 | where: { 8 | projectid: id, 9 | }, 10 | }); 11 | if (!feedbacks) { 12 | return null; 13 | } 14 | return feedbacks; 15 | } catch (err) { 16 | console.error(err); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/actions/getProject.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import prisma from '@/lib/db'; 3 | import { redirect } from 'next/navigation'; 4 | 5 | export default async function getProjectName(id: number) { 6 | const isValidID = await prisma.project.findUnique({ 7 | where: { 8 | id: id, 9 | }, 10 | }); 11 | 12 | if (!isValidID) { 13 | return redirect('/error'); 14 | } 15 | const projectName = await prisma.project.findUnique({ 16 | where: { 17 | id: id, 18 | }, 19 | select: { 20 | name: true, 21 | }, 22 | }); 23 | return projectName?.name; 24 | } 25 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth'; 2 | import { NEXT_AUTH } from '@/lib/auth'; 3 | 4 | const handler = NextAuth(NEXT_AUTH); 5 | 6 | export { handler as GET, handler as POST }; 7 | -------------------------------------------------------------------------------- /app/api/feedback/route.ts: -------------------------------------------------------------------------------- 1 | import prisma from '@/lib/db'; 2 | import { NextRequest, NextResponse } from 'next/server'; 3 | import { z } from 'zod'; 4 | 5 | const feedbackSchema = z.object({ 6 | name: z.string(), 7 | email: z.string().email(), 8 | feedback: z.string(), 9 | rating: z.number().int().min(1).max(5), 10 | projectid: z.number(), 11 | }); 12 | 13 | export async function POST(req: NextRequest) { 14 | try { 15 | const feedback = await req.json(); 16 | const parsedFeedback = feedbackSchema.safeParse(feedback); 17 | if (!parsedFeedback.success) { 18 | return NextResponse.json( 19 | { error: 'Invalid feedback' }, 20 | { 21 | status: 400, 22 | headers: { 23 | 'Access-Control-Allow-Origin': '*', 24 | 'Access-Control-Allow-Headers': 'Content-Type', 25 | }, 26 | } 27 | ); 28 | } 29 | const submittedFeedback = await prisma.feedback.create({ 30 | data: { 31 | name: parsedFeedback.data.name, 32 | email: parsedFeedback.data.email, 33 | feedback: parsedFeedback.data.feedback, 34 | rating: parsedFeedback.data.rating, 35 | projectid: parsedFeedback.data.projectid, 36 | }, 37 | }); 38 | 39 | console.log(submittedFeedback); 40 | return NextResponse.json( 41 | { 42 | message: 'Feedback submitted successfully', 43 | }, 44 | { 45 | status: 200, 46 | headers: { 47 | 'Access-Control-Allow-Origin': '*', 48 | 'Access-Control-Allow-Headers': 'Content-Type', 49 | }, 50 | } 51 | ); 52 | } catch (err: any) { 53 | return NextResponse.json( 54 | { error: err.message }, 55 | { 56 | status: 500, 57 | headers: { 58 | 'Access-Control-Allow-Origin': '*', 59 | 'Access-Control-Allow-Headers': 'Content-Type', 60 | }, 61 | } 62 | ); 63 | } 64 | } 65 | 66 | export async function OPTIONS() { 67 | return new NextResponse(null, { 68 | status: 204, 69 | headers: { 70 | 'Access-Control-Allow-Origin': '*', 71 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 72 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 73 | }, 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /app/api/user/route.ts: -------------------------------------------------------------------------------- 1 | import { getServerSession } from 'next-auth'; 2 | import { NextResponse } from 'next/server'; 3 | import { NEXT_AUTH } from '@/lib/auth'; 4 | 5 | export const GET = async () => { 6 | try { 7 | const session = await getServerSession(NEXT_AUTH); 8 | if (session.user) { 9 | return NextResponse.json({ 10 | user: session.user, 11 | }); 12 | } 13 | } catch (e: any) { 14 | return NextResponse.json( 15 | { 16 | error: e.message, 17 | }, 18 | { 19 | status: 403, 20 | } 21 | ); 22 | } 23 | return NextResponse.json( 24 | { 25 | message: 'You are not logged in', 26 | }, 27 | { 28 | status: 403, 29 | } 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /app/docs/page.tsx: -------------------------------------------------------------------------------- 1 | import { Info, Rocket, Package, CheckCircle } from 'lucide-react'; 2 | import { CodeBlock } from '@/components/ui/code-block'; 3 | import Accordian from '@/components/Accordion'; 4 | import { getServerSession } from 'next-auth'; 5 | import { redirect } from 'next/navigation'; 6 | 7 | export default async function Docs() { 8 | const session = await getServerSession(); 9 | if (!session) { 10 | redirect('/api/auth/signin'); 11 | } 12 | return ( 13 |
14 |
15 |

16 | Feed 17 | -Wall{' '} 18 | Documentation 19 |

20 |

21 | Your ultimate tool to collect, showcase, and gain insights from user 22 | feedback. 23 |

24 |
25 | 26 |
27 |
28 | 29 |

30 | What is Feed-Wall? 31 |

32 |
33 |

34 | Feed-Wall is a feedback collection tool that simplifies embedding 35 | feedback forms on your website. It provides AI-powered insights to 36 | help you understand user sentiments and improve your platform. 37 |

38 |
39 | 40 |
41 |
42 | 43 |

44 | Why Choose Feed-Wall? 45 |

46 |
47 |
    48 |
  • 49 | AI-Powered Insights: Quickly understand user 50 | feedback patterns. 51 |
  • 52 |
  • 53 | Easy Integration: Embed feedback forms with minimal 54 | code. 55 |
  • 56 |
  • 57 | Feedback Showcasing: Display top feedback for 58 | credibility. 59 |
  • 60 |
61 |
62 | 63 |
64 |
65 | 66 |

67 | Installation 68 |

69 |
70 |
    71 |
  1. Sign up for Feed-Wall.
  2. 72 |
  3. Add your project.
  4. 73 |
  5. Copy the embed code.
  6. 74 |
  7. Insert the code into your website.
  8. 75 |
  9. Start collecting feedback.
  10. 76 |
  11. Showcase impactful feedback on your site.
  12. 77 |
78 |
79 | 80 |
81 |
82 | 83 |

84 | Marquee Installation 85 |

86 |
87 |

88 | To display feedback with a marquee effect, run the command below and 89 | then copy and paste the embed code: 90 |

91 | 96 |

97 | If you face any issues, refer to the 98 | 103 | official MagicUI documentation 104 | 105 | . 106 |

107 |
108 | 109 |
110 |
111 | 112 |

113 | Frequently Asked Questions (FAQ) 114 |

115 |
116 | 117 |
118 |
119 | ); 120 | } 121 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PankajKumardev/feedwall/b2a82a5bd6defeafc6d3cc30853c7f91990a9ed9/app/favicon.ico -------------------------------------------------------------------------------- /app/feedbacks/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect, useState } from 'react'; 4 | import { useParams } from 'next/navigation'; 5 | import { CSVLink } from 'react-csv'; 6 | import { ClipLoader } from 'react-spinners'; 7 | import { 8 | Search, 9 | Download, 10 | Star, 11 | Trash2, 12 | ChevronLeft, 13 | ChevronRight, 14 | } from 'lucide-react'; 15 | import getProjectName from '@/app/actions/getProject'; 16 | import getFeedbacks from '@/app/actions/getFeedback'; 17 | import { AISummary } from '@/app/actions/Ai'; 18 | import { EmbededCode } from '@/components/embedCode'; 19 | import { CodeBlock } from '@/components/ui/code-block'; 20 | import { Input } from '@/components/ui/input'; 21 | import { Button } from '@/components/ui/button'; 22 | import { marked } from 'marked'; 23 | 24 | import { 25 | Table, 26 | TableBody, 27 | TableCell, 28 | TableHead, 29 | TableHeader, 30 | TableRow, 31 | } from '@/components/ui/table'; 32 | import { 33 | Dialog, 34 | DialogContent, 35 | DialogDescription, 36 | DialogHeader, 37 | DialogTitle, 38 | DialogTrigger, 39 | } from '@/components/ui/dialog'; 40 | import { 41 | Select, 42 | SelectContent, 43 | SelectItem, 44 | SelectTrigger, 45 | SelectValue, 46 | } from '@/components/ui/select'; 47 | 48 | import { MarqueeSelector } from '@/components/MarqueeFeedback'; 49 | import { deleteFeedback } from '@/app/actions/deleteFeedback'; 50 | import { useSession } from 'next-auth/react'; 51 | import { useRouter } from 'next/navigation'; 52 | 53 | interface Feedback { 54 | id: number; 55 | name: string; 56 | email: string; 57 | createdAt: Date; 58 | feedback: string; 59 | rating: number; 60 | projectid: number; 61 | } 62 | 63 | export default function Page() { 64 | const route = useRouter(); 65 | const session = useSession(); 66 | const { id } = useParams(); 67 | const [project, setProject] = useState(null); 68 | const [feedbacks, setFeedbacks] = useState(null); 69 | const [filteredFeedbacks, setFilteredFeedbacks] = useState( 70 | null 71 | ); 72 | const [summary, setSummary] = useState(null); 73 | const [searchTerm, setSearchTerm] = useState(''); 74 | const [sortBy, setSortBy] = useState<'name' | 'rating' | 'date'>('date'); 75 | const [isLoading, setIsLoading] = useState(true); 76 | const [isDeleting, setIsDeleting] = useState(false); 77 | const [currentPage, setCurrentPage] = useState(1); 78 | const [isSummaryLoading, setIsSummaryLoading] = useState(false); 79 | const [santizedSummary, setSantizedSummary] = useState(null); 80 | 81 | useEffect(() => { 82 | if (!session.data?.user) { 83 | route.push('/signin'); 84 | } 85 | }, [session, route]); 86 | 87 | const feedbacksPerPage = 4; 88 | 89 | async function getSummary(feedbacks: Feedback[] | null) { 90 | if (feedbacks) { 91 | if (!session.data?.user) { 92 | return null; 93 | } 94 | 95 | if (feedbacks.length === 0) { 96 | setSummary('

no summary

'); 97 | } else { 98 | const summary = await AISummary(feedbacks); 99 | if (summary) { 100 | setSummary(summary); 101 | const temp = await marked(summary); 102 | setSantizedSummary(temp); 103 | } 104 | } 105 | setIsSummaryLoading(false); 106 | } 107 | } 108 | 109 | useEffect(() => { 110 | async function fetchProjectName() { 111 | if (id) { 112 | const projectName = await getProjectName(Number(id)); 113 | const Feedbacks = await getFeedbacks(Number(id)); 114 | 115 | if (projectName !== undefined) { 116 | setProject(projectName); 117 | } 118 | if (Feedbacks !== undefined) { 119 | setFeedbacks(Feedbacks); 120 | setFilteredFeedbacks(Feedbacks); 121 | } 122 | setIsLoading(false); 123 | } 124 | } 125 | fetchProjectName(); 126 | }, [id]); 127 | 128 | useEffect(() => { 129 | if (feedbacks) { 130 | const filtered = feedbacks.filter( 131 | (feedback) => 132 | feedback.name.toLowerCase().includes(searchTerm.toLowerCase()) || 133 | feedback.email.toLowerCase().includes(searchTerm.toLowerCase()) || 134 | feedback.feedback.toLowerCase().includes(searchTerm.toLowerCase()) 135 | ); 136 | const sorted = [...filtered].sort((a, b) => { 137 | if (sortBy === 'name') return a.name.localeCompare(b.name); 138 | if (sortBy === 'rating') return b.rating - a.rating; 139 | return ( 140 | new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() 141 | ); 142 | }); 143 | setFilteredFeedbacks(sorted); 144 | setCurrentPage(1); 145 | } 146 | }, [feedbacks, searchTerm, sortBy]); 147 | 148 | const handleDelete = async (id: number) => { 149 | setIsDeleting(true); 150 | await deleteFeedback(id); 151 | setFeedbacks((prev) => 152 | prev ? prev.filter((feedback) => feedback.id !== id) : null 153 | ); 154 | setIsDeleting(false); 155 | }; 156 | 157 | const indexOfLastFeedback = currentPage * feedbacksPerPage; 158 | const indexOfFirstFeedback = indexOfLastFeedback - feedbacksPerPage; 159 | const currentFeedbacks = filteredFeedbacks?.slice( 160 | indexOfFirstFeedback, 161 | indexOfLastFeedback 162 | ); 163 | 164 | const paginate = (pageNumber: number) => setCurrentPage(pageNumber); 165 | 166 | if (isLoading) { 167 | return ( 168 |
169 | 170 |
171 | ); 172 | } 173 | 174 | if (!project) { 175 | return
Project not found
; 176 | } 177 | 178 | return ( 179 |
180 |
181 |

182 | Feed 183 | -Wall{' '} 184 | Feedbacks 185 |

186 |
187 | Review and manage customer feedbacks for {project} 188 |
189 |
190 | 191 |
192 |

Customer Feedbacks

193 |
194 | 📊 View, search, and manage feedback from your customers. Use the 195 | tools below to filter and analyze the data. 196 |
197 |
198 |
199 | 200 | setSearchTerm(e.target.value)} 205 | className="w-full sm:w-[95%] " 206 | /> 207 |
208 |
209 | 224 | 229 | 230 | Export CSV 231 | 232 |
233 |
234 | {currentFeedbacks && currentFeedbacks.length > 0 ? ( 235 | <> 236 | 237 | 238 | 239 | Name 240 | Email 241 | Feedback 242 | Rating 243 | Date 244 | Actions 245 | 246 | 247 | 248 | {currentFeedbacks.map((feedback) => ( 249 | 250 | 251 | {feedback.name} 252 | 253 | {feedback.email} 254 | 255 | {feedback.feedback} 256 | 257 | 258 | {[...Array(5)].map((_, i) => ( 259 | 267 | ))} 268 | 269 | 270 | {new Date(feedback.createdAt).toLocaleDateString()} 271 | 272 | 273 | 274 | 275 | 278 | 279 | 280 | 281 | {feedback.name} 282 | 283 | 284 | {feedback.email} 285 | 286 | {[...Array(5)].map((_, i) => ( 287 | 295 | ))} 296 | 297 | 298 | 299 | 300 | 301 |
302 |
303 | {feedback.feedback} 304 |
305 |
306 | Submitted:{' '} 307 | {new Date(feedback.createdAt).toLocaleString()} 308 |
309 |
310 |
311 | 327 |
328 |
329 |
330 |
331 |
332 | ))} 333 |
334 |
335 |
336 |
337 | Showing {indexOfFirstFeedback + 1} to{' '} 338 | {Math.min(indexOfLastFeedback, filteredFeedbacks?.length || 0)}{' '} 339 | of {filteredFeedbacks?.length} entries 340 |
341 |
342 | 349 | 358 |
359 |
360 | 361 | ) : ( 362 |
363 |
364 | No feedbacks available 😢 365 |
366 |
367 | )} 368 |
369 | 370 |
371 |

Embed Code

372 |
373 | 🔗 Use this code to add the feedback widget to your website. Copy and 374 | paste it into your HTML. 375 |
376 | 379 | 381 | 382 | 383 | 384 | 385 | `} 386 | language="html" 387 | filename="index.html" 388 | /> 389 |
390 | } 391 | tab2Content={ 392 |
393 | { 400 | const script = document.createElement('script'); 401 | script.src = 'https://feedback-widget-weld.vercel.app/feedback-widget.js'; 402 | script.type = 'module'; 403 | document.body.appendChild(script); 404 | }, []); 405 | 406 | return ( 407 | <> 408 | {/* @ts-ignore */} 409 | 410 | 411 | ); 412 | }`} 413 | language="typescript" 414 | filename="index.tsx" 415 | /> 416 |
417 | } 418 | /> 419 |
420 | {feedbacks && } 421 | 422 |
423 |

Feedback Summary

424 |
425 | 🤖 Get an AI-generated summary of your customer feedbacks to quickly 426 | understand trends and insights. 427 |
428 | 442 | {summary && ( 443 |
444 | {santizedSummary && ( 445 |
446 | )} 447 |
448 | )} 449 |
450 |
451 | ); 452 | } 453 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --scrollbar-bg: #f1f1f1; 7 | --scrollbar-thumb: #888; 8 | --scrollbar-thumb-hover: #555; 9 | 10 | } 11 | 12 | @media (prefers-color-scheme: dark) { 13 | :root { 14 | --scrollbar-bg: #121212; 15 | --scrollbar-thumb: #888; 16 | --scrollbar-thumb-hover: #555; 17 | } 18 | } 19 | 20 | body { 21 | font-family: Arial, Helvetica, sans-serif; 22 | @apply bg-white text-black dark:bg-[#121212] dark:text-white; 23 | scroll-behavior: smooth; 24 | } 25 | 26 | @layer base { 27 | :root { 28 | --radius: 0.5rem; 29 | } 30 | } 31 | 32 | ::-webkit-scrollbar { 33 | width: 4px; 34 | } 35 | 36 | ::-webkit-scrollbar-track { 37 | background: var(--scrollbar-bg); 38 | } 39 | 40 | ::-webkit-scrollbar-thumb { 41 | background: var(--scrollbar-thumb); 42 | border-radius: 4px; 43 | } 44 | 45 | ::-webkit-scrollbar-thumb:hover { 46 | background: var(--scrollbar-thumb-hover); 47 | } -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | import { Geist, Geist_Mono } from 'next/font/google'; 3 | import './globals.css'; 4 | import { ThemeProvider } from '@/components/theme-provider'; 5 | import { Providers } from './provider'; 6 | import Navigaton from '@/components/Navigaton'; 7 | import Footer from '@/components/Footer'; 8 | import { Analytics } from "@vercel/analytics/react" 9 | import LenisProvider from '@/components/Provider/LenisProvider'; 10 | 11 | const geistSans = Geist({ 12 | variable: '--font-geist-sans', 13 | subsets: ['latin'], 14 | }); 15 | 16 | const geistMono = Geist_Mono({ 17 | variable: '--font-geist-mono', 18 | subsets: ['latin'], 19 | }); 20 | 21 | export const metadata: Metadata = { 22 | title: 'Feed-Wall', 23 | description: 'Feed-Wall a feedback collection tool', 24 | }; 25 | 26 | export default function RootLayout({ 27 | children, 28 | }: Readonly<{ 29 | children: React.ReactNode; 30 | }>) { 31 | return ( 32 | 33 | 34 | 37 | 43 | 44 | 45 | 46 |
{children}
47 |
48 |