diff --git a/.eslintrc.json b/.eslintrc.json
index bffb357..580f3c9 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,3 +1,7 @@
{
- "extends": "next/core-web-vitals"
+ "extends": "next/core-web-vitals",
+ "rules": {
+ "react/no-unescaped-entities": "off",
+ "@next/next/no-img-element": "off"
+ }
}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8df415c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Metehan Alp Saral
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index c403366..1b6359b 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,20 @@
-This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+# Istanbulkart
-## Getting Started
+Kişiye Özel Istanbulkart Uygulaması; kendi bilgisayarınızda kullanabileceğiniz ve özelleştirebileceğiniz bir arayüz sağlamaktadır. Tarayıcı tarafından takip edilmediğiniz sürece kişisel verileriniz iştahlı gözlerden uzak bir şekilde Istanbulkart'ınıza girebilirsiniz, en son nerede kullandığınızı görebilirsiniz.
-First, run the development server:
+## Gereksinimler
-```bash
-npm run dev
-# or
-yarn dev
-# or
-pnpm dev
-# or
-bun dev
-```
-
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
-
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
-
-This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
-
-## Learn More
+* [Node.js](https://nodejs.org/)
+* bir İstanbulkart
-To learn more about Next.js, take a look at the following resources:
+## Kurulum
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
-
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
-
-## Deploy on Vercel
+```bash
+npm install
+```
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+## Geliştirme
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
+```bash
+npm run dev
+```
diff --git a/app/_variables.scss b/app/_variables.scss
new file mode 100644
index 0000000..3f8237a
--- /dev/null
+++ b/app/_variables.scss
@@ -0,0 +1,3 @@
+$bg-color: #1C1D2B;
+$box-color: #0D0E17;
+$theme-color: #1581be;
diff --git a/app/api/getCardList/route.ts b/app/api/getCardList/route.ts
new file mode 100644
index 0000000..64ccd8e
--- /dev/null
+++ b/app/api/getCardList/route.ts
@@ -0,0 +1,37 @@
+import getRequest from "@/helpers/getRequest";
+import { cookies } from "next/headers";
+import { NextRequest, NextResponse } from "next/server";
+
+export async function POST(req: NextRequest) {
+ const cookieStore = cookies();
+ const token = String(cookieStore.get("IstKart_Token")?.value);
+ const customerNumber = String(cookieStore.get("IstKart_ID")?.value);
+ const sessionToken = String(cookieStore.get("IstKart_SToken")?.value);
+
+ try {
+ const getCardList = await getRequest({
+ token,
+ command: "RI.OnGetCardList",
+ data: {
+ CustomerNumber: customerNumber,
+ SessionToken: sessionToken,
+ },
+ });
+
+ const cardListData = await getCardList.json();
+
+ if (cardListData.data.ResponseCode === "0") {
+ return NextResponse.json({
+ status: true,
+ list: cardListData.data.CardList,
+ });
+ }
+ } catch (error) {
+ console.error("Error fetching card list:", error);
+ }
+
+ return NextResponse.json({
+ status: false,
+ list: [],
+ });
+}
diff --git a/app/api/getCardTransactionList/route.ts b/app/api/getCardTransactionList/route.ts
new file mode 100644
index 0000000..552c136
--- /dev/null
+++ b/app/api/getCardTransactionList/route.ts
@@ -0,0 +1,53 @@
+import getRequest from "@/helpers/getRequest";
+import getThreeMonthsAgoDate from "@/utils/getThreeMonthsAgoDate";
+import { cookies } from "next/headers";
+import { NextRequest, NextResponse } from "next/server";
+
+export async function POST(req: NextRequest) {
+ const cookieStore = cookies();
+ const body: CardTransaction = await req.json();
+
+ if (!body.cardId) {
+ return NextResponse.json({
+ status: false,
+ list: [],
+ });
+ }
+
+ const token = String(cookieStore.get("IstKart_Token")?.value);
+ const customerNumber = Number(String(cookieStore.get("IstKart_ID")?.value));
+ const sessionToken = String(cookieStore.get("IstKart_SToken")?.value);
+
+ const getTransactionList = await getRequest({
+ token,
+ command: "RI.OnGetCardTransactionList",
+ data: {
+ CustomerNumber: customerNumber,
+ SessionToken: sessionToken,
+ CardId: body.cardId,
+ TrnType: 0,
+ PageIndex: 1,
+ PageSize: 30,
+ StartDate: getThreeMonthsAgoDate(),
+ EndDate: new Date().toISOString().slice(0, 10),
+ },
+ });
+
+ try {
+ const transactionListData = await getTransactionList.json();
+
+ if (transactionListData.data.ResponseCode === "0") {
+ return NextResponse.json({
+ status: true,
+ list: transactionListData.data.TransactionList,
+ });
+ }
+ } catch (error) {
+ console.error("Error fetching transaction list:", error);
+ }
+
+ return NextResponse.json({
+ status: false,
+ list: [],
+ });
+}
diff --git a/app/api/getCardTransactionList/types.d.ts b/app/api/getCardTransactionList/types.d.ts
new file mode 100644
index 0000000..ae58a78
--- /dev/null
+++ b/app/api/getCardTransactionList/types.d.ts
@@ -0,0 +1,3 @@
+interface CardTransaction {
+ cardId: number
+}
diff --git a/app/api/login/route.ts b/app/api/login/route.ts
new file mode 100644
index 0000000..9c0ca7e
--- /dev/null
+++ b/app/api/login/route.ts
@@ -0,0 +1,84 @@
+import getRequest from "@/helpers/getRequest";
+import getToken from "@/helpers/getToken";
+import { NextRequest, NextResponse } from "next/server";
+
+export async function POST(req: NextRequest) {
+ const body: LoginBody = await req.json();
+
+ if (body.type === "SEND_SMS" && body.phone_number && body.password) {
+ try {
+ const tokenResponse = await getToken();
+ const token = await tokenResponse.json();
+
+ const loginResponse = await getRequest({
+ token: token.token,
+ command: "RI.OnLogin",
+ data: {
+ CountryCode: "90",
+ CellPhone: String(body.phone_number),
+ Password: String(body.password),
+ DeviceId: "1.1.1.1",
+ OSType: 10,
+ IPAdress: "1.1.1.1",
+ Port: "",
+ },
+ });
+
+ const loginData = await loginResponse.json();
+
+ if (loginData.data.ResponseCode === "0") {
+ return NextResponse.json({
+ status: true,
+ message: "SMS_SENT",
+ token,
+ login: loginData,
+ });
+ } else {
+ return NextResponse.json({
+ status: false,
+ message: "ERROR",
+ });
+ }
+ } catch (error) {
+ console.error("Error during login process:", error);
+ return NextResponse.json({
+ status: false,
+ message: "ERROR",
+ });
+ }
+ } else if (body.type === "SMS_CONTROL") {
+ const smsControlResponse = await getRequest({
+ token: body.token,
+ command: "RI.OnConfirmLoginSms",
+ data: {
+ CountryCode: "90",
+ CellPhone: body.phone_number,
+ SmsToken: body.sms_code,
+ OSType: 10,
+ AppVersion: "0.1.0",
+ IPAdress: "",
+ Port: "",
+ },
+ });
+
+ const smsData = await smsControlResponse.json();
+
+ if (smsData.data.ResponseCode === "1020") {
+ return NextResponse.json({
+ status: false,
+ message: "WRONG_CODE",
+ });
+ } else if (smsData.data.ResponseCode === "0") {
+ return NextResponse.json({
+ status: true,
+ message: "OK",
+ data: smsData.data,
+ });
+ }
+ }
+
+ return NextResponse.json({
+ status: false,
+ message: "WTF",
+ });
+}
diff --git a/app/api/login/types.d.ts b/app/api/login/types.d.ts
new file mode 100644
index 0000000..c82d7a8
--- /dev/null
+++ b/app/api/login/types.d.ts
@@ -0,0 +1,7 @@
+interface LoginBody {
+ phone_number: string
+ password: string
+ sms_code: string
+ token: string
+ type: "SEND_SMS" | "SMS_CONTROL"
+}
diff --git a/app/favicon.ico b/app/favicon.ico
deleted file mode 100644
index 718d6fe..0000000
Binary files a/app/favicon.ico and /dev/null differ
diff --git a/app/globals.css b/app/globals.css
deleted file mode 100644
index d4f491e..0000000
--- a/app/globals.css
+++ /dev/null
@@ -1,107 +0,0 @@
-:root {
- --max-width: 1100px;
- --border-radius: 12px;
- --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
- 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
- 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
-
- --foreground-rgb: 0, 0, 0;
- --background-start-rgb: 214, 219, 220;
- --background-end-rgb: 255, 255, 255;
-
- --primary-glow: conic-gradient(
- from 180deg at 50% 50%,
- #16abff33 0deg,
- #0885ff33 55deg,
- #54d6ff33 120deg,
- #0071ff33 160deg,
- transparent 360deg
- );
- --secondary-glow: radial-gradient(
- rgba(255, 255, 255, 1),
- rgba(255, 255, 255, 0)
- );
-
- --tile-start-rgb: 239, 245, 249;
- --tile-end-rgb: 228, 232, 233;
- --tile-border: conic-gradient(
- #00000080,
- #00000040,
- #00000030,
- #00000020,
- #00000010,
- #00000010,
- #00000080
- );
-
- --callout-rgb: 238, 240, 241;
- --callout-border-rgb: 172, 175, 176;
- --card-rgb: 180, 185, 188;
- --card-border-rgb: 131, 134, 135;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --foreground-rgb: 255, 255, 255;
- --background-start-rgb: 0, 0, 0;
- --background-end-rgb: 0, 0, 0;
-
- --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
- --secondary-glow: linear-gradient(
- to bottom right,
- rgba(1, 65, 255, 0),
- rgba(1, 65, 255, 0),
- rgba(1, 65, 255, 0.3)
- );
-
- --tile-start-rgb: 2, 13, 46;
- --tile-end-rgb: 2, 5, 19;
- --tile-border: conic-gradient(
- #ffffff80,
- #ffffff40,
- #ffffff30,
- #ffffff20,
- #ffffff10,
- #ffffff10,
- #ffffff80
- );
-
- --callout-rgb: 20, 20, 20;
- --callout-border-rgb: 108, 108, 108;
- --card-rgb: 100, 100, 100;
- --card-border-rgb: 200, 200, 200;
- }
-}
-
-* {
- box-sizing: border-box;
- padding: 0;
- margin: 0;
-}
-
-html,
-body {
- max-width: 100vw;
- overflow-x: hidden;
-}
-
-body {
- color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
-}
-
-a {
- color: inherit;
- text-decoration: none;
-}
-
-@media (prefers-color-scheme: dark) {
- html {
- color-scheme: dark;
- }
-}
diff --git a/app/globals.scss b/app/globals.scss
new file mode 100644
index 0000000..d2a7c17
--- /dev/null
+++ b/app/globals.scss
@@ -0,0 +1,16 @@
+* {
+ box-sizing: border-box;
+ padding: 0;
+ margin: 0;
+}
+
+html,
+body {
+ max-width: 100vw;
+ overflow-x: hidden;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 40e027f..c6c9c31 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,21 +1,17 @@
-import type { Metadata } from 'next'
-import { Inter } from 'next/font/google'
-import './globals.css'
+import type { Metadata } from "next"
+import { Inter } from "next/font/google"
+import "./globals.scss"
-const inter = Inter({ subsets: ['latin'] })
+const inter = Inter({ subsets: ["latin"] })
export const metadata: Metadata = {
- title: 'Create Next App',
- description: 'Generated by create next app',
+ title: "Istanbulkart",
+ description: "Kişiye Özel Istanbulkart Uygulaması; kendi bilgisayarınızda kullanabileceğiniz ve özelleştirebileceğiniz bir arayüz sağlamaktadır.",
}
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode
-}) {
+export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
-
+
{children}
)
diff --git a/app/login/page.module.scss b/app/login/page.module.scss
new file mode 100644
index 0000000..523a01e
--- /dev/null
+++ b/app/login/page.module.scss
@@ -0,0 +1,118 @@
+@import "../_variables.scss";
+
+.container {
+ height: 100vh;
+ width: 100vw;
+ background-color: $bg-color;
+ overflow-y: auto;
+
+ .center_content {
+ margin: 0 auto;
+ position: relative;
+ max-width: 500px;
+ width: 100%;
+ height: 100vh;
+ display: flex;
+ align-items: center;
+
+ .login_form {
+ padding: 30px;
+ background-color: $box-color;
+ width: 100%;
+ border-radius: 15px;
+ gap: 30px;
+ display: flex;
+ flex-direction: column;
+
+ .form_title {
+ display: flex;
+ flex-direction: column;
+
+ span {
+ color: #A0A0A0;
+ user-select: none;
+ font-size: 20px;
+
+ &:first-child {
+ color: #fff;
+ font-weight: bold;
+ font-size: 28px;
+ }
+ }
+ }
+
+ .form_inputs {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ border: 2px solid #282934;
+ padding: 20px;
+ border-radius: 15px;
+
+ &[data-active="true"] {
+ border-color: $theme-color;
+ background-color: rgba($theme-color, 0.03);
+
+ .form_input {
+ span {
+ color: lighten($theme-color, 20);
+ }
+
+ input {
+ background-color: rgba($theme-color, 0.05);
+ border-color: $theme-color !important;
+ color: lighten($theme-color, 30);
+ }
+ }
+
+ .send_button {
+ cursor: pointer;
+ background-color: $theme-color;
+ color: lighten($theme-color, 55);
+
+ &:hover {
+ background-color: rgba($theme-color, 0.8);
+ }
+ }
+ }
+
+ .form_input {
+ display: flex;
+ flex-direction: column;
+
+ span {
+ color: #CCCCCC;
+ font-size: 17px;
+ font-weight: bold;
+ }
+
+ input {
+ margin-top: 10px;
+ border-radius: 15px;
+ color: #fff;
+ background-color: rgba($bg-color, 0.5);
+ outline: none;
+ border: 2px solid #282934;
+ padding: 15px;
+ font-size: 17px;
+
+ &:focus {
+ border-color: #64656B;
+ }
+ }
+ }
+
+ .send_button {
+ margin-top: 5px;
+ background-color: rgba($bg-color, 0.5);
+ padding: 15px;
+ border-radius: 15px;
+ font-weight: bold;
+ color: #D3D3D3;
+ text-align: center;
+ cursor: not-allowed;
+ }
+ }
+ }
+ }
+}
diff --git a/app/login/page.tsx b/app/login/page.tsx
new file mode 100644
index 0000000..aaa8f8b
--- /dev/null
+++ b/app/login/page.tsx
@@ -0,0 +1,160 @@
+"use client"
+
+import { useState } from "react"
+import styles from "./page.module.scss"
+import apiRequest from "@/helpers/apiRequest"
+import { useRouter } from "next/navigation"
+import { setCookie, getCookie } from "cookies-next"
+
+function Login() {
+ const router = useRouter();
+
+ const [token, setToken] = useState("");
+ const [phone, setPhone] = useState("");
+ const [password, setPassword] = useState("");
+ const [page, setPage] = useState(0);
+ const [smsCode, setSmsCode] = useState("");
+
+ const sendSms = async () => {
+ if (phone.length === 0 || password.length === 0 || smsCode.length === 0) return;
+
+ try {
+ const newRequest = await apiRequest({
+ service: "/login",
+ body: {
+ phone_number: phone,
+ password: password,
+ sms_code: smsCode,
+ token: token,
+ type: "SMS_CONTROL",
+ },
+ });
+
+ const data = await newRequest.json();
+
+ if (data.status) {
+ setCookies(data);
+ router.push("/profile");
+ } else {
+ alert("Invalid code, please try again!");
+ }
+ } catch (error) {
+ console.error("Error during login process:", error);
+ alert("An error occurred, please try again.");
+ }
+ };
+
+ const getSms = async () => {
+ if (phone.length === 0 || password.length === 0) return;
+
+ try {
+ const newRequest = await apiRequest({
+ service: "/login",
+ body: {
+ phone_number: phone,
+ password: password,
+ type: "SEND_SMS",
+ },
+ });
+
+ const data = await newRequest.json();
+
+ if (data.status) {
+ setToken(data.token.token);
+ setPage(1);
+ } else {
+ resetPageAndInputs();
+ alert("An error occurred while sending SMS, please try again.");
+ }
+ } catch (error) {
+ console.error("Error during login process:", error);
+ resetPageAndInputs();
+ alert("An error occurred, please try again.");
+ }
+ };
+
+ const setCookies = (data: any) => {
+ setCookie("IstKart_ID", `${data.data.CustomerNumber}`);
+ setCookie("IstKart_Name", `${data.data.Name} ${data.data.Surname}`);
+ setCookie("IstKart_Email", `${data.data.Mail}`);
+ setCookie("IstKart_BirthDate", `${data.data.BirthDate}`);
+ setCookie("IstKart_SToken", `${data.data.SessionToken}`);
+ setCookie("IstKart_Token", `${token}`);
+ };
+
+ const resetPageAndInputs = () => {
+ setPage(0);
+ setPhone("");
+ setPassword("");
+ };
+
+ return (
+
+
+ {page === 0 && (
+
+
+ Giriş Yap
+ IstanbulKart bilgilerinle giriş yap
+
+
+
+ Telefon Numarası
+ setPhone(e.target.value)}
+ />
+
+
+ Şifre
+ setPassword(e.target.value)}
+ />
+
+
+ Giriş Yap
+
+
+
+ )}
+ {page === 1 && (
+
+
+ Bilgileri Doğrulama
+ {phone} telefon numarasına gönderilen kodu giriniz
+
+
+
+ Doğrulama Kodu
+ setSmsCode(e.target.value)}
+ />
+
+
+ Doğrula
+
+
+
+ )}
+
+
+ )
+}
+
+export default Login
diff --git a/app/page.module.css b/app/page.module.css
deleted file mode 100644
index 6676d2c..0000000
--- a/app/page.module.css
+++ /dev/null
@@ -1,229 +0,0 @@
-.main {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- align-items: center;
- padding: 6rem;
- min-height: 100vh;
-}
-
-.description {
- display: inherit;
- justify-content: inherit;
- align-items: inherit;
- font-size: 0.85rem;
- max-width: var(--max-width);
- width: 100%;
- z-index: 2;
- font-family: var(--font-mono);
-}
-
-.description a {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 0.5rem;
-}
-
-.description p {
- position: relative;
- margin: 0;
- padding: 1rem;
- background-color: rgba(var(--callout-rgb), 0.5);
- border: 1px solid rgba(var(--callout-border-rgb), 0.3);
- border-radius: var(--border-radius);
-}
-
-.code {
- font-weight: 700;
- font-family: var(--font-mono);
-}
-
-.grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(25%, auto));
- max-width: 100%;
- width: var(--max-width);
-}
-
-.card {
- padding: 1rem 1.2rem;
- border-radius: var(--border-radius);
- background: rgba(var(--card-rgb), 0);
- border: 1px solid rgba(var(--card-border-rgb), 0);
- transition: background 200ms, border 200ms;
-}
-
-.card span {
- display: inline-block;
- transition: transform 200ms;
-}
-
-.card h2 {
- font-weight: 600;
- margin-bottom: 0.7rem;
-}
-
-.card p {
- margin: 0;
- opacity: 0.6;
- font-size: 0.9rem;
- line-height: 1.5;
- max-width: 30ch;
-}
-
-.center {
- display: flex;
- justify-content: center;
- align-items: center;
- position: relative;
- padding: 4rem 0;
-}
-
-.center::before {
- background: var(--secondary-glow);
- border-radius: 50%;
- width: 480px;
- height: 360px;
- margin-left: -400px;
-}
-
-.center::after {
- background: var(--primary-glow);
- width: 240px;
- height: 180px;
- z-index: -1;
-}
-
-.center::before,
-.center::after {
- content: '';
- left: 50%;
- position: absolute;
- filter: blur(45px);
- transform: translateZ(0);
-}
-
-.logo {
- position: relative;
-}
-/* Enable hover only on non-touch devices */
-@media (hover: hover) and (pointer: fine) {
- .card:hover {
- background: rgba(var(--card-rgb), 0.1);
- border: 1px solid rgba(var(--card-border-rgb), 0.15);
- }
-
- .card:hover span {
- transform: translateX(4px);
- }
-}
-
-@media (prefers-reduced-motion) {
- .card:hover span {
- transform: none;
- }
-}
-
-/* Mobile */
-@media (max-width: 700px) {
- .content {
- padding: 4rem;
- }
-
- .grid {
- grid-template-columns: 1fr;
- margin-bottom: 120px;
- max-width: 320px;
- text-align: center;
- }
-
- .card {
- padding: 1rem 2.5rem;
- }
-
- .card h2 {
- margin-bottom: 0.5rem;
- }
-
- .center {
- padding: 8rem 0 6rem;
- }
-
- .center::before {
- transform: none;
- height: 300px;
- }
-
- .description {
- font-size: 0.8rem;
- }
-
- .description a {
- padding: 1rem;
- }
-
- .description p,
- .description div {
- display: flex;
- justify-content: center;
- position: fixed;
- width: 100%;
- }
-
- .description p {
- align-items: center;
- inset: 0 0 auto;
- padding: 2rem 1rem 1.4rem;
- border-radius: 0;
- border: none;
- border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
- background: linear-gradient(
- to bottom,
- rgba(var(--background-start-rgb), 1),
- rgba(var(--callout-rgb), 0.5)
- );
- background-clip: padding-box;
- backdrop-filter: blur(24px);
- }
-
- .description div {
- align-items: flex-end;
- pointer-events: none;
- inset: auto 0 0;
- padding: 2rem;
- height: 200px;
- background: linear-gradient(
- to bottom,
- transparent 0%,
- rgb(var(--background-end-rgb)) 40%
- );
- z-index: 1;
- }
-}
-
-/* Tablet and Smaller Desktop */
-@media (min-width: 701px) and (max-width: 1120px) {
- .grid {
- grid-template-columns: repeat(2, 50%);
- }
-}
-
-@media (prefers-color-scheme: dark) {
- .vercelLogo {
- filter: invert(1);
- }
-
- .logo {
- filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
- }
-}
-
-@keyframes rotate {
- from {
- transform: rotate(360deg);
- }
- to {
- transform: rotate(0deg);
- }
-}
diff --git a/app/page.module.scss b/app/page.module.scss
new file mode 100644
index 0000000..ed68e02
--- /dev/null
+++ b/app/page.module.scss
@@ -0,0 +1,13 @@
+@import "./variables";
+
+.main {
+ background-color: $bg-color;
+
+ .loader {
+ height: 100vh;
+ width: 100vw;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+}
diff --git a/app/page.tsx b/app/page.tsx
index 41d4d97..c036525 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,94 +1,32 @@
-import Image from 'next/image'
-import styles from './page.module.css'
+"use client"
-export default function Home() {
- return (
-
-
-
- Get started by editing
- app/page.tsx
-
-
-
+import { useEffect } from "react"
+import styles from "./page.module.scss"
+import { getCookie } from "cookies-next"
+import { useRouter } from "next/navigation"
+import { Bars } from "react-loader-spinner"
-
-
-
-
-
-
-
- Docs ->
-
- Find in-depth information about Next.js features and API.
-
+export default function Home() {
+ const router = useRouter()
-
-
- Learn ->
-
- Learn about Next.js in an interactive course with quizzes!
-
+ useEffect(() => {
+ const token = getCookie("IstKart_Token")
-
-
- Templates ->
-
- Explore starter templates for Next.js.
-
+ if (typeof token !== "string") {
+ router.push("/login")
+ } else {
+ router.push("/profile")
+ }
+ }, [router])
-
-
- Deploy ->
-
-
- Instantly deploy your Next.js site to a shareable URL with Vercel.
-
-
+ return (
+
+
+
)
diff --git a/app/profile/page.module.scss b/app/profile/page.module.scss
new file mode 100644
index 0000000..1e1ad71
--- /dev/null
+++ b/app/profile/page.module.scss
@@ -0,0 +1,175 @@
+@import "../_variables.scss";
+
+.loader {
+ padding: 30px;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.container {
+ height: 100vh;
+ width: 100vw;
+ background-color: $bg-color;
+ overflow-y: auto;
+
+ .profile {
+ margin: 0 auto;
+ position: relative;
+ max-width: 800px;
+ width: 100%;
+ padding: 30px;
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+
+ .header {
+ background-color: $box-color;
+ width: 100%;
+ border-radius: 15px;
+
+ .banner {
+ img {
+ object-fit: cover;
+ height: 200px;
+ width: 100%;
+ border-radius: 15px 15px 0 0;
+ }
+ }
+
+ .profile_content {
+ display: flex;
+ align-items: center;
+ padding: 30px;
+ }
+
+ .profile_picture {
+ background: linear-gradient(45deg, rgb(50 119 239), lab(75.26 -21.65 -38.17));
+ height: 80px;
+ width: 80px;
+ border-radius: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .profile_details {
+ display: flex;
+ flex-direction: column;
+ margin-left: 15px;
+ span {
+ font-size: 18px;
+ color: #777777;
+
+ &:first-child {
+ font-size: 24px;
+ font-weight: bold;
+ color: #fff;
+ }
+ }
+ }
+ }
+
+ .cards {
+ padding: 30px;
+ background-color: $box-color;
+ width: 100%;
+ border-radius: 15px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+
+ .title {
+ font-size: 24px;
+ font-weight: bold;
+ color: #fff;
+ user-select: none;
+ margin-bottom: 10px;
+ }
+
+ .item {
+ background-color: rgba($bg-color, 0.5);
+ padding: 15px;
+ border: 2px solid #282934;
+ border-radius: 15px;
+ color: #fff;
+ user-select: none;
+
+ display: flex;
+ align-items: center;
+
+ .item_left {
+ display: flex;
+ flex-direction: column;
+ }
+ }
+
+ .card {
+ background-color: rgba($bg-color, 0.5);
+ padding: 15px;
+ border: 2px solid #282934;
+ border-radius: 15px;
+ color: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ cursor: pointer;
+ user-select: none;
+
+ &:hover {
+ opacity: 0.8;
+ }
+
+ .card_left {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ }
+
+ .card_right {
+ display: flex;
+ align-items: center;
+ font-size: 20px;
+ font-weight: bold;
+ color: #fff;
+ }
+
+ .card_icon {
+ background: linear-gradient(45deg, rgb(50 119 239), lab(75.26 -21.65 -38.17));
+ height: 60px;
+ width: 40px;
+ border-radius: 4px;
+ }
+
+ .metro_icon {
+ height: 40px;
+ width: 40px;
+ border-radius: 100%;
+ background: linear-gradient(45deg, rgb(50 119 239), lab(75.26 -21.65 -38.17));;
+ font-weight: bold;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ }
+
+ .card_details {
+ display: flex;
+ flex-direction: column;
+
+ .card_nickname {
+ font-weight: bold;
+ font-size: 18px;
+ }
+
+ .card_description {
+ font-size: 16px;
+ margin-top: 3px;
+ color: #B5B5B5;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/profile/page.tsx b/app/profile/page.tsx
new file mode 100644
index 0000000..1da0266
--- /dev/null
+++ b/app/profile/page.tsx
@@ -0,0 +1,167 @@
+"use client"
+
+import { useEffect, useState } from "react"
+import styles from "./page.module.scss"
+import { setCookie, getCookie } from "cookies-next"
+import getRequest from "@/helpers/getRequest"
+import { useRouter } from "next/navigation"
+import apiRequest from "@/helpers/apiRequest"
+import { FaUser } from "react-icons/fa"
+import capitalizeWords from "@/utils/capitalizeWords"
+import { Bars } from "react-loader-spinner"
+import { THEME_COLOR } from "@/constant"
+import extractLastWord from "@/utils/extractLastWord"
+
+function Profile() {
+ const router = useRouter();
+ const [token, setToken] = useState
("");
+ const [customerNumber, setCustomerNumber] = useState("");
+ const [cards, setCards] = useState([]);
+ const [selectedCard, setSelectedCard] = useState(null);
+ const [transList, setTransList] = useState([]);
+
+ const getCard = async (card: any) => {
+ setSelectedCard(card);
+ setTransList([]);
+
+ try {
+ const getCardRequest = await apiRequest({
+ service: "/getCardTransactionList",
+ body: {
+ cardId: card.CardId,
+ },
+ });
+
+ const cardData = await getCardRequest.json();
+
+ if (cardData.status) {
+ setTransList(cardData.list);
+ } else {
+ setSelectedCard(null);
+ alert("No transactions found, try another card.");
+ }
+ } catch (error) {
+ console.error("Error fetching card transactions:", error);
+ alert("An error occurred, please try again.");
+ }
+ };
+
+ const getCards = async () => {
+ try {
+ const getCardListRequest = await apiRequest({ service: "/getCardList" });
+ const cardList = await getCardListRequest.json();
+
+ if (cardList.status) {
+ setCards(cardList.list);
+ }
+ } catch (error) {
+ console.error("Error fetching card list:", error);
+ }
+ };
+
+ useEffect(() => {
+ const istKartToken = getCookie("IstKart_Token");
+ const istKartId = getCookie("IstKart_ID");
+
+ if (typeof istKartToken === "string" && typeof istKartId === "string") {
+ setToken(istKartToken);
+ setCustomerNumber(istKartId);
+
+ getCards();
+ } else {
+ alert("An error occurred, please log in again.");
+ router.push("/login");
+ }
+ }, [router]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {String(getCookie("IstKart_Name")) !== "undefined" && capitalizeWords(String(getCookie("IstKart_Name")))}
+ {getCookie("IstKart_Email")}
+
+
+
+
+
+
+ Kartlar
+
+
+ {cards.length === 0 && (
+
+
+
+ )}
+
+ {cards.length !== 0 && cards.map((card: any, index) => {
+ return (
+
getCard(card)} className={styles.card} key={index}>
+
+
+
+ {card.NickName ? card.NickName : "İstanbulkart"}
+ {card.ProductCodeDesc}
+
+
+
+ {card.CardBalance}₺
+
+
+ )
+ })}
+
+
+
+ {String(selectedCard) !== "null" && (
+
+
+ {selectedCard.NickName ? selectedCard.NickName : "İstanbulkart"} İşlemleri
+
+ {transList.length === 0 && (
+
+
+
+ )}
+ {transList.length !== 0 && transList.map((item: any, index) => {
+ const line = extractLastWord(item.TrnDescription)
+ return (
+
+
+
{line === "MARMARAY" ? "B1" : line.startsWith("M") ? line : ""}
+
+ {item.TrnCodeDescription} {line.startsWith("M")}
+ {item.TrnDescription}
+
+
+
+ {item.TrnAmount}₺
+
+
+ )
+ })}
+
+ )}
+
+
+ )
+}
+
+export default Profile
diff --git a/constant/index.ts b/constant/index.ts
new file mode 100644
index 0000000..e49c12c
--- /dev/null
+++ b/constant/index.ts
@@ -0,0 +1,5 @@
+export const THEME_COLOR = "#1581be"
+
+export const ISTANBULKART_GATEWAY = "https://apigateway.belbim.istanbul:8080/belbim/getRequest"
+export const ISTANBULKART_REFERRER = "https://bireysel.istanbulkart.istanbul/"
+export const ISTANBULKART_TOKEN = "NTE2ZDU2NGQ1ODMxNjQ0NjUxNmMzODc5NGQ0NDQ5Nzg1NTMwNjQ1NDU4MzE1NTc2NGMzMDY3NzQ1NjQ4NDI3MjUyNmMzODMwNTU1NjVhNDY1YTdhNTk3OTUyNTM0Njc5NWE2YjY0NmM1MjZhNjg2YjRiMzA3NDZlNGE1NzRhNjY2NTZiNjg2Yw=="
diff --git a/helpers/apiRequest/index.ts b/helpers/apiRequest/index.ts
new file mode 100644
index 0000000..5424d28
--- /dev/null
+++ b/helpers/apiRequest/index.ts
@@ -0,0 +1,13 @@
+async function apiRequest({ service, body = {}, method = "POST" }: ApiRequest) {
+ return await fetch("/api" + service, {
+ "headers": {
+ "accept": "application/json, text/plain, */*",
+ "access-control-allow-origin": "*",
+ "content-type": "application/json;charset=UTF-8"
+ },
+ "body": JSON.stringify(body),
+ "method": method
+ });
+}
+
+export default apiRequest
diff --git a/helpers/apiRequest/types.d.ts b/helpers/apiRequest/types.d.ts
new file mode 100644
index 0000000..8058d84
--- /dev/null
+++ b/helpers/apiRequest/types.d.ts
@@ -0,0 +1,5 @@
+interface ApiRequest {
+ service: string
+ body?: any
+ method?: "POST" | "GET"
+}
diff --git a/helpers/getRequest/index.ts b/helpers/getRequest/index.ts
new file mode 100644
index 0000000..78d97d4
--- /dev/null
+++ b/helpers/getRequest/index.ts
@@ -0,0 +1,21 @@
+import { ISTANBULKART_GATEWAY, ISTANBULKART_REFERRER } from "@/constant";
+
+async function getRequest({ token, command, data, method = "POST" }: GetRequest) {
+ return await fetch(ISTANBULKART_GATEWAY, {
+ "headers": {
+ "accept": "application/json, text/plain, */*",
+ "access-control-allow-origin": "*",
+ "content-type": "application/json;charset=UTF-8"
+ },
+ "referrer": ISTANBULKART_REFERRER,
+ "referrerPolicy": "strict-origin-when-cross-origin",
+ "body": JSON.stringify({
+ "command": command,
+ "id": token,
+ "data": data
+ }),
+ "method": method
+ });
+}
+
+export default getRequest
diff --git a/helpers/getRequest/types.d.ts b/helpers/getRequest/types.d.ts
new file mode 100644
index 0000000..dd051c2
--- /dev/null
+++ b/helpers/getRequest/types.d.ts
@@ -0,0 +1,6 @@
+interface GetRequest {
+ token: string
+ command: string
+ data: any,
+ method?: "POST" | "GET"
+}
diff --git a/helpers/getToken/index.ts b/helpers/getToken/index.ts
new file mode 100644
index 0000000..eca45e6
--- /dev/null
+++ b/helpers/getToken/index.ts
@@ -0,0 +1,30 @@
+import { ISTANBULKART_GATEWAY, ISTANBULKART_REFERRER, ISTANBULKART_TOKEN } from "@/constant";
+
+async function getToken() {
+ const token = await fetch(ISTANBULKART_GATEWAY, {
+ "headers": {
+ "accept": "application/json, text/plain, */*",
+ "accept-language": "en-US,en;q=0.9",
+ "access-control-allow-origin": "*",
+ "content-type": "application/json;charset=UTF-8",
+ "password": "",
+ "sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
+ "sec-ch-ua-mobile": "?0",
+ "sec-ch-ua-platform": "\"macOS\"",
+ "sec-fetch-dest": "empty",
+ "sec-fetch-mode": "cors",
+ "sec-fetch-site": "cross-site",
+ "username": "",
+ "Referer": ISTANBULKART_REFERRER,
+ "Referrer-Policy": "strict-origin-when-cross-origin"
+ },
+ "body": JSON.stringify({
+ res: ISTANBULKART_TOKEN
+ }),
+ "method": "POST"
+ });
+
+ return token
+}
+
+export default getToken
diff --git a/package-lock.json b/package-lock.json
index 7867a82..32967d4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,9 +8,13 @@
"name": "istanbulkart-nextjs",
"version": "0.1.0",
"dependencies": {
+ "cookies-next": "^4.1.0",
"next": "14.0.4",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "react-icons": "^4.12.0",
+ "react-loader-spinner": "^6.1.6",
+ "sass": "^1.69.7"
},
"devDependencies": {
"@types/node": "^20",
@@ -42,6 +46,24 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
+ "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
+ "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw=="
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -329,6 +351,11 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/cookie": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
+ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
+ },
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -376,6 +403,11 @@
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"dev": true
},
+ "node_modules/@types/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw=="
+ },
"node_modules/@typescript-eslint/parser": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.0.tgz",
@@ -570,6 +602,18 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -766,6 +810,14 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -780,7 +832,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
@@ -822,6 +873,14 @@
"node": ">=6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001575",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001575.tgz",
@@ -857,6 +916,43 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -886,6 +982,29 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "node_modules/cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookies-next": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cookies-next/-/cookies-next-4.1.0.tgz",
+ "integrity": "sha512-BREVc4TJT4NwXfyKjdjnYFXM6iRns+MYpCd34ClXuYqeisXnkPkbq7Ok9xaqi9mHmV6H2rwPE+p3EpMz4pF/kQ==",
+ "dependencies": {
+ "@types/cookie": "^0.4.1",
+ "@types/node": "^16.10.2",
+ "cookie": "^0.4.0"
+ }
+ },
+ "node_modules/cookies-next/node_modules/@types/node": {
+ "version": "16.18.70",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.70.tgz",
+ "integrity": "sha512-8eIk20G5VVVQNZNouHjLA2b8utE2NvGybLjMaF4lyhA9uhGwnmXF8o+icdXKGSQSNANJewXva/sFUoZLwAaYAg=="
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -900,6 +1019,24 @@
"node": ">= 8"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -1626,7 +1763,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -1685,6 +1821,19 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -1964,6 +2113,11 @@
"node": ">= 4"
}
},
+ "node_modules/immutable": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz",
+ "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA=="
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -2060,6 +2214,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@@ -2119,7 +2284,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2155,7 +2319,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -2188,7 +2351,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
"engines": {
"node": ">=0.12.0"
}
@@ -2622,6 +2784,14 @@
}
}
},
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -2859,7 +3029,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
"engines": {
"node": ">=8.6"
},
@@ -2894,6 +3063,11 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -2966,12 +3140,52 @@
"react": "^18.2.0"
}
},
+ "node_modules/react-icons": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz",
+ "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
+ "node_modules/react-loader-spinner": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-6.1.6.tgz",
+ "integrity": "sha512-x5h1Jcit7Qn03MuKlrWcMG9o12cp9SNDVHVJTNRi9TgtGPKcjKiXkou4NRfLAtXaFB3+Z8yZsVzONmPzhv2ErA==",
+ "dependencies": {
+ "react-is": "^18.2.0",
+ "styled-components": "^6.1.2"
+ },
+ "engines": {
+ "node": ">= 12"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-loader-spinner/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz",
@@ -3130,6 +3344,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/sass": {
+ "version": "1.69.7",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz",
+ "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==",
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
@@ -3182,6 +3412,11 @@
"node": ">= 0.4"
}
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3340,6 +3575,43 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/styled-components": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.6.tgz",
+ "integrity": "sha512-DgTLULSC29xpabJ24bbn1+hulU6vvGFQf4RPwBOJrm8WJFnN42yXpo5voBt3jDSJBa5tBd1L6PqswJjQ0wRKdg==",
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.1",
+ "@emotion/unitless": "0.8.0",
+ "@types/stylis": "4.2.0",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.2",
+ "postcss": "8.4.31",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.1",
+ "tslib": "2.5.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/csstype": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
+ },
+ "node_modules/styled-components/node_modules/tslib": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+ },
"node_modules/styled-jsx": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
@@ -3362,6 +3634,11 @@
}
}
},
+ "node_modules/stylis": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz",
+ "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ=="
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -3405,7 +3682,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
diff --git a/package.json b/package.json
index 4727445..c37c865 100644
--- a/package.json
+++ b/package.json
@@ -9,16 +9,20 @@
"lint": "next lint"
},
"dependencies": {
+ "cookies-next": "^4.1.0",
+ "next": "14.0.4",
"react": "^18",
"react-dom": "^18",
- "next": "14.0.4"
+ "react-icons": "^4.12.0",
+ "react-loader-spinner": "^6.1.6",
+ "sass": "^1.69.7"
},
"devDependencies": {
- "typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
- "eslint-config-next": "14.0.4"
+ "eslint-config-next": "14.0.4",
+ "typescript": "^5"
}
}
diff --git a/public/anna-berdnik-0n0AHB1fgTQ-unsplash.jpg b/public/anna-berdnik-0n0AHB1fgTQ-unsplash.jpg
new file mode 100644
index 0000000..fd99bc1
Binary files /dev/null and b/public/anna-berdnik-0n0AHB1fgTQ-unsplash.jpg differ
diff --git a/public/next.svg b/public/next.svg
deleted file mode 100644
index 5174b28..0000000
--- a/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
deleted file mode 100644
index d2f8422..0000000
--- a/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/utils/capitalizeWords.ts b/utils/capitalizeWords.ts
new file mode 100644
index 0000000..ba79822
--- /dev/null
+++ b/utils/capitalizeWords.ts
@@ -0,0 +1,16 @@
+/**
+ * Capitalizes the first letter of each word in a given text.
+ *
+ * @param {string} text - The input text to be processed.
+ * @return {string} - The modified text with the first letter of each word capitalized.
+ */
+function capitalizeWords(text: string) {
+ const capitalizedWords =
+ String(text).trim().split(" ")
+ .map((word: string) =>
+ word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
+
+ return capitalizedWords.join(" ")
+}
+
+export default capitalizeWords
diff --git a/utils/extractLastWord.ts b/utils/extractLastWord.ts
new file mode 100644
index 0000000..fa0b3a2
--- /dev/null
+++ b/utils/extractLastWord.ts
@@ -0,0 +1,6 @@
+function extractLastWord(text: string) {
+ const splitted_text = text.split("-")
+ return splitted_text[splitted_text.length - 1];
+}
+
+export default extractLastWord
diff --git a/utils/getThreeMonthsAgoDate.ts b/utils/getThreeMonthsAgoDate.ts
new file mode 100644
index 0000000..609aa7f
--- /dev/null
+++ b/utils/getThreeMonthsAgoDate.ts
@@ -0,0 +1,20 @@
+/**
+ * Calculates the date exactly 3 months ago from today and returns it in "YYYY-MM-DD" format.
+ * @returns {string} The date three months ago in "YYYY-MM-DD" format.
+ */
+function getThreeMonthsAgoDate() {
+ // Get today's date
+ let today = new Date();
+
+ // Calculate the date three months ago
+ let threeMonthsAgo = new Date(today);
+ threeMonthsAgo.setMonth(today.getMonth() - 3);
+
+ // Convert the date to the "YYYY-MM-DD" format
+ let formattedDate = threeMonthsAgo.toISOString().slice(0, 10);
+
+ // Return the result
+ return formattedDate;
+}
+
+export default getThreeMonthsAgoDate