Amend app layout
This commit is contained in:
22
components/app-bar.vue
Normal file
22
components/app-bar.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
const { user, clear } = useUserSession();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-16 border-b-4 border-b-orange-500 flex">
|
||||
<UContainer class="max-w-2xl flex justify-between items-center">
|
||||
<div class="flex gap-2 items-center">
|
||||
<NuxtImg src="/ghostwriter-logo.png" class="size-9" />
|
||||
<div class="font-bold text-xl tracking-tight font-fira-code">
|
||||
Ghostwriter
|
||||
</div>
|
||||
</div>
|
||||
<UDropdownMenu :items="[{ label: 'Log out', onSelect: () => clear() }]">
|
||||
<UAvatar
|
||||
:src="user!.avatar"
|
||||
class="border border-gray-200 cursor-pointer"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</UContainer>
|
||||
</div>
|
||||
</template>
|
||||
13
components/app-footer.vue
Normal file
13
components/app-footer.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<UContainer
|
||||
class="max-w-2xl py-8 text-sm text-slate-600 place-items-center grid gap-4"
|
||||
>
|
||||
<div class="text-center">
|
||||
Built with 💪 in Athens, Greece 🇬🇷 by Marios Antonoudiou.
|
||||
<ULink class="underline" href="mailto:mariosant@sent.com"
|
||||
>Send feedback</ULink
|
||||
>.
|
||||
</div>
|
||||
<NuxtImg src="/images/powered-by-strava.svg" width="80px" />
|
||||
</UContainer>
|
||||
</template>
|
||||
35
components/card-field.vue
Normal file
35
components/card-field.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
vertical?: boolean;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="props.vertical"
|
||||
class="flex justify-between items-start gap-2 flex-col"
|
||||
>
|
||||
<div>
|
||||
<div class="font-semibold"><slot name="title" /></div>
|
||||
<div class="text-slate-600 text-sm hidden md:block">
|
||||
<slot name="description" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-nowrap w-full">
|
||||
<slot name="value" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex justify-between items-center gap-4">
|
||||
<div>
|
||||
<div class="font-semibold"><slot name="title" /></div>
|
||||
<div class="text-slate-600 text-sm hidden md:block">
|
||||
<slot name="description" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-nowrap flex items-end">
|
||||
<slot name="value" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
52
components/register.vue
Normal file
52
components/register.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import AppFooter from "./app-footer.vue";
|
||||
|
||||
useHead({ title: "Ghostwriter - Please sign in" });
|
||||
|
||||
const { openInPopup } = useUserSession();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContainer
|
||||
class="flex flex-col items-center gap-4 justify-center w-full p-16"
|
||||
>
|
||||
<UCard class="max-w-sm grid gap-6 justify-center items-center">
|
||||
<div class="flex flex-col gap-10 items-center justify-center">
|
||||
<div class="flex gap-2 items-center">
|
||||
<NuxtImg src="/ghostwriter-logo.png" class="size-9" />
|
||||
<div class="font-bold text-xl tracking-tight font-fira-code">
|
||||
Ghostwriter
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="text-center text-2xl font-bold">
|
||||
Sign in to your account.
|
||||
</div>
|
||||
<div class="text-center">
|
||||
Connect with Strava to automatically add personalized titles and
|
||||
descriptions to your activities.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-role="button"
|
||||
@click="openInPopup('/auth/strava')"
|
||||
class="cursor-pointer w-full max-w-[200px]"
|
||||
>
|
||||
<NuxtImg
|
||||
src="/images/connect-with-strava.svg"
|
||||
class="w-full max-w-[200px]"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
By signing in, you agree to our
|
||||
<NuxtLink
|
||||
href="https://www.ghostwriter.rocks/privacy"
|
||||
class="text-primary-500"
|
||||
>Privacy Policy</NuxtLink
|
||||
>.
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</UContainer>
|
||||
<AppFooter />
|
||||
</template>
|
||||
122
components/rewrite-card.vue
Normal file
122
components/rewrite-card.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormSubmitEvent } from "@nuxt/ui";
|
||||
|
||||
const { user } = useUserSession();
|
||||
const toast = useToast();
|
||||
|
||||
const form = useTemplateRef("form");
|
||||
const submitting = ref(false);
|
||||
|
||||
const formData = {
|
||||
activityUrl: "",
|
||||
};
|
||||
|
||||
const validate = ({
|
||||
activityUrl,
|
||||
}: Partial<{
|
||||
activityUrl: string;
|
||||
}>) => {
|
||||
if (
|
||||
[
|
||||
"https://strava.com/activities/",
|
||||
"https://www.strava.com/activities/",
|
||||
].some((u) => activityUrl!.includes(u))
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
name: "activityUrl",
|
||||
message: "Please write a legit Strava activity URL.",
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const submit = async (event: FormSubmitEvent<typeof formData>) => {
|
||||
await $fetch("/api/rewrite", {
|
||||
method: "POST",
|
||||
query: {
|
||||
activity: event.data.activityUrl,
|
||||
},
|
||||
})
|
||||
.then(() =>
|
||||
toast.add({
|
||||
title: "Success",
|
||||
description: "Activity has been rewritten.",
|
||||
color: "success",
|
||||
}),
|
||||
)
|
||||
.catch(() =>
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: "Something wrong has happened. Maybe try again later.",
|
||||
color: "error",
|
||||
}),
|
||||
)
|
||||
.finally(() => {
|
||||
formData.activityUrl = "";
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:disabled="!user.premium"
|
||||
ref="form"
|
||||
@submit="submit"
|
||||
:validate="validate"
|
||||
:state="formData"
|
||||
class="flex flex-col gap-8"
|
||||
>
|
||||
<template v-slot:default="errors">
|
||||
<UContainer class="max-w-2xl py-8 flex flex-col gap-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="font-bold text-lg">🔄 Re-write activity</div>
|
||||
<UTooltip
|
||||
arrow
|
||||
:disabled="user.premium"
|
||||
text="This feature is enabled for premium users. You can upgrade to
|
||||
premium by supporting Ghostwriter."
|
||||
>
|
||||
<UBadge variant="soft">Premium only</UBadge>
|
||||
</UTooltip>
|
||||
</div>
|
||||
<UCard>
|
||||
<div class="grid gap-4">
|
||||
<CardField vertical>
|
||||
<template #title> Activity URL </template>
|
||||
<template #description>
|
||||
Paste your Strava activity URL to rewrite it.
|
||||
</template>
|
||||
<template #value>
|
||||
<div class="grid gap-2">
|
||||
<UInput
|
||||
:disabled="submitting"
|
||||
v-model="formData.activityUrl"
|
||||
class="w-full"
|
||||
placeholder="https://www.strava.com/activities/12345678901"
|
||||
/>
|
||||
<div class="text-error-500 text-sm">
|
||||
{{ errors?.errors[0]?.message }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</CardField>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<UButton
|
||||
:disabled="!user.premium"
|
||||
loading-auto
|
||||
label="Rewrite"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
@click="form!.submit()"
|
||||
/>
|
||||
</template>
|
||||
</UCard>
|
||||
</UContainer>
|
||||
</template>
|
||||
</UForm>
|
||||
</template>
|
||||
Reference in New Issue
Block a user