Initial commit of MindShift CBT Therapy app
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
.env
|
||||
dist/
|
||||
build/
|
||||
*.log
|
||||
.DS_Store
|
||||
*.db
|
||||
MindShift-CBT-Therapy-Windows-Source.zip
|
||||
93
MindShift-Windows/README-Windows.md
Normal file
93
MindShift-Windows/README-Windows.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# MindShift CBT Therapy - Windows 11 Version
|
||||
|
||||
A powerful Windows 11 application for Cognitive Behavioral Therapy (CBT) self-help, designed to help you shift negative moods into positive ones through structured exercises and mood tracking.
|
||||
|
||||
## Windows 11 Features
|
||||
|
||||
- **Native Windows 11 Integration**: Seamlessly integrates with Windows 11 taskbar and notification system
|
||||
- **Mica Effect**: Supports Windows 11's Mica transparency effect on compatible systems
|
||||
- **Jump List Tasks**: Quick access to breathing exercises directly from the taskbar
|
||||
- **Native Notifications**: Windows 11 native notification system for reminders and achievements
|
||||
- **Data Import/Export**: Easily backup and restore your therapy data
|
||||
|
||||
## Installation
|
||||
|
||||
### Running from Source (Recommended)
|
||||
|
||||
1. Ensure you have Node.js installed (https://nodejs.org/)
|
||||
2. Download the `MindShift-CBT-Therapy-Windows-Source.zip` file
|
||||
3. Extract to your desired location
|
||||
4. Double-click `Start MindShift CBT Therapy.bat`
|
||||
5. The app will automatically install dependencies and launch
|
||||
|
||||
Note: The first run may take a few minutes as it downloads and installs dependencies.
|
||||
|
||||
## Features
|
||||
|
||||
### Core CBT Exercises
|
||||
- **Thought Records**: Challenge and reframe negative thoughts
|
||||
- **Mindfulness Exercises**: Practice present-moment awareness
|
||||
- **Gratitude Journal**: Cultivate positive thinking patterns
|
||||
- **Breathing Exercises**: Quick anxiety relief through guided breathing
|
||||
|
||||
### Situation Builder
|
||||
Build your current mood state from different dimensions:
|
||||
- Emotional state
|
||||
- Physical sensations
|
||||
- Thought patterns
|
||||
- Environmental factors
|
||||
|
||||
### Progress Tracking
|
||||
- Daily mood tracking
|
||||
- Weekly activity charts
|
||||
- Mood trend analysis
|
||||
- Personalized insights
|
||||
|
||||
## Data Management
|
||||
|
||||
### Export Your Data
|
||||
1. Click the menu button (☰)
|
||||
2. Select "Export Data"
|
||||
3. Choose a location to save your backup file
|
||||
|
||||
### Import Your Data
|
||||
1. Click the menu button (☰)
|
||||
2. Select "Import Data"
|
||||
3. Choose your backup file to restore
|
||||
|
||||
## System Requirements
|
||||
|
||||
- Windows 10 (version 1903) or Windows 11
|
||||
- 4GB RAM minimum
|
||||
- 100MB free disk space
|
||||
- Internet connection for initial setup
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### App Won't Start
|
||||
- Ensure Node.js is installed (Option 1 only)
|
||||
- Try running as administrator
|
||||
- Check Windows Defender isn't blocking the app
|
||||
|
||||
### Notifications Not Working
|
||||
- Check Windows notification settings
|
||||
- Ensure notifications are enabled for MindShift in Windows Settings
|
||||
|
||||
### Data Not Saving
|
||||
- Check disk space
|
||||
- Try exporting your data as a backup
|
||||
- Restart the application
|
||||
|
||||
## Privacy
|
||||
|
||||
MindShift stores all your data locally on your Windows device. No data is sent to external servers. Your therapy journey remains completely private.
|
||||
|
||||
## Support
|
||||
|
||||
For support or feature requests, please contact:
|
||||
- Email: support@mindshift.app
|
||||
- Website: https://mindshift.app
|
||||
|
||||
---
|
||||
|
||||
© 2024 MindShift Team. All rights reserved.
|
||||
59
MindShift-Windows/Start MindShift CBT Therapy.bat
Normal file
59
MindShift-Windows/Start MindShift CBT Therapy.bat
Normal file
@@ -0,0 +1,59 @@
|
||||
@echo off
|
||||
title MindShift CBT Therapy - Windows 11
|
||||
color 0A
|
||||
echo.
|
||||
echo ========================================
|
||||
echo MindShift CBT Therapy for Windows 11
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM Check if Node.js is installed
|
||||
node --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] Node.js is not installed.
|
||||
echo.
|
||||
echo Please install Node.js from https://nodejs.org/ and try again.
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [SUCCESS] Node.js is installed
|
||||
echo.
|
||||
|
||||
REM Check if dependencies are installed
|
||||
if not exist "node_modules\electron" (
|
||||
echo [INFO] Installing dependencies for the first time...
|
||||
echo This may take a few minutes on first run...
|
||||
echo.
|
||||
call npm install
|
||||
if %errorlevel% neq 0 (
|
||||
echo.
|
||||
echo [ERROR] Failed to install dependencies.
|
||||
echo Please check your internet connection and try again.
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo.
|
||||
echo [SUCCESS] Dependencies installed successfully!
|
||||
echo.
|
||||
)
|
||||
|
||||
REM Start the app
|
||||
echo [INFO] Launching MindShift CBT Therapy...
|
||||
echo.
|
||||
call npm start
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
echo.
|
||||
echo [ERROR] Failed to start the application.
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Thank you for using MindShift CBT Therapy!
|
||||
echo.
|
||||
pause
|
||||
11
MindShift-Windows/assets/browserconfig.xml
Normal file
11
MindShift-Windows/assets/browserconfig.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square70x70logo src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHZpZXdCb3g9IjAgMCA3MCA3MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjcwIiBoZWlnaHQ9IjcwIiByeD0iMTAiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl8zXzMpIi8+CjxwYXRoIGQ9Ik0zNSAyMUMzMi45MjkgMjEgMzEuNSAyMi45MjkgMzEuNSAyNUMzMS41IDI3LjA3MSAzMi45MjkgMjkgMzUgMjlDMzcuMDcxIDI5IDM4LjUgMjcuMDcxIDM4LjUgMjVDMzguNSAyMi45MjkgMzcuMDcxIDIxIDM1IDIxWiIgZmlsbD0id2hpdGUiLz4KPHA+PC9wPgo8cGF0aCBkPSJNMzUgMzVDMzMuMDQzIDM1IDMxLjUgMzYuNTQzIDMxLjUgMzguNUMzMS41IDQwLjQ1NyAzMy4wNDMgNDIgMzUgNDJDMzYuOTU3IDQyIDM4LjUgNDAuNDU3IDM4LjUgMzguNUMzOC41IDM2LjU0MyAzNi45NTcgMzUgMzUgMzVaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudDBfbGluZWFyXzNfMyIgeDE9IjAiIHkxPSIwIiB4Mj0iNzAiIHkyPSI3MCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRkY2QjZCIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0ZGQjc0RCIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPg=="/>
|
||||
<square150x150logo src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUwIiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDE1MCAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNTAiIGhlaWdodD0iMTUwIiByeD0iMjAiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl80XzQpIi8+CjxwYXRoIGQ9Ik03NSA0NUM3MC41MTc5IDQ1IDY3IDQ4LjUxNzkgNjcgNTNDNjcgNTcuNDgyMSA3MC41MTc5IDYxIDc1IDYxQzc5LjQ4MjEgNjEgODMgNTcuNDgyMSA4MyA1M0M4MyA0OC41MTc5IDc5LjQ4MjEgNDUgNzUgNDVaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNjAgNzVDNjAuMTM2NyA3MC4yMzgzIDYyLjIzODIgNjUuNDc2NyA2NS4yMzIzIDYxLjgzMzNDNjguMjI2NCA1OC4xOSA3Mi4yMjMyIDU1Ljg5MjggNzYuNzYwNiA1NS44OTI4QzgxLjI5OCA1NS44OTI4IDg1LjI5NDYgNTguMTkgODguMjg4NyA2MS44MzMzQzkxLjI4MjggNjUuNDc2NyA5My4zNjMzIDcwLjIzODMgOTMuMzI1IDc1QzkzLjI4NjcgNzkuNzYxNyA5MS4yODI4IDg0LjUyMzMgODguMjg4NyA4OC4xNjY3Qzg1LjI5NDYgOTEuODEgODEuMjk4IDk0LjEwNzIgNzYuNzYwNiA5NC4xMDcyQzcyLjIyMzIgOTQuMTA3MiA2OC4yMjY0IDkxLjgxIDY1LjIzMjMgODguMTY2N0M2Mi4yMzgyIDg0LjUyMzMgNjAuMTM2NyA3OS43NjE3IDYwIDc1WiIgZmlsbD0id2hpdGUiLz4KPHA+PC9wPgo8cGF0aCBkPSJNNzUgMTA1QzY2LjcxNTcgMTA1IDYwIDk4LjI4NDMgNjAgOTBDNjAgODEuNzE1NyA2Ni43MTU3IDc1IDc1IDc1QzgzLjI4NDMgNzUgOTAgODEuNzE1NyA5MCA5MEM5MCA5OC4yODQzIDgzLjI4NDMgMTA1IDc1IDEwNVoiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9ImdyYWRpZW50MF9saW5lYXJfNF80IiB4MT0iMCIgeTE9IjAiIHgyPSIxNTAiIHkyPSIxNTAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZGNkI2QiIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRkI3NEQiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4="/>
|
||||
<square310x310logo src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzEwIiBoZWlnaHQ9IjMxMCIgdmlld0JveD0iMCAwIDMxMCAzMTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIzMTAiIGhlaWdodD0iMzEwIiByeD0iNDAiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl81XzUpIi8+CjxwYXRoIGQ9Ik0xNTUgOTNDMTQ2LjE5MyA5MyAxMzkgMTAwLjE5MyAxMzkgMTA5QzEzOSAxMTcuODA3IDE0Ni4xOTMgMTI1IDE1NSAxMjVDMTYzLjgwNyAxMjUgMTcxIDExNy44MDcgMTcxIDEwOUMxNzEgMTAwLjE5MyAxNjMuODA3IDkzIDE1NSA5M1oiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xMjQgMTU1QzEyNC4yNzMgMTQ1LjQ3NyAxMjguNDc2IDEzNS45NTMgMTM0LjQ2NSAxMjguNjY3QzE0MC40NTMgMTIxLjM4IDE0OC40NjQgMTE2Ljc4NiAxNTcuNTIxIDExNi43ODZDMTY2LjU3OCAxMTYuNzg2IDE3NC41ODkgMTIxLjM4IDE4MC41NzggMTI4LjY2N0MxODYuNTY2IDEzNS45NTMgMTkwLjcyNyAxNDUuNDc3IDE5MC42NSAxNTVDMTkwLjU3MyAxNjQuNTIzIDE4Ni41NjYgMTc0LjA0NyAxODAuNTc4IDE4MS4zMzNDMTc0LjU4OSAxODguNjIgMTY2LjU3OCAxOTMuMjE0IDE1Ny41MjEgMTkzLjIxNEMxNDguNDY0IDE5My4yMTQgMTQwLjQ1MyAxODguNjIgMTM0LjQ2NSAxODEuMzMzQzEyOC40NzYgMTc0LjA0NyAxMjQuMjczIDE2NC41MjMgMTI0IDE1NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNTUgMjEwQzEzMy40MzEgMjEwIDExNiAxOTIuNTY5IDExNiAxNzFDMTE2IDE0OS40MzEgMTMzLjQzMSAxMzIgMTU1IDEzMkMxNzYuNTY5IDEzMiAxOTQgMTQ5LjQzMSAxOTQgMTcxQzE5NCAxOTIuNTY5IDE3Ni41NjkgMjEwIDE1NSAyMTBaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudDBfbGluZWFyXzVfNSIgeDE9IjAiIHkxPSIwIiB4Mj0iMzEwIiB5Mj0iMzEwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRjZCNkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkZCNzREIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+"/>
|
||||
<TileColor>#FF6B6B</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
27
MindShift-Windows/assets/manifest.json
Normal file
27
MindShift-Windows/assets/manifest.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "MindShift - CBT Therapy App",
|
||||
"short_name": "MindShift",
|
||||
"description": "Your personal CBT therapy companion for mood management and mental wellness",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#FFB74D",
|
||||
"theme_color": "#FF6B6B",
|
||||
"orientation": "portrait-primary",
|
||||
"icons": [
|
||||
{
|
||||
"src": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkyIiBoZWlnaHQ9IjE5MiIgdmlld0JveD0iMCAwIDE5MiAxOTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxOTIiIGhlaWdodD0iMTkyIiByeD0iMjQiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl8xXzEpIi8+CjxwYXRoIGQ9Ik05NiA0OEM5MC40NzcyIDQ4IDg2IDUyLjQ3NzIgODYgNjhDODYgNzMuNTIyOCA5MC40NzcyIDc4IDk2IDc4QzEwMS41MjIgNzggMTA2IDczLjUyMjggMTA2IDY4QzEwNiA2Mi40NzcyIDEwMS41MjIgNDggOTYgNDhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNzYgOTZDNzYuMDc0NSA5MC4zMTc3IDc4LjMxNzcgODQuNjQ1MiA4Mi4xODU0IDgwLjE4NTRDODYuMDUzMSA3NS43MjU2IDkxLjI1OTEgNzIuNjg5NyA5Ni43MjA5IDcyLjY4OTdDMTAyLjE4MiA3Mi42ODk3IDEwNy4zODkgNzUuNzI1NiAxMTEuMjU2IDgwLjE4NTRDMTE1LjEyNCA4NC42NDUyIDExNy4zOTUgOTAuMzE3NyAxMTcuMzU1IDk2QzExNy4zMTUgMTAxLjY4MiAxMTUuMDQ0IDEwNy4zNTUgMTExLjE3NiAxMTEuODE0QzEwNy4zMDkgMTE2LjI3NCAxMDIuMTgyIDExOS4zMSA5Ni43MjA5IDExOS4zMUM5MS4yNTkxIDExOS4zMSA4Ni4wNTMxIDExNi4yNzQgODIuMTg1NCAxMTEuODE0Qzc4LjMxNzcgMTA3LjM1NSA3Ni4wNzQ1IDEwMS42ODIgNzYgOTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOTYgMTI0Qzg4LjI2ODAxIDEyNCA4MiAxMTcuNzMyIDgyIDExMEM4MiAxMDIuMjY4IDg4LjI2ODAxIDk2IDk2IDk2QzEwMy43MzIgOTYgMTEwIDEwMi4yNjggMTEwIDExMEMxMTAgMTE3LjczMiAxMDMuNzMyIDEyNCA5NiAxMjRaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudDBfbGluZWFyXzFfMSIgeDE9IjAiIHkxPSIwIiB4Mj0iMTkyIiB5Mj0iMTkyIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRjZCNkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkZCNzREIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+",
|
||||
"sizes": "192x192",
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"src": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiByeD0iNjQiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl8yXzIpIi8+CjxwYXRoIGQ9Ik0yNTYgMTI4QzI0MS4yNTUgMTI4IDIyOSAxNDAuMjU1IDIyOSAxNTVDMjI5IDE2OS43NDUgMjQxLjI1NSAxODIgMjU2IDE4MkMyNzAuNzQ1IDE4MiAyODMgMTY5Ljc0NSAyODMgMTU1QzI4MyAxNDAuMjU1IDI3MC43NDUgMTI4IDI1NiAxMjhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjA0IDI1NkMyMDQuMTk4IDI0MS44MzYgMjA4LjgzNiAyMjcuNjcyIDIxNi40NjkgMjE1LjEyMkMyMjQuMTAyIDIwMi41NzIgMjM0LjMwOSAxOTIuMTg5IDI0Ni4yNTYgMTkyLjE4OUMyNTguMjAzIDE5Mi4xODkgMjY4LjQxIDIwMi41NzIgMjc2LjA0MyAyMTUuMTIyQzI4My42NzYgMjI3LjY3MiAyODguMzE0IDI0MS44MzYgMjg4LjI1NiAyNTZDMjg4LjE5OCAyNzAuMTY0IDI4My41NiAyODQuMzI4IDI3NS45MjcgMjk2Ljg3OEMyNjguMjk0IDMwOS40MjggMjU4LjA4NyAzMTkuODExIDI0Ni4xNCAzMTkuODExQzIzNC4xOTMgMzE5LjgxMSAyMjMuOTg2IDMwOS40MjggMjE2LjM1MyAyOTYuODc4QzIwOC43MiAyODQuMzI4IDIwNC4wODIgMjcwLjE2NCAyMDQgMjU2WiIgZmlsbD0id2hpdGUiLz4KPHA+PC9wPgo8cGF0aCBkPSJNMjU2IDMzMkMyMzUuMDg4IDMzMiAyMTggMzE0LjkxMiAyMTggMjk0QzIxOCAyNzMuMDg4IDIzNS4wODggMjU2IDI1NiAyNTZDMjc2LjkxMiAyNTYgMjk0IDI3My4wODggMjk0IDI5NEMyOTQgMzE0LjkxMiAyNzYuOTEyIDMzMiAyNTYgMzMyWiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQwX2xpbmVhcl8yXzIiIHgxPSIwIiB5MT0iMCIgeDI9IjUxMiIgeTI9IjUxMiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRkY2QjZCIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0ZGQjc0RCIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPg==",
|
||||
"sizes": "512x512",
|
||||
"type": "image/svg+xml"
|
||||
}
|
||||
],
|
||||
"categories": ["health", "fitness", "lifestyle"],
|
||||
"lang": "en",
|
||||
"dir": "ltr",
|
||||
"scope": "/",
|
||||
"prefer_related_applications": false
|
||||
}
|
||||
127
MindShift-Windows/assets/sw.js
Normal file
127
MindShift-Windows/assets/sw.js
Normal file
@@ -0,0 +1,127 @@
|
||||
const CACHE_NAME = 'mindshift-v1';
|
||||
const urlsToCache = [
|
||||
'/',
|
||||
'/cbt-therapy-app-mockup.html',
|
||||
'/manifest.json'
|
||||
];
|
||||
|
||||
// Install event - cache resources
|
||||
self.addEventListener('install', function(event) {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
.then(function(cache) {
|
||||
console.log('Opened cache');
|
||||
return cache.addAll(urlsToCache);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch event - serve from cache when offline
|
||||
self.addEventListener('fetch', function(event) {
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then(function(response) {
|
||||
// Cache hit - return response
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
// Clone the request
|
||||
var fetchRequest = event.request.clone();
|
||||
|
||||
return fetch(fetchRequest).then(
|
||||
function(response) {
|
||||
// Check if valid response
|
||||
if(!response || response.status !== 200 || response.type !== 'basic') {
|
||||
return response;
|
||||
}
|
||||
|
||||
// Clone the response
|
||||
var responseToCache = response.clone();
|
||||
|
||||
caches.open(CACHE_NAME)
|
||||
.then(function(cache) {
|
||||
cache.put(event.request, responseToCache);
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
).catch(function() {
|
||||
// Offline fallback for HTML pages
|
||||
if (event.request.destination === 'document') {
|
||||
return caches.match('/cbt-therapy-app-mockup.html');
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Activate event - clean up old caches
|
||||
self.addEventListener('activate', function(event) {
|
||||
event.waitUntil(
|
||||
caches.keys().then(function(cacheNames) {
|
||||
return Promise.all(
|
||||
cacheNames.map(function(cacheName) {
|
||||
if (cacheName !== CACHE_NAME) {
|
||||
console.log('Deleting old cache:', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Background sync for offline actions
|
||||
self.addEventListener('sync', function(event) {
|
||||
if (event.tag === 'background-sync') {
|
||||
event.waitUntil(doBackgroundSync());
|
||||
}
|
||||
});
|
||||
|
||||
function doBackgroundSync() {
|
||||
// Handle syncing offline data when back online
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Push notification handler
|
||||
self.addEventListener('push', function(event) {
|
||||
const options = {
|
||||
body: event.data ? event.data.text() : 'Time for your CBT exercise!',
|
||||
icon: '/data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkyIiBoZWlnaHQ9IjE5MiIgdmlld0JveD0iMCAwIDE5MiAxOTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxOTIiIGhlaWdodD0iMTkyIiByeD0iMjQiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl8xXzEpIi8+CjxwYXRoIGQ9Ik05NiA0OEM5MC40NzcyIDQ4IDg2IDUyLjQ3NzIgODYgNjhDODYgNzMuNTIyOCA5MC40NzcyIDc4IDk2IDc4QzEwMS41MjIgNzggMTA2IDczLjUyMjggMTA2IDY4QzEwNiA2Mi40NzcyIDEwMS41MjIgNDggOTYgNDhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNzYgOTZDNzYuMDc0NSA5MC4zMTc3IDc4LjMxNzcgODQuNjQ1MiA4Mi4xODU0IDgwLjE4NTRDODYuMDUzMSA3NS43MjU2IDkxLjI1OTEgNzIuNjg5NyA5Ni43MjA5IDcyLjY4OTdDMTAyLjE4MiA3Mi42ODk3IDEwNy4zODkgNzUuNzI1NiAxMTEuMjU2IDgwLjE4NTRDMTE1LjEyNCA4NC42NDUyIDExNy4zOTUgOTAuMzE3NyAxMTcuMzU1IDk2QzExNy4zMTUgMTAxLjY4MiAxMTUuMDQ0IDEwNy4zNTUgMTExLjE3NiAxMTEuODE0QzEwNy4zMDkgMTE2LjI3NCAxMDIuMTgyIDExOS4zMSA5Ni43MjA5IDExOS4zMUM5MS4yNTkxIDExOS4zMSA4Ni4wNTMxIDExNi4yNzQgODIuMTg1NCAxMTEuODE0Qzc4LjMxNzcgMTA3LjM1NSA3Ni4wNzQ1IDEwMS42ODIgNzYgOTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOTYgMTI0Qzg4LjI2ODAxIDEyNCA4MiAxMTcuNzMyIDgyIDExMEM4MiAxMDIuMjY4IDg4LjI2ODAxIDk2IDk2IDk2QzEwMy43MzIgOTYgMTEwIDEwMi4yNjggMTEwIDExMEMxMTAgMTE3LjczMiAxMDMuNzMyIDEyNCA5NiAxMjRaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudDBfbGluZWFyXzFfMSIgeDE9IjAiIHkxPSIwIiB4Mj0iMTkyIiB5Mj0iMTkyIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRjZCNkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkZCNzREIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+',
|
||||
badge: '/data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIHZpZXdCb3g9IjAgMCA5NiA5NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9Ijk2IiByeD0iMTIiIGZpbGw9IiNGRjZCNkIiLz4KPC9zdmc+',
|
||||
vibrate: [100, 50, 100],
|
||||
data: {
|
||||
dateOfArrival: Date.now(),
|
||||
primaryKey: 1
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
action: 'explore',
|
||||
title: 'Open App',
|
||||
icon: '/data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE2IDJMMjggMTZMMTYgMzBMMCAxNkwxNiAyWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+'
|
||||
},
|
||||
{
|
||||
action: 'close',
|
||||
title: 'Dismiss',
|
||||
icon: '/data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTI0IDhMMTcgMTVMMjQgMjJMMjIgMjRMMTUgMTdMOCAyNEwxMCAyMkwxNyAxNUwxMCA4TDEyIDZMMTkgMTNMMTIgNkwyNCA4WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification('MindShift', options)
|
||||
);
|
||||
});
|
||||
|
||||
// Notification click handler
|
||||
self.addEventListener('notificationclick', function(event) {
|
||||
event.notification.close();
|
||||
|
||||
if (event.action === 'explore') {
|
||||
// Open the app
|
||||
event.waitUntil(
|
||||
clients.openWindow('/')
|
||||
);
|
||||
}
|
||||
});
|
||||
30
MindShift-Windows/dev-server.js
Normal file
30
MindShift-Windows/dev-server.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const app = express();
|
||||
const PORT = 12005;
|
||||
|
||||
// Serve static files from the src directory
|
||||
app.use(express.static(path.join(__dirname, 'src')));
|
||||
|
||||
// Serve the main app
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'src', 'index.html'));
|
||||
});
|
||||
|
||||
// Start the dev server
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log(`Frontend dev server running on http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
// Keep the server running
|
||||
process.on('SIGINT', () => {
|
||||
console.log('Shutting down dev server...');
|
||||
server.close(() => {
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
// Prevent the process from exiting
|
||||
setInterval(() => {
|
||||
// Keep alive
|
||||
}, 1000);
|
||||
5086
MindShift-Windows/package-lock.json
generated
Normal file
5086
MindShift-Windows/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
73
MindShift-Windows/package.json
Normal file
73
MindShift-Windows/package.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "mindshift-cbt-therapy",
|
||||
"version": "1.0.0",
|
||||
"description": "MindShift - Your personal CBT therapy companion for Windows 11",
|
||||
"main": "src/main.js",
|
||||
"homepage": "./",
|
||||
"author": {
|
||||
"name": "MindShift Team",
|
||||
"email": "support@mindshift.app"
|
||||
},
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"dev": "concurrently \"npm run dev-server\" \"electron . --dev\"",
|
||||
"dev-server": "node dev-server.js",
|
||||
"build": "electron-builder",
|
||||
"build-win": "electron-builder --win",
|
||||
"dist": "npm run build",
|
||||
"pack": "electron-builder --dir",
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.mindshift.cbt.therapy",
|
||||
"productName": "MindShift CBT Therapy",
|
||||
"directories": {
|
||||
"output": "build"
|
||||
},
|
||||
"files": [
|
||||
"src/**/*",
|
||||
"assets/**/*",
|
||||
"node_modules/**/*",
|
||||
"package.json"
|
||||
],
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"requestedExecutionLevel": "asInvoker",
|
||||
"publisherName": "MindShift Team",
|
||||
"signDlls": false
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"shortcutName": "MindShift CBT Therapy",
|
||||
"include": "installer.nsh"
|
||||
},
|
||||
"msi": {
|
||||
"oneClick": false,
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"shortcutName": "MindShift CBT Therapy"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-updater": "^6.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.2.1",
|
||||
"electron": "^28.1.0",
|
||||
"electron-builder": "^24.9.1",
|
||||
"express": "^5.2.1",
|
||||
"rimraf": "^5.0.5"
|
||||
}
|
||||
}
|
||||
185
MindShift-Windows/src/api.js
Normal file
185
MindShift-Windows/src/api.js
Normal file
@@ -0,0 +1,185 @@
|
||||
// API Configuration
|
||||
const API_BASE_URL = 'http://localhost:12004/api';
|
||||
|
||||
// Authentication token management
|
||||
let authToken = localStorage.getItem('authToken');
|
||||
|
||||
// API helper function
|
||||
async function apiCall(endpoint, options = {}) {
|
||||
const url = `${API_BASE_URL}${endpoint}`;
|
||||
const config = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(authToken && { 'Authorization': `Bearer ${authToken}` }),
|
||||
...options.headers
|
||||
},
|
||||
...options
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, config);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'API request failed');
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication API
|
||||
export const authAPI = {
|
||||
async register(name, email, password) {
|
||||
const data = await apiCall('/auth/register', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, email, password })
|
||||
});
|
||||
authToken = data.token;
|
||||
localStorage.setItem('authToken', authToken);
|
||||
return data;
|
||||
},
|
||||
|
||||
async login(email, password) {
|
||||
const data = await apiCall('/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
authToken = data.token;
|
||||
localStorage.setItem('authToken', authToken);
|
||||
return data;
|
||||
},
|
||||
|
||||
async logout() {
|
||||
await apiCall('/auth/logout', { method: 'POST' });
|
||||
authToken = null;
|
||||
localStorage.removeItem('authToken');
|
||||
},
|
||||
|
||||
async getProfile() {
|
||||
return await apiCall('/auth/profile');
|
||||
}
|
||||
};
|
||||
|
||||
// Mood tracking API
|
||||
export const moodAPI = {
|
||||
async trackMood(moodType, intensity, notes) {
|
||||
return await apiCall('/mood/track', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ moodType, intensity, notes })
|
||||
});
|
||||
},
|
||||
|
||||
async getMoodHistory() {
|
||||
return await apiCall('/mood/history');
|
||||
}
|
||||
};
|
||||
|
||||
// Thought record API
|
||||
export const thoughtAPI = {
|
||||
async saveThoughtRecord(thoughtData) {
|
||||
return await apiCall('/thoughts', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(thoughtData)
|
||||
});
|
||||
},
|
||||
|
||||
async getThoughtRecords() {
|
||||
return await apiCall('/thoughts');
|
||||
},
|
||||
|
||||
async updateThoughtRecord(id, thoughtData) {
|
||||
return await apiCall(`/thoughts/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(thoughtData)
|
||||
});
|
||||
},
|
||||
|
||||
async deleteThoughtRecord(id) {
|
||||
return await apiCall(`/thoughts/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Gratitude API
|
||||
export const gratitudeAPI = {
|
||||
async saveGratitudeEntry(entry) {
|
||||
return await apiCall('/gratitude', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(entry)
|
||||
});
|
||||
},
|
||||
|
||||
async getGratitudeEntries() {
|
||||
return await apiCall('/gratitude');
|
||||
},
|
||||
|
||||
async updateGratitudeEntry(id, entry) {
|
||||
return await apiCall(`/gratitude/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(entry)
|
||||
});
|
||||
},
|
||||
|
||||
async deleteGratitudeEntry(id) {
|
||||
return await apiCall(`/gratitude/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Progress API
|
||||
export const progressAPI = {
|
||||
async getProgressStats() {
|
||||
return await apiCall('/progress/stats');
|
||||
},
|
||||
|
||||
async getProgressHistory() {
|
||||
return await apiCall('/progress/history');
|
||||
}
|
||||
};
|
||||
|
||||
// Notification API
|
||||
export const notificationAPI = {
|
||||
async getNotifications() {
|
||||
return await apiCall('/notifications');
|
||||
},
|
||||
|
||||
async markAsRead(notificationId) {
|
||||
return await apiCall(`/notifications/${notificationId}/read`, {
|
||||
method: 'PUT'
|
||||
});
|
||||
},
|
||||
|
||||
async deleteNotification(notificationId) {
|
||||
return await apiCall(`/notifications/${notificationId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
},
|
||||
|
||||
async addNotification(notification) {
|
||||
return await apiCall('/notifications', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(notification)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Check if user is authenticated
|
||||
export function isAuthenticated() {
|
||||
return !!authToken;
|
||||
}
|
||||
|
||||
// Initialize API with token check
|
||||
export function initializeAPI() {
|
||||
if (!authToken) {
|
||||
// Redirect to login or show login modal
|
||||
console.log('User not authenticated');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
1106
MindShift-Windows/src/app.js
Normal file
1106
MindShift-Windows/src/app.js
Normal file
File diff suppressed because it is too large
Load Diff
111
MindShift-Windows/src/index.html
Normal file
111
MindShift-Windows/src/index.html
Normal file
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MindShift - CBT Therapy App</title>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="theme-color" content="#FF6B6B">
|
||||
<meta name="description" content="Your personal CBT therapy companion for mood management and mental wellness">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="MindShift">
|
||||
<meta name="application-name" content="MindShift">
|
||||
<meta name="msapplication-TileColor" content="#FF6B6B">
|
||||
<meta name="msapplication-config" content="browserconfig.xml">
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Animated Background Orbs -->
|
||||
<div class="background-orbs">
|
||||
<div class="orb orb-1"></div>
|
||||
<div class="orb orb-2"></div>
|
||||
<div class="orb orb-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Success Ping Container -->
|
||||
<div id="success-ping" class="success-ping"></div>
|
||||
|
||||
<!-- Proactive Badge -->
|
||||
<div id="proactive-badge" class="proactive-badge">✨ Time for a vibe check?</div>
|
||||
|
||||
<!-- Welcome Overlay -->
|
||||
<div id="welcome-overlay" class="welcome-overlay">
|
||||
<div class="welcome-emoji">🧠</div>
|
||||
<h1 class="welcome-title">MindShift</h1>
|
||||
<p class="welcome-subtitle">Your personal CBT companion</p>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<header class="app-header">
|
||||
<div class="header-content">
|
||||
<div class="app-title">
|
||||
<span class="material-icons">self_improvement</span>
|
||||
MindShift
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<div style="position: relative;">
|
||||
<button class="nav-item" onclick="toggleNotifications()" style="color: white;">
|
||||
<span class="material-icons">notifications</span>
|
||||
<span id="notification-badge" class="notification-badge" style="display: none;">0</span>
|
||||
</button>
|
||||
<div id="notification-list" class="notification-dropdown">
|
||||
<!-- Notifications will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
<button class="nav-item" onclick="logout()" style="color: white;">
|
||||
<span class="material-icons">logout</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main id="main-content" class="main-content">
|
||||
<!-- Content will be dynamically loaded here -->
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<nav class="bottom-nav">
|
||||
<button class="nav-item active" onclick="navigateTo('home')">
|
||||
<span class="material-icons">home</span>
|
||||
<span class="nav-label">Home</span>
|
||||
</button>
|
||||
<button class="nav-item" onclick="navigateTo('mood')">
|
||||
<span class="material-icons">mood</span>
|
||||
<span class="nav-label">Mood</span>
|
||||
</button>
|
||||
<button class="nav-item" onclick="navigateTo('thoughts')">
|
||||
<span class="material-icons">psychology</span>
|
||||
<span class="nav-label">Thoughts</span>
|
||||
</button>
|
||||
<button class="nav-item" onclick="navigateTo('gratitude')">
|
||||
<span class="material-icons">favorite</span>
|
||||
<span class="nav-label">Gratitude</span>
|
||||
</button>
|
||||
<button class="nav-item" onclick="navigateTo('progress')">
|
||||
<span class="material-icons">insights</span>
|
||||
<span class="nav-label">Progress</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<!-- Floating Action Button -->
|
||||
<button class="fab" onclick="showQuickActionMenu()">
|
||||
<span class="material-icons">add</span>
|
||||
</button>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script type="module" src="api.js"></script>
|
||||
<script type="module" src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
348
MindShift-Windows/src/main.js
Normal file
348
MindShift-Windows/src/main.js
Normal file
@@ -0,0 +1,348 @@
|
||||
const { app, BrowserWindow, Menu, ipcMain, shell, dialog, nativeImage } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// Keep a global reference of the window object
|
||||
let mainWindow;
|
||||
let isDev = process.argv.includes('--dev');
|
||||
|
||||
// Windows 11 specific configurations
|
||||
const windowsConfig = {
|
||||
icon: path.join(__dirname, '../assets/icon.ico'),
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
frame: true,
|
||||
titleBarStyle: 'default',
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
// DPI and scaling optimizations
|
||||
backgroundColor: '#667eea',
|
||||
hasShadow: true,
|
||||
thickFrame: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
enableRemoteModule: false,
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
// Enable zoom and scaling
|
||||
zoomFactor: 1.0,
|
||||
// Better rendering
|
||||
experimentalFeatures: true,
|
||||
// Enable smooth scrolling
|
||||
smoothScroll: true
|
||||
}
|
||||
};
|
||||
|
||||
function createWindow() {
|
||||
// Get primary display for DPI scaling
|
||||
const { screen } = require('electron');
|
||||
const primaryDisplay = screen.getPrimaryDisplay();
|
||||
const scaleFactor = primaryDisplay.scaleFactor;
|
||||
|
||||
// Calculate window size based on DPI
|
||||
const baseWidth = 1200;
|
||||
const baseHeight = 800;
|
||||
const scaledWidth = Math.floor(baseWidth * scaleFactor);
|
||||
const scaledHeight = Math.floor(baseHeight * scaleFactor);
|
||||
|
||||
// Create the browser window with Windows 11 styling
|
||||
mainWindow = new BrowserWindow({
|
||||
...windowsConfig,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
// DPI awareness
|
||||
deviceScaleFactor: scaleFactor,
|
||||
webPreferences: {
|
||||
...windowsConfig.webPreferences,
|
||||
// Enable features for Windows 11
|
||||
experimentalFeatures: true,
|
||||
spellcheck: true,
|
||||
// DPI scaling
|
||||
zoomFactor: 1.0 / scaleFactor
|
||||
}
|
||||
});
|
||||
|
||||
// Load the app
|
||||
if (isDev) {
|
||||
// Load from dev server on port 12005 in development
|
||||
mainWindow.loadURL('http://localhost:12005');
|
||||
} else {
|
||||
// Load from local file in production
|
||||
mainWindow.loadFile('src/index.html');
|
||||
}
|
||||
|
||||
// Show window when ready to prevent visual flash
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.show();
|
||||
|
||||
// Focus the window on show
|
||||
if (isDev) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle window closed
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
// Set Windows 11 specific handlers
|
||||
setupWindowsHandlers();
|
||||
}
|
||||
|
||||
function setupWindowsHandlers() {
|
||||
// Handle Windows notifications
|
||||
mainWindow.webContents.on('did-finish-load', () => {
|
||||
// Inject Windows 11 specific styles
|
||||
mainWindow.webContents.insertCSS(`
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--surface: #1e1e1e;
|
||||
--surface-variant: #2d2d2d;
|
||||
--on-surface: #ffffff;
|
||||
--on-surface-variant: #cccccc;
|
||||
}
|
||||
}
|
||||
|
||||
/* Windows 11 specific styling */
|
||||
.titlebar {
|
||||
-webkit-app-region: drag;
|
||||
height: 32px;
|
||||
background: transparent;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.titlebar button {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
/* Windows 11 Mica effect (if supported) */
|
||||
@supports (backdrop-filter: blur(20px)) {
|
||||
.app-header {
|
||||
backdrop-filter: blur(20px) saturate(180%);
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.app-header {
|
||||
background: rgba(30, 30, 30, 0.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
// Handle external links
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
shell.openExternal(url);
|
||||
return { action: 'deny' };
|
||||
});
|
||||
}
|
||||
|
||||
// Create application menu for Windows
|
||||
function createMenu() {
|
||||
const template = [
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Export Data',
|
||||
accelerator: 'CmdOrCtrl+E',
|
||||
click: () => {
|
||||
mainWindow.webContents.send('export-data');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Import Data',
|
||||
accelerator: 'CmdOrCtrl+I',
|
||||
click: () => {
|
||||
mainWindow.webContents.send('import-data');
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Exit',
|
||||
accelerator: process.platform === 'win32' ? 'Alt+F4' : 'CmdOrCtrl+Q',
|
||||
click: () => {
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ role: 'undo', label: 'Undo' },
|
||||
{ role: 'redo', label: 'Redo' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'cut', label: 'Cut' },
|
||||
{ role: 'copy', label: 'Copy' },
|
||||
{ role: 'paste', label: 'Paste' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{ role: 'reload', label: 'Reload' },
|
||||
{ role: 'forceReload', label: 'Force Reload' },
|
||||
{ role: 'toggleDevTools', label: 'Toggle Developer Tools' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'resetZoom', label: 'Actual Size' },
|
||||
{ role: 'zoomIn', label: 'Zoom In' },
|
||||
{ role: 'zoomOut', label: 'Zoom Out' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'togglefullscreen', label: 'Toggle Fullscreen' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Window',
|
||||
submenu: [
|
||||
{ role: 'minimize', label: 'Minimize' },
|
||||
{ role: 'close', label: 'Close' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'About MindShift',
|
||||
click: () => {
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'info',
|
||||
title: 'About MindShift',
|
||||
message: 'MindShift CBT Therapy',
|
||||
detail: 'Version 1.0.0\n\nYour personal CBT therapy companion for mood management and mental wellness.\n\n© 2024 MindShift Team',
|
||||
buttons: ['OK']
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
||||
|
||||
// App event handlers
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
createMenu();
|
||||
|
||||
// Handle Windows 11 taskbar integration
|
||||
if (process.platform === 'win32') {
|
||||
app.setUserTasks([
|
||||
{
|
||||
program: process.execPath,
|
||||
arguments: '--quick-relax',
|
||||
title: 'Quick Relax',
|
||||
description: 'Start a quick relaxation exercise',
|
||||
iconPath: process.execPath,
|
||||
iconIndex: 0
|
||||
},
|
||||
{
|
||||
program: process.execPath,
|
||||
arguments: '--mood-check',
|
||||
title: 'Mood Check',
|
||||
description: 'Check your current mood',
|
||||
iconPath: process.execPath,
|
||||
iconIndex: 0
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Windows 11 notifications
|
||||
ipcMain.handle('show-notification', (event, title, body) => {
|
||||
const { Notification } = require('electron');
|
||||
|
||||
if (Notification.isSupported()) {
|
||||
const notification = new Notification({
|
||||
title: title,
|
||||
body: body,
|
||||
icon: path.join(__dirname, '../assets/icon.ico'),
|
||||
silent: false
|
||||
});
|
||||
|
||||
notification.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// Handle file operations
|
||||
ipcMain.handle('save-file', async (event, data, filename) => {
|
||||
try {
|
||||
const result = await dialog.showSaveDialog(mainWindow, {
|
||||
defaultPath: filename,
|
||||
filters: [
|
||||
{ name: 'JSON Files', extensions: ['json'] },
|
||||
{ name: 'CSV Files', extensions: ['csv'] },
|
||||
{ name: 'All Files', extensions: ['*'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
fs.writeFileSync(result.filePath, data);
|
||||
return { success: true, path: result.filePath };
|
||||
}
|
||||
|
||||
return { success: false, cancelled: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('open-file', async (event) => {
|
||||
try {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'JSON Files', extensions: ['json'] },
|
||||
{ name: 'CSV Files', extensions: ['csv'] },
|
||||
{ name: 'All Files', extensions: ['*'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
const data = fs.readFileSync(result.filePaths[0], 'utf8');
|
||||
return { success: true, data: data, path: result.filePaths[0] };
|
||||
}
|
||||
|
||||
return { success: false, cancelled: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// Handle protocol for deep linking
|
||||
app.setAsDefaultProtocolClient('mindshift');
|
||||
|
||||
// Handle Windows 11 system theme changes
|
||||
if (process.platform === 'win32') {
|
||||
const { systemPreferences } = require('electron');
|
||||
|
||||
systemPreferences.on('color-changed', () => {
|
||||
// Notify renderer about theme change
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('theme-changed', systemPreferences.isDarkMode());
|
||||
}
|
||||
});
|
||||
}
|
||||
70
MindShift-Windows/src/preload.js
Normal file
70
MindShift-Windows/src/preload.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
// Expose protected methods that allow the renderer process to use
|
||||
// the ipcRenderer without exposing the entire object
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
// Notification handling
|
||||
showNotification: (title, body) => ipcRenderer.invoke('show-notification', title, body),
|
||||
|
||||
// File operations
|
||||
saveFile: (data, filename) => ipcRenderer.invoke('save-file', data, filename),
|
||||
openFile: () => ipcRenderer.invoke('open-file'),
|
||||
|
||||
// Theme handling
|
||||
onThemeChange: (callback) => ipcRenderer.on('theme-changed', callback),
|
||||
|
||||
// Data export/import
|
||||
exportData: () => ipcRenderer.invoke('export-data'),
|
||||
importData: () => ipcRenderer.invoke('import-data'),
|
||||
|
||||
// App info
|
||||
getVersion: () => process.env.npm_package_version || '1.0.0',
|
||||
getPlatform: () => process.platform
|
||||
});
|
||||
|
||||
// Expose localStorage operations for persistence
|
||||
contextBridge.exposeInMainWorld('storage', {
|
||||
get: (key) => localStorage.getItem(key),
|
||||
set: (key, value) => localStorage.setItem(key, value),
|
||||
remove: (key) => localStorage.removeItem(key),
|
||||
clear: () => localStorage.clear(),
|
||||
getAll: () => {
|
||||
const data = {};
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
data[key] = localStorage.getItem(key);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
// Windows 11 specific API
|
||||
contextBridge.exposeInMainWorld('windowsAPI', {
|
||||
// Check if running on Windows
|
||||
isWindows: () => process.platform === 'win32',
|
||||
|
||||
// Get Windows version info
|
||||
getWindowsVersion: () => {
|
||||
if (process.platform === 'win32') {
|
||||
return {
|
||||
platform: 'win32',
|
||||
arch: process.arch,
|
||||
version: process.getSystemVersion?.() || 'Unknown'
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// Check for Windows 11 features
|
||||
supportsMica: () => {
|
||||
// Check if Windows 11 (build 22000+) for Mica effect
|
||||
if (process.platform === 'win32') {
|
||||
const version = process.getSystemVersion?.();
|
||||
if (version) {
|
||||
const buildNumber = parseInt(version.split('.')?.[2] || '0');
|
||||
return buildNumber >= 22000;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
674
MindShift-Windows/src/styles.css
Normal file
674
MindShift-Windows/src/styles.css
Normal file
@@ -0,0 +1,674 @@
|
||||
/* Base Styles & Variables */
|
||||
:root {
|
||||
--primary: #FF6B6B;
|
||||
--primary-light: #FF8E8E;
|
||||
--primary-dark: #E55555;
|
||||
--primary-container: #FFE5E5;
|
||||
--on-primary: #FFFFFF;
|
||||
--on-primary-container: #410002;
|
||||
--secondary: #FFB74D;
|
||||
--secondary-container: #FFF3E0;
|
||||
--on-secondary: #FFFFFF;
|
||||
--on-secondary-container: #4E2B00;
|
||||
--tertiary: #4FC3F7;
|
||||
--tertiary-container: #E1F5FE;
|
||||
--surface: rgba(255, 255, 255, 0.9);
|
||||
--surface-variant: #F5F5F5;
|
||||
--on-surface: #212121;
|
||||
--on-surface-variant: #757575;
|
||||
--outline: #BDBDBD;
|
||||
--shadow: rgba(0,0,0,0.1);
|
||||
--error: #FF5252;
|
||||
--success: #66BB6A;
|
||||
--warning: #FFA726;
|
||||
--joy: #AB47BC;
|
||||
--peace: #26A69A;
|
||||
--energy: #FFEE58;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #FF6B6B 100%);
|
||||
background-size: 400% 400%;
|
||||
animation: gradientBG 15s ease infinite;
|
||||
color: var(--on-surface);
|
||||
line-height: 1.5;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@keyframes gradientBG {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
/* Animated Background Orbs */
|
||||
.background-orbs {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(60px);
|
||||
opacity: 0.6;
|
||||
animation: orbFloat 20s infinite alternate cubic-bezier(0.45, 0.05, 0.55, 0.95);
|
||||
}
|
||||
|
||||
.orb-1 {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: var(--primary);
|
||||
top: -100px;
|
||||
left: -100px;
|
||||
animation-duration: 25s;
|
||||
}
|
||||
|
||||
.orb-2 {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: var(--secondary);
|
||||
bottom: -50px;
|
||||
right: -50px;
|
||||
animation-duration: 18s;
|
||||
animation-delay: -5s;
|
||||
}
|
||||
|
||||
.orb-3 {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
background: var(--tertiary);
|
||||
top: 40%;
|
||||
left: 60%;
|
||||
animation-duration: 22s;
|
||||
animation-delay: -10s;
|
||||
}
|
||||
|
||||
@keyframes orbFloat {
|
||||
0% { transform: translate(0, 0) rotate(0deg); }
|
||||
33% { transform: translate(50px, 100px) rotate(120deg); }
|
||||
66% { transform: translate(-50px, 50px) rotate(240deg); }
|
||||
100% { transform: translate(0, 0) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Welcome Overlay */
|
||||
.welcome-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #4ECDC4 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2000;
|
||||
color: white;
|
||||
animation: fadeOut 0.5s ease-out 2s forwards;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
animation: slideInUp 0.8s ease-out;
|
||||
}
|
||||
|
||||
.welcome-subtitle {
|
||||
font-size: 20px;
|
||||
opacity: 0.9;
|
||||
animation: slideInUp 0.8s ease-out 0.2s both;
|
||||
}
|
||||
|
||||
.welcome-emoji {
|
||||
font-size: 80px;
|
||||
margin-bottom: 24px;
|
||||
animation: bounce 1s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
to { opacity: 0; visibility: hidden; }
|
||||
}
|
||||
|
||||
@keyframes slideInUp {
|
||||
from { transform: translateY(30px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
|
||||
40% { transform: translateY(-30px); }
|
||||
60% { transform: translateY(-15px); }
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.app-header {
|
||||
background-color: rgba(255, 107, 107, 0.95);
|
||||
color: var(--on-primary);
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.app-header:hover {
|
||||
background-color: rgba(255, 107, 107, 1);
|
||||
box-shadow: 0 6px 24px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Notification Badge Pulse */
|
||||
.notification-badge {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background: var(--secondary);
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
padding: 2px 5px;
|
||||
border-radius: 10px;
|
||||
min-width: 16px;
|
||||
text-align: center;
|
||||
animation: pulseBadge 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulseBadge {
|
||||
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 183, 77, 0.7); }
|
||||
70% { transform: scale(1.1); box-shadow: 0 0 0 6px rgba(255, 183, 77, 0); }
|
||||
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 183, 77, 0); }
|
||||
}
|
||||
|
||||
.notification-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||||
padding: 16px;
|
||||
min-width: 300px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
animation: slideInDown 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@keyframes slideInDown {
|
||||
from { transform: translateY(-10px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 -4px 20px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 12px 0;
|
||||
z-index: 100;
|
||||
border-top-left-radius: 20px;
|
||||
border-top-right-radius: 20px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--on-surface-variant);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: var(--primary);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.nav-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: var(--primary);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 10px var(--primary);
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
color: var(--primary);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.nav-item .material-icons {
|
||||
font-size: 24px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-item:hover .material-icons {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
/* Main Content & Cards */
|
||||
.main-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
padding-bottom: 100px;
|
||||
min-height: calc(100vh - 120px);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 24px;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-8px) scale(1.01);
|
||||
box-shadow: 0 12px 40px rgba(0,0,0,0.2);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
color: var(--on-surface);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.card-title::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, var(--primary), transparent);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.4s ease;
|
||||
}
|
||||
|
||||
.card:hover .card-title::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
/* Mood Selector - Enhanced */
|
||||
.mood-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||||
gap: 16px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.mood-card {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mood-card:hover {
|
||||
transform: translateY(-8px) scale(1.05) rotate(2deg);
|
||||
box-shadow: 0 15px 35px rgba(0,0,0,0.2);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
.mood-card.selected {
|
||||
border-color: var(--primary);
|
||||
background: linear-gradient(135deg, var(--primary-container), white);
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 0 4px rgba(255, 107, 107, 0.2);
|
||||
}
|
||||
|
||||
.mood-emoji {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.mood-card:hover .mood-emoji {
|
||||
animation: moodBounce 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@keyframes moodBounce {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.4); }
|
||||
100% { transform: scale(1.2); }
|
||||
}
|
||||
|
||||
/* Buttons - Alive */
|
||||
.btn {
|
||||
padding: 16px 32px;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Shimmer Effect */
|
||||
.btn::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
.btn:hover::after {
|
||||
left: 100%;
|
||||
transition: 0.7s ease-in-out;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(45deg, var(--primary), var(--secondary));
|
||||
color: white;
|
||||
box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4);
|
||||
animation: breathBtn 3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes breathBtn {
|
||||
0% { transform: scale(1); box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4); }
|
||||
50% { transform: scale(1.02); box-shadow: 0 12px 24px rgba(255, 107, 107, 0.6); }
|
||||
100% { transform: scale(1); box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4); }
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-4px) scale(1.05);
|
||||
box-shadow: 0 15px 30px rgba(255, 107, 107, 0.5);
|
||||
animation: none; /* Stop breathing on hover to focus */
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(45deg, var(--tertiary), var(--peace));
|
||||
color: white;
|
||||
box-shadow: 0 8px 20px rgba(79, 195, 247, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
transform: translateY(-4px) scale(1.05);
|
||||
box-shadow: 0 15px 30px rgba(79, 195, 247, 0.5);
|
||||
}
|
||||
|
||||
/* Inputs & Sliders */
|
||||
.slider {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(0,0,0,0.1);
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.slider:hover {
|
||||
height: 10px;
|
||||
background: rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 10px rgba(255, 107, 107, 0.4);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.slider:hover::-webkit-slider-thumb {
|
||||
transform: scale(1.2);
|
||||
box-shadow: 0 6px 15px rgba(255, 107, 107, 0.6);
|
||||
}
|
||||
|
||||
textarea, input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
border: 2px solid rgba(0,0,0,0.1);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
font-size: 16px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
textarea:focus, input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 4px rgba(255, 107, 107, 0.1);
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
/* Proactive Badges */
|
||||
.proactive-badge {
|
||||
position: absolute;
|
||||
background: var(--joy);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4px 15px rgba(171, 71, 188, 0.4);
|
||||
animation: floatBadge 3s ease-in-out infinite;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.5s, transform 0.5s;
|
||||
}
|
||||
|
||||
.proactive-badge.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@keyframes floatBadge {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
/* Breathing Exercise - Super Alive */
|
||||
.breathing-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.breathing-circle {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 30% 30%, var(--tertiary), var(--primary));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 60px rgba(79, 195, 247, 0.4);
|
||||
position: relative;
|
||||
transition: all 4s cubic-bezier(0.45, 0.05, 0.55, 0.95);
|
||||
}
|
||||
|
||||
.breathing-circle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: -20px;
|
||||
right: -20px;
|
||||
bottom: -20px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
animation: ripple 4s infinite;
|
||||
}
|
||||
|
||||
.breathing-circle.inhale {
|
||||
transform: scale(1.5);
|
||||
box-shadow: 0 0 100px rgba(79, 195, 247, 0.6);
|
||||
filter: hue-rotate(30deg);
|
||||
}
|
||||
|
||||
.breathing-circle.exhale {
|
||||
transform: scale(0.8);
|
||||
box-shadow: 0 0 30px rgba(79, 195, 247, 0.2);
|
||||
filter: hue-rotate(0deg);
|
||||
}
|
||||
|
||||
.breathing-text {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
text-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
0% { transform: scale(0.8); opacity: 1; }
|
||||
100% { transform: scale(1.5); opacity: 0; }
|
||||
}
|
||||
|
||||
/* FAB - Alive */
|
||||
.fab {
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
right: 24px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 8px 25px rgba(255, 107, 107, 0.5);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.fab:hover {
|
||||
transform: scale(1.15) rotate(90deg);
|
||||
box-shadow: 0 12px 35px rgba(255, 107, 107, 0.6);
|
||||
}
|
||||
|
||||
.fab .material-icons {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
/* Success Ping */
|
||||
.success-ping {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.ping-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(102, 187, 106, 0.6);
|
||||
animation: pingExpand 1s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes pingExpand {
|
||||
0% { width: 0; height: 0; opacity: 1; }
|
||||
100% { width: 500px; height: 500px; opacity: 0; }
|
||||
}
|
||||
|
||||
/* Mobile Adaptation */
|
||||
@media (max-width: 480px) {
|
||||
.welcome-title { font-size: 36px; }
|
||||
.card { padding: 16px; border-radius: 20px; }
|
||||
.btn { padding: 14px 24px; width: 100%; }
|
||||
.mood-emoji { font-size: 40px; }
|
||||
.breathing-circle { width: 180px; height: 180px; }
|
||||
.bottom-nav { padding: 8px 0; }
|
||||
.nav-item { padding: 6px 12px; }
|
||||
.nav-label { font-size: 10px; }
|
||||
}
|
||||
|
||||
/* Desktop Sidebar */
|
||||
@media (min-width: 1024px) {
|
||||
.bottom-nav { display: none; }
|
||||
/* ... (keep existing desktop styles if needed, but adapt to new look) ... */
|
||||
}
|
||||
2899
backend/package-lock.json
generated
Normal file
2899
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
backend/package.json
Normal file
24
backend/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "mindshift-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend API for MindShift CBT Therapy App",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"sqlite3": "^5.1.6",
|
||||
"uuid": "^9.0.0",
|
||||
"helmet": "^7.0.0",
|
||||
"express-rate-limit": "^6.8.1",
|
||||
"dotenv": "^16.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
}
|
||||
}
|
||||
515
backend/server.js
Normal file
515
backend/server.js
Normal file
@@ -0,0 +1,515 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const path = require('path');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 12004;
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production';
|
||||
|
||||
// Middleware
|
||||
app.use(helmet());
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static(path.join(__dirname, '..')));
|
||||
|
||||
// Rate limiting
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100 // limit each IP to 100 requests per windowMs
|
||||
});
|
||||
app.use('/api/', limiter);
|
||||
|
||||
// Initialize SQLite database
|
||||
const db = new sqlite3.Database('./mindshift.db');
|
||||
|
||||
// Create tables
|
||||
db.serialize(() => {
|
||||
// Users table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login DATETIME
|
||||
)`);
|
||||
|
||||
// Mood tracking table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS mood_entries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
mood_type TEXT NOT NULL,
|
||||
intensity INTEGER NOT NULL,
|
||||
notes TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)`);
|
||||
|
||||
// Thoughts table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS thoughts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
situation TEXT NOT NULL,
|
||||
automatic_thought TEXT NOT NULL,
|
||||
emotion TEXT NOT NULL,
|
||||
emotion_intensity INTEGER NOT NULL,
|
||||
cognitive_distortion TEXT,
|
||||
alternative_thought TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)`);
|
||||
|
||||
// Gratitude entries table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS gratitude_entries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
entry TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)`);
|
||||
|
||||
// Progress tracking table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS progress (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
mood_score INTEGER,
|
||||
thoughts_count INTEGER DEFAULT 0,
|
||||
gratitude_count INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, date),
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)`);
|
||||
|
||||
// Notifications table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS notifications (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
type TEXT DEFAULT 'info',
|
||||
read BOOLEAN DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)`);
|
||||
});
|
||||
|
||||
// JWT authentication middleware
|
||||
function authenticateToken(req, res, next) {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Access token required' });
|
||||
}
|
||||
|
||||
jwt.verify(token, JWT_SECRET, (err, user) => {
|
||||
if (err) {
|
||||
return res.status(403).json({ error: 'Invalid token' });
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// Auth routes
|
||||
app.post('/api/auth/register', async (req, res) => {
|
||||
try {
|
||||
const { email, password, name } = req.body;
|
||||
|
||||
if (!email || !password || !name) {
|
||||
return res.status(400).json({ error: 'Email, password, and name are required' });
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
db.get('SELECT id FROM users WHERE email = ?', [email], async (err, user) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
if (user) {
|
||||
return res.status(400).json({ error: 'User already exists' });
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const saltRounds = 10;
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||
|
||||
// Insert new user
|
||||
db.run('INSERT INTO users (email, password, name) VALUES (?, ?, ?)',
|
||||
[email, hashedPassword, name], function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
// Generate JWT
|
||||
const token = jwt.sign(
|
||||
{ id: this.lastID, email, name },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
message: 'User created successfully',
|
||||
token,
|
||||
user: {
|
||||
id: this.lastID,
|
||||
email,
|
||||
name
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/auth/login', (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({ error: 'Email and password are required' });
|
||||
}
|
||||
|
||||
// Find user
|
||||
db.get('SELECT * FROM users WHERE email = ?', [email], async (err, user) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Check password
|
||||
const validPassword = await bcrypt.compare(password, user.password);
|
||||
if (!validPassword) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Update last login
|
||||
db.run('UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?', [user.id]);
|
||||
|
||||
// Generate JWT
|
||||
const token = jwt.sign(
|
||||
{ id: user.id, email: user.email, name: user.name },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
message: 'Login successful',
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get user profile
|
||||
app.get('/api/user/profile', authenticateToken, (req, res) => {
|
||||
db.get('SELECT id, email, name, created_at, last_login FROM users WHERE id = ?', [req.user.id], (err, user) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
res.json(user);
|
||||
});
|
||||
});
|
||||
|
||||
// Mood tracking
|
||||
app.post('/api/mood/track', authenticateToken, (req, res) => {
|
||||
try {
|
||||
const { moodType, intensity, notes } = req.body;
|
||||
|
||||
if (!moodType || !intensity || intensity < 1 || intensity > 10) {
|
||||
return res.status(400).json({ error: 'Invalid mood data' });
|
||||
}
|
||||
|
||||
// Insert mood entry
|
||||
db.run('INSERT INTO mood_entries (user_id, mood_type, intensity, notes) VALUES (?, ?, ?, ?)',
|
||||
[req.user.id, moodType, intensity, notes || null], function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
// Update or create progress entry for today
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
db.run(`INSERT OR REPLACE INTO progress (user_id, date, mood_score, thoughts_count, gratitude_count)
|
||||
VALUES (?, ?, COALESCE((SELECT mood_score FROM progress WHERE user_id = ? AND date = ?), ?),
|
||||
COALESCE((SELECT thoughts_count FROM progress WHERE user_id = ? AND date = ?), 0),
|
||||
COALESCE((SELECT gratitude_count FROM progress WHERE user_id = ? AND date = ?), 0))`,
|
||||
[req.user.id, today, req.user.id, today, intensity, req.user.id, today, req.user.id, today],
|
||||
function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Mood tracked successfully',
|
||||
entryId: this.lastID
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get mood history
|
||||
app.get('/api/mood/history', authenticateToken, (req, res) => {
|
||||
const limit = parseInt(req.query.limit) || 30;
|
||||
|
||||
db.all('SELECT * FROM mood_entries WHERE user_id = ? ORDER BY created_at DESC LIMIT ?',
|
||||
[req.user.id, limit], (err, entries) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.json(entries);
|
||||
});
|
||||
});
|
||||
|
||||
// Thought recording
|
||||
app.post('/api/thoughts', authenticateToken, (req, res) => {
|
||||
try {
|
||||
const { situation, automaticThought, emotion, emotionIntensity, cognitiveDistortion, alternativeThought } = req.body;
|
||||
|
||||
if (!situation || !automaticThought || !emotion || !emotionIntensity || emotionIntensity < 1 || emotionIntensity > 10) {
|
||||
return res.status(400).json({ error: 'Invalid thought data' });
|
||||
}
|
||||
|
||||
// Insert thought entry
|
||||
db.run('INSERT INTO thoughts (user_id, situation, automatic_thought, emotion, emotion_intensity, cognitive_distortion, alternative_thought) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[req.user.id, situation, automaticThought, emotion, emotionIntensity, cognitiveDistortion || null, alternativeThought || null], function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
// Update progress entry for today
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
db.run(`INSERT OR REPLACE INTO progress (user_id, date, mood_score, thoughts_count, gratitude_count)
|
||||
VALUES (?, ?, COALESCE((SELECT mood_score FROM progress WHERE user_id = ? AND date = ?), 0),
|
||||
COALESCE((SELECT thoughts_count FROM progress WHERE user_id = ? AND date = ?), 0) + 1,
|
||||
COALESCE((SELECT gratitude_count FROM progress WHERE user_id = ? AND date = ?), 0))`,
|
||||
[req.user.id, today, req.user.id, today, req.user.id, today, req.user.id, today],
|
||||
function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Thought recorded successfully',
|
||||
entryId: this.lastID
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get thoughts history
|
||||
app.get('/api/thoughts', authenticateToken, (req, res) => {
|
||||
const limit = parseInt(req.query.limit) || 30;
|
||||
|
||||
db.all('SELECT * FROM thoughts WHERE user_id = ? ORDER BY created_at DESC LIMIT ?',
|
||||
[req.user.id, limit], (err, entries) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.json(entries);
|
||||
});
|
||||
});
|
||||
|
||||
// Gratitude entries
|
||||
app.post('/api/gratitude', authenticateToken, (req, res) => {
|
||||
try {
|
||||
const { entry } = req.body;
|
||||
|
||||
if (!entry || entry.trim() === '') {
|
||||
return res.status(400).json({ error: 'Gratitude entry is required' });
|
||||
}
|
||||
|
||||
// Insert gratitude entry
|
||||
db.run('INSERT INTO gratitude_entries (user_id, entry) VALUES (?, ?)',
|
||||
[req.user.id, entry], function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
// Update progress entry for today
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
db.run(`INSERT OR REPLACE INTO progress (user_id, date, mood_score, thoughts_count, gratitude_count)
|
||||
VALUES (?, ?, COALESCE((SELECT mood_score FROM progress WHERE user_id = ? AND date = ?), 0),
|
||||
COALESCE((SELECT thoughts_count FROM progress WHERE user_id = ? AND date = ?), 0),
|
||||
COALESCE((SELECT gratitude_count FROM progress WHERE user_id = ? AND date = ?), 0) + 1)`,
|
||||
[req.user.id, today, req.user.id, today, req.user.id, today, req.user.id, today],
|
||||
function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Gratitude entry added successfully',
|
||||
entryId: this.lastID
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get gratitude entries
|
||||
app.get('/api/gratitude', authenticateToken, (req, res) => {
|
||||
const limit = parseInt(req.query.limit) || 30;
|
||||
|
||||
db.all('SELECT * FROM gratitude_entries WHERE user_id = ? ORDER BY created_at DESC LIMIT ?',
|
||||
[req.user.id, limit], (err, entries) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.json(entries);
|
||||
});
|
||||
});
|
||||
|
||||
// Get progress data
|
||||
app.get('/api/progress', authenticateToken, (req, res) => {
|
||||
const days = parseInt(req.query.days) || 30;
|
||||
|
||||
db.all('SELECT * FROM progress WHERE user_id = ? ORDER BY date DESC LIMIT ?',
|
||||
[req.user.id, days], (err, entries) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.json(entries);
|
||||
});
|
||||
});
|
||||
|
||||
// Get dashboard stats
|
||||
app.get('/api/dashboard/stats', authenticateToken, (req, res) => {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
// Get today's progress
|
||||
db.get('SELECT * FROM progress WHERE user_id = ? AND date = ?', [req.user.id, today], (err, todayProgress) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
// Get this week's mood entries
|
||||
const weekAgo = new Date();
|
||||
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||
const weekAgoStr = weekAgo.toISOString().split('T')[0];
|
||||
|
||||
db.all('SELECT AVG(intensity) as avgMood, COUNT(*) as count FROM mood_entries WHERE user_id = ? AND created_at >= ?',
|
||||
[req.user.id, weekAgoStr], (err, weekMood) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
// Get total counts
|
||||
db.get(`SELECT
|
||||
(SELECT COUNT(*) FROM mood_entries WHERE user_id = ?) as totalMoods,
|
||||
(SELECT COUNT(*) FROM thoughts WHERE user_id = ?) as totalThoughts,
|
||||
(SELECT COUNT(*) FROM gratitude_entries WHERE user_id = ?) as totalGratitude`,
|
||||
[req.user.id, req.user.id, req.user.id], (err, totals) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
today: todayProgress || { mood_score: 0, thoughts_count: 0, gratitude_count: 0 },
|
||||
week: {
|
||||
avgMood: weekMood[0].avgMood || 0,
|
||||
moodCount: weekMood[0].count || 0
|
||||
},
|
||||
totals: totals
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Notifications
|
||||
app.get('/api/notifications', authenticateToken, (req, res) => {
|
||||
db.all('SELECT * FROM notifications WHERE user_id = ? ORDER BY created_at DESC', [req.user.id], (err, notifications) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.json(notifications);
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/notifications', authenticateToken, (req, res) => {
|
||||
const { title, message, type } = req.body;
|
||||
|
||||
if (!title || !message) {
|
||||
return res.status(400).json({ error: 'Title and message are required' });
|
||||
}
|
||||
|
||||
db.run('INSERT INTO notifications (user_id, title, message, type) VALUES (?, ?, ?, ?)',
|
||||
[req.user.id, title, message, type || 'info'], function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
// Return the created notification
|
||||
db.get('SELECT * FROM notifications WHERE id = ?', [this.lastID], (err, notification) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.status(201).json(notification);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.put('/api/notifications/:id/read', authenticateToken, (req, res) => {
|
||||
const notificationId = req.params.id;
|
||||
|
||||
db.run('UPDATE notifications SET read = 1 WHERE id = ? AND user_id = ?',
|
||||
[notificationId, req.user.id], function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
if (this.changes === 0) {
|
||||
return res.status(404).json({ error: 'Notification not found' });
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
app.delete('/api/notifications/:id', authenticateToken, (req, res) => {
|
||||
const notificationId = req.params.id;
|
||||
|
||||
db.run('DELETE FROM notifications WHERE id = ? AND user_id = ?',
|
||||
[notificationId, req.user.id], function(err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
if (this.changes === 0) {
|
||||
return res.status(404).json({ error: 'Notification not found' });
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`MindShift server running on port ${PORT}`);
|
||||
});
|
||||
11
browserconfig.xml
Normal file
11
browserconfig.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square70x70logo src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHZpZXdCb3g9IjAgMCA3MCA3MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjcwIiBoZWlnaHQ9IjcwIiByeD0iMTAiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl8zXzMpIi8+CjxwYXRoIGQ9Ik0zNSAyMUMzMi45MjkgMjEgMzEuNSAyMi45MjkgMzEuNSAyNUMzMS41IDI3LjA3MSAzMi45MjkgMjkgMzUgMjlDMzcuMDcxIDI5IDM4LjUgMjcuMDcxIDM4LjUgMjVDMzguNSAyMi45MjkgMzcuMDcxIDIxIDM1IDIxWiIgZmlsbD0id2hpdGUiLz4KPHA+PC9wPgo8cGF0aCBkPSJNMzUgMzVDMzMuMDQzIDM1IDMxLjUgMzYuNTQzIDMxLjUgMzguNUMzMS41IDQwLjQ1NyAzMy4wNDMgNDIgMzUgNDJDMzYuOTU3IDQyIDM4LjUgNDAuNDU3IDM4LjUgMzguNUMzOC41IDM2LjU0MyAzNi45NTcgMzUgMzUgMzVaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudDBfbGluZWFyXzNfMyIgeDE9IjAiIHkxPSIwIiB4Mj0iNzAiIHkyPSI3MCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRkY2QjZCIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0ZGQjc0RCIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPg=="/>
|
||||
<square150x150logo src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUwIiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDE1MCAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNTAiIGhlaWdodD0iMTUwIiByeD0iMjAiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl80XzQpIi8+CjxwYXRoIGQ9Ik03NSA0NUM3MC41MTc5IDQ1IDY3IDQ4LjUxNzkgNjcgNTNDNjcgNTcuNDgyMSA3MC41MTc5IDYxIDc1IDYxQzc5LjQ4MjEgNjEgODMgNTcuNDgyMSA4MyA1M0M4MyA0OC41MTc5IDc5LjQ4MjEgNDUgNzUgNDVaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNjAgNzVDNjAuMTM2NyA3MC4yMzgzIDYyLjIzODIgNjUuNDc2NyA2NS4yMzIzIDYxLjgzMzNDNjguMjI2NCA1OC4xOSA3Mi4yMjMyIDU1Ljg5MjggNzYuNzYwNiA1NS44OTI4QzgxLjI5OCA1NS44OTI4IDg1LjI5NDYgNTguMTkgODguMjg4NyA2MS44MzMzQzkxLjI4MjggNjUuNDc2NyA5My4zNjMzIDcwLjIzODMgOTMuMzI1IDc1QzkzLjI4NjcgNzkuNzYxNyA5MS4yODI4IDg0LjUyMzMgODguMjg4NyA4OC4xNjY3Qzg1LjI5NDYgOTEuODEgODEuMjk4IDk0LjEwNzIgNzYuNzYwNiA5NC4xMDcyQzcyLjIyMzIgOTQuMTA3MiA2OC4yMjY0IDkxLjgxIDY1LjIzMjMgODguMTY2N0M2Mi4yMzgyIDg0LjUyMzMgNjAuMTM2NyA3OS43NjE3IDYwIDc1WiIgZmlsbD0id2hpdGUiLz4KPHA+PC9wPgo8cGF0aCBkPSJNNzUgMTA1QzY2LjcxNTcgMTA1IDYwIDk4LjI4NDMgNjAgOTBDNjAgODEuNzE1NyA2Ni43MTU3IDc1IDc1IDc1QzgzLjI4NDMgNzUgOTAgODEuNzE1NyA5MCA5MEM5MCA5OC4yODQzIDgzLjI4NDMgMTA1IDc1IDEwNVoiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9ImdyYWRpZW50MF9saW5lYXJfNF80IiB4MT0iMCIgeTE9IjAiIHgyPSIxNTAiIHkyPSIxNTAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZGNkI2QiIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRkI3NEQiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4="/>
|
||||
<square310x310logo src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzEwIiBoZWlnaHQ9IjMxMCIgdmlld0JveD0iMCAwIDMxMCAzMTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIzMTAiIGhlaWdodD0iMzEwIiByeD0iNDAiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl81XzUpIi8+CjxwYXRoIGQ9Ik0xNTUgOTNDMTQ2LjE5MyA5MyAxMzkgMTAwLjE5MyAxMzkgMTA5QzEzOSAxMTcuODA3IDE0Ni4xOTMgMTI1IDE1NSAxMjVDMTYzLjgwNyAxMjUgMTcxIDExNy44MDcgMTcxIDEwOUMxNzEgMTAwLjE5MyAxNjMuODA3IDkzIDE1NSA5M1oiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xMjQgMTU1QzEyNC4yNzMgMTQ1LjQ3NyAxMjguNDc2IDEzNS45NTMgMTM0LjQ2NSAxMjguNjY3QzE0MC40NTMgMTIxLjM4IDE0OC40NjQgMTE2Ljc4NiAxNTcuNTIxIDExNi43ODZDMTY2LjU3OCAxMTYuNzg2IDE3NC41ODkgMTIxLjM4IDE4MC41NzggMTI4LjY2N0MxODYuNTY2IDEzNS45NTMgMTkwLjcyNyAxNDUuNDc3IDE5MC42NSAxNTVDMTkwLjU3MyAxNjQuNTIzIDE4Ni41NjYgMTc0LjA0NyAxODAuNTc4IDE4MS4zMzNDMTc0LjU4OSAxODguNjIgMTY2LjU3OCAxOTMuMjE0IDE1Ny41MjEgMTkzLjIxNEMxNDguNDY0IDE5My4yMTQgMTQwLjQ1MyAxODguNjIgMTM0LjQ2NSAxODEuMzMzQzEyOC40NzYgMTc0LjA0NyAxMjQuMjczIDE2NC41MjMgMTI0IDE1NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNTUgMjEwQzEzMy40MzEgMjEwIDExNiAxOTIuNTY5IDExNiAxNzFDMTE2IDE0OS40MzEgMTMzLjQzMSAxMzIgMTU1IDEzMkMxNzYuNTY5IDEzMiAxOTQgMTQ5LjQzMSAxOTQgMTcxQzE5NCAxOTIuNTY5IDE3Ni41NjkgMjEwIDE1NSAyMTBaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudDBfbGluZWFyXzVfNSIgeDE9IjAiIHkxPSIwIiB4Mj0iMzEwIiB5Mj0iMzEwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRjZCNkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkZCNzREIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+"/>
|
||||
<TileColor>#FF6B6B</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
2651
cbt-therapy-app-mockup.html
Normal file
2651
cbt-therapy-app-mockup.html
Normal file
File diff suppressed because it is too large
Load Diff
27
manifest.json
Normal file
27
manifest.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "MindShift - CBT Therapy App",
|
||||
"short_name": "MindShift",
|
||||
"description": "Your personal CBT therapy companion for mood management and mental wellness",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#FFB74D",
|
||||
"theme_color": "#FF6B6B",
|
||||
"orientation": "portrait-primary",
|
||||
"icons": [
|
||||
{
|
||||
"src": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkyIiBoZWlnaHQ9IjE5MiIgdmlld0JveD0iMCAwIDE5MiAxOTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxOTIiIGhlaWdodD0iMTkyIiByeD0iMjQiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl8xXzEpIi8+CjxwYXRoIGQ9Ik05NiA0OEM5MC40NzcyIDQ4IDg2IDUyLjQ3NzIgODYgNjhDODYgNzMuNTIyOCA5MC40NzcyIDc4IDk2IDc4QzEwMS41MjIgNzggMTA2IDczLjUyMjggMTA2IDY4QzEwNiA2Mi40NzcyIDEwMS41MjIgNDggOTYgNDhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNzYgOTZDNzYuMDc0NSA5MC4zMTc3IDc4LjMxNzcgODQuNjQ1MiA4Mi4xODU0IDgwLjE4NTRDODYuMDUzMSA3NS43MjU2IDkxLjI1OTEgNzIuNjg5NyA5Ni43MjA5IDcyLjY4OTdDMTAyLjE4MiA3Mi42ODk3IDEwNy4zODkgNzUuNzI1NiAxMTEuMjU2IDgwLjE4NTRDMTE1LjEyNCA4NC42NDUyIDExNy4zOTUgOTAuMzE3NyAxMTcuMzU1IDk2QzExNy4zMTUgMTAxLjY4MiAxMTUuMDQ0IDEwNy4zNTUgMTExLjE3NiAxMTEuODE0QzEwNy4zMDkgMTE2LjI3NCAxMDIuMTgyIDExOS4zMSA5Ni43MjA5IDExOS4zMUM5MS4yNTkxIDExOS4zMSA4Ni4wNTMxIDExNi4yNzQgODIuMTg1NCAxMTEuODE0Qzc4LjMxNzcgMTA3LjM1NSA3Ni4wNzQ1IDEwMS42ODIgNzYgOTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOTYgMTI0Qzg4LjI2ODAxIDEyNCA4MiAxMTcuNzMyIDgyIDExMEM4MiAxMDIuMjY4IDg4LjI2ODAxIDk2IDk2IDk2QzEwMy43MzIgOTYgMTEwIDEwMi4yNjggMTEwIDExMEMxMTAgMTE3LjczMiAxMDMuNzMyIDEyNCA5NiAxMjRaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudDBfbGluZWFyXzFfMSIgeDE9IjAiIHkxPSIwIiB4Mj0iMTkyIiB5Mj0iMTkyIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRjZCNkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkZCNzREIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+",
|
||||
"sizes": "192x192",
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"src": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiByeD0iNjQiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl8yXzIpIi8+CjxwYXRoIGQ9Ik0yNTYgMTI4QzI0MS4yNTUgMTI4IDIyOSAxNDAuMjU1IDIyOSAxNTVDMjI5IDE2OS43NDUgMjQxLjI1NSAxODIgMjU2IDE4MkMyNzAuNzQ1IDE4MiAyODMgMTY5Ljc0NSAyODMgMTU1QzI4MyAxNDAuMjU1IDI3MC43NDUgMTI4IDI1NiAxMjhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjA0IDI1NkMyMDQuMTk4IDI0MS44MzYgMjA4LjgzNiAyMjcuNjcyIDIxNi40NjkgMjE1LjEyMkMyMjQuMTAyIDIwMi41NzIgMjM0LjMwOSAxOTIuMTg5IDI0Ni4yNTYgMTkyLjE4OUMyNTguMjAzIDE5Mi4xODkgMjY4LjQxIDIwMi41NzIgMjc2LjA0MyAyMTUuMTIyQzI4My42NzYgMjI3LjY3MiAyODguMzE0IDI0MS44MzYgMjg4LjI1NiAyNTZDMjg4LjE5OCAyNzAuMTY0IDI4My41NiAyODQuMzI4IDI3NS45MjcgMjk2Ljg3OEMyNjguMjk0IDMwOS40MjggMjU4LjA4NyAzMTkuODExIDI0Ni4xNCAzMTkuODExQzIzNC4xOTMgMzE5LjgxMSAyMjMuOTg2IDMwOS40MjggMjE2LjM1MyAyOTYuODc4QzIwOC43MiAyODQuMzI4IDIwNC4wODIgMjcwLjE2NCAyMDQgMjU2WiIgZmlsbD0id2hpdGUiLz4KPHA+PC9wPgo8cGF0aCBkPSJNMjU2IDMzMkMyMzUuMDg4IDMzMiAyMTggMzE0LjkxMiAyMTggMjk0QzIxOCAyNzMuMDg4IDIzNS4wODggMjU2IDI1NiAyNTZDMjc2LjkxMiAyNTYgMjk0IDI3My4wODggMjk0IDI5NEMyOTQgMzE0LjkxMiAyNzYuOTEyIDMzMiAyNTYgMzMyWiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQwX2xpbmVhcl8yXzIiIHgxPSIwIiB5MT0iMCIgeDI9IjUxMiIgeTI9IjUxMiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRkY2QjZCIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0ZGQjc0RCIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPg==",
|
||||
"sizes": "512x512",
|
||||
"type": "image/svg+xml"
|
||||
}
|
||||
],
|
||||
"categories": ["health", "fitness", "lifestyle"],
|
||||
"lang": "en",
|
||||
"dir": "ltr",
|
||||
"scope": "/",
|
||||
"prefer_related_applications": false
|
||||
}
|
||||
127
sw.js
Normal file
127
sw.js
Normal file
@@ -0,0 +1,127 @@
|
||||
const CACHE_NAME = 'mindshift-v1';
|
||||
const urlsToCache = [
|
||||
'/',
|
||||
'/cbt-therapy-app-mockup.html',
|
||||
'/manifest.json'
|
||||
];
|
||||
|
||||
// Install event - cache resources
|
||||
self.addEventListener('install', function(event) {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
.then(function(cache) {
|
||||
console.log('Opened cache');
|
||||
return cache.addAll(urlsToCache);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch event - serve from cache when offline
|
||||
self.addEventListener('fetch', function(event) {
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then(function(response) {
|
||||
// Cache hit - return response
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
// Clone the request
|
||||
var fetchRequest = event.request.clone();
|
||||
|
||||
return fetch(fetchRequest).then(
|
||||
function(response) {
|
||||
// Check if valid response
|
||||
if(!response || response.status !== 200 || response.type !== 'basic') {
|
||||
return response;
|
||||
}
|
||||
|
||||
// Clone the response
|
||||
var responseToCache = response.clone();
|
||||
|
||||
caches.open(CACHE_NAME)
|
||||
.then(function(cache) {
|
||||
cache.put(event.request, responseToCache);
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
).catch(function() {
|
||||
// Offline fallback for HTML pages
|
||||
if (event.request.destination === 'document') {
|
||||
return caches.match('/cbt-therapy-app-mockup.html');
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Activate event - clean up old caches
|
||||
self.addEventListener('activate', function(event) {
|
||||
event.waitUntil(
|
||||
caches.keys().then(function(cacheNames) {
|
||||
return Promise.all(
|
||||
cacheNames.map(function(cacheName) {
|
||||
if (cacheName !== CACHE_NAME) {
|
||||
console.log('Deleting old cache:', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Background sync for offline actions
|
||||
self.addEventListener('sync', function(event) {
|
||||
if (event.tag === 'background-sync') {
|
||||
event.waitUntil(doBackgroundSync());
|
||||
}
|
||||
});
|
||||
|
||||
function doBackgroundSync() {
|
||||
// Handle syncing offline data when back online
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Push notification handler
|
||||
self.addEventListener('push', function(event) {
|
||||
const options = {
|
||||
body: event.data ? event.data.text() : 'Time for your CBT exercise!',
|
||||
icon: '/data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkyIiBoZWlnaHQ9IjE5MiIgdmlld0JveD0iMCAwIDE5MiAxOTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxOTIiIGhlaWdodD0iMTkyIiByeD0iMjQiIGZpbGw9InVybCgjZ3JhZGllbnQwX2xpbmVhcl8xXzEpIi8+CjxwYXRoIGQ9Ik05NiA0OEM5MC40NzcyIDQ4IDg2IDUyLjQ3NzIgODYgNjhDODYgNzMuNTIyOCA5MC40NzcyIDc4IDk2IDc4QzEwMS41MjIgNzggMTA2IDczLjUyMjggMTA2IDY4QzEwNiA2Mi40NzcyIDEwMS41MjIgNDggOTYgNDhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNzYgOTZDNzYuMDc0NSA5MC4zMTc3IDc4LjMxNzcgODQuNjQ1MiA4Mi4xODU0IDgwLjE4NTRDODYuMDUzMSA3NS43MjU2IDkxLjI1OTEgNzIuNjg5NyA5Ni43MjA5IDcyLjY4OTdDMTAyLjE4MiA3Mi42ODk3IDEwNy4zODkgNzUuNzI1NiAxMTEuMjU2IDgwLjE4NTRDMTE1LjEyNCA4NC42NDUyIDExNy4zOTUgOTAuMzE3NyAxMTcuMzU1IDk2QzExNy4zMTUgMTAxLjY4MiAxMTUuMDQ0IDEwNy4zNTUgMTExLjE3NiAxMTEuODE0QzEwNy4zMDkgMTE2LjI3NCAxMDIuMTgyIDExOS4zMSA5Ni43MjA5IDExOS4zMUM5MS4yNTkxIDExOS4zMSA4Ni4wNTMxIDExNi4yNzQgODIuMTg1NCAxMTEuODE0Qzc4LjMxNzcgMTA3LjM1NSA3Ni4wNzQ1IDEwMS42ODIgNzYgOTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOTYgMTI0Qzg4LjI2ODAxIDEyNCA4MiAxMTcuNzMyIDgyIDExMEM4MiAxMDIuMjY4IDg4LjI2ODAxIDk2IDk2IDk2QzEwMy43MzIgOTYgMTEwIDEwMi4yNjggMTEwIDExMEMxMTAgMTE3LjczMiAxMDMuNzMyIDEyNCA5NiAxMjRaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudDBfbGluZWFyXzFfMSIgeDE9IjAiIHkxPSIwIiB4Mj0iMTkyIiB5Mj0iMTkyIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRjZCNkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkZCNzREIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+',
|
||||
badge: '/data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIHZpZXdCb3g9IjAgMCA5NiA5NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9Ijk2IiByeD0iMTIiIGZpbGw9IiNGRjZCNkIiLz4KPC9zdmc+',
|
||||
vibrate: [100, 50, 100],
|
||||
data: {
|
||||
dateOfArrival: Date.now(),
|
||||
primaryKey: 1
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
action: 'explore',
|
||||
title: 'Open App',
|
||||
icon: '/data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE2IDJMMjggMTZMMTYgMzBMMCAxNkwxNiAyWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+'
|
||||
},
|
||||
{
|
||||
action: 'close',
|
||||
title: 'Dismiss',
|
||||
icon: '/data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTI0IDhMMTcgMTVMMjQgMjJMMjIgMjRMMTUgMTdMOCAyNEwxMCAyMkwxNyAxNUwxMCA4TDEyIDZMMTkgMTNMMTIgNkwyNCA4WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification('MindShift', options)
|
||||
);
|
||||
});
|
||||
|
||||
// Notification click handler
|
||||
self.addEventListener('notificationclick', function(event) {
|
||||
event.notification.close();
|
||||
|
||||
if (event.action === 'explore') {
|
||||
// Open the app
|
||||
event.waitUntil(
|
||||
clients.openWindow('/')
|
||||
);
|
||||
}
|
||||
});
|
||||
90
test-api.js
Normal file
90
test-api.js
Normal file
@@ -0,0 +1,90 @@
|
||||
// Node.js 18+ has built-in fetch
|
||||
|
||||
async function testAPI() {
|
||||
try {
|
||||
// Test login (user already exists)
|
||||
console.log('Testing login...');
|
||||
const loginResponse = await fetch('http://localhost:12004/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: 'test@example.com',
|
||||
password: 'test123'
|
||||
})
|
||||
});
|
||||
|
||||
if (loginResponse.ok) {
|
||||
const loginData = await loginResponse.json();
|
||||
console.log('Login successful:', loginData);
|
||||
const token = loginData.token;
|
||||
|
||||
// Test mood tracking
|
||||
console.log('\nTesting mood tracking...');
|
||||
const moodResponse = await fetch('http://localhost:12004/api/mood/track', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
moodType: 'happy',
|
||||
intensity: 8,
|
||||
notes: 'Feeling good today!'
|
||||
})
|
||||
});
|
||||
|
||||
if (moodResponse.ok) {
|
||||
console.log('Mood tracking successful');
|
||||
} else {
|
||||
console.log('Mood tracking failed');
|
||||
}
|
||||
|
||||
// Test notifications
|
||||
console.log('\nTesting notifications...');
|
||||
const notifResponse = await fetch('http://localhost:12004/api/notifications', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: 'Test Notification',
|
||||
message: 'This is a test notification',
|
||||
type: 'info'
|
||||
})
|
||||
});
|
||||
|
||||
if (notifResponse.ok) {
|
||||
console.log('Notification creation successful');
|
||||
const notifData = await notifResponse.json();
|
||||
console.log('Notification data:', notifData);
|
||||
} else {
|
||||
console.log('Notification creation failed');
|
||||
const errorData = await notifResponse.json();
|
||||
console.log('Error:', errorData);
|
||||
}
|
||||
|
||||
// Get notifications
|
||||
const getNotifResponse = await fetch('http://localhost:12004/api/notifications', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (getNotifResponse.ok) {
|
||||
const notifications = await getNotifResponse.json();
|
||||
console.log('Notifications retrieved:', notifications.length);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('Registration failed');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('API test error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
testAPI();
|
||||
Reference in New Issue
Block a user