mirror of
https://github.com/System-End/hackatime-desktop.git
synced 2026-04-19 16:28:19 +00:00
feat: add login page
This commit is contained in:
parent
4197762a53
commit
547117173b
2 changed files with 390 additions and 147 deletions
312
src/App.vue
312
src/App.vue
|
|
@ -9,6 +9,7 @@ import Home from "./views/Home.vue";
|
|||
import Projects from "./views/Projects.vue";
|
||||
import Settings from "./views/Settings.vue";
|
||||
import Statistics from "./views/Statistics.vue";
|
||||
import Login from "./views/Login.vue";
|
||||
import UserProfileCard from "./components/UserProfileCard.vue";
|
||||
import CustomTitlebar from "./components/CustomTitlebar.vue";
|
||||
import WakatimeSetupModal from "./components/WakatimeSetupModal.vue";
|
||||
|
|
@ -411,8 +412,10 @@ async function copyApiKey() {
|
|||
}
|
||||
|
||||
|
||||
async function handleDirectOAuthAuth() {
|
||||
if (!directOAuthToken.value.trim()) {
|
||||
async function handleDirectOAuthAuth(token?: string) {
|
||||
const tokenToUse = token || directOAuthToken.value;
|
||||
|
||||
if (!tokenToUse.trim()) {
|
||||
alert("Please enter an OAuth authorization code or access token");
|
||||
return;
|
||||
}
|
||||
|
|
@ -420,12 +423,12 @@ async function handleDirectOAuthAuth() {
|
|||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
console.log("Attempting direct OAuth auth with token:", directOAuthToken.value);
|
||||
console.log("Token length:", directOAuthToken.value.length);
|
||||
console.log("Attempting direct OAuth auth with token:", tokenToUse);
|
||||
console.log("Token length:", tokenToUse.length);
|
||||
console.log("API config:", apiConfig.value);
|
||||
|
||||
await invoke("authenticate_with_direct_oauth", {
|
||||
oauthToken: directOAuthToken.value,
|
||||
oauthToken: tokenToUse,
|
||||
apiConfig: apiConfig.value
|
||||
});
|
||||
|
||||
|
|
@ -452,6 +455,11 @@ async function handleDirectOAuthAuth() {
|
|||
}
|
||||
|
||||
async function checkForUpdatesAndInstall() {
|
||||
if (isDevMode.value) {
|
||||
console.info('[AUTO-UPDATE] Skipping auto-update check in development mode');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.info('[AUTO-UPDATE] Checking for updates...');
|
||||
const update = await check();
|
||||
|
|
@ -495,158 +503,168 @@ async function checkForUpdatesAndInstall() {
|
|||
<div class="flex flex-col h-screen text-text-primary font-sans outfit app-window" style="background-color: #322433;">
|
||||
<CustomTitlebar />
|
||||
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<!-- Show login screen when not authenticated -->
|
||||
<div v-if="!authState.is_authenticated" class="flex-1 overflow-hidden">
|
||||
<Login
|
||||
:isLoading="isLoading"
|
||||
:isDevMode="isDevMode"
|
||||
:oauthUrl="oauthUrl"
|
||||
@authenticate="authenticate"
|
||||
@handleDirectOAuthAuth="handleDirectOAuthAuth"
|
||||
@openOAuthUrlManually="openOAuthUrlManually"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="flex flex-1 overflow-hidden">
|
||||
<aside class="w-64 min-w-64 flex flex-col p-0 shadow-xl relative overflow-hidden" style="background-color: #3D2C3E;">
|
||||
<div class="absolute left-0 top-[76px] w-full pointer-events-none z-0">
|
||||
<div class="absolute left-[63px] top-[616.5px] text-[36px] text-black opacity-20 font-light whitespace-nowrap" style="font-family: 'Outfit', sans-serif;">
|
||||
01:55:58
|
||||
</div>
|
||||
|
||||
<img src="/src/assets/suits-icons.svg" alt="" class="absolute left-[200px] top-0 w-[84px] h-[17.778px]" />
|
||||
|
||||
<img src="/src/assets/decorative-lines.svg" alt="" class="absolute left-0 top-[377px] w-[16px] h-[207px]" />
|
||||
|
||||
<img src="/src/assets/decorative-lines.svg" alt="" class="absolute left-[284px] top-[377px] w-[16px] h-[207px]" />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 flex flex-col h-full">
|
||||
<div class="p-6" style="background-color: #3D2C3E;">
|
||||
<div class="flex justify-center items-center">
|
||||
<img src="/src/assets/bird-illustration.svg" alt="Hackatime" class="h-12 w-auto" />
|
||||
<div class="absolute left-0 top-[76px] w-full pointer-events-none z-0">
|
||||
<div class="absolute left-[63px] top-[616.5px] text-[36px] text-black opacity-20 font-light whitespace-nowrap" style="font-family: 'Outfit', sans-serif;">
|
||||
01:55:58
|
||||
</div>
|
||||
|
||||
<img src="/src/assets/suits-icons.svg" alt="" class="absolute left-[200px] top-0 w-[84px] h-[17.778px]" />
|
||||
|
||||
<img src="/src/assets/decorative-lines.svg" alt="" class="absolute left-0 top-[377px] w-[16px] h-[207px]" />
|
||||
|
||||
<img src="/src/assets/decorative-lines.svg" alt="" class="absolute left-[284px] top-[377px] w-[16px] h-[207px]" />
|
||||
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 py-4 px-6 space-y-5">
|
||||
<button
|
||||
@click="currentPage = 'home'"
|
||||
class="pushable w-full"
|
||||
:class="currentPage === 'home' ? 'pushable-active' : 'pushable-inactive'"
|
||||
style="font-family: 'Outfit', sans-serif;"
|
||||
>
|
||||
<span
|
||||
class="front w-full h-16 rounded-lg border-2 border-[rgba(0,0,0,0.35)] flex items-center px-4 text-xl font-bold"
|
||||
:style="currentPage === 'home' ? 'background: linear-gradient(135deg, #E99682 0%, #EB9182 33%, #E88592 66%, #E883AE 100%); color: white;' : 'background-color: #543c55; color: white;'"
|
||||
>
|
||||
<svg class="w-8 h-8 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
|
||||
</svg>
|
||||
<span class="ml-auto">home</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- Projects button -->
|
||||
<button
|
||||
@click="currentPage = 'projects'"
|
||||
class="pushable w-full"
|
||||
:class="currentPage === 'projects' ? 'pushable-active' : 'pushable-inactive'"
|
||||
style="font-family: 'Outfit', sans-serif;"
|
||||
>
|
||||
<span
|
||||
class="front w-full h-16 rounded-lg border-2 border-[rgba(0,0,0,0.35)] flex items-center px-4 text-xl font-bold"
|
||||
:style="currentPage === 'projects' ? 'background: linear-gradient(135deg, #E99682 0%, #EB9182 33%, #E88592 66%, #E883AE 100%); color: white;' : 'background-color: #543c55; color: white;'"
|
||||
>
|
||||
<svg class="w-8 h-8 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
||||
</svg>
|
||||
<span class="ml-auto">projects</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- Statistics button (renamed from friends in Figma, keeping your existing page) -->
|
||||
<button
|
||||
@click="currentPage = 'statistics'"
|
||||
class="pushable w-full"
|
||||
:class="currentPage === 'statistics' ? 'pushable-active' : 'pushable-inactive'"
|
||||
style="font-family: 'Outfit', sans-serif;"
|
||||
>
|
||||
<span
|
||||
class="front w-full h-16 rounded-lg border-2 border-[rgba(0,0,0,0.35)] flex items-center px-4 text-xl font-bold"
|
||||
:style="currentPage === 'statistics' ? 'background: linear-gradient(135deg, #E99682 0%, #EB9182 33%, #E88592 66%, #E883AE 100%); color: white;' : 'background-color: #543c55; color: white;'"
|
||||
>
|
||||
<svg class="w-8 h-8 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
|
||||
</svg>
|
||||
<span class="ml-auto">statistics</span>
|
||||
</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="p-6 mt-auto" style="background-color: #3D2C3E;">
|
||||
<UserProfileCard
|
||||
v-if="authState.is_authenticated"
|
||||
:authState="authState"
|
||||
:userData="userData"
|
||||
:presenceData="presenceData"
|
||||
:apiConfig="apiConfig"
|
||||
@openSettings="currentPage = 'settings'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="flex-1 p-6 overflow-y-auto min-w-0">
|
||||
<!-- Home Page Layout -->
|
||||
<div v-if="currentPage === 'home'" class="flex h-full gap-6 min-h-0 responsive-stack">
|
||||
<!-- Main Home Content (Left Side - 2/3) -->
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
<Home
|
||||
:authState="authState"
|
||||
:apiConfig="apiConfig"
|
||||
:userData="userData"
|
||||
:userStats="userStats"
|
||||
:weeklyChartData="weeklyChartData"
|
||||
:isLoading="isLoading"
|
||||
:isDevMode="isDevMode"
|
||||
:oauthUrl="oauthUrl"
|
||||
v-model:directOAuthToken="directOAuthToken"
|
||||
@authenticate="authenticate"
|
||||
@handleDirectOAuthAuth="handleDirectOAuthAuth"
|
||||
@openOAuthUrlManually="openOAuthUrlManually"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Leaderboard Sidebar (Right Side - 1/3) -->
|
||||
<div v-if="authState.is_authenticated && userStats" class="w-64 min-w-64 flex flex-col responsive-full-width">
|
||||
<div class="card-3d-app h-full">
|
||||
<div class="rounded-[8px] border border-black p-4 card-3d-app-front h-full flex flex-col" style="background-color: #3D2C3E;">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-white text-[16px] font-bold italic m-0" style="font-family: 'Outfit', sans-serif;">
|
||||
leaderboard
|
||||
</h2>
|
||||
<div class="flex gap-2 text-[10px]" style="font-family: 'Outfit', sans-serif;">
|
||||
<span class="text-white underline cursor-pointer">friends</span>
|
||||
<span class="text-white cursor-pointer">global</span>
|
||||
</div>
|
||||
<div class="relative z-10 flex flex-col h-full">
|
||||
<div class="p-6" style="background-color: #3D2C3E;">
|
||||
<div class="flex justify-center items-center">
|
||||
<img src="/src/assets/bird-illustration.svg" alt="Hackatime" class="h-12 w-auto" />
|
||||
</div>
|
||||
<!-- Leaderboard content would go here -->
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 py-4 px-6 space-y-5">
|
||||
<button
|
||||
@click="currentPage = 'home'"
|
||||
class="pushable w-full"
|
||||
:class="currentPage === 'home' ? 'pushable-active' : 'pushable-inactive'"
|
||||
style="font-family: 'Outfit', sans-serif;"
|
||||
>
|
||||
<span
|
||||
class="front w-full h-16 rounded-lg border-2 border-[rgba(0,0,0,0.35)] flex items-center px-4 text-xl font-bold"
|
||||
:style="currentPage === 'home' ? 'background: linear-gradient(135deg, #E99682 0%, #EB9182 33%, #E88592 66%, #E883AE 100%); color: white;' : 'background-color: #543c55; color: white;'"
|
||||
>
|
||||
<svg class="w-8 h-8 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
|
||||
</svg>
|
||||
<span class="ml-auto">home</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- Projects button -->
|
||||
<button
|
||||
@click="currentPage = 'projects'"
|
||||
class="pushable w-full"
|
||||
:class="currentPage === 'projects' ? 'pushable-active' : 'pushable-inactive'"
|
||||
style="font-family: 'Outfit', sans-serif;"
|
||||
>
|
||||
<span
|
||||
class="front w-full h-16 rounded-lg border-2 border-[rgba(0,0,0,0.35)] flex items-center px-4 text-xl font-bold"
|
||||
:style="currentPage === 'projects' ? 'background: linear-gradient(135deg, #E99682 0%, #EB9182 33%, #E88592 66%, #E883AE 100%); color: white;' : 'background-color: #543c55; color: white;'"
|
||||
>
|
||||
<svg class="w-8 h-8 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
||||
</svg>
|
||||
<span class="ml-auto">projects</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- Statistics button (renamed from friends in Figma, keeping your existing page) -->
|
||||
<button
|
||||
@click="currentPage = 'statistics'"
|
||||
class="pushable w-full"
|
||||
:class="currentPage === 'statistics' ? 'pushable-active' : 'pushable-inactive'"
|
||||
style="font-family: 'Outfit', sans-serif;"
|
||||
>
|
||||
<span
|
||||
class="front w-full h-16 rounded-lg border-2 border-[rgba(0,0,0,0.35)] flex items-center px-4 text-xl font-bold"
|
||||
:style="currentPage === 'statistics' ? 'background: linear-gradient(135deg, #E99682 0%, #EB9182 33%, #E88592 66%, #E883AE 100%); color: white;' : 'background-color: #543c55; color: white;'"
|
||||
>
|
||||
<svg class="w-8 h-8 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
|
||||
</svg>
|
||||
<span class="ml-auto">statistics</span>
|
||||
</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="p-6 mt-auto" style="background-color: #3D2C3E;">
|
||||
<UserProfileCard
|
||||
:authState="authState"
|
||||
:userData="userData"
|
||||
:presenceData="presenceData"
|
||||
:apiConfig="apiConfig"
|
||||
@openSettings="currentPage = 'settings'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics Page Layout (full page) -->
|
||||
<div v-else-if="currentPage === 'statistics'" class="flex flex-col h-full">
|
||||
<Statistics :apiConfig="apiConfig" />
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Settings Page Layout (no outer card) -->
|
||||
<div v-else-if="currentPage === 'settings'" class="flex flex-col h-full">
|
||||
<Settings
|
||||
:apiKey="apiKey"
|
||||
v-model:showApiKey="showApiKey"
|
||||
@copyApiKey="copyApiKey"
|
||||
@logout="logout"
|
||||
@checkWakatimeConfig="openWakatimeConfigModal"
|
||||
/>
|
||||
</div>
|
||||
<!-- Main Content Area -->
|
||||
<main class="flex-1 p-6 overflow-y-auto min-w-0">
|
||||
<!-- Home Page Layout -->
|
||||
<div v-if="currentPage === 'home'" class="flex h-full gap-6 min-h-0 responsive-stack">
|
||||
<!-- Main Home Content (Left Side - 2/3) -->
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
<Home
|
||||
:authState="authState"
|
||||
:apiConfig="apiConfig"
|
||||
:userData="userData"
|
||||
:userStats="userStats"
|
||||
:weeklyChartData="weeklyChartData"
|
||||
:isLoading="isLoading"
|
||||
:isDevMode="isDevMode"
|
||||
:oauthUrl="oauthUrl"
|
||||
v-model:directOAuthToken="directOAuthToken"
|
||||
@authenticate="authenticate"
|
||||
@handleDirectOAuthAuth="handleDirectOAuthAuth"
|
||||
@openOAuthUrlManually="openOAuthUrlManually"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Projects Page Layout -->
|
||||
<div v-else class="flex flex-col h-full">
|
||||
<Projects :apiConfig="apiConfig" />
|
||||
</div>
|
||||
</main>
|
||||
<!-- Leaderboard Sidebar (Right Side - 1/3) -->
|
||||
<div v-if="authState.is_authenticated && userStats" class="w-64 min-w-64 flex flex-col responsive-full-width">
|
||||
<div class="card-3d-app h-full">
|
||||
<div class="rounded-[8px] border border-black p-4 card-3d-app-front h-full flex flex-col" style="background-color: #3D2C3E;">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-white text-[16px] font-bold italic m-0" style="font-family: 'Outfit', sans-serif;">
|
||||
leaderboard
|
||||
</h2>
|
||||
<div class="flex gap-2 text-[10px]" style="font-family: 'Outfit', sans-serif;">
|
||||
<span class="text-white underline cursor-pointer">friends</span>
|
||||
<span class="text-white cursor-pointer">global</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Leaderboard content would go here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics Page Layout (full page) -->
|
||||
<div v-else-if="currentPage === 'statistics'" class="flex flex-col h-full">
|
||||
<Statistics :apiConfig="apiConfig" />
|
||||
</div>
|
||||
|
||||
<!-- Settings Page Layout (no outer card) -->
|
||||
<div v-else-if="currentPage === 'settings'" class="flex flex-col h-full">
|
||||
<Settings
|
||||
:apiKey="apiKey"
|
||||
v-model:showApiKey="showApiKey"
|
||||
@copyApiKey="copyApiKey"
|
||||
@logout="logout"
|
||||
@checkWakatimeConfig="openWakatimeConfigModal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Projects Page Layout -->
|
||||
<div v-else class="flex flex-col h-full">
|
||||
<Projects :apiConfig="apiConfig" />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Configuration Modal -->
|
||||
|
|
|
|||
225
src/views/Login.vue
Normal file
225
src/views/Login.vue
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
<template>
|
||||
<div class="flex items-center justify-center h-full w-full" style="background-color: #322433;">
|
||||
<div class="max-w-md w-full px-8">
|
||||
<div v-if="!authInProgress" class="flex justify-center mb-8">
|
||||
<img src="/src/assets/bird-illustration.svg" alt="Hackatime" class="h-24 w-auto" />
|
||||
</div>
|
||||
<div class="card-3d">
|
||||
<div class="rounded-[12px] border-2 border-black card-3d-front p-8" style="background-color: #3D2C3E;">
|
||||
<div class="text-center">
|
||||
<h1 v-if="!authInProgress" class="text-[32px] font-bold text-white mb-8" style="font-family: 'Outfit', sans-serif;">
|
||||
Welcome to Hackatime
|
||||
</h1>
|
||||
<div v-if="!authInProgress">
|
||||
<button
|
||||
@click="handleLogin"
|
||||
:disabled="isLoading"
|
||||
class="pushable w-full mt-8"
|
||||
:class="isLoading ? '' : 'pushable-active'"
|
||||
style="font-family: 'Outfit', sans-serif;"
|
||||
>
|
||||
<span
|
||||
class="front w-full h-16 px-8 rounded-lg border-2 border-[rgba(0,0,0,0.35)] font-bold text-xl flex items-center justify-center gap-3"
|
||||
:style="isLoading ? 'background-color: #543c55; color: white;' : 'background: linear-gradient(135deg, #E99682 0%, #EB9182 33%, #E88592 66%, #E883AE 100%); color: white;'"
|
||||
>
|
||||
<svg v-if="!isLoading" class="w-7 h-7 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"></path>
|
||||
</svg>
|
||||
<svg v-else class="animate-spin h-7 w-7 flex-shrink-0" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span class="flex-shrink-0">{{ isLoading ? 'Initializing...' : 'Sign in with Hackatime' }}</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div v-if="isDevMode" class="mt-8 pt-8 border-t border-white/20">
|
||||
<p class="text-white/60 text-sm mb-3" style="font-family: 'Outfit', sans-serif;">
|
||||
Developer Mode: Paste token directly
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="directToken"
|
||||
type="text"
|
||||
placeholder="Paste token..."
|
||||
class="flex-1 p-3 bg-[#2A1F2B] border border-white/20 rounded-lg text-white font-mono text-sm focus:outline-none focus:border-[#E99682] transition-colors"
|
||||
@keyup.enter="handleDirectAuth"
|
||||
/>
|
||||
<button
|
||||
@click="handleDirectAuth"
|
||||
:disabled="!directToken.trim() || isLoading"
|
||||
class="px-4 py-3 rounded-lg border-2 border-[rgba(0,0,0,0.35)] font-bold text-sm transition-all"
|
||||
:class="!directToken.trim() || isLoading ? 'bg-gray-600 text-white/50 cursor-not-allowed' : 'bg-[#E99682] text-white hover:bg-[#d88672]'"
|
||||
style="font-family: 'Outfit', sans-serif;"
|
||||
>
|
||||
Go
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-4">
|
||||
<div class="mb-8 loader-container">
|
||||
<RandomLoader />
|
||||
</div>
|
||||
|
||||
<h2 class="text-[28px] font-bold text-white mb-5" style="font-family: 'Outfit', sans-serif;">
|
||||
Opening in your browser
|
||||
</h2>
|
||||
<p class="text-white/70 text-[16px]" style="font-family: 'Outfit', sans-serif;">
|
||||
Complete authentication in the browser window that just opened
|
||||
</p>
|
||||
|
||||
<button
|
||||
@click="handleManualOpen"
|
||||
class="pushable w-full mt-10 mb-6"
|
||||
style="font-family: 'Outfit', sans-serif; background-color: #2A1F2B;"
|
||||
>
|
||||
<span
|
||||
class="front w-full h-14 px-6 rounded-lg border-2 border-[rgba(0,0,0,0.35)] font-medium text-base flex items-center justify-center"
|
||||
style="background-color: #543c55; color: white;"
|
||||
>
|
||||
Didn't work? Click here to open manually
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="cancelAuth"
|
||||
class="text-white/60 text-base hover:text-white transition-colors font-medium"
|
||||
style="font-family: 'Outfit', sans-serif;"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import RandomLoader from '../components/RandomLoader.vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
authenticate: [];
|
||||
handleDirectOAuthAuth: [token: string];
|
||||
openOAuthUrlManually: [];
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
isLoading: boolean;
|
||||
isDevMode: boolean;
|
||||
oauthUrl: string | null;
|
||||
}>();
|
||||
|
||||
const authInProgress = ref(false);
|
||||
const directToken = ref('');
|
||||
|
||||
async function handleLogin() {
|
||||
emit('authenticate');
|
||||
setTimeout(() => {
|
||||
authInProgress.value = true;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleManualOpen() {
|
||||
emit('openOAuthUrlManually');
|
||||
}
|
||||
|
||||
function cancelAuth() {
|
||||
authInProgress.value = false;
|
||||
}
|
||||
|
||||
function handleDirectAuth() {
|
||||
if (directToken.value.trim()) {
|
||||
emit('handleDirectOAuthAuth', directToken.value.trim());
|
||||
directToken.value = '';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-3d {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-3d::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 12px;
|
||||
background-color: #2A1F2B;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.card-3d-front {
|
||||
position: relative;
|
||||
transform: translateY(-8px);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.pushable {
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
outline-offset: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pushable-active {
|
||||
background: linear-gradient(135deg, #B85E6D 0%, #B85E6D 33%, #B5546F 66%, #B55389 100%);
|
||||
}
|
||||
|
||||
.pushable:not(.pushable-active) {
|
||||
background-color: #2A1F2B;
|
||||
}
|
||||
|
||||
.pushable:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.front {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
transform: translateY(-6px);
|
||||
transition: transform 0.1s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pushable:active:not(:disabled) .front {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Bounce Animation */
|
||||
@keyframes bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-bounce {
|
||||
animation: bounce 1s infinite;
|
||||
}
|
||||
|
||||
.loader-container {
|
||||
height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loader-container :deep(.flex) {
|
||||
height: 120px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Add table
Reference in a new issue