├── src ├── App.scss ├── assets │ ├── nsfw.jpeg │ └── home │ │ ├── pic1.webp │ │ ├── pic2.webp │ │ ├── pic3.webp │ │ ├── pic4.webp │ │ └── pic5.webp ├── components │ ├── PrivacyPolicy │ │ ├── privacy.scss │ │ └── PrivacyPolicy.jsx │ ├── UserProfile │ │ ├── UserPostItem │ │ │ ├── user-post-item.scss │ │ │ └── UserPostItem.jsx │ │ ├── user-profile.scss │ │ └── UserProfile.jsx │ ├── Header │ │ ├── NewBadge │ │ │ ├── NewBadge.jsx │ │ │ └── new-badge.scss │ │ ├── header.scss │ │ ├── Navigation │ │ │ ├── ProfileInfo │ │ │ │ ├── profile-info.scss │ │ │ │ └── ProfileInfo.jsx │ │ │ ├── navigation.scss │ │ │ └── Navigation.jsx │ │ ├── Header.jsx │ │ └── DesktopNavigation │ │ │ ├── desktp-navagation.scss │ │ │ └── DesktopNavigation.jsx │ ├── LazyLoader │ │ ├── lazy-loader.scss │ │ └── LazyLoader.jsx │ ├── TermsOfService │ │ ├── terms.scss │ │ └── TermsOfService.jsx │ ├── Guide │ │ ├── guide.scss │ │ └── Guide.jsx │ ├── NotFound │ │ ├── not-found.scss │ │ └── NotFound.jsx │ ├── utils │ │ ├── axiosClient.js │ │ └── utilFunctions.js │ ├── Signup │ │ ├── signup.scss │ │ └── SignupSuccess.jsx │ ├── Create │ │ ├── PreviousImages │ │ │ ├── previous-images.scss │ │ │ └── PreviousImages.jsx │ │ ├── PreviousImagesMobile │ │ │ ├── previousimagecontainermobile.scss │ │ │ └── PreviousImagesMobile.jsx │ │ ├── GeneratorSettings │ │ │ ├── generator-settings.scss │ │ │ └── GeneratorSettings.jsx │ │ ├── ImagePreview │ │ │ ├── image-preview.scss │ │ │ └── ImagePreview.jsx │ │ ├── create.scss │ │ └── Create.jsx │ ├── ImgLoader │ │ ├── ImgLoader.jsx │ │ └── home-loader.scss │ ├── Loading │ │ ├── loading.scss │ │ └── Loading.jsx │ ├── Footer │ │ ├── footer.scss │ │ └── Footer.jsx │ ├── Logout │ │ └── Logout.jsx │ ├── MyProfile │ │ ├── PostItem │ │ │ ├── post-item.scss │ │ │ └── PostItem.jsx │ │ ├── my-profile.scss │ │ └── MyProfile.jsx │ ├── ContactPage │ │ ├── contact-page.scss │ │ └── ContactPage.jsx │ ├── Home │ │ ├── home.scss │ │ └── Home.jsx │ ├── PostDetails │ │ ├── post-details.scss │ │ └── PostDetails.jsx │ └── Settings │ │ ├── settings.scss │ │ └── Settings.jsx ├── colors.scss ├── redux │ ├── slices │ │ ├── multiPostCreateSlice.js │ │ ├── utilSlice.js │ │ ├── settingsSlice.js │ │ ├── appSlice.js │ │ ├── imageGenerateSlice.js │ │ ├── homeSlice.js │ │ ├── postDetailsSlice.js │ │ ├── currentUserSlice.js │ │ └── usernameSlice.js │ └── store.js ├── main.jsx ├── AllRoutes.jsx ├── App.jsx └── index.scss ├── vite.config.js ├── .gitignore ├── README.md ├── .eslintrc.cjs ├── index.html ├── package.json └── public └── vite.svg /src/App.scss: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } -------------------------------------------------------------------------------- /src/assets/nsfw.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TusharSoni014/craftura-ai/HEAD/src/assets/nsfw.jpeg -------------------------------------------------------------------------------- /src/assets/home/pic1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TusharSoni014/craftura-ai/HEAD/src/assets/home/pic1.webp -------------------------------------------------------------------------------- /src/assets/home/pic2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TusharSoni014/craftura-ai/HEAD/src/assets/home/pic2.webp -------------------------------------------------------------------------------- /src/assets/home/pic3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TusharSoni014/craftura-ai/HEAD/src/assets/home/pic3.webp -------------------------------------------------------------------------------- /src/assets/home/pic4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TusharSoni014/craftura-ai/HEAD/src/assets/home/pic4.webp -------------------------------------------------------------------------------- /src/assets/home/pic5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TusharSoni014/craftura-ai/HEAD/src/assets/home/pic5.webp -------------------------------------------------------------------------------- /src/components/PrivacyPolicy/privacy.scss: -------------------------------------------------------------------------------- 1 | .privacy-policy-container{ 2 | padding: 100px 30px; 3 | background-color: #FAFAFC; 4 | color: black; 5 | h2{ 6 | margin: 30px 0 10px 0 ; 7 | } 8 | } -------------------------------------------------------------------------------- /src/components/UserProfile/UserPostItem/user-post-item.scss: -------------------------------------------------------------------------------- 1 | .username-post-item{ 2 | position: relative; 3 | img{ 4 | width: 100%; 5 | border-radius: 10px; 6 | cursor: pointer; 7 | } 8 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/components/Header/NewBadge/NewBadge.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./new-badge.scss"; 3 | 4 | export default function NewBadge() { 5 | return
NEW
; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/LazyLoader/lazy-loader.scss: -------------------------------------------------------------------------------- 1 | .lazy-loader-container { 2 | width: 100%; 3 | min-height: 100vh; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | background-color: #FAFAFC; 8 | color: grey; 9 | } 10 | -------------------------------------------------------------------------------- /src/colors.scss: -------------------------------------------------------------------------------- 1 | $darkPrimary: #160031; 2 | $colorPrimary: dodgerblue; 3 | $colorSecondary: rgb(0, 99, 199); 4 | $color3: #ac0000; 5 | $proColor: rgba( 6 | $color: #ffb02e, 7 | $alpha: 0.4, 8 | ); 9 | $proColorAccent: #ffb02e; 10 | $successGreen: #59b259; 11 | $failRed: #e54135; 12 | -------------------------------------------------------------------------------- /src/components/LazyLoader/LazyLoader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./lazy-loader.scss"; 3 | import { RiLoader2Fill } from "react-icons/ri"; 4 | 5 | export default function LazyLoader() { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | .env 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | pnpm-debug.log* 9 | lerna-debug.log* 10 | 11 | node_modules 12 | dist 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /src/components/TermsOfService/terms.scss: -------------------------------------------------------------------------------- 1 | .terms-of-service-container{ 2 | padding: 100px 30px; 3 | background-color: #FAFAFC; 4 | color: black; 5 | h1{ 6 | color: dodgerblue; 7 | } 8 | h2{ 9 | margin: 15px 0 5px 0; 10 | } 11 | .flex{ 12 | display: flex; 13 | gap: 10px; 14 | margin: 5px 0; 15 | } 16 | h4{ 17 | margin: 10px 0 5px 0; 18 | } 19 | } -------------------------------------------------------------------------------- /src/components/Guide/guide.scss: -------------------------------------------------------------------------------- 1 | .guide-page-container { 2 | padding: 100px 20px 40px 20px; 3 | background-color: #FAFAFC; 4 | color: black; 5 | min-height: 100vh; 6 | h1{ 7 | margin: 15px 0; 8 | color: dodgerblue; 9 | } 10 | h2,h3{ 11 | margin: 10px 0; 12 | } 13 | h4{ 14 | margin: 5px 0; 15 | } 16 | ul{ 17 | margin: 0 0 0 20px; 18 | li{ 19 | &.item{ 20 | margin: 10px 0; 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /src/components/Header/header.scss: -------------------------------------------------------------------------------- 1 | @import "../../colors.scss"; 2 | .header-container { 3 | background-color: white; 4 | backdrop-filter: blur(10px); 5 | z-index: 999; 6 | position: fixed; 7 | top: 0; 8 | width: 100%; 9 | color: black; 10 | height: 70px; 11 | display: flex; 12 | justify-content: space-between; 13 | align-items: center; 14 | box-shadow: 0 0 10px rgba($color: #000000, $alpha: 0.1); 15 | h2{ 16 | padding: 0 20px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/NotFound/not-found.scss: -------------------------------------------------------------------------------- 1 | .notfound-container{ 2 | background-color: #fdfdfd; 3 | height: 100vh; 4 | padding: 70px 0 0 0 ; 5 | color: black; 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | flex-direction: column; 10 | img{ 11 | height: 150px; 12 | margin: 10px 0; 13 | } 14 | p{ 15 | font-weight: 700; 16 | color: rgba($color: black, $alpha: 0.6); 17 | text-align: center; 18 | padding: 10px; 19 | } 20 | } -------------------------------------------------------------------------------- /src/components/utils/axiosClient.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | axios.defaults.withCredentials = true; 3 | 4 | const axiosClient = axios.create({ 5 | baseURL: import.meta.env.VITE_SERVER_URL, 6 | }); 7 | 8 | axiosClient.interceptors.request.use((config) => { 9 | return config; 10 | }); 11 | 12 | axiosClient.interceptors.response.use( 13 | (response) => { 14 | return response; 15 | }, 16 | (error) => { 17 | return Promise.reject(error.response); 18 | } 19 | ); 20 | 21 | export default axiosClient; 22 | -------------------------------------------------------------------------------- /src/components/Header/NewBadge/new-badge.scss: -------------------------------------------------------------------------------- 1 | @import "../../../colors.scss"; 2 | 3 | .new-badge-container { 4 | font-size: 11px; 5 | padding: 2px 5px; 6 | border-radius: 5px; 7 | background-color: $proColorAccent; 8 | color: black; 9 | box-shadow: 0 0 10px rgba($color: $proColorAccent, $alpha: 0.5); 10 | animation: bubble 1s linear infinite; 11 | } 12 | 13 | @keyframes bubble { 14 | 0% { 15 | scale: 1; 16 | } 17 | 50% { 18 | scale: 0.95; 19 | } 20 | 100% { 21 | scale: 1; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Signup/signup.scss: -------------------------------------------------------------------------------- 1 | .signon-success-container { 2 | display: flex !important; 3 | justify-content: center !important; 4 | align-content: center !important; 5 | width: 100%; 6 | height: 100vh !important; 7 | position: relative; 8 | background-color: #FAFAFC; 9 | color: white; 10 | p { 11 | text-align: center; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | gap: 5px; 16 | .loader-icon{ 17 | font-size: 25px; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/NotFound/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./not-found.scss"; 3 | import { Link } from "react-router-dom"; 4 | 5 | export default function NotFound() { 6 | return ( 7 |
8 |

404 - Not Found

9 | 13 |

14 | You feel lost? visit home page from here. 15 |

16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/redux/slices/multiPostCreateSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const multiPostCreateSlice = createSlice({ 4 | name: "multiPostCreateSlice", 5 | initialState: { 6 | posts: [], 7 | }, 8 | reducers: { 9 | updatePosts: (state, action) => { 10 | state.posts.push(action.payload); 11 | }, 12 | clearPosts: (state, action) => { 13 | state.posts = []; 14 | }, 15 | }, 16 | }); 17 | 18 | export const { updatePosts, clearPosts } = multiPostCreateSlice.actions; 19 | export default multiPostCreateSlice.reducer; 20 | -------------------------------------------------------------------------------- /.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-refresh/only-export-components': [ 16 | 'warn', 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Create/PreviousImages/previous-images.scss: -------------------------------------------------------------------------------- 1 | .previous-images-container { 2 | height: 200px !important; 3 | display: flex; 4 | overflow-y: auto; 5 | gap: 10px; 6 | position: relative; 7 | img{ 8 | border-radius: 10px !important; 9 | cursor: pointer; 10 | } 11 | .img-preview-text{ 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | font-weight: 700; 16 | width: 100%; 17 | height: 100%; 18 | color: rgba($color: black, $alpha: 0.2); 19 | font-size: 20px; 20 | user-select: none; 21 | pointer-events: none; 22 | text-align: center; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.jsx"; 4 | import "./index.scss"; 5 | import { BrowserRouter } from "react-router-dom"; 6 | import { Provider } from "react-redux"; 7 | import store, { persistor } from "./redux/store"; 8 | import { PersistGate } from "redux-persist/integration/react"; 9 | 10 | ReactDOM.createRoot(document.getElementById("root")).render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/components/ImgLoader/ImgLoader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import './home-loader.scss' 3 | 4 | export default function ImgLoader() { 5 | return ( 6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/UserProfile/UserPostItem/UserPostItem.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./user-post-item.scss"; 3 | import { useDispatch } from "react-redux"; 4 | import { fetchPostDetailsDrawer } from "../../../redux/slices/postDetailsSlice"; 5 | 6 | export default function UserPostItem(post) { 7 | const dispatch = useDispatch() 8 | async function handlePostDetails() { 9 | dispatch( 10 | fetchPostDetailsDrawer({ postId: post.post._id, base64: post.post.url }) 11 | ); 12 | } 13 | return ( 14 |
15 | {post.post.prompt} 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Create/PreviousImagesMobile/previousimagecontainermobile.scss: -------------------------------------------------------------------------------- 1 | .previous-image-mobile-container { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); 4 | gap: 10px; 5 | padding: 20px 0; 6 | width: 100%; 7 | img { 8 | width: 100%; 9 | border-radius: 10px; 10 | cursor: pointer; 11 | } 12 | .img-preview-text { 13 | display: flex; 14 | justify-content: center; 15 | align-items: center; 16 | font-weight: 700; 17 | text-align: center; 18 | width: 100%; 19 | height: 100%; 20 | color: rgba($color: black, $alpha: 0.2); 21 | font-size: 20px; 22 | user-select: none; 23 | pointer-events: none; 24 | width: calc(100vw - 40px); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/redux/slices/utilSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const utilSlice = createSlice({ 4 | name: "utilSlice", 5 | initialState: { 6 | siteLoading: false, 7 | isSignonModalOpen: false, 8 | siteName:"Craftura AI", 9 | }, 10 | reducers: { 11 | updateSiteLoading: (state, action) => { 12 | state.siteLoading = action.payload; 13 | }, 14 | toggleSignonModalOpen: (state) => { 15 | state.isSignonModalOpen = !state.isSignonModalOpen; 16 | }, 17 | closeSignonModal: (state) => { 18 | state.isSignonModalOpen = false; 19 | }, 20 | }, 21 | }); 22 | 23 | export const { updateSiteLoading, toggleSignonModalOpen, closeSignonModal } = 24 | utilSlice.actions; 25 | export default utilSlice.reducer; 26 | -------------------------------------------------------------------------------- /src/components/Loading/loading.scss: -------------------------------------------------------------------------------- 1 | .loader-container { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | flex-direction: column; 6 | gap: 5px; 7 | .loader { 8 | width: 12em; 9 | height: 12em; 10 | path { 11 | stroke: #000; 12 | stroke-width: 0.6px; 13 | animation: dashArray 4s ease-in-out infinite, 14 | dashOffset 4s linear infinite; 15 | } 16 | } 17 | } 18 | 19 | @keyframes dashArray { 20 | 0% { 21 | stroke-dasharray: 0 1 359 0; 22 | } 23 | 24 | 50% { 25 | stroke-dasharray: 0 359 1 0; 26 | } 27 | 28 | 100% { 29 | stroke-dasharray: 359 1 0 0; 30 | } 31 | } 32 | 33 | @keyframes dashOffset { 34 | 0% { 35 | stroke-dashoffset: 365; 36 | } 37 | 38 | 100% { 39 | stroke-dashoffset: 5; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Header/Navigation/ProfileInfo/profile-info.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../colors.scss"; 2 | 3 | .profile-info-container { 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | .profile-info { 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 5px; 12 | .avatar-logo{ 13 | cursor: pointer; 14 | } 15 | .user-info{ 16 | display: flex; 17 | justify-content: center; 18 | flex-direction: column; 19 | } 20 | } 21 | .close-icon-container { 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | cursor: pointer; 26 | color: white !important; 27 | margin-left: auto; 28 | .close-icon { 29 | color: grey !important; 30 | font-size: 25px; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/redux/slices/settingsSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const settingsSlice = createSlice({ 4 | name: "settingsSlice", 5 | initialState: { 6 | negPrompt: "", 7 | seed: "", 8 | amount: 1, 9 | privateMode: false, 10 | }, 11 | reducers: { 12 | updateNegPrompt: (state, action) => { 13 | state.negPrompt = action.payload; 14 | }, 15 | updateSeed: (state, action) => { 16 | state.seed = action.payload; 17 | }, 18 | updateAmount: (state, action) => { 19 | state.amount = action.payload; 20 | }, 21 | togglePrivateMode: (state, action) => { 22 | state.privateMode = action.payload; 23 | }, 24 | }, 25 | }); 26 | 27 | export const { 28 | updateNegPrompt, 29 | updateSeed, 30 | updateAmount, 31 | togglePrivateMode, 32 | } = settingsSlice.actions; 33 | 34 | export default settingsSlice.reducer; 35 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Craftura AI - Generate Unlimited AI Images FREE 9 | 10 | 11 |
12 | 13 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/Header/Navigation/navigation.scss: -------------------------------------------------------------------------------- 1 | @import "../../../colors.scss"; 2 | 3 | .navigation-container { 4 | padding: 0 20px; 5 | .menu-icon-container { 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | cursor: pointer; 10 | font-size: 1.7rem; 11 | } 12 | } 13 | 14 | .drawer-container { 15 | background-color: rgba($color: #FAFAFC, $alpha: 0.8) !important; 16 | backdrop-filter: blur(15px); 17 | .ant-drawer-body { 18 | padding: 0 !important; 19 | } 20 | .drawer-menu-container { 21 | display: flex; 22 | flex-direction: column; 23 | height: calc(100vh - 70px) !important; 24 | .drawer-menu-btn { 25 | padding: 10px; 26 | border-radius: 0; 27 | height: fit-content; 28 | display: flex; 29 | justify-content: center; 30 | align-items: center; 31 | gap: 5px; 32 | } 33 | } 34 | } 35 | 36 | .navigation-footer-links-container { 37 | color: white; 38 | display: flex; 39 | gap: 5px; 40 | flex-wrap: wrap; 41 | font-size: 12px; 42 | } 43 | -------------------------------------------------------------------------------- /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", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@reduxjs/toolkit": "^1.9.6", 14 | "antd": "^5.9.4", 15 | "axios": "^1.5.1", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "react-google-button": "^0.7.2", 19 | "react-icons": "^4.11.0", 20 | "react-redux": "^8.1.3", 21 | "react-router-dom": "^6.16.0", 22 | "react-toastify": "^9.1.3", 23 | "redux-persist": "^6.0.0", 24 | "saas": "^1.0.0" 25 | }, 26 | "devDependencies": { 27 | "@types/react": "^18.2.15", 28 | "@types/react-dom": "^18.2.7", 29 | "@vitejs/plugin-react": "^4.0.3", 30 | "eslint": "^8.45.0", 31 | "eslint-plugin-react": "^7.32.2", 32 | "eslint-plugin-react-hooks": "^4.6.0", 33 | "eslint-plugin-react-refresh": "^0.4.3", 34 | "sass": "^1.68.0", 35 | "vite": "^4.4.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Footer/footer.scss: -------------------------------------------------------------------------------- 1 | @import "../../colors.scss"; 2 | 3 | .footer { 4 | padding: 20px 30px; 5 | background: #ECECF1; 6 | color: black; 7 | .title { 8 | margin: 10px 0; 9 | h2 { 10 | margin: 0; 11 | } 12 | small { 13 | color: grey; 14 | } 15 | } 16 | .social-links { 17 | display: flex; 18 | gap: 10px; 19 | margin: 10px 0; 20 | font-size: 25px; 21 | .icon { 22 | transition-duration: 0.25s; 23 | &.github { 24 | color: black; 25 | } 26 | &.linkedin { 27 | color: #0077b5; 28 | } 29 | &.gmail { 30 | color: #c11510; 31 | } 32 | &.youtube { 33 | color: #ff0000; 34 | } 35 | &:hover { 36 | scale: 1.1; 37 | cursor: pointer; 38 | } 39 | } 40 | } 41 | .footer-links { 42 | display: flex; 43 | justify-content: center; 44 | align-items: center; 45 | gap: 0px 10px; 46 | flex-wrap: wrap; 47 | margin: 50px 0 0 0; 48 | a { 49 | width: fit-content; 50 | text-decoration: none; 51 | font-size: 13px; 52 | } 53 | } 54 | .footer-info{ 55 | text-align: center; 56 | margin: 10px 0 0 0; 57 | color: grey; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/components/Header/Navigation/ProfileInfo/ProfileInfo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Avatar } from "antd"; 3 | import { useSelector } from "react-redux"; 4 | import "./profile-info.scss"; 5 | import { VscClose } from "react-icons/vsc"; 6 | import { useNavigate } from "react-router-dom"; 7 | 8 | export default function ProfileInfo({ close }) { 9 | const user = useSelector((state) => state.appSlice.currentUser); 10 | const isLoggedIn = useSelector((state) => state.appSlice.isLoggedIn); 11 | const navigate = useNavigate(); 12 | 13 | return ( 14 |
15 | {isLoggedIn && ( 16 |
17 | { 20 | navigate("/settings"); 21 | close(); 22 | }} 23 | size={45} 24 | icon={ 25 | <> 26 | 27 | 28 | } 29 | /> 30 |
31 | )} 32 |
close()} className="close-icon-container"> 33 | 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Create/GeneratorSettings/generator-settings.scss: -------------------------------------------------------------------------------- 1 | .settings-modal { 2 | background-color: transparent; 3 | width: fit-content !important; 4 | max-width: 500px; 5 | margin: 10px; 6 | max-height: 90vh; 7 | overflow-y: auto; 8 | .settings-modal-content { 9 | display: flex; 10 | flex-direction: column; 11 | gap: 10px; 12 | .prompt-container{ 13 | .prompt-btn-container{ 14 | display: flex; 15 | margin: 5px 0 0 0; 16 | gap: 5px; 17 | .random-prompt-btn{ 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | } 23 | } 24 | .amount-selection-selector{ 25 | display: flex; 26 | flex-direction: column; 27 | } 28 | .negprompt-textarea, 29 | .prompt-textarea { 30 | color: black; 31 | resize: none; 32 | } 33 | .prev-gen-settings-container{ 34 | .prevgen-settings{ 35 | background-color: rgba($color: black, $alpha: 0.1); 36 | border-radius: 5px; 37 | padding: 5px; 38 | display: flex; 39 | justify-content: space-between; 40 | align-items: center; 41 | margin: 5px 0; 42 | } 43 | } 44 | .ant-modal-content { 45 | width: fit-content; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/components/Header/Header.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "./header.scss"; 3 | import Navigation from "./Navigation/Navigation"; 4 | import { useSelector } from "react-redux"; 5 | import { useNavigate } from "react-router-dom"; 6 | import DesktopNavigation from "./DesktopNavigation/DesktopNavigation"; 7 | import { Modal } from "antd"; 8 | 9 | const Header = () => { 10 | const siteName = useSelector((state) => state.utilSlice.siteName); 11 | const navigate = useNavigate(); 12 | const [pageWidth, setPageWidth] = useState(window.innerWidth); 13 | 14 | useEffect(() => { 15 | const handleResize = () => { 16 | setPageWidth(window.innerWidth); 17 | }; 18 | window.addEventListener("resize", handleResize); 19 | return () => { 20 | window.removeEventListener("resize", handleResize); 21 | }; 22 | }, [pageWidth]); 23 | 24 | return ( 25 | <> 26 |
27 |

navigate("/")} 33 | > 34 | {siteName} 35 |

36 | {pageWidth > 600 ? : } 37 |
38 | 39 | ); 40 | }; 41 | 42 | export default Header; 43 | -------------------------------------------------------------------------------- /src/components/Logout/Logout.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { useNavigate } from "react-router-dom"; 4 | import { setIsLoggedIn, updateCurrentUser } from "../../redux/slices/appSlice"; 5 | import { resetSlice } from "../../redux/slices/imageGenerateSlice"; 6 | import axiosClient from "../utils/axiosClient"; 7 | import { errorMessageHandler } from "../utils/utilFunctions"; 8 | 9 | export default function Logout() { 10 | const dispatch = useDispatch(); 11 | const navigate = useNavigate(); 12 | 13 | async function handleLogout() { 14 | try { 15 | await axiosClient.post("/user/logout"); 16 | dispatch(setIsLoggedIn(false)); 17 | dispatch(updateCurrentUser({})); 18 | dispatch(resetSlice()); 19 | navigate("/"); 20 | } catch (error) { 21 | errorMessageHandler(error); 22 | } 23 | } 24 | 25 | useEffect(() => { 26 | handleLogout(); 27 | }, []); 28 | return ( 29 |
41 | Token Expired, Redirecting... 42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/MyProfile/PostItem/post-item.scss: -------------------------------------------------------------------------------- 1 | @import "../../../colors.scss"; 2 | 3 | .post-item { 4 | border-radius: 10px; 5 | position: relative; 6 | background-color: rgba($color: white, $alpha: 0.1); 7 | img { 8 | width: 100%; 9 | margin: 0; 10 | cursor: pointer; 11 | } 12 | .post-btn-container { 13 | display: flex; 14 | .post-download-btn, 15 | .post-info-btn, 16 | .post-delete-btn { 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | font-size: 15px; 21 | width: fit-content; 22 | padding: 5px 15px; 23 | height: fit-content; 24 | width: 100%; 25 | border-radius: 0; 26 | } 27 | .post-download-btn { 28 | background-color: rgba($color: $successGreen, $alpha: 0.5); 29 | transition-duration: 0.25s; 30 | &:hover { 31 | background-color: lighten($color: $successGreen, $amount: 0); 32 | } 33 | } 34 | .post-delete-btn { 35 | background-color: rgba($color: $color3, $alpha: 0.5); 36 | transition-duration: 0.25s; 37 | &:hover { 38 | background-color: lighten($color: $color3, $amount: 0); 39 | } 40 | } 41 | } 42 | } 43 | 44 | .delete-post-modal { 45 | small { 46 | color: red; 47 | } 48 | .delete-post-btn { 49 | background-color: $failRed; 50 | transition-duration: 0.25s; 51 | &:hover { 52 | background-color: lighten($color: $failRed, $amount: 10) !important; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Signup/SignupSuccess.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "./signup.scss"; 3 | import { useDispatch } from "react-redux"; 4 | import { 5 | setIsLoggedIn, 6 | updateCurrentUser, 7 | } from "../../redux/slices/appSlice"; 8 | import { useNavigate } from "react-router-dom"; 9 | import { RiLoader2Fill } from "react-icons/ri"; 10 | import { errorMessageHandler } from "../utils/utilFunctions"; 11 | import axiosClient from "../utils/axiosClient"; 12 | 13 | export default function SignonSuccess() { 14 | const dispatch = useDispatch(); 15 | const navigate = useNavigate(); 16 | const [loading, setLoading] = useState(true); 17 | async function checkUserAuth() { 18 | try { 19 | const response = await axiosClient.get("/google/user"); 20 | if (response && response.data) { 21 | dispatch(updateCurrentUser(response.data)); 22 | dispatch(setIsLoggedIn(true)); 23 | navigate("/create"); 24 | } 25 | } catch (error) { 26 | setLoading(false); 27 | errorMessageHandler(error); 28 | } 29 | } 30 | useEffect(() => { 31 | checkUserAuth(); 32 | }, []); 33 | return ( 34 |
35 |

36 | {loading ? ( 37 | <> 38 | 39 | Please wait... 40 | 41 | ) : ( 42 | <>Some error occured. Try again ! 43 | )} 44 |

45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import { persistStore, persistReducer } from "redux-persist"; 3 | import storage from "redux-persist/lib/storage"; 4 | import appSlice from "./slices/appSlice"; 5 | import imageGenerateSlice from "./slices/imageGenerateSlice"; 6 | import utilSlice from "./slices/utilSlice"; 7 | import settingsSlice from "./slices/settingsSlice"; 8 | import thunk from "redux-thunk"; 9 | import homeSlice from "./slices/homeSlice"; 10 | import currentUserSlice from "./slices/currentUserSlice"; 11 | import usernameSlice from "./slices/usernameSlice"; 12 | import postDetailsSlice from "./slices/postDetailsSlice"; 13 | import multiPostCreateSlice from "./slices/multiPostCreateSlice"; 14 | 15 | const persistConfig = { 16 | key: "root", 17 | storage, 18 | }; 19 | 20 | const persistedAppSliceReducer = persistReducer(persistConfig, appSlice); 21 | 22 | const store = configureStore({ 23 | reducer: { 24 | appSlice: persistedAppSliceReducer, 25 | imageGenerateSlice: imageGenerateSlice, 26 | utilSlice: utilSlice, 27 | settingsSlice: settingsSlice, 28 | homeSlice: homeSlice, 29 | currentUserSlice: currentUserSlice, 30 | usernameSlice: usernameSlice, 31 | postDetailsSlice: postDetailsSlice, 32 | multiPostCreateSlice: multiPostCreateSlice, 33 | }, 34 | devTools: process.env.NODE_ENV !== "production", 35 | middleware: [thunk], 36 | }); 37 | 38 | const persistor = persistStore(store); 39 | export default store; 40 | export { persistor }; 41 | -------------------------------------------------------------------------------- /src/components/ContactPage/contact-page.scss: -------------------------------------------------------------------------------- 1 | .contact-page-container { 2 | padding: 70px 0 0 0; 3 | min-height: 100vh; 4 | background-color: #FAFAFC; 5 | color: dodgerblue; 6 | display: flex; 7 | .left { 8 | width: 60%; 9 | padding: 15px; 10 | h1 { 11 | margin: 0 0 30px 0; 12 | font-size: 3rem; 13 | } 14 | p { 15 | font-weight: 700; 16 | color: rgba($color: grey, $alpha: 1); 17 | } 18 | } 19 | .right { 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | width: 40%; 24 | padding: 20px 40px; 25 | .contact-form-container { 26 | width: 100%; 27 | padding: 30px; 28 | border-radius: 10px; 29 | box-shadow: 5px 5px 20px rgba($color: #000000, $alpha: 0.1); 30 | background-color: white; 31 | .contact-description-textarea{ 32 | resize: none; 33 | } 34 | .submit-btn-container { 35 | display: flex; 36 | justify-content: flex-end; 37 | .submit-btn { 38 | padding: 10px 30px; 39 | height: auto; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | 46 | @media screen and (max-width: 900px) { 47 | .contact-page-container { 48 | .left { 49 | width: 40%; 50 | } 51 | .right { 52 | width: 60%; 53 | } 54 | } 55 | } 56 | @media screen and (max-width: 600px) { 57 | .contact-page-container { 58 | flex-direction: column; 59 | .left { 60 | width: 100%; 61 | } 62 | .right { 63 | width: 100%; 64 | padding: 15px; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/redux/slices/appSlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; 2 | import axiosClient from "../../components/utils/axiosClient"; 3 | 4 | export const fetchUserDetails = createAsyncThunk( 5 | "fetchUserDetails", 6 | async () => { 7 | const response = await axiosClient.get("/user/userDetails"); 8 | return response.data; 9 | } 10 | ); 11 | 12 | const appSlice = createSlice({ 13 | name: "appSlice", 14 | initialState: { 15 | isLoggedIn: false, 16 | currentUser: {}, 17 | buttonTimerExpiration: null, 18 | helperTourActive: true, 19 | }, 20 | reducers: { 21 | setIsLoggedIn: (state, action) => { 22 | state.isLoggedIn = action.payload; 23 | }, 24 | updateCurrentUser: (state, action) => { 25 | state.currentUser = action.payload; 26 | }, 27 | updateUsername: (state, action) => { 28 | state.currentUser.username = action.payload; 29 | }, 30 | setButtonTimerExpiration: (state, action) => { 31 | state.buttonTimerExpiration = action.payload; 32 | }, 33 | updateHelperTourState: (state, action) => { 34 | state.helperTourActive = action.payload; 35 | }, 36 | }, 37 | extraReducers: (builder) => { 38 | builder.addCase(fetchUserDetails.fulfilled, (state, action) => { 39 | const { picture } = action.payload; 40 | state.currentUser.picture = picture; 41 | }); 42 | }, 43 | }); 44 | 45 | export const { 46 | setIsLoggedIn, 47 | updateCurrentUser, 48 | updateUsername, 49 | setButtonTimerExpiration, 50 | updateHelperTourState, 51 | } = appSlice.actions; 52 | export default appSlice.reducer; 53 | -------------------------------------------------------------------------------- /src/components/Header/DesktopNavigation/desktp-navagation.scss: -------------------------------------------------------------------------------- 1 | @import "../../../colors.scss"; 2 | 3 | .desktop-navigation-container { 4 | padding: 0 20px 0 0; 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | gap: 15px; 9 | .btn-container { 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | .navigation-btn { 14 | width: fit-content !important; 15 | padding: 0 15px; 16 | height: 70px !important; 17 | border-radius: 0; 18 | display: flex; 19 | gap: 5px; 20 | justify-content: center; 21 | align-items: center; 22 | &.active { 23 | color: $colorPrimary !important; 24 | } 25 | } 26 | } 27 | } 28 | 29 | .profile-drawer { 30 | color: white; 31 | box-shadow: none !important; 32 | background-color:rgba($color: #ECECF1, $alpha: 0.8) !important; 33 | backdrop-filter: blur(10px); 34 | height: calc(100vh - 70px) !important; 35 | top: 70px !important; 36 | position: relative; 37 | .ant-drawer-body { 38 | padding: 0 !important; 39 | } 40 | .btn-container { 41 | display: flex; 42 | flex-direction: column; 43 | .empty { 44 | background-color: rgba($color: red, $alpha: 0.2) !important; 45 | } 46 | .drawer-menu-btn { 47 | border-radius: 0; 48 | width: 100%; 49 | border: none !important; 50 | padding: 15px; 51 | height: fit-content; 52 | display: flex; 53 | justify-content: flex-start; 54 | align-items: center; 55 | gap: 5px; 56 | .icon { 57 | font-size: 22px; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Home/home.scss: -------------------------------------------------------------------------------- 1 | .home-container { 2 | min-height: 100vh !important; 3 | .banner { 4 | height: 100vh; 5 | background-color: #FAFAFC; 6 | color: grey; 7 | padding: 10px; 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | flex-direction: column; 12 | .animated-text { 13 | font-size: 5rem; 14 | } 15 | h3 { 16 | color: grey; 17 | margin: 0 0 10px 0; 18 | text-align: center; 19 | } 20 | p { 21 | color: grey; 22 | text-align: center; 23 | span { 24 | color: grey; 25 | font-weight: 700; 26 | } 27 | } 28 | } 29 | .user-post-section { 30 | background-color: #FAFAFC; 31 | color: black; 32 | padding: 20px; 33 | p { 34 | color: grey; 35 | } 36 | .img-creations { 37 | margin: 15px 0; 38 | display: grid; 39 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 40 | gap: 10px; 41 | .home-page-loader { 42 | display: flex; 43 | justify-content: center; 44 | align-items: center; 45 | } 46 | img { 47 | width: 100% !important; 48 | object-fit: contain; 49 | border-radius: 10px; 50 | cursor: pointer; 51 | } 52 | } 53 | .btn-container { 54 | display: flex; 55 | justify-content: center; 56 | align-items: center; 57 | } 58 | } 59 | } 60 | 61 | @media screen and (max-width: 768px) { 62 | .home-container { 63 | .banner { 64 | .animated-text { 65 | font-size: 40px; 66 | } 67 | p { 68 | margin: 0 0 5px 0; 69 | font-size: 13px; 70 | padding: 0 15px; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/Create/PreviousImages/PreviousImages.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./previous-images.scss"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { fetchPostDetailsDrawer } from "../../../redux/slices/postDetailsSlice"; 5 | import { toast } from "react-toastify"; 6 | 7 | export default function PreviousImages() { 8 | const dispatch = useDispatch(); 9 | const previousImages = useSelector( 10 | (state) => state.imageGenerateSlice.previousGenerations 11 | ); 12 | const enablePrevGen = useSelector( 13 | (state) => state.imageGenerateSlice.enablePrevGen 14 | ); 15 | const prevGenPreviewCount = useSelector( 16 | (state) => state.imageGenerateSlice.prevGenPreviewCount 17 | ); 18 | async function handlePostDetails(post) { 19 | if(!post._id){ 20 | toast.error("Post data not found") 21 | } 22 | else{ 23 | dispatch( 24 | fetchPostDetailsDrawer({ 25 | postId: post._id, 26 | base64: `data:image/jpeg;base64,${post.image}`, 27 | }) 28 | ); 29 | } 30 | } 31 | return ( 32 |
33 | {previousImages.length === 0 ? ( 34 |
35 | {enablePrevGen ? ( 36 | <> 37 | Previous {prevGenPreviewCount} generated images will be shown 38 | here. 39 | 40 | ) : ( 41 | <>Disabled 42 | )} 43 |
44 | ) : ( 45 | previousImages.map((img, index) => { 46 | return ( 47 | handlePostDetails(img)} 50 | src={`data:image/jpeg;base64,${img.image}`} 51 | /> 52 | ); 53 | }) 54 | )} 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/AllRoutes.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy } from "react"; 2 | import { Route, Routes } from "react-router-dom"; 3 | import LazyLoader from "./components/LazyLoader/LazyLoader"; 4 | 5 | const Create = lazy(() => import("./components/Create/Create")); 6 | const NotFound = lazy(() => import("./components/NotFound/NotFound")); 7 | const ContactPage = lazy(() => import("./components/ContactPage/ContactPage")); 8 | const Settings = lazy(() => import("./components/Settings/Settings")); 9 | const PrivacyPolicy = lazy(() => 10 | import("./components/PrivacyPolicy/PrivacyPolicy") 11 | ); 12 | const TermsOfService = lazy(() => 13 | import("./components/TermsOfService/TermsOfService") 14 | ); 15 | const Home = lazy(() => import("./components/Home/Home")); 16 | const SignonSuccess = lazy(() => import("./components/Signup/SignupSuccess")); 17 | const MyProfile = lazy(() => import("./components/MyProfile/MyProfile")); 18 | const UserProfile = lazy(() => import("./components/UserProfile/UserProfile")); 19 | const Logout = lazy(() => import("./components/Logout/Logout")); 20 | const Guide = lazy(() => import("./components/Guide/Guide")); 21 | 22 | export default function AllRoutes() { 23 | return ( 24 | }> 25 | 26 | } /> 27 | } /> 28 | } /> 29 | } /> 30 | } /> 31 | } /> 32 | } /> 33 | } /> 34 | } /> 35 | } /> 36 | } /> 37 | } /> 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Create/PreviousImagesMobile/PreviousImagesMobile.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./previousimagecontainermobile.scss"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { fetchPostDetailsDrawer } from "../../../redux/slices/postDetailsSlice"; 5 | import { toast } from "react-toastify"; 6 | 7 | export default function PreviousImagesMobile() { 8 | const dispatch = useDispatch() 9 | const previousImages = useSelector( 10 | (state) => state.imageGenerateSlice.previousGenerations 11 | ); 12 | const enablePrevGen = useSelector( 13 | (state) => state.imageGenerateSlice.enablePrevGen 14 | ); 15 | const prevGenPreviewCount = useSelector( 16 | (state) => state.imageGenerateSlice.prevGenPreviewCount 17 | ); 18 | async function handlePostDetails(post) { 19 | if(!post._id){ 20 | toast.error("Post data not found") 21 | } 22 | else{ 23 | dispatch( 24 | fetchPostDetailsDrawer({ 25 | postId: post._id, 26 | base64: `data:image/jpeg;base64,${post.image}`, 27 | }) 28 | ); 29 | } 30 | } 31 | return ( 32 | <> 33 |

34 | Previously Generated 35 |

36 |
37 | {previousImages.length === 0 ? ( 38 |
39 | {enablePrevGen ? ( 40 | <> 41 | Previous {prevGenPreviewCount} generated images will be shown 42 | here. 43 | 44 | ) : ( 45 | <>Disabled 46 | )} 47 |
48 | ) : ( 49 | previousImages.map((img, index) => { 50 | return ( 51 | handlePostDetails(img)} 53 | key={index} 54 | src={`data:image/jpeg;base64,${img.image}`} 55 | /> 56 | ); 57 | }) 58 | )} 59 |
60 | 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/redux/slices/imageGenerateSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const imageGenerateSlice = createSlice({ 4 | name: "imageGenerateSlice", 5 | initialState: { 6 | isLoading: false, 7 | isGenerated: false, 8 | prompt: "", 9 | imageURL: null, 10 | previousGenerations: [], 11 | enablePrevGen: true, 12 | prevGenPreviewCount: 6, 13 | }, 14 | reducers: { 15 | setIsLoading: (state, action) => { 16 | state.isLoading = action.payload; 17 | }, 18 | setIsGenerated: (state, action) => { 19 | state.isGenerated = action.payload; 20 | }, 21 | updatePrompt: (state, action) => { 22 | state.prompt = action.payload; 23 | }, 24 | updateImageURL: (state, action) => { 25 | state.imageURL = action.payload; 26 | }, 27 | updatePrevGenPreviewCount: (state, action) => { 28 | state.prevGenPreviewCount = action.payload; 29 | }, 30 | updatePreviousGenerations: (state, action) => { 31 | if (state.enablePrevGen) { 32 | const itemsToAdd = Array.isArray(action.payload) 33 | ? action.payload 34 | : [action.payload]; 35 | state.previousGenerations.unshift(...itemsToAdd); 36 | if (state.previousGenerations.length > state.prevGenPreviewCount) { 37 | state.previousGenerations.splice(state.prevGenPreviewCount); 38 | } 39 | } 40 | }, 41 | updatePrevGenSettings: (state, action) => { 42 | state.enablePrevGen = action.payload; 43 | }, 44 | clearPrevGenImages: (state, action) => { 45 | state.previousGenerations = []; 46 | }, 47 | resetSlice: (state) => { 48 | state.isLoading = false; 49 | state.isGenerated = false; 50 | state.prompt = ""; 51 | state.imageURL = null; 52 | state.previousGenerations = []; 53 | }, 54 | }, 55 | }); 56 | 57 | export const { 58 | setIsLoading, 59 | setIsGenerated, 60 | updatePrompt, 61 | updateImageURL, 62 | updatePreviousGenerations, 63 | resetSlice, 64 | updatePrevGenSettings, 65 | clearPrevGenImages, 66 | updatePrevGenPreviewCount, 67 | } = imageGenerateSlice.actions; 68 | export default imageGenerateSlice.reducer; 69 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | import "./footer.scss"; 4 | import { Link } from "react-router-dom"; 5 | import { BiLogoGmail } from "react-icons/bi"; 6 | import { BsGithub, BsLinkedin, BsYoutube } from "react-icons/bs"; 7 | 8 | export default function Footer() { 9 | const siteName = useSelector((state) => state.utilSlice.siteName); 10 | return ( 11 |
12 |
13 |

{siteName}

14 | A advance AI tool for image creation. 15 |
16 |
17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 41 | 45 | 46 |
47 | Terms of Service 48 | Privacy Policy 49 | Contact Us 50 | Guide 51 |
52 | 53 |
54 | 55 | This tool is made by Tushar Soni | © {new Date().getFullYear()} 56 | 57 |
58 | 59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/components/PostDetails/post-details.scss: -------------------------------------------------------------------------------- 1 | .post-details-drawer-container { 2 | display: flex; 3 | gap: 0px; 4 | height: 100%; 5 | gap: 20px; 6 | color: black; 7 | .post-details-loading-container { 8 | width: 100%; 9 | height: 100%; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | font-size: 25px; 14 | } 15 | .details-left { 16 | width: auto; 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | max-width: 50%; 21 | flex: 1; 22 | img { 23 | height: 100%; 24 | object-fit:contain; 25 | width: 100%; 26 | } 27 | } 28 | .details-right { 29 | flex: 1; 30 | display: flex; 31 | flex-direction: column; 32 | gap: 10px; 33 | width: 100%; 34 | height: 100%; 35 | overflow-y: auto; 36 | .detail-item { 37 | small { 38 | display: inline-block; 39 | margin: 0 0 5px 0; 40 | user-select: none; 41 | } 42 | .prompt, 43 | .seed, 44 | .neg-prompt, 45 | .date { 46 | background-color: rgba($color: black, $alpha: 0.1); 47 | padding: 10px; 48 | border-radius: 10px; 49 | width: 100% !important; 50 | display: block; 51 | overflow-y: auto; 52 | cursor: pointer; 53 | color: black; 54 | } 55 | } 56 | .post-actions-buttons-container { 57 | display: flex; 58 | flex-direction: column; 59 | gap: 5px; 60 | margin: 20px 0 0 0; 61 | .buttons { 62 | display: flex; 63 | gap: 5px; 64 | .post-action-btn { 65 | display: flex; 66 | justify-content: center; 67 | align-items: center; 68 | width: fit-content; 69 | .icon { 70 | display: flex; 71 | justify-content: center; 72 | align-items: center; 73 | font-size: 20px; 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | @media screen and (max-width: 900px) { 82 | .post-details-drawer-container { 83 | height: auto; 84 | flex-direction: column; 85 | gap: 20px; 86 | .details-left { 87 | max-width: 100% !important; 88 | img { 89 | width: 100%; 90 | max-width: 400px; 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/components/MyProfile/PostItem/PostItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Modal } from "antd"; 3 | import { toast } from "react-toastify"; 4 | import { useDispatch } from "react-redux"; 5 | import { 6 | emptyCurrentPosts, 7 | fetchMyPosts, 8 | fetchMyPostsCount, 9 | } from "../../../redux/slices/currentUserSlice"; 10 | import "./post-item.scss"; 11 | import { errorMessageHandler } from "../../utils/utilFunctions"; 12 | import axiosClient from "../../utils/axiosClient"; 13 | import { fetchPostDetailsDrawer } from "../../../redux/slices/postDetailsSlice"; 14 | 15 | export default function PostItem({ url, id, setPage }) { 16 | const [deleteLoading, setDeleteLoading] = useState(false); 17 | const [isModalOpen, setIsModalOpen] = useState(false); 18 | const [currentImageId, setCurrentImageId] = useState(null); 19 | const dispatch = useDispatch(); 20 | 21 | async function handlePostDetails() { 22 | dispatch(fetchPostDetailsDrawer({ postId: id, base64: url })); 23 | } 24 | 25 | const handleCancel = () => { 26 | setIsModalOpen(false); 27 | }; 28 | 29 | const handlePostDelete = async () => { 30 | setDeleteLoading(true); 31 | try { 32 | await axiosClient.delete(`/image/delete/${currentImageId}`); 33 | toast.success("Post deleted successfully"); 34 | dispatch(emptyCurrentPosts()); 35 | dispatch(fetchMyPostsCount()); 36 | dispatch(fetchMyPosts({ page: 1 })); 37 | setPage(1); 38 | } catch (error) { 39 | errorMessageHandler(error); 40 | } finally { 41 | setDeleteLoading(false); 42 | setIsModalOpen(false); 43 | setCurrentImageId(null); 44 | } 45 | }; 46 | 47 | return ( 48 | <> 49 |
50 | generated pic 51 |
52 | 62 |

Are you sure you want to delete this post?

63 | This is action is not reversible. 64 |
65 | 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/redux/slices/homeSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import { createAsyncThunk } from "@reduxjs/toolkit"; 3 | import { toast } from "react-toastify"; 4 | import { errorMessageHandler } from "../../components/utils/utilFunctions"; 5 | import axiosClient from "../../components/utils/axiosClient"; 6 | 7 | export const fetchHomeImages = createAsyncThunk( 8 | "fetchHomeImages", 9 | async (body, thunkApi) => { 10 | const { page } = body; 11 | try { 12 | const response = await axiosClient.get(`/image/getAllImages/${page}`); 13 | if (response.data.posts.length === 0) { 14 | thunkApi.dispatch(updatePostFlag(false)); 15 | } 16 | return response.data.posts; 17 | } catch (error) { 18 | errorMessageHandler(error); 19 | } finally { 20 | thunkApi.dispatch(updateLoading(false)); 21 | } 22 | } 23 | ); 24 | 25 | const homeSlice = createSlice({ 26 | name: "homeSlice", 27 | initialState: { 28 | posts: [], 29 | postsLoading: false, 30 | isMorePosts: true, 31 | error: null, 32 | }, 33 | reducers: { 34 | updatePostFlag: (state, action) => { 35 | state.isMorePosts = action.payload; 36 | }, 37 | updateLoading: (state, action) => { 38 | state.postsLoading = action.payload; 39 | }, 40 | }, 41 | extraReducers: (builder) => { 42 | builder 43 | .addCase(fetchHomeImages.fulfilled, (state, action) => { 44 | try { 45 | if (action.meta.arg.page === 1) { 46 | state.posts = action.payload; 47 | } else { 48 | if (action.payload === undefined) { 49 | toast.error("Error try again!"); 50 | } 51 | state.posts = [...state.posts, ...action.payload]; 52 | } 53 | state.postsLoading = false; 54 | state.btnVisible = true; 55 | state.error = null; 56 | } catch (error) { 57 | toast.error("Error trying to load posts!"); 58 | state.postsLoading = false; 59 | } 60 | }) 61 | .addCase(fetchHomeImages.pending, (state) => { 62 | state.postsLoading = true; 63 | }) 64 | .addCase(fetchHomeImages.rejected, (state) => { 65 | state.postsLoading = false; 66 | }); 67 | }, 68 | }); 69 | 70 | export default homeSlice.reducer; 71 | export const { updatePostFlag, updateLoading } = homeSlice.actions; 72 | -------------------------------------------------------------------------------- /src/redux/slices/postDetailsSlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; 2 | import { errorMessageHandler } from "../../components/utils/utilFunctions"; 3 | import axiosClient from "../../components/utils/axiosClient"; 4 | 5 | export const fetchPostDetailsDrawer = createAsyncThunk( 6 | "fetchPostDetailsDrawer", 7 | async (body, thunkApi) => { 8 | const { postId, base64 } = body; 9 | thunkApi.dispatch(updatePostDetailOpenState(true)); 10 | try { 11 | const response = await axiosClient.post("/image/postDetails", { 12 | postId: postId, 13 | }); 14 | const { owner, prompt, negPrompt, seed, createdAt } = response.data; 15 | return { owner, prompt, negPrompt, seed, img: base64, createdAt }; 16 | } catch (error) { 17 | errorMessageHandler(error); 18 | } 19 | } 20 | ); 21 | 22 | const postDetailsSlice = createSlice({ 23 | name: "postDetailsSlice", 24 | initialState: { 25 | post: {}, 26 | postDetailsLoading: true, 27 | postDetailOpenState: false, 28 | }, 29 | reducers: { 30 | clearPostDetails: (state) => { 31 | state.post = {}; 32 | state.postDetailsLoading = false; 33 | }, 34 | updatePostDetailOpenState: (state, action) => { 35 | state.postDetailOpenState = action.payload; 36 | }, 37 | }, 38 | extraReducers: (builder) => { 39 | builder 40 | .addCase(fetchPostDetailsDrawer.pending, (state) => { 41 | state.postDetailsLoading = true; 42 | state.post = {}; 43 | }) 44 | .addCase(fetchPostDetailsDrawer.rejected, (state) => { 45 | state.postDetailsLoading = false; 46 | }) 47 | .addCase(fetchPostDetailsDrawer.fulfilled, (state, action) => { 48 | if (action.payload) { 49 | const { owner, prompt, negPrompt, seed, img, createdAt } = 50 | action.payload; 51 | state.post.owner = owner; 52 | state.post.prompt = prompt; 53 | state.post.negPrompt = negPrompt; 54 | state.post.seed = seed; 55 | state.post.img = img; 56 | state.post.createdAt = createdAt; 57 | state.postDetailsLoading = false; 58 | } else { 59 | state.postDetailsLoading = false; 60 | } 61 | }); 62 | }, 63 | }); 64 | 65 | export const { clearPostDetails, updatePostDetailOpenState } = 66 | postDetailsSlice.actions; 67 | export default postDetailsSlice.reducer; 68 | -------------------------------------------------------------------------------- /src/components/Create/ImagePreview/image-preview.scss: -------------------------------------------------------------------------------- 1 | .image-preview-container { 2 | height: 100% !important; 3 | position: relative; 4 | width: 100%; 5 | height: 100%; 6 | border-radius: 15px; 7 | overflow: hidden; 8 | z-index: 99; 9 | .image-preview-item { 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | height: 100%; 14 | .loading-container { 15 | height: 300px; 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | } 20 | .generated-img-container { 21 | height: 100%; 22 | width: 100%; 23 | background-color: black; 24 | position: relative; 25 | .private-mode-text{ 26 | display: flex; 27 | justify-content: center; 28 | align-items: center; 29 | height: 100%; 30 | color: grey; 31 | font-weight: 700; 32 | user-select: none; 33 | } 34 | .img { 35 | width: 100%; 36 | height: 100%; 37 | object-fit: cover; 38 | } 39 | .generated-img-button-section { 40 | position: absolute; 41 | bottom: 0; 42 | left: 0; 43 | width: 100%; 44 | padding: 10px; 45 | display: flex; 46 | justify-content: flex-end; 47 | .img-download-btn { 48 | display: flex; 49 | justify-content: center; 50 | align-items: center; 51 | background-color: #59b259; 52 | box-shadow: 0 0 10px rgba($color: #000000, $alpha: 0.5); 53 | transition-duration: 0.25s; 54 | &:hover{ 55 | background-color: lighten($color: #59b259, $amount: 10); 56 | } 57 | } 58 | } 59 | } 60 | } 61 | .fade-image { 62 | position: absolute; 63 | top: 0; 64 | left: 0; 65 | width: 100%; 66 | height: 100%; 67 | opacity: 0; 68 | transition: opacity 1s ease-in-out; 69 | display: flex; 70 | &.active { 71 | opacity: 1; 72 | } 73 | .prompt-highlight-text { 74 | position: absolute; 75 | bottom: 0; 76 | width: 100%; 77 | padding: 20px; 78 | background: linear-gradient(to top, black, transparent); 79 | display: flex; 80 | justify-content: flex-start; 81 | align-items: center; 82 | gap: 10px; 83 | color: white; 84 | } 85 | } 86 | img { 87 | height: 100%; 88 | width: 100%; 89 | object-fit: cover; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/MyProfile/my-profile.scss: -------------------------------------------------------------------------------- 1 | @import "../../colors.scss"; 2 | 3 | .my-profile-page-container { 4 | padding: 70px 0 0 0; 5 | background-color: #FAFAFC; 6 | color: black; 7 | width: 100%; 8 | min-height: 100vh; 9 | .profile-header-container { 10 | width: 100%; 11 | height: 250px; 12 | background-color: #FAFAFC; 13 | display: flex; 14 | padding: 10px; 15 | background: linear-gradient(to bottom, rgba($colorPrimary, 0.5), transparent); 16 | .profile-right { 17 | display: flex; 18 | justify-content: center; 19 | align-items: flex-start; 20 | flex-direction: column; 21 | .edit-mode-container { 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | padding: 10px; 26 | background-color: rgba($color: black, $alpha: 0.1); 27 | border-radius: 10px; 28 | gap: 10px; 29 | margin: 0 0 10px 0; 30 | } 31 | .username { 32 | font-size: 30px; 33 | font-weight: 700; 34 | } 35 | .posts-count { 36 | padding: 5px 15px; 37 | border-radius: 10px; 38 | margin: 0 0 5px 0; 39 | background-color: rgba($color: white, $alpha: 0.1); 40 | } 41 | } 42 | .profile-left { 43 | display: flex; 44 | justify-content: flex-start; 45 | align-items: center; 46 | margin: 0 10px 0 0; 47 | img { 48 | width: 150px; 49 | height: 150px; 50 | border-radius: 50%; 51 | background-color: white; 52 | } 53 | } 54 | } 55 | .profile-posts-container { 56 | border: 1px solid transparent; 57 | width: 100%; 58 | .posts-container { 59 | display: grid; 60 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 61 | gap: 10px; 62 | padding: 0 10px; 63 | margin: 0 0 15px 0; 64 | } 65 | .load-more-btn-container { 66 | width: 100%; 67 | display: flex; 68 | justify-content: center; 69 | align-items: center; 70 | margin: 10px 0; 71 | } 72 | } 73 | } 74 | 75 | @media screen and (max-width: 768px) { 76 | .my-profile-page-container { 77 | .profile-header-container { 78 | .profile-right { 79 | .username { 80 | font-size: 20px; 81 | overflow-wrap: break-word; 82 | word-wrap: break-word; 83 | word-break: break-word; 84 | } 85 | .edit-mode-container { 86 | font-size: 12px; 87 | } 88 | } 89 | .profile-left { 90 | img { 91 | width: 100px; 92 | height: 100px; 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/redux/slices/currentUserSlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; 2 | import { toast } from "react-toastify"; 3 | import { errorMessageHandler } from "../../components/utils/utilFunctions"; 4 | import axiosClient from "../../components/utils/axiosClient"; 5 | 6 | export const fetchMyPosts = createAsyncThunk( 7 | "fetchMyPosts", 8 | async (body, thunkApi) => { 9 | const { page } = body; 10 | try { 11 | const response = await axiosClient.get(`/user/get-my-posts/${page}`); 12 | return response.data.posts; 13 | } catch (error) { 14 | thunkApi.dispatch(fetchMyPosts.rejected(error.data.message)); 15 | toast.error(error.response.data.message); 16 | } 17 | } 18 | ); 19 | export const fetchMyPostsCount = createAsyncThunk( 20 | "fetchMyPostsCount", 21 | async (_, thunkApi) => { 22 | try { 23 | const response = await axiosClient.get(`/user/get-post-count`); 24 | return response.data.postsCount; 25 | } catch (error) { 26 | errorMessageHandler(error); 27 | thunkApi.dispatch(fetchMyPosts.rejected(error.data.message)); 28 | } 29 | } 30 | ); 31 | 32 | const currentUserSlice = createSlice({ 33 | name: "currentUserSlice", 34 | initialState: { 35 | posts: [], 36 | myPostsLoading: false, 37 | myPostscount: null, 38 | postError: null, 39 | isLoadMoreBtn: true, 40 | }, 41 | reducers: { 42 | emptyCurrentPosts: (state) => { 43 | state.posts = []; 44 | }, 45 | }, 46 | extraReducers: (builder) => { 47 | builder 48 | .addCase(fetchMyPosts.fulfilled, (state, action) => { 49 | if (action.meta.arg.page === 1) { 50 | state.posts = action.payload; 51 | } else { 52 | if (action.payload === undefined) { 53 | toast.error("Error try again!"); 54 | } 55 | state.posts = [...state.posts, ...action.payload]; 56 | if (action.payload.length == 0) { 57 | state.isLoadMoreBtn = false; 58 | } 59 | } 60 | state.myPostsLoading = false; 61 | state.postError = null; 62 | }) 63 | .addCase(fetchMyPosts.pending, (state, action) => { 64 | state.myPostsLoading = true; 65 | state.postError = null; 66 | }) 67 | .addCase(fetchMyPosts.rejected, (state, action) => { 68 | state.myPostsLoading = false; 69 | state.postError = action.error.message; 70 | }) 71 | .addCase(fetchMyPostsCount.fulfilled, (state, action) => { 72 | state.myPostscount = action.payload; 73 | }) 74 | .addCase(fetchMyPostsCount.rejected, (state, action) => { 75 | state.error = action.error.message; 76 | }); 77 | }, 78 | }); 79 | 80 | export const { emptyCurrentPosts } = currentUserSlice.actions; 81 | export default currentUserSlice.reducer; 82 | -------------------------------------------------------------------------------- /src/components/Home/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "./home.scss"; 3 | import { Button } from "antd"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { fetchHomeImages } from "../../redux/slices/homeSlice"; 6 | import ImgLoader from "../ImgLoader/ImgLoader"; 7 | import { errorMessageHandler } from "../utils/utilFunctions"; 8 | import { fetchPostDetailsDrawer } from "../../redux/slices/postDetailsSlice"; 9 | import Footer from "../Footer/Footer"; 10 | 11 | export default function Home() { 12 | const dispatch = useDispatch(); 13 | const siteName = useSelector((state) => state.utilSlice.siteName); 14 | const posts = useSelector((state) => state.homeSlice.posts); 15 | const isFetching = useSelector((state) => state.homeSlice.postsLoading); 16 | const isMorePosts = useSelector((state) => state.homeSlice.isMorePosts); 17 | const [page, setPage] = useState(1); 18 | 19 | async function handlePostDetails(post) { 20 | dispatch(fetchPostDetailsDrawer({ postId: post._id, base64: post.url })); 21 | } 22 | 23 | async function handleLoadMore() { 24 | try { 25 | setPage(page + 1); 26 | } catch (error) { 27 | errorMessageHandler(error); 28 | } 29 | } 30 | 31 | useEffect(() => { 32 | if (posts?.length === 0 && page === 1) { 33 | dispatch(fetchHomeImages({ page: page })); 34 | } else if (page > 1) { 35 | dispatch(fetchHomeImages({ page: page })); 36 | } 37 | }, [page, dispatch]); 38 | 39 | return ( 40 | <> 41 |
42 |
43 |

{siteName}

44 |

Dall·E Alternative - Generate UNLIMITED Images for FREE.

45 |
46 |
47 |

Latest Posts

48 |

Some awesome creations by you.

49 |
50 | {isFetching && page === 1 ? ( 51 |
52 | 53 |
54 | ) : ( 55 | posts && 56 | posts?.map((img, index) => { 57 | return ( 58 | handlePostDetails(img)} 61 | key={index} 62 | src={img.url} 63 | /> 64 | ); 65 | }) 66 | )} 67 |
68 |
69 | {posts?.length !== 0 && 70 | (isMorePosts ? ( 71 | 79 | ) : null)} 80 |
81 |
82 |
83 |