mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-03 20:12:58 +00:00
uppdate
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { useAuth } from '../services/AuthContext';
|
||||
import { useServerConfig } from '../services/ServerConfigContext';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { useOffline } from '../services/OfflineContext';
|
||||
import { useTheme } from 'react-native-paper';
|
||||
@@ -28,13 +27,9 @@ export type MainTabParamList = {
|
||||
const Tab = createBottomTabNavigator<MainTabParamList>();
|
||||
|
||||
const TabNavigator: React.FC = () => {
|
||||
const { isOnline, pendingChanges } = useOffline();
|
||||
const { pendingChanges } = useOffline();
|
||||
const theme = useTheme();
|
||||
|
||||
const getTabBarIcon = (name: string, color: string) => (
|
||||
<Icon name={name} size={24} color={color} />
|
||||
);
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
screenOptions={({ route }) => ({
|
||||
|
||||
@@ -8,16 +8,12 @@ import {
|
||||
} from 'react-native';
|
||||
import {
|
||||
Text,
|
||||
Card,
|
||||
Title,
|
||||
Paragraph,
|
||||
TextInput,
|
||||
Button,
|
||||
FAB,
|
||||
IconButton,
|
||||
Avatar,
|
||||
Chip,
|
||||
Divider,
|
||||
} from 'react-native-paper';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useRealtimeUpdates } from '../services/RealtimeSyncContext';
|
||||
|
||||
@@ -7,19 +7,16 @@ import {
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import {
|
||||
Text,
|
||||
Card,
|
||||
Title,
|
||||
Paragraph,
|
||||
TextInput,
|
||||
Button,
|
||||
ActivityIndicator,
|
||||
HelperText,
|
||||
} from 'react-native-paper';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useServerConfig } from '../services/ServerConfigContext';
|
||||
import { updateAPIBaseURL } from '../services/api';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
interface ServerConfig {
|
||||
baseUrl: string;
|
||||
@@ -37,7 +34,6 @@ const ServerSetupScreen: React.FC = () => {
|
||||
const [errors, setErrors] = useState<Partial<ServerConfig>>({});
|
||||
|
||||
const { setConfig: saveConfig } = useServerConfig();
|
||||
const navigation = useNavigation();
|
||||
|
||||
const validateConfig = (): boolean => {
|
||||
const newErrors: Partial<ServerConfig> = {};
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
import {
|
||||
TextInput,
|
||||
Button,
|
||||
Text,
|
||||
Card,
|
||||
Title,
|
||||
Paragraph,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||
import { View, Alert, Platform } from 'react-native';
|
||||
import { Camera, useCameraDevices } from 'react-native-vision-camera';
|
||||
import { Alert, Platform } from 'react-native';
|
||||
import { useCameraDevices } from 'react-native-vision-camera';
|
||||
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
|
||||
|
||||
interface CameraContextType {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||
import PushNotification from 'react-native-push-notification';
|
||||
import { Platform, PermissionsAndroid, Alert } from 'react-native';
|
||||
import { Platform, Alert } from 'react-native';
|
||||
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
|
||||
|
||||
interface Notification {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react';
|
||||
import { NetInfoState, useNetInfo } from '@react-native-community/netinfo';
|
||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||
import { useNetInfo } from '@react-native-community/netinfo';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useServerConfig } from './ServerConfigContext';
|
||||
import { DeviceEventEmitter } from 'react-native';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||
import { Alert, Platform, PermissionsAndroid } from 'react-native';
|
||||
import { Alert, Platform } from 'react-native';
|
||||
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
|
||||
import Voice from 'react-native-voice';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import { ApiResponse, User, Bookmark, Task, Note, TimeEntry, CalendarEvent, SearchFilters, SavedSearch } from '../types';
|
||||
import { getStoredAuthData } from '../utils/storage';
|
||||
import { useServerConfig } from './ServerConfigContext';
|
||||
|
||||
let API_BASE_URL = __DEV__
|
||||
? 'http://localhost:8080/api'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getOfflineData, clearOfflineChanges, addOfflineChange } from './storage';
|
||||
import { authAPI, bookmarksAPI, tasksAPI, notesAPI, timeEntriesAPI } from '../services/api';
|
||||
import { bookmarksAPI, tasksAPI, notesAPI, timeEntriesAPI } from '../services/api';
|
||||
|
||||
interface OfflineChange {
|
||||
id: string;
|
||||
|
||||
@@ -4,12 +4,13 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
cryptorand "crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -279,6 +280,15 @@ func main() {
|
||||
courses.POST("/:id/resources", addCourseResource) // Admin/Instructor only
|
||||
}
|
||||
|
||||
// Learning paths (alias for courses with learning path specific endpoints)
|
||||
learningPaths := api.Group("/learning-paths")
|
||||
{
|
||||
learningPaths.GET("", getLearningPaths)
|
||||
learningPaths.GET("/categories", getLearningPathCategories)
|
||||
learningPaths.POST("/:id/enroll", enrollInLearningPath)
|
||||
learningPaths.GET("/:id", getLearningPath)
|
||||
}
|
||||
|
||||
// User progress
|
||||
progress := api.Group("/progress")
|
||||
{
|
||||
@@ -781,14 +791,14 @@ func generateJWTWithEmailToken(userSession map[string]interface{}) string {
|
||||
|
||||
func generateRandomString(length int) string {
|
||||
bytes := make([]byte, length)
|
||||
rand.Read(bytes)
|
||||
cryptorand.Read(bytes)
|
||||
return hex.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
func generateVerificationCode() string {
|
||||
// Generate 6-digit verification code
|
||||
codeBytes := make([]byte, 3)
|
||||
rand.Read(codeBytes)
|
||||
cryptorand.Read(codeBytes)
|
||||
code := int(codeBytes[0])*10000 + int(codeBytes[1])*100 + int(codeBytes[2])
|
||||
code = (code % 900000) + 100000
|
||||
return fmt.Sprintf("%06d", code)
|
||||
@@ -988,6 +998,210 @@ func addCourseResource(c *gin.Context) {
|
||||
c.JSON(http.StatusCreated, resource)
|
||||
}
|
||||
|
||||
// Learning Paths Handlers (frontend-specific format)
|
||||
|
||||
func getLearningPaths(c *gin.Context) {
|
||||
// Get query parameters
|
||||
search := c.Query("search")
|
||||
category := c.Query("category")
|
||||
difficulty := c.Query("difficulty")
|
||||
|
||||
var learningPaths []gin.H
|
||||
|
||||
for _, course := range courses {
|
||||
if !course.IsActive {
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply filters
|
||||
if search != "" && !containsIgnoreCase(course.Title, search) && !containsIgnoreCase(course.Description, search) {
|
||||
continue
|
||||
}
|
||||
if category != "" && !containsIgnoreCase(course.Category, category) {
|
||||
continue
|
||||
}
|
||||
if difficulty != "" && !containsIgnoreCase(course.Difficulty, difficulty) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert to frontend format
|
||||
resources := courseResources[course.ID]
|
||||
tags := make([]gin.H, len(course.Tags))
|
||||
for i, tag := range course.Tags {
|
||||
tags[i] = gin.H{
|
||||
"name": tag,
|
||||
"color": "#3b82f6", // Blue color for all tags
|
||||
}
|
||||
}
|
||||
|
||||
modules := make([]gin.H, len(resources))
|
||||
for i, resource := range resources {
|
||||
modules[i] = gin.H{
|
||||
"id": fmt.Sprintf("module_%d", resource.ID),
|
||||
"title": resource.Title,
|
||||
"description": resource.Description,
|
||||
"completed": false,
|
||||
"resources": []gin.H{
|
||||
{
|
||||
"type": string(resource.Type),
|
||||
"title": resource.Title,
|
||||
"url": resource.URL,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
learningPath := gin.H{
|
||||
"id": course.ID,
|
||||
"title": course.Title,
|
||||
"description": course.Description,
|
||||
"category": course.Category,
|
||||
"difficulty": course.Difficulty,
|
||||
"duration": fmt.Sprintf("%d hours", course.Duration),
|
||||
"thumbnail": course.Thumbnail,
|
||||
"is_featured": course.ID <= 2, // First 2 courses are featured
|
||||
"enrollment_count": rand.Intn(2000) + 200,
|
||||
"rating": 4.0 + rand.Float64(),
|
||||
"review_count": rand.Intn(200) + 20,
|
||||
"creator": gin.H{
|
||||
"username": "instructor",
|
||||
"full_name": "Expert Instructor",
|
||||
},
|
||||
"tags": tags,
|
||||
"modules": modules,
|
||||
"createdAt": course.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
learningPaths = append(learningPaths, learningPath)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, learningPaths)
|
||||
}
|
||||
|
||||
func getLearningPathCategories(c *gin.Context) {
|
||||
categories := []string{
|
||||
"Web Development",
|
||||
"Mobile Development",
|
||||
"Programming",
|
||||
"DevOps",
|
||||
"Data Science",
|
||||
"Design",
|
||||
"Business",
|
||||
"Cybersecurity",
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"categories": categories})
|
||||
}
|
||||
|
||||
func enrollInLearningPath(c *gin.Context) {
|
||||
pathID := parseInt(c.Param("id"))
|
||||
userID := getUserIDFromToken(c)
|
||||
|
||||
if userID == 0 {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if course exists
|
||||
course, exists := courses[pathID]
|
||||
if !exists || !course.IsActive {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Learning path not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Create or update progress
|
||||
key := fmt.Sprintf("%d_%d", userID, pathID)
|
||||
progress, exists := userProgress[key]
|
||||
if !exists {
|
||||
progress = UserProgress{
|
||||
UserID: userID,
|
||||
CourseID: pathID,
|
||||
Status: "in_progress",
|
||||
Progress: 0.0,
|
||||
StartedAt: time.Now(),
|
||||
LastAccessed: time.Now(),
|
||||
}
|
||||
} else {
|
||||
progress.Status = "in_progress"
|
||||
progress.LastAccessed = time.Now()
|
||||
}
|
||||
|
||||
userProgress[key] = progress
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Successfully enrolled in learning path",
|
||||
"enrolled": true,
|
||||
"progress": progress,
|
||||
})
|
||||
}
|
||||
|
||||
func getLearningPath(c *gin.Context) {
|
||||
pathID := parseInt(c.Param("id"))
|
||||
|
||||
course, exists := courses[pathID]
|
||||
if !exists || !course.IsActive {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Learning path not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get resources
|
||||
resources := courseResources[pathID]
|
||||
course.Resources = resources
|
||||
|
||||
// Convert to frontend format
|
||||
tags := make([]gin.H, len(course.Tags))
|
||||
for i, tag := range course.Tags {
|
||||
tags[i] = gin.H{
|
||||
"name": tag,
|
||||
"color": "#3b82f6",
|
||||
}
|
||||
}
|
||||
|
||||
modules := make([]gin.H, len(resources))
|
||||
for i, resource := range resources {
|
||||
modules[i] = gin.H{
|
||||
"id": fmt.Sprintf("module_%d", resource.ID),
|
||||
"title": resource.Title,
|
||||
"description": resource.Description,
|
||||
"completed": false,
|
||||
"resources": []gin.H{
|
||||
{
|
||||
"type": string(resource.Type),
|
||||
"title": resource.Title,
|
||||
"url": resource.URL,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
learningPath := gin.H{
|
||||
"id": course.ID,
|
||||
"title": course.Title,
|
||||
"description": course.Description,
|
||||
"category": course.Category,
|
||||
"difficulty": course.Difficulty,
|
||||
"duration": fmt.Sprintf("%d hours", course.Duration),
|
||||
"thumbnail": course.Thumbnail,
|
||||
"is_featured": course.ID <= 2,
|
||||
"enrollment_count": rand.Intn(2000) + 200,
|
||||
"rating": 4.0 + rand.Float64(),
|
||||
"review_count": rand.Intn(200) + 20,
|
||||
"creator": gin.H{
|
||||
"username": "instructor",
|
||||
"full_name": "Expert Instructor",
|
||||
},
|
||||
"tags": tags,
|
||||
"modules": modules,
|
||||
"createdAt": course.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, learningPath)
|
||||
}
|
||||
|
||||
// Helper function for case-insensitive contains
|
||||
func containsIgnoreCase(s, substr string) bool {
|
||||
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||
}
|
||||
|
||||
// User Progress Handlers
|
||||
|
||||
func getUserProgress(c *gin.Context) {
|
||||
|
||||
Reference in New Issue
Block a user