Autosave: 20260218-135400

This commit is contained in:
Flatlogic Bot 2026-02-18 13:54:00 +00:00
parent c0bb59aeba
commit e38c53b0c9
7 changed files with 338 additions and 6 deletions

View File

@ -166,6 +166,7 @@
</div>
</div>
<div class="call-controls">
<div id="callStatus" class="position-absolute top-0 start-50 translate-middle-x mt-3 text-white-50 small">Connecting...</div>
<button id="toggleMic" class="btn btn-outline-light rounded-circle d-flex align-items-center justify-content-center" style="width: 45px; height: 45px;"><i class="bi bi-mic-fill"></i></button>
<button id="toggleVideo" class="btn btn-outline-light rounded-circle d-flex align-items-center justify-content-center" style="width: 45px; height: 45px;"><i class="bi bi-camera-video-fill"></i></button>
<button id="endCall" class="btn btn-danger rounded-pill px-4 fw-bold">{% trans "End Call" %}</button>
@ -175,6 +176,14 @@
</div>
</div>
<!-- Audio Assets -->
<audio id="incomingRing" loop>
<source src="https://assets.mixkit.co/active_storage/sfx/2358/2358-preview.mp3" type="audio/mpeg">
</audio>
<audio id="outgoingRing" loop>
<source src="https://assets.mixkit.co/active_storage/sfx/1359/1359-preview.mp3" type="audio/mpeg">
</audio>
<!-- Incoming Call UI -->
<div id="incomingCallUI" class="position-fixed top-0 start-50 translate-middle-x mt-4 glass-card p-3 shadow-lg border-danger animate__animated animate__fadeInDown" style="display: none; z-index: 9999; min-width: 320px; border: 2px solid var(--pulse-red) !important;">
<div class="d-flex align-items-center gap-3">
@ -208,8 +217,17 @@
// Sanitize username for PeerJS ID (only alphanumeric, -, _)
const sanitizeId = (id) => id.replace(/[^a-zA-Z0-9-_]/g, '_');
// Initialize PeerJS
// Initialize PeerJS with STUN servers for reliability
let peer = new Peer(PEER_ID_PREFIX + sanitizeId(MY_USERNAME), {
config: {
'iceServers': [
{ url: 'stun:stun.l.google.com:19302' },
{ url: 'stun:stun1.l.google.com:19302' },
{ url: 'stun:stun2.l.google.com:19302' },
{ url: 'stun:stun3.l.google.com:19302' },
{ url: 'stun:stun4.l.google.com:19302' },
]
},
debug: 1
});
@ -221,7 +239,22 @@
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');
const remoteVideoWrapper = document.getElementById('remoteVideoWrapper');
const callStatus = document.getElementById('callStatus');
const incomingRing = document.getElementById('incomingRing');
const outgoingRing = document.getElementById('outgoingRing');
function playRingtone(type) {
if (type === 'incoming') incomingRing.play().catch(e => console.log('Audio blocked'));
if (type === 'outgoing') outgoingRing.play().catch(e => console.log('Audio blocked'));
}
function stopRingtones() {
incomingRing.pause();
incomingRing.currentTime = 0;
outgoingRing.pause();
outgoingRing.currentTime = 0;
}
// Handle Peer Open
peer.on('open', (id) => {
console.log('My peer ID is: ' + id);
@ -230,15 +263,16 @@
// Handle incoming calls
peer.on('call', (call) => {
console.log('Incoming call from: ' + call.peer);
// Only accept if it's the other user we are chatting with
if (call.peer === PEER_ID_PREFIX + sanitizeId(OTHER_USERNAME)) {
// If already in a call, busy logic could go here
currentCall = call;
incomingCallUI.style.display = 'block';
playRingtone('incoming');
// Auto-hide incoming call after 30 seconds if not answered
setTimeout(() => {
if (incomingCallUI.style.display === 'block') {
incomingCallUI.style.display = 'none';
stopRingtones();
}
}, 30000);
}
@ -247,6 +281,7 @@
// Start Call Function
async function startCall(videoEnabled = true) {
try {
callStatus.innerText = "Requesting permissions...";
localStream = await navigator.mediaDevices.getUserMedia({
video: videoEnabled,
audio: true
@ -254,12 +289,14 @@
localVideo.srcObject = localStream;
callModal.show();
callStatus.innerText = "Calling...";
playRingtone('outgoing');
const call = peer.call(PEER_ID_PREFIX + sanitizeId(OTHER_USERNAME), localStream);
handleCall(call);
} catch (err) {
console.error('Failed to get local stream', err);
alert('Could not access camera or microphone. Please ensure you have given permission.');
alert('Could not access camera or microphone.');
}
}
@ -267,6 +304,8 @@
currentCall = call;
call.on('stream', (remoteStream) => {
console.log('Received remote stream');
stopRingtones();
callStatus.innerText = "Connected";
remoteVideo.srcObject = remoteStream;
remoteVideoWrapper.style.display = 'block';
});
@ -275,6 +314,7 @@
});
call.on('error', (err) => {
console.error('Call error:', err);
stopRingtones();
endCall();
});
}
@ -285,7 +325,9 @@
document.getElementById('acceptCall').addEventListener('click', async () => {
incomingCallUI.style.display = 'none';
stopRingtones();
try {
callStatus.innerText = "Answering...";
localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
@ -303,6 +345,7 @@
document.getElementById('rejectCall').addEventListener('click', () => {
incomingCallUI.style.display = 'none';
stopRingtones();
if (currentCall) currentCall.close();
});
@ -311,6 +354,7 @@
});
function endCall() {
stopRingtones();
if (currentCall) currentCall.close();
if (localStream) {
localStream.getTracks().forEach(track => track.stop());

View File

@ -0,0 +1,281 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Welcome to RaktaPulse{% endblock %}
{% block extra_css %}
<style>
:root {
--primary-red: #e63946;
--dark-red: #c1121f;
--white: #f1faee;
}
.welcome-body {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
overflow-x: hidden;
margin-left: -48px; /* Offset the parent padding */
margin-right: -48px;
margin-top: -48px;
margin-bottom: -48px;
}
/* Hide Sidebar and Top Bar on Welcome Page */
#sidebar { display: none !important; }
#content { margin-left: 0 !important; width: 100% !important; }
.top-bar { display: none !important; }
#sosButton { display: none !important; }
.p-4.p-md-5 { padding: 0 !important; }
.hero-section {
min-height: 80vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
padding: 100px 0;
}
.hero-content {
opacity: 0;
transform: translateY(30px);
animation: fadeInUp 1s ease forwards;
}
.hero-image {
opacity: 0;
transform: scale(0.8);
animation: zoomIn 1.2s ease forwards 0.3s;
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes zoomIn {
to {
opacity: 1;
transform: scale(1);
}
}
.floating-icon {
animation: float 3s ease-in-out infinite;
color: var(--primary-red);
}
@keyframes float {
0% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
100% { transform: translateY(0px); }
}
.btn-hero {
padding: 15px 40px;
font-size: 1.2rem;
border-radius: 50px;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 700;
}
.btn-hero-primary {
background-color: var(--primary-red);
border: none;
color: white;
box-shadow: 0 10px 20px rgba(230, 57, 70, 0.3);
}
.btn-hero-primary:hover {
background-color: var(--dark-red);
transform: translateY(-5px);
box-shadow: 0 15px 30px rgba(230, 57, 70, 0.4);
color: white;
}
.feature-card {
border: none;
border-radius: 20px;
transition: all 0.3s ease;
background: white;
padding: 30px;
height: 100%;
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
}
.feature-card:hover {
transform: translateY(-10px);
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
}
.feature-icon {
font-size: 2.5rem;
margin-bottom: 20px;
background: linear-gradient(45deg, var(--primary-red), #ff4d6d);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.pulse-animation {
animation: pulse-red 2s infinite;
}
@keyframes pulse-red {
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(230, 57, 70, 0.7); }
70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(230, 57, 70, 0); }
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(230, 57, 70, 0); }
}
.stats-section {
background: var(--primary-red);
color: white;
padding: 60px 0;
}
.stat-number {
font-size: 3rem;
font-weight: 800;
}
</style>
{% endblock %}
{% block content %}
<div class="welcome-body">
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-light bg-white py-3 shadow-sm sticky-top">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="#">
<div class="bg-danger rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 35px; height: 35px;">
<i class="bi bi-droplet-fill text-white"></i>
</div>
<span class="fs-4 fw-bold text-danger">RaktaPulse</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#welcomeNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="welcomeNav">
<ul class="navbar-nav ms-auto gap-2">
<li class="nav-item"><a class="nav-link fw-bold" href="{% url 'login' %}">Login</a></li>
<li class="nav-item"><a class="btn btn-danger rounded-pill px-4" href="{% url 'register' %}">Sign Up</a></li>
</ul>
</div>
</div>
</nav>
<!-- Hero Section -->
<section class="hero-section">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6 hero-content">
<h1 class="display-3 fw-bold mb-4">Save Lives with <span style="color: var(--primary-red);">RaktaPulse</span></h1>
<p class="lead mb-5 text-secondary">Every drop counts. Connect with donors, find blood banks, and request life-saving help in seconds. Join our mission to make blood donation accessible to everyone.</p>
<div class="d-flex gap-3 flex-wrap">
<a href="{% url 'register' %}" class="btn btn-hero btn-hero-primary">Join as Donor</a>
<a href="{% url 'login' %}" class="btn btn-hero btn-outline-danger border-2">Login to Portal</a>
</div>
</div>
<div class="col-lg-6 text-center hero-image mt-5 mt-lg-0">
<div class="position-relative">
<i class="bi bi-droplet-fill floating-icon" style="font-size: 15rem;"></i>
<div class="position-absolute top-50 start-50 translate-middle">
<i class="bi bi-heart-fill text-white" style="font-size: 4rem;"></i>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section class="py-5">
<div class="container">
<div class="text-center mb-5">
<h2 class="fw-bold">Why Choose RaktaPulse?</h2>
<div class="mx-auto bg-danger" style="width: 80px; height: 4px; border-radius: 2px;"></div>
</div>
<div class="row g-4">
<div class="col-md-4">
<div class="feature-card text-center">
<i class="bi bi-search feature-icon"></i>
<h3>Find Donors</h3>
<p class="text-muted">Locate blood donors in your vicinity using our advanced geolocation matching system.</p>
</div>
</div>
<div class="col-md-4">
<div class="feature-card text-center">
<i class="bi bi-geo-alt feature-icon"></i>
<h3>Nearby Hospitals</h3>
<p class="text-muted">Instantly find hospitals and blood banks with live distance calculation and maps.</p>
</div>
</div>
<div class="col-md-4">
<div class="feature-card text-center">
<i class="bi bi-chat-dots feature-icon"></i>
<h3>Real-time Chat</h3>
<p class="text-muted">Communicate directly with donors via our secure messaging and calling system.</p>
</div>
</div>
</div>
</div>
</section>
<!-- Stats Section -->
<section class="stats-section mt-5">
<div class="container">
<div class="row text-center">
<div class="col-md-4 mb-4 mb-md-0">
<div class="stat-number">1000+</div>
<div class="text-uppercase fw-bold">Active Donors</div>
</div>
<div class="col-md-4 mb-4 mb-md-0">
<div class="stat-number">500+</div>
<div class="text-uppercase fw-bold">Lives Saved</div>
</div>
<div class="col-md-4">
<div class="stat-number">50+</div>
<div class="text-uppercase fw-bold">Partner Hospitals</div>
</div>
</div>
</div>
</section>
<!-- Call to Action -->
<section class="py-5 my-5">
<div class="container">
<div class="bg-dark text-white p-5 rounded-4 shadow-lg text-center position-relative overflow-hidden">
<div class="position-relative z-1">
<h2 class="display-5 fw-bold mb-4">Ready to make a difference?</h2>
<p class="lead mb-4">Register today and start saving lives in your community.</p>
<a href="{% url 'register' %}" class="btn btn-danger btn-lg px-5 py-3 pulse-animation">Get Started Now</a>
</div>
<div class="position-absolute bottom-0 end-0 p-3 opacity-25">
<i class="bi bi-shield-check" style="font-size: 10rem;"></i>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="bg-white py-5 border-top">
<div class="container">
<div class="row align-items-center">
<div class="col-md-6 text-center text-md-start">
<span class="fw-bold text-danger">RaktaPulse</span> &copy; 2026. All rights reserved.
</div>
<div class="col-md-6 text-center text-md-end mt-3 mt-md-0">
<div class="d-flex justify-content-center justify-content-md-end gap-3">
<a href="#" class="text-secondary fs-4"><i class="bi bi-facebook"></i></a>
<a href="#" class="text-secondary fs-4"><i class="bi bi-twitter-x"></i></a>
<a href="#" class="text-secondary fs-4"><i class="bi bi-instagram"></i></a>
<a href="#" class="text-secondary fs-4"><i class="bi bi-linkedin"></i></a>
</div>
</div>
</div>
</div>
</footer>
</div>
{% endblock %}

View File

@ -1,7 +1,7 @@
from django.urls import path
from .views import (
home, login_view, logout_view, register_view, donor_list,
welcome, home, login_view, logout_view, register_view, donor_list,
blood_request_list, blood_bank_list, vaccination_info,
vaccination_dashboard, add_vaccination, live_map,
request_blood, profile, volunteer_for_request,
@ -10,7 +10,8 @@ from .views import (
)
urlpatterns = [
path("", home, name="home"),
path("", welcome, name="welcome"),
path("dashboard/", home, name="home"),
path("login/", login_view, name="login"),
path("logout/", logout_view, name="logout"),
path("register/", register_view, name="register"),

View File

@ -109,6 +109,12 @@ def register_view(request):
form = UserRegisterForm()
return render(request, "core/register.html", {"form": form})
def welcome(request):
"""Render a beautiful animated welcome page."""
if request.user.is_authenticated:
return redirect('home')
return render(request, "core/welcome.html")
def home(request):
"""Render the RaktaPulse Dashboard experience."""
query_blood = request.GET.get('blood_group', '')

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB