A mental health web application for Indonesian Gen Z university students. Part of the Vesya-Who project. RuangJujur provides a safe, anonymous space for students to express their feelings through AI-powered chat, mood tracking, and guided self-reflection.
Freemium model: Anonymous chat on the landing page serves as the entry point. Users are encouraged to register for full features including mood tracking, permanent chat history, and saved AI suggestions.
| Layer | Technology |
|---|---|
| Backend Framework | Laravel 13.x |
| Backend Language | PHP 8.3 |
| Frontend Library | React 18 |
| SPA Bridge | Inertia.js 2 |
| Styling | Tailwind CSS 4 |
| Build Tool | Vite 8 |
| AI / LLM API | Openclaw API (Llama 3.1 8B Instant) |
| Authentication | Laravel Breeze 2.4 + Sanctum 4.0 |
| Database (default) | SQLite |
| Database (optional) | MySQL, PostgreSQL, Redis |
| Testing | PHPUnit 12 |
| PHP Package Manager | Composer |
| JS Package Manager | npm |
| Linter | Laravel Pint |
| Queue Driver | database |
RuangJujur/
├── app/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── Auth/ # Breeze authentication controllers
│ │ │ ├── AnonymousChatController.php # Anonymous chat with AI
│ │ │ ├── Controller.php # Base controller
│ │ │ ├── MoodCanvasController.php # Mood tracking & visualization
│ │ │ └── ProfileController.php # User profile CRUD
│ │ ├── Middleware/
│ │ │ └── HandleInertiaRequests.php # Inertia.js middleware
│ │ └── Requests/
│ │ ├── Auth/
│ │ │ └── LoginRequest.php
│ │ └── ProfileUpdateRequest.php
│ ├── Models/
│ │ ├── AnonymousSession.php # Anonymous chat session (UUID-based)
│ │ ├── ChatMessage.php # Chat messages (user or AI)
│ │ ├── ChatSession.php # Registered user chat sessions
│ │ ├── SavedSuggestion.php # Bookmarked AI suggestions
│ │ └── User.php # User model
│ ├── Providers/
│ │ └── AppServiceProvider.php
│ └── Services/
│ ├── AnonymousNameGenerator.php # Nature-themed pseudonym generator
│ └── OpenClawApiService.php # AI API integration + crisis detection
│
├── bootstrap/
│ ├── app.php
│ ├── cache/
│ └── providers.php
│
├── config/
│ ├── app.php
│ ├── auth.php
│ ├── cache.php
│ ├── database.php
│ ├── filesystems.php
│ ├── logging.php
│ ├── mail.php
│ ├── queue.php
│ ├── services.php # Openclaw API configuration
│ └── session.php
│
├── database/
│ ├── factories/
│ │ └── UserFactory.php
│ ├── migrations/
│ │ ├── 0001_01_01_000000_create_users_table.php
│ │ ├── 0001_01_01_000001_create_cache_table.php
│ │ ├── 0001_01_01_000002_create_jobs_table.php
│ │ ├── 2026_07_06_035549_create_chat_sessions_table.php
│ │ ├── 2026_07_06_035550_create_chat_messages_table.php
│ │ ├── 2026_07_06_035551_create_saved_suggestions_table.php
│ │ └── 2026_07_06_042151_create_anonymous_sessions_table.php
│ └── seeders/
│ └── DatabaseSeeder.php
│
├── public/
│ ├── .htaccess
│ ├── favicon.ico
│ ├── index.php
│ └── robots.txt
│
├── resources/
│ ├── css/
│ │ └── app.css # Tailwind CSS entry point
│ ├── js/
│ │ ├── app.jsx # Inertia React entry point
│ │ ├── bootstrap.js
│ │ ├── Components/ # Reusable UI components (12 files)
│ │ │ ├── ApplicationLogo.jsx
│ │ │ ├── Checkbox.jsx
│ │ │ ├── DangerButton.jsx
│ │ │ ├── Dropdown.jsx
│ │ │ ├── InputError.jsx
│ │ │ ├── InputLabel.jsx
│ │ │ ├── Modal.jsx
│ │ │ ├── NavLink.jsx
│ │ │ ├── PrimaryButton.jsx
│ │ │ ├── ResponsiveNavLink.jsx
│ │ │ ├── SecondaryButton.jsx
│ │ │ └── TextInput.jsx
│ │ ├── Layouts/ # Page layout wrappers
│ │ │ ├── AppLayout.jsx
│ │ │ ├── AuthenticatedLayout.jsx
│ │ │ ├── GuestLayout.jsx
│ │ │ ├── PublicLayout.jsx # Public-facing layout (navbar + footer)
│ │ │ └── WorkspaceLayout.jsx # Authenticated user workspace (sidebar)
│ │ └── Pages/
│ │ ├── AIAssistant/Index.jsx # AI assistant (placeholder)
│ │ ├── AnonymousChat/Index.jsx # Anonymous chat UI
│ │ ├── Archive/Index.jsx # Chat archive (placeholder)
│ │ ├── Auth/ # Authentication pages
│ │ │ ├── ConfirmPassword.jsx
│ │ │ ├── ForgotPassword.jsx
│ │ │ ├── Login.jsx
│ │ │ ├── Register.jsx
│ │ │ ├── ResetPassword.jsx
│ │ │ └── VerifyEmail.jsx
│ │ ├── Dashboard/Index.jsx # User dashboard (placeholder)
│ │ ├── Landing/Index.jsx # Landing page (hero + features + testimonials)
│ │ ├── MoodCanvas/Index.jsx # Mood tracking (calendar + weekly graph)
│ │ └── Profile/
│ │ ├── Edit.jsx
│ │ └── Partials/
│ │ ├── DeleteUserForm.jsx
│ │ ├── UpdatePasswordForm.jsx
│ │ └── UpdateProfileInformationForm.jsx
│ └── views/
│ └── app.blade.php # Root Blade template (Inertia mount point)
│
├── routes/
│ ├── auth.php # Authentication routes (Breeze)
│ ├── console.php # Artisan CLI routes
│ └── web.php # Application routes
│
├── storage/ # Laravel storage (logs, cache, sessions)
├── tests/
│ ├── TestCase.php
│ ├── Feature/
│ │ ├── Auth/
│ │ ├── ExampleTest.php
│ │ └── ProfileTest.php
│ └── Unit/
│ └── ExampleTest.php
│
├── composer.json
├── package.json
├── vite.config.js
├── tailwind.config.js
├── phpunit.xml
├── jsconfig.json
└── .env.example
| Column | Type | Description |
|---|---|---|
| id | bigint (PK) | Auto-increment |
| name | string | User display name |
| string | Unique email | |
| email_verified_at | timestamp | Email verification time |
| password | string | Hashed password |
| remember_token | string | Remember me token |
| created_at | timestamp | |
| updated_at | timestamp |
| Column | Type | Description |
|---|---|---|
| id | uuid (PK) | Browser session token (UUID) |
| pseudonym | string | Nature-themed alias (e.g. "Bulan Tenang #456") |
| messages_count | integer | Messages sent (soft-limit 15) |
| status | enum | active / crisis |
| metadata | json | Stores chat_history array |
| created_at | timestamp | |
| updated_at | timestamp |
| Column | Type | Description |
|---|---|---|
| id | bigint (PK) | Auto-increment |
| user_id | bigint (FK) | References users (cascade) |
| title | string | Chat session title |
| is_archived | boolean | Archive status |
| created_at | timestamp | |
| updated_at | timestamp |
| Column | Type | Description |
|---|---|---|
| id | bigint (PK) | Auto-increment |
| chat_session_id | bigint (FK) | References chat_sessions (cascade) |
| is_user | boolean | true = user, false = AI |
| message | text | Message content |
| created_at | timestamp | |
| updated_at | timestamp |
| Column | Type | Description |
|---|---|---|
| id | bigint (PK) | Auto-increment |
| user_id | bigint (FK) | References users (cascade) |
| title | string | Suggestion title |
| category | string | Suggestion category |
| ai_response | text | Saved AI response |
| created_at | timestamp | |
| updated_at | timestamp |
| Method | Path | Controller | Middleware | Description |
|---|---|---|---|---|
| GET | / |
Landing/Index | guest | Landing page |
| POST | /anonymous-chat/send |
AnonymousChatController::sendMessage | guest | Send anonymous chat message |
| GET | /login |
AuthenticatedSessionController | guest | Login page |
| POST | /login |
AuthenticatedSessionController | guest | Process login |
| GET | /register |
RegisteredUserController | guest | Registration page |
| POST | /register |
RegisteredUserController | guest | Process registration |
| GET | /forgot-password |
PasswordResetLinkController | guest | Forgot password |
| POST | /forgot-password |
PasswordResetLinkController | guest | Send reset link |
| GET | /reset-password/{tkn} |
NewPasswordController | guest | Reset password form |
| POST | /reset-password |
NewPasswordController | guest | Process password reset |
| GET | /verify-email |
EmailVerificationPromptController | auth | Email verification prompt |
| GET | /verify-email/{id}/{h} |
VerifyEmailController | auth, signed | Process email verification |
| POST | /email/verif-notif |
EmailVerificationNotificationCtrl | auth, throttle | Resend verification email |
| GET | /confirm-password |
ConfirmablePasswordController | auth | Confirm password |
| POST | /confirm-password |
ConfirmablePasswordController | auth | Process confirmation |
| PUT | /password |
PasswordController | auth | Update password |
| POST | /logout |
AuthenticatedSessionController | auth | Logout |
| GET | /dashboard |
Dashboard/Index | auth, verified | User dashboard |
| GET | /mood-canvas |
MoodCanvasController::index | auth, verified | Mood Canvas (calendar + graph) |
| POST | /mood-canvas |
MoodCanvasController::store | auth, verified | Save daily mood log |
| GET | /archive |
Archive/Index | auth, verified | Chat session archive |
| GET | /ai-assistant |
AIAssistant/Index | auth, verified | AI Assistant |
- AI-powered chat using Llama 3.1 via Openclaw API
- Nature-themed pseudonym generator (e.g. "Ombak Lembut #742")
- Crisis detection for Indonesian self-harm keywords with emergency hotline referral (Kemenkes RI 119 ext 8)
- Soft limit of 15 messages per session to encourage registration
- Chat history stored in session metadata (no link to user identity)
- Daily mood logging with 5 mood types: senang, tenang, biasa, sedih, cemas
- Monthly calendar view with color-coded mood entries
- Weekly trend graph (SVG polyline visualization)
- Duplicate prevention (one log per day)
- Optional journal notes (max 300 characters)
- Hero section with social proof
- Feature showcase (bento grid layout)
- Anonymous testimonials from students
- Full Breeze authentication (register, login, password reset, email verification)
- Email verification required for authenticated features
-
Crisis Detection (Input Guardrail) — 8 Indonesian self-harm keywords are checked before sending messages to AI. If detected, the session is flagged as
crisis, an emergency response with hotline number is displayed, and the message is NOT sent to AI. -
Anonymity — Anonymous chat uses UUID tokens stored in browser sessions. No connection to user identity. Display names are random nature-themed pseudonyms.
-
Soft Limit — 15 messages per anonymous session. When reached, users are encouraged to register for permanent chat history.
-
System Prompt Guardrails — AI is instructed to:
- Use casual Indonesian language only
- Never provide medical diagnoses
- Never prescribe medication
- Limit responses to 2-3 sentences
- Use Reflective Listening technique
-
Email Verification — Required for all authenticated features (
verifiedmiddleware).
- PHP 8.3+
- Composer
- Node.js & npm
- SQLite (or MySQL/PostgreSQL)
-
Clone the repository:
git clone <repository-url> cd RuangJujur
-
Install PHP dependencies:
composer install
-
Install JavaScript dependencies:
npm install
-
Copy the environment file:
cp .env.example .env
-
Generate the application key:
php artisan key:generate
-
Configure your
.envfile:DB_CONNECTION=sqlite OPENCLAW_API_KEY=your_openclaw_api_key OPENCLAW_MODEL=your_model_name -
Run database migrations:
php artisan migrate
-
Seed the database (optional — creates a test user):
php artisan db:seed
-
Build frontend assets:
npm run dev
-
Start the development server:
php artisan serve
php artisan test./vendor/bin/pint-
Placeholder pages — Three pages are still minimal/empty:
AIAssistant/Index.jsxArchive/Index.jsxDashboard/Index.jsx
-
Registered user chat not implemented — Models, migrations, and route placeholders exist, but no controller or page connects registered users to the AI assistant.
-
Static calendar navigation — Prev/next month buttons in
MoodCanvas/Index.jsxhave no functional handlers.
This project is part of the Vesya-Who initiative.