Skip to content
This repository was archived by the owner on Dec 8, 2022. It is now read-only.

Commit 0ddc557

Browse files
committed
add landing page
1 parent c156528 commit 0ddc557

24 files changed

+3736
-39
lines changed

CodeChallenge/__init__.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
from flask import Flask, jsonify, make_response, send_from_directory
1+
import os
2+
3+
from flask import Flask, jsonify, make_response, send_from_directory, redirect
24
from flask_cors import CORS
35
from werkzeug.middleware.proxy_fix import ProxyFix
6+
from werkzeug.utils import import_string
47

8+
from . import core
59
from .api.eb import bp as eb_bp
610
from .api.questions import bp as questions_bp
711
from .api.users import bp as users_bp
@@ -27,8 +31,9 @@ def create_app(config):
2731
# prevent IP spoofing the rate limiter behind a reverse proxy
2832
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1)
2933

34+
cfg = import_string(f"CodeChallenge.config.{config}")()
3035
app.config.from_object(__name__)
31-
app.config.from_object(f"CodeChallenge.config.{config}")
36+
app.config.from_object(cfg)
3237

3338
# Initialize Plugins
3439
CORS(app)
@@ -53,29 +58,50 @@ def ratelimit_handler(e):
5358
status="error",
5459
reason=f"rate limit exceeded ({e.description})"), 429)
5560

61+
js_dir = os.path.join(app.config["DIST_DIR"], "js")
62+
css_dir = os.path.join(app.config["DIST_DIR"], "css")
63+
fonts_dir = os.path.join(app.config["DIST_DIR"], "fonts")
64+
images_dir = os.path.join(app.config["DIST_DIR"], "images")
65+
5666
@app.route("/js/<path:path>")
5767
def send_js(path):
58-
return send_from_directory("../dist/js", path)
68+
return send_from_directory(js_dir, path)
5969

6070
@app.route("/css/<path:path>")
6171
def send_css(path):
62-
return send_from_directory("../dist/css", path)
72+
return send_from_directory(css_dir, path)
6373

6474
@app.route("/fonts/<path:path>")
6575
def send_fonts(path):
66-
return send_from_directory("../dist/fonts", path)
76+
return send_from_directory(fonts_dir, path)
6777

6878
@app.route("/images/<path:path>")
6979
def send_images(path):
70-
return send_from_directory("../dist/images", path)
80+
return send_from_directory(images_dir, path)
7181

7282
@app.route("/assets/<path:path>")
7383
def send_assets(path):
7484
return send_from_directory("assets", path)
7585

86+
@app.route("/landing", defaults={"path": ""})
87+
@app.route("/landing/<path:path>")
88+
def send_landing(path):
89+
90+
if core.current_rank() != -1:
91+
return redirect("/")
92+
93+
if path:
94+
return send_from_directory("../landing/", path)
95+
return send_from_directory("../landing/", "index.html")
96+
7697
@app.route("/", defaults={"path": ""})
7798
@app.route("/<path:path>")
7899
def catch_all(path):
79-
return send_from_directory("../dist/", "index.html")
100+
101+
# show landing page
102+
if core.current_rank() == -1 and not path or path == "home":
103+
return redirect("/landing")
104+
105+
return send_from_directory(app.config["DIST_DIR"], "index.html")
80106

81107
return app

CodeChallenge/api/questions.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ def json_error(reason, status=400):
1515

1616

1717
@bp.route("/rank", methods=["GET"])
18-
@jwt_required
1918
def get_rank():
2019
return jsonify(status="success", rank=core.current_rank(),
21-
timeUntilNextRank=core.time_until_next_rank())
20+
maxRank=core.max_rank(),
21+
timeUntilNextRank=core.time_until_next_rank(),
22+
startsOn=core.friendly_starts_on())
2223

2324

2425
@bp.route("/next", methods=["GET"])
@@ -61,15 +62,13 @@ def answer_limit_attempts():
6162
return current_app.config.get("ANSWER_ATTEMPT_LIMIT", "3 per 30 minutes")
6263

6364

64-
# XXX: do we want to add a rate-limiter here?
65-
# https://flask-limiter.readthedocs.io/en/stable/
6665
@bp.route("/answer", methods=["POST"])
6766
@jwt_required
6867
@limiter.limit(answer_limit_attempts, key_func=user_rank)
6968
def answer_next_question():
7069
user = get_current_user()
7170
if core.current_rank() == user.rank:
72-
# all questions have been answered up to the corrent rank
71+
# all questions have been answered up to the current rank
7372
return jsonify({"status": "error",
7473
"reason": "no more questions to answer"}), 404
7574

CodeChallenge/core.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from datetime import datetime, timezone, timedelta, time
22

33
from flask import current_app
4+
from sqlalchemy import func
5+
6+
from .models import Question, db
47

58

69
def current_rank() -> int:
@@ -35,3 +38,15 @@ def time_until_next_rank() -> str:
3538
diff = datetime.combine(tomorrow, time.min, now.tzinfo) - now
3639

3740
return str(diff)
41+
42+
43+
def friendly_starts_on() -> str:
44+
"""Formatted specifically for the landing page countdown jQuery plugin"""
45+
epoch = int(current_app.config["CODE_CHALLENGE_START"])
46+
start = datetime.fromtimestamp(epoch, timezone.utc)
47+
48+
return start.strftime("%m/%d/%Y %H:%M%S UTC")
49+
50+
51+
def max_rank() -> int:
52+
return db.session.query(func.max(Question.rank)).scalar()

landing/index.html

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6+
7+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
8+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
9+
<title>The Dragon Quest | CodeWizards Code Challenge</title>
10+
11+
<link rel="shortcut icon"
12+
href="/images/shortcut-icon.png"/>
13+
<link rel='stylesheet'
14+
href='/css/bootstrap.min.css'
15+
type='text/css' media='all'/>
16+
<link rel='stylesheet'
17+
href='/css/font-awesome.min.css'
18+
type='text/css' media='all'/>
19+
<link rel='stylesheet'
20+
href='/css/animate.css'
21+
type='text/css' media='all'/>
22+
<link rel='stylesheet'
23+
href='/css/font-montserrat.css'
24+
type='text/css' media='all'/>
25+
<link rel='stylesheet'
26+
href='/css/styles.css'
27+
type='text/css' media='all'/>
28+
<link rel='stylesheet'
29+
href='/css/main.css'
30+
type='text/css' media='all'/>
31+
<style>
32+
body::before {
33+
background: none !important;
34+
}
35+
</style>
36+
37+
<style>
38+
.main-container a {
39+
color: #fff !important;
40+
transition: all ease 400ms;
41+
}
42+
43+
a:hover {
44+
color: !important;
45+
}
46+
</style>
47+
</head>
48+
49+
<body style="background: #1e2d34 url('/images/landing-bg.jpg'); color:#fff;">
50+
<div id="rocket-wrapper">
51+
<img id="rocketimg" class="img-responsive"
52+
src="http://108.160.158.207/~cwhqcodechalleng/wp-content/plugins/igniteup/includes/templates/launcher/img/red-dragon-head-flip.png">
53+
</div>
54+
<div id="gold-dragon-wrapper">
55+
<img id="gold-dragon" class="img-responsive"
56+
src="/images/yellow-dragon-head.png">
57+
<img src="/images/fire.gif"
58+
alt="fireball" class="fire">
59+
</div>
60+
<div class="container-fluid main-container">
61+
<div class="row">
62+
<div class="col-sm-12">
63+
<h1 class="text-uppercase text-center">
64+
The Dragon Quest </h1>
65+
<div class="text-center text-uppercase sub-text">
66+
CodeWizards Code Challenge
67+
</div>
68+
<div class="container-fluid" id="countdown">
69+
<div class="row text-uppercase">
70+
<div class="col-sm-3 col-xs-6 countdown-time">
71+
<span id="days" class="time">00</span><span class="time-name">d<span class="hidden-sm">ay<span
72+
id="day-s">s</span></span></span>
73+
</div>
74+
<div class="col-sm-3 col-xs-6 countdown-time">
75+
<span id="hrs" class="time">00</span><span class="time-name">h<span class="hidden-sm">our<span
76+
id="hrs-s">s</span></span></span>
77+
</div>
78+
<div class="col-sm-3 col-xs-6 countdown-time">
79+
<span id="mins" class="time">00</span><span class="time-name">m<span class="hidden-sm">in<span
80+
id="min-s">s</span></span></span>
81+
</div>
82+
<div class="col-sm-3 col-xs-6 countdown-time">
83+
<span id="secs" class="time">00</span><span class="time-name">s<span class="hidden-sm">ec<span
84+
id="sec-s">s</span></span></span>
85+
</div>
86+
</div>
87+
</div>
88+
<p class="text-center description">
89+
<h3>A coding challenge for coders ages 8-18</h3>
90+
<p>We are seeking the bravest and brightest kid coders to slay an evil dragon who has invaded CWHQ Land. You
91+
must complete 21 levels of coding challenges in HTML/CSS, Python, JavaScript or your language of
92+
choice. </p>
93+
94+
<p>Only one will be the champion, the bravest and best coder in all of CWHQ Land. The winner receives a cash
95+
prize of $100. Anyone who passes all 21 levels will receive a free coding class from CodeWizardsHQ. </p>
96+
97+
<p>Accept the challenge if you have the skills to defeat the dragon.</p>
98+
99+
<p>Challenge begins <span id="start-date"></span>. Register now. </p> </p>
100+
<div class="subscribe">
101+
<a href="/create-account" class="btn btn-default subscribe-btn" type="button"
102+
style="color: #fff; background: #DB4F4B;">Accept Challenge</a>
103+
</div>
104+
105+
<div class="social">
106+
<ul>
107+
<li><a target="_blank" href="https://www.facebook.com/codewizardshq">
108+
<div class="social-icon"><span class="fab fa-facebook"></span></div>
109+
</a></li>
110+
<li><a target="_blank" href="https://twitter.com/codewizardshq">
111+
<div class="social-icon"><span class="fab fa-twitter-square"></span></div>
112+
</a></li>
113+
<li><a target="_blank" href="https://www.instagram.com/codewizardshq/">
114+
<div class="social-icon"><span class="fab fa-instagram"></span></div>
115+
</a></li>
116+
</ul>
117+
</div>
118+
</div>
119+
</div>
120+
121+
</div>
122+
<script src="/js/moment.min.js"></script>
123+
<script src='/js/jquery.min.js'
124+
type="text/javascript"></script>
125+
<script src='/js/jquery.countdown.js'
126+
type="text/javascript"></script>
127+
<script type="text/javascript">
128+
129+
var count_completed = false;
130+
131+
jQuery
132+
.getJSON("/api/v1/questions/rank")
133+
.then(data => {
134+
console.log(data);
135+
$countdown = data.startsOn;
136+
jQuery("#start-date").text(moment($countdown).format("MMMM Do YYYY"));
137+
138+
jQuery("#secs").countdown($countdown, function (event) {
139+
jQuery(this).text(event.strftime('%S'));
140+
checkSeconds(jQuery(this).text());
141+
});
142+
jQuery("#mins")
143+
.countdown($countdown, function (event) {
144+
jQuery(this).text(
145+
event.strftime('%M')
146+
);
147+
checkMins(jQuery(this).text());
148+
});
149+
jQuery("#hrs")
150+
.countdown($countdown, function (event) {
151+
jQuery(this).text(
152+
event.strftime('%H')
153+
);
154+
checkHours(jQuery(this).text());
155+
});
156+
jQuery("#days")
157+
.countdown($countdown, function (event) {
158+
jQuery(this).text(
159+
event.strftime('%D')
160+
);
161+
checkDays(jQuery(this).text());
162+
});
163+
164+
function checkSeconds(sec) {
165+
if (sec === '01') {
166+
jQuery("#sec-s").addClass('hidden');
167+
} else {
168+
jQuery("#sec-s").removeClass('hidden');
169+
}
170+
}
171+
172+
function checkMins(min) {
173+
if (min === '01') {
174+
jQuery("#min-s").addClass('hidden');
175+
} else {
176+
jQuery("#min-s").removeClass('hidden');
177+
}
178+
}
179+
180+
function checkHours(hrs) {
181+
if (hrs === '01') {
182+
jQuery("#hrs-s").addClass('hidden');
183+
} else {
184+
jQuery("#hrs-s").removeClass('hidden');
185+
}
186+
}
187+
188+
function checkDays(days) {
189+
if (days === '01') {
190+
jQuery("#day-s").addClass('hidden');
191+
} else {
192+
jQuery("#day-s").removeClass('hidden');
193+
}
194+
}
195+
})
196+
197+
198+
jQuery(document).ready(function () {
199+
jQuery('#secs').countdown($countdown).on('finish.countdown', function () {
200+
jQuery('#countdown').hide();
201+
count_completed = true;
202+
});
203+
});
204+
205+
// red dragon
206+
jQuery(function ($) {
207+
$('#rocketimg').animate({
208+
'margin-bottom': "300px"
209+
});
210+
211+
//hover
212+
$('#rocketimg').hover(function () {
213+
$(this).animate({borderSpacing: -90}, {
214+
step: function () {
215+
$(this).css('-webkit-transform', 'rotate(15deg)');
216+
$(this).css('-moz-transform', 'rotate(15deg)');
217+
$(this).css('transform', 'rotate(15deg)');
218+
},
219+
duration: 'slow'
220+
}, 'linear');
221+
}, function () {
222+
$(this).animate({borderSpacing: 0}, {
223+
step: function () {
224+
$(this).css('-webkit-transform', 'rotate(0deg)');
225+
$(this).css('-moz-transform', 'rotate(0deg)');
226+
$(this).css('transform', 'rotate(0deg)');
227+
},
228+
duration: 'slow'
229+
}, 'linear')
230+
});
231+
232+
233+
//gold dragon
234+
$('#gold-dragon').animate({
235+
'right': '0'
236+
}, fireIt);
237+
238+
function fireIt() {
239+
$('.fire').animate({
240+
'right': '100px'
241+
});
242+
}
243+
244+
$('#gold-dragon').click(function () {
245+
$('.fire').animate({
246+
'right': '3000px'
247+
}, 2000, function () {
248+
$(this).removeAttr('style');
249+
});
250+
});
251+
252+
253+
});
254+
</script>
255+
</body>
256+
257+
</html>

0 commit comments

Comments
 (0)