diff --git a/.eslintrc b/.eslintrc index 8043e20..845cc36 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,98 +1,98 @@ -{ - "env": { - "browser": true, - "es6": true, - "node": true, - "react-native/react-native": true, - "jest": true - }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended", - "plugin:@typescript-eslint/recommended", - "prettier" - ], - "plugins": [ - "react", - "react-native", - "@typescript-eslint", - "prettier" - ], - "globals": { - "GLOBAL": true, - "Atomics": "readonly", - "SharedArrayBuffer": "readonly", - "JSX": true, - "NodeJS": true - }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": "latest", - "sourceType": "module" - }, - "settings": { - "react": { - "pragma": "React", - "version": "detect" - }, - "import/parsers": { - "@typescript-eslint/parser": [ - ".ts", - ".tsx" - ] - }, - "import/resolver": { - "typescript": { - "alwaysTryTypes": true - } - } - }, - "rules": { - "react/jsx-key": 0, - "react/prop-types": 0, - "prefer-const": "error", - "no-var": "error", - "no-multi-spaces": "error", - "no-undef": "error", - "quotes": ["error", "single"], - "semi": ["error", "always"], - "object-curly-spacing": ["error", "always"], - "eqeqeq": [ - "error", - "always" - ], - "no-useless-rename": "error", - "object-shorthand": [ - "error", - "always" - ], - "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 0 }], - "padding-line-between-statements": [ - "error", - { - "blankLine": "always", - "prev": "var", - "next": "return" - } - ], - "space-infix-ops": "error", - "keyword-spacing": "error", - "@typescript-eslint/no-empty-interface": "error", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/consistent-type-imports": "error", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-unused-vars": "warn", - "@typescript-eslint/no-require-imports": "off", - "react-native/no-unused-styles": "warn", - "react-native/split-platform-components": "warn", - "react-native/no-color-literals": 1, - "react-native/no-inline-styles": "warn", - "react-native/no-raw-text": "warn", - "react-native/no-single-element-style-arrays": "warn", - "import/no-named-as-default": "off" - } -} +{ + "env": { + "browser": true, + "es6": true, + "node": true, + "react-native/react-native": true, + "jest": true + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "plugins": [ + "react", + "react-native", + "@typescript-eslint", + "prettier" + ], + "globals": { + "GLOBAL": true, + "Atomics": "readonly", + "SharedArrayBuffer": "readonly", + "JSX": true, + "NodeJS": true + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": "latest", + "sourceType": "module" + }, + "settings": { + "react": { + "pragma": "React", + "version": "detect" + }, + "import/parsers": { + "@typescript-eslint/parser": [ + ".ts", + ".tsx" + ] + }, + "import/resolver": { + "typescript": { + "alwaysTryTypes": true + } + } + }, + "rules": { + "react/jsx-key": 0, + "react/prop-types": 0, + "prefer-const": "error", + "no-var": "error", + "no-multi-spaces": "error", + "no-undef": "error", + "quotes": ["error", "single"], + "semi": ["error", "always"], + "object-curly-spacing": ["error", "always"], + "eqeqeq": [ + "error", + "always" + ], + "no-useless-rename": "error", + "object-shorthand": [ + "error", + "always" + ], + "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 0 }], + "padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": "var", + "next": "return" + } + ], + "space-infix-ops": "error", + "keyword-spacing": "error", + "@typescript-eslint/no-empty-interface": "error", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-require-imports": "off", + "react-native/no-unused-styles": "warn", + "react-native/split-platform-components": "warn", + "react-native/no-color-literals": 1, + "react-native/no-inline-styles": "warn", + "react-native/no-raw-text": "warn", + "react-native/no-single-element-style-arrays": "warn", + "import/no-named-as-default": "off" + } +} diff --git a/.gitignore b/.gitignore index c9d575d..fdb97b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,38 +1,40 @@ -# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files - -# dependencies -node_modules/ - -# Expo -.expo/ -dist/ -web-build/ -expo-env.d.ts - -# Native -*.orig.* -*.jks -*.p8 -*.p12 -*.key -*.mobileprovision - -# Metro -.metro-health-check* - -# debug -npm-debug.* -yarn-debug.* -yarn-error.* - -# macOS -.DS_Store -*.pem - -# local env files -.env*.local - -# typescript -*.tsbuildinfo - -app-example +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +app-example + +/src/generated/prisma diff --git a/.idea/rambo-template.iml b/.idea/rambo-template.iml index 24643cc..0b872d8 100644 --- a/.idea/rambo-template.iml +++ b/.idea/rambo-template.iml @@ -1,12 +1,12 @@ - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 9eb88c8..1df8c94 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,12 +1,12 @@ -{ - "singleQuote": true, - "trailingComma": "es5", - "quoteProps": "preserve", - "bracketSpacing": true, - "endOfLine": "lf", - "printWidth": 80, - "tabWidth": 2, - "useTabs": false, - "semi": true, - "arrowParens": "avoid" -} +{ + "singleQuote": true, + "trailingComma": "es5", + "quoteProps": "preserve", + "bracketSpacing": true, + "endOfLine": "lf", + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "arrowParens": "avoid" +} diff --git a/README.md b/README.md index cd4feb8..a29cfd4 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,50 @@ -# Welcome to your Expo app šŸ‘‹ - -This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). - -## Get started - -1. Install dependencies - - ```bash - npm install - ``` - -2. Start the app - - ```bash - npx expo start - ``` - -In the output, you'll find options to open the app in a - -- [development build](https://docs.expo.dev/develop/development-builds/introduction/) -- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) -- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) -- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo - -You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). - -## Get a fresh project - -When you're ready, run: - -```bash -npm run reset-project -``` - -This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. - -## Learn more - -To learn more about developing your project with Expo, look at the following resources: - -- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). -- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. - -## Join the community - -Join our community of developers creating universal apps. - -- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. -- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. +# Welcome to your Expo app šŸ‘‹ + +This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). + +## Get started + +1. Install dependencies + + ```bash + npm install + ``` + +2. Start the app + + ```bash + npx expo start + ``` + +In the output, you'll find options to open the app in a + +- [development build](https://docs.expo.dev/develop/development-builds/introduction/) +- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) +- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) +- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo + +You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). + +## Get a fresh project + +When you're ready, run: + +```bash +npm run reset-project +``` + +This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. + +## Learn more + +To learn more about developing your project with Expo, look at the following resources: + +- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). +- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. + +## Join the community + +Join our community of developers creating universal apps. + +- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. +- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. diff --git a/app.json b/app.json index c5735e4..eb02afc 100644 --- a/app.json +++ b/app.json @@ -1,42 +1,42 @@ -{ - "expo": { - "name": "rambo-template", - "slug": "rambo-template", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./src/assets/images/icon.png", - "scheme": "myapp", - "userInterfaceStyle": "automatic", - "newArchEnabled": true, - "ios": { - "supportsTablet": true - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./src/assets/images/adaptive-icon.png", - "backgroundColor": "#ffffff" - } - }, - "web": { - "bundler": "metro", - "output": "static", - "favicon": "./assets/images/favicon.png" - }, - "plugins": [ - "expo-router", - [ - "expo-splash-screen", - { - "image": "./assets/images/splash-icon.png", - "imageWidth": 200, - "resizeMode": "contain", - "backgroundColor": "#ffffff" - } - ], - "expo-font" - ], - "experiments": { - "typedRoutes": true - } - } -} +{ + "expo": { + "name": "rambo-template", + "slug": "rambo-template", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./src/assets/images/icon.png", + "scheme": "myapp", + "userInterfaceStyle": "automatic", + "newArchEnabled": true, + "ios": { + "supportsTablet": true + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./src/assets/images/adaptive-icon.png", + "backgroundColor": "#ffffff" + } + }, + "web": { + "bundler": "metro", + "output": "static", + "favicon": "./assets/images/favicon.png" + }, + "plugins": [ + "expo-router", + [ + "expo-splash-screen", + { + "image": "./assets/images/splash-icon.png", + "imageWidth": 200, + "resizeMode": "contain", + "backgroundColor": "#ffffff" + } + ], + "expo-font" + ], + "experiments": { + "typedRoutes": true + } + } +} diff --git a/package.json b/package.json index 0c5f312..5be6711 100644 --- a/package.json +++ b/package.json @@ -1,85 +1,95 @@ -{ - "name": "rambo-template", - "main": "expo-router/entry", - "version": "1.0.0", - "scripts": { - "start": "expo start", - "reset-project": "node ./scripts/reset-project.js", - "android": "expo start --android", - "ios": "expo start --ios", - "web": "expo start --web", - "test": "jest --watchAll", - "lint": "expo lint" - }, - "jest": { - "preset": "jest-expo" - }, - "dependencies": { - "@expo/vector-icons": "^14.0.2", - "@gorhom/bottom-sheet": "^5.0.6", - "@gorhom/portal": "^1.0.14", - "@react-navigation/bottom-tabs": "^7.2.0", - "@react-navigation/native": "^7.0.14", - "@tanstack/react-query": "^5.61.3", - "axios": "^1.7.7", - "dayjs": "^1.11.13", - "expo": "~52.0.46", - "expo-blur": "~14.0.3", - "expo-constants": "~17.0.8", - "expo-font": "~13.0.4", - "expo-haptics": "~14.0.1", - "expo-linking": "~7.0.5", - "expo-router": "~4.0.20", - "expo-splash-screen": "~0.29.24", - "expo-status-bar": "~2.0.1", - "expo-symbols": "~0.2.2", - "expo-system-ui": "~4.0.9", - "expo-web-browser": "~14.0.2", - "i18next": "^25.0.2", - "lodash": "^4.17.21", - "react": "18.3.1", - "react-dom": "18.3.1", - "react-hook-form": "^7.54.2", - "react-i18next": "^15.5.1", - "react-native": "0.76.9", - "react-native-gesture-handler": "~2.20.2", - "react-native-mask-text": "^0.14.2", - "react-native-reanimated": "~3.16.1", - "react-native-safe-area-context": "4.12.0", - "react-native-screens": "~4.4.0", - "react-native-svg": "15.8.0", - "react-native-web": "~0.19.13", - "react-native-webview": "13.12.5", - "zod": "^3.23.8", - "zustand": "^5.0.1" - }, - "devDependencies": { - "@babel/core": "^7.25.2", - "@react-native/eslint-config": "^0.76.3", - "@types/jest": "^29.5.12", - "@types/lodash": "^4.17.13", - "@types/react": "~18.3.12", - "@types/react-test-renderer": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", - "eslint": "8.56.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-json": "^4.0.1", - "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-native": "^4.1.0", - "jest": "~29.7.0", - "jest-expo": "~52.0.6", - "prettier": "^3.3.3", - "react-test-renderer": "18.3.1", - "typescript": "^5.3.3" - }, - "expo": { - "doctor": { - "reactNativeDirectoryCheck": { - "listUnknownPackages": false - } - } - }, - "private": true -} +{ + "name": "rambo-template", + "main": "expo-router/entry", + "version": "1.0.0", + "scripts": { + "start": "expo start", + "reset-project": "node ./scripts/reset-project.js", + "android": "expo start --android", + "ios": "expo start --ios", + "web": "expo start --web", + "test": "jest --watchAll", + "lint": "expo lint", + "prisma:generate": "npx prisma generate", + "prisma:migrate": "npx prisma migrate dev", + "prisma:studio": "npx prisma studio", + "prisma:seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" + }, + "jest": { + "preset": "jest-expo" + }, + "dependencies": { + "@expo/vector-icons": "^14.0.2", + "@gorhom/bottom-sheet": "^5.0.6", + "@gorhom/portal": "^1.0.14", + "@prisma/client": "^5.0.0", + "@react-native-async-storage/async-storage": "^2.2.0", + "@react-native-google-signin/google-signin": "^14.0.1", + "@react-navigation/bottom-tabs": "^7.2.0", + "@react-navigation/native": "^7.0.14", + "@supabase/supabase-js": "^2.50.0", + "@tanstack/react-query": "^5.61.3", + "axios": "^1.7.7", + "dayjs": "^1.11.13", + "expo": "~52.0.46", + "expo-apple-authentication": "^7.2.4", + "expo-blur": "~14.0.3", + "expo-constants": "~17.0.8", + "expo-font": "~13.0.4", + "expo-haptics": "~14.0.1", + "expo-linking": "~7.0.5", + "expo-router": "~4.0.20", + "expo-splash-screen": "~0.29.24", + "expo-status-bar": "~2.0.1", + "expo-symbols": "~0.2.2", + "expo-system-ui": "~4.0.9", + "expo-web-browser": "~14.0.2", + "i18next": "^25.0.2", + "lodash": "^4.17.21", + "prisma": "^6.9.0", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-hook-form": "^7.54.2", + "react-i18next": "^15.5.1", + "react-native": "0.76.9", + "react-native-gesture-handler": "~2.20.2", + "react-native-mask-text": "^0.14.2", + "react-native-reanimated": "~3.16.1", + "react-native-safe-area-context": "4.12.0", + "react-native-screens": "~4.4.0", + "react-native-svg": "15.8.0", + "react-native-web": "~0.19.13", + "react-native-webview": "13.12.5", + "zod": "^3.23.8", + "zustand": "^5.0.1" + }, + "devDependencies": { + "@babel/core": "^7.25.2", + "@react-native/eslint-config": "^0.76.3", + "@types/jest": "^29.5.12", + "@types/lodash": "^4.17.13", + "@types/react": "~18.3.12", + "@types/react-test-renderer": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^8.15.0", + "@typescript-eslint/parser": "^8.15.0", + "eslint": "8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-json": "^4.0.1", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-native": "^4.1.0", + "jest": "~29.7.0", + "jest-expo": "~52.0.6", + "prettier": "^3.3.3", + "react-test-renderer": "18.3.1", + "typescript": "^5.3.3" + }, + "expo": { + "doctor": { + "reactNativeDirectoryCheck": { + "listUnknownPackages": false + } + } + }, + "private": true +} diff --git a/prisma/migrations/20250612093024_init/migration.sql b/prisma/migrations/20250612093024_init/migration.sql new file mode 100644 index 0000000..027791d --- /dev/null +++ b/prisma/migrations/20250612093024_init/migration.sql @@ -0,0 +1,136 @@ +-- CreateEnum +CREATE TYPE "PreferredLanguage" AS ENUM ('en', 'ar', 'ms', 'tr', 'fr', 'ur'); + +-- CreateEnum +CREATE TYPE "ChatRole" AS ENUM ('user', 'AI'); + +-- CreateEnum +CREATE TYPE "ChatMode" AS ENUM ('public', 'private'); + +-- CreateTable +CREATE TABLE "users" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "preferredLanguage" "PreferredLanguage" NOT NULL, + "streakCount" INTEGER NOT NULL DEFAULT 0, + "moodId" TEXT, + "subscriptionId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "subscriptions" ( + "id" TEXT NOT NULL, + "plan" TEXT NOT NULL, + + CONSTRAINT "subscriptions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "moods" ( + "id" TEXT NOT NULL, + "type" TEXT NOT NULL, + + CONSTRAINT "moods_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "chats" ( + "id" TEXT NOT NULL, + "role" "ChatRole" NOT NULL, + "content" TEXT NOT NULL, + "mode" "ChatMode" NOT NULL, + "preferredLanguage" "PreferredLanguage" NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "userId" TEXT, + + CONSTRAINT "chats_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "conversation_topics" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "saved" BOOLEAN NOT NULL DEFAULT false, + "chatId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "moodId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "conversation_topics_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "notifications" ( + "id" TEXT NOT NULL, + "conversationTopicId" TEXT NOT NULL, + "content" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "notifications_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_subscriptionId_key" ON "users"("subscriptionId"); + +-- CreateIndex +CREATE INDEX "users_preferredLanguage_idx" ON "users"("preferredLanguage"); + +-- CreateIndex +CREATE INDEX "users_moodId_idx" ON "users"("moodId"); + +-- CreateIndex +CREATE INDEX "subscriptions_plan_idx" ON "subscriptions"("plan"); + +-- CreateIndex +CREATE UNIQUE INDEX "moods_type_key" ON "moods"("type"); + +-- CreateIndex +CREATE INDEX "chats_preferredLanguage_idx" ON "chats"("preferredLanguage"); + +-- CreateIndex +CREATE INDEX "chats_mode_idx" ON "chats"("mode"); + +-- CreateIndex +CREATE INDEX "conversation_topics_chatId_idx" ON "conversation_topics"("chatId"); + +-- CreateIndex +CREATE INDEX "conversation_topics_userId_idx" ON "conversation_topics"("userId"); + +-- CreateIndex +CREATE INDEX "conversation_topics_moodId_idx" ON "conversation_topics"("moodId"); + +-- CreateIndex +CREATE INDEX "conversation_topics_saved_idx" ON "conversation_topics"("saved"); + +-- CreateIndex +CREATE INDEX "notifications_conversationTopicId_idx" ON "notifications"("conversationTopicId"); + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_moodId_fkey" FOREIGN KEY ("moodId") REFERENCES "moods"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "users" ADD CONSTRAINT "users_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "chats" ADD CONSTRAINT "chats_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "conversation_topics" ADD CONSTRAINT "conversation_topics_chatId_fkey" FOREIGN KEY ("chatId") REFERENCES "chats"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "conversation_topics" ADD CONSTRAINT "conversation_topics_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "conversation_topics" ADD CONSTRAINT "conversation_topics_moodId_fkey" FOREIGN KEY ("moodId") REFERENCES "moods"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "notifications" ADD CONSTRAINT "notifications_conversationTopicId_fkey" FOREIGN KEY ("conversationTopicId") REFERENCES "conversation_topics"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..1af22ce --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..11b813f --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,121 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") +} + +enum PreferredLanguage { + en // English + ar // Arabic + ms // Malay/Indonesian + tr // Turkish + fr // French + ur // Urdu (optional) +} + +model User { + id String @id @default(uuid()) + email String @unique + preferredLanguage PreferredLanguage + streakCount Int @default(0) + moodId String? + mood Mood? @relation(fields: [moodId], references: [id]) + subscriptionId String? @unique + subscription Subscription? @relation(fields: [subscriptionId], references: [id]) + chats Chat[] + conversationTopics ConversationTopic[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([preferredLanguage]) + @@index([moodId]) + @@map("users") +} + +model Subscription { + id String @id @default(uuid()) + user User? + plan String + + @@index([plan]) + @@map("subscriptions") +} + +model Mood { + id String @id @default(uuid()) + type String + users User[] + conversationTopics ConversationTopic[] + + @@unique([type]) + @@map("moods") +} + +model Chat { + id String @id @default(uuid()) + role ChatRole + content String + mode ChatMode + preferredLanguage PreferredLanguage + conversationTopics ConversationTopic[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + User User? @relation(fields: [userId], references: [id]) + userId String? + + @@index([preferredLanguage]) + @@index([mode]) + @@map("chats") +} + +model ConversationTopic { + id String @id @default(uuid()) + title String + saved Boolean @default(false) + chatId String + chat Chat @relation(fields: [chatId], references: [id]) + userId String + user User @relation(fields: [userId], references: [id]) + moodId String? + mood Mood? @relation(fields: [moodId], references: [id]) + notifications Notification[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([chatId]) + @@index([userId]) + @@index([moodId]) + @@index([saved]) + @@map("conversation_topics") +} + +model Notification { + id String @id @default(uuid()) + conversationTopicId String + conversationTopic ConversationTopic @relation(fields: [conversationTopicId], references: [id]) + content String + createdAt DateTime @default(now()) + + @@index([conversationTopicId]) + @@map("notifications") +} + +enum ChatRole { + user + AI +} + +enum ChatMode { + public + private +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..3a8bcd7 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,71 @@ +import { PrismaClient } from '@prisma/client'; +const prisma = new PrismaClient(); + +async function main() { + // Seed moods + const moods = [ + { type: 'anxious' }, + { type: 'tempted' }, + { type: 'grateful' }, + { type: 'happy' }, + { type: 'sad' }, + { type: 'angry' }, + { type: 'calm' }, + ]; + for (const mood of moods) { + await prisma.mood.upsert({ + where: { type: mood.type }, + update: {}, + create: mood, + }); + } + + // Seed users (example users for each language) + const users = [ + { email: 'user.en@example.com', preferredLanguage: 'en', streakCount: 0 }, + { email: 'user.ar@example.com', preferredLanguage: 'ar', streakCount: 0 }, + { email: 'user.ms@example.com', preferredLanguage: 'ms', streakCount: 0 }, + { email: 'user.tr@example.com', preferredLanguage: 'tr', streakCount: 0 }, + { email: 'user.fr@example.com', preferredLanguage: 'fr', streakCount: 0 }, + { email: 'user.ur@example.com', preferredLanguage: 'ur', streakCount: 0 }, + ]; + for (const user of users) { + await prisma.user.upsert({ + where: { email: user.email }, + update: {}, + create: user, + }); + } + + // Seed a sample chat and conversation topic for each user + const allUsers = await prisma.user.findMany(); + for (const user of allUsers) { + const chat = await prisma.chat.create({ + data: { + ownerId: user.id, + content: `Welcome chat for ${user.preferredLanguage}`, + mode: 'default', + preferredLanguage: user.preferredLanguage, + }, + }); + const mood = await prisma.mood.findFirst(); + await prisma.conversationTopic.create({ + data: { + title: 'First Topic', + saved: false, + chatId: chat.id, + userId: user.id, + moodId: mood?.id, + }, + }); + } +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); \ No newline at end of file diff --git a/scripts/create-project.sh b/scripts/create-project.sh index b653d2f..8d136f7 100644 --- a/scripts/create-project.sh +++ b/scripts/create-project.sh @@ -1,119 +1,119 @@ -#!/bin/bash - -# Function to validate input -validate_input() { - local input=$1 - if [[ -z "$input" ]]; then - echo "Error: Input cannot be empty." - return 1 - fi - # Check if input contains only alphanumeric characters and underscores - if [[ ! "$input" =~ ^[a-zA-Z0-9_-]+$ ]]; then - echo "Error: Invalid input. Use only alphanumeric characters, underscores, and hyphens." - return 1 - fi - return 0 -} - -# Function to replace text in files -replace_in_file() { - local file=$1 - local old_project_name=$2 - local new_project_name=$3 - local old_scheme=$4 - local new_scheme=$5 - - # Use sed to perform replacements - sed -i "" \ - -e "s/$old_project_name/$new_project_name/g" \ - -e "s/$old_scheme/$new_scheme/g" \ - "$file" -} - -# Main script -clear -echo "React Native Expo Project Cloner" -echo "--------------------------------" - -# Get source project directory with fallback -while true; do - read -p "Enter the full path to the source project directory (press Enter to use parent directory): " source_dir - - # If no input, use parent directory - if [[ -z "$source_dir" ]]; then - source_dir=$(dirname "$(pwd)") - fi - - # Validate directory exists and contains an Expo project - if [[ -d "$source_dir" ]] && [[ -f "$source_dir/app.json" ]]; then - break - else - echo "Error: Invalid directory. Please ensure it's a valid Expo project directory." - fi -done - -# Get project name -while true; do - read -p "Enter new project name: " new_project_name - if validate_input "$new_project_name"; then - break - fi -done - -# Get scheme -while true; do - read -p "Enter new project scheme (e.g., com.example.projectname): " new_scheme - if validate_input "$(echo "$new_scheme" | tr '.' '_')"; then - break - fi -done - -# Determine parent directory -parent_dir=$(dirname "$source_dir") -destination_dir="$parent_dir/$new_project_name" - -# Check if destination directory already exists -if [[ -d "$destination_dir" ]]; then - echo "Error: Destination directory $destination_dir already exists." - exit 1 -fi - -# Create destination directory -mkdir -p "$destination_dir" - -# Copy files and folders, excluding node_modules and .idea -echo "Copying project files..." -rsync -av \ - --exclude "node_modules/" \ - --exclude ".idea/" \ - --exclude ".git/" \ - "$source_dir/" "$destination_dir/" - -# Files to modify (add more as needed) -config_files=( - "$destination_dir/app.json" - "$destination_dir/package.json" - "$destination_dir/app.config.js" - "$destination_dir/babel.config.js" -) - -# Get original project name and scheme from source project -original_project_name=$(basename "$source_dir") -original_scheme=$(grep -o "com\.[^\"']*" "$source_dir/app.json" | head -1) - -# Replace project name and scheme in config files -for file in "${config_files[@]}"; do - if [[ -f "$file" ]]; then - replace_in_file "$file" "$original_project_name" "$new_project_name" "$original_scheme" "$new_scheme" - fi -done - -# Reinitialize git -cd "$destination_dir" -rm -rf .git -git init -git add . -git commit -m "Initial commit of $new_project_name" - -echo "Project cloned successfully!" -echo "New project created at: $destination_dir" +#!/bin/bash + +# Function to validate input +validate_input() { + local input=$1 + if [[ -z "$input" ]]; then + echo "Error: Input cannot be empty." + return 1 + fi + # Check if input contains only alphanumeric characters and underscores + if [[ ! "$input" =~ ^[a-zA-Z0-9_-]+$ ]]; then + echo "Error: Invalid input. Use only alphanumeric characters, underscores, and hyphens." + return 1 + fi + return 0 +} + +# Function to replace text in files +replace_in_file() { + local file=$1 + local old_project_name=$2 + local new_project_name=$3 + local old_scheme=$4 + local new_scheme=$5 + + # Use sed to perform replacements + sed -i "" \ + -e "s/$old_project_name/$new_project_name/g" \ + -e "s/$old_scheme/$new_scheme/g" \ + "$file" +} + +# Main script +clear +echo "React Native Expo Project Cloner" +echo "--------------------------------" + +# Get source project directory with fallback +while true; do + read -p "Enter the full path to the source project directory (press Enter to use parent directory): " source_dir + + # If no input, use parent directory + if [[ -z "$source_dir" ]]; then + source_dir=$(dirname "$(pwd)") + fi + + # Validate directory exists and contains an Expo project + if [[ -d "$source_dir" ]] && [[ -f "$source_dir/app.json" ]]; then + break + else + echo "Error: Invalid directory. Please ensure it's a valid Expo project directory." + fi +done + +# Get project name +while true; do + read -p "Enter new project name: " new_project_name + if validate_input "$new_project_name"; then + break + fi +done + +# Get scheme +while true; do + read -p "Enter new project scheme (e.g., com.example.projectname): " new_scheme + if validate_input "$(echo "$new_scheme" | tr '.' '_')"; then + break + fi +done + +# Determine parent directory +parent_dir=$(dirname "$source_dir") +destination_dir="$parent_dir/$new_project_name" + +# Check if destination directory already exists +if [[ -d "$destination_dir" ]]; then + echo "Error: Destination directory $destination_dir already exists." + exit 1 +fi + +# Create destination directory +mkdir -p "$destination_dir" + +# Copy files and folders, excluding node_modules and .idea +echo "Copying project files..." +rsync -av \ + --exclude "node_modules/" \ + --exclude ".idea/" \ + --exclude ".git/" \ + "$source_dir/" "$destination_dir/" + +# Files to modify (add more as needed) +config_files=( + "$destination_dir/app.json" + "$destination_dir/package.json" + "$destination_dir/app.config.js" + "$destination_dir/babel.config.js" +) + +# Get original project name and scheme from source project +original_project_name=$(basename "$source_dir") +original_scheme=$(grep -o "com\.[^\"']*" "$source_dir/app.json" | head -1) + +# Replace project name and scheme in config files +for file in "${config_files[@]}"; do + if [[ -f "$file" ]]; then + replace_in_file "$file" "$original_project_name" "$new_project_name" "$original_scheme" "$new_scheme" + fi +done + +# Reinitialize git +cd "$destination_dir" +rm -rf .git +git init +git add . +git commit -m "Initial commit of $new_project_name" + +echo "Project cloned successfully!" +echo "New project created at: $destination_dir" diff --git a/scripts/reset-project.js b/scripts/reset-project.js index f82de98..4e7c5c0 100755 --- a/scripts/reset-project.js +++ b/scripts/reset-project.js @@ -1,84 +1,36 @@ -#!/usr/bin/env node - -/** - * This script is used to reset the project to a blank state. - * It moves the /app, /components, /hooks, /scripts, and /constants directories to /app-example and creates a new /app directory with an index.tsx and _layout.tsx file. - * You can remove the `reset-project` script from package.json and safely delete this file after running it. - */ - -const fs = require('fs'); -const path = require('path'); - -const root = process.cwd(); -const oldDirs = ['app', 'components', 'hooks', 'constants', 'scripts']; -const newDir = 'app-example'; -const newAppDir = 'app'; -const newDirPath = path.join(root, newDir); - -const indexContent = `import { Text, View } from "react-native"; - -export default function Index() { - return ( - - Edit app/index.tsx to edit this screen. - - ); -} -`; - -const layoutContent = `import { Stack } from "expo-router"; - -export default function RootLayout() { - return ; -} -`; - -const moveDirectories = async () => { - try { - // Create the app-example directory - await fs.promises.mkdir(newDirPath, { recursive: true }); - console.log(`šŸ“ /${newDir} directory created.`); - - // Move old directories to new app-example directory - for (const dir of oldDirs) { - const oldDirPath = path.join(root, dir); - const newDirPath = path.join(root, newDir, dir); - if (fs.existsSync(oldDirPath)) { - await fs.promises.rename(oldDirPath, newDirPath); - console.log(`āž”ļø /${dir} moved to /${newDir}/${dir}.`); - } else { - console.log(`āž”ļø /${dir} does not exist, skipping.`); - } - } - - // Create new /app directory - const newAppDirPath = path.join(root, newAppDir); - await fs.promises.mkdir(newAppDirPath, { recursive: true }); - console.log('\nšŸ“ New /app directory created.'); - - // Create index.tsx - const indexPath = path.join(newAppDirPath, 'index.tsx'); - await fs.promises.writeFile(indexPath, indexContent); - console.log('šŸ“„ app/index.tsx created.'); - - // Create _layout.tsx - const layoutPath = path.join(newAppDirPath, '_layout.tsx'); - await fs.promises.writeFile(layoutPath, layoutContent); - console.log('šŸ“„ app/_layout.tsx created.'); - - console.log('\nāœ… Project reset complete. Next steps:'); - console.log( - "1. Run `npx expo start` to start a development server.\n2. Edit app/index.tsx to edit the main screen.\n3. Delete the /app-example directory when you're done referencing it." - ); - } catch (error) { - console.error(`Error during script execution: ${error}`); - } +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); + +#!/usr/bin/env node + +/** + * This script is used to reset the project to a blank state. + * It moves the /app, /components, /hooks, /scripts, and /constants directories to /app-example and creates a new /app directory with an index.tsx and _layout.tsx file. + * You can remove the `reset-project` script from package.json and safely delete this file after running it. + */ + +const fs = require('fs'); +const path = require('path'); + +const root = process.cwd(); +const oldDirs = ['app', 'components', 'hooks', 'constants', 'scripts']; +const newDir = 'app-example'; +const newAppDir = 'app'; +const newDirPath = path.join(root, newDir); + +const indexContent = `import { Text, View } from "react-native"; + +export default function Index() { + return ( + + Edit app/index.tsx to edit this screen. + + ); }; - -moveDirectories(); diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx index 103239e..cf582b2 100644 --- a/src/app/(tabs)/_layout.tsx +++ b/src/app/(tabs)/_layout.tsx @@ -1,44 +1,44 @@ -import { Tabs } from 'expo-router'; -import React from 'react'; -import { Platform } from 'react-native'; - -import { useTheme } from '@react-navigation/native'; -import { Ionicons } from '@expo/vector-icons'; - -export default function TabLayout() { - const { colors } = useTheme(); - - return ( - - ( - - ), - }} - /> - ( - - ), - }} - /> - - ); -} +import { Tabs } from 'expo-router'; +import React from 'react'; +import { Platform } from 'react-native'; + +import { useTheme } from '@react-navigation/native'; +import { Ionicons } from '@expo/vector-icons'; + +export default function TabLayout() { + const { colors } = useTheme(); + + return ( + + ( + + ), + }} + /> + ( + + ), + }} + /> + + ); +} diff --git a/src/app/(tabs)/explore.tsx b/src/app/(tabs)/explore.tsx index 80141d9..f0b0ed9 100644 --- a/src/app/(tabs)/explore.tsx +++ b/src/app/(tabs)/explore.tsx @@ -1,14 +1,14 @@ -import { Box } from '@/components/common/Layout/Box'; -import { Text } from '@/components/common/Text/Text'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -export default function TabTwoScreen() { - const { t } = useTranslation(); - - return ( - - {t('explore.title')} - - ); -} +import { Box } from '@/components/common/Layout/Box'; +import { Text } from '@/components/common/Text/Text'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +export default function TabTwoScreen() { + const { t } = useTranslation(); + + return ( + + {t('explore.title')} + + ); +} diff --git a/src/app/(tabs)/index.tsx b/src/app/(tabs)/index.tsx index 8092beb..b2d3a59 100644 --- a/src/app/(tabs)/index.tsx +++ b/src/app/(tabs)/index.tsx @@ -1,100 +1,100 @@ -import { Box } from '@/components/common/Layout/Box'; -import { Text } from '@/components/common/Text/Text'; -import React, { useState } from 'react'; -import TextField from '@/components/common/TextField/TextField'; -import Button from '@/components/common/Button'; -import Spinner from '@/components/common/Button/Spinner'; -import { Ionicons } from '@expo/vector-icons'; -import { useTranslation } from 'react-i18next'; - -export default function HomeScreen() { - const [value, setValue] = useState('Value'); - const { t } = useTranslation(); - - return ( - - - {t('home.title')}{' '} - - +