Browse Source

added helpers texts & template setting. fixed some bugs

master
Aleksandr Bautin 11 months ago
parent
commit
51730dc347
No known key found for this signature in database
GPG Key ID: 9B3364A12DFE9211
  1. 3
      src/api.ts
  2. 209
      src/assets/main.css
  3. 52
      src/components/DomainBar.vue
  4. 44
      src/components/DomainTable.vue
  5. 109
      src/components/SiteSettings.vue
  6. 6
      src/components/TemplatesList.vue
  7. 17
      src/components/Tooltip.vue
  8. 15
      src/components/ZonePricing.vue
  9. 87
      src/components/ZoneTable.vue
  10. 276
      src/result.ts
  11. 2
      src/router/index.ts
  12. 69
      src/views/Explore.vue
  13. 26
      src/views/Find.vue

3
src/api.ts

@ -12,16 +12,19 @@ export class Api {
public readonly tonscan_url: string;
public agorata_adnl: string =
"ed4f2afebb5e49dda9684a474c5771141be1f7d85a2fa39f1823844dd476c52d";
public readonly tonviewer_url: string;
constructor() {
if (process.env.NODE_ENV === "development") {
this.api_url = "http://localhost:5170/";
this.ton_api_url = "https://testnet.tonapi.io/v2/";
this.tonscan_url = "https://testnet.tonscan.org/";
this.tonviewer_url = "https://testnet.tonviewer.com/";
} else {
this.api_url = "https://api.agorata.io/";
this.ton_api_url = "https://tonapi.io/v2/";
this.tonscan_url = "https://tonscan.org/";
this.tonviewer_url = "https://testnet.tonviewer.com/";
}
}
}

209
src/assets/main.css

@ -1,197 +1,196 @@
@import './base.css';
@import "./base.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
#app {
margin: 0 auto;
padding: 0;
min-width: 100vw;
min-height: 100vh;
margin: 0 auto;
padding: 0;
min-width: 100vw;
min-height: 100vh;
}
a,
.green {
text-decoration: none;
color: hsl(201, 100%, 37%);
transition: 0.4s;
text-decoration: none;
color: hsl(201, 100%, 37%);
transition: 0.4s;
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0;
}
}
.b {
border: 2px solid transparent;
border-radius: 5px;
padding: 16px 26px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 1.55rem;
margin: 4px 2px;
cursor: pointer;
font-weight: bold;
border: 2px solid transparent;
border-radius: 5px;
padding: 16px 26px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 1.55rem;
margin: 4px 2px;
cursor: pointer;
font-weight: bold;
}
@media (max-width: 800px) {
.b {
font-size: 1.4rem;
}
.b {
font-size: 1.4rem;
}
}
.b.white {
background-color: white;
color: #282e46;
background-color: white;
color: #282e46;
}
.b.white:hover {
background-color: #282e46;
color: white;
border: 2px solid white;
background-color: #282e46;
color: white;
border: 2px solid white;
}
.b.blue {
background-color: #0088cc;
color: white;
border-radius: 1rem;
background-color: #0088cc;
color: white;
border-radius: 1rem;
}
.b > img:first-child {
max-height: 1.4rem;
margin-right: 0.7rem;
max-height: 1.4rem;
margin-right: 0.7rem;
}
.b > img:not(:first-child) {
max-height: 1.4rem;
margin-left: 2rem;
max-height: 1.4rem;
margin-left: 2rem;
}
.wide.b {
min-width: 16rem;
margin: 0.5rem;
min-width: 16rem;
margin: 0.5rem;
}
/* A #edeef1 box with rounded corners with black content color and content centered vertically and horizontally */
.rbox {
background-color: #edeef1;
border-radius: 2rem;
padding: 2.5rem 32px;
text-align: center;
display: flex;
align-items: center;
place-content: center;
justify-content: center;
font-size: 1.6rem;
font-weight: bold;
color: #282e46;
min-width: 16rem;
margin: 0.5rem 3rem;
background-color: #edeef1;
border-radius: 2rem;
padding: 2.5rem 32px;
text-align: center;
display: flex;
align-items: center;
place-content: center;
justify-content: center;
font-size: 1.6rem;
font-weight: bold;
color: #282e46;
min-width: 16rem;
margin: 0.5rem 3rem;
}
/* small margin on mobile */
@media (max-width: 800px) {
.rbox {
margin: 0.5rem 0.5rem;
}
.rbox {
margin: 0.5rem 0.5rem;
}
}
.rbox > p:not(:first-child) {
margin-top: 1rem;
margin-top: 1rem;
}
.center {
display: flex;
justify-content: center;
align-items: center;
place-items: center;
flex-direction: column;
display: flex;
justify-content: center;
align-items: center;
place-items: center;
flex-direction: column;
}
.b.darkish {
background-color: #4e5a88;
color: white;
border-radius: 0.5rem;
font-size: 1rem;
background-color: #4e5a88;
color: white;
border-radius: 0.5rem;
font-size: 1rem;
}
.mono {
font-family: 'Inconsolata', monospace;
font-family: "Inconsolata", monospace;
}
:root {
--popper-theme-background-color: #fff;
--popper-theme-background-color-hover: #fff;
--popper-theme-text-color: black;
--popper-theme-border-width: 0px;
--popper-theme-border-style: solid;
--popper-theme-border-radius: 6px;
--popper-theme-padding: 4px;
--popper-theme-box-shadow: 0 6px 30px -6px rgba(0, 0, 0, 0.25);
--popper-theme-background-color: #fff;
--popper-theme-background-color-hover: #fff;
--popper-theme-text-color: black;
--popper-theme-border-width: 0px;
--popper-theme-border-style: solid;
--popper-theme-border-radius: 6px;
--popper-theme-padding: 4px;
--popper-theme-box-shadow: 0 6px 30px -6px rgba(0, 0, 0, 0.25);
}
.popper {
font-size: 0.8rem;
font-family: 'Inconsolata', monospace;
font-size: 0.8rem;
font-family: "Inconsolata", monospace;
}
.mobile-scale {
scale: 100%;
scale: 100%;
}
@media (max-width: 800px) {
.mobile-scale {
scale: 80%;
}
.mobile-scale {
scale: 80%;
}
}
.get_b {
background-color: #e36464;
color: #363e5e;
border-radius: 0.5rem;
padding: 0.2rem 0.8rem;
margin-left: 0.9rem;
cursor: pointer;
background-color: #e36464;
color: #363e5e;
border-radius: 0.5rem;
padding: 0.2rem 0.8rem;
cursor: pointer;
}
@media only screen and (max-width: 800px) {
.get_b {
margin-left: 0.1rem;
padding-left: 0.4rem;
padding-right: 0.4rem;
}
.get_b {
margin-left: 0.1rem;
padding-left: 0.4rem;
padding-right: 0.4rem;
}
}
.flex {
display: flex;
display: flex;
}
.b.back {
font-family: 'Inconsolata', monospace;
font-size: 1.5rem;
padding: 0.8rem;
font-family: "Inconsolata", monospace;
font-size: 1.5rem;
padding: 0.8rem;
}
.b.back > img {
max-height: 1rem;
margin-right: 0.4rem;
max-height: 1rem;
margin-right: 0.4rem;
}
.material-icons.language {
position:relative;
display:inline-block;
position: relative;
display: inline-block;
}
.material-icons.language:after {
content: "language";
content: "language";
}

52
src/components/DomainBar.vue

@ -1,8 +1,18 @@
<template>
<div :class="{'domain-bar': true, 'center': !has_button}">
<div class="title">Find the domain of interest in the zone:</div>
<div :class="{ 'domain-bar': true, center: !has_button }">
<div style="display: flex" class="prompt-cont">
<contenteditable :contenteditable="editable" tag="div" class="prompt" :no-nl="true" v-model="val" :no-html="true"
ref="domain_field" @returned="search()" spellcheck="false"></contenteditable>
<contenteditable
:contenteditable="editable"
tag="div"
class="prompt"
:no-nl="true"
v-model="val"
:no-html="true"
ref="domain_field"
@returned="search()"
spellcheck="false"
></contenteditable>
<div class="post-prompt">{{ zone }}</div>
</div>
<div class="search" v-if="has_button" @click="search()">Search</div>
@ -10,33 +20,33 @@
</template>
<script>
import contenteditable from 'vue-contenteditable';
import contenteditable from "vue-contenteditable";
export default {
name: "DomainBar",
components: {contenteditable},
components: { contenteditable },
props: {
zone: {
type: String,
default: ".*"
default: ".*",
},
has_button: {
type: Boolean,
default: true
default: true,
},
value: {
type: String,
default: "example"
default: "example",
},
editable: {
type: Boolean,
default: true
}
default: true,
},
},
data() {
return {
val: this.value
}
val: this.value,
};
},
mounted() {
this.$refs["domain_field"].$el.focus();
@ -44,18 +54,18 @@ export default {
methods: {
search() {
this.$emit("search", this.val);
}
},
},
watch: {
value: function (val) {
val = val.replace('\n', '');
val = val.replace("\n", "");
this.val = val;
},
val: function (val) {
this.$emit("input_d", val);
}
}
}
},
},
};
</script>
<style scoped>
@ -87,9 +97,7 @@ export default {
width: 90vw;
/* allow multiple rows with 0.5 spacing */
flex-wrap: wrap;
}
}
.prompt {
@ -131,6 +139,10 @@ export default {
}
.post-prompt {
font-family: 'Inconsolata', monospace;
font-family: "Inconsolata", monospace;
}
.title {
margin-bottom: 6px;
}
</style>

44
src/components/DomainTable.vue

@ -85,20 +85,36 @@ onMounted(async () => {
zonesAddresses.value = zones.map(({ address }) => address?.toLowerCase());
if (address.value) {
const { data } = await axios.get<{ nft_items: CollectionItem[] }>(
`${config.ton_api_url}accounts/${address.value}/nfts`
);
items.value = data.nft_items
.map((item) => ({
...item,
formattedCollectionAddress: item.collection?.address
? convertAddress(item.collection?.address).toLowerCase()
: "",
}))
.filter(({ formattedCollectionAddress }) =>
zonesAddresses.value.includes(formattedCollectionAddress)
);
const [
resultNfts = { data: { nft_items: [] } },
resultTonNfts = { data: { nft_items: [] } },
] = await Promise.all([
axios.get<{ nft_items: CollectionItem[] }>(
`${config.ton_api_url}accounts/${address.value}/nfts`
),
axios.get<{ nft_items: CollectionItem[] }>(
`${config.ton_api_url}accounts/${address.value}/nfts`,
{
params: {
collection: "EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz",
},
}
),
]);
items.value = [
...resultTonNfts.data.nft_items,
...resultNfts.data.nft_items
.map((item) => ({
...item,
formattedCollectionAddress: item.collection?.address
? convertAddress(item.collection?.address).toLowerCase()
: "",
}))
.filter(({ formattedCollectionAddress }) =>
zonesAddresses.value.includes(formattedCollectionAddress)
),
];
}
});
</script>

109
src/components/SiteSettings.vue

@ -1,24 +1,53 @@
<template>
<div class="center">
<div>Host domain by:</div>
<div class="constr-switcher">
<div
@click="constructor_site = true"
:class="{ inactive: !constructor_site, 'constr-switch': true }"
>
By template
Selected template
</div>
<div
@click="constructor_site = false"
:class="{ inactive: constructor_site, 'constr-switch': true }"
>
Host your own
Custom server
</div>
</div>
<div class="adnl-label" v-if="!constructor_site">
You can use a custom server with an address in ADNL, TON's networking
layer
<Tooltip position="top" :width="300" class="adnl-tooltip">
<template v-slot:content>
The easiest way to set up an ADNL server is to use
<a href="https://github.com/tonwhales/ton-proxy" target="_blank"
>TON Proxy</a
>
</template>
<svg
width="24px"
height="24px"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 how-to-get"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"
/>
</svg>
</Tooltip>
</div>
<div id="site_input" class="rec-field" v-if="!constructor_site">
<div style="display: flex; width: 100%">
<p style="width: 9rem">Site:</p>
<div class="adnl-panel">
<p class="label">Site:</p>
<contenteditable
class="record-inp site-record-field"
class="record-inp site-record-field adnl-input"
tag="div"
:no-hl="true"
:no-html="true"
@ -27,14 +56,13 @@
></contenteditable>
<div
:class="{
'record-submit': true,
get_b: true,
inactive: !siteChanged,
signing: signingSite,
}"
@click="$emit('save')"
>
<template v-if="!signingSite">Save</template>
<template v-if="!signingSite">Save and host</template>
<template v-else>
<Socket
secondary-color="#a7aab3"
@ -50,7 +78,12 @@
<div v-else class="constructor-form">
<Switcher
:items="templates"
@change="(item) => (activeTemplateName = item.name)"
@change="
(item) => {
activeTemplateName = item.name;
selectTemplate(item);
}
"
:active-name="activeTemplateName"
>
<template v-slot:suffix>
@ -63,8 +96,11 @@
</template>
</Switcher>
<TemplatesList
@save-constructor="$emit('save-constructor')"
:site-changed="siteChanged"
:templates="templates"
:active-template-name="activeTemplateName"
:signing-site="signingSite"
/>
</div>
</div>
@ -78,10 +114,11 @@ import { default_links, SiteConstructorParams } from "../result";
import { link_types, link_icons } from "../result";
import TemplatesList from "./TemplatesList.vue";
import Switcher from "./Switcher.vue";
import Tooltip from "./Tooltip.vue";
export default {
name: "SiteSettings",
components: { Socket, contenteditable, TemplatesList, Switcher },
components: { Socket, contenteditable, TemplatesList, Switcher, Tooltip },
props: {
site_rec_init: {
default: null,
@ -98,6 +135,9 @@ export default {
type: Boolean,
default: false,
},
templateId: {
type: String,
},
},
data() {
let site_rec = this.site_rec_init;
@ -141,6 +181,9 @@ export default {
},
deep: true,
},
templateId() {
if (this.templateId) this.activeTemplateName = this.templateId;
},
},
computed: {
site_rec_patched() {
@ -150,6 +193,9 @@ export default {
return this.site_rec;
}
},
templateId() {
return this.templateId;
},
link_types() {
// return the types from link_types that are not in the constructor_params.contacts
return link_types.filter(
@ -181,6 +227,9 @@ export default {
name: `Template #${i + 1}`,
}));
},
selectTemplate(template) {
this.$emit("select-template", template);
},
},
mounted() {
this.setTemplates();
@ -191,12 +240,9 @@ export default {
<style scoped>
.record-inp {
padding: 0.3rem;
margin-left: 0.5rem;
border-radius: 0.3rem;
background-color: #4e5a88;
color: white;
min-width: 50vw;
width: 100%;
}
.rec-field:not(:last-child) {
@ -218,10 +264,6 @@ export default {
font-size: 1.4rem;
}
.get_b {
max-width: 8rem;
}
.constructor-form > div > div:not(:last-child) {
margin-bottom: 1rem;
}
@ -243,8 +285,8 @@ export default {
border-radius: 0.7rem;
background-color: #cdcee8;
color: #282e46;
width: 13rem;
cursor: default;
width: 250px;
}
.constr-switch:not(:last-child) {
@ -299,4 +341,37 @@ export default {
margin-top: 1rem;
margin-bottom: 1rem;
}
.how-to-get {
cursor: pointer;
}
.adnl-panel {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 10px;
& .label {
display: grid;
justify-content: end;
}
}
.adnl-label {
display: grid;
grid-auto-flow: column;
align-items: center;
gap: 6px;
margin-bottom: 6px;
}
.adnl-tooltip {
display: grid;
align-content: center;
}
.adnl-input {
width: 826px;
}
</style>

6
src/components/TemplatesList.vue

@ -65,7 +65,7 @@
}"
@click="$emit('save-constructor')"
>
<template v-if="!signingSite">Save</template>
<template v-if="!signingSite">Save and host</template>
<template v-else>
<Socket
secondary-color="#a7aab3"
@ -219,10 +219,6 @@ export default {
font-size: 1.4rem;
}
.get_b {
max-width: 8rem;
}
.constructor-form > div > div > div:not(:last-child) {
margin-bottom: 1rem;
}

17
src/components/Tooltip.vue

@ -5,7 +5,12 @@
class="tooltip-container"
>
<slot />
<div v-if="showTooltip" class="tooltip">
<div
v-if="showTooltip"
:class="{ tooltip: true, top: position === 'top' }"
:style="width ? `width: ${width}px` : ''"
>
<slot name="content" />
{{ text }}
</div>
</div>
@ -21,6 +26,12 @@ export default {
type: String,
required: true,
},
position: {
type: String,
},
width: {
type: Number,
},
},
setup(props) {
const showTooltip = ref(false);
@ -59,5 +70,9 @@ export default {
color: #fff;
padding: 0.5rem;
border-radius: 0.25rem;
&.top {
transform: translate(-50%, calc(-100% - 24px));
}
}
</style>

15
src/components/ZonePricing.vue

@ -46,21 +46,22 @@
</thead>
<tbody>
<tr>
<td>{{ "*".repeat(zone.length_1) + "." + zone.zone }}</td>
<td>{{ "*".repeat(zone.length_2) + "." + zone.zone }}</td>
<td v-if="zone.canAuction()">
<TonButton>{{ zone.price_auction_1 }}</TonButton>
<TonButton>{{ zone.price_auction_2 }}</TonButton>
</td>
<td v-if="zone.canBuy()">
<TonButton>{{ zone.price_buy_1 }}</TonButton>
<TonButton>{{ zone.price_buy_2 }}</TonButton>
<small style="display: block">to</small>
</td>
</tr>
<tr>
<td>{{ "*".repeat(zone.length_2) + "." + zone.zone }}</td>
<td>{{ "*".repeat(zone.length_1) + "." + zone.zone }}</td>
<td v-if="zone.canAuction()">
<TonButton>{{ zone.price_auction_2 }}</TonButton>
<TonButton>{{ zone.price_auction_1 }}</TonButton>
</td>
<td v-if="zone.canBuy()">
<TonButton>{{ zone.price_buy_2 }}</TonButton>
<TonButton>{{ zone.price_buy_1 }}</TonButton>
</td>
</tr>
</tbody>
@ -122,6 +123,6 @@ tr > th {
}
.get_b {
margin-top: 2.1rem;
margin-top: 3.1rem;
}
</style>

87
src/components/ZoneTable.vue

@ -1,46 +1,77 @@
<template>
<div class="center" v-if="zones === []">
<RotateSquare2 style="width: 5rem; height: 5rem; margin-top: 3rem;"/>
<div class="center" v-if="!zones.length">
<RotateSquare2 style="width: 5rem; height: 5rem; margin-top: 3rem" />
</div>
<div style="overflow-x: auto; max-width: 98vw;" v-else>
<div style="overflow-x: auto; max-width: 98vw" v-else>
<table class="table_outer">
<thead class="table_header">
<tr>
<th>Domain</th>
<th>Terms</th>
</tr>
<tr>
<th>Zone</th>
<th>
Prices
<Tooltip style="display: inline" text="Depends on length">
<svg
style="padding-top: 4px"
width="24px"
height="24px"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 how-to-get"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"
/>
</svg>
</Tooltip>
</th>
</tr>
</thead>
<tbody class="table_content">
<tr v-for="zone in zones" :key="zone.zone">
<td class="zone">
<router-link :to="{name: 'GetZ', params: {zone: zone.zone}}" style="color: white">
{{ zone.zone }}
</router-link>
</td>
<td style="display: flex; justify-content: flex-end">
<ZonePricing :zone="zone"/>
</td>
</tr>
<tr v-for="zone in zones" :key="zone.zone">
<td class="zone">
<router-link
:to="{
name: 'GetZ',
params: { zone: zone.zone, domain_init: domain },
}"
style="color: white"
>
{{ zone.zone }}
</router-link>
</td>
<td style="display: flex; justify-content: flex-end">
<ZonePricing :zone="zone" />
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import {Zone} from "../zone";
import { Zone } from "../zone";
import ZonePricing from "./ZonePricing.vue";
import RotateSquare2 from "./RotateSquare2.vue";
import Tooltip from "./Tooltip.vue";
export default {
name: "ZoneTable",
components: {ZonePricing, RotateSquare2},
components: { ZonePricing, RotateSquare2, Tooltip },
props: {
zones: {
type: Array[Zone],
default: []
}
default: [],
},
domain: {
type: String,
},
},
}
};
</script>
<style scoped>
@ -74,7 +105,8 @@ thead > tr {
display: flex;
}
tr > :last-child { /* place it on the right */
tr > :last-child {
/* place it on the right */
justify-content: flex-end;
text-align: center;
width: 100%;
@ -84,7 +116,8 @@ tr > th:first-child {
text-align: center;
}
tr > :first-child { /* place it on the left */
tr > :first-child {
/* place it on the left */
text-align: left;
}
@ -98,4 +131,10 @@ tr:not(:first-child) > td {
padding: 1rem;
min-height: 10rem;
}
.ton-coin-icon {
margin-left: 4px;
width: 15px;
height: 15px;
}
</style>

276
src/result.ts

@ -1,68 +1,96 @@
// import {call_api} from "@/api";
import {Zone} from "@/zone";
import type {Message} from "@/utils";
import type {Collection} from "@/collection";
import {call_api} from "@/api";
import { Zone } from "@/zone";
import type { Message } from "@/utils";
import type { Collection } from "@/collection";
import { call_api } from "@/api";
// let ex_collection = () => new Collection("example.ton", "Example collection");
export class Result {
domain: string;
buy_price?: number;
auction_price?: number;
owner?: string;
collection_required: Collection | null;
condition_fulfilled: boolean | null = null;
buy_msg: Message | null = null;
content_msg: Message | null = null;
nft_info?: any;
constructor(domain: string, buy_price?: number, auction_price?: number, owner?: string, collection_required: Collection | null = null, condition_fulfilled: boolean | null = null, buy_msg: Message | null = null, content_msg: Message | null = null, nft_info?: any) {
this.domain = domain;
this.buy_price = buy_price;
this.auction_price = auction_price;
this.owner = owner;
this.collection_required = collection_required;
this.condition_fulfilled = condition_fulfilled;
this.buy_msg = buy_msg;
this.content_msg = content_msg;
this.nft_info = nft_info;
}
static fromBackend(data: any): Result {
let domain = data.domain;
let buy_price = data.buy_price;
let auction_price = data.auction_price;
let owner = data.owner;
let collection_required = data.collection_required;
let condition_fulfilled = data.condition_fulfilled;
let buy_msg = data.buy_msg;
let content_msg = data.content_msg;
let nft_info = data.nft_info;
return new Result(domain, buy_price, auction_price, owner, collection_required, condition_fulfilled, buy_msg, content_msg, nft_info);
}
getRouteParams(): any {
return {
domain_init: /* domain up to . */ this.domain.split('.')[0],
domain: /* domain up to . */ this.domain.split('.')[0],
zone: /* domain after . */ this.domain.split('.').slice(1).join('.')
}
}
canAuction(): boolean {
return this.auction_price !== undefined && this.auction_price !== null && this.owner === undefined;
}
canBuy(): boolean {
return this.buy_price !== undefined && this.buy_price !== null && this.owner === undefined;
}
zone(): string {
return this.domain.split('.').slice(1).join('.');
}
domain: string;
buy_price?: number;
auction_price?: number;
owner?: string;
collection_required: Collection | null;
condition_fulfilled: boolean | null = null;
buy_msg: Message | null = null;
content_msg: Message | null = null;
nft_info?: any;
constructor(
domain: string,
buy_price?: number,
auction_price?: number,
owner?: string,
collection_required: Collection | null = null,
condition_fulfilled: boolean | null = null,
buy_msg: Message | null = null,
content_msg: Message | null = null,
nft_info?: any
) {
this.domain = domain;
this.buy_price = buy_price;
this.auction_price = auction_price;
this.owner = owner;
this.collection_required = collection_required;
this.condition_fulfilled = condition_fulfilled;
this.buy_msg = buy_msg;
this.content_msg = content_msg;
this.nft_info = nft_info;
}
static fromBackend(data: any): Result {
let domain = data.domain;
let buy_price = data.buy_price;
let auction_price = data.auction_price;
let owner = data.owner;
let collection_required = data.collection_required;
let condition_fulfilled = data.condition_fulfilled;
let buy_msg = data.buy_msg;
let content_msg = data.content_msg;
let nft_info = data.nft_info;
return new Result(
domain,
buy_price,
auction_price,
owner,
collection_required,
condition_fulfilled,
buy_msg,
content_msg,
nft_info
);
}
getRouteParams(): any {
return {
domain_init: /* domain up to . */ this.domain.split(".")[0],
domain: /* domain up to . */ this.domain.split(".")[0],
zone: /* domain after . */ this.domain.split(".").slice(1).join("."),
};
}
canAuction(): boolean {
return (
this.auction_price !== undefined &&
this.auction_price !== null &&
this.owner === undefined
);
}
canBuy(): boolean {
return (
this.buy_price !== undefined &&
this.buy_price !== null &&
this.owner === undefined
);
}
zone(): string {
return this.domain.split(".").slice(1).join(".");
}
}
// const sleep = (milliseconds: number) => {
@ -70,8 +98,8 @@ export class Result {
// }
export async function get_search_results(query: string) {
return Result.fromBackend(await call_api('find/' + query));
/*await sleep(200);
return Result.fromBackend(await call_api("find/" + query));
/*await sleep(200);
return [
new Result(query + '.ton', 5, 3),
new Result(query + '.ton', 1),
@ -81,8 +109,8 @@ export async function get_search_results(query: string) {
}
export async function get_domain_result(domain: string, _address?: string) {
return Result.fromBackend(await call_api('get/' + domain));
/*await sleep(100);
return Result.fromBackend(await call_api("get/" + domain));
/*await sleep(100);
if (domain === 'test.ton') {
return new Result(domain);
}
@ -105,13 +133,27 @@ export async function get_domain_result(domain: string, _address?: string) {
}
export async function get_records(address: string) {
return await call_api('records/' + address);
return await call_api("records/" + address);
}
export async function get_zones() {
let zones_back = await call_api('zones');
return zones_back.map((zone: any) => new Zone(zone.zone, zone.price_buy_1, zone.price_buy_2, zone.collection_required, zone.price_auction_1, zone.price_auction_2, zone.min_length, zone.length_1, zone.length_2, zone.address));
/*await sleep(10);
let zones_back = await call_api("zones");
return zones_back.map(
(zone: any) =>
new Zone(
zone.zone,
zone.price_buy_1,
zone.price_buy_2,
zone.collection_required,
zone.price_auction_1,
zone.price_auction_2,
zone.min_length,
zone.length_1,
zone.length_2,
zone.address
)
);
/*await sleep(10);
return [
new Zone("example.ton", 3, 5, ex_collection()),
new Zone("agorata.ton", 3, 5),
@ -119,54 +161,78 @@ export async function get_zones() {
}
export class TonLink {
address: string;
sum?: number;
message: string;
constructor(address: string, message: string, sum?: number) {
this.address = address;
this.sum = sum;
this.message = message;
}
getLink(): string {
// todo: use tonapi to run dnsresolve on the address (or move the task to the backend) - domains don't work well in links
let link = `ton://transfer/${this.address}?message=${this.message}`;
if (this.sum !== undefined) {
link += `&amount=${this.sum}`;
}
return link;
address: string;
sum?: number;
message: string;
constructor(address: string, message: string, sum?: number) {
this.address = address;
this.sum = sum;
this.message = message;
}
getLink(): string {
// todo: use tonapi to run dnsresolve on the address (or move the task to the backend) - domains don't work well in links
let link = `ton://transfer/${this.address}?message=${this.message}`;
if (this.sum !== undefined) {
link += `&amount=${this.sum}`;
}
return link;
}
}
// Get the link for buying a domain
export function get_ton_link(res: Result) {
return new TonLink(res.zone(), 'b/' + res.domain, res.buy_price!);
return new TonLink(res.zone(), "b/" + res.domain, res.buy_price!);
}
export let link_types = ['telegram', /*'website',*/ 'getgems', 'email'];
export let link_icons = {'telegram': 'fab fa-telegram', 'website': 'material-icons language', 'getgems': 'fas fa-gem', 'email': 'fas fa-envelope'};
export let default_links = {'telegram': 'https://t.me/', 'website': 'https://', 'getgems': 'https://getgems.org/', 'email': 'example@example.org'};
export let link_types = ["telegram", /*'website',*/ "getgems", "email"];
export let link_icons = {
telegram: "fab fa-telegram",
website: "material-icons language",
getgems: "fas fa-gem",
email: "fas fa-envelope",
};
export let default_links = {
telegram: "https://t.me/",
website: "https://",
getgems: "https://getgems.org/",
email: "example@example.org",
};
export class SiteConstructorParams {
domain: string;
title: string;
description: string;
contacts: Map<string, string> = new Map<string, string>();
constructor(domain: string, title: string = '', description: string = '', contacts: Map<string, string> = new Map<string, string>()) {
this.domain = domain;
this.title = title;
this.description = description;
this.contacts = contacts;
}
copy(): SiteConstructorParams {
return new SiteConstructorParams(this.domain, this.title);
}
domain: string;
title: string;
description: string;
contacts: Map<string, string> = new Map<string, string>();
template_id: string;
constructor(
domain: string,
title: string = "",
description: string = "",
contacts: Map<string, string> = new Map<string, string>(),
template_id: string = ""
) {
this.domain = domain;
this.title = title;
this.description = description;
this.contacts = contacts;
this.template_id = template_id;
}
copy(): SiteConstructorParams {
return new SiteConstructorParams(this.domain, this.title);
}
}
export async function get_constr_params(domain: string) {
let res = await call_api('get-site-data/' + domain);
return new SiteConstructorParams(res.domain, res.title, res.description, res.contacts);
let res = await call_api("get-site-data/" + domain);
return new SiteConstructorParams(
res.domain,
res.title,
res.description,
res.contacts,
res.template_id
);
}

2
src/router/index.ts

@ -38,7 +38,7 @@ const router = createRouter({
component: () => import("../views/TonDns.vue"),
},
{
path: "/get/:zone",
path: "/get/:zone/:domain_init",
name: "GetZ",
component: () => import("../views/Get.vue"),
props: true,

69
src/views/Explore.vue

@ -42,9 +42,13 @@
</router-link>
</div>
<br />
<div>
You can associate your domain with your wallet<br />to receive
incoming transaction at the domain address
</div>
<div id="wallet_input" class="rec-field">
<div style="display: flex; width: 100%">
<p style="width: 9rem">Wallet:</p>
<div class="wallet-panel">
<p class="label">Wallet:</p>
<contenteditable
class="record-inp wallet-record-field"
tag="div"
@ -53,6 +57,15 @@
spellcheck="false"
v-model="wallet_rec"
></contenteditable>
<div
:class="`use-my-wallet ${
wallet_rec === $store.getters.address ? 'disabled' : ''
}`"
:disabled="wallet_rec === $store.getters.address"
@click="useMyWallet"
>
Use my wallet
</div>
<div
:class="{
'record-submit': true,
@ -79,8 +92,10 @@
ref="site_settings"
@save="saveSite()"
@save-constructor="saveSiteConstr()"
@select-template="selectTemplate"
@change="site_rec = $event"
@change-constructor="constructor_params = $event"
:template-id="constructor_params.template_id"
:site-changed="siteChanged"
:signing-site="signingSite"
/>
@ -116,7 +131,6 @@ import contenteditable from "vue-contenteditable";
import { call_api, call_api_post, config } from "../api";
import Socket from "../components/Socket.vue";
import SiteSettings from "../components/SiteSettings.vue";
import { mintCollection } from "@/router/routes";
export default {
name: "Explore",
@ -134,7 +148,7 @@ export default {
},
},
data() {
let saved_constructor_params = new SiteConstructorParams(
const saved_constructor_params = new SiteConstructorParams(
this.domain,
this.domain
);
@ -200,7 +214,10 @@ export default {
},
isMine() {
console.log(this.result);
return this.result.owner === this.$store.getters.address;
return (
this.result.owner === this.$store.getters.address ||
this.result.nft_info.owner.address === this.$store.getters.address
);
},
walletChanged() {
return this.records && this.wallet_rec !== this.records.wallet;
@ -234,7 +251,7 @@ export default {
messages: [
{
amount: (0.05 * 1000000000).toString(),
address: this.result.nft_info.address,
address: this.wallet_rec ?? this.result.nft_info.address,
payload: message,
},
],
@ -301,6 +318,15 @@ export default {
this.saved_constructor_params;
}
},
selectTemplate(template) {
this.constructor_params = {
...this.constructor_params,
template_id: template.name,
};
},
useMyWallet() {
this.wallet_rec = this.$store.getters.address;
},
},
watch: {
records: function (val) {
@ -363,16 +389,13 @@ export default {
.record-inp {
padding: 0.3rem;
margin-left: 0.5rem;
border-radius: 0.3rem;
background-color: #4e5a88;
color: white;
min-width: 50vw;
width: 100%;
}
.rec-field:not(:last-child) {
margin-bottom: 1rem;
margin-bottom: 4rem;
}
.get_b.inactive {
@ -418,4 +441,30 @@ export default {
align-items: center;
justify-content: center;
}
.wallet-panel {
display: grid;
grid-template-columns: auto 1fr 183px auto;
align-items: center;
gap: 10px;
& .label {
display: grid;
justify-content: end;
}
}
.use-my-wallet {
border-radius: 0.5rem;
padding: 0.2rem 0.8rem;
cursor: pointer;
background-color: #cdcee8;
color: #282e46;
&.disabled {
cursor: not-allowed;
background-color: #6a6e95;
color: #cecddb;
}
}
</style>

26
src/views/Find.vue

@ -1,9 +1,9 @@
<template>
<DarkLayout>
<div class="center">
<DomainBar :value="query" @search="search()" @input_d="query = $event"/>
<DomainBar :value="query" @search="search()" @input_d="query = $event" />
</div>
<ZoneTable :zones="zones"/>
<ZoneTable :domain="query" :zones="zones" />
</DarkLayout>
</template>
@ -11,28 +11,24 @@
import DomainBar from "../components/DomainBar.vue";
import DarkLayout from "../components/DarkLayout.vue";
import ZoneTable from "../components/ZoneTable.vue";
import {get_zones} from "../result";
import { get_zones } from "../result";
export default {
name: "Find",
data () {
data() {
return {
query: 'example',
zones: []
}
query: "example",
zones: [],
};
},
async mounted() {
this.zones = await get_zones();
},
methods: {
search() {
this.$router.push({name: 'FindQ', params: {query: this.query}});
}
this.$router.push({ name: "FindQ", params: { query: this.query } });
},
},
components: {ZoneTable, DarkLayout, DomainBar}
}
components: { ZoneTable, DarkLayout, DomainBar },
};
</script>
<style scoped>
</style>
Loading…
Cancel
Save