├── apps
├── native
│ ├── .nvmrc
│ ├── .example.env
│ ├── tsconfig.json
│ ├── assets
│ │ ├── icon.png
│ │ ├── favicon.png
│ │ ├── splash.png
│ │ └── adaptive-icon.png
│ ├── babel.config.js
│ ├── src
│ │ ├── assets
│ │ │ ├── icons
│ │ │ │ ├── logo.png
│ │ │ │ ├── avatar.png
│ │ │ │ ├── google.png
│ │ │ │ ├── saveIcon.png
│ │ │ │ ├── arrow-back.png
│ │ │ │ ├── logo2small.png
│ │ │ │ ├── OrignalIcon.png
│ │ │ │ └── summaryIcon.png
│ │ │ └── fonts
│ │ │ │ ├── Inter-Bold.ttf
│ │ │ │ ├── Inter-Medium.ttf
│ │ │ │ ├── Inter-Regular.ttf
│ │ │ │ ├── Inter-SemiBold.ttf
│ │ │ │ ├── Montserrat-Bold.ttf
│ │ │ │ ├── Montserrat-Light.ttf
│ │ │ │ ├── Montserrat-Medium.ttf
│ │ │ │ ├── Montserrat-Regular.ttf
│ │ │ │ └── Montserrat-SemiBold.ttf
│ │ ├── navigation
│ │ │ └── Navigation.tsx
│ │ └── screens
│ │ │ ├── LoginScreen.tsx
│ │ │ ├── InsideNoteScreen.tsx
│ │ │ ├── NotesDashboardScreen.tsx
│ │ │ └── CreateNoteScreen.tsx
│ ├── .gitignore
│ ├── index.tsx
│ ├── ConvexClientProvider.tsx
│ ├── app.json
│ ├── metro.config.js
│ ├── App.tsx
│ └── package.json
└── web
│ ├── .eslintrc.json
│ ├── .example.env
│ ├── postcss.config.js
│ ├── src
│ ├── app
│ │ ├── favicon.ico
│ │ ├── notes
│ │ │ ├── page.tsx
│ │ │ └── [slug]
│ │ │ │ └── page.tsx
│ │ ├── page.tsx
│ │ ├── layout.tsx
│ │ ├── ConvexClientProvider.tsx
│ │ ├── globals.css
│ │ └── ErrorBoundary.tsx
│ ├── lib
│ │ └── utils.ts
│ ├── middleware.ts
│ └── components
│ │ ├── common
│ │ ├── Logo.tsx
│ │ ├── Menu.tsx
│ │ ├── avatar.tsx
│ │ ├── button.tsx
│ │ ├── UserNav.tsx
│ │ ├── TestTimonialCard.tsx
│ │ └── dropdown-menu.tsx
│ │ ├── notes
│ │ ├── NoteItem.tsx
│ │ ├── NoteDetails.tsx
│ │ ├── Checkbox.tsx
│ │ ├── Notes.tsx
│ │ ├── DeleteNote.tsx
│ │ └── CreateNote.tsx
│ │ ├── home
│ │ ├── FooterHero.tsx
│ │ ├── ComplexToggle.tsx
│ │ ├── Hero.tsx
│ │ ├── Footer.tsx
│ │ ├── Testimonials.tsx
│ │ └── Benefits.tsx
│ │ └── Header.tsx
│ ├── public
│ ├── images
│ │ ├── Add.png
│ │ ├── bot.png
│ │ ├── hero.png
│ │ ├── logo.png
│ │ ├── menu.png
│ │ ├── github.png
│ │ ├── Fantasy.png
│ │ ├── cloudSync.png
│ │ ├── goodNews.png
│ │ ├── monitor.png
│ │ ├── profile.png
│ │ ├── Moe-Partuj.jpeg
│ │ ├── background.png
│ │ ├── left-arrow.png
│ │ ├── googleCalander.png
│ │ ├── star.svg
│ │ ├── blue-circle-right.svg
│ │ ├── blue-circle.svg
│ │ ├── cricle.svg
│ │ ├── hero_image_bg.svg
│ │ ├── message-right.svg
│ │ ├── message-left.svg
│ │ ├── delete.svg
│ │ └── search.svg
│ ├── vercel.svg
│ └── next.svg
│ ├── vercel.json
│ ├── .gitignore
│ ├── tsconfig.json
│ ├── package.json
│ └── README.md
├── .prettierrc
├── packages
└── backend
│ ├── .gitignore
│ ├── convex
│ ├── auth.config.js
│ ├── schema.ts
│ ├── _generated
│ │ ├── api.js
│ │ ├── api.d.ts
│ │ ├── dataModel.d.ts
│ │ ├── server.js
│ │ └── server.d.ts
│ ├── utils.ts
│ ├── tsconfig.json
│ ├── notes.ts
│ ├── openai.ts
│ └── README.md
│ └── package.json
├── renovate.json
├── turbo.json
├── .gitignore
├── package.json
├── README.md
└── LICENSE
/apps/native/.nvmrc:
--------------------------------------------------------------------------------
1 | node-linker=hoisted
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/backend/.gitignore:
--------------------------------------------------------------------------------
1 | .env.local
2 | .env
3 |
--------------------------------------------------------------------------------
/apps/web/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/apps/native/.example.env:
--------------------------------------------------------------------------------
1 | EXPO_PUBLIC_CONVEX_URL=
2 | EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=
3 |
--------------------------------------------------------------------------------
/apps/native/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {},
3 | "extends": "expo/tsconfig.base"
4 | }
5 |
--------------------------------------------------------------------------------
/apps/web/.example.env:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_CONVEX_URL=
2 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
3 | CLERK_SECRET_KEY=
4 |
--------------------------------------------------------------------------------
/apps/web/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | '@tailwindcss/postcss': {},
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/apps/native/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/assets/icon.png
--------------------------------------------------------------------------------
/apps/web/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/src/app/favicon.ico
--------------------------------------------------------------------------------
/apps/native/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/assets/favicon.png
--------------------------------------------------------------------------------
/apps/native/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/assets/splash.png
--------------------------------------------------------------------------------
/apps/web/public/images/Add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/Add.png
--------------------------------------------------------------------------------
/apps/web/public/images/bot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/bot.png
--------------------------------------------------------------------------------
/apps/web/public/images/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/hero.png
--------------------------------------------------------------------------------
/apps/web/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/logo.png
--------------------------------------------------------------------------------
/apps/web/public/images/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/menu.png
--------------------------------------------------------------------------------
/apps/web/public/images/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/github.png
--------------------------------------------------------------------------------
/apps/native/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/apps/web/public/images/Fantasy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/Fantasy.png
--------------------------------------------------------------------------------
/apps/web/public/images/cloudSync.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/cloudSync.png
--------------------------------------------------------------------------------
/apps/web/public/images/goodNews.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/goodNews.png
--------------------------------------------------------------------------------
/apps/web/public/images/monitor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/monitor.png
--------------------------------------------------------------------------------
/apps/web/public/images/profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/profile.png
--------------------------------------------------------------------------------
/apps/native/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ["babel-preset-expo"],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/apps/native/src/assets/icons/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/icons/logo.png
--------------------------------------------------------------------------------
/apps/web/public/images/Moe-Partuj.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/Moe-Partuj.jpeg
--------------------------------------------------------------------------------
/apps/web/public/images/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/background.png
--------------------------------------------------------------------------------
/apps/web/public/images/left-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/left-arrow.png
--------------------------------------------------------------------------------
/apps/native/src/assets/icons/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/icons/avatar.png
--------------------------------------------------------------------------------
/apps/native/src/assets/icons/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/icons/google.png
--------------------------------------------------------------------------------
/apps/native/src/assets/icons/saveIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/icons/saveIcon.png
--------------------------------------------------------------------------------
/apps/web/public/images/googleCalander.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/web/public/images/googleCalander.png
--------------------------------------------------------------------------------
/apps/native/src/assets/fonts/Inter-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/fonts/Inter-Bold.ttf
--------------------------------------------------------------------------------
/apps/native/src/assets/icons/arrow-back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/icons/arrow-back.png
--------------------------------------------------------------------------------
/apps/native/src/assets/icons/logo2small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/icons/logo2small.png
--------------------------------------------------------------------------------
/apps/native/src/assets/fonts/Inter-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/fonts/Inter-Medium.ttf
--------------------------------------------------------------------------------
/apps/native/src/assets/fonts/Inter-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/fonts/Inter-Regular.ttf
--------------------------------------------------------------------------------
/apps/native/src/assets/icons/OrignalIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/icons/OrignalIcon.png
--------------------------------------------------------------------------------
/apps/native/src/assets/icons/summaryIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/icons/summaryIcon.png
--------------------------------------------------------------------------------
/apps/native/src/assets/fonts/Inter-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/fonts/Inter-SemiBold.ttf
--------------------------------------------------------------------------------
/apps/native/src/assets/fonts/Montserrat-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/fonts/Montserrat-Bold.ttf
--------------------------------------------------------------------------------
/apps/native/src/assets/fonts/Montserrat-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/fonts/Montserrat-Light.ttf
--------------------------------------------------------------------------------
/apps/native/src/assets/fonts/Montserrat-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/fonts/Montserrat-Medium.ttf
--------------------------------------------------------------------------------
/apps/native/src/assets/fonts/Montserrat-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/fonts/Montserrat-Regular.ttf
--------------------------------------------------------------------------------
/apps/native/src/assets/fonts/Montserrat-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/get-convex/turbo-expo-nextjs-clerk-convex-monorepo/HEAD/apps/native/src/assets/fonts/Montserrat-SemiBold.ttf
--------------------------------------------------------------------------------
/apps/web/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "buildCommand": "cd ../../packages/backend && npx convex deploy --cmd 'cd ../../apps/web && turbo run build' --cmd-url-env-var-name NEXT_PUBLIC_CONVEX_URL"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/backend/convex/auth.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | providers: [
3 | {
4 | domain: process.env.CLERK_ISSUER_URL,
5 | applicationID: "convex",
6 | },
7 | ],
8 | };
9 |
--------------------------------------------------------------------------------
/apps/web/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/apps/web/src/app/notes/page.tsx:
--------------------------------------------------------------------------------
1 | import Header from "@/components/Header";
2 | import Notes from "@/components/notes/Notes";
3 |
4 | export default function Home() {
5 | return (
6 |
7 |
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/apps/native/.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 |
16 | # Temporary files created by Metro to check the health of the file watcher
17 | .metro-health-check*
18 |
--------------------------------------------------------------------------------
/apps/web/public/images/star.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/packages/backend/convex/schema.ts:
--------------------------------------------------------------------------------
1 | import { defineSchema, defineTable } from "convex/server";
2 | import { v } from "convex/values";
3 |
4 | export default defineSchema({
5 | notes: defineTable({
6 | userId: v.string(),
7 | title: v.string(),
8 | content: v.string(),
9 | summary: v.optional(v.string()),
10 | }),
11 | });
12 |
--------------------------------------------------------------------------------
/apps/native/index.tsx:
--------------------------------------------------------------------------------
1 | import { registerRootComponent } from "expo";
2 |
3 | import App from "./App";
4 |
5 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App);
6 | // It also ensures that whether you load the app in the Expo client or in a native build,
7 | // the environment is set up appropriately
8 | registerRootComponent(App);
9 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "packageRules": [
4 | {
5 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
6 | "automerge": true
7 | },
8 | {
9 | "matchDepTypes": ["devDependencies"],
10 | "automerge": true
11 | }
12 | ],
13 | "extends": ["config:best-practices"]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@packages/backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "dev": "convex dev",
7 | "setup": "convex dev --until-success",
8 | "typecheck": "tsc --noEmit -p convex"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "convex": "^1.29.3",
14 | "openai": "^6.9.1"
15 | },
16 | "devDependencies": {
17 | "typescript": "5.9.3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": ["**/.env.*local"],
4 | "ui": "tui",
5 | "tasks": {
6 | "build": {
7 | "outputs": ["dist/**", ".next/**", "!.next/cache/**"],
8 | "dependsOn": ["^build"]
9 | },
10 | "dev": {
11 | "cache": false,
12 | "persistent": true
13 | },
14 | "lint": {},
15 | "typecheck": {},
16 | "clean": {
17 | "cache": false
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/web/src/app/notes/[slug]/page.tsx:
--------------------------------------------------------------------------------
1 | import Header from "@/components/Header";
2 | import NoteDetails from "@/components/notes/NoteDetails";
3 | import { Id } from "@packages/backend/convex/_generated/dataModel";
4 |
5 | export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
6 | const { slug } = await params;
7 | return (
8 |
9 |
10 | } />
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/apps/web/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
2 | import { NextResponse } from "next/server";
3 |
4 | const isProtectedRoute = createRouteMatcher(["/notes(.*)"]);
5 |
6 | export default clerkMiddleware(async (auth, request) => {
7 | if (isProtectedRoute(request)) {
8 | await auth.protect();
9 | }
10 |
11 | return NextResponse.next();
12 | });
13 |
14 | export const config = {
15 | matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
16 | };
17 |
--------------------------------------------------------------------------------
/apps/web/.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 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/.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 | .swc/
14 | out/
15 | build
16 |
17 | # expo
18 | .expo
19 |
20 | # misc
21 | .DS_Store
22 | *.pem
23 | dist
24 |
25 | # debug
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 |
30 | # local env files
31 | .env.local
32 | .env.development.local
33 | .env.test.local
34 | .env.production.local
35 | .env
36 |
37 | # turbo
38 | .turbo
39 |
--------------------------------------------------------------------------------
/apps/web/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Header from "@/components/Header";
4 | import Benefits from "@/components/home/Benefits";
5 | import Footer from "@/components/home/Footer";
6 | import FooterHero from "@/components/home/FooterHero";
7 | import Hero from "@/components/home/Hero";
8 | import Testimonials from "@/components/home/Testimonials";
9 |
10 | export default function Home() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/packages/backend/convex/_generated/api.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated `api` utility.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * To regenerate, run `npx convex dev`.
8 | * @module
9 | */
10 |
11 | import { anyApi, componentsGeneric } from "convex/server";
12 |
13 | /**
14 | * A utility for referencing Convex functions in your app's API.
15 | *
16 | * Usage:
17 | * ```js
18 | * const myFunctionReference = api.myModule.myFunction;
19 | * ```
20 | */
21 | export const api = anyApi;
22 | export const internal = anyApi;
23 | export const components = componentsGeneric();
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "convex-monorepo",
3 | "private": true,
4 | "scripts": {
5 | "dev": "turbo run dev",
6 | "build": "turbo run build",
7 | "typecheck": "turbo run typecheck",
8 | "clean": "turbo run clean && rm -rf node_modules",
9 | "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path .gitignore"
10 | },
11 | "devDependencies": {
12 | "prettier": "3.7.4",
13 | "turbo": "2.6.2"
14 | },
15 | "engines": {
16 | "node": ">=20.19.4"
17 | },
18 | "workspaces": [
19 | "apps/*",
20 | "packages/*"
21 | ],
22 | "packageManager": "yarn@1.22.22"
23 | }
24 |
--------------------------------------------------------------------------------
/apps/web/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/native/ConvexClientProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ConvexReactClient } from "convex/react";
4 | import { ConvexProviderWithClerk } from "convex/react-clerk";
5 | import { ClerkProvider, useAuth } from "@clerk/clerk-expo";
6 |
7 | const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL);
8 |
9 | export default function ConvexClientProvider({ children }) {
10 | return (
11 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/apps/web/public/images/blue-circle-right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/web/public/images/blue-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/web/public/images/cricle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/web/public/images/hero_image_bg.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/packages/backend/convex/utils.ts:
--------------------------------------------------------------------------------
1 | export function missingEnvVariableUrl(envVarName: string, whereToGet: string) {
2 | const deployment = deploymentName();
3 | if (!deployment) return `Missing ${envVarName} in environment variables.`;
4 | return (
5 | `\n Missing ${envVarName} in environment variables.\n\n` +
6 | ` Get it from ${whereToGet} .\n Paste it on the Convex dashboard:\n` +
7 | ` https://dashboard.convex.dev/d/${deployment}/settings?var=${envVarName}`
8 | );
9 | }
10 |
11 | export function deploymentName() {
12 | const url = process.env.CONVEX_CLOUD_URL;
13 | if (!url) return undefined;
14 | const regex = new RegExp("https://(.+).convex.cloud");
15 | return regex.exec(url)?.[1];
16 | }
17 |
--------------------------------------------------------------------------------
/apps/web/src/components/common/Logo.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 | import React from "react";
4 |
5 | interface Props {
6 | isMobile?: boolean;
7 | }
8 |
9 | const Logo = ({ isMobile }: Props) => {
10 | return (
11 |
12 |
13 |
14 | {!isMobile ? (
15 |
16 | UseNotes
17 |
18 | ) : null}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Logo;
25 |
--------------------------------------------------------------------------------
/apps/web/src/components/common/Menu.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | interface Props {
4 | menuItems: {
5 | title: string;
6 | url: string;
7 | }[];
8 | }
9 |
10 | const Menu = ({ menuItems }: Props) => {
11 | return (
12 |
13 | {menuItems.map((item, index) => (
14 |
15 |
19 | {item.title}
20 |
21 |
22 | ))}
23 |
24 | );
25 | };
26 |
27 | export default Menu;
28 |
--------------------------------------------------------------------------------
/apps/native/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "newArchEnabled": true,
4 | "name": "NotesContract",
5 | "slug": "NotesContract",
6 | "version": "1.0.0",
7 | "orientation": "portrait",
8 | "icon": "./assets/icon.png",
9 | "userInterfaceStyle": "light",
10 | "splash": {
11 | "image": "./assets/splash.png",
12 | "resizeMode": "contain",
13 | "backgroundColor": "#0D87E1"
14 | },
15 | "assetBundlePatterns": ["**/*"],
16 | "ios": {
17 | "supportsTablet": true
18 | },
19 | "android": {
20 | "adaptiveIcon": {
21 | "foregroundImage": "./assets/adaptive-icon.png",
22 | "backgroundColor": "#ffffff"
23 | }
24 | },
25 | "web": {
26 | "favicon": "./assets/favicon.png"
27 | },
28 | "plugins": ["expo-font"]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/backend/convex/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | /* This TypeScript project config describes the environment that
3 | * Convex functions run in and is used to typecheck them.
4 | * You can modify it, but some settings required to use Convex.
5 | */
6 | "compilerOptions": {
7 | /* These settings are not required by Convex and can be modified. */
8 | "allowJs": true,
9 | "strict": true,
10 |
11 | /* These compiler options are required by Convex */
12 | "target": "ESNext",
13 | "lib": ["ES2021", "dom"],
14 | "forceConsistentCasingInFileNames": true,
15 | "allowSyntheticDefaultImports": true,
16 | "module": "ESNext",
17 | "moduleResolution": "Bundler",
18 | "isolatedModules": true,
19 | "skipLibCheck": true,
20 | "noEmit": true
21 | },
22 | "include": ["./**/*"],
23 | "exclude": ["./_generated"]
24 | }
25 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "noEmit": true,
13 | "esModuleInterop": true,
14 | "module": "esnext",
15 | "moduleResolution": "bundler",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "react-jsx",
19 | "incremental": true,
20 | "plugins": [
21 | {
22 | "name": "next"
23 | }
24 | ],
25 | "paths": {
26 | "@/*": [
27 | "./src/*"
28 | ]
29 | }
30 | },
31 | "include": [
32 | "next-env.d.ts",
33 | "**/*.ts",
34 | "**/*.tsx",
35 | ".next/types/**/*.ts",
36 | ".next/dev/types/**/*.ts"
37 | ],
38 | "exclude": [
39 | "node_modules"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/apps/web/public/images/message-right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/apps/web/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter, Montserrat, Lato } from "next/font/google";
3 | import { cn } from "@/lib/utils";
4 | import "./globals.css";
5 | import ConvexClientProvider from "./ConvexClientProvider";
6 |
7 | const inter = Inter({ subsets: ["latin"] });
8 | const montserrat = Montserrat({ subsets: ["latin"] });
9 | const lato = Lato({ weight: "400", subsets: ["latin"] });
10 |
11 | export const metadata: Metadata = {
12 | title: "Notes App",
13 | description: "This is an app to take notes.",
14 | };
15 |
16 | export default function RootLayout({
17 | children,
18 | }: {
19 | children: React.ReactNode;
20 | }) {
21 | return (
22 |
23 |
24 | {children}
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/apps/web/public/images/message-left.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/apps/web/public/images/delete.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/apps/web/src/app/ConvexClientProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ReactNode } from "react";
4 | import { ConvexReactClient } from "convex/react";
5 | import { ConvexProviderWithClerk } from "convex/react-clerk";
6 | import { ClerkProvider, useAuth } from "@clerk/clerk-react";
7 | import { ErrorBoundary } from "./ErrorBoundary";
8 |
9 | const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
10 |
11 | export default function ConvexClientProvider({
12 | children,
13 | }: {
14 | children: ReactNode;
15 | }) {
16 | return (
17 | // NOTE: Once you get Clerk working you can remove this error boundary
18 |
19 |
22 |
23 | {children}
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/apps/native/metro.config.js:
--------------------------------------------------------------------------------
1 | // Learn more https://docs.expo.dev/guides/monorepos
2 |
3 | const { getDefaultConfig } = require("expo/metro-config");
4 | const { FileStore } = require("metro-cache");
5 | const path = require("path");
6 |
7 | const projectRoot = __dirname;
8 | const workspaceRoot = path.resolve(projectRoot, "../..");
9 |
10 | const config = getDefaultConfig(projectRoot);
11 |
12 | // #1 - Watch all files in the monorepo
13 | config.watchFolders = [workspaceRoot];
14 | // #3 - Force resolving nested modules to the folders below
15 | config.resolver.disableHierarchicalLookup = true;
16 | // #2 - Try resolving with project modules first, then workspace modules
17 | config.resolver.nodeModulesPaths = [
18 | path.resolve(projectRoot, "node_modules"),
19 | path.resolve(workspaceRoot, "node_modules"),
20 | ];
21 |
22 | // Use turborepo to restore the cache when possible
23 | config.cacheStores = [
24 | new FileStore({
25 | root: path.join(projectRoot, "node_modules", ".cache", "metro"),
26 | }),
27 | ];
28 |
29 | module.exports = config;
30 |
--------------------------------------------------------------------------------
/apps/web/src/components/notes/NoteItem.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import DeleteNote from "./DeleteNote";
3 |
4 | export interface NoteProps {
5 | note: {
6 | title: string;
7 | _id: string;
8 | _creationTime: number;
9 | };
10 | deleteNote: any;
11 | }
12 |
13 | const NoteItem = ({ note, deleteNote }: NoteProps) => {
14 | return (
15 |
16 |
17 |
18 | {note.title}
19 |
20 |
21 |
22 | {new Date(Number(note._creationTime)).toLocaleDateString()}
23 |
24 |
deleteNote({ noteId: note._id })} />
25 |
26 | );
27 | };
28 |
29 | export default NoteItem;
30 |
--------------------------------------------------------------------------------
/apps/native/src/navigation/Navigation.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavigationContainer } from "@react-navigation/native";
3 | import { createNativeStackNavigator } from "@react-navigation/native-stack";
4 | import LoginScreen from "../screens/LoginScreen";
5 | import NotesDashboardScreen from "../screens/NotesDashboardScreen";
6 | import InsideNoteScreen from "../screens/InsideNoteScreen";
7 | import CreateNoteScreen from "../screens/CreateNoteScreen";
8 |
9 | const Stack = createNativeStackNavigator();
10 |
11 | const Navigation = () => {
12 | return (
13 |
14 |
19 |
20 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default Navigation;
32 |
--------------------------------------------------------------------------------
/apps/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-app",
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 | "typecheck": "tsc --noEmit"
11 | },
12 | "dependencies": {
13 | "@clerk/clerk-react": "^5.57.1",
14 | "@clerk/nextjs": "^6.35.6",
15 | "@headlessui/react": "^2.2.9",
16 | "@heroicons/react": "^2.2.0",
17 | "@packages/backend": "*",
18 | "@radix-ui/react-avatar": "^1.1.11",
19 | "@radix-ui/react-dropdown-menu": "^2.1.16",
20 | "@radix-ui/react-slot": "^1.2.4",
21 | "class-variance-authority": "^0.7.0",
22 | "convex": "^1.29.3",
23 | "lucide-react": "^0.555.0",
24 | "next": "^16.0.9",
25 | "react": "^19.2.2",
26 | "react-dom": "^19.2.1",
27 | "tailwind-merge": "^3.4.0"
28 | },
29 | "devDependencies": {
30 | "@tailwindcss/forms": "0.5.10",
31 | "@tailwindcss/postcss": "4.1.17",
32 | "@types/node": "^24.10.1",
33 | "@types/react": "^19.2.7",
34 | "@types/react-dom": "^19.2.3",
35 | "eslint": "^9.39.1",
36 | "eslint-config-next": "16.0.7",
37 | "postcss": "8.5.6",
38 | "tailwindcss": "4.1.17",
39 | "typescript": "5.9.3"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/backend/convex/_generated/api.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated `api` utility.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * To regenerate, run `npx convex dev`.
8 | * @module
9 | */
10 |
11 | import type * as notes from "../notes.js";
12 | import type * as openai from "../openai.js";
13 | import type * as utils from "../utils.js";
14 |
15 | import type {
16 | ApiFromModules,
17 | FilterApi,
18 | FunctionReference,
19 | } from "convex/server";
20 |
21 | declare const fullApi: ApiFromModules<{
22 | notes: typeof notes;
23 | openai: typeof openai;
24 | utils: typeof utils;
25 | }>;
26 |
27 | /**
28 | * A utility for referencing Convex functions in your app's public API.
29 | *
30 | * Usage:
31 | * ```js
32 | * const myFunctionReference = api.myModule.myFunction;
33 | * ```
34 | */
35 | export declare const api: FilterApi<
36 | typeof fullApi,
37 | FunctionReference
38 | >;
39 |
40 | /**
41 | * A utility for referencing Convex functions in your app's internal API.
42 | *
43 | * Usage:
44 | * ```js
45 | * const myFunctionReference = internal.myModule.myFunction;
46 | * ```
47 | */
48 | export declare const internal: FilterApi<
49 | typeof fullApi,
50 | FunctionReference
51 | >;
52 |
53 | export declare const components: {};
54 |
--------------------------------------------------------------------------------
/apps/web/public/images/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/apps/web/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/web/src/components/notes/NoteDetails.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { api } from "@packages/backend/convex/_generated/api";
4 | import { Id } from "@packages/backend/convex/_generated/dataModel";
5 | import { useQuery } from "convex/react";
6 | import ComplexToggle from "../home/ComplexToggle";
7 | import { useState } from "react";
8 |
9 | interface NoteDetailsProps {
10 | noteId: Id<"notes">;
11 | }
12 |
13 | const NoteDetails = ({ noteId }: NoteDetailsProps) => {
14 | const [isSummary, setIsSummary] = useState(false);
15 | const currentNote = useQuery(api.notes.getNote, { id: noteId });
16 |
17 | return (
18 |
19 |
20 |
21 |
22 |
23 | {currentNote?.title}
24 |
25 |
26 | {!isSummary
27 | ? currentNote?.content
28 | : currentNote?.summary
29 | ? currentNote?.summary
30 | : "No Summary available"}
31 |
32 |
33 | );
34 | };
35 |
36 | export default NoteDetails;
37 |
--------------------------------------------------------------------------------
/apps/web/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 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | 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.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/apps/native/App.tsx:
--------------------------------------------------------------------------------
1 | import { View, StatusBar, Platform } from "react-native";
2 | import { useFonts } from "expo-font";
3 | import { LogBox } from "react-native";
4 | import Navigation from "./src/navigation/Navigation";
5 | import ConvexClientProvider from "./ConvexClientProvider";
6 |
7 | export default function App() {
8 | LogBox.ignoreLogs(["Warning: ..."]);
9 | LogBox.ignoreAllLogs();
10 |
11 | const [loaded] = useFonts({
12 | Bold: require("./src/assets/fonts/Inter-Bold.ttf"),
13 | SemiBold: require("./src/assets/fonts/Inter-SemiBold.ttf"),
14 | Medium: require("./src/assets/fonts/Inter-Medium.ttf"),
15 | Regular: require("./src/assets/fonts/Inter-Regular.ttf"),
16 |
17 | MBold: require("./src/assets/fonts/Montserrat-Bold.ttf"),
18 | MSemiBold: require("./src/assets/fonts/Montserrat-SemiBold.ttf"),
19 | MMedium: require("./src/assets/fonts/Montserrat-Medium.ttf"),
20 | MRegular: require("./src/assets/fonts/Montserrat-Regular.ttf"),
21 | MLight: require("./src/assets/fonts/Montserrat-Light.ttf"),
22 | });
23 | if (!loaded) {
24 | return false;
25 | }
26 |
27 | const STATUS_BAR_HEIGHT =
28 | Platform.OS === "ios" ? 50 : StatusBar.currentHeight;
29 |
30 | return (
31 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/apps/web/src/components/common/avatar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as AvatarPrimitive from "@radix-ui/react-avatar";
3 | import { cn } from "@/lib/utils";
4 |
5 | const Avatar = React.forwardRef<
6 | React.ComponentRef,
7 | React.ComponentPropsWithoutRef
8 | >(({ className, ...props }, ref) => (
9 |
17 | ));
18 | Avatar.displayName = AvatarPrimitive.Root.displayName;
19 |
20 | const AvatarImage = React.forwardRef<
21 | React.ComponentRef,
22 | React.ComponentPropsWithoutRef
23 | >(({ className, ...props }, ref) => (
24 |
29 | ));
30 | AvatarImage.displayName = AvatarPrimitive.Image.displayName;
31 |
32 | const AvatarFallback = React.forwardRef<
33 | React.ComponentRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, ...props }, ref) => (
36 |
44 | ));
45 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
46 |
47 | export { Avatar, AvatarImage, AvatarFallback };
48 |
--------------------------------------------------------------------------------
/apps/web/src/components/home/FooterHero.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 | import React from "react";
4 |
5 | const FooterHero = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | Start Your Intelligent Note-Taking Journey
12 |
13 |
14 | Sign up now and experience the power of AI-enhanced note-taking with
15 | UseNotes
16 |
17 |
18 |
19 | Get Started For Free
20 |
21 |
22 |
23 |
24 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default FooterHero;
37 |
--------------------------------------------------------------------------------
/apps/native/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "native-app",
3 | "version": "1.0.0",
4 | "main": "index.tsx",
5 | "scripts": {
6 | "dev": "expo start",
7 | "start": "expo start",
8 | "android": "expo start --android",
9 | "ios": "expo start --ios",
10 | "web": "expo start --web",
11 | "typecheck": "tsc --noEmit"
12 | },
13 | "dependencies": {
14 | "@clerk/clerk-expo": "^2.19.6",
15 | "@expo/vector-icons": "^15.0.3",
16 | "@packages/backend": "*",
17 | "@react-native/virtualized-lists": "^0.82.1",
18 | "@react-navigation/bottom-tabs": "^7.8.11",
19 | "@react-navigation/native": "^7.1.24",
20 | "@react-navigation/native-stack": "^7.8.5",
21 | "expo": "~54.0.25",
22 | "expo-asset": "~12.0.10",
23 | "expo-auth-session": "~7.0.9",
24 | "expo-font": "~14.0.9",
25 | "expo-system-ui": "~6.0.8",
26 | "expo-web-browser": "~15.0.9",
27 | "react": "19.2.2",
28 | "react-dom": "19.2.1",
29 | "react-native": "0.82.1",
30 | "react-native-gesture-handler": "~2.29.1",
31 | "react-native-keyboard-aware-scroll-view": "^0.9.5",
32 | "react-native-reanimated": "~4.1.5",
33 | "react-native-responsive-fontsize": "^0.5.1",
34 | "react-native-safe-area-context": "5.6.2",
35 | "react-native-screens": "~4.18.0",
36 | "react-native-web": "~0.21.2",
37 | "react-native-webview": "13.16.0",
38 | "use-sync-external-store": "^1.6.0"
39 | },
40 | "devDependencies": {
41 | "@babel/core": "7.28.5",
42 | "@types/jest": "30.0.0",
43 | "@types/react": "^19.2.7",
44 | "@types/react-test-renderer": "19.1.0",
45 | "jest": "30.2.0",
46 | "jest-expo": "54.0.13",
47 | "react-test-renderer": "19.2.1",
48 | "typescript": "5.9.3"
49 | },
50 | "private": true
51 | }
52 |
--------------------------------------------------------------------------------
/apps/web/src/components/notes/Checkbox.tsx:
--------------------------------------------------------------------------------
1 | const Checkbox = ({
2 | isChecked,
3 | checkHandler,
4 | openaiKeySet,
5 | }: {
6 | isChecked: boolean;
7 | checkHandler: () => void;
8 | openaiKeySet: boolean;
9 | }) => {
10 | return (
11 |
47 | );
48 | };
49 |
50 | export default Checkbox;
51 |
--------------------------------------------------------------------------------
/apps/web/src/components/home/ComplexToggle.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Switch } from "@headlessui/react";
3 |
4 | function classNames(...classes: string[]) {
5 | return classes.filter(Boolean).join(" ");
6 | }
7 |
8 | export default function ComplexToggle({
9 | setIsSummary,
10 | isSummary,
11 | }: {
12 | setIsSummary: (value: boolean) => void;
13 | isSummary: boolean;
14 | }) {
15 | return (
16 |
17 |
18 |
23 | Original Note
24 | {" "}
25 |
26 |
34 |
41 |
42 |
43 |
46 | AI Summary
47 | {" "}
48 |
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/packages/backend/convex/notes.ts:
--------------------------------------------------------------------------------
1 | import { mutation, query } from "./_generated/server";
2 | import { v } from "convex/values";
3 | import { internal } from "../convex/_generated/api";
4 | import { Auth } from "convex/server";
5 |
6 | export const getUserId = async (ctx: { auth: Auth }) => {
7 | return (await ctx.auth.getUserIdentity())?.subject;
8 | };
9 |
10 | // Get all notes for a specific user
11 | export const getNotes = query({
12 | args: {},
13 | handler: async (ctx) => {
14 | const userId = await getUserId(ctx);
15 | if (!userId) return null;
16 |
17 | const notes = await ctx.db
18 | .query("notes")
19 | .filter((q) => q.eq(q.field("userId"), userId))
20 | .collect();
21 |
22 | return notes;
23 | },
24 | });
25 |
26 | // Get note for a specific note
27 | export const getNote = query({
28 | args: {
29 | id: v.optional(v.id("notes")),
30 | },
31 | handler: async (ctx, args) => {
32 | const { id } = args;
33 | if (!id) return null;
34 | const note = await ctx.db.get(id);
35 | return note;
36 | },
37 | });
38 |
39 | // Create a new note for a user
40 | export const createNote = mutation({
41 | args: {
42 | title: v.string(),
43 | content: v.string(),
44 | isSummary: v.boolean(),
45 | },
46 | handler: async (ctx, { title, content, isSummary }) => {
47 | const userId = await getUserId(ctx);
48 | if (!userId) throw new Error("User not found");
49 | const noteId = await ctx.db.insert("notes", { userId, title, content });
50 |
51 | if (isSummary) {
52 | await ctx.scheduler.runAfter(0, internal.openai.summary, {
53 | id: noteId,
54 | title,
55 | content,
56 | });
57 | }
58 |
59 | return noteId;
60 | },
61 | });
62 |
63 | export const deleteNote = mutation({
64 | args: {
65 | noteId: v.id("notes"),
66 | },
67 | handler: async (ctx, args) => {
68 | await ctx.db.delete(args.noteId);
69 | },
70 | });
71 |
--------------------------------------------------------------------------------
/packages/backend/convex/_generated/dataModel.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated data model types.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * To regenerate, run `npx convex dev`.
8 | * @module
9 | */
10 |
11 | import type {
12 | DataModelFromSchemaDefinition,
13 | DocumentByName,
14 | TableNamesInDataModel,
15 | SystemTableNames,
16 | } from "convex/server";
17 | import type { GenericId } from "convex/values";
18 | import schema from "../schema.js";
19 |
20 | /**
21 | * The names of all of your Convex tables.
22 | */
23 | export type TableNames = TableNamesInDataModel;
24 |
25 | /**
26 | * The type of a document stored in Convex.
27 | *
28 | * @typeParam TableName - A string literal type of the table name (like "users").
29 | */
30 | export type Doc = DocumentByName<
31 | DataModel,
32 | TableName
33 | >;
34 |
35 | /**
36 | * An identifier for a document in Convex.
37 | *
38 | * Convex documents are uniquely identified by their `Id`, which is accessible
39 | * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
40 | *
41 | * Documents can be loaded using `db.get(id)` in query and mutation functions.
42 | *
43 | * IDs are just strings at runtime, but this type can be used to distinguish them from other
44 | * strings when type checking.
45 | *
46 | * @typeParam TableName - A string literal type of the table name (like "users").
47 | */
48 | export type Id =
49 | GenericId;
50 |
51 | /**
52 | * A type describing your Convex data model.
53 | *
54 | * This type includes information about what tables you have, the type of
55 | * documents stored in those tables, and the indexes defined on them.
56 | *
57 | * This type is used to parameterize methods like `queryGeneric` and
58 | * `mutationGeneric` to make them type-safe.
59 | */
60 | export type DataModel = DataModelFromSchemaDefinition;
61 |
--------------------------------------------------------------------------------
/apps/web/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss';
2 |
3 | @theme {
4 | --breakpoint-*: initial;
5 | --breakpoint-sm: 640px;
6 | --breakpoint-md: 768px;
7 | --breakpoint-lg: 1024px;
8 | --breakpoint-xl: 1440px;
9 |
10 | --color-primary: #0d87e1;
11 |
12 | --font-inter: Inter, sans-serif;
13 | --font-lato: Lato, sans-serif;
14 | --font-montserrat: Montserrat, sans-serif;
15 |
16 | --background-image-gradient-radial: radial-gradient(var(--tw-gradient-stops));
17 | --background-image-gradient-conic: conic-gradient(
18 | from 180deg at 50% 50%,
19 | var(--tw-gradient-stops)
20 | );
21 | }
22 |
23 | @utility container {
24 | margin-inline: auto;
25 | }
26 |
27 | /*
28 | The default border color has changed to `currentcolor` in Tailwind CSS v4,
29 | so we've added these compatibility styles to make sure everything still
30 | looks the same as it did with Tailwind CSS v3.
31 |
32 | If we ever want to remove these styles, we need to add an explicit border
33 | color utility to any element that depends on these defaults.
34 | */
35 | @layer base {
36 | *,
37 | ::after,
38 | ::before,
39 | ::backdrop,
40 | ::file-selector-button {
41 | border-color: var(--color-gray-200, currentcolor);
42 | }
43 | }
44 |
45 | .linear_gradient {
46 | background: linear-gradient(
47 | 267deg,
48 | #fff -9.43%,
49 | #040218 -9.42%,
50 | #fff 4.63%,
51 | #d2e4f2 127.55%
52 | );
53 | }
54 |
55 | .bg_image {
56 | background:
57 | linear-gradient(
58 | 181deg,
59 | rgba(255, 255, 255, 0) 57.92%,
60 | #fff 97.09%,
61 | rgba(255, 255, 255, 0) 127.09%
62 | ),
63 | url("/images/background.png"),
64 | lightgray 0% 0% / 261.2499952316284px 261.2499952316284px repeat;
65 | }
66 | .button {
67 | border-radius: 8px;
68 | background: linear-gradient(
69 | 267deg,
70 | #0983df -9.43%,
71 | #040218 -9.42%,
72 | #0d87e1 4.63%,
73 | #0983df 127.55%
74 | );
75 | box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
76 | }
77 |
--------------------------------------------------------------------------------
/apps/web/src/components/common/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Slot } from "@radix-ui/react-slot";
3 | import { cva, type VariantProps } from "class-variance-authority";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | },
35 | );
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean;
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button";
46 | return (
47 |
52 | );
53 | },
54 | );
55 | Button.displayName = "Button";
56 |
57 | export { Button, buttonVariants };
58 |
--------------------------------------------------------------------------------
/apps/web/src/components/common/UserNav.tsx:
--------------------------------------------------------------------------------
1 | import { useClerk } from "@clerk/clerk-react";
2 | import { LogOut, Paintbrush2 } from "lucide-react";
3 | import Link from "next/link";
4 | import { Avatar, AvatarFallback, AvatarImage } from "./avatar";
5 | import { Button } from "./button";
6 | import {
7 | DropdownMenu,
8 | DropdownMenuContent,
9 | DropdownMenuItem,
10 | DropdownMenuLabel,
11 | DropdownMenuSeparator,
12 | DropdownMenuTrigger,
13 | } from "./dropdown-menu";
14 |
15 | export function UserNav({
16 | image,
17 | name,
18 | email,
19 | }: {
20 | image: string;
21 | name: string;
22 | email: string;
23 | }) {
24 | const { signOut } = useClerk();
25 |
26 | return (
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {name}
43 |
44 |
{email}
45 |
46 |
47 |
48 |
49 |
50 |
51 | Dashboard
52 |
53 |
54 | signOut()}
56 | className="hover:cursor-pointer hover:bg-gray-200"
57 | >
58 |
59 | Log out
60 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/apps/web/src/components/notes/Notes.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { api } from "@packages/backend/convex/_generated/api";
4 | import { useMutation, useQuery } from "convex/react";
5 | import Image from "next/image";
6 | import { useState } from "react";
7 | import CreateNote from "./CreateNote";
8 | import NoteItem from "./NoteItem";
9 |
10 | const Notes = () => {
11 | const [search, setSearch] = useState("");
12 |
13 | const allNotes = useQuery(api.notes.getNotes);
14 | const deleteNote = useMutation(api.notes.deleteNote);
15 |
16 | const finalNotes = search
17 | ? allNotes?.filter(
18 | (note) =>
19 | note.title.toLowerCase().includes(search.toLowerCase()) ||
20 | note.content.toLowerCase().includes(search.toLowerCase()),
21 | )
22 | : allNotes;
23 |
24 | return (
25 |
26 |
27 | Your Notes
28 |
29 |
30 |
31 |
38 | setSearch(e.target.value)}
43 | className="flex-1 text-[#2D2D2D] text-[17px] sm:text-2xl not-italic font-light leading-[114.3%] tracking-[-0.6px] focus:outline-0 focus:ring-0 focus:border-0 border-0"
44 | />
45 |
46 |
47 |
48 |
49 | {finalNotes &&
50 | finalNotes.map((note, index) => (
51 |
52 | ))}
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default Notes;
61 |
--------------------------------------------------------------------------------
/apps/web/src/components/common/TestTimonialCard.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | interface Props {
3 | data: {
4 | feature?: boolean;
5 | review: string;
6 | profile: string;
7 | name: string;
8 | designation: string;
9 | };
10 | }
11 |
12 | const TestTimonialCard = ({ data }: Props) => {
13 | return (
14 |
19 |
20 | {Array(5)
21 | .fill(0)
22 | .map((data, index) => (
23 |
30 | ))}
31 |
32 |
37 | “
38 | {data.review}
39 | ”
40 |
41 |
42 |
43 |
50 |
51 |
52 |
57 | {data.name}
58 |
59 |
64 | {data.designation}
65 |
66 |
67 |
68 |
69 | );
70 | };
71 |
72 | export default TestTimonialCard;
73 |
--------------------------------------------------------------------------------
/packages/backend/convex/openai.ts:
--------------------------------------------------------------------------------
1 | import OpenAI from "openai";
2 | import { internalAction, internalMutation, query } from "./_generated/server";
3 | import { v } from "convex/values";
4 | import { internal } from "./_generated/api";
5 | import { missingEnvVariableUrl } from "./utils";
6 |
7 | export const openaiKeySet = query({
8 | args: {},
9 | handler: async () => {
10 | return !!process.env.OPENAI_API_KEY;
11 | },
12 | });
13 |
14 | export const summary = internalAction({
15 | args: {
16 | id: v.id("notes"),
17 | title: v.string(),
18 | content: v.string(),
19 | },
20 | handler: async (ctx, { id, title, content }) => {
21 | const prompt = `Take in the following note and return a summary: Title: ${title}, Note content: ${content}`;
22 |
23 | const apiKey = process.env.OPENAI_API_KEY;
24 | if (!apiKey) {
25 | const error = missingEnvVariableUrl(
26 | "OPENAI_API_KEY",
27 | "https://platform.openai.com/account/api-keys",
28 | );
29 | console.error(error);
30 | await ctx.runMutation(internal.openai.saveSummary, {
31 | id: id,
32 | summary: error,
33 | });
34 | return;
35 | }
36 | const openai = new OpenAI({ apiKey });
37 | const output = await openai.chat.completions.create({
38 | messages: [
39 | {
40 | role: "system",
41 | content:
42 | "You are a helpful assistant designed to output JSON in this format: {summary: string}",
43 | },
44 | { role: "user", content: prompt },
45 | ],
46 | model: "gpt-4-1106-preview",
47 | response_format: { type: "json_object" },
48 | });
49 |
50 | // Pull the message content out of the response
51 | const messageContent = output.choices[0]?.message.content;
52 |
53 | console.log({ messageContent });
54 |
55 | const parsedOutput = JSON.parse(messageContent!);
56 | console.log({ parsedOutput });
57 |
58 | await ctx.runMutation(internal.openai.saveSummary, {
59 | id: id,
60 | summary: parsedOutput.summary,
61 | });
62 | },
63 | });
64 |
65 | export const saveSummary = internalMutation({
66 | args: {
67 | id: v.id("notes"),
68 | summary: v.string(),
69 | },
70 | handler: async (ctx, { id, summary }) => {
71 | await ctx.db.patch(id, {
72 | summary: summary,
73 | });
74 | },
75 | });
76 |
--------------------------------------------------------------------------------
/apps/web/src/components/home/Hero.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 |
4 | const Hero = () => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | The Ultimate Note-Taking Experience
12 |
13 |
14 | UseNotes harnesses the power of artificial intelligence to
15 | revolutionize the way you capture, organize, and recall your
16 | thoughts
17 |
18 |
19 |
20 | Get Started
21 |
22 |
23 |
24 |
25 |
26 |
27 |
34 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default Hero;
53 |
--------------------------------------------------------------------------------
/apps/web/src/app/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | import { Component, ReactNode } from "react";
2 | import NextLink from "next/link";
3 |
4 | // NOTE: Once you get Clerk working you can remove this error boundary
5 | export class ErrorBoundary extends Component<
6 | { children: ReactNode },
7 | { error: ReactNode | null }
8 | > {
9 | constructor(props: { children: ReactNode }) {
10 | super(props);
11 | this.state = { error: null };
12 | }
13 |
14 | static getDerivedStateFromError(error: unknown) {
15 | const errorText = "" + (error as any).toString();
16 | if (
17 | errorText.includes("@clerk/clerk-react") &&
18 | errorText.includes("publishableKey")
19 | ) {
20 | const [clerkDashboardUrl] = errorText.match(/https:\S+/) ?? [];
21 | return {
22 | error: (
23 | <>
24 |
25 | Add{" "}
26 |
27 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY={'""'}
28 | {" "}
29 | to the{" "}
30 |
31 | .env
32 | {" "}
33 | file
34 |
35 | {clerkDashboardUrl ? (
36 |
37 | You can find it at{" "}
38 |
43 | {clerkDashboardUrl}
44 |
45 |
46 | ) : null}
47 | Raw error: {errorText}
48 | >
49 | ),
50 | };
51 | }
52 |
53 | // propagate error to Next.js provided error boundary
54 | throw error;
55 | }
56 |
57 | componentDidCatch() {}
58 |
59 | render() {
60 | if (this.state.error !== null) {
61 | return (
62 |
63 |
64 | Caught an error while rendering:
65 |
66 | {this.state.error}
67 |
68 | );
69 | }
70 |
71 | return this.props.children;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/apps/web/src/components/home/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Logo from "../common/Logo";
3 | import Menu from "../common/Menu";
4 |
5 | const menuItems = [
6 | {
7 | title: "Home",
8 | url: "/",
9 | },
10 | {
11 | title: "Benefits",
12 | url: "#Benefits",
13 | },
14 | {
15 | title: "Get Started",
16 | url: "/notes",
17 | },
18 | {
19 | title: "Reviews",
20 | url: "#reviews",
21 | },
22 | ];
23 |
24 | const Footer = () => {
25 | return (
26 | <>
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Take more efficient notes with UseNotes
35 |
36 |
37 |
38 | Save countless hours of note-taking and organize your notes
39 | easier.
40 |
41 |
42 | © 2023 UseNotes. All rights reserved.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | Take more efficient notes with UseNotes
54 |
55 |
56 | Save countless hours of note-taking and organize your notes
57 | easier.
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | © 2023 UseNotes. All rights reserved.
66 | Icons by Icons8
67 |
68 |
69 | >
70 | );
71 | };
72 |
73 | export default Footer;
74 |
--------------------------------------------------------------------------------
/apps/web/src/components/home/Testimonials.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import TestTimonialCard from "../common/TestTimonialCard";
3 |
4 | const TestimonialsData = [
5 | {
6 | rating: 5,
7 | review:
8 | "Great note-taking application! The AI features make note-taking a breeze",
9 | name: "Ryan Lowry",
10 | designation: "Engineer & Author",
11 | profile: "/images/profile.png",
12 | feature: false,
13 | },
14 | {
15 | rating: 5,
16 | review:
17 | "Really like the clean design of UseNotes. The AI-driven search is impressively accurate, adding a personal dimension to my notes. Fast and very easy to use.",
18 | name: "John Collins",
19 | designation: "Engineer & Author",
20 | profile: "/images/profile.png",
21 | feature: true,
22 | },
23 | {
24 | rating: 5,
25 | review: "Simply brilliant! UseNotes has elevated my productivity.",
26 | name: "Moe Partuj",
27 | designation: "Student",
28 | profile: "/images/Moe-Partuj.jpeg",
29 | feature: false,
30 | },
31 | ];
32 |
33 | const Testimonials = () => {
34 | return (
35 |
39 |
46 |
47 |
48 | Reviews
49 |
50 |
51 | User Testimonials
52 |
53 |
54 |
55 |
62 |
69 | {TestimonialsData.map((item, index) => (
70 |
71 | ))}
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | export default Testimonials;
79 |
--------------------------------------------------------------------------------
/packages/backend/convex/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to your Convex functions directory!
2 |
3 | Write your Convex functions here. See
4 | https://docs.convex.dev/using/writing-convex-functions for more.
5 |
6 | A query function that takes two arguments looks like:
7 |
8 | ```ts
9 | // functions.js
10 | import { query } from "./_generated/server";
11 | import { v } from "convex/values";
12 |
13 | export const myQueryFunction = query({
14 | // Validators for arguments.
15 | args: {
16 | first: v.number(),
17 | second: v.string(),
18 | },
19 |
20 | // Function implementation.
21 | handler: async (ctx, args) => {
22 | // Read the database as many times as you need here.
23 | // See https://docs.convex.dev/database/reading-data.
24 | const documents = await ctx.db.query("tablename").collect();
25 |
26 | // Arguments passed from the client are properties of the args object.
27 | console.log(args.first, args.second);
28 |
29 | // Write arbitrary JavaScript here: filter, aggregate, build derived data,
30 | // remove non-public properties, or create new objects.
31 | return documents;
32 | },
33 | });
34 | ```
35 |
36 | Using this query function in a React component looks like:
37 |
38 | ```ts
39 | const data = useQuery(api.functions.myQueryFunction, {
40 | first: 10,
41 | second: "hello",
42 | });
43 | ```
44 |
45 | A mutation function looks like:
46 |
47 | ```ts
48 | // functions.js
49 | import { mutation } from "./_generated/server";
50 | import { v } from "convex/values";
51 |
52 | export const myMutationFunction = mutation({
53 | // Validators for arguments.
54 | args: {
55 | first: v.string(),
56 | second: v.string(),
57 | },
58 |
59 | // Function implementation.
60 | handler: async (ctx, args) => {
61 | // Insert or modify documents in the database here.
62 | // Mutations can also read from the database like queries.
63 | // See https://docs.convex.dev/database/writing-data.
64 | const message = { body: args.first, author: args.second };
65 | const id = await ctx.db.insert("messages", message);
66 |
67 | // Optionally, return a value from your mutation.
68 | return await ctx.db.get(id);
69 | },
70 | });
71 | ```
72 |
73 | Using this mutation function in a React component looks like:
74 |
75 | ```ts
76 | const mutation = useMutation(api.functions.myMutationFunction);
77 | function handleButtonPress() {
78 | // fire and forget, the most common way to use mutations
79 | mutation({ first: "Hello!", second: "me" });
80 | // OR
81 | // use the result once the mutation has completed
82 | mutation({ first: "Hello!", second: "me" }).then((result) =>
83 | console.log(result),
84 | );
85 | }
86 | ```
87 |
88 | Use the Convex CLI to push your functions to a deployment. See everything
89 | the Convex CLI can do by running `npx convex -h` in your project root
90 | directory. To learn more, launch the docs with `npx convex docs`.
91 |
--------------------------------------------------------------------------------
/apps/web/src/components/home/Benefits.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | const benefits = [
4 | {
5 | title: "Effortless Note-Taking",
6 | description: "Capture thoughts effortlessly with our intuitive interface",
7 | image: "/images/goodNews.png",
8 | },
9 | {
10 | title: "Seamless Sync",
11 | description:
12 | "Access your notes anytime, anywhere, with seamless cloud synchronization.",
13 | image: "/images/cloudSync.png",
14 | },
15 | {
16 | title: "Enhanced Productivity",
17 | description:
18 | "Let AI handle organization, so you can focus on what matters most.",
19 | image: "/images/googleCalander.png",
20 | },
21 | {
22 | title: "AI-Powered Insights",
23 | description:
24 | "Gain valuable insights with smart analytics based on your note patterns.",
25 | image: "/images/bot.png",
26 | },
27 | ];
28 |
29 | const Benefits = () => {
30 | return (
31 |
32 |
39 |
40 |
41 | Benefits
42 |
43 |
44 | Why Choose UseNotes
45 |
46 |
47 |
48 |
49 | {Array(3)
50 | .fill(0)
51 | .map((_, index) => (
52 |
59 | ))}
60 |
61 |
62 |
63 | {benefits.map((benefit, index) => (
64 |
68 |
69 |
76 |
77 |
78 |
79 | {benefit.title}
80 |
81 |
82 | {benefit.description}
83 |
84 |
85 |
86 | ))}
87 |
88 |
89 |
90 |
91 | );
92 | };
93 |
94 | export default Benefits;
95 |
--------------------------------------------------------------------------------
/packages/backend/convex/_generated/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated utilities for implementing server-side Convex query and mutation functions.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * To regenerate, run `npx convex dev`.
8 | * @module
9 | */
10 |
11 | import {
12 | actionGeneric,
13 | httpActionGeneric,
14 | queryGeneric,
15 | mutationGeneric,
16 | internalActionGeneric,
17 | internalMutationGeneric,
18 | internalQueryGeneric,
19 | } from "convex/server";
20 |
21 | /**
22 | * Define a query in this Convex app's public API.
23 | *
24 | * This function will be allowed to read your Convex database and will be accessible from the client.
25 | *
26 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
27 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
28 | */
29 | export const query = queryGeneric;
30 |
31 | /**
32 | * Define a query that is only accessible from other Convex functions (but not from the client).
33 | *
34 | * This function will be allowed to read from your Convex database. It will not be accessible from the client.
35 | *
36 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
37 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
38 | */
39 | export const internalQuery = internalQueryGeneric;
40 |
41 | /**
42 | * Define a mutation in this Convex app's public API.
43 | *
44 | * This function will be allowed to modify your Convex database and will be accessible from the client.
45 | *
46 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
47 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
48 | */
49 | export const mutation = mutationGeneric;
50 |
51 | /**
52 | * Define a mutation that is only accessible from other Convex functions (but not from the client).
53 | *
54 | * This function will be allowed to modify your Convex database. It will not be accessible from the client.
55 | *
56 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
57 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
58 | */
59 | export const internalMutation = internalMutationGeneric;
60 |
61 | /**
62 | * Define an action in this Convex app's public API.
63 | *
64 | * An action is a function which can execute any JavaScript code, including non-deterministic
65 | * code and code with side-effects, like calling third-party services.
66 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
67 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
68 | *
69 | * @param func - The action. It receives an {@link ActionCtx} as its first argument.
70 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
71 | */
72 | export const action = actionGeneric;
73 |
74 | /**
75 | * Define an action that is only accessible from other Convex functions (but not from the client).
76 | *
77 | * @param func - The function. It receives an {@link ActionCtx} as its first argument.
78 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
79 | */
80 | export const internalAction = internalActionGeneric;
81 |
82 | /**
83 | * Define an HTTP action.
84 | *
85 | * The wrapped function will be used to respond to HTTP requests received
86 | * by a Convex deployment if the requests matches the path and method where
87 | * this action is routed. Be sure to route your httpAction in `convex/http.js`.
88 | *
89 | * @param func - The function. It receives an {@link ActionCtx} as its first argument
90 | * and a Fetch API `Request` object as its second.
91 | * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
92 | */
93 | export const httpAction = httpActionGeneric;
94 |
--------------------------------------------------------------------------------
/apps/web/src/components/notes/DeleteNote.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Fragment, useRef, useState } from "react";
3 | import { Dialog, Transition } from "@headlessui/react";
4 | import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
5 | import Image from "next/image";
6 |
7 | export default function DeleteNote({ deleteAction }: any) {
8 | const [open, setOpen] = useState(false);
9 |
10 | const cancelButtonRef = useRef(null);
11 |
12 | return (
13 | <>
14 | setOpen(true)}
16 | src={"/images/delete.svg"}
17 | width={20}
18 | height={20}
19 | alt="search"
20 | className="cursor-pointer"
21 | />
22 |
23 |
24 |
30 |
39 |
40 |
41 |
42 |
43 |
44 |
53 |
54 |
55 |
56 |
57 |
61 |
62 |
63 |
67 | Delete Note
68 |
69 |
70 |
71 | Are you sure you want to delete this note? All of
72 | your data will be permanently removed. This action
73 | cannot be undone.
74 |
75 |
76 |
77 |
78 |
79 |
80 | {
84 | deleteAction();
85 | setOpen(false);
86 | }}
87 | >
88 | Delete
89 |
90 | setOpen(false)}
94 | ref={cancelButtonRef}
95 | >
96 | Cancel
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | >
106 | );
107 | }
108 |
--------------------------------------------------------------------------------
/apps/native/src/screens/LoginScreen.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { StyleSheet, View, Text, TouchableOpacity, Image } from "react-native";
3 | import { RFValue } from "react-native-responsive-fontsize";
4 | import { useOAuth } from "@clerk/clerk-expo";
5 | import { AntDesign } from "@expo/vector-icons";
6 |
7 | const LoginScreen = ({ navigation }) => {
8 | const { startOAuthFlow: startGoogleAuthFlow } = useOAuth({
9 | strategy: "oauth_google",
10 | });
11 | const { startOAuthFlow: startAppleAuthFlow } = useOAuth({
12 | strategy: "oauth_apple",
13 | });
14 |
15 | const onPress = async (authType: string) => {
16 | try {
17 | if (authType === "google") {
18 | const { createdSessionId, setActive } = await startGoogleAuthFlow();
19 | if (createdSessionId) {
20 | setActive({ session: createdSessionId });
21 | navigation.navigate("NotesDashboardScreen");
22 | }
23 | } else if (authType === "apple") {
24 | const { createdSessionId, setActive } = await startAppleAuthFlow();
25 | if (createdSessionId) {
26 | setActive({ session: createdSessionId });
27 | navigation.navigate("NotesDashboardScreen");
28 | }
29 | }
30 | } catch (err) {
31 | console.error("OAuth error", err);
32 | }
33 | };
34 |
35 | return (
36 |
37 |
38 |
42 | Log in to your account
43 | Welcome! Please login below.
44 | onPress("google")}
47 | >
48 |
52 |
53 | Continue with Google
54 |
55 |
56 |
57 | onPress("apple")}
60 | >
61 |
62 |
65 | Continue with Apple
66 |
67 |
68 |
69 |
70 | Don’t have an account?
71 | Sign up above.
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | const styles = StyleSheet.create({
79 | container: {
80 | flex: 1,
81 | backgroundColor: "#fff",
82 | },
83 | card: {
84 | backgroundColor: "#fff",
85 | padding: 10,
86 | alignItems: "center",
87 | width: "98%",
88 | },
89 | logo: {
90 | width: 74,
91 | height: 74,
92 | marginTop: 20,
93 | },
94 | title: {
95 | marginTop: 49,
96 | fontSize: RFValue(21),
97 | fontFamily: "SemiBold",
98 | },
99 | subtitle: {
100 | marginTop: 8,
101 | fontSize: RFValue(14),
102 | color: "#000",
103 | fontFamily: "Regular",
104 | marginBottom: 32,
105 | textAlign: "center",
106 | },
107 | input: {
108 | width: "100%",
109 | borderWidth: 1,
110 | borderColor: "#D0D5DD",
111 | borderRadius: 10,
112 | padding: 14,
113 | marginBottom: 16,
114 | fontFamily: "Regular",
115 | fontSize: RFValue(14),
116 | },
117 | buttonEmail: {
118 | backgroundColor: "#0D87E1",
119 | padding: 15,
120 | borderRadius: 10,
121 | width: "100%",
122 | marginBottom: 24,
123 | minHeight: 44,
124 | },
125 | buttonText: {
126 | textAlign: "center",
127 | color: "#FFF",
128 | fontFamily: "SemiBold",
129 | fontSize: RFValue(14),
130 | },
131 | buttonTextWithIcon: {
132 | marginLeft: 10,
133 | },
134 | dividerContainer: {
135 | flexDirection: "row",
136 | alignItems: "center",
137 | width: "100%",
138 | marginBottom: 24,
139 | },
140 | divider: {
141 | flex: 1,
142 | height: 1,
143 | backgroundColor: "#000",
144 | },
145 | dividerText: {
146 | marginHorizontal: 10,
147 | color: "#000",
148 | fontFamily: "Medium",
149 | },
150 | buttonGoogle: {
151 | flexDirection: "row",
152 | alignItems: "center",
153 | justifyContent: "center",
154 | backgroundColor: "#FFF",
155 | borderRadius: 10,
156 | borderWidth: 1,
157 | borderColor: "#D0D5DD",
158 | width: "100%",
159 | marginBottom: 12,
160 | height: 44,
161 | },
162 | buttonApple: {
163 | flexDirection: "row",
164 | justifyContent: "center",
165 | alignItems: "center",
166 | backgroundColor: "#FFF",
167 | padding: 15,
168 | borderRadius: 10,
169 | borderWidth: 1,
170 | borderColor: "#D0D5DD",
171 | width: "100%",
172 | marginBottom: 32,
173 | },
174 | signupContainer: {
175 | flexDirection: "row",
176 | },
177 | signupText: {
178 | color: "#4D9DE0",
179 | fontFamily: "SemiBold",
180 | },
181 | googleIcon: {
182 | width: 24,
183 | height: 24,
184 | marginRight: 12,
185 | },
186 | errorText: {
187 | fontSize: RFValue(14),
188 | color: "tomato",
189 | fontFamily: "Medium",
190 | alignSelf: "flex-start",
191 | marginBottom: 8,
192 | marginLeft: 4,
193 | },
194 | });
195 |
196 | export default LoginScreen;
197 |
--------------------------------------------------------------------------------
/apps/native/src/screens/InsideNoteScreen.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | StyleSheet,
4 | Text,
5 | View,
6 | Image,
7 | Dimensions,
8 | TouchableOpacity,
9 | ScrollView,
10 | } from "react-native";
11 | import { RFValue } from "react-native-responsive-fontsize";
12 |
13 | const { width } = Dimensions.get("window");
14 |
15 | export default function InsideNoteScreen({ route, navigation }) {
16 | const { item } = route.params;
17 | console.log({ item });
18 | const [activeTab, setActiveTab] = useState("original"); // State to manage active tab
19 |
20 | return (
21 |
22 |
23 |
27 |
28 |
29 |
30 | navigation.goBack()}>
31 |
35 |
36 |
37 | {item.title}
38 |
39 |
40 |
41 |
45 |
46 |
47 | {activeTab === "original"
48 | ? item.content
49 | : item.summary
50 | ? item.summary
51 | : "No summary available"}
52 |
53 |
54 |
55 |
56 | {/* Sticky footer */}
57 |
58 | setActiveTab("original")}
64 | >
65 |
74 |
82 | Original
83 |
84 |
85 | setActiveTab("summary")}
91 | >
92 |
99 |
107 | Summary
108 |
109 |
110 |
111 |
112 | );
113 | }
114 |
115 | const styles = StyleSheet.create({
116 | container: {
117 | flex: 1,
118 | backgroundColor: "#F5F7FE",
119 | },
120 | header: {
121 | backgroundColor: "#0D87E1",
122 | height: 67,
123 | justifyContent: "center",
124 | alignItems: "center",
125 | },
126 | logo: {
127 | width: 46,
128 | height: 46,
129 | borderRadius: 20,
130 | resizeMode: "contain",
131 | },
132 | underHeaderContainer: {
133 | width: width,
134 | height: 62,
135 | backgroundColor: "#fff",
136 | borderBottomWidth: 2,
137 | borderBottomColor: "#D9D9D9",
138 | flexDirection: "row",
139 | alignItems: "center",
140 | justifyContent: "space-between",
141 | paddingHorizontal: 16,
142 | },
143 | arrowBack: {
144 | width: 20,
145 | height: 20,
146 | resizeMode: "contain",
147 | },
148 | title: {
149 | fontSize: RFValue(17.5),
150 | fontFamily: "MMedium",
151 | color: "#2D2D2D",
152 | },
153 | contentContainer: {
154 | // Add styles for contentContainer if needed
155 | },
156 | contentTitle: {
157 | fontSize: RFValue(17.5),
158 | fontFamily: "MMedium",
159 | color: "#000",
160 | textAlign: "center",
161 | marginTop: 28,
162 | },
163 | contentDescription: {
164 | fontSize: RFValue(17.5),
165 | fontFamily: "MRegular",
166 | alignSelf: "center",
167 | textAlign: "justify",
168 | paddingLeft: 29,
169 | paddingRight: 21,
170 | marginTop: 30,
171 | },
172 | footer: {
173 | flexDirection: "row",
174 | position: "absolute",
175 | bottom: 0,
176 | left: 0,
177 | right: 0,
178 | backgroundColor: "#fff",
179 | borderTopWidth: 1,
180 | borderTopColor: "#D9D9D9",
181 | },
182 | footerTab: {
183 | flex: 1,
184 | padding: 12,
185 | justifyContent: "center",
186 | alignItems: "center",
187 | },
188 | footerIcon: {
189 | width: 25,
190 | height: 25,
191 | resizeMode: "contain",
192 | },
193 | activeTab: {
194 | backgroundColor: "#0D87E1",
195 | },
196 | activeIcon: {
197 | tintColor: "#fff",
198 | },
199 | inactiveIcon: {
200 | tintColor: "#000",
201 | },
202 | footerText: {
203 | fontSize: RFValue(12.5),
204 | fontFamily: "MRegular",
205 | },
206 | activeTabText: {
207 | color: "#fff",
208 | },
209 | inactiveTabText: {
210 | color: "#000",
211 | },
212 | });
213 |
--------------------------------------------------------------------------------
/packages/backend/convex/_generated/server.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Generated utilities for implementing server-side Convex query and mutation functions.
4 | *
5 | * THIS CODE IS AUTOMATICALLY GENERATED.
6 | *
7 | * To regenerate, run `npx convex dev`.
8 | * @module
9 | */
10 |
11 | import {
12 | ActionBuilder,
13 | HttpActionBuilder,
14 | MutationBuilder,
15 | QueryBuilder,
16 | GenericActionCtx,
17 | GenericMutationCtx,
18 | GenericQueryCtx,
19 | GenericDatabaseReader,
20 | GenericDatabaseWriter,
21 | } from "convex/server";
22 | import type { DataModel } from "./dataModel.js";
23 |
24 | /**
25 | * Define a query in this Convex app's public API.
26 | *
27 | * This function will be allowed to read your Convex database and will be accessible from the client.
28 | *
29 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
30 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
31 | */
32 | export declare const query: QueryBuilder;
33 |
34 | /**
35 | * Define a query that is only accessible from other Convex functions (but not from the client).
36 | *
37 | * This function will be allowed to read from your Convex database. It will not be accessible from the client.
38 | *
39 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
40 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
41 | */
42 | export declare const internalQuery: QueryBuilder;
43 |
44 | /**
45 | * Define a mutation in this Convex app's public API.
46 | *
47 | * This function will be allowed to modify your Convex database and will be accessible from the client.
48 | *
49 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
50 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
51 | */
52 | export declare const mutation: MutationBuilder;
53 |
54 | /**
55 | * Define a mutation that is only accessible from other Convex functions (but not from the client).
56 | *
57 | * This function will be allowed to modify your Convex database. It will not be accessible from the client.
58 | *
59 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
60 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
61 | */
62 | export declare const internalMutation: MutationBuilder;
63 |
64 | /**
65 | * Define an action in this Convex app's public API.
66 | *
67 | * An action is a function which can execute any JavaScript code, including non-deterministic
68 | * code and code with side-effects, like calling third-party services.
69 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
70 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
71 | *
72 | * @param func - The action. It receives an {@link ActionCtx} as its first argument.
73 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
74 | */
75 | export declare const action: ActionBuilder;
76 |
77 | /**
78 | * Define an action that is only accessible from other Convex functions (but not from the client).
79 | *
80 | * @param func - The function. It receives an {@link ActionCtx} as its first argument.
81 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
82 | */
83 | export declare const internalAction: ActionBuilder;
84 |
85 | /**
86 | * Define an HTTP action.
87 | *
88 | * The wrapped function will be used to respond to HTTP requests received
89 | * by a Convex deployment if the requests matches the path and method where
90 | * this action is routed. Be sure to route your httpAction in `convex/http.js`.
91 | *
92 | * @param func - The function. It receives an {@link ActionCtx} as its first argument
93 | * and a Fetch API `Request` object as its second.
94 | * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
95 | */
96 | export declare const httpAction: HttpActionBuilder;
97 |
98 | /**
99 | * A set of services for use within Convex query functions.
100 | *
101 | * The query context is passed as the first argument to any Convex query
102 | * function run on the server.
103 | *
104 | * This differs from the {@link MutationCtx} because all of the services are
105 | * read-only.
106 | */
107 | export type QueryCtx = GenericQueryCtx;
108 |
109 | /**
110 | * A set of services for use within Convex mutation functions.
111 | *
112 | * The mutation context is passed as the first argument to any Convex mutation
113 | * function run on the server.
114 | */
115 | export type MutationCtx = GenericMutationCtx;
116 |
117 | /**
118 | * A set of services for use within Convex action functions.
119 | *
120 | * The action context is passed as the first argument to any Convex action
121 | * function run on the server.
122 | */
123 | export type ActionCtx = GenericActionCtx;
124 |
125 | /**
126 | * An interface to read from the database within Convex query functions.
127 | *
128 | * The two entry points are {@link DatabaseReader.get}, which fetches a single
129 | * document by its {@link Id}, or {@link DatabaseReader.query}, which starts
130 | * building a query.
131 | */
132 | export type DatabaseReader = GenericDatabaseReader;
133 |
134 | /**
135 | * An interface to read from and write to the database within Convex mutation
136 | * functions.
137 | *
138 | * Convex guarantees that all writes within a single mutation are
139 | * executed atomically, so you never have to worry about partial writes leaving
140 | * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
141 | * for the guarantees Convex provides your functions.
142 | */
143 | export type DatabaseWriter = GenericDatabaseWriter;
144 |
--------------------------------------------------------------------------------
/apps/web/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/react";
4 | import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
5 | import Logo from "./common/Logo";
6 | import Link from "next/link";
7 | import { useUser } from "@clerk/clerk-react";
8 | import { UserNav } from "./common/UserNav";
9 | import { usePathname } from "next/navigation";
10 |
11 | type NavigationItem = {
12 | name: string;
13 | href: string;
14 | current: boolean;
15 | };
16 |
17 | const navigation: NavigationItem[] = [
18 | { name: "Benefits", href: "#Benefits", current: true },
19 | { name: "Reviews", href: "#reviews", current: false },
20 | ];
21 |
22 | export default function Header() {
23 | const { user } = useUser();
24 | const pathname = usePathname();
25 |
26 | return (
27 |
28 | {({ open }) => (
29 | <>
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {pathname === "/" && (
40 |
41 |
42 |
43 | {navigation.map((item) => (
44 |
45 |
50 | {item.name}
51 |
52 |
53 | ))}
54 |
55 |
56 |
57 | )}
58 | {user ? (
59 |
60 |
61 |
65 | See your Notes
66 |
67 |
68 |
73 |
74 | ) : (
75 |
76 |
80 | Sign in
81 |
82 |
86 | Get Started
87 |
88 |
89 | )}
90 |
91 | {/* Mobile menu button*/}
92 |
93 |
94 | Open main menu
95 | {open ? (
96 |
97 | ) : (
98 |
99 | )}
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | {navigation.map((item) => (
109 |
116 | {item.name}
117 |
118 | ))}
119 |
120 |
124 | Sign in
125 |
126 |
130 | Get Started
131 |
132 |
133 |
134 |
135 | >
136 | )}
137 |
138 | );
139 | }
140 |
--------------------------------------------------------------------------------
/apps/native/src/screens/NotesDashboardScreen.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | StyleSheet,
4 | View,
5 | Text,
6 | TextInput,
7 | TouchableOpacity,
8 | Image,
9 | FlatList,
10 | Dimensions,
11 | } from "react-native";
12 | import { Feather, AntDesign } from "@expo/vector-icons";
13 | import { RFValue } from "react-native-responsive-fontsize";
14 | import { useAuth, useUser } from "@clerk/clerk-expo";
15 | import { api } from "@packages/backend/convex/_generated/api";
16 | import { useQuery } from "convex/react";
17 |
18 | const NotesDashboardScreen = ({ navigation }) => {
19 | const user = useUser();
20 | const imageUrl = user?.user?.imageUrl;
21 | const firstName = user?.user?.firstName;
22 |
23 | const allNotes = useQuery(api.notes.getNotes);
24 | const [search, setSearch] = useState("");
25 |
26 | const finalNotes = search
27 | ? allNotes.filter(
28 | (note) =>
29 | note.title.toLowerCase().includes(search.toLowerCase()) ||
30 | note.content.toLowerCase().includes(search.toLowerCase()),
31 | )
32 | : allNotes;
33 |
34 | const renderItem = ({ item }) => (
35 |
37 | navigation.navigate("InsideNoteScreen", {
38 | item: item,
39 | })
40 | }
41 | activeOpacity={0.5}
42 | style={styles.noteItem}
43 | >
44 | {item.title}
45 |
46 | );
47 |
48 | return (
49 |
50 |
51 |
55 |
56 |
57 |
58 | {/* @ts-ignore, for css purposes */}
59 |
60 | Your Notes
61 | {imageUrl ? (
62 |
63 | ) : (
64 | {firstName ? firstName : ""}
65 | )}
66 |
67 |
68 |
74 |
80 |
81 | {!finalNotes || finalNotes.length === 0 ? (
82 |
83 |
84 | Create your first note to{"\n"}get started
85 |
86 |
87 | ) : (
88 | item._id}
92 | style={styles.notesList}
93 | contentContainerStyle={{
94 | marginTop: 19,
95 | borderTopWidth: 0.5,
96 | borderTopColor: "rgba(0, 0, 0, 0.59)",
97 | }}
98 | />
99 | )}
100 |
101 | navigation.navigate("CreateNoteScreen")}
103 | style={styles.newNoteButton}
104 | >
105 |
106 | Create a New Note
107 |
108 |
109 | );
110 | };
111 |
112 | const styles = StyleSheet.create({
113 | container: {
114 | flex: 1,
115 | backgroundColor: "white",
116 | },
117 | header: {
118 | backgroundColor: "#0D87E1",
119 | height: 67,
120 | justifyContent: "center",
121 | alignItems: "center",
122 | },
123 | logo: {
124 | width: 46,
125 | height: 46,
126 | borderRadius: 20,
127 | resizeMode: "contain",
128 | },
129 | title: {
130 | fontSize: RFValue(17.5),
131 | fontFamily: "MMedium",
132 | alignSelf: "center",
133 | },
134 | yourNotesContainer: {
135 | flexDirection: "row",
136 | alignItems: "center",
137 | justifyContent: "space-between",
138 | paddingHorizontal: 13,
139 | marginTop: 19,
140 | },
141 | avatarSmall: {
142 | width: 28,
143 | height: 28,
144 | borderRadius: 10,
145 | },
146 | searchContainer: {
147 | flexDirection: "row",
148 | alignItems: "center",
149 | borderWidth: 1,
150 | borderColor: "grey",
151 | borderRadius: 10,
152 | padding: 10,
153 | marginHorizontal: 15,
154 | marginTop: 30,
155 | },
156 | searchIcon: {
157 | marginRight: 10,
158 | },
159 | searchInput: {
160 | flex: 1,
161 | fontSize: RFValue(15),
162 | fontFamily: "MRegular",
163 | color: "#2D2D2D",
164 | },
165 | notesList: {
166 | flex: 1,
167 | },
168 | noteItem: {
169 | padding: 20,
170 | borderBottomWidth: 0.5,
171 | borderBottomColor: "rgba(0, 0, 0, 0.59)",
172 |
173 | backgroundColor: "#F9FAFB",
174 | },
175 | noteText: {
176 | fontSize: 16,
177 | fontFamily: "MLight",
178 | color: "#2D2D2D",
179 | },
180 |
181 | newNoteButton: {
182 | flexDirection: "row",
183 | backgroundColor: "#0D87E1",
184 | borderRadius: 7,
185 | width: Dimensions.get("window").width / 1.6,
186 | alignSelf: "center",
187 | alignItems: "center",
188 | justifyContent: "center",
189 | minHeight: 44,
190 | position: "absolute",
191 | bottom: 35,
192 | shadowColor: "#000",
193 | shadowOffset: {
194 | width: 0,
195 | height: 3,
196 | },
197 | shadowOpacity: 0.27,
198 | shadowRadius: 4.65,
199 |
200 | elevation: 6,
201 | },
202 | newNoteButtonText: {
203 | color: "white",
204 | fontSize: RFValue(15),
205 | fontFamily: "MMedium",
206 | marginLeft: 10,
207 | },
208 | switchContainer: {
209 | position: "absolute",
210 | top: 20,
211 | right: 20,
212 | },
213 | emptyStateText: {
214 | textAlign: "center",
215 | alignSelf: "center",
216 | fontSize: RFValue(15),
217 | color: "grey",
218 | fontFamily: "MLight",
219 | },
220 | emptyState: {
221 | width: "100%",
222 | height: "35%",
223 | marginTop: 19,
224 | backgroundColor: "#F9FAFB",
225 | justifyContent: "center",
226 | alignItems: "center",
227 | borderWidth: 0.5,
228 | borderColor: "rgba(0, 0, 0, 0.59)",
229 | },
230 | });
231 |
232 | export default NotesDashboardScreen;
233 |
--------------------------------------------------------------------------------
/apps/web/src/components/notes/CreateNote.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Fragment, useRef, useState } from "react";
4 | import { Dialog, Transition } from "@headlessui/react";
5 | import Image from "next/image";
6 | import Checkbox from "./Checkbox";
7 | import { api } from "@packages/backend/convex/_generated/api";
8 | import { useMutation, useQuery } from "convex/react";
9 |
10 | export default function CreateNote() {
11 | const [open, setOpen] = useState(false);
12 | const [title, setTitle] = useState("");
13 | const [content, setContent] = useState("");
14 | const [isChecked, setIsChecked] = useState(false);
15 |
16 | const cancelButtonRef = useRef(null);
17 |
18 | const createNote = useMutation(api.notes.createNote);
19 | const openaiKeySet = useQuery(api.openai.openaiKeySet) ?? true;
20 |
21 | const createUserNote = async () => {
22 | await createNote({
23 | title,
24 | content,
25 | isSummary: isChecked,
26 | });
27 | setOpen(false);
28 | };
29 |
30 | return (
31 | <>
32 |
33 | setOpen(true)}
35 | className="button text-[#EBECEF] flex gap-4 justify-center items-center text-center px-8 sm:px-16 py-2"
36 | >
37 |
44 |
45 | {" "}
46 | New Note
47 |
48 |
49 |
50 |
51 |
52 |
58 |
67 |
68 |
69 |
70 |
158 |
159 |
160 | >
161 | );
162 | }
163 |
--------------------------------------------------------------------------------
/apps/web/src/components/common/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
3 | import { Check, ChevronRight, Circle } from "lucide-react";
4 | import { cn } from "@/lib/utils";
5 |
6 | const DropdownMenu = DropdownMenuPrimitive.Root;
7 |
8 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
9 |
10 | const DropdownMenuGroup = DropdownMenuPrimitive.Group;
11 |
12 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
13 |
14 | const DropdownMenuSub = DropdownMenuPrimitive.Sub;
15 |
16 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
17 |
18 | const DropdownMenuSubTrigger = React.forwardRef<
19 | React.ComponentRef,
20 | React.ComponentPropsWithoutRef & {
21 | inset?: boolean;
22 | }
23 | >(({ className, inset, children, ...props }, ref) => (
24 |
33 | {children}
34 |
35 |
36 | ));
37 | DropdownMenuSubTrigger.displayName =
38 | DropdownMenuPrimitive.SubTrigger.displayName;
39 |
40 | const DropdownMenuSubContent = React.forwardRef<
41 | React.ComponentRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ));
53 | DropdownMenuSubContent.displayName =
54 | DropdownMenuPrimitive.SubContent.displayName;
55 |
56 | const DropdownMenuContent = React.forwardRef<
57 | React.ComponentRef,
58 | React.ComponentPropsWithoutRef
59 | >(({ className, sideOffset = 4, ...props }, ref) => (
60 |
61 |
70 |
71 | ));
72 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
73 |
74 | const DropdownMenuItem = React.forwardRef<
75 | React.ComponentRef,
76 | React.ComponentPropsWithoutRef & {
77 | inset?: boolean;
78 | }
79 | >(({ className, inset, ...props }, ref) => (
80 |
89 | ));
90 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
91 |
92 | const DropdownMenuCheckboxItem = React.forwardRef<
93 | React.ComponentRef,
94 | React.ComponentPropsWithoutRef
95 | >(({ className, children, checked, ...props }, ref) => (
96 |
105 |
106 |
107 |
108 |
109 |
110 | {children}
111 |
112 | ));
113 | DropdownMenuCheckboxItem.displayName =
114 | DropdownMenuPrimitive.CheckboxItem.displayName;
115 |
116 | const DropdownMenuRadioItem = React.forwardRef<
117 | React.ComponentRef,
118 | React.ComponentPropsWithoutRef
119 | >(({ className, children, ...props }, ref) => (
120 |
128 |
129 |
130 |
131 |
132 |
133 | {children}
134 |
135 | ));
136 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
137 |
138 | const DropdownMenuLabel = React.forwardRef<
139 | React.ComponentRef,
140 | React.ComponentPropsWithoutRef & {
141 | inset?: boolean;
142 | }
143 | >(({ className, inset, ...props }, ref) => (
144 |
153 | ));
154 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
155 |
156 | const DropdownMenuSeparator = React.forwardRef<
157 | React.ComponentRef,
158 | React.ComponentPropsWithoutRef
159 | >(({ className, ...props }, ref) => (
160 |
165 | ));
166 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
167 |
168 | const DropdownMenuShortcut = ({
169 | className,
170 | ...props
171 | }: React.HTMLAttributes) => {
172 | return (
173 |
177 | );
178 | };
179 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
180 |
181 | export {
182 | DropdownMenu,
183 | DropdownMenuTrigger,
184 | DropdownMenuContent,
185 | DropdownMenuItem,
186 | DropdownMenuCheckboxItem,
187 | DropdownMenuRadioItem,
188 | DropdownMenuLabel,
189 | DropdownMenuSeparator,
190 | DropdownMenuShortcut,
191 | DropdownMenuGroup,
192 | DropdownMenuPortal,
193 | DropdownMenuSub,
194 | DropdownMenuSubContent,
195 | DropdownMenuSubTrigger,
196 | DropdownMenuRadioGroup,
197 | };
198 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fullstack monorepo template feat. Expo, Turbo, Next.js, Convex, Clerk
2 |
3 | This is a modern TypeScript monorepo template with AI web and native apps
4 | featuring:
5 |
6 | - Turborepo: Monorepo management
7 | - React 19: Latest React with concurrent features
8 | - Next.js 16: Web app & marketing page with App Router
9 | - Tailwind CSS v4: Modern CSS-first configuration
10 | - React Native [Expo](https://expo.dev/): Mobile/native app with New Architecture
11 | - [Convex](https://convex.dev): Backend, database, server functions
12 | - [Clerk](https://clerk.dev): User authentication
13 | - OpenAI: Text summarization (optional)
14 |
15 | The example app is a note taking app that can summarize notes using AI. Features
16 | include:
17 |
18 | - Marketing page
19 | - Dashboard page (web & native)
20 | - Note taking page (web & native)
21 | - Backend API that serves web & native with the same API
22 | - Relational database
23 | - End to end type safety (schema definition to frontend API clients)
24 | - User authentication
25 | - Asynchronous call to an OpenAI
26 | - Everything is realtime by default
27 |
28 | ## Using this example
29 |
30 | ### 1. Install dependencies
31 |
32 | If you don't have `yarn` installed, run `npm install --global yarn`.
33 |
34 | Run `yarn`.
35 |
36 | ### 2. Configure Convex
37 |
38 | > Note: The following command will print an error and ask you to add the
39 | > appropriate environment variable to proceed. Continue reading on for how to do
40 | > that.
41 |
42 | ```sh
43 | npm run setup --workspace packages/backend
44 | ```
45 |
46 | The script will log you into Convex if you aren't already and prompt you to
47 | create a project (free). It will then wait to deploy your code until you set the
48 | environment variables in the dashboard.
49 |
50 | Configure Clerk with [this guide](https://docs.convex.dev/auth/clerk). Then add
51 | the `CLERK_ISSUER_URL` found in the "convex" template
52 | [here](https://dashboard.clerk.com/last-active?path=jwt-templates), to your
53 | Convex environment variables
54 | [here](https://dashboard.convex.dev/deployment/settings/environment-variables&var=CLERK_ISSUER_URL).
55 |
56 | Make sure to enable **Google and Apple** as possible Social Connection
57 | providers, as these are used by the React Native login implementation.
58 |
59 | After that, optionally add the `OPENAI_API_KEY` env var from
60 | [OpenAI](https://platform.openai.com/account/api-keys) to your Convex
61 | environment variables to get AI summaries.
62 |
63 | The `setup` command should now finish successfully.
64 |
65 | ### 3. Configure both apps
66 |
67 | In each app directory (`apps/web`, `apps/native`) create a `.env.local` file
68 | using the `.example.env` as a template and fill out your Convex and Clerk
69 | environment variables.
70 |
71 | - Use the `CONVEX_URL` from `packages/backend/.env.local` for
72 | `{NEXT,EXPO}_PUBLIC_CONVEX_URL`.
73 | - The Clerk publishable & secret keys can be found
74 | [here](https://dashboard.clerk.com/last-active?path=api-keys).
75 |
76 | ### 4. Run both apps
77 |
78 | Run the following command to run both the web and mobile apps:
79 |
80 | ```sh
81 | npm run dev
82 | ```
83 |
84 | This will allow you to use the ⬆ and ⬇ keyboard keys to see logs for each
85 | of the Convex backend, web app, and mobile app separately.
86 | If you'd rather see all of the logs in one place, delete the
87 | `"ui": "tui",` line in [turbo.json](./turbo.json).
88 |
89 | ## Deploying
90 |
91 | In order to both deploy the frontend and Convex, run this as the build command from the apps/web directory:
92 |
93 | ```sh
94 | cd ../../packages/backend && npx convex deploy --cmd 'cd ../../apps/web && turbo run build' --cmd-url-env-var-name NEXT_PUBLIC_CONVEX_URL
95 | ```
96 |
97 | There is a vercel.json file in the apps/web directory with this configuration for Vercel.
98 |
99 | ## What's inside?
100 |
101 | This monorepo template includes the following packages/apps:
102 |
103 | ### Apps and Packages
104 |
105 | - `web`: a [Next.js 15](https://nextjs.org/) app with Tailwind CSS and Clerk
106 | - `native`: a [React Native](https://reactnative.dev/) app built with
107 | [expo](https://docs.expo.dev/)
108 | - `packages/backend`: a [Convex](https://www.convex.dev/) folder with the
109 | database schema and shared functions
110 |
111 | Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
112 |
113 | To install a new package, `cd` into that directory, such as [packages/backend](./packages/backend/), and then run `yarn add mypackage@latest`
114 |
115 | ### Utilities
116 |
117 | This Turborepo has some additional tools already setup for you:
118 |
119 | - [Expo](https://docs.expo.dev/) for native development
120 | - [TypeScript](https://www.typescriptlang.org/) for static type checking
121 | - [Prettier](https://prettier.io) for code formatting
122 |
123 | # What is Convex?
124 |
125 | [Convex](https://convex.dev) is a hosted backend platform with a built-in
126 | reactive database that lets you write your
127 | [database schema](https://docs.convex.dev/database/schemas) and
128 | [server functions](https://docs.convex.dev/functions) in
129 | [TypeScript](https://docs.convex.dev/typescript). Server-side database
130 | [queries](https://docs.convex.dev/functions/query-functions) automatically
131 | [cache](https://docs.convex.dev/functions/query-functions#caching--reactivity)
132 | and [subscribe](https://docs.convex.dev/client/react#reactivity) to data,
133 | powering a
134 | [realtime `useQuery` hook](https://docs.convex.dev/client/react#fetching-data)
135 | in our [React client](https://docs.convex.dev/client/react). There are also
136 | clients for [Python](https://docs.convex.dev/client/python),
137 | [Rust](https://docs.convex.dev/client/rust),
138 | [ReactNative](https://docs.convex.dev/client/react-native), and
139 | [Node](https://docs.convex.dev/client/javascript), as well as a straightforward
140 | [HTTP API](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L40).
141 |
142 | The database supports
143 | [NoSQL-style documents](https://docs.convex.dev/database/document-storage) with
144 | [relationships](https://docs.convex.dev/database/document-ids) and
145 | [custom indexes](https://docs.convex.dev/database/indexes/) (including on fields
146 | in nested objects).
147 |
148 | The [`query`](https://docs.convex.dev/functions/query-functions) and
149 | [`mutation`](https://docs.convex.dev/functions/mutation-functions) server
150 | functions have transactional, low latency access to the database and leverage
151 | our [`v8` runtime](https://docs.convex.dev/functions/runtimes) with
152 | [determinism guardrails](https://docs.convex.dev/functions/runtimes#using-randomness-and-time-in-queries-and-mutations)
153 | to provide the strongest ACID guarantees on the market: immediate consistency,
154 | serializable isolation, and automatic conflict resolution via
155 | [optimistic multi-version concurrency control](https://docs.convex.dev/database/advanced/occ)
156 | (OCC / MVCC).
157 |
158 | The [`action` server functions](https://docs.convex.dev/functions/actions) have
159 | access to external APIs and enable other side-effects and non-determinism in
160 | either our [optimized `v8` runtime](https://docs.convex.dev/functions/runtimes)
161 | or a more
162 | [flexible `node` runtime](https://docs.convex.dev/functions/runtimes#nodejs-runtime).
163 |
164 | Functions can run in the background via
165 | [scheduling](https://docs.convex.dev/scheduling/scheduled-functions) and
166 | [cron jobs](https://docs.convex.dev/scheduling/cron-jobs).
167 |
168 | Development is cloud-first, with
169 | [hot reloads for server function](https://docs.convex.dev/cli#run-the-convex-dev-server)
170 | editing via the [CLI](https://docs.convex.dev/cli). There is a
171 | [dashboard UI](https://docs.convex.dev/dashboard) to
172 | [browse and edit data](https://docs.convex.dev/dashboard/deployments/data),
173 | [edit environment variables](https://docs.convex.dev/production/environment-variables),
174 | [view logs](https://docs.convex.dev/dashboard/deployments/logs),
175 | [run server functions](https://docs.convex.dev/dashboard/deployments/functions),
176 | and more.
177 |
178 | There are built-in features for
179 | [reactive pagination](https://docs.convex.dev/database/pagination),
180 | [file storage](https://docs.convex.dev/file-storage),
181 | [reactive search](https://docs.convex.dev/text-search),
182 | [https endpoints](https://docs.convex.dev/functions/http-actions) (for
183 | webhooks),
184 | [streaming import/export](https://docs.convex.dev/database/import-export/), and
185 | [runtime data validation](https://docs.convex.dev/database/schemas#validators)
186 | for [function arguments](https://docs.convex.dev/functions/args-validation) and
187 | [database data](https://docs.convex.dev/database/schemas#schema-validation).
188 |
189 | Everything scales automatically, and it’s
190 | [free to start](https://www.convex.dev/plans).
191 |
--------------------------------------------------------------------------------
/apps/native/src/screens/CreateNoteScreen.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import {
3 | StyleSheet,
4 | Text,
5 | View,
6 | Image,
7 | Dimensions,
8 | TouchableOpacity,
9 | TextInput,
10 | Keyboard,
11 | Animated,
12 | } from "react-native";
13 | import { RFValue } from "react-native-responsive-fontsize";
14 | import { AntDesign } from "@expo/vector-icons";
15 | import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
16 | import { api } from "@packages/backend/convex/_generated/api";
17 | import { useMutation, useQuery } from "convex/react";
18 |
19 | const { width } = Dimensions.get("window");
20 |
21 | export default function CreateNoteScreen({ navigation }) {
22 | const createNote = useMutation(api.notes.createNote);
23 | const openaiKeySet = useQuery(api.openai.openaiKeySet) ?? true;
24 |
25 | const [isAdvancedSummarizationEnabled, setIsAdvancedSummarizationEnabled] =
26 | useState(false);
27 | const [noteContent, setNoteContent] = useState("");
28 | const [noteTitle, setNoteTitle] = useState("");
29 | const footerY = new Animated.Value(0);
30 | const toggleAdvancedSummarization = () => {
31 | setIsAdvancedSummarizationEnabled(!isAdvancedSummarizationEnabled);
32 | };
33 |
34 | useEffect(() => {
35 | const keyboardDidShowListener = Keyboard.addListener(
36 | "keyboardDidShow",
37 | () => {
38 | // Slide down the footer
39 | Animated.timing(footerY, {
40 | toValue: 1,
41 | duration: 300,
42 | useNativeDriver: true,
43 | }).start();
44 | },
45 | );
46 | const keyboardDidHideListener = Keyboard.addListener(
47 | "keyboardDidHide",
48 | () => {
49 | // Slide up the footer
50 | Animated.timing(footerY, {
51 | toValue: 0,
52 | duration: 300,
53 | useNativeDriver: true,
54 | }).start();
55 | },
56 | );
57 |
58 | // Clean up function
59 | return () => {
60 | keyboardDidShowListener.remove();
61 | keyboardDidHideListener.remove();
62 | };
63 | }, []);
64 |
65 | // Calculate the position of the footer based on the Animated.Value
66 | const footerTranslateY = footerY.interpolate({
67 | inputRange: [0, 1],
68 | outputRange: [0, 100], // Adjust this range according to the height of your footer
69 | });
70 |
71 | const createUserNote = async () => {
72 | await createNote({
73 | title: noteTitle,
74 | content: noteContent,
75 | isSummary: isAdvancedSummarizationEnabled,
76 | });
77 | navigation.navigate("NotesDashboardScreen");
78 | };
79 |
80 | return (
81 |
82 |
83 |
87 |
88 |
89 |
90 | navigation.goBack()}>
91 |
95 |
96 |
97 | Create a New Note
98 |
99 |
103 |
104 |
105 |
106 |
110 |
111 | Title
112 | setNoteTitle(val)}
115 | style={styles.inputField}
116 | placeholder="Note Title"
117 | placeholderTextColor="#A9A9A9"
118 | />
119 | Content
120 | setNoteContent(val)}
123 | style={[styles.inputField, styles.inputFieldMulti]}
124 | multiline
125 | placeholder="Note Comments"
126 | placeholderTextColor="#A9A9A9"
127 | />
128 |
129 |
132 | AI Features
133 |
134 |
135 | {/* Advanced Summarization Section */}
136 |
137 |
138 |
143 | {isAdvancedSummarizationEnabled && (
144 |
150 | )}
151 |
152 |
153 | Advanced Summarization {openaiKeySet ? "" : " (Disabled)"}
154 |
155 |
156 |
157 | {openaiKeySet
158 | ? "Check this box if you want to generate summaries using AI."
159 | : "Please set OPENAI_API_KEY in your environment variables."}
160 |
161 |
162 |
163 |
169 |
170 |
171 | Create a New Note
172 |
173 |
174 |
175 | );
176 | }
177 |
178 | const styles = StyleSheet.create({
179 | container: {
180 | flex: 1,
181 | backgroundColor: "#fff",
182 | },
183 | header: {
184 | backgroundColor: "#0D87E1",
185 | height: 67,
186 | justifyContent: "center",
187 | alignItems: "center",
188 | },
189 | logo: {
190 | width: 46,
191 | height: 46,
192 | borderRadius: 20,
193 | resizeMode: "contain",
194 | },
195 | underHeaderContainer: {
196 | width: width,
197 | height: 62,
198 | backgroundColor: "#fff",
199 | borderBottomWidth: 2,
200 | borderBottomColor: "#D9D9D9",
201 | flexDirection: "row",
202 | alignItems: "center",
203 | justifyContent: "space-between",
204 | paddingHorizontal: 16,
205 | },
206 | arrowBack: {
207 | width: 20,
208 | height: 20,
209 | resizeMode: "contain",
210 | },
211 | title: {
212 | fontSize: RFValue(17.5),
213 | fontFamily: "MMedium",
214 | color: "#2D2D2D",
215 | },
216 | inputContainer: {
217 | paddingHorizontal: 27,
218 | marginTop: 43,
219 | },
220 | inputLabel: {
221 | fontSize: RFValue(15),
222 | fontFamily: "MMedium",
223 | color: "#000",
224 | marginBottom: 6,
225 | },
226 | inputField: {
227 | backgroundColor: "#FFF",
228 | marginBottom: 30,
229 | fontSize: RFValue(15),
230 | fontFamily: "MLight",
231 | color: "#000",
232 | borderRadius: 8,
233 | paddingHorizontal: 14,
234 | paddingVertical: 12.5,
235 | borderWidth: 1,
236 | borderColor: "#D9D9D9",
237 | },
238 | inputFieldMulti: {
239 | minHeight: 228,
240 | textAlignVertical: "top",
241 | paddingTop: 10,
242 | },
243 | advancedSummarizationContainer: {
244 | paddingHorizontal: 27,
245 | marginTop: 10,
246 | },
247 | advancedSummarizationCheckboxContainer: {
248 | flexDirection: "row",
249 | alignItems: "center",
250 | marginBottom: 8,
251 | },
252 | checkbox: {
253 | width: RFValue(17.5),
254 | height: RFValue(17.5),
255 | borderRadius: RFValue(5),
256 | borderWidth: 1,
257 | borderColor: "#0D87E1",
258 | justifyContent: "center",
259 | alignItems: "center",
260 | marginRight: RFValue(10),
261 | backgroundColor: "#F9F5FF",
262 | },
263 | checkboxDisabled: {
264 | width: RFValue(17.5),
265 | height: RFValue(17.5),
266 | borderRadius: RFValue(5),
267 | borderWidth: 1,
268 | borderColor: "#D9D9D9",
269 | justifyContent: "center",
270 | alignItems: "center",
271 | marginRight: RFValue(10),
272 | backgroundColor: "#F9F5FF",
273 | },
274 | advancedSummarizationText: {
275 | fontSize: RFValue(15),
276 | fontFamily: "MLight",
277 | color: "#000",
278 | },
279 | advancedSummarizationSubtext: {
280 | fontSize: RFValue(12.5),
281 | fontFamily: "MRegular",
282 | color: "#A9A9A9",
283 | paddingHorizontal: 30,
284 | },
285 | newNoteButton: {
286 | flexDirection: "row",
287 | backgroundColor: "#0D87E1",
288 | borderRadius: 7,
289 | width: width / 1.6,
290 | alignSelf: "center",
291 | alignItems: "center",
292 | justifyContent: "center",
293 | minHeight: 44,
294 | position: "absolute",
295 | bottom: 35,
296 | shadowColor: "#000",
297 | shadowOffset: {
298 | width: 0,
299 | height: 3,
300 | },
301 | shadowOpacity: 0.27,
302 | shadowRadius: 4.65,
303 | elevation: 6,
304 | },
305 | newNoteButtonText: {
306 | color: "white",
307 | fontSize: RFValue(15),
308 | fontFamily: "MMedium",
309 | marginLeft: 10,
310 | },
311 | newNoteButtonContainer: {
312 | position: "absolute",
313 | bottom: 0,
314 | alignSelf: "center",
315 | // ... other styles you need
316 | },
317 | });
318 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2024 Convex, Inc.
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------