Initial commit: habit tracker PWA with Google OAuth, push notifications

FastAPI + SQLAlchemy/Alembic + MariaDB backend with Jinja2/htmx/Alpine
server-rendered frontend. Multi-user via Google OAuth, daily habit
tracking, monthly/weekly history views, Web Push reminders via
APScheduler, and PWA support (manifest, service worker, offline caching).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 18:06:17 +09:00
co-authored by Claude Sonnet 5
commit cee589bb3e
80 changed files with 4695 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
<div
class="card"
x-data="{
open: false,
mask: 127,
alarmOn: false,
days: ['월', '화', '수', '목', '금', '토', '일'],
}"
>
<button type="button" class="btn btn-secondary btn-block" @click="open = !open">
<span x-show="!open">{{ '+ 멈추고 싶은 습관 추가' if habit_type == 'quit' else '+ 만들고 싶은 습관 추가' }}</span>
<span x-show="open" x-cloak>닫기</span>
</button>
<form
x-show="open"
x-cloak
style="margin-top: var(--space-2);"
hx-post="/habits/new"
hx-target="#habit-form-error-{{ habit_type }}"
hx-swap="innerHTML"
>
<input type="hidden" name="habit_type" value="{{ habit_type }}" />
<input type="hidden" name="weekdays_mask" :value="mask" />
<div class="field">
<label for="name-{{ habit_type }}">습관 이름</label>
<input type="text" id="name-{{ habit_type }}" name="name" required placeholder="{{ '예: 물 2L 마시기' if habit_type == 'build' else '예: 야식 끊기' }}" />
</div>
<div class="field">
<label for="condition-{{ habit_type }}">달성 조건 (선택)</label>
<input type="text" id="condition-{{ habit_type }}" name="condition_text" placeholder="{{ '예: 하루 8잔 이상' if habit_type == 'build' else '예: 주 3회 이하로 줄이기' }}" />
</div>
{% include "partials/_weekday_alarm_fields.html" %}
<div id="habit-form-error-{{ habit_type }}" class="form-error-text"></div>
<button type="submit" class="btn btn-primary btn-block">추가하기</button>
</form>
</div>