Add rewrite

This commit is contained in:
2025-05-22 13:48:15 +03:00
parent 71d360ea1f
commit 16a4ea0949
19 changed files with 746 additions and 61 deletions

View File

@@ -1,8 +1,6 @@
<template>
<NuxtRouteAnnouncer />
<UApp :toaster="{ position: 'bottom-center' }">
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</UApp>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>

View File

@@ -1,5 +1,27 @@
<script setup lang="ts">
interface Props {
vertical?: boolean;
}
const props = defineProps<Props>();
</script>
<template>
<div class="flex justify-between items-center gap-4">
<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">

View 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>

View File

@@ -3,41 +3,39 @@ const error = useError();
</script>
<template>
<UApp>
<UContainer class="max-w-lg p-8 md:p-16">
<UCard>
<div class="grid gap-4">
<div class="text-center font-bold text-4xl md:text-6xl">
{{ error?.statusCode }}
</div>
<NuxtImg
class="rounded"
src="https://plus.unsplash.com/premium_photo-1738036169480-086c0030803c?q=80&w=512&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
/>
<UContainer class="max-w-lg p-8 md:p-16">
<UCard>
<div class="grid gap-4">
<div class="text-center font-bold text-4xl md:text-6xl">
{{ error?.statusCode }}
</div>
<NuxtImg
class="rounded"
src="https://plus.unsplash.com/premium_photo-1738036169480-086c0030803c?q=80&w=512&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
/>
<div class="text-center text-lg">
Uh oh... Something went super wrong.
</div>
<div
class="text-center font-mono p-4 bg-neutral-100 text-sm dark:bg-neutral-800 max-h-32 overflow-y-auto wrap-normal relative"
>
{{ error?.message }}
</div>
<div class="text-center text-lg">
Uh oh... Something went super wrong.
</div>
<template #footer>
<div class="w-full flex justify-center">
<UButton
label="Navigate back home"
to="/"
icon="heroicons:arrow-left"
/>
</div>
</template>
</UCard>
</UContainer>
<div
class="text-center font-mono p-4 bg-neutral-100 text-sm dark:bg-neutral-800 max-h-32 overflow-y-auto wrap-normal relative"
>
{{ error?.message }}
</div>
</div>
<AppFooter />
</UApp>
<template #footer>
<div class="w-full flex justify-center">
<UButton
label="Navigate back home"
to="/"
icon="heroicons:arrow-left"
/>
</div>
</template>
</UCard>
</UContainer>
<AppFooter />
</template>

View File

@@ -1,5 +1,5 @@
<template>
<UApp>
<UApp :toaster="{ position: 'bottom-center' }">
<AuthState v-slot="{ loggedIn }">
<Register v-if="!loggedIn" />
<template v-if="loggedIn">

View File

@@ -110,6 +110,8 @@ const saveOp = watchPausable(
</UCard>
</UContainer>
<RewriteCard />
<UContainer class="max-w-2xl py-8 flex flex-col gap-4">
<div class="font-bold text-lg">🪪 Your connected Strava account</div>
<UCard class="bg-neutral-50 dark:bg-slate-800">