├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── _redirects ├── pwa-192x192.png ├── pwa-512x512.png ├── screenshotnarrow1080x2344a.jpg ├── screenshotnarrow1080x2344b.jpg ├── screenshotwide1200x630.jpg ├── shorticonmen.png ├── shorticonwishlist.png └── shorticonwomen.png ├── src ├── App.tsx ├── appwrite │ ├── auth.ts │ ├── config.ts │ └── confvars.ts ├── assets │ ├── heart.svg │ ├── heartfill.svg │ └── signinwithgoogle.svg ├── components │ ├── 3dCard.tsx │ ├── AuthModal.tsx │ ├── BannerSlider.tsx │ ├── CollectionCards.tsx │ ├── CreateProducts.tsx │ ├── DeleteProducts.tsx │ ├── ErrorBoundary.tsx │ ├── FloatingMenu.tsx │ ├── Footer.tsx │ ├── Navbar.tsx │ ├── ProductCard.tsx │ ├── ProductCards.tsx │ ├── UpdateProducts.tsx │ ├── UploadImages.tsx │ ├── WishlistCards.tsx │ └── WrongPage.tsx ├── index.css ├── index.ts ├── main.tsx ├── pages │ ├── Admin.tsx │ ├── Collection.tsx │ ├── Contact.tsx │ ├── Cuties.tsx │ ├── Error404Page.tsx │ ├── Home.tsx │ ├── Login.tsx │ ├── PasswordRecovery.tsx │ ├── PrivacyTerms.tsx │ ├── ProductInfo.tsx │ ├── Profile.tsx │ └── Verification.tsx ├── redux_toolkit │ ├── hooks.ts │ ├── productSlice.ts │ ├── store.ts │ └── userSlice.ts ├── utils │ ├── cn.ts │ └── wishlist.ts └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /.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 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 |
4 | Slik 5 |
6 | Slik 7 |
8 |

9 | 10 |

hand-picked high quality fashion from top Indian fashion brands

11 | 12 | 13 | ## About the Project 14 | 15 | * Slik is an e-commerce discovery platform where you can find hand-picked high quality fashion from top Indian fashion brands. You can login and save products to your wishlist to visit later. You can use it in your browser or download it as an app on your device as it is also available as a pwa. 16 | 17 | 18 | ## Tech 19 | 20 | * Slik is built with Typescript, React, Redux Toolkit, Tailwind CSS and Aceternity UI. Appwrite is used for Auth and Database. Cloudinary is used for Storage. Hosting is done using Netlify. PWA is done using vite-plugin-pwa. 21 | 22 | 23 | ## Creators 24 | 25 | * [See the team behind Slik on our site](https://beslik.in/contact) 26 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 30 | 35 | 36 | 37 | 43 | 49 | 55 | 56 | 57 | Slik: drip, drippy, drippin' 58 | 59 | 60 |
61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slik", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@reduxjs/toolkit": "^2.2.2", 14 | "appwrite": "^13.0.2", 15 | "clsx": "^2.1.0", 16 | "framer-motion": "^11.0.22", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "react-ga4": "^2.1.0", 20 | "react-hook-form": "^7.51.2", 21 | "react-redux": "^9.1.0", 22 | "react-router-dom": "^6.23.1", 23 | "react-slick": "^0.30.2", 24 | "slick-carousel": "^1.8.1", 25 | "swiper": "^11.1.3", 26 | "tailwind-merge": "^2.2.2", 27 | "vite-plugin-pwa": "^0.19.8" 28 | }, 29 | "devDependencies": { 30 | "@tailwindcss/aspect-ratio": "^0.4.2", 31 | "@tailwindcss/forms": "^0.5.7", 32 | "@types/react": "^18.2.66", 33 | "@types/react-dom": "^18.2.22", 34 | "@types/react-slick": "^0.23.13", 35 | "@typescript-eslint/eslint-plugin": "^7.2.0", 36 | "@typescript-eslint/parser": "^7.2.0", 37 | "@vitejs/plugin-react": "^4.2.1", 38 | "autoprefixer": "^10.4.19", 39 | "eslint": "^8.57.0", 40 | "eslint-plugin-react-hooks": "^4.6.0", 41 | "eslint-plugin-react-refresh": "^0.4.6", 42 | "postcss": "^8.4.38", 43 | "tailwindcss": "^3.4.1", 44 | "typescript": "^5.2.2", 45 | "vite": "^5.2.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakcurious/Slik/50cc4b911707532dfd084b966158e4a5442f520e/public/pwa-192x192.png -------------------------------------------------------------------------------- /public/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakcurious/Slik/50cc4b911707532dfd084b966158e4a5442f520e/public/pwa-512x512.png -------------------------------------------------------------------------------- /public/screenshotnarrow1080x2344a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakcurious/Slik/50cc4b911707532dfd084b966158e4a5442f520e/public/screenshotnarrow1080x2344a.jpg -------------------------------------------------------------------------------- /public/screenshotnarrow1080x2344b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakcurious/Slik/50cc4b911707532dfd084b966158e4a5442f520e/public/screenshotnarrow1080x2344b.jpg -------------------------------------------------------------------------------- /public/screenshotwide1200x630.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakcurious/Slik/50cc4b911707532dfd084b966158e4a5442f520e/public/screenshotwide1200x630.jpg -------------------------------------------------------------------------------- /public/shorticonmen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakcurious/Slik/50cc4b911707532dfd084b966158e4a5442f520e/public/shorticonmen.png -------------------------------------------------------------------------------- /public/shorticonwishlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakcurious/Slik/50cc4b911707532dfd084b966158e4a5442f520e/public/shorticonwishlist.png -------------------------------------------------------------------------------- /public/shorticonwomen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakcurious/Slik/50cc4b911707532dfd084b966158e4a5442f520e/public/shorticonwomen.png -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { 3 | createWishlist, 4 | fetchAllDocuments, 5 | fetchAllLikes, 6 | fetchWishlist, 7 | } from "./appwrite/config"; 8 | import { getCurrentSession } from "./appwrite/auth"; 9 | import ReactGA from "react-ga4"; 10 | import { Outlet, ScrollRestoration } from "react-router-dom"; 11 | function App() { 12 | const gtrackingid = String(import.meta.env.VITE_GOOGLE_ANALYTICS_TRACKING_ID); 13 | useEffect(() => { 14 | ReactGA.initialize(gtrackingid); 15 | ReactGA.send({ hitType: "pageview", page: "/", title: "Home" }); 16 | 17 | const sessionAndWishlist = async () => { 18 | const userdata = await getCurrentSession(); 19 | if (userdata) { 20 | const wishlist = await fetchWishlist(userdata.$id); 21 | if (wishlist) { 22 | } else { 23 | await createWishlist(userdata.$id); 24 | fetchWishlist(userdata.$id); 25 | } 26 | } 27 | }; 28 | sessionAndWishlist(); 29 | fetchAllDocuments(); 30 | fetchAllLikes(); 31 | }, []); 32 | 33 | return ( 34 | <> 35 |
36 | 37 | 38 |
39 | 40 | ); 41 | } 42 | 43 | export default App; 44 | -------------------------------------------------------------------------------- /src/appwrite/auth.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Account, Client, ID } from "appwrite"; 3 | import { store } from "../redux_toolkit/store"; 4 | import confvars from "./confvars"; 5 | import { setUserData } from "../redux_toolkit/userSlice"; 6 | import { createWishlist, fetchWishlist } from "./config"; 7 | 8 | const client = new Client() 9 | .setEndpoint(confvars.appwriteUrl) 10 | .setProject(confvars.appwriteProjectId); 11 | const account = new Account(client); 12 | 13 | export const getCurrentSession = async () => { 14 | try { 15 | const currentUser = await account.get(); 16 | store.dispatch(setUserData(currentUser)); 17 | return currentUser; 18 | } catch (error: any) { 19 | return null; 20 | } 21 | }; 22 | 23 | export const loginWithGoogle = async () => { 24 | try { 25 | account.createOAuth2Session( 26 | "google", 27 | `https://beslik.in/`, 28 | `https://beslik.in/login` 29 | ); 30 | } catch (error: any) { 31 | console.log(error.message); 32 | } 33 | }; 34 | 35 | export const loginWithEmailAndPassword = async ( 36 | email: string, 37 | password: any 38 | ) => { 39 | try { 40 | const currentUser = await account.createEmailSession(email, password); 41 | if (currentUser) { 42 | const userdata = await getCurrentSession(); 43 | if (userdata) { 44 | const wishlist = await fetchWishlist(userdata.$id); 45 | if (wishlist) { 46 | } else { 47 | await createWishlist(userdata.$id); 48 | fetchWishlist(userdata.$id); 49 | } 50 | } 51 | return { success: true, data: currentUser }; 52 | } else { 53 | return { success: false, error: "Login failed" }; 54 | } 55 | } catch (error: any) { 56 | return { success: false, error: error.message }; 57 | } 58 | }; 59 | 60 | export const signup = async (email: string, password: any, name: any) => { 61 | try { 62 | const currentUser = await account.create( 63 | ID.unique(), 64 | email, 65 | password, 66 | name 67 | ); 68 | if (currentUser) { 69 | const { success, error } = await loginWithEmailAndPassword( 70 | email, 71 | password 72 | ); 73 | if (success) { 74 | const verificationResponse = await startVerification(); 75 | if (verificationResponse.success) { 76 | return { 77 | success: true, 78 | message: "Please check your email inbox to verify", 79 | }; 80 | } else { 81 | return { success: false, error: "Failed to send verification email" }; 82 | } 83 | } else { 84 | return { success: false, error }; 85 | } 86 | } else { 87 | return { success: false, error: "Signup failed" }; 88 | } 89 | } catch (error: any) { 90 | return { success: false, error: error.message }; 91 | } 92 | }; 93 | 94 | export const logout = async () => { 95 | try { 96 | await account.deleteSession("current"); 97 | } catch (error: any) { 98 | console.error("Logout failed:", error); 99 | } 100 | }; 101 | 102 | export const startVerification = async () => { 103 | try { 104 | const response = await account.createVerification( 105 | "https://beslik.in/verification" 106 | ); 107 | if (response) { 108 | return { success: true, data: response }; 109 | } else { 110 | return { success: false, error: "Failed to start verification" }; 111 | } 112 | } catch (error: any) { 113 | return { success: false, error: error.message }; 114 | } 115 | }; 116 | 117 | export const updateVerification = async () => { 118 | const urlParams = new URLSearchParams(window.location.search); 119 | const secret = urlParams.get("secret"); 120 | const userId = urlParams.get("userId"); 121 | 122 | try { 123 | const response = await account.updateVerification(userId, secret); 124 | if (response) { 125 | await getCurrentSession(); 126 | return { success: true, data: response }; 127 | } else { 128 | return { success: false, error: "Verification failed" }; 129 | } 130 | } catch (error: any) { 131 | return { success: false, error: error.message }; 132 | } 133 | }; 134 | 135 | export const startPasswordRecovery = async (email: string) => { 136 | try { 137 | const response = await account.createRecovery( 138 | email, 139 | "https://beslik.in/passwordrecovery" 140 | ); 141 | return { success: true, data: response }; 142 | } catch (error: any) { 143 | return { success: false, error: error.message }; 144 | } 145 | }; 146 | 147 | export const updatePasswordRecovery = async (password: any) => { 148 | const urlParams = new URLSearchParams(window.location.search); 149 | const secret = urlParams.get("secret"); 150 | const userId = urlParams.get("userId"); 151 | 152 | try { 153 | const response = await account.updateRecovery( 154 | userId, 155 | secret, 156 | password, 157 | password 158 | ); 159 | return { success: true, data: response }; 160 | } catch (error: any) { 161 | return { success: false, error: error.message }; 162 | } 163 | }; 164 | -------------------------------------------------------------------------------- /src/appwrite/config.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import { Client, Databases, ID, Query } from "appwrite"; 3 | import { Prods } from "../index"; 4 | import confvars from "./confvars"; 5 | import { store } from "../redux_toolkit/store"; 6 | import { getLikes, getProducts } from "../redux_toolkit/productSlice"; 7 | import { setWishlist } from "../redux_toolkit/userSlice"; 8 | 9 | const client = new Client() 10 | .setEndpoint(confvars.appwriteUrl) 11 | .setProject(confvars.appwriteProjectId); 12 | const databases = new Databases(client); 13 | 14 | 15 | 16 | export const fetchWishlist = async (id: any) => { 17 | try { 18 | const response = await databases.listDocuments( 19 | confvars.appwriteDatabaseId, 20 | confvars.appwriteWishlistCollectionId, 21 | [Query.equal("$id", id)] 22 | ); 23 | let wishlist = response.documents[0]?.likes?.reverse(); 24 | store.dispatch(setWishlist(wishlist)); 25 | return wishlist; 26 | } catch (error) { 27 | console.log(`Wishlist fetching failed: ${error}`); 28 | return null; 29 | } 30 | }; 31 | 32 | export const createWishlist = async (id: any) => { 33 | try { 34 | const response = await databases.createDocument( 35 | confvars.appwriteDatabaseId, 36 | confvars.appwriteWishlistCollectionId, 37 | id, 38 | { likes: [] } 39 | ); 40 | return response; 41 | } catch (error) { 42 | console.log(`Appwrite createWishlist failed:: id: ${id}; error: ${error}`); 43 | return null; 44 | } 45 | }; 46 | 47 | export const updateWishlist = async (id: string, updatedData: string[]) => { 48 | try { 49 | const response = await databases.updateDocument( 50 | confvars.appwriteDatabaseId, 51 | confvars.appwriteWishlistCollectionId, 52 | id, 53 | { likes: updatedData } 54 | ); 55 | return response; 56 | } catch (error) { 57 | console.log(`Wishlist update failed: ${error}`); 58 | return null; 59 | } 60 | }; 61 | 62 | export const createProductInAppwrite = async (product: Prods) => { 63 | try { 64 | const response = await databases.createDocument( 65 | confvars.appwriteDatabaseId, 66 | confvars.appwriteProductsCollectionId, 67 | ID.unique(), 68 | product 69 | ); 70 | await createLikes(response.$id) 71 | return response; 72 | } catch (error) { 73 | console.log(`Appwrite createDocument error: ${error}`); 74 | return null; 75 | } 76 | }; 77 | 78 | export const updateProductInAppwrite = async ( 79 | id: string, 80 | updatedData: Partial 81 | ) => { 82 | try { 83 | const response = await databases.updateDocument( 84 | confvars.appwriteDatabaseId, 85 | confvars.appwriteProductsCollectionId, 86 | id, 87 | updatedData 88 | ); 89 | 90 | return response; 91 | } catch (error) { 92 | console.log(`Appwrite updateDocument error: ${error}`); 93 | return null; 94 | } 95 | }; 96 | 97 | export const deleteProductInAppwrite = async (id: string) => { 98 | try { 99 | const response = await databases.deleteDocument( 100 | confvars.appwriteDatabaseId, 101 | confvars.appwriteProductsCollectionId, 102 | id 103 | ); 104 | if(response){ 105 | await deleteLikes(id) 106 | } 107 | return response; 108 | } catch (error) { 109 | console.log(`Appwrite deleteDocument error: ${error}`); 110 | return null; 111 | } 112 | }; 113 | 114 | export const fetchAllDocuments = async () => { 115 | try { 116 | const response = await databases.listDocuments( 117 | confvars.appwriteDatabaseId, 118 | confvars.appwriteProductsCollectionId, 119 | [Query.limit(2000)] 120 | ); 121 | store.dispatch(getProducts(response.documents)); 122 | } catch (error) { 123 | console.log(`Appwrite listDocuments error: ${error}`); 124 | return null; 125 | } 126 | }; 127 | 128 | export const fetchAllLikes = async () => { 129 | try { 130 | const response = await databases.listDocuments( 131 | confvars.appwriteDatabaseId, 132 | confvars.appwriteLikesCollectionId, 133 | [Query.limit(2000)] 134 | ); 135 | let likes = response.documents.map((prod)=> { 136 | let x = []; 137 | prod.wishlist.forEach(element => { 138 | x.push(element.$id) 139 | }); 140 | return {$id:prod.$id, wishlist: x} 141 | }) 142 | store.dispatch(getLikes(likes)) 143 | } catch (error) { 144 | console.log(`Appwrite list Likes error: ${error}`); 145 | return null; 146 | } 147 | }; 148 | 149 | export const createLikes = async (id: any) => { 150 | try { 151 | const response = await databases.createDocument( 152 | confvars.appwriteDatabaseId, 153 | confvars.appwriteLikesCollectionId, 154 | id, 155 | { wishlist: [] } 156 | ); 157 | } catch (error) { 158 | console.log(id); 159 | console.log(`Appwrite createLikes failed:: ${error}`); 160 | return null; 161 | } 162 | }; 163 | 164 | export const deleteLikes = async (id: string) => { 165 | try { 166 | const response = await databases.deleteDocument( 167 | confvars.appwriteDatabaseId, 168 | confvars.appwriteLikesCollectionId, 169 | id 170 | ); 171 | return response; 172 | } catch (error) { 173 | console.log(`Appwrite delete Likes error: ${error}`); 174 | return null; 175 | } 176 | }; -------------------------------------------------------------------------------- /src/appwrite/confvars.ts: -------------------------------------------------------------------------------- 1 | const confvars = { 2 | appwriteUrl: String(import.meta.env.VITE_APPWRITE_URL), 3 | appwriteProjectId: String(import.meta.env.VITE_APPWRITE_PROJECT_ID), 4 | appwriteDatabaseId: String(import.meta.env.VITE_APPWRITE_DATABASE_ID), 5 | appwriteProductsCollectionId: String(import.meta.env.VITE_APPWRITE_PRODUCTS_COLLECTION_ID), 6 | appwriteUsersCollectionId: String(import.meta.env.VITE_APPWRITE_USERS_COLLECTION_ID), 7 | appwriteLikesCollectionId: String(import.meta.env.VITE_APPWRITE_LIKES_COLLECTION_ID), 8 | appwriteWishlistCollectionId: String(import.meta.env.VITE_APPWRITE_WISHLIST_COLLECTION_ID), 9 | }; 10 | 11 | export default confvars; -------------------------------------------------------------------------------- /src/assets/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/heartfill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/signinwithgoogle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/3dCard.tsx: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import React, { 3 | createContext, 4 | useState, 5 | useContext, 6 | useRef, 7 | useEffect, 8 | } from "react"; 9 | import { cn } from "../utils/cn"; 10 | 11 | const MouseEnterContext = createContext< 12 | [boolean, React.Dispatch>] | undefined 13 | >(undefined); 14 | 15 | export const CardContainer = ({ 16 | children, 17 | className, 18 | containerClassName, 19 | }: { 20 | children?: React.ReactNode; 21 | className?: string; 22 | containerClassName?: string; 23 | }) => { 24 | const containerRef = useRef(null); 25 | const [isMouseEntered, setIsMouseEntered] = useState(false); 26 | 27 | const handleMouseMove = (e: React.MouseEvent) => { 28 | if (!containerRef.current) return; 29 | const { left, top, width, height } = 30 | containerRef.current.getBoundingClientRect(); 31 | const x = (e.clientX - left - width / 2) / 25; 32 | const y = (e.clientY - top - height / 2) / 25; 33 | containerRef.current.style.transform = `rotateY(${x}deg) rotateX(${y}deg)`; 34 | }; 35 | 36 | const handleMouseEnter = (e: React.MouseEvent) => { 37 | setIsMouseEntered(true); 38 | if (!containerRef.current) return; 39 | }; 40 | 41 | const handleMouseLeave = (e: React.MouseEvent) => { 42 | if (!containerRef.current) return; 43 | setIsMouseEntered(false); 44 | containerRef.current.style.transform = `rotateY(0deg) rotateX(0deg)`; 45 | }; 46 | 47 | return ( 48 | 49 |
55 |
67 | {children} 68 |
69 |
70 |
71 | ); 72 | }; 73 | 74 | export const CardBody = ({ 75 | children, 76 | className, 77 | }: { 78 | children: React.ReactNode; 79 | className?: string; 80 | }) => { 81 | return ( 82 |
*]:[transform-style:preserve-3d] ${className}` 85 | )} 86 | > 87 | {children} 88 |
89 | ); 90 | }; 91 | 92 | export const CardItem = ({ 93 | as: Tag = "div", 94 | children, 95 | className, 96 | translateX = 0, 97 | translateY = 0, 98 | translateZ = 0, 99 | rotateX = 0, 100 | rotateY = 0, 101 | rotateZ = 0, 102 | ...rest 103 | }: { 104 | as?: React.ElementType; 105 | children: React.ReactNode; 106 | className?: string; 107 | translateX?: number | string; 108 | translateY?: number | string; 109 | translateZ?: number | string; 110 | rotateX?: number | string; 111 | rotateY?: number | string; 112 | rotateZ?: number | string; 113 | [key: string]: any; 114 | }) => { 115 | const ref = useRef(null); 116 | const [isMouseEntered] = useMouseEnter(); 117 | 118 | useEffect(() => { 119 | handleAnimations(); 120 | }, [isMouseEntered]); 121 | 122 | const handleAnimations = () => { 123 | if (!ref.current) return; 124 | if (isMouseEntered) { 125 | ref.current.style.transform = `translateX(${translateX}px) translateY(${translateY}px) translateZ(${translateZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`; 126 | } else { 127 | ref.current.style.transform = `translateX(0px) translateY(0px) translateZ(0px) rotateX(0deg) rotateY(0deg) rotateZ(0deg)`; 128 | } 129 | }; 130 | 131 | return ( 132 | 137 | {children} 138 | 139 | ); 140 | }; 141 | 142 | export const useMouseEnter = () => { 143 | const context = useContext(MouseEnterContext); 144 | if (context === undefined) { 145 | throw new Error("useMouseEnter must be used within a MouseEnterProvider"); 146 | } 147 | return context; 148 | }; 149 | -------------------------------------------------------------------------------- /src/components/AuthModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { startVerification } from "../appwrite/auth"; 3 | 4 | interface ModalProps { 5 | isOpen: boolean; 6 | isAuthenticated: boolean; 7 | isVerified: boolean; 8 | onClose: () => void; 9 | onLogin: () => void; 10 | } 11 | 12 | const Modal: React.FC = ({ 13 | isOpen, 14 | isAuthenticated, 15 | isVerified, 16 | onClose, 17 | onLogin, 18 | }) => { 19 | if (!isOpen) return null; 20 | 21 | const [verificationStarted, setVerificationStarted] = useState(false); 22 | 23 | const handleVerify = async () => { 24 | const verificationResponse = await startVerification(); 25 | if (verificationResponse.success) { 26 | setVerificationStarted(true); 27 | } 28 | }; 29 | 30 | return ( 31 |
32 |
36 |
37 | {!isAuthenticated ? ( 38 | <> 39 |

40 | Please login to use wishlist 41 |

42 | 48 | 49 | ) : !isVerified ? ( 50 | <> 51 |

52 | Please verify your email to use wishlist 53 |

54 | {!verificationStarted ? ( 55 | 61 | ) : ( 62 |

63 | We have sent you a verification email from appwrite. Please 64 | verify your email address using the link in the email 65 |

66 | )} 67 | 68 | ) : null} 69 |
70 |
71 | ); 72 | }; 73 | 74 | export default Modal; 75 | -------------------------------------------------------------------------------- /src/components/BannerSlider.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Slider from "react-slick"; 3 | import "slick-carousel/slick/slick.css"; 4 | import "slick-carousel/slick/slick-theme.css"; 5 | import { useAppSelector } from "../redux_toolkit/hooks"; 6 | import { selectCollections } from "../redux_toolkit/productSlice"; 7 | import { useNavigate } from "react-router-dom"; 8 | 9 | const BannerSlider: React.FC<{ 10 | category: string; 11 | }> = ({ category }) => { 12 | const navigate = useNavigate(); 13 | 14 | let collections = useAppSelector(selectCollections); 15 | 16 | collections = collections.filter( 17 | (collection) => collection.category.toLowerCase() === category 18 | ); 19 | 20 | collections = [...collections].reverse(); 21 | 22 | const navigateToCollection = (category: string, slug: string) => { 23 | navigate(`/${category}/${slug}`); 24 | }; 25 | 26 | const settings = { 27 | dots: true, 28 | infinite: true, 29 | speed: 500, 30 | slidesToShow: 1, 31 | slidesToScroll: 1, 32 | autoplay: true, 33 | autoplaySpeed: 3000, 34 | adaptiveHeight: true, 35 | swipeToSlide: true, 36 | }; 37 | 38 | return ( 39 |
40 |
41 | 42 | {collections.map((collection) => ( 43 |
44 | 46 | navigateToCollection(collection.category, collection.slug) 47 | } 48 | src={collection?.headerImage} 49 | alt={collection?.name} 50 | className="cursor-pointer w-full aspect-[4/1] object-cover rounded-xl" 51 | /> 52 |
53 | ))} 54 |
55 |
56 |
57 | 58 | {collections.map((collection) => ( 59 |
60 | 62 | navigateToCollection(collection.category, collection.slug) 63 | } 64 | src={collection?.cardImage} 65 | alt={collection?.name} 66 | className="w-full aspect-1 rounded-xl" 67 | /> 68 |
69 | ))} 70 |
71 |
72 |
73 | ); 74 | }; 75 | 76 | export default BannerSlider; 77 | -------------------------------------------------------------------------------- /src/components/CollectionCards.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { CardBody, CardContainer, CardItem } from "./3dCard"; 4 | import { useAppSelector } from "../redux_toolkit/hooks"; 5 | import { selectCollections } from "../redux_toolkit/productSlice"; 6 | const CollectionCards: React.FC<{ 7 | category: string; 8 | }> = ({ category }) => { 9 | let collections = useAppSelector(selectCollections); 10 | 11 | const navigate = useNavigate(); 12 | 13 | if (category === "men" || category === "women" || category === "brands") { 14 | collections = collections.filter( 15 | (collection) => collection.category.toLowerCase() == category 16 | ); 17 | } else if (category === "home") { 18 | collections = collections.filter( 19 | (collection) => 20 | collection.category.toLowerCase() == "men" || 21 | collection.category.toLowerCase() == "women" 22 | ); 23 | } 24 | 25 | const navigateToCollection = (category: string, slug: string) => { 26 | navigate(`/${category}/${slug}`); 27 | }; 28 | 29 | return ( 30 | <> 31 |

32 | {category === "brands" ? "Brands" : "Collections"} 33 |

34 |
35 | {collections.map((collection) => ( 36 | 37 | 38 | 42 | 44 | navigateToCollection(collection.category, collection.slug) 45 | } 46 | src={collection.cardImage} 47 | className="cursor-pointer h-full w-full object-cover rounded-lg group-hover/card:shadow-xl" 48 | alt={`${collection.name} ${collection.name}`} 49 | /> 50 | 51 | 52 | 53 | ))} 54 |
55 | 56 | ); 57 | }; 58 | 59 | export default CollectionCards; 60 | -------------------------------------------------------------------------------- /src/components/CreateProducts.tsx: -------------------------------------------------------------------------------- 1 | import { useForm, SubmitHandler } from "react-hook-form"; 2 | import { createProductInAppwrite } from "../appwrite/config"; 3 | import UploadImages from "./UploadImages"; 4 | import { useAppDispatch } from "../redux_toolkit/hooks"; 5 | import { addProduct } from "../redux_toolkit/productSlice"; 6 | import { Prods } from "../index"; 7 | const CreateProducts: React.FC = () => { 8 | const dispatch = useAppDispatch(); 9 | const { register, handleSubmit, reset } = useForm(); 10 | 11 | const slugify = (str: string) => 12 | str 13 | .toLowerCase() 14 | .trim() 15 | .replace(/[^\w\s-]/g, "") 16 | .replace(/[\s_-]+/g, "-") 17 | .replace(/^-+|-+$/g, ""); 18 | 19 | const onSubmit: SubmitHandler = async (data) => { 20 | const createdProduct = await createProductInAppwrite({ 21 | ...data, 22 | price: Number(data.price), 23 | slug: slugify(data.title + " " + data.category + " " + data.type), 24 | //@ts-ignore 25 | images: data.images.split(","), 26 | }); 27 | if (createdProduct) { 28 | dispatch(addProduct(createdProduct)); 29 | reset(); 30 | alert("Product added"); 31 | } else { 32 | alert("Failed to add product"); 33 | } 34 | }; 35 | 36 | return ( 37 | <> 38 | 39 |
40 |
41 |
42 | 43 | 44 | 45 | 51 | 52 | 53 | 54 |
55 | 60 |
61 |
62 |
63 | 64 | ); 65 | }; 66 | 67 | export default CreateProducts; 68 | -------------------------------------------------------------------------------- /src/components/DeleteProducts.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | import { deleteProductInAppwrite } from "../appwrite/config"; 4 | import { useAppDispatch, useAppSelector } from "../redux_toolkit/hooks"; 5 | import { selectProducts } from "../redux_toolkit/productSlice"; 6 | import { deleteProduct } from "../redux_toolkit/productSlice"; 7 | import { Prods } from "../index"; 8 | 9 | const DeleteProducts: React.FC = () => { 10 | const [id, setId] = useState(""); 11 | const dispatch = useAppDispatch(); 12 | const [product, setProduct] = useState(null); 13 | const products = useAppSelector(selectProducts); 14 | 15 | const getProductDetails = () => { 16 | const foundProduct = products.find((product) => product.$id === id); 17 | //@ts-ignore 18 | setProduct(foundProduct); 19 | }; 20 | const handleDeleteProduct = async () => { 21 | const isDeleted = await deleteProductInAppwrite(id); 22 | if (isDeleted) { 23 | dispatch(deleteProduct(id)); 24 | setId(""); 25 | alert("Product deleted successfully"); 26 | setProduct(null); 27 | } else { 28 | alert("Failed to delete product"); 29 | } 30 | }; 31 | 32 | return ( 33 | <> 34 |
35 | setId(e.target.value)} 40 | /> 41 | 47 | 48 | {product && ( 49 | <> 50 |
51 |

{product.$id}

52 |

{product.brand}

53 |

{product.title}

54 |

{product.type}

55 |

{product.category}

56 |

₹{Number(product.price)}

57 |
58 | 59 | 65 | 66 | )} 67 |
68 | 69 | ); 70 | }; 71 | 72 | export default DeleteProducts; 73 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import { Link, useRouteError } from "react-router-dom"; 2 | 3 | const ErrorBoundary = () => { 4 | const error: any = useRouteError(); 5 | 6 | return ( 7 |
8 | Slik 13 |

Oops, an error!

14 |

15 | don't worry, just reload/refresh the page. 16 |

17 |

18 | If you still see the error after refreshing multiple times, or if this 19 | error is very frequent, please let us know ASAP via email. Just take a 20 | screenshot of this page and send it to{" "} 21 | 22 | rakcurious@gmail.com 23 | 24 |

25 |

Error text:

26 |

27 | {error.statusText || error.message} 28 |

29 |
30 | 31 | 34 | 35 | 36 | 39 | 40 | 41 | 44 | 45 |
46 |
47 | ); 48 | }; 49 | 50 | export default ErrorBoundary; 51 | -------------------------------------------------------------------------------- /src/components/FloatingMenu.tsx: -------------------------------------------------------------------------------- 1 | import { NavLink, useNavigate } from "react-router-dom"; 2 | import { logout } from "../appwrite/auth"; 3 | import { useAppDispatch } from "../redux_toolkit/hooks"; 4 | import { setUserData } from "../redux_toolkit/userSlice"; 5 | 6 | const FloatingMenu: React.FC<{ 7 | userdata: any; 8 | isOpen: boolean; 9 | onClose: () => void; 10 | }> = ({ userdata, isOpen, onClose }) => { 11 | const dispatch = useAppDispatch(); 12 | const navigate = useNavigate(); 13 | 14 | const handleLogout = async () => { 15 | await logout(); 16 | dispatch(setUserData(null)); 17 | navigate("/"); 18 | }; 19 | 20 | return ( 21 |
22 |
28 |
33 |
34 |
35 |

{userdata?.name}

36 |

37 | {userdata?.email} 38 |

39 |
40 | 60 |
61 | {userdata && ( 62 | 68 | )} 69 | 128 |
129 |
130 | ); 131 | }; 132 | 133 | export default FloatingMenu; 134 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | const Footer = () => { 3 | return ( 4 | <> 5 |
6 |
7 | Slik Logo 12 |
13 |
14 | 15 | Privacy Policy & Terms 16 | 17 | 18 | Team & Contact 19 | 20 |
21 |
22 | 23 | ); 24 | }; 25 | 26 | export default Footer; 27 | -------------------------------------------------------------------------------- /src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { NavLink, useNavigate } from "react-router-dom"; 2 | import { useEffect, useState } from "react"; 3 | import { useAppSelector } from "../redux_toolkit/hooks"; 4 | import { selectUserData } from "../redux_toolkit/userSlice"; 5 | import FloatingMenu from "./FloatingMenu"; 6 | 7 | function Navbar() { 8 | const [isMenuOpen, setIsMenuOpen] = useState(false); 9 | 10 | const navigate = useNavigate(); 11 | const userdata = useAppSelector(selectUserData); 12 | 13 | const toggleMenu = () => { 14 | setIsMenuOpen(!isMenuOpen); 15 | }; 16 | 17 | const [isVisible, setIsVisible] = useState(true); 18 | const [prevScrollPos, setPrevScrollPos] = useState(window.scrollY); 19 | 20 | useEffect(() => { 21 | const handleScroll = () => { 22 | const currentScrollPos = window.scrollY; 23 | const isScrollingDown = currentScrollPos > prevScrollPos; 24 | 25 | setIsVisible(currentScrollPos === 0 || !isScrollingDown); 26 | setPrevScrollPos(currentScrollPos); 27 | }; 28 | 29 | window.addEventListener('scroll', handleScroll); 30 | 31 | return () => { 32 | window.removeEventListener('scroll', handleScroll); 33 | }; 34 | }, [prevScrollPos]); 35 | 36 | return ( 37 | <> 38 | 116 | 121 | 122 | ); 123 | } 124 | 125 | export default Navbar; 126 | -------------------------------------------------------------------------------- /src/components/ProductCard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from "react"; 2 | import { CardBody, CardContainer, CardItem } from "./3dCard"; 3 | import Modal from "./AuthModal"; 4 | import { Prods } from "../index"; 5 | import { 6 | selectUserData, 7 | selectWishlist, 8 | selectWishlistIds, 9 | } from "../redux_toolkit/userSlice"; 10 | import { selectLikes, selectProducts } from "../redux_toolkit/productSlice"; 11 | import { useAppSelector } from "../redux_toolkit/hooks"; 12 | import heartfill from "../assets/heartfill.svg"; 13 | import heart from "../assets/heart.svg"; 14 | import { Link, useNavigate } from "react-router-dom"; 15 | import { handleWishlistUpdate } from "../utils/wishlist"; 16 | 17 | const ProductCard: React.FC<{ product: Prods }> = ({ product }) => { 18 | const navigate = useNavigate(); 19 | const userdata = useAppSelector(selectUserData); 20 | const products = useAppSelector(selectProducts); 21 | const wishlist = useAppSelector(selectWishlist); 22 | const wishIds = useAppSelector(selectWishlistIds); 23 | let likeList = useAppSelector(selectLikes); 24 | let likes; 25 | if (likeList.length > 0) { 26 | likes = likeList?.find((pro) => pro.$id === product.$id).wishlist?.length; 27 | } 28 | const [showModal, setShowModal] = useState(false); 29 | const isAuthenticated = !!userdata; 30 | const isVerified = userdata?.emailVerification || false; 31 | const [isLoaded, setIsLoaded] = useState(false); 32 | const imageRef = useRef(null); 33 | 34 | const handleIntersection = useCallback( 35 | (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { 36 | entries.forEach((entry) => { 37 | if (entry.isIntersecting) { 38 | setIsLoaded(true); 39 | observer.unobserve(entry.target); 40 | } 41 | }); 42 | }, 43 | [] 44 | ); 45 | 46 | useEffect(() => { 47 | const observer = new IntersectionObserver(handleIntersection, { 48 | rootMargin: "600px", 49 | }); 50 | 51 | if (imageRef.current) { 52 | observer.observe(imageRef.current); 53 | } 54 | 55 | return () => { 56 | if (imageRef.current) { 57 | observer.unobserve(imageRef.current); 58 | } 59 | }; 60 | }, [handleIntersection]); 61 | 62 | return ( 63 | <> 64 | 65 | 66 | 70 | 71 | {!isLoaded && ( 72 |
73 | )} 74 | {product.title} 80 | 81 |
82 |
83 | 87 |
88 | {product.title} 89 |
90 |
91 | {product.brand} 92 |
93 |
94 | {`₹${product.price.toLocaleString("en-IN")}`} 95 |
96 |
97 | 102 | heart icon 105 | handleWishlistUpdate( 106 | wishIds, 107 | wishlist, 108 | userdata, 109 | product.$id, 110 | products, 111 | likeList, 112 | setShowModal 113 | ) 114 | } 115 | src={wishIds?.includes(product?.$id) ? heartfill : heart} 116 | className="h-6 w-6 lg:h-8 lg:w-8 2xl:h-10 2xl:w-10 cursor-pointer" 117 | /> 118 |
119 | {likes>0 ? likes : ''} 120 |
121 |
122 |
123 |
124 |
125 | setShowModal(false)} 130 | onLogin={() => { 131 | navigate("/login"); 132 | setShowModal(false); 133 | }} 134 | /> 135 | 136 | ); 137 | }; 138 | 139 | export default ProductCard; 140 | -------------------------------------------------------------------------------- /src/components/ProductCards.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Prods } from "../index"; 3 | import { useAppSelector } from "../redux_toolkit/hooks.ts"; 4 | import { selectLikes, selectProducts } from "../redux_toolkit/productSlice.ts"; 5 | import ProductCard from "./ProductCard.tsx"; 6 | 7 | const ProductCards: React.FC<{ category: string; collection: string }> = ({ 8 | category, 9 | collection, 10 | }) => { 11 | const [sort, setSort] = useState("new"); 12 | 13 | let products: Prods[] = useAppSelector(selectProducts); 14 | let likeList = useAppSelector(selectLikes); 15 | 16 | if (category === "men" || category == "women") { 17 | products = products.filter( 18 | (product) => 19 | product.category === category || product.category === "unisex" 20 | ); 21 | } else if (category == "brands") { 22 | products = products.filter( 23 | (product) => product.brand.toLowerCase() === collection.toLowerCase() 24 | ); 25 | } 26 | 27 | if (category !== collection && category !== "brands") { 28 | products = products.filter( 29 | (product) => product.type.toLowerCase() == collection.toLowerCase() 30 | ); 31 | } 32 | 33 | const filteredLikes = likeList?.filter((like: any) => 34 | products.some((product) => product.$id === like.$id) 35 | ); 36 | 37 | if (filteredLikes.length > 0) { 38 | filteredLikes?.sort( 39 | (a: any, b: any) => b.wishlist.length - a.wishlist.length 40 | ); 41 | } 42 | 43 | let prd = [...products]; 44 | 45 | const sortedByLikes = filteredLikes?.map((prod: any) => 46 | products?.find((pro) => pro.$id === prod.$id) 47 | ); 48 | const sortedByPriceHighToLow = [...prd].sort((a, b) => b.price - a.price); 49 | const sortedByPriceLowToHigh = [...prd].sort((a, b) => a.price - b.price); 50 | 51 | if (sort === "likes") { 52 | if (filteredLikes.length > 0) { 53 | //@ts-ignore 54 | products = sortedByLikes; 55 | } 56 | } else if (sort === "pricehigh") { 57 | products = sortedByPriceHighToLow; 58 | } else if (sort === "pricelow") { 59 | products = sortedByPriceLowToHigh; 60 | } else if (sort === "new") { 61 | products = [...products].reverse(); 62 | } 63 | return ( 64 | <> 65 |

66 | {category === collection 67 | ? "ALL PRODUCTS" 68 | : category === "brands" 69 | ? collection?.toUpperCase() 70 | : `${category}'s ${collection}`} 71 |

72 | {category !== collection && ( 73 |
74 |

Sort by

75 |
76 |

setSort("likes")} 78 | className={ 79 | sort === "likes" 80 | ? "ring-1 ring-violet-300" 81 | : "hover:ring-1 hover:ring-violet-300" 82 | } 83 | > 84 | Love 85 |

86 |

setSort("pricehigh")} 88 | className={ 89 | sort === "pricehigh" 90 | ? "ring-1 ring-violet-300" 91 | : "hover:ring-1 hover:ring-violet-300" 92 | } 93 | > 94 | Price{" "} 95 | 107 | {" "} 108 | 109 | 110 |

111 |

setSort("pricelow")} 113 | className={ 114 | sort === "pricelow" 115 | ? "ring-1 ring-violet-300" 116 | : "hover:ring-1 hover:ring-violet-300" 117 | } 118 | > 119 | Price{" "} 120 | 132 | 133 | 134 |

135 |

setSort("new")} 137 | className={ 138 | sort === "new" 139 | ? "ring-1 ring-violet-300" 140 | : "hover:ring-1 hover:ring-violet-300" 141 | } 142 | > 143 | New 144 |

145 |
146 |
147 | )} 148 | {products.length !== 0 ? ( 149 |
150 | {products.map((product: Prods) => ( 151 | 152 | ))} 153 |
154 | ) : ( 155 |
156 | Slik 161 |
162 | LOADING 163 |
164 |
165 | )} 166 | 167 | ); 168 | }; 169 | 170 | export default ProductCards; 171 | -------------------------------------------------------------------------------- /src/components/UpdateProducts.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { useForm, SubmitHandler } from "react-hook-form"; 3 | import { Prods } from "../index"; 4 | import UploadImages from "./UploadImages"; 5 | import { updateProductInAppwrite } from "../appwrite/config"; 6 | import { useAppDispatch, useAppSelector } from "../redux_toolkit/hooks"; 7 | import { updateProduct } from "../redux_toolkit/productSlice"; 8 | import { useEffect, useState } from "react"; 9 | 10 | const UpdateProducts: React.FC = () => { 11 | type ProductsWithoutMeta = Omit< 12 | Prods, 13 | | "$databaseId" 14 | | "$collectionId" 15 | | "$id" 16 | | "$permissions" 17 | | "$createdAt" 18 | | "$updatedAt" 19 | >; 20 | 21 | const slugify = (str: string) => 22 | str 23 | .toLowerCase() 24 | .trim() 25 | .replace(/[^\w\s-]/g, "") 26 | .replace(/[\s_-]+/g, "-") 27 | .replace(/^-+|-+$/g, ""); 28 | 29 | const [product, setProduct] = useState | null>( 30 | null 31 | ); 32 | const [id, setId] = useState(""); 33 | const dispatch = useAppDispatch(); 34 | const products = useAppSelector((state) => state.products.products); 35 | 36 | const { 37 | register, 38 | handleSubmit, 39 | reset, 40 | setValue, 41 | formState: { isDirty }, 42 | } = useForm({ 43 | defaultValues: product || {}, 44 | }); 45 | 46 | const getProductDetails = () => { 47 | const foundProduct = products.find((product) => product.$id === id); 48 | if (foundProduct) { 49 | const updatedProduct: Partial = {}; 50 | for (const key in foundProduct) { 51 | if ( 52 | key !== "$databaseId" && 53 | key !== "$collectionId" && 54 | key !== "$id" && 55 | key !== "$permissions" && 56 | key !== "$createdAt" && 57 | key !== "$updatedAt" 58 | ) { 59 | updatedProduct[key] = foundProduct[key as keyof Prods]; 60 | } 61 | } 62 | setProduct(updatedProduct); 63 | } else { 64 | setProduct(null); 65 | } 66 | }; 67 | 68 | useEffect(() => { 69 | if (product) { 70 | Object.keys(product).forEach((key) => { 71 | setValue(key as keyof ProductsWithoutMeta, product[key]); 72 | }); 73 | } 74 | }, [product, setValue]); 75 | 76 | const onSubmit: SubmitHandler = async (data) => { 77 | const updatedProduct = await updateProductInAppwrite(id, { 78 | ...data, 79 | price: Number(data.price), 80 | slug: slugify(data.title + " " + data.category + " " + data.type), 81 | images: Array.isArray(data.images) ? data.images : data.images.split(","), 82 | }); 83 | if (updatedProduct) { 84 | dispatch(updateProduct(updatedProduct)); 85 | reset(); 86 | setId(""); 87 | alert("Product updated"); 88 | } else { 89 | alert("Failed to update product"); 90 | } 91 | }; 92 | 93 | return ( 94 | <> 95 | 96 |
97 | setId(e.target.value)} 102 | /> 103 | 109 |
110 | {product && ( 111 | <> 112 |
113 |
114 |
115 | 116 | 117 | 118 | 124 | 125 | 130 | 135 |
136 | 142 |
143 |
144 | 145 | )} 146 |
147 | 148 | ); 149 | }; 150 | 151 | export default UpdateProducts; 152 | -------------------------------------------------------------------------------- /src/components/UploadImages.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { useCallback, useRef, useState } from "react"; 3 | 4 | const UploadImages: React.FC = () => { 5 | const [imageUrls, setImageUrls] = useState([]); 6 | const picsRef = useRef(); 7 | const urlRef = useRef(null); 8 | const [share, setShare] = useState(false); 9 | 10 | const copyUrlToClipboard = useCallback( 11 | (id: number) => { 12 | window.navigator.clipboard.writeText(imageUrls[id]); 13 | }, 14 | [imageUrls] 15 | ); 16 | const copyAll = useCallback(() => { 17 | window.navigator.clipboard.writeText(imageUrls.join(",")); 18 | setShare(true); 19 | setTimeout(() => { 20 | setShare(false); 21 | setImageUrls([]); 22 | }, 300); 23 | }, [imageUrls]); 24 | 25 | function submitImages() { 26 | const fileList = picsRef.current.files; 27 | const filesArray = Array.from(fileList); 28 | 29 | filesArray.forEach((file) => { 30 | const data = new FormData(); 31 | data.append("file", file); 32 | data.append("upload_preset", "beslik"); 33 | data.append("folder", "slik/products"); 34 | 35 | fetch( 36 | `https://api.cloudinary.com/v1_1/dnhz5reqf/image/upload?upload_preset=beslik&log=true`, 37 | { 38 | method: "post", 39 | body: data, 40 | } 41 | ) 42 | .then((res) => res.json()) 43 | .then((data) => { 44 | const url = data.secure_url; 45 | setImageUrls((prev) => [...prev, url]); 46 | }) 47 | .catch((error) => console.log(error)); 48 | }); 49 | } 50 | 51 | return ( 52 | <> 53 |
54 | 55 | 61 | 62 |
63 |
64 | {imageUrls && 65 | imageUrls.map((image, index) => ( 66 |
70 |

71 | {index + 1}. 72 |

{" "} 73 | 79 | 85 |
86 | ))} 87 |
88 | {imageUrls.length > 0 && ( 89 | 99 | )} 100 |
101 |
102 |
103 | 104 | ); 105 | }; 106 | 107 | export default UploadImages; 108 | -------------------------------------------------------------------------------- /src/components/WishlistCards.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import { useScroll, useTransform, motion } from "framer-motion"; 3 | import { Prods } from "../index"; 4 | import { CardBody, CardContainer, CardItem } from "./3dCard"; 5 | import { cn } from "../utils/cn"; 6 | import { logout } from "../appwrite/auth"; 7 | import { setUserData, setWishlist } from "../redux_toolkit/userSlice"; 8 | import { useAppDispatch } from "../redux_toolkit/hooks"; 9 | import { Link, useNavigate } from "react-router-dom"; 10 | 11 | const WishlistCards: React.FC<{ products: Prods[]; userdata: any }> = ({ 12 | products, 13 | userdata, 14 | }) => { 15 | const dispatch = useAppDispatch(); 16 | 17 | const gridRef = useRef(null); 18 | const navigate = useNavigate(); 19 | 20 | const { scrollYProgress } = useScroll({ 21 | container: gridRef, 22 | offset: ["start start", "end start"], 23 | }); 24 | 25 | const translateFirst = useTransform(scrollYProgress, [0, 1], [0, -200]); 26 | const translateSecond = useTransform(scrollYProgress, [0, 1], [0, 200]); 27 | 28 | const isTwoColumns = window.innerWidth < 1024; 29 | const numColumns = isTwoColumns ? 2 : 4; 30 | 31 | const columnArrays = Array.from({ length: numColumns }, (_, i) => 32 | products.filter((_, index) => index % numColumns === i) 33 | ); 34 | 35 | const handleLogout = async () => { 36 | await logout(); 37 | dispatch(setUserData(null)); 38 | dispatch(setWishlist([])); 39 | navigate("/"); 40 | }; 41 | 42 | return ( 43 |
49 |
50 |
51 |

{userdata.name}

52 |

53 | {userdata.email} 54 |

55 |
56 |

57 | WISHLIST 58 |

59 |
60 | 66 |
67 |
68 |
72 | {columnArrays?.map((columnProducts, columnIndex) => ( 73 |
77 | {columnProducts?.map((product, idx) => ( 78 | 84 | 88 | 89 | 93 | 94 | {product?.title} 100 | 101 | 102 | 106 |
107 | {product?.title} 108 |
109 |
110 | {product?.brand} 111 |
112 |
113 | {product 114 | ? `₹${product?.price.toLocaleString("en-IN")}` 115 | : ""} 116 |
117 |
118 |
119 |
120 |
121 | ))} 122 |
123 | ))} 124 |
125 |
126 | ); 127 | }; 128 | 129 | export default WishlistCards; 130 | -------------------------------------------------------------------------------- /src/components/WrongPage.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | import { useState } from "react"; 3 | 4 | const Error: React.FC = () => { 5 | const [load, setLoad] = useState(false); 6 | 7 | const loadingtime = () => { 8 | setTimeout(() => { 9 | setLoad(true); 10 | }, 5000); 11 | }; 12 | loadingtime(); 13 | const navigate = useNavigate(); 14 | 15 | return ( 16 | <> 17 | { 18 |
19 | Slik 24 | {!load &&
25 | LOADING 26 |
} 27 | {load && ( 28 | <> 29 |

30 | Oops, this page is empty! 31 |

32 |

33 | Go back and continue your manifesting session 34 |

35 |
36 | 42 | 48 |
49 | 50 | )} 51 |
52 | } 53 | 54 | ); 55 | }; 56 | 57 | export default Error; 58 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap"); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | html, 8 | body { 9 | height: 100%; 10 | width: 100%; 11 | margin: 0; 12 | padding: 0; 13 | overflow-x: clip; 14 | background-size: 100%; 15 | position: sticky; 16 | } 17 | 18 | .scrollbar-none { 19 | scrollbar-width: none; /* Firefox */ 20 | -ms-overflow-style: none; /* Internet Explorer 10+ */ 21 | } 22 | .scrollbar-none::-webkit-scrollbar { 23 | display: none; /* Chrome, Safari, and Opera */ 24 | } 25 | 26 | main { 27 | flex-grow: 1; 28 | } 29 | 30 | :root:has(.no-scroll) { 31 | overflow: hidden; 32 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export interface Prods { 2 | $collectionId?: string; 3 | $createdAt?: string; 4 | $databaseId?: string; 5 | $id?: string; 6 | $permissions?: string[]; 7 | $updatedAt?: string; 8 | brand: string; 9 | category: string; 10 | images: string[]; 11 | price: number; 12 | target: string; 13 | title: string; 14 | type: string; 15 | slug: string; 16 | } 17 | 18 | export interface Collection { 19 | $id?: string; 20 | name: string; //Collection Name eg: Shirts or Aristobrat 21 | category: string; // BrandName or Men or Women 22 | cardImage: string; //Image on the card on homepage or women's page etc 23 | headerImage: string; //Image at the top of the collection page 24 | slug: string; //slug of the name of collection 25 | } 26 | 27 | export const collections = [ 28 | { 29 | "$id": "6624d34d9c3da32a2b37", 30 | "name": "Freakins", 31 | "category": "brands", 32 | "slug": "freakins", 33 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714036382/slik/misc/cq0yrhjujijszsrj85uv.webp", 34 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713689401/slik/misc/p7wjxdv4qqetd8ntsdbq.webp" 35 | }, 36 | { 37 | "$id": "6628b3bce0c3534210ac", 38 | "name": "House of Stori", 39 | "category": "brands", 40 | "slug": "house-of-stori", 41 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713943467/slik/misc/fryaoo9ihnuzuzazqu6a.webp", 42 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713943475/slik/misc/haqlxojujnjmba1kanhe.webp" 43 | }, 44 | { 45 | "$id": "662dac99bf1606c1b6a5", 46 | "name": "Dresses", 47 | "category": "women", 48 | "slug": "dresses", 49 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714269312/slik/misc/bu5tngdhmkb2qnwnnor6.webp", 50 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714269323/slik/misc/aq1x6im9tbcthsvgvwu9.webp" 51 | }, 52 | { 53 | "$id": "6640b2ca0036c3057b18", 54 | "name": "Urbanic", 55 | "category": "brands", 56 | "slug": "urbanic", 57 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1715515961/slik/misc/urbaniclogo_f10bji.webp", 58 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1715515962/slik/misc/urbanicheader_nzfhz3.webp" 59 | }, 60 | { 61 | "$id": "6624a1721766dc83d0f4", 62 | "name": "The Souled Store", 63 | "category": "brands", 64 | "slug": "the-souled-store", 65 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713679748/slik/misc/wkzbskgshamrzlahy2w6.webp", 66 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713676643/slik/misc/wilt0wvv7r1gmkmo0ysc.webp" 67 | }, 68 | { 69 | "$id": "6640be46001390de8677", 70 | "name": "Virgio", 71 | "category": "brands", 72 | "slug": "virgio", 73 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1715518976/slik/misc/virgiologo_mtynu2.webp", 74 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1715518976/slik/misc/virgioheader_so2ndj.webp" 75 | }, 76 | { 77 | "$id": "662da72bf29f6f8387bc", 78 | "name": "Shirts", 79 | "category": "men", 80 | "slug": "shirts", 81 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714267916/slik/misc/tu1evdplaja2mrliscbq.webp", 82 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714267927/slik/misc/udabglgiwqgubdemhtuq.webp" 83 | }, 84 | { 85 | "$id": "662d978d36ac53fd3db1", 86 | "name": "co-ords", 87 | "category": "women", 88 | "slug": "co-ords", 89 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714733362/slik/misc/rqrqibilpgcv7yeyw8dg.webp", 90 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714264941/slik/misc/z9uggv7vy3fqi6m2gafp.webp" 91 | }, 92 | { 93 | "$id": "6634dae6bf6f412e8716", 94 | "name": "oversized t-shirts", 95 | "category": "men", 96 | "slug": "oversized-t-shirts", 97 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714739923/slik/misc/tpzuqx2dem4qguir6c61.webp", 98 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714739935/slik/misc/ndkku8bldndcnfrmbzxr.webp" 99 | }, 100 | { 101 | "$id": "663520b43247f8b06d73", 102 | "name": "shirts", 103 | "category": "women", 104 | "slug": "shirts", 105 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714757788/slik/misc/b0fhktyqhbg2nzfmb8cc.webp", 106 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714757803/slik/misc/ubyydjdaiajm9mi717jy.webp" 107 | }, 108 | { 109 | "$id": "66202fbb2d9919fb64c4", 110 | "name": "pants", 111 | "category": "men", 112 | "slug": "pants", 113 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714271511/slik/misc/an39c9csnecxxj8zykzb.webp", 114 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714271528/slik/misc/f7smivo3o5lazlcdqiud.webp" 115 | }, 116 | { 117 | "$id": "66278af00823a882531f", 118 | "name": "Siesta o'Clock", 119 | "category": "brands", 120 | "slug": "siesta-oclock", 121 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713867400/slik/misc/xwshqpajy0gjpzxdovqh.webp", 122 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713867407/slik/misc/cmnfvciqm7r5y4cjy8ws.webp" 123 | }, 124 | { 125 | "$id": "66201c3de127c5c61a22", 126 | "name": "oversized t-shirts", 127 | "category": "women", 128 | "slug": "oversized-t-shirts", 129 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714730514/slik/misc/oversizedtshirtscard_gohrx4.webp", 130 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714276161/slik/misc/hbov1hpuxo8d6ukxc0rp.webp" 131 | }, 132 | { 133 | "$id": "66288a96f0dcf046e7b3", 134 | "name": "Bombay Shirt Company", 135 | "category": "brands", 136 | "slug": "bombay-shirt-company", 137 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713932923/slik/misc/irzznjqz49x8u24m2023.webp", 138 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713932934/slik/misc/o954pzfjnmxnopirrtow.webp" 139 | }, 140 | { 141 | "$id": "6624cf9e8624562faf52", 142 | "name": "Aristobrat", 143 | "category": "brands", 144 | "slug": "aristobrat", 145 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714036411/slik/misc/sxq7nyzrpnxakhdd6ggn.webp", 146 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713688473/slik/misc/cokheyh8h66d1xthh6k9.webp" 147 | }, 148 | { 149 | "$id": "6635157fd541ef1bfacc", 150 | "name": "t-shirts", 151 | "category": "men", 152 | "slug": "t-shirts", 153 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714754917/slik/misc/iehmrg2yses3v6gbmmcf.webp", 154 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714754928/slik/misc/bl6xd6j5ptdwz5nrg0iw.webp" 155 | }, 156 | { 157 | "$id": "6624e05ab0d21a07b2a9", 158 | "name": "Bonkers Corner", 159 | "category": "brands", 160 | "slug": "bonkers-corner", 161 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714036300/slik/misc/wjaqvdzwhjkt5t6jbmk4.webp", 162 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713692707/slik/misc/rdfgeuft5tbnjauxswak.webp" 163 | }, 164 | { 165 | "$id": "6635213bbc5f0fef15ff", 166 | "name": "pants", 167 | "category": "women", 168 | "slug": "pants", 169 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714757927/slik/misc/wzrrtwmxaqymwgsosbex.webp", 170 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714757940/slik/misc/aarva1ywdtjxuufda3hv.webp" 171 | }, 172 | { 173 | "$id": "6628c27b64ba343cba20", 174 | "name": "Andamen", 175 | "category": "brands", 176 | "slug": "andamen", 177 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713947243/slik/misc/ojdxdjzsmixifkxtkpjh.webp", 178 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713947251/slik/misc/wivbj7w5ru4ugex0xnso.webp" 179 | }, 180 | { 181 | "$id": "663515e044c51432b5fe", 182 | "name": "co-ords", 183 | "category": "men", 184 | "slug": "co-ords", 185 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714755018/slik/misc/xcuv9oyan5oiyg0jpzmt.webp", 186 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714755028/slik/misc/eykdzttlflmxs44c9ici.webp" 187 | }, 188 | { 189 | "$id": "6624cf6f0438079ec789", 190 | "name": "Kaira by Nikita", 191 | "category": "brands", 192 | "slug": "kaira-by-nikita", 193 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714036493/slik/misc/xs7e66vh4xjfn060ajw4.webp", 194 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713688407/slik/misc/b6qdfakofcipgfzwtnpc.webp" 195 | }, 196 | { 197 | "$id": "6635218020f8f2c74a24", 198 | "name": "tops", 199 | "category": "women", 200 | "slug": "tops", 201 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714757996/slik/misc/dusqwglnegwds324j8sx.webp", 202 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714758006/slik/misc/aj1hyluccnddqkjmihj9.webp" 203 | }, 204 | { 205 | "$id": "662a8ec5a9ee75d2db41", 206 | "name": "Shasak", 207 | "category": "brands", 208 | "slug": "shasak", 209 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714065064/slik/misc/k6gjnggbnv9x5j9t9nay.webp", 210 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714065076/slik/misc/bcqpyg9mptwnlxi2b4ed.webp" 211 | }, 212 | { 213 | "$id": "66351632bde3c51b42a4", 214 | "name": "shorts", 215 | "category": "men", 216 | "slug": "shorts", 217 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714755071/slik/misc/eam9jwnx3svv3us9szdf.webp", 218 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714755113/slik/misc/njwbkeuqxjgdtsfpggmd.webp" 219 | }, 220 | { 221 | "$id": "6627b4944cbe0e5a68bc", 222 | "name": "Label Tasos", 223 | "category": "brands", 224 | "slug": "label-tasos", 225 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713878145/slik/misc/i0oidhqryrcslldkgirb.webp", 226 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713878157/slik/misc/pga0vwwzekdbrpfpg9rb.webp" 227 | }, 228 | { 229 | "$id": "663521b60c3772e33bd9", 230 | "name": "shorts", 231 | "category": "women", 232 | "slug": "shorts", 233 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714758050/slik/misc/qthbqhth0rvvjwhniqdf.webp", 234 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714758055/slik/misc/ixgq5za0yfa1fatfy9ys.webp" 235 | }, 236 | { 237 | "$id": "6624dbb54df94f06f6d7", 238 | "name": "Adah by Leesha", 239 | "category": "brands", 240 | "slug": "adah-by-leesha", 241 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714036343/slik/misc/kzs27hwtjb1matmmrjcp.webp", 242 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713691559/slik/misc/jawcnfwwf4tcfdgsdodc.webp" 243 | }, 244 | { 245 | "$id": "6634e045925e0a19c9f6", 246 | "name": "Bewakoof", 247 | "category": "brands", 248 | "slug": "bewakoof", 249 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714741298/slik/misc/qgzuocybct2girc0qgfp.webp", 250 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714741309/slik/misc/geasv43jcmmntecl0frk.webp" 251 | }, 252 | { 253 | "$id": "6624c43a6654968dd768", 254 | "name": "Snitch", 255 | "category": "brands", 256 | "slug": "snitch", 257 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714036460/slik/misc/d7tdp9brqrruqqhespuq.webp", 258 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713685552/slik/misc/uhse6xifymoyr6da47vf.webp" 259 | }, 260 | { 261 | "$id": "6624e08a3edd53655050", 262 | "name": "House of Chikankari", 263 | "category": "brands", 264 | "slug": "house-of-chikankari", 265 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714036268/slik/misc/rvqil3c4gosfporurkgb.webp", 266 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713692794/slik/misc/ioy31u9lhyjjd8pl7qkl.webp" 267 | }, 268 | { 269 | "$id": "6627e90d56e941ff0463", 270 | "name": "Botnia", 271 | "category": "brands", 272 | "slug": "botnia", 273 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713891574/slik/misc/mdaywbmfzjomi9jzad92.webp", 274 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713891583/slik/misc/nntyo7qmztdr8to3d6uq.webp" 275 | }, 276 | { 277 | "$id": "6634a74c42fa1257a1a9", 278 | "name": "Burger Bae", 279 | "category": "brands", 280 | "slug": "burger-bae", 281 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714726698/slik/misc/qvml7iqem0zors5wxj2h.webp", 282 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714726722/slik/misc/efmhqpk1znbkhkaahvzm.webp" 283 | }, 284 | { 285 | "$id": "6627db24bd02ce2a2a30", 286 | "name": "AakarTaro", 287 | "category": "brands", 288 | "slug": "aakartaro", 289 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714035988/slik/misc/e0xbmue0y3dtz90zsu3o.webp", 290 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713888014/slik/misc/vjxht6g4vjlemayfkjlq.webp" 291 | }, 292 | { 293 | "$id": "662882e286698539c7e1", 294 | "name": "5feet11", 295 | "category": "brands", 296 | "slug": "5feet11", 297 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713930958/slik/misc/vfji6dik1q1gk5xsuz0z.webp", 298 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1713930966/slik/misc/b1ja2jmgfuatkhq63ird.webp" 299 | }, 300 | { 301 | "$id": "662a4404b8920f05dff6", 302 | "name": "Cottonworld", 303 | "category": "brands", 304 | "slug": "cottonworld", 305 | "cardImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714045918/slik/misc/gbv3c0kdqwo7rfqnj92v.webp", 306 | "headerImage": "https://res.cloudinary.com/dnhz5reqf/image/upload/v1714045928/slik/misc/nipfbyhbjksswohq8iln.webp" 307 | } 308 | ] 309 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client"; 2 | import App from "./App.tsx"; 3 | import "./index.css"; 4 | import { 5 | Route, 6 | RouteObject, 7 | createBrowserRouter, 8 | createRoutesFromElements, 9 | RouterProvider, 10 | } from "react-router-dom"; 11 | import { Provider } from "react-redux"; 12 | import { store } from "./redux_toolkit/store.ts"; 13 | import React from "react"; 14 | import Collection from "./pages/Collection.tsx"; 15 | import ErrorBoundary from "./components/ErrorBoundary.tsx"; 16 | import Home from "./pages/Home.tsx"; 17 | import Cutie from "./pages/Cuties.tsx"; 18 | import Profile from "./pages/Profile.tsx"; 19 | import Admin from "./pages/Admin.tsx"; 20 | import Login from "./pages/Login.tsx"; 21 | import Error404 from "./pages/Error404Page.tsx"; 22 | import Verification from "./pages/Verification.tsx"; 23 | import PasswordRecovery from "./pages/PasswordRecovery.tsx"; 24 | import ProductInfo from "./pages/ProductInfo.tsx"; 25 | import PrivacyTerms from "./pages/PrivacyTerms.tsx"; 26 | import Contact from "./pages/Contact.tsx"; 27 | 28 | const router = createBrowserRouter( 29 | //@ts-ignore 30 | createRoutesFromElements( 31 | } errorElement={}> 32 | } /> 33 | } /> 34 | } /> 35 | } /> 36 | } /> 37 | } /> 38 | } /> 39 | } /> 40 | } /> 41 | } /> 42 | } /> 43 | } /> 44 | 45 | ) 46 | ); 47 | 48 | ReactDOM.createRoot(document.getElementById("root")!).render( 49 | 50 | 51 | 52 | 53 | 54 | ); 55 | -------------------------------------------------------------------------------- /src/pages/Admin.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Prods, Collection } from "../index"; 3 | import { fetchAllDocuments } from "../appwrite/config"; 4 | import CreateProducts from "../components/CreateProducts"; 5 | import UpdateProducts from "../components/UpdateProducts"; 6 | import DeleteProducts from "../components/DeleteProducts"; 7 | import Navbar from "../components/Navbar"; 8 | import { useAppSelector, useAppDispatch } from "../redux_toolkit/hooks"; 9 | import { 10 | selectCollections, 11 | selectProducts, 12 | } from "../redux_toolkit/productSlice"; 13 | import { selectUserData } from "../redux_toolkit/userSlice"; 14 | import Error from "../components/WrongPage"; 15 | 16 | const Admin: React.FC = () => { 17 | const dispatch = useAppDispatch(); 18 | const collections = useAppSelector(selectCollections); 19 | let products = useAppSelector(selectProducts); 20 | const [toggle, setToggle] = useState("create"); 21 | const [topToggle, setTopToggle] = useState("products"); 22 | const userdata = useAppSelector(selectUserData); 23 | 24 | useEffect(() => { 25 | fetchAllDocuments(); 26 | }, [dispatch]); 27 | 28 | if (products) { 29 | products = [...products].reverse(); 30 | } 31 | return ( 32 | <> 33 | {userdata?.$id === "660963212d52965c7a7f" || 34 | userdata?.$id === "6633e3a9e0ec5c0491bb" ? ( 35 | <> 36 | 37 |
38 |
39 |

setTopToggle("products")} 41 | className={ 42 | topToggle === "products" 43 | ? "ring-1 ring-violet-300" 44 | : "hover:ring-1 hover:ring-violet-300" 45 | } 46 | > 47 | Products 48 |

49 |

setTopToggle("collections")} 51 | className={ 52 | topToggle === "collections" 53 | ? "ring-1 ring-violet-300" 54 | : "hover:ring-1 hover:ring-violet-300" 55 | } 56 | > 57 | Collections 58 |

59 |
60 | {topToggle === "products" && ( 61 |
62 |

setToggle("create")} 64 | className={ 65 | toggle === "create" 66 | ? "ring-1 ring-violet-300" 67 | : "hover:ring-1 hover:ring-violet-300" 68 | } 69 | > 70 | Create 71 |

72 |

setToggle("update")} 74 | className={ 75 | toggle === "update" 76 | ? "ring-1 ring-violet-300" 77 | : "hover:ring-1 hover:ring-violet-300" 78 | } 79 | > 80 | Update 81 |

82 |

setToggle("delete")} 84 | className={ 85 | toggle === "delete" 86 | ? "ring-1 ring-violet-300" 87 | : "hover:ring-1 hover:ring-violet-300" 88 | } 89 | > 90 | Delete 91 |

92 |
93 | )} 94 |
95 | {topToggle === "products" && ( 96 | <> 97 | {toggle === "create" && } 98 | {toggle === "update" && } 99 | {toggle === "delete" && } 100 |

PRODUCTS

101 | {products && products.length > 0 ? ( 102 |
103 | {products.map((product: Prods) => ( 104 |
106 | //@ts-ignore 107 | window.navigator.clipboard.writeText(product?.$id) 108 | } 109 | className="cursor-pointer overflow-x-0 w-screen px-4 flex flex-wrap bg-purple-200 *:truncate *:h-auto *:text-center *:p-2 *:mx-2 *:text-clip" 110 | key={product.$id} 111 | > 112 |

{product.$id}

113 |

{product.brand}

114 |

{product.title}

115 |

{product.type}

116 |

{product.category}

117 |

₹{Number(product.price)}

118 |
119 | ))} 120 |
121 | ) : ( 122 |

123 | No products found 124 |

125 | )} 126 | 127 | )} 128 | 129 | {topToggle === "collections" && ( 130 | <> 131 |

132 | COLLECTIONS 133 |

134 | {collections && collections.length > 0 ? ( 135 |
136 | {collections.map((collection: Collection) => ( 137 |
139 | //@ts-ignore 140 | window.navigator.clipboard.writeText(collection?.$id) 141 | } 142 | className="cursor-pointer overflow-x-0 flex flex-wrap bg-purple-200 *:truncate *:h-8 *:w-40 *:text-center *:p-1 *:text-clip" 143 | key={collection.$id} 144 | > 145 |

{collection.$id}

146 |

{collection.name}

147 |

{collection.category}

148 |

{collection.slug}

149 |
150 | ))} 151 |
152 | ) : ( 153 |

No Collections found.

154 | )} 155 | 156 | )} 157 | 158 | ) : ( 159 | <> 160 | 161 | 162 | 163 | )} 164 | 165 | ); 166 | }; 167 | 168 | export default Admin; 169 | -------------------------------------------------------------------------------- /src/pages/Collection.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "react-router-dom"; 2 | import { selectCollections } from "../redux_toolkit/productSlice"; 3 | import { useAppSelector } from "../redux_toolkit/hooks"; 4 | import Navbar from "../components/Navbar"; 5 | import ProductCards from "../components/ProductCards"; 6 | import Error from "../components/WrongPage"; 7 | 8 | const Collection: React.FC = () => { 9 | const collections = useAppSelector(selectCollections); 10 | 11 | const { category, slug } = useParams(); 12 | 13 | const collection = collections.find( 14 | (collection) => collection.slug == slug && collection.category == category 15 | ); 16 | 17 | return ( 18 | <> 19 | 20 | {(category === "men" || category == "women" || category === "brands") && 21 | collection ? ( 22 | <> 23 | 27 | 28 | 29 | ) : ( 30 | 31 | )} 32 | 33 | ); 34 | }; 35 | 36 | export default Collection; 37 | -------------------------------------------------------------------------------- /src/pages/Contact.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import Navbar from "../components/Navbar"; 3 | 4 | const Contact = () => { 5 | const team = [ 6 | { 7 | role: 'Idea and Research', 8 | image: 'https://res.cloudinary.com/dnhz5reqf/image/upload/v1714635700/slik/SlikIdeaAndResearch_hsqk62.webp' 9 | }, 10 | { 11 | role: 'Designer', 12 | image: 'https://res.cloudinary.com/dnhz5reqf/image/upload/v1714635700/slik/SlikDesigner_xlpixd.webp' 13 | }, 14 | { 15 | role: 'Developer', 16 | image: 'https://res.cloudinary.com/dnhz5reqf/image/upload/v1714635700/slik/SlikDeveloper_zqyn2d.webp' 17 | }, 18 | { 19 | role: 'Vibes', 20 | image: 'https://res.cloudinary.com/dnhz5reqf/image/upload/v1714719598/slik/SlikVibes_ouahlm.webp' 21 | }, 22 | { 23 | role: 'Catalogue Curation (Women)', 24 | image: 'https://res.cloudinary.com/dnhz5reqf/image/upload/v1714635700/slik/SlikCatalogueCurationWomen_hrah6r.webp' 25 | }, 26 | { 27 | role: 'Catalogue Curation (Men)', 28 | image: 'https://res.cloudinary.com/dnhz5reqf/image/upload/v1714635700/slik/SlikCatalogueCurationMen_ow2fto.webp' 29 | }, 30 | { 31 | role: 'Customer Support', 32 | image: 'https://res.cloudinary.com/dnhz5reqf/image/upload/v1714719598/slik/SlikCustomerSupport_uvweoq.webp' 33 | }, 34 | { 35 | role: 'In-house Therapist', 36 | image: 'https://res.cloudinary.com/dnhz5reqf/image/upload/v1714635700/slik/SlikTherapist_b9bcvc.webp' 37 | }, 38 | 39 | ] 40 | 41 | useEffect(() => { 42 | document.body.scrollTo(0, 0); 43 | }); 44 | 45 | return ( 46 | <> 47 | 48 |

49 | Team 50 |

51 |
52 | 53 | {team.map((genius) => ( 54 | {genius.role}))} 60 |
61 |
62 |

Contact Us

63 |

Email: rakcurious@gmail.com

64 |
65 |

For Brands

66 |

67 | If you are a brand, you can reach out to us via email for 68 | collaboration or discussions about the presentation of your products 69 | on our site. you can also reach out to us for removal of your 70 | products and brand from our site or any suggestion or feedback. If you are a brand and you think you should be on Slik, reach out to us. We are a young company and still learning as we grow. We would love to hear from you. 71 |

72 |
73 |
74 |

For Users

75 |

76 | {" "} 77 | You can reach out to us via email for feedback, suggestions, bug 78 | reports or anything else about the site. We are a young company and 79 | still learning as we grow. We would love to hear from you. Please 80 | let us know if you love what we are doing.{" "} 81 |

82 |
83 |
84 | 85 | ); 86 | }; 87 | 88 | export default Contact; 89 | -------------------------------------------------------------------------------- /src/pages/Cuties.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "react-router-dom"; 2 | import Navbar from "../components/Navbar"; 3 | import BannerSlider from "../components/BannerSlider"; 4 | import CollectionCards from "../components/CollectionCards"; 5 | import ProductCards from "../components/ProductCards"; 6 | import Footer from "../components/Footer"; 7 | import Error from "../components/WrongPage"; 8 | 9 | const Cutie: React.FC = () => { 10 | const { cutie } = useParams(); 11 | return ( 12 |
13 | 14 | 15 | {cutie === "men" || cutie === "women" ? ( 16 | <> 17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 | ) : cutie === "brands" ? ( 25 | <> 26 |
27 | 28 |
29 |
30 | 31 | ) : ( 32 | 33 | )} 34 |
35 | ); 36 | }; 37 | 38 | export default Cutie; 39 | -------------------------------------------------------------------------------- /src/pages/Error404Page.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "../components/Navbar"; 2 | import Error from "../components/WrongPage"; 3 | 4 | const Error404: React.FC = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default Error404; 14 | -------------------------------------------------------------------------------- /src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import Navbar from "../components/Navbar"; 3 | import CollectionCards from "../components/CollectionCards"; 4 | import Footer from "../components/Footer"; 5 | function Home() { 6 | return ( 7 |
8 | 9 |
10 |
11 | 12 | Womenswear 17 | 18 | 19 | Menswear 24 | 25 |
26 | 27 | 28 |
29 |
30 |
31 | ); 32 | } 33 | 34 | export default Home; 35 | -------------------------------------------------------------------------------- /src/pages/Login.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import signinwithgoogle from "../assets/signinwithgoogle.svg"; 3 | import { 4 | loginWithEmailAndPassword, 5 | loginWithGoogle, 6 | signup, 7 | startPasswordRecovery, 8 | } from "../appwrite/auth"; 9 | import { useNavigate } from "react-router-dom"; 10 | 11 | export default function Login() { 12 | const [email, setEmail] = useState(""); 13 | const [password, setPassword] = useState(""); 14 | const [name, setName] = useState(""); 15 | const [page, setPage] = useState("Sign up"); 16 | const [errorMsg, setErrorMsg] = useState(""); 17 | const [successMsg, setSuccessMsg] = useState(""); 18 | const [isLoading, setIsLoading] = useState(false); 19 | 20 | const navigate = useNavigate(); 21 | 22 | const handleLoginWithGoogle = async () => { 23 | await loginWithGoogle(); 24 | }; 25 | 26 | const handleSubmit = async () => { 27 | setIsLoading(true); 28 | if (page === "Login") { 29 | const { success, error } = await loginWithEmailAndPassword( 30 | email, 31 | password 32 | ); 33 | if (success) { 34 | navigate("/"); 35 | } else { 36 | setErrorMsg(error); 37 | } 38 | } else if (page === "Sign up") { 39 | const { success, error, message } = await signup(email, password, name); 40 | if (success) { 41 | //@ts-ignore 42 | setSuccessMsg(message); 43 | setPage("Verification"); 44 | } else { 45 | setErrorMsg(error); 46 | } 47 | } else if (page === "Reset Password") { 48 | const { success, error } = await startPasswordRecovery(email); 49 | if (success) { 50 | setErrorMsg(""); 51 | setSuccessMsg( 52 | "We have sent you a Password reset email from appwrite. You can reset your password using the link in the email" 53 | ); 54 | } else { 55 | setErrorMsg(error); 56 | } 57 | } 58 | setIsLoading(false); 59 | }; 60 | 61 | return ( 62 | <> 63 |
64 |
65 | navigate("/")} 67 | className="mx-auto h-14 w-auto mb-10" 68 | src="https://res.cloudinary.com/dnhz5reqf/image/upload/v1713705965/slik/sliklogo_iiawiz.webp" 69 | alt="Slik" 70 | /> 71 | {(page == "Login" || page === "Sign up") && ( 72 |
73 |

74 | Recommended 75 |

76 | 81 |

OR

82 |
83 | )} 84 |

85 | {page} 86 |

87 |
88 | 89 | {page === "Verification" ? ( 90 |
91 |

92 | We have sent you a verification email from appwrite. Please verify 93 | your email address using the link in the email to make the most 94 | out of Slik 95 |

96 |
97 | ) : ( 98 |
99 |
e.preventDefault()} className="space-y-6"> 100 | {(page == "Login" || 101 | page === "Sign up" || 102 | page === "Reset Password") && ( 103 |
104 | 110 |
111 | setEmail(e.target.value)} 118 | required 119 | className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-black sm:text-lg font-medium sm:leading-6" 120 | /> 121 |
122 |
123 | )} 124 | 125 | {(page == "Login" || page === "Sign up") && ( 126 |
127 |
128 | 134 | {page === "Login" && ( 135 |
136 |

setPage("Reset Password")} 138 | className="font-semibold text-indigo-700 cursor-pointer" 139 | > 140 | Forgot password? 141 |

142 |
143 | )} 144 |
145 |
146 | setPassword(e.target.value)} 153 | required 154 | className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-black sm:text-lg font-medium sm:leading-6" 155 | /> 156 |
157 |
158 | )} 159 | {page === "Sign up" && ( 160 |
161 |
162 | 168 |
169 |
170 | setName(e.target.value)} 176 | required 177 | className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-black sm:text-lg font-medium sm:leading-6" 178 | /> 179 |
180 |
181 | )} 182 | 183 |
184 | 220 |
221 |

222 | {errorMsg} 223 |

224 |
225 | 226 | {(page === "Login" || page === "Sign up") && ( 227 | <> 228 | {" "} 229 |

230 | {page === "Login" 231 | ? "New user? " 232 | : page === "Sign up" 233 | ? "Already have an account? " 234 | : ""} 235 | 237 | setPage((prev) => 238 | prev === "Login" ? "Sign up" : "Login" 239 | ) 240 | } 241 | className="text-indigo-700 cursor-pointer text-md font-semibold" 242 | > 243 | {page === "Login" ? "Sign up" : "Login"} 244 | 245 |

246 | 247 | )} 248 | 249 | {page === "Reset Password" && ( 250 |

251 | {successMsg}{" "} 252 |

253 | )} 254 |
255 | )} 256 |
257 | 258 | ); 259 | } 260 | -------------------------------------------------------------------------------- /src/pages/PasswordRecovery.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | import { useState } from "react"; 3 | import { updatePasswordRecovery } from "../appwrite/auth"; 4 | 5 | export default function PasswordRecovery() { 6 | const navigate = useNavigate(); 7 | const [reset, setReset] = useState(false); 8 | const [password, setPassword] = useState(""); 9 | const [errorMsg, setErrorMsg] = useState(""); 10 | const [successMsg, setSuccessMsg] = useState(""); 11 | 12 | const resetPassword = async () => { 13 | try { 14 | const { success, error } = await updatePasswordRecovery(password); 15 | if (success) { 16 | setSuccessMsg("Password reset successful"); 17 | setReset(true); 18 | } else { 19 | setErrorMsg(error); 20 | } 21 | } catch (error) { 22 | setErrorMsg("Password reset failed, error: " + error); 23 | } 24 | }; 25 | 26 | return ( 27 | <> 28 |
29 | Slik 30 |

31 | Password Reset 32 |

33 |
34 |

{errorMsg}

35 |

36 | {successMsg} 37 |

38 | 44 |
45 | setPassword(e.target.value)} 52 | required 53 | className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-black sm:text-lg font-medium sm:leading-6" 54 | /> 55 |
56 | 62 |
63 | {reset && ( 64 | <> 65 | 71 | 72 | )} 73 |
74 | 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/pages/PrivacyTerms.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import Navbar from "../components/Navbar"; 3 | 4 | const PrivacyTerms = () => { 5 | useEffect(() => { 6 | document.body.scrollTo(0, 0); 7 | }); 8 | return ( 9 | <> 10 | 11 |
12 |
13 |

14 | Privacy Policy & Terms of Use 15 |

16 |
17 |

18 | Privacy Policy 19 |

20 |

21 | Introduction 22 |
23 | This Privacy Policy governs your use of our website and services. 24 | By accessing or using our services, you agree to be bound by this 25 | policy. 26 |

27 |

28 | Information We Collect 29 |
30 | When you create an account with us, we collect your name and email 31 | address. We also automatically receive your device's internet 32 | protocol (IP) address to help us understand usage by browser and 33 | operating system. 34 |

35 |

36 | Use of Information 37 |
38 | We use the information we collect to provide, maintain, and 39 | improve our services. This includes allowing you to create 40 | wishlists, browse products, and receive account updation emails 41 | like password reset emails when requested. We will not send you 42 | marketing emails or share your personal information without your 43 | consent. 44 |

45 |

46 | Cookies 47 |
48 | We use cookies to maintain your user session. These are not used 49 | to personally identify you on other websites. 50 |

51 |

52 | Data Security 53 |
54 | We take reasonable precautions to protect your personal 55 | information from loss, misuse or unauthorized access or 56 | disclosure. 57 |

58 |

59 | Third-Party Links 60 |
61 | Our website may contain links to third-party sites we do not 62 | control. Though we make sure we don't put links to random sites 63 | without verification, We are not responsible for their privacy 64 | practices. 65 |

66 |

67 | Children's Privacy 68 |
69 | Our services are not intended for children under 13. We do not 70 | knowingly collect personal information from children. 71 |

72 |

73 | Policy Updates 74 |
75 | We may update this policy from time to time. Your continued use of 76 | our services means you accept any updates. 77 |

78 |

79 | Contact 80 |
81 | Please contact rakcurious@gmail.com with any privacy questions. 82 |

83 |
84 |
85 |

Terms of Use

86 |

87 | Introduction 88 |
89 | This website, Slik, is operated by Slik team. Throughout the site, 90 | the terms "we", "us", and "our" refer to Slik team. We offer this 91 | website, including all information, tools, and services available 92 | on this site, to you, the user, upon your acceptance of these 93 | terms, conditions, policies, and notices. 94 |
95 | By visiting our site or using our services, you engage in our 96 | "Service" and agree to be bound by these Terms of Use ("Terms"), 97 | including any additional terms, conditions, and policies 98 | referenced herein or available through hyperlinks. These Terms 99 | apply to all users of the site. 100 |
101 | Please read these Terms carefully before accessing or using our 102 | website. By accessing or using any part of the site, you agree to 103 | be bound by these Terms. If you do not agree with all the terms 104 | and conditions, you may not access the website or use any 105 | services. If these Terms are considered an offer, acceptance is 106 | expressly limited to these Terms. 107 |
108 | Any new features or tools added to the website shall also be 109 | subject to these Terms of Use. We reserve the right to update, 110 | change, or replace any part of these Terms by posting updates on 111 | our website. It is your responsibility to check this page 112 | periodically for changes. Your continued use of or access to the 113 | website following the posting of any changes constitutes 114 | acceptance of those changes. 115 |

116 |

117 | Account Use 118 |
119 | You must be 13 or older to use our services. You are responsible 120 | for your account and actions. Do not share your account or use the 121 | services illegally or to violate the rights of others. 122 |

123 |

124 | Our Services 125 |
126 | We provide a platform to discover products from various brands. We 127 | do not process transactions, ship products, or receive payments. 128 | To purchase, you must visit the brand's website. We do not own or 129 | control the products. If you are a brand, you can contact us about 130 | collaborating or removing your products or for any objection or 131 | queries you have regarding the presentation of your products on 132 | our site. 133 |

134 |

135 | Third-Party Links 136 |
137 | Our site contains links to third-party sites we do not control. We 138 | are not liable for their content or actions. 139 |

140 |

141 | Intellectual Property 142 |
143 | Do not copy or use our branding without permission. 144 |

145 |

146 | Disclaimer of Warranties 147 |
148 | We provide our services as-is without warranties. We are not 149 | liable for errors, interruptions, or issues using our services. 150 |

151 |

152 | Limitation of Liability 153 |
154 | We are not liable for any damages from your use of our services. 155 |

156 |

157 | Indemnification 158 |
159 | You will indemnify us against claims arising from your violation 160 | of these Terms. 161 |

162 |

163 | Termination 164 |
165 | We may suspend or terminate your account for violations of these 166 | Terms. 167 |

168 |

169 | Disputes 170 |
171 | These Terms are governed by the laws of India. Disputes will be 172 | resolved in Indian courts. 173 |

174 |

175 | Changes 176 |
177 | We may update these Terms at any time. Your continued use means 178 | you accept the updated Terms. 179 |

180 |
181 |
182 |
183 | 184 | ); 185 | }; 186 | 187 | export default PrivacyTerms; 188 | -------------------------------------------------------------------------------- /src/pages/ProductInfo.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate, useParams } from "react-router-dom"; 2 | import { useState } from "react"; 3 | import React from "react"; 4 | import { handleWishlistUpdate } from "../utils/wishlist"; 5 | import { useAppSelector } from "../redux_toolkit/hooks"; 6 | import { selectLikes, selectProducts } from "../redux_toolkit/productSlice"; 7 | import { 8 | selectUserData, 9 | selectWishlist, 10 | selectWishlistIds, 11 | } from "../redux_toolkit/userSlice"; 12 | import Navbar from "../components/Navbar"; 13 | import Modal from "../components/AuthModal"; 14 | import Error from "../components/WrongPage"; 15 | import { Swiper, SwiperSlide } from "swiper/react"; 16 | 17 | import "swiper/css"; 18 | import "swiper/css/pagination"; 19 | import { Autoplay, Pagination } from "swiper/modules"; 20 | 21 | const ProductInfo: React.FC = () => { 22 | const products = useAppSelector(selectProducts); 23 | const userdata = useAppSelector(selectUserData); 24 | const wishlist = useAppSelector(selectWishlist); 25 | const wishIds = useAppSelector(selectWishlistIds); 26 | const likeList = useAppSelector(selectLikes); 27 | const [share, setShare] = useState(false); 28 | 29 | const navigate = useNavigate(); 30 | 31 | const { slug } = useParams(); 32 | const [showModal, setShowModal] = useState(false); 33 | 34 | const product = products.find((product) => product.slug == slug); 35 | const isAuthenticated = !!userdata; 36 | const isVerified = userdata?.emailVerification || false; 37 | 38 | const copyToShare = () => { 39 | window.navigator.clipboard.writeText(window.location.href); 40 | setShare(true); 41 | setTimeout(() => { 42 | setShare(false); 43 | }, 5000); 44 | }; 45 | 46 | const slugify = (str: string) => 47 | str 48 | .toLowerCase() 49 | .trim() 50 | .replace(/[^\w\s-]/g, "") 51 | .replace(/[\s_-]+/g, "-") 52 | .replace(/^-+|-+$/g, ""); 53 | 54 | const brandPage = (name: string) => { 55 | const brandSlug = slugify(name); 56 | navigate(`/brands/${brandSlug}`); 57 | }; 58 | 59 | return ( 60 | <> 61 | 62 | {product ? ( 63 | <> 64 |
65 |
66 | {product?.images.map((image, index) => ( 67 |
68 | {`Product 78 |
79 | ))} 80 |
81 |
82 |

83 | {product?.title} 84 |

85 |

brandPage(product?.brand.toLowerCase())} 87 | className="cursor-pointer text-purple-900 uppercase text-center" 88 | > 89 | {product?.brand} 90 |

91 |

92 | ₹{product?.price.toLocaleString("en-IN")} 93 |

94 |
95 | 113 | 118 | Buy Now 119 | 120 | 128 |
129 |
130 |
131 |
132 |
133 | 139 | {product.images.map((image, index) => ( 140 | 144 | {product.title} 155 | 156 | ))} 157 | 158 |
159 |
160 |

161 | {product?.title} 162 |

163 |

brandPage(product?.brand.toLowerCase())} 165 | className="cursor-pointer uppercase text-purple-900 text-center" 166 | > 167 | {product?.brand} 168 |

169 |

170 | ₹{product?.price.toLocaleString("en-IN")} 171 |

172 |
173 | 191 | 196 | Buy Now 197 | 198 | 206 |
207 |
208 |
209 | setShowModal(false)} 214 | onLogin={() => { 215 | navigate("/login"); 216 | setShowModal(false); 217 | }} 218 | /> 219 | 220 | ) : ( 221 | 222 | )} 223 | 224 | ); 225 | }; 226 | 227 | export default ProductInfo; 228 | -------------------------------------------------------------------------------- /src/pages/Profile.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { useAppSelector } from "../redux_toolkit/hooks"; 4 | import { selectUserData, selectWishlist} from "../redux_toolkit/userSlice"; 5 | import Navbar from "../components/Navbar"; 6 | import WishlistCards from "../components/WishlistCards"; 7 | import Modal from "../components/AuthModal"; 8 | import { selectProducts } from "../redux_toolkit/productSlice"; 9 | 10 | function Profile() { 11 | const [load, setLoad] = useState(true); 12 | const navigate = useNavigate(); 13 | const userdata = useAppSelector(selectUserData); 14 | const wishlist = useAppSelector(selectWishlist) 15 | const [showModal, setShowModal] = useState(true); 16 | const products = useAppSelector(selectProducts) 17 | 18 | 19 | 20 | let wishProds = wishlist?.map((prod:any)=> products?.find((pro)=> pro.$id === prod.$id) 21 | ) 22 | 23 | const isAuthenticated = !!userdata; 24 | const isVerified = userdata?.emailVerification || false; 25 | 26 | const loadingtime = () => { 27 | setTimeout(() => { 28 | setLoad(false); 29 | }, 10000); 30 | }; 31 | loadingtime(); 32 | return ( 33 | <> 34 | 35 | {(userdata && products.length>0) ? ( 36 | <> 37 | {wishProds && } 38 | 39 | ) : load ? ( 40 |
41 | 45 |
46 | ) : ( 47 | setShowModal(false)} 52 | onLogin={() => { 53 | navigate("/login"); 54 | setShowModal(false); 55 | }} 56 | /> 57 | )} 58 | 59 | ); 60 | } 61 | 62 | export default Profile; 63 | -------------------------------------------------------------------------------- /src/pages/Verification.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { updateVerification } from "../appwrite/auth"; 4 | 5 | export default function Verification() { 6 | const navigate = useNavigate(); 7 | const [verified, setVerified] = useState(false); 8 | const [errorMsg, setErrorMsg] = useState(""); 9 | 10 | useEffect(() => { 11 | const verification = async () => { 12 | try { 13 | const { success, error } = await updateVerification(); 14 | if (success) { 15 | setVerified(true); 16 | } else { 17 | setErrorMsg(error); 18 | } 19 | } catch (error) { 20 | setErrorMsg("Verification failed, error: " + error); 21 | } 22 | }; 23 | 24 | verification(); 25 | }, []); 26 | 27 | return ( 28 | <> 29 |
30 | Slik 31 |

32 | Verification 33 |

34 |

{errorMsg}

35 | {verified ? ( 36 | <> 37 |

38 | Congratulations, You are verified. Start manifesting... 39 |

40 | 46 | 47 | ) : (errorMsg == "") ? ( 48 |

49 | Verification is in Progress, Please wait... 50 |

51 | ) :

52 | Verification failed 53 |

} 54 |
55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/redux_toolkit/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from 'react-redux' 2 | import type { AppDispatch, RootState } from './store' 3 | 4 | //custom hooks to use throughout the app instead of plain `useDispatch` and `useSelector` (typescript recommended) 5 | export const useAppDispatch = useDispatch.withTypes() 6 | export const useAppSelector = useSelector.withTypes() -------------------------------------------------------------------------------- /src/redux_toolkit/productSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState } from "./store"; 3 | import { Collection, Prods, collections } from "../index"; 4 | 5 | interface ProductState { 6 | products: Prods[]; 7 | collections: Collection[]; 8 | likes: any[]; 9 | } 10 | 11 | const initialState: ProductState = { 12 | products: [], 13 | collections: collections, 14 | likes: [], 15 | }; 16 | const productSlice = createSlice({ 17 | name: "products", 18 | initialState, 19 | reducers: { 20 | addProduct: (state, action: PayloadAction) => { 21 | state.products.push(action.payload); 22 | }, 23 | updateProduct: (state, action: PayloadAction) => { 24 | const { $id, ...updatedData } = action.payload; 25 | const productIndex = state.products.findIndex( 26 | (product) => product.$id === $id 27 | ); 28 | if (productIndex !== -1) { 29 | state.products[productIndex] = { 30 | ...state.products[productIndex], 31 | ...updatedData, 32 | }; 33 | } 34 | }, 35 | deleteProduct: (state, action: PayloadAction) => { 36 | state.products = state.products.filter( 37 | (product) => product.$id !== action.payload 38 | ); 39 | }, 40 | getProducts: (state, action: PayloadAction) => { 41 | state.products = action.payload; 42 | }, 43 | getLikes: (state, action: PayloadAction) => { 44 | state.likes = action.payload; 45 | }, 46 | updateLike: (state, action: PayloadAction) => { 47 | const { $id, wishlist } = action.payload; 48 | const likeIndex = state.likes.findIndex( 49 | (product) => product.$id === $id 50 | ); 51 | if (likeIndex !== -1) { 52 | state.likes[likeIndex] = { 53 | $id, wishlist 54 | }; 55 | } 56 | }, 57 | }, 58 | }); 59 | 60 | export const { addProduct, updateProduct, deleteProduct, getProducts, getLikes, updateLike} = 61 | productSlice.actions; 62 | export const selectProducts = (state: RootState) => state.products.products; 63 | export const selectCollections = (state: RootState) => state.products.collections; 64 | export const selectLikes = (state: RootState) => state.products.likes; 65 | 66 | export default productSlice.reducer; 67 | -------------------------------------------------------------------------------- /src/redux_toolkit/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import productReducer from './productSlice'; 3 | import userReducer from './userSlice'; 4 | 5 | export const store = configureStore({ 6 | reducer: { 7 | products: productReducer, 8 | user: userReducer, 9 | }, 10 | }); 11 | 12 | export type RootState = ReturnType; 13 | export type AppDispatch = typeof store.dispatch; -------------------------------------------------------------------------------- /src/redux_toolkit/userSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState } from "./store"; 3 | import { Prods } from ".."; 4 | 5 | 6 | interface UserState { 7 | userData: any | null; 8 | wishlist: any | null; 9 | wishlistIds: any; 10 | } 11 | 12 | const initialState: UserState = { 13 | userData: null, 14 | wishlist: null, 15 | wishlistIds: null, 16 | }; 17 | const userSlice = createSlice({ 18 | name: "user", 19 | initialState, 20 | reducers: { 21 | setUserData: (state, action: PayloadAction) => { 22 | state.userData = action.payload; 23 | }, 24 | setWishlist: (state, action: PayloadAction) => { 25 | state.wishlist = action.payload; 26 | state.wishlistIds = action.payload.map((prod)=> prod.$id) 27 | }, 28 | }, 29 | }); 30 | 31 | export const { setUserData, setWishlist } = userSlice.actions; 32 | export const selectUserData = (state: RootState) => state.user.userData; 33 | export const selectWishlist = (state: RootState) => state.user.wishlist; 34 | export const selectWishlistIds = (state: RootState) => state.user.wishlistIds; 35 | export default userSlice.reducer; -------------------------------------------------------------------------------- /src/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/utils/wishlist.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Prods } from "../index"; 3 | import { updateWishlist } from "../appwrite/config"; 4 | import { updateLike } from "../redux_toolkit/productSlice"; 5 | import { store } from "../redux_toolkit/store"; 6 | import { setWishlist } from "../redux_toolkit/userSlice"; 7 | 8 | export const handleWishlistUpdate = async ( 9 | wishIds: string[], 10 | wishlist: Prods[], 11 | userdata: any, 12 | productId: any, 13 | products: Prods[], 14 | likeList: any[], 15 | setShowModal: (show: boolean) => void 16 | ) => { 17 | if (!userdata) { 18 | setShowModal(true); 19 | return; 20 | } 21 | 22 | if (!userdata.emailVerification) { 23 | setShowModal(true); 24 | return; 25 | } 26 | 27 | if (likeList.length === 0) { 28 | return; 29 | } 30 | 31 | const userid = userdata.$id; 32 | // const likeUser = { 33 | // $collectionId: userdata.$collectionId, 34 | // $createdAt: userdata.$createdAt, 35 | // $databaseId: userdata.$databaseId, 36 | // $id: userdata.$id, 37 | // $permissions: userdata.$permissions, 38 | // $updatedAt: userdata.$updatedAt, 39 | // }; 40 | const product = products.find((prod) => prod.$id == productId); 41 | 42 | const updatedWishlist = wishIds?.includes(productId) 43 | ? wishlist.filter((prod: Prods) => prod.$id !== productId) 44 | : [product, ...wishlist]; 45 | 46 | store.dispatch(setWishlist(updatedWishlist)); 47 | 48 | const updatedWishIds = wishIds.includes(productId) 49 | ? wishIds.filter((id) => id !== productId) 50 | : [productId, ...wishIds]; 51 | 52 | let likeProd = likeList?.find((prod) => prod.$id === product.$id); 53 | 54 | let likeWishlist: string[] = likeProd.wishlist; 55 | 56 | likeWishlist = likeWishlist?.includes(userid) 57 | ? likeWishlist.filter((id: string) => id !== userid) 58 | : [...likeWishlist, userid]; 59 | const updatedLike = { $id: product?.$id, wishlist: likeWishlist }; 60 | 61 | store.dispatch(updateLike(updatedLike)); 62 | 63 | const response = await updateWishlist(userdata.$id, updatedWishIds); 64 | if (response) { 65 | } else { 66 | console.log("failed to update user wishlist"); 67 | store.dispatch(setWishlist(wishlist)); 68 | store.dispatch(updateLike(likeProd)); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: { 9 | fontFamily : { 10 | 'main' : ['Urbanist', 'sans-serif'] 11 | }, 12 | screens: { 13 | 'lol': {'max': '1023px'}, 14 | }, 15 | }, 16 | }, 17 | plugins: [ 18 | require('@tailwindcss/aspect-ratio'), 19 | require('@tailwindcss/forms'), 20 | ], 21 | } 22 | 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | import { VitePWA } from 'vite-plugin-pwa'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | react(), 8 | VitePWA({ 9 | registerType: 'autoUpdate', 10 | injectRegister: 'auto', 11 | workbox: { 12 | globPatterns: ['**/*.{js,css,html,ico,jpg,png,svg,webp,aviv}'], 13 | }, 14 | manifest: { 15 | name: 'Slik', 16 | short_name: 'Slik', 17 | description: "India's finest fashion hand-picked from top brands", 18 | theme_color: '#F3E8FF', 19 | background_color: '#F3E8FF', 20 | start_url: '/', 21 | display: 'standalone', 22 | prefer_related_applications: false, 23 | shortcuts: [ 24 | { 25 | name: 'Women', 26 | url: '/women', 27 | icons: [{ src : 'shorticonwomen.png', sizes: "192x192" }], 28 | }, 29 | { 30 | name: 'Men', 31 | url: '/men', 32 | icons: [{ src : 'shorticonmen.png', sizes: "192x192" }], 33 | }, 34 | { 35 | name: 'Wishlist', 36 | url: '/profile', 37 | icons: [{ src : 'shorticonwishlist.png', sizes: "192x192" }] 38 | }, 39 | ], 40 | icons: [ 41 | { 42 | src: 'pwa-192x192.png', 43 | sizes: '192x192', 44 | type: 'image/png', 45 | purpose: 'any' 46 | }, 47 | { 48 | src: 'pwa-512x512.png', 49 | sizes: '512x512', 50 | type: 'image/png', 51 | purpose: 'maskable' 52 | }, 53 | ], 54 | screenshots: [ 55 | { 56 | "src": "screenshotwide1200x630.jpg", 57 | "sizes": "1200x630", 58 | "type": "image/jpg", 59 | "form_factor": "wide", 60 | "label": "drip, drippy, drippin'", 61 | 62 | }, 63 | { 64 | "src": "screenshotnarrow1080x2344a.jpg", 65 | "sizes": "1080x2344", 66 | "type": "image/jpg", 67 | "form_factor": "narrow", 68 | "label": "drip, drippy, drippin'" 69 | }, 70 | { 71 | "src": "screenshotnarrow1080x2344b.jpg", 72 | "sizes": "1080x2344", 73 | "type": "image/jpg", 74 | "form_factor": "narrow", 75 | "label": "drip, drippy, drippin'" 76 | }, 77 | ] 78 | }, 79 | devOptions: { 80 | enabled: false, // for enabling pwa in dev mode ;) 81 | }, 82 | }), 83 | ], 84 | }); --------------------------------------------------------------------------------