add: purchased accounts

This commit is contained in:
trisua 2025-07-03 21:56:21 -04:00
parent 0aa2ea362f
commit 2ec8d86edf
22 changed files with 1279 additions and 124 deletions

View file

@ -1,7 +1,7 @@
(text "{% extends \"root.html\" %} {% block body %}")
(main
("class" "flex flex-col gap-2")
("style" "max-width: 25rem")
("style" "max-width: 48ch")
(h2
("class" "w-full text-center")
; block for title

View file

@ -48,7 +48,8 @@
("name" "totp")
("id" "totp"))))
(button
(text "Submit")))
(icon (text "arrow-right"))
(str (text "auth:action.continue"))))
(script
(text "let flow_page = 1;

View file

@ -37,16 +37,31 @@
(text "{% if config.security.enable_invite_codes -%}")
(div
("class" "flex flex-col gap-1")
("oninput" "check_should_show_purchase(event)")
(label
("for" "invite_code")
(b
(text "Invite code")))
(text "Invite code (optional)")))
(input
("type" "text")
("placeholder" "invite code")
("required" "")
("name" "invite_code")
("id" "invite_code")))
(script
(text "function check_should_show_purchase(e) {
if (e.target.value.length > 0) {
document.querySelector('[ui_ident=purchase_account]').classList.add('hidden');
document.querySelector('[ui_ident=create_account]').classList.remove('hidden');
globalThis.DO_PURCHASE = false;
} else {
document.querySelector('[ui_ident=purchase_account]').classList.remove('hidden');
document.querySelector('[ui_ident=create_account]').classList.add('hidden');
globalThis.DO_PURCHASE = true;
}
}
globalThis.DO_PURCHASE = true;"))
(text "{%- endif %}")
(hr)
(div
@ -84,8 +99,33 @@
("class" "cf-turnstile")
("data-sitekey" "{{ config.turnstile.site_key }}"))
(hr)
(text "{% if config.security.enable_invite_codes -%}")
(div
("class" "w-full flex gap-2 justify-between")
("ui_ident" "purchase_account")
(button
(icon (text "credit-card"))
(str (text "auth:action.purchase_account")))
(button
("class" "small square lowered")
("type" "button")
("onclick" "document.querySelector('[ui_ident=purchase_help]').classList.toggle('hidden')")
(icon (text "circle-question-mark"))))
(div
("class" "hidden lowered card w-full no_p_margin")
("ui_ident" "purchase_help")
(b (text "What does \"Purchase account\" mean?"))
(p (text "Your account will be created, but you cannot use it until you activate it for {{ config.stripe.supporter_price_text }}."))
(p (text "Alternatively, you can provide an invite code to create your account for free.")))
(text "{%- endif %}")
(button
(text "Submit")))
("class" "{% if config.security.enable_invite_codes -%} hidden {%- endif %}")
("ui_ident" "create_account")
(icon (text "plus"))
(str (text "auth:action.create_account"))))
(script
(text "async function register(e) {
@ -104,6 +144,7 @@
\"[name=cf-turnstile-response]\",
).value,
invite_code: (e.target.invite_code || { value: \"\" }).value,
purchase: globalThis.DO_PURCHASE,
}),
})
.then((res) => res.json())

View file

@ -2285,3 +2285,80 @@
(text "{{ self::note_mover_dirs_listing(dir=subdir, dirs=dirs) }}")
(text "{%- endif %} {% endfor %}"))
(text "{%- endmacro %}")
(text "{% macro become_supporter_button() -%}")
(p
(text "You're ")
(b
(text "not "))
(text "currently a supporter! No
pressure, but it helps us do some pretty cool
things! As a supporter, you'll get:"))
(ul
("style" "margin-bottom: var(--pad-4)")
(li
(text "Vanity badge on profile"))
(li
(text "No more supporter ads (duh)"))
(li
(text "Ability to upload gif avatars/banners"))
(li
(text "Be an admin/owner of up to 10 communities"))
(li
(text "Use custom CSS on your profile"))
(li
(text "Use community emojis outside of
their community"))
(li
(text "Upload and use gif emojis"))
(li
(text "Create infinite stack timelines"))
(li
(text "Upload images to posts"))
(li
(text "Save infinite post drafts"))
(li
(text "Ability to search through all posts"))
(li
(text "Ability to create forges"))
(li
(text "Create more than 1 app"))
(li
(text "Create up to 10 stack blocks"))
(li
(text "Add unlimited users to stacks"))
(li
(text "Increased proxied image size"))
(li
(text "Create infinite journals"))
(li
(text "Create infinite notes in each journal"))
(li
(text "Publish up to 50 notes"))
(text "{% if config.security.enable_invite_codes -%}")
(li
(text "Create up to 48 invite codes")
(sup (a ("href" "#footnote-1") (text "1"))))
(text "{%- endif %}"))
(a
("href" "{{ config.stripe.payment_link }}?client_reference_id={{ user.id }}")
("class" "button")
("target" "_blank")
(text "Become a supporter ({{ config.stripe.supporter_price_text }})"))
(span
("class" "fade")
(text "Please use your")
(b
(text " real email "))
(text "when
completing payment. It is required to manage
your billing settings."))
(text "{% if config.security.enable_invite_codes -%}")
(span
("class" "fade")
("id" "footnote-1")
(b (text "1: ")) (text "After your account is at least 1 month old"))
(text "{%- endif %}")
(text "{%- endmacro %}")

View file

@ -168,6 +168,11 @@
\"{{ profile.is_verified }}\",
\"checkbox\",
],
[
[\"awaiting_purchase\", \"Awaiting purchase\"],
\"{{ profile.awaiting_purchase }}\",
\"checkbox\",
],
[
[\"role\", \"Permission level\"],
\"{{ profile.permissions }}\",
@ -181,6 +186,11 @@
is_verified: value,
});
},
awaiting_purchase: (value) => {
profile_request(false, \"awaiting_purchase\", {
awaiting_purchase: value,
});
},
role: (new_role) => {
return update_user_role(new_role);
},

View file

@ -671,73 +671,31 @@
("target" "_blank")
(text "Manage billing"))
(text "{% else %}")
(p
(text "You're ")
(b
(text "not "))
(text "currently a supporter! No
pressure, but it helps us do some pretty cool
things! As a supporter, you'll get:"))
(ul
("style" "margin-bottom: var(--pad-4)")
(li
(text "Vanity badge on profile"))
(li
(text "No more supporter ads (duh)"))
(li
(text "Ability to upload gif avatars/banners"))
(li
(text "Be an admin/owner of up to 10 communities"))
(li
(text "Use custom CSS on your profile"))
(li
(text "Use community emojis outside of
their community"))
(li
(text "Upload and use gif emojis"))
(li
(text "Create infinite stack timelines"))
(li
(text "Upload images to posts"))
(li
(text "Save infinite post drafts"))
(li
(text "Ability to search through all posts"))
(li
(text "Ability to create forges"))
(li
(text "Create more than 1 app"))
(li
(text "Create up to 10 stack blocks"))
(li
(text "Add unlimited users to stacks"))
(li
(text "Increased proxied image size"))
(li
(text "Create infinite journals"))
(li
(text "Create infinite notes in each journal"))
(li
(text "Publish up to 50 notes"))
(text "{% if config.security.enable_invite_codes -%}")
(li
(text "Create up to 48 invite codes"))
(text "{%- endif %}"))
(a
("href" "{{ config.stripe.payment_link }}?client_reference_id={{ user.id }}")
("class" "button")
("target" "_blank")
(text "Become a supporter"))
(span
("class" "fade")
(text "Please use your")
(b
(text "real email"))
(text "when
completing payment. It is required to manage
your billing settings."))
(text "{{ components::become_supporter_button() }}")
(text "{%- endif %}")))
(text "{% if user.was_purchased and user.invite_code == 0 -%}")
(form
("class" "card w-full lowered flex flex-col gap-2")
("onsubmit" "update_invite_code(event)")
(p (text "Your account is currently activated without an invite code. If you stop paying for supporter, your account will be locked again until you renew. You can provide an invite code to avoid this if you're planning on cancelling."))
(div
("class" "flex flex-col gap-1")
(label
("for" "invite_code")
(b
(text "Invite code")))
(input
("type" "text")
("placeholder" "invite code")
("name" "invite_code")
("required" "")
("id" "invite_code")))
(button
(text "Submit")))
(text "{%- endif %}")
(text "{%- endif %}")))))
(div
("class" "w-full hidden flex flex-col gap-2")
@ -1198,6 +1156,11 @@
globalThis.delete_account = async (e) => {
e.preventDefault();
// {% if user.permissions|has_supporter %}
alert(\"Please cancel your membership before deleting your account. You'll have to wait until the next cycle to delete your account after, or you can request support if it is urgent.\");
return;
// {% endif %}
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this?\",
@ -1381,6 +1344,31 @@
});
};
globalThis.update_invite_code = async (e) => {
e.preventDefault();
await trigger(\"atto::debounce\", [\"invite_codes::try\"]);
fetch(\"/api/v1/auth/user/me/invite_code\", {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
invite_code: e.target.invite_code.value,
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
if (res.ok) {
window.location.reload();
}
});
}
const account_settings =
document.getElementById(\"account_settings\");
const profile_settings =

View file

@ -72,7 +72,73 @@
(str (text "general:label.account_banned_body"))))))
; if we aren't banned, just show the page body
(text "{% else %} {% block body %}{% endblock %} {%- endif %}")
(text "{% elif user and user.awaiting_purchase %}")
; account waiting for payment message
(article
(main
(div
("class" "card-nest")
(div
("class" "card small flex items-center gap-2 red")
(icon (text "frown"))
(str (text "general:label.must_activate_account")))
(div
("class" "card no_p_margin flex flex-col gap-2")
(p (text "Since you didn't provide an invite code, you'll need to activate your account to use it."))
(p (text "Supporter is a recurring membership. If you cancel it, your account will be locked again unless you renew your subscription or provide an invite code."))
(div
("class" "card w-full lowered flex flex-col gap-2")
(text "{{ components::become_supporter_button() }}"))
(p (text "Alternatively, you can provide an invite code to activate your account."))
(form
("class" "card w-full lowered flex flex-col gap-2")
("onsubmit" "update_invite_code(event)")
(div
("class" "flex flex-col gap-1")
(label
("for" "invite_code")
(b
(text "Invite code")))
(input
("type" "text")
("placeholder" "invite code")
("name" "invite_code")
("required" "")
("id" "invite_code")))
(button
(text "Submit")))
(script
(text "async function update_invite_code(e) {
e.preventDefault();
await trigger(\"atto::debounce\", [\"invite_codes::try\"]);
fetch(\"/api/v1/auth/user/me/invite_code\", {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
invite_code: e.target.invite_code.value,
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
if (res.ok) {
window.location.reload();
}
});
}"))))))
(text "{% else %}")
; page body
(text "{% block body %}{% endblock %}")
(text "{%- endif %}")
(text "<!-- html_footer_goes_here -->"))
(text "{% include \"body.html\" %}")))