├── .cursorignore ├── .env.template ├── .gitignore ├── Procfile ├── README.md ├── client ├── .env.template ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── components.json ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── all.html │ ├── assets │ │ ├── discord.svg │ │ ├── favicon.ico │ │ ├── logo.png │ │ └── ship.json │ ├── contact.html │ ├── privacy.html │ ├── refunds.html │ ├── robots.txt │ ├── sitemap.xml │ └── terms.html ├── src │ ├── App.jsx │ ├── assets │ │ └── lottie │ │ │ ├── india.json │ │ │ └── ship.json │ ├── components │ │ ├── Assets.jsx │ │ ├── CardContainer.jsx │ │ ├── Chat.jsx │ │ ├── ChatSuggestions.jsx │ │ ├── ChatWidget.jsx │ │ ├── ChoosePaymentOptionDialog.jsx │ │ ├── FilePreview.jsx │ │ ├── FileUpload.jsx │ │ ├── Footer.jsx │ │ ├── Header.jsx │ │ ├── IframePreview.jsx │ │ ├── LoaderOverlay.jsx │ │ ├── LoadingGameOverlay.jsx │ │ ├── LoginDialog.jsx │ │ ├── LoginForm.jsx │ │ ├── PaypalButton.jsx │ │ ├── PortfolioContent.jsx │ │ ├── RazorpayButton.jsx │ │ ├── RecentlyShipped.jsx │ │ ├── ShipContent.jsx │ │ ├── ShipDesign.jsx │ │ ├── ShipForm.jsx │ │ ├── ShipOnboarding.jsx │ │ ├── Stepper.jsx │ │ ├── StrictModeDroppable.jsx │ │ ├── SubscriptionDialog.jsx │ │ ├── SuccessOverlay.jsx │ │ ├── ThemeToggle.jsx │ │ ├── editor │ │ │ ├── ChatPanel.jsx │ │ │ ├── CodeEditor.jsx │ │ │ ├── DomainPanel.jsx │ │ │ ├── Header.jsx │ │ │ ├── LoaderCircle.jsx │ │ │ ├── PreviewPanel.jsx │ │ │ ├── SlugChangeDialog.jsx │ │ │ ├── UserAccountMenu.jsx │ │ │ └── ViewOptions.jsx │ │ ├── layout │ │ │ └── AppLayout.jsx │ │ ├── portfolio-builder │ │ │ ├── ColorPaletteEditor.jsx │ │ │ ├── CustomDesignPrompt.jsx │ │ │ ├── DesignApproachSelector.jsx │ │ │ ├── FontSelector.jsx │ │ │ ├── MobilePortfolioBuilder.jsx │ │ │ ├── PortfolioBuilder.jsx │ │ │ ├── PortfolioTypeSelector.jsx │ │ │ ├── PresetDesignSelector.jsx │ │ │ └── WebsiteGallery.jsx │ │ ├── random │ │ │ ├── Dice.jsx │ │ │ ├── EmojiOverlay.jsx │ │ │ └── ThreeDotLoader.jsx │ │ └── ui │ │ │ ├── AutosizeTextarea.jsx │ │ │ ├── accordion.jsx │ │ │ ├── alert-dialog.jsx │ │ │ ├── avatar.jsx │ │ │ ├── badge.jsx │ │ │ ├── button.jsx │ │ │ ├── card.jsx │ │ │ ├── checkbox.jsx │ │ │ ├── collapsible.jsx │ │ │ ├── dialog.jsx │ │ │ ├── dropdown-menu.jsx │ │ │ ├── focus-cards.jsx │ │ │ ├── input.jsx │ │ │ ├── label.jsx │ │ │ ├── popover.jsx │ │ │ ├── radio-group.jsx │ │ │ ├── resizable.jsx │ │ │ ├── select.jsx │ │ │ ├── separator.jsx │ │ │ ├── skeleton.jsx │ │ │ ├── switch.jsx │ │ │ ├── tabs.jsx │ │ │ ├── textarea.jsx │ │ │ ├── tooltip.jsx │ │ │ └── use-toast.js │ ├── constants.js │ ├── context │ │ ├── AuthContext.jsx │ │ └── SocketProvider.jsx │ ├── hooks │ │ ├── useDisclosure.js │ │ ├── useLocalStorage.js │ │ ├── useMediaQuery.js │ │ ├── useProject.js │ │ ├── useShipInfo.js │ │ └── useWebsites.js │ ├── index.css │ ├── lib │ │ ├── axiosConfig.js │ │ ├── supabaseClient.js │ │ └── utils │ │ │ ├── editorUtils.js │ │ │ ├── fileToBase64.js │ │ │ ├── index.js │ │ │ ├── portfolioUtils.js │ │ │ ├── sanitizeFileName.js │ │ │ └── urlsToLinks.jsx │ ├── main.jsx │ ├── pages │ │ ├── Edit │ │ │ └── index.jsx │ │ ├── Home │ │ │ └── index.jsx │ │ ├── Portfolio │ │ │ └── index.jsx │ │ └── Ship │ │ │ └── index.jsx │ ├── store │ │ ├── deploymentSlice.js │ │ ├── fileUploadSlice.js │ │ ├── index.js │ │ ├── onboardingSlice.js │ │ └── shipSlice.js │ ├── styles │ │ ├── dice.css │ │ └── main.css │ └── vite-env.d.ts ├── tailwind.config.js └── vite.config.js ├── editor-service └── AIService.js ├── eslint.config.mjs ├── instrument.js ├── package-lock.json ├── package.json ├── public ├── all.html ├── assets │ ├── browser-Db46EzOh.js │ ├── discord.svg │ ├── favicon.ico │ ├── index-BSBsAO_O.js │ ├── index-Dt_7dNOI.css │ ├── logo.png │ └── ship.json ├── contact.html ├── index.html ├── privacy.html ├── refunds.html ├── robots.txt ├── sitemap.xml └── terms.html ├── server.js ├── server ├── config │ ├── awsConfig.js │ ├── tools.js │ └── tools │ │ ├── analyzeAndRepairTool.json │ │ ├── codeWriterTool.json │ │ ├── contentTool.json │ │ ├── ctoTool.json │ │ ├── deployProjectTool.json │ │ ├── fileCreatorTool.json │ │ ├── getDataForLandingPage.json │ │ ├── getDataForPortfolio.json │ │ ├── headshotTool.json │ │ ├── imageAnalysisTool.json │ │ ├── imageFinderTool.json │ │ ├── pdfParserTool.json │ │ ├── placeholderImageTool.json │ │ ├── productManagerTool.json │ │ ├── searchTool.json │ │ ├── startShippingEmailTemplateTool.json │ │ ├── startShippingLandingPageTool.json │ │ ├── startShippingPortfolioTool.json │ │ └── taskAssignerTool.json ├── controllers │ ├── assetController.js │ ├── fileController.js │ ├── miscController.js │ ├── paymentController.js │ ├── slugController.js │ └── websiteController.js ├── middleware │ └── auth.js ├── routes │ ├── assetRoutes.js │ ├── fileRoutes.js │ ├── miscRoutes.js │ ├── paymentRoutes.js │ ├── reactRoutes.js │ └── websiteRoutes.js ├── services │ ├── analyzeAndRepairService.js │ ├── analyzeImageService.js │ ├── anthropicService.js │ ├── authService.js │ ├── codeRefinementService.js │ ├── codeService.js │ ├── constants.js │ ├── ctoService.js │ ├── dbService.js │ ├── domainService.js │ ├── editor-service │ │ ├── AIService.js │ │ ├── HTMLRefiner.js │ │ └── index.js │ ├── fileService.js │ ├── images-services │ │ ├── headshotImageService.js │ │ ├── pexelsService.js │ │ └── unsplashService.js │ ├── onboadingService.js │ ├── openai-editor-service │ │ ├── codeRefinementService.js │ │ ├── index.js │ │ └── utils │ │ │ ├── codeUtils.js │ │ │ ├── prompts.js │ │ │ └── versionControl.js │ ├── paymentService.js │ ├── pdfParserService.js │ ├── promptUpdateService.js │ ├── prompts │ │ ├── analyzeAndRepairPrompt.js │ │ ├── analyzeWebDesignPrompt.js │ │ ├── buildSiteFromAnalysisPrompt.js │ │ ├── codePrompt.js │ │ ├── ctoPrompt.js │ │ └── userBuildSitePrompt.js │ ├── screenshotService.js │ ├── searchService.js │ ├── siteContentService.js │ ├── strategies │ │ ├── localStorageStrategy.js │ │ ├── s3StorageStrategy.js │ │ └── supabaseStorageStrategy.js │ ├── supabaseService.js │ ├── webDesignAnalysisService.js │ └── webhookService.js ├── setup.sql ├── tool-controllers │ ├── codeRefinementToolController.js │ ├── codeToolController.js │ ├── ctoToolController.js │ ├── onboardingToolController.js │ └── siteContentToolController.js └── utils │ ├── date.js │ └── file.js ├── supabase ├── config.toml ├── migrations │ ├── .gitignore │ ├── 20240913134008_shipstation-2.sql │ ├── 20240913144109_conversations-table.sql │ ├── 20240916052453_code_versions.sql │ ├── 20240916100453_add_version_to_ships.sql │ ├── 20240917062455_add-assets-column.sql │ ├── 20240924133022_design-presets.sql │ ├── 20240924134500_alter-design-preset.sql │ ├── 20241004045142_alter-ship-table.sql │ ├── 20241007191635_website-likes.sql │ └── 20241021122915_ship_slug_to_ship_id.sql └── seed.sql ├── test.js └── test └── test.js /.cursorignore: -------------------------------------------------------------------------------- 1 | # Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) 2 | node_modules 3 | websites 4 | public/test.html 5 | scripts/components.js 6 | styles/main.css -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | # Node.js dev server port, also to be used in the client .env in VITE_BACKEND_URL 2 | PORT=3000 3 | 4 | # Anthropic 5 | ANTHROPIC_API_KEY= # Your Anthropic API key 6 | DEFAULT_MODEL=claude-3-5-sonnet-latest 7 | 8 | # OpenAI 9 | OPENAI_API_KEY= # OpenAI API key - Used for editor service 10 | 11 | # Helicone - Use for monitoring and debugging 12 | HELICONE_API_KEY= # Helicone API key 13 | 14 | # Tavily https://tavily.com/ - For LLM based Search 15 | TAVILY_API_KEY= # Your Tavily API key 16 | 17 | # Supabase - For Auth and backend 18 | SUPABASE_KEY= # Supabase service role key, not the anon key 19 | SUPABASE_POSTGRES_PASSWORD= # Supabase postgres password 20 | SUPABASE_URL= # Supabase project url 21 | 22 | # Path where websites will be stored on s3 and local 23 | WEBSITES_PATH=websites 24 | 25 | # Choose between 3 storage strategies: 'local', 's3', or 'supabase', BUCKET_NAME env var is needed for supabase storage also 26 | STORAGE_STRATEGY=supabase 27 | 28 | # AWS S3 Configuration 29 | # access-key= # Your access key 30 | # secret-key= # Your secret key 31 | # AWS_REGION= 32 | # BUCKET_NAME= 33 | # S3_EXTERNAL_ENDPOINT= # Only if you are using Wasabi or any other s3 compatible storage 34 | 35 | # Razorpay Configuration 36 | RAZORPAY_KEY_ID= # Your Razorpay key ID 37 | RAZORPAY_SECRET= # Your Razorpay secret 38 | RAZORPAY_WEBHOOK_SECRET= # Your Razorpay webhook secret 39 | 40 | # Number of ships to be added to the user profile on successful payment 41 | NUMBER_OF_SHIPS= 42 | 43 | # Google Configuration 44 | GOOGLE_CLIENT_ID= # Your Google Client ID 45 | 46 | # Application URLs 47 | APP_URL= # Your app URL 48 | MAIN_URL= # Your main URL 49 | 50 | # Discord Webhook 51 | DISCORD_WEBHOOK_URL= # Your Discord webhook URL for payment notifications 52 | DISCORD_NEW_SHIP_WEBHOOK_URL= # Your Discord webhook URL for new ship notifications 53 | 54 | # Browserless 55 | BROWSERLESS_API_URL= # Browserless API URL 56 | BROWSERLESS_API_TOKEN= # Browserless API token 57 | 58 | # Sentry 59 | SENTRY_DSN= # Your Sentry DSN 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | websites 3 | .DS_Store 4 | .env 5 | .env.local 6 | out.log 7 | landing 8 | next 9 | screenshots -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /client/.env.template: -------------------------------------------------------------------------------- 1 | # Supabase Configuration 2 | # To get these values, create a new project in the Supabase dashboard: 3 | # 1. Click on Project Settings -> API 4 | # 2. Find Project URL and copy it to VITE_SUPABASE_URL 5 | # 3. Find Project API keys -> anon/public and copy it to VITE_SUPABASE_ANON_KEY 6 | VITE_SUPABASE_URL= 7 | VITE_SUPABASE_ANON_KEY= 8 | 9 | # Main application URL 10 | VITE_MAIN_URL= 11 | 12 | # Google OAuth Configuration 13 | # Get this from Google Cloud Console -> Credentials -> OAuth 2.0 Client IDs 14 | VITE_GOOGLE_CLIENT_ID= 15 | 16 | # Paddle Payment Configuration 17 | # Get these values from your Paddle Dashboard 18 | VITE_PADDLE_KEY= 19 | VITE_PADDLE_ENVIRONMENT= # 'sandbox' for testing, 'production' for live 20 | VITE_PADDLE_PRODUCT_ID= 21 | VITE_PADDLE_YEARLY_PRICE_ID= 22 | VITE_PADDLE_MONTHLY_PRICE_ID= 23 | 24 | # Backend API Configuration 25 | # For local development, use localhost 26 | # For production, use your deployed API URL 27 | VITE_BACKEND_URL=http://localhost:3000/api 28 | VITE_SOCKET_URL=http://localhost:3000 29 | 30 | # External Services 31 | VITE_MYPROFILE_URL= 32 | 33 | # PostHog 34 | VITE_PUBLIC_POSTHOG_KEY= 35 | VITE_PUBLIC_POSTHOG_HOST= 36 | -------------------------------------------------------------------------------- /client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env.local 26 | .env.production 27 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # ShipStation Frontend App -------------------------------------------------------------------------------- /client/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": false, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "zinc", 10 | "cssVariables": false 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /client/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | }, 7 | "jsx": "react-jsx", 8 | "checkJs": true, 9 | "resolveJsonModule": true, 10 | "moduleResolution": "node", 11 | "target": "es2020", 12 | "module": "esnext" 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["node_modules", "build"] 16 | } 17 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build --outDir ../public --emptyOutDir", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@hello-pangea/dnd": "^17.0.0", 14 | "@monaco-editor/react": "^4.6.0", 15 | "@paddle/paddle-js": "^1.3.2", 16 | "@paypal/react-paypal-js": "^8.7.0", 17 | "@radix-ui/react-accordion": "^1.2.0", 18 | "@radix-ui/react-alert-dialog": "^1.1.2", 19 | "@radix-ui/react-avatar": "^1.1.1", 20 | "@radix-ui/react-checkbox": "^1.1.1", 21 | "@radix-ui/react-collapsible": "^1.1.0", 22 | "@radix-ui/react-dialog": "^1.1.1", 23 | "@radix-ui/react-dropdown-menu": "^2.1.2", 24 | "@radix-ui/react-label": "^2.1.0", 25 | "@radix-ui/react-popover": "^1.1.1", 26 | "@radix-ui/react-radio-group": "^1.2.0", 27 | "@radix-ui/react-select": "^2.1.1", 28 | "@radix-ui/react-separator": "^1.1.0", 29 | "@radix-ui/react-slot": "^1.1.0", 30 | "@radix-ui/react-switch": "^1.1.0", 31 | "@radix-ui/react-tabs": "^1.1.0", 32 | "@radix-ui/react-toast": "^1.2.1", 33 | "@radix-ui/react-tooltip": "^1.1.2", 34 | "@reduxjs/toolkit": "^2.2.7", 35 | "@supabase/supabase-js": "^2.44.2", 36 | "@tanstack/react-query": "^5.59.16", 37 | "@uiw/react-color": "^2.3.1", 38 | "axios": "^1.7.7", 39 | "class-variance-authority": "^0.7.0", 40 | "clsx": "^2.1.1", 41 | "date-fns": "^3.6.0", 42 | "framer-motion": "^11.5.4", 43 | "lucide-react": "^0.400.0", 44 | "pdfjs-dist": "^4.6.82", 45 | "posthog-js": "^1.203.3", 46 | "react": "^18.3.1", 47 | "react-colorful": "^5.6.1", 48 | "react-confetti": "^6.1.0", 49 | "react-device-frameset": "^1.3.4", 50 | "react-dom": "^18.3.1", 51 | "react-dropzone": "^14.2.3", 52 | "react-google-font": "^1.1.0", 53 | "react-intersection-observer": "^9.13.1", 54 | "react-lazy-load-image-component": "^1.6.2", 55 | "react-lottie-player": "^2.0.0", 56 | "react-redux": "^9.1.2", 57 | "react-resizable-panels": "^2.0.22", 58 | "react-router-dom": "^6.24.1", 59 | "redux-persist": "^6.0.0", 60 | "socket.io-client": "^4.7.5", 61 | "sonner": "^1.5.0", 62 | "tailwind-merge": "^2.5.2" 63 | }, 64 | "devDependencies": { 65 | "@types/react": "^18.3.3", 66 | "@types/react-dom": "^18.3.0", 67 | "@vitejs/plugin-react": "^4.3.1", 68 | "autoprefixer": "^10.4.19", 69 | "eslint": "^8.57.0", 70 | "eslint-plugin-react": "^7.34.2", 71 | "eslint-plugin-react-hooks": "^4.6.2", 72 | "eslint-plugin-react-refresh": "^0.4.7", 73 | "postcss": "^8.4.39", 74 | "tailwindcss": "^3.4.4", 75 | "vite": "^5.3.1" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/public/all.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ShipStation | All Websites 7 | 8 | 23 | 24 | 25 | 26 |
27 |

28 | All Websites generated by ShipStation 29 |

30 |
31 |
35 |
36 |
37 | 38 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /client/public/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytimedrinkingclub/shipstation/8827be5ea3baff701dedcebfcd80671650fd9908/client/public/assets/favicon.ico -------------------------------------------------------------------------------- /client/public/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytimedrinkingclub/shipstation/8827be5ea3baff701dedcebfcd80671650fd9908/client/public/assets/logo.png -------------------------------------------------------------------------------- /client/public/contact.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Contact Details - ShipStation 7 | 33 | 34 | 35 |

Contact Details

36 |

Last updated on May 23rd 2024

37 | 38 |

You may contact us using the information below:

39 | 40 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /client/public/privacy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Privacy Policy - ShipStation 7 | 13 | 14 | 15 |

Privacy Policy

16 |

Last updated on May 23rd 2024

17 | 18 |

19 | This privacy policy sets out how ShipStation uses and protects any 20 | information that you give ShipStation when you visit their website and/or 21 | agree to purchase from them. 22 |

23 | 24 |

25 | ShipStation is committed to ensuring that your privacy is protected. 26 | Should we ask you to provide certain information by which you can be 27 | identified when using this website, then you can be assured that it will 28 | only be used in accordance with this privacy statement. 29 |

30 | 31 |

32 | ShipStation may change this policy from time to time by updating this 33 | page. You should check this page from time to time to ensure that you are 34 | aware of these changes. 35 |

36 | 37 |

Information We Collect

38 |

We may collect the following information:

39 | 48 | 49 |

What We Do With The Information We Gather

50 |

51 | We require this information to understand your needs and provide you with 52 | a better service, and in particular for the following reasons: 53 |

54 | 69 | 70 |

71 | We are committed to ensuring that your information is secure. In order to 72 | prevent unauthorised access or disclosure, we have put in suitable 73 | measures. 74 |

75 | 76 |

How We Use Cookies

77 |

78 | A cookie is a small file which asks permission to be placed on your 79 | computer's hard drive. Once you agree, the file is added and the cookie 80 | helps 81 |

82 | 83 | 84 | -------------------------------------------------------------------------------- /client/public/refunds.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cancellation & Refund Policy 7 | 13 | 14 | 15 |

Cancellation & Refund Policy

16 | 17 |

Last updated on May 23rd, 2024

18 | 19 |

No cancellations & refunds are entertained.

20 | 21 | 22 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | Disallow: /site 4 | Disallow: /all.html 5 | 6 | Sitemap: https://shipstation.ai/sitemap.xml -------------------------------------------------------------------------------- /client/public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://shipstation.ai/ 5 | 2024-07-11 6 | daily 7 | 1.0 8 | 9 | 10 | https://shipstation.ai/contact.html 11 | 2024-07-11 12 | monthly 13 | 0.8 14 | 15 | 16 | https://shipstation.ai/terms.html 17 | 2024-07-11 18 | monthly 19 | 0.7 20 | 21 | 22 | https://shipstation.ai/privacy.html 23 | 2024-07-11 24 | monthly 25 | 0.7 26 | 27 | 28 | https://shipstation.ai/refunds.html 29 | 2024-07-11 30 | monthly 31 | 0.7 32 | 33 | 34 | -------------------------------------------------------------------------------- /client/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; 3 | import { Provider } from "react-redux"; 4 | import { PersistGate } from "redux-persist/integration/react"; 5 | import { store, persistor } from "./store"; 6 | import Home from "./pages/Home"; 7 | import { Toaster } from "sonner"; 8 | import Ship from "./pages/Ship"; 9 | import Edit from "./pages/Edit"; 10 | import Portfolio from "./pages/Portfolio"; 11 | import { AuthProvider } from "./context/AuthContext"; 12 | import { SocketProvider } from "./context/SocketProvider"; 13 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query' 14 | 15 | const queryClient = new QueryClient() 16 | 17 | function App() { 18 | useEffect(() => { 19 | // Add 'dark' class to the element 20 | document.documentElement.classList.add("dark"); 21 | }, []); 22 | 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | } /> 32 | } /> 33 | } /> 34 | } /> 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | } 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /client/src/components/CardContainer.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Card, 3 | CardHeader, 4 | CardTitle, 5 | CardDescription, 6 | } from "@/components/ui/card"; 7 | import { 8 | SHIP_AI_AGENT, 9 | SHIP_EMAIL_TEMPLATE, 10 | SHIP_LANDING_PAGE, 11 | SHIP_PORTFOLIO, 12 | } from "@/constants"; 13 | import { Layout, Cpu, Wallpaper, Mail } from "lucide-react"; 14 | 15 | const CardContainer = ({ onCardClick }) => { 16 | const cards = [ 17 | { 18 | icon: , 19 | title: "Ship landing page", 20 | description: 21 | "Craft a sleek, high-converting landing page that captivates your audience from the first scroll.", 22 | type: SHIP_LANDING_PAGE, 23 | }, 24 | { 25 | icon: , 26 | title: "Ship personal website", 27 | description: 28 | "Showcase your unique story and skills with a stunning personal website that leaves a lasting impression.", 29 | type: SHIP_PORTFOLIO, 30 | }, 31 | { 32 | icon: , 33 | title: "Ship email template", 34 | description: 35 | "Create stunning, AI-assisted email templates to boost your marketing efforts.", 36 | type: SHIP_EMAIL_TEMPLATE, 37 | }, 38 | ]; 39 | 40 | return ( 41 |
42 | {cards.map((card, index) => ( 43 | !card.isComingSoon && onCardClick(card.type)} 47 | > 48 |
49 | 50 |
{card.icon}
51 | {card.title} 52 | {card.description} 53 | {card.badge && ( 54 | 55 | {card.badge} 56 | 57 | )} 58 |
59 | 60 | ))} 61 |
62 | ); 63 | }; 64 | 65 | export default CardContainer; 66 | -------------------------------------------------------------------------------- /client/src/components/ChatSuggestions.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | 3 | const suggestions = [ 4 | "Redesign the layout using a bento grid style for a modern look.", 5 | "Apply a neubrutalism design language with bold colors and sharp contrasts.", 6 | "Implement a glassmorphism effect on card elements for a sleek appearance.", 7 | ]; 8 | 9 | const ChatSuggestions = ({ onSuggestionClick }) => { 10 | return ( 11 |
12 |

13 | Quick suggestions to get started: 14 |

15 |
16 | {suggestions.map((suggestion, index) => ( 17 | 26 | ))} 27 |
28 |
29 | ); 30 | }; 31 | 32 | export default ChatSuggestions; 33 | -------------------------------------------------------------------------------- /client/src/components/ChatWidget.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | const ChatWidget = ({ user, onClose }) => { 4 | useEffect(() => { 5 | // Define Chatwoot settings 6 | window.chatwootSettings = { 7 | hideMessageBubble: true, // Hide the default message bubble 8 | position: "right", // Position the widget on the right 9 | locale: "en", // Set the language to English 10 | type: "expanded_bubble", // Use the expanded bubble type 11 | showPopoutButton: false, // Disable the popout button 12 | }; 13 | 14 | (function (d, t) { 15 | var BASE_URL = "https://chatwoot.badalhibadal.com"; 16 | var g = d.createElement(t), 17 | s = d.getElementsByTagName(t)[0]; 18 | g.src = BASE_URL + "/packs/js/sdk.js"; 19 | g.defer = true; 20 | g.async = true; 21 | s.parentNode.insertBefore(g, s); 22 | g.onload = function () { 23 | window.chatwootSDK.run({ 24 | websiteToken: "fTtARiySzQBu1etch6QJnKDE", 25 | baseUrl: BASE_URL, 26 | }); 27 | 28 | window.addEventListener("chatwoot:ready", function () { 29 | if (user) { 30 | window.$chatwoot.setUser(user.id, { 31 | email: user.email, 32 | }); 33 | } 34 | 35 | window.$chatwoot.on("widget:closed", onClose); 36 | }); 37 | // Open the widget immediately 38 | window.$chatwoot.toggle("open"); 39 | }; 40 | })(document, "script"); 41 | 42 | return () => { 43 | onClose(); 44 | }; 45 | }, [user, onClose]); 46 | 47 | // This component doesn't render anything visible 48 | return null; 49 | }; 50 | 51 | export default ChatWidget; 52 | -------------------------------------------------------------------------------- /client/src/components/FilePreview.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { FileIcon } from "lucide-react"; 3 | 4 | const FilePreview = ({ file }) => { 5 | const [preview, setPreview] = useState(null); 6 | 7 | useEffect(() => { 8 | if (file.type.startsWith("image/")) { 9 | const reader = new FileReader(); 10 | reader.onloadend = () => setPreview(reader.result); 11 | reader.readAsDataURL(file); 12 | } 13 | }, [file]); 14 | 15 | const isImageFile = (fileName) => { 16 | return /\.(jpg|jpeg|png|gif|webp)$/i.test(fileName); 17 | }; 18 | 19 | const getFileExtension = (fileName) => { 20 | return fileName.split(".").pop().toUpperCase(); 21 | }; 22 | 23 | return ( 24 |
25 | {isImageFile(file.name) ? ( 26 | {file.name} 31 | ) : ( 32 |
33 | 34 | 35 | {getFileExtension(file.name)} 36 | 37 |
38 | )} 39 |
40 | ); 41 | }; 42 | 43 | export default FilePreview; 44 | -------------------------------------------------------------------------------- /client/src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import { HelpCircle, Ship, Star } from "lucide-react"; 2 | 3 | const Footer = () => { 4 | return ( 5 | 33 | ); 34 | }; 35 | 36 | export default Footer; 37 | -------------------------------------------------------------------------------- /client/src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from "react"; 2 | import { AuthContext } from "../context/AuthContext"; 3 | import { Button } from "@/components/ui/button"; 4 | import { GripIcon, Images, LogIn, LogOut, Ship, User } from "lucide-react"; 5 | import ThemeToggle from "./ThemeToggle"; 6 | import UserAccountMenu from "./editor/UserAccountMenu"; 7 | import useDisclosure from "@/hooks/useDisclosure"; 8 | import LoginDialog from "./LoginDialog"; 9 | 10 | const Header = () => { 11 | const { user, handleLogout } = useContext(AuthContext); 12 | const { isOpen, onClose } = useDisclosure(); 13 | 14 | const browseDesignsButton = ( 15 | 24 | ); 25 | 26 | return ( 27 |
28 |
29 |
30 | 31 |

(window.location.href = "/")} 34 | > 35 | ShipStation 36 |

37 |
38 | 39 | {/* Desktop menu */} 40 |
41 | 42 | {browseDesignsButton} 43 | {user && } 44 |
45 | 46 | {/* Mobile menu */} 47 |
48 | {!user && browseDesignsButton} 49 | 50 | {user && ( 51 | 56 | )} 57 |
58 |
59 | 60 | 61 |
62 | ); 63 | }; 64 | 65 | export default Header; 66 | -------------------------------------------------------------------------------- /client/src/components/LoaderOverlay.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useRef } from "react"; 2 | import { useSocket } from "@/context/SocketProvider"; 3 | import IframePreview, { DEVICE_FRAMES } from "./IframePreview"; 4 | import Dice from "./random/Dice"; 5 | 6 | const LoaderOverlay = ({ isOpen, type }) => { 7 | const { socket } = useSocket(); 8 | 9 | const [loaderText, setLoaderText] = useState( 10 | "It will take around 2-4 minutes. Please dont close this tab while we fix this bug." 11 | ); 12 | const [currentDevice, setCurrentDevice] = useState(DEVICE_FRAMES[0]); 13 | const [slug, setSlug] = useState(null); 14 | const [isLoading, setIsLoading] = useState(true); 15 | const iframeRef = useRef(null); 16 | 17 | useEffect(() => { 18 | if (socket) { 19 | socket.on("error", ({ error }) => { 20 | console.error("Error:", error); 21 | // You can add a function to show error messages here 22 | }); 23 | 24 | socket.on("progress", ({ message }) => { 25 | setLoaderText(message); 26 | if (iframeRef.current) { 27 | iframeRef.current.reload(); 28 | } 29 | }); 30 | 31 | socket.on("project_started", ({ slug }) => { 32 | setSlug(slug); 33 | setIsLoading(false); 34 | }); 35 | 36 | return () => { 37 | socket.off("error"); 38 | socket.off("progress"); 39 | socket.off("project_started"); 40 | }; 41 | } 42 | }, [socket]); 43 | 44 | if (!isOpen) return null; 45 | return ( 46 |
47 |
48 |
49 |

50 | {type === "portfolio" 51 | ? "Generating your portfolio" 52 | : "Generating your landing page"} 53 |

54 |

{loaderText}

55 |
56 |
57 | 59 | setCurrentDevice( 60 | DEVICE_FRAMES[Math.floor(Math.random() * DEVICE_FRAMES.length)] 61 | ) 62 | } 63 | /> 64 |
65 |
66 | ); 67 | }; 68 | 69 | export default LoaderOverlay; 70 | -------------------------------------------------------------------------------- /client/src/components/PaypalButton.jsx: -------------------------------------------------------------------------------- 1 | 2 | const PaypalButton = ({ productId }) => { 3 | return ( 4 | 5 | 6 | Pay with PayPal 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default PaypalButton; 18 | -------------------------------------------------------------------------------- /client/src/components/RazorpayButton.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | const RazorpayButton = ({ productId }) => { 4 | const formRef = useRef(null); 5 | 6 | useEffect(() => { 7 | const script = document.createElement('script'); 8 | script.src = 'https://checkout.razorpay.com/v1/payment-button.js'; 9 | script.setAttribute('data-payment_button_id', productId); 10 | script.async = true; 11 | 12 | formRef.current.appendChild(script); 13 | 14 | return () => { 15 | if (formRef.current) { 16 | formRef.current.removeChild(script); 17 | } 18 | }; 19 | }, [productId]); 20 | 21 | return
; 22 | }; 23 | 24 | export default RazorpayButton; -------------------------------------------------------------------------------- /client/src/components/Stepper.jsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import { Check } from "lucide-react"; 3 | 4 | export default function Stepper({ steps, currentStep, onStepClick }) { 5 | return ( 6 |
7 | {steps.map((step, index) => ( 8 | onStepClick(index)} 14 | initial={{ opacity: 0, x: -20 }} 15 | animate={{ opacity: 1, x: 0 }} 16 | transition={{ duration: 0.5, delay: index * 0.1 }} 17 | > 18 | 29 | {index < currentStep ? ( 30 | 31 | ) : ( 32 | {index + 1} 33 | )} 34 | 35 |
{step}
36 |
37 | ))} 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /client/src/components/StrictModeDroppable.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { Droppable } from "@hello-pangea/dnd"; 3 | 4 | export const StrictModeDroppable = ({ 5 | children, 6 | type = "DEFAULT_TYPE", 7 | ...props 8 | }) => { 9 | const [enabled, setEnabled] = useState(false); 10 | 11 | useEffect(() => { 12 | const animation = requestAnimationFrame(() => setEnabled(true)); 13 | return () => { 14 | cancelAnimationFrame(animation); 15 | setEnabled(false); 16 | }; 17 | }, []); 18 | 19 | if (!enabled) { 20 | return null; 21 | } 22 | 23 | return ( 24 | 25 | {children} 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /client/src/components/SuccessOverlay.jsx: -------------------------------------------------------------------------------- 1 | import Lottie from "react-lottie-player"; 2 | import { Button } from "./ui/button"; 3 | import { CodeXml, ExternalLinkIcon } from "lucide-react"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog"; 6 | 7 | const SuccessOverlay = ({ isOpen, onClose, slug }) => { 8 | const navigate = useNavigate(); 9 | 10 | const successText = `Your website "${slug}" is live!`; 11 | const link = `${import.meta.env.VITE_MYPROFILE_URL}/${slug}/`; 12 | 13 | const handleEditProject = () => { 14 | navigate(`/project/${slug}`); 15 | }; 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | {successText} 23 | 24 | 25 |
26 | 31 |
32 |
33 | 37 | 44 |
45 |
46 |
47 | ); 48 | }; 49 | 50 | export default SuccessOverlay; 51 | -------------------------------------------------------------------------------- /client/src/components/ThemeToggle.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { Sun, Moon } from "lucide-react"; 3 | import { Button } from "@/components/ui/button"; 4 | import useLocalStorage from "../hooks/useLocalStorage"; 5 | 6 | const ThemeToggle = () => { 7 | const [theme, setTheme] = useLocalStorage("theme", "dark"); 8 | 9 | useEffect(() => { 10 | document.documentElement.classList.toggle("dark", theme === "dark"); 11 | }, [theme]); 12 | 13 | const toggleTheme = () => { 14 | setTheme(theme === "light" ? "dark" : "light"); 15 | }; 16 | 17 | return null; 18 | 19 | return ( 20 | 28 | ); 29 | }; 30 | 31 | export default ThemeToggle; 32 | -------------------------------------------------------------------------------- /client/src/components/editor/ChatPanel.jsx: -------------------------------------------------------------------------------- 1 | import Chat from "@/components/Chat"; 2 | import { useSelector } from "react-redux"; 3 | 4 | const ChatPanel = ({ 5 | onCodeUpdate, 6 | onAssetsUpdate, 7 | assets, 8 | assetCount, 9 | initialPrompt, 10 | isDeploying, 11 | }) => { 12 | const { id: shipId, slug: shipSlug } = useSelector((state) => state.ship); 13 | 14 | console.log("ChatPanel", { shipId, shipSlug }); 15 | 16 | return ( 17 | 27 | ); 28 | }; 29 | 30 | export default ChatPanel; 31 | -------------------------------------------------------------------------------- /client/src/components/editor/CodeEditor.jsx: -------------------------------------------------------------------------------- 1 | import Editor from "@monaco-editor/react"; 2 | import { Button } from "@/components/ui/button"; 3 | import { Badge } from "@/components/ui/badge"; 4 | import { Save, Download } from "lucide-react"; 5 | 6 | const CodeEditor = ({ 7 | fileContent, 8 | isFileLoading, 9 | handleFileChange, 10 | handleFileSave, 11 | unsavedChanges, 12 | submitting, 13 | handledownloadzip, // Add this prop 14 | }) => { 15 | return ( 16 |
17 |
18 |
19 | index.html 20 | {unsavedChanges && Unsaved changes} 21 |
22 |
23 | 33 | 43 |
44 |
45 | {isFileLoading ? ( 46 |
47 |
48 |
49 |
50 |
51 |
52 | ) : ( 53 | 75 | )} 76 |
77 | ); 78 | }; 79 | 80 | export default CodeEditor; 81 | -------------------------------------------------------------------------------- /client/src/components/editor/LoaderCircle.jsx: -------------------------------------------------------------------------------- 1 | import { Loader } from "lucide-react"; 2 | 3 | const LoaderCircle = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default LoaderCircle; 12 | -------------------------------------------------------------------------------- /client/src/components/editor/PreviewPanel.jsx: -------------------------------------------------------------------------------- 1 | import IframePreview from "@/components/IframePreview"; 2 | import LoaderCircle from "./LoaderCircle"; 3 | import Dice from "@/components/random/Dice"; 4 | import { useEffect } from "react"; 5 | import { useSelector } from "react-redux"; 6 | 7 | const PreviewPanel = ({ 8 | currentView, 9 | currentDevice, 10 | iframeRef, 11 | isFileLoading, 12 | isDeploying, 13 | isUndoing, 14 | isRedoing, 15 | isCodeUpdating, 16 | isChatUpdating, 17 | shuffleDevice, 18 | }) => { 19 | const isMobileView = currentView === "mobile"; 20 | const { slug: shipSlug } = useSelector((state) => state.ship); 21 | 22 | return ( 23 |
24 | {isMobileView ? ( 25 |
26 |
27 |
28 |
32 | 40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 | ) : ( 48 | 55 | )} 56 | {(isUndoing || isRedoing || isCodeUpdating || isChatUpdating) && ( 57 | 58 | )} 59 |
60 | ); 61 | }; 62 | 63 | export default PreviewPanel; 64 | -------------------------------------------------------------------------------- /client/src/components/editor/ViewOptions.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { 3 | Tooltip, 4 | TooltipContent, 5 | TooltipTrigger, 6 | } from "@/components/ui/tooltip"; 7 | import { Columns2, Smartphone, Maximize2 } from "lucide-react"; 8 | 9 | const ViewOptions = ({ currentView, onViewChange }) => { 10 | const views = [ 11 | { id: "horizontal", icon: Columns2, tooltip: "Horizontal View" }, 12 | { id: "mobile", icon: Smartphone, tooltip: "Mobile View" }, 13 | { id: "fullscreen", icon: Maximize2, tooltip: "Fullscreen View" }, 14 | ]; 15 | 16 | return ( 17 |
18 | {views.map((view, index) => ( 19 | 20 | 21 | 35 | 36 | {view.tooltip} 37 | 38 | ))} 39 |
40 | ); 41 | }; 42 | 43 | export default ViewOptions; 44 | -------------------------------------------------------------------------------- /client/src/components/layout/AppLayout.jsx: -------------------------------------------------------------------------------- 1 | import Footer from "../Footer"; 2 | import Header from "../Header"; 3 | 4 | const AppLayout = ({ children }) => { 5 | return ( 6 |
7 |
8 |
9 |
10 | {children} 11 |
12 |
13 |
14 |
15 | ); 16 | }; 17 | 18 | export default AppLayout; 19 | -------------------------------------------------------------------------------- /client/src/components/portfolio-builder/ColorPaletteEditor.jsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import { Sketch } from "@uiw/react-color"; 3 | import { Card, CardHeader, CardTitle } from "@/components/ui/card"; 4 | import { Plus } from "lucide-react"; 5 | 6 | export default function ColorPaletteEditor({ 7 | colorPalette, 8 | activeColor, 9 | setActiveColor, 10 | handleColorChange, 11 | }) { 12 | return ( 13 | 20 |

21 | Color Palette 22 |

23 |
24 | {Object.entries(colorPalette).map(([key, color], index) => ( 25 |
29 |
setActiveColor(key)} 33 | /> 34 |
35 |

36 | {color.label} 37 |

38 |

{color.value}

39 |
40 |
41 | ))} 42 |
43 | {activeColor && ( 44 |
45 |
46 | handleColorChange(color.hex, activeColor)} 49 | /> 50 | 56 |
57 |
58 | )} 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /client/src/components/portfolio-builder/DesignApproachSelector.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Card, 3 | CardHeader, 4 | CardTitle, 5 | CardDescription, 6 | } from "@/components/ui/card"; 7 | import { Wand2, Paintbrush } from "lucide-react"; 8 | 9 | const designApproaches = [ 10 | { 11 | id: "custom", 12 | title: "Describe your design", 13 | description: "Let AI generate a unique design based on your description", 14 | icon: , 15 | }, 16 | { 17 | id: "preset", 18 | title: "Select from presets", 19 | description: "Choose from our curated collection of design templates", 20 | icon: , 21 | }, 22 | ]; 23 | 24 | export default function DesignApproachSelector({ 25 | designChoice, 26 | setDesignChoice, 27 | }) { 28 | return ( 29 |
30 |

31 | Choose Design Approach 32 |

33 |
34 | {designApproaches.map((approach) => ( 35 | setDesignChoice(approach.id)} 43 | > 44 | 45 | 46 | {approach.icon} 47 | {approach.title} 48 | 49 | {approach.description} 50 | 51 | 52 | ))} 53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /client/src/components/random/Dice.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | const Dice = ({ animationType = "in-place", onRoll = () => {} }) => { 4 | const [rolling, setRolling] = useState(false); 5 | const [number, setNumber] = useState(Math.floor(Math.random() * 6) + 1); 6 | 7 | const getDots = (num) => { 8 | const dots = []; 9 | for (let i = 0; i < num; i++) { 10 | dots.push(); 11 | } 12 | return dots; 13 | }; 14 | 15 | const rollDice = () => { 16 | if (!rolling) { 17 | setRolling(true); 18 | const newNumber = Math.floor(Math.random() * 6) + 1; 19 | setTimeout(() => { 20 | setNumber(newNumber); 21 | setRolling(false); 22 | onRoll(); 23 | }, 2000); 24 | } 25 | }; 26 | 27 | const containerClassName = `dice-container ${ 28 | rolling ? (animationType === "chaotic" ? "jumping" : "") : "" 29 | }`; 30 | 31 | return ( 32 |
33 |
34 |
{getDots(number)}
35 |
{getDots(6)}
36 |
{getDots(4)}
37 |
{getDots(3)}
38 |
{getDots(2)}
39 |
{getDots(5)}
40 |
41 |
42 | ); 43 | }; 44 | 45 | export default Dice; 46 | -------------------------------------------------------------------------------- /client/src/components/random/EmojiOverlay.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { motion, AnimatePresence } from "framer-motion"; 3 | 4 | const animationOptions = [ 5 | { 6 | name: "wave", 7 | animate: { 8 | rotate: [0, 10, -10, 0], 9 | scale: [0, 1.2, 1], 10 | transition: { duration: 2, repeat: Infinity, repeatType: "reverse" }, 11 | }, 12 | }, 13 | { 14 | name: "bounce", 15 | animate: { 16 | y: [0, -20, 0], 17 | transition: { duration: 1.5, repeat: Infinity, ease: "easeInOut" }, 18 | }, 19 | }, 20 | { 21 | name: "spin", 22 | animate: { 23 | rotate: [0, 360], 24 | transition: { duration: 2, repeat: Infinity, ease: "linear" }, 25 | }, 26 | }, 27 | { 28 | name: "pulse", 29 | animate: { 30 | scale: [1, 1.2, 1], 31 | opacity: [1, 0.7, 1], 32 | transition: { duration: 1.5, repeat: Infinity }, 33 | }, 34 | }, 35 | { 36 | name: "zigzag", 37 | animate: (i) => ({ 38 | x: [0, i % 2 === 0 ? 50 : -50, 0], 39 | y: [0, 50, 0], 40 | transition: { duration: 2, repeat: Infinity, repeatType: "reverse" }, 41 | }), 42 | }, 43 | ]; 44 | 45 | const EmojiOverlay = ({ 46 | emoji = "🇮🇳", 47 | duration = 3000, 48 | count = 20, 49 | animationName = "wave", 50 | }) => { 51 | const [show, setShow] = useState(true); 52 | const [selectedAnimation, setSelectedAnimation] = useState( 53 | animationOptions.find((option) => option.name === animationName) || 54 | animationOptions[0] 55 | ); 56 | 57 | useEffect(() => { 58 | const timer = setTimeout(() => { 59 | setShow(false); 60 | }, duration); 61 | 62 | return () => clearTimeout(timer); 63 | }, [duration]); 64 | 65 | const randomPosition = () => ({ 66 | x: Math.random() * window.innerWidth, 67 | y: Math.random() * window.innerHeight, 68 | }); 69 | 70 | return ( 71 | 72 | {show && ( 73 |
74 | {[...Array(count)].map((_, index) => ( 75 | 86 | {emoji} 87 | 88 | ))} 89 |
90 | )} 91 |
92 | ); 93 | }; 94 | 95 | export default EmojiOverlay; 96 | -------------------------------------------------------------------------------- /client/src/components/random/ThreeDotLoader.jsx: -------------------------------------------------------------------------------- 1 | const ThreeDotLoader = () => { 2 | return ( 3 |
4 | 26 |
27 |
28 |
29 |
30 |
31 |
32 | ); 33 | }; 34 | 35 | export default ThreeDotLoader; 36 | -------------------------------------------------------------------------------- /client/src/components/ui/AutosizeTextarea.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cn } from '@/lib/utils'; 3 | import { useImperativeHandle } from 'react'; 4 | 5 | const useAutosizeTextArea = ({ 6 | textAreaRef, 7 | triggerAutoSize, 8 | maxHeight = Number.MAX_SAFE_INTEGER, 9 | minHeight = 0, 10 | }) => { 11 | const [init, setInit] = React.useState(true); 12 | React.useEffect(() => { 13 | const offsetBorder = 2; 14 | if (textAreaRef) { 15 | if (init) { 16 | textAreaRef.style.minHeight = `${minHeight + offsetBorder}px`; 17 | if (maxHeight > minHeight) { 18 | textAreaRef.style.maxHeight = `${maxHeight}px`; 19 | } 20 | setInit(false); 21 | } 22 | textAreaRef.style.height = `${minHeight + offsetBorder}px`; 23 | const scrollHeight = textAreaRef.scrollHeight; 24 | if (scrollHeight > maxHeight) { 25 | textAreaRef.style.height = `${maxHeight}px`; 26 | } else { 27 | textAreaRef.style.height = `${scrollHeight + offsetBorder}px`; 28 | } 29 | } 30 | }, [textAreaRef, triggerAutoSize]); 31 | }; 32 | 33 | export const AutosizeTextarea = React.forwardRef( 34 | ( 35 | { 36 | maxHeight = Number.MAX_SAFE_INTEGER, 37 | minHeight = 52, 38 | className, 39 | onChange, 40 | value, 41 | ...props 42 | }, 43 | ref 44 | ) => { 45 | const textAreaRef = React.useRef(null); 46 | const [triggerAutoSize, setTriggerAutoSize] = React.useState(''); 47 | 48 | useAutosizeTextArea({ 49 | textAreaRef: textAreaRef.current, 50 | triggerAutoSize: triggerAutoSize, 51 | maxHeight, 52 | minHeight, 53 | }); 54 | 55 | useImperativeHandle(ref, () => ({ 56 | textArea: textAreaRef.current, 57 | focus: () => textAreaRef?.current?.focus(), 58 | maxHeight, 59 | minHeight, 60 | })); 61 | 62 | React.useEffect(() => { 63 | setTriggerAutoSize(value); 64 | }, [props?.defaultValue, value]); 65 | 66 | return ( 67 |