├── .env ├── styles ├── globals.css └── Home.module.css ├── public ├── favicon.ico └── vercel.svg ├── next-env.d.ts ├── vercel.json ├── pages ├── _app.tsx ├── api │ ├── redirect.ts │ └── shorten_link.ts └── index.tsx ├── .gitignore ├── package.json ├── tsconfig.json └── README.md /.env: -------------------------------------------------------------------------------- 1 | MONGODB_URI="" -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.css'; -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mateuszsokola/yals/main/public/favicon.ico -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{ "source": "/r/:id", "destination": "/api/redirect?id=:id" }] 3 | } 4 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | 3 | function MyApp({ Component, pageProps }) { 4 | return 5 | } 6 | 7 | export default MyApp 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yals", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@types/mongodb": "^3.6.3", 12 | "antd": "^4.10.2", 13 | "axios": "^0.21.1", 14 | "mongodb": "^3.6.3", 15 | "next": "10.0.5", 16 | "react": "17.0.1", 17 | "react-dom": "17.0.1" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^14.14.20", 21 | "@types/react": "^17.0.0", 22 | "@vercel/node": "^1.9.0", 23 | "typescript": "^4.1.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | float: left; 3 | width: 120px; 4 | height: 30px; 5 | margin: 16px 24px 16px 0; 6 | background: rgba(255,255,255,.3); 7 | } 8 | 9 | .content { 10 | display: flex; 11 | align-items: center; 12 | padding: 0 50px; 13 | min-height: calc(100vh - 64px - 70px); 14 | } 15 | 16 | .shortner { 17 | width: 100%; 18 | background: #fff; 19 | padding: 24px 20px; 20 | } 21 | 22 | .linkField { 23 | display: flex; 24 | width: 100%; 25 | } 26 | 27 | .linkFieldInput { 28 | flex: 100%; 29 | margin-right: 5px; 30 | } 31 | 32 | .linkFieldButton { 33 | width: 120px; 34 | } 35 | 36 | .footer { 37 | text-align: center; 38 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve" 20 | }, 21 | "include": [ 22 | "next-env.d.ts", 23 | "**/*.ts", 24 | "**/*.tsx" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /pages/api/redirect.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import { MongoClient, ObjectID } from 'mongodb'; 3 | import { NowRequest, NowResponse } from '@vercel/node'; 4 | 5 | let cachedDb; 6 | 7 | async function connectToDatabase() { 8 | if (cachedDb) { 9 | return cachedDb; 10 | } 11 | const client = new MongoClient(process.env.MONGODB_URI, { useNewUrlParser: true }); 12 | 13 | cachedDb = client; 14 | return await client.connect(); 15 | } 16 | 17 | export default async (req: NowRequest, res: NowResponse) => { 18 | const db = await connectToDatabase(); 19 | 20 | const entry = await db.db('links_db').collection('links_collection').findOne({ _id: new ObjectID(req.query.id as string) }); 21 | 22 | if (entry !== null) { 23 | return res.redirect(301, entry.link); 24 | } 25 | 26 | return res.redirect(301, '/'); 27 | } 28 | -------------------------------------------------------------------------------- /pages/api/shorten_link.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import { MongoClient } from 'mongodb'; 3 | import { NowRequest, NowResponse } from '@vercel/node'; 4 | 5 | let cachedDb; 6 | 7 | async function connectToDatabase() { 8 | if (cachedDb) { 9 | return cachedDb; 10 | } 11 | const client = new MongoClient(process.env.MONGODB_URI, { useNewUrlParser: true }); 12 | 13 | cachedDb = client; 14 | return await client.connect(); 15 | } 16 | 17 | export default async (req: NowRequest, res: NowResponse) => { 18 | const db = await connectToDatabase(); 19 | 20 | if (req.body.link) { 21 | const entry = await db.db('links_db').collection('links_collection').insertOne({ link: req.body.link }); 22 | 23 | res.statusCode = 201; // created 24 | return res.json({ short_link: `${process.env.VERCEL_URL}/r/${entry.insertedId}` }); 25 | } 26 | 27 | res.statusCode = 409; // conflict 28 | res.json({ error: 'no_link_found', error_description: 'No link found'}) 29 | } 30 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 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). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import axios, { AxiosError } from 'axios' 3 | import { useState } from 'react'; 4 | import { Alert, Button, Form, Input, Layout, Typography } from 'antd' 5 | import styles from '../styles/Home.module.css' 6 | 7 | const { Header, Content, Footer } = Layout; 8 | const { Title } = Typography; 9 | 10 | type ShortenLinkResponse = { 11 | short_link: string; 12 | } 13 | 14 | type ShortenLinkError = { 15 | error: string; 16 | error_description: string; 17 | } 18 | 19 | type FormValues = { 20 | link: string; 21 | } 22 | 23 | export default function Home() { 24 | const [status, setStatus] = useState<'initial' | 'error' | 'success'>('initial'); 25 | const [message, setMessage] = useState(''); 26 | const [form] = Form.useForm(); 27 | 28 | const onFinish = async ({ link }: FormValues) => { 29 | try { 30 | const response = await axios.post('/api/shorten_link', { link }); 31 | setStatus('success'); 32 | setMessage(response.data?.short_link); 33 | } 34 | catch(e) { 35 | const error = e as AxiosError; 36 | setStatus('error'); 37 | setMessage(error.response?.data?.error_description || 'Something went wrong!'); 38 | } 39 | } 40 | 41 | const onFinishedFailed = () => { 42 | setStatus('error'); 43 | const error = form.getFieldError('link').join(' '); 44 | setMessage(error); 45 | } 46 | 47 | return ( 48 | 49 | 50 | Create Next App 51 | 52 | 53 |
54 |
55 |
56 | 57 |
58 | Copy & Paste your lengthy link 59 |
64 |
65 |
66 | 71 | 72 | 73 |
74 |
75 | 76 | 79 | 80 |
81 |
82 |
83 | {['error', 'success'].includes(status) && ()} 84 |
85 |
86 | 89 |
90 | ) 91 | } 92 | --------------------------------------------------------------------------------