428 lines
No EOL
12 KiB
Svelte
428 lines
No EOL
12 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { fade, fly } from 'svelte/transition';
|
|
import { discordDataStore, discordTokenStore, authTokenStore } from '$lib/stores';
|
|
import { discordAuthUrl, bkAPIUrl } from '$lib/config.json';
|
|
|
|
let isLoggedIn = false;
|
|
let isNewLogin = false;
|
|
let isLoading = true;
|
|
let userAvatar = '';
|
|
let username = '';
|
|
let userId = '';
|
|
let animationComplete = false;
|
|
let authError = '';
|
|
let availablePanels: any[] = [];
|
|
|
|
type PanelDefinition = {
|
|
name: string;
|
|
route: string;
|
|
description: string;
|
|
icon: string;
|
|
};
|
|
|
|
const defaultPanels: PanelDefinition[] = [
|
|
{
|
|
name: "View Tournaments",
|
|
route: "/tournaments",
|
|
description: "Browse ongoing and upcoming tournaments",
|
|
icon: "public"
|
|
},
|
|
{
|
|
name: "My Tokens",
|
|
route: "/authTokens",
|
|
description: "View your current bot tokens",
|
|
icon: "token"
|
|
}
|
|
];
|
|
|
|
async function validateAuthToken() {
|
|
try {
|
|
const response = await fetch(`${bkAPIUrl}/users/@me`, {
|
|
headers: {
|
|
authorization: $authTokenStore,
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (error) {
|
|
console.error('Error validating auth token:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function handleNewLogin(tokenType: string, accessToken: string) {
|
|
try {
|
|
discordTokenStore.set(`${tokenType} ${accessToken}`);
|
|
|
|
const response = await fetch(`${bkAPIUrl}/requestAuthToken`, {
|
|
headers: {
|
|
authorization: $discordTokenStore,
|
|
},
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.token) {
|
|
authTokenStore.set(`Bearer ${data.token}`);
|
|
discordDataStore.set(data.discordData);
|
|
|
|
userId = data.discordData.id;
|
|
username = data.discordData.username;
|
|
userAvatar = `https://cdn.discordapp.com/avatars/${data.discordData.id}/${data.discordData.avatar}.png`;
|
|
|
|
isLoggedIn = true;
|
|
isNewLogin = true;
|
|
|
|
// Clean the URL
|
|
window.history.replaceState({}, document.title, window.location.pathname);
|
|
|
|
availablePanels = defaultPanels;
|
|
|
|
// Set timer for animation completion
|
|
setTimeout(() => {
|
|
animationComplete = true;
|
|
}, 3000);
|
|
} else {
|
|
authError = 'Failed to retrieve authentication token. Please try again.';
|
|
}
|
|
} catch (error) {
|
|
console.error('Authentication error:', error);
|
|
authError = 'An error occurred during authentication. Please try again.';
|
|
isLoggedIn = false;
|
|
} finally {
|
|
isLoading = false;
|
|
}
|
|
}
|
|
|
|
onMount(async () => {
|
|
// Check if token parameters are in URL (new login)
|
|
const hash = window.location.hash.substring(1);
|
|
const params = new URLSearchParams(hash);
|
|
const tokenType = params.get('token_type');
|
|
const accessToken = params.get('access_token');
|
|
|
|
if (tokenType && accessToken) {
|
|
await handleNewLogin(tokenType, accessToken);
|
|
} else {
|
|
// Check if user is already logged in
|
|
if ($authTokenStore && $discordDataStore) {
|
|
const isValid = await validateAuthToken();
|
|
|
|
if (isValid) {
|
|
isLoggedIn = true;
|
|
username = $discordDataStore.global_name;
|
|
userId = $discordDataStore.id;
|
|
userAvatar = `https://cdn.discordapp.com/avatars/${$discordDataStore.id}/${$discordDataStore.avatar}.png`;
|
|
availablePanels = defaultPanels;
|
|
} else {
|
|
// Token invalid, clear stores
|
|
authTokenStore.set(null);
|
|
discordDataStore.set(null);
|
|
discordTokenStore.set(null);
|
|
}
|
|
}
|
|
|
|
isLoading = false;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<main>
|
|
{#if isLoading}
|
|
<div class="loading-container">
|
|
<div class="loading-spinner"></div>
|
|
<p>Authenticating...</p>
|
|
</div>
|
|
{:else if !isLoggedIn}
|
|
<div class="auth-container" in:fade={{ duration: 800 }}>
|
|
<h1>Log in with Discord</h1>
|
|
<p>Connect your Discord account to access TAUI</p>
|
|
|
|
{#if authError}
|
|
<div class="error-message">
|
|
<i class="material-icons">error</i>
|
|
<p>{authError}</p>
|
|
</div>
|
|
{/if}
|
|
|
|
<a href={discordAuthUrl} class="discord-login-button">
|
|
<i class="material-icons">discord</i>
|
|
Login with Discord
|
|
</a>
|
|
</div>
|
|
{:else if isNewLogin && !animationComplete}
|
|
<div class="welcome-animation">
|
|
<div class="avatar-container" in:fade={{ duration: 300 }}>
|
|
<img src={userAvatar} alt="User avatar" class="large-avatar" />
|
|
<h2 in:fade={{ delay: 500, duration: 500 }}>Welcome, {username}!</h2>
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<div class="dashboard-container" in:fade={{ duration: 800 }}>
|
|
<div class="user-info">
|
|
<img src={userAvatar} alt="User avatar" class="user-avatar" />
|
|
<h2>Welcome, {username}</h2>
|
|
</div>
|
|
|
|
<div class="panels-grid">
|
|
{#each availablePanels as panel}
|
|
<a href={panel.route} class="panel-card" in:fly={{ y: 20, duration: 300, delay: 200 }}>
|
|
<div class="panel-icon">
|
|
<i class="material-icons">{panel.icon}</i>
|
|
</div>
|
|
<h3>{panel.name}</h3>
|
|
<p>{panel.description}</p>
|
|
</a>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</main>
|
|
|
|
<style>
|
|
main {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 2rem 1.5rem;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
min-height: 80vh;
|
|
}
|
|
|
|
/* Loading state */
|
|
.loading-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 50px;
|
|
height: 50px;
|
|
border: 4px solid var(--bg-secondary);
|
|
border-radius: 50%;
|
|
border-top: 4px solid var(--accent-color);
|
|
animation: spin 1s linear infinite;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Login state */
|
|
.auth-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
text-align: center;
|
|
max-width: 500px;
|
|
}
|
|
|
|
.auth-container h1 {
|
|
font-size: 3rem;
|
|
margin-bottom: 1.5rem;
|
|
background: linear-gradient(90deg, var(--accent-color), var(--accent-hover));
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
text-shadow: 0 0 15px var(--accent-glow);
|
|
}
|
|
|
|
.auth-container p {
|
|
font-size: 1.25rem;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 3rem;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.discord-login-button {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: #5865F2;
|
|
color: white;
|
|
padding: 1rem 2rem;
|
|
border-radius: 4px;
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.discord-login-button:hover {
|
|
background-color: #4752C4;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.discord-login-button i {
|
|
margin-right: 0.75rem;
|
|
}
|
|
|
|
.error-message {
|
|
display: flex;
|
|
align-items: center;
|
|
background-color: rgba(255, 0, 0, 0.1);
|
|
border-left: 4px solid #ff5555;
|
|
padding: 1rem;
|
|
border-radius: 4px;
|
|
margin-bottom: 2rem;
|
|
width: 100%;
|
|
}
|
|
|
|
.error-message i {
|
|
color: #ff5555;
|
|
margin-right: 0.75rem;
|
|
}
|
|
|
|
/* Welcome animation */
|
|
.welcome-animation {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.avatar-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
animation: avatar-shrink 2.5s forwards ease-in-out;
|
|
}
|
|
|
|
.large-avatar {
|
|
width: 200px;
|
|
height: 200px;
|
|
border-radius: 50%;
|
|
border: 4px solid var(--accent-color);
|
|
box-shadow: 0 0 20px var(--accent-glow);
|
|
}
|
|
|
|
@keyframes avatar-shrink {
|
|
0% {
|
|
transform: scale(1);
|
|
}
|
|
80% {
|
|
transform: scale(1);
|
|
}
|
|
100% {
|
|
transform: scale(0.3) translateY(-100px);
|
|
}
|
|
}
|
|
|
|
/* Dashboard */
|
|
.dashboard-container {
|
|
width: 100%;
|
|
}
|
|
|
|
.user-info {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.user-avatar {
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: 50%;
|
|
border: 2px solid var(--accent-color);
|
|
margin-right: 1rem;
|
|
}
|
|
|
|
.panels-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 2rem;
|
|
width: 100%;
|
|
}
|
|
|
|
.panel-card {
|
|
background-color: var(--bg-secondary);
|
|
border-radius: 8px;
|
|
padding: 2rem;
|
|
text-align: center;
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
border: 1px solid var(--border-color);
|
|
text-decoration: none;
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 200px;
|
|
background: linear-gradient(145deg, var(--bg-secondary), var(--bg-tertiary));
|
|
}
|
|
|
|
.panel-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.panel-icon {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.panel-icon i {
|
|
font-size: 3rem;
|
|
color: var(--accent-color);
|
|
}
|
|
|
|
.panel-card h3 {
|
|
font-size: 1.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.panel-card p {
|
|
color: var(--text-secondary);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Responsive styles */
|
|
@media (max-width: 768px) {
|
|
.auth-container h1 {
|
|
font-size: 2.5rem;
|
|
}
|
|
|
|
.auth-container p {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.large-avatar {
|
|
width: 150px;
|
|
height: 150px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.auth-container h1 {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.panels-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.panel-card:hover i {
|
|
animation: icon-bounce 0.5s ease;
|
|
}
|
|
|
|
@keyframes icon-bounce {
|
|
0%, 100% { transform: translateY(0); }
|
|
50% { transform: translateY(-5px); }
|
|
}
|
|
</style> |