├── .eslintrc.json ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── enhancement-request.md │ ├── feature_request.md │ └── pull_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── app ├── (public) │ ├── privacy │ │ └── page.tsx │ └── terms │ │ └── page.tsx ├── blogs │ ├── [slug] │ │ └── page.tsx │ ├── layout.tsx │ └── page.tsx ├── editor │ ├── old │ │ └── page.tsx │ └── page.tsx ├── globals.css ├── layout.tsx ├── not-found.tsx └── page.tsx ├── components.json ├── components ├── icons.tsx ├── layout │ ├── Header.tsx │ ├── LandingHeader.tsx │ ├── LandingPage.tsx │ ├── OldLandingPage.txt │ └── background-gradient.tsx ├── sections │ ├── CTASection.tsx │ ├── FAQSection.tsx │ ├── FeatureSection.tsx │ └── PricingSection.tsx ├── shadow-manager.tsx ├── shared │ ├── Editor.tsx │ ├── Editor.v2.tsx │ ├── Header.v2.tsx │ ├── NotAvailable.tsx │ ├── PeerlistSpotlight.tsx │ ├── ShowcaseImage.tsx │ ├── ValidatedInput.tsx │ ├── buttons │ │ └── ExportButton.tsx │ ├── new.txt │ ├── types.ts │ └── utils.ts ├── tailwind-indicator.tsx ├── text-manager.tsx ├── theme-provider.tsx ├── theme-toggle.tsx └── ui │ ├── accordion.tsx │ ├── alert.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── button.tsx │ ├── card.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── input.tsx │ ├── label.tsx │ ├── popover.tsx │ ├── select.tsx │ ├── sheet.tsx │ ├── slider.tsx │ ├── tabs.tsx │ ├── textarea.tsx │ ├── toggle-group.tsx │ └── toggle.tsx ├── config └── config.ts ├── content ├── hacktoberfest.mdx └── introduction.mdx ├── lib ├── blog.ts ├── canvas.ts ├── constants.ts ├── extractColors.ts ├── fonts.ts └── utils.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── apple-touch-icon.png ├── demo-hero.png ├── favicon.ico ├── feature-demo.png ├── icon-192-maskable.png ├── icon-192.png ├── icon-512-maskable.png ├── icon-512.png ├── mockly-demo.png ├── peerlist-dark.svg ├── peerlist-light.svg ├── peerlist.png ├── showcase-dark.png ├── showcase-light.png ├── showcase.png └── users.png ├── pull_request_template.md ├── showcase ├── hacktoberfest.png └── introduction.png ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug] Sample Title" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug Report Template 11 | 12 | ### **Describe the bug** 13 | _A clear and concise description of what the bug is. Example: "The application crashes when..."_ 14 | 15 | ### **To Reproduce** 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '...' 19 | 3. Scroll down to '...' 20 | 4. See error 21 | 22 | ### **Expected behavior** 23 | _A clear and concise description of what you expected to happen._ 24 | 25 | ### **Screenshots** 26 | _If applicable, add screenshots to help explain your problem._ 27 | 28 | ### **Environment (please complete the following information):** 29 | - **OS**: [e.g., Windows, macOS] 30 | - **Browser**: [e.g., Chrome, Safari] 31 | - **Version**: [e.g., 22] 32 | - **Other relevant environment details** (e.g., Node version, database version) 33 | 34 | ### **Additional context** 35 | _Add any other context about the problem here (e.g., error logs, stack traces)._ 36 | 37 | --- 38 | 39 | ### **Optional Sections (if relevant):** 40 | 41 | - **Priority (Low / Medium / High)** 42 | - **Is this issue blocking your progress?** 43 | - **Can you submit a PR to fix this?** 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement Request 3 | about: Improve an existing feature for this project 4 | title: "[Enchancement] Sample Title" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Enhancement Request Template 11 | 12 | ### **Is your enhancement request related to a problem? Please describe.** 13 | _A clear and concise description of the problem you're facing or the limitation in the existing feature. Example: "The current implementation does not allow..."_ 14 | 15 | ### **Describe the enhancement you'd like** 16 | _A clear and concise description of what you would like to see improved. Example: "I would like the feature to..."_ 17 | 18 | ### **Describe alternatives you've considered** 19 | _A clear and concise description of alternative approaches or workarounds. Example: "I tried solving this by..."_ 20 | 21 | ### **Possible Implementation Details** 22 | _If applicable, describe how the enhancement could be implemented. Include code snippets, mockups, or diagrams if helpful._ 23 | 24 | ### **Additional context** 25 | _Add any other context, screenshots, or references relevant to the enhancement here._ 26 | 27 | --- 28 | 29 | ### **Optional Sections (if relevant):** 30 | 31 | - **Priority (Low / Medium / High)** 32 | - **Are you willing to submit a PR for this enhancement?** 33 | - **Does this enhancement require changes in documentation?** 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request] Sample Title" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Feature Request Template 11 | 12 | ### **Is your feature request related to a problem? Please describe.** 13 | _A clear and concise description of the problem you're facing. Example: "It is frustrating when I cannot..."_ 14 | 15 | ### **Describe the solution you'd like** 16 | _A clear and concise description of what you want to happen. Example: "I would like to have..."_ 17 | 18 | ### **Describe alternatives you've considered** 19 | _A clear and concise description of any alternative solutions or features you've considered. Example: "Another way to solve this might be..."_ 20 | 21 | ### **Additional context** 22 | _Add any other context or screenshots about the feature request here._ 23 | 24 | --- 25 | 26 | ### **Optional Sections (if relevant):** 27 | 28 | - **Priority (Low / Medium / High)** 29 | - **Are you willing to contribute to this feature?** 30 | - **Does this feature require any third-party libraries or changes in existing ones?** 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/pull_request.md: -------------------------------------------------------------------------------- 1 | ## Pull Request Template 2 | 3 | ### **Description** 4 | _A clear and concise description of what this pull request does. Please include the purpose and details of the changes being made._ 5 | 6 | ### **Related Issue** 7 | _If this PR is related to an open issue, please provide a link to the issue here._ 8 | 9 | Closes # (issue) 10 | 11 | ### **Changes Made** 12 | _List out the specific changes made in this pull request:_ 13 | - _Changed X to Y..._ 14 | - _Refactored Z..._ 15 | 16 | ### **Type of Change** 17 | _Select the type of change made with this pull request:_ 18 | 19 | - [ ] Bug fix (non-breaking change that fixes an issue) 20 | - [ ] New feature (non-breaking change that adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 22 | - [ ] Documentation update 23 | 24 | ### **Screenshots (if applicable)** 25 | _Add screenshots, if necessary, to illustrate changes made to the UI or visual elements._ 26 | 27 | ### **How Has This Been Tested?** 28 | _Explain how the changes have been tested. Include testing framework details, if any, and how the results can be reproduced._ 29 | 30 | - _Tested with unit tests_ 31 | - _Manually tested on local environment_ 32 | 33 | ### **Checklist** 34 | _Check off the following items before submitting the pull request:_ 35 | 36 | - [ ] My code follows the style guidelines of this project 37 | - [ ] I have performed a self-review of my code 38 | - [ ] I have commented my code, particularly in hard-to-understand areas 39 | - [ ] I have made corresponding changes to the documentation 40 | - [ ] My changes generate no new warnings 41 | - [ ] I have added tests that prove my fix is effective or that my feature works 42 | - [ ] New and existing unit tests pass locally with my changes 43 | - [ ] Any dependent changes have been merged and published in downstream modules 44 | 45 | ### **Additional Context** 46 | _Provide any additional context or information that might be useful for the reviewers._ 47 | -------------------------------------------------------------------------------- /.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.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /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 | suryanshsingh.general@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 | # Contributing to Mockly 2 | 3 | 🎉 **Welcome, Hacktoberfest Contributors!** 🎉 4 | We’re excited that you’re here to help make Mockly even better. Whether it’s fixing bugs, adding new features, or improving documentation, we’re thrilled to have you onboard. 5 | 6 | ## Getting Started 7 | 8 | 1. **Fork the Repo**: Click the "Fork" button on the top-right of the repository page. 9 | 2. **Clone the Fork**: 10 | ``` 11 | git clone https://github.com/YOUR-USERNAME/mockly.git 12 | cd mockly 13 | ``` 14 | 3. **Install Dependencies**: 15 | ``` 16 | npm install 17 | ``` 18 | 4. **Create a New Branch**: 19 | ``` 20 | git checkout -b feature/your-feature-name 21 | ``` 22 | 23 | ## How to Contribute 24 | 25 | - **Check Existing Issues**: We have a list of issues tagged for **Hacktoberfest** that you can start with. Feel free to pick one or suggest your own improvements. 26 | - **Create an Issue**: If you find a bug or have a feature request that isn’t listed, open a new issue and explain the problem or idea. 27 | - **Submit Pull Requests (PRs)**: Once you’ve made your changes, push your branch to your fork and submit a pull request to the `main` branch. 28 | 29 | ## Areas to Contribute 30 | 31 | Here are some ideas for contributions: 32 | - **Fix Bugs**: Check for issues like performance problems (e.g., canvas flickering). 33 | - **Add Features**: More export options, custom screen sizes, UI improvements, etc. 34 | - **Improve Documentation**: Help make our docs clearer and more beginner-friendly. 35 | - **Code Review**: Provide feedback on others’ PRs and help keep the project quality high. 36 | 37 | ## Hacktoberfest Guidelines 38 | 39 | To ensure your PRs count toward Hacktoberfest: 40 | - Your pull request must be **merged**, **approved**, or tagged with **hacktoberfest-accepted** by a maintainer. 41 | - Spammy or low-quality PRs will not be accepted (we’re all here to learn and improve together!). 42 | 43 | ## Code of Conduct 44 | 45 | Be respectful, kind, and encouraging. We’re here to support each other and grow as a community. Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing. 46 | 47 | ## Thank You! 48 | 49 | Your contributions mean the world to us! Let’s build something great together. Happy Hacktoberfest! 💻🎃 50 | 51 | If you have any questions, feel free to open an issue or reach out directly. 😊 52 | 53 | 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Suryansh Singh 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 | # Mockly 🎨 2 | [![Live Demo](https://img.shields.io/badge/demo-online-green.svg)](https://www.mockly.site) 3 | [![Next.js](https://img.shields.io/badge/Next.js-13.0-blueviolet.svg)](https://nextjs.org/) 4 | [![React](https://img.shields.io/badge/React-18.0-blue.svg)](https://reactjs.org/) 5 | [![TypeScript](https://img.shields.io/badge/TypeScript-4.9-blue.svg)](https://www.typescriptlang.org/) 6 | [![Tailwind CSS](https://img.shields.io/badge/Tailwind%20CSS-3.3-blue.svg)](https://tailwindcss.com/) 7 | [![shadcn/ui](https://img.shields.io/badge/shadcn%2Fui-latest-blue.svg)](https://ui.shadcn.com/) 8 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 9 | 10 | 11 | ![screenshot1727467497308](https://github.com/user-attachments/assets/67a2c9fe-44e6-41c5-8fdf-443b8b324e17) 12 | 13 | 14 | Mockly is an open-source tool that allows developers to create professional-looking screenshots and mockups with ease. With Mockly, you can pick, place, and zoom—done. It's designed for fast, stress-free mockups for developers who'd rather code than design. 15 | 16 | 17 | 18 | ## ✨ Features 19 | 20 | 1. **Instant Mockups**: Upload, tweak, done. It's like magic, but without the wand. 21 | 2. **Multi-device Preview**: Instantly see how your designs look on any screen size—because nobody has time for resizing. 22 | - **Example**: Select "Screen Size" dropdown menu to automatically generate views for mobile, tablet, and desktop versions. 23 | 3. **Zoom & Place**: Precisely position your screenshot, and zoom in or out like a pro—minus the struggle. 24 | - **Example**: Drag your cursor to specify the position and use the zoom slider in the "Design" tab as needed. 25 | 4. **Text & Style**: Add text, pick your style, and make it pop. Because plain screenshots are so last year. 26 | - **Example**: Enter your text under the "Text" tab. Adjust the font size, weight, color, and drag it with around to align it your mockup. 27 | 5. **No Login Required**: Start creating immediately without any sign-up process. 28 | 6. **No Watermarks**: Your designs, your brand—no unwanted additions. 29 | 7. **Free to Use**: Powerful features accessible to everyone at no cost. 30 | 31 | ## 🚀 Getting Started 32 | 33 | ### Prerequisites 34 | 35 | - **Node.js** (v14 or later): Check your current version by running `node -v`. 36 | - **npm or yarn**: If you're using npm or Yarn, you can run `npm -v` or `yarn -v`. 37 | 38 | ### Installation 39 | 40 | 1. Clone the repository: 41 | ``` 42 | git clone https://github.com/suryanshsingh2001/mockly.git 43 | ``` 44 | 45 | 2. Navigate to the project directory: 46 | ``` 47 | cd mockly 48 | ``` 49 | 50 | 3. Install dependencies: 51 | ``` 52 | npm install 53 | # or 54 | yarn install 55 | ``` 56 | 57 | 4. Start the development server: 58 | ``` 59 | npm run dev 60 | # or 61 | yarn dev 62 | ``` 63 | 64 | 5. Open [http://localhost:3000](http://localhost:3000) in your browser to see the application. 65 | 66 | ## 🖱️ Usage 67 | 68 | 1. Navigate to the editor page. 69 | 2. Upload your screenshot or choose from available templates. 70 | 3. Use the intuitive interface to adjust, zoom, and place your image. 71 | 4. Add text, choose fonts, and style as needed. 72 | 5. Preview your design on multiple device sizes. 73 | 6. Download your finished mockup. 74 | 75 | ## 🔮 Future Scope 76 | 77 | We have exciting plans for the future of Mockly, and we welcome contributions in these areas: 78 | 79 | 1. **Video Editor**: We're planning to expand Mockly's capabilities to include a video editor that follows the same philosophy as our image editor—quick, intuitive, and designed for developers who want to create professional-looking video content without the complexity of traditional video editing software. 80 | 81 | 2. **AI-Assisted Design**: Implementing AI to suggest design improvements and automate repetitive tasks. 82 | 83 | 3. **Template Marketplace**: A platform for users to share and download custom templates. 84 | 85 | 4. **Collaboration Features**: Real-time collaboration tools for team projects. 86 | 87 | 5. **Plugin System**: Allowing developers to extend Mockly's functionality with custom plugins. 88 | 89 | We encourage contributors to think creatively about these future directions and propose innovative solutions to help bring these ideas to life. 90 | 91 | ## 🎉 Hacktoberfest 2024 92 | 93 | We're excited to participate in Hacktoberfest 2024! Here's how you can contribute: 94 | 95 | ### 📜 Ground Rules 96 | 97 | 1. Contributions must be meaningful and add value to the project. 98 | 2. Follow our code style and best practices. 99 | 3. Be respectful and collaborative in discussions. 100 | 4. Test your changes thoroughly before submitting a PR. 101 | 102 | ### 🛠️ How to Contribute 103 | 104 | 1. Fork the repository to your GitHub account. 105 | 2. Create a new branch for your feature or bug fix: 106 | ``` 107 | git checkout -b feature/your-feature-name 108 | ``` 109 | or 110 | ``` 111 | git checkout -b fix/your-bug-fix-name 112 | ``` 113 | 3. Make your changes, ensuring they align with the issue template if addressing a specific issue. 114 | 4. Commit your changes with a clear and descriptive commit message. 115 | 5. Push your branch to your forked repository: 116 | ``` 117 | git push origin feature/your-feature-name 118 | ``` 119 | 6. Open a Pull Request (PR) to our `main` branch. 120 | 121 | ### 🔄 Pull Request Process 122 | 123 | 1. Ensure your PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 124 | 2. Include screenshots or GIFs in your PR if you've made UI changes. 125 | 3. Make sure your code is properly formatted and passes all tests. 126 | 4. Your PR will be reviewed by maintainers. Be open to feedback and make necessary changes. 127 | 5. Once approved, your PR will be merged into the main codebase. 128 | 129 | ### 📝 Issue Templates 130 | 131 | When creating a new issue or PR, please use our provided templates: 132 | 133 | - For bug reports: [Bug Report Template](.github/ISSUE_TEMPLATE/bug_report.md) 134 | - For feature requests: [Feature Request Template](.github/ISSUE_TEMPLATE/feature_request.md) 135 | - For pull requests: [Pull Request Template](.github/PULL_REQUEST_TEMPLATE.md) 136 | 137 | ### 🐛 Existing Issues and Contributions 138 | 139 | We encourage contributors to explore our [existing issues](https://github.com/suryanshsingh2001/mockly/issues) and contribute to them. Here are some ways you can help: 140 | 141 | 1. **Bug Fixes**: Look for issues labeled `bug` and help us squash them! 142 | 2. **Feature Implementation**: Issues labeled `enhancement` are great opportunities to add new features to Mockly. 143 | 3. **Documentation**: Help us improve our docs by addressing issues labeled `documentation`. 144 | 4. **UI/UX Improvements**: If you have design skills, look for issues labeled `ui` or `ux`. 145 | 5. **Performance Optimization**: Help make Mockly faster by tackling issues labeled `performance`. 146 | 147 | Don't see an issue that matches your interests? Feel free to [create a new issue](https://github.com/suryanshsingh2001/mockly/issues/new/choose) and discuss your ideas with the community! 148 | 149 | We look forward to your contributions and hope you enjoy participating in Hacktoberfest with Mockly! 150 | 151 | ## ❓ Frequently Asked Questions (FAQs) 152 | 153 |
154 | 1. What is Mockly? 155 | Mockly is an open-source tool designed to help developers create professional-looking screenshots and mockups quickly and easily. It’s perfect for developers who want to produce high-quality visuals without needing to dive into complex design tools. 156 |
157 | 158 |
159 | 2. Do I need to sign up to use Mockly? 160 | No, Mockly does not require any login or account creation. You can start using the tool right away without the need for any sign-up process. 161 |
162 | 163 |
164 | 3. Is Mockly free to use? 165 | Yes, Mockly is completely free to use, and there are no hidden fees or watermarks. You get access to all its powerful features without any cost. 166 |
167 | 168 |
169 | 4. Is my data secure when using Mockly? 170 | Yes, it is. Mockly does not store any of your images, text, or designs. All processing is done in real-time, and nothing is saved on our servers. 171 |
172 | 173 |
174 | 5. What formats does Mockly support for screenshots? 175 | You can upload images in common formats like PNG, JPEG, and SVG. These formats are widely supported and make it easy to incorporate your screenshots into the tool. 176 |
177 | 178 |
179 | 6. How can I customize background image? 180 | You can customize the background image by uploading your own image or selecting from the available templates. You can also adjust the position, zoom, and text to create a unique mockup. 181 |
182 | 183 |
184 | 7. Can I preview my designs on multiple devices? 185 | Yes! Mockly allows you to preview your designs on various screen sizes and devices. This feature ensures that your mockups will look great on any platform. 186 |
187 | 188 |
189 | 8. Does Mockly support multi-language text input? 190 | Yes, Mockly supports multi-language text input. Just enter your preferred language in the text field, and Mockly will render it accordingly. 191 |
192 | 193 |
194 | 9. Are there any system requirements for Mockly? 195 | To run Mockly locally, you'll need Node.js (v14 or later) installed on your machine. Mockly works best in modern web browsers like Chrome, Firefox, and Safari for its web version. 196 |
197 | 198 |
199 | 9. Can I collaborate with others on Mockly? 200 | As of now, Mockly doesn't have real-time collaboration features, but they are part of the future roadmap! Stay tuned for updates. 201 |
202 | 203 |
204 | 10. Does Mockly have a plugin system? 205 | Not yet, but we plan to introduce a plugin system in the future that will allow developers to extend the tool's functionality by creating and integrating custom plugins. 206 |
207 | 208 |
209 | 11. How do I contribute to the project? 210 | Mockly is open-source and welcomes contributions! You can check out our contribution guidelines and participate in Hacktoberfest or submit features, bug fixes, and more throughout the year. 211 |
212 | 213 |
214 | 12. Where can I report bugs or suggest features? 215 | You can report bugs or suggest features by creating an issue in the Mockly GitHub repository. Make sure to check if an issue already exists before creating a new one! 216 |
217 | 218 |
219 | 13. Can I use Mockly for commercial purposes? 220 | Yes, Mockly is licensed under the MIT License, which allows you to use it freely for both personal and commercial purposes. 221 |
222 | 223 |
224 | 14. Will Mockly support video editing? 225 | Yes, we are planning to add video editing capabilities in the future, so you can create stunning video content with the same ease as creating screenshots. 226 |
227 | 228 |
229 | 15. What if I need help with Mockly? 230 | For any questions or help, feel free to reach out through our GitHub Discussions or contact us via our project website or GitHub repository. 231 |
232 | 233 | ## 👥 Contributing 234 | 235 | We welcome contributions from the community! Please read our [Contribution Guidelines](CONTRIBUTING.md) for more details on our year-round contribution process. 236 | 237 | 238 | 239 | 240 | 241 | 242 | ## 🤝 Code of Conduct 243 | 244 | We are committed to fostering an inclusive and welcoming community. Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before participating. 245 | 246 | ## 📄 License 247 | 248 | This project is licensed under the [MIT License](LICENSE). 249 | 250 | ## 📞 Contact 251 | 252 | - Project Maintainer: [Suryansh](https://www.linkedin.com/in/suryanshsingh2001/) 253 | - Project Website: [https://www.mockly.site](https://www.mockly.site) 254 | 255 | ## 🙏 Acknowledgments 256 | 257 | - Thanks to all contributors who have helped shape Mockly. 258 | - Built with [Next.js](https://nextjs.org/) and [shadcn/ui](https://ui.shadcn.com/). 259 | - Icons provided by [Lucide](https://lucide.dev/). 260 | 261 | --- 262 | 263 | Made with ❤️ by the Mockly community 264 | -------------------------------------------------------------------------------- /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/(public)/privacy/page.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import Link from "next/link" 3 | 4 | export default function PrivacyPolicy() { 5 | return ( 6 |
7 |
8 |

Privacy Policy

9 |
10 |

11 | Last updated: {new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })} 12 |

13 | 14 |

1. Information We Collect

15 |

16 | We collect information you provide directly to us when you create an account, use our Service, or communicate with us. This may include: 17 |

18 |
    19 |
  • Personal information such as your name and email address
  • 20 |
  • Usage data and preferences
  • 21 |
  • Content you create, upload, or share using our Service
  • 22 |
23 | 24 |

2. How We Use Your Information

25 |

26 | We use the information we collect to: 27 |

28 |
    29 |
  • Provide, maintain, and improve our Service
  • 30 |
  • Process transactions and send related information
  • 31 |
  • Send you technical notices, updates, security alerts, and support messages
  • 32 |
  • Respond to your comments, questions, and requests
  • 33 |
  • Develop new features and services
  • 34 |
35 | 36 |

3. Information Sharing and Disclosure

37 |

38 | We do not share, sell, rent, or trade your personal information with third parties for their commercial purposes. We may share information as follows: 39 |

40 |
    41 |
  • With vendors, consultants, and other service providers who need access to such information to carry out work on our behalf
  • 42 |
  • In response to a request for information if we believe disclosure is in accordance with any applicable law, regulation, or legal process
  • 43 |
  • If we believe your actions are inconsistent with our user agreements or policies, or to protect the rights, property, and safety of Mockly or others
  • 44 |
45 | 46 |

4. Data Security

47 |

48 | We use reasonable measures to help protect information about you from loss, theft, misuse and unauthorized access, disclosure, alteration, and destruction. However, no internet or email transmission is ever fully secure or error-free. 49 |

50 | 51 |

5. Your Choices

52 |

53 | You may update, correct, or delete your account information at any time by logging into your online account. If you wish to delete or deactivate your account, please email us, but note that we may retain certain information as required by law or for legitimate business purposes. 54 |

55 | 56 |

6. Changes to this Policy

57 |

58 | We may change this privacy policy from time to time. If we make changes, we will notify you by revising the date at the top of the policy and, in some cases, we may provide you with additional notice (such as adding a statement to our homepage or sending you a notification). 59 |

60 | 61 |

7. Contact Us

62 |

63 | If you have any questions about this Privacy Policy, please contact us at: privacy@mockly.com 64 |

65 |
66 |
67 | 70 |
71 |
72 |
73 | ) 74 | } -------------------------------------------------------------------------------- /app/(public)/terms/page.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import Link from "next/link" 3 | 4 | export default function TermsOfService() { 5 | return ( 6 |
7 |
8 |

Terms of Service

9 |
10 |

11 | Last updated: {new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })} 12 |

13 | 14 |

1. Acceptance of Terms

15 |

16 | By accessing or using Mockly, you agree to be bound by these Terms of Service. If you disagree with any part of the terms, you may not use our Service. 17 |

18 | 19 |

2. Description of Service

20 |

21 | Mockly provides an online platform for creating and editing screenshots and mockups . We reserve the right to modify or discontinue, temporarily or permanently, the Service with or without notice. 22 |

23 | 24 |

3. User Accounts

25 |

26 | When you create an account with us, you must provide accurate and complete information. You are solely responsible for the activity that occurs on your account, and you must keep your account password secure. 27 |

28 | 29 |

4. Intellectual Property Rights

30 |

31 | The Service and its original content, features, and functionality are owned by Mockly and are protected by international copyright, trademark, patent, trade secret, and other intellectual property or proprietary rights laws. 32 |

33 | 34 |

5. User-Generated Content

35 |

36 | You retain your intellectual property rights for content you submit to Mockly. By submitting content, you grant us a worldwide, non-exclusive, royalty-free license to use, reproduce, adapt, publish, translate, and distribute your content. 37 |

38 | 39 |

6. Termination

40 |

41 | We may terminate or suspend access to our Service immediately, without prior notice or liability, for any reason whatsoever, including without limitation if you breach the Terms. 42 |

43 | 44 |

7. Limitation of Liability

45 |

46 | In no event shall Mockly, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from your access to or use of or inability to access or use the Service. 47 |

48 | 49 |

8. Changes to Terms

50 |

51 | We reserve the right, at our sole discretion, to modify or replace these Terms at any time. What constitutes a material change will be determined at our sole discretion. By continuing to access or use our Service after those revisions become effective, you agree to be bound by the revised terms. 52 |

53 |
54 |
55 | 58 |
59 |
60 |
61 | ) 62 | } -------------------------------------------------------------------------------- /app/blogs/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { siteConfig } from "@/config/config"; 2 | import { getPost } from "@/lib/blog"; 3 | import { formatDate, calculateReadTime } from "@/lib/utils"; 4 | import type { Metadata } from "next"; 5 | import { notFound } from "next/navigation"; 6 | import { Suspense } from "react"; 7 | import { Clock, Calendar, ArrowLeft } from "lucide-react"; 8 | import Image from "next/image"; 9 | import { Button } from "@/components/ui/button"; 10 | import Link from "next/link"; 11 | import { Avatar, AvatarImage } from "@/components/ui/avatar"; 12 | 13 | 14 | 15 | export async function generateMetadata({ 16 | params, 17 | }: { 18 | params: { 19 | slug: string; 20 | }; 21 | }): Promise { 22 | const post = await getPost(params.slug); 23 | 24 | const { 25 | title, 26 | publishedAt: publishedTime, 27 | summary: description, 28 | image, 29 | } = post.metadata; 30 | 31 | return { 32 | title, 33 | description, 34 | openGraph: { 35 | title, 36 | description, 37 | type: "article", 38 | publishedTime, 39 | url: `${siteConfig.url}/blog/${post.slug}`, 40 | images: [ 41 | { 42 | url: siteConfig.ogImage, 43 | }, 44 | ], 45 | }, 46 | twitter: { 47 | card: "summary_large_image", 48 | title, 49 | description, 50 | images: [siteConfig.ogImage], 51 | }, 52 | }; 53 | } 54 | 55 | export default async function Blog({ 56 | params, 57 | }: { 58 | params: { 59 | slug: string; 60 | }; 61 | }) { 62 | const post = await getPost(params.slug); 63 | 64 | 65 | 66 | 67 | if (!post) { 68 | notFound(); 69 | } 70 | 71 | const readTime = calculateReadTime(post.source); 72 | 73 | return ( 74 |
75 |
76 |
77 | 78 | 79 | 80 | 84 | 85 |
86 | 87 |
88 | {post.metadata.title} 95 |
96 | 97 |
98 |

99 | {post.metadata.title} 100 |

101 | 102 |
103 |
104 | 105 | 106 | 107 |
108 |

{siteConfig.author}

109 |

{"Owner and Maintainer"}

110 |
111 |
112 | 113 |
114 |
115 | 116 | 119 |
120 |
121 | 122 | {readTime} min read 123 |
124 |
125 |
126 |
127 | 128 |
129 |
130 |
131 |
132 |
133 | ); 134 | } 135 | -------------------------------------------------------------------------------- /app/blogs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { BackgroundGradient } from "@/components/layout/background-gradient"; 2 | import { Header } from "@/components/layout/LandingHeader"; 3 | 4 | interface RootLayoutProps { 5 | children: React.ReactNode; 6 | } 7 | 8 | export default function RootLayout({ children }: RootLayoutProps) { 9 | return ( 10 |
11 | {/* Apply explicitly defined background gradient instead of using dynamic props */} 12 |
13 | {/* Gradient blobs */} 14 |
15 |
16 |
17 | 18 | {/* Decorative shapes */} 19 |
20 |
21 |
22 | 23 | {/* Grid pattern */} 24 |
25 |
26 | 27 | {/* Header component */} 28 |
29 | 30 | {/* Main content with proper z-index to appear above gradient */} 31 |
{children}
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /app/blogs/page.tsx: -------------------------------------------------------------------------------- 1 | import { getBlogPosts } from "@/lib/blog"; 2 | import { formatDate } from "@/lib/utils"; 3 | import Image from "next/image"; 4 | import Link from "next/link"; 5 | import { ArrowRight } from "lucide-react"; 6 | import { Button } from "@/components/ui/button"; 7 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 8 | 9 | export const metadata = { 10 | title: "Blog", 11 | description: "My thoughts on software development, life, and more.", 12 | }; 13 | 14 | export default async function BlogPage() { 15 | const posts = await getBlogPosts(); 16 | 17 | return ( 18 |
19 |
20 |

21 | Mockly Blog 22 |

23 |

24 | Blogs about Mockly and more. 25 |

26 | 27 |
28 | {posts 29 | .sort( 30 | (a, b) => 31 | new Date(b.metadata.publishedAt).getTime() - 32 | new Date(a.metadata.publishedAt).getTime() 33 | ) 34 | .map((post) => ( 35 |
39 | 40 | {post.metadata.title} 47 |

48 | {post.metadata.title} 49 |

50 |

51 | {post.metadata.summary} 52 |

53 | 56 | 57 |
58 | ))} 59 |
60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /app/editor/old/page.tsx: -------------------------------------------------------------------------------- 1 | import EditorNotAvailable from "@/components/shared/NotAvailable" 2 | import MockupEditor from "@/components/shared/Editor" 3 | import { siteConfig } from "@/config/config" 4 | 5 | export default function EditorPage() { 6 | return siteConfig.isEditorActive ? : 7 | } -------------------------------------------------------------------------------- /app/editor/page.tsx: -------------------------------------------------------------------------------- 1 | import EditorNotAvailable from "@/components/shared/NotAvailable" 2 | import MockupEditor from "@/components/shared/Editor.v2" 3 | import { siteConfig } from "@/config/config" 4 | 5 | 6 | 7 | 8 | export async function generateMetadata() { 9 | return { 10 | title: "Editor", 11 | description: "Design and customize mockups for your projects.", 12 | openGraph: { 13 | title: "Mockly Editor", 14 | description: "Design and customize mockups for your projects.", 15 | type: "website", 16 | images: [ 17 | { 18 | url: siteConfig.ogImage, 19 | }, 20 | ], 21 | }, 22 | } 23 | } 24 | 25 | export default function EditorPage() { 26 | return siteConfig.isEditorActive ? : 27 | } -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | html { 7 | --scroll-behavior: smooth!important; 8 | scroll-behavior: smooth!important; 9 | } 10 | 11 | :root { 12 | --foreground-rgb: 0, 0, 0; 13 | --background-start-rgb: 214, 219, 220; 14 | --background-end-rgb: 255, 255, 255; 15 | } 16 | 17 | @media (prefers-color-scheme: dark) { 18 | :root { 19 | --foreground-rgb: 255, 255, 255; 20 | --background-start-rgb: 0, 0, 0; 21 | --background-end-rgb: 0, 0, 0; 22 | } 23 | } 24 | 25 | @layer utilities { 26 | .text-balance { 27 | text-wrap: balance; 28 | } 29 | } 30 | 31 | 32 | @layer base { 33 | :root { 34 | --background: 0 0% 100%; 35 | --foreground: 224 71.4% 4.1%; 36 | --card: 0 0% 100%; 37 | --card-foreground: 224 71.4% 4.1%; 38 | --popover: 0 0% 100%; 39 | --popover-foreground: 224 71.4% 4.1%; 40 | --primary: 262.1 83.3% 57.8%; 41 | --primary-foreground: 210 20% 98%; 42 | --secondary: 220 14.3% 95.9%; 43 | --secondary-foreground: 220.9 39.3% 11%; 44 | --muted: 220 14.3% 95.9%; 45 | --muted-foreground: 220 8.9% 46.1%; 46 | --accent: 220 14.3% 95.9%; 47 | --accent-foreground: 220.9 39.3% 11%; 48 | --destructive: 0 84.2% 60.2%; 49 | --destructive-foreground: 210 20% 98%; 50 | --border: 220 13% 91%; 51 | --input: 220 13% 91%; 52 | --ring: 262.1 83.3% 57.8%; 53 | --radius: 0.5rem; 54 | --chart-1: 12 76% 61%; 55 | --chart-2: 173 58% 39%; 56 | --chart-3: 197 37% 24%; 57 | --chart-4: 43 74% 66%; 58 | --chart-5: 27 87% 67%; 59 | } 60 | 61 | .dark { 62 | --background: 224 71.4% 4.1%; 63 | --foreground: 210 20% 98%; 64 | --card: 224 71.4% 4.1%; 65 | --card-foreground: 210 20% 98%; 66 | --popover: 224 71.4% 4.1%; 67 | --popover-foreground: 210 20% 98%; 68 | --primary: 263.4 70% 50.4%; 69 | --primary-foreground: 210 20% 98%; 70 | --secondary: 215 27.9% 16.9%; 71 | --secondary-foreground: 210 20% 98%; 72 | --muted: 215 27.9% 16.9%; 73 | --muted-foreground: 217.9 10.6% 64.9%; 74 | --accent: 215 27.9% 16.9%; 75 | --accent-foreground: 210 20% 98%; 76 | --destructive: 0 62.8% 30.6%; 77 | --destructive-foreground: 210 20% 98%; 78 | --border: 215 27.9% 16.9%; 79 | --input: 215 27.9% 16.9%; 80 | --ring: 263.4 70% 50.4%; 81 | --chart-1: 220 70% 50%; 82 | --chart-2: 160 60% 45%; 83 | --chart-3: 30 80% 55%; 84 | --chart-4: 280 65% 60%; 85 | --chart-5: 340 75% 55%; 86 | } 87 | } 88 | 89 | @layer base { 90 | * { 91 | @apply border-border; 92 | } 93 | body { 94 | @apply bg-background text-foreground; 95 | 96 | } 97 | } 98 | 99 | h3 code { 100 | @apply !text-lg md:!text-xl; 101 | } 102 | 103 | pre { 104 | @apply !px-0 rounded-lg overflow-x-auto py-4 105 | } 106 | 107 | pre [data-line] { 108 | @apply px-4 109 | } 110 | 111 | code { 112 | @apply text-sm md:text-base !leading-loose; 113 | } 114 | 115 | pre > code { 116 | counter-reset: line; 117 | } 118 | 119 | code[data-theme*=" "], 120 | code[data-theme*=" "] span { 121 | color: var(--shiki-light); 122 | background-color: var(--shiki-light-bg); 123 | } 124 | 125 | @media (prefers-color-scheme: dark) { 126 | code[data-theme*=" "], 127 | code[data-theme*=" "] span { 128 | color: var(--shiki-dark); 129 | background-color: var(--shiki-dark-bg); 130 | } 131 | } 132 | 133 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import { Metadata } from "next"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | import { TailwindIndicator } from "@/components/tailwind-indicator"; 6 | import { ThemeProvider } from "@/components/theme-provider"; 7 | import { siteConfig } from "@/config/config"; 8 | import { Rethink_Sans } from "next/font/google"; 9 | 10 | const raleway = Rethink_Sans({ 11 | subsets: ["latin"], 12 | }); 13 | 14 | export const metadata: Metadata = { 15 | title: { 16 | default: siteConfig.name, 17 | template: `%s - ${siteConfig.name}`, 18 | }, 19 | description: siteConfig.description, 20 | openGraph: { 21 | images: siteConfig.ogImage, 22 | title: `${siteConfig.name}`, 23 | description: siteConfig.description, 24 | url: siteConfig.url, 25 | siteName: `${siteConfig.name}`, 26 | locale: "en_US", 27 | type: "website", 28 | }, 29 | 30 | icons: { 31 | icon: "/favicon.ico", 32 | apple: "/apple-touch-icon.png", 33 | }, 34 | robots: { 35 | index: true, 36 | follow: true, 37 | googleBot: { 38 | index: true, 39 | follow: true, 40 | "max-video-preview": -1, 41 | "max-image-preview": "large", 42 | "max-snippet": -1, 43 | }, 44 | }, 45 | twitter: { 46 | title: `${siteConfig.name}`, 47 | card: "summary_large_image", 48 | images: siteConfig.ogImage, 49 | }, 50 | }; 51 | 52 | interface RootLayoutProps { 53 | children: React.ReactNode; 54 | } 55 | 56 | export default function RootLayout({ children }: RootLayoutProps) { 57 | return ( 58 | <> 59 | 60 | 61 | 62 | 68 | {children} 69 | {/* */} 70 | 71 | 72 | 73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import { ArrowLeft, Search } from "lucide-react" 3 | import Link from "next/link" 4 | 5 | export default function NotFound() { 6 | return ( 7 |
8 |
9 |
10 | 11 |
12 |

404

13 |

Page Not Found

14 |

15 | Oops! The page you're looking for doesn't exist or has been moved. 16 |

17 |
18 | 24 | 30 |
31 |
32 |
33 | ) 34 | } -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import LandingPage from "@/components/layout/LandingPage"; 2 | import React from "react"; 3 | 4 | const App = () => { 5 | return ; 6 | }; 7 | 8 | export default App; 9 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /components/icons.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | LucideProps, 3 | Moon, 4 | SunMedium, 5 | Twitter, 6 | type Icon as LucideIcon, 7 | } from "lucide-react"; 8 | 9 | export type Icon = typeof LucideIcon; 10 | 11 | export const Icons = { 12 | sun: SunMedium, 13 | moon: Moon, 14 | twitter: Twitter, 15 | logo: (props: LucideProps) => ( 16 | 17 | 21 | 22 | ), 23 | gitHub: (props: LucideProps) => ( 24 | 25 | 29 | 30 | ), 31 | }; 32 | -------------------------------------------------------------------------------- /components/layout/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { useTheme } from "next-themes"; 4 | import { Button } from "@/components/ui/button"; 5 | import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; 6 | import { ImageIcon, Menu, Moon, Sun } from "lucide-react"; 7 | import { Icons } from "@/components/icons"; 8 | import { siteConfig } from "@/config/config"; 9 | import { buttonVariants } from "@/components/ui/button"; 10 | 11 | export default function Header() { 12 | const { theme, setTheme } = useTheme(); 13 | 14 | return ( 15 |
16 |
17 |
18 | 19 | 20 | 24 | 25 | 26 | 46 | 47 | 48 | 49 |
50 | 51 | Mockly 52 |
53 | 54 |
55 |
56 | 98 |
99 |
100 |
101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /components/layout/LandingHeader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import Link from "next/link"; 5 | import { Menu, Sparkles, X } from "lucide-react"; 6 | import { AnimatePresence, motion } from "framer-motion"; 7 | import { Button } from "@/components/ui/button"; 8 | import { ThemeToggle } from "@/components/theme-toggle"; 9 | import { BackgroundGradient } from "@/components/layout/background-gradient"; 10 | import Image from "next/image"; 11 | import { siteConfig } from "@/config/config"; 12 | 13 | export function Header() { 14 | const [mobileMenuOpen, setMobileMenuOpen] = useState(false); 15 | 16 | const toggleMobileMenu = () => { 17 | setMobileMenuOpen(!mobileMenuOpen); 18 | }; 19 | 20 | return ( 21 | <> 22 |
23 |
24 | {/* Subtle background gradient specifically for header */} 25 | 32 | 33 | {/* Additional glass effect for the header */} 34 |
35 | 36 |
37 | 43 | 44 | Logo 51 | Mockly 52 | 53 | 54 | 55 | {/* Desktop Navigation */} 56 | 129 | 130 | {/* Mobile Menu Button */} 131 | 139 | 140 | 141 |
142 |
143 | 144 | {/* Decorative accent line with gradient */} 145 |
146 |
147 | 148 | {/* Mobile Side Menu */} 149 | 150 | 151 | ); 152 | } 153 | 154 | function MobileSideMenu({ 155 | isOpen, 156 | onClose, 157 | }: { 158 | isOpen: boolean; 159 | onClose: () => void; 160 | }) { 161 | return ( 162 | 163 | {isOpen && ( 164 | <> 165 | 172 | 179 | {/* Gradient background for mobile menu */} 180 |
181 | 187 |
188 | 189 |
190 |
191 |
192 | Logo 199 | Mockly 200 |
201 | 208 |
209 | 249 | 250 | {/* Decorative elements */} 251 |
252 |
253 |
254 |
255 | 256 | )} 257 |
258 | ); 259 | } 260 | -------------------------------------------------------------------------------- /components/layout/background-gradient.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "framer-motion"; 4 | 5 | type BackgroundGradientProps = { 6 | className?: string; 7 | primaryColor?: string; 8 | secondaryColor?: string; 9 | accentColor?: string; 10 | gridOpacity?: string; 11 | disableShapes?: boolean; // New prop to disable decorative shapes 12 | }; 13 | 14 | export function BackgroundGradient({ 15 | className = "", 16 | primaryColor = "primary", 17 | secondaryColor = "violet-500", 18 | accentColor = "fuchsia-500", 19 | gridOpacity = "0.03", 20 | disableShapes = false // Default to false to maintain current behavior 21 | }: BackgroundGradientProps) { 22 | return ( 23 |
24 | {/* Gradient blobs */} 25 |
26 |
27 |
28 | 29 | {/* Decorative shapes - rendered conditionally based on disableShapes prop */} 30 | {!disableShapes && ( 31 | <> 32 |
33 |
34 |
35 | 36 | )} 37 | 38 | {/* Grid pattern */} 39 |
40 |
41 | ); 42 | } -------------------------------------------------------------------------------- /components/sections/CTASection.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { ArrowRight, Github, Star, GitFork, Eye } from "lucide-react"; 3 | import { Button } from "@/components/ui/button"; 4 | 5 | export default function CTASection() { 6 | return ( 7 |
8 |
9 |
10 |
11 |

12 | Ready to Get Started? 13 |

14 |

15 | Take control of your projects with powerful tools and a flexible, 16 | open-source platform. We are proudly open source! 17 |

18 |
19 |
20 |
21 | 22 | 15.2k Stars 23 |
24 |
25 | 26 | 2.8k Forks 27 |
28 |
29 | 30 | 342 Watchers 31 |
32 |
33 |
34 | 35 | 42 | 43 | 44 | 52 | 53 |
54 |
55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /components/sections/FAQSection.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useState } from 'react' 4 | import { ChevronDown } from 'lucide-react' 5 | import { Card, CardContent } from "@/components/ui/card" 6 | import { faqs } from "@/lib/constants" 7 | import { motion } from "framer-motion" 8 | import { BackgroundGradient } from "../layout/background-gradient" 9 | 10 | export default function FAQSection() { 11 | const [openIndex, setOpenIndex] = useState(null) 12 | 13 | const toggleQuestion = (index: number) => { 14 | setOpenIndex(openIndex === index ? null : index) 15 | } 16 | 17 | return ( 18 |
19 | {/* Custom background with diagonal grid pattern */} 20 | 27 | 28 | 29 | 30 |
31 | 38 |
39 |

40 | Frequently Asked Questions 41 |

42 |

43 | Got questions? We've got answers. If you can't find what you're looking for, feel free to reach out. 44 |

45 |
46 |
47 | 48 | 55 | {faqs.map((faq, index) => ( 56 | 63 | 64 | 65 | 76 |
81 |

{faq.answer}

82 |
83 |
84 |
85 |
86 | ))} 87 |
88 | 89 | 90 |
91 |
92 | ) 93 | } -------------------------------------------------------------------------------- /components/sections/FeatureSection.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "framer-motion"; 4 | import Image from "next/image"; 5 | import { Laptop, LucideIcon, MousePointer } from "lucide-react"; 6 | import { cn } from "@/lib/utils"; 7 | 8 | import { 9 | ImagePlus, 10 | Monitor, 11 | ZoomIn, 12 | Type, 13 | Palette, 14 | Layers, 15 | Share, 16 | Sparkles, 17 | MoreHorizontal, 18 | } from "lucide-react"; 19 | import { BackgroundGradient } from "../layout/background-gradient"; 20 | type FeatureProps = { 21 | icon: React.ReactNode | LucideIcon; 22 | title: string; 23 | description: string; 24 | className?: string; 25 | }; 26 | 27 | export function FeaturesSection() { 28 | const title = "Features that make your work easier"; 29 | const subtitle = 30 | "Everything you need to create beautiful mockups in seconds, without the complexity."; 31 | 32 | const features = [ 33 | { 34 | icon: , 35 | title: "Instant Mockups", 36 | description: 37 | "Upload, tweak, done. It's like magic, but without the wand.", 38 | }, 39 | { 40 | icon: , 41 | title: "Multi-device Preview", 42 | description: 43 | "Instantly see how your designs look on any screen size—because nobody has time for resizing.", 44 | }, 45 | { 46 | icon: , 47 | title: "Zoom & Place", 48 | description: 49 | "Put your screenshots exactly where you want, and zoom in like a pro—minus the struggle.", 50 | }, 51 | { 52 | icon: , 53 | title: "Text & Style", 54 | description: 55 | "Add text, pick your font, and make it pop. Because plain screenshots are so last year.", 56 | }, 57 | { 58 | icon: , 59 | title: "Custom Colors", 60 | description: 61 | "Choose from a wide palette or create your own. Your mockups, your style.", 62 | }, 63 | { 64 | icon: , 65 | title: "No Watermarks", 66 | description: 67 | "No ugly watermarks, no strings attached. Just beautiful mockups, ready to use.", 68 | }, 69 | { 70 | icon: , 71 | title: "Easy Export", 72 | description: 73 | "Share your creations with a single click. Available in all the formats you need.", 74 | }, 75 | { 76 | icon: , 77 | title: "Auto Color Detection", 78 | description: 79 | "Mockly automatically detects the dominant colors in your screenshots and applies them to your mockups. No more manual adjustments—just upload and let Mockly do the rest.", 80 | }, 81 | { 82 | icon: , 83 | title: "Video Editor (Coming Soon)", 84 | description: 85 | "Turn your screenshots into stunning videos. Because sometimes, a picture is not enough.", 86 | }, 87 | { 88 | icon: , 89 | title: "And More", 90 | description: 91 | "We're always adding new features to make your experience even better. Stay tuned!", 92 | }, 93 | ]; 94 | return ( 95 |
96 | 97 |
98 | 105 |

106 | Designed for developers,{" "} 107 | built for speed 108 |

109 |

110 | Mockly gives you all the tools you need to create stunning mockups 111 | without the learning curve. 112 |

113 |
114 | 115 | {/* Bento Grid */} 116 |
117 | {/* Large feature card - Instant Mockups */} 118 | 130 |
131 |
132 |
133 | {features[0].icon} 134 |
135 |

{features[0].title}

136 |

137 | {features[0].description} Choose from dozens of device frames to 138 | showcase your work in the perfect context. 139 |

140 | {features[0].title} 147 |
148 |
149 | 150 | {/* Medium feature card - Multi-device Preview */} 151 | 163 |
164 |
165 |
166 | {features[1].icon} 167 |
168 |

{features[1].title}

169 |

{features[1].description}

170 |
171 |
172 | 173 | {/* Small feature card - Zoom & Place */} 174 | 186 |
187 |
188 |
189 | {features[2].icon} 190 |
191 |

{features[2].title}

192 |

{features[2].description}

193 |
194 |
195 | 196 | {/* Medium feature card - Text & Style */} 197 | 209 |
210 |
211 |
212 | {features[3].icon} 213 |
214 |

{features[3].title}

215 |

{features[3].description}

216 |
217 |
218 | 219 | {/* Medium feature card - Custom Colors */} 220 | 232 |
233 |
234 |
235 | {features[4].icon} 236 |
237 |

{features[4].title}

238 |

239 | {features[4].description} Perfect for creating a consistent 240 | brand experience across your mockups. 241 |

242 |
243 |
244 | 245 | {/* Medium feature card - No Watermarks */} 246 | 258 |
259 |
260 |
261 | {features[5].icon} 262 |
263 |

{features[5].title}

264 |

{features[5].description}

265 |
266 |
267 | 268 | {/* Small feature card - Easy Export */} 269 | 281 |
282 |
283 |
284 | {features[6].icon} 285 |
286 |

{features[6].title}

287 |

{features[6].description}

288 |
289 |
290 | 291 | {/* Small feature card - Auto Color Detection */} 292 | 304 |
305 |
306 |
307 | {features[7].icon} 308 |
309 |

{features[7].title}

310 |

{features[7].description}

311 |
312 |
313 |
314 |
315 |
316 | ); 317 | } 318 | -------------------------------------------------------------------------------- /components/sections/PricingSection.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "framer-motion"; 4 | import { Check } from "lucide-react"; 5 | import { siteConfig } from "@/config/config"; 6 | import { Button } from "@/components/ui/button"; 7 | import { Card, CardContent } from "@/components/ui/card"; 8 | import { BackgroundGradient } from "../layout/background-gradient"; 9 | 10 | export function PricingSection() { 11 | return ( 12 |
13 | 18 |
19 | 26 |

27 | Simple, transparent pricing 28 |

29 |

30 | No hidden fees, no complicated tiers. Choose the plan that works 31 | for you. 32 |

33 |
34 | 35 |
36 | {siteConfig.pricing.map((plan, index) => ( 37 | 44 | 51 | {plan.isRecommended && ( 52 |
53 | )} 54 | 55 |
56 |

{plan.name}

57 | {plan.isRecommended && ( 58 | 59 | Popular 60 | 61 | )} 62 |
63 |
64 | {plan.price} 65 | {plan.price !== "Free" && plan.price !== "Custom" && plan.price !== "Coming Soon" && ( 66 | /month 67 | )} 68 |
69 |

70 | {plan.name === "Starter" && "Perfect for hobbyists and small projects."} 71 | {plan.name === "Pro" && "For professionals and growing teams."} 72 | {plan.name === "Enterprise" && "For large teams and organizations."} 73 |

74 |
    75 | {plan.features.map((feature, i) => ( 76 |
  • 77 | 78 | {feature} 79 |
  • 80 | ))} 81 |
82 | 88 |
89 |
90 |
91 | ))} 92 |
93 |
94 |
95 | ); 96 | } -------------------------------------------------------------------------------- /components/shadow-manager.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { 3 | Popover, 4 | PopoverContent, 5 | PopoverTrigger, 6 | } from "@/components/ui/popover"; 7 | import { Label } from "./ui/label"; 8 | import { Input } from "./ui/input"; 9 | import { Settings2Icon } from "lucide-react"; 10 | 11 | export type Shadow = { 12 | color: string; 13 | x: number; 14 | y: number; 15 | blur: number; 16 | }; 17 | 18 | interface ShadowManagerProps { 19 | shadowValue: Shadow; 20 | setShadowValue: React.Dispatch> 21 | } 22 | 23 | export const ShadowManager: React.FC = ({ 24 | shadowValue, 25 | setShadowValue, 26 | }) => { 27 | const [popoverVisible, setPopoverVisible] = useState(false); 28 | const [isMobile, setIsMobile] = useState(false); 29 | 30 | const handleInputChange = (newValues: Partial) => { 31 | const updatedShadow = { 32 | x: shadowValue.x, 33 | y: shadowValue.y, 34 | color: shadowValue.color, 35 | blur: shadowValue.blur, 36 | ...newValues, 37 | }; 38 | setShadowValue(updatedShadow); 39 | }; 40 | 41 | useEffect(() => { 42 | const handleResize = () => { 43 | setIsMobile(window.innerWidth < 1024); 44 | }; 45 | 46 | window.addEventListener("resize", handleResize); 47 | 48 | return () => { 49 | window.removeEventListener("resize", handleResize); 50 | }; 51 | }, []); 52 | 53 | const handleBlur = () => { 54 | const updatedShadow = { 55 | ...shadowValue, 56 | x: shadowValue.x ? shadowValue.x : 0, 57 | y: shadowValue.y ? shadowValue.y : 0, 58 | blur: shadowValue.blur ? shadowValue.blur : 0 59 | } 60 | setShadowValue(updatedShadow); 61 | } 62 | 63 | return ( 64 | 65 | 66 | 67 | 68 | 69 | 73 |
74 |

Shadow Settings

75 |

76 | Tweak how the shadow is applied. 77 |

78 |
79 | 80 |
81 | 87 | { 93 | const newValue = e.target.value; 94 | setShadowValue({ ...shadowValue, color: newValue }); 95 | handleInputChange({ color: newValue }); 96 | }} 97 | /> 98 | 99 | 105 | { 111 | const newValue = parseInt(e.target.value); 112 | setShadowValue({ ...shadowValue, x: newValue }); 113 | handleInputChange({ x: newValue }); 114 | }} 115 | onBlur={handleBlur} 116 | /> 117 | 118 | 124 | { 130 | const newValue = parseInt(e.target.value); 131 | setShadowValue({ ...shadowValue, y: newValue }); 132 | handleInputChange({ y: newValue }); 133 | }} 134 | onBlur={handleBlur} 135 | /> 136 | 137 | 143 | { 150 | const newValue = parseInt(e.target.value); 151 | setShadowValue({ ...shadowValue, blur: newValue }); 152 | handleInputChange({ blur: newValue }); 153 | }} 154 | onBlur={handleBlur} 155 | /> 156 |
157 |
158 |
159 | ); 160 | }; 161 | -------------------------------------------------------------------------------- /components/shared/Header.v2.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { useTheme } from "next-themes"; 4 | import { Button } from "@/components/ui/button"; 5 | import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; 6 | import { ImageIcon, Menu, Moon, Sun } from "lucide-react"; 7 | import { Icons } from "@/components/icons"; 8 | import { siteConfig } from "@/config/config"; 9 | import { buttonVariants } from "@/components/ui/button"; 10 | 11 | export default function Header() { 12 | const { theme, setTheme } = useTheme(); 13 | 14 | return ( 15 |
16 |
17 |
18 | 19 | 20 | 24 | 25 | 26 | 46 | 47 | 48 | 49 |
50 | 51 | Mockly 52 |
53 | 54 |
55 |
56 | 98 |
99 |
100 |
101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /components/shared/NotAvailable.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import { ArrowLeft, AlertTriangle } from "lucide-react" 3 | import Link from "next/link" 4 | 5 | export default function NotAvailable() { 6 | return ( 7 |
8 |
9 |
10 | 11 |
12 |

Editor Unavailable

13 |

14 | Whoops 🙈! The editor is currently unavailable. Please try again later. 15 |

16 |
17 | 23 | 24 |
25 |
26 |
27 | ) 28 | } -------------------------------------------------------------------------------- /components/shared/PeerlistSpotlight.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useTheme } from "next-themes"; 3 | import Image from "next/image"; 4 | import { useEffect } from "react"; 5 | 6 | export function PeerlistSpotlight() { 7 | const { theme } = useTheme(); 8 | 9 | let imageUrl = "/peerlist-dark.svg"; 10 | 11 | if (theme === "dark") { 12 | imageUrl = "/peerlist-light.svg"; 13 | } 14 | 15 | return ( 16 |
17 | Peerlist Spotlight 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /components/shared/ShowcaseImage.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useTheme } from "next-themes"; 3 | import Image from "next/image"; 4 | import React from "react"; 5 | 6 | const ShowcaseImage = () => { 7 | const { theme } = useTheme(); 8 | return ( 9 | ScreenCraft Editor Preview 16 | ); 17 | }; 18 | 19 | export default ShowcaseImage; 20 | -------------------------------------------------------------------------------- /components/shared/ValidatedInput.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from '@/components/ui/input'; 2 | import React, { useRef } from 'react'; 3 | import { validateInput } from './utils'; 4 | 5 | type Props = { 6 | value: string; 7 | setValue: React.Dispatch> 8 | className?:string; 9 | placeholder:string; 10 | onSuccess: () => void; 11 | setError: (errorMsg: string) => void; 12 | } 13 | 14 | export default function ValidatedInput ({ 15 | onSuccess, 16 | value, 17 | setValue, 18 | placeholder, 19 | setError, 20 | className = '' 21 | }:Props) { 22 | const inputRef = useRef(null); 23 | 24 | const handleSubmit = () => { 25 | setError(''); 26 | onSuccess() 27 | } 28 | 29 | const handleChange = (e: React.ChangeEvent) => { 30 | setValue(e.target.value); 31 | setError(''); 32 | }; 33 | 34 | const handleBlur = (e: React.FocusEvent) => { 35 | const {success, errorMsg} = validateInput(value) 36 | if (!success) { 37 | setError(errorMsg) 38 | e.preventDefault(); 39 | if (inputRef.current) { 40 | inputRef.current.focus(); 41 | } 42 | } else { 43 | handleSubmit() 44 | } 45 | }; 46 | 47 | const handleKeyDown = (e: React.KeyboardEvent) => { 48 | if (e.key === 'Enter') { 49 | const {success, errorMsg} = validateInput(value) 50 | if (!success) { 51 | setError(errorMsg) 52 | e.preventDefault(); 53 | } else { 54 | handleSubmit() 55 | } 56 | } 57 | }; 58 | 59 | return ( 60 | 70 | 71 | ); 72 | }; 73 | 74 | -------------------------------------------------------------------------------- /components/shared/buttons/ExportButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { ChevronDown, Image, FileImage, FileType, FileText, Star } from "lucide-react" 3 | import { saveAs } from 'file-saver' 4 | import jsPDF from 'jspdf' 5 | 6 | import { Button } from "@/components/ui/button" 7 | import { 8 | DropdownMenu, 9 | DropdownMenuContent, 10 | DropdownMenuItem, 11 | DropdownMenuTrigger, 12 | } from "@/components/ui/dropdown-menu" 13 | import { 14 | Dialog, 15 | DialogContent, 16 | DialogDescription, 17 | DialogFooter, 18 | DialogHeader, 19 | DialogTitle, 20 | } from "@/components/ui/dialog" 21 | import { Textarea } from "@/components/ui/textarea" 22 | 23 | interface ExportButtonProps { 24 | canvasRef: React.RefObject 25 | handleReset: () => void 26 | } 27 | 28 | export default function ExportButton({ canvasRef, handleReset }: ExportButtonProps) { 29 | const [complete, setComplete] = React.useState(false) 30 | const [rating, setRating] = React.useState(0) 31 | const [comment, setComment] = React.useState("") 32 | const [downloadedFormat, setDownloadedFormat] = React.useState(null) 33 | 34 | const handleDownload = (format: "png" | "jpg" | "svg" | "pdf") => { 35 | if (canvasRef.current) { 36 | const canvas = canvasRef.current; 37 | 38 | let imageData: string | undefined; 39 | if (format === "png" || format === "jpg") { 40 | const mimeType = format === "png" ? "image/png" : "image/jpeg"; 41 | imageData = canvas.toDataURL(mimeType); 42 | const filename = `screenshot${Date.now()}.${format}`; 43 | saveAs(imageData, filename); 44 | } else if (format === "svg") { 45 | // Convert canvas to SVG data 46 | if (!imageData) imageData = canvas.toDataURL("image/png"); 47 | 48 | const svgWidth = canvas.width; 49 | const svgHeight = canvas.height; 50 | const svgData = ` 51 | 52 | 53 | 54 | `; 55 | const svgBlob = new Blob([svgData], { 56 | type: "image/svg+xml;charset=utf-8", 57 | }); 58 | const filename = `screenshot${Date.now()}.svg`; 59 | saveAs(svgBlob, filename); 60 | } else if (format === "pdf") { 61 | if (!imageData) imageData = canvas.toDataURL("image/png"); 62 | 63 | const imgWidth = canvas.width; 64 | const imgHeight = canvas.height; 65 | const pdf = new jsPDF({ 66 | orientation: imgWidth > imgHeight ? "landscape" : "portrait", 67 | unit: "px", 68 | format: [imgWidth, imgHeight], 69 | }); 70 | 71 | pdf.addImage(imageData, "PNG", 0, 0, imgWidth, imgHeight); 72 | const filename = `screenshot${Date.now()}.pdf`; 73 | pdf.save(filename); 74 | } 75 | 76 | setDownloadedFormat(format.toUpperCase()) 77 | setComplete(true); 78 | } 79 | }; 80 | 81 | const handleCloseDialog = () => { 82 | setComplete(false) 83 | 84 | 85 | setRating(0) 86 | setComment("") 87 | setDownloadedFormat(null) 88 | } 89 | 90 | const handleSubmitFeedback = () => { 91 | // Implement feedback submission logic here 92 | handleCloseDialog() 93 | handleReset() 94 | } 95 | 96 | return ( 97 | <> 98 | 99 | 100 | 104 | 105 | 106 | handleDownload('png')}> 107 | 108 | PNG 109 | 110 | handleDownload('jpg')}> 111 | 112 | JPG 113 | 114 | handleDownload('svg')}> 115 | 116 | SVG 117 | 118 | handleDownload('pdf')}> 119 | 120 | PDF 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | Thank you for using Mockly! 129 | 130 | Your image has been downloaded as {downloadedFormat}. We'd love to hear your feedback. How was your experience? 131 | 132 | 133 |
134 |
135 | {[1, 2, 3, 4, 5].map((star) => ( 136 | setRating(star)} 144 | /> 145 | ))} 146 |
147 |