├── Client ├── .env ├── assets │ ├── icon.png │ ├── favicon.png │ ├── splash.png │ └── adaptive-icon.png ├── src │ ├── assets │ │ ├── ssl.png │ │ ├── logo.png │ │ ├── logo1.png │ │ ├── logos.png │ │ ├── order.png │ │ ├── visa.png │ │ ├── address.png │ │ ├── favorite.png │ │ ├── feedback.png │ │ ├── maestro.png │ │ ├── credit_card.png │ │ ├── emptyChart.png │ │ ├── heart_img.png │ │ ├── mastercard.png │ │ └── evaluationComplete.png │ ├── services │ │ ├── CategoryService.js │ │ ├── AuthService.js │ │ ├── UserService.js │ │ ├── CommentService.js │ │ ├── FavoriteService.js │ │ ├── AddressService.js │ │ ├── BankCardService.js │ │ ├── OrderService.js │ │ ├── HttpConnection.js │ │ ├── StoreService.js │ │ └── ProductService.js │ ├── consts │ │ ├── colors.js │ │ └── time.js │ ├── view │ │ ├── screens │ │ │ ├── SplashScreen.js │ │ │ ├── EditCommentScreen.js │ │ │ ├── AddCommentScreen.js │ │ │ ├── HomeScreen.js │ │ │ ├── AddAddressScreen.js │ │ │ ├── EditAddressScreen.js │ │ │ ├── SearchResultsScreen.js │ │ │ ├── MyOrdersScreen.js │ │ │ ├── AccountScreen.js │ │ │ ├── BasketScreen.js │ │ │ ├── CategoriesScreen.js │ │ │ ├── FavoriteListScreen.js │ │ │ └── CheckoutScreen.js │ │ └── components │ │ │ ├── general │ │ │ ├── ToastMessage.js │ │ │ ├── ValidationModal.js │ │ │ ├── ModalMessage.js │ │ │ ├── CardImageSlider.js │ │ │ └── ProductCard.js │ │ │ ├── productDetail │ │ │ ├── ProductBadges.js │ │ │ ├── ImageSlider.js │ │ │ ├── SizeOptionsSection.js │ │ │ ├── ColorOptionsSection.js │ │ │ ├── ProductInfo.js │ │ │ ├── RecommendProductItem.js │ │ │ ├── RecommendProducts.js │ │ │ ├── ProductProperties.js │ │ │ └── AnimatedHeader.js │ │ │ ├── checkout │ │ │ ├── ContractForms.js │ │ │ ├── ConfirmationField.js │ │ │ ├── PaymentBottomSheet.js │ │ │ ├── SelectAddressSection.js │ │ │ └── BankCardBottomSheet.js │ │ │ ├── favorite │ │ │ └── FavoritesMenu.js │ │ │ ├── evaluation │ │ │ └── EvaluationMenu.js │ │ │ ├── basket │ │ │ └── RecommendBasketItem.js │ │ │ └── address │ │ │ └── AddressBottomSheet.js │ └── Context │ │ ├── CategoryContext.js │ │ ├── AuthContext.js │ │ ├── FavoriteContext.js │ │ ├── ProductContext.js │ │ └── StoreContext.js ├── .gitignore ├── babel.config.js ├── app.json ├── App.js └── package.json └── Server ├── Config └── env │ └── config.env ├── Helpers ├── error │ └── CustomError.js ├── database │ └── connectDatabase.js ├── input │ └── inputHelpers.js ├── Libraries │ └── imageUpload.js └── authorization │ └── tokenHelpers.js ├── Routers ├── banner.js ├── user.js ├── auth.js ├── address.js ├── comment.js ├── bankCard.js ├── favorite.js ├── category.js ├── order.js ├── store.js ├── index.js └── product.js ├── Models ├── lastviewed.js ├── subcategory.js ├── category.js ├── banner.js ├── evaluation.js ├── orderitem.js ├── comment.js ├── address.js ├── bankCard.js ├── product.js ├── order.js └── user.js ├── package.json ├── server.js ├── Controllers ├── banner.js ├── user.js ├── address.js ├── favorite.js ├── category.js ├── bankCard.js ├── auth.js ├── comment.js ├── store.js └── order.js └── Middlewares ├── Errors └── customErrorHandler.js └── Authorization └── auth.js /Client/.env: -------------------------------------------------------------------------------- 1 | NGROK_URL = -------------------------------------------------------------------------------- /Client/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/assets/icon.png -------------------------------------------------------------------------------- /Client/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/assets/favicon.png -------------------------------------------------------------------------------- /Client/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/assets/splash.png -------------------------------------------------------------------------------- /Client/src/assets/ssl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/ssl.png -------------------------------------------------------------------------------- /Client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/logo.png -------------------------------------------------------------------------------- /Client/src/assets/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/logo1.png -------------------------------------------------------------------------------- /Client/src/assets/logos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/logos.png -------------------------------------------------------------------------------- /Client/src/assets/order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/order.png -------------------------------------------------------------------------------- /Client/src/assets/visa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/visa.png -------------------------------------------------------------------------------- /Client/src/assets/address.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/address.png -------------------------------------------------------------------------------- /Client/src/assets/favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/favorite.png -------------------------------------------------------------------------------- /Client/src/assets/feedback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/feedback.png -------------------------------------------------------------------------------- /Client/src/assets/maestro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/maestro.png -------------------------------------------------------------------------------- /Client/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/assets/adaptive-icon.png -------------------------------------------------------------------------------- /Client/src/assets/credit_card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/credit_card.png -------------------------------------------------------------------------------- /Client/src/assets/emptyChart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/emptyChart.png -------------------------------------------------------------------------------- /Client/src/assets/heart_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/heart_img.png -------------------------------------------------------------------------------- /Client/src/assets/mastercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/mastercard.png -------------------------------------------------------------------------------- /Client/src/assets/evaluationComplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muhammet-Yildiz/HizliSepet/HEAD/Client/src/assets/evaluationComplete.png -------------------------------------------------------------------------------- /Client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /Server/Config/env/config.env: -------------------------------------------------------------------------------- 1 | # Server variables 2 | 3 | MONGO_URI = 4 | 5 | PORT = 5000 6 | 7 | NODE_ENV = development 8 | 9 | 10 | # Json web token 11 | 12 | JWT_SECRET_KEY = 13 | 14 | 15 | -------------------------------------------------------------------------------- /Client/src/services/CategoryService.js: -------------------------------------------------------------------------------- 1 | import http from "./HttpConnection"; 2 | 3 | const getAllCategories = () => { 4 | return http.get("/categories/all"); 5 | }; 6 | 7 | export default { 8 | getAllCategories 9 | } -------------------------------------------------------------------------------- /Server/Helpers/error/CustomError.js: -------------------------------------------------------------------------------- 1 | class CustomError extends Error { 2 | 3 | constructor(message,status ) { 4 | 5 | super(message) 6 | 7 | this.status =status ; 8 | } 9 | 10 | } 11 | 12 | 13 | module.exports = CustomError ; -------------------------------------------------------------------------------- /Client/src/consts/colors.js: -------------------------------------------------------------------------------- 1 | const COLORS = { 2 | white: '#fff', 3 | dark: '#000', 4 | red: '#F52A2A', 5 | light: '#F1F1F1', 6 | green: '#00B761', 7 | yellow :'#F5A623', 8 | grey : '#A9A9A9', 9 | }; 10 | 11 | export default COLORS; -------------------------------------------------------------------------------- /Client/src/services/AuthService.js: -------------------------------------------------------------------------------- 1 | import http from "./HttpConnection"; 2 | 3 | const login = data => { 4 | return http.post("/auth/login", data); 5 | }; 6 | 7 | const register = data => { 8 | return http.post("/auth/register", data); 9 | } 10 | 11 | export default { 12 | login , 13 | register 14 | } -------------------------------------------------------------------------------- /Client/src/services/UserService.js: -------------------------------------------------------------------------------- 1 | import http from "./HttpConnection"; 2 | 3 | const changeEmail = (data) => { 4 | return http.post("/user/changeEmail",data); 5 | }; 6 | 7 | const changePassword = (data) => { 8 | return http.post("/user/changePassword",data); 9 | }; 10 | 11 | export default { 12 | changeEmail, 13 | changePassword 14 | } -------------------------------------------------------------------------------- /Client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | "plugins": [ 6 | [ 7 | "module:react-native-dotenv", 8 | { 9 | "envName": "APP_ENV", 10 | "moduleName": "@env", 11 | "path": ".env", 12 | } 13 | ] 14 | ] 15 | 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /Client/src/consts/time.js: -------------------------------------------------------------------------------- 1 | export const MONTHS = ['OCAK', 'ŞUBAT', 'MART', 'NİSAN', 'MAYIS', 'HAZİRAN', 'TEMMUZ', 'AĞUSTOS', 'EYLÜL', 'EKİM', 'KASIM', 'ARALIK'] 2 | export const LOWERMONTHS = ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'] 3 | 4 | export const DAYS = ['Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'] -------------------------------------------------------------------------------- /Server/Helpers/database/connectDatabase.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const connectDatabase = () => { 4 | 5 | mongoose.connect(process.env.MONGO_URI ,{useNewUrlParser : true}) 6 | .then( () => { 7 | console.log("MongoDb Connection Successful") 8 | }) 9 | .catch(err => { 10 | console.log(err) 11 | }) 12 | 13 | } 14 | 15 | module.exports = connectDatabase -------------------------------------------------------------------------------- /Server/Helpers/input/inputHelpers.js: -------------------------------------------------------------------------------- 1 | const bycrpt = require("bcryptjs") 2 | 3 | const validateUserInput = (email,password) => { 4 | return email && password 5 | } 6 | 7 | const comparePassword = (password ,hashedPassword) => { 8 | 9 | return bycrpt.compareSync(password,hashedPassword) 10 | 11 | } 12 | 13 | 14 | module.exports ={ 15 | validateUserInput, 16 | comparePassword 17 | } -------------------------------------------------------------------------------- /Server/Routers/banner.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express'); 3 | const {addBanner,getAllBanners} = require('../Controllers/banner'); 4 | const imageUpload = require('../Helpers/Libraries/imageUpload'); 5 | const router = express.Router(); 6 | 7 | 8 | router.get('/all' , getAllBanners) 9 | 10 | router.post("/add" , [imageUpload.single("banner_image")] , addBanner) 11 | 12 | 13 | module.exports = router; -------------------------------------------------------------------------------- /Server/Routers/user.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { changeEmail ,changePassword} = require('../Controllers/user') 3 | const { getAccessToRoute } = require('../Middlewares/Authorization/auth') 4 | 5 | const router = express.Router() 6 | 7 | router.use(getAccessToRoute) 8 | 9 | 10 | router.post("/changeEmail" , changeEmail) 11 | 12 | router.post("/changePassword" , changePassword) 13 | 14 | module.exports = router -------------------------------------------------------------------------------- /Client/src/services/CommentService.js: -------------------------------------------------------------------------------- 1 | import http from "./HttpConnection"; 2 | 3 | const addComment = data => { 4 | return http.post("/comments/add", data); 5 | }; 6 | 7 | const editComment = data => { 8 | return http.put("/comments/edit", data); 9 | }; 10 | 11 | const deleteComment = data => { 12 | return http.post("/comments/delete", data); 13 | }; 14 | 15 | 16 | export default { 17 | addComment, 18 | editComment, 19 | deleteComment 20 | } -------------------------------------------------------------------------------- /Client/src/view/screens/SplashScreen.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View,ActivityIndicator } from 'react-native' 3 | import COLORS from '../../consts/colors' 4 | 5 | const SplashScreen = () => { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default SplashScreen 14 | -------------------------------------------------------------------------------- /Server/Models/lastviewed.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const lastViewedSchema = new Schema({ 6 | 7 | user: { 8 | type: Schema.Types.ObjectId, 9 | ref: "User", 10 | required: true 11 | }, 12 | products: [{ 13 | type: Schema.Types.ObjectId, 14 | ref: "Product" 15 | }], 16 | 17 | }) 18 | 19 | 20 | module.exports = mongoose.model("LastViewed", lastViewedSchema) -------------------------------------------------------------------------------- /Client/src/services/FavoriteService.js: -------------------------------------------------------------------------------- 1 | import http from "./HttpConnection"; 2 | 3 | const getAllFavoriteItems = () => { 4 | return http.get(`/favorites/all`); 5 | } 6 | 7 | const addItemToFavoritelist = (id) => { 8 | return http.post(`/favorites/${id}/add`); 9 | } 10 | 11 | const removeItemFromFavoritelist = (id) => { 12 | return http.delete(`/favorites/${id}/remove`); 13 | } 14 | 15 | export default { 16 | getAllFavoriteItems, 17 | addItemToFavoritelist, 18 | removeItemFromFavoritelist 19 | } -------------------------------------------------------------------------------- /Server/Routers/auth.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { register, login, getActiveUser , logout } = require('../Controllers/auth') 3 | const { getAccessToRoute } = require('../Middlewares/Authorization/auth') 4 | 5 | const router = express.Router() 6 | 7 | 8 | router.post("/register" , register) 9 | 10 | router.post("/login" , login) 11 | 12 | router.get("/logout" , getAccessToRoute, logout) 13 | 14 | router.get("/activeUser" ,getAccessToRoute , getActiveUser) 15 | 16 | 17 | module.exports = router -------------------------------------------------------------------------------- /Server/Routers/address.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const {addAddress,editAddress,deleteAddress,getAllAddresses} = require('../Controllers/address'); 3 | const { getAccessToRoute } = require('../Middlewares/Authorization/auth') 4 | 5 | const router = express.Router(); 6 | 7 | router.use(getAccessToRoute) 8 | 9 | router.get('/all' , getAllAddresses) 10 | 11 | router.post('/add' , addAddress) 12 | 13 | router.put("/edit/:id" , editAddress) 14 | 15 | router.delete("/delete/:id" , deleteAddress) 16 | 17 | module.exports = router; -------------------------------------------------------------------------------- /Server/Routers/comment.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express'); 3 | const {addComment,getAllComments ,editComment ,deleteComment} = require('../Controllers/comment'); 4 | const router = express.Router(); 5 | const { getAccessToRoute } = require('../Middlewares/Authorization/auth') 6 | 7 | router.use(getAccessToRoute) 8 | 9 | 10 | router.get("/all" , getAllComments) 11 | 12 | router.post("/add" , addComment) 13 | 14 | router.put("/edit" , editComment) 15 | 16 | router.post("/delete" , deleteComment) 17 | 18 | 19 | module.exports = router; -------------------------------------------------------------------------------- /Server/Models/subcategory.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const subCategorySchema = new Schema({ 6 | name : { 7 | type : String, 8 | required : [true, "Please provide a subcategory name "], 9 | }, 10 | image : { 11 | type : String, 12 | }, 13 | category : { 14 | type : Schema.Types.ObjectId, 15 | ref : "Category", 16 | required : true 17 | } 18 | 19 | }) 20 | 21 | 22 | module.exports = mongoose.model("SubCategory", subCategorySchema) -------------------------------------------------------------------------------- /Server/Routers/bankCard.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express'); 3 | const {editBankCard,deleteBankCard, addBankCard,getAllBankCards} = require('../Controllers/bankCard'); 4 | const { getAccessToRoute } = require('../Middlewares/Authorization/auth') 5 | 6 | const router = express.Router(); 7 | 8 | router.use(getAccessToRoute) 9 | 10 | router.get('/all' , getAllBankCards) 11 | 12 | router.post("/add" , addBankCard) 13 | 14 | router.put("/edit/:id" , editBankCard) 15 | 16 | router.delete("/delete/:id" , deleteBankCard) 17 | 18 | 19 | module.exports = router; -------------------------------------------------------------------------------- /Server/Models/category.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const categorySchema = new Schema({ 6 | name: { 7 | unique: true, 8 | type: String, 9 | required: [true, "Please provide a category name "], 10 | trim: true, 11 | }, 12 | image: { 13 | type: String, 14 | }, 15 | subCategories: [{ 16 | type: Schema.Types.ObjectId, 17 | ref: "SubCategory" 18 | }] 19 | 20 | }) 21 | 22 | 23 | module.exports = mongoose.model("Category", categorySchema) -------------------------------------------------------------------------------- /Server/Routers/favorite.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { getAllFavoriteItems,addItemToFavoritelist,removeItemFromFavoritelist } = require('../Controllers/favorite'); 3 | const { getAccessToRoute } = require('../Middlewares/Authorization/auth') 4 | 5 | const router = express.Router(); 6 | 7 | 8 | router.get('/all',getAccessToRoute , getAllFavoriteItems) 9 | 10 | router.post("/:id/add" ,getAccessToRoute , addItemToFavoritelist) 11 | 12 | router.delete("/:id/remove" ,getAccessToRoute , removeItemFromFavoritelist) 13 | 14 | 15 | module.exports = router; -------------------------------------------------------------------------------- /Client/src/services/AddressService.js: -------------------------------------------------------------------------------- 1 | import http from "./HttpConnection"; 2 | 3 | const getAllAddresses = () => { 4 | return http.get("/address/all"); 5 | }; 6 | 7 | const addAddress = data => { 8 | return http.post("/address/add", data); 9 | }; 10 | const editAddress = (data ,id ) => { 11 | return http.put(`/address/edit/${id}`, data); 12 | }; 13 | 14 | const deleteAddress = id => { 15 | return http.delete(`/address/delete/${id}`); 16 | }; 17 | 18 | export default { 19 | getAllAddresses, 20 | addAddress, 21 | editAddress, 22 | deleteAddress 23 | } -------------------------------------------------------------------------------- /Client/src/services/BankCardService.js: -------------------------------------------------------------------------------- 1 | import http from "./HttpConnection"; 2 | 3 | const getAllBankCards = () => { 4 | return http.get("/bankCards/all"); 5 | }; 6 | const addBankCard = data => { 7 | return http.post("/bankCards/add", data); 8 | }; 9 | 10 | const editBankCard = (id ,data ) => { 11 | return http.put(`/bankCards/edit/${id}`, data); 12 | }; 13 | 14 | const deleteBankCard = id => { 15 | return http.delete(`/bankCards/delete/${id}`); 16 | }; 17 | 18 | export default { 19 | getAllBankCards, 20 | editBankCard, 21 | deleteBankCard, 22 | addBankCard 23 | } -------------------------------------------------------------------------------- /Server/Routers/category.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express'); 3 | const {addCategory,getAllCategories,getAllSubCategories,addSubCategory} = require('../Controllers/category'); 4 | const imageUpload = require('../Helpers/Libraries/imageUpload'); 5 | const router = express.Router(); 6 | 7 | 8 | router.get('/all' , getAllCategories) 9 | 10 | router.post("/add" , imageUpload.single("category_image") ,addCategory) 11 | 12 | router.get('/allSubCategories' , getAllSubCategories) 13 | 14 | router.post("/:categoryId/add" , imageUpload.single("subCategory_image") ,addSubCategory) 15 | 16 | 17 | module.exports = router; -------------------------------------------------------------------------------- /Server/Models/banner.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const bannerSchema = new Schema({ 6 | 7 | name : { 8 | type : String, 9 | required : [true, "Please provide a name "], 10 | trim : true, 11 | unique : true 12 | }, 13 | image : { 14 | type : String, 15 | }, 16 | shippingTime : { 17 | type : Number, 18 | default : 0 19 | }, 20 | deliveryTime : { 21 | type : Number, 22 | default : 0 23 | } 24 | }) 25 | 26 | 27 | module.exports = mongoose.model("Banner", bannerSchema) -------------------------------------------------------------------------------- /Client/src/view/components/general/ToastMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, Text } from 'react-native' 3 | const ToastMessage = ({ text1 ,bgColor}) => { 4 | return ( 5 | 13 | 16 | {text1} 17 | 18 | 19 | ) 20 | } 21 | 22 | export default ToastMessage 23 | -------------------------------------------------------------------------------- /Server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "HIZLI SEPET - Backend", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "nodemon server.js", 8 | "dev": "nodemon server.js " 9 | }, 10 | "author": "Muhammet Yıldız", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcryptjs": "^2.4.3", 14 | "dotenv": "^16.0.3", 15 | "express": "^4.18.2", 16 | "express-async-handler": "^1.2.0", 17 | "jsonwebtoken": "^8.5.1", 18 | "mongoose": "^6.7.3", 19 | "multer": "^1.4.5-lts.1", 20 | "uuid": "^9.0.0" 21 | }, 22 | "devDependencies": { 23 | "nodemon": "^2.0.20" 24 | } 25 | } -------------------------------------------------------------------------------- /Server/Routers/order.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express'); 3 | const { completeOrder ,getAllMyOrders,getOrderDetails ,getAllEvaluatableItems, getAllApprovedEvaluatableItems} = require('../Controllers/order'); 4 | const { getAccessToRoute } = require('../Middlewares/Authorization/auth') 5 | 6 | const router = express.Router(); 7 | 8 | router.use(getAccessToRoute) 9 | 10 | 11 | router.post('/completeOrder' , completeOrder) 12 | 13 | router.get("/getAllMyOrders" , getAllMyOrders) 14 | 15 | router.get("/detail/:id" , getOrderDetails) 16 | 17 | router.get("/getAllEvaluatableItems" , getAllEvaluatableItems) 18 | 19 | router.get("/getAllApprovedEvaluatableItems" , getAllApprovedEvaluatableItems) 20 | 21 | 22 | module.exports = router; -------------------------------------------------------------------------------- /Client/src/view/components/general/ValidationModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ConfirmDialog } from 'react-native-simple-dialogs' 3 | 4 | const ValidationModal = ({message,visibility ,close}) => { 5 | return ( 6 | { 15 | close() 16 | }, 17 | titleStyle: { color: 'green' }, 18 | }} 19 | /> 20 | ) 21 | } 22 | 23 | export default ValidationModal -------------------------------------------------------------------------------- /Client/src/services/OrderService.js: -------------------------------------------------------------------------------- 1 | import http from "./HttpConnection"; 2 | 3 | 4 | 5 | const getAllEvaluatableItems = () => { 6 | return http.get(`/orders/getAllEvaluatableItems`); 7 | } 8 | 9 | const getAllApprovedEvaluatableItems = () => { 10 | return http.get(`/orders/getAllApprovedEvaluatableItems`); 11 | } 12 | 13 | const completeOrder = (data) => { 14 | return http.post(`/orders/completeOrder`,data); 15 | } 16 | 17 | const getAllMyOrders = () => { 18 | return http.get(`/orders/getAllMyOrders`); 19 | } 20 | 21 | const getOrderDetail = (id) => { 22 | return http.get(`/orders/detail/${id}`); 23 | } 24 | 25 | export default { 26 | getAllEvaluatableItems, 27 | getAllApprovedEvaluatableItems, 28 | completeOrder, 29 | getAllMyOrders, 30 | getOrderDetail 31 | } -------------------------------------------------------------------------------- /Server/Routers/store.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express'); 3 | const { getAllBaskettems ,addToBasket ,increaseQuantity,decreaseQuantity,deleteAllBasketItems ,deleteBasketItem ,getAllDeliveryItems} = require('../Controllers/store'); 4 | const { getAccessToRoute } = require('../Middlewares/Authorization/auth') 5 | 6 | const router = express.Router(); 7 | 8 | router.use(getAccessToRoute) 9 | 10 | 11 | router.post("/addToBasket" , addToBasket) 12 | 13 | router.get('/getAllBasketItems',getAllBaskettems) 14 | 15 | router.post('/increaseQuantity' , increaseQuantity) 16 | 17 | router.post('/decreaseQuantity' , decreaseQuantity) 18 | 19 | router.delete('/deleteAllBasketItems' , deleteAllBasketItems) 20 | 21 | router.delete('/deleteBasketItem/:itemId' , deleteBasketItem) 22 | 23 | router.get('/getAllDeliveryItems',getAllDeliveryItems) 24 | 25 | 26 | module.exports = router; -------------------------------------------------------------------------------- /Server/Models/evaluation.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const evaluationSchema = new Schema({ 6 | user : { 7 | type : mongoose.Schema.ObjectId , 8 | ref : "User", 9 | }, 10 | orderItem : { 11 | type : mongoose.Schema.ObjectId , 12 | ref : "OrderItem", 13 | required : [true, "OrderItem must belong to an order"] 14 | }, 15 | product : { 16 | type : mongoose.Schema.ObjectId , 17 | ref : "Product", 18 | required : [true, "Product must belong to an order"] 19 | }, 20 | comment : { 21 | type : mongoose.Schema.ObjectId , 22 | ref : "Comment", 23 | }, 24 | appravolStatus : { 25 | type : Boolean, 26 | default : false 27 | } 28 | 29 | }) 30 | 31 | 32 | module.exports = mongoose.model("Evaluation", evaluationSchema) -------------------------------------------------------------------------------- /Server/Models/orderitem.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const orderItemSchema = new Schema({ 6 | product : { 7 | type : mongoose.Schema.ObjectId , 8 | ref : "Product", 9 | required : [true, "Order must belong to a product"] 10 | }, 11 | order : { 12 | type : mongoose.Schema.ObjectId , 13 | ref : "Order", 14 | required : [true, "Order must belong to an order"] 15 | }, 16 | quantity : { 17 | type : Number, 18 | default : 0 19 | }, 20 | selectedSize : { 21 | type : String, 22 | }, 23 | date_added : { 24 | type : Date, 25 | default : Date.now 26 | }, 27 | orderStatus : { 28 | type : Boolean, 29 | default : false 30 | }, 31 | 32 | }) 33 | 34 | 35 | module.exports = mongoose.model("OrderItem", orderItemSchema) -------------------------------------------------------------------------------- /Server/Models/comment.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const commentSchema = new Schema({ 6 | 7 | user : { 8 | type : mongoose.Schema.ObjectId , 9 | ref : "User", 10 | }, 11 | orderItem : { 12 | type : mongoose.Schema.ObjectId , 13 | ref : "OrderItem", 14 | }, 15 | content : { 16 | type : String, 17 | required : [true, "Please provide a content "], 18 | trim : true, 19 | }, 20 | nameVisible : { 21 | type : Boolean, 22 | default : false 23 | }, 24 | rating : { 25 | type : Number, 26 | default : 0 27 | }, 28 | image : { 29 | type : String, 30 | }, 31 | created_at : { 32 | type : Date, 33 | default : Date.now 34 | }, 35 | 36 | }) 37 | 38 | 39 | module.exports = mongoose.model("Comment", commentSchema) -------------------------------------------------------------------------------- /Server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const dotenv = require("dotenv") 3 | const app = express() ; 4 | 5 | app.use(express.json()) 6 | 7 | const routers = require("./Routers/index") 8 | 9 | const path =require('path') 10 | 11 | const {customErrorHandler} = require("./Middlewares/Errors/customErrorHandler") 12 | 13 | 14 | dotenv.config({ 15 | path : './Config/env/config.env' 16 | }) 17 | 18 | 19 | const connectDatabase = require("./Helpers/database/connectDatabase") 20 | 21 | connectDatabase() 22 | 23 | 24 | const PORT = process.env.PORT 25 | 26 | // Routers Middleware 27 | 28 | app.use("/api" ,routers ) 29 | 30 | 31 | // Error Handler 32 | 33 | app.use(customErrorHandler) 34 | 35 | 36 | // Static Files 37 | app.use(express.static(path.join(__dirname , "public") )) 38 | 39 | app.listen(PORT, () => { 40 | 41 | console.log(`App started on ${PORT} : ${process.env.NODE_ENV}`) 42 | }) -------------------------------------------------------------------------------- /Server/Controllers/banner.js: -------------------------------------------------------------------------------- 1 | const asyncErrorWrapper = require("express-async-handler"); 2 | const Banner = require("../Models/banner") 3 | 4 | const getAllBanners = asyncErrorWrapper(async (req, res, next) => { 5 | 6 | const banners = await Banner.find(); 7 | 8 | return res.status(200).json({ 9 | success: true, 10 | banners 11 | }); 12 | } 13 | ); 14 | 15 | const addBanner = asyncErrorWrapper(async (req, res, next) => { 16 | 17 | const {bannerName , shippingTime ,deliveryTime } = req.body; 18 | 19 | const newBanner = await Banner.create({ 20 | name : bannerName, 21 | shippingTime, 22 | deliveryTime, 23 | image : req.savedImage 24 | }); 25 | 26 | return res.status(200).json({ 27 | success: true, 28 | newBanner 29 | }); 30 | }); 31 | 32 | 33 | module.exports = { 34 | addBanner , 35 | getAllBanners, 36 | } -------------------------------------------------------------------------------- /Client/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Client", 4 | "slug": "Client", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "updates": { 15 | "fallbackToCacheTimeout": 0 16 | }, 17 | "assetBundlePatterns": [ 18 | "**/*" 19 | ], 20 | "ios": { 21 | "supportsTablet": true 22 | }, 23 | "android": { 24 | "adaptiveIcon": { 25 | "foregroundImage": "./assets/adaptive-icon.png", 26 | "backgroundColor": "#FFFFFF" 27 | } 28 | }, 29 | "web": { 30 | "favicon": "./assets/favicon.png" 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Client/src/services/HttpConnection.js: -------------------------------------------------------------------------------- 1 | import AsyncStorage from "@react-native-async-storage/async-storage"; 2 | import axios from "axios"; 3 | import { NGROK_URL } from '@env' 4 | 5 | const axiosInstance = axios.create({ 6 | baseURL: `${NGROK_URL}` + "/api", 7 | headers: { 8 | "Content-type": "application/json", 9 | "Access-Control-Allow-Origin": "*", 10 | "Cache-Control": "no-cache" 11 | } 12 | }); 13 | 14 | axiosInstance.interceptors.request.use( 15 | async (config) => { 16 | config.headers['Authorization'] = `Bearer ${JSON.parse(await AsyncStorage.getItem("accessToken")) 17 | }`; 18 | return config; 19 | }, 20 | error => { 21 | console.log("error", error) 22 | return Promise.reject(error); 23 | } 24 | ); 25 | 26 | 27 | axiosInstance.interceptors.response.use((response) => { 28 | return response; 29 | 30 | }, (error) => { 31 | return Promise.reject(error.response); 32 | }); 33 | export default axiosInstance; -------------------------------------------------------------------------------- /Server/Middlewares/Errors/customErrorHandler.js: -------------------------------------------------------------------------------- 1 | const CustomError = require('../../Helpers/error/CustomError') 2 | 3 | const customErrorHandler = (err, req, res, next) => { 4 | 5 | console.log("Custom Error Handler ", err.name) 6 | 7 | if (err.name === 'SyntaxError') { 8 | 9 | err = new CustomError('Unexpected Sytax ', 400) 10 | } 11 | if (err.name === 'ValidationError') { 12 | 13 | err = new CustomError(err.message, 400) 14 | 15 | } 16 | if (err.code === 11000) { 17 | 18 | err = new CustomError("Please enter different values for unique field", 400) 19 | } 20 | 21 | if (err.name === "CastError") { 22 | 23 | err = new CustomError("Please provide a valid id ", 400) 24 | 25 | } 26 | 27 | console.log(err.name, err.message, err.status) 28 | 29 | return res.status(err.status || 500).json({ 30 | success: false, 31 | message: err.message 32 | }) 33 | 34 | } 35 | 36 | 37 | module.exports = { 38 | customErrorHandler 39 | }; -------------------------------------------------------------------------------- /Server/Routers/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const auth = require('./auth') 3 | const user = require('./user') 4 | const product = require('./product') 5 | const favorite = require('./favorite') 6 | const store = require('./store') 7 | const order = require('./order') 8 | const address = require('./address') 9 | const bankCard = require('./bankCard') 10 | const banner = require('./banner') 11 | const comment = require('./comment') 12 | const category = require('./category') 13 | 14 | const router = express.Router() 15 | 16 | router.use('/auth' ,auth) 17 | 18 | router.use('/user' ,user) 19 | 20 | router.use('/products' ,product) 21 | 22 | router.use('/favorites' ,favorite) 23 | 24 | router.use('/store' ,store) 25 | 26 | router.use('/orders' ,order) 27 | 28 | router.use('/address' ,address) 29 | 30 | router.use('/bankCards' ,bankCard) 31 | 32 | router.use('/banners' ,banner) 33 | 34 | router.use('/comments' ,comment) 35 | 36 | router.use('/categories' ,category) 37 | 38 | 39 | module.exports = router -------------------------------------------------------------------------------- /Client/src/services/StoreService.js: -------------------------------------------------------------------------------- 1 | import http from "./HttpConnection"; 2 | 3 | const addToBasket = (data) => { 4 | return http.post(`/store/addToBasket`,data); 5 | } 6 | 7 | const getAllBasketItems = () => { 8 | return http.get(`/store/getAllBasketItems`); 9 | } 10 | 11 | const increaseQuanity = (data) => { 12 | return http.post(`/store/increaseQuantity`,data) 13 | 14 | } 15 | const decreaseQuanity = (data) => { 16 | return http.post(`/store/decreaseQuantity`,data) 17 | } 18 | 19 | const deleteBasketItem = (id) => { 20 | return http.delete(`/store/deleteBasketItem/${id}`); 21 | } 22 | 23 | const deleteAllBasketItems = () => { 24 | return http.delete(`/store/deleteAllBasketItems`); 25 | } 26 | 27 | const getAllDeliveryItems = () => { 28 | return http.get(`/store/getAllDeliveryItems`); 29 | } 30 | 31 | export default { 32 | addToBasket, 33 | getAllBasketItems, 34 | increaseQuanity, 35 | decreaseQuanity, 36 | deleteBasketItem, 37 | deleteAllBasketItems, 38 | getAllDeliveryItems, 39 | } -------------------------------------------------------------------------------- /Client/src/services/ProductService.js: -------------------------------------------------------------------------------- 1 | import http from "./HttpConnection"; 2 | 3 | const getAllProducts = () => { 4 | return http.get("/products/all"); 5 | }; 6 | const getDetailProduct = (id) => { 7 | return http.get(`/products/${id}/detail`); 8 | }; 9 | 10 | const lastViewedProducts = () => { 11 | return http.get("/products/lastViewed/all"); 12 | }; 13 | 14 | const getProductsInSameSubCategory = (id) => { 15 | return http.get(`/products/${id}/inSameSubCategory`); 16 | }; 17 | 18 | const getRecommendationsForItemsInBasket = () => { 19 | return http.get("/products/recommendationsForItemsInBasket"); 20 | }; 21 | 22 | const getSuggestedSearchWords = () => { 23 | return http.get("/products/suggestedSearchWords/all"); 24 | }; 25 | 26 | const searchProduct = (queryObj) => { 27 | return http.post("/products/search",queryObj); 28 | }; 29 | export default { 30 | getAllProducts, 31 | getDetailProduct, 32 | lastViewedProducts, 33 | getProductsInSameSubCategory, 34 | getRecommendationsForItemsInBasket, 35 | getSuggestedSearchWords, 36 | searchProduct 37 | } -------------------------------------------------------------------------------- /Server/Helpers/Libraries/imageUpload.js: -------------------------------------------------------------------------------- 1 | 2 | const multer = require('multer') 3 | const path = require('path') 4 | const CustomError = require('../error/CustomError') 5 | 6 | const storage = multer.diskStorage({ 7 | 8 | destination : function(req, file ,cb) { 9 | 10 | const rootDir = path.dirname(require.main.filename) ; 11 | 12 | cb(null , path.join(rootDir , "/public/uploads")) 13 | }, 14 | filename : function(req,file,cb ) { 15 | 16 | const extensions = file.mimetype.split("/")[1] ; 17 | 18 | req.savedImage = "image_" + Date.now() + "." + extensions 19 | 20 | cb(null ,req.savedImage) 21 | 22 | } 23 | 24 | }) 25 | 26 | const fileFilter = (req,file ,cb ) => { 27 | 28 | allowedMimeTypes = ["image/jpg" ,"image/jpeg","image/png","image/gif"] 29 | 30 | if (! allowedMimeTypes.includes(file.mimetype)) { 31 | 32 | return cb(new CustomError("Please provide a valid image file",400),false); 33 | 34 | } 35 | 36 | return cb(null ,true ) 37 | 38 | } 39 | 40 | const imageUpload = multer({storage ,fileFilter}) 41 | 42 | module.exports = imageUpload ; -------------------------------------------------------------------------------- /Server/Middlewares/Authorization/auth.js: -------------------------------------------------------------------------------- 1 | const asyncErrorWrapper = require("express-async-handler"); 2 | const CustomError = require('../../Helpers/error/CustomError') 3 | const jwt = require("jsonwebtoken") 4 | const { isTokenIncluded, getAccessTokenFromHeader } = require('../../Helpers/authorization/tokenHelpers') 5 | 6 | 7 | const getAccessToRoute = asyncErrorWrapper((req, res, next) => { 8 | const { JWT_SECRET_KEY } = process.env 9 | 10 | if (!isTokenIncluded(req)) { 11 | 12 | return next(new CustomError("You are not authorized to access this route ", 401)) 13 | 14 | } 15 | 16 | const accessToken = getAccessTokenFromHeader(req) 17 | 18 | jwt.verify(accessToken, JWT_SECRET_KEY, (err, decoded) => { 19 | 20 | if (err) { 21 | return next(new CustomError("You are not authorized to access this route", 401)) 22 | } 23 | 24 | req.user = { 25 | id: decoded.id, 26 | name: decoded.name, 27 | email: decoded.email 28 | } 29 | 30 | }) 31 | 32 | next(); 33 | 34 | }) 35 | 36 | 37 | module.exports = { 38 | getAccessToRoute 39 | } -------------------------------------------------------------------------------- /Client/src/Context/CategoryContext.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from 'react' 2 | import CategoryService from '../services/CategoryService'; 3 | 4 | export const CategoryContext = React.createContext(); 5 | 6 | const CategoryContextProvider = ({ children }) => { 7 | const [allCategories, setAllCategories] = useState([]); 8 | 9 | const getAllCategories = async () => { 10 | try { 11 | 12 | const { data } = await CategoryService.getAllCategories(); 13 | 14 | setAllCategories(data.categories); 15 | } 16 | catch (err) { 17 | console.log("get all categories error :", err) 18 | setAllCategories([]); 19 | 20 | } 21 | 22 | } 23 | 24 | useEffect(() => { 25 | getAllCategories() 26 | }, []) 27 | 28 | 29 | return ( 30 | 33 | {children} 34 | 35 | ) 36 | 37 | } 38 | 39 | 40 | export const useCategories = () => { 41 | return useContext(CategoryContext) 42 | } 43 | 44 | export default CategoryContextProvider -------------------------------------------------------------------------------- /Server/Routers/product.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { addProduct,addImageForProduct ,getAllProducts ,getDetailProduct ,editProduct,lastViewedProducts, getProductsInSameSubCategory ,getRecommendationsForItemsInBasket,getSuggestedSearchWords ,searchProduct } = require('../Controllers/product'); 3 | const { getAccessToRoute } = require('../Middlewares/Authorization/auth') 4 | 5 | const imageUpload = require('../Helpers/Libraries/imageUpload'); 6 | 7 | const router = express.Router(); 8 | 9 | 10 | router.get('/all',getAllProducts) 11 | 12 | router.post("/add" , addProduct) 13 | 14 | router.get("/:id/detail" , getAccessToRoute,getDetailProduct) 15 | 16 | router.post("/:id/addImage" , [imageUpload.single("product_image")] 17 | ,addImageForProduct) 18 | 19 | router.put("/edit/:id" ,editProduct) 20 | 21 | router.get("/lastViewed/all", getAccessToRoute , lastViewedProducts) 22 | 23 | router.get("/:id/inSameSubCategory", getProductsInSameSubCategory) 24 | 25 | router.get("/recommendationsForItemsInBasket", getAccessToRoute, getRecommendationsForItemsInBasket) 26 | 27 | router.get("/suggestedSearchWords/all", getSuggestedSearchWords) 28 | 29 | router.post('/search',searchProduct) 30 | 31 | 32 | module.exports = router; -------------------------------------------------------------------------------- /Server/Helpers/authorization/tokenHelpers.js: -------------------------------------------------------------------------------- 1 | const generateJwtFromUser = require('../../Models/user') 2 | 3 | const sendJwtToClient = (user, res) => { 4 | 5 | const token = user.generateJwtFromUser(); 6 | 7 | const { NODE_ENV } = process.env; 8 | 9 | return res 10 | .status(200) 11 | .cookie("access_token", token, { 12 | httpOnly: true, 13 | secure: NODE_ENV === "development" ? false : true 14 | }) 15 | .json({ 16 | success: true, 17 | access_token: token, 18 | data: { 19 | id: user._id, 20 | email: user.email, 21 | username: user.username, 22 | } 23 | }) 24 | 25 | 26 | } 27 | 28 | const isTokenIncluded = (req) => { 29 | return ( 30 | req.headers.authorization && req.headers.authorization.startsWith("Bearer") 31 | ) 32 | } 33 | 34 | 35 | const getAccessTokenFromHeader = (req) => { 36 | 37 | const authorization = req.headers.authorization 38 | 39 | const access_token = authorization.substr(7).trim() 40 | 41 | return access_token 42 | } 43 | 44 | 45 | module.exports = { sendJwtToClient, isTokenIncluded, getAccessTokenFromHeader }; -------------------------------------------------------------------------------- /Server/Models/address.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const addressSchema = new Schema({ 6 | 7 | user: { 8 | type: mongoose.Schema.ObjectId, 9 | ref: "User", 10 | required: [true, "Order must belong to a user"] 11 | }, 12 | title: { 13 | type: String, 14 | required: [true, "Please provide a title"], 15 | trim: true, 16 | }, 17 | name: { 18 | type: String, 19 | required: [true, "Please provide a user name"], 20 | }, 21 | surname: { 22 | type: String, 23 | required: [true, "Please provide a user surname"], 24 | }, 25 | phone: { 26 | type: String, 27 | required: [true, "Please provide a phone"], 28 | }, 29 | city: { 30 | type: String, 31 | required: [true, "Please provide a city"], 32 | }, 33 | district: { 34 | type: String, 35 | required: [true, "Please provide a district"], 36 | }, 37 | neighborhood: { 38 | type: String, 39 | required: [true, "Please provide a neighborhood"], 40 | }, 41 | detail: { 42 | type: String, 43 | required: [true, "Please provide a explanation"], 44 | }, 45 | timestamp : { 46 | type : Date, 47 | default : Date.now 48 | } 49 | 50 | }) 51 | 52 | 53 | module.exports = mongoose.model("Address", addressSchema) -------------------------------------------------------------------------------- /Server/Models/bankCard.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const bankCardSchema = new Schema({ 6 | 7 | user: { 8 | type: mongoose.Schema.ObjectId, 9 | ref: "User", 10 | required: [true, "Order must belong to a user"] 11 | }, 12 | number: { 13 | type: String, 14 | required: [true, "Please provide a number"], 15 | unique: true, 16 | trim: true, 17 | maxLength: [16, "Please maximum 16 characters"], 18 | }, 19 | name: { 20 | type: String, 21 | required: [true, "Please provide a name"], 22 | trim: true, 23 | }, 24 | cvv: { 25 | type: String, 26 | required: [true, "Please provide a cvv"], 27 | trim: true, 28 | maxLength: [3, "Please maximum 3 characters"], 29 | }, 30 | expiredMonth: { 31 | type: String, 32 | required: [true, "Please provide a expiredMonth"], 33 | trim: true, 34 | maxLength: [2, "Please maximum 2 characters"], 35 | }, 36 | expiredYear: { 37 | type: String, 38 | required: [true, "Please provide a expiredYear"], 39 | maxLength: [4, "Please maximum 2 characters"], 40 | trim: true, 41 | }, 42 | createdAt: { 43 | type: Date, 44 | default: Date.now 45 | }, 46 | 47 | }) 48 | 49 | 50 | module.exports = mongoose.model("BankCard", bankCardSchema) -------------------------------------------------------------------------------- /Server/Models/product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const productSchema = new Schema({ 6 | code: { 7 | type: String, 8 | required: [true, "Please provide a code "], 9 | trim: true, 10 | }, 11 | name: { 12 | type: String, 13 | required: [true, "Please provide a name "], 14 | trim: true, 15 | }, 16 | price: { 17 | type: Number, 18 | default: 0, 19 | }, 20 | sizes: [], 21 | about: [], 22 | properties: {}, 23 | images: [{ 24 | type: String, 25 | }], 26 | likes: [{ 27 | type: mongoose.Schema.ObjectId, 28 | ref: "User" 29 | }], 30 | likeCount: { 31 | type: Number, 32 | default: 0 33 | }, 34 | 35 | seller: { 36 | type: 'String', 37 | required: [true, "Please provide a seller "], 38 | }, 39 | banner: { 40 | type: mongoose.Schema.ObjectId, 41 | ref: "Banner", 42 | }, 43 | comments: [{ 44 | type: mongoose.Schema.ObjectId, 45 | ref: "Comment" 46 | }], 47 | averageRating: { 48 | type: Number, 49 | default: 0 50 | }, 51 | subCategory: { 52 | type: mongoose.Schema.ObjectId, 53 | ref: "SubCategory", 54 | }, 55 | createdAt: { 56 | type: Date, 57 | default: Date.now 58 | }, 59 | 60 | }) 61 | 62 | 63 | module.exports = mongoose.model("Product", productSchema) -------------------------------------------------------------------------------- /Server/Controllers/user.js: -------------------------------------------------------------------------------- 1 | const CustomError = require("../Helpers/error/CustomError") 2 | const asyncErrorWrapper = require("express-async-handler"); 3 | const { comparePassword } = require('../Helpers/input/inputHelpers') 4 | const User = require("../Models/user") 5 | 6 | const changeEmail = asyncErrorWrapper(async (req, res, next) => { 7 | 8 | const { newEmail } = req.body; 9 | 10 | const isUser = await User.findOne({ email: newEmail }) 11 | 12 | if (isUser) { 13 | return next(new CustomError("Bu email daha önce kullanılmış", 400)) 14 | } 15 | const user = await User.findById(req.user.id) 16 | 17 | user.email = newEmail 18 | 19 | await user.save() 20 | 21 | return res.status(200).json({ 22 | success: true, 23 | message: "Email başarıyla değiştirildi", 24 | }) 25 | }) 26 | 27 | 28 | const changePassword = asyncErrorWrapper(async (req, res, next) => { 29 | const { currentPass, newPass } = req.body; 30 | 31 | const user = await User.findById(req.user.id).select("+password") 32 | 33 | if (!comparePassword(currentPass.value, user.password)) { 34 | 35 | return next(new CustomError('Mevcut şifrenizi yanlış girdiniz.', 400)) 36 | 37 | } 38 | 39 | user.password = newPass.value 40 | await user.save() 41 | 42 | return res.status(200).json({ 43 | success: true, 44 | message: "Şifre başarıyla değiştirildi", 45 | }) 46 | 47 | }) 48 | 49 | 50 | module.exports = { 51 | changeEmail, 52 | changePassword 53 | } -------------------------------------------------------------------------------- /Client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthContextProvider from './src/Context/AuthContext'; 3 | import Navigation from './src/Navigation'; 4 | import ProductContextProvider from './src/Context/ProductContext'; 5 | import { MenuProvider } from 'react-native-popup-menu'; 6 | import StoreContextProvider from './src/Context/StoreContext'; 7 | import FavoriteContextProvider from './src/Context/FavoriteContext'; 8 | import CategoryContextProvider from './src/Context/CategoryContext'; 9 | import Toast from 'react-native-toast-message'; 10 | import ToastMessage from './src/view/components/general/ToastMessage'; 11 | import COLORS from './src/consts/colors'; 12 | const toastConfig = { 13 | success : ({ text1, props }) => ( 14 | 15 | ), 16 | customToast: ({ text1, props }) => ( 17 | 18 | ) 19 | }; 20 | const App = () => { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | 39 | export default App 40 | -------------------------------------------------------------------------------- /Client/src/Context/AuthContext.js: -------------------------------------------------------------------------------- 1 | import AsyncStorage from '@react-native-async-storage/async-storage'; 2 | import React, { useState, useContext, useEffect } from 'react' 3 | 4 | export const AuthContext = React.createContext(); 5 | 6 | const AuthContextProvider = ({ children }) => { 7 | const [userInfo, setUserInfo] = useState({}); 8 | const [splashLoading, setSplashLoading] = useState(false); 9 | 10 | const logout = async () => { 11 | await AsyncStorage.removeItem('userInfo'); 12 | await AsyncStorage.removeItem('accessToken'); 13 | setUserInfo({}); 14 | } 15 | 16 | const isLoggedIn = async () => { 17 | setSplashLoading(true); 18 | try { 19 | 20 | let userInfo = await AsyncStorage.getItem('userInfo'); 21 | userInfo = JSON.parse(userInfo); 22 | if (userInfo) { 23 | setUserInfo(userInfo); 24 | } 25 | 26 | } 27 | catch (err) { 28 | console.log("isLoggedIn error :", err) 29 | } 30 | setSplashLoading(false); 31 | } 32 | 33 | useEffect(() => { 34 | isLoggedIn(); 35 | }, []) 36 | 37 | return ( 38 | 46 | {children} 47 | 48 | ) 49 | 50 | } 51 | 52 | export const AuthState = () => { 53 | return useContext(AuthContext) 54 | } 55 | 56 | export default AuthContextProvider -------------------------------------------------------------------------------- /Client/src/view/screens/EditCommentScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import CommentService from '../../services/CommentService' 3 | import CommentPostForm from '../components/general/CommentPostForm' 4 | import Toast from 'react-native-toast-message' 5 | 6 | const EditCommentScreen = ({ route,navigation }) => { 7 | 8 | const { item } = route.params; 9 | const [disabled , setDisabled] = useState(false) 10 | 11 | const editComment = async (content,rating,nameVisible) => { 12 | setDisabled(true) 13 | try { 14 | await CommentService.editComment({ 15 | content, 16 | rating, 17 | nameVisible, 18 | orderItemId: item.orderItem, 19 | productId : item.product._id 20 | }) 21 | navigation.navigate('MyEvaluations') 22 | } 23 | catch (err) { 24 | Toast.show({ 25 | type: 'customToast', 26 | position: 'bottom', 27 | text1: 'İstek iletilirken bir sorun oluştu.Tekrar deneyiniz.', 28 | visibilityTime: 2000, 29 | autoHide: true, 30 | bottomOffset: 0, 31 | }); 32 | setTimeout(() => { 33 | setDisabled(false) 34 | } , 2000); 35 | } 36 | } 37 | 38 | return ( 39 | 45 | ) 46 | } 47 | 48 | export default EditCommentScreen -------------------------------------------------------------------------------- /Client/src/view/components/productDetail/ProductBadges.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, StyleSheet, Text } from 'react-native' 3 | import COLORS from '../../../consts/colors' 4 | 5 | const ProductBadges = ({ properties }) => { 6 | return ( 7 | 8 | 9 | {properties && Object.keys(properties).slice(0, 3).map((key, index) => ( 10 | 11 | {key} 12 | 15 | {properties[key]} 16 | 17 | 18 | ))} 19 | 20 | ) 21 | } 22 | 23 | const styles = StyleSheet.create({ 24 | container : { 25 | flexDirection: 'row', 26 | alignItems: 'center', 27 | marginVertical: 10, 28 | borderBottomColor: '#f1f1f1', 29 | borderBottomWidth: 1, 30 | paddingBottom: 15, 31 | }, 32 | badgeItem: { 33 | marginRight: 15, 34 | backgroundColor : 'rgba(0,183,97,0.07)', 35 | paddingVertical: 4, 36 | borderRadius : 5, 37 | paddingHorizontal: 7, 38 | }, 39 | badgeTitle : { 40 | fontSize: 9, 41 | fontWeight: 'bold', 42 | color: COLORS.green, 43 | }, 44 | badgeContent : { 45 | fontSize: 10, 46 | fontWeight: 'bold', 47 | color:'#78787a', 48 | marginTop: 3.7, 49 | } 50 | }) 51 | 52 | export default ProductBadges -------------------------------------------------------------------------------- /Client/src/view/screens/AddCommentScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import CommentService from '../../services/CommentService' 3 | import CommentPostForm from '../components/general/CommentPostForm' 4 | import Toast from 'react-native-toast-message' 5 | 6 | const AddCommentScreen = ({ route,navigation }) => { 7 | 8 | const { item } = route.params; 9 | const [disabled , setDisabled] = useState(false) 10 | 11 | const addComment = async (content,rating,nameVisible) => { 12 | setDisabled(true) 13 | try { 14 | await CommentService.addComment({ 15 | content, 16 | rating, 17 | nameVisible, 18 | orderItemId: item.orderItem._id, 19 | productId : item.product._id 20 | }) 21 | 22 | navigation.navigate('MyEvaluations') 23 | } 24 | catch (err) { 25 | Toast.show({ 26 | type: 'customToast', 27 | position: 'bottom', 28 | text1: 'İstek iletilirken bir sorun oluştu.Tekrar deneyiniz.', 29 | visibilityTime: 2000, 30 | autoHide: true, 31 | bottomOffset: 0, 32 | }); 33 | setTimeout(() => { 34 | setDisabled(false) 35 | } , 2000); 36 | } 37 | 38 | } 39 | 40 | return ( 41 | 47 | ) 48 | } 49 | 50 | export default AddCommentScreen -------------------------------------------------------------------------------- /Client/src/view/screens/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { StyleSheet, FlatList } from 'react-native' 3 | import COLORS from '../../consts/colors' 4 | import ProductCard from '../components/general/ProductCard' 5 | import { ProductsState } from '../../Context/ProductContext' 6 | import { SafeAreaView } from 'react-native-safe-area-context' 7 | import SearchSection from '../components/general/SearchSection' 8 | 9 | const HomeScreen = ({ navigation }) => { 10 | 11 | const { allProducts, getAllProducts } = ProductsState(); 12 | 13 | useEffect(() => { 14 | const onfocus = navigation.addListener('focus', () => { 15 | getAllProducts() 16 | } 17 | ); 18 | return onfocus; 19 | }, [navigation]); 20 | 21 | 22 | return ( 23 | 24 | 27 | 28 | index} 36 | numColumns={2} 37 | columnWrapperStyle={{ justifyContent: 'space-between' }} 38 | renderItem={({ item }) => ( 39 | 40 | )} 41 | /> 42 | 43 | 44 | ) 45 | } 46 | 47 | 48 | const styles = StyleSheet.create({ 49 | container: { 50 | height: '100%', 51 | backgroundColor: COLORS.white, 52 | }, 53 | 54 | }) 55 | 56 | export default HomeScreen -------------------------------------------------------------------------------- /Client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "@react-native-async-storage/async-storage": "^1.17.11", 13 | "@react-navigation/bottom-tabs": "^6.4.1", 14 | "@react-navigation/native": "^6.0.14", 15 | "@react-navigation/native-stack": "^6.9.2", 16 | "axios": "^1.3.2", 17 | "expo": "~47.0.8", 18 | "expo-status-bar": "~1.4.2", 19 | "react": "18.1.0", 20 | "react-native": "0.70.5", 21 | "react-native-animated-loader": "^1.0.0", 22 | "react-native-bouncy-checkbox": "^3.0.7", 23 | "react-native-element-textinput": "^2.2.0", 24 | "react-native-gesture-bottom-sheet": "^1.1.0", 25 | "react-native-indicators": "^0.17.0", 26 | "react-native-modal": "^13.0.1", 27 | "react-native-popup-menu": "^0.16.1", 28 | "react-native-progress": "^5.0.0", 29 | "react-native-ratings": "^8.1.0", 30 | "react-native-safe-area-context": "^4.4.1", 31 | "react-native-screens": "^3.18.2", 32 | "react-native-simple-dialogs": "^1.5.0", 33 | "react-native-step-indicator": "^1.0.3", 34 | "react-native-svg": "13.4.0", 35 | "react-native-toast-message": "^2.1.6" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.12.9", 39 | "react-native-dotenv": "^3.4.7" 40 | }, 41 | "private": true 42 | } 43 | -------------------------------------------------------------------------------- /Server/Controllers/address.js: -------------------------------------------------------------------------------- 1 | const CustomError = require("../Helpers/error/CustomError"); 2 | const asyncErrorWrapper = require("express-async-handler"); 3 | const Address = require("../Models/address") 4 | 5 | const getAllAddresses = asyncErrorWrapper(async (req, res, next) => { 6 | 7 | const addresses = await Address.find({ user: req.user.id }); 8 | 9 | return res.status(200).json({ 10 | success: true, 11 | addresses 12 | }); 13 | 14 | }); 15 | 16 | 17 | 18 | const addAddress = asyncErrorWrapper(async (req, res, next) => { 19 | 20 | const { address } = req.body; 21 | 22 | if (!address) { 23 | return next(new CustomError("Please provide an address", 400)); 24 | } 25 | 26 | const newAddress = await Address.create({ 27 | ...address, 28 | user: req.user.id 29 | }); 30 | 31 | return res.status(200).json({ 32 | success: true, 33 | newAddress 34 | }); 35 | }); 36 | 37 | 38 | const editAddress = asyncErrorWrapper(async (req, res, next) => { 39 | 40 | const { address, id } = req.body; 41 | 42 | if (!address) { 43 | return next(new CustomError("Please provide an address", 400)); 44 | } 45 | 46 | const editedAddress = await Address.findByIdAndUpdate(id, { 47 | ...address, 48 | user: req.user.id 49 | }); 50 | 51 | return res.status(200).json({ 52 | success: true, 53 | editedAddress 54 | }); 55 | }); 56 | 57 | 58 | const deleteAddress = asyncErrorWrapper(async (req, res, next) => { 59 | 60 | const { id } = req.params; 61 | 62 | if (!id) { 63 | return next(new CustomError("Please provide an id", 400)); 64 | } 65 | 66 | await Address.findByIdAndDelete(id); 67 | 68 | return res.status(200).json({ 69 | success: true, 70 | message: "address deleted", 71 | }); 72 | }); 73 | 74 | module.exports = { 75 | getAllAddresses, 76 | addAddress, 77 | editAddress, 78 | deleteAddress 79 | } -------------------------------------------------------------------------------- /Server/Controllers/favorite.js: -------------------------------------------------------------------------------- 1 | const asyncErrorWrapper = require("express-async-handler"); 2 | const User = require("../Models/user"); 3 | const Product = require("../Models/product"); 4 | 5 | const getAllFavoriteItems = asyncErrorWrapper(async (req, res, next) => { 6 | const user = await User.findById(req.user.id) 7 | .select('') 8 | .populate({ 9 | path: "likedProducts", 10 | select: "name price about images sizes seller averageRating comments" 11 | }) 12 | 13 | return res.status(200).json({ 14 | success: true, 15 | favorites: user.likedProducts 16 | }) 17 | 18 | }) 19 | 20 | const addItemToFavoritelist = asyncErrorWrapper(async (req, res, next) => { 21 | const { id } = req.params; 22 | const product = await Product.findById(id); 23 | const activeUser = await User.findById(req.user.id); 24 | 25 | product.likes.unshift(req.user.id); 26 | product.likeCount = product.likes.length; 27 | activeUser.likedProducts.unshift(id); 28 | 29 | await activeUser.save(); 30 | await product.save(); 31 | 32 | return res.status(200).json({ 33 | success: true, 34 | product 35 | }); 36 | }); 37 | 38 | 39 | const removeItemFromFavoritelist = asyncErrorWrapper(async (req, res, next) => { 40 | const { id } = req.params; 41 | const product = await Product.findById(id); 42 | const activeUser = await User.findById(req.user.id); 43 | 44 | const index = product.likes.indexOf(req.user.id); 45 | product.likes.splice(index, 1); 46 | product.likeCount = product.likes.length; 47 | activeUser.likedProducts.splice(activeUser.likedProducts.indexOf(id), 1); 48 | await activeUser.save(); 49 | await product.save(); 50 | 51 | return res.status(200).json({ 52 | success: true, 53 | message: "item removed from favorite list" 54 | }); 55 | 56 | }) 57 | 58 | 59 | 60 | module.exports = { 61 | getAllFavoriteItems, 62 | addItemToFavoritelist, 63 | removeItemFromFavoritelist 64 | } -------------------------------------------------------------------------------- /Server/Controllers/category.js: -------------------------------------------------------------------------------- 1 | const CustomError = require("../Helpers/error/CustomError"); 2 | const asyncErrorWrapper = require("express-async-handler"); 3 | const Category = require("../Models/category"); 4 | const Subcategory = require("../Models/subcategory"); 5 | 6 | const getAllCategories = asyncErrorWrapper(async (req, res, next) => { 7 | 8 | const categories = await Category.find().populate("subCategories"); 9 | 10 | return res.status(200).json({ 11 | success: true, 12 | categories 13 | }); 14 | }); 15 | 16 | const getAllSubCategories = asyncErrorWrapper(async (req, res, next) => { 17 | 18 | const subCategories = await Subcategory.find().populate("category"); 19 | 20 | return res.status(200).json({ 21 | success: true, 22 | subCategories 23 | }); 24 | 25 | }); 26 | 27 | const addSubCategory = asyncErrorWrapper(async (req, res, next) => { 28 | 29 | const {name } = req.body; 30 | 31 | const {categoryId} = req.params; 32 | 33 | const category = await Category.findById(categoryId); 34 | 35 | if(!category){ 36 | return next(new CustomError("There is no such category with that id",400)); 37 | } 38 | 39 | const subCategory = await Subcategory.create({ 40 | name, 41 | image : req.savedImage, 42 | category : categoryId 43 | }); 44 | 45 | 46 | category.subCategories.push(subCategory.id); 47 | 48 | await category.save(); 49 | 50 | return res.status(200).json({ 51 | success: true, 52 | subCategory 53 | }); 54 | 55 | }); 56 | 57 | 58 | 59 | const addCategory = asyncErrorWrapper(async (req, res, next) => { 60 | 61 | const {name} = req.body; 62 | 63 | const category = await Category.create({ 64 | name, 65 | image : req.savedImage 66 | }); 67 | 68 | return res.status(200).json({ 69 | success: true, 70 | category 71 | }); 72 | }); 73 | 74 | 75 | module.exports = { 76 | addCategory, 77 | getAllCategories, 78 | getAllSubCategories, 79 | addSubCategory 80 | } -------------------------------------------------------------------------------- /Client/src/Context/FavoriteContext.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from 'react' 2 | import FavoriteService from '../services/FavoriteService'; 3 | import { AuthState } from './AuthContext'; 4 | 5 | export const FavoriteContext = React.createContext(); 6 | 7 | const FavoriteContextProvider = ({ children }) => { 8 | const { userInfo } = AuthState() 9 | const [favoriteList, setFavoriteList] = useState([]); 10 | const [loading, setLoading] = useState(false); 11 | const [favoriteListStatus, setFavoriteListStatus] = useState(false); 12 | 13 | const getAllFavoriteItems = async () => { 14 | const { data } = await FavoriteService.getAllFavoriteItems() 15 | setFavoriteList(data.favorites) 16 | setLoading(false) 17 | } 18 | 19 | const addItemToFavoriteList = async (id) => { 20 | setLoading(true) 21 | 22 | await FavoriteService.addItemToFavoritelist(id); 23 | 24 | setFavoriteListStatus(!favoriteListStatus) 25 | 26 | } 27 | 28 | const removeItemFromFavoriteList = async (id) => { 29 | 30 | setLoading(true) 31 | try { 32 | 33 | await FavoriteService.removeItemFromFavoritelist(id); 34 | setFavoriteListStatus(!favoriteListStatus) 35 | } 36 | catch (error) { 37 | console.log(error) 38 | setLoading(false) 39 | } 40 | } 41 | 42 | useEffect(() => { 43 | getAllFavoriteItems() 44 | }, [favoriteListStatus, userInfo]) 45 | 46 | 47 | 48 | return ( 49 | 59 | {children} 60 | 61 | ) 62 | 63 | } 64 | 65 | 66 | export const FavoriteState = () => { 67 | return useContext(FavoriteContext) 68 | } 69 | 70 | export default FavoriteContextProvider -------------------------------------------------------------------------------- /Server/Controllers/bankCard.js: -------------------------------------------------------------------------------- 1 | const CustomError = require("../Helpers/error/CustomError"); 2 | const asyncErrorWrapper = require("express-async-handler"); 3 | const BankCard = require("../Models/bankCard") 4 | 5 | const getAllBankCards = asyncErrorWrapper(async (req, res, next) => { 6 | 7 | const bankCards = await BankCard.find({user: req.user.id}); 8 | 9 | return res.status(200).json({ 10 | success: true, 11 | bankCards 12 | }); 13 | } 14 | ); 15 | 16 | const addBankCard = asyncErrorWrapper(async (req, res, next) => { 17 | 18 | const {number,cvv,expiredMonth ,expiredYear } = req.body; 19 | 20 | const cardType = number.at(0) === "4" && "Visa" || number.at(0) === "5" && "MasterCard" || number.at(0) === "3" && "American Express" || "1."; 21 | 22 | if(!number) { 23 | return next(new CustomError("Please provide an number", 400)); 24 | } 25 | 26 | const newBankCard = await BankCard.create({ 27 | number , 28 | cvv, 29 | expiredMonth , 30 | expiredYear, 31 | name : cardType + ' Kartım', 32 | user: req.user.id 33 | }); 34 | 35 | return res.status(200).json({ 36 | success: true, 37 | newBankCard 38 | }); 39 | 40 | }); 41 | 42 | 43 | const editBankCard = asyncErrorWrapper(async (req, res, next) => { 44 | 45 | const { name } = req.body; 46 | const {id} = req.params; 47 | 48 | const editedBankCard = await BankCard.findByIdAndUpdate(id, { 49 | name 50 | }); 51 | 52 | return res.status(200).json({ 53 | success: true, 54 | editedBankCard 55 | }); 56 | 57 | }); 58 | 59 | 60 | const deleteBankCard = asyncErrorWrapper(async (req, res, next) => { 61 | 62 | const { id } = req.params; 63 | 64 | const deletedBankCard = await BankCard.findByIdAndDelete(id); 65 | 66 | return res.status(200).json({ 67 | success: true, 68 | deletedBankCard 69 | }); 70 | 71 | }); 72 | 73 | 74 | module.exports = { 75 | getAllBankCards, 76 | editBankCard, 77 | deleteBankCard, 78 | addBankCard 79 | } -------------------------------------------------------------------------------- /Server/Models/order.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Orderitem = require('./orderitem'); 3 | const Evaluation = require('./evaluation'); 4 | 5 | const Schema = mongoose.Schema; 6 | 7 | const orderSchema = new Schema({ 8 | 9 | user : { 10 | type : mongoose.Schema.ObjectId , 11 | ref : "User", 12 | required : [true, "Order must belong to a user"] 13 | }, 14 | complete : { 15 | type : Boolean, 16 | default : false 17 | }, 18 | transaction_id : { 19 | type : String, 20 | required : [true, "Please provide a transaction id"], 21 | unique : true , 22 | trim : true , 23 | }, 24 | dateOrdered : { 25 | type : Date, 26 | default : Date.now 27 | }, 28 | orderAddress : { 29 | type : mongoose.Schema.ObjectId, 30 | ref : "Address", 31 | }, 32 | orderPayment : { 33 | type : mongoose.Schema.ObjectId, 34 | ref : "BankCard", 35 | }, 36 | } 37 | ) 38 | 39 | orderSchema.post("save", async function (next) { 40 | 41 | if(this.complete) { 42 | 43 | const orderItems = await Orderitem.find(({ order: this._id, orderStatus: true })) 44 | 45 | const filteredOrderItems = orderItems.filter((orderItem, index, self) => 46 | index === self.findIndex((t) => ( 47 | t.product._id.toString() === orderItem.product._id.toString() 48 | )) 49 | ) 50 | 51 | filteredOrderItems.forEach(async (orderItem) =>{ 52 | 53 | const evaluation = await Evaluation.findOne({ 54 | user : this.user._id , 55 | product : orderItem.product._id 56 | }) 57 | 58 | if(!evaluation) { 59 | await Evaluation.create({ 60 | user : this.user._id , 61 | orderItem : orderItem._id, 62 | product : orderItem.product._id, 63 | }) 64 | } 65 | }) 66 | } 67 | }) 68 | 69 | 70 | module.exports = mongoose.model("Order", orderSchema) -------------------------------------------------------------------------------- /Client/src/Context/ProductContext.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from 'react' 2 | import ProductService from '../services/ProductService'; 3 | import { MONTHS } from '../consts/time'; 4 | 5 | export const ProductContext = React.createContext(); 6 | 7 | const ProductContextProvider = ({ children }) => { 8 | const [allProducts, setAllProducts] = useState([]); 9 | const [suggestedList, setSuggestedList] = useState([]); 10 | 11 | const getAllProducts = async () => { 12 | try { 13 | const { data } = await ProductService.getAllProducts(); 14 | setAllProducts(data.products); 15 | } 16 | catch (err) { 17 | setAllProducts([]); 18 | } 19 | } 20 | 21 | const getSuggestedSearchWords = async () => { 22 | 23 | try { 24 | const { data } = await ProductService.getSuggestedSearchWords(); 25 | setSuggestedList(data.allOptions); 26 | } 27 | catch (err) { 28 | setSuggestedList([]); 29 | } 30 | } 31 | 32 | 33 | 34 | useEffect(() => { 35 | getAllProducts() 36 | getSuggestedSearchWords() 37 | }, []) 38 | 39 | const editTimestamp = (timestamp, addDay = 0, status = null) => { 40 | const date = new Date(timestamp); 41 | date.setTime(date.getTime() + (addDay * 24 * 60 * 60 * 1000)) 42 | 43 | const localDate = new Date(date); 44 | const month = MONTHS[localDate.getMonth()][0].toUpperCase() + MONTHS[localDate.getMonth()].toLowerCase().substring(1); 45 | 46 | if (status === 'exactDate') { 47 | return `${localDate.getDate()} ${month} ` 48 | } 49 | return `${localDate.getDate()} - ${localDate.getDate() + 1} ${month} ` 50 | } 51 | 52 | return ( 53 | 61 | {children} 62 | 63 | ) 64 | 65 | } 66 | 67 | 68 | export const ProductsState = () => { 69 | return useContext(ProductContext) 70 | } 71 | 72 | export default ProductContextProvider -------------------------------------------------------------------------------- /Client/src/view/components/general/ModalMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Modal from "react-native-modal"; 3 | import { ActivityIndicator, Text, View } from 'react-native' 4 | import { AuthState } from '../../../Context/AuthContext'; 5 | import COLORS from '../../../consts/colors'; 6 | 7 | const ModalMessage = ({message}) => { 8 | const { isModalVisible, setModalVisible, isLoading } = AuthState(); 9 | 10 | return ( 11 | 12 | setModalVisible(false)} 14 | animationIn="bounceIn" 15 | animationOut="bounceOut" 16 | backdropOpacity={0.5} 17 | animationInTiming={650} 18 | animationOutTiming={350} 19 | > 20 | 28 | {!isLoading ? 29 | 35 | 41 | {message} 42 | 43 | setModalVisible(false)} 45 | > 46 | OKEY 47 | 48 | 49 | : 50 | 58 | 61 | 62 | Yükleniyor 63 | 64 | 65 | } 66 | 67 | 68 | 69 | 70 | ) 71 | } 72 | 73 | export default ModalMessage -------------------------------------------------------------------------------- /Server/Controllers/auth.js: -------------------------------------------------------------------------------- 1 | const CustomError = require("../Helpers/error/CustomError") 2 | const { sendJwtToClient } = require('../Helpers/authorization/tokenHelpers') 3 | const { validateUserInput, comparePassword } = require('../Helpers/input/inputHelpers') 4 | const asyncErrorWrapper = require("express-async-handler") 5 | const User = require("../Models/user") 6 | 7 | const register = asyncErrorWrapper(async (req, res, next) => { 8 | 9 | const { username, email, password, role } = req.body 10 | 11 | const isUser = await User.findOne({ email }) 12 | 13 | if (isUser) { 14 | return next(new CustomError("Bu email kullanılamaz. Lütfen başka bir email deneyiniz.", 400)) 15 | } 16 | 17 | const newUser = await User.create({ 18 | username, 19 | email, 20 | password, 21 | role 22 | }) 23 | 24 | sendJwtToClient(newUser, res) 25 | 26 | }) 27 | 28 | 29 | const login = asyncErrorWrapper(async (req, res, next) => { 30 | 31 | const { email, password } = req.body; 32 | 33 | if (!validateUserInput(email, password)) { 34 | 35 | return next(new CustomError("Please check your inputs ", 400)) 36 | } 37 | 38 | const user = await User.findOne({ email }).select("+password") 39 | 40 | if (!user) { 41 | return next(new CustomError('Invalid credentials ', 400)); 42 | } 43 | 44 | if (!comparePassword(password, user.password)) { 45 | 46 | return next(new CustomError('Please check your credentails ', 400)) 47 | 48 | } 49 | 50 | sendJwtToClient(user, res) 51 | 52 | }) 53 | 54 | const getActiveUser = asyncErrorWrapper(async (req, res, next) => { 55 | 56 | const user = await User.findById(req.user.id) 57 | .select("email username ") 58 | .populate("likedProducts", "name price") 59 | 60 | return res.status(200).json({ 61 | success: true, 62 | user 63 | }) 64 | 65 | }) 66 | 67 | 68 | const logout = asyncErrorWrapper(async (req, res, next) => { 69 | 70 | const { NODE_ENV } = process.env; 71 | 72 | return res.status(200) 73 | .cookie({ 74 | httpOnly: true, 75 | expires: new Date(Date.now()), 76 | secure: NODE_ENV === "development" ? false : true 77 | }).json({ 78 | success: true, 79 | message: "Logout Successfull" 80 | }) 81 | 82 | }) 83 | module.exports = { 84 | register, 85 | login, 86 | getActiveUser, 87 | logout 88 | } -------------------------------------------------------------------------------- /Client/src/view/components/productDetail/ImageSlider.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { SafeAreaView, View, Text, StyleSheet, Image, Dimensions, ScrollView } from 'react-native'; 3 | import {NGROK_URL} from '@env' 4 | 5 | const WIDTH = Dimensions.get('window').width; 6 | const HEIGHT = Dimensions.get('window').height; 7 | 8 | const ImageSlider = ({images}) => { 9 | 10 | const [imgActive, setImgActive] = useState(0) 11 | const onChange = (nativeEvent) => { 12 | if(nativeEvent){ 13 | const slide = Math.floor(nativeEvent.contentOffset.x / nativeEvent.layoutMeasurement.width) 14 | if (slide !== imgActive) { 15 | setImgActive(slide) 16 | } 17 | } 18 | } 19 | 20 | return ( 21 | 22 | 23 | onChange(nativeEvent)} 25 | showsHorizontalScrollIndicator={false} 26 | pagingEnabled 27 | horizontal 28 | style={styles.wrap} 29 | > 30 | { images?.map((image, index) => ( 31 | 37 | 38 | ))} 39 | 40 | 41 | 42 | {images?.map((e, index) => ( 43 | 47 | ⬤ 48 | 49 | 50 | ))} 51 | 52 | 53 | 54 | 55 | 56 | ) 57 | } 58 | 59 | const styles = StyleSheet.create({ 60 | container: { 61 | height :'100%', 62 | }, 63 | wrap: { 64 | width: WIDTH, 65 | height: HEIGHT *0.64, 66 | }, 67 | wrapDot: { 68 | backgroundColor: 'rgba(0,0,0,0.7)', 69 | paddingHorizontal: 10, 70 | borderRadius: 10, 71 | position: 'absolute', 72 | bottom: 0, 73 | display: 'flex', 74 | flexDirection: 'row', 75 | alignSelf: 'center', 76 | alignItems: 'center', 77 | justifyContent: 'center' 78 | }, 79 | dotActive: { 80 | margin: 3, 81 | color: "white", 82 | fontSize: 11 83 | }, 84 | dot: { 85 | margin: 3, 86 | color: '#dedede', 87 | fontSize: 10 88 | } 89 | }) 90 | 91 | export default ImageSlider -------------------------------------------------------------------------------- /Client/src/view/screens/AddAddressScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import AddressPostForm from '../components/address/AddressPostForm' 3 | import AddressService from '../../services/AddressService' 4 | import Toast from 'react-native-toast-message' 5 | 6 | const AddAddressScreen = ({ navigation ,route}) => { 7 | 8 | const fields = ["title", "name", "surname", "phone", "detail", "neighborhood", "city", "district"] 9 | const [address, setAddress] = useState( 10 | fields.reduce((acc, field) => { 11 | acc[field] = { 12 | value: '', 13 | error: { 14 | message: '', 15 | status: false 16 | }, 17 | isFocus: false 18 | } 19 | return acc 20 | }, {}) 21 | ) 22 | const [disabled , setDisabled] = useState(false) 23 | 24 | const onSubmit = async (address) => { 25 | setDisabled(true) 26 | let newAddress = { 27 | title: address.title.value, 28 | name: address.name.value, 29 | surname: address.surname.value, 30 | phone: address.phone.value, 31 | detail: address.detail.value, 32 | neighborhood: address.neighborhood.value, 33 | city: address.city.value, 34 | district: address.district.value 35 | } 36 | try { 37 | await AddressService.addAddress( {address:newAddress}) 38 | 39 | if(route?.params?.prevRouteName === "Checkout"){ 40 | navigation.navigate('Checkout', { address: newAddress }) 41 | } 42 | else { 43 | navigation.navigate('MyAddresses') 44 | } 45 | } 46 | catch (error) { 47 | Toast.show({ 48 | type: 'customToast', 49 | position: 'bottom', 50 | text1: 'İstek iletilirken bir sorun oluştu.Tekrar deneyiniz.', 51 | visibilityTime: 2000, 52 | autoHide: true, 53 | bottomOffset: 0, 54 | }); 55 | setTimeout(() => { 56 | setDisabled(false) 57 | } , 2000); 58 | } 59 | } 60 | 61 | return ( 62 | 69 | ) 70 | } 71 | 72 | export default AddAddressScreen 73 | -------------------------------------------------------------------------------- /Client/src/view/components/general/CardImageSlider.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { SafeAreaView, View, Text, StyleSheet, Image, ScrollView } from 'react-native'; 3 | import {NGROK_URL} from '@env' 4 | 5 | const CardImageSlider = ({images}) => { 6 | const [imgActive, setImgActive] = useState(0) 7 | 8 | const onChange = (nativeEvent) => { 9 | if(nativeEvent){ 10 | const slide = Math.floor(nativeEvent.contentOffset.x / nativeEvent.layoutMeasurement.width) 11 | if (slide !== imgActive) { 12 | setImgActive(slide) 13 | } 14 | 15 | } 16 | } 17 | 18 | return ( 19 | 20 | 21 | onChange(nativeEvent)} 23 | showsHorizontalScrollIndicator={false} 24 | pagingEnabled 25 | horizontal 26 | style={styles.wrap} 27 | > 28 | { images?.map((image, index) => ( 29 | 37 | ))} 38 | 39 | 40 | 41 | {images?.map((e, index) => ( 42 | 46 | ⬤ 47 | 48 | 49 | ))} 50 | 51 | 52 | 53 | 54 | 55 | ) 56 | } 57 | 58 | const styles = StyleSheet.create({ 59 | container: { 60 | height :'100%', 61 | width: 155, 62 | flexDirection: 'row', 63 | justifyContent: 'center', 64 | alignItems: 'center', 65 | zIndex: 10, 66 | }, 67 | wrap: { 68 | height: '100%', 69 | width: 150, 70 | }, 71 | wrapDot: { 72 | backgroundColor: 'rgba(0,0,0,0.6)', 73 | paddingHorizontal: 5, 74 | borderRadius: 10, 75 | position: 'absolute', 76 | bottom: 5, 77 | display: 'flex', 78 | flexDirection: 'row', 79 | alignSelf: 'center', 80 | alignItems: 'center', 81 | justifyContent: 'center' 82 | 83 | }, 84 | dotActive: { 85 | margin: 2.5, 86 | color: "white", 87 | fontSize: 6 88 | }, 89 | dot: { 90 | margin: 2.5, 91 | color: '#dedede', 92 | fontSize: 6 93 | } 94 | }) 95 | 96 | 97 | export default CardImageSlider -------------------------------------------------------------------------------- /Client/src/view/components/productDetail/SizeOptionsSection.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, StyleSheet, Text, ScrollView,TouchableOpacity } from 'react-native' 3 | import COLORS from '../../../consts/colors' 4 | 5 | const SizeOptionsSection = ({product,selectedSize,setSelectedSize}) => { 6 | return ( 7 | 8 | 9 | {product.subCategory.name === 'Ayakkabı' && 'Ayakkabı Numarası' || 10 | product.subCategory.name === 'Telefon' && 'Dahili Hafıza' || 11 | product.subCategory.name === 'Tablet' && 'Kapasite' || 12 | 'Beden' 13 | } 14 | 15 | 23 | {product?.sizes?.map((size, index) => ( 24 | setSelectedSize(size)} 27 | > 28 | 29 | {size} 30 | 31 | 32 | ))} 33 | 34 | 35 | 36 | ) 37 | } 38 | 39 | const styles = StyleSheet.create({ 40 | chooseSizeSection: { 41 | height: 125, 42 | backgroundColor: COLORS.white, 43 | marginTop: 20, 44 | marginBottom: 0, 45 | paddingHorizontal: 12, 46 | elevation: 2, 47 | 48 | }, 49 | sizeTxt: { 50 | fontSize: 14, fontWeight: '500', paddingVertical: 13, color: '#2C2E43' 51 | , borderBottomColor: '#eee', borderBottomWidth: 1 52 | }, 53 | sizeBtn: { 54 | paddingHorizontal: 10, 55 | borderRadius: 5, 56 | marginVertical: 10, 57 | marginRight: 14, 58 | borderWidth: 1, 59 | borderColor: 'gray', 60 | height: 39, 61 | width: 'auto', 62 | minWidth: 55, 63 | flexDirection: 'row', 64 | alignItems: 'center', 65 | justifyContent: 'center', 66 | }, 67 | activeSizeBtn: { 68 | backgroundColor: 'rgba(16, 176, 2,0.1 )', 69 | borderColor: COLORS.green, 70 | }, 71 | 72 | }) 73 | 74 | export default SizeOptionsSection 75 | -------------------------------------------------------------------------------- /Client/src/view/components/productDetail/ColorOptionsSection.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, Image, StyleSheet, Text } from 'react-native' 3 | import COLORS from '../../../consts/colors' 4 | import { NGROK_URL } from '@env' 5 | 6 | const ColorOptionsSection = ({colorOptions,navigation,product}) => { 7 | return ( 8 | 9 | 10 | 11 | Renk 12 | 13 | 16 | {colorOptions.length} Farklı Renk 17 | 18 | 19 | 20 | { 21 | colorOptions.map((item, index) => ( 22 | 23 | { 25 | navigation.push('Details', { productId: item._id }) 26 | }} 27 | style={[styles.colorOption, 28 | { 29 | borderWidth: 1, 30 | borderColor: item._id === product._id ? COLORS.green : '#dedede' 31 | } 32 | 33 | ]} 34 | key={index} 35 | > 36 | 37 | 41 | 42 | )) 43 | } 44 | 45 | 46 | ) 47 | } 48 | 49 | const styles = StyleSheet.create({ 50 | chooseColorSection: { 51 | height: 205, 52 | backgroundColor: COLORS.white, 53 | marginTop: 20, 54 | paddingHorizontal: 12, 55 | elevation: 2, 56 | }, 57 | colorOptions: { 58 | flexDirection: 'row', 59 | alignItems: 'center', 60 | justifyContent: 'flex-start', 61 | }, 62 | colorOption: { 63 | height: '80%', 64 | width: 110, 65 | borderRadius: 8, 66 | marginRight: 20, 67 | }, 68 | colorOptionImage: { 69 | width: '100%', 70 | height: '100%', 71 | resizeMode: 'contain', 72 | }, 73 | colorTxt: { 74 | fontSize: 14, fontWeight: '500', paddingVertical: 13, color: '#2C2E43' 75 | }, 76 | headerColorSec: { 77 | borderBottomColor: '#eee', borderBottomWidth: 1, 78 | flexDirection: 'row', 79 | alignItems: 'center', 80 | justifyContent: 'space-between' 81 | }, 82 | 83 | }) 84 | 85 | export default ColorOptionsSection -------------------------------------------------------------------------------- /Server/Models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const bcrypt = require('bcryptjs'); 3 | const jwt = require('jsonwebtoken') 4 | const Order = require('./order'); 5 | const { v4: uuidv4 } = require('uuid'); 6 | const Schema = mongoose.Schema; 7 | 8 | const userSchema = new Schema({ 9 | 10 | username: { 11 | type: String, 12 | required: [true, "Please provide a username "] 13 | }, 14 | email: { 15 | type: String, 16 | required: [true, 'Please provide a email '], 17 | unique: true, 18 | match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Please fill a valid email address'] 19 | }, 20 | role: { 21 | type: String, 22 | default: "user", 23 | enum: ["user", "admin"] 24 | }, 25 | password: { 26 | type: String, 27 | minlength: [6, "Please provide a password with min length : 6 "], 28 | required: [true, "Please provide a password"], 29 | select: false 30 | }, 31 | createdAt: { 32 | type: Date, 33 | default: Date.now 34 | }, 35 | likedProducts: [{ 36 | type: mongoose.Schema.ObjectId, 37 | ref: "Product" 38 | }], 39 | about: { 40 | type: String 41 | }, 42 | profile_image: { 43 | type: String, 44 | default: 'default.jpg' 45 | }, 46 | blocked: { 47 | type: Boolean, 48 | default: false 49 | }, 50 | resetPasswordToken: { 51 | type: String 52 | }, 53 | resetPasswordExpire: { 54 | type: Date 55 | } 56 | 57 | }) 58 | 59 | userSchema.methods.generateJwtFromUser = function () { 60 | 61 | const { JWT_SECRET_KEY } = process.env; 62 | 63 | const payload = { 64 | id: this._id, 65 | name: this.name, 66 | email: this.email 67 | }; 68 | 69 | const token = jwt.sign(payload, JWT_SECRET_KEY); 70 | 71 | return token 72 | 73 | } 74 | 75 | 76 | userSchema.pre('save', function (next) { 77 | 78 | if (!this.isModified("password")) { 79 | next() 80 | } 81 | 82 | bcrypt.genSalt(10, (err, salt) => { 83 | 84 | if (err) next(err); 85 | 86 | bcrypt.hash(this.password, salt, (err, hash) => { 87 | if (err) next(err); 88 | this.password = hash 89 | next() 90 | }); 91 | }); 92 | 93 | }) 94 | 95 | userSchema.post('save', async function () { 96 | 97 | const order = await Order.findOne({ user: this._id, complete: false }) 98 | 99 | if (!order) { 100 | 101 | await Order.create({ 102 | user: this._id, 103 | complete: false, 104 | transaction_id: uuidv4() 105 | 106 | }) 107 | } 108 | 109 | }) 110 | 111 | 112 | module.exports = mongoose.model("User", userSchema) -------------------------------------------------------------------------------- /Client/src/view/screens/EditAddressScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import AddressPostForm from '../components/address/AddressPostForm' 3 | import AddressService from '../../services/AddressService' 4 | import Toast from 'react-native-toast-message' 5 | 6 | const EditAddressScreen = ({ route, navigation }) => { 7 | 8 | const fields = ["title", "name", "surname", "phone", "detail", "neighborhood", "city", "district"] 9 | const [address, setAddress] = useState({}) 10 | const { prevRouteName } = route.params 11 | const [disabled, setDisabled] = useState(false) 12 | 13 | useEffect(() => { 14 | setAddress( 15 | fields.reduce((acc, field) => { 16 | acc[field] = { 17 | value: route.params.address[field], 18 | error: { 19 | message: '', 20 | status: false 21 | }, 22 | isFocus: false 23 | } 24 | return acc 25 | }, {}) 26 | ) 27 | }, [route.params.address]) 28 | 29 | const onSubmit = async (address) => { 30 | setDisabled(true) 31 | let editAddress = { 32 | title: address.title.value, 33 | name: address.name.value, 34 | surname: address.surname.value, 35 | phone: address.phone.value, 36 | detail: address.detail.value, 37 | neighborhood: address.neighborhood.value, 38 | city: address.city.value, 39 | district: address.district.value 40 | } 41 | try { 42 | await AddressService.editAddress({ address: editAddress, id: route.params.address._id }) 43 | 44 | if (prevRouteName === "Checkout") { 45 | navigation.navigate('Checkout', { address: editAddress, id: route.params.address._id }) 46 | } 47 | else { 48 | navigation.navigate('MyAddresses') 49 | } 50 | 51 | } 52 | catch (error) { 53 | Toast.show({ 54 | type: 'customToast', 55 | position: 'bottom', 56 | text1: 'İstek iletilirken bir sorun oluştu.Tekrar deneyiniz.', 57 | visibilityTime: 2000, 58 | autoHide: true, 59 | bottomOffset: 0, 60 | }); 61 | setTimeout(() => { 62 | setDisabled(false) 63 | }, 2000); 64 | 65 | } 66 | 67 | } 68 | 69 | const deleteAddress = async () => { 70 | await AddressService.deleteAddress(route.params.address._id) 71 | navigation.navigate('MyAddresses') 72 | } 73 | 74 | return ( 75 | 83 | ) 84 | } 85 | 86 | export default EditAddressScreen -------------------------------------------------------------------------------- /Client/src/view/components/productDetail/ProductInfo.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { View, StyleSheet, Text, TouchableOpacity } from 'react-native' 3 | import { Octicons } from 'react-native-vector-icons' 4 | import COLORS from '../../../consts/colors' 5 | 6 | const ProductInfo = ({ list }) => { 7 | const [showMore, setShowMore] = useState(false) 8 | return ( 9 | 10 | 11 | Ürün Bilgileri 12 | 13 | 14 | {list?.slice(0, 4).map((item, index) => ( 15 | 16 | 17 | {item} 18 | 19 | ))} 20 | 21 | 22 | 25 | {list?.slice(4, list.length).map((item, index) => ( 26 | 27 | 28 | 29 | {item} 30 | 31 | ))} 32 | 33 | 34 | {list?.length > 4 && 35 | setShowMore(!showMore)} 37 | activeOpacity={0.8} 38 | > 39 | 40 | 41 | 42 | {showMore ? 'Daha az göster' : 'Daha fazla göster'} 43 | 44 | 45 | 46 | 47 | } 48 | 49 | 50 | ) 51 | } 52 | 53 | const styles = StyleSheet.create({ 54 | container: { 55 | backgroundColor: 'white', 56 | marginTop: 20, 57 | paddingHorizontal: 20, 58 | paddingBottom: 10, 59 | elevation: 2, 60 | marginBottom: 20, 61 | }, 62 | flex: { 63 | display: 'flex', flexDirection: 'row', alignItems: 'center' 64 | }, 65 | title: { 66 | fontSize: 14, fontWeight: '500', paddingVertical: 15, color: '#2C2E43' 67 | , borderBottomColor: '#eee', borderBottomWidth: 1 68 | }, 69 | 70 | infoItem: { 71 | flexDirection: 'row', 72 | paddingVertical: 5, 73 | marginTop: 3, 74 | }, 75 | showCloseBtn: { 76 | paddingVertical: 13, 77 | borderRadius: 5, 78 | alignItems: 'center', 79 | justifyContent: 'center', 80 | borderWidth: 1, 81 | borderColor: '#F2F2F2', 82 | marginTop: 10, 83 | } 84 | }) 85 | 86 | export default ProductInfo -------------------------------------------------------------------------------- /Client/src/view/components/checkout/ContractForms.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text, View, StyleSheet, ScrollView } from 'react-native' 3 | import COLORS from '../../../consts/colors' 4 | import TEXTS from '../../../consts/text' 5 | 6 | const ContractForms = () => { 7 | return ( 8 | 9 | 18 | Sözleşme ve Formlar 19 | 20 | 21 | 22 | 26 | Ön Bilgilendirme Koşulları 27 | 28 | 34 | 35 | {TEXTS.contractText} 36 | 37 | 38 | 42 | Mesafeli Satış Sözleşmesi 43 | 44 | 50 | 51 | {TEXTS.sellingContract} 52 | 53 | 54 | 55 | 56 | 57 | ) 58 | } 59 | 60 | const styles = StyleSheet.create({ 61 | contractForms: { 62 | backgroundColor: 'white', 63 | paddingHorizontal: 15, 64 | paddingBottom: 25, 65 | paddingTop: 15, 66 | elevation: 3, 67 | marginBottom: 85, 68 | marginTop: 20, 69 | }, 70 | headText: { 71 | fontSize: 16, 72 | fontWeight: 'bold', 73 | color: 'gray', 74 | marginBottom: 15, 75 | }, 76 | contractText: { 77 | fontSize: 11, 78 | color: 'rgb(153, 153, 153)', 79 | lineHeight: 17, 80 | }, 81 | sellingContract: { 82 | fontSize: 11, 83 | color: 'rgb(153, 153, 153)', 84 | lineHeight: 17, 85 | }, 86 | formWrap: { 87 | borderWidth: 1, 88 | overflow: 'scroll', 89 | height: 110, 90 | backgroundColor: 'rgb(246,246,246)', 91 | borderRadius: 5, 92 | paddingHorizontal: 15, 93 | paddingVertical: 10, 94 | borderColor: 'rgba(200,200,200,0.5)' 95 | }, 96 | 97 | }) 98 | 99 | export default ContractForms -------------------------------------------------------------------------------- /Client/src/view/components/favorite/FavoritesMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, Text, TouchableOpacity } from 'react-native' 3 | import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-menu'; 4 | import { Entypo, AntDesign, MaterialCommunityIcons } from 'react-native-vector-icons' 5 | import { FavoriteState } from '../../../Context/FavoriteContext'; 6 | 7 | const FavoritesMenu = ({ productId }) => { 8 | const { removeItemFromFavoriteList } = FavoriteState(); 9 | 10 | return ( 11 | 14 | 23 | 32 | 33 | 48 | 49 | 54 | Benzer Ürünleri Gör 55 | 56 | 57 | removeItemFromFavoriteList(productId)} 60 | > 61 | 66 | 67 | Delete 68 | 69 | 70 | 71 | 72 | ) 73 | } 74 | 75 | const styles = StyleSheet.create({ 76 | menu: { 77 | width: 31, 78 | borderRadius: 50, 79 | marginLeft: 300, 80 | height: 33, 81 | position: 'absolute', 82 | zIndex: 5, 83 | top: 10, 84 | right: 10, 85 | }, 86 | menuOption: { 87 | padding: 15, 88 | flexDirection: 'row', 89 | alignItems: 'center', 90 | }, 91 | menuIcon: { 92 | marginRight: 10 93 | } 94 | }) 95 | export default FavoritesMenu -------------------------------------------------------------------------------- /Client/src/Context/StoreContext.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from 'react' 2 | import StoreService from '../services/StoreService'; 3 | import { AuthState } from './AuthContext'; 4 | import Toast from 'react-native-toast-message'; 5 | 6 | export const StoreContext = React.createContext(); 7 | 8 | const StoreContextProvider = ({ children }) => { 9 | const { userInfo } = AuthState() 10 | const [basketItemsLength, setBasketItemsLength] = useState(0); 11 | const [basketItems, setBasketItems] = useState([]); 12 | const [totalPrice, setTotalPrice] = useState(0); 13 | 14 | const [loading, setLoading] = useState(false); 15 | const [basketItemStatus, setBasketItemStatus] = useState(false); 16 | 17 | const getAllBasketItems = async () => { 18 | const { data } = await StoreService.getAllBasketItems(); 19 | setBasketItems(data.orderItems) 20 | setBasketItemsLength(data.orderItems.length) 21 | setTotalPrice(data.totalPrice) 22 | setLoading(false) 23 | 24 | } 25 | const addToBasket = async ({ productId, selectedSize }) => { 26 | setLoading(true) 27 | 28 | try { 29 | await StoreService.addToBasket({ productId, selectedSize }); 30 | 31 | } 32 | catch (err) { 33 | Toast.show({ 34 | type: 'customToast', 35 | position: 'bottom', 36 | text1: 'İstek iletilirken bir sorun oluştu.Tekrar deneyiniz.', 37 | visibilityTime: 2000, 38 | autoHide: true, 39 | bottomOffset: 0, 40 | }); 41 | } 42 | 43 | setBasketItemStatus(!basketItemStatus) 44 | 45 | } 46 | 47 | const increaseQuantity = async (id) => { 48 | await StoreService.increaseQuanity({ itemId: id }); 49 | 50 | } 51 | const decreaseQuantity = async (id) => { 52 | await StoreService.decreaseQuanity({ itemId: id }); 53 | } 54 | 55 | const deleteBasketItem = async (id) => { 56 | setLoading(true) 57 | await StoreService.deleteBasketItem(id); 58 | setBasketItemStatus(!basketItemStatus) 59 | } 60 | 61 | const deleteAllBasketItems = async () => { 62 | setLoading(true) 63 | await StoreService.deleteAllBasketItems(); 64 | setBasketItems([]) 65 | setBasketItemsLength(0) 66 | setTotalPrice(0) 67 | setLoading(false) 68 | } 69 | 70 | 71 | useEffect(() => { 72 | getAllBasketItems() 73 | }, [basketItemStatus, userInfo]) 74 | 75 | return ( 76 | 92 | {children} 93 | 94 | ) 95 | 96 | } 97 | 98 | 99 | export const StoreState = () => { 100 | return useContext(StoreContext) 101 | } 102 | 103 | export default StoreContextProvider -------------------------------------------------------------------------------- /Client/src/view/components/productDetail/RecommendProductItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, StyleSheet, Text, Image } from 'react-native' 3 | import COLORS from '../../../consts/colors' 4 | import { Rating } from 'react-native-ratings' 5 | import { NGROK_URL } from '@env' 6 | 7 | const RecommendProductItem = ({ item, productId, navigation }) => { 8 | return ( 9 | 14 | navigation.push('Details', { productId: item._id })} 17 | > 18 | 22 | 23 | 24 | 25 | {item.seller} {item.name.substring(0, 35)}{item.name.length > 35 && '...'} 26 | 27 | 28 | 29 | 30 | 43 | 44 | ({item?.comments?.length}) 45 | 46 | 47 | 48 | 49 | 50 | {item.price} TL 51 | 52 | 53 | 54 | ) 55 | } 56 | const styles = StyleSheet.create({ 57 | container: { 58 | backgroundColor: COLORS.white, 59 | marginTop: 5, 60 | paddingHorizontal: 12, 61 | elevation: 2, 62 | width: '100%', 63 | marginBottom: 55, 64 | }, 65 | productItem: { 66 | width: 130, 67 | marginRight: 14, 68 | height: 300, 69 | }, 70 | title: { 71 | fontSize: 14, fontWeight: '500', paddingVertical: 17, color: '#2C2E43' 72 | }, 73 | imageWrapper: { 74 | height: 170, 75 | borderWidth: 1, 76 | borderColor: 'rgba(0,0,0,0.09)', 77 | marginBottom: 10, 78 | borderRadius: 6, 79 | }, 80 | image: { 81 | width: '100%', 82 | height: '100%', 83 | resizeMode: 'contain', 84 | }, 85 | productName: { 86 | fontSize: 11, 87 | fontWeight: 'bold', 88 | color: '#8c8c8c', 89 | lineHeight: 15, 90 | paddingHorizontal: 2, 91 | marginBottom: 5, 92 | height: 37, 93 | }, 94 | flex: { 95 | flexDirection: 'row', 96 | alignItems: 'center', 97 | paddingLeft: 2, 98 | }, 99 | productPrice: { 100 | fontSize: 12, 101 | fontWeight: 'bold', 102 | color: COLORS.green, 103 | paddingLeft: 2, 104 | marginTop: 12, 105 | } 106 | 107 | }) 108 | 109 | 110 | export default RecommendProductItem -------------------------------------------------------------------------------- /Client/src/view/components/productDetail/RecommendProducts.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { View, StyleSheet, Text, FlatList } from 'react-native' 3 | import COLORS from '../../../consts/colors' 4 | import ProductService from '../../../services/ProductService' 5 | import RecommendProductItem from './RecommendProductItem' 6 | 7 | const RecommendProducts = ({ productId, subCategoryId ,navigation}) => { 8 | 9 | const [products, setProducts] = useState([]) 10 | const [similarProducts, setSimilarProducts] = useState([]) 11 | 12 | const getProductsInSameSubCategory = async () => { 13 | try { 14 | const { data } = await ProductService.getProductsInSameSubCategory(subCategoryId); 15 | setSimilarProducts(data.products) 16 | } 17 | catch (err) { 18 | console.log("error :", err) 19 | setSimilarProducts([]) 20 | } 21 | 22 | } 23 | 24 | 25 | const getLastViewedProducts = async () => { 26 | try { 27 | const { data } = await ProductService.lastViewedProducts(); 28 | 29 | setProducts(data.lastVieweds.products) 30 | 31 | } 32 | catch (err) { 33 | console.log(" error :", err) 34 | setProducts([]) 35 | } 36 | 37 | } 38 | 39 | useEffect(() => { 40 | getLastViewedProducts() 41 | getProductsInSameSubCategory() 42 | }, [subCategoryId]) 43 | 44 | 45 | 46 | return ( 47 | <> 48 | {similarProducts.length >= 2 && 49 | 50 | 51 | Benzer Ürünler 52 | 53 | 54 | item._id} 59 | renderItem={({ item }) => ( 60 | 61 | )} 62 | /> 63 | 64 | 65 | } 66 | {products.length >= 3 && 67 | 68 | 69 | Son Baktıgınız Ürünler 70 | 71 | item._id} 76 | renderItem={({ item }) => ( 77 | 78 | 79 | )} 80 | /> 81 | 82 | 83 | } 84 | 85 | 86 | ) 87 | } 88 | const styles = StyleSheet.create({ 89 | container: { 90 | backgroundColor: COLORS.white, 91 | marginTop: 5, 92 | paddingHorizontal: 12, 93 | elevation: 2, 94 | width: '100%', 95 | marginBottom: 55, 96 | }, 97 | title: { 98 | fontSize: 14, fontWeight: '500', paddingVertical: 17, color: '#2C2E43', 99 | paddingBottom:10, 100 | marginBottom: 10, 101 | }, 102 | 103 | }) 104 | 105 | 106 | export default RecommendProducts -------------------------------------------------------------------------------- /Client/src/view/components/productDetail/ProductProperties.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { View, StyleSheet, Text, TouchableOpacity } from 'react-native' 3 | 4 | const ProductProperties = ({ properties }) => { 5 | const [showMore, setShowMore] = useState(false) 6 | return ( 7 | properties && 8 | 9 | 10 | Ürün Özellikleri 11 | 12 | 13 | {properties && Object.keys(properties).slice(0, 4).map((key, index) => ( 14 | 15 | {key} : 16 | 19 | {properties[key]} 20 | 21 | 22 | ))} 23 | 24 | 25 | 28 | {properties && Object.keys(properties).slice(4, properties.length).map((key, index) => ( 29 | 30 | 31 | {key} : 32 | 35 | {properties[key]} 36 | 37 | 38 | ))} 39 | 40 | 41 | {properties && Object.keys(properties)?.length > 2 && 42 | setShowMore(!showMore)} 44 | activeOpacity={0.8} 45 | > 46 | 47 | 48 | 49 | 50 | {showMore ? 'Daha az göster' : 'Daha fazla göster'} 51 | 52 | 53 | 54 | } 55 | 56 | 57 | 58 | ) 59 | } 60 | 61 | const styles = StyleSheet.create({ 62 | container: { 63 | backgroundColor: 'white', 64 | marginTop: 0, 65 | paddingHorizontal: 20, 66 | paddingBottom: 10, 67 | elevation: 2, 68 | marginBottom: 20, 69 | }, 70 | flex: { 71 | display: 'flex', flexDirection: 'row', alignItems: 'center' 72 | }, 73 | title: { 74 | fontSize: 14, fontWeight: '500', paddingVertical: 15, color: '#2C2E43' 75 | , borderBottomColor: '#eee', borderBottomWidth: 1 76 | }, 77 | propertyItem: { 78 | flexDirection: 'row', 79 | paddingVertical: 13, 80 | marginTop: 6, 81 | justifyContent: 'space-between', 82 | borderBottomColor: '#f5f5f5', 83 | borderBottomWidth: 1, 84 | }, 85 | showCloseBtn: { 86 | paddingVertical: 13, 87 | borderRadius: 5, 88 | alignItems: 'center', 89 | justifyContent: 'center', 90 | borderWidth: 1, 91 | borderColor: '#F2F2F2', 92 | marginTop: 10, 93 | } 94 | }) 95 | 96 | export default ProductProperties -------------------------------------------------------------------------------- /Client/src/view/components/checkout/ConfirmationField.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text, View, StyleSheet, Dimensions } from 'react-native' 3 | import COLORS from '../../../consts/colors' 4 | import BouncyCheckbox from 'react-native-bouncy-checkbox' 5 | 6 | const ConfirmationField = ({ confirmChecked, setConfirmChecked }) => { 7 | return ( 8 | 9 | 23 | 28 | Ön Bilgilendirme formunu 29 | 30 | 36 | ve 37 | 38 | 41 | Mesafeli Satış Sözleşmesini 42 | 43 | 51 | onaylıyorum 52 | 53 | 54 | } 55 | iconStyle={{ borderColor: "black", marginRight: -5, }} 56 | innerIconStyle={{ 57 | borderWidth: 2, 58 | borderRadius: 5, 59 | }} 60 | onPress={() => 61 | setConfirmChecked({ 62 | value: !confirmChecked.value, 63 | error: false, 64 | }) 65 | } 66 | style={{ 67 | marginBottom: 17, 68 | }} 69 | isChecked={confirmChecked.value} 70 | /> 71 | 72 | 73 | ) 74 | } 75 | 76 | const styles = StyleSheet.create({ 77 | confirmationField: { 78 | height: 80, 79 | padding: 15, 80 | paddingTop: 20, 81 | flexDirection: 'row', 82 | alignItems: 'center', 83 | marginVertical: 10, 84 | }, 85 | textUnderline: { 86 | textDecorationLine: 'underline', 87 | color: COLORS.green, 88 | fontWeight: 'bold', 89 | marginHorizontal: 6, 90 | fontSize: 11, 91 | letterSpacing: 0.3, 92 | }, 93 | errorText: { 94 | color: '#ed071e', 95 | } 96 | }) 97 | 98 | export default ConfirmationField -------------------------------------------------------------------------------- /Server/Controllers/comment.js: -------------------------------------------------------------------------------- 1 | 2 | const asyncErrorWrapper = require("express-async-handler"); 3 | const Comment = require("../Models/comment"); 4 | const Product = require("../Models/product"); 5 | const Evaluation = require("../Models/evaluation"); 6 | 7 | 8 | const addComment = asyncErrorWrapper(async (req, res, next) => { 9 | 10 | const { content, rating, nameVisible, orderItemId, productId } = req.body; 11 | 12 | const newComment = await Comment.create({ 13 | orderItem: orderItemId, 14 | user: req.user.id, 15 | content, 16 | rating, 17 | nameVisible 18 | }) 19 | const product = await Product.findById(productId).populate("comments", "rating") 20 | 21 | product.comments.push(newComment) 22 | 23 | product.averageRating = (product.comments.reduce((acc, item) => item.rating + acc, 0)) / product.comments.length 24 | 25 | await product.save() 26 | 27 | const evaluationItem = await Evaluation.findOne({ user: req.user.id, product: productId }) 28 | 29 | evaluationItem.appravolStatus = true 30 | evaluationItem.comment = newComment; 31 | 32 | await evaluationItem.save() 33 | 34 | return res.status(200).json({ 35 | success: true, 36 | comment: newComment 37 | }) 38 | 39 | }) 40 | 41 | const deleteComment = asyncErrorWrapper(async (req, res, next) => { 42 | 43 | const { commentId, productId } = req.body; 44 | 45 | const product = await Product.findById(productId).populate("comments", "rating") 46 | 47 | const index = product.comments.findIndex(item => item._id.toString() === commentId) 48 | 49 | if (product.comments.length === 1) { 50 | product.averageRating = 0 51 | } 52 | else { 53 | product.averageRating = (product.comments.reduce((acc, item) => item.rating + acc, 0) - product.comments[index].rating) / (product.comments.length - 1) 54 | } 55 | 56 | product.comments.splice(index, 1) 57 | 58 | await product.save() 59 | 60 | const evaluationItem = await Evaluation.findOne({ user: req.user.id, product: productId }) 61 | evaluationItem.appravolStatus = false 62 | evaluationItem.comment = null;; 63 | await evaluationItem.save() 64 | 65 | await Comment.findByIdAndDelete(commentId) 66 | 67 | return res.status(200).json({ 68 | success: true, 69 | message: "Comment deleted" 70 | }) 71 | 72 | }) 73 | 74 | 75 | 76 | const editComment = asyncErrorWrapper(async (req, res, next) => { 77 | 78 | const { content, rating, nameVisible, orderItemId, productId } = req.body; 79 | 80 | const comment = await Comment.findOne({ 81 | orderItem: orderItemId, 82 | user: req.user.id, 83 | }) 84 | 85 | comment.content = content 86 | 87 | comment.rating = rating 88 | 89 | comment.nameVisible = nameVisible 90 | 91 | await comment.save() 92 | 93 | const product = await Product.findById(productId).populate("comments", "rating") 94 | 95 | product.averageRating = (product.comments.reduce((acc, item) => item.rating + acc, 0)) / product.comments.length 96 | 97 | await product.save() 98 | 99 | return res.status(200).json({ 100 | success: true, 101 | comment: comment 102 | }) 103 | 104 | }) 105 | 106 | const getAllComments = asyncErrorWrapper(async (req, res, next) => { 107 | 108 | const comments = await Comment.find({ product: req.params.id }) 109 | 110 | return res.status(200).json({ 111 | success: true, 112 | comments 113 | }) 114 | 115 | }) 116 | 117 | 118 | module.exports = { 119 | getAllComments, 120 | addComment, 121 | editComment, 122 | deleteComment, 123 | } -------------------------------------------------------------------------------- /Client/src/view/screens/SearchResultsScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { View, Text, StyleSheet, ScrollView } from 'react-native' 3 | import { SafeAreaView } from 'react-native-safe-area-context'; 4 | import SearchSection from '../components/general/SearchSection'; 5 | import COLORS from '../../consts/colors'; 6 | import { MaterialIndicator } from 'react-native-indicators' 7 | import ProductService from '../../services/ProductService'; 8 | import ProductCard from '../components/general/ProductCard'; 9 | import { Ionicons } from 'react-native-vector-icons' 10 | 11 | const SearchResultsScreen = ({ navigation, route }) => { 12 | 13 | const { queryObj } = route.params; 14 | const [loading, setLoading] = useState(true) 15 | const [searchResults, setSearchResults] = useState([]) 16 | const [warnMsg, setWarnMsg] = useState('') 17 | 18 | const searchProduct = async () => { 19 | 20 | try { 21 | if (queryObj.type) { 22 | const { data } = await ProductService.searchProduct({ queryObj }) 23 | setSearchResults(data.results) 24 | data.message ? setWarnMsg(data.message) : setWarnMsg('') 25 | setLoading(false) 26 | } 27 | } 28 | catch (error) { 29 | setSearchResults([]) 30 | setLoading(false) 31 | } 32 | 33 | } 34 | 35 | useEffect(() => { 36 | setLoading(true) 37 | searchProduct() 38 | }, [queryObj]) 39 | 40 | 41 | return ( 42 | 45 | 49 | {loading ? 50 | 54 | : 55 | 58 | {warnMsg && 59 | 62 | 63 | {warnMsg} 66 | 67 | } 68 | 77 | { 78 | searchResults.map((item, index) => ( 79 | 82 | )) 83 | } 84 | 85 | 86 | 87 | 88 | } 89 | 90 | 91 | ) 92 | } 93 | const styles = StyleSheet.create({ 94 | container: { 95 | flex: 1, 96 | backgroundColor: COLORS.white, 97 | 98 | }, 99 | messageWrapper : { 100 | flexDirection: 'row', 101 | alignItems: 'center', 102 | paddingHorizontal: 15, 103 | marginHorizontal: 10, 104 | marginTop: 10, 105 | paddingRight: 35, 106 | paddingVertical: 15, 107 | elevation: 5, 108 | backgroundColor: COLORS.white, 109 | } 110 | }) 111 | export default SearchResultsScreen -------------------------------------------------------------------------------- /Client/src/view/components/evaluation/EvaluationMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, Text, TouchableOpacity } from 'react-native' 3 | import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-menu'; 4 | import { MaterialIcons, Entypo, MaterialCommunityIcons } from 'react-native-vector-icons' 5 | import COLORS from '../../../consts/colors'; 6 | import CommentService from '../../../services/CommentService'; 7 | 8 | const EvaluationMenu = ({ item, navigation, getAllData }) => { 9 | 10 | const deleteEvaluation = async () => { 11 | try { 12 | await CommentService.deleteComment({ 13 | commentId: item.comment._id, 14 | productId: item.product._id 15 | }) 16 | getAllData() 17 | 18 | } 19 | catch (error) { 20 | console.log("error", error) 21 | } 22 | 23 | } 24 | 25 | 26 | return ( 27 | 30 | 39 | 48 | 49 | 63 | { 66 | navigation.navigate('EditComment', { item }) 67 | 68 | }} 69 | > 70 | 75 | 78 | Düzenle 79 | 80 | 81 | { 84 | deleteEvaluation() 85 | }} 86 | > 87 | 92 | 93 | Delete 94 | 95 | 96 | 97 | 98 | ) 99 | } 100 | 101 | const styles = StyleSheet.create({ 102 | 103 | menu: { 104 | width: 31, 105 | borderRadius: 50, 106 | marginLeft: 300, 107 | height: 33, 108 | position: 'absolute', 109 | zIndex: 5, 110 | top: 15, 111 | right: 5, 112 | }, 113 | menuOption: { 114 | paddingVertical: 12, 115 | paddingHorizontal: 15, 116 | flexDirection: 'row', 117 | alignItems: 'center', 118 | }, 119 | menuIcon: { 120 | marginRight: 10 121 | } 122 | }) 123 | 124 | export default EvaluationMenu -------------------------------------------------------------------------------- /Client/src/view/components/checkout/PaymentBottomSheet.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, StyleSheet, Text, TouchableOpacity, FlatList } from 'react-native' 3 | import BottomSheet from 'react-native-gesture-bottom-sheet' 4 | import { Ionicons } from 'react-native-vector-icons' 5 | import COLORS from '../../../consts/colors' 6 | 7 | const PaymentBottomSheet = ({ bottomSheet, options , selectedCard,setSelectedCard ,headText,height}) => { 8 | 9 | return ( 10 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | {headText} 22 | 23 | { bottomSheet.current.close() }} 25 | > 26 | 31 | 32 | 33 | 34 | 35 | item} 38 | renderItem={({ item }) => ( 39 | { 49 | headText === "Ay Seçiniz" ? 50 | setSelectedCard({ 51 | ...selectedCard, 52 | cardExpiredMonth : { 53 | isValid :false, 54 | value : item 55 | } 56 | }) 57 | : 58 | setSelectedCard({ 59 | ...selectedCard, 60 | cardExpiredYear : { 61 | isValid :false, 62 | value : item 63 | } 64 | }) 65 | 66 | bottomSheet.current.close() 67 | } 68 | } 69 | > 70 | 71 | {item} 72 | 73 | 74 | )} 75 | /> 76 | 77 | 78 | 79 | 80 | 81 | ) 82 | } 83 | const styles = StyleSheet.create({ 84 | bottomSheetContent: { 85 | backgroundColor: 'white', 86 | }, 87 | bottomSheetContentHeader: { 88 | backgroundColor: 'rgba(0,0,0,0.09)', 89 | paddingVertical: 10, 90 | paddingHorizontal: 20, 91 | flexDirection: 'row', 92 | justifyContent: 'space-between', 93 | alignItems: 'center', 94 | marginBottom: 10, 95 | } 96 | }) 97 | 98 | export default PaymentBottomSheet -------------------------------------------------------------------------------- /Client/src/view/components/basket/RecommendBasketItem.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from 'react' 2 | import { View, StyleSheet, Text, Image, Dimensions, TouchableOpacity } from 'react-native' 3 | import COLORS from '../../../consts/colors' 4 | import CartBottomSheet from '../general/CartBottomSheet'; 5 | import { NGROK_URL } from '@env' 6 | 7 | const RecommendBasketItem = ({ item, navigation }) => { 8 | const bottomSheet = useRef(null); 9 | const [selectedSize, setSelectedSize] = useState(item?.sizes[0]) 10 | 11 | return ( 12 | <> 13 | 20 | 21 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | {item?.seller} {item?.name.substring(0, 65)}{item.name.length > 65 && '...'} 35 | 36 | 37 | 38 | 39 | {item?.price} TL 40 | 41 | 42 | { 45 | bottomSheet.current.show() 46 | }} 47 | > 48 | 49 | Sepete Ekle 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ) 60 | } 61 | 62 | 63 | const styles = StyleSheet.create({ 64 | recommendItem: { 65 | height: 120, 66 | flexDirection: 'row', 67 | alignItems: 'center', 68 | justifyContent: 'space-between', 69 | marginBottom: 7, 70 | borderBottomColor: '#f5f5f5', 71 | borderBottomWidth: 1, 72 | paddingBottom: 12, 73 | }, 74 | imageWrapper: { 75 | width: 95, 76 | height: 95, 77 | flexDirection: 'row', 78 | alignItems: 'center', 79 | justifyContent: 'center', 80 | }, 81 | image: { 82 | width: 75, 83 | height: 75, 84 | resizeMode: 'contain', 85 | }, 86 | productInfoWrapper: { 87 | width: Dimensions.get('window').width - 130, 88 | paddingHorizontal: 10, 89 | height: '100%', 90 | paddingTop: 10, 91 | paddingBottom: 5, 92 | flexDirection: 'column', 93 | justifyContent: 'space-between', 94 | }, 95 | productName: { 96 | fontSize: 12, 97 | fontWeight: 'bold', 98 | color: '#8c8c8c', 99 | lineHeight: 16, 100 | paddingHorizontal: 2, 101 | marginBottom: 5, 102 | height: 37, 103 | }, 104 | flex: { 105 | flexDirection: 'row', 106 | alignItems: 'center', 107 | justifyContent: 'space-between', 108 | }, 109 | productPrice: { 110 | fontSize: 12, 111 | fontWeight: 'bold', 112 | color: COLORS.green, 113 | paddingLeft: 2, 114 | alignSelf: 'flex-end', 115 | }, 116 | addBasketText: { 117 | fontSize: 11, 118 | fontWeight: 'bold', 119 | color: COLORS.green, 120 | borderWidth: 1, 121 | borderColor: COLORS.green, 122 | paddingHorizontal: 14, 123 | paddingVertical: 4, 124 | borderRadius: 5, 125 | elevation: 1, 126 | backgroundColor: 'white', 127 | textAlign: 'center', 128 | }, 129 | }) 130 | 131 | 132 | export default RecommendBasketItem -------------------------------------------------------------------------------- /Client/src/view/components/productDetail/AnimatedHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, Text, Animated } from 'react-native' 3 | import COLORS from '../../../consts/colors' 4 | import { Ionicons, AntDesign, Feather } from 'react-native-vector-icons' 5 | import {useSafeAreaInsets } from 'react-native-safe-area-context' 6 | import { StoreState } from '../../../Context/StoreContext' 7 | 8 | const HEADER_HEIGHT = 0; 9 | 10 | const AnimatedHeader = ({ animatedValue,likeStatus,navigation,product }) => { 11 | const { basketItemsLength } = StoreState(); 12 | const insets = useSafeAreaInsets(); 13 | 14 | const headerHeight = animatedValue.interpolate({ 15 | inputRange: [0, HEADER_HEIGHT + insets.top], 16 | outputRange: [HEADER_HEIGHT + insets.top, 55], 17 | extrapolate: 'clamp' 18 | }); 19 | 20 | return ( 21 | 34 | 39 | navigation.goBack()} 44 | style={{ 45 | position: 'absolute', 46 | bottom: 12, 47 | left: 12 48 | }} 49 | /> 50 | 58 | 0 ? 'flex' : 'none' 74 | }} 75 | > 76 | { 77 | basketItemsLength 78 | } 79 | 80 | 81 | 88 | 89 | 90 | 100 | 101 | 102 | 111 | { 112 | product?.name?.length > 35 ? product?.name.substring(0, 35) + '...' : product?.name 113 | } 114 | 115 | 116 | 117 | 118 | 119 | 120 | ); 121 | }; 122 | 123 | export default AnimatedHeader; -------------------------------------------------------------------------------- /Client/src/view/screens/MyOrdersScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { Text, View, StyleSheet, Image, ScrollView, TouchableOpacity } from 'react-native' 3 | import COLORS from '../../consts/colors'; 4 | import { UIActivityIndicator } from 'react-native-indicators'; 5 | import OrderCard from '../components/order/OrderCard'; 6 | import OrderService from '../../services/OrderService'; 7 | 8 | const MyOrdersScreen = ({ navigation }) => { 9 | 10 | const [orders, setOrders] = useState([]) 11 | const [loading, setLoading] = useState(false) 12 | 13 | const getAllMyOrders = async () => { 14 | setLoading(true) 15 | try { 16 | const { data } = await OrderService.getAllMyOrders() 17 | setOrders(data.orderContent) 18 | setLoading(false) 19 | } 20 | catch (error) { 21 | setLoading(false) 22 | } 23 | } 24 | 25 | useEffect(() => { 26 | getAllMyOrders() 27 | }, []) 28 | 29 | return ( 30 | 33 | { 34 | loading ? 35 | 40 | : 41 | 42 | orders.length === 0 ? 43 | 51 | 60 | 61 | Siparişiniz Bulunamadı 62 | 63 | 64 | 65 | Şu anda vermiş oldugunuz sipariş bulunmamaktadır. 66 | 67 | navigation.navigate('Home')} 77 | > 78 | 79 | Alışverişe Devam Et 80 | 81 | 82 | 83 | 84 | 85 | : 86 | 87 | 90 | { 91 | orders.map((order, index) => { 92 | return ( 93 | 98 | ) 99 | } 100 | ) 101 | } 102 | 103 | } 104 | 105 | 106 | ) 107 | } 108 | 109 | const styles = StyleSheet.create({ 110 | container: { 111 | flex: 1, 112 | backgroundColor: "#fff", 113 | }, 114 | textLight: { 115 | color: '#413F42', 116 | fontSize: 12, 117 | color: '#757575', 118 | marginTop: 10, 119 | }, 120 | btnWrapper: { 121 | backgroundColor: COLORS.green, 122 | color: 'white', 123 | paddingVertical: 12, 124 | borderRadius: 5, 125 | fontWeight: 'bold', 126 | marginTop: 10, 127 | width: '90%', 128 | textAlign: 'center', 129 | }, 130 | 131 | }) 132 | 133 | export default MyOrdersScreen -------------------------------------------------------------------------------- /Client/src/view/components/address/AddressBottomSheet.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, Text, StyleSheet, TouchableOpacity, FlatList } from 'react-native' 3 | import BottomSheet from 'react-native-gesture-bottom-sheet' 4 | import { Ionicons } from 'react-native-vector-icons' 5 | import COLORS from '../../../consts/colors' 6 | import { AntDesign } from '@expo/vector-icons'; 7 | 8 | const AddressBottomSheet = ({ bottomSheet, addresses, setSelectedAddress, navigation }) => { 9 | return ( 10 | 13 | 14 | 15 | 16 | 17 | 18 | Adres Seçiniz 19 | 20 | { bottomSheet.current.close() }} 22 | > 23 | 28 | 29 | 30 | 31 | 32 | 33 | item._id} 36 | renderItem={({ item }) => ( 37 | { 48 | setSelectedAddress(item) 49 | bottomSheet.current.close() 50 | } 51 | } 52 | > 53 | 54 | {item.title} 55 | 56 | 57 | {item.neighborhood} Mah / {item.district} / {item.city} 58 | 59 | 60 | )} 61 | /> 62 | { 65 | bottomSheet.current.close() 66 | navigation.navigate('AddAddress', { prevRouteName: 'Checkout' }) 67 | } 68 | } 69 | > 70 | 75 | 83 | Yeni Adres Ekle 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | ) 92 | } 93 | 94 | const styles = StyleSheet.create({ 95 | bottomSheetContent: { 96 | backgroundColor: 'white', 97 | height: '100%', 98 | }, 99 | bottomSheetContentHeader: { 100 | backgroundColor: 'rgba(0,0,0,0.06)', 101 | paddingVertical: 10, 102 | paddingHorizontal: 20, 103 | flexDirection: 'row', 104 | justifyContent: 'space-between', 105 | alignItems: 'center', 106 | marginBottom: 10, 107 | }, 108 | flex: { 109 | flexDirection: 'row', 110 | alignItems: 'center', 111 | paddingVertical: 11, 112 | paddingHorizontal: 20, 113 | } 114 | }) 115 | 116 | export default AddressBottomSheet 117 | -------------------------------------------------------------------------------- /Client/src/view/screens/AccountScreen.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, Text, TouchableOpacity, View } from 'react-native' 3 | import { SafeAreaView } from 'react-native-safe-area-context' 4 | import { AuthState } from '../../Context/AuthContext' 5 | import { Ionicons, Entypo, AntDesign, MaterialCommunityIcons, FontAwesome5, MaterialIcons, Feather } from 'react-native-vector-icons' 6 | import COLORS from '../../consts/colors'; 7 | const options = [ 8 | { 9 | title: 'Siparişlerim', 10 | icon: 'package', 11 | packageName: 'Feather', 12 | href: 'MyOrders' 13 | }, 14 | { 15 | title: 'Adreslerim', 16 | packageName: 'Ionicons', 17 | icon: 'location-outline', 18 | href: 'MyAddresses' 19 | }, 20 | { 21 | title: 'Kayıtlı Kartlarım', 22 | packageName: 'AntDesign', 23 | icon: 'creditcard', 24 | href: 'BankCards' 25 | }, 26 | { 27 | title: 'Değerlendirmelerim', 28 | packageName: 'MaterialCommunityIcons', 29 | icon: 'comment-processing-outline', 30 | href: 'MyEvaluations' 31 | }, 32 | { 33 | title: 'E-Mail Degişikligi', 34 | packageName: 'FontAwesome5', 35 | icon: 'envelope', 36 | href: 'ChangeEmail' 37 | }, 38 | { 39 | title: 'Şifre Degişikligi', 40 | packageName: 'MaterialIcons', 41 | icon: 'lock-outline', 42 | href: 'ChangePassword' 43 | }, 44 | , { 45 | title: 'Çıkış Yap', 46 | packageName: 'MaterialCommunityIcons', 47 | icon: 'logout', 48 | href: 'Login' 49 | } 50 | 51 | ] 52 | const AccountScreen = ({ navigation }) => { 53 | 54 | const { logout } = AuthState() 55 | 56 | return ( 57 | 60 | 61 | 62 | { 64 | navigation.goBack() 65 | }} 66 | > 67 | 68 | 69 | 70 | 71 | Hesabım 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {options.map((item, index) => { 82 | return ( 83 | { 86 | if (item.title === 'Çıkış Yap') { 87 | logout() 88 | } 89 | else { 90 | navigation.navigate(item.href) 91 | } 92 | }} 93 | > 94 | 95 | 96 | {item.packageName === 'Ionicons' && || 97 | item.packageName === 'MaterialCommunityIcons' && || 98 | item.packageName === 'AntDesign' && || 99 | item.packageName === 'FontAwesome5' && || 100 | item.packageName === 'MaterialIcons' && || 101 | item.packageName === 'Feather' && 102 | } 103 | 106 | {item.title} 107 | 108 | 109 | 110 | 111 | 112 | 113 | ) 114 | 115 | })} 116 | 117 | 118 | 119 | 120 | 121 | 122 | ) 123 | } 124 | 125 | const styles = StyleSheet.create({ 126 | headerSection: { 127 | padding: 18, 128 | backgroundColor: COLORS.grey, 129 | }, 130 | headerInfo: { 131 | width: '100%', 132 | display: 'flex', 133 | flexDirection: 'row', 134 | justifyContent: 'space-between', 135 | alignItems: 'center', 136 | }, 137 | logoutBtn: { 138 | backgroundColor: 'red', 139 | padding: 10, 140 | borderRadius: 5, 141 | margin: 50, 142 | width: 100, 143 | }, 144 | 145 | text: { 146 | textAlign: 'center', 147 | marginTop: 30, 148 | fontSize: 17, 149 | fontWeight: 'bold', 150 | color: 'white', 151 | marginLeft: 20, 152 | }, 153 | mainSection: { 154 | backgroundColor: 'white', 155 | height: 550, 156 | padding: 15, 157 | paddingTop: 20, 158 | 159 | }, 160 | mainSectionItem: { 161 | display: 'flex', 162 | flexDirection: 'row', 163 | justifyContent: 'space-between', 164 | alignItems: 'center', 165 | padding: 12, 166 | paddingVertical: 17, 167 | borderBottomWidth: 1, 168 | borderBottomColor: 'rgba(0,0,0,0.05)', 169 | } 170 | 171 | }) 172 | 173 | export default AccountScreen -------------------------------------------------------------------------------- /Client/src/view/components/checkout/SelectAddressSection.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { View, Text, StyleSheet, Dimensions, TouchableOpacity } from 'react-native' 3 | import COLORS from '../../../consts/colors' 4 | import { Feather } from '@expo/vector-icons'; 5 | import AddressBottomSheet from '../address/AddressBottomSheet'; 6 | 7 | const SelectAddressSection = ({ addresses, selectedAddress, setSelectedAddress, navigation }) => { 8 | 9 | const bottomSheet = useRef(null) 10 | 11 | return ( 12 | <> 13 | 19 | 20 | 30 | Teslimat Adresi 31 | { 35 | navigation.navigate('MyAddresses') 36 | } 37 | } 38 | > 39 | Ekle / Düzenle 45 | 46 | 47 | 48 | 49 | 50 | { bottomSheet.current.show() } 55 | } 56 | > 57 | 63 | 64 | 72 | {selectedAddress?.title ?? 'Siparişiniz için adres ekleyiniz '} 73 | 74 | {selectedAddress?.title && 82 | { 83 | String('(' + selectedAddress.neighborhood + ' Mah. / ' + selectedAddress.district + ' / ' + selectedAddress.city + ')').substring(0, (Dimensions.get('window').width - 112 - String(selectedAddress.title).length * 7) / 7) 84 | + 85 | (String(selectedAddress.neighborhood + 'Mah. / ' + selectedAddress.district + ' / ' + selectedAddress.city).length * 7 > Dimensions.get('window').width - 112 - String(selectedAddress.title).length * 7 ? '...' : '') 86 | } 87 | 88 | 89 | } 90 | 91 | {selectedAddress?.title && 92 | 97 | } 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | ) 106 | } 107 | 108 | const styles = StyleSheet.create({ 109 | container: { 110 | width: '100%', 111 | backgroundColor: 'white', 112 | paddingHorizontal: 15, 113 | paddingTop: 20, 114 | paddingBottom: 25, 115 | marginTop: 10, 116 | elevation: 3 117 | }, 118 | headText: { 119 | fontSize: 14, 120 | fontWeight: 'bold', 121 | color: COLORS.green, 122 | marginBottom: 5, 123 | marginLeft: -5 124 | }, 125 | flex: { 126 | flexDirection: 'row', 127 | alignItems: 'center', 128 | justifyContent: 'space-between', 129 | }, 130 | selectAddressWrapper: { 131 | position: 'relative', 132 | flexDirection: 'row', 133 | alignItems: 'center', 134 | justifyContent: 'space-between', 135 | borderWidth: 1, 136 | borderColor: 'rgba(0,0,0,0.15)', 137 | paddingHorizontal: 10, 138 | height: 50, 139 | paddingLeft: 15, 140 | marginTop: 10, 141 | borderRadius: 5, 142 | backgroundColor: 'rgba(0,0,0,0.03)', 143 | } 144 | }) 145 | 146 | export default SelectAddressSection -------------------------------------------------------------------------------- /Client/src/view/screens/BasketScreen.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, Text, View, ScrollView, TouchableOpacity, Dimensions, Image } from 'react-native' 3 | import { SafeAreaView } from 'react-native-safe-area-context' 4 | import BasketItem from '../components/basket/BasketItem' 5 | import { StoreState } from '../../Context/StoreContext' 6 | import COLORS from '../../consts/colors' 7 | import * as Progress from 'react-native-progress'; 8 | import FixedStoreField from '../components/general/FixedConfirmationField' 9 | import RecommendBasketItems from '../components/basket/RecommendBasketItems' 10 | 11 | const BasketScreen = ({ navigation }) => { 12 | 13 | const { basketItems, totalPrice, loading, basketItemsLength } = StoreState() 14 | 15 | return ( 16 | <> 17 | 23 | 24 | 25 | 26 | 27 | 28 | Sepetim 29 | 30 | {basketItemsLength > 0 && 31 | <> 32 | 33 | - 34 | 35 | 36 | 37 | {basketItemsLength} ürün 38 | 39 | 40 | 41 | } 42 | 43 | 44 | 45 | 57 | 58 | 59 | 65 | 66 | 69 | 70 | {basketItemsLength > 0 ? 71 | ( 72 | basketItems.map((item, index) => ( 73 | 74 | 75 | 79 | 80 | )) 81 | 82 | ) 83 | : 84 | 92 | 100 | 101 | Sepetim 104 | 107 | Sepetinizde ürün bulunmamaktadır. 108 | 109 | navigation.navigate('Home')} 112 | style={{ 113 | width: '90%', 114 | }} 115 | > 116 | 119 | Alışverişe Devam Et 120 | 121 | 122 | 123 | 124 | 125 | } 126 | 127 | 128 | 129 | 130 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 144 | 145 | ) 146 | } 147 | 148 | const styles = StyleSheet.create({ 149 | headerInfo: { 150 | paddingHorizontal: 18, 151 | paddingVertical: 20, 152 | display: 'flex', 153 | flexDirection: 'row', 154 | justifyContent: 'space-between', 155 | backgroundColor: 'white', 156 | shadowColor: '#000', 157 | shadowOffset: { 158 | width: 0, 159 | height: 2, 160 | }, 161 | shadowOpacity: 0.25, 162 | shadowRadius: 3.84, 163 | elevation: 5, 164 | zIndex: 5, 165 | }, 166 | headerTxtWrap: { 167 | display: 'flex', 168 | flexDirection: 'row', 169 | 170 | }, 171 | basketItems: { 172 | borderRadius: 5, 173 | display: 'flex', 174 | marginBottom: 15, 175 | }, 176 | flex: { 177 | display: 'flex', 178 | flexDirection: 'row', 179 | justifyContent: 'space-between', 180 | padding: 12, 181 | alignItems: 'center', 182 | }, 183 | btnWrapper: { 184 | backgroundColor: COLORS.green, 185 | color: 'white', 186 | paddingVertical: 12, 187 | borderRadius: 5, 188 | fontWeight: 'bold', 189 | marginTop: 10, 190 | width: '100%', 191 | textAlign: 'center', 192 | }, 193 | textLight: { 194 | color: '#413F42', 195 | fontSize: 12, 196 | color: '#757575', 197 | marginBottom: 10, 198 | }, 199 | text: { 200 | fontWeight: 'bold', 201 | fontSize: 15, 202 | marginBottom: 10, 203 | color: '#575656', 204 | }, 205 | 206 | }) 207 | 208 | export default BasketScreen -------------------------------------------------------------------------------- /Client/src/view/screens/CategoriesScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { View, Text, StyleSheet, Image, FlatList, TouchableOpacity, Dimensions } from 'react-native' 3 | import { SafeAreaView } from 'react-native-safe-area-context' 4 | import { useCategories } from '../../Context/CategoryContext' 5 | import { NGROK_URL } from '@env' 6 | import COLORS from '../../consts/colors' 7 | import SearchSection from '../components/general/SearchSection' 8 | 9 | const CategoriesScreen = ({ navigation }) => { 10 | 11 | const { allCategories } = useCategories() 12 | const [activeIndex, setActiveIndex] = useState(0) 13 | 14 | return ( 15 | <> 16 | 17 | 18 | 19 | 22 | item.name} 26 | renderItem={({ item, index }) => ( 27 | setActiveIndex(index)} 33 | > 34 | 39 | 43 | {item.name} 44 | 45 | 46 | 47 | )} 48 | /> 49 | 50 | 51 | 52 | 53 | 54 | { 55 | allCategories[activeIndex].subCategories.map((item, index) => { 56 | 57 | return ( 58 | navigation.navigate('SearchResults', { queryObj: { type: 'category', name: item.name, categoryName: allCategories[activeIndex].name } })} 62 | > 63 | 64 | 68 | 69 | 70 | 71 | {item.name} 72 | 73 | 74 | 75 | ) 76 | }) 77 | 78 | } 79 | 80 | 81 | 82 | 83 | 84 | 85 | ) 86 | } 87 | const styles = StyleSheet.create({ 88 | container: { 89 | flex: 1, 90 | backgroundColor: 'white', 91 | flexDirection: 'row', 92 | justifyContent: 'space-between', 93 | paddingTop: 15, 94 | }, 95 | categoriesWrapper: { 96 | width: 85, 97 | paddingLeft: 5, 98 | }, 99 | image: { 100 | width: 50, 101 | height: 50, 102 | resizeMode: 'contain', 103 | marginBottom: 10, 104 | }, 105 | categoryItem: { 106 | borderWidth: 1, 107 | borderColor: 'rgba(0,0,0,0.05)', 108 | alignItems: 'center', 109 | justifyContent: 'center', 110 | paddingBottom: 12, 111 | paddingTop: 10, 112 | borderRadius: 8, 113 | marginBottom: 10, 114 | elevation: 3, 115 | backgroundColor: 'white', 116 | }, 117 | categoryItemActive: { 118 | borderWidth: 2, 119 | borderColor: COLORS.green 120 | }, 121 | categoryText: { 122 | fontSize: 8, 123 | fontWeight: 'bold', 124 | color: '#70706f', 125 | paddingHorizontal: 5, 126 | lineHeight: 11, 127 | width: 68, 128 | textAlign: 'center', 129 | }, 130 | categoryTextActive: { 131 | color: '#19a102', 132 | } 133 | , 134 | subCategoryWrapper: { 135 | paddingHorizontal: 10, 136 | width: Dimensions.get('window').width - 100, 137 | position: 'relative', 138 | flexDirection: 'row', 139 | justifyContent: 'space-between', 140 | flexWrap: 'wrap', 141 | }, 142 | subCategoryItem: { 143 | width: 80, 144 | height: 165, 145 | alignItems: 'center', 146 | }, 147 | subCatImageWrapper: { 148 | marginBottom: 14, 149 | borderWidth: 1, 150 | padding: 6, 151 | borderColor: 'rgba(0,0,0,0.002)', 152 | elevation: 2, 153 | backgroundColor: 'white', 154 | borderRadius: 8, 155 | }, 156 | subCatImage: { 157 | width: 75, 158 | height: 105, 159 | resizeMode: 'contain', 160 | }, 161 | subCategoryText: { 162 | fontSize: 10, 163 | fontWeight: 'bold', 164 | color: '#70706f', 165 | lineHeight: 11, 166 | textAlign: 'center', 167 | } 168 | 169 | }) 170 | 171 | export default CategoriesScreen -------------------------------------------------------------------------------- /Client/src/view/components/general/ProductCard.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { Dimensions, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; 3 | import COLORS from '../../../consts/colors'; 4 | import { MaterialCommunityIcons } from 'react-native-vector-icons'; 5 | import { AuthState } from '../../../Context/AuthContext'; 6 | import { Rating } from 'react-native-ratings'; 7 | import { FavoriteState } from '../../../Context/FavoriteContext'; 8 | import CardImageSlider from './CardImageSlider'; 9 | import { NGROK_URL } from '@env' 10 | 11 | const ProductCard = ({ product, navigation }) => { 12 | const { userInfo } = AuthState() 13 | const userId = userInfo.data.id 14 | const { addItemToFavoriteList, removeItemFromFavoriteList } = FavoriteState() 15 | const [likeStatus, setLikeStatus] = useState(false) 16 | const toggleLikeStatus = () => { 17 | setLikeStatus(!likeStatus) 18 | if (!likeStatus) { 19 | addItemToFavoriteList(product._id) 20 | } 21 | else { 22 | removeItemFromFavoriteList(product._id) 23 | } 24 | } 25 | 26 | useEffect(() => { 27 | setLikeStatus(product.likes.includes(userId)) 28 | 29 | }, [product]) 30 | 31 | return ( 32 | <> 33 | 34 | 35 | 38 | { 41 | toggleLikeStatus() 42 | }} 43 | > 44 | 51 | 52 | 53 | 54 | 55 | 56 | { 58 | navigation.navigate('Details', { productId: product._id }) 59 | }} 60 | > 61 | {product.images.length == 1 ? 62 | 67 | : 68 | 71 | } 72 | 73 | 74 | 75 | { 77 | navigation.navigate('Details', { productId: product._id }) 78 | }} 79 | 80 | > 81 | 82 | {product?.seller} {product.name.length > 36 ? product.name.substring(0, 36) + '...' : product.name} 83 | 84 | 85 | 86 | 87 | 103 | ({product.comments.length}) 104 | 105 | 106 | {product.price} TL 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | ) 115 | } 116 | const styles = StyleSheet.create({ 117 | 118 | card: { 119 | height: 322, 120 | backgroundColor: 'white', 121 | width: Dimensions.get('screen').width / 2 - 20, 122 | borderRadius: 10, 123 | marginBottom: 20, 124 | padding: 10, 125 | marginHorizontal: 2, 126 | elevation: 3, 127 | }, 128 | cardImgContainer: { 129 | height: 185, 130 | overflow: 'hidden', 131 | marginBottom: 7, 132 | marginTop: 8, 133 | }, 134 | cardImg: { 135 | height: 150, 136 | resizeMode: 'contain', 137 | width: '100%', 138 | height: '100%', 139 | }, 140 | cardDetails: { 141 | marginLeft: 4, 142 | }, 143 | cardTitle: { 144 | fontSize: 12, 145 | fontWeight: 'bold', 146 | color: COLORS.dark, 147 | marginTop: 2, 148 | color: 'gray', 149 | lineHeight: 17, 150 | // borderWidth :1, 151 | // borderColor: 'red', 152 | height: 39, 153 | }, 154 | heartIcon: { 155 | position: 'absolute', 156 | top: -2, 157 | borderRadius: 50, 158 | padding: 5, 159 | right: -5, 160 | zIndex: 5, 161 | }, 162 | price: { 163 | fontSize: 13, 164 | fontWeight: 'bold', 165 | color: COLORS.green, 166 | marginTop: 13, 167 | marginLeft: 2 168 | }, 169 | flex: { 170 | display: 'flex', flexDirection: 'row', alignItems: 'center' 171 | } 172 | }) 173 | 174 | export default ProductCard; -------------------------------------------------------------------------------- /Server/Controllers/store.js: -------------------------------------------------------------------------------- 1 | const asyncErrorWrapper = require("express-async-handler"); 2 | const Order = require("../Models/order"); 3 | const CustomError = require("../Helpers/error/CustomError"); 4 | const OrderItem = require("../Models/orderitem"); 5 | 6 | const getAllBaskettems = asyncErrorWrapper(async (req, res, next) => { 7 | 8 | const order = await Order.findOne({ user: req.user.id, complete: false }) 9 | const orderItems = await OrderItem.find({ order: order._id, orderStatus: false }) 10 | .populate( "product", "name price images seller banner subCategory") 11 | .populate({ 12 | path: "product", 13 | populate: { 14 | path: 'banner', 15 | } 16 | }) 17 | .populate({ 18 | path: "product", 19 | populate: { 20 | path: 'subCategory', 21 | select: 'name ' 22 | } 23 | }) 24 | 25 | 26 | 27 | 28 | const totalPrice = orderItems.reduce((acc, item) => { 29 | return acc + item.product.price * item.quantity 30 | }, 0) 31 | 32 | return res.status(200).json({ 33 | success: true, 34 | totalPrice, 35 | orderItems 36 | }) 37 | 38 | }) 39 | 40 | const addToBasket = asyncErrorWrapper(async (req, res, next) => { 41 | const { productId ,selectedSize } = req.body; 42 | 43 | if (!productId) { 44 | return next(new CustomError("Please provide a product id", 400)) 45 | } 46 | 47 | const order = await Order.findOne({ user: req.user.id, complete: false }) 48 | 49 | const orderItem = await OrderItem.findOne({ 50 | product: productId, orderStatus: false, 51 | order: order._id, 52 | selectedSize: selectedSize 53 | }) 54 | 55 | if (orderItem) { 56 | orderItem.quantity += 1; 57 | await orderItem.save() 58 | } 59 | else { 60 | const newOrderItem = await OrderItem.create({ 61 | product: productId, 62 | order, 63 | quantity: 1, 64 | orderStatus: false, 65 | selectedSize: selectedSize 66 | }) 67 | await newOrderItem.save() 68 | 69 | return res.status(200).json({ 70 | success: true, 71 | orderItem: newOrderItem 72 | }) 73 | 74 | } 75 | 76 | return res.status(200).json({ 77 | success: true, 78 | orderItem 79 | }) 80 | 81 | }) 82 | 83 | const deleteBasketItem = asyncErrorWrapper(async (req, res, next) => { 84 | const {itemId} = req.params; 85 | const order = await Order.findOne({ user: req.user.id, complete: false }) 86 | 87 | const orderItem = await OrderItem.findOne({ 88 | _id: itemId, 89 | order: order._id, 90 | orderStatus: false 91 | }) 92 | 93 | if (!orderItem) { 94 | return next(new CustomError("Order item not found", 400)) 95 | } 96 | 97 | await orderItem.remove() 98 | 99 | return res.status(200).json({ 100 | success: true, 101 | message: "Item removed from card" 102 | }) 103 | 104 | }) 105 | 106 | const deleteAllBasketItems = asyncErrorWrapper(async (req, res, next) => { 107 | 108 | const order = await Order.findOne({ user: req.user.id, complete: false }) 109 | 110 | await OrderItem.deleteMany({ order: order._id, orderStatus: false }) 111 | 112 | return res.status(200).json({ 113 | success: true, 114 | message: "All items removed from card" 115 | }) 116 | 117 | }) 118 | 119 | 120 | 121 | const increaseQuantity = asyncErrorWrapper(async (req, res, next) => { 122 | const { itemId } = req.body; 123 | 124 | if (!itemId) { 125 | return next(new CustomError("Please provide a item id", 400)) 126 | } 127 | const order = await Order.findOne({ user: req.user.id, complete: false }) 128 | 129 | const orderItem = await OrderItem.findOne({ 130 | _id: itemId, 131 | order: order._id, 132 | orderStatus: false 133 | }) 134 | if (!orderItem) { 135 | return next(new CustomError("Order item not found", 400)) 136 | } 137 | orderItem.quantity += 1; 138 | await orderItem.save() 139 | return res.status(200).json({ 140 | success: true, 141 | message: "Quantity increased", 142 | }) 143 | }) 144 | 145 | 146 | const decreaseQuantity = asyncErrorWrapper(async (req, res, next) => { 147 | const { itemId } = req.body; 148 | 149 | if (!itemId) { 150 | return next(new CustomError("Please provide a product id", 400)) 151 | } 152 | const order = await Order.findOne({ user: req.user.id, complete: false }) 153 | 154 | const orderItem = await OrderItem.findOne({ 155 | _id: itemId, 156 | order: order._id, 157 | orderStatus: false 158 | }) 159 | if (!orderItem) { 160 | return next(new CustomError("Order item not found", 400)) 161 | } 162 | orderItem.quantity -= 1; 163 | 164 | if (orderItem.quantity === 0) { 165 | await orderItem.remove() 166 | return res.status(200).json({ 167 | success: true, 168 | message: "Order Item removed " 169 | }) 170 | } 171 | await orderItem.save() 172 | 173 | return res.status(200).json({ 174 | success: true, 175 | message: "Quantity increased", 176 | }) 177 | }) 178 | 179 | 180 | const getAllDeliveryItems = asyncErrorWrapper(async (req, res, next) => { 181 | 182 | const order = await Order.findOne({ user: req.user.id, complete: false }) 183 | 184 | const orderItems = await OrderItem.find({ order: order._id, orderStatus: false }).select("quantity product selectedSize ") 185 | .populate({ 186 | path: "product", 187 | select: "name price images seller banner", 188 | populate: { 189 | path: "banner", 190 | } 191 | }) 192 | 193 | const productGroupSeller = orderItems.reduce((acc, item) => { 194 | const seller = item.product.seller 195 | if (!acc[seller]) { 196 | acc[seller] = { 197 | seller: item.product.seller, 198 | products: [] 199 | } 200 | } 201 | acc[seller].products.push(item) 202 | return acc 203 | }, {}) 204 | 205 | 206 | const totalPrice = orderItems.reduce((acc, item) => { 207 | return acc + item.product.price * item.quantity 208 | }, 0) 209 | 210 | return res.status(200).json({ 211 | success: true, 212 | totalPrice, 213 | productGroupSeller 214 | }) 215 | 216 | }) 217 | 218 | module.exports = { 219 | addToBasket, 220 | getAllBaskettems, 221 | deleteBasketItem, 222 | deleteAllBasketItems, 223 | increaseQuantity, 224 | decreaseQuantity, 225 | getAllDeliveryItems, 226 | } -------------------------------------------------------------------------------- /Client/src/view/components/checkout/BankCardBottomSheet.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, Text, StyleSheet, TouchableOpacity, FlatList, Image } from 'react-native' 3 | import BottomSheet from 'react-native-gesture-bottom-sheet' 4 | import { Ionicons } from 'react-native-vector-icons' 5 | import COLORS from '../../../consts/colors' 6 | 7 | const BankCardBottomSheet = ({ bottomSheet, bankCards, setSelectedCard }) => { 8 | return ( 9 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | Kart Seçiniz 21 | 22 | { bottomSheet.current.close() }} 24 | > 25 | 30 | 31 | 32 | 33 | 34 | item._id} 37 | renderItem={({ item }) => ( 38 | { 51 | setSelectedCard({ 52 | cardNumber: { 53 | value: item.number, 54 | isValid: true 55 | }, 56 | cardName: { 57 | value: item.name, 58 | isValid: true 59 | }, 60 | cardExpiredYear: { 61 | value: item.expiredYear, 62 | isValid: true 63 | }, 64 | cardExpiredMonth: { 65 | value: item.expiredMonth, 66 | isValid: true 67 | }, 68 | cardCvv: { 69 | value: item.cvv, 70 | isValid: true 71 | }, 72 | }) 73 | 74 | bottomSheet.current.close() 75 | } 76 | } 77 | > 78 | 79 | 80 | 83 | {item.name} 84 | 85 | 88 | { 89 | String(item.number).replace( 90 | String(item.number).substring(6, 12), 91 | '****' 92 | ) 93 | } 94 | 95 | 96 | 97 | 98 | 99 | { 100 | String(item.number).substring(0, 1) === '4' && 101 | 105 | || 106 | String(item.number).substring(0, 1) === '5' && 107 | 111 | || 112 | 116 | 117 | } 118 | 119 | 120 | )} 121 | /> 122 | 123 | 124 | 125 | 126 | 127 | 128 | ) 129 | } 130 | 131 | const styles = StyleSheet.create({ 132 | bottomSheetContent: { 133 | backgroundColor: 'white', 134 | borderWidth: 1, 135 | borderColor: COLORS.grey, 136 | height: 395, 137 | }, 138 | bottomSheetContentHeader: { 139 | backgroundColor: 'rgba(0,0,0,0.06)', 140 | paddingVertical: 10, 141 | paddingHorizontal: 20, 142 | flexDirection: 'row', 143 | justifyContent: 'space-between', 144 | alignItems: 'center', 145 | marginBottom: 10, 146 | } 147 | }) 148 | 149 | export default BankCardBottomSheet -------------------------------------------------------------------------------- /Client/src/view/screens/FavoriteListScreen.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, Text, View, Image, ScrollView, TouchableOpacity, Dimensions } from 'react-native' 3 | import { SafeAreaView } from 'react-native-safe-area-context' 4 | import { AuthState } from '../../Context/AuthContext' 5 | import { Entypo } from 'react-native-vector-icons' 6 | import COLORS from '../../consts/colors' 7 | import * as Progress from 'react-native-progress'; 8 | import { FavoriteState } from '../../Context/FavoriteContext' 9 | import FavoriteCard from '../components/favorite/FavoriteCard' 10 | 11 | const FavoriteListScreen = ({ navigation, route }) => { 12 | 13 | const { userInfo } = AuthState() 14 | const { favoriteList, loading } = FavoriteState(); 15 | 16 | return ( 17 | 18 | 25 | { 26 | favoriteList.length > 0 && 27 | 28 | 35 | 36 | 37 | 40 | 46 | 47 | {userInfo.data.username} 48 | 49 | 50 | 51 | 52 | Favorilerim 53 | 54 | {favoriteList.length} ürün 55 | 56 | 57 | 58 | 59 | } 60 | 61 | 72 | 73 | 74 | {favoriteList.map((item, index) => { 75 | 76 | return ( 77 | 78 | 82 | 83 | 84 | ) 85 | }) } 86 | 87 | 88 | { 89 | favoriteList.length === 0 && 90 | 93 | 100 | 101 | 102 | Favorilerim 103 | 104 | 105 | Favorilerinizde ürün bulunmamaktadır. 106 | 107 | navigation.navigate('Home')} 115 | > 116 | 119 | Alışverişe Devam Et 120 | 121 | 122 | 123 | } 124 | 125 | 126 | 127 | 128 | ) 129 | } 130 | 131 | const styles = StyleSheet.create({ 132 | bannerSection: { 133 | height: 190, 134 | justifyContent: 'center', 135 | alignItems: 'center', 136 | borderWidth: 2, 137 | borderColor: 'red', 138 | flexDirection: 'row', 139 | justifyContent: 'space-between', 140 | }, 141 | banner_right: { 142 | backgroundColor: 'rgb(239, 50, 49)', 143 | height: 190, 144 | width: Dimensions.get('screen').width - 170, 145 | paddingLeft: 15, 146 | paddingTop: 20, 147 | }, 148 | banner_title: { 149 | color: COLORS.white, 150 | fontSize: 29, 151 | fontWeight: 'bold', 152 | marginBottom: 28, 153 | marginTop: 10, 154 | }, 155 | banner_right_top: { 156 | flexDirection: 'row', 157 | alignItems: 'center', 158 | marginBottom: 20, 159 | }, 160 | banner_text: { 161 | color: COLORS.white, 162 | fontWeight: 'bold', 163 | fontSize: 13 164 | }, 165 | itemsContainer: { 166 | flexDirection: 'column', 167 | alignItems: 'center', 168 | backgroundColor: COLORS.white, 169 | }, 170 | btnWrapper: { 171 | backgroundColor: COLORS.green, 172 | color: 'white', 173 | paddingVertical: 12, 174 | borderRadius: 5, 175 | fontWeight: 'bold', 176 | marginTop: 10, 177 | width: '90%', 178 | textAlign: 'center', 179 | }, 180 | emptyFavoritesWrapper: { 181 | height: Dimensions.get('screen').height - 125, 182 | backgroundColor: '#fff', 183 | justifyContent: 'center', 184 | alignItems: 'center', 185 | }, 186 | textLight: { 187 | color: '#413F42', 188 | fontSize: 12, 189 | color: '#757575', 190 | marginBottom: 10, 191 | }, 192 | text: { 193 | fontWeight: 'bold', 194 | fontSize: 15, 195 | marginBottom: 10, 196 | color: '#575656', 197 | } 198 | 199 | }) 200 | 201 | export default FavoriteListScreen -------------------------------------------------------------------------------- /Server/Controllers/order.js: -------------------------------------------------------------------------------- 1 | const asyncErrorWrapper = require("express-async-handler"); 2 | const Order = require("../Models/order"); 3 | const OrderItem = require("../Models/orderitem"); 4 | const { v4: uuidv4 } = require('uuid'); 5 | const BankCard = require("../Models/bankCard"); 6 | const Address = require("../Models/address"); 7 | const Evaluation = require("../Models/evaluation"); 8 | 9 | const completeOrder = asyncErrorWrapper(async (req, res, next) => { 10 | 11 | const { selectedCard, selectedAddress } = req.body; 12 | 13 | const order = await Order.findOne({ user: req.user.id, complete: false }) 14 | 15 | const orderItems = await OrderItem.find({ order: order._id, orderStatus: false }) 16 | 17 | orderItems.forEach(async (item) => { 18 | item.orderStatus = true; 19 | item.date_added = Date.now(); 20 | await item.save() 21 | }) 22 | 23 | order.complete = true; 24 | order.dateOrdered = Date.now(); 25 | 26 | if(selectedAddress._id) { 27 | order.orderAddress = selectedAddress; 28 | } 29 | else { 30 | const address = await Address.findOne({ 31 | user: req.user.id, 32 | title: selectedAddress.title, 33 | detail: selectedAddress.detail, 34 | city: selectedAddress.city, 35 | }) 36 | order.orderAddress = address._id; 37 | } 38 | 39 | 40 | 41 | const bankCard = await BankCard.findOne({ user: req.user.id, number: selectedCard.cardNumber.value }) 42 | 43 | if (!bankCard) { 44 | 45 | const cardNumber = selectedCard.cardNumber.value.replace(/\s/g, ''); 46 | 47 | const cardType = cardNumber.at(0) === "4" && "Visa" || cardNumber.at(0) === "5" && "MasterCard" || cardNumber.at(0) === "3" && "American Express" || "1."; 48 | 49 | const newBankCard = await BankCard.create({ 50 | user: req.user.id, 51 | number: cardNumber, 52 | name: cardType + "Kartım", 53 | expiredMonth: selectedCard.cardExpiredMonth.value, 54 | expiredYear: selectedCard.cardExpiredYear.value, 55 | cvv: selectedCard.cardCvv.value 56 | }) 57 | order.orderPayment = newBankCard._id; 58 | 59 | } 60 | else { 61 | 62 | order.orderPayment = bankCard._id; 63 | } 64 | 65 | await order.save() 66 | 67 | 68 | await Order.create({ 69 | user: req.user.id, 70 | complete: false, 71 | transaction_id: uuidv4() 72 | }) 73 | 74 | return res.status(200).json({ 75 | success: true, 76 | message: "Order completed" 77 | }) 78 | 79 | 80 | }) 81 | 82 | const getAllMyOrders = asyncErrorWrapper(async (req, res, next) => { 83 | 84 | const orders = await Order.find({ user: req.user.id, complete: true }).select("dateOrdered user transaction_id ").sort({ dateOrdered: -1 }) 85 | 86 | const orderContent = [] 87 | 88 | await Promise.all(orders.map(async (order) => { 89 | const orderItems = await OrderItem.find({ order: order._id, orderStatus: true }).select("-order -orderStatus") 90 | .populate({ 91 | path: "product", 92 | select: "name price images seller banner", 93 | populate: { 94 | path: "banner", 95 | } 96 | }).sort({ date_added: -1 }) 97 | 98 | const totalPrice = orderItems.reduce((acc, item) => { 99 | 100 | return acc + item.product.price * item.quantity 101 | }, 0) 102 | 103 | orderContent.push({ 104 | order, 105 | orderItems, 106 | totalPrice 107 | }) 108 | 109 | 110 | })) 111 | 112 | 113 | orderContent.sort((a, b) => { 114 | return new Date(b.order.dateOrdered) - new Date(a.order.dateOrdered) 115 | }) 116 | 117 | 118 | return res.status(200).json({ 119 | success: true, 120 | orderContent 121 | }) 122 | 123 | }) 124 | 125 | const getOrderDetails = asyncErrorWrapper(async (req, res, next) => { 126 | 127 | const { id } = req.params; 128 | 129 | const order = await Order.findById(id).select("dateOrdered transaction_id orderAddress orderPayment").populate({ 130 | path: "orderAddress", 131 | select: "name surname phone address city district neighborhood detail" 132 | }).populate({ 133 | path: "orderPayment", 134 | select: "number" 135 | }) 136 | const orderItems = await OrderItem.find({ order: order._id, orderStatus: true }).select("quantity product selectedSize ") 137 | .populate({ 138 | path: "product", 139 | select: "name price images seller banner", 140 | populate: { 141 | path: "banner", 142 | } 143 | }) 144 | 145 | const totalPrice = orderItems.reduce((acc, item) => { 146 | 147 | return acc + item.product.price * item.quantity 148 | 149 | }, 0) 150 | 151 | const productGroupSeller = orderItems.reduce((acc, item) => { 152 | const seller = item.product.seller 153 | 154 | if (!acc[seller]) { 155 | acc[seller] = { 156 | banner: item.product.banner, 157 | seller: item.product.seller, 158 | products: [] 159 | } 160 | } 161 | acc[seller].products.push(item) 162 | return acc 163 | }, {}) 164 | 165 | 166 | return res.status(200).json({ 167 | success: true, 168 | order, 169 | totalPrice, 170 | orderItems: productGroupSeller, 171 | totalItemCount: orderItems.length 172 | }) 173 | 174 | }) 175 | 176 | const getAllEvaluatableItems = asyncErrorWrapper(async (req, res, next) => { 177 | 178 | const items = await Evaluation.find({ user: req.user.id, appravolStatus: false }).populate({ 179 | path: "product", 180 | select: "name banner images price averageRating", 181 | populate: { 182 | path: "banner", 183 | select: "deliveryTime name" 184 | } 185 | }). 186 | populate({ 187 | path: "orderItem", 188 | select: "date_added", 189 | }).sort({ date_added: -1 }) 190 | 191 | 192 | return res.status(200).json({ 193 | success: true, 194 | len: items.length, 195 | items 196 | }) 197 | 198 | }) 199 | 200 | const getAllApprovedEvaluatableItems = asyncErrorWrapper(async (req, res, next) => { 201 | 202 | const approvedItems = await Evaluation.find({ 203 | user: req.user.id, 204 | appravolStatus: true, 205 | comment: { $ne: null } 206 | }).populate({ 207 | 208 | path: "product", 209 | select: "name banner images price ", 210 | populate: { 211 | path: "banner", 212 | select: "name" 213 | } 214 | }).populate({ 215 | path: "comment", 216 | select: "content rating nameVisible", 217 | }) 218 | 219 | return res.status(200).json({ 220 | success: true, 221 | approvedItems 222 | }) 223 | 224 | }) 225 | 226 | 227 | module.exports = { 228 | completeOrder, 229 | getAllMyOrders, 230 | getOrderDetails, 231 | getAllEvaluatableItems, 232 | getAllApprovedEvaluatableItems 233 | } -------------------------------------------------------------------------------- /Client/src/view/screens/CheckoutScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { Text, View, StyleSheet, TouchableOpacity, Image, ScrollView, Keyboard } from 'react-native' 3 | import { SafeAreaView } from 'react-native-safe-area-context' 4 | import { Ionicons } from 'react-native-vector-icons' 5 | import StoreService from '../../services/StoreService' 6 | import FixedConfirmationField from '../components/general/FixedConfirmationField' 7 | import PaymentSection from '../components/checkout/PaymentSection' 8 | import SelectAddressSection from '../components/checkout/SelectAddressSection' 9 | import AddressService from '../../services/AddressService' 10 | import BankCardService from '../../services/BankCardService' 11 | import DeliveryOptions from '../components/checkout/DeliveryOptions' 12 | import ConfirmationField from '../components/checkout/ConfirmationField' 13 | import ContractForms from '../components/checkout/ContractForms' 14 | 15 | const CheckoutScreen = ({ navigation, route }) => { 16 | const [confirmChecked, setConfirmChecked] = useState({ 17 | value: false, 18 | error: null 19 | }) 20 | const [deliveryItems, setDeliveryItems] = useState({}) 21 | const [totalPrice, setTotalPrice] = useState(0) 22 | const [addresses, setAddresses] = useState([]) 23 | const [bankCards, setBankCards] = useState([]) 24 | const [selectedCard, setSelectedCard] = useState({ 25 | cardNumber: { 26 | value: null, 27 | isValid: false 28 | }, 29 | cardName: { 30 | value: null, 31 | isValid: false 32 | }, 33 | cardExpiredYear: { 34 | value: null, 35 | isValid: false 36 | }, 37 | cardExpiredMonth: { 38 | value: null, 39 | isValid: false 40 | }, 41 | cardCvv: { 42 | value: null, 43 | isValid: false 44 | }, 45 | }) 46 | const [selectedAddress, setSelectedAddress] = useState({ 47 | title: null, 48 | detail: null, 49 | city: null, 50 | district: null, 51 | neighborhood: null, 52 | name: null, 53 | surname: null, 54 | phone: null, 55 | isValid: false 56 | }) 57 | const [keyboardVisible, setKeyboardVisible] = useState(false) 58 | const [disabled, setDisabled] = useState(false) 59 | 60 | const getAllDeliveryItems = async () => { 61 | const { data } = await StoreService.getAllDeliveryItems(); 62 | setDeliveryItems(data.productGroupSeller) 63 | setTotalPrice(data.totalPrice) 64 | } 65 | 66 | const getAllAddresses = async (status = false) => { 67 | try { 68 | const { data } = await AddressService.getAllAddresses() 69 | setAddresses(data.addresses) 70 | if (!status) { 71 | setSelectedAddress(data.addresses[0]) 72 | } 73 | 74 | } catch (error) { 75 | setAddresses([]) 76 | setSelectedAddress({}) 77 | } 78 | } 79 | 80 | const getAllBankCards = async () => { 81 | try { 82 | const { data } = await BankCardService.getAllBankCards() 83 | setBankCards(data.bankCards) 84 | } 85 | catch (error) { 86 | setBankCards([]) 87 | } 88 | } 89 | 90 | useEffect(() => { 91 | getAllDeliveryItems() 92 | getAllAddresses() 93 | getAllBankCards() 94 | 95 | }, []) 96 | 97 | useEffect(() => { 98 | const unsubscribe = navigation.addListener('focus', () => { 99 | if (route?.params?.address) { 100 | getAllAddresses(true) 101 | setSelectedAddress(route?.params?.address) 102 | } 103 | } ) 104 | 105 | return unsubscribe 106 | 107 | }, [route.params]) 108 | 109 | useEffect(() => { 110 | Keyboard.addListener("keyboardDidShow", () => { 111 | setKeyboardVisible(true) 112 | }) 113 | Keyboard.addListener("keyboardDidHide", () => { 114 | setKeyboardVisible(false) 115 | }) 116 | }, []) 117 | 118 | return ( 119 | 120 | 126 | 127 | 128 | 129 | navigation.goBack()} 134 | /> 135 | 136 | Güvenli Ödeme 141 | 148 | 149 | 155 | 161 | 162 | 167 | 168 | 172 | 173 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | {!keyboardVisible && 195 | } 196 | 197 | 198 | 199 | ) 200 | } 201 | 202 | 203 | const styles = StyleSheet.create({ 204 | headerSection: { 205 | height: 60, 206 | padding: 18, 207 | backgroundColor: 'white', 208 | elevation: 5, 209 | flexDirection: 'row', 210 | justifyContent: 'space-between', 211 | alignItems: 'center', 212 | zIndex: 1, 213 | }, 214 | 215 | }) 216 | 217 | 218 | export default CheckoutScreen --------------------------------------------------------------------------------