Commit c39af584 authored by Mahmoud Aglan's avatar Mahmoud Aglan

docs(proposal): add Nady El-Seid mobile-app + data-portal proposal microsite

Phase-1 scope only: membership renewals/installments, sports activity
invoices, QR gate entry + invitations. Interactive HTML prototypes for
8 mobile screens and 6 portal screens, 6 downloadable CSV import
templates, 12-week plan, 700,000 EGP commercial offer.

Deployed to CapRover as app 'sayd-mobile'.
Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
parent 01e3b4ef
FROM nginx:alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY . /usr/share/nginx/html
EXPOSE 80
{
"schemaVersion": 2,
"dockerfilePath": "./Dockerfile"
}
This diff is collapsed.
This diff is collapsed.
(function () {
'use strict';
/* ---------- scroll reveal ---------- */
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } });
}, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('.reveal').forEach(function (el) { io.observe(el); });
/* ---------- nav state + progress ---------- */
var nav = document.getElementById('nav');
var bar = document.getElementById('progress');
var links = Array.prototype.slice.call(document.querySelectorAll('#navlinks a'));
var targets = links.map(function (a) { return document.querySelector(a.getAttribute('href')); });
function onScroll() {
var y = window.scrollY;
nav.classList.toggle('stuck', y > 20);
var h = document.documentElement.scrollHeight - window.innerHeight;
bar.style.width = (h > 0 ? (y / h) * 100 : 0) + '%';
var active = -1;
targets.forEach(function (t, i) { if (t && t.getBoundingClientRect().top <= 140) active = i; });
links.forEach(function (a, i) { a.classList.toggle('on', i === active); });
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
/* ---------- QR filler (deterministic pseudo-random) ---------- */
document.querySelectorAll('[data-qr]').forEach(function (g) {
var n = 121, seed = 7;
var frag = document.createDocumentFragment();
for (var i = 0; i < n; i++) {
seed = (seed * 1103515245 + 12345) & 0x7fffffff;
var c = i % 11, r = Math.floor(i / 11);
// finder squares in three corners
var finder = (c < 3 && r < 3) || (c > 7 && r < 3) || (c < 3 && r > 7);
var on = finder ? !((c === 1 || c === 9) && (r === 1 || r === 9)) : (seed % 100) > 48;
var cell = document.createElement('i');
if (!on) cell.style.background = 'transparent';
frag.appendChild(cell);
}
g.appendChild(frag);
});
/* ---------- mobile screen switcher ---------- */
var NOTES = {
login: {
t: 'دخول بلا كلمة مرور',
p: 'العضو يدخل برقم موبايله المسجَّل في بيانات النادي فقط. لا حسابات تُنشأ ولا كلمات مرور تُنسى ولا موظف يفعّل الحساب يدويًا.',
b: ['التعرف الفوري على العضو من ملف الأعضاء المرفوع', 'كود تحقق OTP صالح لدقيقتين', 'جهاز واحد نشط لكل عضوية لمنع التداول']
},
home: {
t: 'كل ما يهم العضو في شاشة واحدة',
p: 'أول ما يراه العضو: هل عضويته سارية، وكم عليه، وبكم زر يسدّد. البطاقة الرقمية تغني عن الكارنيه الورقي.',
b: ['حالة العضوية وتاريخ سريانها', 'إجمالي المستحق مع تنبيه التأخير', 'اشتراكات الأبناء ورصيد الدعوات']
},
dues: {
t: 'شفافية كاملة في المستحقات',
p: 'كل بند مستحق بمفرده: عن أي شهر، ولماذا، ومتى يستحق. لا مبالغ مجمّعة غامضة تُشكِّك العضو وتؤخر السداد.',
b: ['تجديدات وأقساط وغرامات وفواتير أنشطة', 'سداد بند واحد أو عدة بنود معًا', 'أرشيف كامل للمسدَّد مع إيصالاته']
},
pay: {
t: 'التحصيل حيث يوجد العضو',
p: 'بطاقة أو محفظة إلكترونية &mdash; أو كود سداد يُقدَّم لخزنة النادي لمن يفضّل الدفع نقدًا. كل الطرق تُسجَّل في نفس التقرير.',
b: ['Visa و Mastercard و Meeza', 'فودافون كاش و إنستاباي', 'كود سداد للخزنة يربط الدفع النقدي بالنظام']
},
qr: {
t: 'البوابة تعرف من يدخل',
p: 'كود متجدد كل ٦٠ ثانية لا يمكن تصويره وإرساله لغير العضو. القارئ على البوابة يعرض فورًا حالة العضوية.',
b: ['منع الدخول عند إيقاف العضوية أو تجاوز حد التأخير', 'سجل دخول لحظي لكل بوابة', 'يعمل بلا إنترنت لدى العضو (كود دون اتصال)']
},
invites: {
t: 'الدعوات: انضباط وإيراد',
p: 'رصيد شهري واضح حسب نوع العضوية، وبيع منظّم للدعوات الإضافية بدل التقدير الشخصي على البوابة.',
b: ['رصيد مجاني يتجدد شهريًا', 'شراء دعوات إضافية بحد أقصى', 'ربط اختياري بين الدعوة وانتظام السداد']
},
sports: {
t: 'السعر معلن، والاشتراك بضغطة',
p: 'أكبر عائق أمام اشتراك الأنشطة هو أن ولي الأمر لا يعرف السعر ولا الأماكن المتاحة. الكتالوج يزيل العائق.',
b: ['أسعار وفئات سنية وعدد حصص معلنة', 'السعة المتاحة وقائمة انتظار عند الاكتمال', 'فاتورة شهرية تلقائية بعد الاشتراك']
},
schedule: {
t: 'الجدول من المدرب مباشرة',
p: 'يرفع المدرب جدول مجموعته من قالب موحّد، فيظهر لكل مشترك في تطبيقه &mdash; مع تنبيه فوري عند أي إلغاء أو تعويض.',
b: ['جدول أسبوعي لكل لاعب على حدة', 'ملاحظات المدرب على الحصة', 'إشعار فوري بالتغييرات والحصص التعويضية']
}
};
var noteBox = document.getElementById('scr-note');
function renderNote(k) {
var n = NOTES[k];
if (!n || !noteBox) return;
noteBox.innerHTML =
'<div class="card" style="border-color:var(--line-2);">' +
'<div class="card-ico">◆</div>' +
'<h4>' + n.t + '</h4>' +
'<p>' + n.p + '</p>' +
'<ul class="scope-list" style="margin-top:14px;">' +
n.b.map(function (x) { return '<li><span class="tick">✓</span><span>' + x + '</span></li>'; }).join('') +
'</ul>' +
'</div>';
}
var scrBtns = document.querySelectorAll('.pn-item[data-scr]');
scrBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
var k = btn.dataset.scr;
scrBtns.forEach(function (b) { b.classList.toggle('on', b === btn); });
document.querySelectorAll('.scr').forEach(function (s) {
s.style.display = (s.dataset.scr === k) ? 'flex' : 'none';
});
renderNote(k);
});
});
renderNote('login');
/* ---------- portal screen switcher ---------- */
var URLS = {
dash: 'https://portal.sayd-club.eg/dashboard',
import: 'https://portal.sayd-club.eg/import/members',
members: 'https://portal.sayd-club.eg/members',
catalog: 'https://portal.sayd-club.eg/activities/pricing',
invites: 'https://portal.sayd-club.eg/invitations/policy',
sched: 'https://portal.sayd-club.eg/schedules'
};
var burl = document.getElementById('burl');
var pBtns = document.querySelectorAll('.pn-item[data-pscr]');
var sideItems = document.querySelectorAll('.side .si[data-jump]');
function showPortal(k) {
pBtns.forEach(function (b) { b.classList.toggle('on', b.dataset.pscr === k); });
sideItems.forEach(function (s) { s.classList.toggle('on', s.dataset.jump === k); });
document.querySelectorAll('.pscr').forEach(function (s) {
s.style.display = (s.dataset.pscr === k) ? 'block' : 'none';
});
if (burl && URLS[k]) burl.textContent = URLS[k];
}
pBtns.forEach(function (b) { b.addEventListener('click', function () { showPortal(b.dataset.pscr); }); });
sideItems.forEach(function (s) { s.addEventListener('click', function () { showPortal(s.dataset.jump); }); });
})();
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
charset utf-8;
location / { try_files $uri $uri/ /index.html; }
location ~* \.csv$ {
add_header Content-Type "text/csv; charset=utf-8";
add_header Content-Disposition "attachment";
}
gzip on;
gzip_types text/plain text/css application/javascript text/csv image/svg+xml;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
}
"membership_no","full_name","national_id","mobile","membership_type","status","join_date","expiry_date","email"
"كود العضوية","الاسم الرباعي","الرقم القومي","رقم الموبايل","نوع العضوية","الحالة","تاريخ الانضمام","تاريخ انتهاء الاشتراك","البريد الإلكتروني"
"10245","محمد أحمد سيد إبراهيم","28503121201234","01001234567","عضو عامل","نشط","2011-03-15","2026-12-31","m.ahmed@example.com"
"10246","ياسمين خالد محمود علي","29907114500987","01112223344","عضو عامل","نشط","2015-08-02","2026-12-31",""
"10247","عمرو مصطفى فتحي حسن","30101010100111","01223334455","عضو منتسب","موقوف","2019-01-20","2026-06-30",""
"membership_no","due_type","description","period","amount","due_date","status"
"كود العضوية","نوع المستحق","الوصف","عن شهر / سنة","المبلغ","تاريخ الاستحقاق","الحالة"
"10245","اشتراك سنوي","تجديد العضوية 2026","2026","4500","2026-01-31","غير محصل"
"10245","قسط","القسط الأول من رسوم التجديد","2026-01","1500","2026-01-31","محصل"
"10245","قسط","القسط الثاني من رسوم التجديد","2026-02","1500","2026-02-28","غير محصل"
"10246","اشتراك سنوي","تجديد العضوية 2026","2026","4500","2026-01-31","غير محصل"
"10247","غرامة تأخير","غرامة تأخر السداد عن 2025","2025","350","2026-01-31","غير محصل"
"activity_code","activity_name","category","age_group","sessions_per_month","monthly_fee","joining_fee","capacity","coach_name"
"كود النشاط","اسم النشاط","الفئة","الفئة السنية","عدد الحصص شهريًا","الاشتراك الشهري","رسوم الانضمام","السعة","المدرب المسؤول"
"SW-01","سباحة مبتدئين","سباحة","6 - 9 سنوات","12","900","300","24","كابتن هشام صلاح"
"SW-02","سباحة متقدم","سباحة","10 - 14 سنة","16","1200","300","20","كابتن هشام صلاح"
"KR-01","كاراتيه مبتدئين","كاراتيه","6 - 12 سنة","8","700","250","30","كابتن أيمن رشدي"
"FB-01","كرة القدم — الموهبة","كرة القدم","8 - 11 سنة","12","1100","400","28","كابتن طارق منير"
"TN-01","تنس أرضي — أطفال","تنس","7 - 12 سنة","8","1400","350","12","كابتن نهى سامي"
"GY-01","جمباز فني — سيدات","جمباز","12 - 18 سنة","12","1000","300","18","كابتن دينا عادل"
"SQ-01","اسكواش — ناشئين","اسكواش","10 - 16 سنة","8","1300","350","10","كابتن كريم لطفي"
"membership_no","player_name","activity_code","group_name","start_date","end_date","monthly_fee","status"
"كود العضوية","اسم اللاعب","كود النشاط","اسم المجموعة","تاريخ البدء","تاريخ الانتهاء","الاشتراك الشهري","الحالة"
"10245","يوسف محمد أحمد","SW-01","مجموعة أ — صباحي","2026-09-01","2027-06-30","900","نشط"
"10246","لُجين خالد محمود","GY-01","مجموعة ب — مسائي","2026-09-01","2027-06-30","1000","نشط"
"10247","مالك عمرو مصطفى","FB-01","الموهبة 2016","2026-09-01","2027-06-30","1100","موقوف"
"membership_type","free_invites_per_month","extra_invite_price","max_extra_per_month","guest_child_price","valid_days"
"نوع العضوية","دعوات مجانية شهريًا","سعر الدعوة الإضافية","حد أقصى للشراء شهريًا","سعر دعوة طفل","صلاحية الدعوة (أيام)"
"عضو عامل","4","150","10","75","1"
"عضو منتسب","2","200","6","100","1"
"عضو فخري","8","0","0","0","1"
"عضو مؤقت","0","250","4","125","1"
"activity_code","group_name","weekday","start_time","end_time","venue","coach_name","notes"
"كود النشاط","اسم المجموعة","اليوم","من","إلى","المكان","المدرب","ملاحظات"
"SW-01","مجموعة أ — صباحي","السبت","09:00","10:00","حمام السباحة الأولمبي","كابتن هشام صلاح",""
"SW-01","مجموعة أ — صباحي","الاثنين","09:00","10:00","حمام السباحة الأولمبي","كابتن هشام صلاح",""
"SW-01","مجموعة أ — صباحي","الأربعاء","09:00","10:00","حمام السباحة الأولمبي","كابتن هشام صلاح","حصة قياس مستوى"
"GY-01","مجموعة ب — مسائي","الأحد","17:00","18:30","صالة الجمباز","كابتن دينا عادل",""
"GY-01","مجموعة ب — مسائي","الثلاثاء","17:00","18:30","صالة الجمباز","كابتن دينا عادل",""
"FB-01","الموهبة 2016","السبت","16:00","17:30","الملعب رقم 2","كابتن طارق منير",""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment