├── .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 |
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 |
41 | Merchant Legal entity name: ShipStation
42 |
43 | Whatsapp:
44 | +91-9884002291
45 |
46 |
47 | E-Mail ID:
48 | crew@shipstation.ai
49 |
50 | Address: Bellandur, 560103, Karnataka, India
51 |
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 |
40 | Name
41 | Contact information including email address
42 |
43 | Demographic information such as postcode, preferences and interests, if
44 | required
45 |
46 | Other information relevant to customer surveys and/or offers
47 |
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 |
55 | Internal record keeping.
56 | We may use the information to improve our products and services.
57 |
58 | We may periodically send promotional emails about new products, special
59 | offers or other information which we think you may find interesting
60 | using the email address which you have provided.
61 |
62 |
63 | From time to time, we may also use your information to contact you for
64 | market research purposes. We may contact you by email, phone, fax or
65 | mail. We may use the information to customise the website according to
66 | your interests.
67 |
68 |
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 | onSuggestionClick(suggestion)}
23 | >
24 | {suggestion}
25 |
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 |
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 |
18 | window.open(`${import.meta.env.VITE_MAIN_URL}/showcase`, "_blank")
19 | }
20 | >
21 |
22 | View showcase
23 |
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 |
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 |
34 |
35 | Open Editor
36 |
37 | window.open(link, "_blank")}
39 | className="w-full"
40 | >
41 | Visit now
42 |
43 |
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 |
21 | {theme === "light" ? (
22 |
23 | ) : (
24 |
25 | )}
26 | Toggle theme
27 |
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 |
30 |
31 | Export
32 |
33 |
40 |
41 | Save
42 |
43 |
44 |
45 | {isFileLoading ? (
46 |
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 |
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 | onViewChange(view.id)}
25 | className={`w-10 h-10 px-2 ${
26 | index === 0
27 | ? "rounded-l-md rounded-r-none"
28 | : index === views.length - 1
29 | ? "rounded-r-md rounded-l-none"
30 | : "rounded-none"
31 | } ${index !== 0 ? "-ml-px" : ""}`}
32 | >
33 |
34 |
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 | setActiveColor(null)}
53 | >
54 | Close
55 |
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 |
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 |