├── .gitignore ├── .npmrc ├── README.md ├── app.db ├── app.db-journal ├── app.json ├── app ├── (tabs) │ ├── _layout.tsx │ ├── explore.tsx │ ├── index.tsx │ └── new-notion.tsx ├── +html.tsx ├── +not-found.tsx ├── _layout.tsx ├── doc-actions-sheet.tsx └── new-notion.tsx ├── assets ├── fonts │ └── SpaceMono-Regular.ttf └── images │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ ├── partial-react-logo.png │ ├── react-logo.png │ ├── react-logo@2x.png │ ├── react-logo@3x.png │ └── splash.png ├── babel.config.js ├── components ├── Collapsible.tsx ├── DraggableNotionList.tsx ├── DraggableNotionListItem.tsx ├── ExternalLink.tsx ├── HelloWave.tsx ├── NotionButton.tsx ├── ParallaxScrollView.tsx ├── ResentFileCard.tsx ├── ResentFiles.tsx ├── ThemedText.tsx ├── ThemedView.tsx ├── __tests__ │ ├── ThemedText-test.tsx │ └── __snapshots__ │ │ └── ThemedText-test.tsx.snap └── navigation │ └── TabBarIcon.tsx ├── constants ├── Colors.ts ├── Gradients.ts └── MarkdownStyle.ts ├── eas.json ├── hooks ├── useColorScheme.ts ├── useColorScheme.web.ts └── useThemeColor.ts ├── migrations ├── 20240703032200_one │ └── migration.sql ├── 20240703215900_add_order_to_notionfile │ └── migration.sql ├── 20240704014730_add_on_delete_cascade_for_notion_files │ └── migration.sql └── migration_lock.toml ├── myDbModule.ts ├── package.json ├── pnpm-lock.yaml ├── queries.sql ├── schema.prisma ├── scripts └── reset-project.js └── tsconfig.json /.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 | ios/ 13 | android/ 14 | 15 | # macOS 16 | .DS_Store 17 | 18 | # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb 19 | # The following patterns were generated by expo-cli 20 | 21 | expo-env.d.ts 22 | # @end expo-cli -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![NotionClone2](https://github.com/user-attachments/assets/3f94efd5-4c19-4aff-a7e0-3fdbcfefa407) 2 | # Welcome to Local-First Notion Clone 3 | 4 | This project is built using Prisma & Expo. 5 | 6 | **Live demo at:** [x.com/betomoedano/status/1812483695418261620](https://x.com/betomoedano/status/1812483695418261620) 7 | 8 | **YouTube, Source Code, Dependencies and More at:** [codewithbeto.dev/projects/notion-clone](https://codewithbeto.dev/projects/notion-clone) 9 | 10 | ## Get started 11 | 12 | 1. Install dependencies with pnpm 13 | 14 | ```bash 15 | pnpm install 16 | ``` 17 | 18 | 2. prebuild app 19 | 20 | Haven't tested on Android yet! 21 | 22 | ```bash 23 | npx expo prebuild -p ios --clean && pnpm ios 24 | ``` 25 | 26 | You can create the database file and initial migration using Prisma migrate: 27 | 28 | ```bash 29 | npx prisma@latest migrate dev 30 | ``` 31 | 32 | you can now generate the Prisma Client like this: 33 | 34 | ```bash 35 | npx prisma@latest generate 36 | ``` 37 | 38 | ## Support My Work 39 | 40 | If you find this project helpful and want to support my work, the best way is by enrolling in one of my courses: 41 | 42 | - **React Native Course**: [codewithbeto.dev/learn](https://codewithbeto.dev/learn) 43 | - **React with TypeScript Course**: [codewithbeto.dev/learnReact](https://codewithbeto.dev/learnReact) 44 | - **Git & GitHub Course**: [codewithbeto.dev/learnGit](https://codewithbeto.dev/learnGit) 45 | 46 | For other ways to support my work, please consider: 47 | 48 | - **Become a Code with Beto channel member**: [YouTube Membership](https://www.youtube.com/channel/UCh247h68vszOMA_OWpGEa5g/join) 49 | - **GitHub Sponsors**: [Sponsor Me](https://github.com/sponsors/betomoedano) 50 | 51 | You can also support me by using my referral links: 52 | 53 | - Get an exclusive 40% discount on CodeCrafters: [Referral Link](https://app.codecrafters.io/join?via=betomoedano) 54 | - Get a 10% discount on Vexo Analytics with code "BETO10": [Vexo](https://vexo.co) 55 | - Sign up for Robinhood and we'll both pick our own gift stock 🎁: [Robinhood](https://join.robinhood.com/albertm-8254f5) 56 | - Get 500 MB of Dropbox storage: [Dropbox](https://www.dropbox.com/referrals/AAC52bYrrPqp8FZ7K5gxa-I74wecLpiQuB4?src=global9) 57 | 58 | Your support helps me keep creating amazing projects! 59 | 60 | 61 | ## Connect with Me 62 | 63 | - **Website**: [Code With Beto](https://codewithbeto.dev) 64 | - **X (formerly Twitter)**: [@betomoedano](https://x.com/betomoedano) 65 | - **GitHub**: [betomoedano](https://github.com/betomoedano) 66 | - **LinkedIn**: [Beto Moedano](https://www.linkedin.com/in/betomoedano/) 67 | - **Discord**: [Join Our Community](https://discord.com/invite/G2RnuUD8) 68 | - **Medium**: [@betomoedano01](https://medium.com/@betomoedano01) 69 | - **Figma**: [betomoedano](https://www.figma.com/@betomoedano) 70 | 71 | -------------------------------------------------------------------------------- /app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/React-Native-Notion-Clone/24d83b54cf73e27b63a0d37ec5e432e3fe49cbc8/app.db -------------------------------------------------------------------------------- /app.db-journal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betomoedano/React-Native-Notion-Clone/24d83b54cf73e27b63a0d37ec5e432e3fe49cbc8/app.db-journal -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "notion-clone", 4 | "slug": "notion-clone", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "myapp", 9 | "userInterfaceStyle": "automatic", 10 | "splash": { 11 | "image": "./assets/images/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "ios": { 16 | "supportsTablet": true, 17 | "bundleIdentifier": "com.betomoedano.notion-clone" 18 | }, 19 | "android": { 20 | "adaptiveIcon": { 21 | "foregroundImage": "./assets/images/adaptive-icon.png", 22 | "backgroundColor": "#ffffff" 23 | }, 24 | "package": "com.betomoedano.notionclone" 25 | }, 26 | "web": { 27 | "bundler": "metro", 28 | "output": "static", 29 | "favicon": "./assets/images/favicon.png" 30 | }, 31 | "plugins": [ 32 | "@prisma/react-native", 33 | "expo-router" 34 | ], 35 | "experiments": { 36 | "typedRoutes": true 37 | }, 38 | "extra": { 39 | "router": { 40 | "origin": false 41 | }, 42 | "eas": { 43 | "projectId": "f71d029f-67c9-4aa1-aae4-15473a79f760" 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/(tabs)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from "expo-router"; 2 | import React from "react"; 3 | 4 | import { TabBarIcon } from "@/components/navigation/TabBarIcon"; 5 | import { Colors } from "@/constants/Colors"; 6 | import { useColorScheme } from "@/hooks/useColorScheme"; 7 | 8 | export default function TabLayout() { 9 | const colorScheme = useColorScheme(); 10 | 11 | return ( 12 | 19 | ( 23 | 27 | ), 28 | }} 29 | /> 30 | ( 34 | 38 | ), 39 | }} 40 | /> 41 | ( 46 | 50 | ), 51 | headerShown: true, 52 | headerTitle: "", 53 | headerStyle: { shadowColor: "transparent" }, 54 | }} 55 | /> 56 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /app/(tabs)/explore.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | StyleSheet, 3 | SafeAreaView, 4 | ScrollView, 5 | TouchableOpacity, 6 | View, 7 | } from "react-native"; 8 | 9 | import { ThemedView } from "@/components/ThemedView"; 10 | import { extendedClient } from "@/myDbModule"; 11 | import { ThemedText } from "@/components/ThemedText"; 12 | import { Link } from "expo-router"; 13 | 14 | export default function ExploreScreen() { 15 | const today = new Date(); 16 | const startOfToday = new Date(today.setHours(0, 0, 0, 0)); 17 | const endOfToday = new Date(today.setHours(23, 59, 59, 999)); 18 | const startOfLastWeek = new Date(startOfToday); 19 | startOfLastWeek.setDate(startOfToday.getDate() - startOfToday.getDay() - 6); // Start of last week (7 days ago) 20 | const endOfLastWeek = new Date(startOfToday); 21 | endOfLastWeek.setDate(startOfToday.getDate() - startOfToday.getDay()); // End of last week (yesterday) 22 | 23 | const todayFiles = extendedClient.notionFile.useFindMany({ 24 | where: { 25 | updatedAt: { 26 | gte: startOfToday, 27 | lte: endOfToday, 28 | }, 29 | }, 30 | orderBy: { 31 | updatedAt: "desc", 32 | }, 33 | }); 34 | 35 | const lastWeekFiles = extendedClient.notionFile.useFindMany({ 36 | where: { 37 | updatedAt: { 38 | gte: startOfLastWeek, 39 | lte: endOfLastWeek, 40 | }, 41 | }, 42 | orderBy: { 43 | updatedAt: "desc", 44 | }, 45 | }); 46 | 47 | return ( 48 | 49 | 50 | 54 | 55 | Today 56 | {todayFiles.map((file) => ( 57 | 65 | 66 | 67 | - {file.icon} {file.title} 68 | 69 | 70 | 71 | ))} 72 | 73 | 74 | Last week 75 | {lastWeekFiles.map((file) => ( 76 | 84 | 85 | 86 | - {file.icon} {file.title} 87 | 88 | 89 | 90 | ))} 91 | 92 | 93 | 94 | 95 | ); 96 | } 97 | 98 | const styles = StyleSheet.create({ 99 | container: { 100 | flex: 1, 101 | }, 102 | section: { 103 | marginBottom: 15, 104 | }, 105 | item: { 106 | marginBottom: 8, 107 | }, 108 | }); 109 | -------------------------------------------------------------------------------- /app/(tabs)/index.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet, SafeAreaView, Button } from "react-native"; 2 | 3 | import { ThemedView } from "@/components/ThemedView"; 4 | import { extendedClient } from "@/myDbModule"; 5 | import ResentFiles from "@/components/ResentFiles"; 6 | import DraggableNotionList from "@/components/DraggableNotionList"; 7 | 8 | export default function HomeScreen() { 9 | const user = extendedClient.user.useFindFirst({ where: { id: 1 } }); 10 | 11 | const createUser = () => { 12 | const newUser = { name: "Beto", email: "beto@expo.dev" }; 13 | extendedClient.user.create({ data: newUser }); 14 | }; 15 | return ( 16 | 17 | 18 | {/*