Connecting apis

This commit is contained in:
mustafa-rezae 2025-04-12 07:04:15 +03:30
parent 363d1269f2
commit 4745294ffc
45 changed files with 1277 additions and 676 deletions

View File

@ -2,5 +2,25 @@ export default {
search: { search: {
list: "repo/monir/search/@index_key/@search_type/@type_key/@listkey/@field_collapsed/@offset/@limit/@q=none", list: "repo/monir/search/@index_key/@search_type/@type_key/@listkey/@field_collapsed/@offset/@limit/@q=none",
show: "repo/public/get/byid/@index_key/@id", show: "repo/public/get/byid/@index_key/@id",
synonym: "synonym/get/words",
prevNextHadith: "monir/next/@index_key/@vol_id/@parag_order/@step",
},
favorite: {
add: "favorite/add/{{data_type}}/{{ref_key}}",
delete: "favorite/delete/{{data_type}}/{{id}}", //id = portal_meet_22569
deleteByRefid: "favorite/delete/{{data_type}}/{{index_key}}/{{ref_id}}", //id = portal_meet_22569
// getListSearch: "favorite/list/{{data_type}}/{{offset}}/{{limit}}", //offset=0 , limit=10
// counts: "favorite/counts/@data_type", // get
setFavoritesCat: "favorite/tags/@data_type/set/doc/@id",
getCategories: "favorite/tags/@data_type/get",
setCategories: "favorite/tags/@data_type/set",
getCounts: "favorite/tags/@data_type/counts",
getList: "favorite/list/@data_type/@offset/@limit/@filter",
},
library: {
list: "monir/book/volume/@field_collapsed/@offset/@limit/@q",
show: "@appname/book/page/@page_start/@page_end/@vol_id",
}, },
}; };

View File

@ -0,0 +1,9 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.7668 1.71576H7.22222C6.10322 1.71849 5.03079 2.1869 4.23953 3.01851C3.44827 3.85013 3.0026 4.97725 3 6.15333L3 20.3516C3 22.4209 4.41105 23.2946 6.13932 22.2945L11.4773 19.179C11.7955 19.0144 12.1454 18.9289 12.5 18.9289C12.8546 18.9289 13.2045 19.0144 13.5227 19.179L18.8607 22.2945C20.5889 23.3061 22 22.4324 22 20.3516V6.15333C21.9931 4.97666 21.5445 3.85043 20.7518 3.01946C19.9592 2.1885 18.8864 1.71998 17.7668 1.71576Z" fill="url(#paint0_linear_67_5429)"/>
<defs>
<linearGradient id="paint0_linear_67_5429" x1="18.5723" y1="34.3787" x2="21.3731" y2="3.44045" gradientUnits="userSpaceOnUse">
<stop stop-color="#4BE8AE"/>
<stop offset="1" stop-color="#00A762"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 783 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -3,8 +3,20 @@ import type { InputMenuItem } from "@nuxt/ui";
import hadithaApi from "../../apis/hadithaApi"; import hadithaApi from "../../apis/hadithaApi";
import { useStorage } from "@vueuse/core"; import { useStorage } from "@vueuse/core";
import * as z from "zod"; import * as z from "zod";
import type { FormSubmitEvent } from "@nuxt/ui"; import routeGlobal from "~/middleware/route.global";
// import type { FormSubmitEvent } from "@nuxt/ui";
export type Synonym = {
title: string;
enable: boolean;
enableForm: boolean;
subTitles: [
{
title: string;
active: boolean;
}
];
};
// #region props // #region props
const props = defineProps({ const props = defineProps({
showFilter: { showFilter: {
@ -26,15 +38,22 @@ const userSearchHistory = useStorage(
new Set() // Initial value new Set() // Initial value
); );
const searchTerm = useStorage<string>("searchPhrase", ""); const searchTerm = useStorage<string>("searchPhrase", "");
// when comming from main page search.
const route = useRoute();
if (route.query.q) {
searchTerm.value = route.query.q;
route.query.q = undefined
}
const open = ref(false); const open = ref(false);
const typeDropdownOpen = ref(false);
const loading = ref(false); const loading = ref(false);
const httpService = useNuxtApp()["$http"]; const httpService = useNuxtApp()["$http"];
const search_type = ref("normal"); const search_type = ref("normal");
const type_key = ref("hadith"); const type_key = ref("hadith");
const typeModelValue = ref("normal");
const synonymOne = ref(false); const typeModelValueFa = ref("");
const synonymTwo = ref(false);
// If you want to share state across multiple components, // If you want to share state across multiple components,
// you can use the same key in useState. Nuxt will ensure // you can use the same key in useState. Nuxt will ensure
@ -60,27 +79,57 @@ const state = reactive({
items: [ items: [
{ {
label: "جستجو در همه", label: "جستجو در همه",
value: "normal",
class: "",
onSelect(e: Event) { onSelect(e: Event) {
console.info(e); console.info(e);
search_type.value = "normal"; search_type.value = "normal";
state.type.label = e.explicitOriginalTarget?.innerText;
sendQuery();
state.type.items[0].class = "active";
}, },
}, },
{ {
label: "فقط در متن عربی حدیث", label: "فقط در متن عربی حدیث",
value: "arabic",
valueFa: "عربی",
class: "",
onSelect(e: Event) { onSelect(e: Event) {
search_type.value = "arabic"; search_type.value = "arabic";
state.type.label = e.explicitOriginalTarget?.innerText;
console.info(e);
state.type.items[0].class = "active";
sendQuery();
}, },
}, },
{ {
label: "فقط در ترجمه ها", label: "فقط در ترجمه ها",
value: "translations",
valueFa: "ترجمه",
onSelect(e: Event) { onSelect(e: Event) {
search_type.value = "translations"; search_type.value = "translations";
state.type.label = e.explicitOriginalTarget?.innerText;
console.info(e);
state.type.items[0].class = "active";
sendQuery();
}, },
}, },
{ {
label: "فقط در شروح", label: "فقط در شروح",
value: "descriptions",
valueFa: "شروح",
onSelect(e: Event) { onSelect(e: Event) {
search_type.value = "descriptions"; search_type.value = "descriptions";
state.type.label = e.explicitOriginalTarget?.innerText;
state.type.items[0].class = "active";
console.info(e);
sendQuery();
}, },
}, },
], ],
@ -89,24 +138,7 @@ const state = reactive({
value: "synonym", value: "synonym",
label: "مترادف", label: "مترادف",
icon: "i-haditha-chevron-down", icon: "i-haditha-chevron-down",
items: [ items: [],
{
label: "جستجو در همه",
slot: "arabic",
},
{
label: "فقط در متن عربی حدیث",
slot: "arabic",
},
{
label: "فقط در ترجمه ها",
slot: "arabic",
},
{
label: "فقط در شروح",
slot: "arabic",
},
],
}, },
type_key: { type_key: {
label: "ترجمه", label: "ترجمه",
@ -130,9 +162,6 @@ const state = reactive({
// ); // );
// #endregion emits // #endregion emits
onMounted(() => {
if (searchTerm.value.length) sendQuery();
});
// #region methods // #region methods
const clearSimilar = () => { const clearSimilar = () => {
@ -162,7 +191,7 @@ const setKey = (type: string) => {
sendQuery(); sendQuery();
}; };
const sendQuery = async () => { const sendQuery = async (payload = {}) => {
if (loading.value) return; if (loading.value) return;
loading.value = true; loading.value = true;
@ -175,9 +204,20 @@ const sendQuery = async () => {
url = url.replace("@limit", "10"); url = url.replace("@limit", "10");
url = url.replace("@listkey", "normal"); url = url.replace("@listkey", "normal");
url = url.replace("@field_collapsed", "normal"); url = url.replace("@field_collapsed", "normal");
// اگر نوع انتخاب شود.
const isTypeSelected =
typeModelValue.value == "arabic" ||
typeModelValue.value == "translations" ||
typeModelValue.value == "descriptions";
url = url.replace( url = url.replace(
"@q=none", "@q=none",
searchTerm.value.length ? `q=${searchTerm.value}` : "q=none" searchTerm.value.length
? `q=${isTypeSelected ? "#" + typeModelValueFa.value + " " : ""}${
searchTerm.value
}`
: "q=none"
); );
// const baseURL = // const baseURL =
@ -185,7 +225,7 @@ const sendQuery = async () => {
// fetch search list from backend(ssr) // fetch search list from backend(ssr)
return await httpService return await httpService
.postRequest(url) .postRequest(url, payload)
.then((res) => { .then((res) => {
// pass res and search query to the parent. // pass res and search query to the parent.
emit("response-ready", { emit("response-ready", {
@ -211,35 +251,127 @@ const sendQuery = async () => {
}); });
}; };
// ------------------- form ------------------- // ------------------- form -------------------
type Schema = z.output<typeof schema>;
const toast = useToast();
const schema = z.object({ const schema = z.object({
name: z.string(), name: z.string().min(1, "این فیلد ضروری است"),
}); });
const showForm = ref(false);
const isSynonymPopupOpen = ref(false); const isSynonymPopupOpen = ref(false);
const Formstate = reactive({ const Formstate = reactive({
name: "", name: "",
}); });
async function onSubmit(event: FormSubmitEvent<Schema>) { // async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ // toast.add({
title: "Success", // title: "Success",
description: "The form has been submitted.", // description: "The form has been submitted.",
color: "success", // color: "success",
}); // });
console.log(event.data); // console.log(event.data);
} // }
// get synonyms // get synonyms
const synonymIsSwitchedOn = computed(() => {
return state.synonym.items.filter((i) => i.enable).length;
});
const onClearSynonymClear = () => {
search_type.value = "normal";
state.synonym.items.forEach((element) => {
element.enable = false;
});
};
async function openSynonymPopup(type: string) { async function openSynonymPopup(type: string) {
search_type.value = type; search_type.value = type;
console.info("openSynonymPopup"); console.info("openSynonymPopup");
sendQuery().then(() => { getSynonyms().then(() => {
isSynonymPopupOpen.value = true; isSynonymPopupOpen.value = true;
}); });
} }
const onTypeSelectChanged = (value: string) => {
console.info(value);
if (value == "translations") typeModelValueFa.value = "ترجمه";
else if (value == "arabic") typeModelValueFa.value = "عربی";
else if (value == "descriptions") typeModelValueFa.value = "شروح";
else typeModelValueFa.value = "همه";
sendQuery();
};
const getSynonyms = async () => {
let url = repoUrl() + hadithaApi.search.synonym;
const payload = {
query: searchTerm.value,
};
// fetch search list from backend(ssr)
return await httpService.postRequest(url, payload).then((res) => {
state.synonym.items = [];
Object.entries(res.data).forEach((item, index) => {
const synonyms = {} as Synonym;
synonyms.title = item[0];
synonyms.enable = false;
synonyms.enableForm = false;
const subTitlesStrList = item[1].value.split(",");
const subTitlesObjList = subTitlesStrList
.filter((i) => i)
.map((i) => {
return {
title: i,
active: false,
};
});
synonyms.subTitles = subTitlesObjList;
state.synonym.items.push(synonyms);
});
});
};
const prepareSynonym = () => {
const enabledSwitches = state.synonym.items.filter((i) => i.enable);
const res = {};
console.info(enabledSwitches);
enabledSwitches.forEach((item) => {
console.info(item);
if (item.subTitles.length)
res[item.title] = item.subTitles
.filter((i) => i.active)
.map((i) => i.title)
.join(",");
});
console.info(res);
return {
synonym: res,
};
};
const onUpdateSwitch = () => {
sendQuery(prepareSynonym());
};
const onUpdateSubTitle = (subTitle) => {
subTitle.active = !subTitle.active;
sendQuery(prepareSynonym());
};
const onAddNewTitle = (subTitles) => {
subTitles.push({
active: true,
title: Formstate.name,
});
Formstate.name = "";
sendQuery(prepareSynonym());
};
// #endregion methods // #endregion methods
onMounted(() => {
if (searchTerm.value.length) sendQuery();
});
</script> </script>
<template> <template>
@ -305,7 +437,7 @@ async function openSynonymPopup(type: string) {
v-if="props.showFilter && searchTerm.length" v-if="props.showFilter && searchTerm.length"
> >
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<!-- معنایی --> <!-- #region معنایی -->
<!-- @click.self="search_type = 'vector'" --> <!-- @click.self="search_type = 'vector'" -->
<UButton <UButton
@click.self="setType('vector')" @click.self="setType('vector')"
@ -318,14 +450,15 @@ async function openSynonymPopup(type: string) {
<UIcon <UIcon
v-if="search_type == 'vector'" v-if="search_type == 'vector'"
@click.self="search_type = 'normal'" @click.self="setType('normal')"
name="i-haditha-close-bg-circle" name="i-haditha-close-bg-circle"
size="20px" size="20px"
> >
</UIcon> </UIcon>
</UButton> </UButton>
<!-- مترادف --> <!-- #endregion -->
<!-- #region مترادف -->
<UPopover <UPopover
:content="{ :content="{
align: 'start', align: 'start',
@ -339,119 +472,94 @@ async function openSynonymPopup(type: string) {
> >
<UButton <UButton
@click="openSynonymPopup('synonym')" @click="openSynonymPopup('synonym')"
:class="{ active: search_type == 'synonym' }"
class="filter-item" class="filter-item"
:label="state.synonym.label" type="button"
trailingIcon="i-haditha-dropdown-chevron-down" trailingIcon="i-haditha-dropdown-chevron-down"
/> >
{{ state.synonym.label }}
<UIcon
v-if="synonymIsSwitchedOn"
@click.self="onClearSynonymClear"
name="i-haditha-close-bg-circle"
size="20px"
>
</UIcon>
</UButton>
<template #content> <template #content>
<!-- synonym item --> <!-- synonym item -->
<div class="synonymItem px-2 py-4"> <template v-for="(syn, itemIndex) in state.synonym.items">
<div class="flex justify-between items-center p-3 mb-2"> <div class="synonymItem px-2 py-4">
<span class="title"> نماز </span> <div class="flex justify-between items-center p-3 mb-2">
<USwitch dir="ltr" v-model="synonymOne" /> <span class="title"> {{ syn.title }} </span>
</div> <USwitch
<div class="flex items-center px-2"> dir="ltr"
<UButton v-model="syn.enable"
type="button" @update:modelValue="
@click="true" onUpdateSwitch(syn.subTitles, itemIndex)
class="me-2.5 mb-3.5 promotion-item" "
:class="{ active: false }" />
> </div>
صلات <div class="flex items-center px-2 flex-wrap">
<!-- <UIcon name="i-haditha-close-bg-circle" size="12px"> </UIcon> --> <UButton
</UButton> v-for="(sub, subIndex) in syn.subTitles"
<UButton :disabled="!syn.enable"
type="button" :key="subIndex"
@click="true" type="button"
class="me-2.5 mb-3.5 promotion-item" @click="onUpdateSubTitle(sub, itemIndex, subIndex)"
:class="{ active: false }" class="me-2.5 mb-3.5 promotion-item"
> :class="{ active: sub.active }"
عبادت >
<!-- <UIcon name="i-haditha-close-bg-circle" size="12px"> </UIcon> --> {{ sub.title }}
</UButton> <!-- <UIcon name="i-haditha-close-bg-circle" size="12px"> </UIcon> -->
<UButton </UButton>
v-if="!showForm"
type="button"
@click="showForm = true"
class="me-2.5 mb-3.5 add-button"
:class="{ active: false }"
icon="i-haditha-add"
>
</UButton>
<UForm
v-else
:schema="schema"
:state="state"
class="w-25 me-2.5 mb-3.5"
>
<UFormField name="name" required size="md">
<UInput
v-model="Formstate.name"
placeholder="بنویسید ..."
/>
</UFormField>
</UForm>
</div>
</div>
<USeparator class="px-2" color="neutral" type="solid" size="xs" /> <UButton
v-if="syn.enable && !syn.enableForm"
type="button"
@click="syn.enableForm = true"
class="me-2.5 mb-3.5 add-button"
:class="{ active: syn.enable }"
icon="i-haditha-add"
>
</UButton>
<UForm
v-if="syn.enable && syn.enableForm"
:schema="schema"
:state="state"
class="w-25 me-2.5 mb-3.5"
>
<UFormField name="name" size="md">
<UInput
v-model="Formstate.name"
placeholder="بنویسید ..."
@keyup.enter="onAddNewTitle(syn.subTitles)"
>
<template v-if="syn.enableForm" #trailing>
<UButton
color=""
variant=""
size="sm"
icon="i-lucide-x"
aria-label="Clear input"
@click="syn.enableForm = false"
/>
</template>
</UInput>
</UFormField>
</UForm>
</div>
</div>
<!-- synonym item --> <USeparator class="px-2" color="neutral" type="solid" size="xs" />
<div class="synonymItem px-2 py-4"> </template>
<div class="flex justify-between items-center p-3 mb-2">
<span class="title"> نماز </span>
<USwitch dir="ltr" v-model="synonymTwo" />
</div>
<div class="flex items-center px-2">
<UButton
type="button"
@click="true"
class="me-2.5 mb-3.5 promotion-item"
:class="{ active: false }"
>
صلات
<!-- <UIcon name="i-haditha-close-bg-circle" size="12px"> </UIcon> -->
</UButton>
<UButton
type="button"
@click="true"
class="me-2.5 mb-3.5 promotion-item"
:class="{ active: false }"
>
عبادت
<!-- <UIcon name="i-haditha-close-bg-circle" size="12px"> </UIcon> -->
</UButton>
<UButton
v-if="!showForm"
type="button"
@click="showForm = true"
class="me-2.5 mb-3.5 add-button"
:class="{ active: false }"
icon="i-haditha-add"
>
</UButton>
<UForm
v-else
:schema="schema"
:state="state"
class="w-25 me-2.5 mb-3.5"
>
<UFormField name="name" required size="md">
<UInput
v-model="Formstate.name"
placeholder="بنویسید ..."
/>
</UFormField>
</UForm>
</div>
</div>
<USeparator class="px-2" color="neutral" type="solid" size="xs" />
</template> </template>
</UPopover> </UPopover>
<!-- #endregion -->
<!-- ترجمه --> <!-- #region ترجمه -->
<UButton <!-- <UButton
@click.self="setKey('hadith_fa')" @click.self="setKey('hadith_fa')"
:class="{ active: type_key == 'hadith_fa' }" :class="{ active: type_key == 'hadith_fa' }"
type="button" type="button"
@ -466,8 +574,9 @@ async function openSynonymPopup(type: string) {
size="20px" size="20px"
> >
</UIcon> </UIcon>
</UButton> </UButton> -->
<!-- عین عبارت --> <!-- #endregion -->
<!-- #region عین عبارت -->
<UButton <UButton
@click.self="setType('phrase')" @click.self="setType('phrase')"
:class="{ active: search_type == 'phrase' }" :class="{ active: search_type == 'phrase' }"
@ -482,11 +591,12 @@ async function openSynonymPopup(type: string) {
> >
</UIcon> </UIcon>
</UButton> </UButton>
<!-- #endregion -->
</div> </div>
<!-- نوع --> <!-- #region نوع -->
<div> <div>
<UDropdownMenu <USelect
:items="state.type.items" :items="state.type.items"
:content="{ :content="{
align: 'start', align: 'start',
@ -496,28 +606,44 @@ async function openSynonymPopup(type: string) {
:ui="{ :ui="{
content: 'w-48', content: 'w-48',
}" }"
class="filter-item"
:class="{ active: state.type.value == 'hadith' }" :class="{ active: state.type.value == 'hadith' }"
v-model="typeModelValue"
trailing-icon=""
value-key="value"
@update:modelValue="onTypeSelectChanged"
> >
<UButton <!-- <UButton
class="filter-item" class="filter-item"
:label="state.type.label" :label="state.type.label"
trailingIcon="i-haditha-dropdown-chevron-down" trailingIcon="i-haditha-dropdown-chevron-down"
:class="{
active:
search_type == 'arabic' ||
search_type == 'translations' ||
search_type == 'descriptions',
}"
> >
{{ state.type.label }} {{ state.type.label }}
<UIcon <UIcon
v-if="state.type.value != 'normal'" v-if="
@click.self="state.type.value = 'normal'" search_type == 'arabic' ||
search_type == 'translations' ||
search_type == 'descriptions'
"
@click.self="closeTypeDropdown"
name="i-haditha-close-bg-circle" name="i-haditha-close-bg-circle"
size="20px" size="20px"
> >
</UIcon> </UIcon>
</UButton> </UButton>-->
<!-- <template #item> item </template> --> <!-- <template #item> item </template> -->
<!-- <template #item-label> item label </template> --> <!-- <template #item-label> item label </template> -->
</UDropdownMenu> </USelect>
</div> </div>
<!-- #endregion -->
</div> </div>
</div> </div>
</template> </template>
@ -542,7 +668,8 @@ async function openSynonymPopup(type: string) {
backdrop-filter: blur(60px); backdrop-filter: blur(60px);
background: linear-gradient(137.41deg, #ffffff -42.82%, #e5e0ff 87.9%); background: linear-gradient(137.41deg, #ffffff -42.82%, #e5e0ff 87.9%);
filter: blur(60px); filter: blur(60px);
width: 626px; max-width: 626px;
width:100%;
height: 68px; height: 68px;
z-index: 0; z-index: 0;
} }
@ -730,6 +857,7 @@ async function openSynonymPopup(type: string) {
} }
} }
.popover-root-content { .popover-root-content {
overflow-y: auto;
width: 20.5em; width: 20.5em;
height: 17.75em; height: 17.75em;
/* gap: 8px; */ /* gap: 8px; */
@ -782,6 +910,10 @@ async function openSynonymPopup(type: string) {
border: 0.3px solid #29d985; border: 0.3px solid #29d985;
color: #626b84; color: #626b84;
} }
&[disabled="true"] {
filter: grayscale(0.7);
}
} }
.add-button { .add-button {
width: 48; width: 48;

View File

@ -18,7 +18,7 @@ const props = defineProps({
<div class="library-list-contianer"> <div class="library-list-contianer">
<div class="page-header flex items-center"> <div class="page-header flex items-center">
<span class="title">کتابخانه</span> <span class="title">کتابخانه</span>
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder
@ -41,30 +41,35 @@ const props = defineProps({
> >
<!-- <template #header></template> --> <!-- <template #header></template> -->
<ULink <client-only>
:to="{ <ULink
name:'hadithaLibraryShow', :to="{
params:{ name: 'hadithaLibraryShow',
id:1, params: {
slug:'اصول کافی-جلد 1' id: 1,
} slug: item?._source?.title,
}" },
color="neutral" }"
variant="outline" color="neutral"
:ui="{ variant="outline"
leadingIcon: 'text-(--ui-primary)', :ui="{
}" leadingIcon: 'text-(--ui-primary)',
@click="" }"
> @click=""
<NuxtImg >
fit="auto" <img
quality="80" fit="auto"
placeholder quality="80"
src="/img/haditha/sample-bgi.svg" placeholder
/> src="/img/haditha/sample-bgi.svg"
<p class="title">اصول کافی</p> />
<p class="version">جلد 1</p> <p class="title">{{ item?._source?.title }}</p>
</ULink> <p class="version">
{{ item?._source?.vol_title + item?._source?.vol_num }}
</p>
</ULink>
</client-only>
<!-- <template #footer> </template> --> <!-- <template #footer> </template> -->
</UCard> </UCard>
@ -72,7 +77,7 @@ const props = defineProps({
class="h-full w-full flex flex-col justify-center items-center" class="h-full w-full flex flex-col justify-center items-center"
v-else v-else
> >
<NuxtImg fit="auto" quality="80" placeholder :src="props.noDataIcon" /> <img fit="auto" quality="80" placeholder :src="props.noDataIcon" />
<p class="no-data-text">{{ props.noDataText }}</p> <p class="no-data-text">{{ props.noDataText }}</p>
</no-data> </no-data>
</div> </div>

View File

@ -59,13 +59,17 @@ const items = ref([
{ {
label: "خروج از حساب", label: "خروج از حساب",
icon: "i-haditha-logout", icon: "i-haditha-logout",
to: "/haditha/logout", type: "button" as const,
onSelect(e: Event) {
e.preventDefault();
logout();
},
}, },
], ],
}, },
]); ]);
const leftItem = ref([ const leftItem = computed(() => [
{ {
label: "نشان شده ها", label: "نشان شده ها",
icon: "i-haditha-bookmark", icon: "i-haditha-bookmark",
@ -94,7 +98,11 @@ const leftItem = ref([
{ {
label: "خروج از حساب", label: "خروج از حساب",
icon: "i-haditha-logout", icon: "i-haditha-logout",
to: "/haditha/logout", type: "button" as const,
onSelect(e: Event) {
e.preventDefault();
logout();
},
}, },
], ],
}, },
@ -186,22 +194,22 @@ onMounted(() => {
.fixed { .fixed {
z-index: 999; z-index: 999;
.my-navbar { .my-navbar {
max-width: 1200px; max-width: 75em; //1200px
height: 68px; height: 4.25em; // 68px;
border-radius: 16px; border-radius: 1em;
border-width: 0.3px; border-width: 0.3px;
// justify-content: space-between; // justify-content: space-between;
padding-top: 4px; padding-top: 0.25em;
padding-right: 16px; padding-right: 1em;
padding-bottom: 4px; padding-bottom: 0.25em;
padding-left: 16px; padding-left: 1em;
background-color: #fff; background-color: #fff;
border: 0.3px solid #e0e0e0; border: 0.3px solid #e0e0e0;
box-shadow: 0px 4px 15px 0px #0000001a; box-shadow: 0px 4px 15px 0px #0000001a;
@media screen and (max-width: 991.99px) { @media screen and (max-width: 991.99px) {
height: 76px; height: 5em; // 76px;
} }
nav > div { nav > div {
@ -226,23 +234,23 @@ onMounted(() => {
&::before { &::before {
background-color: color-mix(in oklab, #00a762 50%, transparent); background-color: color-mix(in oklab, #00a762 50%, transparent);
box-shadow: 0px 4px 10px 0px #00745933; box-shadow: 0px 4px 10px 0px #00745933;
border-radius: 12px; border-radius: 0.75em; //12px;
} }
} }
// max-width: 112px; // max-width: 112px;
height: 48px; height: 3.5em;
gap: 4px; gap: 4px;
border-radius: 12px; border-radius: 0.75em;
padding-top: 6px; padding-top: 0.37em; /*6px*/
padding-right: 16px; padding-right: 1.2em;
padding-bottom: 6px; padding-bottom: 0.37em; /*6px*/
padding-left: 16px; padding-left: 1.2em;
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 400; font-weight: 400;
font-size: 14px; font-size: 0.87rem; /*14px*/
line-height: 21px; line-height: 1.3rem; /*6px*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: center; text-align: center;
@ -260,7 +268,7 @@ onMounted(() => {
} }
@media screen and (max-width: 991.99px) { @media screen and (max-width: 991.99px) {
height: 60px; height: 5em;
} }
} }
@ -285,10 +293,10 @@ onMounted(() => {
gap: 4px; gap: 4px;
border-radius: 12px; border-radius: 12px;
padding-top: 14px; padding-top: 0.875em; /*14px*/
padding-right: 16px; padding-right: 1em; /*16px*/
padding-bottom: 14px; padding-bottom: 0.875em; /*14px*/
padding-left: 16px; padding-left: 1em; /*16px*/
background: linear-gradient(102.02deg, #4be8ae 7.38%, #00a762 91.78%); background: linear-gradient(102.02deg, #4be8ae 7.38%, #00a762 91.78%);
box-shadow: 0px 4px 10px 0px #00745933; box-shadow: 0px 4px 10px 0px #00745933;

View File

@ -40,7 +40,7 @@ const state = reactive({
<div class="header flex items-center justify-center mb-8"> <div class="header flex items-center justify-center mb-8">
<span class="title me-1">تجربه شما با </span> <span class="title me-1">تجربه شما با </span>
<NuxtImg <img
width="49" width="49"
height="18" height="18"
fit="auto" fit="auto"
@ -78,7 +78,7 @@ const state = reactive({
dot: 'dot size-2', dot: 'dot size-2',
}" }"
> >
<NuxtImg <img
width="100" width="100"
height="100" height="100"
fit="auto" fit="auto"
@ -108,8 +108,8 @@ const state = reactive({
.title { .title {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 300; font-weight: 300;
font-size: 20px; font-size: 1.25rem; /* 20px;*/
line-height: 30px; line-height: 1.87rem; /* 30px;*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: center; text-align: center;
@ -120,8 +120,8 @@ const state = reactive({
.title { .title {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 400; font-weight: 400;
font-size: 16px; font-size: 1rem;
line-height: 24px; line-height: 1.5rem; /* 24px;*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: center; text-align: center;
@ -143,8 +143,8 @@ const state = reactive({
.job { .job {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 300; font-weight: 300;
font-size: 16px; font-size: 1rem;
line-height: 24px; line-height: 1.5rem;
letter-spacing: 0%; letter-spacing: 0%;
text-align: center; text-align: center;
color: #626b84; color: #626b84;
@ -155,8 +155,8 @@ const state = reactive({
margin-bottom: 0.3em; margin-bottom: 0.3em;
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 300; font-weight: 300;
font-size: 16px; font-size: 1rem;
line-height: 24px; line-height: 1.5rem;
letter-spacing: 0%; letter-spacing: 0%;
text-align: justify; text-align: justify;
@ -173,7 +173,7 @@ const state = reactive({
width: 40; width: 40;
height: 56; height: 56;
padding: 1.2em 0.6em; padding: 1.2em 0.6em;
border-radius: 12px; border-radius: 0.75em;
background: #ffffff; background: #ffffff;
border: 0.3px solid #e0e0e0; border: 0.3px solid #e0e0e0;
@ -181,6 +181,12 @@ const state = reactive({
color: #545aea; color: #545aea;
} }
.prev {
right: 0;
}
.next {
left: 0;
}
.content { .content {
.controls { .controls {
@ -198,8 +204,7 @@ const state = reactive({
#4be8ae 7.38%, #4be8ae 7.38%,
#00a762 91.78% #00a762 91.78%
); );
border:0; border: 0;
} }
} }
} }

View File

@ -38,10 +38,10 @@ const state = reactive({
<UContainer <UContainer
class="section-container mx-auto max-w-[var(--ui-container-two)] sm:px-6 lg:px-4" class="section-container mx-auto max-w-[var(--ui-container-two)] sm:px-6 lg:px-4"
> >
<div class="header flex items-center justify-center mb-8 "> <div class="header flex items-center justify-center mb-8">
<span class="title me-1">امکانات </span> <span class="title me-1">امکانات </span>
<NuxtImg <img
width="49" width="49"
height="18" height="18"
fit="auto" fit="auto"
@ -53,7 +53,9 @@ const state = reactive({
</div> </div>
<!-- grid-cols-1 md:grid-cols-2 lg:grid-cols-3 --> <!-- grid-cols-1 md:grid-cols-2 lg:grid-cols-3 -->
<div class="content flex justify-center flex-wrap items-start p-4 space-y-3"> <div
class="content flex justify-center flex-wrap items-start p-4 space-y-3"
>
<UCard <UCard
:ui="{ :ui="{
root: 'ring ring-[white] shadow-none bg-transparent card-item flex flex-col items-center basis-[360px] p-4', root: 'ring ring-[white] shadow-none bg-transparent card-item flex flex-col items-center basis-[360px] p-4',
@ -65,12 +67,7 @@ const state = reactive({
:key="index" :key="index"
> >
<template #header> <template #header>
<NuxtImg <img fit="auto" quality="100" :src="item.img" class="me-2" />
fit="auto"
quality="100"
:src="item.img"
class="me-2"
/>
</template> </template>
<p class="mb-2 title">{{ item.title }}</p> <p class="mb-2 title">{{ item.title }}</p>
@ -91,8 +88,8 @@ const state = reactive({
.title { .title {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 300; font-weight: 300;
font-size: 20px; font-size: 1.25rem; /* 20px;*/
line-height: 30px; line-height: 1.87rem; /* 30px;*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: center; text-align: center;
@ -106,16 +103,16 @@ const state = reactive({
.title { .title {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 600; font-weight: 600;
font-size: 16px; font-size: 1rem;
line-height: 24px; line-height: 1.5rem; /* 24px;*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: center; text-align: center;
} }
.description { .description {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 300; font-weight: 300;
font-size: 14px; font-size: 0.87rem; /* 14px;*/
line-height: 21px; line-height: 1.31rem; /* 21px;*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: center; text-align: center;
} }

View File

@ -1,9 +1,11 @@
<script setup> <script setup>
const img = useImage(); const img = useImage();
const router = useRouter();
import { useStorage } from "@vueuse/core";
const backgroundImageStyle = computed(() => { const backgroundImageStyle = computed(() => {
// Use $img to generate an optimized image URL // Use $img to generate an optimized image URL
const optimizedImageUrl = img("/img/haditha/background.png", { const optimizedImageUrl = img("/img/haditha/background.webp", {
quality: 80, quality: 80,
fit: "auto", fit: "auto",
}); });
@ -13,6 +15,19 @@ const backgroundImageStyle = computed(() => {
}; };
}); });
const searchPhrase = useStorage("searchPhrase", "");
searchPhrase.value = '';
const handleResponseReady = (payload) => {
router.push({
name: "hadithaSearch",
query: {
q: payload.searchQuery,
},
});
};
const NavigationMenu = defineAsyncComponent(() => const NavigationMenu = defineAsyncComponent(() =>
import("@haditha/components/haditha/NavigationMenu.vue") import("@haditha/components/haditha/NavigationMenu.vue")
); );
@ -26,7 +41,12 @@ const AutoComplation = defineAsyncComponent(() =>
<navigation-menu></navigation-menu> <navigation-menu></navigation-menu>
<div class="logo-container flex justify-center flex-col items-center"> <div class="logo-container flex justify-center flex-col items-center">
<NuxtImg fit="auto" quality="80" placeholder src="/img/haditha/logo.png" /> <img
fit="auto"
quality="80"
placeholder
src="/img/haditha/logo.webp"
/>
<div class="title"> <div class="title">
کاوش با کاوش با
<span class="badge-style me-1"> هوش مصنوعی </span> <span class="badge-style me-1"> هوش مصنوعی </span>
@ -35,7 +55,9 @@ const AutoComplation = defineAsyncComponent(() =>
</div> </div>
</div> </div>
<div class="search-box-container flex justify-center"> <div class="search-box-container flex justify-center">
<auto-complation></auto-complation> <auto-complation
@response-ready="handleResponseReady($event)"
></auto-complation>
</div> </div>
</section> </section>
</template> </template>

View File

@ -3,8 +3,8 @@ const img = useImage();
const backgroundImageStyle = computed(() => { const backgroundImageStyle = computed(() => {
// Use $img to generate an optimized image URL // Use $img to generate an optimized image URL
const optimizedImageUrl = img("/img/haditha/section-three-bgi.png", { const optimizedImageUrl = img("/img/haditha/section-three-bgi.webp", {
quality: 80, quality: 30,
fit: "auto", fit: "auto",
}); });
@ -17,14 +17,12 @@ const backgroundImageStyle = computed(() => {
<section class="section-three flex" :style="backgroundImageStyle"> <section class="section-three flex" :style="backgroundImageStyle">
<div class="section-container mx-auto"> <div class="section-container mx-auto">
<div class="header flex items-center mb-2"> <div class="header flex items-center mb-2">
<NuxtImg <img
width="88"
height="34"
fit="auto" fit="auto"
quality="80" quality="80"
placeholder title="حدیثا"
src="/img/haditha/section-three-logo.svg" src="/img/haditha/section-three-logo.png"
class="me-2" class="me-2 haditha-text"
/> />
<span class="title"> چگونه کار میکند؟ </span> <span class="title"> چگونه کار میکند؟ </span>
</div> </div>
@ -64,17 +62,21 @@ const backgroundImageStyle = computed(() => {
width: 352.611083984375; width: 352.611083984375;
height: 56; height: 56;
gap: 10px; gap: 10px;
padding-top: 4px; padding-top: 0.25em;
padding-right: 16px; padding-right: 1em;
padding-bottom: 4px; padding-bottom: 0.25em;
padding-left: 16px; padding-left: 1em;
border-radius: 16px; border-radius: 1em;
.haditha-text {
width: 5.5em; /*"88";*/
height: 2.1em; /*"34";*/
}
.title { .title {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 300; font-weight: 300;
font-size: 32px; font-size: 2rem; /*32px;*/
line-height: 48px; line-height: 3rem; /*48px;*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: center; text-align: center;
@ -87,17 +89,17 @@ const backgroundImageStyle = computed(() => {
height: 184; height: 184;
gap: 10px; gap: 10px;
padding-top: 8px; padding-top: 0.5em;
padding-right: 16px; padding-right: 1em;
padding-bottom: 8px; padding-bottom: 0.5em;
padding-left: 16px; padding-left: 1em;
border-radius: 16px; border-radius: 1em;
background: #1b2132cc; background: #1b2132cc;
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 300; font-weight: 300;
font-size: 16px; font-size: 1rem;
line-height: 24px; line-height: 1.5rem; /*24px;*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: right; text-align: right;
color: #ffffff; color: #ffffff;

View File

@ -3,7 +3,7 @@
<div class="flex flex-col items-center py-6"> <div class="flex flex-col items-center py-6">
<div>قابلیت ها</div> <div>قابلیت ها</div>
<div> <div>
<NuxtImg <img
width="32" width="32"
height="32" height="32"
fit="auto" fit="auto"
@ -17,7 +17,7 @@
<div class="flex flex-col lg:flex-row"> <div class="flex flex-col lg:flex-row">
<div class="my-card card-one basis-full flex justify-center items-center"> <div class="my-card card-one basis-full flex justify-center items-center">
<div class="inner-container"> <div class="inner-container">
<div> <div class="px-4">
<p class="title">جستجوی معنایی</p> <p class="title">جستجوی معنایی</p>
<div class="mb-6"> <div class="mb-6">
<p class="description">ارائه نتایج دقیق و معنادار از میان</p> <p class="description">ارائه نتایج دقیق و معنادار از میان</p>
@ -25,17 +25,17 @@
</div> </div>
</div> </div>
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder
src="/img/haditha/card-one.png" src="/img/haditha/card-one.webp"
/> />
</div> </div>
</div> </div>
<div class="my-card card-two basis-full flex justify-center items-center"> <div class="my-card card-two basis-full flex justify-center items-center">
<div class="inner-container"> <div class="inner-container">
<div class=""> <div class="px-4">
<p class="title">جستجوی مترادفها</p> <p class="title">جستجوی مترادفها</p>
<div class="mb-6"> <div class="mb-6">
<p class="description">یافتن سریع واژگان مرتبط برای</p> <p class="description">یافتن سریع واژگان مرتبط برای</p>
@ -43,11 +43,11 @@
</div> </div>
</div> </div>
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder
src="/img/haditha/card-two.png" src="/img/haditha/card-two.webp"
/> />
</div> </div>
</div> </div>
@ -55,7 +55,7 @@
class="my-card card-three basis-full flex flex-col justify-center items-center" class="my-card card-three basis-full flex flex-col justify-center items-center"
> >
<div class="inner-container"> <div class="inner-container">
<div> <div class="px-4">
<p class="title">مشابهتیابی حدیث</p> <p class="title">مشابهتیابی حدیث</p>
<div class="mb-6"> <div class="mb-6">
<p class="description">امکان یافتن احادیث یا مفاهیمی</p> <p class="description">امکان یافتن احادیث یا مفاهیمی</p>
@ -63,11 +63,11 @@
</div> </div>
</div> </div>
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder
src="/img/haditha/card-three.png" src="/img/haditha/card-three.webp"
/> />
</div> </div>
</div> </div>
@ -78,15 +78,16 @@
<style scoped> <style scoped>
.section-two { .section-two {
font-weight: 200; font-weight: 200;
font-size: 20px; font-size: 1.25rem; /*20px;*/
line-height: 30px; line-height: 1.8rem; /*30px;*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: center; text-align: center;
.my-card { .my-card {
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: auto; background-size: auto;
height: 660px; height: 41.25em; /* 660px*/
padding: 4.7em 0;
.inner-container { .inner-container {
max-width: 21.25em; max-width: 21.25em;
@ -95,8 +96,8 @@
.title { .title {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 700; font-weight: 700;
font-size: 24px; font-size: 1.5rem; /*24px*/
line-height: 36px; line-height: 2.25rem; /* 36px;*/
letter-spacing: 0%; letter-spacing: 0%;
color: var(--ui-color-two); color: var(--ui-color-two);
text-align: right; text-align: right;
@ -105,23 +106,23 @@
.description { .description {
font-weight: 300; font-weight: 300;
font-size: 20px; font-size: 1.25rem; /* 20px;*/
line-height: 30px; line-height: 1.87rem; /* 30px;*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: right; text-align: right;
color: #626b84; color: #626b84;
} }
&.card-one { &.card-one {
background-image: url("/img/haditha/card-one-bgi.png"), background-image: url("/img/haditha/card-one-bgi.webp"),
linear-gradient(134.17deg, #ffffff -9.81%, #e5e0ff 87.62%); linear-gradient(134.17deg, #ffffff -9.81%, #e5e0ff 87.62%);
} }
&.card-two { &.card-two {
background-image: url("/img/haditha/card-two-bgi.png"), background-image: url("/img/haditha/card-two-bgi.webp"),
linear-gradient(329.16deg, #b9fde0 13.45%, #eefff8 63.57%); linear-gradient(329.16deg, #b9fde0 13.45%, #eefff8 63.57%);
} }
&.card-three { &.card-three {
background-image: url("/img/haditha/card-three-bgi.png"), background-image: url("/img/haditha/card-three-bgi.webp"),
linear-gradient(134.17deg, #ffffff -9.81%, #e5e0ff 87.62%); linear-gradient(134.17deg, #ffffff -9.81%, #e5e0ff 87.62%);
} }
} }

View File

@ -1,13 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import * as z from "zod"; import * as z from "zod";
import type { FormSubmitEvent } from "@nuxt/ui"; import type { FormSubmitEvent } from "@nuxt/ui";
import hadithaApi from "../../../apis/hadithaApi";
import { useAuthStore } from "~/stores/authStore";
import { useStorage } from "@vueuse/core";
definePageMeta({
layout: false,
name: "hadithaLogin",
});
useHead({ useHead({
title: `${import.meta.env.VITE_HADITH_PAGE_TITLE} | ورود`, title: `${
import.meta.env.VITE_HADITH_PAGE_TITLE
} | وارد کردن شماره تلفن همراه`,
meta: [ meta: [
{ name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" }, { name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" },
], ],
@ -16,6 +17,12 @@ useHead({
}, },
}); });
const emits = defineEmits(["next-step"]);
const router = useRouter();
const authStore = useAuthStore();
const loading = ref(false);
const localizedErrors = { const localizedErrors = {
required: "Invalid email address", required: "Invalid email address",
// Add more error codes and translations as needed // Add more error codes and translations as needed
@ -25,7 +32,7 @@ const schema = z.object({
mobile: z mobile: z
.string() .string()
.min(1, "این فیلد ضروری است.(exmaple : 09--*******)") .min(1, "این فیلد ضروری است.(exmaple : 09--*******)")
.max(11, "موبایل حداکثر 11 عدد می باشد.(exmaple : 09--*******)") .max(11, "موبایل حداکثر 11 عدد می باشد.(exmaple : 09--*******)"),
}); });
type Schema = z.output<typeof schema>; type Schema = z.output<typeof schema>;
@ -36,84 +43,80 @@ const state = reactive<Partial<Schema>>({
const toast = useToast(); const toast = useToast();
async function onSubmit(event: FormSubmitEvent<Schema>) { async function onSubmit(event: FormSubmitEvent<Schema>) {
toast.add({ // const payoload = {
title: "Success", // username: "dev",
description: "The form has been submitted.", // password: "dev123",
color: "success", // // captcha: "",
}); // };
console.log(event.data);
}
// components declaration authStore
const HadithaLayout = defineAsyncComponent( .loginAsGuest()
() => import("@haditha/layouts/HadithaLayout.vue") .then((res) => {
); loading.value = false;
const NavigationMenu = defineAsyncComponent( // check if search term is not empty
() => import("@haditha/components/haditha/NavigationMenu.vue")
); toast.add({
title: "Success",
description: res.data.message,
color: "success",
});
if (!res.data.isNew) emits("next-step");
else
navigateTo({
name: "haditha",
});
})
.catch((err) => {
console.info(err);
})
.finally(() => {
loading.value = false;
});
}
</script> </script>
<template> <template>
<HadithaLayout> <p class="title">خوش آمدید</p>
<div class="haditha-login-page h-full"> <p class="description">لطفا شماره موبایل خود را وارد کنید</p>
<div class="page-container pt-20 h-full">
<navigation-menu></navigation-menu>
<UContainer <UForm
ui="{ :schema="schema"
base: 'sm:px-6 lg:px-4', :state="state"
}" class="space-y-4 w-full"
class="page-inner-container sm:px-6 lg:px-4 h-full" @submit="onSubmit"
> >
<div <UFormField
class="page-content flex flex-col items-center justify-center h-full" dir="ltr"
> name="mobile"
<p class="title">خوش آمدید</p> class="mb-6"
<p class="description">لطفا شماره موبایل خود را وارد کنید</p> :ui="{
root: 'root',
<UForm wrapper: 'wrapper',
:schema="schema" labelWrapper: 'labelWrapper',
:state="state" label: 'label',
class="space-y-4 w-full" container: 'container',
@submit="onSubmit" description: 'description',
> error: 'error',
<UFormField hint: 'hint',
dir="ltr" help: 'help',
name="mobile" }"
class="mb-6" >
:ui="{ <UInput
root: 'root', class="input-elem w-full"
wrapper: 'wrapper', placeholder="09--*******"
labelWrapper: 'labelWrapper', v-model="state.mobile"
label: 'label', type="text"
container: 'container', />
description: 'description', </UFormField>
error: 'error', <UButton
hint: 'hint', class="submit-btn w-full flex justify-center"
help: 'help', variant="solid"
}" type="submit"
> >
<UInput ورود / ثبت نام
class="input-elem w-full" </UButton>
placeholder="09--*******" </UForm>
v-model="state.mobile"
type="text"
/>
</UFormField>
<UButton
class="submit-btn w-full flex justify-center"
variant="solid"
type="submit"
>
ورود / ثبت نام
</UButton>
</UForm>
</div>
</UContainer>
</div>
</div>
</HadithaLayout>
</template> </template>
<style scoped> <style scoped>
@ -123,7 +126,8 @@ const NavigationMenu = defineAsyncComponent(
.page-container { .page-container {
.page-inner-container { .page-inner-container {
margin: auto; margin: auto;
width: 512px; /* max-width: 512px; */
max-width: 300px;
/* height: 125px; */ /* height: 125px; */
gap: 24px; gap: 24px;
@ -170,7 +174,7 @@ const NavigationMenu = defineAsyncComponent(
.page-inner-container { .page-inner-container {
.input-elem { .input-elem {
input { input {
width: 480; width: 48;
height: 53px; height: 53px;
justify-content: space-between; justify-content: space-between;
border-radius: 12px; border-radius: 12px;
@ -205,6 +209,31 @@ const NavigationMenu = defineAsyncComponent(
border: 0.3px solid #d9d9d9; border: 0.3px solid #d9d9d9;
box-shadow: 0px 1px 4px 0px #0000000d; box-shadow: 0px 1px 4px 0px #0000000d;
} }
.root-pin-input {
direction: ltr;
.base-pin-input {
width: 56px;
height: 53px;
justify-content: space-between;
border-radius: 12px;
border-width: 0.3px;
padding: 16px;
border: 0.3px solid #d9d9d9;
box-shadow: 0px 1px 4px 0px #0000000d;
font-family: IRANSansX;
font-weight: 400;
font-size: 16px;
line-height: 100%;
letter-spacing: 0%;
text-align: center;
color: var(--ui-color-two);
}
}
} }
} }
} }

View File

@ -0,0 +1,248 @@
<script setup lang="ts">
import * as z from "zod";
import type { FormSubmitEvent } from "@nuxt/ui";
import hadithaApi from "../../../apis/hadithaApi";
import { useAuthStore } from "~/stores/authStore";
import { useTemplateRef, ref } from "vue";
useHead({
title: `${
import.meta.env.VITE_HADITH_PAGE_TITLE
} | وارد کردن شماره تلفن همراه`,
meta: [
{ name: "description", content: "کاوش با هوش مصنوعی در احادیث اسلامی" },
],
bodyAttrs: {
class: import.meta.env.VITE_HADITH_SYSTEM,
},
});
const router = useRouter();
const authStore = useAuthStore();
const loading = ref(false);
const pinInputRef = ref(null);
// const localizedErrors = {
// required: "Invalid email address",
// // Add more error codes and translations as needed
// };
const schema = z.object({
verifyCode: z.string().array(), // string[]
});
type Schema = z.output<typeof schema>;
const state = reactive<Partial<Schema>>({
verifyCode: [] as string[],
});
const toast = useToast();
async function onSubmit(event: FormSubmitEvent<Schema>) {
const payoload = {
username: "m.rezae",
password: "1234",
// captcha: "",
};
console.info("verify code");
// .loginAsGuest()
authStore
.login(payoload)
.then((res) => {
loading.value = false;
// check if search term is not empty
toast.add({
title: "Success",
description: res.data.message,
color: "success",
});
navigateTo({
name: "haditha",
});
})
.catch((err) => {
console.info(err);
})
.finally(() => {
loading.value = false;
});
}
onMounted(() => {
// console.info(pinInputRef.value);
// if (pinInputRef.value) {
// pinInputRef.value.focus(); // Or whatever focus method your PinInput component provides
// }
});
</script>
<template>
<p class="description">کد پیامک شده را وارد کنید</p>
<UForm
:schema="schema"
:state="state"
class="space-y-4 w-full"
@submit="onSubmit"
>
<UFormField
dir="ltr"
name="mobile"
class="mb-6"
:ui="{
root: 'root',
wrapper: 'wrapper',
labelWrapper: 'labelWrapper',
label: 'label',
container: 'container',
description: 'description',
error: 'error',
hint: 'hint',
help: 'help',
}"
>
<UPinInput
dir="ltr"
ref="pinInputRef"
otp
:length="4"
highlight
color="primary"
variant="outline"
v-model="state.verifyCode"
:ui="{
root: 'root-pin-input ltr w-full flex justify-between',
base: 'base-pin-input',
}"
/>
</UFormField>
<UButton
class="submit-btn w-full flex justify-center"
variant="solid"
type="submit"
>
ثبت کد
</UButton>
</UForm>
</template>
<style scoped>
.haditha-login-page {
background: #f7fffd;
.page-container {
.page-inner-container {
margin: auto;
/* max-width: 512px; */
max-width: 300px;
/* height: 125px; */
gap: 24px;
.title {
margin-bottom: 1.2em;
font-family: IRANSansX;
font-weight: 400;
font-size: 20px;
line-height: 30px;
letter-spacing: 0%;
/* Fallback color */
color: #4d00ff;
/* Gradient background */
background: linear-gradient(268.94deg, #d284ff -0.65%, #4d00ff 104.59%);
/* Clip the background to the text */
background-clip: text;
-webkit-background-clip: text; /* For Safari */
/* Make the text transparent */
color: transparent;
-webkit-text-fill-color: transparent; /* For Safari */
}
.description {
font-family: IRANSansX;
font-weight: 400;
font-size: 14px;
line-height: 21px;
letter-spacing: 0%;
color: #626b84;
margin-bottom: 5em; /* 70px */
}
}
}
}
</style>
<style>
.haditha-login-page {
.page-container {
.page-inner-container {
.input-elem {
input {
width: 48;
height: 53px;
justify-content: space-between;
border-radius: 12px;
border-width: 0.3px;
padding: 16px;
background: #ffffff;
border: 0.3px solid #d9d9d9;
box-shadow: 0px 1px 4px 0px #0000000d;
font-family: IRANSansX;
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0%;
text-align: center;
color: var(--ui-color-two);
}
}
.submit-btn {
width: 480;
height: 48px;
gap: 4px;
padding-top: 9.1px;
padding-right: 32px;
padding-bottom: 9.1px;
padding-left: 32px;
border-radius: 12px;
border-width: 0.3px;
background: linear-gradient(102.02deg, #4be8ae 7.38%, #00a762 91.78%);
border: 0.3px solid #d9d9d9;
box-shadow: 0px 1px 4px 0px #0000000d;
}
.root-pin-input {
direction: ltr;
.base-pin-input {
width: 56px;
height: 53px;
justify-content: space-between;
border-radius: 12px;
border-width: 0.3px;
padding: 16px;
border: 0.3px solid #d9d9d9;
box-shadow: 0px 1px 4px 0px #0000000d;
font-family: IRANSansX;
font-weight: 400;
font-size: 16px;
line-height: 100%;
letter-spacing: 0%;
text-align: center;
color: var(--ui-color-two);
}
}
}
}
}
</style>

View File

@ -62,17 +62,23 @@ function openModal(selectedItem) {
:key="index" :key="index"
> >
<p @click="openModal(item)" class="from-person"> <p @click="openModal(item)" class="from-person">
{{ item._source.meta.hadith_masoum ?? "بدون عنوان" }} {{
item?._source?.meta?.hadith_masoum ??
item?._source?.meta?.hadith_sanad ??
"بدون عنوان"
}}
</p>
<p @click="openModal(item)" class="arabic-text">
{{ item?._source?.content_ar }}
</p> </p>
<p @click="openModal(item)" class="arabic-text">بدون متن عربی</p>
<p <p
class="persian-text" class="persian-text"
v-html="item.highlight['content.fa'] ?? item._source.content" v-html="item?.highlight?.['content.fa'] ?? item?._source?.content"
></p> ></p>
<div class="flex justify-end"> <div class="flex justify-end">
<p class="reference"> <p class="reference">
{{ item._source.address.vol_title }}، صفحه {{ item?._source?.address?.vol_title }}، صفحه
{{ item._source.address.page_num }} {{ item?._source?.address?.page_num }}
</p> </p>
</div> </div>
</div> </div>
@ -81,7 +87,7 @@ function openModal(selectedItem) {
class="h-full w-full flex flex-col justify-center items-center" class="h-full w-full flex flex-col justify-center items-center"
v-else v-else
> >
<NuxtImg fit="auto" quality="80" placeholder :src="props.noDataIcon" /> <img fit="auto" quality="80" placeholder :src="props.noDataIcon" />
<p class="no-data-text">{{ props.noDataText }}</p> <p class="no-data-text">{{ props.noDataText }}</p>
</no-data> </no-data>
</div> </div>
@ -113,7 +119,7 @@ function openModal(selectedItem) {
<style scoped> <style scoped>
.search-list-contianer { .search-list-contianer {
max-width: 656px; max-width: 41em; /*656px*/
width: 100%; width: 100%;
margin: 0 1em; margin: 0 1em;
@ -122,8 +128,8 @@ function openModal(selectedItem) {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 0.68rem; /*11px*/
line-height: 16.5px; line-height: 1rem;
letter-spacing: 0%; letter-spacing: 0%;
text-align: right; text-align: right;
color: #b4c2cf; color: #b4c2cf;
@ -145,8 +151,8 @@ function openModal(selectedItem) {
.from-person { .from-person {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 300; font-weight: 300;
font-size: 12px; font-size: 0.75rem; /*12px*/
line-height: 18px; line-height: 1.125rem; /*18px*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: right; text-align: right;
color: #00a762; /* #4be8ae 7.38% */ color: #00a762; /* #4be8ae 7.38% */
@ -162,8 +168,8 @@ function openModal(selectedItem) {
.arabic-text { .arabic-text {
font-family: Takrim; font-family: Takrim;
font-weight: 400; font-weight: 400;
font-size: 18px; font-size: 1.125rem; /*18px*/
line-height: 32px; line-height: 2rem; /*23px*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: right; text-align: right;
color: var(--ui-color-two); color: var(--ui-color-two);
@ -179,8 +185,8 @@ function openModal(selectedItem) {
.persian-text { .persian-text {
font-family: Takrim; font-family: Takrim;
font-weight: 400; font-weight: 400;
font-size: 16px; font-size: 1rem; /*16px*/
line-height: 22px; line-height: 1.375rem; /*22px*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: right; text-align: right;
color: #626b84; color: #626b84;
@ -189,18 +195,18 @@ function openModal(selectedItem) {
.reference { .reference {
height: 24px; height: 24px;
gap: 4px; gap: 4px;
padding-top: 4px; padding-top: 0.25em; /*4px*/
padding-right: 8px; padding-right: 0.5em; /*8px*/
padding-bottom: 4px; padding-bottom: 0.25em; /*4px*/
padding-left: 8px; padding-left: 0.5em; /*8px*/
border-radius: 6px; border-radius: 6px; /*18px*/
border-width: 0.5px; border-width: 0.5px;
border: 0.5px solid #d9d9d9; border: 0.5px solid #d9d9d9;
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 300; font-weight: 300;
font-size: 10px; font-size: 0.625rem; /*10px*/
line-height: 15px; line-height: 0.9rem; /*15px*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: right; text-align: right;
color: #8a92a8; color: #8a92a8;
@ -217,8 +223,8 @@ function openModal(selectedItem) {
.no-data-text { .no-data-text {
font-family: IRANSansX; font-family: IRANSansX;
font-weight: 300; font-weight: 300;
font-size: 16px; font-size: 1rem;
line-height: 24px; line-height: 1.5rem; /*24px*/
letter-spacing: 0%; letter-spacing: 0%;
text-align: center; text-align: center;
} }
@ -235,14 +241,14 @@ function openModal(selectedItem) {
box-shadow: 0px 8px 20px 0px #0000001a; box-shadow: 0px 8px 20px 0px #0000001a;
background: #ffffff; background: #ffffff;
width: 100%; width: 100%;
max-width: 720px; max-width: 720px; /*18px*/
border-radius: 16px; border-radius: 16px; /*18px*/
gap: 8px; gap: 8px;
border-width: 0.3px; border-width: 0.3px;
.modal-body { .modal-body {
border-radius: 16px; border-radius: 16px; /*18px*/
height: 800px; height: 800px; /*18px*/
position: relative; position: relative;
.top-left-bgi { .top-left-bgi {

View File

@ -20,7 +20,7 @@ const closeModal = () => {
<div class="body-header"> <div class="body-header">
<span class="top-left-bgi z-0"></span> <span class="top-left-bgi z-0"></span>
<div class="modal-title flex justify-between"> <div class="modal-title flex justify-between">
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder

View File

@ -1,4 +1,6 @@
<script setup> <script setup>
import hadithaApi from "@haditha/apis/hadithaApi";
definePageMeta({ definePageMeta({
layout: false, layout: false,
name: "hadithaFavorites", name: "hadithaFavorites",
@ -14,6 +16,72 @@ useHead({
}, },
}); });
// #region refs
const loading = ref(false);
const httpService = useNuxtApp()["$http"];
// #endregion refs
// #region reactive
const state = reactive({
// list: new Array(5).fill(0),
list: [],
counts: [],
totalCounts: [],
});
// #endregion reactive
// #region methods
const getCategories = async (dataType = "bookmark") => {
if (loading.value) return;
loading.value = true;
let url = repoUrl() + hadithaApi.favorite.getList;
url = url.replace("@field_collapsed", "book_title");
url = url.replace("@offset", 0);
url = url.replace("@limit", 20);
url = url.replace("@q", "none");
return await httpService
.getRequest(url)
.then((res) => {
state.list = res.data;
getCounts();
})
.catch((err) => {
console.info(err);
loading.value = false;
});
};
const getCounts = async () => {
let url = repoUrl() + hadithaApi.favorite.getCounts;
url = url.replace("@data_type", "bookmark");
await httpService
.getRequest(url)
.then((res) => {
state.totalCounts = res.hits.total.value;
state.counts = res.aggregations.result.buckets;
loading.value = false;
})
.catch((err) => {
console.info(err);
})
.finally(() => {
loading.value = false;
});
};
// #endregion methods
// #region hooks
onMounted(() => {
getCategories();
});
// #endregion methods
const HadithaLayout = defineAsyncComponent(() => const HadithaLayout = defineAsyncComponent(() =>
import("@haditha/layouts/HadithaLayout.vue") import("@haditha/layouts/HadithaLayout.vue")
); );
@ -24,10 +92,6 @@ const NavigationMenu = defineAsyncComponent(() =>
const SearchList = defineAsyncComponent(() => const SearchList = defineAsyncComponent(() =>
import("@haditha/components/haditha/search-page/SearchList.vue") import("@haditha/components/haditha/search-page/SearchList.vue")
); );
const state = reactive({
searchList: new Array(5).fill(0),
});
</script> </script>
<template> <template>
@ -41,7 +105,7 @@ const state = reactive({
<search-list <search-list
no-data-text="هنوز چیزی ذخیره نکرده‌اید!" no-data-text="هنوز چیزی ذخیره نکرده‌اید!"
no-data-icon="/img/haditha/save.png" no-data-icon="/img/haditha/save.png"
:list="state.searchList" :list="state.list"
></search-list> ></search-list>
</div> </div>
</div> </div>

View File

@ -1,11 +1,29 @@
<script setup lang="ts"> <script setup lang="ts">
// 1. Imports
// 2. Metas
// 3. Props
// 2. Reactive State
// 3. Computed Properties
// 4. Functions / Methods
// 5. Lifecycle Hooks
// 6. Watchers
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
// #region imports
import hadithaApi from "@haditha/apis/hadithaApi";
// import type { HadithResponseModel } from "@haditha/types/hadithType";
import type { HadithResponseModel } from "~/systems/hadith_ui/types/hadithType";
// #endregion imports
// #region meta
definePageMeta({ definePageMeta({
layout: false, layout: false,
name: "hadithaLibraryShow", name: "hadithaLibraryShow",
}); });
useHead({ useHead({
title: `${import.meta.env.VITE_HADITH_PAGE_TITLE} | ${route.params.slug}`, title: `${import.meta.env.VITE_HADITH_PAGE_TITLE} | ${route.params.slug}`,
meta: [ meta: [
@ -15,6 +33,8 @@ useHead({
class: import.meta.env.VITE_HADITH_SYSTEM, class: import.meta.env.VITE_HADITH_SYSTEM,
}, },
}); });
// #endregion imports
// #region props
const props = defineProps({ const props = defineProps({
list: { list: {
@ -29,6 +49,9 @@ const props = defineProps({
default: "/img/haditha/no-data.png", default: "/img/haditha/no-data.png",
}, },
}); });
// #endregion props
// #region refs and reactives
const treeItems = [ const treeItems = [
{ {
@ -83,7 +106,52 @@ const treeItems = [
{ title: "فصل پنجم", icon: "vscode-icons:file-type-nuxt" }, { title: "فصل پنجم", icon: "vscode-icons:file-type-nuxt" },
]; ];
const isModalOpen = ref(false); const isModalOpen = ref(false);
const loading = ref(false);
const httpService = useNuxtApp()["$http"];
const state = reactive({
selectedItem: {} as HadithResponseModel,
});
// #endregion refs and reactives
// #region methods
const fetchData = async () => {
if (loading.value) return;
loading.value = true;
console.info(route.params.id);
let url = repoUrl() + hadithaApi.library.show;
url = url.replace("@appname", "monir");
url = url.replace("@page_start", "0");
url = url.replace("@page_end", "10");
url = url.replace("@vol_id", route.params.id.toString());
// fetch search list from backend(ssr)
const { data, status, error, refresh, clear } =
await useHadithaSearchComposable<HadithResponseModel>(url, {
method: "get",
});
// console.info(data.value);
console.info(status.value);
if (status.value == "success") {
console.info(data.value);
console.info(data.value.hits.hits);
state.selectedItem = <HadithResponseModel>data.value.hits.hits;
}
loading.value = false;
};
fetchData();
const goToTheLibrary = (type: string) => {
router.push({
name: "hadithaLibrary",
});
};
const onOpenList = () => { const onOpenList = () => {
console.info("onOpenList"); console.info("onOpenList");
isModalOpen.value = true; isModalOpen.value = true;
@ -97,6 +165,8 @@ const onClose = () => {
name: "hadithLibrary", name: "hadithLibrary",
}); });
}; };
// #endregion methods
// components declaration // components declaration
const HadithaLayout = defineAsyncComponent( const HadithaLayout = defineAsyncComponent(
() => import("@haditha/layouts/HadithaLayout.vue") () => import("@haditha/layouts/HadithaLayout.vue")
@ -143,7 +213,7 @@ const UTree = defineAsyncComponent(
</UButton> </UButton>
<UButton <UButton
@click="onClose" @click="goToTheLibrary"
class="my-trailing-button close p-0 ms-8" class="my-trailing-button close p-0 ms-8"
icon="i-lucide:x" icon="i-lucide:x"
variant="" variant=""

View File

@ -1,4 +1,6 @@
<script setup> <script setup>
import hadithaApi from "@haditha/apis/hadithaApi";
definePageMeta({ definePageMeta({
layout: false, layout: false,
name: "hadithaLibrary", name: "hadithaLibrary",
@ -14,9 +16,53 @@ useHead({
}, },
}); });
// #region refs
const loading = ref(false);
const httpService = useNuxtApp()["$http"];
// #endregion refs
// #region reactive
const state = reactive({ const state = reactive({
list: [],
counts: [],
totalCounts: [],
libraryList: new Array(20).fill(0), libraryList: new Array(20).fill(0),
}); });
// #endregion reactive
// #region methods
const getLibraryList = async (dataType = "bookmark") => {
if (loading.value) return;
loading.value = true;
let url = repoUrl() + hadithaApi.library.list;
url = url.replace("@field_collapsed", "book_title");
url = url.replace("@offset", 0);
url = url.replace("@limit", 20);
url = url.replace("@q", "none");
return await httpService
.postRequest(url)
.then((res) => {
state.libraryList = res.hits.hits;
console.info(state.libraryList)
})
.catch((err) => {
console.info(err);
loading.value = false;
})
.finally(() => (loading.value = false));
};
// #endregion methods
// #region hooks
onMounted(() => {
getLibraryList();
});
// #endregion methods
// components declaration // components declaration
const HadithaLayout = defineAsyncComponent(() => const HadithaLayout = defineAsyncComponent(() =>

View File

@ -1,9 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import * as z from "zod";
import type { FormSubmitEvent } from "@nuxt/ui";
import hadithaApi from "../../apis/hadithaApi";
import { useAuthStore } from "~/stores/authStore";
definePageMeta({ definePageMeta({
layout: false, layout: false,
name: "hadithaLogin", name: "hadithaLogin",
@ -18,60 +13,7 @@ useHead({
}, },
}); });
const router = useRouter(); const stepOne = ref(true);
const authStore = useAuthStore();
// const localizedErrors = {
// required: "Invalid email address",
// // Add more error codes and translations as needed
// };
const schema = z.object({
mobile: z
.string()
.min(1, "این فیلد ضروری است.(exmaple : 09--*******)")
.max(11, "موبایل حداکثر 11 عدد می باشد.(exmaple : 09--*******)"),
});
const loading = ref(false);
type Schema = z.output<typeof schema>;
const state = reactive<Partial<Schema>>({
mobile: undefined,
});
const toast = useToast();
async function onSubmit(event: FormSubmitEvent<Schema>) {
console.log(event.data);
const payoload = {
username: "dev",
password: "dev123",
};
authStore
.login(payoload)
.then((res) => {
loading.value = false;
// check if search term is not empty
toast.add({
title: "Success",
description: res.data.message,
color: "success",
});
navigateTo({
name: "haditha",
});
})
.catch((err) => {
console.info(err);
})
.finally(() => {
loading.value = false;
});
}
// components declaration // components declaration
const HadithaLayout = defineAsyncComponent( const HadithaLayout = defineAsyncComponent(
@ -80,6 +22,12 @@ const HadithaLayout = defineAsyncComponent(
const NavigationMenu = defineAsyncComponent( const NavigationMenu = defineAsyncComponent(
() => import("@haditha/components/haditha/NavigationMenu.vue") () => import("@haditha/components/haditha/NavigationMenu.vue")
); );
const EnterMobile = defineAsyncComponent(
() => import("@haditha/components/haditha/login/EnterMobile.vue")
);
const EnterVerifyCode = defineAsyncComponent(
() => import("@haditha/components/haditha/login/EnterVerifyCode.vue")
);
</script> </script>
<template> <template>
@ -97,47 +45,11 @@ const NavigationMenu = defineAsyncComponent(
<div <div
class="page-content flex flex-col items-center justify-center h-full" class="page-content flex flex-col items-center justify-center h-full"
> >
<p class="title">خوش آمدید</p> <enter-mobile
<p class="description">لطفا شماره موبایل خود را وارد کنید</p> v-if="stepOne"
@next-step="stepOne = !stepOne"
<UForm ></enter-mobile>
:schema="schema" <enter-verify-code v-else></enter-verify-code>
:state="state"
class="space-y-4 w-full"
@submit="onSubmit"
>
<UFormField
dir="ltr"
name="mobile"
class="mb-6"
:ui="{
root: 'root',
wrapper: 'wrapper',
labelWrapper: 'labelWrapper',
label: 'label',
container: 'container',
description: 'description',
error: 'error',
hint: 'hint',
help: 'help',
}"
>
<UInput
class="input-elem w-full"
placeholder="09--*******"
v-model="state.mobile"
type="text"
/>
</UFormField>
<UButton
class="submit-btn w-full flex justify-center"
variant="solid"
type="submit"
>
ورود / ثبت نام
</UButton>
</UForm>
</div> </div>
</UContainer> </UContainer>
</div> </div>
@ -152,7 +64,8 @@ const NavigationMenu = defineAsyncComponent(
.page-container { .page-container {
.page-inner-container { .page-inner-container {
margin: auto; margin: auto;
width: 512px; /* max-width: 512px; */
max-width: 300px;
/* height: 125px; */ /* height: 125px; */
gap: 24px; gap: 24px;
@ -199,7 +112,7 @@ const NavigationMenu = defineAsyncComponent(
.page-inner-container { .page-inner-container {
.input-elem { .input-elem {
input { input {
width: 480; width: 48;
height: 53px; height: 53px;
justify-content: space-between; justify-content: space-between;
border-radius: 12px; border-radius: 12px;
@ -234,6 +147,31 @@ const NavigationMenu = defineAsyncComponent(
border: 0.3px solid #d9d9d9; border: 0.3px solid #d9d9d9;
box-shadow: 0px 1px 4px 0px #0000000d; box-shadow: 0px 1px 4px 0px #0000000d;
} }
.root-pin-input {
direction: ltr;
.base-pin-input {
width: 56px;
height: 53px;
justify-content: space-between;
border-radius: 12px;
border-width: 0.3px;
padding: 16px;
border: 0.3px solid #d9d9d9;
box-shadow: 0px 1px 4px 0px #0000000d;
font-family: IRANSansX;
font-weight: 400;
font-size: 16px;
line-height: 100%;
letter-spacing: 0%;
text-align: center;
color: var(--ui-color-two);
}
}
} }
} }
} }

View File

@ -33,7 +33,7 @@ const NavigationMenu = defineAsyncComponent(() =>
<div class="page-header pt-38 pb-4 flex justify-between items-center"> <div class="page-header pt-38 pb-4 flex justify-between items-center">
<div class="flex items-center"> <div class="flex items-center">
<h1 class="m-0 title">درباره</h1> <h1 class="m-0 title">درباره</h1>
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder

View File

@ -64,11 +64,11 @@ const NavigationMenu = defineAsyncComponent(() =>
> >
</div> </div>
<div class="location"> <div class="location">
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder
src="/img/haditha/location.png" src="/img/haditha/location.webp"
/> />
</div> </div>
</div> </div>

View File

@ -52,7 +52,7 @@ onMounted(() => {
<div class="page-header pt-38 pb-4 flex justify-between items-center"> <div class="page-header pt-38 pb-4 flex justify-between items-center">
<div class="flex items-center"> <div class="flex items-center">
<h1 class="m-0 title">درباره</h1> <h1 class="m-0 title">درباره</h1>
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder

View File

@ -33,7 +33,7 @@ const NavigationMenu = defineAsyncComponent(() =>
<div class="page-header pt-38 pb-4 flex justify-between items-center"> <div class="page-header pt-38 pb-4 flex justify-between items-center">
<div class="flex items-center"> <div class="flex items-center">
<h1 class="m-0 title">قوانین و مقررات</h1> <h1 class="m-0 title">قوانین و مقررات</h1>
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder

View File

@ -40,13 +40,14 @@ useHead({
// }, // },
// }, // },
// }); // });
// #endregion imports // #endregion props
// #region refs and reactives // #region refs and reactives
const emit = defineEmits(["close"]); const emit = defineEmits(["close"]);
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const loading = ref(false); const loading = ref(false);
const httpService = useNuxtApp()["$http"];
const state = reactive({ const state = reactive({
selectedItem: {} as HadithResponseShowModel, selectedItem: {} as HadithResponseShowModel,
@ -58,6 +59,8 @@ const fetchData = async () => {
if (loading.value) return; if (loading.value) return;
loading.value = true; loading.value = true;
console.info(route.params.id);
let url = hadithaApi.search.show; let url = hadithaApi.search.show;
url = url.replace("@index_key", "dhparag"); url = url.replace("@index_key", "dhparag");
url = url.replace("@id", route.params.id); url = url.replace("@id", route.params.id);
@ -68,12 +71,17 @@ const fetchData = async () => {
method: "get", method: "get",
}); });
console.info(data);
if (status.value == "success") { if (status.value == "success") {
loading.value = false;
state.selectedItem = <HadithResponseShowModel>data.value; state.selectedItem = <HadithResponseShowModel>data.value;
} }
loading.value = false;
}; };
fetchData();
const goToTheSearch = (type: string) => { const goToTheSearch = (type: string) => {
router.push({ router.push({
name: "hadithaSearch", name: "hadithaSearch",
@ -85,9 +93,82 @@ const goToTheChatbot = () => {
}); });
}; };
const handleFavorite = async (item = {}) => {
console.info("addToFavorites");
addToFavorites();
// // add
// if (!item._source.tbookmark) {
// addToFavorites();
// }
// // delete
// else {
// removeToFavorites();
// }
};
const addToFavorites = async (item = {}) => {
console.info("addToFavorites");
let url = repoUrl() + hadithaApi.favorite.add;
url = url.replace("{{data_type}}", "bookmark");
url = url.replace("{{ref_key}}", "dhparag");
const formData = {
// ref_id: item._id,
ref_id: "pdh388_2113",
title: "عنوان تست_حدیثا بوک مارک",
// title: item._source.title,
};
httpService.postRequest(url, formData).then((res) => {
// this.updateListAnswer(index, "tbookmark", 1);
});
};
const removeFromFavorites = async (item = {}, index = 0) => {
let url = repoUrl() + hadithaApi.favorite.deleteByRefid;
url = url.replace("{{data_type}}", "bookmark");
url = url.replace("{{index_key}}", "dhparag");
url = url.replace("{{ref_id}}", "pdh388_2113");
const formData = {
ref_id: item._id,
title: item._source.title,
};
httpService.postRequest(url, formData).then((res) => {
// this.updateListAnswer(index, "tbookmark", 0);
});
};
const handlePagination = (prevNextIndicator: string) => {
console.info("fsdfsdfsdf");
if (loading.value) return;
loading.value = true;
let url = repoUrl() + hadithaApi.search.prevNextHadith;
url = url.replace("@index_key", "dhparag");
url = url.replace("@vol_id", state.selectedItem._source.address.vol_id);
url = url.replace("@parag_order", state.selectedItem._source.parag_order);
url = url.replace("@step", prevNextIndicator);
httpService
.getRequest(url)
.then((res) => {
state.selectedItem = res.hits.hits?.[0];
})
.finally(() => (loading.value = false));
};
const localCopyTextToClipboard = (text: string) => {
copyTextToClipboard(text);
};
// #endregion methods // #endregion methods
fetchData(); // #region hooks
// onMounted(() => {
// console.info("mounted");
// });
// #endregion methods
</script> </script>
<template> <template>
@ -101,7 +182,7 @@ fetchData();
<div class="body-header"> <div class="body-header">
<span class="top-left-bgi z-0"></span> <span class="top-left-bgi z-0"></span>
<div class="modal-title flex justify-between"> <div class="modal-title flex justify-between">
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder
@ -123,9 +204,17 @@ fetchData();
<div class="bg-container h-full"> <div class="bg-container h-full">
<div class="header flex"> <div class="header flex">
<UButton <UButton
variant="ghost" @click="handleFavorite"
:variant="
state.selectedItem?._source?.tbookmark ? 'soft' : 'ghost'
"
color="primary"
class="bookmark-btn" class="bookmark-btn"
icon="i-haditha-tag" :icon="
state.selectedItem?._source?.tbookmark
? 'i-haditha-tag-active'
: 'i-haditha-tag'
"
> >
</UButton> </UButton>
<div class="referene"> <div class="referene">
@ -146,7 +235,16 @@ fetchData();
}} }}
</span> </span>
<UButton variant="ghost" class="copy-btn" label="کپی" /> <UButton
@click="
localCopyTextToClipboard(
state.selectedItem?._source?.meta?.hadith_masoum
)
"
variant="ghost"
class="copy-btn"
label="کپی"
/>
</div> </div>
<div class="arabic-text"> <div class="arabic-text">
<p>بدون متن عربی</p> <p>بدون متن عربی</p>
@ -158,7 +256,16 @@ fetchData();
<div class="text-persian-section"> <div class="text-persian-section">
<div class="section-header"> <div class="section-header">
<span class="section-title"> ترجمه </span> <span class="section-title"> ترجمه </span>
<UButton variant="ghost" class="copy-btn" label="کپی" /> <UButton
@click="
localCopyTextToClipboard(
state.selectedItem?._source?.content
)
"
variant="ghost"
class="copy-btn"
label="کپی"
/>
</div> </div>
<p class="from"> <p class="from">
@ -178,13 +285,52 @@ fetchData();
<div class="text-description-section"> <div class="text-description-section">
<div class="section-header"> <div class="section-header">
<span class="section-title"> شرح </span> <span class="section-title"> شرح </span>
<UButton variant="ghost" class="copy-btn" label="کپی" /> <UButton
@click="
localCopyTextToClipboard(
state.selectedItem?._source?.content
)
"
variant="ghost"
class="copy-btn"
label="کپی"
/>
</div> </div>
<p <p
class="description-item" class="description-item"
v-html="state.selectedItem?._source?.content" v-html="state.selectedItem?._source?.content"
></p> ></p>
</div> </div>
<div class="separator"></div>
<div class="text-description-section">
<div class="text-sm mb-1">
<span class=""> کلیدواژگان: </span>
<span
class="me-1 text-gray-400 font-light"
v-for="(aiClass, i) in state.selectedItem?._source
?.ai_keywords"
:key="i"
>
{{ i > 0 ? "," : "" }}
{{ aiClass }}
</span>
</div>
<div class="text-sm">
<span class=""> دسته بندی ها: </span>
<span
class="me-1 text-gray-400 font-light"
v-for="(aiClass, i) in state.selectedItem?._source
?.ai_classes"
:key="i"
>
{{ i > 0 ? "," : "" }}
{{ aiClass.label }}
</span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -195,13 +341,14 @@ fetchData();
<div class="mt-5 z-2"> <div class="mt-5 z-2">
<div class="flex justify-between actions"> <div class="flex justify-between actions">
<UButton <UButton
disabled
class="similar-btn" class="similar-btn"
icon="i-haditha-search-3" icon="i-haditha-search-3"
label="مشابه" label="مشابه"
color="neutral" color="neutral"
variant="outline" variant="outline"
@click.prevent="goToTheSearch('similar')" />
/> <!-- @click.prevent="goToTheSearch('similar')" -->
<UButton <UButton
class="explore-btn" class="explore-btn"
trailing-icon="i-haditha-explore" trailing-icon="i-haditha-explore"
@ -212,6 +359,7 @@ fetchData();
</div> </div>
<div class="flex justify-between pagination"> <div class="flex justify-between pagination">
<UButton <UButton
@click="handlePagination('-1')"
class="prev-haditha" class="prev-haditha"
label="حدیث قبل" label="حدیث قبل"
color="" color=""
@ -219,6 +367,7 @@ fetchData();
icon="i-haditha-chevron-right" icon="i-haditha-chevron-right"
/> />
<UButton <UButton
@click="handlePagination('1')"
class="next-haditha" class="next-haditha"
label="حدیث بعد" label="حدیث بعد"
color="" color=""
@ -263,7 +412,7 @@ fetchData();
padding-bottom: 4px; padding-bottom: 4px;
padding-left: 12px; padding-left: 12px;
border: 0.5px solid #d9d9d9; border: 0.5px solid #d9d9d9;
background: #ffffff; /* background: #ffffff; */
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -364,7 +513,7 @@ fetchData();
padding-right: 12px; padding-right: 12px;
padding-bottom: 4px; padding-bottom: 4px;
padding-left: 12px; padding-left: 12px;
background: #ffffff; /* background: #ffffff; */
border: 0.5px solid #d9d9d9; border: 0.5px solid #d9d9d9;
font-family: IRANSansX; font-family: IRANSansX;
@ -374,7 +523,7 @@ fetchData();
letter-spacing: 0%; letter-spacing: 0%;
text-align: right; text-align: right;
color: #8a92a8; /* color: #8a92a8; */
} }
} }
.text-arabic-section { .text-arabic-section {

View File

@ -23,12 +23,12 @@ const state = reactive({
const backgroundImageStyle = computed(() => { const backgroundImageStyle = computed(() => {
// Use $img to generate an optimized image URL // Use $img to generate an optimized image URL
let optimizedImageUrl = img("/img/haditha/background.png", { let optimizedImageUrl = img("/img/haditha/background.webp", {
quality: 40, quality: 40,
}); });
if (searchQuery.value?.length) { if (searchQuery.value?.length) {
optimizedImageUrl = img("/img/haditha/sub-header-bgi.png", { optimizedImageUrl = img("/img/haditha/sub-header-bgi.webp", {
quality: 80, quality: 80,
}); });
@ -86,11 +86,11 @@ const SearchList = defineAsyncComponent(() =>
v-if="searchQuery?.length == 0" v-if="searchQuery?.length == 0"
class="flex justify-center flex-col items-center" class="flex justify-center flex-col items-center"
> >
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder
src="/img/haditha/logo.png" src="/img/haditha/logo.webp"
/> />
<div class="title"> <div class="title">
کاوش با کاوش با
@ -103,7 +103,7 @@ const SearchList = defineAsyncComponent(() =>
class="h-full" class="h-full"
v-if="searchQuery?.length && state.searchList?.length == 0" v-if="searchQuery?.length && state.searchList?.length == 0"
> >
<NuxtImg <img
fit="auto" fit="auto"
quality="80" quality="80"
placeholder placeholder