diff --git a/README.md b/README.md index 02e882a..6f1e94b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# TaskNexus - Task Manager React Native App πŸ“± +# TaskNexus - A Task Manager React Native App πŸ“± **Time to get organized!** TaskNexus is a modern, cross-platform task management app built with React Native, Expo, and TypeScript. It allows users to efficiently manage their daily tasks with features like task addition, completion, deletion, drag-and-drop reordering, and interactive statistics visualization. @@ -56,7 +56,8 @@ - [iOS](#ios) - [Android](#android) - [Web](#web) - - [Screen Recordings (GIFs)](#screen-recordings-gifs) + - [Screen Recordings (GIF)](#screen-recordings-gif) + - [Realtime Synchronization Demo](#realtime-synchronization-demo) - [Features](#features) - [Technical Stack & Libraries](#technical-stack--libraries) - [Core Dependencies](#core-dependencies) @@ -164,7 +165,7 @@ I hope its name and branding convey a sense of connectivity and organization, wi > The above GIFs and images may not fully represent the app's current state, as they were recorded during development. The app has since been updated with new features and improvements. Please clone the repository and run the app to see the latest version in action! > [!NOTE] -> My apologies for the low quality of the GIFs - they were converted from high-resolution videos to reduce file size, which resulted in great losses of quality. The app looks much better in real life, especially on mobile devices! +> My apologies for the low quality of the GIFs - they were converted from high-resolution videos to reduce file size, which resulted in huge losses of quality. The app looks much better in real life, especially on mobile devices! ## Features @@ -425,7 +426,7 @@ Task-Manager-ReactNative β”‚ β”œβ”€β”€ TaskItemStyles.ts # Custom styles for individual task items β”‚ β”œβ”€β”€ NotFoundStyles.ts # Custom styles for the Not Found screen β”‚ └── CustomTabStyles.ts # Custom styles for the tab bar and related UI components -β”œβ”€β”€ ruby # Ruby backend for backup task management (optional) +β”œβ”€β”€ ruby # Ruby backend for backup task management API β”‚ β”œβ”€β”€ Gemfile # Ruby dependencies for the backup Rails backend β”‚ β”œβ”€β”€ app.rb # Main Ruby API application file β”‚ β”œβ”€β”€ config.ru # Rack configuration file for the Ruby app @@ -552,21 +553,22 @@ This is useful during development to quickly see the results of changes without ## License -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details. +This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for more details. > [!CAUTION] > This project is for educational purposes only and is not intended for production use. It is a personal project to demonstrate my skills in React Native, Expo, and TypeScript. Please be sure to credit the original author if you use any part of this code in your own projects, regardless of use case. ## Contact -For any questions, feedback, or suggestions, please contact: +For any questions, feedback, or suggestions, please contact the repository owner: - **Name:** [Son Nguyen](https://sonnguyenhoang.com) - **Email:** [hoangson091104@gmail.com](mailto:hoangson091104@gmail.com) - **GitHub:** [@hoangsonww](https://github.com/hoangsonww) +- **LinkedIn:** [Son Nguyen](https://www.linkedin.com/in/hoangsonw/) --- -**Thank you for checking out TaskNexus - Task Manager React Native App! πŸš€πŸ“²** +**Thank you for checking out TaskNexus - A Task Manager React Native App! πŸš€πŸ“²**. Please feel free to contribute, report issues, or suggest improvements. Your feedback is always welcome! **[πŸ” Back to top](#tasknexus---task-manager-react-native-app-)** diff --git a/bootstrap-tasknexus-ruby.sh b/bootstrap-tasknexus-ruby.sh deleted file mode 100644 index 460dfac..0000000 --- a/bootstrap-tasknexus-ruby.sh +++ /dev/null @@ -1,233 +0,0 @@ -#!/usr/bin/env bash -set -e - -# 1) Clean and create ruby/ structure -rm -rf ruby -mkdir -p ruby/{app/models,app/controllers,config,initializers,public} - -# 2) Gemfile -cat > ruby/Gemfile << 'EOF' -source 'https://rubygems.org' -ruby '3.1.0' - -gem 'sinatra', require: 'sinatra/base' -gem 'mongoid', '~> 7.3' -gem 'jwt' -gem 'bcrypt' -gem 'rack-cors' -gem 'json' -EOF - -# 3) config/mongoid.yml -cat > ruby/config/mongoid.yml << 'EOF' -development: - clients: - default: - uri: <%= ENV['MONGODB_URI'] || 'mongodb://127.0.0.1:27017/tasknexus_dev' %> -production: - clients: - default: - uri: <%= ENV['MONGODB_URI'] %> -EOF - -# 4) initializers/jwt.rb -cat > ruby/initializers/jwt.rb << 'EOF' -JWT_SECRET = ENV['JWT_SECRET'] || 'super$ecretKey' # change in prod! -def issue_token(payload) - JWT.encode(payload, JWT_SECRET, 'HS256') -end -def decode_token(token) - JWT.decode(token, JWT_SECRET, true, algorithm: 'HS256')[0] -rescue - nil -end -EOF - -# 5) app/models/user.rb -cat > ruby/app/models/user.rb << 'EOF' -require 'mongoid' -require 'bcrypt' - -class User - include Mongoid::Document - include Mongoid::Timestamps - field :username, type: String - field :password_hash, type: String - - validates :username, presence: true, uniqueness: true - validates :password_hash, presence: true - - def password=(raw) - self.password_hash = BCrypt::Password.create(raw) - end - - def authenticate(raw) - BCrypt::Password.new(password_hash) == raw - end -end -EOF - -# 6) app/models/task.rb -cat > ruby/app/models/task.rb << 'EOF' -require 'mongoid' - -class Task - include Mongoid::Document - include Mongoid::Timestamps - field :title, type: String - field :completed, type: Mongoid::Boolean, default: false - field :position, type: Integer - - belongs_to :user - - validates :title, presence: true -end -EOF - -# 7) app/controllers/auth_controller.rb -cat > ruby/app/controllers/auth_controller.rb << 'EOF' -require 'sinatra/base' -require 'json' -require_relative '../models/user' -require_relative '../../initializers/jwt' - -class AuthController < Sinatra::Base - post '/register' do - data = JSON.parse(request.body.read) - user = User.new(username: data['username']) - user.password = data['password'] - halt 400, { error: user.errors.full_messages }.to_json unless user.save - token = issue_token({ user_id: user.id.to_s }) - { token: token }.to_json - end - - post '/login' do - data = JSON.parse(request.body.read) - user = User.find_by(username: data['username']) rescue nil - halt 401, { error: 'Invalid credentials' }.to_json unless user&.authenticate(data['password']) - { token: issue_token({ user_id: user.id.to_s }) }.to_json - end -end -EOF - -# 8) app/controllers/tasks_controller.rb -cat > ruby/app/controllers/tasks_controller.rb << 'EOF' -require 'sinatra/base' -require 'json' -require_relative '../models/task' -require_relative '../models/user' -require_relative '../../initializers/jwt' - -class TasksController < Sinatra::Base - before do - pass if request.path_info =~ %r{^/tasks/public} - auth_header = request.env['HTTP_AUTHORIZATION'] || '' - token = auth_header.split(' ').last - payload = decode_token(token) - halt 401, { error: 'Unauthorized' }.to_json unless payload - @current_user = User.find(payload['user_id']) rescue nil - halt 401, { error: 'Unauthorized' }.to_json unless @current_user - end - - # public endpoint for stats - get '/tasks/public/stats' do - total = Task.count - completed = Task.where(completed: true).count - { total: total, completed: completed, incomplete: total - completed }.to_json - end - - # CRUD - get '/tasks' do - tasks = @current_user.tasks.order_by(:position.asc) - tasks.to_json - end - - post '/tasks' do - data = JSON.parse(request.body.read) - task = @current_user.tasks.new(title: data['title'], position: data['position']) - halt 400, { error: task.errors.full_messages }.to_json unless task.save - status 201 - task.to_json - end - - put '/tasks/:id' do - task = @current_user.tasks.find(params['id']) - data = JSON.parse(request.body.read) - task.update(title: data['title'], completed: data['completed'], position: data['position']) - task.to_json - end - - delete '/tasks/:id' do - task = @current_user.tasks.find(params['id']) - task.destroy - status 204 - end -end -EOF - -# 9) app.rb (main entry) -cat > ruby/app.rb << 'EOF' -require 'sinatra/base' -require 'mongoid' -require 'rack/cors' -require_relative './config/mongoid' -require_relative './initializers/jwt' -require_relative 'app/controllers/auth_controller' -require_relative 'app/controllers/tasks_controller' - -Mongoid.load!('config/mongoid.yml', ENV['RACK_ENV'] || :development) - -class TaskNexusApp < Sinatra::Base - use Rack::Cors do - allow do - origins '*' - resource '*', headers: :any, methods: [:get, :post, :put, :delete, :options] - end - end - - use AuthController - use TasksController - - get '/' do - 'TaskNexus API is running!' - end -end - -run TaskNexusApp -EOF - -# 10) config.ru for Rack -cat > ruby/config.ru << 'EOF' -require './app' -run TaskNexusApp -EOF - -# 11) Dockerfile -cat > ruby/Dockerfile << 'EOF' -FROM ruby:3.1 - -WORKDIR /usr/src/app -COPY Gemfile* ./ -RUN bundle install - -COPY . . -EXPOSE 4567 -CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "4567"] -EOF - -# 12) .env.example -cat > ruby/.env.example << 'EOF' -MONGODB_URI=mongodb://mongo:27017/tasknexus_prod -JWT_SECRET=replace_with_strong_secret -EOF - -echo "βœ… Ruby backend scaffold complete under ruby/" -echo "β€’ To run locally without Docker:" -echo " cd ruby" -echo " export MONGODB_URI=mongodb://127.0.0.1:27017/tasknexus_dev" -echo " bundle install" -echo " bundle exec rackup" -echo "β€’ To run via Docker:" -echo " cd ruby" -echo " docker build -t hoangsonww/tasknexus-api ." -echo " docker run -e MONGODB_URI=mongodb://host.docker.internal:27017/tasknexus_dev -e JWT_SECRET=you_choose --rm -p 4567:4567 hoangsonww/tasknexus-api" diff --git a/img/sync.gif b/img/sync.gif index b64fd21..1053d14 100644 Binary files a/img/sync.gif and b/img/sync.gif differ diff --git a/jest.setup.js b/jest.setup.js index 9630a06..21eb245 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,4 +1,3 @@ import "react-native-gesture-handler/jestSetup"; -// Silence the warning: β€œAn update to Animated... was not wrapped in act(...)” jest.mock("react-native/Libraries/Animated/NativeAnimatedHelper"); diff --git a/package.json b/package.json index 53e94c8..51ac4b7 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,18 @@ "bugs": { "url": "https://github.com/hoangsonww/Task-Manager-ReactNative/issues" }, + "keywords": [ + "react-native", + "expo", + "task-manager", + "productivity", + "mobile-app", + "todo-list", + "task-tracker", + "task-management", + "cross-platform", + "react-navigation" + ], "scripts": { "start": "expo start", "reset-project": "node ./scripts/reset-project.js", @@ -77,5 +89,5 @@ "react-test-renderer": "^18.3.1", "typescript": "^5.3.3" }, - "private": true + "private": false }