removed todo_app
This commit is contained in:
parent
1eee5aa334
commit
9e8c79c046
25 changed files with 0 additions and 21516 deletions
29
todo_app/.gitignore
vendored
29
todo_app/.gitignore
vendored
|
@ -1,29 +0,0 @@
|
|||
node_modules/
|
||||
.expo/
|
||||
.idea/
|
||||
dist/
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
|
||||
# The following patterns were generated by expo-cli
|
||||
|
||||
expo-env.d.ts
|
||||
|
||||
|
||||
api/index_.js
|
||||
.env
|
||||
README.md
|
||||
scripts/*
|
||||
hooks/*
|
||||
constants/*
|
||||
components/*
|
|
@ -1,173 +0,0 @@
|
|||
const express = require("express");
|
||||
const bodyParser = require("body-parser");
|
||||
const mongoose = require("mongoose");
|
||||
const crypto = require("crypto");
|
||||
const User = require("./models/user");
|
||||
const Todo = require("./models/todo");
|
||||
const app = express();
|
||||
const port = 3030;
|
||||
const cors = require("cors");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const moment = require("moment")
|
||||
|
||||
app.use(cors());
|
||||
app.use(bodyParser.urlencoded({extended: false}));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
//connect with database
|
||||
mongoose.connect(process.env.MONGO_URL).then(() => {
|
||||
console.log("Connectd to mongodb");
|
||||
}).catch((error) => {
|
||||
console.log("Error to connect with mongodb", error);
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log("Server is running on port 3030")
|
||||
});
|
||||
|
||||
//login & register
|
||||
const generateSecretKey = () => {
|
||||
const secretKey = crypto.randomBytes(32).toString("hex")
|
||||
return secretKey;
|
||||
};
|
||||
const secretKey = generateSecretKey();
|
||||
|
||||
app.post("/register", async(req, res) => {
|
||||
try {
|
||||
const {name, email, password} = req.body;
|
||||
//check existing user
|
||||
const existingUser = await User.findOne({email});
|
||||
if(existingUser){
|
||||
console.log("Email already registered");
|
||||
}
|
||||
|
||||
const newUser = new User({name, email, password});
|
||||
await newUser.save();
|
||||
res.status(202).json({message: "Registration succeeded!"});
|
||||
|
||||
} catch (error) {
|
||||
console.log("Fail to register an account!", error);
|
||||
res.status(500).json({message: "Registration failed!"});
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/login", async(req, res) => {
|
||||
try {
|
||||
const {email, password} = req.body;
|
||||
const user = await User.findOne({email});
|
||||
if(!user){
|
||||
return res.status(401).json({message: "Wrong credentials: invalid username or passowrd"});
|
||||
}
|
||||
if(user.password !== password){
|
||||
return res.status(401).json({message: "Wrong credentials: invalid username or passowrd"});
|
||||
}
|
||||
const token = jwt.sign({userId:user._id,}, secretKey);
|
||||
res.status(200).json({token});
|
||||
|
||||
} catch (error) {
|
||||
console.log("Fail to login!", error);
|
||||
res.status(500).json({message: "Login failed!"});
|
||||
}
|
||||
});
|
||||
//todo list
|
||||
app.post("/todos/:userId", async(req, res) => {
|
||||
try {
|
||||
const userId = req.params.userId;
|
||||
const {title, category} = req.body;
|
||||
const newTodo = new Todo({
|
||||
title,
|
||||
category,
|
||||
dueDate: moment().format("MMM Do YY")
|
||||
});
|
||||
await newTodo.save();
|
||||
|
||||
//add todo id
|
||||
const user = await User.findById(userId);
|
||||
if(!user){
|
||||
res.status(404).json({message: "User not found!"});
|
||||
}
|
||||
user?.todos.push(newTodo._id);
|
||||
await user.save();
|
||||
|
||||
res.status(200).json({message: "A list is added successfully!", todo:newTodo});
|
||||
} catch (error) {
|
||||
res.status(500).json({message: "Fail to add a list!"});
|
||||
console.log(error)
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/users/:userId/todos", async(req, res) => {
|
||||
try {
|
||||
const userId = req.params.userId;
|
||||
const user = await User.findById(userId).populate("todos");
|
||||
if(!user){
|
||||
return res.status(404).json({message: "user not found"});
|
||||
}
|
||||
res.status(200).json({todos:user.todos});
|
||||
} catch (error) {
|
||||
res.status(500).json({message: "Error, something went wrong!"});
|
||||
}
|
||||
});
|
||||
|
||||
app.patch("/todos/:todoId/complete", async(req, res) => {
|
||||
try {
|
||||
const todoId = req.params.todoId;
|
||||
const updatedTodo = await Todo.findByIdAndUpdate(todoId, {
|
||||
status:"completed"
|
||||
},
|
||||
{new:true}
|
||||
);
|
||||
if(!updatedTodo){
|
||||
return res.status(404).json({error:"Todo not found"});
|
||||
}
|
||||
res.status(200).json({message: "Mark as completed!", todo:updatedTodo});
|
||||
} catch (error) {
|
||||
res.status(500).json({message: "Error, something went wrong!"});
|
||||
}
|
||||
});
|
||||
//calendar
|
||||
app.get("/users/:userId/todos/completed/:date", async(req, res) => {
|
||||
try {
|
||||
const date = req.params.date;
|
||||
const userId = req.params.userId;
|
||||
const user = await User.findById(userId);
|
||||
const userToDos = await user.populate('todos');
|
||||
const toDos = userToDos.todos;
|
||||
|
||||
const completedTodos = toDos.filter(item => {
|
||||
return item.status == "completed" && // status filter
|
||||
item.createdAt.getTime() >= new Date(`${date}T00:00:00.000Z`) && // gte filter
|
||||
item.createdAt.getTime() <= new Date(`${date}T23:59:59.999Z`) // lt filter
|
||||
});
|
||||
|
||||
|
||||
res.status(200).json({completedTodos})
|
||||
} catch (error) {
|
||||
res.status(500).json({message: "Error, something went wrong!"});
|
||||
console.log(error)
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/users/:userId/todos/count", async(req, res) => {
|
||||
try {
|
||||
const userId = req.params.userId;
|
||||
const user = await User.findById(userId);
|
||||
const userToDos = await user.populate('todos');
|
||||
const toDos = userToDos.todos;
|
||||
const completedTodos = toDos.filter(item => {
|
||||
return item.status == "completed"
|
||||
});
|
||||
const pendingTodos = toDos.filter(item => {
|
||||
return item.status == "pending"
|
||||
});
|
||||
|
||||
const totalCompletedTodos = completedTodos.length;
|
||||
const totalPendingTodos = pendingTodos.length;
|
||||
|
||||
res.status(200).json({totalCompletedTodos, totalPendingTodos});
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({message: "Error, something went wrong!"});
|
||||
console.log(error)
|
||||
}
|
||||
});
|
|
@ -1,28 +0,0 @@
|
|||
const mongoose = require("mongoose");
|
||||
|
||||
const todoSchema = new mongoose.Schema({
|
||||
title:{
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
status:{
|
||||
type: String,
|
||||
enum: ["pending", "completed"],
|
||||
default: "pending"
|
||||
},
|
||||
category:{
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
dueDate:{
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
createdAt:{
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
|
||||
const Todo= mongoose.model("Todo", todoSchema);
|
||||
module.exports = Todo
|
|
@ -1,30 +0,0 @@
|
|||
const mongoose = require("mongoose");
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
name:{
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
email:{
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
password:{
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
todos:[
|
||||
{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Todo",
|
||||
}
|
||||
],
|
||||
createdAt:{
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
|
||||
const User = mongoose.model("User", userSchema);
|
||||
module.exports = User
|
1388
todo_app/api/package-lock.json
generated
1388
todo_app/api/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"name": "api",
|
||||
"version": "1.0.0",
|
||||
"description": "server_side",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "nodemon index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mongodb": "^6.8.0",
|
||||
"mongoose": "^8.5.1",
|
||||
"nodemon": "^3.1.4"
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"expo": {
|
||||
"name": "todo_app",
|
||||
"slug": "todo_app",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "todo_app",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"splash": {
|
||||
"image": "./assets/images/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
"output": "static",
|
||||
"favicon": "./assets/images/logo.png"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router"
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import {Stack} from "expo-router";
|
||||
|
||||
export default function Layout(){
|
||||
return(
|
||||
<Stack>
|
||||
<Stack.Screen name="login" options={{headerShown: false}}/>
|
||||
<Stack.Screen name="register" options={{headerShown: false}}/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
import { StyleSheet, Text, View, SafeAreaView, KeyboardAvoidingView, Image, TextInput, Pressable } from 'react-native'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Fontisto } from '@expo/vector-icons';
|
||||
import { Entypo } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import axios from "axios";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
const login = () => {
|
||||
const router = useRouter();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
useEffect(() => {
|
||||
const checkLoginStatus = async() => {
|
||||
try {
|
||||
const token = await AsyncStorage.getItem("authToken");
|
||||
if(token){
|
||||
router.replace("/(tabs)/home")
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
};
|
||||
checkLoginStatus();
|
||||
}, [])
|
||||
|
||||
const handleLogin = () => {
|
||||
const user = {
|
||||
email: email,
|
||||
password: password,
|
||||
};
|
||||
axios.post("http://localhost:3030/login", user).then((res) => {
|
||||
const token = res.data.token;
|
||||
AsyncStorage.setItem("authToken", JSON.stringify(token));
|
||||
router.replace("/(tabs)/home")
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{flex:1, backgroundColor: "white", alignItems: "center", justifyContent:"center"}}>
|
||||
<View>
|
||||
<View style={{alignItems: "center"}}>
|
||||
<Image
|
||||
style={{width: 150, height: 150, resizeMode: "containe"}}
|
||||
source={{
|
||||
uri:"./assets/images/logo.png",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Text style={{fontSize: 24, fontWeight: 600, color: "#222831", marginTop: 20}}>Keep Tracking</Text>
|
||||
</View>
|
||||
<KeyboardAvoidingView>
|
||||
<View style={{alignItems: "center"}}>
|
||||
<Text style={{fontSize: 16, fontWeight: 600, color: "black", marginTop: 20}}>Welcome back!</Text>
|
||||
</View>
|
||||
<View style={{marginTop: 50}}>
|
||||
<View style={{flexDirection: "row", justifyContent: "center", alignItems: "center", gap:5, backgroundColor: "#EEEEEE", paddingHorizontal:10, borderRadius:5}}>
|
||||
<Fontisto name="email" size={16} color="#31363F" />
|
||||
<TextInput value={email} onChangeText={(text) => setEmail(text)} style={{color: "#31363F", width:250, outlineStyle: 'none', marginVertical: 10, fontSize:email ? 14 : 14}} placeholder='Enter your email'/>
|
||||
</View>
|
||||
<View style={{flexDirection: "row", justifyContent: "center", alignItems: "center", gap:5, backgroundColor: "#EEEEEE", paddingHorizontal:10, borderRadius:5, marginTop: 10}}>
|
||||
<Entypo name="key" size={16} color="#31363F" />
|
||||
<TextInput value={password} onChangeText={(text) => setPassword(text)} secureTextEntry={true} style={{color: "#31363F", width:250, outlineStyle: 'none', marginVertical: 10, fontSize:email ? 14 : 14}} placeholder='Enter your password'/>
|
||||
</View>
|
||||
<View style={{marginTop:10}}>
|
||||
<Text style={{color: "#61677A", fontSize: 12 }}>Forgot password?</Text>
|
||||
</View>
|
||||
<View style={{marginTop: 50}}/>
|
||||
<Pressable onPress={handleLogin} style={{backgroundColor: "#61677A", padding:10, borderRadius:5, alignItems: "center"}}>
|
||||
<Text style={{color: "#D8D9DA", fontSize: 16, fontWeight: 600}}>Login</Text>
|
||||
</Pressable>
|
||||
<Pressable onPress={() => router.replace("/register")} style={{marginTop: 10}}>
|
||||
<Text style={{fontSize: 12, textAlign:"center"}}>Don't have an account? <Text style={{fontWeight: 700}}>Register</Text></Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
export default login
|
||||
|
||||
const styles = StyleSheet.create({})
|
|
@ -1,83 +0,0 @@
|
|||
import { StyleSheet, Text, View, SafeAreaView, KeyboardAvoidingView, Image, TextInput, Pressable, Alert } from 'react-native'
|
||||
import React, { useState } from 'react'
|
||||
import { Fontisto } from '@expo/vector-icons';
|
||||
import { Entypo } from '@expo/vector-icons';
|
||||
import { AntDesign } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import axios from "axios";
|
||||
|
||||
const register = () => {
|
||||
const [name, setName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const router = useRouter();
|
||||
|
||||
const handleRegister = () => {
|
||||
const user = {
|
||||
name: name,
|
||||
email: email,
|
||||
password: password,
|
||||
}
|
||||
axios.post("http://localhost:3030/register", user).then((res) => {
|
||||
console.log(res);
|
||||
Alert.alert("Registration succeeded!");
|
||||
setEmail('');
|
||||
setPassword('');
|
||||
setName('');
|
||||
}).catch((error) => {
|
||||
Alert.alert("Registration failed!");
|
||||
console.log("error", error);
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{flex:1, backgroundColor: "white", alignItems: "center", justifyContent:"center"}}>
|
||||
<View>
|
||||
<View style={{alignItems: "center"}}>
|
||||
<Image
|
||||
style={{width: 150, height: 150, resizeMode: "containe"}}
|
||||
source={{
|
||||
uri:"./assets/images/logo.png",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Text style={{fontSize: 24, fontWeight: 600, color: "#222831", marginTop: 20}}>Keep Tracking</Text>
|
||||
</View>
|
||||
<KeyboardAvoidingView>
|
||||
<View style={{alignItems: "center"}}>
|
||||
<Text style={{fontSize: 16, fontWeight: 600, color: "black", marginTop: 20}}>Register</Text>
|
||||
</View>
|
||||
|
||||
<View style={{marginTop: 50}}>
|
||||
<View style={{flexDirection: "row", justifyContent: "center", alignItems: "center", gap:5, backgroundColor: "#EEEEEE", paddingHorizontal:10, borderRadius:5}}>
|
||||
<AntDesign name="user" size={16} color="#31363F" />
|
||||
<TextInput value={name} onChangeText={(text) => setName(text)} style={{color: "#31363F", width:250, outlineStyle: 'none', marginVertical: 10, fontSize:email ? 14 : 14}} placeholder='Username'/>
|
||||
</View>
|
||||
<View style={{flexDirection: "row", justifyContent: "center", alignItems: "center", gap:5, backgroundColor: "#EEEEEE", paddingHorizontal:10, borderRadius:5, marginTop: 10}}>
|
||||
<Fontisto name="email" size={16} color="#31363F" />
|
||||
<TextInput value={email} onChangeText={(text) => setEmail(text)} style={{color: "#31363F", width:250, outlineStyle: 'none', marginVertical: 10, fontSize:email ? 14 : 14}} placeholder='Enter your email'/>
|
||||
</View>
|
||||
|
||||
<View style={{flexDirection: "row", justifyContent: "center", alignItems: "center", gap:5, backgroundColor: "#EEEEEE", paddingHorizontal:10, borderRadius:5, marginTop: 10}}>
|
||||
<Entypo name="key" size={16} color="#31363F" />
|
||||
<TextInput value={password} onChangeText={(text) => setPassword(text)} secureTextEntry={true} style={{color: "#31363F", width:250, outlineStyle: 'none', marginVertical: 10, fontSize:email ? 14 : 14}} placeholder='Enter your password'/>
|
||||
</View>
|
||||
|
||||
<View style={{marginTop: 50}}/>
|
||||
|
||||
<Pressable onPress={handleRegister} style={{backgroundColor: "#61677A", padding:10, borderRadius:5, alignItems: "center"}}>
|
||||
<Text style={{color: "#D8D9DA", fontSize: 16, fontWeight: 600}}>Register</Text>
|
||||
</Pressable>
|
||||
|
||||
<Pressable onPress={() => router.replace("/login")} style={{marginTop: 10}}>
|
||||
<Text style={{fontSize: 12, textAlign:"center"}}>Already have an account? <Text style={{fontWeight: 700}}>Login</Text></Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
export default register
|
||||
|
||||
const styles = StyleSheet.create({})
|
|
@ -1,53 +0,0 @@
|
|||
import {Tabs} from "expo-router";
|
||||
import { FontAwesome } from '@expo/vector-icons';
|
||||
import { AntDesign } from '@expo/vector-icons';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
|
||||
export default function Layout(){
|
||||
return(
|
||||
<Tabs>
|
||||
<Tabs.Screen
|
||||
name="home"
|
||||
options={{
|
||||
tabBarLabel:"Home",
|
||||
tabBarLabelStyle:{color:"black"},
|
||||
headerShown: false,
|
||||
tabBarIcon:({focused}) =>
|
||||
focused? (
|
||||
<FontAwesome name="tasks" size={24} color="black" />
|
||||
) : (
|
||||
<FontAwesome name="tasks" size={24} color="#45474B" />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="calendar"
|
||||
options={{
|
||||
tabBarLabel:"calendar",
|
||||
tabBarLabelStyle:{color:"black"},
|
||||
headerShown: false,
|
||||
tabBarIcon:({focused}) =>
|
||||
focused? (
|
||||
<AntDesign name="calendar" size={24} color="black" />
|
||||
) : (
|
||||
<AntDesign name="calendar" size={24} color="#45474B" />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="profile"
|
||||
options={{
|
||||
tabBarLabel:"profile",
|
||||
tabBarLabelStyle:{color:"black"},
|
||||
headerShown: false,
|
||||
tabBarIcon:({focused}) =>
|
||||
focused? (
|
||||
<MaterialCommunityIcons name="account-details" size={24} color="black" />
|
||||
) : (
|
||||
<MaterialCommunityIcons name="account-details" size={24} color="#45474B" />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import {Stack} from "expo-router";
|
||||
|
||||
export default function Layout(){
|
||||
return(
|
||||
<Stack screenOptions={{headerShown:false}}>
|
||||
<Stack.Screen name="index"/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import {Calendar} from "react-native-calendars";
|
||||
import axios from "axios"
|
||||
import { Feather } from '@expo/vector-icons';
|
||||
import { Buffer } from 'buffer';
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
|
||||
const getToken = async () => {
|
||||
try {
|
||||
let token = await AsyncStorage.getItem('authToken'); // get the authToken stored in AsyncStorage from login.js
|
||||
let decodeToken = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||
let userId = decodeToken.userId;
|
||||
return userId;
|
||||
} catch (e) {
|
||||
// error reading value
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const index = () => {
|
||||
const today = moment().format("YYYY-MM-DD");
|
||||
const [selectedDate, setSelectedDate] = useState(today);
|
||||
const [todos, setTodos] = useState([]);
|
||||
|
||||
const fetchCompletedTodos = () => {
|
||||
try {
|
||||
getToken().then( async (resp) => {
|
||||
let userId = resp;
|
||||
|
||||
const res = await axios.get(`http://localhost:3030/users/${userId}/todos/completed/${selectedDate}`);
|
||||
const completedTodos = res.data.completedTodos || [];
|
||||
|
||||
setTodos(completedTodos);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.log("Error", error)
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
fetchCompletedTodos();
|
||||
|
||||
}, [selectedDate]);
|
||||
//Daypress
|
||||
const handleDayPress = (day) => {
|
||||
setSelectedDate(day.dateString)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<View style={{flex:1, backgroundColor: "white"}}>
|
||||
<Calendar onDayPress={handleDayPress}
|
||||
theme = {{calendarBackground: "white", todayTextColor: 'red', arrowColor: "black",}}
|
||||
markedDates={{[selectedDate]: {selected: true, selectedColor: "red"}}}
|
||||
/>
|
||||
{todos?.length > 0 && (
|
||||
<View style={{marginTop: 20, marginHorizontal: 10}}>
|
||||
<View style={{flexDirection:"row", alignItems: "center", gap: 10}}>
|
||||
<Text style={{marginBottom: 20, fontSize: 16}}>You completed <Text style={{fontWeight: 600, color: "#61677A"}}>{todos.length}</Text> tasks</Text>
|
||||
</View>
|
||||
{todos.map((item, index) => (
|
||||
<Pressable key={index} style={{backgroundColor: "#EEEEEE", marginBottom: 10, padding: 5, borderRadius: 5}}>
|
||||
<View style={{flexDirection:"row", alignItems: "center", gap: 10}}>
|
||||
<Feather name="check-circle" size={15} color="#1A5319" />
|
||||
<Text style={{flex: 1, textDecorationLine: "line-through", color: "#1A5319"}}>{item.title}</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default index
|
||||
|
||||
const styles = StyleSheet.create({})
|
|
@ -1,13 +0,0 @@
|
|||
import {Stack} from "expo-router";
|
||||
import {ModalPortal} from "react-native-modals"
|
||||
|
||||
export default function Layout(){
|
||||
return(
|
||||
<>
|
||||
<Stack screenOptions={{headerShown:false}}>
|
||||
<Stack.Screen name="index"/>
|
||||
</Stack>
|
||||
<ModalPortal />
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
import { Image, Pressable, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { AntDesign } from '@expo/vector-icons';
|
||||
import { BottomModal, ModalContent, ModalTitle, SlideAnimation } from 'react-native-modals';
|
||||
import { Entypo } from '@expo/vector-icons';
|
||||
import { Feather } from '@expo/vector-icons';
|
||||
import axios from "axios"
|
||||
import moment from 'moment';
|
||||
import { Buffer } from 'buffer';
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
|
||||
|
||||
const index = () => {
|
||||
const [todos, setTodos] = useState([]);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [todo, setTodo] = useState('');
|
||||
const [category, setCategory] = useState('all');
|
||||
const [pendingTodos, setPendingTodos] = useState([]);
|
||||
const [compledTodos, setCompletedTodos] = useState([]);
|
||||
const [markCompleted, setMarkCompleted] = useState(false);
|
||||
const todayDate = moment().format("MMM Do YYYY");
|
||||
|
||||
const getToken = async () => {
|
||||
try {
|
||||
let token = await AsyncStorage.getItem('authToken'); // get the authToken stored in AsyncStorage from login.js
|
||||
let decodeToken = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||
let userId = decodeToken.userId;
|
||||
return userId;
|
||||
} catch (e) {
|
||||
// error reading value
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const addTodo = async() => {
|
||||
try {
|
||||
const todoData = {
|
||||
title:todo,
|
||||
category:category,
|
||||
}
|
||||
|
||||
getToken().then((resp) => {
|
||||
let userId = resp;
|
||||
axios.post(`http://localhost:3030/todos/${userId}`, todoData).then((res) => {
|
||||
})
|
||||
|
||||
}).catch((error) => {
|
||||
console.log("Error", error);
|
||||
})
|
||||
await getUserTodos();
|
||||
setModalVisible(false);
|
||||
setTodo('');
|
||||
|
||||
} catch (error) {
|
||||
console.log("Error", error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getUserTodos();
|
||||
}, [markCompleted, modalVisible])
|
||||
|
||||
const getUserTodos = () => {
|
||||
try {
|
||||
getToken().then( async(resp) => {
|
||||
let userId = resp;
|
||||
const res = await axios.get(`http://localhost:3030/users/${userId}/todos`);
|
||||
setTodos(res.data.todos);
|
||||
|
||||
const fetchTodos = res.data.todos || [];
|
||||
const pending = fetchTodos.filter((todo) => todo.status !== "completed");
|
||||
const completed = fetchTodos.filter((todo) => todo.status === "completed");
|
||||
|
||||
setPendingTodos(pending);
|
||||
setCompletedTodos(completed);
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.log("Error", error)
|
||||
}
|
||||
};
|
||||
|
||||
const markTodocompleted = async (todoId) => {
|
||||
try {
|
||||
setMarkCompleted(true);
|
||||
|
||||
const res = await axios.patch(`http://localhost:3030/todos/${todoId}/complete`);
|
||||
} catch (error) {
|
||||
console.log("Error", error)
|
||||
}
|
||||
}
|
||||
//data
|
||||
const suggestion = [
|
||||
{
|
||||
id: 1,
|
||||
todo: "Walk the dog"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
todo: "Go to the gym"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
todo: "Call mom"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
todo: "Get goceries"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
todo: "5 minute meditation"
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={{marginHorizontal:20, marginVertical:20, alignItems: "center", flexDirection: "row", gap: 10}}>
|
||||
<Pressable style={{backgroundColor: "#000000", paddingHorizontal:10, paddingVertical: 5, borderRadius:25, alignItems: "center", justifyContent: "center"}}>
|
||||
<Text style={{color: "white", textAlign: "center"}}>All</Text>
|
||||
</Pressable>
|
||||
<Pressable style={{backgroundColor: "#000000", paddingHorizontal:10, paddingVertical: 5, borderRadius:25, alignItems: "center", justifyContent: "center"}}>
|
||||
<Text style={{color: "white", textAlign: "center"}}>Work</Text>
|
||||
</Pressable>
|
||||
<Pressable style={{backgroundColor: "#000000", paddingHorizontal:10, paddingVertical: 5, borderRadius:25, alignItems: "center", justifyContent: "center", marginRight: "auto"}}>
|
||||
<Text style={{color: "white", textAlign: "center"}}>Personal</Text>
|
||||
</Pressable>
|
||||
<Pressable onPress={() => setModalVisible(!modalVisible)}>
|
||||
<AntDesign name="plus" size={24} color="black" />
|
||||
</Pressable>
|
||||
</View>
|
||||
<ScrollView style={{flex:1, backgroundColor: "white"}}>
|
||||
<View style={{padding: 10}}>
|
||||
{todos?.length > 0 ? (
|
||||
<View>
|
||||
{pendingTodos?.length > 0 &&
|
||||
<>
|
||||
<Text style={{fontSize: 20, fontWeight: 600, marginBottom: 10}}>{todayDate}</Text>
|
||||
<Text style={{marginBottom: 20, fontSize: 16}}>You have <Text style={{fontWeight: 600, color: "#61677A"}}>{pendingTodos.length}</Text> tasks to do!</Text>
|
||||
</>
|
||||
}
|
||||
{pendingTodos.map((item, index) => (
|
||||
<Pressable key={index} style={{backgroundColor: "#EEEEEE", marginBottom: 10, padding: 5, borderRadius: 5}}>
|
||||
<View style={{flexDirection:"row", alignItems: "center", gap: 10}}>
|
||||
<Entypo onPress={() => markTodocompleted(item?._id)} name="circle" size={15} color="black" />
|
||||
<Text style={{flex: 1}}>{item.title}</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
))}
|
||||
|
||||
{compledTodos?.length > 0 && (
|
||||
<View style={{marginTop: 20}}>
|
||||
<View style={{flexDirection:"row", alignItems: "center", gap: 10}}>
|
||||
<Text style={{marginBottom: 20, fontSize: 16}}>You have completed <Text style={{fontWeight: 600, color: "#61677A"}}>{compledTodos.length}</Text> tasks</Text>
|
||||
</View>
|
||||
{compledTodos.map((item, index) => (
|
||||
<Pressable key={index} style={{backgroundColor: "#EEEEEE", marginBottom: 10, padding: 5, borderRadius: 5}}>
|
||||
<View style={{flexDirection:"row", alignItems: "center", gap: 10}}>
|
||||
<Feather name="check-circle" size={15} color="#1A5319" />
|
||||
<Text style={{flex: 1, textDecorationLine: "line-through", color: "#1A5319"}}>{item.title}</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<View style={{flex:1, justifyContent:"center", alignItems:"center", marginTop:150, marginLeft:"auto", marginRight:"auto"}}>
|
||||
<Image
|
||||
style={{width: 100, height: 100, resizeMode: "containe"}}
|
||||
source={{
|
||||
uri:"./assets/images/to-do.png",
|
||||
}}
|
||||
/>
|
||||
<Text style={{fontSize:16, fontWeight:600, marginTop: 20, textAlign:"center"}}>Hooray! There is no tasks</Text>
|
||||
<Pressable onPress={() => setModalVisible(!modalVisible)} style={{backgroundColor: "black", marginTop: 20, padding:5, borderRadius: 5}}>
|
||||
<Text style={{fontSize:14, fontWeight:600, textAlign:"center", color: "white"}}>Add task <AntDesign name="plus" size={16} color="white" /></Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<BottomModal
|
||||
onBackdropPress={() => setModalVisible(!modalVisible)}
|
||||
onHardwareBackPress={() => setModalVisible(!modalVisible)}
|
||||
swipeDirection={['up', 'down']}
|
||||
swipeThreshold={200}
|
||||
modalTitle={<ModalTitle title="Add Task"/>}
|
||||
modalAnimation={
|
||||
new SlideAnimation({
|
||||
slideFrom: 'bottom',
|
||||
})
|
||||
}
|
||||
visible={modalVisible}
|
||||
onTouchOutside={() => setModalVisible(!modalVisible)}
|
||||
>
|
||||
<ModalContent style={{width:"100%", height:280}}>
|
||||
<View style={{marginTop: 10, flexDirection: "row", alignItems: "center", gap: 10}}>
|
||||
<TextInput value={todo} onChangeText={(text) => setTodo(text)} style={{padding:10, borderColor: "#61677A", borderRadius:5, borderWidth:1, flex:1, outlineStyle: 'none', color: "#31363F"}} placeholder='Add your new task'/>
|
||||
<Entypo onPress={addTodo} name="add-to-list" size={24} color="black" />
|
||||
</View>
|
||||
|
||||
<Text style={{marginTop: 10, fontWeight: 600}}>Category:</Text>
|
||||
<View style={{flexDirection: "row", alignItems: "center", gap: 10, marginVertical: 10}}>
|
||||
<Pressable onPress={() => setCategory('personal')} style={{backgroundColor: "#D8D9DA", padding: 5, borderRadius: 5,}}>
|
||||
<Text>Personal</Text>
|
||||
</Pressable>
|
||||
|
||||
<Pressable onPress={() => setCategory('work')} style={{backgroundColor: "#D8D9DA", padding: 5, borderRadius: 5}}>
|
||||
<Text>Work</Text>
|
||||
</Pressable>
|
||||
|
||||
<Pressable onPress={() => setCategory('house')} style={{backgroundColor: "#D8D9DA", padding: 5, borderRadius: 5}}>
|
||||
<Text>House</Text>
|
||||
</Pressable>
|
||||
|
||||
<Pressable onPress={() => setCategory('social')} style={{backgroundColor: "#D8D9DA", padding: 5, borderRadius: 5}}>
|
||||
<Text>Social</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Text style={{marginTop: 10, fontWeight: 600}}>Suggestions:</Text>
|
||||
<View style={{flexDirection: "row", alignItems: "center", gap: 10, marginVertical: 10, flexWrap: "wrap"}}>
|
||||
{suggestion?.map((item, index) =>
|
||||
<Pressable onPress={() => setTodo(item?.todo)} key={index} style={{backgroundColor: "#EEEEEE", padding: 5, borderRadius: 5}}>
|
||||
<Text>{item.todo}</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
</ModalContent>
|
||||
</BottomModal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default index
|
||||
|
||||
const styles = StyleSheet.create({})
|
|
@ -1,9 +0,0 @@
|
|||
import {Stack} from "expo-router";
|
||||
|
||||
export default function Layout(){
|
||||
return(
|
||||
<Stack screenOptions={{headerShown:false}}>
|
||||
<Stack.Screen name="index"/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
import { Dimensions, Image, Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import axios from "axios";
|
||||
import {BarChart} from "react-native-chart-kit";
|
||||
import { Buffer } from 'buffer';
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
|
||||
const getToken = async () => {
|
||||
try {
|
||||
let token = await AsyncStorage.getItem('authToken'); // get the authToken stored in AsyncStorage from login.js
|
||||
let decodeToken = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||
let userId = decodeToken.userId;
|
||||
return userId;
|
||||
} catch (e) {
|
||||
// error reading value
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const index = () => {
|
||||
const [completedTasks, setCompletedTasks] = useState(0);
|
||||
const [pendingTasks, setPendingTasks] = useState(0);
|
||||
|
||||
const fetchTaskData = () => {
|
||||
try {
|
||||
|
||||
getToken().then( async (resp) => {
|
||||
let userId = resp;
|
||||
|
||||
const res = await axios.get(`http://localhost:3030/users/${userId}/todos/count`);
|
||||
const {totalCompletedTodos, totalPendingTodos} = res.data;
|
||||
|
||||
setCompletedTasks(totalCompletedTodos);
|
||||
setPendingTasks(totalPendingTodos);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.log("Error", error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchTaskData();
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={{marginHorizontal:20, marginVertical:20, alignItems: "center", flexDirection: "row", gap: 10}}>
|
||||
<Pressable style={{marginRight: "auto"}}>
|
||||
<Text style={{color: "black", textAlign: "center", fontSize: 20, fontWeight: 600}}>Keep Tracking</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<View style={{padding: 10, flex: 1, backgroundColor: "white"}}>
|
||||
<View style={{marginTop: 10, marginHorizontal:10}}>
|
||||
<Text style={{fontSize: 16, fontWeight: 600}}>Overview</Text>
|
||||
<View style={{flexDirection: "row", alignItems: "center", gap: 10, marginVertical: 10}}>
|
||||
<View style={{padding:10, backgroundColor: "#E8C4C4", borderRadius: 5, flex:1, justifyContent: "center", alignItems: "center"}}>
|
||||
<Text style={{color: "#CE7777", fontWeight: 600}}>{pendingTasks}</Text>
|
||||
<Text style={{color: "#CE7777", fontWeight: 600}}>Pending Tasks</Text>
|
||||
</View>
|
||||
|
||||
<View style={{padding:10, backgroundColor: "#C8DBBE", borderRadius: 5, flex:1, justifyContent: "center", alignItems: "center"}}>
|
||||
<Text style={{color: "#829460", fontWeight: 600}}>{completedTasks}</Text>
|
||||
<Text style={{color: "#829460", fontWeight: 600}}>Completed Tasks</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/*========== chart =========*/}
|
||||
<BarChart
|
||||
data={{
|
||||
labels: ["Pending", "Completed"],
|
||||
datasets: [
|
||||
{
|
||||
data: [pendingTasks, completedTasks],
|
||||
},
|
||||
],
|
||||
}}
|
||||
width={Dimensions.get("window").width - 20}
|
||||
height={200}
|
||||
yAxisInterval={2}
|
||||
chartConfig={{
|
||||
backgroundColor: "#BFACE2",
|
||||
backgroundGradientFrom: "#BCCEF8",
|
||||
backgroundGradientTo: "#F3E8FF",
|
||||
decimalPlaces: 2,
|
||||
color: (opacity = 1) => `rgba(106, 90, 205, ${opacity})`,
|
||||
labelColor: (opacity = 1) => `rgba(108, 56, 172, ${opacity})`,
|
||||
style: {
|
||||
borderRadius: 5
|
||||
},
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
marginTop: 10
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default index
|
||||
|
||||
const styles = StyleSheet.create({})
|
|
@ -1,13 +0,0 @@
|
|||
import { StyleSheet, Text, View } from "react-native";
|
||||
import React from 'react';
|
||||
import { Redirect } from "expo-router";
|
||||
|
||||
const index = () => {
|
||||
return (
|
||||
<Redirect href="/(authenticate)/login"/>
|
||||
)
|
||||
}
|
||||
|
||||
export default index
|
||||
|
||||
const styles = StyleSheet.create({})
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
Before Width: | Height: | Size: 17 KiB |
|
@ -1,6 +0,0 @@
|
|||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
};
|
||||
};
|
19034
todo_app/package-lock.json
generated
19034
todo_app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
"name": "todo_app",
|
||||
"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",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"axios": "^1.7.2",
|
||||
"expo": "~51.0.20",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-font": "~12.0.8",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-router": "~3.5.18",
|
||||
"expo-splash-screen": "~0.27.5",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"expo-system-ui": "~3.0.7",
|
||||
"expo-web-browser": "~13.0.3",
|
||||
"moment": "^2.30.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "0.74.3",
|
||||
"react-native-calendars": "^1.1305.0",
|
||||
"react-native-chart-kit": "^6.12.0",
|
||||
"react-native-gesture-handler": "~2.16.1",
|
||||
"react-native-modals": "^0.22.3",
|
||||
"react-native-reanimated": "~3.10.1",
|
||||
"react-native-safe-area-context": "4.10.1",
|
||||
"react-native-screens": "3.31.1",
|
||||
"react-native-web": "~0.19.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "~18.2.45",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
"jest": "^29.2.1",
|
||||
"jest-expo": "~51.0.3",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"typescript": "~5.3.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".expo/types/**/*.ts",
|
||||
"expo-env.d.ts"
|
||||
]
|
||||
}
|
Loading…
Add table
Reference in a new issue