How to make a Kali Linux themed Terminal Website Portfolio using React JS
Hello Beautiful People,
You must have known about Kali Linux and most probably have used it. If not no problem then, we are going to create our own Kali Linux (oops, Sorry... Our Kali Linux themed portfolio.
For reference you people can see these terminal themed portfolio websites:
3. ShellFolio
One day I was just chilling about it and somehow found a website in the theme of terminal. Then I thought why not make something like that? A terminal themed portfolio it isn't that bad...
Prerequisites
Previous knowloedge of React + JavaScript
No knowledge? No problem😥 Learn by building things just like I do...
Let's Begin
What else we will add?
We will make our own linux like login screen and will make a custom loader. Cool right? Let's start Coding.
Create a React App
Go to your desired folder, open terminal and run this command to create a react app in that directory.
npx create-react-app ./
After the app gets created,
Consider the folder structure we are going to use in this project...
Create the folders which are not available in your default app folder according to this structure.
After making the following file structure we are ready to start working with our Application.
Packages
Packages we are going to use in this project are as:
Run the below command to install all the packages, if you want to explore about these packages, click on the names to go to their respective pages.
npm i react-icons react-router-dom
Components
Now we are going to work with our components. Create these files in your components folder with the same names to use in your app.
There are total 7 components I've made.
This file contains the layout of our terminal.
Paste the below code into this file
import React from 'react'
import Contact from './Contact'
import Terminal from './Terminal'
export default function TerminalLayout(props) {
return (
<>
<div className='kali_desktop'>
<div className='kali_terminal'>
<Terminal loginname={props.loginname} />
</div>
<Contact />
</div>
</>
)
}
This file is our actual terminal
import React, { useState } from 'react'
import TerminalData from './TerminalData';
export default function Terminal(props) {
let logo = `
_______ __ __ ____ _______ __ ___
/ _____| | |____| | / \\ / _____| | |____| |
/ /_____ | ____ | / /_\\ \\ / /_____ | _____ |
\\_____ | | | | | / ___ \\ \\_____ | | | | |
_____/ / | | | |_ | | | |_ _____/ / | | | |
\\______/ |__ | |____| |__| |____| \\______/ |__ | |___|
`
const [history, setHistory] = useState([])
const [command, setCommand] = useState('')
const [dirname, setdirname] = useState('')
const dirs_list = ["home", "about"]
const handleAdd = () => {
const newList = [...history];
newList.push(command);
setHistory(newList);
}
const handleKeyDown = event => {
// what if ctrl + c is pressed break the command
var key = event.which || event.keyCode;
var ctrl = event.ctrlKey ? event.ctrlKey : ((key === 17)
? true : false);
if (key == 67 && ctrl) {
event.key = "Enter"
}
if (event.key === 'Enter') {
// Enter key pressed ✅
handleAdd()
setCommand('')
// console.log(history)
// hide the logo terminal
if (command === "hide") {
document.querySelector(".terminal_logo").style.display = "none";
}
// show the logo terminal
if (command === "show") {
document.querySelector(".terminal_logo").style.display = "block";
}
// clear the terminal
if (command === "clear") {
setHistory([])
}
const contact = document.querySelector(".contact_container")
if (command.substring(0, 5) === "mkmsg") {
contact.style.display = "block"
}
if (command.substring(0, 2) === "cd") {
if (command.includes("..")) {
setdirname('')
} else {
if (dirs_list.includes(command.slice(3))) {
setdirname(command.slice(3))
}
}
}
}
};
return (
<>
<div className='terminal_logo'>
<pre>{logo}</pre>
<div className='instructions'>
<ol type='number'>
<li>Type 'hide/show' to hide or show these instructions.</li>
<li>Type 'clear' to clear the terminal.</li>
<li>Type '-help' to get help instructions of the terminal.</li>
<li>Type 'whoishe' to know about me.</li>
</ol>
</div>
<br />
</div>
<div className='terminal_container'>
<TerminalData loginname={props.loginname} array={history} dirname={dirname} />
<div className='terminal_area'>
<div className='input_area'>
<div className='terminal_name'>
<font style={{ color: "lightgreen" }}>(</font>
{props.loginname}@shash
<font style={{ color: "lightgreen" }}>)
-[
<font style={{ color: "white" }}>~{dirname && <b>/{dirname}</b>}</font>]
</font>
</div>
<div className='input_box'>
$ <input
id='terminal_input'
value={command}
onKeyDown={handleKeyDown}
onBlur={({ target }) => target.focus()}
onChange={(e) => setCommand(e.target.value)}
type="text" autoFocus={true} />
</div>
</div>
</div>
</div>
</>
)
}
This file gives the result of the terminal (what user inputs)
import React from 'react'
import parse from "html-react-parser";
export default function TerminalResult(props) {
const projects = "https://mrdevknown.blogspot.com"
const dirs_list = ["home", "about"]
const keywords = [
{
"name": "help",
"abbr": ["-help", "-h", "-H", "-Help", "-HELP", "--help"],
"text": `
<h4>Shash Help</h4>
<br/>
<p/>Shash is a linux terminal themed website.
<p/>This website is made just for fun.
<br/><br/>
Commands:
<br/><br/>
<div className="command_desc">
<div className="command_name">-h, -help, --help</div>
<div className="command_details">Get Help</div>
</div>
<div className="command_desc">
<div className="command_name">cd</div>
<div className="command_details">To get inside a directory</div>
</div>
<div className="command_desc">
<div className="command_name">mkmsg</div>
<div className="command_details">Make me a new message</div>
</div>
<div className="command_desc">
<div className="command_name">dir</div>
<div className="command_details">Show the directories</div>
</div>
`
},
{
"name": "cd",
"abbr": ["cd"]
},
{
"name": "whoishe",
"abbr": ["whoishe"],
"text": `Hello,
<p/>I am <font className="primarybg">Shivesh</font>
<br/><br/>
<ul style="margin-left: 20px">
<li type="square">I started to code when I was 11.</li>
</ul>
<br/>
<ul style="margin-left: 20px">
<li type="square">I love to make <a href=${projects} target="_blank">stupid things</a> like this.</li>
</ul>`
},
{
"name": "dir",
"abbr": ["dir"],
"text": `Directories:
<br/><br/>
<div className="dirs_name">
<div>home</div>
<div>about</div>
</div>
`
},
{
"name": "mkmsg",
"abbr": ["mkmsg"],
"text": `send a message`
},
{
"name": "hide",
"abbr": ["hide"],
"text": `hidden`
},
{
"name": "show",
"abbr": ["show"],
"text": `showing`
},
{
"name": "clear",
"abbr": ["clear"],
"text": `clear`
}
]
let dirname = ""
let text = "Command Not Found";
for (let i = 0; i < keywords.length; i++) {
const keywordResult = (item) => {
for (let j = 0; j < keywords[i].abbr.length; j++) {
if (item === `${keywords[i].abbr[j]}`) {
if (keywords[i].name === "help") {
text = `${keywords[i].text}<br/><br/>`
}
if (keywords[i].name === "dir") {
text = `${keywords[i].text}<br/><br/>`
}
if (keywords[i].name === "mkmsg") {
text = `${keywords[i].text}<br/><br/>`
}
if (keywords[i].name === "hide") {
text = `${keywords[i].text}<br/><br/>`
}
if (keywords[i].name === "show") {
text = `${keywords[i].text}<br/><br/>`
}
if (keywords[i].name === "whoishe") {
text = `${keywords[i].text}<br/><br/>`
}
}
} if (props.command.substring(0, 2) === "cd") {
dirname = props.command.slice(3)
if (dirs_list.includes(dirname) || dirname === "..") {
text = ""
} else {
text = "No Such Directory"
}
}
}
keywordResult(props.command)
}
return (
<>
<div className='terminal_result'>
{parse(text)}
</div>
</>
)
}
This file stores the terminal data
import React from 'react'
import TerminalResult from './TerminalResult';
export default function TerminalData(props) {
let dirname = ''
return (
<>{props.array.map((com) => {
if (com.substring(0,2) === "cd") {
dirname = com.slice(3)
if (dirname === "..") {
dirname = ""
}
}
return (
<div className='terminal_area'>
<div className='input_area'>
<div className='terminal_name'>
<font style={{ color: "lightgreen" }}>(</font>
{props.loginname}@shash
<font style={{ color: "lightgreen" }}>)
-[
<font style={{ color: "white" }}>~{dirname && <b>/{dirname}</b>}</font>]
</font>
</div>
<div className='input_box'>
$ {com}
</div>
</div>
<TerminalResult command={com} />
</div>
);
})}
</>
)
}
Now our Terminal is Ready.
But I would like to add a few more things...
Here comes our 5th component
This file will display a popup window when user types mkmsg command, it will open and display our contact links.
Paste the below code into this file
import React from 'react'
import { IoIosClose, IoIosMail, IoIosSquareOutline, IoLogoGithub, IoLogoLinkedin } from 'react-icons/io';
import { BiMessageAltDetail, BiMinus } from 'react-icons/bi';
export default function Contact() {
const logo = `
.--------\\ /--------.
| - |
| |
| |
_____|____________|_____
=========.==========
/ ~~~~~ ~~~~~ \\
/| | |\\
W ------ / \\ -------- W
\\. |o o| ./
| |
\\ ######### /
\\ ## --------- ## /
\\## ##/
\\_____v____/
`
var isMaximised = false;
const maximize = () => {
if (!isMaximised) {
document.querySelector(".contact_container").classList.add("fullscreen")
isMaximised = true
} else {
document.querySelector(".contact_container").classList.remove("fullscreen")
isMaximised = false
}
}
const hide = () => {
document.querySelector(".contact_container").classList.remove("fullscreen")
document.querySelector(".contact_container").style.display = "none"
}
return (
<div className='contact_container'>
<div className='title_bar'>
<div className='title'>
<BiMessageAltDetail />
Contact Me
</div>
<div className='buttons'>
<div className='button' onClick={hide}><BiMinus style={{ fontSize: "10px" }} /></div>
<div className='button' onClick={maximize}><IoIosSquareOutline style={{ fontSize: "10px" }} /></div>
<div className='button' onClick={hide}><IoIosClose /></div>
</div>
</div>
<div className='contact_window'>
<pre>{logo}</pre>
<div className="container">
<h1>Contact Me</h1>
<div className='contact_links'>
<a href='https://mail.google.com/mail/?view=cm&to=YOUR_EMAIL' target='_blank' className='link'>
<IoIosMail />
"Your Email"
</a>
<a href='gmail.com' className='link'>
<IoLogoGithub />
TheShiveshNetwork
</a>
<a href='gmail.com' className='link'>
<IoLogoLinkedin />
WhoIsShivesh
</a>
</div>
</div>
</div>
</div>
)
}
One more thing I would add is a login page, where user will enter their name or they can just enter as guest and we will display their entered name on the terminal. This is a little extra thing but I feel this adds to our portfolio and makes it a little more interactive.
import React from 'react'
import logo from '../kali-logo.png'
import { Link } from "react-router-dom";
export default function Login(props) {
return (
<>
<div className='login_container'>
<div className='login_box'>
<div className='login_area'>
{/* <h1>Login</h1> */}
<div className='logo_image'>
<img src={logo} alt="logo" />
</div>
<div className='input_login'>
Enter your name here
<input value={props.name} onChange={(e) => props.setName(e.target.value)} type="text" autoFocus={true} />
</div>
</div>
<div className='login_btns'>
<Link to="/" className='secondaryBtn' onClick={props.guestName}>Guest</Link>
<Link to="/" className='primaryBtn disabled-link' onClick={props.submitName}>Log In</Link>
</div>
</div>
</div>
</>
)
}
Now we are done with all our components.
Now let's use all these components into our app.
App.js
import './App.css';
import Login from './components/Login';
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import TerminalLayout from './components/TerminalLayout';
import { useState } from 'react';
import Loader from './components/Loader';
function App() {
const [name, setName] = useState("")
const [loggedIn, setLoggedIn] = useState(false)
// let loggedIn = false
if (name !== "") { document.querySelector(".primaryBtn").classList.remove("disabled-link") }
const submitName = () => {
setName(name)
// loggedIn = true;
if (name !== "") { setLoggedIn(true); }
// console.log(loggedIn)
}
const guestName = () => {
setName("guest")
setLoggedIn(true);
}
return (
<>
<BrowserRouter>
<Routes>
<Route path="/" element={loggedIn ? <TerminalLayout loginname={name} /> : <Navigate to="/login" />} />
<Route path='*' element={<Navigate to="/" />} />
<Route path="/login" element={<Login name={name} setName={setName} guestName={guestName} submitName={submitName} />} />
</Routes>
</BrowserRouter>
<Loader />
</>
);
}
export default App;
With this app.js file, we are done with our project. Now start the app by running "npm start" command in the terminal.
But the design will definitely look ugly since we haven't added any styles. So let's do so.
I am going to modify the index.css file.
Paste the below code to get the output like me, it's just simple styling so anyone can easily go through it and understand it.
Index.css
.kali_desktop {
min-height: 100vh;
width: 100vw;
background: url('../public/assets/wallpaper3.jpg');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-attachment: fixed;
}
.kali_terminal {
background: black;
min-height: 100vh;
width: 100%;
opacity: 70%;
font-family: 'Ubuntu', sans-serif;
color: white;
}
.terminal_container {
padding: 25px;
display: flex;
flex-direction: column;
gap: 12px;
}
a {
color: #fff;
}
.input_area {
display: flex;
/* gap: 8px; */
flex-direction: column;
letter-spacing: 2px;
}
.input_area::after {
z-index: -1;
content: '';
position: absolute;
/* background: red; */
border-left: 2px solid lightgreen;
border-top: 2px solid lightgreen;
border-bottom: 2px solid lightgreen;
padding: 0;
height: 16px;
width: 50px;
transform: translate(-15px, 10px);
}
.kali_terminal input {
width: 80%;
height: auto;
background: none;
border: none;
color: white;
font-size: 16px;
letter-spacing: 2px;
font-family: 'Ubuntu', sans-serif;
}
input:focus {
outline: none;
}
.input_box {
background: black;
width: 100%;
padding-left: 8px;
}
.terminal_name {
background: black;
margin-left: 15px;
padding-left: 5px;
color: royalblue;
}
.dirs_name {
display: grid;
width: 350px;
grid-template-columns: auto auto auto;
gap: 25px;
}
pre {
font-family: 'Ubuntu', sans-serif;
}
.command_desc {
display: flex;
gap: 105px;
padding-bottom: 10px;
}
.command_name {
width: 70px;
font-style: italic;
color: orange;
}
.terminal_result {
margin: 10px 0 10px -10px;
}
.terminal_logo {
padding: 10px;
}
.instructions {
width: 100%;
margin: 10px 20px 0 20px;
font-size: 16px;
}
.login_container {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: url('../public/assets/wallpaper1.jpg');
background-position: center;
background-size: cover;
}
.login_box {
backdrop-filter: blur(5px);
color: white;
height: 230px;
width: 440px;
border-radius: 5px;
/* padding: 20px; */
display: flex;
flex-direction: column;
align-items: center;
}
.login_area {
font-size: 18px;
width: 100%;
height: 70%;
gap: 30px;
display: flex;
align-items: center;
justify-content: center;
background: #0a0a0bed;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.input_login {
display: flex;
flex-direction: column;
gap: 10px;
}
.input_login input {
width: 250px;
font-size: 16px;
}
.login_btns {
width: calc(100% - 80px);
height: 30%;
padding: 0 40px 0 40px;
display: flex;
align-items: center;
justify-content: space-between;
background: #000000;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
.login_area .logo_image {
height: 100px;
width: 100px;
background: #071a54;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.logo_image img {
height: 80%;
}
.primaryBtn,
.secondaryBtn {
border: none;
padding: 5px 15px 5px 15px;
border-radius: 2px;
cursor: pointer;
font-size: 14px;
text-decoration: none;
}
.primarybg {
background: #071a54;
padding: 2px 5px 2px 5px;
border-radius: 2px;
}
.terminal_result li:before {
content: '\26A1';
margin-left: -20px;
margin-right: 10px;
}
.primaryBtn:hover,
.secondaryBtn:hover {
text-decoration: underline;
}
.disabled-link {
pointer-events: none;
background: #829ff7;
}
.primaryBtn {
background: #28408b;
color: white;
}
.secondaryBtn {
background: #acacac;
color: black;
}
.contact_container {
display: none;
position: fixed;
top: 50%;
right: 20%;
transform: translate(0, -50%);
width: 600px;
background: rgba(10, 10, 10, 0.9);
backdrop-filter: blur(1px);
border-radius: 10px;
box-shadow: 0 0 20px rgb(8, 8, 8);
/* border: 5px solid lightseagreen; */
color: #fff;
font-family: 'Ubuntu', sans-serif;
transition: .1s all;
}
.contact_container .container {
margin: 20px;
}
.title_bar {
width: 100%;
min-height: 30px;
background: rgb(16, 16, 16);
border-radius: 10px 10px 0 0;
display: flex;
align-items: center;
justify-content: space-between;
}
.title_bar .title {
display: flex;
align-items: center;
margin-left: 10px;
font-size: 14px;
display: flex;
gap: 10px;
}
.title_bar .buttons {
margin-right: 10px;
display: flex;
gap: 10px;
}
.title_bar .button {
height: 15px;
width: 15px;
border-radius: 50%;
background: royalblue;
cursor: pointer;
color: #000;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
}
.title_bar .button:hover {
background: rgb(32, 51, 109);
color: white;
}
.fullscreen {
border-radius: 0%;
width: 100vw !important;
height: 100vh;
position: fixed;
left: 50%;
transform: translate(-50%, -50%);
}
.fullscreen .contact_window {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.contact_container .contact_links {
margin-top: 25px;
display: flex;
flex-direction: column;
gap: 20px;
}
.contact_container .contact_links .link {
background: black;
padding: 10px 30px 10px 30px;
border-radius: 15px;
height: 50px;
display: flex;
align-items: center;
gap: 30px;
text-decoration: none;
transition: .2s all;
}
.contact_container .contact_links .link:hover {
color: rgb(223, 223, 223);
background: rgb(19, 19, 19);
text-decoration: underline;
}
.contact_window {
display: flex;
justify-content: space-evenly;
}
@media (max-width: 650px) {
.terminal_logo {
font-size: 10px !important;
}
.login_box {
width: 70%;
height: 400px;
}
.login_area {
flex-direction: column;
padding: 40px;
width: calc(100% - 80px);
}
.login_area input {
width: 100%;
}
}
@media (max-width: 880px) {
.contact_container {
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
}
.contact_window {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
.loader {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background: #000;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 74px;
}
.loader .icon {
animation: loader-anime 1s infinite;
}
@keyframes loader-anime {
0% {
shadow: 0 0 20px #fff;
}
100% {
shadow: 0 0 10px #fff;
}
}
Adding these styles, our Kali Linux Terminal themed portfolio is ready.
Final Look
We have created an awesome login screen, a working terminal and a great loader. What else?
Go ahead and feel free to experiment with the code.
See ya in the next post, till then Bye Bye!
0 Comments