Ultimos cambios

parent 451d56f2
import RecipeDetail from '@/views/RecipeDetail.vue';
import api from './api';
export const recipeService = {
......@@ -8,19 +7,43 @@ export const recipeService = {
if (size != null) params.append('size', size);
if (sortDirection != null) params.append('sortDirection', sortDirection);
const response = await api.get(`/recipe?${params.toString()}`);
const response = await api.get(`/recipe?${params.toString()}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
madeFavorite: async (id) => {
const response = await api.patch(`/recipe/${id}/favorite`);
const response = await api.patch(`/recipe/${id}/favorite`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
removeFavorite: async (id) => {
const response = await api.delete(`/recipe/${id}/favorite`);
const response = await api.delete(`/recipe/${id}/favorite`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
readDetail: async (id) => {
const response = await api.get(`/recipe/${id}`);
const response = await api.get(`/recipe/${id}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
create: async (recipeData) => {
......@@ -28,7 +51,13 @@ export const recipeService = {
return response.data;
},
delete: async (id) => {
const response = await api.delete(`/recipe/${id}`);
const response = await api.delete(`/recipe/${id}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
update: async (recipeId, newData) => {
......@@ -41,7 +70,13 @@ export const recipeService = {
if (page) params.append('page', page);
if (size) params.append('size', size);
const response = await api.get(`/recipe/search?${params.toString()}`)
const response = await api.get(`/recipe/search?${params.toString()}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
readFavorites: async (page, size, sortDirection) => {
......@@ -50,7 +85,49 @@ export const recipeService = {
if (size != null) params.append('size', size);
if (sortDirection != null) params.append('sortDirection', sortDirection);
const response = await api.get(`/recipe/favorites?${params.toString()}`);
const response = await api.get(`/recipe/favorites?${params.toString()}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
searchAI: async (query) => {
const response = await api.post(`/recipe/search?ingredients=${query}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
searchMoreAI: async (sessionId) => {
const response = await api.post(
'/recipe/search/more',
{},
{
headers: {
"Content-Type": "application/json",
"X-Session-ID": sessionId
}
}
)
return response.data;
},
getDetailAI: async (sessionId, index) => {
const response = await api.post(
`/recipe/detail/${index}`,
{},
{
headers: {
"Content-Type": "application/json",
"X-Session-ID": sessionId
}
}
)
return response.data;
}
}
\ No newline at end of file
......@@ -2,7 +2,13 @@ import api from './api';
export const userService = {
readUser: async () => {
const response = await api.get('/user/me');
const response = await api.get('/user/me', {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
readAll: async (page, size, sortBy, sortDirection) => {
......@@ -16,31 +22,73 @@ export const userService = {
}
// Petición al endpoint con los parámetros
const response = await api.get(`/user?${params.toString()}`);
const response = await api.get(`/user?${params.toString()}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
deactivate: async (id) => {
const response = await api.patch(`/user/${id}/deactivate`);
const response = await api.patch(`/user/${id}/deactivate`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
activate: async (id) => {
const response = await api.patch(`/user/${id}/activate`);
const response = await api.patch(`/user/${id}/activate`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
deactivateMe: async () => {
const response = await api.patch(`/user/deactivate`);
const response = await api.patch(`/user/deactivate`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
update: async (updatedUser) => {
const response = await api.put('/user', updatedUser);
const response = await api.put('/user', updatedUser,
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
changePassword: async (passwords) => {
const response = await api.patch(`user/password`, passwords);
const response = await api.patch(`user/password`, passwords,
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
changeRole: async (id, role) => {
const response = await api.patch(`/user/${id}/role`, role);
const response = await api.patch(`/user/${id}/role`, role,
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
search: async (query, page, size) => {
......@@ -49,7 +97,13 @@ export const userService = {
if (page) params.append('page', page);
if (size) params.append('size', size);
const response = await api.get(`/user/search?${params.toString()}`)
const response = await api.get(`/user/search?${params.toString()}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
}
}
\ No newline at end of file
......@@ -9,7 +9,13 @@ export const useRecipeStore = defineStore('recipe', {
pageSize: 10,
totalElements: 0,
totalPages: 0,
sortDirection: 'desc'
sortDirection: 'desc',
// Estados búsqueda por IA
aiRecipes: [],
aiSessionId: null,
loading: false,
aiRecipe: null,
loadingDetail: false
}),
actions: {
async readAll(page, size, sortDirection) {
......@@ -111,6 +117,63 @@ export const useRecipeStore = defineStore('recipe', {
this.totalPages = 0;
throw error;
}
},
async searchAI(query) {
this.loading = true;
// Limpiamos los resultados de la búsqueda anterior
this.aiRecipes = [];
this.aiSessionId = null;
try {
const { recipes, sessionId } = await recipeService.searchAI(query);
if (recipes) {
this.aiRecipes = recipes;
this.aiSessionId = sessionId;
} else {
this.aiRecipes = [];
this.aiSessionId = null;
}
} catch (error) {
console.error('Error al hacer la búsqueda por IA:', error);
this.aiRecipes = [];
this.aiSessionId = null;
throw error;
} finally {
this.loading = false;
}
},
async searchMoreAI(sessionId) {
this.loading = true;
try {
const response = await recipeService.searchMoreAI(sessionId);
if (!response || response.length === 0) {
return;
}
this.aiRecipes.push(...response);
} catch (error) {
console.error('Error al cargar más recetas', error);
throw error;
} finally {
this.loading = false;
}
},
async getDetailAI(sessionId, index) {
this.loadingDetail = true;
this.aiRecipe = null;
try {
const response = await recipeService.getDetailAI(sessionId, index);
this.aiRecipe = response;
} catch (error) {
console.error('Error al obtener el detalle de la receta:', error);
this.aiRecipe = null;
throw error;
} finally {
this.loadingDetail = false;
}
}
}
});
\ No newline at end of file
......@@ -9,7 +9,7 @@
<section class="row justify-content-center mb-5">
<div class="col-md-8">
<form @submit.prevent="searchRecipes" class="d-flex search-box-custom">
<form @submit.prevent="handleAISearch" class="d-flex search-box-custom">
<input
type="text"
class="form-control me-2"
......@@ -24,37 +24,224 @@
</div>
</section>
<section v-if="results.length" class="recipes-container">
<div class="row mb-4" v-for="recipe in results" :key="recipe.id">
<!-- Estado de carga de la IA -->
<section v-if="loading && aiRecipes.length === 0" class="text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Cargando...</span>
</div>
<p>Buscando recetas...</p>
</section>
<!-- Mostrar las recetas -->
<section v-if="aiRecipes.length" class="recipes-container">
<div class="row mb-4" v-for="(recipe, index) in aiRecipes" :key="recipe.id">
<div class="card h-100 recipe-card-custom">
<div class="card-body">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title">{{ recipe.name }}</h5>
<p class="card-text">{{ recipe.description }}</p>
<div class="d-flex justify-content-end">
<button @click="openModal(recipe)" class="btn btn-primary-custom mt-auto">Ver más</button>
</div>
<button @click="openModal(index)" class="btn btn-primary-custom mt-auto">Ver más</button>
</div>
</div>
</div>
</section>
<section v-if="loading && aiRecipes.length" class="text-center mt-4">
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Cargando...</span>
</div>
<p>Cargando más recetas...</p>
</section>
<div class="text-center mt-4">
<button @click="loadMore" class="btn btn-secondary-custom">
<section v-if="!loading && aiSessionId" class="text-center mt-4">
<button @click="handleLoadMore" class="btn btn-secondary-custom">
Ver más recetas
</button>
</section>
<section v-else-if="errorMsg" class="text-center">
<div class="alert alert-danger">
{{ errorMsg }}
</div>
</section>
<!-- No devuelve resultados -->
<section v-else-if="!loading && searchCompleted" class="text-center">
<div class="alert alert-info">
No se encontraron recetas para tu búsqueda.
</div>
</section>
<!-- Modal para el detalle de la receta seleccionada -->
<div class="modal fade" id="recipeModal" tabindex="-1" aria-labelledby="recipeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content modal-custom">
<div class="modal-content modal-custom" v-if="!loadingDetail && aiRecipe">
<div class="modal-header">
<h3 class="modal-title fw-bold w-100 text-center" id="recipeModalLabel">{{ aiRecipe.name }}</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div>
<!-- Descripción -->
<div class="text-center w-75 mx-auto mb-3">
<p class="recipe-description">{{ aiRecipe.description }}</p>
</div>
<!-- Ingredientes -->
<h5 class="fw-bold mt-4">Ingredientes</h5>
<ul v-if="aiRecipe.ingredients && aiRecipe.ingredients.length" class="ingredients-list">
<li v-for="(ingredient, i) in aiRecipe.ingredients" :key="i" class="ingredient-item">
<span class="ingredient-name">{{ ingredient.name }}</span>
<span class="ingredient-amount">
{{ ingredient.quantity }} {{ ingredient.unitOfMeasure }}
</span>
</li>
</ul>
<p v-else>No hay ingredientes especificados.</p>
<!-- Pasos -->
<h5 class="fw-bold mt-4">Pasos de preparación</h5>
<ol v-if="aiRecipe.steps && aiRecipe.steps.length" class="steps-list">
<li v-for="(step, i) in aiRecipe.steps" :key="i" class="step-item">
<span class="step-number">{{ i + 1 }}</span>
<span class="step-text">{{ step.description }}</span>
</li>
</ol>
<p v-else>No hay pasos especificados.</p>
</div>
</div>
<div class="modal-footer d-flex justify-content-center">
<button @click="importRecipe" class="btn btn-import">
<i class="bi bi-save"></i> Importar Receta
</button>
</div>
</div>
<div v-else-if="loadingDetail" class="modal-content modal-custom">
<div class="modal-body text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Cargando...</span>
</div>
<p>Cargando detalles de la receta...</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useRecipeStore } from '@/stores/recipeStore';
import { Modal } from 'bootstrap';
import * as bootstrap from 'bootstrap';
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
const recipeStore = useRecipeStore();
const router = useRouter();
const query = ref('');
const searchCompleted = ref(false);
// Propiedades computadas para acceder al estado del store
const aiRecipes = computed(() => recipeStore.aiRecipes);
const aiSessionId = computed(() => recipeStore.aiSessionId);
const loading = computed(() => recipeStore.loading);
const aiRecipe = computed(() => recipeStore.aiRecipe);
const loadingDetail = computed(() => recipeStore.loadingDetail);
const errorMsg = ref('');
// Función para hacer la primera búsqueda
async function handleAISearch() {
if(!query.value.trim()) {
return;
}
searchCompleted.value = true;
try {
await recipeStore.searchAI(query.value);
} catch (error) {
console.error('Error al hacer la búsqueda por IA:', error);
if (error.response.status == 429 || error.response.status == 500) {
errorMsg.value = 'Hemos alcanzado temporalmente nuestro límite de uso de la IA. Vuelve a intentarlo en otro momento.'
}
}
};
// Función para cargar más recetas (mismo query)
async function handleLoadMore() {
try {
await recipeStore.searchMoreAI(aiSessionId.value);
} catch (error) {
console.error('Error al cargar más recetas:', error);
}
}
// Función para mostrar el modal con la receta seleccionada
async function openModal(index) {
recipeStore.aiRecipe = null;
// Se muestra el modal
const modalElement = document.getElementById('recipeModal');
const bsModal = new bootstrap.Modal(modalElement);
bsModal.show();
try {
await recipeStore.getDetailAI(aiSessionId.value, index);
} catch (error) {
console.error('Error al obtener el detalle de la receta:', error);
bsModal.hide();
}
};
// Función para importar la receta
async function importRecipe() {
try {
const aiRecipeData = aiRecipe.value;
if (!aiRecipeData) {
console.error('No hay receta para importar.');
return;
}
// Formato para subir archivos
const formData = new FormData();
formData.append('name', aiRecipeData.name);
formData.append('description', aiRecipeData.description);
aiRecipeData.ingredients.forEach((ingredient, index) => {
formData.append(`ingredients[${index}].name`, ingredient.name);
formData.append(`ingredients[${index}].quantity`, ingredient.quantity);
formData.append(`ingredients[${index}].unitOfMeasure`, ingredient.unitOfMeasure);
});
aiRecipeData.steps.forEach((step, index) => {
formData.append(`steps[${index}].number`, step.number);
formData.append(`steps[${index}].description`, step.description);
});
const newRecipeId = await recipeStore.create(formData);
const modalElement = document.getElementById('recipeModal');
const bsModal = Modal.getInstance(modalElement);
if (bsModal) bsModal.hide();
router.push(`/recipes/detail/${newRecipeId}`);
} catch (error) {
console.error('Error al importar la receta:', error);
alert('Ocurrió un error al importar la receta. Inténtalo de nuevo.');
}
};
</script>
<style scoped>
.text-center-custom {
.text-center-custom, .modal-title {
color: #2C0C21;
}
......@@ -63,13 +250,13 @@
box-shadow: 0 0 0 0.25rem rgba(121, 62, 108, 0.25);
}
.btn-search-ai {
.btn-search-ai, .btn-import {
background-color: #793E6C;
color: #fff;
font-weight: 600;
}
.btn-search-ai:hover {
.btn-search-ai:hover, .btn-import:hover {
background-color: #5e3054;
color: #fff;
}
......@@ -118,58 +305,73 @@
background-color: #f1e0ee;
border-color: #d1c8cd;
}
</style>
<script>
import { Modal } from 'bootstrap';
/* Estilo ingredientes */
.modal-body h5 {
background: #f5f5f5;
padding: 6px 12px;
border-radius: 6px;
margin-bottom: 12px;
}
export default {
data() {
return {
query: '',
results: [],
page: 0,
selectedRecipe: null
};
},
methods: {
async searchRecipes() {
this.page = 0;
this.results = [];
await this.fetchRecipes();
},
async loadMore() {
this.page++;
await this.fetchRecipes();
},
async fetchRecipes() {
// Simula la llamada a la API
const newRecipes = this.simulateApiCall(this.query, this.page);
this.results.push(...newRecipes);
},
simulateApiCall(query, page) {
// Esta es una función de ejemplo que simula los resultados
console.log(`Buscando "${query}" - Página ${page}`);
const baseId = page * 4;
return [
{ id: baseId + 1,
name: 'Receta de Ejemplo 1',
description: 'Auto-layout for flexbox grid columns also means you can set the width of one column and have the sibling columns automatically resize around it. You may use predefined grid classes.'},
{ id: baseId + 2,
name: 'Receta de Ejemplo 2',
description: 'Una descripción corta de la receta...'},
{ id: baseId + 3,
name: 'Receta de Ejemplo 3',
description: 'Una descripción corta de la receta...'}
];
},
openModal(recipe) {
this.selectedRecipe = recipe;
// Aquí puedes cargar el contenido completo de la receta, ya sea desde la API o si ya lo tienes
const modalElement = document.getElementById('recipeModal');
const bsModal = new Modal(modalElement);
bsModal.show();
}
}
};
</script>
\ No newline at end of file
.ingredients-list {
list-style: none;
padding: 0;
margin: 0;
}
.ingredient-item {
display: flex;
justify-content: space-between;
padding: 6px 10px;
border-bottom: 1px solid #eee;
max-width: 60%;
}
.ingredient-name {
font-weight: 500;
}
.ingredient-amount {
color: #555;
font-size: 0.95rem;
}
/* Estilo pasos */
.steps-list {
list-style: none;
padding: 0;
margin: 0;
}
.step-item {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
background: light;
padding: 10px 14px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.step-number {
flex-shrink: 0;
width: 28px;
height: 28px;
border-radius: 50%;
background: #793e6c;
color: white;
font-weight: bold;
font-size: 0.9rem;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.step-text {
flex: 1;
color: #333;
line-height: 1.4;
}
</style>
\ No newline at end of file
......@@ -42,7 +42,7 @@
</div>
<div v-for="ingredient in ingredients" :key="ingredient.id" class="row align-items-center mb-2">
<div class="col-3">
<input type="number" class="form-control" v-model="ingredient.quantity" placeholder="Ej: 2">
<input type="text" class="form-control" v-model="ingredient.quantity" placeholder="Ej: 2">
</div>
<div class="col-3">
<input type="text" class="form-control" v-model="ingredient.unitOfMeasure" placeholder="Ej: tazas">
......
......@@ -53,7 +53,7 @@
<div v-else class="mb-4 mt-3 text-center">
<label for="recipePicture" class="form-label fw-bold">Cambiar imagen</label>
<input id="recipePicture" type="file" class="form-control mx-auto" style="max-width: 300px" @change="handlePictureChange" />
<input id="recipePicture" type="file" class="form-control mx-auto" @change="handlePictureChange" />
</div>
<!-- Descripción -->
......@@ -68,25 +68,37 @@
<hr class="my-4" />
<!-- Ingredientes -->
<div class="row">
<!-- Ingredientes -->
<div class="col-lg-6">
<div class="mb-4">
<h5 class="fw-bold">Ingredientes</h5>
<ul v-if="mode === 'view'" class="list-unstyled">
<li v-for="(ing, index) in processedIngredients" :key="index" class="mb-1">
<i class="bi bi-check-circle-fill me-2 text-success"></i>
{{ ing.quantity }} {{ ing.unit }} {{ ing.connective }} {{ ing.name }}
<h5 class="section-title">
<i class="bi bi-basket me-2"></i> Ingredientes
</h5>
<!-- Vista -->
<ul v-if="mode === 'view'" class="ingredients-list">
<li v-for="(ing, index) in processedIngredients" :key="index" class="ingredient-item">
<span class="ingredient-name">{{ ing.name }}</span>
<span class="ingredient-amount">
{{ ing.quantity }} {{ ing.unit }} {{ ing.connective }}
</span>
</li>
</ul>
<!-- Edición -->
<div v-else class="ingredient-form-section">
<div v-for="(ing, index) in editableRecipe.ingredients" :key="index" class="input-group mb-2">
<input type="number" v-model="ing.quantity" class="form-control" placeholder="Cantidad">
<div v-for="(ing, index) in editableRecipe.ingredients" :key="index" class="ingredient-form mb-2">
<input type="text" v-model="ing.name" class="form-control mb-2" placeholder="Ingrediente">
<div class="d-flex gap-2 mt-2">
<input type="text" v-model="ing.quantity" class="form-control" placeholder="Cantidad">
<input type="text" v-model="ing.unitOfMeasure" class="form-control" placeholder="Unidad">
<input type="text" v-model="ing.name" class="form-control" placeholder="Ingrediente">
<button @click="editableRecipe.ingredients.splice(index, 1)" class="btn btn-outline-danger" type="button"><i class="bi bi-trash"></i></button>
<button @click="editableRecipe.ingredients.splice(index, 1)" class="btn btn-outline-danger" type="button">
<i class="bi bi-trash"></i>
</button>
</div>
<button @click="editableRecipe.ingredients.push({ quantity: '', unit: '', ingredient: '' })" class="btn btn-outline-primary-custom mt-2 w-100">
</div>
<button @click="editableRecipe.ingredients.push({ quantity: '', unit: '', name: '' })" class="btn btn-outline-primary-custom mt-2 w-100">
<i class="bi bi-plus-circle"></i> Añadir Ingrediente
</button>
</div>
......@@ -96,21 +108,28 @@
<!-- Pasos -->
<div class="col-lg-6">
<div class="mb-4">
<h5 class="fw-bold">Pasos de Preparación</h5>
<ol v-if="mode === 'view'">
<li v-for="(step, index) in recipe.steps" :key="index" class="mb-2">
{{ step.description }}
<h5 class="section-title">
<i class="bi bi-list-ol me-2"></i> Pasos de preparación
</h5>
<!-- Vista -->
<ol v-if="mode === 'view'" class="steps-list">
<li v-for="(step, index) in recipe.steps" :key="index" class="step-item">
<span class="step-number">{{ index + 1 }}</span>
<span class="step-text">{{ step.description }}</span>
</li>
</ol>
<!-- Edición -->
<div v-else class="steps-form-section">
<div v-for="(step, index) in editableRecipe.steps" :key="index" class="input-group mb-2">
<div v-for="(step, index) in editableRecipe.steps" :key="index" class="step-form">
<div class="d-flex gap-2 mb-2">
<span class="input-group-text">{{ index + 1 }}</span>
<textarea
v-model="step.description"
class="form-control"
rows="3">
</textarea>
<button @click="editableRecipe.steps.splice(index, 1)" class="btn btn-outline-danger" type="button"><i class="bi bi-trash"></i></button>
<textarea v-model="step.description" class="form-control" rows="2"></textarea>
<button @click="editableRecipe.steps.splice(index, 1)" class="btn btn-outline-danger" type="button">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
<button @click="editableRecipe.steps.push({ description: '' })" class="btn btn-outline-primary-custom mt-2 w-100">
<i class="bi bi-plus-circle"></i> Añadir Paso
......@@ -120,6 +139,7 @@
</div>
</div>
</div>
</div>
</div>
</div>
......@@ -175,12 +195,12 @@ const processedIngredients = computed(() => {
return recipe.value.ingredients.map(ing => {
const unit = ing.unitOfMeasure ? ing.unitOfMeasure.toLowerCase() : '';
const name = ing.name ? ing.name.toLowerCase() : '';
const connective = ing.unitOfMeasure ? 'de' : '';
// const connective = ing.unitOfMeasure ? 'de' : '';
return {
...ing,
unit,
name,
connective
name
// connective
};
});
});
......@@ -333,4 +353,73 @@ h5 {
color: white;
font-size: 14px;
}
/* Títulos de sección */
.section-title {
font-weight: 700;
color: #793E6C;
border-bottom: 2px solid #eee;
padding-bottom: 4px;
margin-bottom: 16px;
display: flex;
align-items: center;
}
/* INGREDIENTES vista */
.ingredients-list {
list-style: none;
padding: 0;
margin: 0;
}
.ingredient-item {
display: flex;
justify-content: space-between;
padding: 6px 10px;
border-bottom: 1px solid #eee;
}
.ingredient-name {
font-weight: 500;
}
.ingredient-amount {
color: #555;
font-size: 0.9rem;
}
/* PASOS vista */
.steps-list {
list-style: none;
padding: 0;
margin: 0;
}
.step-item {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
background: #f5f5f5;
padding: 10px 14px;
border-radius: 8px;
}
.step-number {
flex-shrink: 0;
width: 28px;
height: 28px;
border-radius: 50%;
background: #793e6c;
color: white;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.step-text {
flex: 1;
}
</style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment