mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 10:29:21 +03:00
contacts form
This commit is contained in:
parent
26e6276b23
commit
838a74c086
@ -1,2 +1,3 @@
|
|||||||
DIRECTUS_URL=
|
DIRECTUS_URL=
|
||||||
|
DIRECTUS_URL_GRAPHQL=
|
||||||
DIRECTUS_TOKEN=
|
DIRECTUS_TOKEN=
|
||||||
@ -12,12 +12,21 @@ export default defineConfig({
|
|||||||
site: "https://yarkov.tech",
|
site: "https://yarkov.tech",
|
||||||
compressHTML: true,
|
compressHTML: true,
|
||||||
output: "server",
|
output: "server",
|
||||||
|
server: {
|
||||||
|
port: 9966,
|
||||||
|
allowedHosts: ["showcase.yarkov.tech"],
|
||||||
|
},
|
||||||
adapter: node({
|
adapter: node({
|
||||||
mode: "standalone",
|
mode: "standalone",
|
||||||
}),
|
}),
|
||||||
env: {
|
env: {
|
||||||
schema: {
|
schema: {
|
||||||
DIRECTUS_URL: envField.string({ context: "server", access: "secret", default: "" }),
|
DIRECTUS_URL: envField.string({ context: "server", access: "secret", default: "" }),
|
||||||
|
DIRECTUS_URL_GRAPHQL: envField.string({
|
||||||
|
context: "server",
|
||||||
|
access: "secret",
|
||||||
|
default: "http://directus:8055/graphql",
|
||||||
|
}),
|
||||||
DIRECTUS_TOKEN: envField.string({ context: "server", access: "secret", default: "" }),
|
DIRECTUS_TOKEN: envField.string({ context: "server", access: "secret", default: "" }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,11 +11,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev --host",
|
||||||
"build": "astro check && astro build",
|
"build": "astro check && astro build",
|
||||||
"start": "node --env-file=.env ./dist/server/entry.mjs",
|
"start": "node --env-file=.env ./dist/server/entry.mjs",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro",
|
"astro": "astro",
|
||||||
|
"telemetry-disable": "astro telemetry disable",
|
||||||
"codegen": "graphql-codegen --require dotenv/config --config codegen.ts --watch"
|
"codegen": "graphql-codegen --require dotenv/config --config codegen.ts --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
41
src/actions/index.ts
Normal file
41
src/actions/index.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { ActionError, defineAction } from "astro:actions";
|
||||||
|
import { z } from "astro/zod";
|
||||||
|
import { createAppeal, getLatestAppealsByClientIp } from "../queries";
|
||||||
|
|
||||||
|
const CREATE_APPEAL_LIMIT_HRS = 1;
|
||||||
|
const APPEALS_LAST_HRS_LIMIT = 2;
|
||||||
|
|
||||||
|
export const server = {
|
||||||
|
appeal: defineAction({
|
||||||
|
accept: "form",
|
||||||
|
input: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
email: z.string().email(),
|
||||||
|
subject: z.string(),
|
||||||
|
message: z.string(),
|
||||||
|
}),
|
||||||
|
handler: async (data, ctx) => {
|
||||||
|
const currentDate = new Date();
|
||||||
|
const appealsLastHrs = (await getLatestAppealsByClientIp(ctx.clientAddress, 5)).filter((a) => {
|
||||||
|
const hourDiff = (currentDate.getTime() - new Date(a.date_created).getTime()) / (1000 * 60 * 60);
|
||||||
|
return hourDiff <= CREATE_APPEAL_LIMIT_HRS;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (appealsLastHrs.length >= APPEALS_LAST_HRS_LIMIT) {
|
||||||
|
throw new ActionError({
|
||||||
|
code: "TOO_MANY_REQUESTS",
|
||||||
|
message: "Appeal create limit exceeded.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const appeal = await createAppeal({ ...data, client_ip: ctx.clientAddress });
|
||||||
|
|
||||||
|
if (appeal) return true;
|
||||||
|
|
||||||
|
throw new ActionError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: "Unable to create appeal.",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
@ -2,17 +2,20 @@
|
|||||||
import Button from "@components/Button.astro";
|
import Button from "@components/Button.astro";
|
||||||
import UIString from "./UIString.astro";
|
import UIString from "./UIString.astro";
|
||||||
import { useTranslation } from "@i18n/utils";
|
import { useTranslation } from "@i18n/utils";
|
||||||
|
import { actions } from "astro:actions";
|
||||||
|
|
||||||
const t = useTranslation(Astro);
|
const t = useTranslation(Astro);
|
||||||
|
const formResult = Astro.getActionResult(actions.appeal);
|
||||||
---
|
---
|
||||||
|
|
||||||
<form name="contact" action="/api/appeal" method="post">
|
<>
|
||||||
|
<form name="contact" action={actions.appeal} method="post">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div>
|
<div>
|
||||||
<input name="name" placeholder={t("input.nameField")} type="text" maxlength="20" autocomplete="off" required />
|
<input name="name" placeholder={t("input.nameField")} type="text" maxlength="20" autocomplete="off" required />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input name="email" placeholder="example@email.com" autocomplete="off" type="email" required />
|
<input name="email" placeholder="example@email.com" type="email" autocomplete="off" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input name="subject" placeholder={t("input.subjectField")} type="text" autocomplete="off" required />
|
<input name="subject" placeholder={t("input.subjectField")} type="text" autocomplete="off" required />
|
||||||
@ -25,11 +28,19 @@ const t = useTranslation(Astro);
|
|||||||
rows={10}
|
rows={10}
|
||||||
required></textarea>
|
required></textarea>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
|
{
|
||||||
|
formResult?.error && (
|
||||||
|
<span>
|
||||||
|
<UIString key={formResult.error.status === 429 ? "submitted.limit" : "submitted.error"} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
<Button type={"submit"}>
|
<Button type={"submit"}>
|
||||||
<UIString key={"btn.submit"} />
|
<UIString key={"btn.submit"} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
form {
|
form {
|
||||||
@ -48,7 +59,10 @@ const t = useTranslation(Astro);
|
|||||||
border: 1px solid var(--color-error);
|
border: 1px solid var(--color-error);
|
||||||
}
|
}
|
||||||
& .footer {
|
& .footer {
|
||||||
text-align: right;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 560px) {
|
@media screen and (max-width: 560px) {
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import type { ExecutionResult } from "graphql";
|
import type { ExecutionResult } from "graphql";
|
||||||
import type { TypedDocumentString } from "./graphql";
|
import type { TypedDocumentString } from "./graphql";
|
||||||
import { DIRECTUS_URL, DIRECTUS_TOKEN } from "astro:env/server";
|
import { DIRECTUS_URL_GRAPHQL, DIRECTUS_TOKEN } from "astro:env/server";
|
||||||
|
|
||||||
export default async function execute<TResult, TVariables>(
|
export default async function execute<TResult, TVariables>(
|
||||||
query: TypedDocumentString<TResult, TVariables>,
|
query: TypedDocumentString<TResult, TVariables>,
|
||||||
...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
|
...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
|
||||||
) {
|
) {
|
||||||
const response = await fetch(DIRECTUS_URL + "/graphql", {
|
const response = await fetch(DIRECTUS_URL_GRAPHQL, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|||||||
@ -15,6 +15,8 @@ import * as types from './graphql';
|
|||||||
* Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size
|
* Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size
|
||||||
*/
|
*/
|
||||||
type Documents = {
|
type Documents = {
|
||||||
|
"\n\t\tquery LatestAppealByClientIpQuery($client_ip: String!, $limit: Int!) {\n\t\t\tappeal(filter: { client_ip: { _eq: $client_ip } }, sort: [\"-date_created\"], limit: $limit) {\n\t\t\t\tclient_ip\n\t\t\t\tdate_created\n\t\t\t}\n\t\t}\n\t": typeof types.LatestAppealByClientIpQueryDocument,
|
||||||
|
"\n\t\tmutation CreateAppealQuery(\n\t\t\t$name: String!\n\t\t\t$email: String!\n\t\t\t$subject: String!\n\t\t\t$message: String!\n\t\t\t$client_ip: String!\n\t\t) {\n\t\t\tcreate_appeal_item(\n\t\t\t\tdata: { name: $name, email: $email, subject: $subject, message: $message, client_ip: $client_ip }\n\t\t\t) {\n\t\t\t\tid\n\t\t\t}\n\t\t}\n\t": typeof types.CreateAppealQueryDocument,
|
||||||
"\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.ArticleDocument,
|
"\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.ArticleDocument,
|
||||||
"\n\t\tquery RecentArticle {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\ttitle\n\t\t\t\t\tcover_image {\n\t\t\t\t\t\tid\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tviews\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.RecentArticleDocument,
|
"\n\t\tquery RecentArticle {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\ttitle\n\t\t\t\t\tcover_image {\n\t\t\t\t\t\tid\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tviews\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.RecentArticleDocument,
|
||||||
"\n\t\tquery Article_translations($slug: String!) {\n\t\t\tarticle_translations(filter: { slug: { _eq: $slug } }) {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tslug\n\t\t\t\tcontent\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t}\n\t\t\t\tpub_date\n\t\t\t\tviews\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcover_image {\n\t\t\t\t\ttitle\n\t\t\t\t\ttype\n\t\t\t\t\tfilename_disk\n\t\t\t\t}\n\t\t\t\tarticle_id {\n\t\t\t\t\ttranslations {\n\t\t\t\t\t\tslug\n\t\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\t\tcode\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpub_date\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.Article_TranslationsDocument,
|
"\n\t\tquery Article_translations($slug: String!) {\n\t\t\tarticle_translations(filter: { slug: { _eq: $slug } }) {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tslug\n\t\t\t\tcontent\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t}\n\t\t\t\tpub_date\n\t\t\t\tviews\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcover_image {\n\t\t\t\t\ttitle\n\t\t\t\t\ttype\n\t\t\t\t\tfilename_disk\n\t\t\t\t}\n\t\t\t\tarticle_id {\n\t\t\t\t\ttranslations {\n\t\t\t\t\t\tslug\n\t\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\t\tcode\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpub_date\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.Article_TranslationsDocument,
|
||||||
@ -26,6 +28,8 @@ type Documents = {
|
|||||||
"\n\t\tquery Tag {\n\t\t\ttag {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t}\n\t\t}\n\t": typeof types.TagDocument,
|
"\n\t\tquery Tag {\n\t\t\ttag {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t}\n\t\t}\n\t": typeof types.TagDocument,
|
||||||
};
|
};
|
||||||
const documents: Documents = {
|
const documents: Documents = {
|
||||||
|
"\n\t\tquery LatestAppealByClientIpQuery($client_ip: String!, $limit: Int!) {\n\t\t\tappeal(filter: { client_ip: { _eq: $client_ip } }, sort: [\"-date_created\"], limit: $limit) {\n\t\t\t\tclient_ip\n\t\t\t\tdate_created\n\t\t\t}\n\t\t}\n\t": types.LatestAppealByClientIpQueryDocument,
|
||||||
|
"\n\t\tmutation CreateAppealQuery(\n\t\t\t$name: String!\n\t\t\t$email: String!\n\t\t\t$subject: String!\n\t\t\t$message: String!\n\t\t\t$client_ip: String!\n\t\t) {\n\t\t\tcreate_appeal_item(\n\t\t\t\tdata: { name: $name, email: $email, subject: $subject, message: $message, client_ip: $client_ip }\n\t\t\t) {\n\t\t\t\tid\n\t\t\t}\n\t\t}\n\t": types.CreateAppealQueryDocument,
|
||||||
"\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.ArticleDocument,
|
"\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.ArticleDocument,
|
||||||
"\n\t\tquery RecentArticle {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\ttitle\n\t\t\t\t\tcover_image {\n\t\t\t\t\t\tid\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tviews\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.RecentArticleDocument,
|
"\n\t\tquery RecentArticle {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\ttitle\n\t\t\t\t\tcover_image {\n\t\t\t\t\t\tid\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tviews\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.RecentArticleDocument,
|
||||||
"\n\t\tquery Article_translations($slug: String!) {\n\t\t\tarticle_translations(filter: { slug: { _eq: $slug } }) {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tslug\n\t\t\t\tcontent\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t}\n\t\t\t\tpub_date\n\t\t\t\tviews\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcover_image {\n\t\t\t\t\ttitle\n\t\t\t\t\ttype\n\t\t\t\t\tfilename_disk\n\t\t\t\t}\n\t\t\t\tarticle_id {\n\t\t\t\t\ttranslations {\n\t\t\t\t\t\tslug\n\t\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\t\tcode\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpub_date\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.Article_TranslationsDocument,
|
"\n\t\tquery Article_translations($slug: String!) {\n\t\t\tarticle_translations(filter: { slug: { _eq: $slug } }) {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tslug\n\t\t\t\tcontent\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t}\n\t\t\t\tpub_date\n\t\t\t\tviews\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcover_image {\n\t\t\t\t\ttitle\n\t\t\t\t\ttype\n\t\t\t\t\tfilename_disk\n\t\t\t\t}\n\t\t\t\tarticle_id {\n\t\t\t\t\ttranslations {\n\t\t\t\t\t\tslug\n\t\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\t\tcode\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpub_date\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.Article_TranslationsDocument,
|
||||||
@ -37,6 +41,14 @@ const documents: Documents = {
|
|||||||
"\n\t\tquery Tag {\n\t\t\ttag {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t}\n\t\t}\n\t": types.TagDocument,
|
"\n\t\tquery Tag {\n\t\t\ttag {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t}\n\t\t}\n\t": types.TagDocument,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
|
*/
|
||||||
|
export function graphql(source: "\n\t\tquery LatestAppealByClientIpQuery($client_ip: String!, $limit: Int!) {\n\t\t\tappeal(filter: { client_ip: { _eq: $client_ip } }, sort: [\"-date_created\"], limit: $limit) {\n\t\t\t\tclient_ip\n\t\t\t\tdate_created\n\t\t\t}\n\t\t}\n\t"): typeof import('./graphql').LatestAppealByClientIpQueryDocument;
|
||||||
|
/**
|
||||||
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
|
*/
|
||||||
|
export function graphql(source: "\n\t\tmutation CreateAppealQuery(\n\t\t\t$name: String!\n\t\t\t$email: String!\n\t\t\t$subject: String!\n\t\t\t$message: String!\n\t\t\t$client_ip: String!\n\t\t) {\n\t\t\tcreate_appeal_item(\n\t\t\t\tdata: { name: $name, email: $email, subject: $subject, message: $message, client_ip: $client_ip }\n\t\t\t) {\n\t\t\t\tid\n\t\t\t}\n\t\t}\n\t"): typeof import('./graphql').CreateAppealQueryDocument;
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -4517,6 +4517,25 @@ export type Version_Tag = {
|
|||||||
title?: Maybe<Scalars['String']['output']>;
|
title?: Maybe<Scalars['String']['output']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LatestAppealByClientIpQueryQueryVariables = Exact<{
|
||||||
|
client_ip: Scalars['String']['input'];
|
||||||
|
limit: Scalars['Int']['input'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type LatestAppealByClientIpQueryQuery = { __typename?: 'Query', appeal: Array<{ __typename?: 'appeal', client_ip: string, date_created?: any | null }> };
|
||||||
|
|
||||||
|
export type CreateAppealQueryMutationVariables = Exact<{
|
||||||
|
name: Scalars['String']['input'];
|
||||||
|
email: Scalars['String']['input'];
|
||||||
|
subject: Scalars['String']['input'];
|
||||||
|
message: Scalars['String']['input'];
|
||||||
|
client_ip: Scalars['String']['input'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type CreateAppealQueryMutation = { __typename?: 'Mutation', create_appeal_item?: { __typename?: 'appeal', id: string } | null };
|
||||||
|
|
||||||
export type ArticleQueryVariables = Exact<{ [key: string]: never; }>;
|
export type ArticleQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
@ -4590,6 +4609,27 @@ export class TypedDocumentString<TResult, TVariables>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const LatestAppealByClientIpQueryDocument = new TypedDocumentString(`
|
||||||
|
query LatestAppealByClientIpQuery($client_ip: String!, $limit: Int!) {
|
||||||
|
appeal(
|
||||||
|
filter: {client_ip: {_eq: $client_ip}}
|
||||||
|
sort: ["-date_created"]
|
||||||
|
limit: $limit
|
||||||
|
) {
|
||||||
|
client_ip
|
||||||
|
date_created
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`) as unknown as TypedDocumentString<LatestAppealByClientIpQueryQuery, LatestAppealByClientIpQueryQueryVariables>;
|
||||||
|
export const CreateAppealQueryDocument = new TypedDocumentString(`
|
||||||
|
mutation CreateAppealQuery($name: String!, $email: String!, $subject: String!, $message: String!, $client_ip: String!) {
|
||||||
|
create_appeal_item(
|
||||||
|
data: {name: $name, email: $email, subject: $subject, message: $message, client_ip: $client_ip}
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`) as unknown as TypedDocumentString<CreateAppealQueryMutation, CreateAppealQueryMutationVariables>;
|
||||||
export const ArticleDocument = new TypedDocumentString(`
|
export const ArticleDocument = new TypedDocumentString(`
|
||||||
query Article {
|
query Article {
|
||||||
article {
|
article {
|
||||||
|
|||||||
@ -17,8 +17,7 @@ export default {
|
|||||||
/* Page descriptions */
|
/* Page descriptions */
|
||||||
"pageDescription.articles":
|
"pageDescription.articles":
|
||||||
"Technical articles, tutorials and various reference materials related to IT topics can be found here.",
|
"Technical articles, tutorials and various reference materials related to IT topics can be found here.",
|
||||||
"pageDescription.projects":
|
"pageDescription.projects": "A few of my main projects that have been developed and published in open access.",
|
||||||
"A few of my main projects that have been developed and published in open access.",
|
|
||||||
"pageDescription.contacts": "Use the feedback form or write to me on social networks.",
|
"pageDescription.contacts": "Use the feedback form or write to me on social networks.",
|
||||||
/* Navigation */
|
/* Navigation */
|
||||||
"navigation.articles": "Articles",
|
"navigation.articles": "Articles",
|
||||||
@ -57,4 +56,9 @@ export default {
|
|||||||
/* Footer */
|
/* Footer */
|
||||||
"footer.rssFeed": "RSS Feed",
|
"footer.rssFeed": "RSS Feed",
|
||||||
"footer.developed": "Developed",
|
"footer.developed": "Developed",
|
||||||
|
/* Submitted page */
|
||||||
|
"submitted.heading": "Message sent",
|
||||||
|
"submitted.description": "Your message has been sent, thank you.",
|
||||||
|
"submitted.error": "Error while sending a message. Try again later.",
|
||||||
|
"submitted.limit": "Send your message later.",
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,10 +17,8 @@ export default {
|
|||||||
/* Page descriptions */
|
/* Page descriptions */
|
||||||
"pageDescription.articles":
|
"pageDescription.articles":
|
||||||
"Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой находятся здесь.",
|
"Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой находятся здесь.",
|
||||||
"pageDescription.projects":
|
"pageDescription.projects": "Несколько моих основных проектов которые были разработаны и выложены в открытый доступ.",
|
||||||
"Несколько моих основных проектов которые были разработаны и выложены в открытый доступ.",
|
"pageDescription.contacts": "Воспользуйтесь формой обратной связи или напишите мне в социальные сети.",
|
||||||
"pageDescription.contacts":
|
|
||||||
"Воспользуйтесь формой обратной связи или напишите мне в социальные сети.",
|
|
||||||
/* Navigation */
|
/* Navigation */
|
||||||
"navigation.articles": "Статьи",
|
"navigation.articles": "Статьи",
|
||||||
"navigation.projects": "Проекты",
|
"navigation.projects": "Проекты",
|
||||||
@ -58,4 +56,9 @@ export default {
|
|||||||
/* Footer */
|
/* Footer */
|
||||||
"footer.rssFeed": "RSS Лента",
|
"footer.rssFeed": "RSS Лента",
|
||||||
"footer.developed": "Разработка",
|
"footer.developed": "Разработка",
|
||||||
|
/* Submitted page */
|
||||||
|
"submitted.heading": "Сообщение отправлено",
|
||||||
|
"submitted.description": "Ваше сообщение отправлено, спасибо.",
|
||||||
|
"submitted.error": "Ошибка отправки сообщения. Повторите позже.",
|
||||||
|
"submitted.limit": "Отправьте ваше сообщение позже.",
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
import { APIRoute } from "astro";
|
|
||||||
|
|
||||||
export const POST: APIRoute = async ({ request, clientAddress }) => {
|
|
||||||
const data = await request.formData();
|
|
||||||
return new Response("done.", { status: 200 });
|
|
||||||
};
|
|
||||||
43
src/queries/appeal.ts
Normal file
43
src/queries/appeal.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { graphql } from "../graphql";
|
||||||
|
import execute from "../graphql/execute";
|
||||||
|
|
||||||
|
export async function getLatestAppealsByClientIp(client_ip: string, limit: number = 5) {
|
||||||
|
const LatestAppealByClientIpQuery = graphql(`
|
||||||
|
query LatestAppealByClientIpQuery($client_ip: String!, $limit: Int!) {
|
||||||
|
appeal(filter: { client_ip: { _eq: $client_ip } }, sort: ["-date_created"], limit: $limit) {
|
||||||
|
client_ip
|
||||||
|
date_created
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const result = await execute(LatestAppealByClientIpQuery, { client_ip, limit });
|
||||||
|
|
||||||
|
if (!result.data) return [];
|
||||||
|
|
||||||
|
return result.data.appeal;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateAppealParams = { name: string; email: string; subject: string; message: string; client_ip: string };
|
||||||
|
|
||||||
|
export async function createAppeal({ name, email, subject, message, client_ip }: CreateAppealParams) {
|
||||||
|
const CreateAppealQuery = graphql(`
|
||||||
|
mutation CreateAppealQuery(
|
||||||
|
$name: String!
|
||||||
|
$email: String!
|
||||||
|
$subject: String!
|
||||||
|
$message: String!
|
||||||
|
$client_ip: String!
|
||||||
|
) {
|
||||||
|
create_appeal_item(
|
||||||
|
data: { name: $name, email: $email, subject: $subject, message: $message, client_ip: $client_ip }
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const result = await execute(CreateAppealQuery, { name, email, subject, message, client_ip });
|
||||||
|
|
||||||
|
return result.data?.create_appeal_item;
|
||||||
|
}
|
||||||
@ -2,3 +2,4 @@ export * from "./tag";
|
|||||||
export * from "./article";
|
export * from "./article";
|
||||||
export * from "./project";
|
export * from "./project";
|
||||||
export * from "./global";
|
export * from "./global";
|
||||||
|
export * from "./appeal";
|
||||||
|
|||||||
@ -3,21 +3,25 @@ import PageLayout from "@layouts/default.astro";
|
|||||||
import PageHeading from "@components/PageHeading.astro";
|
import PageHeading from "@components/PageHeading.astro";
|
||||||
import ContactForm from "@components/ContactForm.astro";
|
import ContactForm from "@components/ContactForm.astro";
|
||||||
import ContactsList from "@components/ContactsList.astro";
|
import ContactsList from "@components/ContactsList.astro";
|
||||||
|
import SubmittedTemplate from "@templates/Submitted.astro";
|
||||||
|
import { actions } from "astro:actions";
|
||||||
import { getLanguageFromURL, useTranslation } from "@i18n/utils";
|
import { getLanguageFromURL, useTranslation } from "@i18n/utils";
|
||||||
|
|
||||||
const t = useTranslation(Astro);
|
const t = useTranslation(Astro);
|
||||||
const lang = getLanguageFromURL(Astro.url.pathname);
|
const lang = getLanguageFromURL(Astro.url.pathname);
|
||||||
|
const contactsFormSubmitResult = Astro.getActionResult(actions.appeal);
|
||||||
---
|
---
|
||||||
|
|
||||||
<PageLayout
|
{
|
||||||
noIndex
|
!contactsFormSubmitResult || contactsFormSubmitResult.error ? (
|
||||||
lang={lang}
|
<PageLayout noIndex lang={lang} title={t("pageTitle.contacts")} description={t("pageDescription.contacts")}>
|
||||||
title={t("pageTitle.contacts")}
|
|
||||||
description={t("pageDescription.contacts")}
|
|
||||||
>
|
|
||||||
<PageHeading heading={t("pageTitle.contacts")} description={t("pageDescription.contacts")} />
|
<PageHeading heading={t("pageTitle.contacts")} description={t("pageDescription.contacts")} />
|
||||||
<section>
|
<section>
|
||||||
<ContactForm />
|
<ContactForm />
|
||||||
<ContactsList />
|
<ContactsList />
|
||||||
</section>
|
</section>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
) : (
|
||||||
|
<SubmittedTemplate />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
13
src/templates/Submitted.astro
Normal file
13
src/templates/Submitted.astro
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
import PageLayout from "@layouts/default.astro";
|
||||||
|
import { getLanguageFromURL } from "@i18n/utils";
|
||||||
|
import UIString from "@root/components/UIString.astro";
|
||||||
|
const lang = getLanguageFromURL(Astro.url.pathname);
|
||||||
|
---
|
||||||
|
|
||||||
|
<PageLayout noIndex lang={lang}>
|
||||||
|
<div class="container">
|
||||||
|
<h1 style="font-size:var(--font-size-5xl);"><UIString key={"submitted.heading"} /></h1>
|
||||||
|
<p><UIString key={"submitted.description"} /></p>
|
||||||
|
</div>
|
||||||
|
</PageLayout>
|
||||||
Loading…
x
Reference in New Issue
Block a user