notifications

This commit is contained in:
rizwanisready
2024-03-13 18:25:54 +05:30
parent f2e65c2950
commit 01469b41c6
56 changed files with 2581 additions and 103 deletions

View File

@@ -149,6 +149,7 @@ class ProfileSerializer(serializers.ModelSerializer):
"principal_type",
"principal_type_name",
"profile_photo",
"player_id",
"first_name",
"last_name",
"email",
@@ -284,3 +285,7 @@ class ProfilePhotoSerializer(serializers.ModelSerializer):
class Meta:
model = IAmPrincipal
fields = ("email", "profile_photo", "first_name")
class PlayerIDSerializer(serializers.Serializer):
player_id = serializers.CharField(max_length=255)

View File

@@ -23,8 +23,16 @@ urlpatterns = [
path('profile/password-reset/', views.ProfilePasswordResetView.as_view(), name='password_reset'),
# path('profile/kyc/', views.KycDocumentView.as_view(), name='pofile_kyc'),
path('player-id/add/', views.IAmPrincipalPlayerIDAPIView.as_view(), name='add_player_id'),
path('google-login/', views.GoogleLoginAPIView.as_view(), name='google-login'),
path(
"apple-login/",
views.decode_apple_token,
name="apple_login",
),

View File

@@ -1,11 +1,17 @@
import json
from django.db import transaction
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone
import jwt
from rest_framework import status
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken
from django.conf import settings
import requests
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
# from .authenticate import authticate_with_otp_and_passsword
from accounts.models import (
@@ -32,6 +38,7 @@ from .serializers import (
# RegistrationPasswordSerializer,
# PhoneSerializer,
EmailSerializer,
PlayerIDSerializer,
RegistrationPasswordSerializer,
RegistrationSerializer,
ReferralCodeSerializer,
@@ -156,6 +163,7 @@ class RegistrationDetailsView(APIView):
email = request.data.get("email")
print("email: ", email)
referral_code = request.data.get("referral_code")
player_id = request.data.get("player_id")
print("referral_code", referral_code)
# Filter the user instance by phone number through reusable function
@@ -177,6 +185,8 @@ class RegistrationDetailsView(APIView):
try:
instance = serializer.save()
instance.register_complete = True
if player_id:
instance.player_id = player_id
instance.save()
except Exception as e:
print("instance: E-", e)
@@ -499,7 +509,9 @@ class ProfileView(APIView):
def post(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.serializer(instance, data=request.data)
serializer = self.serializer(
instance, data=request.data, context={"request": request}
)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
@@ -514,7 +526,7 @@ class ProfileView(APIView):
return ApiResponse.error(
message=constants.INTERNAL_SERVER_ERROR, errors=str(e)
)
return ApiResponse.success(message=constants.SUCCESS)
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
class ProfilePasswordResetView(APIView):
@@ -618,6 +630,7 @@ class GoogleLoginAPIView(APIView):
def post(self, request, *args, **kwargs):
access_token = request.data.get("access_token")
principal_type = request.data.get("principal_type")
referral_code = request.data.get("referral_code")
if not access_token or not principal_type:
return Response(
{"error": "Access token & Principal Type is required"},
@@ -694,6 +707,47 @@ class GoogleLoginAPIView(APIView):
principal.save()
print("Created new user")
message = "Details updated"
try:
ReferralCode.create_referral_code_for_user_manager(
principal=principal, principal_type=principal.principal_type
)
except Exception as e:
print("ReferralCode: E-", e)
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.FAILURE,
"errors": str(e),
}
return ApiResponse.error(**error_response)
if referral_code:
already_register_through_referral = ReferralRecord.objects.filter(
referred_principal=principal
).exists()
if not already_register_through_referral:
try:
whos_referral_code = ReferralCode.objects.get(
referral_code=referral_code
)
except Exception as e:
print("whos_referral_code: E-", e)
error_response = {
"status": status.HTTP_404_NOT_FOUND,
"message": constants.RECORD_NOT_FOUND,
"errors": str(e),
}
return ApiResponse.error(**error_response)
# principal_type = IAmPrincipalType.objects
ReferralRecord.objects.create(
referrer_principal=whos_referral_code.principal, # principal id of the User who invited
referred_principal=principal, # principal id of the User who join through invitation
principal_type=whos_referral_code.principal_type, # principal type of the user who invited
is_completed=True,
)
token_data = generate_token_and_user_data(principal)
token_data["type"] = str(principal.principal_type)
@@ -718,3 +772,138 @@ class GoogleLoginAPIView(APIView):
"error": error_details,
"status": response.status_code,
}
# Apple's public keys URL
APPLE_PUBLIC_KEYS_URL = "https://appleid.apple.com/auth/keys"
# Your client ID
AUDIENCE = "com.app.goodTimes"
@csrf_exempt
@require_http_methods(["POST"])
def decode_apple_token(request):
try:
data = request.POST
identity_token = data.get("token")
if not identity_token:
return JsonResponse({"error": "Token is required"}, status=400)
principal_type = data.get("principal_type")
principal_type_instance = IAmPrincipalType.objects.filter(
name=principal_type
).first()
if principal_type_instance is None:
return JsonResponse(
{"error": "No Principal Type Exists"},
status=status.HTTP_400_BAD_REQUEST,
content_type="application/json",
)
# Fetch Apple's public keys
# Note: You might want to cache these keys and update them periodically rather than fetching them with every request
apple_keys_response = requests.get(APPLE_PUBLIC_KEYS_URL)
apple_keys = apple_keys_response.json()
# Decode the token
# Note: This is a simplified example; you should handle the selection of the key and any potential errors during decoding
header_data = jwt.get_unverified_header(identity_token)
kid = header_data["kid"]
apple_key = next((key for key in apple_keys["keys"] if key["kid"] == kid), None)
if apple_key is None:
return JsonResponse({"error": "Invalid key ID"}, status=400)
# Construct the public key
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(apple_key))
# Decode the token
decoded = jwt.decode(
identity_token, public_key, algorithms=["RS256"], audience=AUDIENCE
)
# Check if there was an error in fetching user data
if "error" in decoded:
error_message = decoded.get("error", {}).get(
"error_description", "An error occurred while fetching user data."
)
return JsonResponse(
{"error": error_message},
status=decoded.get("status", status.HTTP_400_BAD_REQUEST),
)
print("decoded: ", decoded)
email = decoded.get("email")
apple_id = decoded.get("sub")
print("apple_id: ", apple_id)
# Checking if essential values are present and valid
if not apple_id:
# Return an error response if either 'email' or 'sub' is missing or empty
return JsonResponse(
{"error": "The token is missing required information."},
status=status.HTTP_400_BAD_REQUEST,
)
with transaction.atomic():
apple_source, _ = IAmPrincipalSource.objects.get_or_create(name="apple")
principal = IAmPrincipal.objects.filter(apple_id=apple_id).first()
print("principal: ", principal)
if principal:
principal.email_verified = True
principal.principal_source = apple_source
principal.apple_id = apple_id
# Update any other fields that might change on each login
principal.save()
print("Updated existing user")
message = "Already Registered and Verified User"
else:
defaults = {
"email": f"{apple_id}@gmail.com",
"register_complete": True,
"email_verified": True,
"username": apple_id, # Or generate a unique username if necessary
"principal_source": apple_source,
}
defaults["principal_type"] = principal_type_instance
defaults["apple_id"] = apple_id
principal = IAmPrincipal(**defaults)
default_password = f"SEMTG{apple_id[::-1]}"
principal.set_password(default_password)
principal.save()
print("Created new user")
message = "Registered Successfully"
token_data = generate_token_and_user_data(principal)
token_data["type"] = str(principal.principal_type)
return JsonResponse({"message": message, "data": token_data}, status=200)
# return JsonResponse(decoded)
except Exception as e:
return JsonResponse({"error": str(e)}, status=400)
class IAmPrincipalPlayerIDAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
serializer = PlayerIDSerializer(data=request.data)
print("serializer: ", serializer)
if serializer.is_valid():
principal = request.user
principal.player_id = serializer.validated_data["player_id"]
principal.save()
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
return ApiResponse.error(
status=status.HTTP_400_BAD_REQUEST,
message=constants.FAILURE,
errors=serializer.errors,
)

View File

@@ -14,6 +14,7 @@ from accounts.resource_action import (
RESOURCE_MANAGE_DASHBOARD,
RESOURCE_MANAGE_IAM,
RESOURCE_MANAGE_CUSTOMER,
RESOURCE_MANAGE_NOTIFICATIONS,
RESOURCE_MANAGE_WALLET,
RESOURCE_MANAGE_PAYMENT,
RESOURCE_MANAGE_EVENTS,
@@ -269,4 +270,16 @@ fixture_data = [
"action": [1, 2, 3, 4],
},
},
{
"model": "accounts.iamappresource",
"pk": 13,
"fields": {
"name": RESOURCE_MANAGE_NOTIFICATIONS,
"label": RESOURCE_MANAGE_NOTIFICATIONS,
"slug": RESOURCE_MANAGE_NOTIFICATIONS,
"created_on": "2023-09-28T16:17:42.815",
"modified_on": "2023-09-28T16:17:42.815",
"action": [1, 2, 3, 4],
},
},
]

View File

@@ -301,5 +301,22 @@
4
]
}
},
{
"model": "accounts.iamappresource",
"pk": 13,
"fields": {
"name": "manage_notifications",
"label": "manage_notifications",
"slug": "manage_notifications",
"created_on": "2023-09-28T16:17:42.815",
"modified_on": "2023-09-28T16:17:42.815",
"action": [
1,
2,
3,
4
]
}
}
]

View File

@@ -23,6 +23,7 @@ RESOURCE_MANAGE_REPORTS = "manage_reports"
RESOURCE_MANAGE_SUBSCRIPTIONS = "manage_subscriptions"
RESOURCE_MANAGE_FEEDBACK = "manage_feedback"
RESOURCE_MANAGE_REFERRALS = "manage_referrals"
RESOURCE_MANAGE_NOTIFICATIONS = "manage_notifications"
# These constants are used solely for managing the active and inactive state of pages

View File

@@ -43,10 +43,10 @@ PASSWORD_CHANGE_FAILURE = "Password change failed. Please try again later."
# Mobile OTP Related Constants
PHONE_NUMBER_EXISTS = "This phone number is already in use."
PHONE_NUMBER_EXISTS = "This email is already in use."
EMAIL_EXISTS = "This email is already in use."
PHONE_NUMBER_NOT_REGISTERED = "This phone number is not registered."
EMAIL_NOT_REGISTERED = "This phone number is not registered."
EMAIL_NOT_REGISTERED = "This email is not registered."
PHONE_NUMBER_NOT_FOUND = "Phone number not found."
PHONE_FIELD_IS_REQUIRED = "Phone field is required."
PHONE_NUMBER_INVALID = 'Invalid phone number.'

View File

@@ -176,6 +176,7 @@ AUTHENTICATION_BACKENDS = [
# rest framework permission and authentication settings
# https://www.django-rest-framework.org/api-guide/permissions/#setting-the-permission-policy
REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
"DEFAULT_PERMISSION_CLASSES": [
@@ -303,6 +304,9 @@ SIMPLE_JWT = {
STRIPE_SECRET_KEY = env.str("STRIPE_SECRET_KEY")
STRIPE_PUBLISH_KEY = env.str("STRIPE_PUBLISH_KEY")
ONE_SIGNAL_APP_ID = env.str("ONE_SIGNAL_APP_ID")
ONE_SIGNAL_API_KEY = env.str("ONE_SIGNAL_API_KEY")
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
@@ -327,26 +331,4 @@ CRONJOBS = [
# ("0 9 * * 1-5", "manage_games.cron.update_game_status_live"),
]
# GOOGLE_OAUTH2_CLIENT_ID = env.str("DJANGO_GOOGLE_OAUTH2_CLIENT_ID", default="")
# GOOGLE_OAUTH2_CLIENT_SECRET = env.str("DJANGO_GOOGLE_OAUTH2_CLIENT_SECRET", default="")
# GOOGLE_OAUTH2_PROJECT_ID = env.str("DJANGO_GOOGLE_OAUTH2_PROJECT_ID", default="")
GOOGLE_MAPS_API_KEY = env.str("GOOGLE_MAPS_API_KEY")
# SOCIALACCOUNT_PROVIDERS = {
# "google": {
# "APP": {
# "client_id": GOOGLE_OAUTH2_CLIENT_ID, # replace me
# "secret": GOOGLE_OAUTH2_CLIENT_SECRET, # replace me
# "key": "", # leave empty
# },
# "SCOPE": [
# "profile",
# "email",
# ],
# "AUTH_PARAMS": {
# "access_type": "online",
# },
# "VERIFIED_EMAIL": True,
# },
# }

View File

@@ -1,4 +1,5 @@
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer
from rest_framework import status
import string
import random

View File

@@ -14,4 +14,5 @@ urlpatterns = [
path('education/material/', views.EducationMaterialView.as_view(), name='education_material'),
path('education/tags/', views.EducationTagsView.as_view(), name='education_tags'),
]

View File

@@ -11,6 +11,9 @@ from manage_cms import models
from taggit.models import Tag
from taggit.models import TaggedItem
from django.contrib.contenttypes.models import ContentType
import requests
from django.conf import settings
import urllib.parse
# from nifty11_project.services import SMSError, SMSService
from goodtimes.utils import ApiResponse
@@ -141,10 +144,9 @@ class EducationTagsView(APIView):
education_content_type = ContentType.objects.get_for_model(self.model)
# Fetch tags associated with Education instances
education_tags = (
TaggedItem.objects.filter(content_type=education_content_type)
.values_list("tag", flat=True)
)
education_tags = TaggedItem.objects.filter(
content_type=education_content_type
).values_list("tag", flat=True)
# Retrieve actual tag objects from Tag model using the tag IDs obtained
queryset = Tag.objects.filter(id__in=education_tags)
@@ -156,3 +158,4 @@ class EducationTagsView(APIView):
"data": serializer.data,
}
return ApiResponse.success(**success_response)

View File

@@ -49,4 +49,5 @@ urlpatterns = [
path('education/material/add/', views.EducationMaterialCreateOrUpdateView.as_view(), name='education_add_material'),
path('education/material/edit/<int:pk>/', views.EducationMaterialCreateOrUpdateView.as_view(), name='education_edit_material'),
]

View File

@@ -26,7 +26,7 @@ from .forms import (
FaqsForm,
FaqCategoryFrom,
EducationVideoForm,
EducationMaterialForm
EducationMaterialForm,
)
@@ -70,7 +70,7 @@ class NewsArticleCreateOrUpdateView(LoginRequiredMixin, generic.View):
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/news_article_add.html"
model = NewsAndArticles
@@ -155,7 +155,7 @@ class NewsArticleCategoryCreateOrUpdateView(LoginRequiredMixin, generic.View):
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/news_article_category_add.html"
model = NewsAndArticlesCategory
@@ -321,7 +321,7 @@ class AboutUsCreateOrUpdateView(LoginRequiredMixin, generic.View):
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/about_us_add.html"
model = Organization
@@ -401,7 +401,7 @@ class TermsConditionCreateOrUpdateView(LoginRequiredMixin, generic.View):
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/terms_and_condition_edit.html"
model = Organization
@@ -467,11 +467,7 @@ class FaqListView(LoginRequiredMixin, generic.ListView):
context_object_name = "faqs_obj"
def get_queryset(self):
return (
super()
.get_queryset()
.filter(deleted=False)
)
return super().get_queryset().filter(deleted=False)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -486,7 +482,7 @@ class FaqCreateOrUpdateView(LoginRequiredMixin, generic.View):
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/faq_add.html"
model = Faqs
@@ -551,7 +547,7 @@ class FaqCategoryCreateOrUpdateView(LoginRequiredMixin, generic.View):
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/faq_category_add.html"
model = FaqCategory
@@ -634,7 +630,7 @@ class PrivacyPolicyCreateOrUpdateView(LoginRequiredMixin, generic.View):
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/privacy_policy_edit.html"
model = Organization
@@ -723,7 +719,7 @@ class OrganizationCreateOrUpdateView(LoginRequiredMixin, generic.View):
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_cms/organization_add.html"
model = Organization
@@ -788,18 +784,15 @@ class EducationView(LoginRequiredMixin, generic.ListView):
context_object_name = "education_obj"
def get_queryset(self):
return (
super()
.get_queryset()
.prefetch_related("tags")
.filter(deleted=False)
)
return super().get_queryset().prefetch_related("tags").filter(deleted=False)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
context["video_obj"] = self.get_queryset().filter(content_type=Education.VIDEO)
context["material_obj"] = self.get_queryset().filter(content_type=Education.MATERIAL)
context["material_obj"] = self.get_queryset().filter(
content_type=Education.MATERIAL
)
print("video data", context["video_obj"])
return context
@@ -810,7 +803,7 @@ class EducationCreateOrUpdateView(LoginRequiredMixin, generic.View):
resource = resource_action.RESOURCE_MANAGE_CMS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
action = resource_action.ACTION_CREATE # Default action
page_title = None
template_name = "manage_cms/education_add.html"

View File

@@ -0,0 +1,17 @@
from django import forms
from .models import PushNotification, NotificationCategory, PrincipalType
class PushNotificationForm(forms.ModelForm):
class Meta:
model = PushNotification
fields = [
"title",
"notification_category",
"banner_image",
"principal_type",
"message",
]
widgets = {
"message": forms.Textarea(attrs={"rows": 4}),
}

View File

@@ -0,0 +1,234 @@
# Generated by Django 5.0.2 on 2024-03-13 12:55
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="NotificationCategory",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=100)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "notification_settings_category",
},
),
migrations.CreateModel(
name="NotificationSettings",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=100)),
(
"category",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="notification_category",
to="manage_notifications.notificationcategory",
),
),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"db_table": "notification_settings",
},
),
migrations.CreateModel(
name="IAmPrincipalNotificationSettings",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("is_enabled", models.BooleanField(default=True)),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"principal",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="notifications_principal",
to=settings.AUTH_USER_MODEL,
),
),
(
"notification_setting",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="manage_notifications.notificationsettings",
),
),
],
options={
"db_table": "iam_principal_notification_settings",
},
),
migrations.CreateModel(
name="PushNotification",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("active", models.BooleanField(default=True)),
("deleted", models.BooleanField(default=False)),
("created_on", models.DateTimeField(auto_now_add=True)),
("modified_on", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
(
"banner_image",
models.ImageField(
blank=True, null=True, upload_to="push_notification_images/"
),
),
(
"principal_type",
models.CharField(
choices=[
("event_user", "Event User"),
("event_manager", "Event Manager"),
("both", "Both"),
],
max_length=50,
),
),
("message", models.TextField()),
(
"created_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_created",
to=settings.AUTH_USER_MODEL,
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_modified",
to=settings.AUTH_USER_MODEL,
),
),
(
"notification_category",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="push_category",
to="manage_notifications.notificationcategory",
),
),
],
options={
"abstract": False,
},
),
]

View File

@@ -1,57 +1,70 @@
# from django.db import models
# from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType
from django.db import models
from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType
# # Create your models here.
# class PushNotification(BaseModel):
# title = models.CharField(max_length=255)
# banner_image = models.ImageField(
# upload_to="push_notification_images/", blank=True, null=True
# )
# principal_type = models.CharField(max_length=50)
# message = models.TextField()
# published_datetime = models.DateTimeField()
# Create your models here.
class NotificationCategory(BaseModel):
name = models.CharField(max_length=100)
# def __str__(self):
# return self.title
class Meta:
db_table = "notification_settings_category"
def __str__(self) -> str:
return f"name : {self.name}"
# class NotificationSettingsCategory(BaseModel):
# name = models.CharField(max_length=100)
# class Meta:
# db_table = "notification_settings_category"
# def __str__(self) -> str:
# return f"name : {self.name}"
class PrincipalType(models.TextChoices):
EVENT_USER = "event_user", "Event User"
EVENT_MANAGER = "event_manager", "Event Manager"
BOTH = "both", "Both"
# class NotificationSettings(BaseModel):
# name = models.CharField(max_length=100)
# category = models.ForeignKey(
# NotificationSettingsCategory,
# on_delete=models.CASCADE,
# related_name="notification_category",
# )
class PushNotification(BaseModel):
title = models.CharField(max_length=255)
notification_category = models.ForeignKey(
NotificationCategory,
on_delete=models.CASCADE,
related_name="push_category",
)
banner_image = models.ImageField(
upload_to="push_notification_images/", blank=True, null=True
)
principal_type = models.CharField(
max_length=50,
choices=PrincipalType.choices,
)
message = models.TextField()
# class Meta:
# db_table = "notification_settings"
# def __str__(self) -> str:
# return f"name: {self.name}"
def __str__(self):
return self.title
# class IAmPrincipalNotificationSettings(BaseModel):
# principal = models.ForeignKey(
# IAmPrincipal, on_delete=models.CASCADE, related_name="notifications_principal"
# )
# notification_setting = models.ForeignKey(
# NotificationSettings, on_delete=models.CASCADE
# )
# is_enabled = models.BooleanField(default=True)
class NotificationSettings(BaseModel):
name = models.CharField(max_length=100)
category = models.ForeignKey(
NotificationCategory,
on_delete=models.CASCADE,
related_name="notification_category",
)
# class Meta:
# db_table = "iam_principal_notification_settings"
class Meta:
db_table = "notification_settings"
# def __str__(self):
# return f"{self.principal.first_name} - {self.notification_setting.name}"
def __str__(self) -> str:
return f"name: {self.name}"
class IAmPrincipalNotificationSettings(BaseModel):
principal = models.ForeignKey(
IAmPrincipal, on_delete=models.CASCADE, related_name="notifications_principal"
)
notification_setting = models.ForeignKey(
NotificationSettings, on_delete=models.CASCADE
)
is_enabled = models.BooleanField(default=True)
class Meta:
db_table = "iam_principal_notification_settings"
def __str__(self):
return f"{self.principal.first_name} - {self.notification_setting.name}"

View File

@@ -6,5 +6,19 @@ from django.views.generic import TemplateView
app_name = 'manage_notifications'
urlpatterns = [
path(
"notification/add/",
views.PushNotificationsCreateOrUpdateView.as_view(),
name="notification_add",
),
path(
"notification/edit/<int:pk>/",
views.PushNotificationsCreateOrUpdateView.as_view(),
name="notification_edit",
),
path(
"notification/list/",
views.PushNotificationView.as_view(),
name="notification_list",
),
]

View File

@@ -0,0 +1,101 @@
# import requests
# from django.conf import settings
# def onesignal_send_notification(notification):
# onesignal_app_id = settings.ONE_SIGNAL_APP_ID
# onesignal_rest_api_key = settings.ONE_SIGNAL_API_KEY
# headers = {
# "Content-Type": "application/json; charset=utf-8",
# "Authorization": f"Basic {onesignal_rest_api_key}"
# }
# # Determine player IDs based on notification_category and principal_type
# player_ids = []
# if notification.principal_type == PrincipalType.BOTH:
# # Get player IDs for both event_user and event_manager
# from .models import IAmPrincipal # Assuming your IAmPrincipal model is in the same app
# event_user_principals = IAmPrincipal.objects.filter(iam_principal_type__name=PrincipalType.EVENT_USER)
# event_manager_principals = IAmPrincipal.objects.filter(iam_principal_type__name=PrincipalType.EVENT_MANAGER)
# for principal in event_user_principals:
# # Assuming you have a field in IAmPrincipal that stores the player ID (e.g., 'one_signal_player_id')
# player_ids.append(principal.one_signal_player_id)
# for principal in event_manager_principals:
# player_ids.append(principal.one_signal_player_id)
# else:
# # Handle filtering for EVENT_USER or EVENT_MANAGER as needed (similar logic as above)
# # Construct the notification payload
# data = {
# "app_id": onesignal_app_id,
# "headings": {"en": notification.title},
# "contents": {"en": notification.message},
# "include_player_ids": player_ids, # Include the filtered player IDs
# # Add other optional notification data according to OneSignal documentation
# }
# if notification.banner_image:
# # Include image URL if provided (requires additional OneSignal configuration)
# data["large_icon"] = notification.banner_image.url # Assuming you have a URL property for the image field
# # Send the notification request to OneSignal
# response = requests.post("https://onesignal.com/api/v1/notifications", headers=headers, json=data)
# if response.status_code == 200:
# print("Notification sent successfully!")
# else:
# print(f"Error sending notification: {response.text}")
from onesignal_sdk.client import Client as OneSignalClient
from django.conf import settings
from .models import IAmPrincipalNotificationSettings, NotificationSettings, IAmPrincipal
def send_notification(notification_type, title, message, image_url=None):
# Initialize OneSignal client
onesignal_client = OneSignalClient(
app_id=settings.ONE_SIGNAL_APP_ID, rest_api_key=settings.ONE_SIGNAL_API_KEY
)
# Find all users who have enabled this type of notification
notification_setting = NotificationSettings.objects.filter(
name=notification_type
).first()
if not notification_setting:
print("Notification type does not exist.")
return
user_ids = IAmPrincipalNotificationSettings.objects.filter(
notification_setting=notification_setting, is_enabled=True
).values_list("principal__id", flat=True)
principals = (
IAmPrincipal.objects.filter(id__in=user_ids)
.exclude(player_id__isnull=True)
.exclude(player_id__exact="")
)
# Extract OneSignal player IDs
player_ids = principals.values_list("player_id", flat=True)
# Prepare notification payload
notification_payload = {
"headings": {"en": title},
"contents": {"en": message},
"include_player_ids": list(player_ids),
}
if image_url:
notification_payload["big_picture"] = image_url
# Send notification
response = onesignal_client.send_notification(notification_payload)
print(response.status_code, response.body)
return response

View File

@@ -1,3 +1,90 @@
from django.shortcuts import render
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from accounts import resource_action
from goodtimes import constants
from manage_notifications.forms import PushNotificationForm
from manage_notifications.models import PushNotification
# Create your views here.
class PushNotificationsCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_NOTIFICATIONS
resource = resource_action.RESOURCE_MANAGE_NOTIFICATIONS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_notifications/notification_add.html"
model = PushNotification
form_class = PushNotificationForm
success_url = reverse_lazy("manage_notifications:notification_list")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk'
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"operation": "Add" if not self.object else "Edit",
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(instance=self.object)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = resource_action.ACTION_UPDATE
form = self.form_class(request.POST, request.FILES, instance=self.object)
if not form.is_valid():
print(form.errors)
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
form.save()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class PushNotificationView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_EVENTS
resource = resource_action.RESOURCE_MANAGE_EVENTS
action = resource_action.ACTION_READ
model = PushNotification
template_name = "manage_notifications/notification_list.html"
context_object_name = "notification_obj"
def get_queryset(self):
return super().get_queryset().filter(deleted=False)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context

View File

@@ -43,7 +43,8 @@ class ReferralCode(BaseModel):
The method ensures each generated code is unique to avoid conflicts.
"""
type = type.upper()
# type = type.upper()
type = type.replace("_", "").upper()
name = name[:3].upper() if name else "GDTM"
while True:
random_number = "".join(random.choice(string.digits) for _ in range(4))

View File

@@ -0,0 +1,737 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap') @import url('https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap') * {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Poppins", sans-serif;
}
:root {
--black: #000000;
--light-black: #050505;
--main-yellow: rgba(209, 170, 88, 1);
--light-yellow: rgba(229, 25, 94, 0.2);
--white: #ffffff;
--light-white: #f8f8f8;
--white-other: rgba(207, 207, 207, 1);
--white-mix: #cecece;
--border: #ff72a285;
}
body {
font-family: "Poppins", sans-serif;
}
.ptb {
padding: 40px 0;
}
.sec-heading {
font-size: 38px;
font-weight: 600;
text-align: center;
/* padding-top: 40px; */
color: var(--main-yellow);
}
.sec-subheading {
font-size: 18px;
font-weight: 400;
text-align: center;
color: var(--white);
}
.big-heading {
font-size: 52px;
font-weight: 700;
color: var(--white);
letter-spacing: 1.8px;
}
.para {
font-size: 18px;
font-weight: 400;
color: rgba(255, 255, 255, 1);
}
.sec-mini-heading {
font-size: 24px;
font-weight: 600;
}
.para-dark {
font-size: 24px;
/* font-weight: 600; */
}
.para-mid {
font-size: 18px;
color: rgba(255, 255, 255, 0.69);
}
li {
list-style: none;
}
.pt {
padding: 40px 0;
}
/* header */
header {
border-bottom: 1px solid var(--main-yellow);
position: absolute;
background-color: transparent;
top: 0;
left: 0;
width: 100%;
padding: 22px 0;
display: flex;
align-items: center;
z-index: 9999;
}
header .header-main-inner {
display: flex;
align-items: center;
justify-content: space-between;
}
header nav ul {
display: flex;
gap: 80px;
align-items: center;
margin: 0;
}
.header-main-inner .logo {
width: 212px;
height: 52px;
}
.header-main-inner .logo img {
width: 100%;
}
header nav ul .menu-btn img {
width: 24px;
height: 24px;
}
header nav ul li a {
color: var(--white);
font-size: 18px;
/* font-weight: 600; */
position: relative;
text-decoration: none;
}
.sticky {
background-color: var(--black);
position: fixed;
animation: slideDown 0.8s ease-out;
-webkit-animation: slideDown 0.8s ease-out;
}
@keyframes slideDown {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0);
}
}
header a.active {
color: var(--main-yellow);
}
header nav ul li a::after {
content: "";
position: absolute;
left: 50%;
width: 0%;
bottom: -10px;
border-bottom: 2px solid var(--main-yellow);
transition: all 0.3s;
transform: translateX(-50%);
}
header nav ul li a:hover {
color: var(--main-yellow);
}
header nav ul li a:hover:after {
width: 100%;
}
.hamburger {
display: none;
}
.hamburger {
position: relative;
width: 25px;
height: 25px;
display: none;
cursor: pointer;
}
.hamburger img {
width: 25px;
height: 25px;
}
.overlay {
display: none;
}
.cross-btn {
padding: 2px 20px;
text-align: right;
display: none;
font-size: 40px;
cursor: pointer;
color: var(--main-yellow);
}
.cross-btn i {
font-size: 20px;
font-weight: 500;
}
/* about-head */
.head-sec header,
.terms-sec header {
/* position: inherit; */
background-color: var(--black);
}
/* baner */
.baner-section {
background-image: linear-gradient(rgba(4, 9, 10, 0.7), rgba(4, 9, 10, 0.7)), url(images/baner.jpg);
background-position: center;
background-size: cover;
height: 100vh;
display: flex;
align-items: center;
}
.baner-section .row {
align-items: center;
padding-top: 100px;
}
.baner-section .store-app {
display: flex;
align-items: center;
gap: 15px;
margin: 30px 0;
}
.baner-img {
text-align: center;
padding-top: 20px;
}
.baner-section .baner-img img {
width: 75%;
}
.baner-content .big-heading span {
color: var(--main-yellow);
}
.baner-content .grey-para {
margin-top: 24px;
font-size: 20px;
color: var(--white-other);
}
.baner-btn {
margin-top: 24px;
}
.common-btn {
background: linear-gradient(90.02deg, #CDA34C 0.02%, #F1D6A0 52%, #D1A956 98.68%);
width: 252px;
/* height: 50px; */
font-weight: 500;
border: none;
font-size: 18px;
font-weight: 600;
padding: 12px 0;
}
/* key-feature */
.key-features {
background-color: #10100e;
color: white;
}
.key-features .row {
align-items: center;
padding-bottom: 40px;
}
.key-features .key-main-img img {
width: 75%;
}
.key-features .key-right-first,
.key-right-second {
display: flex;
align-items: start;
gap: 20px;
}
.key-right-second {
margin-top: 60px;
}
/* easy-steps */
.easy-steps {
background-color: var(--black);
}
.easy-steps .para-dark {
margin-top: 50px;
color: var(--white);
}
.easy-steps .para {
color: rgba(192, 192, 192, 1);
text-align: center;
}
.easy-steps-main {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 25px;
margin-top: 100px;
padding-bottom: 40px;
}
.easy-steps-first {
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid var(--main-yellow);
border-top-left-radius: 14px;
border-top-right-radius: 14px;
display: flex;
align-items: center;
flex-direction: column;
position: relative;
height: 400px;
padding: 0px 20px;
}
.easy-steps-first-img-num {
width: 75px;
height: 75px;
position: absolute;
top: -50px;
}
img.easy-steps-first-img-bot {
position: absolute;
bottom: -1px;
}
/* Adventure
*/
.Adventure {
background-color: #10100e;
color: white;
}
.Adventure .row {
align-items: center;
padding-top: 40px;
}
.Adventure-rti {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 40px;
}
.Adventure-right img {
width: 75%;
}
.Adventure-left .store-app {
display: flex;
gap: 15px;
}
.Adventure-btn {
margin-top: 40px;
}
/* faq */
.faq {
background-color: black;
}
.main-faq {
padding: 40px 0 80px;
}
div#accordionExample {
display: flex;
flex-direction: column;
gap: 30px;
}
.faq .accordion-item {
border: 1px solid var(--main-yellow);
background-color: transparent;
color: #fff;
border-radius: 5px;
}
.faq button.accordion-button:focus {
box-shadow: inherit;
}
.faq button.accordion-button {
background-color: transparent;
color: var(--white);
}
.accordion-button:not(.collapsed) {
color: var(--main-yellow) !important;
font-family: "Nunito Sans", sans-serif;
font-weight: 600;
}
.accordion-item:first-of-type .accordion-button {
box-shadow: none;
}
.accordion-button::after {
background-image: url(images/ab.png);
}
.accordion-button:not(.collapsed)::after {
background-image: url(images/at.png);
transform: none;
}
/* footer */
.footer {
background-color: rgba(16, 16, 14, 1);
}
.footer .footer-main-img {
width: 212px;
height: 52px;
padding: 40px 0;
}
.footer .footer-main-img img {
width: 100%;
}
.footer .footer-main-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
color: var(--white);
padding: 3rem 0 2rem;
}
.footer .footer-main-grid .para-dark {
font-size: 18px;
font-weight: 600;
}
.footer .footer-main-grid .para {
font-size: 16px;
}
.footer .store-app {
display: flex;
gap: 15px;
margin: 0;
flex-direction: column;
}
.footer-btn .common-btn {
margin-bottom: 16px;
}
.footer-main-grid-fourth {
margin-top: -20px;
}
.copy-right {
color: #fff;
text-decoration: none;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 20px;
}
.store-app img {
width: 165px;
}
/* About us */
.about-us {
background-image: url(images/About\ Us\ Banner.png);
background-position: top;
background-size: cover;
background-repeat: no-repeat;
height: 500px;
display: flex;
align-items: center;
margin-top: 90px;
}
.about-us .row {
align-items: center;
}
.about,
.terms {
background-color: var(--black);
color: var(--white);
padding: 40px 0 70px;
}
.terms-main {
background-image: url(images/terms.png);
background-position: top;
background-size: cover;
background-repeat: no-repeat;
height: 500px;
display: flex;
align-items: center;
margin-top: 90px;
}
/* mediascreen */
@media (max-width: 1199px) {
.big-heading br {
display: none;
}
}
@media (max-width: 1024px) {
.big-heading {
font-size: 42px;
}
}
@media (max-width: 991px) {
/* .big-heading br {
display: none;
} */
.big-heading {
font-size: 35px;
}
.store-app img {
width: 142px;
}
.sec-mini-heading {
font-size: 18px;
}
.para {
font-size: 14px;
}
.easy-steps-main {
grid-template-columns: repeat(2, 1fr);
gap: 70px 20px;
margin-top: 70px;
}
.footer .footer-main-grid {
grid-template-columns: repeat(2, 1fr);
}
.footer-main-grid-fourth {
margin-top: 0px;
}
}
@media (max-width: 767px) {
.ptb {
padding: 20px 0 40px 0;
}
.overlay {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: -100%;
background-color: #60606054;
}
.cross-btn {
display: block;
}
.hamburger,
.overlay {
display: block;
}
.navs {
position: fixed;
top: 0;
left: -100%;
width: 300px;
height: 100%;
background: rgb(0 0 0);
transition: all .3s;
z-index: 1;
}
.navs ul {
flex-direction: column;
padding: 00px 20px;
align-items: start;
gap: 14px;
}
.navs ul li a {
color: whitesmoke;
}
.common-btn {
width: 175px;
height: 40px;
}
.baner-section {
height: inherit;
padding: 40px 0;
}
.baner-section .row {
flex-direction: column-reverse;
}
.baner-section .store-app {
justify-content: center;
}
.baner-btn {
text-align: center;
}
.easy-steps-main {
grid-template-columns: repeat(1, 1fr);
}
.footer .footer-main-grid {
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.easy-steps-first {
height: 400px;
}
.key-features .key-main-img img {
width: 50%;
margin-bottom: 20px;
}
.Adventure-rti {
margin-bottom: 20px;
}
.Adventure .row {
flex-direction: column-reverse;
gap: 40px;
padding: 0;
}
.Adventure {
padding: 30px 0;
}
.Adventure-btn {
margin-top: 30px;
}
.Adventure-right img {
width: 50%;
}
.sec-heading {
font-size: 30px;
padding-top: 0;
}
.about .para-mid,
.terms .para-mid {
font-size: 16px;
}
.faq {
padding: 30px 0;
}
.main-faq {
padding: 20px 0 30px;
}
br {
display: none;
}
}
@media (max-width: 444px) {
/* .easy-steps-main {
grid-template-columns: repeat(1, 1fr);
} */
.footer .footer-main-grid {
grid-template-columns: repeat(1, 1fr);
gap: 0px;
}
.footer .store-app {
margin-bottom: 16px;
}
}

View File

@@ -0,0 +1,61 @@
// aos animation
AOS.init();
// siderbar menu
let cross_btn = document.querySelector('.cross-btn');
let nav = document.querySelector('.navs');
let ham = document.querySelector('.hamburger');
let overlay = document.querySelector('.overlay');
let navLinks = document.querySelector('.navLinks');
let openSide = function () {
nav.style.left = '0';
overlay.style.left = '0';
}
let closeSide = function () {
nav.style.left = '-100%';
overlay.style.left = '-100%';
}
ham.addEventListener('click', function () {
openSide();
})
cross_btn.addEventListener('click', function () {
closeSide();
})
overlay.addEventListener('click', function () {
closeSide();
})
navLinks.addEventListener('click', function () {
navLinks();
})
// active page
document.addEventListener("DOMContentLoaded", function () {
var currentUrl = window.location.href;
var links = document.querySelectorAll("#header nav ul li a");
links.forEach(function (link) {
if (link.href === currentUrl) {
link.classList.add("active");
}
});
});
// sticky header
const header = document.querySelector("#header");
window.addEventListener("scroll", () => {
const currentScroll = window.scrollY;
if (currentScroll > 100) {
header.classList.add('sticky');
} else {
header.classList.remove('sticky');
}
});

View File

@@ -148,7 +148,7 @@
</div>
</a>
</li>
<li class="menu">
<!-- <li class="menu">
<a href="./app-calendar.html" aria-expanded="false" class="dropdown-toggle">
<div class="">
<span class="material-symbols-outlined">analytics</span>
@@ -163,7 +163,7 @@
<span>Manage Coupon</span>
</div>
</a>
</li>
</li> -->
<li class="menu {% if page_name == resource_context.RESOURCE_MANAGE_FEEDBACK %}active{% endif %}">
<a href="{% url 'manage_communications:feedback_list'%}" aria-expanded="false" class="dropdown-toggle">
@@ -182,7 +182,7 @@
</a>
</li>
<li class="menu">
<a href="./app-calendar.html" aria-expanded="false" class="dropdown-toggle">
<a href="{% url 'manage_notifications:notification_list'%}" aria-expanded="false" class="dropdown-toggle">
<div class="">
<span class="material-symbols-outlined">notifications</span>
<span>Notification</span>

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h3>
Here you will get the CODE
</h3>
</body>
</html>

View File

@@ -0,0 +1,135 @@
{% extends 'layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/filepond_cdn_css.html" %}
{% include "cdn_through_html/quill_cdn_css.html" %}
{% include "cdn_through_html/tagify_cdn_css.html" %}
{{form.media}}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>{{operation}} {{page_name}}</h3>
</div>
<div class="col text-end">
<button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="POST" novalidate>
{% csrf_token %}
{% include 'includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
</div>
{% comment %} <div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id="title" aria-describedby="title">
<div id="emailHelp" class="form-text" style="color: grey;">We'll never share your email with anyone else.</div>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<div id="description"></div>
</div>
<div class="mb-3">
<label for="product-images">Image</label>
<div class="multiple-file-upload">
<div class="filepond--root filepond file-upload-multiple filepond--hopper" id="images" data-style-button-remove-item-position="left" data-style-button-process-item-position="right" data-style-load-indicator-position="right" data-style-progress-indicator-position="right" data-style-button-remove-item-align="false" style="height: 57px;"><input class="filepond--browser" type="file" id="filepond--browser-feeq8o6dj" name="filepond" aria-controls="filepond--assistant-feeq8o6dj" aria-labelledby="filepond--drop-label-feeq8o6dj" multiple=""><a class="filepond--credits" aria-hidden="true" href="https://pqina.nl/" target="_blank" rel="noopener noreferrer" style="transform: translateY(49px);">Powered by PQINA</a><div class="filepond--drop-label" style="transform: translate3d(0px, 0px, 0px); opacity: 1;"><label for="filepond--browser-feeq8o6dj" id="filepond--drop-label-feeq8o6dj" aria-hidden="true">Drag &amp; Drop your files or <span class="filepond--label-action" tabindex="0">Browse</span></label></div><div class="filepond--list-scroller" style="transform: translate3d(0px, 41px, 0px);"><ul class="filepond--list" role="list"></ul></div><div class="filepond--panel filepond--panel-root" data-scalable="true"><div class="filepond--panel-top filepond--panel-root"></div><div class="filepond--panel-center filepond--panel-root" style="transform: translate3d(0px, 8px, 0px) scale3d(1, 0.41, 1);"></div><div class="filepond--panel-bottom filepond--panel-root" style="transform: translate3d(0px, 49px, 0px);"></div></div><span class="filepond--assistant" id="filepond--assistant-feeq8o6dj" role="status" aria-live="polite" aria-relevant="additions"></span><div class="filepond--drip"></div><fieldset class="filepond--data"></fieldset></div>
</div>
</div>
<div class="mb-3">
<label for="tags">Tags</label>
<input id="tags" class="tags" value="">
</div>
<button type="submit" class="btn btn-primary">Submit</button> {% endcomment %}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/filepond_cdn_js.html" %}
{% include "cdn_through_html/quill_cdn_js.html" %}
{% include "cdn_through_html/tagify_cdn_js.html" %}
<script>
/**
* ===================================
* Blog Description Editor
* ===================================
*/
var quill = new Quill('#description', {
modules: {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline'],
['image', 'code-block']
]
},
placeholder: 'Write description...',
theme: 'snow' // or 'bubble'
});
/**
* ====================
* File Pond
* ====================
*/
// We want to preview images, so we register
// the Image Preview plugin, We also register
// exif orientation (to correct mobile image
// orientation) and size validation, to prevent
// large files from being added
FilePond.registerPlugin(
FilePondPluginImagePreview,
FilePondPluginImageExifOrientation,
FilePondPluginFileValidateSize,
// FilePondPluginImageEdit
);
// Select the file input and use
// create() to turn it into a pond
var ecommerce = FilePond.create(document.querySelector('.file-upload-multiple'));
/**
* =====================
* Blog Tags
* =====================
*/
// The DOM element you wish to replace with Tagify
var input = document.querySelector('#id_tags');
// initialize Tagify on the above input node reference
new Tagify(input,{
originalInputValueFormat: valuesArr => valuesArr.map(item => item.value).join(', ')
})
</script>
{% endblock %}

View File

@@ -0,0 +1,129 @@
{% extends 'layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row">
<div class="col-sm-6">
<h3>Manage Plans</h3>
</div>
<div class="col-sm-6 text-md-end">
<!--
<button class="btn btn-dark mb-2 me-md-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button>
-->
<a class="btn btn-primary mb-2" href="{% url 'manage_notifications:notification_add' %}">Send Notifications</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="style-3_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="style-3" class="table style-3 dt-table-hover dataTable no-footer" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Record Id </th>
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Title </th>
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Notification Category </th>
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Banner Image </th>
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Principal Type </th>
<th class="sorting" tabindex="7" aria-controls="style-3"
style="width: 79.7969px;">Active</th>
<th class="dt-no-sorting sorting" tabindex="8"
aria-controls="style-3"
style="width: 100.625px;">Action</th>
</tr>
</thead>
<tbody>
{% for data_obj in notification_obj %}
<tr role="row">
<td class="checkbox-column text-center sorting_1"> {{data_obj.id}}</td>
<td>{{data_obj.title}}</td>
<td>{{data_obj.notification_category}}</td>
<td>{{data_obj.banner_image}}</td>
<td>{{data_obj.principal_type}}</td>
<td class="text-center">
<span class="shadow-none badge {% if data_obj.active %}badge-primary{% else %}badge-danger{% endif %}">{{data_obj.active}}</span>
</td>
<td class="text-center">
<ul class="table-controls">
<li><a href="{% url 'manage_subscriptions:notification_edit' data_obj.id %}" class="bs-tooltip"
data-bs-toggle="tooltip" data-bs-placement="top" title=""
data-original-title="Edit" data-bs-original-title="Edit"
aria-label="Edit"><svg xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="feather feather-edit-2 p-1 br-8 mb-1">
<path
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z">
</path>
</svg></a></li>
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
<script>
c3 = $('#style-3').DataTable({
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
"oLanguage": {
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
"sInfo": "Showing page _PAGE_ of _PAGES_",
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
"sSearchPlaceholder": "Search...",
"sLengthMenu": "Results : _MENU_",
},
"stripeClasses": [],
"lengthMenu": [5, 10, 20, 50],
"pageLength": 10
});
multiCheck(c3);
</script>
{% endblock %}

View File

@@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="/images/icon.png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
<link rel="stylesheet" href="style.css">
<title>Good times</title>
</head>
<body>
<!-- Header -->
<section class="head-sec">
<header class="header-main" id="header">
<div class="container">
<div class="header-main-inner">
<div class="logo">
<a href="index.html">
<img loading="lazy" src="images/logo.png" alt>
</a>
</div>
<nav class="navs">
<div class="cross-btn">
&times;
</div>
<ul class="navLinks">
<li><a href="index.html">Home</a></li>
<li><a href="about-us.html">About Us</a></li>
<li><a href="terms.html">Terms &
Condition</a></li>
</ul>
</nav>
<div class="hamburger">
<img loading="lazy" src="images/ham.png" alt>
</div>
<div class="overlay"></div>
</div>
</div>
</div>
</header>
</section>
<!-- About Us page -->
<section class="about-us">
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="about-us-left">
<h1 class="big-heading">About Us</h1>
</div>
</div>
</div>
</div>
</section>
<!-- about-us -->
<section class="about">
<div class="container">
<p class="para-mid">Welcome to Good Times - Where Every Moment
Counts!
</p>
<p class="para-mid">Good Times is an event management app founded by
Matthew Weightman and developed by WDI. Our mission is to
empower people to discover and engage with the vibrant events
happening around them.
</p>
<p class="para-mid">Whether you're a seasoned culture enthusiast or
someone looking to broaden your horizons, our platform is
designed to cater to every taste and interest.
</p>
<p class="para-mid">But Good Times is more than just an event
discovery app. You can earn real money by redeeming your Good
Times points. What are the Good Times points you ask? These are
the points you are awarded every time a new user joins the app
using your referral code. Not just that! You receive points
every time the user renews their subscription, giving you a
consistent stream of income.
</p>
<p class="para-mid">We believe that the best experiences are those
shared with others, and our platform facilitates connections and
social interactions that enrich your event-going experience.
<p class="para-mid">Whether you're searching for the hottest
concerts, the trendiest art exhibitions, or the most
thrilling sports events, Good Times has you covered. Join us
on this journey of exploration, discovery, and celebration
as we redefine the way you experience the world <br> around
you.
</p>
</div>
</section>
<!-- footer -->
<section class="footer">
<div class="container">
<div class="footer-main-img">
<img loading="lazy" src="images/logo.png" alt>
</div>
<div class="footer-main-grid">
<div class="footer-main-grid-first">
<div class="store-app">
<a href>
<img loading="lazy" src="images/menu-left-btn.png" alt>
</a>
<a href class>
<img loading="lazy" src="images/menu-right-btn.png" alt>
</a>
</div>
</div>
<div class="footer-main-grid-second">
<p class="para-dark">Help</p>
<p class="para">About Us</p>
<p class="para">Terms & Conditions</p>
</div>
<div class="footer-main-grid-third">
<p class="para-dark">Quick Links</p>
<p class="para">Key Features Us</p>
<p class="para">3 Easy Steps to Stay Informed
</p>
</div>
<div class="footer-main-grid-fourth">
<div class="footer-btn">
<button class="common-btn">Join now</button>
</div>
<p class="para">Let the Adventure Begin</p>
<p class="para">FAQ
</p>
</div>
</div>
<div class>
<a href="#" class="copy-right">
Copyright © Good Times
</a>
</div>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous">
</script>
<script src="custom.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1,403 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="/images/icon.png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<title>Good times</title>
</head>
<body>
<!-- Header -->
<header class="header-main" id="header">
<div class="container">
<div class="header-main-inner">
<div class="logo">
<a href="index.html">
<img loading="lazy" src="images/logo.png" alt="logo">
</a>
</div>
<nav class="navs">
<div class="cross-btn">
&times;
</div>
<ul class="navLinks">
<li><a href="index.html">Home</a></li>
<li><a href="about-us.html">About Us</a></li>
<li><a href="terms.html">Terms & Condition</a></li>
</ul>
</nav>
<div class="hamburger">
<img loading="lazy" src="images/ham.png" alt>
</div>
<div class="overlay"></div>
</div>
</div>
</div>
</header>
<!-- baner -->
<section class="baner-section">
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="baner-content" data-aos="fade-right" data-aos-duration="1000">
<h1 class="big-heading">Discover the <span>Pulse</span>
<br>
of
<span>Your City!</span>
</h1>
<p class="grey-para">Stay in the loop: concerts,
festivals,
art <br> exhibits, and more near you!</p>
<div class="store-app">
<a href>
<img loading="lazy" src="images/menu-left-btn.png" alt>
</a>
<a href class>
<img loading="lazy" src="images/menu-right-btn.png" alt>
</a>
</div>
<div class="baner-btn">
<button class="common-btn">Join now</button>
</div>
</div>
</div>
<div class="col-md-6">
<div class="baner-img" data-aos="zoom-in-up" data-aos-duration="1000">
<img loading="lazy" src="images/banner-mobile.png" alt>
</div>
</div>
</div>
</div>
</section>
<!-- Key Features -->
<section class="key-features ptb">
<div class="container">
<h2 class="sec-heading">Key Features</h2>
<p class="sec-subheading">Find Whats Up</p>
<div class="row">
<div class="col-md-6">
<div class="key-main-img text-center" data-aos="flip-left">
<img loading="lazy" src="images/key-main.png" alt>
</div>
</div>
<div class="col-md-6" data-aos="flip-left" data-aos-duration="1000">
<div class="key-right-first">
<img loading="lazy" src="images/key mini.png" alt>
<div class="key-right-first-inner">
<p class="sec-mini-heading">Comprehensive Event
Listings</p>
<p class="para">Explore all the exciting stuff
happening in your city. From live music
performances to theatre shows, we've got it all
covered.</p>
</div>
</div>
<div class="key-right-second">
<img loading="lazy" src="images/key nini 2.png" alt>
<div class="key-right-second-inner">
<p class="sec-mini-heading">Personalized
Recommendations</p>
<p class="para">Receive customized event suggestions
based on your preferences and
interests. Say goodbye to FOMO and hello to
unforgettable experiences.</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- easy-steps -->
<section class="easy-steps ptb">
<div class="container">
<h2 class="sec-heading">3 Easy Steps to Stay Informed
</h2>
<p class="sec-subheading">Experience the thrill of your city like never
before <br> with Good Times.
</p>
<div class="easy-steps-main">
<div class="easy-steps-first">
<img loading="lazy" src="images/1.png" alt class="easy-steps-first-img-num">
<h4 class="para-dark">Sign Up</h4>
<p class="para">Create your account using your email <br> or
social media account.
</p>
<img loading="lazy" src="images/1 inner.png" alt class="easy-steps-first-img-bot">
</div>
<div class="easy-steps-first">
<img loading="lazy" src="images/2.png" alt class="easy-steps-first-img-num">
<h4 class="para-dark">Subscribe</h4>
<p class="para">Subscribe, pick your city, and share <br>
your
interests with us.
</p>
<img loading="lazy" src="images/2 inner.png" alt class="easy-steps-first-img-bot">
</div>
<div class="easy-steps-first">
<img loading="lazy" src="images/3.png" alt class="easy-steps-first-img-num">
<h4 class="para-dark">Start Exploring</h4>
<p class="para">Discover your citys best experiences <br>
and
events.
</p>
<img loading="lazy" src="images/3 inner.png" alt class="easy-steps-first-img-bot">
</div>
</div>
</div>
</section>
<!-- Adventure -->
<section class="Adventure ptb">
<div class="container">
<h2 class="sec-heading">Let the Adventure Begin
</h2>
<p class="sec-subheading">Plan Your Outings With Confidence
</p>
<div class="row">
<div class="col-md-6">
<div class="Adventure-left">
<div class="Adventure-rti">
<div class="Adventure-icon">
<img loading="lazy" src="images/Star 1.png" alt>
</div>
<div class="Adventure-text">Stay Informed</div>
</div>
<div class="Adventure-rti">
<div class="Adventure-icon">
<img loading="lazy" src="images/Star 1.png" alt>
</div>
<div class="Adventure-text">Curated
Recommendations</div>
</div>
<div class="Adventure-rti">
<div class="Adventure-icon">
<img loading="lazy" src="images/Star 1.png" alt>
</div>
<div class="Adventure-text">Social Sharing</div>
</div>
<div class="store-app">
<a href>
<img loading="lazy" src="images/menu-left-btn.png" alt>
</a>
<a href class>
<img loading="lazy" src="images/menu-right-btn.png" alt>
</a>
</div>
<div class="Adventure-btn">
<button class="common-btn">Join now</button>
</div>
</div>
</div>
<div class="col-md-6">
<div class="Adventure-right text-center">
<img loading="lazy" src="images/Adventure.png" alt>
</div>
</div>
</div>
</section>
<!-- fAQ -->
<section class="faq ptb">
<div class="container">
<h2 class="sec-heading">FAQ</h2>
<div class="main-faq">
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
What types of events does Good Times cover?
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show"
data-bs-parent="#accordionExample">
<div class="accordion-body">
Good Times covers a wide range of events,
including concerts, sporting events,
business
meets, tech showcases, and more. Whatever
your interests, we've got something for you.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
How can I find events in my city?
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<strong>This is the second item's accordion
body.</strong> It is hidden by default,
until the collapse plugin adds the
appropriate classes that we use to style
each element. These classes control the
overall appearance, as well as the showing
and hiding via CSS transitions. You can
modify any of this with custom CSS or
overriding our default variables. It's also
worth noting that just about any HTML can go
within the <code>.accordion-body</code>,
though the transition does limit overflow.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
How does Good Times personalise event
recommendations for me?
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<strong>This is the third item's accordion
body.</strong> It is hidden by default,
until the collapse plugin adds the
appropriate classes that we use to style
each element. These classes control the
overall appearance, as well as the showing
and hiding via CSS transitions. You can
modify any of this with custom CSS or
overriding our default variables. It's also
worth noting that just about any HTML can go
within the <code>.accordion-body</code>,
though the transition does limit overflow.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseThree">
Are there any membership fees or
subscription charges for using Good Times?
</button>
</h2>
<div id="collapseFour" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<strong>This is the third item's accordion
body.</strong> It is hidden by default,
until the collapse plugin adds the
appropriate classes that we use to style
each element. These classes control the
overall appearance, as well as the showing
and hiding via CSS transitions. You can
modify any of this with custom CSS or
overriding our default variables. It's also
worth noting that just about any HTML can go
within the <code>.accordion-body</code>,
though the transition does limit overflow.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseFive" aria-expanded="false" aria-controls="collapseThree">
Can I use Good Times as an event manager?
</button>
</h2>
<div id="collapseFive" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<strong>This is the third item's accordion
body.</strong> It is hidden by default,
until the collapse plugin adds the
appropriate classes that we use to style
each element. These classes control the
overall appearance, as well as the showing
and hiding via CSS transitions. You can
modify any of this with custom CSS or
overriding our default variables. It's also
worth noting that just about any HTML can go
within the <code>.accordion-body</code>,
though the transition does limit overflow.
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- footer -->
<section class="footer">
<div class="container">
<div class="footer-main-img">
<img loading="lazy" src="images/logo.png" alt>
</div>
<div class="footer-main-grid">
<div class="footer-main-grid-first">
<div class="store-app">
<a href>
<img loading="lazy" src="images/menu-left-btn.png" alt>
</a>
<a href class>
<img loading="lazy" src="images/menu-right-btn.png" alt>
</a>
</div>
</div>
<div class="footer-main-grid-second">
<p class="para-dark">Help</p>
<p class="para">About Us</p>
<p class="para">Terms & Conditions</p>
</div>
<div class="footer-main-grid-third">
<p class="para-dark">Quick Links</p>
<p class="para">Key Features Us</p>
<p class="para">3 Easy Steps to Stay Informed
</p>
</div>
<div class="footer-main-grid-fourth">
<div class="footer-btn">
<button class="common-btn">Join now</button>
</div>
<p class="para">Let the Adventure Begin</p>
<p class="para">FAQ
</p>
</div>
</div>
<div class>
<a href="#" class="copy-right">
Copyright © Good Times
</a>
</div>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous">
</script>
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script src="custom.js"></script>
</body>
</html>

View File

@@ -0,0 +1,166 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="/images/icon.png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
<link rel="stylesheet" href="style.css">
<title>Good times</title>
</head>
<body>
<!-- Header -->
<section class="terms-sec">
<header class="header-main" id="header">
<div class="container">
<div class="header-main-inner">
<div class="logo">
<a href="index.html">
<img loading="lazy" src="images/logo.png" alt>
</a>
</div>
<nav class="navs">
<div class="cross-btn">
&times;
</div>
<ul class="navLinks">
<li><a href="index.html">Home</a></li>
<li><a href="about-us.html">About Us</a></li>
<li><a href="terms.html">Terms &
Condition</a></li>
</ul>
</nav>
<div class="hamburger">
<img loading="lazy" src="images/ham.png" alt>
</div>
<div class="overlay"></div>
</div>
</div>
</div>
</header>
</section>
<!-- terms page -->
<section class="terms-main">
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="about-us-left">
<h1 class="big-heading">Terms & Condition</h1>
</div>
</div>
</div>
</div>
</section>
<!-- about-us -->
<section class="terms">
<div class="container">
<p class="para-mid">Welcome to Good Times
</p>
<p class="para-mid">These terms and conditions outline the rules and
regulations for the use of Good Times, located at Website.com.
</p>
<p class="para-mid">By accessing this website we assume you accept
these terms and conditions. Do not continue to use Good
Times if you do not agree to take all of the terms and
conditions stated on this page.
</p>
<p class="para-mid">The following terminology applies to these Terms
and Conditions, Privacy Statement and Disclaimer Notice and all
Agreements: “Client”, “You” and “Your” refers to you, the
person
log on this website and compliant to the Company's terms and
conditions. “The Company”, “Ourselves”, “We”, “Our” and
“Us”,
refers to our Company. “Party”, “Parties”, or “Us”, refers to
both the Client and ourselves. All terms refer to the
offer,
acceptance and consideration of payment necessary to undertake
the process of our assistance to the Client in the most
appropriate manner for the express purpose of meeting the
Client's needs in respect of provision of the Company's
stated
services, in accordance with and subject to, prevailing law of
Netherlands. Any use of the above terminology or other
words in
the singular, plural, capitalization and/or he/she or they, are
taken as interchangeable and therefore as referring to
same.
</p>
<p class="para-mid">We employ the use of cookies. By accessing Good
Times, you agreed to use cookies in agreement with the Good
Times Privacyc Policy. Most interactive websites use
cookies to let us retrieve the user's details for each visit.
Cookies are used by our website to enable the functionality
of certain areas to make it easier for people visiting our
website. Some of our affiliate/advertising partners may
also use cookies.
</div>
</section>
<!-- footer -->
<section class="footer">
<div class="container">
<div class="footer-main-img">
<img loading="lazy" src="images/logo.png" alt>
</div>
<div class="footer-main-grid">
<div class="footer-main-grid-first">
<!-- <div class="footer-main-grid-first-img"> -->
<div class="store-app">
<a href>
<img loading="lazy" src="images/menu-left-btn.png" alt>
</a>
<a href class>
<img loading="lazy" src="images/menu-right-btn.png" alt>
</a>
</div>
</div>
<div class="footer-main-grid-second">
<p class="para-dark">Help</p>
<p class="para">About Us</p>
<p class="para">Terms & Conditions</p>
</div>
<div class="footer-main-grid-third">
<p class="para-dark">Quick Links</p>
<p class="para">Key Features Us</p>
<p class="para">3 Easy Steps to Stay Informed
</p>
</div>
<div class="footer-main-grid-fourth">
<div class="footer-btn">
<button class="common-btn">Join now</button>
</div>
<p class="para">Let the Adventure Begin</p>
<p class="para">FAQ
</p>
</div>
</div>
<div class>
<a href="#" class="copy-right">
Copyright © Good Times
</a>
</div>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous">
</script>
<script src="custom.js"></script>
</body>
</html>