Compare commits

...

5 Commits

Author SHA1 Message Date
0ca694ca99 loading entries 2026-03-31 14:16:38 +05:30
d8f90c7d6c Update icon.svg 2026-03-31 12:44:21 +05:30
b86f02699e Create CICD_SETUP.md 2026-03-31 12:31:20 +05:30
d1800b4888 profile settings 2026-03-31 11:54:39 +05:30
2defb7c02f Update App.css 2026-03-31 11:43:09 +05:30
6 changed files with 170 additions and 29 deletions

133
CICD_SETUP.md Normal file
View File

@@ -0,0 +1,133 @@
# CI/CD Setup — Gitea Actions (Auto Deploy)
This doc covers how to set up automatic deployment to your production server whenever you push to `main`. The deploy runs `deploy.sh` (`git pull && docker-compose down && docker-compose up -d --build`).
---
## Prerequisites
### On Gitea
- Gitea Actions must be enabled (check Site Administration → Configuration)
- At least one Actions runner must be registered and online
### On the Production Server
- Docker and docker-compose installed
- The repo already cloned at a known path
- SSH access configured
---
## Step 1 — Install a Gitea Actions Runner
If you don't already have a runner:
1. Download the runner binary from your Gitea instance or from the Gitea releases page
2. Register it:
```
./act_runner register --instance https://your-gitea-url --token <runner-token>
```
Get the token from: Gitea → Site Administration → Runners → Create Runner
3. Start the runner:
```
./act_runner daemon
```
Consider running it as a systemd service so it survives reboots.
---
## Step 2 — Set Up SSH Key for Deployment
On your local machine or CI machine, generate a dedicated deploy key:
```bash
ssh-keygen -t ed25519 -C "gitea-deploy" -f ~/.ssh/gitea_deploy
```
Copy the **public key** (`gitea_deploy.pub`) to your production server:
```bash
ssh-copy-id -i ~/.ssh/gitea_deploy.pub user@your-server
```
Or manually append it to `~/.ssh/authorized_keys` on the server.
---
## Step 3 — Add Secrets in Gitea
Go to: your repo → Settings → Secrets → Add Secret
| Secret Name | Value |
|------------------|--------------------------------------------|
| `DEPLOY_HOST` | IP address or hostname of your server |
| `DEPLOY_USER` | SSH username (e.g. `ubuntu`, `root`) |
| `DEPLOY_SSH_KEY` | Full contents of the **private** key file |
| `DEPLOY_PORT` | SSH port (default: `22`) |
---
## Step 4 — Create the Workflow File
Create `.gitea/workflows/deploy.yml` in the repo root:
```yaml
name: Deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: ${{ secrets.DEPLOY_PORT }}
script: |
cd /path/to/grateful-journal # <-- update this path
bash deploy.sh
```
Update the `cd` path to wherever the repo lives on your server.
---
## Alternative — Runner Running Directly on the Server
If your Gitea Actions runner is already installed on the production server itself, you can skip SSH entirely and simplify the workflow:
```yaml
name: Deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Run deploy script
run: |
cd /path/to/grateful-journal # <-- update this path
bash deploy.sh
```
This is simpler and avoids managing SSH keys.
---
## Verifying It Works
1. Push a commit to `main`
2. Go to your repo → Actions tab in Gitea
3. You should see the workflow run and each step's log output
If the runner isn't picking up jobs, check that the runner is online in Site Administration → Runners.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -860,12 +860,11 @@
}
.bottom-nav-avatar {
width: 26px;
height: 26px;
width: 22px;
height: 22px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
border: 2px solid transparent;
}
.bottom-nav-avatar-placeholder {
@@ -879,7 +878,8 @@
}
.bottom-nav-btn-active .bottom-nav-avatar {
border-color: currentColor;
outline: 2px solid currentColor;
outline-offset: 1px;
}
.bottom-nav-btn span {
@@ -2167,8 +2167,8 @@
}
.bottom-nav-avatar {
width: 31px;
height: 31px;
width: 22px;
height: 22px;
}
/* Active pill keeps green but full-width */

View File

@@ -1,4 +1,5 @@
import { useNavigate, useLocation } from 'react-router-dom'
import { useState } from 'react'
import { useAuth } from '../contexts/AuthContext'
export default function BottomNav() {
@@ -6,7 +7,9 @@ export default function BottomNav() {
const location = useLocation()
const { user, mongoUser } = useAuth()
const displayName = mongoUser?.displayName || user?.displayName || 'U'
const photoURL = (mongoUser && 'photoURL' in mongoUser) ? (mongoUser.photoURL || null) : (user?.photoURL || null)
const mongoPhoto = mongoUser && 'photoURL' in mongoUser ? mongoUser.photoURL : null
const photoURL = (mongoPhoto?.startsWith('data:')) ? mongoPhoto : (user?.photoURL || null)
const [imgError, setImgError] = useState(false)
const isActive = (path: string) => location.pathname === path
@@ -54,8 +57,8 @@ export default function BottomNav() {
onClick={() => navigate('/settings')}
aria-label="Settings"
>
{photoURL ? (
<img src={photoURL} alt={displayName} className="bottom-nav-avatar" />
{photoURL && !imgError ? (
<img src={photoURL} alt={displayName} className="bottom-nav-avatar" onError={() => setImgError(true)} />
) : (
<div className="bottom-nav-avatar bottom-nav-avatar-placeholder">
{displayName.charAt(0).toUpperCase()}

View File

@@ -270,9 +270,7 @@ export default function HistoryPage() {
</h3>
{loadingEntries ? (
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.875rem', textAlign: 'center', padding: '1.5rem 0', fontFamily: '"Sniglet", system-ui' }}>
Loading entries
</p>
<PageLoader />
) : (
<div className="entries-list">
{selectedDateEntries.length === 0 ? (

View File

@@ -77,8 +77,9 @@ export default function SettingsPage() {
}
const displayName = mongoUser?.displayName || user?.displayName || 'User'
// Prefer mongo photo; only fall back to Google photo if mongo has no photo set
const photoURL = (mongoUser && 'photoURL' in mongoUser) ? (mongoUser.photoURL || null) : (user?.photoURL || null)
// Use custom uploaded photo (base64) if set, otherwise always use Firebase's fresh Google URL
const mongoPhoto = mongoUser && 'photoURL' in mongoUser ? mongoUser.photoURL : null
const photoURL = (mongoPhoto?.startsWith('data:')) ? mongoPhoto : (user?.photoURL || null)
const openEditModal = () => {
setEditName(displayName)
@@ -206,9 +207,11 @@ export default function SettingsPage() {
<main className="settings-container">
{/* Profile Section */}
<div id="tour-edit-profile" className="settings-profile">
<div className="settings-avatar">
<div className="settings-avatar" onClick={openEditModal} style={{ cursor: 'pointer' }}>
{photoURL ? (
<img src={photoURL} alt={displayName} className="settings-avatar-img" />
<img src={photoURL} alt={displayName} className="settings-avatar-img"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
/>
) : (
<div className="settings-avatar-placeholder" style={{ fontSize: '1.75rem' }}>
{displayName.charAt(0).toUpperCase()}
@@ -465,9 +468,18 @@ export default function SettingsPage() {
<div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
<h3 className="edit-modal-title">Edit Profile</h3>
<div className="edit-modal-avatar" onClick={() => fileInputRef.current?.click()}>
<label className="edit-modal-avatar" style={{ cursor: 'pointer' }}>
<input
ref={fileInputRef}
type="file"
accept="image/*"
style={{ display: 'none' }}
onChange={handlePhotoSelect}
/>
{editPhotoPreview ? (
<img src={editPhotoPreview} alt="Preview" className="edit-modal-avatar-img" />
<img src={editPhotoPreview} alt="Preview" className="edit-modal-avatar-img"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
/>
) : (
<div className="edit-modal-avatar-placeholder">
{editName.charAt(0).toUpperCase() || 'U'}
@@ -479,14 +491,7 @@ export default function SettingsPage() {
<circle cx="12" cy="13" r="4" />
</svg>
</div>
<input
ref={fileInputRef}
type="file"
accept="image/*"
style={{ display: 'none' }}
onChange={handlePhotoSelect}
/>
</div>
</label>
{editPhotoPreview && (
<button
type="button"