NBA Feed
팀 뉴스와 경기 정보를 제공, 점수는 직접 확인할 때까지 숨겨져 있습니다
App Store에서 Rayane Debbarh의 ScotSwim을 다운로드하고 스크린샷·평점·리뷰·사용자 팁 등을 확인하세요
Download ScotSwim by Rayane Debbarh on the App Store. See screenshots, ratings and reviews, user tips, and more apps like ScotSwim.
스포츠·피트니스JavaScript · HTMLGitHub @rayanedebbarh온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Full-stack iOS team management app for Alma College Swimming & Diving — live on the App Store, used daily by 31 athletes and 2 coaches. Built end-to-end in JavaScript with Firebase and Capacitor, replacing third-party tools the team relied on.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗ScotSwim was designed and built as the final project for a Data Engineering internship at Enopps — an IT consulting and engineering company based in Morocco, specializing in data solutions, software development, and digital transformation. The internship was hybrid, and the project was developed and shipped end-to-end over its course: from initial concept to a live App Store release used by a real team every day.
Before ScotSwim, the season ran across paper heat sheets, group texts, spreadsheets, and third-party results sites. Athletes often didn't know what practice was that day. Coaches had no single place to keep any of it.
ScotSwim is that single place.
| 🏠 Dashboard | Week's practice schedule, next meet countdown, personal best highlights, coach goals summary, and team announcements — all in one view |
| 📈 Progression | Log times and dive scores after every meet; the app builds a personal progression chart per event automatically, with improvement deltas against personal bests |
| 🏊 Meets | Full season meet calendar with locations and dates; view the official meet lineup posted by coaches, with your specific events highlighted |
| 📋 Meet Lineup | Coaches post the heat sheet (as images or PDF) with a note; every athlete sees their own events called out directly in the lineup |
| 🎯 Goals | View season goals and prescribed workouts set by the coach; track your own targets alongside them |
| 🏋️ Training Log | Log training volume — miles swum and time in the pool — and check off independent lifts from the coach's prescribed lift plan |
| 🤽 Diving | Diving-specific goal tracking, dive scores logged per meet, board assignments, and a progression chart built for dive scores rather than swim times |
| 🏆 Lane & Board Rivalry | Head-to-head competition block on the dashboard pairing you against a rival teammate — updated live as times and scores are logged |
| 👥 Team | Full roster with athlete profiles, stats, bio, and photo |
| 👤 My Profile | Personal stats (year, height, weight, hometown, strokes), bio, and profile photo with custom framing |
| 🔔 Notifications | Real-time push alerts for announcements, lineup posts, schedule changes, attendance marks, and new goals — with configurable quiet hours and a daily practice reminder |
| ⚙️ Settings | Control which notification types you receive, set quiet hours, pause alerts temporarily, and manage your account |
| 🏠 Dashboard | Season overview with the next meet, recent announcements, and quick access to all coaching tools |
| 🏊 Meets | Edit the full season meet calendar; post meet updates (bus time, warm-up instructions, what to wear) visible to the whole team |
| 📋 Meet Lineup | Upload the official heat sheet (photos or PDF) for the upcoming meet, add a coach note, and push a notification to the team — athletes see their own events highlighted |
| 📅 Training Schedule | Edit the weekly practice schedule (sessions, times, pool lanes) visible to all athletes |
| ✅ Attendance | Take practice attendance in seconds with present / excused / unexcused markings; athletes are notified of unexcused absences automatically |
| 📊 Lift Grid | Week-at-a-glance grid showing which athletes completed their independent lifts |
| 👥 Team & Roster | Manage the full team roster — add and remove athletes, view profiles, stats, and photos |
| 🎯 Goal Setting | Set individual season goals for each athlete and write a prescribed workout per goal; athletes see these alongside their own targets |
| 📢 Announcements & Workout Sheets | Post team-wide announcements and attach dated workout sheets for athletes to reference |
| 🤽 Diving | Full diving-specific section: dive scores, board assignments, diving goals, and progression — managed separately from swim data |
| 🧮 Meet Simulator | Project dual-meet scores against MIAA and Division III opponents event by event, using both teams' entry times — so coaches know the score before the bus leaves |
| ⚙️ Settings | Notification preferences and account management |
Access is invite-only. Every account comes from a coach-generated invite link bound to a specific email and roster spot. Nothing in the app is visible outside the team.
| Layer | Technology |
|---|---|
| Language | JavaScript — 100% of the codebase |
| UI Runtime | Custom component system with declarative templating, rendering directly to the DOM — no framework |
| Native Shell | Capacitor — wraps the web app in a WKWebView for iOS |
| Authentication | Firebase Authentication (email/password) |
| Database | Cloud Firestore — 11 collections, real-time listeners |
| Push Notifications | Firebase Cloud Messaging |
| OTA Updates | Capgo — ships bug fixes and UI changes without a store review cycle |
| CI/CD | GitHub Actions — iOS compile check, Android signed release build, OTA publish |
A single HTML file is the entire application. The UI layer is a custom declarative component runtime: components declare their output as a function of state, and the runtime diffs and patches the DOM. Capacitor wraps it in a native shell without requiring a rewrite — there is one copy of the source in the repo, and a sync script copies it into the native build so the shipped app and the hosted site can never drift apart.
| Collection | Holds | Written by |
|---|---|---|
users | Account profile: role, roster binding | Account owner |
invites | Outstanding invite tokens | Coaches |
roster | Team roster additions and removals | Coaches |
myEvents | Logged meet times and dive scores | Athletes |
trainingLog | Training volume and lift check-ins | Athletes |
goalWorkouts | Coach-prescribed workouts | Coaches |
training | Weekly practice schedule | Coaches |
schedule | Season meet calendar | Coaches |
attendance | Per-day attendance marks | Coaches |
announcements | Team announcements | Coaches |
workoutSheets | Dated workout sheet attachments | Coaches |
Per-athlete documents are keyed by roster identity, so ownership is a property of the document path — not something the client asserts.
Access control lives in Firestore security rules, not in client code. Two predicates do most of the work: isCoach() reads the caller's own profile to verify their role, and isSelf(rosterId) verifies the caller is bound to the document they're writing.
Nothing is stored that can be computed. Attendance rates, practice streaks (swim sessions only — a lift-only day neither extends nor breaks one), per-event improvement deltas against personal bests, weekly lift roll-ups, and the meet simulator's projected score are all derived at read time.
Safe-area insets returned zero.
env(safe-area-inset-*) reports 0 inside the Capacitor WKWebView, so the header sat under the status bar and the nav sat over the home indicator. Fixed by probing for a live value on mount and falling back to insets derived from screen dimensions, with a high-water mark so rotation never collapses the padding.
Sign-in took seconds — and it wasn't the network. Loading the dashboard fired a separate state update per athlete profile, re-rendering the whole component tree ~30 times before it settled. Batching those reads into a single flush cut it to 9 DOM mutations.
Account deletion silently did nothing. The profile document was deleted before the auth account, and a Firestore security rule blocked that delete — so the promise chain aborted and the account survived. Profile cleanup is now best-effort; the credential deletion runs regardless.
"No account found" on a valid address.
A race between Firebase Auth issuing a token and Firestore accepting it surfaced as permission-denied, which the app reported as a missing account. Transient codes are now classified and retried with exponential backoff — a wrong password still fails immediately rather than being masked by retries.
Notification vibration fired on every login. The notifications listener compared incoming data against the previous state — which on first load was always zero, so any existing notification triggered the haptic. Fixed with a connection-established flag that skips the first snapshot.
ScotSwim.dc.html the entire app — UI, state machine, Firestore wiring
privacy.html privacy policy (served via GitHub Pages)
photos/ athlete profile photos
mobile-app/
ios/ Xcode project (Capacitor shell)
android/ Android project (Capacitor shell)
scripts/sync-web.sh copies repo root into www/ before every cap sync
store-listing/
screenshots/ios/ App Store screenshots (iPhone 6.9")
app-store-listing.webp App Store page screenshot
listing.md store description copy and keyword list
.github/workflows/ CI: Android signed build, iOS compile check, OTA publish
Serve the repo root over HTTP and open ScotSwim.dc.html — there is no build step.
For the native builds:
cd mobile-app
npm install
npm run sync # copies web assets from the repo root, then runs cap sync
npx cap open ios # opens Xcode
Firebase credentials are the project's public web config. Access is enforced by Firestore security rules, not by keeping the config secret.