social media linking rough
This commit is contained in:
@@ -63,7 +63,23 @@ public class RecipeController : ControllerBase
|
|||||||
_context.Recipes.Add(recipe);
|
_context.Recipes.Add(recipe);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
return Ok(new { message = "Recipe saved to your collection!" });
|
return Ok(new {id = recipe.Id, message = "Recipe saved to your collection!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<Recipe>> GetRecipe(int id)
|
||||||
|
{
|
||||||
|
var recipe = await _context.Recipes
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(r => r.Id == id);
|
||||||
|
|
||||||
|
if (recipe == null)
|
||||||
|
{
|
||||||
|
return NotFound("That recipe seems to have vanished from the archives.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(recipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("update/{id}")]
|
[HttpPut("update/{id}")]
|
||||||
@@ -123,7 +139,7 @@ public class RecipeController : ControllerBase
|
|||||||
public async Task<ActionResult<IEnumerable<Recipe>>> SearchRecipes([FromQuery] string query)
|
public async Task<ActionResult<IEnumerable<Recipe>>> SearchRecipes([FromQuery] string query)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"--> Search hit! Query: {query}");
|
Console.WriteLine($"--> Search hit! Query: {query}");
|
||||||
|
|
||||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(query))
|
if (string.IsNullOrWhiteSpace(query))
|
||||||
|
|||||||
@@ -287,6 +287,25 @@ html, body {
|
|||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.public-cta-container {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px dashed #dccca7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-title {
|
||||||
|
font-family: 'Libre Baskerville', serif !important;
|
||||||
|
color: #2e1e0a;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-text {
|
||||||
|
font-family: 'Libre Baskerville', serif !important;
|
||||||
|
color: #5d4a36;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
@page {
|
@page {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
@@ -308,7 +327,7 @@ html, body {
|
|||||||
.chat-container, .v-app-bar, .no-print, .separator, .v-divider,
|
.chat-container, .v-app-bar, .no-print, .separator, .v-divider,
|
||||||
.recipe-description, button, .v-btn, .drop-zone, .v-card-actions,
|
.recipe-description, button, .v-btn, .drop-zone, .v-card-actions,
|
||||||
.v-btn--variant-text, .v-btn--variant-elevated,
|
.v-btn--variant-text, .v-btn--variant-elevated,
|
||||||
footer, .v-footer, .recipe-actions-row, .share-btn {
|
footer, .v-footer, .recipe-actions-row, .share-btn, .public-cta-container {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
visibility: hidden !important;
|
visibility: hidden !important;
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
<v-row justify="center" class="mt-12 pb-10">
|
<v-row justify="center" class="mt-12 pb-10">
|
||||||
<v-btn
|
<v-btn
|
||||||
class="print-btn px-12"
|
class="px=8 print-btn"
|
||||||
size="large"
|
size="large"
|
||||||
elevation="0"
|
elevation="0"
|
||||||
@click="printRecipe"
|
@click="printRecipe"
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
class="px-12 transition-swing"
|
class="px-8 transition-swing"
|
||||||
size="large"
|
size="large"
|
||||||
elevation="0"
|
elevation="0"
|
||||||
:loading="isSaving"
|
:loading="isSaving"
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
class="px-12 transition-swing"
|
class="px-8 transition-swing"
|
||||||
size="large"
|
size="large"
|
||||||
elevation="0"
|
elevation="0"
|
||||||
:color="hasShared ? '#556b2f' : '#5d4a36'"
|
:color="hasShared ? '#556b2f' : '#5d4a36'"
|
||||||
@@ -90,6 +90,24 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<v-fade-transition>
|
||||||
|
<div v-if="isPublicView" class="public-cta-container mt-16 text-center pa-8">
|
||||||
|
<v-divider class="mb-8 separator"></v-divider>
|
||||||
|
<h3 class="cta-title mb-4">Enjoyed this recipe?</h3>
|
||||||
|
<p class="cta-text mb-6">
|
||||||
|
Join <strong>Seasoned</strong> to scan your own family recipes,
|
||||||
|
consult with the Chef, and build your digital archive.
|
||||||
|
</p>
|
||||||
|
<v-btn
|
||||||
|
to="/login"
|
||||||
|
class="auth-btn px-10"
|
||||||
|
size="large"
|
||||||
|
elevation="4"
|
||||||
|
>
|
||||||
|
Start Your Collection
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-fade-transition>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
@@ -112,10 +130,14 @@ const printRecipe = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shareRecipe = async () => {
|
const shareRecipe = async () => {
|
||||||
|
const shareUrl = props.recipe.id
|
||||||
|
? `${window.location.origin}/recipe/${props.recipe.id}`
|
||||||
|
: window.location.href;
|
||||||
|
|
||||||
const shareData = {
|
const shareData = {
|
||||||
title: props.recipe.title,
|
title: props.recipe.title,
|
||||||
text: `Check out this delicious ${props.recipe.title} recipe!`,
|
text: `Check out this delicious ${props.recipe.title} recipe on Seasoned!`,
|
||||||
url: window.location.href
|
url: shareUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -123,7 +145,7 @@ const shareRecipe = async () => {
|
|||||||
await navigator.share(shareData)
|
await navigator.share(shareData)
|
||||||
triggerShareSuccess()
|
triggerShareSuccess()
|
||||||
} else {
|
} else {
|
||||||
await navigator.clipboard.writeText(window.location.href)
|
await navigator.clipboard.writeText(shareUrl)
|
||||||
triggerShareSuccess()
|
triggerShareSuccess()
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
31
Seasoned.Frontend/app/pages/recipe/[id].vue
Normal file
31
Seasoned.Frontend/app/pages/recipe/[id].vue
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<div class="recipe-bg min-h-screen">
|
||||||
|
<v-container>
|
||||||
|
<RecipeDisplay v-if="recipe" :recipe="recipe" :is-public-view="true" />
|
||||||
|
<v-progress-linear v-else indeterminate color="#8c4a32" />
|
||||||
|
</v-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const route = useRoute();
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
|
||||||
|
const { data: recipe, error } = await useAsyncData(`recipe-${route.params.id}`, () =>
|
||||||
|
$fetch(`${config.public.apiBase}api/recipe/${route.params.id}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (error.value || !recipe.value) {
|
||||||
|
throw createError({ statusCode: 404, statusMessage: 'Recipe not found' })
|
||||||
|
}
|
||||||
|
|
||||||
|
useSeoMeta({
|
||||||
|
title: `${recipe.value.title} | Seasoned`,
|
||||||
|
ogTitle: `Chef's Choice: ${recipe.value.title}`,
|
||||||
|
description: `Check out this delicious recipe for ${recipe.value.title} on Seasoned.`,
|
||||||
|
ogDescription: `A hand-crafted parchment recipe for ${recipe.value.title}.`,
|
||||||
|
ogImage: recipe.value.imageUrl || '/images/seasoned-logo.png',
|
||||||
|
twitterCard: 'summary_large_image',
|
||||||
|
ogType: 'article',
|
||||||
|
})
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user