mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-08 08:28:15 -04:00
merge main
This commit is contained in:
@@ -162,6 +162,17 @@ def _csp(config):
|
|||||||
|
|
||||||
|
|
||||||
def create_app(application):
|
def create_app(application):
|
||||||
|
application.config["FEATURE_BEST_PRACTICES_ENABLED"] = (
|
||||||
|
os.getenv("FEATURE_BEST_PRACTICES_ENABLED", "false").lower() == "true"
|
||||||
|
)
|
||||||
|
|
||||||
|
@application.context_processor
|
||||||
|
def inject_feature_flags():
|
||||||
|
feature_best_practices_enabled = application.config[
|
||||||
|
"FEATURE_BEST_PRACTICES_ENABLED"
|
||||||
|
]
|
||||||
|
return dict(FEATURE_BEST_PRACTICES_ENABLED=feature_best_practices_enabled)
|
||||||
|
|
||||||
notify_environment = os.environ["NOTIFY_ENVIRONMENT"]
|
notify_environment = os.environ["NOTIFY_ENVIRONMENT"]
|
||||||
|
|
||||||
application.config.from_object(configs[notify_environment])
|
application.config.from_object(configs[notify_environment])
|
||||||
|
|||||||
@@ -21,16 +21,10 @@ from notifications_utils.url_safe_token import generate_token
|
|||||||
|
|
||||||
# Hook to check for feature flags
|
# Hook to check for feature flags
|
||||||
@main.before_request
|
@main.before_request
|
||||||
def check_feature_flags():
|
def check_guidance_feature():
|
||||||
if (
|
if (
|
||||||
request.path.startswith("/best-practices")
|
request.path.startswith("/guides/best-practices")
|
||||||
and not current_app.config.get("FEATURE_BEST_PRACTICES_ENABLED", False)
|
and not current_app.config["FEATURE_BEST_PRACTICES_ENABLED"]
|
||||||
):
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
if (
|
|
||||||
request.path.startswith("/about")
|
|
||||||
and not current_app.config.get("FEATURE_ABOUT_PAGE_ENABLED", False)
|
|
||||||
):
|
):
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
@@ -40,6 +34,8 @@ def index():
|
|||||||
if current_user and current_user.is_authenticated:
|
if current_user and current_user.is_authenticated:
|
||||||
return redirect(url_for("main.choose_account"))
|
return redirect(url_for("main.choose_account"))
|
||||||
|
|
||||||
|
ttl = 24 * 60 * 60
|
||||||
|
|
||||||
# make and store the state
|
# make and store the state
|
||||||
state = generate_token(
|
state = generate_token(
|
||||||
str(request.remote_addr),
|
str(request.remote_addr),
|
||||||
@@ -47,12 +43,12 @@ def index():
|
|||||||
current_app.config["DANGEROUS_SALT"],
|
current_app.config["DANGEROUS_SALT"],
|
||||||
)
|
)
|
||||||
state_key = f"login-state-{unquote(state)}"
|
state_key = f"login-state-{unquote(state)}"
|
||||||
redis_client.set(state_key, state)
|
redis_client.set(state_key, state, ex=ttl)
|
||||||
|
|
||||||
# make and store the nonce
|
# make and store the nonce
|
||||||
nonce = secrets.token_urlsafe()
|
nonce = secrets.token_urlsafe()
|
||||||
nonce_key = f"login-nonce-{unquote(nonce)}"
|
nonce_key = f"login-nonce-{unquote(nonce)}"
|
||||||
redis_client.set(nonce_key, nonce)
|
redis_client.set(nonce_key, nonce, ex=ttl)
|
||||||
|
|
||||||
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
||||||
if url is not None:
|
if url is not None:
|
||||||
@@ -210,7 +206,7 @@ def trial_mode_new():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/best-practices")
|
@main.route("/guides/best-practices")
|
||||||
@user_is_logged_in
|
@user_is_logged_in
|
||||||
def best_practices():
|
def best_practices():
|
||||||
return render_template(
|
return render_template(
|
||||||
@@ -219,7 +215,7 @@ def best_practices():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/best-practices/clear-goals")
|
@main.route("/guides/best-practices/clear-goals")
|
||||||
@user_is_logged_in
|
@user_is_logged_in
|
||||||
def clear_goals():
|
def clear_goals():
|
||||||
return render_template(
|
return render_template(
|
||||||
@@ -228,7 +224,7 @@ def clear_goals():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/best-practices/rules-and-regulations")
|
@main.route("/guides/best-practices/rules-and-regulations")
|
||||||
@user_is_logged_in
|
@user_is_logged_in
|
||||||
def rules_and_regulations():
|
def rules_and_regulations():
|
||||||
return render_template(
|
return render_template(
|
||||||
@@ -237,7 +233,7 @@ def rules_and_regulations():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/best-practices/establish-trust")
|
@main.route("/guides/best-practices/establish-trust")
|
||||||
@user_is_logged_in
|
@user_is_logged_in
|
||||||
def establish_trust():
|
def establish_trust():
|
||||||
return render_template(
|
return render_template(
|
||||||
@@ -246,7 +242,7 @@ def establish_trust():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/best-practices/write-for-action")
|
@main.route("/guides/best-practices/write-for-action")
|
||||||
@user_is_logged_in
|
@user_is_logged_in
|
||||||
def write_for_action():
|
def write_for_action():
|
||||||
return render_template(
|
return render_template(
|
||||||
@@ -255,7 +251,7 @@ def write_for_action():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/best-practices/multiple-languages")
|
@main.route("/guides/best-practices/multiple-languages")
|
||||||
@user_is_logged_in
|
@user_is_logged_in
|
||||||
def multiple_languages():
|
def multiple_languages():
|
||||||
return render_template(
|
return render_template(
|
||||||
@@ -264,7 +260,7 @@ def multiple_languages():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/best-practices/benchmark-performance")
|
@main.route("/guides/best-practices/benchmark-performance")
|
||||||
@user_is_logged_in
|
@user_is_logged_in
|
||||||
def benchmark_performance():
|
def benchmark_performance():
|
||||||
return render_template(
|
return render_template(
|
||||||
@@ -273,15 +269,7 @@ def benchmark_performance():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/about")
|
@main.route("/guides/using-notify/guidance")
|
||||||
def about_notify():
|
|
||||||
return render_template(
|
|
||||||
"views/about/about.html",
|
|
||||||
navigation_links=about_notify_nav(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@main.route("/using-notify/guidance")
|
|
||||||
@user_is_logged_in
|
@user_is_logged_in
|
||||||
def guidance_index():
|
def guidance_index():
|
||||||
return render_template(
|
return render_template(
|
||||||
@@ -293,6 +281,13 @@ def guidance_index():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@main.route("/about")
|
||||||
|
def about_notify():
|
||||||
|
return render_template(
|
||||||
|
"views/about/about.html",
|
||||||
|
navigation_links=about_notify_nav(),
|
||||||
|
)
|
||||||
|
|
||||||
@main.route("/using-notify/guidance/create-and-send-messages")
|
@main.route("/using-notify/guidance/create-and-send-messages")
|
||||||
@user_is_logged_in
|
@user_is_logged_in
|
||||||
def create_and_send_messages():
|
def create_and_send_messages():
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ from app.main.views.verify import activate_user
|
|||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.utils import hide_from_search_engines
|
from app.utils import hide_from_search_engines
|
||||||
from app.utils.login import get_id_token, is_safe_redirect_url
|
from app.utils.login import get_id_token, is_safe_redirect_url
|
||||||
from app.utils.time import is_less_than_days_ago
|
|
||||||
|
# from app.utils.time import is_less_than_days_ago
|
||||||
from app.utils.user import is_gov_user
|
from app.utils.user import is_gov_user
|
||||||
from notifications_utils.url_safe_token import generate_token
|
from notifications_utils.url_safe_token import generate_token
|
||||||
|
|
||||||
@@ -108,11 +109,15 @@ def _do_login_dot_gov(): # $ pragma: no cover
|
|||||||
)
|
)
|
||||||
raise Exception(f"Could not login with login.gov {login_gov_error}")
|
raise Exception(f"Could not login with login.gov {login_gov_error}")
|
||||||
elif code and state:
|
elif code and state:
|
||||||
state_key = f"login-state-{unquote(state)}"
|
verify_key = f"login-verify_email-{unquote(state)}"
|
||||||
stored_state = unquote(redis_client.get(state_key).decode("utf8"))
|
verify_path = bool(redis_client.get(verify_key))
|
||||||
if state != stored_state:
|
|
||||||
current_app.logger.error(f"State Error: {state} != {stored_state}")
|
if not verify_path:
|
||||||
abort(403)
|
state_key = f"login-state-{unquote(state)}"
|
||||||
|
stored_state = unquote(redis_client.get(state_key).decode("utf8"))
|
||||||
|
if state != stored_state:
|
||||||
|
current_app.logger.error(f"State Error: {state} != {stored_state}")
|
||||||
|
abort(403)
|
||||||
|
|
||||||
# activate the user
|
# activate the user
|
||||||
try:
|
try:
|
||||||
@@ -130,12 +135,17 @@ def _do_login_dot_gov(): # $ pragma: no cover
|
|||||||
f"Retrieved user {user['id']} from db #notify-admin-1505"
|
f"Retrieved user {user['id']} from db #notify-admin-1505"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if the email needs to be revalidated
|
# Temporary disabling of this until we figure out what is happening.
|
||||||
is_fresh_email = is_less_than_days_ago(
|
# # Check if the email needs to be revalidated
|
||||||
user["email_access_validated_at"], 90
|
# is_fresh_email = is_less_than_days_ago(
|
||||||
)
|
# user["email_access_validated_at"], 90
|
||||||
if not is_fresh_email:
|
# )
|
||||||
return verify_email(user, redirect_url)
|
# if not is_fresh_email:
|
||||||
|
# # send email verify
|
||||||
|
# ttl = 24 * 60 * 60
|
||||||
|
# verify_key = f"login-verify_email-{unquote(state)}"
|
||||||
|
# redis_client.set(verify_key, state, ex=ttl)
|
||||||
|
# return verify_email(user, redirect_url)
|
||||||
|
|
||||||
usr = User.from_email_address(user["email_address"])
|
usr = User.from_email_address(user["email_address"])
|
||||||
current_app.logger.info(f"activating user {usr.id} #notify-admin-1505")
|
current_app.logger.info(f"activating user {usr.id} #notify-admin-1505")
|
||||||
@@ -209,17 +219,19 @@ def sign_in(): # pragma: no cover
|
|||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
return redirect(url_for("main.show_accounts_or_dashboard"))
|
return redirect(url_for("main.show_accounts_or_dashboard"))
|
||||||
|
|
||||||
|
ttl = 24 * 60 * 60
|
||||||
|
|
||||||
state = generate_token(
|
state = generate_token(
|
||||||
str(request.remote_addr),
|
str(request.remote_addr),
|
||||||
current_app.config["SECRET_KEY"],
|
current_app.config["SECRET_KEY"],
|
||||||
current_app.config["DANGEROUS_SALT"],
|
current_app.config["DANGEROUS_SALT"],
|
||||||
)
|
)
|
||||||
state_key = f"login-state-{unquote(state)}"
|
state_key = f"login-state-{unquote(state)}"
|
||||||
redis_client.set(state_key, state)
|
redis_client.set(state_key, state, ex=ttl)
|
||||||
|
|
||||||
nonce = secrets.token_urlsafe()
|
nonce = secrets.token_urlsafe()
|
||||||
nonce_key = f"login-nonce-{unquote(nonce)}"
|
nonce_key = f"login-nonce-{unquote(nonce)}"
|
||||||
redis_client.set(nonce_key, nonce)
|
redis_client.set(nonce_key, nonce, ex=ttl)
|
||||||
|
|
||||||
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
||||||
# handle unit tests
|
# handle unit tests
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ def using_notify_nav():
|
|||||||
"name": "Get started",
|
"name": "Get started",
|
||||||
"link": "main.get_started",
|
"link": "main.get_started",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Guides",
|
||||||
|
"link": "main.best_practices",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Trial mode",
|
"name": "Trial mode",
|
||||||
"link": "main.trial_mode_new",
|
"link": "main.trial_mode_new",
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ class HeaderNavigation(Navigation):
|
|||||||
"roadmap",
|
"roadmap",
|
||||||
"security",
|
"security",
|
||||||
},
|
},
|
||||||
|
"best_practices": {
|
||||||
|
"best_practices",
|
||||||
|
"clear_goals",
|
||||||
|
"rules_and_regulations",
|
||||||
|
"establish_trust",
|
||||||
|
"write_for_action",
|
||||||
|
"multiple_languages",
|
||||||
|
"benchmark_performance"
|
||||||
|
},
|
||||||
"using_notify": {
|
"using_notify": {
|
||||||
"get_started",
|
"get_started",
|
||||||
"using_notify",
|
"using_notify",
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
}
|
}
|
||||||
data = _attach_current_user(data)
|
data = _attach_current_user(data)
|
||||||
|
|
||||||
|
ttl = 24 * 60 * 60
|
||||||
|
|
||||||
# make and store the state
|
# make and store the state
|
||||||
state = generate_token(
|
state = generate_token(
|
||||||
str(request.remote_addr),
|
str(request.remote_addr),
|
||||||
@@ -48,12 +50,12 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
current_app.config["DANGEROUS_SALT"],
|
current_app.config["DANGEROUS_SALT"],
|
||||||
)
|
)
|
||||||
state_key = f"login-state-{unquote(state)}"
|
state_key = f"login-state-{unquote(state)}"
|
||||||
redis_client.set(state_key, state)
|
redis_client.set(state_key, state, ex=ttl)
|
||||||
|
|
||||||
# make and store the nonce
|
# make and store the nonce
|
||||||
nonce = secrets.token_urlsafe()
|
nonce = secrets.token_urlsafe()
|
||||||
nonce_key = f"login-nonce-{unquote(nonce)}"
|
nonce_key = f"login-nonce-{unquote(nonce)}"
|
||||||
redis_client.set(nonce_key, nonce) # save the nonce to redis.
|
redis_client.set(nonce_key, nonce, ex=ttl) # save the nonce to redis.
|
||||||
|
|
||||||
data["nonce"] = nonce # This is passed to api for the invite url.
|
data["nonce"] = nonce # This is passed to api for the invite url.
|
||||||
data["state"] = state # This is passed to api for the invite url.
|
data["state"] = state # This is passed to api for the invite url.
|
||||||
@@ -64,7 +66,7 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
invite_data_key = f"invitedata-{unquote(state)}"
|
invite_data_key = f"invitedata-{unquote(state)}"
|
||||||
redis_invite_data = resp["invite"]
|
redis_invite_data = resp["invite"]
|
||||||
redis_invite_data = json.dumps(redis_invite_data)
|
redis_invite_data = json.dumps(redis_invite_data)
|
||||||
redis_client.set(invite_data_key, redis_invite_data)
|
redis_client.set(invite_data_key, redis_invite_data, ex=ttl)
|
||||||
|
|
||||||
return resp_data
|
return resp_data
|
||||||
|
|
||||||
@@ -97,6 +99,8 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data)
|
self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data)
|
||||||
|
|
||||||
def resend_invite(self, service_id, invited_user_id):
|
def resend_invite(self, service_id, invited_user_id):
|
||||||
|
ttl = 24 * 60 * 60
|
||||||
|
|
||||||
# make and store the state
|
# make and store the state
|
||||||
state = generate_token(
|
state = generate_token(
|
||||||
str(request.remote_addr),
|
str(request.remote_addr),
|
||||||
@@ -104,12 +108,12 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
current_app.config["DANGEROUS_SALT"],
|
current_app.config["DANGEROUS_SALT"],
|
||||||
)
|
)
|
||||||
state_key = f"login-state-{unquote(state)}"
|
state_key = f"login-state-{unquote(state)}"
|
||||||
redis_client.set(state_key, state)
|
redis_client.set(state_key, state, ex=ttl)
|
||||||
|
|
||||||
# make and store the nonce
|
# make and store the nonce
|
||||||
nonce = secrets.token_urlsafe()
|
nonce = secrets.token_urlsafe()
|
||||||
nonce_key = f"login-nonce-{unquote(nonce)}"
|
nonce_key = f"login-nonce-{unquote(nonce)}"
|
||||||
redis_client.set(nonce_key, nonce)
|
redis_client.set(nonce_key, nonce, ex=ttl)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"nonce": nonce,
|
"nonce": nonce,
|
||||||
@@ -122,7 +126,7 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
invite_data_key = f"invitedata-{unquote(state)}"
|
invite_data_key = f"invitedata-{unquote(state)}"
|
||||||
redis_invite_data = resp["invite"]
|
redis_invite_data = resp["invite"]
|
||||||
redis_invite_data = json.dumps(redis_invite_data)
|
redis_invite_data = json.dumps(redis_invite_data)
|
||||||
redis_client.set(invite_data_key, redis_invite_data)
|
redis_client.set(invite_data_key, redis_invite_data, ex=ttl)
|
||||||
|
|
||||||
@cache.delete("service-{service_id}")
|
@cache.delete("service-{service_id}")
|
||||||
@cache.delete("user-{invited_user_id}")
|
@cache.delete("user-{invited_user_id}")
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class UserApiClient(NotifyAdminAPIClient):
|
|||||||
data["next"] = next_string
|
data["next"] = next_string
|
||||||
if code_type == "email":
|
if code_type == "email":
|
||||||
data["email_auth_link_host"] = self.admin_url
|
data["email_auth_link_host"] = self.admin_url
|
||||||
endpoint = "/user/{0}/{1}-code".format(user_id, code_type)
|
endpoint = f"/user/{user_id}/{code_type}-code"
|
||||||
current_app.logger.warn(hilite(f"Sending verify_code {code_type} to {user_id}"))
|
current_app.logger.warn(hilite(f"Sending verify_code {code_type} to {user_id}"))
|
||||||
self.post(endpoint, data=data)
|
self.post(endpoint, data=data)
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,43 @@
|
|||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
{% set navigation = [
|
{% set navigation = [
|
||||||
{"href": url_for("main.show_accounts_or_dashboard"), "text": "Current service", "active": header_navigation.is_selected('accounts-or-dashboard')},
|
{"href": url_for("main.show_accounts_or_dashboard"), "text": "Current service", "active":
|
||||||
{"href": url_for('main.get_started'), "text": "Using Notify", "active": header_navigation.is_selected('using_notify')},
|
header_navigation.is_selected('accounts-or-dashboard')},
|
||||||
{"href": url_for('main.features'), "text": "Features", "active": header_navigation.is_selected('features')},
|
{"href": url_for('main.get_started'), "text": "Using Notify", "active": header_navigation.is_selected('using_notify')}
|
||||||
{"href": url_for('main.support'), "text": "Contact us", "active": header_navigation.is_selected('support')}
|
] %}
|
||||||
] %}
|
|
||||||
|
|
||||||
{% if current_user.platform_admin %}
|
{% if FEATURE_BEST_PRACTICES_ENABLED %}
|
||||||
{% set navigation = navigation + [{"href": url_for('main.platform_admin_splash_page'), "text": "Platform admin", "active": header_navigation.is_selected('platform-admin')}] %}
|
{% set navigation = navigation + [{"href": url_for('main.best_practices'), "text": "Guides", "active":
|
||||||
{% else %}
|
header_navigation.is_selected('best_practices')}] %}
|
||||||
{% set navigation = navigation + [{"href": url_for('main.user_profile'), "text": "User profile", "active": header_navigation.is_selected('user-profile')}] %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if current_service %}
|
{% set navigation = navigation + [
|
||||||
{% if current_user.has_permissions('manage_service') %}
|
{"href": url_for('main.features'), "text": "Features", "active": header_navigation.is_selected('features')},
|
||||||
{% set secondaryNavigation = [
|
{"href": url_for('main.support'), "text": "Contact us", "active": header_navigation.is_selected('support')}
|
||||||
{"href": url_for('main.service_settings', service_id=current_service.id), "text": "Settings", "active": secondary_navigation.is_selected('settings')},
|
] %}
|
||||||
{"href": url_for('main.sign_out'), "text": "Sign out"}
|
|
||||||
] %}
|
|
||||||
{% else %}
|
|
||||||
{% set secondaryNavigation = [
|
|
||||||
{"href": url_for('main.sign_out'), "text": "Sign out"}
|
|
||||||
] %}
|
|
||||||
|
|
||||||
{% endif %}
|
{% if current_user.platform_admin %}
|
||||||
{% else %}
|
{% set navigation = navigation + [{"href": url_for('main.platform_admin_splash_page'), "text": "Platform admin",
|
||||||
{% set secondaryNavigation = [{"href": url_for('main.sign_out'), "text": "Sign out"}] %}
|
"active": header_navigation.is_selected('platform-admin')}] %}
|
||||||
{% endif %}
|
{% else %}
|
||||||
|
{% set navigation = navigation + [{"href": url_for('main.user_profile'), "text": "User profile", "active":
|
||||||
|
header_navigation.is_selected('user-profile')}] %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if current_service %}
|
||||||
|
{% if current_user.has_permissions('manage_service') %}
|
||||||
|
{% set secondaryNavigation = [
|
||||||
|
{"href": url_for('main.service_settings', service_id=current_service.id), "text": "Settings", "active":
|
||||||
|
secondary_navigation.is_selected('settings')},
|
||||||
|
{"href": url_for('main.sign_out'), "text": "Sign out"}
|
||||||
|
] %}
|
||||||
|
{% else %}
|
||||||
|
{% set secondaryNavigation = [
|
||||||
|
{"href": url_for('main.sign_out'), "text": "Sign out"}
|
||||||
|
] %}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% set secondaryNavigation = [{"href": url_for('main.sign_out'), "text": "Sign out"}] %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<header class="usa-header usa-header--extended">
|
<header class="usa-header usa-header--extended">
|
||||||
@@ -36,12 +47,12 @@
|
|||||||
<div class="logo-img display-flex">
|
<div class="logo-img display-flex">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<span class="usa-sr-only">Notify.gov logo</span>
|
<span class="usa-sr-only">Notify.gov logo</span>
|
||||||
<img src="{{ (asset_path | default('/static')) + 'images/notify-logo.svg' }}" alt="Notify.gov logo" class="usa-flag-logo margin-right-1">
|
<img src="{{ (asset_path | default('/static')) + 'images/notify-logo.svg' }}" alt="Notify.gov logo"
|
||||||
|
class="usa-flag-logo margin-right-1">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% if navigation %}
|
{% if navigation %}
|
||||||
<button type="button" class="usa-menu-btn">Menu</button>
|
<button type="button" class="usa-menu-btn">Menu</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,29 +63,29 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="usa-nav__primary usa-accordion margin-right-1">
|
<ul class="usa-nav__primary usa-accordion margin-right-1">
|
||||||
{% for item in navigation %}
|
{% for item in navigation %}
|
||||||
{% if item.href and item.text %}
|
{% if item.href and item.text %}
|
||||||
<li class="usa-nav__primary-item{{ ' is-current' if item.active }}">
|
<li class="usa-nav__primary-item{{ ' is-current' if item.active }}">
|
||||||
<a class="usa-nav__link {{ ' usa-current' if item.active }}" href="{{ item.href }}" {% for attribute, value in
|
<a class="usa-nav__link {{ ' usa-current' if item.active }}" href="{{ item.href }}" {% for attribute, value
|
||||||
item.attributes %} {{attribute}}="{{value}}" {% endfor %}>
|
in item.attributes %} {{attribute}}="{{value}}" {% endfor %}>
|
||||||
<span>{{ item.text }}</span>
|
<span>{{ item.text }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="usa-nav__secondary margin-bottom-6">
|
<div class="usa-nav__secondary margin-bottom-6">
|
||||||
<ul class="usa-nav__secondary-links">
|
<ul class="usa-nav__secondary-links">
|
||||||
{% if secondaryNavigation %}
|
{% if secondaryNavigation %}
|
||||||
{% for item in secondaryNavigation %}
|
{% for item in secondaryNavigation %}
|
||||||
{% if item.href and item.text %}
|
{% if item.href and item.text %}
|
||||||
<li class="usa-nav__secondary-item{{ ' is-current' if item.active }}">
|
<li class="usa-nav__secondary-item{{ ' is-current' if item.active }}">
|
||||||
<a class="usa-nav__link {{ ' usa-current' if item.active }}" href="{{ item.href }}" {% for attribute, value in
|
<a class="usa-nav__link {{ ' usa-current' if item.active }}" href="{{ item.href }}" {% for attribute,
|
||||||
item.attributes %} {{attribute}}="{{value}}" {% endfor %}>
|
value in item.attributes %} {{attribute}}="{{value}}" {% endfor %}>
|
||||||
<span>{{ item.text }}</span>
|
<span>{{ item.text }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,37 +23,37 @@ Best Practices
|
|||||||
"svg_src": "goal",
|
"svg_src": "goal",
|
||||||
"card_heading": "Establish clear goals",
|
"card_heading": "Establish clear goals",
|
||||||
"p_text": "Start with a singular purpose. Make explicit what you want to achieve.",
|
"p_text": "Start with a singular purpose. Make explicit what you want to achieve.",
|
||||||
"link": "/best-practices/clear-goals"
|
"link": "/guides/best-practices/clear-goals"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"svg_src": "compliant",
|
"svg_src": "compliant",
|
||||||
"card_heading": "Follow rules & regulations",
|
"card_heading": "Follow rules & regulations",
|
||||||
"p_text": "Understand what is required when texting the public.",
|
"p_text": "Understand what is required when texting the public.",
|
||||||
"link": "/best-practices/rules-and-regulations"
|
"link": "/guides/best-practices/rules-and-regulations"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"svg_src": "trust",
|
"svg_src": "trust",
|
||||||
"card_heading": "Establish trust",
|
"card_heading": "Establish trust",
|
||||||
"p_text": "Help your audience anticipate and welcome your texts.",
|
"p_text": "Help your audience anticipate and welcome your texts.",
|
||||||
"link": "/best-practices/establish-trust"
|
"link": "/guides/best-practices/establish-trust"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"svg_src": "runner",
|
"svg_src": "runner",
|
||||||
"card_heading": "Write texts that provoke action",
|
"card_heading": "Write texts that provoke action",
|
||||||
"p_text": "Help your audience know what to do with the information you send.",
|
"p_text": "Help your audience know what to do with the information you send.",
|
||||||
"link": "/best-practices/write-for-action"
|
"link": "/guides/best-practices/write-for-action"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"svg_src": "language",
|
"svg_src": "language",
|
||||||
"card_heading": "Send texts in multiple languages",
|
"card_heading": "Send texts in multiple languages",
|
||||||
"p_text": "What to know as you plan translated texts.",
|
"p_text": "What to know as you plan translated texts.",
|
||||||
"link": "/best-practices/multiple-languages"
|
"link": "/guides/best-practices/multiple-languages"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"svg_src": "chart",
|
"svg_src": "chart",
|
||||||
"card_heading": "Measure performance with benchmarking",
|
"card_heading": "Measure performance with benchmarking",
|
||||||
"p_text": "Learn how effective your texting program can be.",
|
"p_text": "Learn how effective your texting program can be.",
|
||||||
"link": "/best-practices/benchmark-performance"
|
"link": "/guides/best-practices/benchmark-performance"
|
||||||
}
|
}
|
||||||
] %}
|
] %}
|
||||||
|
|
||||||
|
|||||||
26
urls.js
26
urls.js
@@ -13,13 +13,25 @@ const sublinks = [
|
|||||||
{ label: 'Roadmap', path: '/features/roadmap' },
|
{ label: 'Roadmap', path: '/features/roadmap' },
|
||||||
{ label: 'Security', path: '/features/security' },
|
{ label: 'Security', path: '/features/security' },
|
||||||
{ label: 'Support', path: '/support' },
|
{ label: 'Support', path: '/support' },
|
||||||
{ label: 'Best Practices', path: '/best-practices' },
|
{ label: 'Best Practices', path: '/guides/best-practices' },
|
||||||
{ label: 'Clear Goals', path: '/best-practices/clear-goals' },
|
{ label: 'Clear Goals', path: '/guides/best-practices/clear-goals' },
|
||||||
{ label: 'Rules And Regulations', path: '/best-practices/rules-and-regulations' },
|
{
|
||||||
{ label: 'Establish Trust', path: '/best-practices/establish-trust' },
|
label: 'Rules And Regulations',
|
||||||
{ label: 'Write For Action', path: '/best-practices/write-for-action' },
|
path: '/guides/best-practices/rules-and-regulations',
|
||||||
{ label: 'Multiple Languages', path: '/best-practices/multiple-languages' },
|
},
|
||||||
{ label: 'Benchmark Performance', path: '/best-practices/benchmark-performance' },
|
{ label: 'Establish Trust', path: '/guides/best-practices/establish-trust' },
|
||||||
|
{
|
||||||
|
label: 'Write For Action',
|
||||||
|
path: '/guides/best-practices/write-for-action',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Multiple Languages',
|
||||||
|
path: '/guides/best-practices/multiple-languages',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Benchmark Performance',
|
||||||
|
path: '/guides/best-practices/benchmark-performance',
|
||||||
|
},
|
||||||
// Add more links here as needed
|
// Add more links here as needed
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user