- Apple MacBook Pro 17
+ {{ data.ticket_category }}
- Silver
- Laptop
- $2999
+ {{ data.subject }}
+ {{ data.created_at }}
+ {{ data.status }}
-
+
diff --git a/frontend/components/profile/tickets/index/TicketsTableRowLoading.vue b/frontend/components/profile/tickets/index/TicketsTableRowLoading.vue
new file mode 100644
index 0000000..1af0932
--- /dev/null
+++ b/frontend/components/profile/tickets/index/TicketsTableRowLoading.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/profile/tickets/new/NewAttachment.vue b/frontend/components/profile/tickets/new/NewAttachment.vue
new file mode 100644
index 0000000..bb9d62c
--- /dev/null
+++ b/frontend/components/profile/tickets/new/NewAttachment.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
{{ (size / 1024).toFixed(2) }}KB
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/composables/api/account/useCreateOrUpdateAddress.ts b/frontend/composables/api/account/useCreateOrUpdateAddress.ts
index f5a069c..a2f978b 100644
--- a/frontend/composables/api/account/useCreateOrUpdateAddress.ts
+++ b/frontend/composables/api/account/useCreateOrUpdateAddress.ts
@@ -5,17 +5,17 @@ import { API_ENDPOINTS } from "~/constants";
// types
-export type CreateOrUpdateAddressRequest = Omit;
+export type CreateOrUpdateAddressResponse = Omit;
const useCreateOrUpdateAddress = (update: ComputedRef) => {
// state
const { $axios: axios } = useNuxtApp();
- // method
+ // methods
const handleCreateOrUpdateAddress = async (
- addressData: CreateOrUpdateAddressRequest
+ addressData: CreateOrUpdateAddressResponse
) => {
const { data } = await axios[update.value ? "put" : "post"](
update.value
@@ -30,7 +30,7 @@ const useCreateOrUpdateAddress = (update: ComputedRef) => {
};
return useMutation({
- mutationFn: (addressData: CreateOrUpdateAddressRequest) =>
+ mutationFn: (addressData: CreateOrUpdateAddressResponse) =>
handleCreateOrUpdateAddress(addressData),
});
};
diff --git a/frontend/composables/api/account/useDeleteAddress.ts b/frontend/composables/api/account/useDeleteAddress.ts
index f894016..ada4989 100644
--- a/frontend/composables/api/account/useDeleteAddress.ts
+++ b/frontend/composables/api/account/useDeleteAddress.ts
@@ -14,7 +14,7 @@ const useDeleteAddress = () => {
const { $axios: axios } = useNuxtApp();
- // method
+ // methods
const handleDeleteAddress = async (id: number) => {
const { data } = await axios.delete(
diff --git a/frontend/composables/api/account/useUpdateAccount.ts b/frontend/composables/api/account/useUpdateAccount.ts
index 521d56a..8a4882e 100644
--- a/frontend/composables/api/account/useUpdateAccount.ts
+++ b/frontend/composables/api/account/useUpdateAccount.ts
@@ -20,7 +20,7 @@ const useUpdateAccount = () => {
const { $axios: axios } = useNuxtApp();
- // method
+ // methods
const handleUpdateAccount = async (params: UpdateAccountRequest) => {
const { data } = await axios.patch(
diff --git a/frontend/composables/api/auth/useAuth.ts b/frontend/composables/api/auth/useAuth.ts
index e74d967..d65e25d 100644
--- a/frontend/composables/api/auth/useAuth.ts
+++ b/frontend/composables/api/auth/useAuth.ts
@@ -1,11 +1,10 @@
export const useAuth = () => {
-
// state
const token = useCookie("token");
const refreshToken = useCookie("refresh-token");
- // method
+ // methods
const updateToken = (newToken: string) => {
token.value = newToken;
@@ -15,7 +14,7 @@ export const useAuth = () => {
refreshToken.value = newToken;
};
- const logout = (reload ?: boolean) => {
+ const logout = (reload?: boolean) => {
token.value = undefined;
refreshToken.value = undefined;
if (reload) window.location.reload();
@@ -25,6 +24,12 @@ export const useAuth = () => {
const isLoggedIn = computed(() => !!token.value);
- return { token, refreshToken, updateRefreshToken, updateToken, logout, isLoggedIn };
-
-};
\ No newline at end of file
+ return {
+ token,
+ refreshToken,
+ updateRefreshToken,
+ updateToken,
+ logout,
+ isLoggedIn,
+ };
+};
diff --git a/frontend/composables/api/auth/useRefreshAuth.ts b/frontend/composables/api/auth/useRefreshAuth.ts
index 6540427..520c175 100644
--- a/frontend/composables/api/auth/useRefreshAuth.ts
+++ b/frontend/composables/api/auth/useRefreshAuth.ts
@@ -6,17 +6,15 @@ import { API_ENDPOINTS } from "~/constants";
// types
export type RefreshAuthRequest = {
- refresh: string,
+ refresh: string;
};
export type RefreshAuthResponse = {
- access: string,
- refresh: string,
+ access: string;
+ refresh: string;
};
-
const useRefreshAuth = () => {
-
// state
const { $axios: axios } = useNuxtApp();
@@ -24,12 +22,16 @@ const useRefreshAuth = () => {
// methods
const handleRefreshAuth = async (variables: RefreshAuthRequest) => {
- const { data } = await axios.post(`${API_ENDPOINTS.auth.refresh}/`, variables);
+ const { data } = await axios.post(
+ `${API_ENDPOINTS.auth.refresh}`,
+ variables
+ );
return data;
};
return useMutation({
- mutationFn: (variables: RefreshAuthRequest) => handleRefreshAuth(variables)
+ mutationFn: (variables: RefreshAuthRequest) =>
+ handleRefreshAuth(variables),
});
};
diff --git a/frontend/composables/api/auth/useSignIn.ts b/frontend/composables/api/auth/useSignIn.ts
index 60eeb7e..92f68e3 100644
--- a/frontend/composables/api/auth/useSignIn.ts
+++ b/frontend/composables/api/auth/useSignIn.ts
@@ -11,13 +11,11 @@ export type SignInRequest = {
};
export type SignInResponse = {
- access: string,
- refresh: string,
+ access: string;
+ refresh: string;
};
-
const useSignIn = () => {
-
// state
const { $axios: axios } = useNuxtApp();
@@ -25,12 +23,15 @@ const useSignIn = () => {
// methods
const handleSignIn = async (variables: SignInRequest) => {
- const { data } = await axios.post(`${API_ENDPOINTS.auth.signin}/`, variables);
+ const { data } = await axios.post(
+ `${API_ENDPOINTS.auth.signin}`,
+ variables
+ );
return data;
};
return useMutation({
- mutationFn: (variables: SignInRequest) => handleSignIn(variables)
+ mutationFn: (variables: SignInRequest) => handleSignIn(variables),
});
};
diff --git a/frontend/composables/api/chat/useCreateChatMessage.ts b/frontend/composables/api/chat/useCreateChatMessage.ts
index 87f5da6..700b8b4 100644
--- a/frontend/composables/api/chat/useCreateChatMessage.ts
+++ b/frontend/composables/api/chat/useCreateChatMessage.ts
@@ -11,64 +11,37 @@ export type CreateChatMessageRequest = {
new_message: string;
};
-export type CreateChatMessageResponse = Chat[]
+export type CreateChatMessageResponse = Chat[];
const useCreateChatMessage = (queryClient: QueryClient) => {
-
// state
const { $axios: axios } = useNuxtApp();
- // method
+ // methods
- const handleCreateChatMessage = async (variables: CreateChatMessageRequest) => {
-
- const { data } = await axios.post(`${API_ENDPOINTS.chat.new_message}/${variables.productId}`, variables);
+ const handleCreateChatMessage = async (
+ variables: CreateChatMessageRequest
+ ) => {
+ const { data } = await axios.post(
+ `${API_ENDPOINTS.chat.new_message}/${variables.productId}`,
+ variables
+ );
return data;
};
return useMutation({
mutationKey: [MUTATION_KEYS.create_chat],
- mutationFn: (variables: CreateChatMessageRequest) => handleCreateChatMessage(variables),
+ mutationFn: (variables: CreateChatMessageRequest) =>
+ handleCreateChatMessage(variables),
onMutate: (newMessage) => {
- const prevData = queryClient.getQueriesData({ queryKey: [QUERY_KEYS.chat] });
-
- queryClient.setQueryData>>([QUERY_KEYS.chat], (oldData) => {
- const lastPage = oldData!.pages[oldData!.pages.length - 1];
-
- return {
- pages: [
- {
- count: lastPage.count,
- next: lastPage.next,
- previous: lastPage.previous,
- results: [
- {
- id: Date.now(),
- content: newMessage.new_message,
- sender: "user"
- }
- ]
- },
- ...oldData!.pages
- ],
- pageParams: [
- ...oldData!.pageParams,
- {
- limit: 10,
- offset: 0
- }
- ]
- };
+ const prevData = queryClient.getQueriesData({
+ queryKey: [QUERY_KEYS.chat],
});
-
- return { prevData: prevData ? prevData[0][1] : undefined };
- },
- onSuccess: (response) => {
-
- queryClient.setQueryData>>([QUERY_KEYS.chat], (oldData) => {
- if (oldData) {
+ queryClient.setQueryData>>(
+ [QUERY_KEYS.chat],
+ (oldData) => {
const lastPage = oldData!.pages[oldData!.pages.length - 1];
return {
@@ -77,38 +50,72 @@ const useCreateChatMessage = (queryClient: QueryClient) => {
count: lastPage.count,
next: lastPage.next,
previous: lastPage.previous,
- results: {
- ...response[0],
- id: Date.now()
- }
+ results: [
+ {
+ id: Date.now(),
+ content: newMessage.new_message,
+ sender: "user",
+ },
+ ],
},
- ...oldData!.pages
+ ...oldData!.pages,
],
pageParams: [
...oldData!.pageParams,
{
limit: 10,
- offset: 0
- }
- ]
+ offset: 0,
+ },
+ ],
};
}
+ );
- return oldData;
- });
+ return { prevData: prevData ? prevData[0][1] : undefined };
+ },
+ onSuccess: (response) => {
+ queryClient.setQueryData>>(
+ [QUERY_KEYS.chat],
+ (oldData) => {
+ if (oldData) {
+ const lastPage =
+ oldData!.pages[oldData!.pages.length - 1];
+ return {
+ pages: [
+ {
+ count: lastPage.count,
+ next: lastPage.next,
+ previous: lastPage.previous,
+ results: {
+ ...response[0],
+ id: Date.now(),
+ },
+ },
+ ...oldData!.pages,
+ ],
+ pageParams: [
+ ...oldData!.pageParams,
+ {
+ limit: 10,
+ offset: 0,
+ },
+ ],
+ };
+ }
+
+ return oldData;
+ }
+ );
},
onError: (err, newMessage, context) => {
if (context) {
- queryClient.setQueryData(
- [QUERY_KEYS.chat],
- context.prevData
- );
+ queryClient.setQueryData([QUERY_KEYS.chat], context.prevData);
}
},
onSettled: (newMessage) => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.chat] });
- }
+ },
});
};
diff --git a/frontend/composables/api/chat/useGetChat.ts b/frontend/composables/api/chat/useGetChat.ts
index 8209937..927af3e 100644
--- a/frontend/composables/api/chat/useGetChat.ts
+++ b/frontend/composables/api/chat/useGetChat.ts
@@ -8,34 +8,36 @@ import { useAuth } from "~/composables/api/auth/useAuth";
export type GetBranchResponse = ApiPaginated;
-const useGetBranch = (
- productId: string | number,
- enabled: Ref
-) => {
-
+const useGetBranch = (productId: string | number, enabled: Ref) => {
// state
const { $axios: axios } = useNuxtApp();
const { isLoggedIn } = useAuth();
- // method
+ // methods
- const handleGetChat = async ({ productId, limit, offset }: {
- productId: number | string,
- limit: number,
- offset: number
+ const handleGetChat = async ({
+ productId,
+ limit,
+ offset,
+ }: {
+ productId: number | string;
+ limit: number;
+ offset: number;
}) => {
-
- const { data } = await axios.get(`${API_ENDPOINTS.chat.messages}/${productId}`, {
- params: {
- offset,
- limit
- },
- headers: {
- Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoyMTY3ODE2OTAwLCJpYXQiOjE3MzU4MTY5MDAsImp0aSI6ImQwN2E2Y2Y2NzgwZjRlNTE5NWIzOGQxMTAzYzU4NDQ3IiwidXNlcl9pZCI6NX0.slwd7ZSV7nUXEuDTYwwHUOo9ekCefwEEL4kVv2vSTFo`
+ const { data } = await axios.get(
+ `${API_ENDPOINTS.chat.messages}/${productId}`,
+ {
+ params: {
+ offset,
+ limit,
+ },
+ headers: {
+ Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoyMTY3ODE2OTAwLCJpYXQiOjE3MzU4MTY5MDAsImp0aSI6ImQwN2E2Y2Y2NzgwZjRlNTE5NWIzOGQxMTAzYzU4NDQ3IiwidXNlcl9pZCI6NX0.slwd7ZSV7nUXEuDTYwwHUOo9ekCefwEEL4kVv2vSTFo`,
+ },
}
- });
+ );
return data;
};
@@ -44,21 +46,22 @@ const useGetBranch = (
queryKey: [QUERY_KEYS.chat],
initialPageParam: {
limit: 10,
- offset: 0
+ offset: 0,
},
- queryFn: ({ pageParam }) => handleGetChat({
- limit: pageParam.limit,
- offset: pageParam.offset,
- productId: productId
- }),
+ queryFn: ({ pageParam }) =>
+ handleGetChat({
+ limit: pageParam.limit,
+ offset: pageParam.offset,
+ productId: productId,
+ }),
getNextPageParam: (lastPage, pages) => {
if (!lastPage.next) return undefined;
return {
limit: 10,
- offset: pages.length * 10
+ offset: pages.length * 10,
};
- }
+ },
});
};
diff --git a/frontend/composables/api/product/useCreateComment.ts b/frontend/composables/api/product/useCreateComment.ts
index 9443850..cd43eff 100644
--- a/frontend/composables/api/product/useCreateComment.ts
+++ b/frontend/composables/api/product/useCreateComment.ts
@@ -6,24 +6,27 @@ import { API_ENDPOINTS } from "~/constants";
// types
export type CreateCommentRequest = {
- content: string
+ content: string;
};
const useCreateComment = (id: number | string | undefined) => {
-
// state
const { $axios: axios } = useNuxtApp();
- // method
+ // methods
const handleCreateComment = async (variables: CreateCommentRequest) => {
- const { data } = await axios.post(`${API_ENDPOINTS.product.create_comment}/${id}`, variables);
+ const { data } = await axios.post(
+ `${API_ENDPOINTS.product.create_comment}/${id}`,
+ variables
+ );
return data;
};
return useMutation({
- mutationFn: (variables: CreateCommentRequest) => handleCreateComment(variables)
+ mutationFn: (variables: CreateCommentRequest) =>
+ handleCreateComment(variables),
});
};
diff --git a/frontend/composables/api/tickets/useCreateTicket.ts b/frontend/composables/api/tickets/useCreateTicket.ts
new file mode 100644
index 0000000..353e1cc
--- /dev/null
+++ b/frontend/composables/api/tickets/useCreateTicket.ts
@@ -0,0 +1,50 @@
+// imports
+
+import { useMutation } from "@tanstack/vue-query";
+import { API_ENDPOINTS } from "~/constants";
+
+// types
+
+export type CreateTicketRequest = {
+ ticket_category: string | undefined;
+ order: number | undefined;
+ subject: string;
+ content: string;
+ attachments: {
+ id: number;
+ file_link: string;
+ date: string;
+ size: number;
+ name: string;
+ }[];
+};
+
+const useCreateTicket = () => {
+ // state
+
+ const { $axios: axios } = useNuxtApp();
+
+ // methods
+
+ const handleCreateTicket = async (params: CreateTicketRequest) => {
+ const { data } = await axios.post(
+ API_ENDPOINTS.account.address.update,
+ {
+ ...params,
+ },
+ {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ },
+ }
+ );
+ return data;
+ };
+
+ return useMutation({
+ mutationFn: (ticketData: CreateTicketRequest) =>
+ handleCreateTicket(ticketData),
+ });
+};
+
+export default useCreateTicket;
diff --git a/frontend/composables/api/tickets/useDeleteAttachment.ts b/frontend/composables/api/tickets/useDeleteAttachment.ts
new file mode 100644
index 0000000..12f06b3
--- /dev/null
+++ b/frontend/composables/api/tickets/useDeleteAttachment.ts
@@ -0,0 +1,36 @@
+// imports
+
+import { useMutation } from "@tanstack/vue-query";
+import { API_ENDPOINTS } from "~/constants";
+
+// types
+
+export type DeleteAttachmentRequest = {
+ id: number | string;
+};
+
+// methods
+
+export const handleDeleteAttachment = async ({
+ id,
+}: DeleteAttachmentRequest) => {
+ // state
+
+ const { $axios: axios } = useNuxtApp();
+
+ const { data } = await axios.delete(
+ `${API_ENDPOINTS.tickets.delete_attachment}/${id}`
+ );
+ return data;
+};
+
+// composable
+
+const useDeleteAttachment = () => {
+ return useMutation({
+ mutationFn: (data: DeleteAttachmentRequest) =>
+ handleDeleteAttachment({ ...data }),
+ });
+};
+
+export default useDeleteAttachment;
diff --git a/frontend/composables/api/tickets/useGetAllTickets.ts b/frontend/composables/api/tickets/useGetAllTickets.ts
new file mode 100644
index 0000000..81c7c27
--- /dev/null
+++ b/frontend/composables/api/tickets/useGetAllTickets.ts
@@ -0,0 +1,44 @@
+// imports
+
+import { useQuery } from "@tanstack/vue-query";
+import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
+
+// types
+
+export type GetAllTicketsResponse = ApiPaginated;
+
+export type GetAllTicketsFilters = {
+ sort: string | undefined;
+ filter: string | undefined;
+ page: string | string[];
+};
+
+const useGetAllTickets = (params: Ref) => {
+ // state
+
+ const { $axios: axios } = useNuxtApp();
+
+ // methods
+
+ const handleGetAllTickets = async (params: GetAllTicketsFilters) => {
+ const { data } = await axios.get(
+ API_ENDPOINTS.tickets.get_all,
+ {
+ params: {
+ sort: params.sort,
+ filter: params.filter,
+ offset: Number(params.page) * 10 - 10,
+ limit: 10,
+ },
+ }
+ );
+ return data;
+ };
+
+ return useQuery({
+ queryKey: [QUERY_KEYS.tickets, params],
+ queryFn: () => handleGetAllTickets(params.value),
+ });
+};
+
+export default useGetAllTickets;
diff --git a/frontend/composables/api/tickets/useUploadAttachment.ts b/frontend/composables/api/tickets/useUploadAttachment.ts
new file mode 100644
index 0000000..46ad07d
--- /dev/null
+++ b/frontend/composables/api/tickets/useUploadAttachment.ts
@@ -0,0 +1,52 @@
+// imports
+
+import { useMutation } from "@tanstack/vue-query";
+import { API_ENDPOINTS } from "~/constants";
+
+// types
+
+export type UploadAttachmentRequest = {
+ file: File;
+};
+
+export type UploadAttachmentResponse = {
+ id: number;
+ file_link: string;
+ date: string;
+ size: number;
+ name: string;
+};
+
+// methods
+
+export const handleUploadAttachment = async ({
+ file,
+}: UploadAttachmentRequest) => {
+ // state
+
+ const { $axios: axios } = useNuxtApp();
+
+ const { data } = await axios.post(
+ API_ENDPOINTS.tickets.upload_attachment,
+ {
+ file,
+ },
+ {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ },
+ }
+ );
+ return data;
+};
+
+// composable
+
+const useUploadAttachment = () => {
+ return useMutation({
+ mutationFn: (data: UploadAttachmentRequest) =>
+ handleUploadAttachment({ file: data.file }),
+ });
+};
+
+export default useUploadAttachment;
diff --git a/frontend/constants/index.ts b/frontend/constants/index.ts
index 98f6206..4a835d5 100644
--- a/frontend/constants/index.ts
+++ b/frontend/constants/index.ts
@@ -34,6 +34,12 @@ export const API_ENDPOINTS = {
get_all: "/products",
categories: "/products/categories",
},
+ tickets: {
+ get_all: "/tickets",
+ create: "/tickets/create",
+ upload_attachment: "/tickets/attachment/create",
+ delete_attachment: "/tickets/attachment/delete",
+ },
};
export const QUERY_KEYS = {
@@ -47,6 +53,7 @@ export const QUERY_KEYS = {
account: "account",
categories: "categories",
addresses: "addresses",
+ tickets: "tickets",
};
export const MUTATION_KEYS = {
diff --git a/frontend/pages/profile/tickets/index.vue b/frontend/pages/profile/tickets/index.vue
index 4051d53..363050c 100644
--- a/frontend/pages/profile/tickets/index.vue
+++ b/frontend/pages/profile/tickets/index.vue
@@ -1,4 +1,10 @@
@@ -30,7 +56,10 @@ const tableHeads = ref([
-
+
ترتیب بر اساس
-
+
وضعیت پرداخت
@@ -63,7 +95,14 @@ const tableHeads = ref([
-
+
+
+
+
+
diff --git a/frontend/pages/profile/tickets/new.vue b/frontend/pages/profile/tickets/new.vue
index e79aff9..e409be9 100644
--- a/frontend/pages/profile/tickets/new.vue
+++ b/frontend/pages/profile/tickets/new.vue
@@ -1,4 +1,13 @@
@@ -75,7 +154,7 @@ const ticketData = ref({
@@ -133,23 +212,39 @@ const ticketData = ref({
+
+
+
-
+
-
- ارسال تیکت
+ />
+ ارسال تیکت
diff --git a/frontend/plugins/axios.ts b/frontend/plugins/axios.ts
index 296a285..6218f29 100644
--- a/frontend/plugins/axios.ts
+++ b/frontend/plugins/axios.ts
@@ -8,7 +8,9 @@ export default defineNuxtPlugin(() => {
const { token, logout } = useAuth();
const axios = axiosOriginal.create({
- baseURL: config.public.API_BASE_URL
+ timeout: 30000,
+ timeoutErrorMessage: "فرآیند بیش از حد انتظار طول کشید",
+ baseURL: config.public.API_BASE_URL,
});
axios.interceptors.request.use((config) => {
@@ -16,29 +18,32 @@ export default defineNuxtPlugin(() => {
!config.url?.includes(API_ENDPOINTS.auth.signin) &&
!config.url?.includes(API_ENDPOINTS.account.send_otp)
) {
- config.headers.Authorization = token.value ? `Bearer ${token.value}` : undefined;
+ config.headers.Authorization = token.value
+ ? `Bearer ${token.value}`
+ : undefined;
}
return config;
});
- axios.interceptors.response.use((response) => {
- return response;
- }, async function(error) {
+ axios.interceptors.response.use(
+ (response) => {
+ return response;
+ },
+ async function (error) {
+ await Logger.axiosErrorLog(error);
- await Logger.axiosErrorLog(error);
+ // if (error.status === 401) {
+ // logout();
+ // }
- // if (error.status === 401) {
- // logout();
- // }
-
- return Promise.reject(error);
- });
+ return Promise.reject(error);
+ }
+ );
return {
provide: {
- axios
- }
+ axios,
+ },
};
-
-});
\ No newline at end of file
+});
diff --git a/frontend/types/global.d.ts b/frontend/types/global.d.ts
index 49d5f98..16ca7f9 100644
--- a/frontend/types/global.d.ts
+++ b/frontend/types/global.d.ts
@@ -165,4 +165,13 @@ declare global {
iconClass?: string;
onClick?: () => void;
};
+
+ type Ticket = {
+ id: number;
+ subject: string;
+ ticket_category: string;
+ status: string;
+ created_at: string;
+ updated_at: string;
+ };
}