mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 02:19:08 +03:00
contacts form
This commit is contained in:
parent
26e6276b23
commit
838a74c086
@ -1,2 +1,3 @@
|
||||
DIRECTUS_URL=
|
||||
DIRECTUS_URL_GRAPHQL=
|
||||
DIRECTUS_TOKEN=
|
||||
@ -12,12 +12,21 @@ export default defineConfig({
|
||||
site: "https://yarkov.tech",
|
||||
compressHTML: true,
|
||||
output: "server",
|
||||
server: {
|
||||
port: 9966,
|
||||
allowedHosts: ["showcase.yarkov.tech"],
|
||||
},
|
||||
adapter: node({
|
||||
mode: "standalone",
|
||||
}),
|
||||
env: {
|
||||
schema: {
|
||||
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: "" }),
|
||||
},
|
||||
},
|
||||
|
||||
@ -11,11 +11,12 @@
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev": "astro dev --host",
|
||||
"build": "astro check && astro build",
|
||||
"start": "node --env-file=.env ./dist/server/entry.mjs",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"telemetry-disable": "astro telemetry disable",
|
||||
"codegen": "graphql-codegen --require dotenv/config --config codegen.ts --watch"
|
||||
},
|
||||
"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,34 +2,45 @@
|
||||
import Button from "@components/Button.astro";
|
||||
import UIString from "./UIString.astro";
|
||||
import { useTranslation } from "@i18n/utils";
|
||||
import { actions } from "astro:actions";
|
||||
|
||||
const t = useTranslation(Astro);
|
||||
const formResult = Astro.getActionResult(actions.appeal);
|
||||
---
|
||||
|
||||
<form name="contact" action="/api/appeal" method="post">
|
||||
<div class="flex">
|
||||
<div>
|
||||
<input name="name" placeholder={t("input.nameField")} type="text" maxlength="20" autocomplete="off" required />
|
||||
<>
|
||||
<form name="contact" action={actions.appeal} method="post">
|
||||
<div class="flex">
|
||||
<div>
|
||||
<input name="name" placeholder={t("input.nameField")} type="text" maxlength="20" autocomplete="off" required />
|
||||
</div>
|
||||
<div>
|
||||
<input name="email" placeholder="example@email.com" type="email" autocomplete="off" required />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<input name="email" placeholder="example@email.com" autocomplete="off" type="email" required />
|
||||
<input name="subject" placeholder={t("input.subjectField")} type="text" autocomplete="off" required />
|
||||
<textarea
|
||||
name="message"
|
||||
placeholder={t("input.messageField")}
|
||||
id="message"
|
||||
autocomplete="off"
|
||||
cols={30}
|
||||
rows={10}
|
||||
required></textarea>
|
||||
<div class="footer">
|
||||
{
|
||||
formResult?.error && (
|
||||
<span>
|
||||
<UIString key={formResult.error.status === 429 ? "submitted.limit" : "submitted.error"} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
<Button type={"submit"}>
|
||||
<UIString key={"btn.submit"} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<input name="subject" placeholder={t("input.subjectField")} type="text" autocomplete="off" required />
|
||||
<textarea
|
||||
name="message"
|
||||
placeholder={t("input.messageField")}
|
||||
id="message"
|
||||
autocomplete="off"
|
||||
cols={30}
|
||||
rows={10}
|
||||
required></textarea>
|
||||
<div class="footer">
|
||||
<Button type={"submit"}>
|
||||
<UIString key={"btn.submit"} />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</>
|
||||
|
||||
<style lang="scss">
|
||||
form {
|
||||
@ -48,7 +59,10 @@ const t = useTranslation(Astro);
|
||||
border: 1px solid var(--color-error);
|
||||
}
|
||||
& .footer {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 560px) {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import type { ExecutionResult } 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>(
|
||||
query: TypedDocumentString<TResult, TVariables>,
|
||||
...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
|
||||
) {
|
||||
const response = await fetch(DIRECTUS_URL + "/graphql", {
|
||||
const response = await fetch(DIRECTUS_URL_GRAPHQL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"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
|
||||
*/
|
||||
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 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,
|
||||
@ -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,
|
||||
};
|
||||
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 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,
|
||||
@ -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,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@ -4517,6 +4517,25 @@ export type Version_Tag = {
|
||||
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; }>;
|
||||
|
||||
|
||||
@ -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(`
|
||||
query Article {
|
||||
article {
|
||||
|
||||
@ -17,8 +17,7 @@ export default {
|
||||
/* Page descriptions */
|
||||
"pageDescription.articles":
|
||||
"Technical articles, tutorials and various reference materials related to IT topics can be found here.",
|
||||
"pageDescription.projects":
|
||||
"A few of my main projects that have been developed and published in open access.",
|
||||
"pageDescription.projects": "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.",
|
||||
/* Navigation */
|
||||
"navigation.articles": "Articles",
|
||||
@ -57,4 +56,9 @@ export default {
|
||||
/* Footer */
|
||||
"footer.rssFeed": "RSS Feed",
|
||||
"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 */
|
||||
"pageDescription.articles":
|
||||
"Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой находятся здесь.",
|
||||
"pageDescription.projects":
|
||||
"Несколько моих основных проектов которые были разработаны и выложены в открытый доступ.",
|
||||
"pageDescription.contacts":
|
||||
"Воспользуйтесь формой обратной связи или напишите мне в социальные сети.",
|
||||
"pageDescription.projects": "Несколько моих основных проектов которые были разработаны и выложены в открытый доступ.",
|
||||
"pageDescription.contacts": "Воспользуйтесь формой обратной связи или напишите мне в социальные сети.",
|
||||
/* Navigation */
|
||||
"navigation.articles": "Статьи",
|
||||
"navigation.projects": "Проекты",
|
||||
@ -58,4 +56,9 @@ export default {
|
||||
/* Footer */
|
||||
"footer.rssFeed": "RSS Лента",
|
||||
"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 "./project";
|
||||
export * from "./global";
|
||||
export * from "./appeal";
|
||||
|
||||
@ -3,21 +3,25 @@ import PageLayout from "@layouts/default.astro";
|
||||
import PageHeading from "@components/PageHeading.astro";
|
||||
import ContactForm from "@components/ContactForm.astro";
|
||||
import ContactsList from "@components/ContactsList.astro";
|
||||
import SubmittedTemplate from "@templates/Submitted.astro";
|
||||
import { actions } from "astro:actions";
|
||||
import { getLanguageFromURL, useTranslation } from "@i18n/utils";
|
||||
|
||||
const t = useTranslation(Astro);
|
||||
const lang = getLanguageFromURL(Astro.url.pathname);
|
||||
const contactsFormSubmitResult = Astro.getActionResult(actions.appeal);
|
||||
---
|
||||
|
||||
<PageLayout
|
||||
noIndex
|
||||
lang={lang}
|
||||
title={t("pageTitle.contacts")}
|
||||
description={t("pageDescription.contacts")}
|
||||
>
|
||||
<PageHeading heading={t("pageTitle.contacts")} description={t("pageDescription.contacts")} />
|
||||
<section>
|
||||
<ContactForm />
|
||||
<ContactsList />
|
||||
</section>
|
||||
</PageLayout>
|
||||
{
|
||||
!contactsFormSubmitResult || contactsFormSubmitResult.error ? (
|
||||
<PageLayout noIndex lang={lang} title={t("pageTitle.contacts")} description={t("pageDescription.contacts")}>
|
||||
<PageHeading heading={t("pageTitle.contacts")} description={t("pageDescription.contacts")} />
|
||||
<section>
|
||||
<ContactForm />
|
||||
<ContactsList />
|
||||
</section>
|
||||
</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