📝 docs: add Mana earning system documentation

Document fraud-resistant mechanisms for users to earn Mana credits:
- Karma/XP system (non-monetary gamification)
- Creator rewards with social proof and fraud detection
- Community bug bounty program with manual review
- Database schemas, API endpoints, and implementation TODOs
This commit is contained in:
Till-JS 2026-02-16 13:52:22 +01:00
parent 711db9ee3e
commit 2236f83a58
9 changed files with 1501 additions and 588 deletions

View file

@ -37,6 +37,7 @@
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-i18n": "workspace:*",
"@manacore/shared-icons": "workspace:*",
"@manacore/shared-stores": "workspace:*",
"@manacore/shared-tailwind": "workspace:*",

View file

@ -11,9 +11,5 @@
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
<script>
window.__PUBLIC_MANA_CORE_AUTH_URL__ = '%env:PUBLIC_MANA_CORE_AUTH_URL_CLIENT%' || '%env:PUBLIC_MANA_CORE_AUTH_URL%';
window.__PUBLIC_BACKEND_URL__ = '%env:PUBLIC_BACKEND_URL_CLIENT%' || '%env:PUBLIC_BACKEND_URL%';
</script>
</body>
</html>

View file

@ -0,0 +1,27 @@
/**
* Server Hooks for SvelteKit
* - Injects runtime environment variables for client-side use
* - Auth is handled client-side via Mana Core Auth
*/
import type { Handle } from '@sveltejs/kit';
// Get client-side URLs from environment (Docker runtime)
const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
const PUBLIC_BACKEND_URL_CLIENT =
process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
export const handle: Handle = async ({ event, resolve }) => {
return resolve(event, {
transformPageChunk: ({ html }) => {
// Inject runtime environment variables into the HTML
// These will be available on window.__PUBLIC_*__ for client-side code
const envScript = `<script>
window.__PUBLIC_MANA_CORE_AUTH_URL__ = "${PUBLIC_MANA_CORE_AUTH_URL_CLIENT}";
window.__PUBLIC_BACKEND_URL__ = "${PUBLIC_BACKEND_URL_CLIENT}";
</script>`;
return html.replace('<head>', `<head>${envScript}`);
},
});
};

View file

@ -168,6 +168,27 @@ export const authStore = {
}
},
async resendVerificationEmail(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const sourceAppUrl = browser ? window.location.origin : undefined;
const result = await authService.resendVerificationEmail(email, sourceAppUrl);
if (!result.success) {
return { success: false, error: result.error || 'Failed to resend verification email' };
}
return { success: true };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
async getValidToken(): Promise<string | null> {
const tokenManager = getTokenManager();
if (!tokenManager) {

View file

@ -0,0 +1,5 @@
<script lang="ts">
let { children } = $props();
</script>
{@render children()}

View file

@ -0,0 +1,68 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { browser } from '$app/environment';
import { LoginPage } from '@manacore/shared-auth-ui';
import { getLoginTranslations } from '@manacore/shared-i18n';
import { authStore } from '$lib/stores/auth.svelte';
// Simple LightWrite Logo component
function LightWriteLogo() {
return null; // Will use appName text instead
}
// Get redirect URL from query params or sessionStorage
const redirectTo = $derived.by(() => {
const queryRedirect = $page.url.searchParams.get('redirectTo');
if (queryRedirect) return queryRedirect;
if (browser) {
const sessionRedirect = sessionStorage.getItem('auth-return-url');
if (sessionRedirect) {
sessionStorage.removeItem('auth-return-url');
return sessionRedirect;
}
}
return '/';
});
// Use English translations
const translations = getLoginTranslations('en');
// Read verification status from query params
const verified = $derived($page.url.searchParams.get('verified') === 'true');
const initialEmail = $derived($page.url.searchParams.get('email') || '');
async function handleSignIn(email: string, password: string) {
return authStore.signIn(email, password);
}
async function handleResendVerification(email: string) {
// Implement if needed
return { success: true };
}
</script>
<svelte:head>
<title>Login - LightWrite</title>
</svelte:head>
<LoginPage
appName="LightWrite"
logo={LightWriteLogo}
primaryColor="#f97316"
onSignIn={handleSignIn}
onResendVerification={handleResendVerification}
{goto}
enableGoogle={false}
enableApple={false}
successRedirect={redirectTo}
registerPath="/register"
forgotPasswordPath="/forgot-password"
lightBackground="#fff7ed"
darkBackground="#1c1917"
{translations}
{verified}
{initialEmail}
/>

View file

@ -0,0 +1,54 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { RegisterPage } from '@manacore/shared-auth-ui';
import { getRegisterTranslations } from '@manacore/shared-i18n';
import { authStore } from '$lib/stores/auth.svelte';
// Simple LightWrite Logo component
function LightWriteLogo() {
return null; // Will use appName text instead
}
// Get redirect URL from sessionStorage
const redirectTo = $derived.by(() => {
if (browser) {
const sessionRedirect = sessionStorage.getItem('auth-return-url');
if (sessionRedirect) {
sessionStorage.removeItem('auth-return-url');
return sessionRedirect;
}
}
return '/';
});
// Use English translations
const translations = getRegisterTranslations('en');
async function handleSignUp(email: string, password: string) {
return authStore.signUp(email, password);
}
async function handleResendVerification(email: string) {
// Implement if needed
return { success: true };
}
</script>
<svelte:head>
<title>Register - LightWrite</title>
</svelte:head>
<RegisterPage
appName="LightWrite"
logo={LightWriteLogo}
primaryColor="#f97316"
onSignUp={handleSignUp}
onResendVerification={handleResendVerification}
{goto}
successRedirect={redirectTo}
loginPath="/login"
lightBackground="#fff7ed"
darkBackground="#1c1917"
{translations}
/>