Add CheckIcon component and update routes, auth store, and various UI improvements
This commit is contained in:
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -18,6 +18,7 @@ declare module 'vue' {
|
||||
BellFilled: typeof import('./src/components/icons/BellFilled.vue')['default']
|
||||
Button: typeof import('primevue/button')['default']
|
||||
Checkbox: typeof import('primevue/checkbox')['default']
|
||||
CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||
Home: typeof import('./src/components/icons/Home.vue')['default']
|
||||
HomeFilled: typeof import('./src/components/icons/HomeFilled.vue')['default']
|
||||
@@ -42,6 +43,7 @@ declare global {
|
||||
const BellFilled: typeof import('./src/components/icons/BellFilled.vue')['default']
|
||||
const Button: typeof import('primevue/button')['default']
|
||||
const Checkbox: typeof import('primevue/checkbox')['default']
|
||||
const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||
const Home: typeof import('./src/components/icons/Home.vue')['default']
|
||||
const HomeFilled: typeof import('./src/components/icons/HomeFilled.vue')['default']
|
||||
|
||||
@@ -164,7 +164,6 @@ const login = async (username: string, password: string) => {
|
||||
};
|
||||
|
||||
async function checkAuth() {
|
||||
console.log("Check auth called");
|
||||
const context = getContext<HonoVarTypes>();
|
||||
const token = getCookie(context, 'auth_token');
|
||||
|
||||
@@ -183,7 +182,6 @@ async function checkAuth() {
|
||||
if (!userRecord) {
|
||||
return { authenticated: false, user: null };
|
||||
}
|
||||
// console.log("Check auth called 2", userRecord);
|
||||
|
||||
return {
|
||||
authenticated: true,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
<router-view/>
|
||||
</template>
|
||||
3
src/components/icons/CheckIcon.vue
Normal file
3
src/components/icons/CheckIcon.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" viewBox="0 0 532 532"><path d="M10 266c0 37 21 69 51 85-10 33-2 70 24 96s63 34 96 24c16 30 48 51 85 51s69-21 85-51c33 10 70 2 96-24s34-63 24-96c30-16 51-48 51-85s-21-69-51-85c10-33 2-70-24-96s-63-34-96-24c-16-30-48-51-85-51s-69 21-85 51c-33-10-70-2-96 24s-34 63-24 96c-30 16-51 48-51 85zm152 42c-9-10-9-25 1-34 9-9 25-9 34 0l36 37 106-145c8-11 23-14 33-6 11 8 13 23 6 34L255 363c-4 5-11 9-18 10-7 0-14-3-19-8l-56-57z" fill="#a6acb9"/><path d="M339 166c8-11 23-14 33-6 11 8 13 23 6 34L255 363c-4 5-11 9-18 10-7 0-14-3-19-8l-56-57c-9-10-9-25 1-34 9-9 25-9 34 0l36 37 106-145z" fill="#1e3050"/></svg>
|
||||
</template>
|
||||
@@ -33,12 +33,12 @@ app.get("*", async (c) => {
|
||||
const url = new URL(c.req.url);
|
||||
const { app, router, head, pinia, bodyClass } = createApp();
|
||||
app.provide("honoContext", c);
|
||||
const auth = useAuthStore();
|
||||
auth.$reset();
|
||||
auth.initialized = false;
|
||||
await auth.init();
|
||||
await router.push(url.pathname);
|
||||
await router.isReady().then(() => {
|
||||
const auth = useAuthStore();
|
||||
auth.initialized = false;
|
||||
auth.init();
|
||||
});
|
||||
await router.isReady();
|
||||
let usedStyles = new Set(defaultNames);
|
||||
Base.setLoadedStyleName = async (name: string) => usedStyles.add(name)
|
||||
return streamText(c, async (stream) => {
|
||||
|
||||
23
src/main.ts
23
src/main.ts
@@ -4,12 +4,13 @@ import { createSSRApp } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
||||
import { vueSWR } from './lib/swr/use-swrv';
|
||||
import router from './routes';
|
||||
import createAppRouter from './routes';
|
||||
import PrimeVue from 'primevue/config';
|
||||
import Aura from '@primeuix/themes/aura';
|
||||
import { createPinia } from "pinia";
|
||||
import { useAuthStore } from './stores/auth';
|
||||
|
||||
import ToastService from 'primevue/toastservice';
|
||||
import Tooltip from 'primevue/tooltip';
|
||||
const bodyClass = ":uno: font-sans bg-[#f9fafd] text-gray-800 antialiased flex flex-col min-h-screen"
|
||||
export function createApp() {
|
||||
const pinia = createPinia();
|
||||
@@ -30,22 +31,22 @@ export function createApp() {
|
||||
}
|
||||
}
|
||||
});
|
||||
app.use(ToastService);
|
||||
app.directive('no-hydrate', {
|
||||
created(el) {
|
||||
el.__v_skip = true;
|
||||
}
|
||||
});
|
||||
app.use(vueSWR({revalidateOnFocus: false}));
|
||||
app.use(router);
|
||||
app.use(pinia);
|
||||
|
||||
// Initialize auth store on client side
|
||||
app.directive("tooltip", Tooltip)
|
||||
if (!import.meta.env.SSR) {
|
||||
router.isReady().then(() => {
|
||||
const auth = useAuthStore();
|
||||
auth.init();
|
||||
});
|
||||
if ((window as any).__PINIA_STATE__ ) {
|
||||
pinia.state.value = (window as any).__PINIA_STATE__;
|
||||
}
|
||||
}
|
||||
app.use(pinia);
|
||||
app.use(vueSWR({revalidateOnFocus: false}));
|
||||
const router = createAppRouter();
|
||||
app.use(router);
|
||||
|
||||
return { app, router, head, pinia, bodyClass };
|
||||
}
|
||||
@@ -1,15 +1,18 @@
|
||||
<template>
|
||||
<div class="w-full max-w-md bg-white p-8 rounded-xl border border-primary m-auto">
|
||||
<div class="w-full max-w-md bg-white p-8 rounded-xl border border-primary m-auto overflow-hidden">
|
||||
<div class="text-center mb-8">
|
||||
<div class="inline-flex items-center justify-center w-12 h-12 mb-4">
|
||||
<router-link to="/" class="inline-flex items-center justify-center w-12 h-12 mb-4">
|
||||
<img class="w-12 h-12" src="/apple-touch-icon.png" alt="Logo" />
|
||||
</div>
|
||||
</router-link>
|
||||
<h2 class="text-2xl font-bold text-gray-900">
|
||||
{{ content[route.name as keyof typeof content]?.title || '' }}
|
||||
</h2>
|
||||
<p class="text-gray-500 text-sm mt-1">
|
||||
{{ content[route.name as keyof typeof content]?.subtitle || '' }}
|
||||
</p>
|
||||
<vue-head :input="{
|
||||
title: content[route.name as keyof typeof content]?.headTitle || 'Authentication'
|
||||
}" />
|
||||
</div>
|
||||
<router-view />
|
||||
</div>
|
||||
@@ -20,16 +23,19 @@ import { useRoute } from 'vue-router';
|
||||
const route = useRoute();
|
||||
const content = {
|
||||
login: {
|
||||
headTitle: "Login to your account",
|
||||
title: 'Welcome back',
|
||||
subtitle: 'Please enter your details to sign in.'
|
||||
},
|
||||
signup: {
|
||||
headTitle: "Create your account",
|
||||
title: 'Create your account',
|
||||
subtitle: 'Please fill in the information to create your account.'
|
||||
},
|
||||
forgot: {
|
||||
title: 'Forgot your password?',
|
||||
subtitle: "Enter your email address and we'll send you a link to reset your password."
|
||||
subtitle: "Enter your email address and we'll send you a link to reset your password.",
|
||||
headTitle: "Reset your password"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,13 +1,8 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<Toast />
|
||||
<Form v-slot="$form" :resolver="resolver" :initialValues="initialValues" @submit="onFormSubmit"
|
||||
class="flex flex-col gap-4 w-full">
|
||||
|
||||
<!-- Global error message -->
|
||||
<Message v-if="auth.error" severity="error" size="small" variant="simple">
|
||||
{{ auth.error }}
|
||||
</Message>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="email" class="text-sm font-medium text-gray-700">Email or Username</label>
|
||||
<InputText name="email" type="text" placeholder="admin or user@example.com" fluid
|
||||
@@ -58,7 +53,7 @@
|
||||
|
||||
<p class="mt-4 text-center text-sm text-gray-600">
|
||||
Don't have an account?
|
||||
<router-link to="/signup" class="font-medium text-blue-600 hover:text-blue-500 hover:underline">Sign up
|
||||
<router-link to="/sign-up" class="font-medium text-blue-600 hover:text-blue-500 hover:underline">Sign up
|
||||
for free</router-link>
|
||||
</p>
|
||||
|
||||
@@ -80,8 +75,16 @@ import { Form, type FormSubmitEvent } from '@primevue/forms';
|
||||
import { zodResolver } from '@primevue/forms/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
|
||||
import Toast from 'primevue/toast';
|
||||
import { useToast } from "primevue/usetoast";
|
||||
const t = useToast();
|
||||
const auth = useAuthStore();
|
||||
// const $form = Form.useFormContext();
|
||||
watch(() => auth.error, (newError) => {
|
||||
if (newError) {
|
||||
t.add({ severity: 'error', summary: String(auth.error), detail: newError, life: 5000 });
|
||||
}
|
||||
});
|
||||
|
||||
const initialValues = reactive({
|
||||
email: '',
|
||||
@@ -97,14 +100,7 @@ const resolver = zodResolver(
|
||||
);
|
||||
|
||||
const onFormSubmit = async ({ valid, values }: FormSubmitEvent) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await auth.login(values.email, values.password);
|
||||
} catch (error) {
|
||||
// Error is already set in the store
|
||||
console.error('Login failed:', error);
|
||||
}
|
||||
}
|
||||
if (valid) auth.login(values.email, values.password);
|
||||
};
|
||||
|
||||
const loginWithGoogle = () => {
|
||||
|
||||
@@ -2,23 +2,16 @@
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<!-- Logo -->
|
||||
<div class="flex items-center gap-2 cursor-pointer" onclick="window.scrollTo(0,0)">
|
||||
<img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />
|
||||
<div class="flex items-center gap-2 cursor-pointer" onclick="window.scrollTo(0,0)"><img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />
|
||||
<span class="font-bold text-xl tracking-tight text-slate-900">EcoStream</span>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="#features" class="text-sm font-medium text-slate-600 hover:text-brand-600 transition-colors">Features</a>
|
||||
<a href="#analytics" class="text-sm font-medium text-slate-600 hover:text-brand-600 transition-colors">Analytics</a>
|
||||
<a href="#pricing" class="text-sm font-medium text-slate-600 hover:text-brand-600 transition-colors">Pricing</a>
|
||||
</div>
|
||||
|
||||
<!-- Auth Buttons -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<RouterLink to="/login" class="text-sm font-semibold text-slate-600 hover:text-slate-900 cursor-pointer">Log in</RouterLink>
|
||||
<RouterLink to="/signup" class="bg-slate-900 hover:bg-black text-white px-5 py-2.5 rounded-lg text-sm font-semibold cursor-pointer">
|
||||
<RouterLink to="/sign-up" class="bg-slate-900 hover:bg-black text-white px-5 py-2.5 rounded-lg text-sm font-semibold cursor-pointer">
|
||||
Start for free
|
||||
</RouterLink>
|
||||
</div>
|
||||
@@ -26,7 +19,6 @@
|
||||
</div>
|
||||
</nav>
|
||||
<section class="relative pt-32 pb-20 lg:pt-48 lg:pb-32 overflow-hidden">
|
||||
<!-- Background Elements -->
|
||||
<div class="absolute inset-0 opacity-[0.4] -z-10"></div>
|
||||
<div class="absolute top-0 right-0 -translate-y-1/2 translate-x-1/2 w-[800px] h-[800px] bg-brand-100/50 rounded-full blur-3xl -z-10 mix-blend-multiply"></div>
|
||||
<div class="absolute bottom-0 left-0 translate-y-1/2 -translate-x-1/2 w-[600px] h-[600px] bg-teal-100/50 rounded-full blur-3xl -z-10 mix-blend-multiply"></div>
|
||||
@@ -62,24 +54,19 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<!-- Large Feature -->
|
||||
<div class="md:col-span-2 bg-slate-50 rounded-2xl p-8 border border-slate-100 hover:border-primary/60 transition-all group overflow-hidden relative">
|
||||
<div class="relative z-10">
|
||||
<div class="w-12 h-12 bg-white rounded-xl flex items-center justify-center mb-6 border border-slate-100">
|
||||
<!-- <i class="fas fa-globe text-xl"></i> -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="532" viewBox="-8 -258 529 532"><path d="M342 32c-2 69-16 129-35 172-10 23-22 40-32 49-10 10-16 11-19 11h-1c-3 0-9-1-19-11-10-9-22-26-32-49-19-43-33-103-35-172h173zm169 0c-9 103-80 188-174 219 30-51 50-129 53-219h121zm-390 0c3 89 23 167 53 218C80 219 11 134 2 32h119zm53-266c-30 51-50 129-53 218H2c9-102 78-186 172-218zm82-14c3 0 9 1 19 11 10 9 22 26 32 50 19 42 33 102 35 171H169c3-69 16-129 35-171 10-24 22-41 32-50s16-11 19-11h1zm81 13c94 31 165 116 174 219H390c-3-90-23-168-53-219z" fill="#059669"/></svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-slate-900 mb-2">Global Edge Network</h3>
|
||||
<p class="text-slate-500 max-w-md">Content delivered from 200+ PoPs worldwide. Automatic region selection ensures the lowest latency for every viewer.</p>
|
||||
</div>
|
||||
<!-- Decor -->
|
||||
<div class="absolute right-0 bottom-0 opacity-10 translate-x-1/4 translate-y-1/4">
|
||||
<!-- <i class="fas fa-globe-americas text-[200px] text-brand-900"></i> -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="-10 -258 532 532"><path d="M464 8c0-19-3-38-8-56l-27-5c-8-2-15 2-19 9-6 11-19 17-31 13l-14-5c-8-2-17 0-22 5-4 4-4 10 0 14l33 33c5 5 8 12 8 19 0 12-8 23-20 26l-6 1c-3 1-6 5-6 9v12c0 13-4 27-13 38l-25 34c-6 8-16 13-26 13-18 0-32-14-32-32V88c0-9-7-16-16-16h-32c-26 0-48-22-48-48V-4c0-13 6-24 16-32l39-30c6-4 13-6 20-6 3 0 7 1 10 2l32 10c7 3 15 3 22 1l36-9c10-2 17-11 17-22 0-8-5-16-13-20l-29-15c-3-2-8-1-11 2l-4 4c-4 4-11 7-17 7-4 0-8-1-11-3l-15-7c-7-4-15-2-20 4l-13 17c-6 7-16 8-22 1-3-2-5-6-5-10v-41c0-6-1-11-4-16l-10-18C102-154 48-79 48 8c0 115 93 208 208 208S464 123 464 8zM0 8c0-141 115-256 256-256S512-133 512 8 397 264 256 264 0 149 0 8z" fill="#1e3050"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tall Feature -->
|
||||
<div class="md:row-span-2 bg-slate-900 rounded-2xl p-8 text-white relative overflow-hidden group">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-slate-800/50 to-transparent"></div>
|
||||
<div class="relative z-10">
|
||||
@@ -125,60 +112,27 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- Pricing -->
|
||||
<section id="pricing" class="py-24 bg-white border-t border-slate-100">
|
||||
<section id="pricing" class="py-24 border-t border-slate-100 bg-white">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-3xl font-bold text-slate-900 mb-4">Simple, transparent pricing</h2>
|
||||
<p class="text-slate-500">No hidden fees. Pay as you grow.</p>
|
||||
<h2 class="text-3xl font-bold text-slate-900 mb-4">{{ pricing.title }}</h2>
|
||||
<p class="text-slate-500">{{ pricing.subtitle }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||
<!-- Hobby -->
|
||||
<div class="p-8 rounded-2xl border border-slate-200 hover:border-slate-300 transition-colors">
|
||||
<h3 class="font-semibold text-slate-900 mb-2">Hobby</h3>
|
||||
<div class="flex items-baseline gap-1 mb-6">
|
||||
<span class="text-4xl font-bold text-slate-900">$0</span>
|
||||
<span class="text-slate-500">/mo</span>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 w-full">
|
||||
<div v-for="pack in pricing.packs" :key="pack.name" :class="cn(':uno: p-8 rounded-2xl relative overflow-hidden hover:border-primary transition-colors flex flex-col justify-between', pack.tag == 'POPULAR' ? 'border-primary/80 border-2' : 'border-slate-200 border')" :style="{background: pack.bg}">
|
||||
<div v-if="pack.tag" class="absolute top-0 right-0 bg-primary/80 text-white text-xs font-bold px-3 py-1 rounded-bl-lg uppercase">{{ pack.tag }}</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-slate-900 text-xl mb-2">{{ pack.name }}</h3>
|
||||
<div class="flex items-baseline gap-1 mb-6">
|
||||
<span class="text-4xl font-bold text-slate-900">{{ pack.price }}</span>
|
||||
<span class="text-slate-500">/mo</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="space-y-3 mb-8 text-sm text-slate-600">
|
||||
<li class="flex items-center gap-3"><i class="fas fa-check text-brand-500"></i> 100 GB Bandwidth</li>
|
||||
<li class="flex items-center gap-3"><i class="fas fa-check text-brand-500"></i> 1 Hour of Storage</li>
|
||||
<li class="flex items-center gap-3"><i class="fas fa-check text-brand-500"></i> Standard Support</li>
|
||||
<li v-for="value in pack.features" :key="value" class="flex items-center gap-3"><Check-Icon class="fas fa-check text-brand-500"/> {{ value }}</li>
|
||||
</ul>
|
||||
<button class="w-full py-2.5 rounded-lg border border-slate-200 font-semibold text-slate-700 hover:bg-slate-50 transition-colors">Start Free</button>
|
||||
</div>
|
||||
|
||||
<!-- Pro -->
|
||||
<div class="p-8 rounded-2xl bg-slate-900 text-white shadow-2xl relative overflow-hidden transform">
|
||||
<div class="absolute top-0 right-0 bg-primary/50 text-white text-xs font-bold px-3 py-1 rounded-bl-lg">POPULAR</div>
|
||||
<h3 class="font-semibold mb-2 text-brand-400">Pro</h3>
|
||||
<div class="flex items-baseline gap-1 mb-6">
|
||||
<span class="text-4xl font-bold">$0</span>
|
||||
<span class="text-lg font-bold line-through">$29</span>
|
||||
<span class="text-slate-400">/mo</span>
|
||||
</div>
|
||||
<ul class="space-y-3 mb-8 text-sm text-slate-300">
|
||||
<li class="flex items-center gap-3"><i class="fas fa-check text-primary/60"></i> 1 TB Bandwidth</li>
|
||||
<li class="flex items-center gap-3"><i class="fas fa-check text-primary/60"></i> 100 Hours Storage</li>
|
||||
<li class="flex items-center gap-3"><i class="fas fa-check text-primary/60"></i> Remove Branding</li>
|
||||
<li class="flex items-center gap-3"><i class="fas fa-check text-primary/60"></i> 4K Encoding</li>
|
||||
</ul>
|
||||
<button class="w-full py-2.5 rounded-lg bg-primary/60 hover:bg-primary/70 font-semibold transition-colors shadow-lg shadow-primary/30">Get Started</button>
|
||||
</div>
|
||||
|
||||
<!-- Scale -->
|
||||
<div class="p-8 rounded-2xl border border-slate-200 hover:border-slate-300 transition-colors">
|
||||
<h3 class="font-semibold text-slate-900 mb-2">Scale</h3>
|
||||
<div class="flex items-baseline gap-1 mb-6">
|
||||
<span class="text-4xl font-bold text-slate-900">$99</span>
|
||||
<span class="text-slate-500">/mo</span>
|
||||
</div>
|
||||
<ul class="space-y-3 mb-8 text-sm text-slate-600">
|
||||
<li class="flex items-center gap-3"><i class="fas fa-check text-brand-500"></i> 5 TB Bandwidth</li>
|
||||
<li class="flex items-center gap-3"><i class="fas fa-check text-brand-500"></i> 500 Hours Storage</li>
|
||||
<li class="flex items-center gap-3"><i class="fas fa-check text-brand-500"></i> Priority Support</li>
|
||||
</ul>
|
||||
<button class="w-full py-2.5 rounded-lg border border-slate-200 font-semibold text-slate-700 hover:bg-slate-50 transition-colors">Contact Sales</button>
|
||||
<router-link to="/sign-up" :class="cn('btn flex justify-center w-full !py-2.5', pack.tag == 'POPULAR' ? 'btn-primary' : 'btn-outline-primary')">{{ pack.buttonText }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -226,6 +180,55 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<Head>
|
||||
<title>EcoStream - Video infrastructure for modern internet</title>
|
||||
<meta name="description" content="Seamlessly host, encode, and stream video with our developer-first API. Optimized for speed, built for scale." />
|
||||
</Head>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Head } from '@unhead/vue/components'
|
||||
import { cn } from '@/lib/utils';
|
||||
const pricing = {
|
||||
title: "Simple, transparent pricing",
|
||||
subtitle: "Choose the plan that fits your needs. No hidden fees.",
|
||||
packs: [
|
||||
{
|
||||
name: "Hobby",
|
||||
price: "$0",
|
||||
features: [
|
||||
"Unlimited upload",
|
||||
"1 Hour of Storage",
|
||||
"Standard Support",
|
||||
],
|
||||
buttonText: "Start Free",
|
||||
tag: "",
|
||||
bg: "#f9fafb",
|
||||
},
|
||||
{
|
||||
name: "Pro",
|
||||
price: "$29",
|
||||
features: [
|
||||
"Ads free player",
|
||||
"Support M3U8",
|
||||
"Unlimited upload",
|
||||
"Custom ads"
|
||||
],
|
||||
buttonText: "Get Started",
|
||||
tag: "POPULAR",
|
||||
bg: "#eff6ff",
|
||||
},
|
||||
{
|
||||
name: "Scale",
|
||||
price: "$99",
|
||||
features: [
|
||||
"5 TB Bandwidth",
|
||||
"500 Hours Storage",
|
||||
"Priority Support"
|
||||
],
|
||||
buttonText: "Contact Sales",
|
||||
tag: "Best Value",
|
||||
bg: "#eef4f7",
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -38,7 +38,7 @@ const routes: RouteData[] = [
|
||||
component: () => import("./auth/login.vue"),
|
||||
},
|
||||
{
|
||||
path: "signup",
|
||||
path: "sign-up",
|
||||
name: "signup",
|
||||
component: () => import("./auth/signup.vue"),
|
||||
},
|
||||
@@ -84,6 +84,7 @@ const routes: RouteData[] = [
|
||||
],
|
||||
},
|
||||
];
|
||||
const createAppRouter = () => {
|
||||
const router = createRouter({
|
||||
history: import.meta.env.SSR
|
||||
? createMemoryHistory() // server
|
||||
@@ -93,7 +94,6 @@ const router = createRouter({
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const auth = useAuthStore();
|
||||
console.log("Call on server:", Math.random());
|
||||
if (to.matched.some((record) => record.meta.requiresAuth)) {
|
||||
if (!auth.user) {
|
||||
next({ name: "login" });
|
||||
@@ -104,5 +104,7 @@ router.beforeEach((to, from, next) => {
|
||||
next();
|
||||
}
|
||||
});
|
||||
return router;
|
||||
}
|
||||
|
||||
export default router;
|
||||
export default createAppRouter;
|
||||
|
||||
@@ -20,15 +20,10 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
// Check auth status on init (reads from cookie)
|
||||
async function init() {
|
||||
console.log("Auth store init called");
|
||||
// if (initialized.value) return;
|
||||
if (initialized.value) return;
|
||||
|
||||
try {
|
||||
const response = await client.checkAuth().then((res) => {
|
||||
|
||||
console.log("call", res);
|
||||
return res;
|
||||
});
|
||||
const response = await client.checkAuth();
|
||||
if (response.authenticated && response.user) {
|
||||
user.value = response.user;
|
||||
// Get CSRF token if authenticated
|
||||
@@ -49,18 +44,30 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
async function login(username: string, password: string) {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const response = await client.login(username, password);
|
||||
return client.login(username, password).then((response) => {
|
||||
user.value = response.user;
|
||||
csrfToken.value = response.csrfToken;
|
||||
router.push('/');
|
||||
} catch (e: any) {
|
||||
console.log(JSON.parse(e.message))
|
||||
error.value = e.message || 'Login failed';
|
||||
}).catch((e: any) => {
|
||||
// error.value = e.message || 'Login failed';
|
||||
error.value = 'Login failed';
|
||||
throw e;
|
||||
} finally {
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
// try {
|
||||
// const response = await client.login(username, password);
|
||||
// user.value = response.user;
|
||||
// csrfToken.value = response.csrfToken;
|
||||
// router.push('/');
|
||||
// } catch (e: any) {
|
||||
// // console.log(JSON.parse(e.message))
|
||||
// // error.value = e.message || 'Login failed';
|
||||
// error.value = 'Login failed';
|
||||
// throw e;
|
||||
// } finally {
|
||||
// loading.value = false;
|
||||
// }
|
||||
}
|
||||
|
||||
async function register(username: string, email: string, password: string) {
|
||||
@@ -72,7 +79,8 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
csrfToken.value = response.csrfToken;
|
||||
router.push('/');
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Registration failed';
|
||||
// error.value = e.message || 'Registration failed';
|
||||
error.value = 'Registration failed';
|
||||
throw e;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -90,5 +98,11 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
router.push('/');
|
||||
}
|
||||
|
||||
return { user, loading, error, csrfToken, initialized, init, login, register, logout };
|
||||
return { user, loading, error, csrfToken, initialized, init, login, register, logout, $reset: () => {
|
||||
user.value = null;
|
||||
loading.value = false;
|
||||
error.value = null;
|
||||
csrfToken.value = null;
|
||||
initialized.value = false;
|
||||
} };
|
||||
});
|
||||
|
||||
@@ -121,6 +121,16 @@ export default defineConfig({
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
`;
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user