├── CODEOWNERS ├── frontend ├── .npmrc ├── postcss.config.js ├── public │ └── airbnb-logo.png ├── constants │ ├── index.js │ └── airbnb.json ├── styles │ └── globals.css ├── utils │ ├── index.js │ └── string.js ├── tailwind.config.js ├── next.config.js ├── pages │ ├── _app.js │ └── index.js ├── context │ └── WalletConnectionProvider.js ├── components │ ├── Listing │ │ ├── Listings.js │ │ ├── ListingItem.js │ │ ├── ReserveListingModal.js │ │ ├── AddListingModal.js │ │ └── EditListingModal.js │ ├── Footer.js │ ├── FilterMenu.js │ └── Header.js ├── package.json ├── data │ ├── airbnb.js │ └── listings.js ├── README.md └── hooks │ └── useAirbnb.js ├── package.json ├── solana-solution ├── constant.rs ├── error.rs ├── states.rs └── lib.rs └── .gitignore /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @davidrakosi -------------------------------------------------------------------------------- /frontend/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "react-hot-toast": "^2.3.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/airbnb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverProgrammers/airbnb-clone-youtube/HEAD/frontend/public/airbnb-logo.png -------------------------------------------------------------------------------- /frontend/constants/index.js: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | export const AIRBNB_PROGRAM_PUBKEY = new PublicKey("J5yzCZrNZJR6K5FUwwZ2SzjTvg8DzomcYqcaZJm27Y6v"); 4 | -------------------------------------------------------------------------------- /frontend/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .phantom-button:hover { 6 | background-color: rgb(243 244 246) !important; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/utils/index.js: -------------------------------------------------------------------------------- 1 | export const authorFilter = (authorBase58PublicKey) => ({ 2 | memcmp: { 3 | offset: 8, // Discriminator. 4 | bytes: authorBase58PublicKey, 5 | }, 6 | }) 7 | -------------------------------------------------------------------------------- /frontend/utils/string.js: -------------------------------------------------------------------------------- 1 | export const truncate = (longString, limit = 10) => { 2 | if (longString.length > limit) { 3 | return longString.substring(0, limit) + '...' 4 | } 5 | 6 | return longString 7 | } 8 | -------------------------------------------------------------------------------- /solana-solution/constant.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[constant] 4 | pub const USER_TAG: &[u8] = b"USER_STATE"; 5 | 6 | #[constant] 7 | pub const AIRBNB_TAG: &[u8] = b"AIRBNB_STATE"; 8 | 9 | #[constant] 10 | pub const BOOK_TAG: &[u8] = b"BOOK_STATE"; -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [require('tailwind-scrollbar-hide')], 8 | } 9 | -------------------------------------------------------------------------------- /frontend/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | env: { 5 | REACT_APP_CLUSTER: process.env.REACT_APP_CLUSTER, 6 | }, 7 | images: { 8 | domains: [ 9 | 'a0.muscache.com' 10 | ], 11 | }, 12 | } 13 | 14 | module.exports = nextConfig 15 | -------------------------------------------------------------------------------- /solana-solution/error.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum AirbnbError { 5 | #[msg("You are not authorized to perform this action.")] 6 | Unauthorized, 7 | #[msg("Not allowed")] 8 | NotAllowed, 9 | #[msg("Math operation overflow")] 10 | MathOverflow, 11 | #[msg("Already marked")] 12 | AlreadyMarked, 13 | } -------------------------------------------------------------------------------- /frontend/pages/_app.js: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic' 2 | import Head from 'next/head' 3 | import '../styles/globals.css' 4 | 5 | const WalletConnectionProvider = dynamic(() => import('../context/WalletConnectionProvider'), { 6 | ssr: false, 7 | }) 8 | 9 | function MyApp({ Component, pageProps }) { 10 | return ( 11 | <> 12 | 13 | Airbnb Clone 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default MyApp 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | /.history 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | .pnpm-debug.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # lock files 36 | yarn.lock 37 | package-lock.json 38 | 39 | # snyk code quality 40 | .dccache 41 | 42 | # anchor specific 43 | .anchor 44 | .DS_Store 45 | target 46 | **/*.rs.bk 47 | -------------------------------------------------------------------------------- /frontend/context/WalletConnectionProvider.js: -------------------------------------------------------------------------------- 1 | import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react' 2 | import { WalletModalProvider } from '@solana/wallet-adapter-react-ui' 3 | import { PhantomWalletAdapter } from '@solana/wallet-adapter-wallets' 4 | import { useMemo } from 'react' 5 | 6 | const WalletConnectionProvider = ({ children }) => { 7 | const endpoint = useMemo(() => 'https://api.devnet.solana.com', []) 8 | 9 | const wallets = useMemo(() => [new PhantomWalletAdapter()], []) 10 | 11 | return ( 12 | 13 | 14 | {children} 15 | 16 | 17 | ) 18 | } 19 | 20 | export default WalletConnectionProvider 21 | -------------------------------------------------------------------------------- /frontend/components/Listing/Listings.js: -------------------------------------------------------------------------------- 1 | import ListingItem from './ListingItem' 2 | 3 | function Listings({ connected, showReservedListing, listings, toggleEditListingModal, toggleReserveListingModal, removeListing, unreserveListing }) { 4 | return ( 5 |
6 |
7 | {listings.map((listing) => ( 8 | // console.log(listing.account) 9 | 10 | ))} 11 |
12 |
13 | ) 14 | } 15 | 16 | export default Listings 17 | -------------------------------------------------------------------------------- /solana-solution/states.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[account] 4 | #[derive(Default)] 5 | pub struct UserProfile { 6 | pub authority: Pubkey, 7 | pub last_airbnb: u8, 8 | pub airbnb_count: u8, 9 | } 10 | 11 | /// Size 2605 = 32 + 1 + 4 + 256 + 4 + 2048 + 4 + 256 12 | #[account] 13 | #[derive(Default)] 14 | pub struct AirbnbAccount { 15 | pub authority: Pubkey, // 32 16 | pub idx: u8, // 1 17 | pub location: String, // 4 + 256 18 | pub country: String, // 4 + 256 19 | pub image: String, // 4 + 2048 20 | pub price: String, // 4 + 256 21 | pub isReserved: bool // 8 22 | } 23 | 24 | #[account] 25 | #[derive(Default)] 26 | pub struct BookingAccount { 27 | pub authority: Pubkey, //32 28 | pub date: String, // 4 + 256 29 | pub idx: u8, // 1 30 | pub location: String, // 4 + 256 31 | pub country: String, // 4 + 256 32 | pub image: String, // 4 + 2048 33 | pub price: String, // 4 + 256 34 | pub isReserved: bool // 8 35 | } -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "export": "npm run build && next export -o _static" 11 | }, 12 | "dependencies": { 13 | "@badrap/bar-of-progress": "^0.2.1", 14 | "@headlessui/react": "^1.6.6", 15 | "@heroicons/react": "^2.0.7", 16 | "@project-serum/anchor": "^0.25.0", 17 | "@solana/wallet-adapter-react": "^0.15.18", 18 | "@solana/wallet-adapter-react-ui": "^0.9.16", 19 | "@solana/wallet-adapter-wallets": "^0.18.6", 20 | "@solana/web3.js": "^1.56.0", 21 | "date-fns": "^2.29.2", 22 | "next": "12.1.5", 23 | "react": "^18.2.0", 24 | "react-date-range": "^1.4.0", 25 | "react-dom": "18.0.0", 26 | "react-hot-toast": "^2.3.0", 27 | "tailwind-scrollbar-hide": "^1.1.7" 28 | }, 29 | "devDependencies": { 30 | "autoprefixer": "^10.4.8", 31 | "eslint": "8.13.0", 32 | "eslint-config-next": "12.1.5", 33 | "postcss": "^8.4.16", 34 | "tailwindcss": "^3.1.8" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /frontend/components/Footer.js: -------------------------------------------------------------------------------- 1 | import { GlobeAmericasIcon } from '@heroicons/react/24/solid' 2 | 3 | function Footer() { 4 | return ( 5 |
6 |
7 |

© 2022 Airbnb, Inc.

8 |

Privacy

9 |

Terms

10 |

Sitemap

11 |

Destinations

12 |
13 | 14 |
15 |
16 | 17 |

English (US)

18 |
19 |
20 |

RM

21 |

MYR

22 |
23 |
24 |

Support & resources

25 |
26 |
27 |
28 | ) 29 | } 30 | 31 | export default Footer 32 | -------------------------------------------------------------------------------- /frontend/data/airbnb.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: '1', 4 | name: 'Abiansemal', 5 | country: 'Indonesia', 6 | description: '', 7 | distance: { 8 | km: 1997, 9 | }, 10 | price: 1608, 11 | rating: 4.87, 12 | imageURL: 'https://a0.muscache.com/im/pictures/e25a9b25-fa98-4160-bfd1-039287bf38b6.jpg', 13 | }, 14 | { 15 | id: '2', 16 | name: 'Tambon Nong Kae', 17 | country: 'Thailand', 18 | description: '', 19 | distance: { 20 | km: 1050, 21 | }, 22 | price: 494, 23 | rating: 4.95, 24 | imageURL: 'https://a0.muscache.com/im/pictures/77c3c61e-930a-4e7c-ab4d-59413c1f0b87.jpg', 25 | }, 26 | { 27 | id: '3', 28 | name: 'Cabangan', 29 | country: 'Philippines', 30 | description: '', 31 | distance: { 32 | km: 2414, 33 | }, 34 | price: 636, 35 | rating: 4.79, 36 | imageURL: 'https://a0.muscache.com/im/pictures/64fc0202-bcd7-46e3-87f1-3f7f0f8ead15.jpg', 37 | }, 38 | { 39 | id: '4', 40 | name: 'Ko Samui District', 41 | country: 'Thailand', 42 | description: '', 43 | distance: { 44 | km: 718, 45 | }, 46 | price: 478, 47 | rating: 4.77, 48 | imageURL: 'https://a0.muscache.com/im/pictures/36e5b4b5-0f4f-4703-a2c3-7a3cc9d9d82e.jpg', 49 | }, 50 | ] 51 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /frontend/components/Listing/ListingItem.js: -------------------------------------------------------------------------------- 1 | import { StarIcon } from '@heroicons/react/20/solid' 2 | import { PencilIcon, TrashIcon, HeartIcon } from '@heroicons/react/24/outline' 3 | 4 | function ListingItem({ idx, publickey, connected, showReservedListing,location,country, date, distance, price, rating, image, isReserved, reservation, removeListing, toggleEditListingModal, toggleReserveListingModal, unreserveListing }) { 5 | 6 | const formatNumber = (num) => { 7 | return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') 8 | } 9 | 10 | const handleReserve = () => { 11 | if (isReserved) { 12 | unreserveListing(publickey,idx) 13 | return 14 | } 15 | 16 | toggleReserveListingModal(true, idx) 17 | } 18 | return ( 19 |
20 |
21 | 22 | 23 | {connected && ( 24 |
25 | toggleEditListingModal(idx)} className="w-6 h-6 text-white opacity-80" /> 26 | removeListing(publickey,idx)} className="w-6 h-6 text-white opacity-80" /> 27 | 28 |
29 | )} 30 |
31 | 32 |
33 |
34 |

35 | {location}, {country} 36 |

37 | 38 |
39 | 40 |

{4.8}

41 |
42 |
43 | 44 |

{788} kilometers

45 | 46 | {showReservedListing &&

{date}

} 47 | 48 |

49 | RM {price} 50 |  night 51 |

52 |
53 |
54 | ) 55 | } 56 | 57 | export default ListingItem 58 | -------------------------------------------------------------------------------- /frontend/constants/airbnb.json: -------------------------------------------------------------------------------- 1 | {"version":"0.1.0","name":"clever_airbnb","constants":[{"name":"USER_TAG","type":{"defined":"&[u8]"},"value":"b\"USER_STATE\""},{"name":"AIRBNB_TAG","type":{"defined":"&[u8]"},"value":"b\"AIRBNB_STATE\""},{"name":"BOOK_TAG","type":{"defined":"&[u8]"},"value":"b\"BOOK_STATE\""}],"instructions":[{"name":"initializeUser","accounts":[{"name":"authority","isMut":true,"isSigner":true},{"name":"userProfile","isMut":true,"isSigner":false},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[]},{"name":"addAirbnb","accounts":[{"name":"userProfile","isMut":true,"isSigner":false},{"name":"airbnbAccount","isMut":true,"isSigner":false},{"name":"authority","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"location","type":"string"},{"name":"country","type":"string"},{"name":"price","type":"string"},{"name":"img","type":"string"}]},{"name":"updateAirbnb","accounts":[{"name":"userProfile","isMut":true,"isSigner":false},{"name":"airbnbAccount","isMut":true,"isSigner":false},{"name":"authority","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"airbnbIdx","type":"u8"},{"name":"location","type":"string"},{"name":"country","type":"string"},{"name":"price","type":"string"},{"name":"img","type":"string"}]},{"name":"removeAirbnb","accounts":[{"name":"userProfile","isMut":true,"isSigner":false},{"name":"airbnbAccount","isMut":true,"isSigner":false},{"name":"authority","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"airbnbIdx","type":"u8"}]},{"name":"bookAirbnb","accounts":[{"name":"userProfile","isMut":true,"isSigner":false},{"name":"bookingAccount","isMut":true,"isSigner":false},{"name":"authority","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"idx","type":"u8"},{"name":"date","type":"string"},{"name":"location","type":"string"},{"name":"country","type":"string"},{"name":"price","type":"string"},{"name":"img","type":"string"}]},{"name":"cancelBooking","accounts":[{"name":"userProfile","isMut":true,"isSigner":false},{"name":"bookingAccount","isMut":true,"isSigner":false},{"name":"authority","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"bookingIdx","type":"u8"}]}],"accounts":[{"name":"UserProfile","type":{"kind":"struct","fields":[{"name":"authority","type":"publicKey"},{"name":"lastAirbnb","type":"u8"},{"name":"airbnbCount","type":"u8"}]}},{"name":"AirbnbAccount","type":{"kind":"struct","fields":[{"name":"authority","type":"publicKey"},{"name":"idx","type":"u8"},{"name":"location","type":"string"},{"name":"country","type":"string"},{"name":"image","type":"string"},{"name":"price","type":"string"},{"name":"isReserved","type":"bool"}]}},{"name":"BookingAccount","type":{"kind":"struct","fields":[{"name":"authority","type":"publicKey"},{"name":"date","type":"string"},{"name":"idx","type":"u8"},{"name":"location","type":"string"},{"name":"country","type":"string"},{"name":"image","type":"string"},{"name":"price","type":"string"},{"name":"isReserved","type":"bool"}]}}]} -------------------------------------------------------------------------------- /frontend/components/FilterMenu.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import { AdjustmentsHorizontalIcon } from '@heroicons/react/24/outline' 3 | 4 | function FilterMenu() { 5 | const menus = [ 6 | { 7 | title: 'OMG!', 8 | icon: 'https://a0.muscache.com/pictures/c5a4f6fc-c92c-4ae8-87dd-57f1ff1b89a6.jpg', 9 | }, 10 | { 11 | title: 'Beach', 12 | icon: 'https://a0.muscache.com/pictures/10ce1091-c854-40f3-a2fb-defc2995bcaf.jpg', 13 | }, 14 | { 15 | title: 'Amazing pools', 16 | icon: 'https://a0.muscache.com/pictures/3fb523a0-b622-4368-8142-b5e03df7549b.jpg', 17 | }, 18 | { 19 | title: 'Tiny homes', 20 | icon: 'https://a0.muscache.com/pictures/35919456-df89-4024-ad50-5fcb7a472df9.jpg', 21 | }, 22 | { 23 | title: 'Islands', 24 | icon: 'https://a0.muscache.com/pictures/8e507f16-4943-4be9-b707-59bd38d56309.jpg', 25 | }, 26 | { 27 | title: 'National parks', 28 | icon: 'https://a0.muscache.com/pictures/c0a24c04-ce1f-490c-833f-987613930eca.jpg', 29 | }, 30 | { 31 | title: 'Design', 32 | icon: 'https://a0.muscache.com/pictures/50861fca-582c-4bcc-89d3-857fb7ca6528.jpg', 33 | }, 34 | { 35 | title: 'Arctic', 36 | icon: 'https://a0.muscache.com/pictures/8b44f770-7156-4c7b-b4d3-d92549c8652f.jpg', 37 | }, 38 | { 39 | title: 'Amazing views', 40 | icon: 'https://a0.muscache.com/pictures/3b1eb541-46d9-4bef-abc4-c37d77e3c21b.jpg', 41 | }, 42 | { 43 | title: 'Lakefront', 44 | icon: 'https://a0.muscache.com/pictures/677a041d-7264-4c45-bb72-52bff21eb6e8.jpg', 45 | }, 46 | { 47 | title: 'Surfing', 48 | icon: 'https://a0.muscache.com/pictures/957f8022-dfd7-426c-99fd-77ed792f6d7a.jpg', 49 | }, 50 | { 51 | title: 'Cabins', 52 | icon: 'https://a0.muscache.com/pictures/732edad8-3ae0-49a8-a451-29a8010dcc0c.jpg', 53 | }, 54 | ] 55 | 56 | return ( 57 |
58 |
59 | {menus.map((menu, index) => ( 60 |
61 |
62 | 63 |
64 | 65 |

{menu.title}

66 |
67 | ))} 68 |
69 | 73 |
74 | ) 75 | } 76 | 77 | export default FilterMenu 78 | -------------------------------------------------------------------------------- /frontend/data/listings.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: '1', 4 | location: { 5 | name: 'Abiansemal', 6 | country: 'Indonesia', 7 | }, 8 | description: '', 9 | distance: { 10 | km: 1997, 11 | }, 12 | availability: { 13 | startDate: { 14 | month: 7, 15 | day: 9, 16 | }, 17 | endDate: { 18 | month: 7, 19 | day: 14, 20 | }, 21 | }, 22 | price: { 23 | perNight: 1608, 24 | }, 25 | rating: 4.87, 26 | imageURL: 'https://a0.muscache.com/im/pictures/e25a9b25-fa98-4160-bfd1-039287bf38b6.jpg', 27 | isReserved: false, 28 | reservation: null, 29 | }, 30 | { 31 | id: '2', 32 | location: { 33 | name: 'Tambon Nong Kae', 34 | country: 'Thailand', 35 | }, 36 | description: '', 37 | distance: { 38 | km: 1050, 39 | }, 40 | availability: { 41 | startDate: { 42 | month: 9, 43 | day: 8, 44 | }, 45 | endDate: { 46 | month: 9, 47 | day: 13, 48 | }, 49 | }, 50 | price: { 51 | perNight: 494, 52 | }, 53 | rating: 4.95, 54 | imageURL: 'https://a0.muscache.com/im/pictures/77c3c61e-930a-4e7c-ab4d-59413c1f0b87.jpg', 55 | isReserved: false, 56 | reservation: null, 57 | }, 58 | { 59 | id: '3', 60 | location: { 61 | name: 'Cabangan', 62 | country: 'Philippines', 63 | }, 64 | description: '', 65 | distance: { 66 | km: 2414, 67 | }, 68 | availability: { 69 | startDate: { 70 | month: 9, 71 | day: 4, 72 | }, 73 | endDate: { 74 | month: 9, 75 | day: 10, 76 | }, 77 | }, 78 | price: { 79 | perNight: 636, 80 | }, 81 | rating: 4.79, 82 | imageURL: 'https://a0.muscache.com/im/pictures/64fc0202-bcd7-46e3-87f1-3f7f0f8ead15.jpg', 83 | isReserved: false, 84 | reservation: null, 85 | }, 86 | { 87 | id: '4', 88 | location: { 89 | name: 'Ko Samui District', 90 | country: 'Thailand', 91 | }, 92 | description: '', 93 | distance: { 94 | km: 718, 95 | }, 96 | availability: { 97 | startDate: { 98 | month: 9, 99 | day: 26, 100 | }, 101 | endDate: { 102 | month: 10, 103 | day: 3, 104 | }, 105 | }, 106 | price: { 107 | perNight: 478, 108 | }, 109 | rating: 4.77, 110 | imageURL: 'https://a0.muscache.com/im/pictures/36e5b4b5-0f4f-4703-a2c3-7a3cc9d9d82e.jpg', 111 | isReserved: false, 112 | reservation: null, 113 | }, 114 | ] 115 | -------------------------------------------------------------------------------- /frontend/components/Listing/ReserveListingModal.js: -------------------------------------------------------------------------------- 1 | import { Dialog, Transition } from '@headlessui/react' 2 | import { Fragment, useState } from 'react' 3 | import { DateRangePicker } from 'react-date-range' 4 | import 'react-date-range/dist/styles.css' // main style file 5 | import 'react-date-range/dist/theme/default.css' // theme css file 6 | import { format } from 'date-fns' 7 | 8 | export default function ReserveListingModal({ reserveListing,currentEditListing, reserveListingModalOpen, setReserveListingModalOpen }) { 9 | const [startDate, setStartDate] = useState(new Date()) 10 | const [endDate, setEndDate] = useState(new Date()) 11 | 12 | const selectionRange = { 13 | startDate, 14 | endDate, 15 | key: 'selection', 16 | } 17 | 18 | const handleSelect = (ranges) => { 19 | setStartDate(ranges.selection.startDate) 20 | setEndDate(ranges.selection.endDate) 21 | } 22 | 23 | const closeModal = () => { 24 | setReserveListingModalOpen(false) 25 | } 26 | 27 | const onConfirm = (e) => { 28 | e.preventDefault() 29 | 30 | const formattedStartDate = format(new Date(startDate), 'MMM d') 31 | const formattedEndDate = format(new Date(endDate), 'MMM d') 32 | const range = `${formattedStartDate} - ${formattedEndDate}` 33 | 34 | reserveListing(currentEditListing.account,range) 35 | 36 | closeModal() 37 | } 38 | 39 | return ( 40 | 41 | 42 | 43 |
44 | 45 | 46 |
47 |
48 | 49 | 50 | 51 | Reserve Listing 52 | 53 | 54 |
55 | 56 | 57 |
58 | 61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /frontend/pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Header from '../components/Header' 3 | import Footer from '../components/Footer' 4 | import FilterMenu from '../components/FilterMenu' 5 | import Listings from '../components/Listing/Listings' 6 | import { useMemo, useState,useEffect } from 'react' 7 | import listingsData from '../data/airbnb' 8 | import AddListingModal from '../components/Listing/AddListingModal' 9 | import EditListingModal from '../components/Listing/EditListingModal' 10 | import { useWallet } from '@solana/wallet-adapter-react' 11 | import ReserveListingModal from '../components/Listing/ReserveListingModal' 12 | import { useAirbnb } from '../hooks/useAirbnb' 13 | 14 | 15 | import { PublicKey } from "@solana/web3.js"; 16 | 17 | 18 | 19 | export default function Home() { 20 | 21 | 22 | const {initializeUser, airbnbs, bookings, transactionPending, addAirbnb, updateAirbnb, removeAirbnb, bookAirbnb, cancelBooking, initialized} = useAirbnb() 23 | // console.log(airbnbs, "👈") 24 | 25 | 26 | const { connected, publicKey } = useWallet() 27 | const [showReservedListing, setShowReservedListing] = useState(false) 28 | const [listings, setListings] = useState(listingsData) 29 | const [addListingModalOpen, setAddListingModalOpen] = useState(false) 30 | const [editListingModalOpen, setEditListingModalOpen] = useState(false) 31 | const [reserveListingModalOpen, setReserveListingModalOpen] = useState(false) 32 | const [currentEditListingID, setCurrentEditListingID] = useState(null) 33 | const [currentReserveListingID, setCurrentReserveListingID] = useState(null) 34 | const currentEditListing = useMemo(() => airbnbs.find((listing) => listing.account.idx === currentEditListingID), [currentEditListingID]) 35 | const displayListings = useMemo(() => (showReservedListing ? bookings : airbnbs), [showReservedListing, airbnbs]) 36 | 37 | const toggleShowReservedListing = () => { 38 | setShowReservedListing(!showReservedListing) 39 | } 40 | 41 | 42 | 43 | const toggleEditListingModal = (listingID) => { 44 | setCurrentEditListingID(listingID) 45 | 46 | setEditListingModalOpen(true) 47 | } 48 | 49 | 50 | const removeListing = (listingID) => { 51 | setListings(listings.filter((listing) => listing.id !== listingID)) 52 | } 53 | 54 | const toggleReserveListingModal = (value, listingID) => { 55 | setCurrentEditListingID(listingID) 56 | 57 | setReserveListingModalOpen(value) 58 | } 59 | 60 | const reserveListing = ({location, country, price, image},range) => { 61 | console.log(location, country, price, image, "BETTT",range) 62 | } 63 | 64 | const unreserveListing = () => { 65 | 66 | } 67 | 68 | return ( 69 |
70 | 71 | Airbnb Clone 72 | 73 | 74 |
75 | 76 |
77 | 78 | 79 | {connected && ( 80 |
81 | 84 | 87 |
88 | )} 89 | 90 | 91 | 92 | 93 | 94 |
95 | 96 |
98 | ) 99 | } 100 | -------------------------------------------------------------------------------- /frontend/components/Listing/AddListingModal.js: -------------------------------------------------------------------------------- 1 | import { Dialog, Transition } from '@headlessui/react' 2 | import { Fragment, useState } from 'react' 3 | 4 | export default function AddListingModal({ addAirbnb, addListingModalOpen, setAddListingModalOpen }) { 5 | const [location, setLocation] = useState('') 6 | const [country, setCountry] = useState('') 7 | const [price, setPrice] = useState(0) 8 | const [imageURL, setImageURL] = useState('') 9 | 10 | const closeModal = () => { 11 | setAddListingModalOpen(false) 12 | } 13 | 14 | const onCreate = (e) => { 15 | e.preventDefault() 16 | 17 | addAirbnb({ 18 | location, 19 | country, 20 | price, 21 | imageURL, 22 | }) 23 | 24 | closeModal() 25 | } 26 | 27 | return ( 28 | 29 | 30 | 31 |
32 | 33 | 34 |
35 |
36 | 37 | 38 | 39 | Add Listing 40 | 41 | 42 |
43 |
44 | 48 | 49 | 53 | 54 | 58 | 59 | 63 |
64 | 65 |
66 | 69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /frontend/components/Listing/EditListingModal.js: -------------------------------------------------------------------------------- 1 | import { Dialog, Transition } from '@headlessui/react' 2 | import { Fragment, useState } from 'react' 3 | 4 | export default function EditListingModal({ editListing, currentEditListing, editListingModalOpen, setEditListingModalOpen }) { 5 | const [location, setLocation] = useState('') 6 | const [country, setCountry] = useState('') 7 | const [price, setPrice] = useState(0) 8 | const [imageURL, setImageURL] = useState('') 9 | 10 | const closeModal = () => { 11 | setEditListingModalOpen(false) 12 | } 13 | 14 | const onEdit = (e) => { 15 | e.preventDefault() 16 | 17 | editListing({ 18 | airbnbPda: currentEditListing.publicKey, 19 | airbnbIdx: currentEditListing?.account.idx, 20 | location, 21 | country, 22 | price, 23 | imageURL, 24 | }) 25 | 26 | closeModal() 27 | } 28 | 29 | return ( 30 | 31 | 32 | 33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 | Edit Listing 42 | 43 | 44 |
45 |
46 | 50 | 51 | 55 | 56 | 60 | 61 | 65 |
66 | 67 |
68 | 71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | ) 80 | } 81 | -------------------------------------------------------------------------------- /frontend/components/Header.js: -------------------------------------------------------------------------------- 1 | import { GlobeAmericasIcon, Bars3Icon, UserCircleIcon, MagnifyingGlassIcon } from '@heroicons/react/24/solid' 2 | import { WalletMultiButton } from '@solana/wallet-adapter-react-ui' 3 | import Image from 'next/image' 4 | import { truncate } from '../utils/string' 5 | require('@solana/wallet-adapter-react-ui/styles.css') 6 | 7 | function Header({ connected, publicKey, initializeUser , initialized, transactionPending}) { 8 | return ( 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 32 |
33 | 34 |
35 |
36 | 37 | Become a Host 38 | 39 |
40 | 41 |
42 | 43 |
44 | 45 | {/*
46 | 47 | 48 | 49 |
*/} 50 | {initialized ? (<>) : ()} 53 | }> 54 | {connected ? truncate(publicKey.toString()) : 'Connect Wallet'} 55 | 56 |
57 |
58 | ) 59 | } 60 | 61 | export default Header 62 | -------------------------------------------------------------------------------- /solana-solution/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | pub mod constant; 3 | pub mod states; 4 | use crate::{constant::*, states::*}; 5 | 6 | declare_id!("J5yzCZrNZJR6K5FUwwZ2SzjTvg8DzomcYqcaZJm27Y6v"); 7 | 8 | #[program] 9 | pub mod clever_airbnb { 10 | use super::*; 11 | 12 | pub fn initialize_user( 13 | ctx: Context 14 | ) -> Result<()> { 15 | // Initialize user profile with default data 16 | 17 | let user_profile = &mut ctx.accounts.user_profile; 18 | user_profile.authority = ctx.accounts.authority.key(); 19 | user_profile.last_airbnb = 0; 20 | user_profile.airbnb_count = 0; 21 | 22 | Ok(()) 23 | } 24 | 25 | pub fn add_airbnb( 26 | ctx: Context, 27 | location: String, 28 | country: String, 29 | price: String, 30 | img: String, 31 | ) -> Result<()> { 32 | let airbnb_account = &mut ctx.accounts.airbnb_account; 33 | let user_profile = &mut ctx.accounts.user_profile; 34 | 35 | // Fill contents with argument 36 | airbnb_account.authority = ctx.accounts.authority.key(); 37 | airbnb_account.idx = user_profile.last_airbnb; 38 | airbnb_account.location = location; 39 | airbnb_account.country = country; 40 | airbnb_account.price = price; 41 | airbnb_account.image = img; 42 | airbnb_account.isReserved = false; 43 | 44 | // Increase airbnb idx for PDA 45 | user_profile.last_airbnb = user_profile.last_airbnb 46 | .checked_add(1) 47 | .unwrap(); 48 | 49 | // Increase total airbnb count 50 | user_profile.airbnb_count = user_profile.airbnb_count 51 | .checked_add(1) 52 | .unwrap(); 53 | 54 | Ok(()) 55 | } 56 | 57 | pub fn update_airbnb( 58 | ctx: Context, 59 | airbnb_idx: u8, 60 | location: String, 61 | country: String, 62 | price: String, 63 | img: String, 64 | ) -> Result<()> { 65 | let airbnb_account = &mut ctx.accounts.airbnb_account; 66 | 67 | // Mark todo 68 | airbnb_account.location = location; 69 | airbnb_account.country = country; 70 | airbnb_account.price = price; 71 | airbnb_account.image = img; 72 | Ok(()) 73 | } 74 | 75 | pub fn remove_airbnb(ctx: Context, _airbnb_idx: u8) -> Result<()> { 76 | // Decreate total airbnb count 77 | let user_profile = &mut ctx.accounts.user_profile; 78 | user_profile.airbnb_count = user_profile.airbnb_count 79 | .checked_sub(1) 80 | .unwrap(); 81 | 82 | // No need to decrease last airbnb idx 83 | 84 | // Todo PDA already closed in context 85 | 86 | Ok(()) 87 | } 88 | 89 | // Need a function that reserves an Airbnb 90 | pub fn book_airbnb( 91 | ctx: Context, 92 | idx: u8, 93 | date: String, 94 | location: String, 95 | country: String, 96 | price: String, 97 | img: String, 98 | ) -> Result<()> { 99 | let booking_account = &mut ctx.accounts.booking_account; 100 | 101 | // // Fill contents with argument 102 | booking_account.authority = ctx.accounts.authority.key(); 103 | booking_account.idx = idx; 104 | booking_account.date = date; 105 | booking_account.location = location; 106 | booking_account.country = country; 107 | booking_account.price = price; 108 | booking_account.image = img; 109 | booking_account.isReserved = true; 110 | 111 | 112 | Ok(()) 113 | } 114 | 115 | pub fn cancel_booking(ctx: Context, _booking_idx: u8) -> Result<()> { 116 | // Decreate total airbnb count 117 | let user_profile = &mut ctx.accounts.user_profile; 118 | 119 | Ok(()) 120 | } 121 | } 122 | 123 | #[derive(Accounts)] 124 | pub struct InitializeUser<'info> { 125 | #[account(mut)] 126 | pub authority: Signer<'info>, 127 | 128 | #[account( 129 | init, 130 | seeds = [USER_TAG, authority.key().as_ref()], 131 | bump, 132 | payer = authority, 133 | space = 8 + std::mem::size_of::(), 134 | )] 135 | pub user_profile: Box>, 136 | 137 | pub system_program: Program<'info, System>, 138 | } 139 | 140 | #[derive(Accounts)] 141 | #[instruction()] 142 | pub struct AddAirbnb<'info> { 143 | #[account( 144 | mut, 145 | seeds = [USER_TAG, authority.key().as_ref()], 146 | bump, 147 | has_one = authority, 148 | )] 149 | pub user_profile: Box>, 150 | 151 | #[account( 152 | init, 153 | seeds = [AIRBNB_TAG, authority.key().as_ref(), &[user_profile.last_airbnb]], 154 | bump, 155 | payer = authority, 156 | space = 2865 + 8, 157 | )] 158 | pub airbnb_account: Box>, 159 | 160 | #[account(mut)] 161 | pub authority: Signer<'info>, 162 | 163 | pub system_program: Program<'info, System>, 164 | } 165 | 166 | #[derive(Accounts)] 167 | #[instruction(airbnb_idx: u8)] 168 | pub struct UpdateAirbnb<'info> { 169 | #[account( 170 | mut, 171 | seeds = [USER_TAG, authority.key().as_ref()], 172 | bump, 173 | has_one = authority, 174 | )] 175 | pub user_profile: Box>, 176 | 177 | #[account( 178 | mut, 179 | seeds = [AIRBNB_TAG, authority.key().as_ref(), &[airbnb_idx].as_ref()], 180 | bump, 181 | has_one = authority, 182 | )] 183 | pub airbnb_account: Box>, 184 | 185 | #[account(mut)] 186 | pub authority: Signer<'info>, 187 | 188 | pub system_program: Program<'info, System>, 189 | } 190 | 191 | #[derive(Accounts)] 192 | #[instruction(airbnb_idx: u8)] 193 | pub struct RemoveAirbnb<'info> { 194 | #[account( 195 | mut, 196 | seeds = [USER_TAG, authority.key().as_ref()], 197 | bump, 198 | has_one = authority, 199 | )] 200 | pub user_profile: Box>, 201 | 202 | #[account( 203 | mut, 204 | close = authority, 205 | seeds = [AIRBNB_TAG, authority.key().as_ref(), &[airbnb_idx].as_ref()], 206 | bump, 207 | has_one = authority, 208 | )] 209 | pub airbnb_account: Box>, 210 | 211 | #[account(mut)] 212 | pub authority: Signer<'info>, 213 | 214 | pub system_program: Program<'info, System>, 215 | } 216 | 217 | // #[derive(Accounts)] 218 | // #[instruction()] 219 | // pub struct BookAirbnb<'info> { 220 | // #[account( 221 | // mut, 222 | // seeds = [USER_TAG, authority.key().as_ref()], 223 | // bump, 224 | // has_one = authority, 225 | // )] 226 | // pub user_profile: Box>, 227 | 228 | // #[account( 229 | // init, 230 | // seeds = [BOOK_TAG, airbnb_account.key().as_ref()], 231 | // bump, 232 | // payer = booking_authority, 233 | // space = 3125 + 8, 234 | // )] 235 | // pub booking_account: Box>, 236 | 237 | // #[account(mut)] 238 | // pub authority: Signer<'info>, 239 | 240 | // pub system_program: Program<'info, System>, 241 | // } 242 | 243 | #[derive(Accounts)] 244 | #[instruction()] 245 | pub struct BookAirbnb<'info> { 246 | #[account( 247 | mut, 248 | seeds = [USER_TAG, authority.key().as_ref()], 249 | bump, 250 | has_one = authority, 251 | )] 252 | pub user_profile: Box>, 253 | 254 | #[account( 255 | init, 256 | seeds = [BOOK_TAG, authority.key().as_ref() ], 257 | bump, 258 | payer = authority, 259 | space = 3125 + 8, 260 | )] 261 | pub booking_account: Box>, 262 | 263 | #[account(mut)] 264 | pub authority: Signer<'info>, 265 | 266 | pub system_program: Program<'info, System>, 267 | } 268 | 269 | #[derive(Accounts)] 270 | pub struct CancelBook<'info> { 271 | #[account( 272 | mut, 273 | seeds = [USER_TAG, authority.key().as_ref()], 274 | bump, 275 | has_one = authority, 276 | )] 277 | pub user_profile: Box>, 278 | 279 | #[account( 280 | mut, 281 | close = authority, 282 | seeds = [BOOK_TAG, authority.key().as_ref()], 283 | bump, 284 | has_one = authority, 285 | )] 286 | pub booking_account: Box>, 287 | 288 | #[account(mut)] 289 | pub authority: Signer<'info>, 290 | 291 | pub system_program: Program<'info, System>, 292 | } 293 | -------------------------------------------------------------------------------- /frontend/hooks/useAirbnb.js: -------------------------------------------------------------------------------- 1 | import * as anchor from '@project-serum/anchor' 2 | import { useEffect, useMemo, useState } from 'react' 3 | import toast from 'react-hot-toast' 4 | import { AIRBNB_PROGRAM_PUBKEY } from '../constants' 5 | import airbnbIDL from '../constants/airbnb.json' 6 | import { SystemProgram } from '@solana/web3.js' 7 | import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes' 8 | import { findProgramAddressSync } from '@project-serum/anchor/dist/cjs/utils/pubkey' 9 | import { useAnchorWallet, useConnection, useWallet } from '@solana/wallet-adapter-react' 10 | import { authorFilter } from '../utils' 11 | import { PublicKey } from '@solana/web3.js' 12 | import { set } from 'date-fns' 13 | import { tr } from 'date-fns/locale' 14 | 15 | 16 | // Static data that reflects the todo struct of the solana program 17 | 18 | 19 | export function useAirbnb() { 20 | const { connection } = useConnection() 21 | const { publicKey } = useWallet() 22 | const anchorWallet = useAnchorWallet() 23 | 24 | const [initialized, setInitialized] = useState(false) 25 | const [user,setUser] = useState({}) 26 | const [airbnbs, setAirbnbs] = useState([]) 27 | const [bookings, setBookings] = useState([]) 28 | const [lastAirbnb, setLastAirbnb] = useState(0) 29 | const [lastBookId,setLastBookId] = useState(0) 30 | const [loading, setLoading] = useState(false) 31 | const [transactionPending, setTransactionPending] = useState(false) 32 | 33 | // const program = new PublicKey( 34 | // "8ktkADKMec8BE1q47k6LnMWqYpNTjqtinZ6ng219wMwf" 35 | // ); 36 | 37 | const program = useMemo(() => { 38 | if (anchorWallet) { 39 | const provider = new anchor.AnchorProvider(connection, anchorWallet, anchor.AnchorProvider.defaultOptions()) 40 | return new anchor.Program(airbnbIDL, AIRBNB_PROGRAM_PUBKEY, provider) 41 | } 42 | }, [connection, anchorWallet]) 43 | 44 | 45 | useEffect(()=> { 46 | 47 | const start = async () => { 48 | if (program && publicKey && !transactionPending) { 49 | try { 50 | const [profilePda, profileBump] = await findProgramAddressSync([utf8.encode('USER_STATE'), publicKey.toBuffer()], program.programId) 51 | const profileAccount = await program.account.userProfile.fetch(profilePda) 52 | console.log(profileAccount) 53 | if (profileAccount) { 54 | setLastAirbnb(profileAccount.lastAirbnb) 55 | setInitialized(true) 56 | setLoading(true) 57 | 58 | 59 | const listings = await program.account.airbnbAccount.all(); 60 | const allBookings = await program.account.bookingAccount.all(); 61 | setUser(profileAccount.toString()) 62 | setAirbnbs(listings) 63 | 64 | const myBookings = allBookings.filter(booking=> booking.account.authority.toString() == profileAccount.authority.toString()) 65 | 66 | setBookings(myBookings) 67 | } else { 68 | setInitialized(false) 69 | } 70 | } catch (error) { 71 | console.log(error) 72 | setInitialized(false) 73 | } finally { 74 | setLoading(false) 75 | } 76 | } 77 | 78 | } 79 | 80 | start() 81 | 82 | },[publicKey,program, transactionPending]) 83 | 84 | const initializeUser = async () => { 85 | if (program && publicKey) { 86 | try { 87 | setTransactionPending(true) 88 | setLoading(true) 89 | const [profilePda] = findProgramAddressSync([utf8.encode('USER_STATE'), publicKey.toBuffer()], program.programId) 90 | 91 | const tx = await program.methods 92 | .initializeUser() 93 | .accounts({ 94 | userProfile: profilePda, 95 | authority: publicKey, 96 | systemProgram: SystemProgram.programId, 97 | }) 98 | .rpc() 99 | setInitialized(true) 100 | toast.success('Successfully initialized user.') 101 | } catch (error) { 102 | console.log(error) 103 | } finally { 104 | setLoading(false) 105 | setTransactionPending(false) 106 | } 107 | } 108 | } 109 | 110 | const addAirbnb = async ({location, country, price, imageURL}) => { 111 | console.log(location,country,imageURL,price, "YOOO" ) 112 | if (program && publicKey) { 113 | setTransactionPending(true) 114 | setLoading(true) 115 | try { 116 | const [profilePda] = findProgramAddressSync([utf8.encode('USER_STATE'), publicKey.toBuffer()], program.programId) 117 | const [airbnbPda] = findProgramAddressSync([utf8.encode('AIRBNB_STATE'), publicKey.toBuffer(), Uint8Array.from([lastAirbnb])], program.programId) 118 | 119 | console.log(publicKey.toString(), program.programId, profilePda.toString(), airbnbPda.toString(), lastAirbnb) 120 | 121 | await program.methods 122 | .addAirbnb(location, country, price, imageURL) 123 | .accounts({ 124 | userProfile: profilePda, 125 | airbnbAccount: airbnbPda, 126 | authority: publicKey, 127 | systemProgram: SystemProgram.programId, 128 | }) 129 | .rpc() 130 | toast.success("SUCCESSFULLY ADDED A LISTING") 131 | } catch (error) { 132 | console.error(error) 133 | } finally { 134 | setTransactionPending(false) 135 | setLoading(false) 136 | } 137 | } 138 | } 139 | 140 | const updateAirbnb = async ({airbnbPda, airbnbIdx,location, country, price, imageURL}) => { 141 | console.log(airbnbPda.toString()) 142 | if (program && publicKey) { 143 | try { 144 | setLoading(true) 145 | setTransactionPending(true) 146 | const [profilePda] = findProgramAddressSync([utf8.encode('USER_STATE'), publicKey.toBuffer()], program.programId) 147 | 148 | await program.methods 149 | .updateAirbnb(airbnbIdx,location, country, price, imageURL) 150 | .accounts({ 151 | userProfile: profilePda, 152 | airbnbAccount: airbnbPda, 153 | authority: publicKey, 154 | systemProgram: SystemProgram.programId, 155 | }) 156 | .rpc() 157 | toast.success('Successfully EDIT AIRBNB.') 158 | } catch (error) { 159 | console.error(error) 160 | } finally { 161 | setLoading(false) 162 | setTransactionPending(false) 163 | } 164 | } 165 | } 166 | 167 | const editListing = ({ publicKey, idx, location, country, price, description, imageURL }) => { 168 | console.log(publicKey,idx, location, country, price, description, imageURL, "YAY" ) 169 | } 170 | 171 | const removeAirbnb = async (airbnbPda, airbnbIdx) => { 172 | if (program && publicKey) { 173 | try { 174 | setTransactionPending(true) 175 | setLoading(true) 176 | const [profilePda, profileBump] = findProgramAddressSync([utf8.encode('USER_STATE'), publicKey.toBuffer()], program.programId) 177 | console.log(airbnbPda.toString(), airbnbIdx, publicKey.toString(), profilePda.toString()) 178 | await program.methods 179 | .removeAirbnb(airbnbIdx) 180 | .accounts({ 181 | userProfile: profilePda, 182 | airbnbAccount: airbnbPda, 183 | authority: publicKey, 184 | systemProgram: SystemProgram.programId, 185 | }) 186 | .rpc() 187 | toast.success("Deleted listing") 188 | } catch (error) { 189 | console.log(error) 190 | } finally { 191 | setLoading(false) 192 | setTransactionPending(false) 193 | } 194 | } 195 | } 196 | 197 | const bookAirbnb = async ({location, country, price, image},date) => { 198 | console.log(location, country, price, image, "BETTT") 199 | 200 | const id = lastBookId + 1 201 | if (program && publicKey) { 202 | try { 203 | setLoading(true) 204 | setTransactionPending(true) 205 | const [profilePda] = findProgramAddressSync([utf8.encode('USER_STATE'), publicKey.toBuffer()], program.programId) 206 | const [bookPda] = findProgramAddressSync([utf8.encode('BOOK_STATE'), publicKey.toBuffer()], program.programId) 207 | console.log(profilePda) 208 | await program.methods 209 | .bookAirbnb(id,date,location, country, price, image) 210 | .accounts({ 211 | userProfile: profilePda, 212 | bookingAccount: bookPda, 213 | authority: publicKey, 214 | systemProgram: SystemProgram.programId, 215 | }) 216 | .rpc() 217 | toast.success("SUCCESSFULLY BOOOOOKED") 218 | setLastBookId(id) 219 | } catch (error) { 220 | console.error(error) 221 | } finally { 222 | setLoading(false) 223 | setTransactionPending(false) 224 | } 225 | } 226 | } 227 | 228 | const cancelBooking = async (bookingPda,idx) => { 229 | console.log("RUNNING") 230 | if (program && publicKey) { 231 | try { 232 | setLoading(true) 233 | setTransactionPending(true) 234 | const [profilePda] = findProgramAddressSync([utf8.encode('USER_STATE'), publicKey.toBuffer()], program.programId) 235 | await program.methods 236 | .cancelBooking(idx) 237 | .accounts({ 238 | userProfile: profilePda, 239 | bookingAccount: bookingPda, 240 | authority: publicKey, 241 | systemProgram: SystemProgram.programId, 242 | }) 243 | .rpc() 244 | toast.success("Canceled Booking") 245 | } catch (error) { 246 | console.log(error) 247 | } finally { 248 | setLoading(false) 249 | setTransactionPending(false) 250 | } 251 | } 252 | } 253 | 254 | // const removeListing = (listingID) => { 255 | // setListings(listings.filter((listing) => listing.id !== listingID)) 256 | // } 257 | 258 | // const addListing = ({ location, country, price, description, imageURL }) => { 259 | // const id = listings.length + 1 260 | 261 | // setListings([ 262 | // ...listings, 263 | // { 264 | // id, 265 | // location: { 266 | // name: location, 267 | // country: country, 268 | // }, 269 | // description, 270 | // distance: { 271 | // km: 0, 272 | // }, 273 | // price: { 274 | // perNight: price, 275 | // }, 276 | // rating: 5, 277 | // imageURL, 278 | // }, 279 | // ]) 280 | // } 281 | 282 | 283 | return { airbnbs, bookings, addAirbnb,updateAirbnb, removeAirbnb,bookAirbnb, cancelBooking, initializeUser , initialized , loading, transactionPending} 284 | } 285 | --------------------------------------------------------------------------------