feat: add public site pages and social login
This commit is contained in:
@@ -45,23 +45,32 @@ export function useFacebookLogin() {
|
||||
const loginWithFacebook = () => {
|
||||
if (!isSdkLoaded.value) {
|
||||
console.error("Facebook SDK non encore chargé !");
|
||||
return;
|
||||
return Promise.reject(new Error("Facebook SDK is not loaded"));
|
||||
}
|
||||
|
||||
window.FB.login(
|
||||
(response) => {
|
||||
if (response.authResponse) {
|
||||
console.log("Utilisateur connecté :", response);
|
||||
const authStore = useAuthStore();
|
||||
authStore.loginWithFacebook(response.authResponse);
|
||||
} else {
|
||||
console.log("Connexion annulée ou échouée.");
|
||||
return new Promise((resolve, reject) => {
|
||||
window.FB.login(
|
||||
async (response) => {
|
||||
if (!response.authResponse) {
|
||||
console.log("Connexion annulée ou échouée.");
|
||||
reject(new Error("Facebook login was cancelled or failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("Utilisateur connecté :", response);
|
||||
const authStore = useAuthStore();
|
||||
const result = await authStore.loginWithFacebook(response.authResponse);
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
{
|
||||
scope: "public_profile,email"
|
||||
}
|
||||
},
|
||||
{
|
||||
scope: "public_profile,email"
|
||||
}
|
||||
);
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -1,94 +1,116 @@
|
||||
<template>
|
||||
<div class="flex min-h-full w-full items-center justify-center p-4">
|
||||
<div class="flex w-full max-w-[512px] flex-col gap-10">
|
||||
<h1 class="login-text text-center text-2xl font-bold">
|
||||
{{ t('title') }}
|
||||
</h1>
|
||||
<div class="login-page">
|
||||
<div class="login-wrap">
|
||||
<router-link
|
||||
class="login-brand"
|
||||
to="/"
|
||||
>
|
||||
<span class="login-brand-mark">S</span>
|
||||
<span class="login-brand-text">Socialize</span>
|
||||
</router-link>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<google-login
|
||||
:callback="googleCallback"
|
||||
popup-type="TOKEN"
|
||||
>
|
||||
<button class="secondary">
|
||||
<section class="login-card">
|
||||
<h1 class="login-text text-center text-2xl font-bold">
|
||||
{{ t('title') }}
|
||||
</h1>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<google-login
|
||||
:callback="googleCallback"
|
||||
popup-type="TOKEN"
|
||||
>
|
||||
<button class="secondary">
|
||||
<v-icon
|
||||
:icon="mdiGoogle"
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ t('continueWithGoogle') }}
|
||||
</button>
|
||||
</google-login>
|
||||
|
||||
<button
|
||||
class="secondary"
|
||||
type="button"
|
||||
@click="handleFacebookLogin"
|
||||
>
|
||||
<v-icon
|
||||
:icon="mdiGoogle"
|
||||
:icon="mdiFacebook"
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ t('continueWithGoogle') }}
|
||||
{{ t('continueWithFacebook') }}
|
||||
</button>
|
||||
</google-login>
|
||||
</div>
|
||||
|
||||
<div class="my-4 flex items-center">
|
||||
<div class="h-px grow bg-gray-200"></div>
|
||||
<span class="px-3 text-sm font-semibold uppercase text-gray-300">{{ t('orContinueWith') }}</span>
|
||||
<div class="h-px grow bg-gray-200"></div>
|
||||
</div>
|
||||
|
||||
<!-- Add email/password form -->
|
||||
<v-form @submit.prevent="handleLocalLogin">
|
||||
<div class="flex flex-col gap-4">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
:label="t('email')"
|
||||
required
|
||||
type="email"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
:label="t('password')"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
required
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
:icon="showPassword ? mdiEyeOff : mdiEye"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
@click="showPassword = !showPassword"
|
||||
/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-btn
|
||||
block
|
||||
color="primary"
|
||||
type="submit"
|
||||
>
|
||||
{{ t('signIn') }}
|
||||
</v-btn>
|
||||
|
||||
<div class="text-center">
|
||||
<a
|
||||
class="cursor-pointer text-sm text-blue-500"
|
||||
@click="forgotPassword"
|
||||
>
|
||||
{{ t('forgotPassword') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-center">
|
||||
<a
|
||||
class="cursor-pointer text-sm text-blue-500"
|
||||
@click="resendVerification"
|
||||
>
|
||||
{{ t('resendVerification') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
{{ t('noAccount') }}
|
||||
<router-link
|
||||
class="text-blue-500"
|
||||
to="/register"
|
||||
>
|
||||
{{ t('register') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</v-form>
|
||||
|
||||
<div class="my-4 flex items-center">
|
||||
<div class="h-px grow bg-gray-200"></div>
|
||||
<span class="px-3 text-sm font-semibold uppercase text-gray-300">{{ t('orContinueWith') }}</span>
|
||||
<div class="h-px grow bg-gray-200"></div>
|
||||
</div>
|
||||
|
||||
<!-- Add email/password form -->
|
||||
<v-form @submit.prevent="handleLocalLogin">
|
||||
<div class="flex flex-col gap-4">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
:label="t('email')"
|
||||
required
|
||||
type="email"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
:label="t('password')"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
required
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
:icon="showPassword ? mdiEyeOff : mdiEye"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
@click="showPassword = !showPassword"
|
||||
/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-btn
|
||||
block
|
||||
color="primary"
|
||||
type="submit"
|
||||
>
|
||||
{{ t('signIn') }}
|
||||
</v-btn>
|
||||
|
||||
<div class="text-center">
|
||||
<a
|
||||
class="cursor-pointer text-sm text-blue-500"
|
||||
@click="forgotPassword"
|
||||
>
|
||||
{{ t('forgotPassword') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-center">
|
||||
<a
|
||||
class="cursor-pointer text-sm text-blue-500"
|
||||
@click="resendVerification"
|
||||
>
|
||||
{{ t('resendVerification') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
{{ t('noAccount') }}
|
||||
<router-link
|
||||
class="text-blue-500"
|
||||
to="/register"
|
||||
>
|
||||
{{ t('register') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</v-form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Error notification -->
|
||||
@@ -105,13 +127,15 @@
|
||||
import { ref } from 'vue';
|
||||
import { GoogleLogin } from 'vue3-google-login';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useFacebookLogin } from '@/features/auth/composables/useFacebookLogin.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { mdiEye, mdiEyeOff, mdiGoogle } from '@mdi/js';
|
||||
import { mdiEye, mdiEyeOff, mdiFacebook, mdiGoogle } from '@mdi/js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
const { loginWithFacebook } = useFacebookLogin();
|
||||
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
@@ -149,6 +173,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFacebookLogin() {
|
||||
try {
|
||||
const response = await loginWithFacebook();
|
||||
if (response === true) {
|
||||
await router.push(props.returnUrl);
|
||||
} else {
|
||||
errorSnackBar.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Facebook login failed:', error);
|
||||
errorSnackBar.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function forgotPassword() {
|
||||
router.push('/forgot-password');
|
||||
}
|
||||
@@ -159,6 +197,41 @@
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-page {
|
||||
@apply flex min-h-screen w-full items-stretch justify-center sm:items-center sm:p-4;
|
||||
}
|
||||
|
||||
.login-wrap {
|
||||
@apply flex min-h-screen w-full max-w-[512px] flex-col gap-6 sm:min-h-0;
|
||||
}
|
||||
|
||||
.login-brand {
|
||||
@apply mx-auto flex items-center gap-3 px-4 pt-6 no-underline sm:px-0 sm:pt-0;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.login-brand-mark {
|
||||
@apply flex h-11 w-11 items-center justify-center rounded-2xl text-lg font-black;
|
||||
background: linear-gradient(135deg, #ff8a3d 0%, #ef4444 100%);
|
||||
color: #fffaf2;
|
||||
}
|
||||
|
||||
.login-brand-text {
|
||||
@apply text-lg font-black uppercase tracking-[0.18em];
|
||||
}
|
||||
|
||||
.login-card {
|
||||
@apply flex min-h-0 w-full flex-1 flex-col justify-center gap-10 bg-white/80 px-5 py-8 sm:flex-none sm:rounded-[1.5rem] sm:border sm:p-8;
|
||||
border-color: rgba(23, 32, 51, 0.08);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.login-card {
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.visibility-toggle {
|
||||
@apply cursor-pointer;
|
||||
@apply transition-opacity duration-300;
|
||||
@@ -194,7 +267,8 @@
|
||||
"noAccount": "Don't have an account?",
|
||||
"register": "Register",
|
||||
"loginFailed": "Login failed. Please check your credentials.",
|
||||
"continueWithGoogle": "Continue with Google"
|
||||
"continueWithGoogle": "Continue with Google",
|
||||
"continueWithFacebook": "Continue with Facebook"
|
||||
},
|
||||
"fr": {
|
||||
"title": "Se connecter",
|
||||
@@ -208,7 +282,8 @@
|
||||
"noAccount": "Vous n'avez pas de compte?",
|
||||
"register": "S'inscrire",
|
||||
"loginFailed": "Échec de la connexion. Veuillez vérifier vos identifiants.",
|
||||
"continueWithGoogle": "Continuer avec Google"
|
||||
"continueWithGoogle": "Continuer avec Google",
|
||||
"continueWithFacebook": "Continuer avec Facebook"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
113
frontend/src/features/landing/components/LandingSiteMenu.vue
Normal file
113
frontend/src/features/landing/components/LandingSiteMenu.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<header class="site-menu">
|
||||
<div class="site-menu-inner">
|
||||
<router-link
|
||||
class="site-brand"
|
||||
to="/"
|
||||
>
|
||||
<span class="site-brand-mark">S</span>
|
||||
<span class="site-brand-text">Socialize</span>
|
||||
</router-link>
|
||||
|
||||
<nav
|
||||
class="site-nav"
|
||||
aria-label="Public site navigation"
|
||||
>
|
||||
<router-link to="/product">Product</router-link>
|
||||
<router-link to="/pricing">Pricing</router-link>
|
||||
<div class="site-nav-group">
|
||||
<button type="button">Resources</button>
|
||||
<div class="site-nav-menu">
|
||||
<router-link to="/blogs">Blogs</router-link>
|
||||
<router-link to="/guides">Guides</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<router-link
|
||||
class="site-login"
|
||||
to="/login"
|
||||
>
|
||||
Login
|
||||
</router-link>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.site-menu {
|
||||
@apply sticky top-0 z-30 w-full;
|
||||
background: rgba(255, 250, 242, 0.9);
|
||||
backdrop-filter: blur(18px);
|
||||
border-bottom: 1px solid rgba(23, 32, 51, 0.08);
|
||||
}
|
||||
|
||||
.site-menu-inner {
|
||||
@apply mx-auto flex w-full max-w-7xl items-center justify-between gap-4 px-5 py-4 md:px-8;
|
||||
}
|
||||
|
||||
.site-brand {
|
||||
@apply flex min-w-0 items-center gap-3 no-underline;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.site-brand-mark {
|
||||
@apply flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-2xl text-base font-black;
|
||||
background: linear-gradient(135deg, #ff8a3d 0%, #ef4444 100%);
|
||||
color: #fffaf2;
|
||||
}
|
||||
|
||||
.site-brand-text {
|
||||
@apply truncate text-lg font-black uppercase tracking-[0.18em];
|
||||
}
|
||||
|
||||
.site-nav {
|
||||
@apply hidden items-center justify-center gap-2 sm:flex;
|
||||
}
|
||||
|
||||
.site-nav a {
|
||||
@apply rounded-full px-4 py-2 text-sm font-semibold no-underline transition-colors;
|
||||
color: #44516a;
|
||||
}
|
||||
|
||||
.site-nav a:hover,
|
||||
.site-nav-group:hover > button {
|
||||
background: rgba(23, 32, 51, 0.06);
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.site-nav-group {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.site-nav-group > button {
|
||||
@apply rounded-full px-4 py-2 text-sm font-semibold transition-colors;
|
||||
color: #44516a;
|
||||
}
|
||||
|
||||
.site-nav-menu {
|
||||
@apply invisible absolute left-0 top-[calc(100%+0.5rem)] flex min-w-36 flex-col gap-1 rounded-[1rem] border p-2 opacity-0 transition-opacity;
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
border-color: rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.12);
|
||||
}
|
||||
|
||||
.site-nav-group:hover .site-nav-menu,
|
||||
.site-nav-group:focus-within .site-nav-menu {
|
||||
@apply visible opacity-100;
|
||||
}
|
||||
|
||||
.site-nav-menu a {
|
||||
@apply rounded-[0.75rem] px-3 py-2;
|
||||
}
|
||||
|
||||
.site-login {
|
||||
@apply flex h-10 items-center rounded-full px-4 text-sm font-bold no-underline transition-colors;
|
||||
background: #172033;
|
||||
color: #fffaf2;
|
||||
}
|
||||
|
||||
.site-login:hover {
|
||||
background: #0f766e;
|
||||
}
|
||||
</style>
|
||||
52
frontend/src/features/landing/views/BlogsPage.vue
Normal file
52
frontend/src/features/landing/views/BlogsPage.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="public-page">
|
||||
<LandingSiteMenu />
|
||||
|
||||
<main class="public-page-content">
|
||||
<section class="public-page-panel">
|
||||
<div class="eyebrow">Blogs</div>
|
||||
<h1>Practical notes on content review workflows.</h1>
|
||||
<p>
|
||||
Articles are coming soon. This area will cover approval operations, client review habits,
|
||||
revision tracking, and publication handoff.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.public-page {
|
||||
@apply min-h-screen w-full;
|
||||
}
|
||||
|
||||
.public-page-content {
|
||||
@apply mx-auto flex w-full max-w-7xl flex-col px-5 py-8 md:px-8 md:py-12;
|
||||
}
|
||||
|
||||
.public-page-panel {
|
||||
@apply rounded-[2rem] p-6 md:p-10;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
border: 1px solid rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
@apply text-xs font-bold uppercase tracking-[0.26em];
|
||||
color: #ff8a3d;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply mt-4 max-w-4xl text-4xl font-black leading-tight md:text-6xl;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply mt-5 max-w-3xl text-base leading-7 md:text-lg;
|
||||
color: #44516a;
|
||||
}
|
||||
</style>
|
||||
52
frontend/src/features/landing/views/GuidesPage.vue
Normal file
52
frontend/src/features/landing/views/GuidesPage.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="public-page">
|
||||
<LandingSiteMenu />
|
||||
|
||||
<main class="public-page-content">
|
||||
<section class="public-page-panel">
|
||||
<div class="eyebrow">Guides</div>
|
||||
<h1>Reusable guides for managing review and approval work.</h1>
|
||||
<p>
|
||||
Guides are coming soon. This area will collect repeatable playbooks for content intake,
|
||||
review rounds, approval decisions, and delivery readiness.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.public-page {
|
||||
@apply min-h-screen w-full;
|
||||
}
|
||||
|
||||
.public-page-content {
|
||||
@apply mx-auto flex w-full max-w-7xl flex-col px-5 py-8 md:px-8 md:py-12;
|
||||
}
|
||||
|
||||
.public-page-panel {
|
||||
@apply rounded-[2rem] p-6 md:p-10;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
border: 1px solid rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
@apply text-xs font-bold uppercase tracking-[0.26em];
|
||||
color: #ff8a3d;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply mt-4 max-w-4xl text-4xl font-black leading-tight md:text-6xl;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply mt-5 max-w-3xl text-base leading-7 md:text-lg;
|
||||
color: #44516a;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
|
||||
const pillars = computed(() => [
|
||||
{
|
||||
@@ -25,37 +26,43 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="landing-shell">
|
||||
<section class="hero-card">
|
||||
<div class="hero-copy">
|
||||
<div class="eyebrow">Social media approval workflow</div>
|
||||
<h1>Replace Drive links, scattered comments, and manual follow-up with one review system.</h1>
|
||||
<p>
|
||||
Socialize is being rebuilt as an agency workflow product for content review, revision tracking,
|
||||
client approval, and publication readiness.
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<router-link to="/login">
|
||||
<button class="primary">Open the app</button>
|
||||
</router-link>
|
||||
<router-link to="/register">
|
||||
<button class="secondary">Create an internal account</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="landing-page">
|
||||
<LandingSiteMenu />
|
||||
|
||||
<div class="hero-panel">
|
||||
<div class="hero-panel-title">Version 1 workflow</div>
|
||||
<ol class="workflow-list">
|
||||
<li
|
||||
v-for="step in workflow"
|
||||
:key="step"
|
||||
>
|
||||
{{ step }}
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
<main class="landing-shell">
|
||||
<section
|
||||
id="products"
|
||||
class="hero-card"
|
||||
>
|
||||
<div class="hero-copy">
|
||||
<div class="eyebrow">Social media approval workflow</div>
|
||||
<h1>Replace Drive links, scattered comments, and manual follow-up with one review system.</h1>
|
||||
<p>
|
||||
Socialize is being rebuilt as an agency workflow product for content review, revision tracking,
|
||||
client approval, and publication readiness.
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<router-link to="/login">
|
||||
<button class="primary">Open the app</button>
|
||||
</router-link>
|
||||
<router-link to="/register">
|
||||
<button class="secondary">Create an internal account</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-panel">
|
||||
<div class="hero-panel-title">Version 1 workflow</div>
|
||||
<ol class="workflow-list">
|
||||
<li
|
||||
v-for="step in workflow"
|
||||
:key="step"
|
||||
>
|
||||
{{ step }}
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pillars-grid">
|
||||
<article
|
||||
@@ -88,10 +95,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="pricing"
|
||||
class="pricing-card"
|
||||
>
|
||||
<div>
|
||||
<div class="eyebrow">Pricing</div>
|
||||
<h2>Workspace pricing for teams that manage content approvals with clients.</h2>
|
||||
</div>
|
||||
<router-link to="/login">
|
||||
<button class="secondary pricing-action">Open the app</button>
|
||||
</router-link>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.landing-page {
|
||||
@apply min-h-screen w-full;
|
||||
}
|
||||
|
||||
.landing-shell {
|
||||
@apply mx-auto flex w-full max-w-7xl flex-col gap-8 px-5 py-8 md:px-8 md:py-12;
|
||||
}
|
||||
@@ -189,4 +214,20 @@
|
||||
@apply mt-2 block text-sm leading-6;
|
||||
color: #3f4d63;
|
||||
}
|
||||
|
||||
.pricing-card {
|
||||
@apply flex flex-col gap-5 rounded-[1.75rem] p-6 md:flex-row md:items-center md:justify-between md:p-8;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
border: 1px solid rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
|
||||
}
|
||||
|
||||
.pricing-card h2 {
|
||||
@apply mt-3 max-w-3xl text-2xl font-black leading-tight md:text-3xl;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.pricing-action {
|
||||
@apply w-full sm:w-auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
68
frontend/src/features/landing/views/PricingPage.vue
Normal file
68
frontend/src/features/landing/views/PricingPage.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<script setup>
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="public-page">
|
||||
<LandingSiteMenu />
|
||||
|
||||
<main class="public-page-content">
|
||||
<section class="public-page-panel">
|
||||
<div class="eyebrow">Pricing</div>
|
||||
<h1>Simple workspace pricing for teams managing client approvals.</h1>
|
||||
<p>
|
||||
Pricing details are coming soon. For now, Socialize is focused on the core review workflow:
|
||||
workspaces, content items, assets, comments, and approval decisions.
|
||||
</p>
|
||||
<router-link
|
||||
class="pricing-login"
|
||||
to="/login"
|
||||
>
|
||||
Login
|
||||
</router-link>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.public-page {
|
||||
@apply min-h-screen w-full;
|
||||
}
|
||||
|
||||
.public-page-content {
|
||||
@apply mx-auto flex w-full max-w-7xl flex-col px-5 py-8 md:px-8 md:py-12;
|
||||
}
|
||||
|
||||
.public-page-panel {
|
||||
@apply rounded-[2rem] p-6 md:p-10;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
border: 1px solid rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
@apply text-xs font-bold uppercase tracking-[0.26em];
|
||||
color: #ff8a3d;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply mt-4 max-w-4xl text-4xl font-black leading-tight md:text-6xl;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply mt-5 max-w-3xl text-base leading-7 md:text-lg;
|
||||
color: #44516a;
|
||||
}
|
||||
|
||||
.pricing-login {
|
||||
@apply mt-8 inline-flex h-11 w-fit items-center rounded-full px-5 text-sm font-bold no-underline transition-colors;
|
||||
background: #172033;
|
||||
color: #fffaf2;
|
||||
}
|
||||
|
||||
.pricing-login:hover {
|
||||
background: #0f766e;
|
||||
}
|
||||
</style>
|
||||
52
frontend/src/features/landing/views/ProductPage.vue
Normal file
52
frontend/src/features/landing/views/ProductPage.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="public-page">
|
||||
<LandingSiteMenu />
|
||||
|
||||
<main class="public-page-content">
|
||||
<section class="public-page-panel">
|
||||
<div class="eyebrow">Product</div>
|
||||
<h1>Social media content approval, organized around the work itself.</h1>
|
||||
<p>
|
||||
Socialize keeps content items, assets, revisions, comments, approval decisions, and publishing
|
||||
handoff details in one workspace so teams do not have to coordinate review across scattered tools.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.public-page {
|
||||
@apply min-h-screen w-full;
|
||||
}
|
||||
|
||||
.public-page-content {
|
||||
@apply mx-auto flex w-full max-w-7xl flex-col px-5 py-8 md:px-8 md:py-12;
|
||||
}
|
||||
|
||||
.public-page-panel {
|
||||
@apply rounded-[2rem] p-6 md:p-10;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
border: 1px solid rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
@apply text-xs font-bold uppercase tracking-[0.26em];
|
||||
color: #ff8a3d;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply mt-4 max-w-4xl text-4xl font-black leading-tight md:text-6xl;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply mt-5 max-w-3xl text-base leading-7 md:text-lg;
|
||||
color: #44516a;
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,10 @@ import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
const LoginView = () => import('@/features/auth/views/LoginView.vue');
|
||||
const Landing = () => import('@/features/landing/views/Landing.vue');
|
||||
const ProductPage = () => import('@/features/landing/views/ProductPage.vue');
|
||||
const PricingPage = () => import('@/features/landing/views/PricingPage.vue');
|
||||
const BlogsPage = () => import('@/features/landing/views/BlogsPage.vue');
|
||||
const GuidesPage = () => import('@/features/landing/views/GuidesPage.vue');
|
||||
const RegisterView = () => import('@/features/auth/views/RegisterView.vue');
|
||||
const ForgotPasswordView = () => import('@/features/auth/views/ForgotPasswordView.vue');
|
||||
const ResetPasswordView = () => import('@/features/auth/views/ResetPasswordView.vue');
|
||||
@@ -32,6 +36,26 @@ const routes = [
|
||||
name: 'landing',
|
||||
component: Landing,
|
||||
},
|
||||
{
|
||||
path: '/product',
|
||||
name: 'product',
|
||||
component: ProductPage,
|
||||
},
|
||||
{
|
||||
path: '/pricing',
|
||||
name: 'pricing',
|
||||
component: PricingPage,
|
||||
},
|
||||
{
|
||||
path: '/blogs',
|
||||
name: 'blogs',
|
||||
component: BlogsPage,
|
||||
},
|
||||
{
|
||||
path: '/guides',
|
||||
name: 'guides',
|
||||
component: GuidesPage,
|
||||
},
|
||||
{
|
||||
path: '/app',
|
||||
redirect: { name: 'dashboard' },
|
||||
|
||||
Reference in New Issue
Block a user