Skip to content

Commit 1cd15e4

Browse files
authoredMar 17, 2025··
Add files via upload
1 parent 729d10e commit 1cd15e4

25 files changed

+4418
-2
lines changed
 

‎README.md

+68-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,68 @@
1-
# linkify
2-
shortened any looooooooooong link
1+
# Linkify
2+
3+
Linkify is a URL shortening service that allows users to shorten long URLs quickly and efficiently. It provides a user-friendly interface and additional features like QR code generation and local storage for managing shortened links.
4+
5+
## Features
6+
7+
- **URL Shortening**: Shorten long URLs with ease.
8+
- **QR Code Generation**: Generate QR codes for shortened URLs.
9+
- **Local Storage**: Save and manage shortened links in local storage.
10+
- **Responsive Design**: Optimized for both desktop and mobile devices.
11+
- **Captcha Verification**: Captcha verification for sign-up and sign-in pages.
12+
13+
## Installation
14+
15+
1. Clone the repository:
16+
17+
```sh
18+
git clone https://github.com/yourusername/linkify.git
19+
cd linkify
20+
```
21+
22+
2. Install dependencies:
23+
24+
```sh
25+
npm install
26+
```
27+
28+
3. Start the development server:
29+
30+
```sh
31+
npm run dev
32+
```
33+
34+
4. Build the project for production:
35+
36+
```sh
37+
npm run build
38+
```
39+
40+
5. Preview the production build:
41+
```sh
42+
npm run preview
43+
```
44+
45+
## Usage
46+
47+
- **Shorten a URL**: Enter a long URL in the input field and click the "Shorten" button.
48+
- **View Shortened Links**: View the list of shortened links in the table.
49+
- **Generate QR Code**: Click the QR code button to generate a QR code for the shortened URL.
50+
- **Copy to Clipboard**: Click the copy button to copy the shortened URL to the clipboard.
51+
- **Delete a Link**: Click the delete button to remove a shortened link from the list.
52+
- **Clear Local Storage**: Click the "Clear Local Storage" button to remove all saved links.
53+
54+
## Configuration
55+
56+
- **Tailwind CSS**: Customize the design using the `tailwind.config.js` file.
57+
- **Vite**: Configure the build process using the `vite.config.js` file.
58+
59+
## Dependencies
60+
61+
- [Toastify JS](https://github.com/apvarun/toastify-js)
62+
- [QRCode.js](https://github.com/davidshimjs/qrcodejs)
63+
- [Tailwind CSS](https://tailwindcss.com/)
64+
- [Vite](https://vitejs.dev/)
65+
66+
## License
67+
68+
This project is licensed under the MIT License.

‎index.html

+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<!DOCTYPE html>
2+
<html
3+
lang="fa"
4+
dir="rtl">
5+
<head>
6+
<meta charset="UTF-8" />
7+
<link
8+
rel="icon"
9+
type="image/svg+xml"
10+
href="/assests/icons/logo.png" />
11+
<meta
12+
name="viewport"
13+
content="width=device-width, initial-scale=1.0" />
14+
<meta
15+
name="description"
16+
content="لینکیفای یک ابزار برای کوتاه کردن لینک‌های طولانی شما است. سریع، امن و کاربردی." />
17+
<meta
18+
name="author"
19+
content="Mahdi Rostami" />
20+
<meta
21+
name="keywords"
22+
content="کوتاه کننده لینک, لینکیفای, کوتاه کردن لینک" />
23+
<title>لینکیفای - پلتفرم کوتاه کننده لینک</title>
24+
<!-- ionicons -->
25+
<script
26+
type="module"
27+
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
28+
<!-- tostify -->
29+
<link
30+
rel="stylesheet"
31+
type="text/css"
32+
href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css" />
33+
<!-- css -->
34+
<link
35+
rel="stylesheet"
36+
href="src/style.css" />
37+
</head>
38+
<body class="relative">
39+
<div class="fixed h-full w-full inset-0 z-[-1] backdrop-blur-md"></div>
40+
<div id="app">
41+
<header
42+
class="max-w-screen-2xl p-8 m-auto mb-16 max-[420px]:p-4"
43+
role="banner">
44+
<div class="flex justify-between items-center">
45+
<div class="logo">
46+
<a href="/">
47+
<img
48+
src="/assests/icons/logo.png"
49+
alt="لوگو لینکیفای"
50+
class="w-24 h-24" />
51+
</a>
52+
</div>
53+
<div class="flex items-center gap-4">
54+
<div class="flex">
55+
<a
56+
href="nested/login.html"
57+
class="primary-btn"
58+
lite="true"
59+
hover="true"
60+
rotate="true">
61+
<span>ورود</span>
62+
<ion-icon
63+
name="log-in-outline"
64+
aria-hidden="true"></ion-icon>
65+
</a>
66+
</div>
67+
<div class="max-[420px]:hidden">
68+
<a
69+
href="nested/register.html"
70+
class="primary-btn"
71+
hover="true">
72+
<span>ثبت نام</span>
73+
</a>
74+
</div>
75+
</div>
76+
</div>
77+
</header>
78+
<div class="max-w-screen-2xl p-8 m-auto mb-16 max-[420px]:p-4">
79+
<section class="text-center flex flex-col justify-center items-center gap-4">
80+
<h1 class="font-dana-bold text-6xl leading-loose gradient-text max-[530px]:text-3xl">
81+
لینک های بلــــــند خود را کوتاه کنید
82+
</h1>
83+
<p class="leading-loose max-[530px]:text-sm">
84+
لینکیفای یک سرویس کوتاه کننده لینک بسیار سریع و کار آمد است که تجربه بازدید کنندگان شما را آسان تر میکند.
85+
</p>
86+
</section>
87+
</div>
88+
<div class="max-w-screen-2xl p-8 m-auto mb-8 max-[420px]:p-4">
89+
<div class="flex justify-center items-center relative min-[991px]:max-w-[881px] m-auto">
90+
<input
91+
type="url"
92+
name="url"
93+
id="url"
94+
required
95+
placeholder="لینک را وارد کنید"
96+
class="w-full rounded-[30px] border-2 border-[var(--lite-border)] bg-[var(--gray)] py-4 pr-10 duration-200 ease-in-out focus:border-[var(--brand-primary-blue)]" />
97+
<div class="absolute left-[0.3rem]">
98+
<button
99+
type="button"
100+
id="shortener-btn"
101+
class="primary-btn"
102+
icon="true"
103+
rotate="true"
104+
aria-label="کوتاه کردن لینک">
105+
<i class="block">
106+
<ion-icon
107+
name="arrow-back-outline"
108+
aria-hidden="true"></ion-icon>
109+
</i>
110+
<ul class="hidden">
111+
<li></li>
112+
<li></li>
113+
<li></li>
114+
</ul>
115+
</button>
116+
</div>
117+
<div class="absolute right-[1rem] flex justify-center items-center">
118+
<ion-icon
119+
name="link-outline"
120+
aria-hidden="true"></ion-icon>
121+
</div>
122+
</div>
123+
<div class="text-center mt-4 max-w-[881px] m-auto">
124+
<div
125+
class="text-sm p-2 bg-[var(--black)] text-indigo-100 inline-flex justify-center items-center rounded-full">
126+
<span
127+
>شما تعداد
128+
<span
129+
id="count"
130+
class="text-[var(--brand-primary-pink)] font-bold"
131+
>5</span
132+
>
133+
لینک میتوانید کوتاه کنید.</span
134+
>
135+
<span class="rounded-full bg-[var(--brand-primary-blue)] px-2 py-1 font-bold mr-3 text-nowrap">
136+
<a href="nested/register.html">ثبت نام</a>
137+
</span>
138+
</div>
139+
</div>
140+
</div>
141+
<div class="max-w-screen-2xl p-8 m-auto mb-16 max-[420px]:p-4">
142+
<div class="relative">
143+
<table
144+
class="w-full text-right border-separate border-spacing-y-4 relative"
145+
id="table">
146+
<tr class="bg-[--gray] dana-bold text-lg text-white text-opacity-80">
147+
<th class="p-8 w-full max-[1120px]:p-4 hidden max-[1120px]:table-cell">لینک های کوتاه شده</th>
148+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">لینک کوتاه</th>
149+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">لینک اورجینال</th>
150+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">QR Code</th>
151+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">تعداد کلیک</th>
152+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">
153+
<button
154+
type="button"
155+
title="مرتب سازی"
156+
id="sort"
157+
class="flex justify-center items-center gap-2"
158+
aria-label="مرتب سازی بر اساس تاریخ">
159+
<span>تاریخ</span>
160+
<span class="flex justify-center items-center"
161+
><ion-icon
162+
name="swap-vertical-outline"
163+
aria-hidden="true"></ion-icon
164+
></span>
165+
</button>
166+
</th>
167+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">اقدامات</th>
168+
</tr>
169+
<tbody class="result-container"></tbody>
170+
</table>
171+
</div>
172+
</div>
173+
</div>
174+
<div
175+
role="dialog"
176+
class="backdrop fixed inset-0 z-10 bg-black bg-opacity-70 backdrop-blur-sm hidden"></div>
177+
<div
178+
role="dialog"
179+
class="qr-code-modal p-8 w-[580px] h-max max-w-[95%] bg-[var(--gray)] rounded-md border-2 border-[var(--lite-border)] fixed z-20 top-[50%] right-[50%] translate-y-[-50%] translate-x-[50%] justify-center items-center hidden">
180+
<div class="qr-code-modal-inner flex flex-col justify-center items-center gap-8">
181+
<div
182+
id="qrcode"
183+
class="qr-code relative overflow-hidden"></div>
184+
<a
185+
id="download"
186+
class="primary-btn"
187+
hover="true"
188+
lite="true"
189+
aria-label="دانلود QR Code">
190+
<span>دانلود</span>
191+
<i>
192+
<ion-icon
193+
name="download-outline"
194+
aria-hidden="true"></ion-icon>
195+
</i>
196+
</a>
197+
</div>
198+
<button
199+
type="button"
200+
class="primary-btn absolute top-2 right-2 bg-rose-600 shadow-none close-button"
201+
hover="true"
202+
icon="true"
203+
aria-label="بستن">
204+
<i class="block">
205+
<ion-icon
206+
name="close-outline"
207+
aria-hidden="true"></ion-icon>
208+
</i>
209+
</button>
210+
</div>
211+
<div
212+
role="dialog"
213+
class="info-modal p-4 w-[580px] h-max max-w-[95%] bg-[var(--gray)] rounded-md border-2 border-[var(--lite-border)] fixed z-10 top-[50%] right-[50%] translate-y-[-50%] translate-x-[50%] justify-center items-center hidden">
214+
<!-- -->
215+
</div>
216+
<div class="fixed right-4 bottom-4">
217+
<button
218+
type="button"
219+
title="پاک کردن لوکال استوریج"
220+
id="clear-localStorage"
221+
class="flex justify-center items-center text-white py-3 px-4 text-base ring-2 ring-rose-500 bg-[var(--gray)] shadow-none rounded-md duration-200 hover:ring-offset-2 hover:ring-offset-rose-500 group"
222+
aria-label="پاک کردن لوکال استوریج">
223+
<span
224+
class="block opacity-0 w-0 translate-y-full overflow-hidden duration-200 transition-[opacity,trasnform] group-hover:w-full group-hover:opacity-100 group-hover:translate-y-0"
225+
>پاکسازی</span
226+
>
227+
<ion-icon
228+
name="trash-bin-outline"
229+
class="text-xl duration-200 group-hover:w-0 group-hover:overflow-hidden group-hover:text-[0px]"
230+
aria-hidden="true"></ion-icon>
231+
</button>
232+
</div>
233+
234+
<script
235+
type="text/javascript"
236+
src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
237+
<script src="https://cdn.jsdelivr.net/gh/davidshimjs/qrcodejs/qrcode.min.js"></script>
238+
<script
239+
type="module"
240+
src="/src/main.js"></script>
241+
</body>
242+
</html>

‎nested/logged.html

+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<!DOCTYPE html>
2+
<html
3+
lang="fa"
4+
dir="rtl">
5+
<head>
6+
<meta charset="UTF-8" />
7+
<link
8+
rel="icon"
9+
type="image/svg+xml"
10+
href="/assests/icons/logo.png" />
11+
<meta
12+
name="viewport"
13+
content="width=device-width, initial-scale=1.0" />
14+
<meta
15+
name="description"
16+
content="لینکیفای یک ابزار برای کوتاه کردن لینک‌های طولانی شما است. سریع، امن و کاربردی." />
17+
<meta
18+
name="author"
19+
content="Mahdi Rostami" />
20+
<meta
21+
name="keywords"
22+
content="کوتاه کننده لینک, لینکیفای, کوتاه کردن لینک" />
23+
<title>لینکیفای - پلتفرم کوتاه کننده لینک</title>
24+
<!-- ionicons -->
25+
<script
26+
type="module"
27+
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
28+
<!-- tostify -->
29+
<link
30+
rel="stylesheet"
31+
type="text/css"
32+
href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css" />
33+
<!-- css -->
34+
<link
35+
rel="stylesheet"
36+
href="/src/style.css" />
37+
</head>
38+
<body class="relative">
39+
<div class="fixed h-full w-full inset-0 z-[-1] backdrop-blur-md"></div>
40+
<div id="app">
41+
<header
42+
class="max-w-screen-2xl p-8 m-auto mb-16 max-[420px]:p-4"
43+
role="banner">
44+
<div class="flex justify-between items-center">
45+
<div class="logo">
46+
<a href="/">
47+
<img
48+
src="/assests/icons/logo.png"
49+
alt="لوگو لینکیفای"
50+
class="w-24 h-24" />
51+
</a>
52+
</div>
53+
<div class="flex items-center gap-4">
54+
<!-- -->
55+
<div class="flex items-center gap-4">
56+
<div class="btn relative side-nav-container">
57+
<a
58+
role="button"
59+
title="باز کردن"
60+
href="javascript:void(0)"
61+
class="primary-btn"
62+
id="open-menu"
63+
lite="true">
64+
<span role="text">Mahdi</span>
65+
<ion-icon name="chevron-down-outline"></ion-icon>
66+
</a>
67+
<ul
68+
role="navigation"
69+
id="side-nav"
70+
class="text-center text-base overflow-hidden flex-col absolute z-10 left-0 mt-2 bg-[var(--black)] border border-[var(--lite-border)] rounded-md shadow-2xl hidden">
71+
<li>
72+
<a
73+
role="link"
74+
href="#"
75+
class="text-nowrap block w-full py-4 px-8 h-full duration-200 hover:bg-[var(--brand-primary-blue)] hover:text-white"
76+
>تماس با پشتیبانی</a
77+
>
78+
</li>
79+
<hr class="opacity-10 mx-[-2rem]" />
80+
<li>
81+
<a
82+
role="link"
83+
href="#"
84+
class="text-nowrap block w-full py-4 px-8 h-full duration-200 hover:bg-rose-500 hover:text-white"
85+
>خروج از حساب</a
86+
>
87+
</li>
88+
</ul>
89+
</div>
90+
</div>
91+
<!-- -->
92+
</div>
93+
</div>
94+
</header>
95+
<div class="max-w-screen-2xl p-8 m-auto mb-16 max-[420px]:p-4">
96+
<section class="text-center flex flex-col justify-center items-center gap-4">
97+
<h1 class="font-dana-bold text-6xl leading-loose gradient-text max-[530px]:text-3xl">
98+
لینک های بلــــــند خود را کوتاه کنید
99+
</h1>
100+
<p class="leading-loose max-[530px]:text-sm">
101+
لینکیفای یک سرویس کوتاه کننده لینک بسیار سریع و کار آمد است که تجربه بازدید کنندگان شما را آسان تر میکند.
102+
</p>
103+
</section>
104+
</div>
105+
<div class="max-w-screen-2xl p-8 m-auto mb-8 max-[420px]:p-4">
106+
<div class="flex justify-center items-center relative min-[991px]:max-w-[881px] m-auto">
107+
<input
108+
type="url"
109+
name="url"
110+
id="url"
111+
required
112+
placeholder="لینک را وارد کنید"
113+
class="w-full rounded-[30px] border-2 border-[var(--lite-border)] bg-[var(--gray)] py-4 pr-10 duration-200 ease-in-out focus:border-[var(--brand-primary-blue)]" />
114+
<div class="absolute left-[0.3rem]">
115+
<button
116+
type="button"
117+
id="shortener-btn"
118+
class="primary-btn"
119+
icon="true"
120+
rotate="true"
121+
aria-label="کوتاه کردن لینک">
122+
<i class="block">
123+
<ion-icon
124+
name="arrow-back-outline"
125+
aria-hidden="true"></ion-icon>
126+
</i>
127+
<ul class="hidden">
128+
<li></li>
129+
<li></li>
130+
<li></li>
131+
</ul>
132+
</button>
133+
</div>
134+
<div class="absolute right-[1rem] flex justify-center items-center">
135+
<ion-icon
136+
name="link-outline"
137+
aria-hidden="true"></ion-icon>
138+
</div>
139+
</div>
140+
</div>
141+
<div class="max-w-screen-2xl p-8 m-auto mb-16 max-[420px]:p-4">
142+
<div class="relative">
143+
<table
144+
class="w-full text-right border-separate border-spacing-y-4 relative"
145+
id="table">
146+
<tr class="bg-[--gray] dana-bold text-lg text-white text-opacity-80">
147+
<th class="p-8 w-full max-[1120px]:p-4 hidden max-[1120px]:table-cell">لینک های کوتاه شده</th>
148+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">لینک کوتاه</th>
149+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">لینک اورجینال</th>
150+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">QR Code</th>
151+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">تعداد کلیک</th>
152+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">
153+
<button
154+
type="button"
155+
title="مرتب سازی"
156+
id="sort"
157+
class="flex justify-center items-center gap-2"
158+
aria-label="مرتب سازی بر اساس تاریخ">
159+
<span>تاریخ</span>
160+
<span class="flex justify-center items-center"
161+
><ion-icon
162+
name="swap-vertical-outline"
163+
aria-hidden="true"></ion-icon
164+
></span>
165+
</button>
166+
</th>
167+
<th class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">اقدامات</th>
168+
</tr>
169+
<tbody class="result-container"></tbody>
170+
</table>
171+
</div>
172+
</div>
173+
</div>
174+
<div
175+
role="dialog"
176+
class="backdrop fixed inset-0 z-10 bg-black bg-opacity-70 backdrop-blur-sm hidden"></div>
177+
<div
178+
role="dialog"
179+
class="qr-code-modal p-8 w-[580px] h-max max-w-[95%] bg-[var(--gray)] rounded-md border-2 border-[var(--lite-border)] fixed z-20 top-[50%] right-[50%] translate-y-[-50%] translate-x-[50%] justify-center items-center hidden">
180+
<div class="qr-code-modal-inner flex flex-col justify-center items-center gap-8">
181+
<div
182+
id="qrcode"
183+
class="qr-code relative overflow-hidden"></div>
184+
<a
185+
id="download"
186+
class="primary-btn"
187+
hover="true"
188+
lite="true"
189+
aria-label="دانلود QR Code">
190+
<span>دانلود</span>
191+
<i>
192+
<ion-icon
193+
name="download-outline"
194+
aria-hidden="true"></ion-icon>
195+
</i>
196+
</a>
197+
</div>
198+
<button
199+
type="button"
200+
class="primary-btn absolute top-2 right-2 bg-rose-600 shadow-none close-button"
201+
hover="true"
202+
icon="true"
203+
aria-label="بستن">
204+
<i class="block">
205+
<ion-icon
206+
name="close-outline"
207+
aria-hidden="true"></ion-icon>
208+
</i>
209+
</button>
210+
</div>
211+
<div
212+
role="dialog"
213+
class="info-modal p-4 w-[580px] h-max max-w-[95%] bg-[var(--gray)] rounded-md border-2 border-[var(--lite-border)] fixed z-10 top-[50%] right-[50%] translate-y-[-50%] translate-x-[50%] justify-center items-center hidden">
214+
<!-- -->
215+
</div>
216+
<div class="fixed right-4 bottom-4">
217+
<button
218+
type="button"
219+
title="پاک کردن لوکال استوریج"
220+
id="clear-localStorage"
221+
class="flex justify-center items-center text-white py-3 px-4 text-base ring-2 ring-rose-500 bg-[var(--gray)] shadow-none rounded-md duration-200 hover:ring-offset-2 hover:ring-offset-rose-500 group"
222+
aria-label="پاک کردن لوکال استوریج">
223+
<span
224+
class="block opacity-0 w-0 translate-y-full overflow-hidden duration-200 transition-[opacity,trasnform] group-hover:w-full group-hover:opacity-100 group-hover:translate-y-0"
225+
>پاکسازی</span
226+
>
227+
<ion-icon
228+
name="trash-bin-outline"
229+
class="text-xl duration-200 group-hover:w-0 group-hover:overflow-hidden group-hover:text-[0px]"
230+
aria-hidden="true"></ion-icon>
231+
</button>
232+
</div>
233+
<script
234+
type="text/javascript"
235+
src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
236+
<script src="https://cdn.jsdelivr.net/gh/davidshimjs/qrcodejs/qrcode.min.js"></script>
237+
<!-- -->
238+
<script
239+
type="module"
240+
src="/src/main.js"></script>
241+
</body>
242+
</html>

‎nested/login.html

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<!DOCTYPE html>
2+
<html lang="fa">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link
6+
rel="icon"
7+
type="image/svg+xml"
8+
href="/assests/icons/logo.png" />
9+
<meta
10+
name="viewport"
11+
content="width=device-width, initial-scale=1.0" />
12+
<meta
13+
name="description"
14+
content="لینکیفای یک ابزار برای کوتاه کردن لینک‌های طولانی شما است. سریع، امن و کاربردی." />
15+
<title>ورود</title>
16+
<!-- ionicons -->
17+
<script
18+
type="module"
19+
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
20+
<!-- tostify -->
21+
<link
22+
rel="stylesheet"
23+
type="text/css"
24+
href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css" />
25+
<!-- css -->
26+
<link
27+
rel="stylesheet"
28+
href="/src/style.css" />
29+
</head>
30+
<body class="relative">
31+
<div class="fixed h-full w-full inset-0 z-[-1] backdrop-blur-md"></div>
32+
<div id="app">
33+
<section class="max-w-screen-2xl m-auto px-8 max-[420px]:p-4">
34+
<div class="grid grid-cols-2 justify-center items-center min-h-screen max-[991px]:grid-cols-1">
35+
<div class="flex flex-col max-[991px]:hidden">
36+
<figure>
37+
<img
38+
src="/assests/figures/sign-img.svg"
39+
alt="easy-url"
40+
class="w-full h-full drop-shadow-2xl" />
41+
</figure>
42+
</div>
43+
<!-- -->
44+
<div class="flex flex-col">
45+
<div class="flex flex-col gap-2 mb-8">
46+
<h1 class="text-2xl font-dana-bold text-white text-opacity-80">خوش آمدید.</h1>
47+
<p class="text-sm">
48+
<a
49+
href="register.html"
50+
class="text-[var(--brand-primary-blue)]"
51+
>ثبت نام
52+
</a>
53+
کنید و یا
54+
<a
55+
href="login.html"
56+
class="text-[var(--brand-primary-blue)]"
57+
>وارد شوید</a
58+
>
59+
تا از خدمات کامل ما بهره مند شوید.
60+
</p>
61+
</div>
62+
<form
63+
action="javascript:void(0)"
64+
method="post"
65+
name="sign-in"
66+
id="sign-in"
67+
class="w-full flex flex-col gap-4">
68+
<div class="w-full">
69+
<input
70+
type="email"
71+
name="email"
72+
class="block w-full p-4 rounded-md text-sm border border-[var(--lite-border)] bg-[var(--gray)] duration-200 focus:border-[var(--brand-primary-blue)] placeholder:duration-200 focus:placeholder:opacity-0"
73+
id="email"
74+
placeholder="ایمیل" />
75+
</div>
76+
<div class="w-full">
77+
<input
78+
type="password"
79+
name="password"
80+
class="block w-full p-4 rounded-md text-sm border border-[var(--lite-border)] bg-[var(--gray)] duration-200 focus:border-[var(--brand-primary-blue)] placeholder:duration-200 focus:placeholder:opacity-0"
81+
id="password"
82+
placeholder="پسورد" />
83+
</div>
84+
<!-- -->
85+
<div class="w-full flex justify-between items-center gap-4 max-[580px]:flex-col">
86+
<!-- -->
87+
<div class="flex gap-2 items-center max-[580px]:w-full max-[580px]:flex-col">
88+
<div
89+
class="text-center w-36 max-[580px]:w-full rounded-md select-none pointer-events-none border-2 border-[var(--lite-border)] bg-slate-600 px-4 pr-0.5 py-2 tracking-[1rem] font-[Kanit]"
90+
id="captcha-container"></div>
91+
<!-- -->
92+
<div class="flex gap-2 w-full">
93+
<div class="flex w-36 max-[580px]:w-full">
94+
<input
95+
type="text"
96+
name="captcha-input"
97+
id="captcha-input"
98+
class="w-36 max-[580px]:w-full block p-3 rounded-md text-sm border border-[var(--lite-border)] bg-[var(--gray)] duration-200 focus:border-[var(--brand-primary-blue)] placeholder:duration-200 focus:placeholder:opacity-0" />
99+
</div>
100+
<button
101+
id="captcha-refresh"
102+
class="primary-btn rounded-md py-3 px-3"
103+
lite="true"
104+
title="بارگزاری مجدد">
105+
<ion-icon name="refresh-outline"></ion-icon>
106+
</button>
107+
</div>
108+
<!-- -->
109+
</div>
110+
<div class="flex max-[580px]:mr-auto">
111+
<button
112+
type="submit"
113+
class="primary-btn"
114+
hover="true">
115+
ورود
116+
</button>
117+
</div>
118+
<!-- -->
119+
</div>
120+
<!-- -->
121+
<div class="w-full mt-8 flex flex-col gap-8">
122+
<div class="flex items-center gap-4">
123+
<div class="line w-full h-[1px] bg-white bg-opacity-10"></div>
124+
<h2 class="text-sm text-nowrap text-white text-opacity-50">یا از طریق</h2>
125+
<div class="line w-full h-[1px] bg-white bg-opacity-10"></div>
126+
</div>
127+
<div class="flex justify-center items-center gap-4 max-[420px]:flex-col">
128+
<a
129+
href="#"
130+
class="primary-btn rounded-md text-opacity-90 max-[420px]:w-full"
131+
lite="true"
132+
hover="true">
133+
<p>گوگل وارد شوید</p>
134+
<ion-icon
135+
name="logo-google"
136+
class="text-[var(--brand-primary-pink)]"></ion-icon>
137+
</a>
138+
<a
139+
href="register.html"
140+
class="primary-btn rounded-md text-opacity-90 max-[420px]:w-full"
141+
lite="true"
142+
rotate="true"
143+
hover="true">
144+
<p>ساخت حساب</p>
145+
<ion-icon
146+
name="log-in-outline"
147+
class="text-[var(--brand-primary-pink)]"></ion-icon>
148+
</a>
149+
</div>
150+
</div>
151+
</form>
152+
<!-- -->
153+
</div>
154+
</div>
155+
</section>
156+
</div>
157+
<!-- -->
158+
<!-- -->
159+
<script
160+
type="text/javascript"
161+
src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
162+
<!-- -->
163+
<script
164+
type="module"
165+
defer>
166+
import captchaGenerator from "/src/captchaGenerator.js";
167+
document.addEventListener("DOMContentLoaded", () => {
168+
captchaGenerator();
169+
});
170+
</script>
171+
</body>
172+
</html>

‎nested/register.html

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<!DOCTYPE html>
2+
<html lang="fa">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link
6+
rel="icon"
7+
type="image/svg+xml"
8+
href="/assests/icons/logo.png" />
9+
<meta
10+
name="viewport"
11+
content="width=device-width, initial-scale=1.0" />
12+
<meta
13+
name="description"
14+
content="لینکیفای یک ابزار برای کوتاه کردن لینک‌های طولانی شما است. سریع، امن و کاربردی." />
15+
<title>ثبت نام</title>
16+
<!-- ionicons -->
17+
<script
18+
type="module"
19+
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
20+
<!-- tostify -->
21+
<link
22+
rel="stylesheet"
23+
type="text/css"
24+
href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css" />
25+
<!-- css -->
26+
<link
27+
rel="stylesheet"
28+
href="/src/style.css" />
29+
</head>
30+
<body class="relative">
31+
<div class="fixed h-full w-full inset-0 z-[-1] backdrop-blur-md"></div>
32+
<div id="app">
33+
<section class="max-w-screen-2xl m-auto px-8 max-[420px]:p-4">
34+
<div class="grid grid-cols-2 justify-center items-center min-h-screen max-[991px]:grid-cols-1">
35+
<div class="flex flex-col max-[991px]:hidden">
36+
<figure>
37+
<img
38+
src="/assests/figures/sign-img.svg"
39+
alt="easy-url"
40+
class="w-full h-full drop-shadow-2xl" />
41+
</figure>
42+
</div>
43+
<!-- -->
44+
<div class="flex flex-col">
45+
<div class="flex flex-col gap-2 mb-8">
46+
<h1 class="text-2xl font-dana-bold text-white text-opacity-80">خوش آمدید.</h1>
47+
<p class="text-sm">
48+
<a
49+
href="register.html"
50+
class="text-[var(--brand-primary-blue)]"
51+
>ثبت نام
52+
</a>
53+
کنید و یا
54+
<a
55+
href="login.html"
56+
class="text-[var(--brand-primary-blue)]"
57+
>وارد شوید</a
58+
>
59+
تا از خدمات کامل ما بهره مند شوید.
60+
</p>
61+
</div>
62+
<form
63+
action="javascript:void(0)"
64+
method="post"
65+
name="sign-up"
66+
id="sign-up"
67+
class="w-full flex flex-col gap-4">
68+
<div class="w-full">
69+
<input
70+
type="text"
71+
name="name"
72+
class="block w-full p-4 rounded-md text-sm border border-[var(--lite-border)] bg-[var(--gray)] duration-200 focus:border-[var(--brand-primary-blue)] placeholder:duration-200 focus:placeholder:opacity-0"
73+
id="name"
74+
placeholder="نام شما" />
75+
</div>
76+
<div class="w-full">
77+
<input
78+
type="email"
79+
name="email"
80+
class="block w-full p-4 rounded-md text-sm border border-[var(--lite-border)] bg-[var(--gray)] duration-200 focus:border-[var(--brand-primary-blue)] placeholder:duration-200 focus:placeholder:opacity-0"
81+
id="email"
82+
placeholder="ایمیل" />
83+
</div>
84+
<div class="w-full">
85+
<input
86+
type="password"
87+
name="password"
88+
class="block w-full p-4 rounded-md text-sm border border-[var(--lite-border)] bg-[var(--gray)] duration-200 focus:border-[var(--brand-primary-blue)] placeholder:duration-200 focus:placeholder:opacity-0"
89+
id="password"
90+
placeholder="پسورد" />
91+
</div>
92+
<div class="w-full">
93+
<input
94+
type="password"
95+
name="password-r"
96+
class="block w-full p-4 rounded-md text-sm border border-[var(--lite-border)] bg-[var(--gray)] duration-200 focus:border-[var(--brand-primary-blue)] placeholder:duration-200 focus:placeholder:opacity-0"
97+
id="password-r"
98+
placeholder="تکرار پسورد" />
99+
</div>
100+
<!-- -->
101+
<div class="w-full flex justify-between items-center gap-4 max-[580px]:flex-col">
102+
<!-- -->
103+
<div class="flex gap-2 items-center max-[580px]:w-full max-[580px]:flex-col">
104+
<div
105+
class="text-center w-36 max-[580px]:w-full rounded-md select-none pointer-events-none border-2 border-[var(--lite-border)] bg-slate-600 px-4 pr-0.5 py-2 tracking-[1rem] font-[Kanit]"
106+
id="captcha-container"></div>
107+
<!-- -->
108+
<div class="flex gap-2 w-full">
109+
<div class="flex w-36 max-[580px]:w-full">
110+
<input
111+
type="text"
112+
name="captcha-input"
113+
id="captcha-input"
114+
class="w-36 max-[580px]:w-full block p-3 rounded-md text-sm border border-[var(--lite-border)] bg-[var(--gray)] duration-200 focus:border-[var(--brand-primary-blue)] placeholder:duration-200 focus:placeholder:opacity-0" />
115+
</div>
116+
<button
117+
id="captcha-refresh"
118+
class="primary-btn rounded-md py-3 px-3"
119+
lite="true"
120+
title="بارگزاری مجدد">
121+
<ion-icon name="refresh-outline"></ion-icon>
122+
</button>
123+
</div>
124+
<!-- -->
125+
</div>
126+
<div class="flex max-[580px]:mr-auto">
127+
<button
128+
type="submit"
129+
class="primary-btn"
130+
hover="true">
131+
ثبت نام
132+
</button>
133+
</div>
134+
<!-- -->
135+
</div>
136+
<!-- -->
137+
<div class="w-full mt-8 flex flex-col gap-8">
138+
<div class="flex items-center gap-4">
139+
<div class="line w-full h-[1px] bg-white bg-opacity-10"></div>
140+
<h2 class="text-sm text-nowrap text-white text-opacity-50">یا از طریق</h2>
141+
<div class="line w-full h-[1px] bg-white bg-opacity-10"></div>
142+
</div>
143+
<div class="flex justify-center items-center gap-4 max-[420px]:flex-col">
144+
<a
145+
href="#"
146+
class="primary-btn rounded-md text-opacity-90 max-[420px]:w-full"
147+
lite="true"
148+
hover="true">
149+
<p>گوگل وارد شوید</p>
150+
<ion-icon
151+
name="logo-google"
152+
class="text-[var(--brand-primary-pink)]"></ion-icon>
153+
</a>
154+
<a
155+
href="login.html"
156+
class="primary-btn rounded-md text-opacity-90 max-[420px]:w-full"
157+
lite="true"
158+
rotate="true"
159+
hover="true">
160+
<p>حساب کاربری</p>
161+
<ion-icon
162+
name="log-in-outline"
163+
class="text-[var(--brand-primary-pink)]"></ion-icon>
164+
</a>
165+
</div>
166+
</div>
167+
</form>
168+
<!-- -->
169+
</div>
170+
</div>
171+
</section>
172+
</div>
173+
<!-- -->
174+
<script
175+
type="text/javascript"
176+
src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
177+
<!-- -->
178+
<script type="module">
179+
import captchaGenerator from "/src/captchaGenerator.js";
180+
document.addEventListener("DOMContentLoaded", () => {
181+
captchaGenerator();
182+
});
183+
</script>
184+
</body>
185+
</html>

‎package-lock.json

+2,699
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "linkify",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "vite build",
9+
"preview": "vite preview --port 8080",
10+
"deploy": "gh-pages -d dist"
11+
},
12+
"devDependencies": {
13+
"autoprefixer": "^10.4.20",
14+
"gh-pages": "^6.3.0",
15+
"postcss": "^8.5.1",
16+
"tailwindcss": "^3.4.17",
17+
"vite": "^6.0.5"
18+
},
19+
"dependencies": {
20+
"toastify-js": "^1.12.0"
21+
}
22+
}

‎postcss.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
}

‎public/assests/figures/cubes.png

334 KB
Loading

‎public/assests/figures/sign-img.svg

+82
Loading

‎public/assests/icons/logo.png

24.9 KB
Loading

‎public/assests/icons/qrcode.png

1.35 KB
Loading
19.1 KB
Binary file not shown.
8.11 KB
Binary file not shown.

‎public/assests/sound/qr-code-scan.mp3

11.3 KB
Binary file not shown.
25.5 KB
Binary file not shown.
24.9 KB
Binary file not shown.

‎src/alertFunction.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// tostify function
2+
export const danger = "#f43f5e";
3+
export const succses = "#10b981";
4+
5+
export const alertFunction = (msg, type) => {
6+
Toastify({
7+
text: msg,
8+
duration: 2000,
9+
position: "right",
10+
gravity: "top",
11+
style: {
12+
boxShadow: "none",
13+
borderRadius: "6px",
14+
background: type,
15+
},
16+
}).showToast();
17+
};

‎src/captchaGenerator.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// captcha generator for sign up & sign in pages
2+
export const captchaContainer = document.getElementById("captcha-container");
3+
export const captchaInput = document.getElementById("captcha-input");
4+
export const captchaRefresh = document.getElementById("captcha-refresh");
5+
6+
export default function captchaGenerator() {
7+
let captcha = Math.floor(Math.random() * 90000) + 10000;
8+
return captcha;
9+
}
10+
11+
captchaContainer.textContent = captchaGenerator();
12+
13+
captchaRefresh.addEventListener("click", () => {
14+
captchaContainer.classList.add("glitch");
15+
setTimeout(() => {
16+
captchaContainer.classList.remove("glitch");
17+
}, 300);
18+
captchaContainer.textContent = captchaGenerator();
19+
captchaInput.value = "";
20+
});

‎src/main.js

+370
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
// importing alert & sound & modal function
2+
import { alertFunction, danger, succses } from "./alertFunction";
3+
import { soundEffect } from "./soundEffect";
4+
import { modalActivator, backdropActivator, backdrop } from "./modalController";
5+
6+
export const infoModal = document.querySelector(".info-modal");
7+
const shortenButton = document.getElementById("shortener-btn");
8+
const shortenerArrow = shortenButton.firstElementChild;
9+
const shortenerDots = shortenButton.lastElementChild;
10+
const input = document.getElementById("url");
11+
const resultContainer = document.querySelector(".result-container");
12+
const sideNavContainer = document.querySelector(".side-nav-container");
13+
const openMenuButton = document.getElementById("open-menu");
14+
const sideNav = document.getElementById("side-nav");
15+
const clearStorageButton = document.getElementById("clear-localStorage");
16+
// url Pattern
17+
const urlPattern = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;
18+
19+
// alert function with catching sound effect & message
20+
function showAlert(message, type) {
21+
alertFunction(message, type);
22+
soundEffect(type === danger ? "dangerAudio" : "succsesAudio");
23+
}
24+
25+
// localStorage
26+
function saveLinkToStorage(originalLink, shortenedLink, id) {
27+
const storedLinks = JSON.parse(localStorage.getItem("shortenedLinks")) || [];
28+
29+
if (storedLinks.length >= 5) {
30+
storedLinks.shift();
31+
}
32+
33+
storedLinks.push({ originalLink, shortenedLink, id });
34+
localStorage.setItem("shortenedLinks", JSON.stringify(storedLinks));
35+
}
36+
function deleteLinkFromStorage(id) {
37+
const storedLinks = JSON.parse(localStorage.getItem("shortenedLinks")) || [];
38+
const updatedLinks = storedLinks.filter((link) => link.id !== id);
39+
localStorage.setItem("shortenedLinks", JSON.stringify(updatedLinks));
40+
}
41+
function loadLinksFromStorage() {
42+
resultContainer.innerHTML = "";
43+
44+
const storedLinks = JSON.parse(localStorage.getItem("shortenedLinks")) || [];
45+
46+
storedLinks.forEach((link) => {
47+
createResult(link.originalLink, link.shortenedLink, link.id);
48+
});
49+
}
50+
// localStorage end
51+
52+
// get Time
53+
const getDate = {
54+
year: new Date().toLocaleDateString("fa-IR", { year: "numeric" }),
55+
month: new Date().toLocaleDateString("fa-IR", { month: "long" }),
56+
day: new Date().toLocaleDateString("fa-IR", { day: "numeric" }),
57+
};
58+
59+
// create result - table row
60+
const createResult = (originalLink, shortenedLink, id) => {
61+
const tr = document.createElement("tr");
62+
tr.classList.add("fade-in", "bg-[rgb(24,30,41)]", "bg-opacity-50", "w-full");
63+
tr.innerHTML =
64+
/* html */
65+
`<td class="p-8 max-[1120px]:p-4">
66+
<div class="copy-container flex justify-between items-center"></div>
67+
</td>
68+
<!-- -->
69+
<td class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">
70+
<a
71+
href="${originalLink}"
72+
target="_blank"
73+
title="بازدید لینک"
74+
class="max-w-32 block overflow-hidden text-nowrap text-ellipsis duration-200 hover:text-[var(--brand-primary-pink)]">
75+
${originalLink}
76+
</a>
77+
</td>
78+
<!-- -->
79+
<td class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">
80+
<div class="qr-container"></div>
81+
</td>
82+
<!-- -->
83+
<td class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">
84+
<p
85+
title="تعداد کلیک ها">
86+
${0}
87+
</p>
88+
</td>
89+
<!-- -->
90+
<td class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">
91+
<p
92+
class="flex flex-row-reverse justify-end items-end gap-2 text-white text-opacity-60"
93+
title="تاریخ ایجاد">
94+
<span>${getDate.year}</span>
95+
<span>${getDate.month}</span>
96+
<span>${getDate.day}</span>
97+
</p>
98+
</td>
99+
<!-- -->
100+
<td class="p-8 max-[1120px]:p-4 max-[1120px]:hidden">
101+
<div class="delete-container"></div>
102+
</td>
103+
`;
104+
105+
// call button generators generators
106+
const copyButton = copyButtonGenerator(shortenedLink);
107+
const qrCodeButton = qrButtonGenerator(shortenedLink);
108+
const deleteButton = deleteButtonGenerator(tr, id);
109+
const eyeButton = eyeButtonGenerator(
110+
copyButton,
111+
originalLink,
112+
qrCodeButton,
113+
getDate.year,
114+
getDate.month,
115+
getDate.day,
116+
deleteButton
117+
);
118+
tr.querySelector(".copy-container").appendChild(eyeButton);
119+
tr.querySelector(".copy-container").prepend(copyButton);
120+
tr.querySelector(".qr-container").appendChild(qrCodeButton);
121+
tr.querySelector(".delete-container").appendChild(deleteButton);
122+
tr.setAttribute("table-id", id);
123+
resultContainer.prepend(tr);
124+
};
125+
126+
// eye button generator
127+
const eyeButtonGenerator = (copyButton, originalLink, qrcode, year, month, day, deleteButton) => {
128+
const div = document.createElement("div");
129+
div.classList.add("hidden", "max-[1120px]:block");
130+
div.innerHTML =
131+
/* html */
132+
`<button
133+
title="باز کردن"
134+
class="text-xl flex justify-center items-center w-10 h-10 px-3 py-3 rounded-full bg-[var(--lite-border)] duration-200 hover:bg-[var(--brand-primary-blue)] group">
135+
<ion-icon
136+
name="eye-outline"
137+
class="duration-200 group-hover:text-white"></ion-icon>
138+
</button>`;
139+
div.querySelector("button").addEventListener("click", () => {
140+
const container = document.createElement("div");
141+
container.classList.add("flex", "flex-col", "gap-8");
142+
const callDate = document.createElement("div");
143+
callDate.innerHTML =
144+
/* html */
145+
`<div class="flex flex-row-reverse justify-end items-end gap-2 text-white text-opacity-60">
146+
<span>${year}</span>
147+
<span>${month}</span>
148+
<span>${day}</span>
149+
</div>`;
150+
const orgLink = document.createElement("div");
151+
orgLink.innerHTML = /* html */ `<a href="${originalLink}" target="_blank" class="duration-200 hover:text-[var(--brand-primary-pink)]">${originalLink}</a>`;
152+
const header = document.createElement("header");
153+
header.classList.add("flex", "justify-between", "items-center");
154+
header.appendChild(qrcode);
155+
header.appendChild(deleteButton);
156+
container.appendChild(header);
157+
container.appendChild(copyButton);
158+
container.appendChild(orgLink);
159+
container.appendChild(callDate);
160+
infoModal.appendChild(container);
161+
infoModal.classList.replace("hidden", "fade-in");
162+
backdrop.classList.remove("hidden");
163+
document.body.classList.add("overflow-hidden");
164+
loadLinksFromStorage();
165+
});
166+
//
167+
return div;
168+
};
169+
170+
// copy buttton generator
171+
const copyButtonGenerator = (shortenedLink) => {
172+
const div = document.createElement("div");
173+
div.classList.add("flex", "items-center", "gap-2");
174+
div.innerHTML =
175+
/* html */
176+
`
177+
<button
178+
title="کپی در کلیپ بورد"
179+
class="text-xl flex justify-center items-center w-10 h-10 px-3 py-3 rounded-full bg-[var(--lite-border)] duration-200 hover:bg-[var(--brand-primary-pink)] group">
180+
<ion-icon
181+
name="copy-outline"
182+
class="duration-200 group-hover:text-white"></ion-icon>
183+
</button>
184+
<a
185+
href="${shortenedLink}"
186+
target="_blank"
187+
title="بازدید لینک"
188+
class="hover:text-[var(--brand-primary-pink)] duration-200 max-w-[200px] overflow-hidden text-nowrap text-ellipsis max-[380px]:max-w-[100px]">
189+
${shortenedLink}
190+
</a>
191+
`;
192+
div.querySelector("button").addEventListener("click", () => {
193+
navigator.clipboard.writeText(shortenedLink);
194+
showAlert("با موفقیت کپی شد", succses);
195+
});
196+
return div;
197+
};
198+
199+
// qr button generator
200+
const qrButtonGenerator = (shortenedLink) => {
201+
const div = document.createElement("div");
202+
div.innerHTML =
203+
/* html */
204+
`
205+
<button
206+
title="qrcode"
207+
class="qr-btn"
208+
>
209+
<img
210+
src="./assests/icons/qrcode.png"
211+
alt="qrcode"
212+
class="w-full h-full" />
213+
</button>
214+
`;
215+
div.querySelector("button").addEventListener("click", () => {
216+
soundEffect("scanAudio");
217+
const qrcodeContainer = document.getElementById("qrcode");
218+
qrcodeContainer.classList.add("scanner");
219+
setTimeout(() => {
220+
qrcodeContainer.classList.remove("scanner");
221+
}, 3000);
222+
qrcodeContainer.innerHTML = "";
223+
modalActivator();
224+
backdropActivator();
225+
const qrcode = new QRCode(qrcodeContainer, {
226+
text: shortenedLink,
227+
width: 420,
228+
height: 420,
229+
colorDark: "#fff",
230+
colorLight: "#000",
231+
correctLevel: QRCode.CorrectLevel.H,
232+
});
233+
});
234+
return div;
235+
};
236+
237+
// delete button generator
238+
const deleteButtonGenerator = (section, id) => {
239+
const div = document.createElement("div");
240+
div.innerHTML =
241+
/* html */
242+
`
243+
<button
244+
title="پاک کردن"
245+
class="flex justify-center items-center text-2xl w-12 h-12 rounded-full border-2 border-rose-500 text-rose-500 duration-200 hover:opacity-70">
246+
<ion-icon name="trash-outline"></ion-icon>
247+
</button>
248+
`;
249+
div.querySelector("button").addEventListener("click", () => {
250+
infoModal.innerHTML = "";
251+
infoModal.classList.add("hidden");
252+
backdrop.classList.add("hidden");
253+
document.body.classList.remove("overflow-hidden");
254+
deleteLinkFromStorage(id);
255+
section.remove();
256+
resultContainer.innerHTML = "";
257+
loadLinksFromStorage();
258+
showAlert("بخش مورد نظر حذف شد", danger);
259+
});
260+
return div;
261+
};
262+
263+
// check table row counts
264+
function maxResultCounter() {
265+
if (resultContainer.childElementCount > 5) {
266+
resultContainer.lastElementChild.remove();
267+
}
268+
}
269+
270+
// start progress and fetching data
271+
shortenButton.addEventListener("click", async () => {
272+
let inputVal = input.value;
273+
274+
// input feild & validate
275+
if (inputVal === "" || inputVal === null) {
276+
input.classList.add("border-rose-500");
277+
showAlert("مقدار فرم خالی میباشد", danger);
278+
return;
279+
}
280+
if (!urlPattern.test(inputVal)) {
281+
input.value = "";
282+
input.classList.add("border-rose-500");
283+
showAlert("مقدار وارد شده معتبر نمیباشد", danger);
284+
return;
285+
}
286+
// check user web browser if online & offline
287+
if (!window.navigator.onLine) {
288+
input.value = "";
289+
input.classList.add("border-rose-500");
290+
showAlert("لطفا اینترنت خود را بررسی کنید", danger);
291+
return;
292+
}
293+
294+
// disabled button & adding loading animation on the button and remove red border from input
295+
input.classList.remove("border-rose-500");
296+
shortenButton.setAttribute("loading", true);
297+
shortenButton.setAttribute("lite", true);
298+
shortenButton.setAttribute("disabled", true);
299+
shortenerArrow.classList.add("hidden");
300+
shortenerDots.classList.remove("hidden");
301+
window.location.href = "#table";
302+
303+
// fetching
304+
setTimeout(async () => {
305+
try {
306+
const response = await fetch("https://url-shortener-service.p.rapidapi.com/shorten", {
307+
method: "POST",
308+
headers: {
309+
"x-rapidapi-key": "8ae2231c9fmsh16c33a7ca564496p144334jsna9c67947ba81",
310+
"x-rapidapi-host": "url-shortener-service.p.rapidapi.com",
311+
"Content-Type": "application/x-www-form-urlencoded",
312+
},
313+
body: `url=${inputVal}`,
314+
});
315+
316+
if (response.status === 429) {
317+
showAlert("تعداد درخواست ها بیش از حد مجاز میباشد, لطفا بعدا امتحان کنید", danger);
318+
return;
319+
}
320+
321+
const result = await response.text();
322+
const message = result ? result.slice(15, -2) : "متاسفانه جوابی دریافت نشد.";
323+
const id = `tr-${Date.now()}`;
324+
// call function for create table row & save table row to storage & check max count on the table container & call showAlert function
325+
createResult(inputVal, message, id);
326+
saveLinkToStorage(inputVal, message, id);
327+
maxResultCounter();
328+
showAlert("لینک کوتاه با موفقیت اضافه شد", succses);
329+
} catch (error) {
330+
setTimeout(() => {
331+
showAlert("لطفا فیلترشکن خود را بررسی کنید", danger);
332+
console.clear();
333+
}, 1000);
334+
} finally {
335+
// enabled shortener button & remove current value from input
336+
input.value = "";
337+
shortenButton.removeAttribute("loading");
338+
shortenButton.removeAttribute("lite");
339+
shortenButton.removeAttribute("disabled");
340+
shortenerArrow.classList.remove("hidden");
341+
shortenerDots.classList.add("hidden");
342+
}
343+
}, 1000);
344+
});
345+
// side nav menu
346+
if (sideNavContainer) {
347+
openMenuButton.addEventListener("click", () => {
348+
sideNav.classList.toggle("hidden");
349+
sideNav.classList.toggle("fade-in");
350+
});
351+
352+
window.addEventListener("click", (e) => {
353+
if (!e.target.closest(".side-nav-container")) {
354+
sideNav.classList.add("hidden");
355+
sideNav.classList.remove("fade-in");
356+
}
357+
});
358+
}
359+
// clear storage button
360+
clearStorageButton.addEventListener("click", () => {
361+
if (localStorage.getItem("shortenedLinks") == null || localStorage.getItem("shortenedLinks") == "[]") {
362+
showAlert("داده ای موجود نیست", danger);
363+
} else {
364+
localStorage.removeItem("shortenedLinks");
365+
resultContainer.innerHTML = "";
366+
showAlert("داده ها حذف شدند", succses);
367+
}
368+
});
369+
// loading data from storage when DOM loaded
370+
document.addEventListener("DOMContentLoaded", loadLinksFromStorage);

‎src/modalController.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { infoModal } from "../src/main";
2+
3+
export const qrCodeModal = document.querySelector(".qr-code-modal");
4+
export const backdrop = document.querySelector(".backdrop");
5+
export const closeModal = document.querySelector(".close-button");
6+
export const downloadButton = document.getElementById("download");
7+
8+
// modal displayer
9+
export function modalActivator() {
10+
qrCodeModal.classList.replace("hidden", "flex");
11+
qrCodeModal.classList.add("fade-in");
12+
}
13+
14+
export function modalDisabler() {
15+
qrCodeModal.classList.replace("flex", "hidden");
16+
qrCodeModal.classList.add("hidden");
17+
}
18+
19+
export function backdropActivator() {
20+
backdrop.classList.replace("hidden", "block");
21+
document.body.classList.add("overflow-hidden");
22+
}
23+
24+
export function backdropDisabler() {
25+
backdrop.classList.replace("block", "hidden");
26+
document.body.classList.remove("overflow-hidden");
27+
}
28+
29+
// backdrop (black mask)
30+
backdrop.addEventListener("click", () => {
31+
infoModal.classList.add("hidden");
32+
backdrop.classList.add("hidden");
33+
infoModal.textContent = "";
34+
modalDisabler();
35+
backdropDisabler();
36+
});
37+
38+
// modal close button
39+
closeModal.addEventListener("click", () => {
40+
modalDisabler();
41+
backdropDisabler();
42+
});
43+
44+
// download qr image button
45+
downloadButton.addEventListener("click", () => {
46+
let qrScanImage = downloadButton.previousElementSibling.lastChild.src;
47+
downloadButton.setAttribute("href", qrScanImage);
48+
downloadButton.setAttribute("download", "qr-scan-image");
49+
});

‎src/soundEffect.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function soundEffect(type) {
2+
const effect = {
3+
succsesAudio: new Audio("./assests/sound/discord-nofications.mp3"),
4+
dangerAudio: new Audio("./assests/sound/discord-leave.mp3"),
5+
scanAudio: new Audio("./assests/sound/qr-code-scan.mp3"),
6+
};
7+
8+
effect[type].play();
9+
}

‎src/style.css

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
@import url("https://fonts.googleapis.com/css2?family=Kanit:wght@500&display=swap");
2+
3+
@font-face {
4+
font-family: "dana-regular";
5+
src: url(../fonts/dana/dana-fanum-regular.woff2) format("woff2");
6+
font-display: swap;
7+
}
8+
9+
@font-face {
10+
font-family: "dana-bold";
11+
src: url(../fonts/dana/dana-fanum-bold.woff2) format("woff2");
12+
font-display: swap;
13+
}
14+
15+
/* */
16+
@tailwind base;
17+
@tailwind components;
18+
@tailwind utilities;
19+
/* starting styles */
20+
@layer base {
21+
.font-dana {
22+
font-family: "dana-regular";
23+
}
24+
25+
.font-dana-bold {
26+
font-family: "dana-bold";
27+
}
28+
29+
:root {
30+
--brand-primary-pink: rgb(235, 86, 142);
31+
--brand-primary-blue: rgb(20, 78, 227);
32+
--black: rgb(11, 16, 27);
33+
--gray: rgb(24, 30, 41);
34+
--lite: rgb(201, 206, 214);
35+
--lite-border: rgb(53, 60, 74);
36+
--white: rgb(255, 255, 255);
37+
--body-bg: rgb(21, 26, 36);
38+
--primary-linear: linear-gradient(to right, #144ee3, #eb568e, #a353aa, #144ee3);
39+
color-scheme: dark;
40+
}
41+
42+
* {
43+
@apply p-0 m-0;
44+
}
45+
46+
*,
47+
*::after,
48+
*::before {
49+
@apply box-border;
50+
}
51+
52+
button,
53+
input {
54+
@apply block outline-none font-dana;
55+
}
56+
57+
html {
58+
@apply scroll-pt-8 scroll-smooth;
59+
}
60+
61+
body,
62+
html {
63+
direction: rtl;
64+
@apply text-[var(--lite)] font-dana;
65+
}
66+
67+
body {
68+
background: url(/assests/figures/cubes.png) var(--body-bg) no-repeat fixed center;
69+
background-size: contain;
70+
}
71+
}
72+
73+
@layer components {
74+
.primary-btn {
75+
@apply py-2 px-4 rounded-3xl bg-[var(--brand-primary-blue)] border border-transparent shadow-[0_14px_14px_rgba(20,78,227,20%)] cursor-pointer
76+
relative text-lg text-white flex
77+
justify-center items-center gap-2 duration-200 ease-linear;
78+
79+
ion-icon {
80+
@apply block;
81+
}
82+
}
83+
84+
.primary-btn[rotate="true"] {
85+
ion-icon {
86+
@apply rotate-180;
87+
}
88+
}
89+
90+
.primary-btn[icon="true"] {
91+
@apply w-12 h-12;
92+
}
93+
94+
.primary-btn[rounded="true"] {
95+
@apply rounded-full;
96+
}
97+
98+
.primary-btn[disabled="true"] {
99+
@apply bg-[rgb(20,78,227,50%)] border border-[rgb(20,78,227,50%)] pointer-events-none select-none;
100+
}
101+
102+
.primary-btn[lite="true"] {
103+
background: var(--gray);
104+
border: 1px solid var(--lite-border);
105+
box-shadow: 0 14px 14px rgb(11, 16, 27, 20%);
106+
}
107+
108+
.primary-btn[hover="true"] {
109+
&:hover {
110+
@apply shadow-none rounded-md opacity-90 translate-y-[-0.2rem];
111+
}
112+
}
113+
114+
.primary-btn[loading="true"] {
115+
ul {
116+
@apply flex justify-center items-center gap-2;
117+
118+
li {
119+
@apply bg-white w-1 h-1 rounded-full translate-y-0;
120+
animation: loading 0.5s linear infinite alternate;
121+
will-change: transform;
122+
}
123+
li + li {
124+
animation-delay: 0.4s;
125+
}
126+
li + li + li {
127+
animation-delay: 0.3s;
128+
}
129+
}
130+
}
131+
@keyframes loading {
132+
to {
133+
transform: translateY(6px);
134+
}
135+
}
136+
137+
.gradient-text {
138+
@apply bg-clip-text text-transparent;
139+
background-image: var(--primary-linear);
140+
}
141+
142+
#qrcode {
143+
img {
144+
@apply rounded-md min-w-full drop-shadow-2xl;
145+
}
146+
}
147+
148+
.qr-btn:hover {
149+
animation: glitch 0.3s cubic-bezier(0.075, 0.82, 0.165, 1) forwards;
150+
will-change: transform, opacity, filter;
151+
}
152+
153+
.glitch {
154+
animation: glitch 0.3s cubic-bezier(0.075, 0.82, 0.165, 1) forwards;
155+
will-change: transform, opacity, filter;
156+
}
157+
@keyframes glitch {
158+
0% {
159+
opacity: 0.8;
160+
filter: blur(1px) hue-rotate(120deg);
161+
transform: skewX(10deg);
162+
}
163+
164+
50% {
165+
opacity: 0.2;
166+
filter: blur(2px) hue-rotate(180deg);
167+
transform: skewX(-10deg);
168+
}
169+
170+
100% {
171+
opacity: 1;
172+
filter: blur(0) hue-rotate(0);
173+
transform: skewX(0);
174+
}
175+
}
176+
177+
.fade-in {
178+
opacity: 0;
179+
animation: fade-in 0.3s ease-in-out forwards;
180+
will-change: opacity;
181+
}
182+
@keyframes fade-in {
183+
to {
184+
opacity: 1;
185+
}
186+
}
187+
188+
.scanner {
189+
&::after {
190+
content: "";
191+
animation: scan 0.5s linear forwards infinite alternate;
192+
background: linear-gradient(0deg, rgba(0, 163, 206, 0) 70%, rgba(244, 63, 94, 0.8) 100%);
193+
@apply absolute inset-0 bottom-0 w-full h-full z-10;
194+
will-change: transform, opacity;
195+
}
196+
}
197+
@keyframes scan {
198+
from {
199+
transform: translateY(-10%);
200+
opacity: 0;
201+
}
202+
to {
203+
transform: translateY(110%);
204+
opacity: 1;
205+
}
206+
}
207+
}

‎tailwind.config.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/** @type {import('tailwindcss').Config} */
2+
export default {
3+
content: ["./*.html", "./nested/*.html", "./src/*.js"],
4+
theme: {
5+
extend: {},
6+
},
7+
plugins: [],
8+
};

‎vite.config.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { dirname, resolve } from "node:path";
2+
import { fileURLToPath } from "node:url";
3+
import { defineConfig } from "vite";
4+
5+
const __dirname = dirname(fileURLToPath(import.meta.url));
6+
7+
export default defineConfig({
8+
base: "/",
9+
10+
build: {
11+
rollupOptions: {
12+
input: {
13+
main: resolve(__dirname, "index.html"),
14+
logged: resolve(__dirname, "nested/logged.html"),
15+
register: resolve(__dirname, "nested/register.html"),
16+
login: resolve(__dirname, "nested/login.html"),
17+
},
18+
},
19+
},
20+
});

0 commit comments

Comments
 (0)
Please sign in to comment.