Compare commits

..

No commits in common. "master" and "development" have entirely different histories.

21 changed files with 4954 additions and 9749 deletions

@ -14,14 +14,14 @@ on:
jobs: jobs:
docker: docker:
runs-on: ubuntu-22.04 runs-on: ubuntu-20.04
steps: steps:
- name: Get branch name - name: Get branch name
id: branch-name id: branch-name
uses: tj-actions/branch-names@v8 uses: tj-actions/branch-names@v7
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Restore Docker Cache - name: Restore Docker Cache
uses: actions/cache@v3 uses: actions/cache@v3
@ -39,10 +39,10 @@ jobs:
echo "Done" echo "Done"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v2
- name: Login to Repo - name: Login to Repo
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
username: ${{ secrets.REPO_USERNAME }} username: ${{ secrets.REPO_USERNAME }}
password: ${{ secrets.REPO_TOKEN }} password: ${{ secrets.REPO_TOKEN }}
@ -55,7 +55,7 @@ jobs:
key: ${{ runner.os }}-buildx key: ${{ runner.os }}-buildx
- name: Build and Push (Latest) - name: Build and Push (Latest)
uses: docker/build-push-action@v5 uses: docker/build-push-action@v4
if: steps.branch-name.outputs.current_branch == 'master' if: steps.branch-name.outputs.current_branch == 'master'
with: with:
push: true push: true
@ -65,7 +65,7 @@ jobs:
cache-to: type=local,dest=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache
- name: Build and Push (Other Branches) - name: Build and Push (Other Branches)
uses: docker/build-push-action@v5 uses: docker/build-push-action@v4
if: steps.branch-name.outputs.current_branch != 'master' if: steps.branch-name.outputs.current_branch != 'master'
with: with:
push: true push: true

2
.gitignore vendored

@ -145,4 +145,4 @@ dist
.pnp.* .pnp.*
# Config # Config
config.yml config.json

@ -1,12 +1,15 @@
FROM fascinated/docker-images:node-pnpm-latest FROM node:lts-slim
ENV NODE_ENV=production ENV NODE_ENV=production
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN apt update
RUN DEBIAN_FRONTEND=noninteractive apt install wget -y
# Copy package.json and package-lock.json separately to fully utilize Docker layer caching # Copy package.json and package-lock.json separately to fully utilize Docker layer caching
COPY package.json ./ COPY package.json ./
COPY pnpm-lock.yaml ./ COPY package-lock.json ./
RUN pnpm install --production --silent RUN npm ci --production --silent && npm cache clean --force
# Opt out of Next.js telemetry # Opt out of Next.js telemetry
RUN npx next telemetry disable RUN npx next telemetry disable
@ -14,6 +17,9 @@ RUN npx next telemetry disable
# Copy the rest of the files # Copy the rest of the files
COPY . . COPY . .
# Remove development dependencies
RUN npm prune --production
# Environment Variables # Environment Variables
ENV NODE_ENV=production ENV NODE_ENV=production

104
README.md

@ -29,10 +29,6 @@ Simple Links is a lightweight alternative to Linktree and others.
3. Run `docker compose up -d` 3. Run `docker compose up -d`
4. The application is now updated 4. The application is now updated
## Support
If you need any help, you can contact me on my Discord server [here](https://discord.gg/yjj2U3ctEG) or open a issue!
## What if I don't want buttons or icon only links? ## What if I don't want buttons or icon only links?
Just simply remove it from the config and it will not show anymore Just simply remove it from the config and it will not show anymore
@ -40,3 +36,103 @@ Just simply remove it from the config and it will not show anymore
## Showing Discord status ## Showing Discord status
You must be in the [lanyard server](https://discord.gg/UrXF2cfJ7F) for it to work. You must be in the [lanyard server](https://discord.gg/UrXF2cfJ7F) for it to work.
## Help!!! I'm getting an error when launching it
### Config Issue
If your error looks similar to this you are most likely missing part of the config, as some new things may have been added.
```bash
simple-links | Type error: Property 'theme' does not exist on type '{ name: string; description: string; avatar: string; background: { showBackground: boolean; blur: boolean; darken: { enabled: boolean; amount: number; }; backgroundImage: string; }; infoCard: { transparency: number; }; metadata: { ...; }; options: { ...; }; analytics: { ...; }; links: { ...; }[]; }'.
```
## Config Example
```json
{
"name": "Your Name", // The name you want to display on the site
"description": "A description about yourself", // The description you want to use
"avatar": "https://cdn.fascinated.cc/KWprz2.jpg", // The avatar that is shown at the top of the site
// or use a local image
"avatar": "/avatar.webp",
// If you want to use a custom (not dark) background
"background": {
"showBackground": true, // Whether it is enabled or not
"blur": true, // Should we blur the background?
"darken": {
// Should we darken the background?
"enabled": true,
"amount": 0.7
},
"backgroundImage": "https://cdn.fascinated.cc/8twdW8.jpg", // The image to use in the background
// or use a local image
"backgroundImage": "/background.jpg"
},
"theme": "dark", // "dark" or "light" themes
// The card that displays your info and buttons
"infoCard": {
"transparency": 0.85 // How transparent should it be?
},
// Website options
"options": {
"showSourceLink": true // Should we show the "Source Code" link
},
// Show your discord status on the site (You MUST be in the Lanyard server)
// https://discord.gg/UrXF2cfJ7F
"discord": {
"id": "474221560031608833" // Your discord ID
},
// Search engine and embedding metadata (discord, twitter, etc embeds)
"metadata": {
"title": "Your Name", // The title of the embed
"description": "website description", // The description of the embed
"themeColor": "#6441a5", // The color of the embed
"authors": [
// SEO metadata
{
"name": "Fascinated",
"url": "https://fascinated.cc"
}
]
},
// The buttons to show links for
"links": [
{
"title": "Git", // The shown title of the button
"url": "https://git.fascinated.cc", // Where the button goes to when clicked
"icon": "fab fa-github", // The icon of the button (can be removed to show no icon)
// The color of the icon (tailwindcss colors)
"color": "bg-green-700"
},
{
"title": "Twitch",
"url": "https://twitch.tv/fascinated_",
"icon": "fab fa-twitch",
"color": "bg-[#6441a5]"
},
{
"title": "Documentation",
"url": "https://docs.fascinated.cc",
"color": "bg-neutral-700"
}
],
// Icon only links
"socialLinks": [
{
"icon": "fab fa-twitter", // The icon to use
"color": "#1DA1F2", // An optional color
"url": "https://twitter.com" // The link to go to
},
{
"icon": "fab fa-reddit",
"color": "#FF4300",
"url": "https://www.reddit.com/user/ImFascinatedMC"
},
{
"icon": "fab fa-git-alt",
"url": "https://git.fascinated.cc"
}
]
}
```

73
config-example.json Normal file

@ -0,0 +1,73 @@
{
"configVersion": "0.1.4",
"name": "Your Name",
"description": "A description about yourself",
"avatar": "/avatar.webp",
"background": {
"showBackground": true,
"blur": true,
"darken": {
"enabled": true,
"amount": 0.7
},
"backgroundImage": "/background.jpg"
},
"theme": "dark",
"infoCard": {
"transparency": 0.85
},
"discord": {
"id": "set me"
},
"options": {
"showSourceLink": true
},
"metadata": {
"title": "Your Name",
"description": "website description",
"themeColor": "#6441a5",
"authors": [
{
"name": "Fascinated",
"url": "https://fascinated.cc"
}
]
},
"links": [
{
"title": "Git",
"url": "https://git.fascinated.cc",
"icon": "fab fa-git-alt",
"color": {
"normal": "bg-green-700"
}
},
{
"title": "Twitch",
"url": "https://twitch.tv/fascinated_",
"icon": "fab fa-twitch",
"color": "bg-[#6441a5]"
},
{
"title": "Documentation",
"url": "https://docs.fascinated.cc",
"color": "bg-neutral-700"
}
],
"socialLinks": [
{
"icon": "fab fa-twitter",
"color": "#1DA1F2",
"url": "https://twitter.com"
},
{
"icon": "fab fa-reddit",
"color": "#FF4300",
"url": "https://www.reddit.com/user/ImFascinatedMC"
},
{
"icon": "fab fa-git-alt",
"url": "https://git.fascinated.cc"
}
]
}

@ -1,75 +0,0 @@
# The version of the config (DO NOT CHANGE)
configVersion: "0.2.0"
# Your name to show in the info card
name: "Your Name"
# The description shown in the info card
description: "A description about yourself"
# The avatar to show
avatar: "/avatar.webp"
# Background configuration
background:
# Should we use a custom background?
showBackground: true
# Should we blur that background?
blur: true
# Should we darken that background? and by how much
darken:
enabled: true
amount: 0.7
# The image to show when the background is enabled
backgroundImage: "/background.jpg"
# The theme to use (light or dark)
theme: "dark"
# The info card settings
infoCard:
# How transparent should it be?
transparency: 0.85
# Discord settings (Status indicator)
discord:
# Your discord id
id: "set me"
# Global settings for the site
options:
# Should we show the "Website Source" button?
showSourceLink: true
# Metadata for the website (controls embed settings and SEO info)
# see: https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image
metadata:
title: "Your Name"
description: "website description"
themeColor: "#6441a5"
authors:
- name: "Fascinated"
url: "https://fascinated.cc"
# The links to show (buttons)
links:
# The title of the button
- title: "Git"
# Where the button goes to
url: "https://git.fascinated.cc"
# The icon of the button
# see: https://fontawesome.com/search
icon: "fab fa-git-alt"
# The color of the button (TailwindCSS colors or hex color, see below for hex)
color: "bg-green-700"
- title: "Twitch"
url: "https://twitch.tv/fascinated_"
icon: "fab fa-twitch"
color: "bg-[#6441a5]"
- title: "Documentation"
url: "https://docs.fascinated.cc"
color: "bg-neutral-700"
# The links to show (icons only, below buttons)
socialLinks:
# The icon to use
# see: https://fontawesome.com/search
- icon: "fab fa-twitter"
# The color of the icon
color: "#1DA1F2"
# Where the icon goes to when clicked
url: "https://twitter.com"
- icon: "fab fa-reddit"
color: "#FF4300"
url: "https://www.reddit.com/user/ImFascinatedMC"
- icon: "fab fa-git-alt"
url: "https://git.fascinated.cc"

@ -13,5 +13,5 @@ services:
ports: ports:
- 3000:3000 - 3000:3000
volumes: volumes:
- ./config.yml:/usr/src/app/config.yml:ro # The application config (Must be created before starting the service) - ./config.json:/usr/src/app/config.json:ro # The application config (Must be created before starting the service)
- ./public:/usr/src/app/public:rw # Used for public facing images (eg: avatar and background) - ./public:/usr/src/app/public:rw # Used for public facing images (eg: avatar and background)

@ -1,5 +1,3 @@
const withYaml = require("next-plugin-yaml");
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
images: { images: {
@ -14,4 +12,4 @@ const nextConfig = {
}, },
}; };
module.exports = withYaml(nextConfig); module.exports = nextConfig;

4697
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

@ -1,35 +1,32 @@
{ {
"name": "simple-links", "name": "simple-links",
"version": "0.2.1", "version": "0.1.9",
"private": false, "private": false,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next build && next start", "start": "next build && next start",
"lint": "next lint", "lint": "next lint"
"update-depends": "npm-check -u"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0", "@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.2", "@fortawesome/react-fontawesome": "^0.2.0",
"@types/node": "^22.5.1", "@types/node": "20.4.1",
"@types/react": "^18.3.4", "@types/react": "18.2.14",
"@types/react-dom": "^18.3.0", "@types/react-dom": "18.2.6",
"autoprefixer": "^10.4.20", "autoprefixer": "10.4.14",
"eslint": "^9.9.1", "eslint": "8.44.0",
"eslint-config-next": "^14.2.7", "eslint-config-next": "13.4.9",
"next": "^14.2.7", "next": "13.4.9",
"next-plugin-yaml": "^1.0.1", "postcss": "8.4.25",
"npm-check": "^6.0.1", "react": "18.2.0",
"postcss": "^8.4.41", "react-dom": "18.2.0",
"react": "18.3.1", "sharp": "^0.32.2",
"react-dom": "18.3.1", "tailwindcss": "3.3.2",
"sharp": "^0.33.5", "typescript": "5.1.6",
"tailwindcss": "^3.4.10", "use-lanyard": "^1.4.4"
"typescript": "^5.5.4",
"use-lanyard": "^1.5.2"
} }
} }

4676
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"local>Fascinated/renovate-config"
]
}

@ -1,11 +1,10 @@
"use client"; "use client";
import { Config } from "@/src/types/config";
import Image from "next/image"; import Image from "next/image";
import { Fragment } from "react"; import { Fragment } from "react";
import { useLanyardWS } from "use-lanyard"; import { useLanyardWS } from "use-lanyard";
const config: Config = require("../../../config.yml") as any; import Config from "../../../config.json";
function LanyardWrapper(props: { children: any }) { function LanyardWrapper(props: { children: any }) {
return <Fragment>{props.children}</Fragment>; return <Fragment>{props.children}</Fragment>;
@ -13,7 +12,7 @@ function LanyardWrapper(props: { children: any }) {
export default function Avatar(props: any) { export default function Avatar(props: any) {
const { avatar } = props; const { avatar } = props;
const { discord }: any = config; const { discord }: any = Config;
return ( return (
<div className="relative inline-block"> <div className="relative inline-block">
@ -40,10 +39,10 @@ function LanyardComponent(props: { discord: any }) {
const hasLanyard = lanyardData !== undefined; const hasLanyard = lanyardData !== undefined;
const statusColor = { const statusColor = {
online: "bg-[#43B581]", online: "bg-green-400",
offline: "bg-transparent", offline: "bg-slate-400",
dnd: "bg-[#F04747]", dnd: "bg-red-500",
idle: "bg-[#FAA61A]", idle: "bg-orange-400",
}; };
const currentStatus = const currentStatus =
lanyardData !== undefined lanyardData !== undefined

@ -1,18 +1,20 @@
import { Config } from "@/src/types/config"; import { config } from "@fortawesome/fontawesome-svg-core";
import { config as fontAwesomeConfig } from "@fortawesome/fontawesome-svg-core";
import "@fortawesome/fontawesome-svg-core/styles.css"; import "@fortawesome/fontawesome-svg-core/styles.css";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { ReactNode } from "react";
const config: Config = require("../../config.yml") as any; import Config from "../../config.json";
fontAwesomeConfig.autoAddCss = false; config.autoAddCss = false;
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
export const metadata = config.metadata; // Site metadata from the config export const metadata = Config.metadata; // Site metadata from the config
export default function RootLayout({ children }: { children: ReactNode }) { export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return ( return (
<html lang="en"> <html lang="en">
<body className={inter.className}>{children}</body> <body className={inter.className}>{children}</body>

@ -1,15 +1,12 @@
import { Config } from "@/src/types/config";
import { library } from "@fortawesome/fontawesome-svg-core"; import { library } from "@fortawesome/fontawesome-svg-core";
import { fab } from "@fortawesome/free-brands-svg-icons"; import { fab } from "@fortawesome/free-brands-svg-icons";
import { far } from "@fortawesome/free-regular-svg-icons"; import { far } from "@fortawesome/free-regular-svg-icons";
import { fas } from "@fortawesome/free-solid-svg-icons"; import { fas } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Config from "../../config.json";
import Avatar from "./components/Avatar"; import Avatar from "./components/Avatar";
const config: Config = require("../../config.yml") as any;
library.add(fab, far, fas); // Loading in the icons from FontAwesome library.add(fab, far, fas); // Loading in the icons from FontAwesome
export default function Home() { export default function Home() {
@ -23,7 +20,7 @@ export default function Home() {
options, options,
description, description,
theme, theme,
} = config; // All the settings pulled from the config file } = Config; // All of the settings pulled from the config file
// Theme colors to use when using the selected theme // Theme colors to use when using the selected theme
// all used colors are from TailwindCSS // all used colors are from TailwindCSS
@ -54,26 +51,29 @@ export default function Home() {
const selectedTheme = themeColors[theme] || themeColors.dark; // The theme to use (fallback of dark) const selectedTheme = themeColors[theme] || themeColors.dark; // The theme to use (fallback of dark)
return ( return (
<div className={"relative"}> <>
{/* Background Image */}
{background.showBackground && background.backgroundImage && (
// eslint-disable-next-line @next/next/no-img-element
<img
alt="Background image"
src={background.backgroundImage}
className={`absolute z-0 object-cover w-full h-full ${background.blur && "blur-sm"} brightness-${background.darken.enabled && background.darken.amount / 2}`}
/>
)}
<main <main
className={`flex flex-col items-center justify-center w-screen h-screen ${selectedTheme.backgroundColor} ${selectedTheme.textColor}`} className={`flex flex-col items-center justify-center w-screen h-screen ${selectedTheme.backgroundColor} ${selectedTheme.textColor}`}
> >
{/* Info Card */} {/* Background Image */}
{background.showBackground && background.backgroundImage && (
<Image
alt="Background image"
src={background.backgroundImage}
fill={true}
style={{
zIndex: 0,
filter: `${background.blur && "blur(4px)"} brightness(${
background.darken.enabled && background.darken.amount / 2
})`,
}}
/>
)}
<div <div
className={`${infoCard.transparency != 0 && `${selectedTheme.infoCardColor} shadow-lg rounded-lg`} text-center`} className={`${selectedTheme.infoCardColor} rounded-lg text-center shadow-lg`}
style={{ style={{
zIndex: 1, zIndex: 1,
opacity: infoCard.transparency != 0 ? infoCard.transparency : 1, opacity: infoCard.transparency,
}} }}
> >
<div className="m-5"> <div className="m-5">
@ -106,13 +106,7 @@ export default function Home() {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={`flex flex-row items-center justify-center mt-4 px-4 w-60 py-2 rounded className={`flex flex-row items-center justify-center mt-4 px-4 w-60 py-2 rounded
${selectedTheme.buttonTextColor} ${color} hover:brightness-75 transition gap-2 shadow-lg`} ${selectedTheme.buttonTextColor} ${color} hover:brightness-75 transition gap-2`}
style={{
opacity:
infoCard.transparency != 0
? infoCard.transparency
: 0.8,
}}
> >
{link.icon && <FontAwesomeIcon icon={icons} />} {link.icon && <FontAwesomeIcon icon={icons} />}
<p>{link.title}</p> <p>{link.title}</p>
@ -133,13 +127,7 @@ export default function Home() {
href={link.url} href={link.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="hover:brightness-75 transition shadow-lg" className="hover:brightness-75 transition"
style={{
opacity:
infoCard.transparency != 0
? infoCard.transparency
: 0.8,
}}
> >
{link.icon && ( {link.icon && (
<FontAwesomeIcon <FontAwesomeIcon
@ -160,21 +148,16 @@ export default function Home() {
className={`absolute bottom-0 right-0 mb-5 mr-5 ${selectedTheme.footerTextColor}`} className={`absolute bottom-0 right-0 mb-5 mr-5 ${selectedTheme.footerTextColor}`}
> >
{options.showSourceLink && ( {options.showSourceLink && (
<Link <a
href="https://git.fascinated.cc/Fascinated/simple-links" href="https://git.fascinated.cc/Fascinated/simple-links"
target="_blank" target="_blank"
className="mt-5" className="mt-5"
> >
<Image Website Source
src="https://cdn.fascinated.cc/assets/simple-links/icon.png" </a>
alt="Simple Links Icon"
width={32}
height={32}
/>
</Link>
)} )}
</div> </div>
</main> </main>
</div> </>
); );
} }

55
src/types/config.d.ts vendored

@ -1,55 +0,0 @@
export interface Config {
configVersion: string;
name: string;
description: string;
avatar: string;
background: {
showBackground: boolean;
blur: boolean;
darken: {
enabled: boolean;
amount: number;
};
backgroundImage: string;
};
theme: string;
infoCard: {
transparency: number;
};
discord: {
id: string;
};
options: {
showSourceLink: boolean;
};
metadata: {
title: string;
description: string;
themeColor: string;
authors: {
name: string;
url: string;
}[];
};
links: {
title: string;
url: string;
icon: string;
color:
| {
normal: string;
}
| string;
}[];
socialLinks: {
icon: string;
color: string;
url: string;
}[];
}
// Parse the YAML content and convert it to the defined type
declare module "*.yml" {
const content: any;
export default content;
}

@ -1,7 +1,7 @@
workDir=/usr/src/app workDir=/usr/src/app
baseUrl=https://git.fascinated.cc/Fascinated/simple-links/raw/branch/master/public/ baseUrl=https://git.fascinated.cc/Fascinated/simple-links/raw/branch/master/public/
toDownload=("avatar.webp" "background.jpg" "favicon.ico") toDownload=("avatar.webp" "background.jpg")
echo "Checking if files need to be downloaded" echo "Checking if files need to be downloaded"
if [ -z "$(ls -A $workDir/public)" ]; then if [ -z "$(ls -A $workDir/public)" ]; then

@ -4,7 +4,7 @@ module.exports = {
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}", "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
"./config.yml", "./config.json",
], ],
theme: {}, theme: {},
plugins: [], plugins: [],

@ -21,16 +21,8 @@
], ],
"paths": { "paths": {
"@/*": ["./*"] "@/*": ["./*"]
}, }
"typeRoots": ["./src/types", "./node_modules/@types"],
"types": ["node"]
}, },
"include": [ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"next-env.d.ts",
"src/types/**/*.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }

4826
yarn.lock

File diff suppressed because it is too large Load Diff