prettier fixes #16
This commit is contained in:
@@ -3,10 +3,10 @@
|
||||
import { ACCOUNT_ACCESS } from '~~/prisma/account-access-enum';
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const { activeMembership, activeAccountMembers } = storeToRefs(accountStore)
|
||||
const { activeMembership, activeAccountMembers } = storeToRefs(accountStore);
|
||||
|
||||
const config = useRuntimeConfig();
|
||||
const newAccountName = ref("");
|
||||
const newAccountName = ref('');
|
||||
|
||||
onMounted(async () => {
|
||||
await accountStore.init();
|
||||
@@ -14,8 +14,12 @@
|
||||
});
|
||||
|
||||
function formatDate(date: Date | undefined) {
|
||||
if (!date) { return ""; }
|
||||
return new Intl.DateTimeFormat('default', { dateStyle: 'long' }).format(date);
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
return new Intl.DateTimeFormat('default', { dateStyle: 'long' }).format(
|
||||
date
|
||||
);
|
||||
}
|
||||
|
||||
function joinURL() {
|
||||
@@ -25,7 +29,9 @@
|
||||
<template>
|
||||
<div class="container mx-auto p-6">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">Account Information
|
||||
<h2
|
||||
class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">
|
||||
Account Information
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -34,24 +40,39 @@
|
||||
<span class="font-bold w-32">Account Name:</span>
|
||||
<span>{{ activeMembership?.account.name }}</span>
|
||||
<template
|
||||
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)">
|
||||
<input v-model="newAccountName" type="text" class="p-2 border border-gray-400 rounded w-1/3"
|
||||
placeholder="Enter new name">
|
||||
<button @click.prevent="accountStore.changeAccountName(newAccountName)"
|
||||
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded">Change Name</button>
|
||||
v-if="
|
||||
activeMembership &&
|
||||
(activeMembership.access === ACCOUNT_ACCESS.OWNER ||
|
||||
activeMembership.access === ACCOUNT_ACCESS.ADMIN)
|
||||
">
|
||||
<input
|
||||
v-model="newAccountName"
|
||||
type="text"
|
||||
class="p-2 border border-gray-400 rounded w-1/3"
|
||||
placeholder="Enter new name" />
|
||||
<button
|
||||
@click.prevent="accountStore.changeAccountName(newAccountName)"
|
||||
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded">
|
||||
Change Name
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Current Period Ends:</span>
|
||||
<span>{{ formatDate(activeMembership?.account.current_period_ends) }}</span>
|
||||
<span>{{
|
||||
formatDate(activeMembership?.account.current_period_ends)
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Permitted Features:</span>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span v-for="feature in activeMembership?.account.features"
|
||||
class="bg-gray-200 text-gray-700 font-semibold py-1 px-2 rounded-full">{{ feature }}</span>
|
||||
<span
|
||||
v-for="feature in activeMembership?.account.features"
|
||||
class="bg-gray-200 text-gray-700 font-semibold py-1 px-2 rounded-full">
|
||||
{{ feature }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,7 +83,10 @@
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">AI Gens for this Month/Max:</span>
|
||||
<span>{{ activeMembership?.account.ai_gen_count }} / {{ activeMembership?.account.ai_gen_max_pm }}</span>
|
||||
<span>
|
||||
{{ activeMembership?.account.ai_gen_count }} /
|
||||
{{ activeMembership?.account.ai_gen_max_pm }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
@@ -72,9 +96,15 @@
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Access Level:</span>
|
||||
<span class="bg-green-500 text-white font-semibold py-1 px-2 rounded-full">{{ activeMembership?.access }}</span>
|
||||
<button @click.prevent="accountStore.claimOwnershipOfAccount()"
|
||||
v-if="activeMembership && activeMembership.access === ACCOUNT_ACCESS.ADMIN "
|
||||
<span
|
||||
class="bg-green-500 text-white font-semibold py-1 px-2 rounded-full">
|
||||
{{ activeMembership?.access }}
|
||||
</span>
|
||||
<button
|
||||
@click.prevent="accountStore.claimOwnershipOfAccount()"
|
||||
v-if="
|
||||
activeMembership && activeMembership.access === ACCOUNT_ACCESS.ADMIN
|
||||
"
|
||||
class="bg-orange-500 hover:bg-orange-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Claim Ownership
|
||||
</button>
|
||||
@@ -88,47 +118,109 @@
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Join Link:</span>
|
||||
<div class="flex gap-2 items-center">
|
||||
<input disabled type="text" class="p-2 border border-gray-400 rounded w-full" :value="joinURL()">
|
||||
<button @click.prevent="accountStore.rotateJoinPassword()"
|
||||
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">ReGen</button>
|
||||
<input
|
||||
disabled
|
||||
type="text"
|
||||
class="p-2 border border-gray-400 rounded w-full"
|
||||
:value="joinURL()" />
|
||||
<button
|
||||
@click.prevent="accountStore.rotateJoinPassword()"
|
||||
v-if="
|
||||
activeMembership &&
|
||||
(activeMembership.access === ACCOUNT_ACCESS.OWNER ||
|
||||
activeMembership.access === ACCOUNT_ACCESS.ADMIN)
|
||||
"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">
|
||||
ReGen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<h2 class="text-lg font-bold">Members</h2>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-for="accountMember in activeAccountMembers" class="flex gap-4 items-center">
|
||||
<div
|
||||
v-for="accountMember in activeAccountMembers"
|
||||
class="flex gap-4 items-center">
|
||||
<span>{{ accountMember.user.display_name }}</span>
|
||||
<span class="bg-green-500 text-white font-semibold py-1 px-2 rounded-full">{{ accountMember.access }}</span>
|
||||
<span
|
||||
class="bg-green-500 text-white font-semibold py-1 px-2 rounded-full">
|
||||
{{ accountMember.access }}
|
||||
</span>
|
||||
<span class="text-gray-500">({{ accountMember.user.email }})</span>
|
||||
<button @click.prevent="accountStore.acceptPendingMembership(accountMember.id)"
|
||||
v-if="accountMember.pending && activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"
|
||||
<button
|
||||
@click.prevent="
|
||||
accountStore.acceptPendingMembership(accountMember.id)
|
||||
"
|
||||
v-if="
|
||||
accountMember.pending &&
|
||||
activeMembership &&
|
||||
(activeMembership.access === ACCOUNT_ACCESS.OWNER ||
|
||||
activeMembership.access === ACCOUNT_ACCESS.ADMIN)
|
||||
"
|
||||
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Accept Pending Membership
|
||||
</button>
|
||||
<button @click.prevent="accountStore.rejectPendingMembership(accountMember.id)"
|
||||
v-if="accountMember.pending && activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"
|
||||
<button
|
||||
@click.prevent="
|
||||
accountStore.rejectPendingMembership(accountMember.id)
|
||||
"
|
||||
v-if="
|
||||
accountMember.pending &&
|
||||
activeMembership &&
|
||||
(activeMembership.access === ACCOUNT_ACCESS.OWNER ||
|
||||
activeMembership.access === ACCOUNT_ACCESS.ADMIN)
|
||||
"
|
||||
class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Reject Pending Membership
|
||||
</button>
|
||||
<button @click.prevent="accountStore.changeUserAccessWithinAccount(accountMember.user.id, ACCOUNT_ACCESS.READ_WRITE)"
|
||||
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN) && accountMember.access === ACCOUNT_ACCESS.READ_ONLY && !accountMember.pending"
|
||||
<button
|
||||
@click.prevent="
|
||||
accountStore.changeUserAccessWithinAccount(
|
||||
accountMember.user.id,
|
||||
ACCOUNT_ACCESS.READ_WRITE
|
||||
)
|
||||
"
|
||||
v-if="
|
||||
activeMembership &&
|
||||
(activeMembership.access === ACCOUNT_ACCESS.OWNER ||
|
||||
activeMembership.access === ACCOUNT_ACCESS.ADMIN) &&
|
||||
accountMember.access === ACCOUNT_ACCESS.READ_ONLY &&
|
||||
!accountMember.pending
|
||||
"
|
||||
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Promote to Read/Write
|
||||
</button>
|
||||
<button @click.prevent="accountStore.changeUserAccessWithinAccount(accountMember.user.id, ACCOUNT_ACCESS.ADMIN)"
|
||||
v-if="activeMembership && activeMembership.access === ACCOUNT_ACCESS.OWNER && accountMember.access === ACCOUNT_ACCESS.READ_WRITE && !accountMember.pending"
|
||||
<button
|
||||
@click.prevent="
|
||||
accountStore.changeUserAccessWithinAccount(
|
||||
accountMember.user.id,
|
||||
ACCOUNT_ACCESS.ADMIN
|
||||
)
|
||||
"
|
||||
v-if="
|
||||
activeMembership &&
|
||||
activeMembership.access === ACCOUNT_ACCESS.OWNER &&
|
||||
accountMember.access === ACCOUNT_ACCESS.READ_WRITE &&
|
||||
!accountMember.pending
|
||||
"
|
||||
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Promote to Admin
|
||||
</button>
|
||||
<button @click.prevent="accountStore.deleteMembership(accountMember.id)"
|
||||
v-if="activeMembership && activeMembership.access === ACCOUNT_ACCESS.OWNER && accountMember.access !== ACCOUNT_ACCESS.OWNER && !accountMember.pending"
|
||||
<button
|
||||
@click.prevent="accountStore.deleteMembership(accountMember.id)"
|
||||
v-if="
|
||||
activeMembership &&
|
||||
activeMembership.access === ACCOUNT_ACCESS.OWNER &&
|
||||
accountMember.access !== ACCOUNT_ACCESS.OWNER &&
|
||||
!accountMember.pending
|
||||
"
|
||||
class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div></template>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<div class="prose lg:prose-xl m-5">
|
||||
<p>
|
||||
We are sorry that you canceled your transaction!
|
||||
</p>
|
||||
<p>We are sorry that you canceled your transaction!</p>
|
||||
<p>
|
||||
<NuxtLink to="/pricing">Pricing</NuxtLink>
|
||||
</p>
|
||||
@@ -10,4 +8,4 @@
|
||||
<NuxtLink to="/dashboard">To Your Dashboard</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -3,56 +3,86 @@
|
||||
import { ACCOUNT_ACCESS } from '~~/prisma/account-access-enum';
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['auth'],
|
||||
middleware: ['auth']
|
||||
});
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const { activeMembership } = storeToRefs(accountStore)
|
||||
const { activeMembership } = storeToRefs(accountStore);
|
||||
const notesStore = useNotesStore();
|
||||
const { notes } = storeToRefs(notesStore); // ensure the notes list is reactive
|
||||
const newNoteText = ref('')
|
||||
const { notes } = storeToRefs(notesStore); // ensure the notes list is reactive
|
||||
const newNoteText = ref('');
|
||||
|
||||
async function addNote(){
|
||||
await notesStore.createNote(newNoteText.value)
|
||||
async function addNote() {
|
||||
await notesStore.createNote(newNoteText.value);
|
||||
newNoteText.value = '';
|
||||
}
|
||||
|
||||
async function genNote(){
|
||||
const genNoteText = await notesStore.generateAINoteFromPrompt(newNoteText.value)
|
||||
async function genNote() {
|
||||
const genNoteText = await notesStore.generateAINoteFromPrompt(
|
||||
newNoteText.value
|
||||
);
|
||||
newNoteText.value = genNoteText;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await accountStore.init();
|
||||
await notesStore.fetchNotesForCurrentUser();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col items-center max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:py-16 lg:px-8">
|
||||
<div
|
||||
class="flex flex-col items-center max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:py-16 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">Notes Dashboard</h2>
|
||||
<h2
|
||||
class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">
|
||||
Notes Dashboard
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.READ_WRITE || activeMembership.access === ACCOUNT_ACCESS.ADMIN || activeMembership.access === ACCOUNT_ACCESS.OWNER)" class="w-full max-w-md mx-auto mb-3">
|
||||
<textarea v-model="newNoteText" type="text" class="w-full rounded-l-md py-2 px-4 border-gray-400 border-2 focus:outline-none focus:border-blue-500" rows="5" placeholder="Add a note..."/>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
activeMembership &&
|
||||
(activeMembership.access === ACCOUNT_ACCESS.READ_WRITE ||
|
||||
activeMembership.access === ACCOUNT_ACCESS.ADMIN ||
|
||||
activeMembership.access === ACCOUNT_ACCESS.OWNER)
|
||||
"
|
||||
class="w-full max-w-md mx-auto mb-3">
|
||||
<textarea
|
||||
v-model="newNoteText"
|
||||
type="text"
|
||||
class="w-full rounded-l-md py-2 px-4 border-gray-400 border-2 focus:outline-none focus:border-blue-500"
|
||||
rows="5"
|
||||
placeholder="Add a note..." />
|
||||
<div class="flex justify-evenly">
|
||||
<button @click.prevent="addNote()" type="button"
|
||||
<button
|
||||
@click.prevent="addNote()"
|
||||
type="button"
|
||||
class="px-4 py-2 text-white bg-blue-600 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-opacity-50">
|
||||
Add
|
||||
</button>
|
||||
<button v-if="activeMembership.account.features.includes('SPECIAL_FEATURE')" @click.prevent="genNote()" type="button"
|
||||
<button
|
||||
v-if="activeMembership.account.features.includes('SPECIAL_FEATURE')"
|
||||
@click.prevent="genNote()"
|
||||
type="button"
|
||||
class="px-4 py-2 text-white bg-purple-600 rounded-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-600 focus:ring-opacity-50">
|
||||
Gen
|
||||
<Icon name="mdi:magic" class="h-6 w-6"/>
|
||||
<Icon name="mdi:magic" class="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-md">
|
||||
<div v-for="note in notes" class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4">
|
||||
<div
|
||||
v-for="note in notes"
|
||||
class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4">
|
||||
<p class="text-gray-600 mb-4">{{ note.note_text }}</p>
|
||||
<button @click.prevent="notesStore.deleteNote(note.id)"
|
||||
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.ADMIN || activeMembership.access === ACCOUNT_ACCESS.OWNER)"
|
||||
<button
|
||||
@click.prevent="notesStore.deleteNote(note.id)"
|
||||
v-if="
|
||||
activeMembership &&
|
||||
(activeMembership.access === ACCOUNT_ACCESS.ADMIN ||
|
||||
activeMembership.access === ACCOUNT_ACCESS.OWNER)
|
||||
"
|
||||
class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<div class="prose lg:prose-xl m-5">
|
||||
<p>
|
||||
We are sorry that you were unable to subscribe.
|
||||
</p>
|
||||
<p>We are sorry that you were unable to subscribe.</p>
|
||||
<p>
|
||||
<NuxtLink to="/pricing">Pricing</NuxtLink>
|
||||
</p>
|
||||
@@ -10,4 +8,4 @@
|
||||
<NuxtLink to="/dashboard">To Your Dashboard</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -3,23 +3,30 @@
|
||||
const config = useRuntimeConfig();
|
||||
const notifyStore = useNotifyStore();
|
||||
|
||||
const loading = ref(false)
|
||||
const email = ref('')
|
||||
const loading = ref(false);
|
||||
const email = ref('');
|
||||
|
||||
const sendResetPasswordLink = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data, error } = await supabase.auth.resetPasswordForEmail(email.value, {
|
||||
redirectTo: `${config.public.siteRootUrl}/resetpassword`,
|
||||
})
|
||||
if (error) throw error
|
||||
else notifyStore.notify("Password Reset link sent, check your email.", NotificationType.Success);
|
||||
loading.value = true;
|
||||
const { data, error } = await supabase.auth.resetPasswordForEmail(
|
||||
email.value,
|
||||
{
|
||||
redirectTo: `${config.public.siteRootUrl}/resetpassword`
|
||||
}
|
||||
);
|
||||
if (error) throw error;
|
||||
else
|
||||
notifyStore.notify(
|
||||
'Password Reset link sent, check your email.',
|
||||
NotificationType.Success
|
||||
);
|
||||
} catch (error) {
|
||||
notifyStore.notify(error, NotificationType.Error);
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center h-screen bg-gray-100">
|
||||
@@ -28,11 +35,20 @@
|
||||
<form @submit.prevent="sendResetPasswordLink" class="space-y-4">
|
||||
<div>
|
||||
<label for="email" class="block mb-2 font-bold">Email</label>
|
||||
<input v-model="email" id="email" type="email" class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your email" required>
|
||||
<input
|
||||
v-model="email"
|
||||
id="email"
|
||||
type="email"
|
||||
class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your email"
|
||||
required />
|
||||
</div>
|
||||
<button :disabled="loading || email === ''" type="submit"
|
||||
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">Send Reset Password Link</button>
|
||||
<button
|
||||
:disabled="loading || email === ''"
|
||||
type="submit"
|
||||
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">
|
||||
Send Reset Password Link
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
169
pages/index.vue
169
pages/index.vue
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
const user = useSupabaseUser()
|
||||
const user = useSupabaseUser();
|
||||
watchEffect(() => {
|
||||
if (user.value) {
|
||||
navigateTo('/dashboard', {replace: true})
|
||||
navigateTo('/dashboard', { replace: true });
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="container mx-auto m-5">
|
||||
@@ -13,57 +13,72 @@ const user = useSupabaseUser()
|
||||
<div class="container mx-auto">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-16">
|
||||
<div class="m-5">
|
||||
<h1 class="text-5xl font-bold mb-4">
|
||||
Build Your Next SaaS Faster
|
||||
</h1>
|
||||
<h1 class="text-5xl font-bold mb-4">Build Your Next SaaS Faster</h1>
|
||||
<p class="text-gray-700 text-lg mb-8">
|
||||
With SupaNuxt SaaS, you can easily get started building your
|
||||
next web application. Our pre-configured tech stack and
|
||||
industry leading features make it easy to get up and running in no time. Look! this guy is working so fast,
|
||||
his hands are just a blur.. you could be this fast.
|
||||
With SupaNuxt SaaS, you can easily get started building your next
|
||||
web application. Our pre-configured tech stack and industry
|
||||
leading features make it easy to get up and running in no time.
|
||||
Look! this guy is working so fast, his hands are just a blur.. you
|
||||
could be this fast.
|
||||
</p>
|
||||
<NuxtLink to="/signup" class="inline-block py-3 px-6 bg-blue-600 text-white rounded-lg hover:bg-blue-700">Get Started</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/signup"
|
||||
class="inline-block py-3 px-6 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
|
||||
>Get Started</NuxtLink
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<img src="~/assets/images/supanuxt_logo_200.png" alt="SupaNuxt SaaS Logo"/>
|
||||
<img
|
||||
src="~/assets/images/supanuxt_logo_200.png"
|
||||
alt="SupaNuxt SaaS Logo" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-12">
|
||||
<div class="container px-4 mx-auto">
|
||||
<div class="flex flex-col md:flex-row items-center justify-center md:justify-between mb-8">
|
||||
<div class="container px-4 mx-auto">
|
||||
<div
|
||||
class="flex flex-col md:flex-row items-center justify-center md:justify-between mb-8">
|
||||
<h2 class="text-3xl font-bold mb-4 md:mb-0">Tech Stack</h2>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row items-center mb-16">
|
||||
|
||||
<div class="md:w-full">
|
||||
<ul class="grid grid-cols-3 gap-10 list-none">
|
||||
<li>
|
||||
<Icon name="skill-icons:nuxtjs-dark" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Nuxt 3</h3>
|
||||
<p class="mt-2 text-base text-gray-500">The Progressive Vue.js Framework</p>
|
||||
<p class="mt-2 text-base text-gray-500">
|
||||
The Progressive Vue.js Framework
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="skill-icons:supabase-dark" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Supabase</h3>
|
||||
<p class="mt-2 text-base text-gray-500">Auth including OAuth + Postgresql instance</p>
|
||||
<p class="mt-2 text-base text-gray-500">
|
||||
Auth including OAuth + Postgresql instance
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="skill-icons:postgresql-dark" class="h-12 w-12 mb-2" />
|
||||
<Icon
|
||||
name="skill-icons:postgresql-dark"
|
||||
class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">PostgreSQL</h3>
|
||||
<p class="mt-2 text-base text-gray-500">Relational Database</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="logos:prisma" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Prisma</h3>
|
||||
<p class="mt-2 text-base text-gray-500">Schema management + Strongly typed client</p>
|
||||
<p class="mt-2 text-base text-gray-500">
|
||||
Schema management + Strongly typed client
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="simple-icons:trpc" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">TRPC</h3>
|
||||
<p class="mt-2 text-base text-gray-500">Server/Client communication with Strong types, SSR compatible</p>
|
||||
<p class="mt-2 text-base text-gray-500">
|
||||
Server/Client communication with Strong types, SSR compatible
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="skill-icons:vuejs-dark" class="h-12 w-12 mb-2" />
|
||||
@@ -73,104 +88,148 @@ const user = useSupabaseUser()
|
||||
<li>
|
||||
<Icon name="logos:stripe" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Stripe</h3>
|
||||
<p class="mt-2 text-base text-gray-500">Payments including Webhook integration</p>
|
||||
<p class="mt-2 text-base text-gray-500">
|
||||
Payments including Webhook integration
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="skill-icons:tailwindcss-dark" class="h-12 w-12 mb-2" />
|
||||
<Icon
|
||||
name="skill-icons:tailwindcss-dark"
|
||||
class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Tailwind</h3>
|
||||
<p class="mt-2 text-base text-gray-500">A utility-first CSS framework</p>
|
||||
<p class="mt-2 text-base text-gray-500">
|
||||
A utility-first CSS framework
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="skill-icons:vuejs-dark" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Vue.js</h3>
|
||||
<p class="mt-2 text-base text-gray-500">The Progressive JavaScript Framework</p>
|
||||
<p class="mt-2 text-base text-gray-500">
|
||||
The Progressive JavaScript Framework
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="logos:openai-icon" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">OpenAI</h3>
|
||||
<p class="mt-2 text-base text-gray-500">AI Completions including Note generation from prompt</p>
|
||||
<p class="mt-2 text-base text-gray-500">
|
||||
AI Completions including Note generation from prompt
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row items-center justify-center md:justify-between mb-8">
|
||||
<div
|
||||
class="flex flex-col md:flex-row items-center justify-center md:justify-between mb-8">
|
||||
<h2 class="text-3xl font-bold mb-4 md:mb-0">Features</h2>
|
||||
</div>
|
||||
<!-- User Management (text left) -->
|
||||
<div class="flex flex-col md:flex-row-reverse items-center mb-16">
|
||||
<div class="md:w-1/2 md:ml-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_user_management.jpeg" alt="User Management"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:ml-4">
|
||||
<img
|
||||
src="~/assets/images/landing_user_management.jpeg"
|
||||
alt="User Management"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:ml-4" />
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">User Management</h3>
|
||||
<p class="mb-4">SupaNuxt SaaS includes robust user management features, including
|
||||
authentication with social login (oauth) or email/password, management of user roles and permissions, and
|
||||
multi-user/team accounts that permit multiple users to share plan features including a team administration
|
||||
facility and user roles within team. This is a great feature for businesses or community groups who want to
|
||||
share the cost of the plan.</p>
|
||||
<p class="mb-4">
|
||||
SupaNuxt SaaS includes robust user management features, including
|
||||
authentication with social login (oauth) or email/password,
|
||||
management of user roles and permissions, and multi-user/team
|
||||
accounts that permit multiple users to share plan features
|
||||
including a team administration facility and user roles within
|
||||
team. This is a great feature for businesses or community groups
|
||||
who want to share the cost of the plan.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- DB Schema (text right)-->
|
||||
<div class="flex flex-col md:flex-row items-center mb-16">
|
||||
<div class="md:w-1/2 md:mr-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_db_schema_management.jpeg" alt="DB Schema Management"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:mr-4">
|
||||
<img
|
||||
src="~/assets/images/landing_db_schema_management.jpeg"
|
||||
alt="DB Schema Management"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:mr-4" />
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">DB Schema Management</h3>
|
||||
<p class="mb-4">We use Prisma for schema management to make sure you can easily
|
||||
manage and keep track of your database schema. We also utilise Prisma based strong types which, with some help from TRPC, penetrate the entire stack all
|
||||
the way to the web front end. This ensures that you can move fast with your feature development, alter schema and have those
|
||||
type changes instantly available and validated everywhere.</p>
|
||||
<p class="mb-4">
|
||||
We use Prisma for schema management to make sure you can easily
|
||||
manage and keep track of your database schema. We also utilise
|
||||
Prisma based strong types which, with some help from TRPC,
|
||||
penetrate the entire stack all the way to the web front end. This
|
||||
ensures that you can move fast with your feature development,
|
||||
alter schema and have those type changes instantly available and
|
||||
validated everywhere.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Config (text left) -->
|
||||
<div class="flex flex-col md:flex-row-reverse items-center mb-16">
|
||||
<div class="md:w-1/2 md:ml-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_config_environment.jpeg" alt="Config and Environment"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:ml-4">
|
||||
<img
|
||||
src="~/assets/images/landing_config_environment.jpeg"
|
||||
alt="Config and Environment"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:ml-4" />
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">Config and Environment</h3>
|
||||
<p class="mb-4">SupaNuxt SaaS includes an approach to config and environment
|
||||
management that enables customisation and management of api keys.</p>
|
||||
<p class="mb-4">
|
||||
SupaNuxt SaaS includes an approach to config and environment
|
||||
management that enables customisation and management of api keys.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- State Management (text right)-->
|
||||
<div class="flex flex-col md:flex-row items-center mb-16">
|
||||
<div class="md:w-1/2 md:mr-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_state_management.jpeg" alt="State Management"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:mr-4">
|
||||
<img
|
||||
src="~/assets/images/landing_state_management.jpeg"
|
||||
alt="State Management"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:mr-4" />
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">State Management</h3>
|
||||
<p class="mb-4">SupaNuxt SaaS includes multi modal state management that supports both Single Page Application (SPA)
|
||||
pages such as dashboards and Server Side Rendered (SSR) style pages for public content that are crawlable by Search
|
||||
engines like google and facilitate excellent Search Engine Optimisation (SEO).</p>
|
||||
<p class="mb-4">
|
||||
SupaNuxt SaaS includes multi modal state management that supports
|
||||
both Single Page Application (SPA) pages such as dashboards and
|
||||
Server Side Rendered (SSR) style pages for public content that are
|
||||
crawlable by Search engines like google and facilitate excellent
|
||||
Search Engine Optimisation (SEO).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stripe (text left) -->
|
||||
<div class="flex flex-col md:flex-row-reverse items-center mb-16">
|
||||
<div class="md:w-1/2 md:ml-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_stripe_integration.jpeg" alt="Stripe Integration"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:ml-4">
|
||||
<img
|
||||
src="~/assets/images/landing_stripe_integration.jpeg"
|
||||
alt="Stripe Integration"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:ml-4" />
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">Stripe Integration</h3>
|
||||
<p class="mb-4">SupaNuxt SaaS includes Stripe integration for subscription payments including
|
||||
Subscription based support for multi pricing and multiple plans.</p>
|
||||
<p class="mb-4">
|
||||
SupaNuxt SaaS includes Stripe integration for subscription
|
||||
payments including Subscription based support for multi pricing
|
||||
and multiple plans.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tailwind (text right)-->
|
||||
<div class="flex flex-col md:flex-row items-center mb-16">
|
||||
<div class="md:w-1/2 md:mr-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_style_system.jpeg" alt="Style System"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:mr-4">
|
||||
<img
|
||||
src="~/assets/images/landing_style_system.jpeg"
|
||||
alt="Style System"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:mr-4" />
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">Style System</h3>
|
||||
<p class="mb-4">SupaNuxt SaaS includes Tailwind integration for site styling including a themable UI components with daisyUI</p>
|
||||
<p class="mb-4">
|
||||
SupaNuxt SaaS includes Tailwind integration for site styling
|
||||
including a themable UI components with daisyUI
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
// this could probably be an elegant destructure here but I lost patience
|
||||
let account: AccountWithMembers | undefined;
|
||||
if (join_password) {
|
||||
const result = await $client.account.getAccountByJoinPassword.useQuery({ join_password });
|
||||
const result = await $client.account.getAccountByJoinPassword.useQuery({
|
||||
join_password
|
||||
});
|
||||
account = result.data.value?.account;
|
||||
}
|
||||
|
||||
@@ -19,45 +21,51 @@
|
||||
async function doJoin() {
|
||||
if (account) {
|
||||
await accountStore.joinUserToAccountPending(account.id);
|
||||
navigateTo('/dashboard', {replace: true})
|
||||
navigateTo('/dashboard', { replace: true });
|
||||
} else {
|
||||
console.log(`Unable to Join`)
|
||||
console.log(`Unable to Join`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="py-10 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-md mx-auto">
|
||||
<h2 class="text-3xl font-extrabold text-gray-900">Request to Join {{ account?.name }}</h2>
|
||||
<h2 class="text-3xl font-extrabold text-gray-900">
|
||||
Request to Join {{ account?.name }}
|
||||
</h2>
|
||||
<template v-if="dbUser?.dbUser">
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
Click below to request to Join the team.
|
||||
Your request to join will remain as 'Pending'
|
||||
untill the team administrators complete their review.</p>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
If your requeste is approved, you will become a member of the team and
|
||||
will be able to switch to the team account at any time in order to share
|
||||
the benefits of the team plan.
|
||||
Click below to request to Join the team. Your request to join will
|
||||
remain as 'Pending' untill the team administrators complete their
|
||||
review.
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
If your requeste is approved, you will become a member of the team and
|
||||
will be able to switch to the team account at any time in order to
|
||||
share the benefits of the team plan.
|
||||
</p>
|
||||
<div class="mt-6">
|
||||
<button @click.prevent="doJoin()" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md
|
||||
shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700
|
||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
<button
|
||||
@click.prevent="doJoin()"
|
||||
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
Join
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="m-5 text-sm text-gray-500">Only signed in users can join a team. Please either Signup or Signin and then return to this page using the join link.</p>
|
||||
<button @click.prevent="navigateTo('/signup')" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md
|
||||
shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700
|
||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
<p class="m-5 text-sm text-gray-500">
|
||||
Only signed in users can join a team. Please either Signup or Signin
|
||||
and then return to this page using the join link.
|
||||
</p>
|
||||
<button
|
||||
@click.prevent="navigateTo('/signup')"
|
||||
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
Sign Up
|
||||
</button>
|
||||
<div class="m-10"></div>
|
||||
<button @click.prevent="navigateTo('/signin')" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md
|
||||
shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700
|
||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
<button
|
||||
@click.prevent="navigateTo('/signin')"
|
||||
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
Sign In
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const { $client } = useNuxtApp();
|
||||
const { data: note } = await $client.notes.getById.useQuery({note_id: +route.params.note_id});
|
||||
const route = useRoute();
|
||||
const { $client } = useNuxtApp();
|
||||
const { data: note } = await $client.notes.getById.useQuery({
|
||||
note_id: +route.params.note_id
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="prose lg:prose-xl m-5">
|
||||
<h3>Note Detail {{ route.params.note_id }}</h3>
|
||||
<div class="prose lg:prose-xl m-5">{{ note?.note.note_text }}</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ACCOUNT_ACCESS } from '~~/prisma/account-access-enum';
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
const accountStore = useAccountStore();
|
||||
const { activeMembership } = storeToRefs(accountStore);
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -10,79 +10,117 @@
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col items-center max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:py-16 lg:px-8">
|
||||
<div
|
||||
class="flex flex-col items-center max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:py-16 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">Flexible Pricing</h2>
|
||||
<p class="text-xl text-gray-500">SupaNuxt SaaS is completely free and open source but you can price your own SaaS like this</p>
|
||||
<h2
|
||||
class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">
|
||||
Flexible Pricing
|
||||
</h2>
|
||||
<p class="text-xl text-gray-500">
|
||||
SupaNuxt SaaS is completely free and open source but you can price your
|
||||
own SaaS like this
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex flex-col md:flex-row justify-center items-center">
|
||||
<!-- Free Plan -->
|
||||
<div class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4 md:flex-1">
|
||||
<div
|
||||
class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4 md:flex-1">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-4">Free Plan</h3>
|
||||
<p class="text-gray-600 mb-4">Single user, 10 notes</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mb-4">$0<span class="text-gray-600 text-lg">/mo</span></p>
|
||||
<button
|
||||
v-if="!activeMembership"
|
||||
@click.prevent="navigateTo('/signup')"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Start for free
|
||||
<p class="text-3xl font-bold text-gray-900 mb-4">
|
||||
$0<span class="text-gray-600 text-lg">/mo</span>
|
||||
</p>
|
||||
<button
|
||||
v-if="!activeMembership"
|
||||
@click.prevent="navigateTo('/signup')"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Start for free
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Personal Plan -->
|
||||
<div class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4 md:flex-1">
|
||||
<div
|
||||
class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4 md:flex-1">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-4">Personal Plan</h3>
|
||||
<p class="text-gray-600 mb-4">Single user, 100 notes</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mb-4">$15<span class="text-gray-600 text-lg">/mo</span></p>
|
||||
|
||||
<p class="text-3xl font-bold text-gray-900 mb-4">
|
||||
$15<span class="text-gray-600 text-lg">/mo</span>
|
||||
</p>
|
||||
|
||||
<!-- logged in user gets a subscribe button-->
|
||||
<form
|
||||
action="/create-checkout-session"
|
||||
<form
|
||||
action="/create-checkout-session"
|
||||
method="POST"
|
||||
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access !== ACCOUNT_ACCESS.ADMIN) && (activeMembership?.account.plan_name !== 'Individual Plan')">
|
||||
<input type="hidden" name="price_id" value="price_1MpOiwJfLn4RhYiLqfy6U8ZR" />
|
||||
<input type="hidden" name="account_id" :value="activeMembership?.account_id" />
|
||||
<button
|
||||
type="submit"
|
||||
v-if="
|
||||
activeMembership &&
|
||||
(activeMembership.access === ACCOUNT_ACCESS.OWNER ||
|
||||
activeMembership.access !== ACCOUNT_ACCESS.ADMIN) &&
|
||||
activeMembership?.account.plan_name !== 'Individual Plan'
|
||||
">
|
||||
<input
|
||||
type="hidden"
|
||||
name="price_id"
|
||||
value="price_1MpOiwJfLn4RhYiLqfy6U8ZR" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="account_id"
|
||||
:value="activeMembership?.account_id" />
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<!-- anon user gets a link to sign up -->
|
||||
<button
|
||||
v-if="!activeMembership"
|
||||
@click.prevent="navigateTo('/signup')"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Get started
|
||||
<button
|
||||
v-if="!activeMembership"
|
||||
@click.prevent="navigateTo('/signup')"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Get started
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Team Plan -->
|
||||
<div class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4 md:flex-1">
|
||||
<div
|
||||
class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4 md:flex-1">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-4">Team Plan</h3>
|
||||
<p class="text-gray-600 mb-4">10 users, 200 notes</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mb-4">$25<span class="text-gray-600 text-lg">/mo</span></p>
|
||||
|
||||
<p class="text-3xl font-bold text-gray-900 mb-4">
|
||||
$25<span class="text-gray-600 text-lg">/mo</span>
|
||||
</p>
|
||||
|
||||
<!-- logged in user gets a subscribe button-->
|
||||
<form
|
||||
action="/create-checkout-session"
|
||||
<form
|
||||
action="/create-checkout-session"
|
||||
method="POST"
|
||||
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access !== ACCOUNT_ACCESS.ADMIN) && (activeMembership?.account.plan_name !== 'Team Plan')">
|
||||
<input type="hidden" name="price_id" value="price_1MpOjtJfLn4RhYiLsjzAso90" />
|
||||
<input type="hidden" name="account_id" :value="activeMembership?.account_id" />
|
||||
<button
|
||||
type="submit"
|
||||
v-if="
|
||||
activeMembership &&
|
||||
(activeMembership.access === ACCOUNT_ACCESS.OWNER ||
|
||||
activeMembership.access !== ACCOUNT_ACCESS.ADMIN) &&
|
||||
activeMembership?.account.plan_name !== 'Team Plan'
|
||||
">
|
||||
<input
|
||||
type="hidden"
|
||||
name="price_id"
|
||||
value="price_1MpOjtJfLn4RhYiLsjzAso90" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="account_id"
|
||||
:value="activeMembership?.account_id" />
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<!-- anon user gets a link to sign up -->
|
||||
<button
|
||||
v-if="!activeMembership"
|
||||
@click.prevent="navigateTo('/signup')"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Get started
|
||||
<button
|
||||
v-if="!activeMembership"
|
||||
@click.prevent="navigateTo('/signup')"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Get started
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,53 +2,83 @@
|
||||
<div class="prose lg:prose-xl m-5">
|
||||
<h1>Privacy Statement</h1>
|
||||
|
||||
<p>Your privacy is important to us. This privacy statement explains what personal data we collect from you and how we
|
||||
use it. By using our website, you agree to the terms of this privacy statement.</p>
|
||||
<p>
|
||||
Your privacy is important to us. This privacy statement explains what
|
||||
personal data we collect from you and how we use it. By using our website,
|
||||
you agree to the terms of this privacy statement.
|
||||
</p>
|
||||
|
||||
<h2>Information we collect</h2>
|
||||
|
||||
<p>We collect personal information that you voluntarily provide to us when you use our website, including your email
|
||||
and full name. We also collect non-personal information about your use of our website, such as your IP address,
|
||||
browser type, and the pages you visit.</p>
|
||||
<p>
|
||||
We collect personal information that you voluntarily provide to us when
|
||||
you use our website, including your email and full name. We also collect
|
||||
non-personal information about your use of our website, such as your IP
|
||||
address, browser type, and the pages you visit.
|
||||
</p>
|
||||
|
||||
<p>In addition to the personal data that we collect directly from you, we also use a third-party authentication
|
||||
provider called Supabase to manage user authentication. When you use our website, Supabase may collect personal data
|
||||
about you, such as your email address and authentication credentials. For more information about Supabase's data
|
||||
practices, please refer to their privacy policy at <a
|
||||
href="https://supabase.com/privacy">https://supabase.com/privacy</a>.</p>
|
||||
<p>
|
||||
In addition to the personal data that we collect directly from you, we
|
||||
also use a third-party authentication provider called Supabase to manage
|
||||
user authentication. When you use our website, Supabase may collect
|
||||
personal data about you, such as your email address and authentication
|
||||
credentials. For more information about Supabase's data practices, please
|
||||
refer to their privacy policy at
|
||||
<a href="https://supabase.com/privacy">https://supabase.com/privacy</a>.
|
||||
</p>
|
||||
|
||||
<h2>How we use your information</h2>
|
||||
|
||||
<p>We use your personal information to provide you with the products and services you request, to communicate with
|
||||
you, and to improve our website. We may also use your information for marketing purposes, but we will always give
|
||||
you the option to opt-out of receiving marketing communications from us.</p>
|
||||
<p>
|
||||
We use your personal information to provide you with the products and
|
||||
services you request, to communicate with you, and to improve our website.
|
||||
We may also use your information for marketing purposes, but we will
|
||||
always give you the option to opt-out of receiving marketing
|
||||
communications from us.
|
||||
</p>
|
||||
|
||||
<h2>Disclosure of your information</h2>
|
||||
|
||||
<p>We may disclose your personal information to third parties who provide services to us, such as website hosting,
|
||||
data analysis, and customer service. We may also disclose your information if we believe it is necessary to comply
|
||||
with the law or to protect our rights or the rights of others.</p>
|
||||
<p>
|
||||
We may disclose your personal information to third parties who provide
|
||||
services to us, such as website hosting, data analysis, and customer
|
||||
service. We may also disclose your information if we believe it is
|
||||
necessary to comply with the law or to protect our rights or the rights of
|
||||
others.
|
||||
</p>
|
||||
|
||||
<p>As mentioned above, we use a third-party authentication provider called Supabase to manage user authentication.
|
||||
Supabase may share your personal data with other third-party service providers that they use to provide their
|
||||
services, such as hosting and cloud storage providers. For more information about Supabase's data sharing practices,
|
||||
please refer to their privacy policy at <a href="https://supabase.com/privacy">https://supabase.com/privacy</a>.</p>
|
||||
<p>
|
||||
As mentioned above, we use a third-party authentication provider called
|
||||
Supabase to manage user authentication. Supabase may share your personal
|
||||
data with other third-party service providers that they use to provide
|
||||
their services, such as hosting and cloud storage providers. For more
|
||||
information about Supabase's data sharing practices, please refer to their
|
||||
privacy policy at
|
||||
<a href="https://supabase.com/privacy">https://supabase.com/privacy</a>.
|
||||
</p>
|
||||
|
||||
<h2>Security of your information</h2>
|
||||
|
||||
<p>We take reasonable measures to protect your personal information from unauthorized access, use, or disclosure.
|
||||
However, no data transmission over the internet or electronic storage is completely secure, so we cannot guarantee
|
||||
the absolute security of your information.</p>
|
||||
<p>
|
||||
We take reasonable measures to protect your personal information from
|
||||
unauthorized access, use, or disclosure. However, no data transmission
|
||||
over the internet or electronic storage is completely secure, so we cannot
|
||||
guarantee the absolute security of your information.
|
||||
</p>
|
||||
|
||||
<h2>Changes to this privacy statement</h2>
|
||||
|
||||
<p>We may update this privacy statement from time to time. Any changes will be posted on this page, so please check
|
||||
back periodically to review the most current version of the statement.</p>
|
||||
<p>
|
||||
We may update this privacy statement from time to time. Any changes will
|
||||
be posted on this page, so please check back periodically to review the
|
||||
most current version of the statement.
|
||||
</p>
|
||||
|
||||
<h2>Contact us</h2>
|
||||
|
||||
<p>If you have any questions or concerns about our privacy practices, please contact us at [insert contact
|
||||
information].</p>
|
||||
|
||||
<p>
|
||||
If you have any questions or concerns about our privacy practices, please
|
||||
contact us at [insert contact information].
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -3,27 +3,27 @@
|
||||
|
||||
const notifyStore = useNotifyStore();
|
||||
|
||||
const loading = ref(false)
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const loading = ref(false);
|
||||
const password = ref('');
|
||||
const confirmPassword = ref('');
|
||||
|
||||
const changePassword = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
const { data, error } = await supabase.auth.updateUser({
|
||||
password: password.value
|
||||
});
|
||||
if (error) throw error
|
||||
if (error) throw error;
|
||||
else {
|
||||
notifyStore.notify("password changed", NotificationType.Success);
|
||||
navigateTo('/signin', {replace: true}); // navigate to signin because it is best practice although the auth session seems to be valid so it immediately redirects to dashboard
|
||||
notifyStore.notify('password changed', NotificationType.Success);
|
||||
navigateTo('/signin', { replace: true }); // navigate to signin because it is best practice although the auth session seems to be valid so it immediately redirects to dashboard
|
||||
}
|
||||
} catch (error) {
|
||||
notifyStore.notify(error, NotificationType.Error);
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center h-screen bg-gray-100">
|
||||
@@ -31,17 +31,35 @@
|
||||
<h1 class="text-3xl font-bold text-center">Forgot Pasword</h1>
|
||||
<form @submit.prevent="changePassword" class="space-y-4">
|
||||
<div>
|
||||
<label for="password" class="block mb-2 font-bold">New Password</label>
|
||||
<input v-model="password" id="password" type="password" class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your new password" required>
|
||||
<label for="password" class="block mb-2 font-bold"
|
||||
>New Password</label
|
||||
>
|
||||
<input
|
||||
v-model="password"
|
||||
id="password"
|
||||
type="password"
|
||||
class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your new password"
|
||||
required />
|
||||
</div>
|
||||
<div>
|
||||
<label for="confirmPassword" class="block mb-2 font-bold">Confirm New Password</label>
|
||||
<input v-model="confirmPassword" id="confirmPassword" type="password" class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Confirm new password" required>
|
||||
<label for="confirmPassword" class="block mb-2 font-bold"
|
||||
>Confirm New Password</label
|
||||
>
|
||||
<input
|
||||
v-model="confirmPassword"
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Confirm new password"
|
||||
required />
|
||||
</div>
|
||||
<button :disabled="loading || password === '' || (confirmPassword !== password)" type="submit"
|
||||
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">Change Password</button>
|
||||
<button
|
||||
:disabled="loading || password === '' || confirmPassword !== password"
|
||||
type="submit"
|
||||
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">
|
||||
Change Password
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,47 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
const user = useSupabaseUser()
|
||||
const user = useSupabaseUser();
|
||||
const supabase = useSupabaseAuthClient();
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
const accountStore = useAccountStore();
|
||||
const notifyStore = useNotifyStore();
|
||||
|
||||
const loading = ref(false)
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
const loading = ref(false);
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
|
||||
const handleStandardSignin = async () => {
|
||||
console.log(`handleStandardSignin email.value:${email.value}, password.value:${password.value}`);
|
||||
console.log(
|
||||
`handleStandardSignin email.value:${email.value}, password.value:${password.value}`
|
||||
);
|
||||
try {
|
||||
loading.value = true
|
||||
const { error } = await supabase.auth.signInWithPassword({ email: email.value, password: password.value })
|
||||
if (error) throw error
|
||||
loading.value = true;
|
||||
const { error } = await supabase.auth.signInWithPassword({
|
||||
email: email.value,
|
||||
password: password.value
|
||||
});
|
||||
if (error) throw error;
|
||||
} catch (error) {
|
||||
notifyStore.notify(error, NotificationType.Error);
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoogleSignin = async () => {
|
||||
console.log('handleGoogleSignin');
|
||||
try {
|
||||
loading.value = true
|
||||
const { error } = await supabase.auth.signInWithOAuth({ provider: 'google' })
|
||||
if (error) throw error
|
||||
loading.value = true;
|
||||
const { error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'google'
|
||||
});
|
||||
if (error) throw error;
|
||||
} catch (error) {
|
||||
notifyStore.notify(error, NotificationType.Error);
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
watchEffect(async () => {
|
||||
if (user.value) {
|
||||
await accountStore.init();
|
||||
navigateTo('/dashboard', {replace: true})
|
||||
navigateTo('/dashboard', { replace: true });
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center h-screen bg-gray-100">
|
||||
@@ -50,20 +56,40 @@
|
||||
<form @submit.prevent="handleStandardSignin" class="space-y-4">
|
||||
<div>
|
||||
<label for="email" class="block mb-2 font-bold">Email</label>
|
||||
<input v-model="email" id="email" type="email" class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your email" required>
|
||||
<input
|
||||
v-model="email"
|
||||
id="email"
|
||||
type="email"
|
||||
class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your email"
|
||||
required />
|
||||
</div>
|
||||
<div>
|
||||
<label for="password" class="block mb-2 font-bold">Password</label>
|
||||
<input v-model="password" id="password" type="password" class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your password" required>
|
||||
<input
|
||||
v-model="password"
|
||||
id="password"
|
||||
type="password"
|
||||
class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your password"
|
||||
required />
|
||||
</div>
|
||||
<NuxtLink id="forgotPasswordLink" to="/forgotpassword" class="text-right block">Forgot your password?</NuxtLink>
|
||||
<button :disabled="loading || password === ''" type="submit"
|
||||
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">Sign in</button>
|
||||
<NuxtLink
|
||||
id="forgotPasswordLink"
|
||||
to="/forgotpassword"
|
||||
class="text-right block"
|
||||
>Forgot your password?</NuxtLink
|
||||
>
|
||||
<button
|
||||
:disabled="loading || password === ''"
|
||||
type="submit"
|
||||
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">
|
||||
Sign in
|
||||
</button>
|
||||
</form>
|
||||
<p class="text-center">or</p>
|
||||
<button @click="handleGoogleSignin()"
|
||||
<button
|
||||
@click="handleGoogleSignin()"
|
||||
class="w-full py-2 text-white bg-red-600 rounded-md hover:bg-red-700">
|
||||
<span class="flex items-center justify-center space-x-2">
|
||||
<Icon name="fa-brands:google" class="w-5 h-5" />
|
||||
|
||||
@@ -1,37 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
const user = useSupabaseUser()
|
||||
const user = useSupabaseUser();
|
||||
const supabase = useSupabaseAuthClient();
|
||||
|
||||
const notifyStore = useNotifyStore();
|
||||
|
||||
const loading = ref(false)
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const signUpOk = ref(false)
|
||||
const loading = ref(false);
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
const confirmPassword = ref('');
|
||||
const signUpOk = ref(false);
|
||||
|
||||
const handleStandardSignup = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data, error } = await supabase.auth.signUp({ email: email.value, password: password.value })
|
||||
loading.value = true;
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email: email.value,
|
||||
password: password.value
|
||||
});
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
else {
|
||||
throw error;
|
||||
} else {
|
||||
signUpOk.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
notifyStore.notify(error, NotificationType.Error);
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (user.value) {
|
||||
navigateTo('/dashboard', { replace: true })
|
||||
navigateTo('/dashboard', { replace: true });
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center h-screen bg-gray-100">
|
||||
@@ -40,26 +42,51 @@
|
||||
<form @submit.prevent="handleStandardSignup" class="space-y-4">
|
||||
<div>
|
||||
<label for="email" class="block mb-2 font-bold">Email</label>
|
||||
<input v-model="email" id="email" type="email" class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your email" required>
|
||||
<input
|
||||
v-model="email"
|
||||
id="email"
|
||||
type="email"
|
||||
class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your email"
|
||||
required />
|
||||
</div>
|
||||
<div>
|
||||
<label for="password" class="block mb-2 font-bold">Password</label>
|
||||
<input v-model="password" id="password" type="password" class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your password" required>
|
||||
<input
|
||||
v-model="password"
|
||||
id="password"
|
||||
type="password"
|
||||
class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Enter your password"
|
||||
required />
|
||||
</div>
|
||||
<div>
|
||||
<label for="confirmPassword" class="block mb-2 font-bold">Confirm Password</label>
|
||||
<input v-model="confirmPassword" id="confirmPassword" type="password"
|
||||
class="w-full p-2 border border-gray-400 rounded-md" placeholder="Confirm your password" required>
|
||||
<label for="confirmPassword" class="block mb-2 font-bold"
|
||||
>Confirm Password</label
|
||||
>
|
||||
<input
|
||||
v-model="confirmPassword"
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
class="w-full p-2 border border-gray-400 rounded-md"
|
||||
placeholder="Confirm your password"
|
||||
required />
|
||||
</div>
|
||||
<button :disabled="loading || password === '' || (confirmPassword !== password)" type="submit"
|
||||
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">Sign up</button>
|
||||
|
||||
<p v-if="signUpOk" class="mt-4 text-lg text-center">You have successfully signed up. Please check your email for a link to confirm your email address and proceed.</p>
|
||||
<button
|
||||
:disabled="loading || password === '' || confirmPassword !== password"
|
||||
type="submit"
|
||||
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">
|
||||
Sign up
|
||||
</button>
|
||||
|
||||
<p v-if="signUpOk" class="mt-4 text-lg text-center">
|
||||
You have successfully signed up. Please check your email for a link to
|
||||
confirm your email address and proceed.
|
||||
</p>
|
||||
</form>
|
||||
<p class="text-center">or</p>
|
||||
<button @click="supabase.auth.signInWithOAuth({ provider: 'google' })"
|
||||
<button
|
||||
@click="supabase.auth.signInWithOAuth({ provider: 'google' })"
|
||||
class="w-full py-2 text-white bg-red-600 rounded-md hover:bg-red-700">
|
||||
<span class="flex items-center justify-center space-x-2">
|
||||
<Icon name="fa-brands:google" class="w-5 h-5" />
|
||||
@@ -67,8 +94,9 @@
|
||||
</span>
|
||||
</button>
|
||||
<p class="mt-4 text-xs text-center text-gray-500">
|
||||
By proceeding, I agree to the <NuxtLink to="/privacy">Privacy Statement</NuxtLink> and <NuxtLink to="/terms">Terms
|
||||
of Service</NuxtLink>
|
||||
By proceeding, I agree to the
|
||||
<NuxtLink to="/privacy">Privacy Statement</NuxtLink> and
|
||||
<NuxtLink to="/terms">Terms of Service</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import Stripe from 'stripe';
|
||||
const config = useRuntimeConfig();
|
||||
const stripe = new Stripe(config.stripeSecretKey, { apiVersion: '2022-11-15' });
|
||||
const route = useRoute();
|
||||
let customer: Stripe.Response<Stripe.Customer | Stripe.DeletedCustomer>
|
||||
try{
|
||||
const session = await stripe.checkout.sessions.retrieve(route?.query?.session_id as string);
|
||||
customer = await stripe.customers.retrieve(session?.customer as string);
|
||||
} catch(e) {
|
||||
console.log(`Error ${e}`)
|
||||
}
|
||||
import Stripe from 'stripe';
|
||||
const config = useRuntimeConfig();
|
||||
const stripe = new Stripe(config.stripeSecretKey, {
|
||||
apiVersion: '2022-11-15'
|
||||
});
|
||||
const route = useRoute();
|
||||
let customer: Stripe.Response<Stripe.Customer | Stripe.DeletedCustomer>;
|
||||
try {
|
||||
const session = await stripe.checkout.sessions.retrieve(
|
||||
route?.query?.session_id as string
|
||||
);
|
||||
customer = await stripe.customers.retrieve(session?.customer as string);
|
||||
} catch (e) {
|
||||
console.log(`Error ${e}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="prose lg:prose-xl m-5">
|
||||
<p>
|
||||
<span v-if="customer && !customer.deleted">We appreciate your business {{customer.name}}!</span>
|
||||
<span v-if="customer && customer.deleted">It appears your stripe customer information has been deleted!</span>
|
||||
<span v-if="customer && !customer.deleted">
|
||||
We appreciate your business {{ customer.name }}!
|
||||
</span>
|
||||
<span v-if="customer && customer.deleted">
|
||||
It appears your stripe customer information has been deleted!
|
||||
</span>
|
||||
</p>
|
||||
<p>Go to Your <NuxtLink to="/dashboard">Dashboard</NuxtLink></p>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -2,57 +2,95 @@
|
||||
<div class="prose lg:prose-xl m-5">
|
||||
<h1>Terms of Service</h1>
|
||||
|
||||
<p>These terms of service (the "Agreement") govern your use of our website and services (collectively, the
|
||||
"Service"). By using the Service, you agree to be bound by the terms of this Agreement. If you do not agree to
|
||||
these terms, you may not use the Service.</p>
|
||||
<p>
|
||||
These terms of service (the "Agreement") govern your use of our website
|
||||
and services (collectively, the "Service"). By using the Service, you
|
||||
agree to be bound by the terms of this Agreement. If you do not agree to
|
||||
these terms, you may not use the Service.
|
||||
</p>
|
||||
|
||||
<h2>Use of the Service</h2>
|
||||
|
||||
<p>You may use the Service only for lawful purposes and in accordance with this Agreement. You agree not to use the
|
||||
Service:</p>
|
||||
<p>
|
||||
You may use the Service only for lawful purposes and in accordance with
|
||||
this Agreement. You agree not to use the Service:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>In any way that violates any applicable federal, state, local, or international law or regulation</li>
|
||||
<li>To impersonate or attempt to impersonate us, our employees, another user, or any other person or entity</li>
|
||||
<li>To engage in any other conduct that restricts or inhibits anyone's use or enjoyment of the Service, or which,
|
||||
as determined by us, may harm us or users of the Service or expose them to liability</li>
|
||||
<li>
|
||||
In any way that violates any applicable federal, state, local, or
|
||||
international law or regulation
|
||||
</li>
|
||||
<li>
|
||||
To impersonate or attempt to impersonate us, our employees, another
|
||||
user, or any other person or entity
|
||||
</li>
|
||||
<li>
|
||||
To engage in any other conduct that restricts or inhibits anyone's use
|
||||
or enjoyment of the Service, or which, as determined by us, may harm us
|
||||
or users of the Service or expose them to liability
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Intellectual Property</h2>
|
||||
|
||||
<p>The Service and its entire contents, features, and functionality (including but not limited to all information,
|
||||
software, text, displays, images, video, and audio, and the design, selection, and arrangement thereof) are owned
|
||||
by us, our licensors, or other providers of such material and are protected by United States and international
|
||||
copyright, trademark, patent, trade secret, and other intellectual property or proprietary rights laws.</p>
|
||||
<p>
|
||||
The Service and its entire contents, features, and functionality
|
||||
(including but not limited to all information, software, text, displays,
|
||||
images, video, and audio, and the design, selection, and arrangement
|
||||
thereof) are owned by us, our licensors, or other providers of such
|
||||
material and are protected by United States and international copyright,
|
||||
trademark, patent, trade secret, and other intellectual property or
|
||||
proprietary rights laws.
|
||||
</p>
|
||||
|
||||
<p>You may not reproduce, distribute, modify, create derivative works of, publicly display, publicly perform,
|
||||
republish, download, store, or transmit any of the material on our website, except as follows:</p>
|
||||
<p>
|
||||
You may not reproduce, distribute, modify, create derivative works of,
|
||||
publicly display, publicly perform, republish, download, store, or
|
||||
transmit any of the material on our website, except as follows:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Your computer may temporarily store copies of such materials in RAM incidental to your accessing and viewing
|
||||
those materials</li>
|
||||
<li>You may store files that are automatically cached by your Web browser for display enhancement purposes</li>
|
||||
<li>You may print or download one copy of a reasonable number of pages of the website for your own personal,
|
||||
non-commercial use and not for further reproduction, publication, or distribution</li>
|
||||
<li>
|
||||
Your computer may temporarily store copies of such materials in RAM
|
||||
incidental to your accessing and viewing those materials
|
||||
</li>
|
||||
<li>
|
||||
You may store files that are automatically cached by your Web browser
|
||||
for display enhancement purposes
|
||||
</li>
|
||||
<li>
|
||||
You may print or download one copy of a reasonable number of pages of
|
||||
the website for your own personal, non-commercial use and not for
|
||||
further reproduction, publication, or distribution
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Disclaimer of Warranties</h2>
|
||||
|
||||
<p>The Service is provided on an "as is" and "as available" basis, without any warranties of any kind, either
|
||||
express or implied, including but not limited to warranties of merchantability, fitness for a particular purpose,
|
||||
or non-infringement. We make no warranty that the Service will meet your requirements, be available on an
|
||||
uninterrupted, secure, or error-free basis, or be free from viruses or other harmful components.</p>
|
||||
<p>
|
||||
The Service is provided on an "as is" and "as available" basis, without
|
||||
any warranties of any kind, either express or implied, including but not
|
||||
limited to warranties of merchantability, fitness for a particular
|
||||
purpose, or non-infringement. We make no warranty that the Service will
|
||||
meet your requirements, be available on an uninterrupted, secure, or
|
||||
error-free basis, or be free from viruses or other harmful components.
|
||||
</p>
|
||||
|
||||
<h2>Limitation of Liability</h2>
|
||||
|
||||
<p>In no event shall we be liable for any direct, indirect, incidental, special, or consequential damages arising
|
||||
out of or in any way connected with the use of the Service, whether based on contract, tort, strict liability, or
|
||||
any other theory of liability.</p>
|
||||
<p>
|
||||
In no event shall we be liable for any direct, indirect, incidental,
|
||||
special, or consequential damages arising out of or in any way connected
|
||||
with the use of the Service, whether based on contract, tort, strict
|
||||
liability, or any other theory of liability.
|
||||
</p>
|
||||
|
||||
<h2>Indemnification</h2>
|
||||
|
||||
<p>You agree to defend, indemnify, and hold us harmless from and against any claims, liabilities, damages,
|
||||
judgments, fines, costs, and expenses.</p>
|
||||
|
||||
<p>
|
||||
You agree to defend, indemnify, and hold us harmless from and against any
|
||||
claims, liabilities, damages, judgments, fines, costs, and expenses.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user