Compare commits

..

No commits in common. 'master' and 'vue' have entirely different histories.
master ... vue

  1. 19
      .idea/workspace.xml
  2. 1
      package-lock.json
  3. 2
      package.json
  4. 2
      public/tonconnect-manifest.json
  5. 71
      src/api.ts
  6. 3
      src/assets/icons/globe.svg
  7. 3
      src/assets/icons/link.svg
  8. 209
      src/assets/main.css
  9. 98
      src/components/DnD.vue
  10. 54
      src/components/DomainBar.vue
  11. 198
      src/components/DomainTable.vue
  12. 31
      src/components/GetDomainBtn.vue
  13. 81
      src/components/Header.vue
  14. 317
      src/components/SiteSettings.vue
  15. 56
      src/components/Switcher.vue
  16. 294
      src/components/TemplatesList.vue
  17. 10
      src/components/TonButton.vue
  18. 78
      src/components/Tooltip.vue
  19. 21
      src/components/WhiteLayout.vue
  20. 110
      src/components/ZonePricing.vue
  21. 92
      src/components/ZoneTable.vue
  22. 182
      src/main.ts
  23. 293
      src/result.ts
  24. 153
      src/router/index.ts
  25. 3
      src/router/routes.ts
  26. 24
      src/types.ts
  27. 134
      src/views/AddTemplate.vue
  28. 67
      src/views/Checkout.vue
  29. 363
      src/views/Explore.vue
  30. 26
      src/views/Find.vue
  31. 42
      src/views/Get.vue
  32. 42
      src/views/IHave.vue
  33. 35
      src/views/IKnow.vue
  34. 15
      src/views/Landing.vue
  35. 139
      src/views/MintCollection.vue
  36. 18
      src/views/MyDomains.vue
  37. 26
      src/views/TonDns.vue
  38. 45
      src/views/TonWeb.vue
  39. 15
      vite.config.ts

19
.idea/workspace.xml

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="ddb8afd5-d3ba-47b1-b6d0-227403f1abf7" name="Changes" comment="Fixed the contact editor">
<list default="true" id="ddb8afd5-d3ba-47b1-b6d0-227403f1abf7" name="Changes" comment="Updated the address">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/SiteSettings.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/SiteSettings.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/views/Explore.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/views/Explore.vue" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -102,7 +104,7 @@
<workItem from="1678453431365" duration="7802000" />
<workItem from="1678966587213" duration="1715000" />
<workItem from="1679134080413" duration="6305000" />
<workItem from="1679494839005" duration="12422000" />
<workItem from="1679494839005" duration="10779000" />
</task>
<task id="LOCAL-00001" summary="Wrote the landing">
<created>1670844191163</created>
@ -286,14 +288,7 @@
<option name="project" value="LOCAL" />
<updated>1679234696595</updated>
</task>
<task id="LOCAL-00027" summary="Fixed the contact editor">
<created>1679584324701</created>
<option name="number" value="00027" />
<option name="presentableId" value="LOCAL-00027" />
<option name="project" value="LOCAL" />
<updated>1679584324701</updated>
</task>
<option name="localTasksCounter" value="28" />
<option name="localTasksCounter" value="27" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@ -311,6 +306,7 @@
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="TON Web page" />
<MESSAGE value="Adaptivity + more assets" />
<MESSAGE value="DarkLayout + Domain Bar" />
<MESSAGE value="Started writing ZoneTable + improvements to the search bar" />
@ -335,7 +331,6 @@
<MESSAGE value="Adding links (contacts)" />
<MESSAGE value="Fix domain result when it's not available + remove failed icon" />
<MESSAGE value="Updated the address" />
<MESSAGE value="Fixed the contact editor" />
<option name="LAST_COMMIT_MESSAGE" value="Fixed the contact editor" />
<option name="LAST_COMMIT_MESSAGE" value="Updated the address" />
</component>
</project>

1
package-lock.json generated

@ -14,7 +14,6 @@
"bulma": "^0.9.4",
"qr-code-styling": "^1.6.0-rc.1",
"sass": "^1.56.2",
"semver": "^7.3.8",
"tonweb": "^0.0.59",
"vue": "^3.2.45",
"vue-contenteditable": "^4.1.0",

2
package.json

@ -16,12 +16,12 @@
"bulma": "^0.9.4",
"qr-code-styling": "^1.6.0-rc.1",
"sass": "^1.56.2",
"semver": "^7.3.8",
"tonweb": "^0.0.59",
"vue": "^3.2.45",
"vue-contenteditable": "^4.1.0",
"vue-router": "^4.1.6",
"vue3-popper": "^1.5.0",
"semver": "^7.3.8",
"vuex": "^4.0.2"
},
"devDependencies": {

2
public/tonconnect-manifest.json

@ -1,5 +1,5 @@
{
"url": "https://tonski-an7.pages.dev",
"url": "https://agorata.io/",
"name": "Agorata",
"iconUrl": "https://front.agorata.io/favicon.png"
}

71
src/api.ts

@ -1,71 +1,30 @@
import axios from "axios";
import axios from 'axios'
declare var process: {
declare var process : {
env: {
NODE_ENV: string;
};
};
NODE_ENV: string
}
}
export class Api {
public readonly api_url: string;
public readonly ton_api_url: string;
public readonly tonscan_url: string;
public agorata_adnl: string =
"ed4f2afebb5e49dda9684a474c5771141be1f7d85a2fa39f1823844dd476c52d";
public readonly tonviewer_url: string;
public readonly api_url: string;
public agorata_adnl: string = "ed4f2afebb5e49dda9684a474c5771141be1f7d85a2fa39f1823844dd476c52d";
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://agorata.io/api/";
this.ton_api_url = "https://tonapi.io/v2/";
this.tonscan_url = "https://tonscan.org/";
this.tonviewer_url = "https://testnet.tonviewer.com/";
constructor() {
if (process.env.NODE_ENV === 'development') {
this.api_url = 'http://localhost:5170/';
} else {
this.api_url = 'https://api.agorata.io/';
}
}
}
}
export const config = new Api();
export async function call_api(url: string) {
return (await axios.get(config.api_url + url)).data;
return (await axios.get(config.api_url + url)).data;
}
export async function call_api_post(url: string, data: any) {
return (await axios.post(config.api_url + url, data)).data;
}
export async function get_templates(url: string) {
return await new Promise((resolve) =>
resolve([
{
title: "",
description: "",
links: [{ telegram: "", mail: "", site: "" }],
preview: "https://api.agorata.io/static/mountain.jpg",
},
{
title: "",
description: "",
links: [{ telegram: "", mail: "", site: "" }],
preview: "https://api.agorata.io/static/mountain.jpg",
},
{
title: "",
description: "",
links: [{ telegram: "", mail: "", site: "" }],
preview: "https://api.agorata.io/static/mountain.jpg",
},
// {
// title: "",
// description: "",
// links: [{ telegram: "", mail: "", site: "" }],
// preview: "https://api.agorata.io/static/mountain.jpg",
// },
])
);
return (await axios.post(config.api_url + url, data)).data;
}

3
src/assets/icons/globe.svg

@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 12C21 16.9706 16.9706 21 12 21M21 12C21 7.02944 16.9706 3 12 3M21 12H3M12 21C7.02944 21 3 16.9706 3 12M12 21C13.6569 21 15 16.9706 15 12C15 7.02944 13.6569 3 12 3M12 21C10.3431 21 9 16.9706 9 12C9 7.02944 10.3431 3 12 3M3 12C3 7.02944 7.02944 3 12 3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 455 B

3
src/assets/icons/link.svg

@ -1,3 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 2C12.4477 2 12 1.55228 12 1C12 0.447715 12.4477 0 13 0H19C19.5523 0 20 0.447715 20 1V7C20 7.55228 19.5523 8 19 8C18.4477 8 18 7.55228 18 7V3.41421L8.70711 12.7071C8.31658 13.0976 7.68342 13.0976 7.29289 12.7071C6.90237 12.3166 6.90237 11.6834 7.29289 11.2929L16.5858 2H13ZM3 5C2.73478 5 2.48043 5.10536 2.29289 5.29289C2.10536 5.48043 2 5.73478 2 6V17C2 17.2652 2.10536 17.5196 2.29289 17.7071C2.48043 17.8946 2.73478 18 3 18H14C14.2652 18 14.5196 17.8946 14.7071 17.7071C14.8946 17.5196 15 17.2652 15 17V11C15 10.4477 15.4477 10 16 10C16.5523 10 17 10.4477 17 11V17C17 17.7957 16.6839 18.5587 16.1213 19.1213C15.5587 19.6839 14.7957 20 14 20H3C2.20435 20 1.44129 19.6839 0.87868 19.1213C0.31607 18.5587 0 17.7957 0 17V6C0 5.20435 0.31607 4.44129 0.87868 3.87868C1.44129 3.31607 2.20435 3 3 3H9C9.55229 3 10 3.44772 10 4C10 4.55228 9.55229 5 9 5H3Z" fill="currentColor" />
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

209
src/assets/main.css

@ -1,196 +1,197 @@
@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;
cursor: pointer;
background-color: #e36464;
color: #363e5e;
border-radius: 0.5rem;
padding: 0.2rem 0.8rem;
margin-left: 0.9rem;
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";
}

98
src/components/DnD.vue

@ -1,98 +0,0 @@
<template>
<div class="drag-drop-uploader">
<div class="drag-drop-area" @dragover="handleDragOver" @drop="handleDrop">
<template v-if="uploadedFiles.length === 0">
<button class="button" type="button" @click="openFileInput">
Add File
</button>
<p>...or drag and drop a file.</p>
</template>
<template v-else>
<button
class="button"
type="button"
v-if="uploadedFiles.length > 0"
@click="openFileInput"
>
Change
</button>
<p>{{ uploadedFiles.at(-1).name }}</p>
</template>
</div>
<input
type="file"
ref="fileInput"
style="display: none"
@change="handleFileInput"
multiple
/>
</div>
</template>
<script>
export default {
data() {
return {
uploadedFiles: [],
};
},
methods: {
handleDragOver(event) {
event.preventDefault();
},
handleDrop(event) {
event.preventDefault();
const files = event.dataTransfer.files;
this.uploadFiles(files);
},
openFileInput() {
this.$refs.fileInput.click();
},
handleFileInput(event) {
const files = event.target.files;
this.uploadFiles(files);
},
uploadFiles(files) {
console.log(files);
for (let i = 0; i < files.length; i++) {
this.uploadedFiles.push(files[i]);
}
},
change() {
this.openFileInput();
},
},
};
</script>
<style scoped>
.drag-drop-uploader {
margin: 0 auto;
}
.drag-drop-area {
display: grid;
align-content: center;
border: 2px dashed #ccc;
border-radius: 10px;
padding: 20px;
text-align: center;
}
ul {
margin-top: 20px;
}
.button {
justify-self: center;
border: 0;
border-radius: 0.5rem;
height: 3rem;
background-color: #e36464;
color: #363e5e;
font-size: 1.3rem;
font-weight: 500;
padding: 0 1rem;
cursor: pointer;
}
</style>

54
src/components/DomainBar.vue

@ -1,18 +1,8 @@
<template>
<div class="title">Find the domain of interest in the zone:</div>
<div :class="{ 'domain-bar': true, center: !has_button }">
<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>
@ -20,33 +10,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();
@ -54,18 +44,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>
@ -97,7 +87,9 @@ export default {
width: 90vw;
/* allow multiple rows with 0.5 spacing */
flex-wrap: wrap;
}
}
.prompt {
@ -139,10 +131,6 @@ export default {
}
.post-prompt {
font-family: "Inconsolata", monospace;
}
.title {
margin-bottom: 6px;
font-family: 'Inconsolata', monospace;
}
</style>
</style>

198
src/components/DomainTable.vue

@ -1,198 +0,0 @@
<template>
<table class="table_outer">
<tbody class="table_content">
<tr v-for="item in items">
<td>
<div style="display: flex; align-items: center; gap: 12px">
<img
style="border-radius: 10px"
width="50"
height="50"
:src="item.previews[1].url"
:alt="item?.metadata?.name"
/>
<Tooltip text="View in explorer">
<a
target="_blank"
:href="`${config.tonscan_url}address/${item?.address}`"
>
<img src="@/assets/icons/link.svg" />
</a>
</Tooltip>
<span
style="
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
"
>
{{ item?.metadata?.name }}
</span>
</div>
</td>
<td>
<Tooltip text="Go to site">
<a target="_blank" :href="item?.dns">
<img
src="@/assets/icons/globe.svg"
style="display: flex"
cursor="pointer"
/>
</a>
</Tooltip>
</td>
<td>
<button @click="onManageClick(item)" class="button">Manage</button>
</td>
</tr>
</tbody>
</table>
<div class="center">
<RotateSquare2
v-if="isLoading"
style="width: 5rem; height: 5rem; margin-top: 3rem"
/>
<p v-if="error">{{ error }}</p>
</div>
</template>
<script setup lang="ts">
import type { CollectionItem } from "@/types";
import axios, { AxiosError } from "axios";
import { computed, onMounted, ref, watch } from "vue";
import Tooltip from "./Tooltip.vue";
import { useRouter } from "vue-router";
import { get_zones } from "@/result";
import type { Zone } from "@/zone";
import { convertAddress } from "@/utils";
import { useStore } from "vuex";
import { config } from "@/api";
import RotateSquare2 from "../components/RotateSquare2.vue";
const store = useStore();
const { push } = useRouter();
const onManageClick = (item: CollectionItem) => {
push({ name: "Explore", params: { domain: item.metadata.name } });
};
const zonesAddresses = ref<(string | undefined)[]>([]);
const items = ref<CollectionItem[]>([]);
const isLoading = ref(false);
const error = ref("");
const address = computed(() =>
store.getters.address ? convertAddress(store.getters.address) : ""
);
const fetchDomains = async () => {
if (address.value) {
isLoading.value = true;
const zones = (await get_zones()) as Zone[];
zonesAddresses.value = zones.map(({ address }) => address?.toLowerCase());
try {
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)
),
];
} catch (e) {
if ((e as AxiosError).response?.status) {
error.value = "You have made too many requests, please try again later";
console.log("too many requests");
}
} finally {
isLoading.value = false;
}
}
};
onMounted(fetchDomains);
watch(address, fetchDomains);
</script>
<style scoped>
.table_outer {
background-color: #4e5a88;
border-radius: 0.5rem;
}
td {
padding: 8px 16px;
}
td:first-child {
max-width: 400px;
}
a {
color: white;
}
@media only screen and (max-width: 800px) {
td.zone {
font-size: 1.1rem;
}
}
th {
padding: 4px 8px;
}
.table_content {
padding: 1rem;
min-height: 10rem;
}
.button {
border: 0;
border-radius: 0.5rem;
height: 3rem;
background-color: #e36464;
display: flex;
justify-content: center;
align-items: center;
place-items: center;
color: #363e5e;
font-size: 1.3rem;
font-weight: 500;
padding: 0 1rem;
cursor: pointer;
margin-left: 1rem;
margin-top: 1rem;
margin-bottom: 1rem;
}
</style>

31
src/components/GetDomainBtn.vue

@ -1,16 +1,14 @@
<template>
<div class="get-domain">
Get<br />
Get<br/>
<p class="domain">{{ domain }}</p>
for<br />
for<br/>
<span class="price">
{{ price }}
<img src="@/assets/icons/ton_bottom.svg" class="ton_img" alt="TON" />
<img src="@/assets/icons/ton_bottom.svg" class="ton_img" alt="TON"/>
</span>
<template
v-if="collection_required !== undefined && collection_required !== null"
>
<br />
<template v-if="collection_required !== undefined && collection_required !== null">
<br/>
<div class="collection_required">
for owners of
<div class="collection">{{ collection_required.name }}</div>
@ -25,18 +23,18 @@ export default {
props: {
domain: {
type: String,
required: true,
required: true
},
price: {
type: Number,
required: true,
required: true
},
collection_required: {
// type: Collection | null,
default: null,
},
},
};
default: null
}
}
}
</script>
<style scoped>
@ -56,11 +54,12 @@ export default {
width: 90vw;
/* allow multiple rows with 0.5 spacing */
flex-wrap: wrap;
}
}
.domain {
font-family: "Inconsolata", monospace;
font-family: 'Inconsolata', monospace;
font-weight: bold;
font-size: 2.5rem;
}
@ -74,7 +73,7 @@ export default {
.price {
font-size: 3rem;
font-weight: bold;
font-family: "Inconsolata", monospace;
font-family: 'Inconsolata', monospace;
}
.price > img {
@ -88,4 +87,4 @@ export default {
background-color: white;
color: #0088cc;
}
</style>
</style>

81
src/components/Header.vue

@ -1,9 +1,7 @@
<template>
<header>
<div class="wrapper flex" style="width: 100%">
<router-link to="/">
<img class="logo" src="@/assets/logo.png" alt="Agorata" />
</router-link>
<router-link to="/"><img class="logo" src="@/assets/logo.png" alt="Agorata"/></router-link>
<nav style="display: flex">
<!-- <router-link to="/">Home</router-link>-->
<!-- <router-link to="/about">About</router-link>-->
@ -13,27 +11,13 @@
</div>
<!-- right-aligned login button -->
<div class="login-b" v-if="showLogin">
<button
class="b blue wide"
@click="$emit('login-modal')"
v-if="!$store.getters.is_connected"
>
<button class="b blue wide" @click="$emit('login-modal')" v-if="!$store.getters.is_connected">
<span>Login</span>
</button>
<div v-else>
<div class="address">
<span>{{ address }}</span>
<img
src="@/assets/icons/logout.svg"
alt="Logout"
@click="$emit('logout')"
class="logout-icon"
/>
<div class="menu">
<router-link to="/my-domains">
<span class="menu-item">My domains</span>
</router-link>
</div>
<span>{{address}}</span>
<img src="@/assets/icons/logout.svg" alt="Logout" @click="$emit('logout')" class="logout-icon"/>
</div>
</div>
</div>
@ -43,36 +27,33 @@
</template>
<script>
import { convertAddress } from "../utils.ts";
import {convertAddress} from "../utils.ts";
export default {
name: "Header",
props: {
showLogin: {
type: Boolean,
default: true,
},
default: true
}
},
computed: {
address() {
if (!this.$store.getters.is_connected) {
return "";
return '';
}
let address_raw = this.$store.getters.address;
let address_raw = this.$store.getters.address;
if (address_raw === undefined) {
return "";
return '';
}
let address = convertAddress(address_raw);
return address.slice(0, 5) + "..." + address.slice(-4);
},
},
async mounted() {
this.$store.getters.connector.restoreConnection();
},
};
return address.slice(0, 5) + '...' + address.slice(-4);
}
}
}
</script>
<style lang="scss" scoped>
<style scoped>
header {
line-height: 1.5;
max-height: 10rem;
@ -97,40 +78,12 @@ nav {
text-align: center;
}
.menu {
background-color: #4e5a88;
position: absolute;
bottom: -30px;
right: 0;
text-align: center;
left: 0;
display: none;
border-bottom-left-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
border-top: 1px solid #363e5e;
}
.menu-item {
display: block;
padding: 4px 16px;
color: white;
}
.address {
color: white;
background-color: #4e5a88;
border-radius: 0.5rem;
padding: 0.5rem 1rem;
font-family: "Inconsolata", monospace;
position: relative;
&:hover {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
& .menu {
display: block;
}
}
font-family: 'Inconsolata', monospace;
}
@media (min-width: 1024px) {
@ -174,4 +127,4 @@ nav {
display: flex;
align-items: center;
}
</style>
</style>

317
src/components/SiteSettings.vue

@ -1,192 +1,127 @@
<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 }"
>
Selected template
<div @click="constructor_site = true"
:class="{'inactive': !constructor_site, 'constr-switch': true}">
By template
</div>
<div
@click="constructor_site = false"
:class="{ inactive: constructor_site, 'constr-switch': true }"
>
Custom server
<div @click="constructor_site = false"
:class="{'inactive': constructor_site, 'constr-switch': true}">
Host your own
</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 class="adnl-panel">
<p class="label">Site:</p>
<contenteditable
class="record-inp site-record-field adnl-input"
tag="div"
:no-hl="true"
:no-html="true"
spellcheck="false"
v-model="site_rec"
></contenteditable>
<div
:class="{
get_b: true,
inactive: inactiveSave,
signing: signingSite,
}"
@click="$emit('save')"
>
<template v-if="!signingSite">Save and host</template>
<div style="display: flex; width: 100%">
<p style="width: 9rem;">Site:</p>
<contenteditable class="record-inp site-record-field" tag="div" :no-hl="true" :no-html="true" spellcheck="false"
v-model="site_rec"></contenteditable>
<div :class="{'record-submit': true, 'get_b': true, 'inactive': !siteChanged, 'signing': signingSite}"
@click="$emit('save')">
<template v-if="!signingSite">Save</template>
<template v-else>
<Socket
secondary-color="#a7aab3"
color="#282e46"
size="50px"
style="min-width: 3rem"
/>
<Socket secondary-color="#a7aab3" color="#282e46" size="50px" style="min-width: 3rem"/>
Confirm in the wallet...
</template>
</div>
</div>
</div>
<div v-else class="constructor-form">
<Switcher
:items="templates"
@change="
(item) => {
activeTemplateName = item.name;
selectTemplate(item);
}
"
:active-name="activeTemplateName"
>
<template v-slot:suffix>
<router-link
:to="`/add-template/${$route?.params?.domain}`"
class="button"
>
Add
</router-link>
</template>
</Switcher>
<TemplatesList
:site-changed="siteChanged"
:templates="templates"
:active-template-name="activeTemplateName"
:constructor-changed="constructorChanged"
:site_rec_init="site_rec_init"
:signing-site="signingSite"
@change="
site_rec = $event;
$emit('change', site_rec_patched);
"
@change-constructor="
constructor_params = $event;
$emit('change-constructor', constructor_params);
"
@save="$emit('save', $event)"
@save-constructor="$emit('save-constructor', $event)"
/>
<div style="display: flex; width: 100%">
<p style="width: 9rem;">Title:</p>
<contenteditable class="record-inp" tag="div" :no-hl="true" :no-html="true" spellcheck="false"
v-model="constructor_params.title"></contenteditable>
</div>
<div style="display: flex; width: 100%">
<p style="width: 9rem;">Description:</p>
<contenteditable class="record-inp" tag="div" :no-hl="true" :no-html="true" spellcheck="false"
v-model="constructor_params.description"></contenteditable>
</div>
<div style="display: flex; width: 100%">
<p style="width: 9rem;">Add link:</p>
<div v-for="link_type in link_types" :key="link_type" class="link-type" @click="addLink(link_type)">
<i class="link-type-icon add" :class="link_icons[link_type]"></i>
</div>
</div>
<!-- The links themselves (editing constructor_params.contacts[link_type] for each key) -->
<div v-for="contact in contacts" :key="contact[0]" class="link-type">
<div style="display: flex; width: 100%">
<!-- the icon as a label -->
<i style="width: 9rem;" class="link-type-icon" :class="link_icons[contact[0]]"></i>
<!-- editing the link - constructor_params.contacts[link_type] -->
<contenteditable class="record-inp" tag="div" :no-hl="true" :no-html="true" spellcheck="false"
v-model="constructor_params.contacts[contact[0]]"></contenteditable>
</div>
</div>
<div class="save_container center">
<div :class="{'record-submit': true, 'get_b': true, 'inactive': !siteChanged, 'signing': signingSite}"
@click="$emit('save-constructor')">
<template v-if="!signingSite">Save</template>
<template v-else>
<Socket secondary-color="#a7aab3" color="#282e46" size="50px" style="min-width: 3rem"/>
Confirm in the wallet...
</template>
</div>
</div>
</div>
</div>
</template>
<script>
import Socket from "./Socket.vue";
import contenteditable from "vue-contenteditable";
import { config, get_templates } from "../api";
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";
import contenteditable from 'vue-contenteditable';
import {config} from "../api";
import {default_links, SiteConstructorParams} from "../result";
import {link_types, link_icons} from "../result";
export default {
name: "SiteSettings",
components: { Socket, contenteditable, TemplatesList, Switcher, Tooltip },
components: {Socket, contenteditable},
props: {
site_rec_init: {
default: null,
default: null
},
siteChanged: {
type: Boolean,
default: false,
default: false
},
constructorChanged: {
type: Boolean,
default: false,
default: false
},
signingSite: {
type: Boolean,
default: false,
},
templateId: {
type: String,
default: false
},
},
data() {
let site_rec = this.site_rec_init;
if (!site_rec) {
site_rec = config.agorata_adnl;
this.$emit("change", this.site_rec);
}
let constructor_site =
site_rec.toLowerCase() === config.agorata_adnl.toLowerCase();
if (!site_rec) site_rec = config.agorata_adnl;
let constructor_site = site_rec.toLowerCase() === config.agorata_adnl.toLowerCase();
return {
site_rec,
constructor_site,
constructor_params: new SiteConstructorParams(""),
saved_constructor_params: new SiteConstructorParams(""),
contacts: [],
templates: [],
activeTemplateName: "Template #1",
};
constructor_params: new SiteConstructorParams(''),
saved_constructor_params: new SiteConstructorParams(''),
contacts: []
}
},
watch: {
site_rec_patched() {
this.$emit("change", this.site_rec_patched);
this.$emit('change', this.site_rec_patched);
},
constructor_params: {
handler: function (newVal) {
this.$emit("change-constructor", newVal);
this.constructor_params.template_id = this.activeTemplateName;
this.$emit('change-constructor', newVal);
},
deep: true,
deep: true
},
saved_constructor_params: {
handler: function (newVal, oldVal) {
if (newVal === oldVal) return;
// this.constructor_params.template_id = this.activeTemplateName;
// this.constructor_params = newVal.copy();
this.contacts = newVal.contacts ? Object.entries(newVal.contacts) : [];
this.constructor_params = newVal.copy();
this.contacts = Object.entries(newVal.contacts);
},
deep: true,
deep: true
},
contacts: {
handler: function (newVal) {
@ -195,10 +130,7 @@ export default {
this.constructor_params.contacts[contact[0]] = contact[1];
}
},
deep: true,
},
templateId() {
if (this.templateId) this.activeTemplateName = this.templateId;
deep: true
},
},
computed: {
@ -209,17 +141,9 @@ export default {
return this.site_rec;
}
},
templateId() {
return this.templateId;
},
inactiveSave() {
return !this.siteChanged && this.site_rec !== null;
},
link_types() {
// return the types from link_types that are not in the constructor_params.contacts
return link_types.filter(
(link_type) => !(link_type in this.constructor_params.contacts)
);
return link_types.filter(link_type => !(link_type in this.constructor_params.contacts));
},
link_icons() {
return link_icons;
@ -233,39 +157,25 @@ export default {
this.site_rec = site_rec;
},
addLink(link_type) {
console.log("adding link", link_type);
console.log('adding link', link_type);
// If there's already a link of this type, don't add it
if (link_type in this.constructor_params.contacts) return;
this.contacts.push([link_type, default_links[link_type]]);
this.contacts.push([link_type, default_links[link_type]])
this.constructor_params.contacts[link_type] = default_links[link_type];
},
async setTemplates() {
this.templates = await get_templates();
this.templates = this.templates.map((template, i) => ({
...template,
name: `Template #${i + 1}`,
}));
},
selectTemplate(template) {
this.$emit("select-template", template);
},
},
mounted() {
this.setTemplates();
if (this.site_rec === null) {
this.site_rec = config.agorata_adnl;
this.$emit("change", this.site_rec);
}
},
};
}
}
</script>
<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) {
@ -287,7 +197,11 @@ export default {
font-size: 1.4rem;
}
.constructor-form > div > div:not(:last-child) {
.get_b {
max-width: 8rem;
}
.constructor-form > div:not(:last-child) {
margin-bottom: 1rem;
}
@ -299,6 +213,7 @@ export default {
margin-bottom: 1rem;
border-radius: 1.3rem;
padding: 1rem;
max-width: 35rem;
background-color: #4e5a88;
}
@ -308,8 +223,8 @@ export default {
border-radius: 0.7rem;
background-color: #cdcee8;
color: #282e46;
width: 13rem;
cursor: default;
width: 250px;
}
.constr-switch:not(:last-child) {
@ -324,7 +239,7 @@ export default {
}
.site-record-field {
font-family: "Inconsolata", monospace;
font-family: 'Inconsolata', monospace;
}
.link-type:not(:last-child) {
@ -343,58 +258,4 @@ export default {
.link-type-icon {
font-size: 2.5rem;
}
.button {
border: 0;
border-radius: 0.5rem;
height: 3rem;
background-color: #e36464;
display: flex;
justify-content: center;
align-items: center;
place-items: center;
justify-self: end;
min-width: 150px;
color: #363e5e;
font-size: 1.3rem;
font-weight: 500;
padding: 0 1rem;
cursor: pointer;
margin-left: 1rem;
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>
</style>

56
src/components/Switcher.vue

@ -1,56 +0,0 @@
<template>
<div class="constr-switcher">
<div
v-for="item in items"
@click="$emit('change', item)"
:class="['constr-switch', { inactive: activeName !== item.name }]"
>
{{ item.name }}
</div>
<slot name="suffix" />
</div>
</template>
<script setup lang="ts">
type Item = { name: string };
defineProps<{ items: Item[]; activeName: string }>();
defineEmits<{
(e: "change", item: Item): void;
}>();
</script>
<style scoped>
/* The switcher with two buttons - create from template or use custom */
.constr-switcher {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
border-radius: 1.3rem;
padding: 1rem;
background-color: #4e5a88;
}
/* The button inside the switcher */
.constr-switch {
padding: 0.5rem 1rem;
border-radius: 0.7rem;
background-color: #cdcee8;
color: #282e46;
width: 13rem;
cursor: default;
}
.constr-switch:not(:last-child) {
margin-right: 1rem;
}
.constr-switch.inactive {
/*background-color: #5d5f79;*/
background-color: #6a6e95;
color: #cecddb;
cursor: pointer;
}
</style>

294
src/components/TemplatesList.vue

@ -1,294 +0,0 @@
<template>
<div v-for="template in templates">
<div v-if="template.name === activeTemplateName">
<img class="preview" width="280" height="280" :src="template.preview" />
<div style="display: flex; width: 100%">
<p style="width: 9rem">Title:</p>
<contenteditable
class="record-inp"
tag="div"
:no-hl="true"
:no-html="true"
spellcheck="false"
v-model="constructor_params.title"
></contenteditable>
</div>
<div style="display: flex; width: 100%">
<p style="width: 9rem">Description:</p>
<contenteditable
class="record-inp"
tag="div"
:no-hl="true"
:no-html="true"
spellcheck="false"
v-model="constructor_params.description"
></contenteditable>
</div>
<div style="display: flex; width: 100%">
<p style="width: 9rem">Picture:</p>
<contenteditable
class="record-inp"
tag="div"
:no-hl="true"
:no-html="true"
spellcheck="false"
v-model="constructor_params.picture"
></contenteditable>
</div>
<div style="display: flex; width: 100%">
<p style="width: 9rem">Add link:</p>
<div
v-for="link_type in link_types"
:key="link_type"
class="link-type"
@click="addLink(link_type)"
>
<i class="link-type-icon add" :class="link_icons[link_type]"></i>
</div>
</div>
<!-- The links themselves (editing constructor_params.contacts[link_type] for each key) -->
<div v-for="contact in contacts" :key="contact[0]" class="link-type">
<div style="display: flex; width: 100%">
<!-- the icon as a label -->
<i
style="width: 9rem"
class="link-type-icon"
:class="link_icons[contact[0]]"
></i>
<!-- editing the link - constructor_params.contacts[link_type] -->
<contenteditable
class="record-inp"
tag="div"
:no-hl="true"
:no-html="true"
spellcheck="false"
v-model="constructor_params.contacts[contact[0]]"
></contenteditable>
</div>
</div>
<div class="save_container center">
<div
:class="{
'record-submit': true,
get_b: true,
inactive: !siteChanged,
signing: signingSite,
}"
@click="$emit('save-constructor')"
>
<template v-if="!signingSite">Save and host</template>
<template v-else>
<Socket
secondary-color="#a7aab3"
color="#282e46"
size="50px"
style="min-width: 3rem"
/>
Confirm in the wallet...
</template>
</div>
</div>
</div>
</div>
</template>
<script>
import Socket from "./Socket.vue";
import contenteditable from "vue-contenteditable";
import { config } from "../api";
import { default_links, SiteConstructorParams } from "../result";
import { link_types, link_icons } from "../result";
export default {
name: "TemplatesList",
components: { Socket, contenteditable },
props: {
site_rec_init: {
default: null,
},
siteChanged: {
type: Boolean,
default: false,
},
constructorChanged: {
type: Boolean,
default: false,
},
signingSite: {
type: Boolean,
default: false,
},
templates: {
default: [],
},
activeTemplateName: "",
},
data() {
let site_rec = this.site_rec_init;
if (!site_rec) site_rec = config.agorata_adnl;
let constructor_site =
site_rec.toLowerCase() === config.agorata_adnl.toLowerCase();
return {
site_rec,
constructor_site,
constructor_params: new SiteConstructorParams(""),
saved_constructor_params: new SiteConstructorParams(""),
contacts: [],
};
},
watch: {
site_rec_patched() {
this.$emit("change", this.site_rec_patched);
},
constructor_params: {
handler: function (newVal) {
this.$emit("change-constructor", newVal);
},
deep: true,
},
saved_constructor_params: {
handler: function (newVal, oldVal) {
if (newVal === oldVal) return;
this.constructor_params = newVal.copy();
this.contacts = Object.entries(newVal.contacts);
},
deep: true,
},
contacts: {
handler: function (newVal) {
this.constructor_params.contacts = new Map();
for (let contact of newVal) {
this.constructor_params.contacts[contact[0]] = contact[1];
}
},
deep: true,
},
},
computed: {
site_rec_patched() {
if (this.constructor_site) {
return config.agorata_adnl;
} else {
return this.site_rec;
}
},
link_types() {
// return the types from link_types that are not in the constructor_params.contacts
return link_types.filter(
(link_type) => !(link_type in this.constructor_params.contacts)
);
},
link_icons() {
return link_icons;
},
// used_link_types() {
// return Object.keys(this.constructor_params.contacts);
// },
},
methods: {
set_site_rec(site_rec) {
this.site_rec = site_rec;
},
addLink(link_type) {
console.log("adding link", link_type);
// If there's already a link of this type, don't add it
if (link_type in this.constructor_params.contacts) return;
this.contacts.push([link_type, default_links[link_type]]);
this.constructor_params.contacts[link_type] = default_links[link_type];
},
},
};
</script>
<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) {
margin-bottom: 1rem;
}
.get_b.inactive {
opacity: 0.5;
cursor: default;
}
.get_b.signing {
/* Center elements inside horizontally, allow rows */
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
background-color: white;
font-size: 1.4rem;
}
.constructor-form > div > div > div:not(:last-child) {
margin-bottom: 1rem;
}
/* The switcher with two buttons - create from template or use custom */
.constr-switcher {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
border-radius: 1.3rem;
padding: 1rem;
max-width: 35rem;
background-color: #4e5a88;
}
/* The button inside the switcher */
.constr-switch {
padding: 0.5rem 1rem;
border-radius: 0.7rem;
background-color: #cdcee8;
color: #282e46;
width: 13rem;
cursor: default;
}
.constr-switch:not(:last-child) {
margin-right: 1rem;
}
.constr-switch.inactive {
/*background-color: #5d5f79;*/
background-color: #6a6e95;
color: #cecddb;
cursor: pointer;
}
.site-record-field {
font-family: "Inconsolata", monospace;
}
.link-type:not(:last-child) {
margin-right: 1rem;
}
.link-type-icon.add {
font-size: 2rem;
cursor: pointer;
}
.link-type-icon.add:hover {
color: #e88484;
}
.link-type-icon {
font-size: 2.5rem;
}
.preview {
border-radius: 1.5rem;
}
</style>

10
src/components/TonButton.vue

@ -1,14 +1,14 @@
<template>
<button class="b darkish mobile-scale">
<span><slot></slot></span>
<img src="@/assets/icons/ton_bottom.svg" alt="TON" />
<img src="@/assets/icons/ton_bottom.svg" alt="TON"/>
</button>
</template>
<script>
export default {
name: "TonButton",
};
name: "TonButton"
}
</script>
<style scoped>
@ -16,7 +16,7 @@ button.b.darkish {
width: 4rem;
height: 2rem;
padding: 0.3rem;
font-family: "Inconsolata", monospace;
font-family: 'Inconsolata', monospace;
font-weight: bold;
font-size: 1.15rem;
cursor: default;
@ -34,4 +34,4 @@ button > img:not(:first-child) {
padding: 0.15rem;
}
}
</style>
</style>

78
src/components/Tooltip.vue

@ -1,78 +0,0 @@
<template>
<div
@mouseleave="onMouseLeave"
@mouseenter="onMouseEnter"
class="tooltip-container"
>
<slot />
<div
v-if="showTooltip"
:class="{ tooltip: true, top: position === 'top' }"
:style="width ? `width: ${width}px` : ''"
>
<slot name="content" />
{{ text }}
</div>
</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "Tooltip",
props: {
text: {
type: String,
required: true,
},
position: {
type: String,
},
width: {
type: Number,
},
},
setup(props) {
const showTooltip = ref(false);
function onMouseEnter() {
showTooltip.value = true;
}
function onMouseLeave() {
showTooltip.value = false;
}
return {
showTooltip,
onMouseEnter,
onMouseLeave,
};
},
};
</script>
<style>
.tooltip-container {
position: relative;
}
.tooltip {
min-width: 100px;
font-size: large;
z-index: 1;
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 0.5rem;
border-radius: 0.25rem;
&.top {
transform: translate(-50%, calc(-100% - 24px));
}
}
</style>

21
src/components/WhiteLayout.vue

@ -1,6 +1,6 @@
<template>
<div style="width: 100vw">
<Header :show-login="false" />
<Header :show-login="false"/>
<div class="header-logo">
<slot name="header"></slot>
</div>
@ -14,8 +14,8 @@
<router-link :to="next">
<!-- "Next" button of class "bblue wide" with @/assets/icons/ton_right.svg icon on the right -->
<button class="b blue wide">
<span>{{ nexttext }}</span>
<img src="@/assets/icons/ton_right.svg" alt="Next" />
<span>{{nexttext}}</span>
<img src="@/assets/icons/ton_right.svg" alt="Next"/>
</button>
</router-link>
</slot>
@ -28,17 +28,17 @@ import Header from "../components/Header.vue";
export default {
name: "WhiteLayout",
components: { Header },
components: {Header},
props: {
next: {
type: String,
type: String
},
nexttext: {
type: String,
default: "Next",
},
},
};
default: "Next"
}
}
}
</script>
<style scoped>
@ -59,10 +59,11 @@ export default {
align-items: center;
place-items: center;
/* Center the buttons */
}
.rbox {
min-width: 90%;
margin: auto 3%;
}
</style>
</style>

110
src/components/ZonePricing.vue

@ -10,99 +10,79 @@
-->
<table>
<thead>
<tr>
<th></th>
<th v-if="zone.canAuction()">
<Popper
content="Buy on auction"
placement="left"
:hover="true"
:arrow="true"
class="mobile-scale"
>
<img
src="@/assets/icons/buy.svg"
class="buy_img"
alt="Buy on auction"
/>
</Popper>
</th>
<th v-if="zone.canBuy()">
<Popper
content="Buy instantly"
placement="right"
:hover="true"
:arrow="true"
class="mobile-scale"
>
<img
src="@/assets/icons/instant_buy.svg"
class="buy_img"
alt="Instant Buy"
/>
</Popper>
</th>
</tr>
<tr>
<th></th>
<th v-if="zone.canAuction()">
<Popper content="Buy on auction" placement="left" :hover="true" :arrow="true" class="mobile-scale">
<img src="@/assets/icons/buy.svg" class="buy_img" alt="Buy on auction"/>
</Popper>
</th>
<th v-if="zone.canBuy()">
<Popper content="Buy instantly" placement="right" :hover="true" :arrow="true" class="mobile-scale">
<img src="@/assets/icons/instant_buy.svg" class="buy_img" alt="Instant Buy"/>
</Popper>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ "*".repeat(zone.length_2) + "." + zone.zone }}</td>
<td v-if="zone.canAuction()">
<TonButton>{{ zone.price_auction_2 }}</TonButton>
</td>
<td v-if="zone.canBuy()">
<TonButton>{{ zone.price_buy_2 }}</TonButton>
<small style="display: block">to</small>
</td>
</tr>
<tr>
<td>{{ "*".repeat(zone.length_1) + "." + zone.zone }}</td>
<td v-if="zone.canAuction()">
<TonButton>{{ zone.price_auction_1 }}</TonButton>
</td>
<td v-if="zone.canBuy()">
<TonButton>{{ zone.price_buy_1 }}</TonButton>
</td>
</tr>
<tr>
<td>{{ '*'.repeat(zone.length_1) + '.' + zone.zone }}</td>
<td v-if="zone.canAuction()">
<TonButton>{{ zone.price_auction_1 }}</TonButton>
</td>
<td v-if="zone.canBuy()">
<TonButton>{{ zone.price_buy_1 }}</TonButton>
</td>
</tr>
<tr>
<td>{{ '*'.repeat(zone.length_2) + '.' + zone.zone }}</td>
<td v-if="zone.canAuction()">
<TonButton>{{ zone.price_auction_2 }}</TonButton>
</td>
<td v-if="zone.canBuy()">
<TonButton>{{ zone.price_buy_2 }}</TonButton>
</td>
</tr>
</tbody>
</table>
<router-link
:to="{ name: 'Get', params: { domain_init: 'example', zone: zone.zone } }"
>
<div v-if="has_get" class="get_b mobile-scale">Get</div>
<router-link :to="{name: 'Get', params: {domain_init: 'example', zone: zone.zone}}">
<div v-if="has_get" class="get_b mobile-scale">
Get
</div>
</router-link>
</div>
</template>
<script>
import { Zone } from "../zone";
import {Zone} from "../zone";
import Popper from "vue3-popper";
import TonButton from "./TonButton.vue";
export default {
name: "ZonePricing",
components: { TonButton, Popper },
components: {TonButton, Popper},
props: {
zone: {
type: Zone,
default: new Zone("example.ton", 3, 5),
default: new Zone("example.ton", 3, 5)
},
// Whether to show the "Get" button
has_get: {
type: Boolean,
default: true,
default: true
},
},
};
}
}
</script>
<style scoped>
tr > td:first-child {
font-family: "Inconsolata", monospace;
font-family: 'Inconsolata', monospace;
padding-right: 1rem;
padding-left: 3rem;
}
@media only screen and (max-width: 800px) {
tr > td:first-child {
font-size: 0.9rem;
@ -123,6 +103,6 @@ tr > th {
}
.get_b {
margin-top: 3.1rem;
margin-top: 2.1rem;
}
</style>
</style>

92
src/components/ZoneTable.vue

@ -1,80 +1,46 @@
<template>
<div class="center" v-if="!zones.length">
<RotateSquare2 style="width: 5rem; height: 5rem; margin-top: 3rem" />
<div class="center" v-if="zones === []">
<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>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>
<tr>
<th>Domain</th>
<th>Terms</th>
</tr>
</thead>
<tbody class="table_content">
<tr v-for="zone in zones" :key="zone.zone">
<td v-if="zone.zone !== 'ton'" class="zone">
<router-link
:to="{
name: 'GetZ',
params: { zone: zone.zone, domain_init: domain },
}"
style="color: white"
>
{{ zone.zone }}
</router-link>
</td>
<td
v-if="zone.zone !== 'ton'"
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}}" 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, Tooltip },
components: {ZonePricing, RotateSquare2},
props: {
zones: {
type: Array[Zone],
default: [],
},
domain: {
type: String,
},
default: []
}
},
};
}
</script>
<style scoped>
@ -108,8 +74,7 @@ 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%;
@ -119,8 +84,7 @@ 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;
}
@ -134,10 +98,4 @@ tr:not(:first-child) > td {
padding: 1rem;
min-height: 10rem;
}
.ton-coin-icon {
margin-left: 4px;
width: 15px;
height: 15px;
}
</style>
</style>

182
src/main.ts

@ -1,112 +1,100 @@
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createStore } from "vuex";
import TonConnect from "@tonconnect/sdk";
import { isWalletInfoInjected } from "@tonconnect/sdk";
import type {
WalletInfo,
WalletInfoInjected,
WalletInfoRemote,
} from "@tonconnect/sdk";
import "./assets/main.css";
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import {createStore} from 'vuex'
import TonConnect from '@tonconnect/sdk';
import {isWalletInfoInjected} from '@tonconnect/sdk';
import type {WalletInfo, WalletInfoInjected, WalletInfoRemote} from '@tonconnect/sdk';
import './assets/main.css'
const app = createApp(App);
const app = createApp(App)
class State {
connector: TonConnect;
walletList: WalletInfo[] = [];
initialization: Promise<void>;
connector: TonConnect;
walletList: WalletInfo[] = [];
initialization: Promise<void>;
constructor() {
this.connector = new TonConnect({
manifestUrl: "https://agorata.io/tonconnect-manifest.json",
});
this.initialization = this.initialize();
}
constructor() {
this.connector = new TonConnect({manifestUrl: 'https://front.agorata.io/tonconnect-manifest.json'});
this.initialization = this.initialize();
}
async initialize() {
await this.connector.restoreConnection();
this.walletList = await this.connector.getWallets();
}
async initialize() {
await this.connector.restoreConnection();
this.walletList = await this.connector.getWallets();
}
}
const store = createStore({
state() {
return new State();
},
mutations: {
set_connector(state, connector) {
state.connector = connector;
state() {
return new State()
},
},
getters: {
is_connected(state) {
return state.connector.connected;
},
address(state) {
if (
state.connector.account !== null &&
state.connector.account.address !== undefined
) {
return state.connector.account.address;
} else {
return "";
}
mutations: {
set_connector(state, connector) {
state.connector = connector
}
},
connector(state) {
return state.connector;
getters: {
is_connected(state) {
return state.connector.connected;
},
address(state) {
if (state.connector.account !== null && state.connector.account.address !== undefined) {
return state.connector.account.address;
} else {
return '';
}
}
},
},
actions: {
async connect_embedded({ state }) {
const walletsList = await TonConnect.getWallets();
let connector = state.connector;
const embeddedWallet = walletsList.find(
(wallet) => isWalletInfoInjected(wallet) && wallet.embedded
) as WalletInfoInjected;
console.log(walletsList, embeddedWallet);
actions: {
async connect_embedded({state}) {
const walletsList = await TonConnect.getWallets();
let connector = state.connector;
const embeddedWallet = walletsList.find(
wallet => isWalletInfoInjected(wallet) && wallet.embedded
) as WalletInfoInjected;
console.log(walletsList, embeddedWallet);
if (embeddedWallet) {
connector.connect({ jsBridgeKey: embeddedWallet.jsBridgeKey });
}
},
/* Connect embedded wallet if it exists, otherwise get connection url for a QR code unless we're already connected */
async get_connection_url({ state }): Promise<string | null> {
await state.initialization;
await this.dispatch("connect_embedded");
if (state.connector.connected) {
return null;
}
let walletList = this.state.walletList as WalletInfo[];
if (walletList.length === 0) {
return null;
}
/* iterate through wallets and do try-catch */
for (let wallet of walletList) {
try {
let wallet_r = wallet as WalletInfoRemote;
let wallet_desc = {
bridgeUrl: wallet_r.bridgeUrl,
universalLink: wallet_r.universalLink,
};
let res = await state.connector.connect(wallet_desc);
if (typeof res === "string") {
return res;
}
} catch (e) {
console.log(wallet, e);
if (embeddedWallet) {
connector.connect({jsBridgeKey: embeddedWallet.jsBridgeKey});
}
},
/* Connect embedded wallet if it exists, otherwise get connection url for a QR code unless we're already connected */
async get_connection_url({state}): Promise<string | null> {
await state.initialization;
await this.dispatch('connect_embedded');
if (state.connector.connected) {
return null;
}
let walletList = this.state.walletList as WalletInfo[];
if (walletList.length === 0) {
return null;
}
/* iterate through wallets and do try-catch */
for (let wallet of walletList) {
try {
let wallet_r = wallet as WalletInfoRemote;
let wallet_desc = {
bridgeUrl: wallet_r.bridgeUrl,
universalLink: wallet_r.universalLink
}
let res = await state.connector.connect(wallet_desc);
if (typeof res === 'string') {
return res;
}
} catch (e) {
console.log(wallet, e)
}
}
return null;
},
async disconnect({state}) {
await state.connector.disconnect();
}
}
return null;
},
async disconnect({ state }) {
await state.connector.disconnect();
},
},
});
}
})
app.use(router);
app.use(store);
app.use(router)
app.use(store)
app.mount("#app");
app.mount('#app')

293
src/result.ts

@ -1,96 +1,68 @@
// 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;
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;
}
return new Result(
domain,
buy_price,
auction_price,
owner,
collection_required,
condition_fulfilled,
buy_msg,
content_msg,
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("."),
};
}
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
);
}
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
);
}
canBuy(): boolean {
return this.buy_price !== undefined && this.buy_price !== null && this.owner === undefined;
}
zone(): string {
return this.domain.split(".").slice(1).join(".");
}
zone(): string {
return this.domain.split('.').slice(1).join('.');
}
}
// const sleep = (milliseconds: number) => {
@ -98,31 +70,8 @@ export class Result {
// }
export async function get_search_results(query: string) {
const res = await call_api("find/" + query);
return res.map((item: any) => ({
...item,
getRouteParams: () => ({
domain_init: /* domain up to . */ item.domain.split(".")[0],
domain: /* domain up to . */ item.domain.split(".")[0],
zone: /* domain after . */ item.domain.split(".").slice(1).join("."),
}),
canAuction: () =>
item.auction_price !== undefined &&
item.auction_price !== null &&
item.owner === undefined,
canBuy: () =>
item.buy_price !== undefined &&
item.buy_price !== null &&
item.owner === undefined,
zone: () => item.domain.split(".").slice(1).join("."),
}));
// canAuction
// canBuy
// zone
// return {...res, };};
/*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),
@ -132,8 +81,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);
}
@ -156,27 +105,13 @@ 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),
@ -184,86 +119,54 @@ 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;
}
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}`;
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;
}
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>();
template_id: string;
picture: string;
blog: string[];
constructor(
domain: string,
title: string = "",
description: string = "",
contacts: Map<string, string> = new Map<string, string>(),
template_id: string = "",
picture: string = "",
blog = []
) {
this.domain = domain;
this.title = title;
this.description = description;
this.contacts = contacts;
this.template_id = template_id;
this.picture = picture;
this.blog = blog;
}
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);
}
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,
res.template_id,
res.picture,
res.blog
);
let res = await call_api('get-site-data/' + domain);
return new SiteConstructorParams(res.domain, res.title, res.description, res.contacts);
}

153
src/router/index.ts

@ -1,6 +1,5 @@
import { createRouter, createWebHistory } from "vue-router";
import Landing from "../views/Landing.vue";
import { addTemplate, mintCollection, myDomains } from "./routes";
import {createRouter, createWebHistory} from 'vue-router'
import Landing from '../views/Landing.vue'
/*
/find - bar + table
@ -10,87 +9,69 @@ import { addTemplate, mintCollection, myDomains } from "./routes";
*/
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: Landing,
},
{
path: "/tonweb",
name: "tonweb",
component: () => import("../views/TonWeb.vue"),
},
{
path: "/i-know",
name: "IKnow",
component: () => import("../views/IKnow.vue"),
},
{
path: "/i-have",
name: "IHave",
component: () => import("../views/IHave.vue"),
},
{
path: "/tondns",
name: "tondns",
component: () => import("../views/TonDns.vue"),
},
{
path: "/get/:zone/:domain_init",
name: "GetZ",
component: () => import("../views/Get.vue"),
props: true,
},
{
path: "/get/:zone/:domain_init",
name: "Get",
component: () => import("../views/Get.vue"),
props: true,
},
{
path: "/find",
name: "Find",
component: () => import("../views/Find.vue"),
},
{
path: "/checkout/:zone/:domain",
name: "Checkout",
component: () => import("../views/Checkout.vue"),
props: true,
},
{
path: "/find/:query",
name: "FindQ",
component: () => import("../views/FindQ.vue"),
props: true,
},
{
path: "/explore/:domain",
name: "Explore",
component: () => import("../views/Explore.vue"),
props: true,
},
{
path: "/my-domains",
name: myDomains,
component: () => import("../views/MyDomains.vue"),
props: true,
},
{
path: "/mint-collection/:domain",
name: mintCollection,
component: () => import("../views/MintCollection.vue"),
props: true,
},
{
path: "/add-template/:domain",
name: addTemplate,
component: () => import("../views/AddTemplate.vue"),
props: true,
},
],
});
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: Landing
},
{
path: '/tonweb',
name: 'tonweb',
component: () => import('../views/TonWeb.vue')
},
{
path: '/i-know',
name: 'IKnow',
component: () => import('../views/IKnow.vue')
},
{
path: '/i-have',
name: 'IHave',
component: () => import('../views/IHave.vue')
},
{
path: '/tondns',
name: 'tondns',
component: () => import('../views/TonDns.vue')
},
{
path: '/get/:zone',
name: 'GetZ',
component: () => import('../views/Get.vue'),
props: true
},
{
path: '/get/:zone/:domain_init',
name: 'Get',
component: () => import('../views/Get.vue'),
props: true
},
{
path: '/find',
name: 'Find',
component: () => import('../views/Find.vue')
},
{
path: '/checkout/:zone/:domain',
name: 'Checkout',
component: () => import('../views/Checkout.vue'),
props: true
},
{
path: '/find/:query',
name: 'FindQ',
component: () => import('../views/FindQ.vue'),
props: true
},
{
path: '/explore/:domain',
name: 'Explore',
component: () => import('../views/Explore.vue'),
props: true
}
]
})
export default router;
export default router

3
src/router/routes.ts

@ -1,3 +0,0 @@
export const myDomains = "my-domains";
export const mintCollection = "mint-collection";
export const addTemplate = "add-template";

24
src/types.ts

@ -1,24 +0,0 @@
export type Collection = {
address: string;
metadata: {
description: string;
image: string;
name: string;
};
next_item_index: number;
raw_collection_content: string;
};
export type CollectionItem = {
address: string;
metadata: {
name: string;
description: string;
image: string;
};
collection: {
address: string;
};
dns: string;
previews: { resolution: string; url: string }[];
};

134
src/views/AddTemplate.vue

@ -1,134 +0,0 @@
<template>
<DarkLayout>
<form class="form">
<div class="input-wrapper">
<label for="template-name">Template name</label>
<input v-model="name" class="input" id="template-name" type="text" />
</div>
<DragDropUploader class="dnd" />
<div v-for="(_, i) in fields" class="input-wrapper">
<div class="fields-wrapper">
<div class="input-wrapper">
<label :for="`field-${i}`">Field {{ i + 1 }}</label>
<input
placeholder="Name"
v-model="fields[i].name"
class="input"
:id="`field-${i}`"
type="text"
/>
</div>
<div>
<input
placeholder="Value"
v-model="fields[i].value"
class="input"
:id="`field-${i}`"
type="text"
/>
</div>
</div>
</div>
<button
class="button button-plus"
style="justify-self: start"
type="button"
@click="fields.push({ name: '', value: '' })"
>
+
</button>
<button class="button" type="button">Submit</button>
</form>
<template v-slot:header>
<router-link to="/find">
<button class="b darkish back">
<img src="@/assets/icons/ton_left.svg" alt="TON" />
Back
</button>
</router-link>
</template>
</DarkLayout>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import DarkLayout from "../components/DarkLayout.vue";
import DragDropUploader from "@/components/DnD.vue";
const name = ref("");
const fields = ref([{ name: "", value: "" }]);
</script>
<style>
.input-wrapper {
display: flex;
flex-direction: column;
align-items: start;
}
.input {
display: block;
width: 100%;
min-height: 40px;
border: 0;
border-radius: 10px;
padding: 12px 24px;
background-color: #4e5a88;
color: white;
font-size: 24px;
font-family: "Raleway", serif;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.form {
display: grid;
gap: 24px;
min-width: 600px;
}
.button {
border: 0;
border-radius: 0.5rem;
height: 3rem;
background-color: #e36464;
display: flex;
justify-content: center;
align-items: center;
place-items: center;
justify-self: end;
color: #363e5e;
font-size: 1.3rem;
font-weight: 500;
padding: 0 1rem;
cursor: pointer;
}
.button-plus {
background-color: #4e5a88;
color: white;
}
.info {
width: 600px;
font-size: 16px;
text-align: right;
margin-top: 40px;
}
.fields-wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
align-items: end;
gap: 40px;
}
.dnd {
width: 600px;
}
</style>

67
src/views/Checkout.vue

@ -1,26 +1,19 @@
<template>
<DarkLayout>
<template v-slot:header>
<router-link
:to="{ name: 'Get', params: { domain_init: domain, zone: zone } }"
>
<router-link :to="{name: 'Get', params: {domain_init: domain, zone: zone}}">
<button class="b darkish back">
<img src="@/assets/icons/ton_left.svg" alt="TON" />
<img src="@/assets/icons/ton_left.svg" alt="TON"/>
Back
</button>
</router-link>
</template>
<!-- todo: handle auction -->
<div class="text">To buy</div>
<DomainBar
:zone="'.' + zone"
:value="domain"
:has_button="false"
:editable="false"
/>
<DomainBar :zone="'.' + zone" :value="domain" :has_button="false" :editable="false"/>
<div class="text">Confirm the transaction in your wallet</div>
<div class="center">
<Socket />
<Socket/>
</div>
</DarkLayout>
</template>
@ -28,32 +21,32 @@
<script>
import DarkLayout from "../components/DarkLayout.vue";
import DomainBar from "../components/DomainBar.vue";
import { get_domain_result, get_ton_link } from "../result";
import {get_domain_result, get_ton_link} from "../result";
import RotateSquare2 from "../components/RotateSquare2.vue";
import { qr_options } from "../utils";
import {qr_options} from "../utils";
import Socket from "../components/Socket.vue";
export default {
name: "Checkout",
components: { Socket, RotateSquare2, DomainBar, DarkLayout },
components: {Socket, RotateSquare2, DomainBar, DarkLayout},
props: {
domain: {
type: String,
},
zone: {
type: String,
},
}
},
data() {
return {
result: null,
ton_link: null,
qr_code: null,
};
}
},
methods: {
async get_result() {
this.result = await get_domain_result(this.domain + "." + this.zone);
this.result = await get_domain_result(this.domain + '.' + this.zone);
this.ton_link = await get_ton_link(this.result);
},
async sign_transaction() {
@ -62,34 +55,22 @@ export default {
let validness = parseInt((d.getTime() / 1000).toFixed(0)) + 360000;
const transaction = {
validUntil: validness,
messages: [Object.assign({}, this.result.buy_msg)],
};
messages: [
Object.assign({}, this.result.buy_msg)
]
}
console.log("Signing the transaction", transaction);
const result = await this.$store.state.connector.sendTransaction(
transaction
);
const result = await this.$store.state.connector.sendTransaction(transaction);
console.log(result);
if (result.boc === "") {
this.$router.push({
name: "Get",
params: { domain_init: this.domain, zone: this.zone },
});
}
let res = await get_domain_result(this.domain + "." + this.zone);
while (res.owner !== this.$store.getters.address) {
res = await get_domain_result(this.domain + "." + this.zone);
if (result.boc === '') {
this.$router.push({name: 'Get', params: {domain_init: this.domain, zone: this.zone}});
}
this.$router.push({name: 'Explore', params: {domain: this.domain + '.' + this.zone}});
}
this.$router.push({
name: "Explore",
params: { domain: this.domain + "." + this.zone },
});
},
},
mounted() {
this.sign_transaction();
this.sign_transaction()
},
computed: {
loaded() {
@ -100,13 +81,13 @@ export default {
return null;
}
return qr_options(this.ton_link.getLink());
},
},
};
}
}
}
</script>
<style>
.qr > div > svg {
border-radius: 2rem;
}
</style>
</style>

363
src/views/Explore.vue

@ -1,115 +1,58 @@
<template>
<DarkLayout>
<template v-slot:header>
<router-link
:to="{ name: 'Get', params: { domain_init: core_domain, zone: zone } }"
>
<router-link :to="{name: 'Get', params: {domain_init: core_domain, zone: zone}}">
<button class="b darkish back">
<img src="@/assets/icons/ton_left.svg" alt="TON" />
<img src="@/assets/icons/ton_left.svg" alt="TON"/>
Back
</button>
</router-link>
</template>
<div style="min-width: 50%">
<DomainBar
:value="core_domain"
:zone="'.' + zone"
:has_button="false"
:editable="false"
/>
<DomainBar :value="core_domain" :zone="'.' + zone" :has_button="false" :editable="false"/>
<div v-if="loading" class="center">
<RotateSquare2 style="width: 5rem; height: 5rem; margin-top: 3rem" />
<RotateSquare2 style="width: 5rem; height: 5rem; margin-top: 3rem;"/>
</div>
<div v-else>
<div class="owned" v-if="result.owner && !isMine">
<p class="domain">{{ domain }}</p>
<div>
is owned by
<div>is owned by
<div class="owner-address">{{ ownerAddr }}</div>
</div>
<a :href="result.nft_info.explorer_link">View nft</a>
</div>
<div v-else-if="isMine" class="owned">
<p class="domain">{{ domain }}</p>
<div>
is owned by
<div>is owned by
<div class="owner-address">you</div>
</div>
<div class="buttons">
<a :href="result.nft_info.explorer_link">View nft</a>
<router-link class="button" :to="`/mint-collection/${domain}`">
Mint a subdomain collection
</router-link>
</div>
<br />
<div>
You can associate your domain with your wallet<br />to receive
incoming transaction at the domain address
</div>
<a :href="result.nft_info.explorer_link">View nft</a>
<br/>
<div id="wallet_input" class="rec-field">
<div class="wallet-panel">
<p class="label">Wallet:</p>
<contenteditable
class="record-inp wallet-record-field"
tag="div"
:no-hl="true"
:no-html="true"
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,
get_b: true,
inactive: !walletChanged,
signing: signingWallet,
}"
@click="saveWallet()"
>
<div style="display: flex; width: 100%">
<p style="width: 9rem">Wallet:</p>
<contenteditable class="record-inp wallet-record-field" tag="div" :no-hl="true" :no-html="true" spellcheck="false"
v-model="wallet_rec"></contenteditable>
<div :class="{'record-submit': true, 'get_b': true, 'inactive': !walletChanged, 'signing': signingWallet}" @click="saveWallet()">
<template v-if="!signingWallet">Save</template>
<template v-else>
<Socket
secondary-color="#a7aab3"
color="#282e46"
size="50px"
style="min-width: 3rem"
/>
<Socket secondary-color="#a7aab3" color="#282e46" size="50px" style="min-width: 3rem"/>
Confirm in the wallet...
</template>
</div>
</div>
</div>
<SiteSettings
ref="site_settings"
@save="saveSite()"
@save-constructor="saveSiteConstr()"
@select-template="selectTemplate"
@change="site_rec = $event"
:template-id="constructor_params.template_id"
:site-changed="siteChanged"
:signing-site="signingSite"
@change-constructor="changeConstructor"
/>
<!-- @change-constructor="changeConstructor" -->
<SiteSettings ref="site_settings"
@save="saveSite()" @save-constructor="saveSiteConstr()"
@change="site_rec = $event" @change-constructor="constructor_params = $event"
:site-changed="siteChanged" :signing-site="signingSite"/>
</div>
<div v-else>
This domain is not owned.
<router-link
:to="{
name: 'Get',
params: { domain_init: core_domain, zone: zone },
}"
>
<button class="b darkish back">Get it</button>
<router-link :to="{name: 'Get', params: {domain_init: core_domain, zone: zone}}">
<button class="b darkish back">
Get it
</button>
</router-link>
</div>
</div>
@ -121,86 +64,58 @@
import DarkLayout from "../components/DarkLayout.vue";
import DomainBar from "../components/DomainBar.vue";
import RotateSquare2 from "../components/RotateSquare2.vue";
import {
get_constr_params,
get_domain_result,
get_records,
SiteConstructorParams,
} from "../result";
import { convertAddress } from "../utils";
import contenteditable from "vue-contenteditable";
import { call_api, call_api_post, config } from "../api";
import {get_constr_params, get_domain_result, get_records, SiteConstructorParams} from "../result";
import {convertAddress} from "../utils";
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";
export default {
name: "Explore",
components: {
SiteSettings,
Socket,
DomainBar,
DarkLayout,
RotateSquare2,
contenteditable,
},
components: {SiteSettings, Socket, DomainBar, DarkLayout, RotateSquare2, contenteditable},
props: {
domain: {
type: String,
},
}
},
data() {
const saved_constructor_params = new SiteConstructorParams(
this.domain,
this.domain
);
let saved_constructor_params = new SiteConstructorParams(this.domain, this.domain);
return {
result: null,
records: {
wallet: null,
storage: null,
uri: null,
next_resolver: null,
site: null,
},
records: {wallet: null, storage: null, uri: null, next_resolver: null, site: null},
wallet_rec: null,
site_rec: null,
signingSite: false,
signingWallet: false,
constructor_params: saved_constructor_params,
saved_constructor_params,
interval: null,
};
interval: null
}
},
mounted() {
this.interval = setInterval(
() =>
get_domain_result(this.domain, this.$store.getters.address).then(
(r) => {
this.result = Object.assign({}, r);
if (this.isMine) {
get_constr_params(this.domain).then((r) => {
this.constructor_params = r;
this.saved_constructor_params = r;
this.updSettingsComponent();
});
get_records(r.nft_info.address).then((r) => {
this.records = r;
// this.timer = setTimeout(this.updRecords, 10000);
this.updSettingsComponent();
});
}
}
),
20000
);
this.interval = setInterval(() => get_domain_result(this.domain, this.$store.getters.address).then(r => {
this.result = Object.assign({}, r);
if (this.isMine) {
get_constr_params(this.domain).then(r => {
this.constructor_params = r;
this.saved_constructor_params = r;
this.updSettingsComponent();
});
get_records(r.nft_info.address).then(r => {
this.records = r;
// this.timer = setTimeout(this.updRecords, 10000);
this.updSettingsComponent()
})
}
}), 20000);
},
computed: {
core_domain() {
return this.domain.split(".")[0];
return this.domain.split('.')[0];
},
zone() {
return this.domain.split(".").slice(1).join(".");
return this.domain.split('.').slice(1).join('.');
},
loading() {
return this.result === null;
@ -208,17 +123,13 @@ export default {
ownerAddr() {
let owner = this.result.owner;
if (!owner instanceof String) {
return "";
return '';
}
let address = convertAddress(owner);
return address.slice(0, 5) + "..." + address.slice(-4);
return address.slice(0, 5) + '...' + address.slice(-4);
},
isMine() {
console.log(this.result);
return (
this.result.owner === this.$store.getters.address ||
this.result.nft_info.owner.address === this.$store.getters.address
);
return this.result.owner === this.$store.getters.address;
},
walletChanged() {
return this.records && this.wallet_rec !== this.records.wallet;
@ -226,19 +137,13 @@ export default {
siteChanged() {
let constr_change = false;
if (this.site_rec === config.agorata_adnl) {
constr_change =
this.constructor_params !== this.saved_constructor_params;
constr_change = this.constructor_params !== this.saved_constructor_params;
}
return (
(this.records &&
(this.site_rec !== this.records.site || constr_change)) ||
(!this.records && constr_change) ||
(this.records !== null && this.site_rec === null)
);
return this.records && (this.site_rec !== this.records.site || constr_change);
},
settingsCompLoaded() {
return this.$refs.site_settings !== undefined;
},
}
},
methods: {
async saveWallet() {
@ -246,7 +151,7 @@ export default {
return;
}
// get the message from api at /set-record/site/{site} and sign it with tonconnect (from storage)
let message = await call_api("set-record/wallet/" + this.wallet_rec);
let message = await call_api('set-record/wallet/' + this.wallet_rec);
// send the message to tonconnect
let d = new Date();
let validness = parseInt((d.getTime() / 1000).toFixed(0)) + 360000;
@ -256,15 +161,13 @@ export default {
{
amount: (0.05 * 1000000000).toString(),
address: this.result.nft_info.address,
payload: message,
},
],
payload: message
}
]
};
this.signingWallet = true;
console.log("Sending transaction", transaction);
const result = await this.$store.state.connector.sendTransaction(
transaction
);
const result = await this.$store.state.connector.sendTransaction(transaction);
this.signingWallet = false;
if (!result.boc) {
// todo
@ -276,7 +179,7 @@ export default {
return;
}
// get the message from api at /set-record/site/{site} and sign it with tonconnect (from storage)
let message = await call_api("set-record/site/" + this.site_rec);
let message = await call_api('set-record/site/' + this.site_rec);
// send the message to tonconnect
let d = new Date();
let validness = parseInt((d.getTime() / 1000).toFixed(0)) + 360000;
@ -286,14 +189,12 @@ export default {
{
amount: (0.05 * 1000000000).toString(),
address: this.result.nft_info.address,
payload: message,
},
],
payload: message
}
]
};
this.signingSite = true;
const result = await this.$store.state.connector.sendTransaction(
transaction
);
const result = await this.$store.state.connector.sendTransaction(transaction);
this.signingSite = false;
if (!result.boc) {
// todo
@ -301,93 +202,59 @@ export default {
this.records.site = this.site_rec;
},
async saveSiteConstr() {
if (!this.site_rec) {
this.site_rec = config.agorata_adnl.toLowerCase();
await this.saveSite();
} else if (this.site_rec !== this.records.site) {
if (this.site_rec !== this.records.site) {
await this.saveSite();
}
this.constructor_params.domain = this.domain;
console.log("this.constructor_params", this.constructor_params);
await call_api_post("set-site-data", {
...this.constructor_params,
blog:
this.constructor_params.template_id === "Template #3"
? [
"Ipsum dolor sit amet",
"Consectetur adipiscing elit",
"Sed do eiusmod tempor",
]
: undefined,
});
await call_api_post('set-site-data', this.constructor_params);
this.saved_constructor_params = this.constructor_params;
this.updSettingsComponent();
},
updRecords() {
get_records(this.result.nft_info.address).then((r) => {
get_records(this.result.nft_info.address).then(r => {
if (this.records.wallet !== r.wallet || this.records.site !== r.site) {
this.records = r;
}
});
})
},
updSettingsComponent() {
if (this.$refs.site_settings) {
this.$refs.site_settings.set_site_rec(this.site_rec);
this.$refs.site_settings.saved_constructor_params =
this.saved_constructor_params;
}
},
selectTemplate(template) {
this.constructor_params = {
...this.constructor_params,
template_id: template.name,
};
},
useMyWallet() {
this.wallet_rec = this.$store.getters.address;
},
changeConstructor(evt) {
this.constructor_params = evt;
},
this.$refs.site_settings.set_site_rec(this.site_rec);
this.$refs.site_settings.saved_constructor_params = this.saved_constructor_params;
}
},
watch: {
records: function (val) {
if (val) {
this.wallet_rec = val.wallet;
if (!this.site_rec || val.site) {
this.site_rec = val.site;
}
this.site_rec = val.site;
}
},
site_rec() {
if (this.$refs.site_settings) {
this.$refs.site_settings.set_site_rec(this.site_rec);
}
site_rec () {
this.$refs.site_settings.set_site_rec(this.site_rec);
},
saved_constructor_params() {
this.$refs.site_settings.saved_constructor_params =
this.saved_constructor_params;
saved_constructor_params () {
this.$refs.site_settings.saved_constructor_params = this.saved_constructor_params;
},
settingsCompLoaded() {
settingsCompLoaded () {
if (!this.loading && this.settingsCompLoaded) {
this.updSettingsComponent();
}
},
loading() {
loading () {
if (!this.loading && this.settingsCompLoaded) {
this.updSettingsComponent();
}
},
}
},
unmounted() {
clearInterval(this.interval);
},
};
}
}
</script>
<style scoped>
.domain {
font-family: "Inconsolata", monospace;
font-family: 'Inconsolata', monospace;
font-weight: bold;
font-size: 2rem;
}
@ -408,20 +275,23 @@ export default {
}
.owner-address {
font-family: "Inconsolata", monospace;
font-family: 'Inconsolata', monospace;
color: #c86161;
font-size: 2rem;
}
.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: 4rem;
margin-bottom: 1rem;
}
.get_b.inactive {
@ -440,57 +310,6 @@ export default {
}
.wallet-record-field {
font-family: "Inconsolata", monospace;
}
.button {
border: 0;
border-radius: 0.5rem;
height: 3rem;
background-color: #e36464;
display: flex;
justify-content: center;
align-items: center;
place-items: center;
color: #363e5e;
font-size: 1.3rem;
font-weight: 500;
padding: 0 1rem;
cursor: pointer;
margin-left: 1rem;
margin-top: 1rem;
margin-bottom: 1rem;
}
.buttons {
display: flex;
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;
}
font-family: 'Inconsolata', monospace;
}
</style>
</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 :domain="query" :zones="zones" />
<ZoneTable :zones="zones"/>
</DarkLayout>
</template>
@ -11,24 +11,28 @@
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>

42
src/views/Get.vue

@ -1,24 +1,15 @@
<template>
<DarkLayout ref="dl">
<template v-slot:header>
<router-link :to="{ name: 'Find' }">
<router-link :to="{name: 'Find'}">
<button class="b darkish back">
<img src="@/assets/icons/ton_left.svg" alt="TON" />
<img src="@/assets/icons/ton_left.svg" alt="TON"/>
All zones
</button>
</router-link>
</template>
<DomainBar
:zone="'.' + zone"
:value="domain"
@search="search()"
@input_d="handle_input($event)"
/>
<DomainResult
:domain="domain + '.' + zone"
v-if="domain !== ''"
@login="$refs.dl.login()"
/>
<DomainBar :zone="'.' + zone" :value="domain" @search="search()" @input_d="handle_input($event)"/>
<DomainResult :domain="domain + '.' + zone" v-if="domain !== ''" @login="$refs.dl.login()"/>
</DarkLayout>
</template>
@ -34,31 +25,30 @@ export default {
},
zone: {
type: String,
},
}
},
components: { DomainResult, DomainBar, DarkLayout },
components: {DomainResult, DomainBar, DarkLayout},
data() {
return {
domain: this.domain_init,
};
domain: this.domain_init
}
},
methods: {
search() {
this.$router.push({
name: "Get",
params: { domain_init: this.domain, zone: this.zone },
});
this.$router.push({name: 'Get', params: {domain_init: this.domain, zone: this.zone}});
},
handle_input(val) {
this.domain = val;
},
}
},
watch: {
domain_init: function (val) {
this.domain = val;
},
},
};
}
}
}
</script>
<style scoped></style>
<style scoped>
</style>

42
src/views/IHave.vue

@ -1,37 +1,19 @@
<template>
<WhiteLayout next="">
<template v-slot:header>
<img
src="@/assets/headers/i-know.svg"
style="max-height: 100%; max-width: 90%"
alt="TON DNS"
/>
<img src="@/assets/headers/i-know.svg" style="max-height: 100%; max-width: 90%" alt="TON DNS"/>
</template>
<template v-slot:content>
<div class="cont2">
<div
class="columns-3"
style="
display: flex;
columns: 3;
justify-content: center;
align-items: center;
"
>
<div></div>
<div class="columns-3" style="display: flex; columns: 3; justify-content: center; align-items: center">
<div>
</div>
<div id="great">
<p>
Very good!<br />We can help you host a website there or sell
subdomains.<br />
For now, you will have to
<a href="https://t.me/ennucore">contact us on Telegram</a>. <br />
We are especially interested in partnering with different projects
and communities which are interested in distributing domains to
their users.<br />
Also, you can always
<router-link :to="{ name: 'Find' }"
>buy another domain</router-link
>.
Very good!<br/>We can help you host a website there or sell subdomains.<br/>
For now, you will have to <a href="https://t.me/ennucore">contact us on Telegram</a>. <br/>
We are especially interested in partnering with different projects and communities which are interested in distributing domains to their users.<br/>
Also, you can always <router-link :to="{name: 'Find'}">buy another domain</router-link>.
</p>
</div>
</div>
@ -43,7 +25,7 @@
<span>Contact us</span>
</button>
</a>
<router-link :to="{ name: 'Find' }">
<router-link :to="{name: 'Find'}">
<button class="b blue wide">
<span>Buy a domain</span>
</button>
@ -57,8 +39,8 @@ import WhiteLayout from "../components/WhiteLayout.vue";
export default {
name: "IHave",
components: { WhiteLayout },
};
components: {WhiteLayout}
}
</script>
<style scoped>
@ -85,4 +67,4 @@ export default {
align-items: center;
height: 100%;
}
</style>
</style>

35
src/views/IKnow.vue

@ -1,40 +1,29 @@
<template>
<WhiteLayout next="">
<template v-slot:header>
<img
src="@/assets/headers/i-know.svg"
style="max-height: 100%; max-width: 90%"
alt="TON DNS"
/>
<img src="@/assets/headers/i-know.svg" style="max-height: 100%; max-width: 90%" alt="TON DNS"/>
</template>
<template v-slot:content>
<div class="cont2">
<div
class="columns-3"
style="
display: flex;
columns: 3;
justify-content: center;
align-items: center;
"
>
<div></div>
<div class="columns-3" style="display: flex; columns: 3; justify-content: center; align-items: center">
<div>
</div>
<div id="great">
<p>That's great!<br />Maybe, you even have a domain already?</p>
<p>That's great!<br/>Maybe, you even have a domain already?</p>
</div>
</div>
</div>
</template>
<template v-slot:buttons>
<router-link :to="{ name: 'IHave' }">
<router-link :to="{name: 'IHave'}">
<button class="b blue wide">
<img src="@/assets/icons/ton_top.svg" alt="Next" />
<img src="@/assets/icons/ton_top.svg" alt="Next"/>
<span>Yes</span>
</button>
</router-link>
<router-link :to="{ name: 'tondns' }">
<router-link :to="{name: 'tondns'}">
<button class="b blue wide">
<img src="@/assets/icons/ton_bottom.svg" alt="Next" />
<img src="@/assets/icons/ton_bottom.svg" alt="Next"/>
<span>No</span>
</button>
</router-link>
@ -47,8 +36,8 @@ import WhiteLayout from "../components/WhiteLayout.vue";
export default {
name: "IKnow",
components: { WhiteLayout },
};
components: {WhiteLayout}
}
</script>
<style scoped>
@ -75,4 +64,4 @@ export default {
align-items: center;
height: 100%;
}
</style>
</style>

15
src/views/Landing.vue

@ -1,20 +1,18 @@
<script setup lang="ts"></script>
<script setup lang="ts">
</script>
<template>
<main>
<!-- @/assets/logo_single.png centered horizontally and slightly above center vertically -->
<img class="logo" src="@/assets/logo_landing.png" alt="Agorata" />
<p style="font-size: 1.8rem; padding: 2rem">
Helping you <b style="font-weight: bold">be</b> the new internet
</p>
<p style="font-size: 1.8rem; padding: 2rem;">Helping you <b style="font-weight: bold">be</b> the new internet</p>
<div id="do-you-know">
<p>Do you know what TON Web is?</p>
<br />
<p>Do you know what TON Web is?</p><br/>
<router-link to="/i-know">
<button class="b white wide">Yes</button>
</router-link>
<br />
<br/>
<router-link to="/tonweb">
<button class="b white wide">No</button>
</router-link>
@ -33,7 +31,7 @@ main {
justify-content: center;
align-items: center;
color: white;
font-family: "Raleway", serif;
font-family: 'Raleway',serif;
font-size: 1.5rem;
text-align: center;
}
@ -54,4 +52,5 @@ main {
align-items: center;
padding: 2rem;
}
</style>

139
src/views/MintCollection.vue

@ -1,139 +0,0 @@
<template>
<DarkLayout>
<form class="form">
<div class="input-wrapper">
<label for="collection-name">Collection name</label>
<input v-model="name" class="input" id="collection-name" type="text" />
</div>
<div class="input-wrapper">
<label for="first-domen-price">Domen price with 3 symbols</label>
<input
v-model="firstPrice"
class="input"
id="first-domen-price"
type="number"
/>
</div>
<div class="input-wrapper">
<label for="second-domen-price">Domen price with 8 symbols</label>
<input
v-model="secondPrice"
class="input"
id="second-domen-price"
type="number"
/>
</div>
<p class="info" v-show="firstPrice && secondPrice && name">
By pressing the Mint button, you create an NFT collection with the name
"{{ name }}" and the price: {{ firstPrice }} ({{
secondPrice
}}).<br />Any users also will be able to buy domains from this
collection.
</p>
<button @click="mint()" class="button" type="button">Mint</button>
</form>
<template v-slot:header>
<router-link to="/find">
<button class="b darkish back">
<img src="@/assets/icons/ton_left.svg" alt="TON" />
Back
</button>
</router-link>
</template>
</DarkLayout>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import DarkLayout from "../components/DarkLayout.vue";
import { useStore } from "vuex";
import { call_api } from "@/api";
const store = useStore();
const mint = async () => {
const message = await call_api(`set-record/wallet/${store.getters.address}`);
const d = new Date();
const validness = parseInt((d.getTime() / 1000).toFixed(0)) + 360000;
const transaction = {
validUntil: validness,
messages: [
{
amount: (0.05 * 1000000000).toString(),
address: store.getters.address,
payload: message,
},
],
};
const result = await store.state.connector.sendTransaction(transaction);
console.log(result);
};
const name = ref("");
const firstPrice = ref(0);
const secondPrice = ref(0);
</script>
<style>
.input-wrapper {
display: flex;
flex-direction: column;
align-items: start;
}
.input {
display: block;
width: 100%;
min-height: 40px;
border: 0;
border-radius: 10px;
padding: 12px 24px;
background-color: #4e5a88;
color: white;
font-size: 24px;
font-family: "Raleway", serif;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.form {
display: grid;
gap: 24px;
min-width: 600px;
}
.button {
border: 0;
border-radius: 0.5rem;
height: 3rem;
background-color: #e36464;
display: flex;
justify-content: center;
align-items: center;
place-items: center;
justify-self: end;
min-width: 150px;
color: #363e5e;
font-size: 1.3rem;
font-weight: 500;
padding: 0 1rem;
cursor: pointer;
margin-left: 1rem;
margin-top: 1rem;
margin-bottom: 1rem;
}
.info {
width: 600px;
font-size: 16px;
text-align: right;
margin-top: 40px;
}
</style>

18
src/views/MyDomains.vue

@ -1,18 +0,0 @@
<template>
<DarkLayout>
<DomainTable />
<template v-slot:header>
<router-link to="/find">
<button class="b darkish back">
<img src="@/assets/icons/ton_left.svg" alt="TON" />
Back
</button>
</router-link>
</template>
</DarkLayout>
</template>
<script setup>
import DarkLayout from "../components/DarkLayout.vue";
import DomainTable from "@/components/DomainTable.vue";
</script>

26
src/views/TonDns.vue

@ -1,29 +1,27 @@
<template>
<WhiteLayout>
<template v-slot:header>
<img src="@/assets/tondns.svg" style="height: 100%" alt="TON DNS" />
<img src="@/assets/tondns.svg" style="height: 100%" alt="TON DNS"/>
</template>
<template v-slot:content>
<div style="justify-content: center; align-items: center">
<div>
<p>
TON domains .ton and .t.me are your names in the TON world.<br />
They can be used to host a website or to transfer money.<br />
You can host a TON website for your personal brand.
</p>
<p>TON domains .ton and .t.me are your names in the TON world.<br/>
They can be used to host a website or to transfer money.<br/>
You can host a TON website for your personal brand.</p>
</div>
<div class="columns-3 images">
<img class="img" src="@/assets/images/usage.png" alt="Usage" />
<img class="img" src="@/assets/images/my_name_is.png" alt="lame" />
<img class="img" src="@/assets/images/anton_ton.png" alt="cool" />
<img class="img" src="@/assets/images/usage.png" alt="Usage"/>
<img class="img" src="@/assets/images/my_name_is.png" alt="lame"/>
<img class="img" src="@/assets/images/anton_ton.png" alt="cool"/>
</div>
</div>
</template>
<template v-slot:buttons>
<router-link :to="{ name: 'Find' }">
<router-link :to="{name: 'Find'}">
<button class="b blue wide">
<span>Omg, can I get one?</span>
<img src="@/assets/icons/ton_right.svg" alt="Next" />
<img src="@/assets/icons/ton_right.svg" alt="Next"/>
</button>
</router-link>
</template>
@ -35,8 +33,8 @@ import WhiteLayout from "../components/WhiteLayout.vue";
export default {
name: "TonDns",
components: { WhiteLayout },
};
components: {WhiteLayout}
}
</script>
<style scoped>
@ -50,4 +48,4 @@ export default {
width: 75vw;
}
}
</style>
</style>

45
src/views/TonWeb.vue

@ -1,43 +1,26 @@
<template>
<WhiteLayout next="tondns" nexttext="Wow, what else?">
<template v-slot:header>
<img src="@/assets/tonweb.svg" style="height: 100%" alt="TON Web" />
<img src="@/assets/tonweb.svg" style="height: 100%" alt="TON Web"/>
</template>
<template v-slot:content>
<div class="columns-2">
<div>
<img
src="@/assets/images/tonweb_duck.png"
alt="TON Web"
style="width: 20rem"
/>
<img src="@/assets/images/tonweb_duck.png" alt="TON Web" style="width: 20rem"/>
</div>
<div>
<p>
TON Sites are just websites hosted on the TON Network.<br />
After some configuration, you can visit a website like
foundation.ton.
</p>
<p>TON Sites are just websites hosted on the TON Network.<br/>
After some configuration, you can visit a website like foundation.ton.</p>
<p>They have a lot of advantages:</p>
<ul class="wbox_ul">
<li>
The DApps are truly D: they cannot be banned or pressured, the
servers cannot be traced, the apps don't get the user's IP
</li>
<li>There can be no censorship</li>
<li>
Validity of websites is determined on the blockchain instead of
trusting authorities for SSL
</li>
<li>
Domain ownership has clear rules on TON, there is no central
authority, no registrators
</li>
<li>The TON Web is nicely integrated with the TON blockchain</li>
<li>
There is no need for logins and passwords you just use your TON
account
<li> The DApps are truly D: they cannot be banned or pressured, the servers cannot be traced, the apps
don't get the user's IP
</li>
<li> There can be no censorship</li>
<li> Validity of websites is determined on the blockchain instead of trusting authorities for SSL</li>
<li> Domain ownership has clear rules on TON, there is no central authority, no registrators</li>
<li> The TON Web is nicely integrated with the TON blockchain</li>
<li> There is no need for logins and passwords you just use your TON account</li>
</ul>
</div>
</div>
@ -50,8 +33,8 @@ import WhiteLayout from "../components/WhiteLayout.vue";
export default {
name: "TonWeb",
components: { WhiteLayout },
};
components: {WhiteLayout}
}
</script>
<style scoped>
@ -70,4 +53,4 @@ export default {
img {
max-width: 80vw;
}
</style>
</style>

15
vite.config.ts

@ -1,13 +1,14 @@
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

Loading…
Cancel
Save