Redid the front end so it now uses gitea as data source
continuous-integration/drone/push Build is running Details

main
Guus van Meerveld 2 years ago
parent 18c1970550
commit 6fa3241768
Signed by: Guusvanmeerveld
GPG Key ID: 2BA7D7912771966E

@ -1,5 +1,6 @@
** **
!.env
!next.config.js !next.config.js
!tsconfig.json !tsconfig.json
!package.json !package.json

@ -12,7 +12,6 @@ steps:
- name: build docker file and push to docker hub - name: build docker file and push to docker hub
image: plugins/docker image: plugins/docker
pull: never
settings: settings:
repo: guusvanmeerveld/portfolio repo: guusvanmeerveld/portfolio
tags: latest tags: latest

@ -1 +1,2 @@
NEXT_PUBLIC_GITHUB_USERNAME=Guusvanmeerveld NEXT_PUBLIC_GITEA_USERNAME=Guusvanmeerveld
NEXT_PUBLIC_GITEA_SERVER=git.guusvanmeerveld.dev

@ -1,67 +1,3 @@
{ {
"root": true, "extends": "next/core-web-vitals"
"env": {
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 8
},
"plugins": [
"prettier",
"css-modules"
],
"ignorePatterns": [
"node_modules/*",
".next/*",
".out/*"
],
"extends": [
"eslint:recommended",
"plugin:css-modules/recommended"
],
"overrides": [
{
"files": [
"**/*.ts",
"**/*.tsx"
],
"parser": "@typescript-eslint/parser",
"settings": {
"react": {
"version": "detect"
}
},
"env": {
"browser": true,
"node": true,
"es6": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended"
],
"rules": {
"prettier/prettier": "error",
"react/prop-types": "off",
"react/react-in-jsx-scope": "off",
"jsx-a11y/anchor-is-valid": "off",
"jsx-a11y/no-autofocus": "off",
"@typescript-eslint/no-unused-vars": [
"error"
],
"@typescript-eslint/explicit-function-return-type": [
"warn",
{
"allowExpressions": true,
"allowConciseArrowFunctionExpressionsStartingWithVoid": true,
"allowTypedFunctionExpressions": true
}
]
}
}
]
} }

@ -1,9 +0,0 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-idiomatic-order"
],
"rules": {
"indentation": "tab"
}
}

@ -1,7 +1,10 @@
// @ts-check // @ts-check
/** /**
* @type {import('next/dist/next-server/server/config').NextConfig} * @type {import('next').NextConfig}
**/ **/
module.exports = { module.exports = {
reactStrictMode: true, reactStrictMode: true,
} images: {
domains: [process.env.NEXT_PUBLIC_GITEA_SERVER]
}
};

@ -6,7 +6,7 @@
"start": "next start", "start": "next start",
"export": "next build && next export", "export": "next build && next export",
"test-build": "tsc", "test-build": "tsc",
"lint": "eslint src", "lint": "next lint",
"stylelint": "npx stylelint **/*.scss", "stylelint": "npx stylelint **/*.scss",
"full-test": "yarn test-build && yarn lint && yarn stylelint" "full-test": "yarn test-build && yarn lint && yarn stylelint"
}, },
@ -18,25 +18,17 @@
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"spectre.css": "^0.5.9" "spectre.css": "^0.5.9",
"zod": "^3.20.6"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^15.12.1", "@types/node": "^15.12.1",
"@types/react": "^17.0.9", "@types/react": "^17.0.9",
"@types/react-dom": "^17.0.6", "@types/react-dom": "^17.0.6",
"@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
"eslint": "^7.28.0", "eslint": "^7.28.0",
"eslint-plugin-css-modules": "^2.11.0", "eslint-config-next": "13.1.6",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
"prettier": "^2.3.1", "prettier": "^2.3.1",
"sass": "^1.34.1", "sass": "^1.34.1",
"stylelint": "^13.13.1",
"stylelint-config-idiomatic-order": "^8.1.0",
"stylelint-config-standard": "^22.0.0",
"typescript": "^4.3.2" "typescript": "^4.3.2"
} }
} }

@ -1,40 +1,47 @@
import Link from "next/link"; import Link from "next/link";
import { FC } from "react"; import { FC } from "react";
import z from "zod";
import { BestRepository } from "@interfaces/repository"; import { RepositoryResponse } from "@models/responses";
const BestRepository: FC<{ repository: BestRepository }> = ({ repository }) => { const BestRepository: FC<{ repository: z.infer<typeof RepositoryResponse> }> =
return ( ({ repository }) => {
<div className="hero bg-primary"> return (
<div className="container"> <div className="hero bg-primary">
<div className="columns"> <div className="container">
<div className="column col-8 col-md-12 col-mx-auto"> <div className="columns">
<h3 className="text-secondary">My most popular project:</h3> <div className="column col-8 col-md-12 col-mx-auto">
<h1>{repository.name}</h1> <h3 className="text-secondary">My most popular project:</h3>
<h3 className="text-secondary"> <h1>{repository.full_name}</h1>
{repository.stargazers_count} Star(s) <h3 className="text-secondary">{repository.size} byes</h3>
</h3> <h5>{repository.description}</h5>
<h5>{repository.description}</h5> <p className="text-secondary">
<p className="text-secondary"> Written in {repository.language}, has{" "}
Written in {repository.language}, has{" "} {repository.open_issues_count} issue(s) and{" "}
{repository.open_issues_count} issue(s) and{" "} {repository.forks_count} fork(s).
{repository.forks_count} fork(s). </p>
</p>
<Link href={repository.url}> <p>
<a className="btn mr-2">Github</a> <Link href={repository.html_url}>
</Link> <a className="btn mr-2">Github</a>
{repository.homepage && ( </Link>
<Link href={repository.homepage}> {repository.website && (
<a className="btn">Website</a> <Link href={repository.website}>
</Link> <a className="btn">Website</a>
)} </Link>
)}
</p>
<h5 className="text-secondary">
Last commit on{" "}
{new Date(repository.updated_at).toLocaleDateString()}
</h5>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> );
); };
};
export default BestRepository; export default BestRepository;

@ -1,21 +1,22 @@
import z from "zod";
import { FC } from "react"; import { FC } from "react";
import Link from "next/link"; import Link from "next/link";
import { RecentRepository } from "@interfaces/repository";
import multipleClassNames from "@utils/multipleClassNames"; import multipleClassNames from "@utils/multipleClassNames";
import { RepositoryResponse } from "@models/responses";
import styles from "./repositories.module.scss"; import styles from "./repositories.module.scss";
const RecentRepositories: FC<{ repositories: RecentRepository[] }> = ({ const FeaturedRepositories: FC<{
repositories repositories: z.infer<typeof RepositoryResponse>[];
}) => { }> = ({ repositories }) => {
return ( return (
<div className={multipleClassNames("container", styles.main)}> <div className={multipleClassNames("container", styles.main)}>
<div className="columns"> <div className="columns">
<div className="column col-6 col-mx-auto text-center"> <div className="column col-6 col-mx-auto text-center">
<h3>Some of my recent projects:</h3> <h3>Some of my featured projects:</h3>
</div> </div>
</div> </div>
<div className="columns"> <div className="columns">
@ -24,7 +25,7 @@ const RecentRepositories: FC<{ repositories: RecentRepository[] }> = ({
{repositories.map((repository) => { {repositories.map((repository) => {
return ( return (
<div <div
key={repository.name} key={repository.full_name}
className="column col-3 col-md-12 col-mx-auto mb-2" className="column col-3 col-md-12 col-mx-auto mb-2"
> >
<div <div
@ -35,19 +36,21 @@ const RecentRepositories: FC<{ repositories: RecentRepository[] }> = ({
)} )}
> >
<div className="card-header text-primary"> <div className="card-header text-primary">
<div className="card-title h5">{repository.name}</div> <div className="card-title h5">
{repository.full_name}
</div>
<div className="card-subtitle text-gray"> <div className="card-subtitle text-gray">
{repository.stargazers_count} Star(s) {repository.size} bytes
</div> </div>
</div> </div>
<div className="card-body">{repository.description}</div> <div className="card-body">{repository.description}</div>
<div className="card-footer"> <div className="card-footer">
<Link href={repository.url}> <Link href={repository.html_url}>
<a className="btn btn-primary">Github</a> <a className="btn btn-primary">Github</a>
</Link> </Link>
{repository.homepage && ( {repository.website && (
<Link href={repository.homepage}> <Link href={repository.website}>
<a className="btn btn-primary ml-2">Website</a> <a className="btn btn-primary ml-2">Website</a>
</Link> </Link>
)} )}
@ -63,4 +66,4 @@ const RecentRepositories: FC<{ repositories: RecentRepository[] }> = ({
); );
}; };
export default RecentRepositories; export default FeaturedRepositories;

@ -1,46 +0,0 @@
import { FC } from "react";
import styles from "./intro.module.scss";
const Intro: FC<{ isAvailable: boolean }> = ({ isAvailable }) => {
return (
<div className={styles.main}>
<div className="container">
<div className="columns">
<div className="column col-8 col-md-12 col-mx-auto text-center">
<h1>Guus van Meerveld</h1>
<h3>
Open source <u>web developer</u>
</h3>
<p>
<a
target="_blank"
rel="noreferrer"
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}`}
className="btn btn-primary mr-2"
>
Github
</a>
<a
href="mailto:contact@guusvanmeerveld.dev"
className="btn btn-primary"
>
Contact
</a>
</p>
<p className="text-gray">
Availibility: {isAvailable && "Available"}
{!isAvailable && "Not available"}
</p>
</div>
</div>
</div>
</div>
);
};
export default Intro;

@ -0,0 +1,67 @@
import { FC } from "react";
import z from "zod";
import Image from "next/image";
import { UserResponse } from "@models/responses";
import { giteaServerUrl } from "@utils/config";
import multipleClassNames from "@utils/multipleClassNames";
import styles from "./user.module.scss";
const User: FC<{ isAvailable: boolean; user: z.infer<typeof UserResponse> }> =
({ isAvailable, user }) => {
return (
<div className={styles.main}>
<div className="container">
<div className="columns">
<div
className={multipleClassNames(
"column",
"col-4",
"col-mx-auto",
styles.avatarCol
)}
>
<div className={styles.avatarContainer}>
<Image
src={user.avatar_url}
className={styles.avatar}
width={256}
height={256}
alt={`${user.full_name}'s avatar`}
/>
</div>
</div>
<div className="column col-8 col-md-12 col-mx-auto">
<h1>{user.full_name}</h1>
<h3>{user.description}</h3>
<p>
<a
target="_blank"
rel="noreferrer"
href={`https://${giteaServerUrl}/${user.login}`}
className="btn btn-primary mr-2"
>
Git
</a>
<a href={`mailto:${user.email}`} className="btn btn-primary">
Contact
</a>
</p>
<p className="text-gray">
Availibility: {isAvailable && "Available"}
{!isAvailable && "Not available"}
</p>
</div>
</div>
</div>
</div>
);
};
export default User;

@ -1,3 +0,0 @@
.main {
padding-top: 10rem;
}

@ -0,0 +1,18 @@
.main {
padding-top: 10rem;
margin-bottom: 2rem;
}
.avatar {
border-radius: 5px;
}
.avatarContainer {
margin-right: 2rem;
}
.avatarCol {
display: flex;
justify-content: right;
}

@ -1,124 +0,0 @@
export interface GithubAPIRepository {
id: number;
node_id: string;
name: string;
full_name: string;
private: boolean;
owner: Owner;
html_url: string;
description: null | string;
fork: boolean;
url: string;
forks_url: string;
keys_url: string;
collaborators_url: string;
teams_url: string;
hooks_url: string;
issue_events_url: string;
events_url: string;
assignees_url: string;
branches_url: string;
tags_url: string;
blobs_url: string;
git_tags_url: string;
git_refs_url: string;
trees_url: string;
statuses_url: string;
languages_url: string;
stargazers_url: string;
contributors_url: string;
subscribers_url: string;
subscription_url: string;
commits_url: string;
git_commits_url: string;
comments_url: string;
issue_comment_url: string;
contents_url: string;
compare_url: string;
merges_url: string;
archive_url: string;
downloads_url: string;
issues_url: string;
pulls_url: string;
milestones_url: string;
notifications_url: string;
labels_url: string;
releases_url: string;
deployments_url: string;
created_at: Date;
updated_at: Date;
pushed_at: Date;
git_url: string;
ssh_url: string;
clone_url: string;
svn_url: string;
homepage: null | string;
size: number;
stargazers_count: number;
watchers_count: number;
language: null | string;
has_issues: boolean;
has_projects: boolean;
has_downloads: boolean;
has_wiki: boolean;
has_pages: boolean;
forks_count: number;
mirror_url: null;
archived: boolean;
disabled: boolean;
open_issues_count: number;
license: License | null;
allow_forking: boolean;
is_template: boolean;
web_commit_signoff_required: boolean;
topics: string[];
visibility: string;
forks: number;
open_issues: number;
watchers: number;
default_branch: string;
}
export interface License {
key: string;
name: string;
spdx_id: string;
url: string;
node_id: string;
}
export interface Owner {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
}
export interface RecentRepository {
name: string;
description: string;
url: string;
homepage?: string;
stargazers_count: number;
}
export interface BestRepository extends RecentRepository {
forks_count: number;
language: string;
open_issues_count: number;
pushed_at: Date;
}

@ -0,0 +1,28 @@
import z from "zod";
export const RepositoryResponse = z.object({
full_name: z.string(),
html_url: z.string(),
language: z.string(),
website: z.string(),
created_at: z.string(),
updated_at: z.string(),
forks_count: z.number(),
open_issues_count: z.number(),
description: z.string(),
size: z.number()
});
export const SearchResultsResponse = z.object({
ok: z.boolean(),
data: RepositoryResponse.array()
});
export const UserResponse = z.object({
id: z.number(),
login: z.string(),
email: z.string(),
avatar_url: z.string(),
full_name: z.string(),
description: z.string()
});

@ -2,58 +2,26 @@ import { NextSeo } from "next-seo";
import { GetStaticProps, InferGetStaticPropsType, NextPage } from "next"; import { GetStaticProps, InferGetStaticPropsType, NextPage } from "next";
import axios from "axios"; import User from "@components/User";
import Intro from "@components/Intro";
import Layout from "@components/Layout"; import Layout from "@components/Layout";
import RecentRepositories from "@components/RecentRepositories"; import FeaturedRepositories from "@components/FeaturedRepositories";
import BestRepository from "@components/BestRepository"; import BestRepository from "@components/BestRepository";
import { GithubAPIRepository } from "@interfaces/repository"; import { fetchAvailability, fetchRepositories, fetchUser } from "@utils/fetch";
import { giteaUsername } from "@utils/config";
import createConfigCatClient from "@utils/createConfigCatClient";
export const getStaticProps: GetStaticProps = async () => { export const getStaticProps: GetStaticProps = async () => {
const { data } = await axios.get<GithubAPIRepository[]>( const isAvailable = await fetchAvailability();
`https://api.github.com/users/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}/repos`
);
const configCatClient = createConfigCatClient();
const isAvailable: boolean = const user = await fetchUser(giteaUsername);
(await configCatClient?.getValueAsync("amiavailable", true)) ?? true;
const bestRepository = data.sort( const repositories = await fetchRepositories(user.id);
(a, b) => b.stargazers_count - a.stargazers_count
)[0];
return { return {
props: { props: {
isAvailable, isAvailable,
repositories: data user,
.sort( repositories
(a, b) =>
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
)
.map(({ name, description, html_url, stargazers_count, homepage }) => ({
name,
url: html_url,
stargazers_count,
homepage,
description
}))
.slice(0, 3),
bestRepository: {
name: bestRepository.name,
description: bestRepository.description,
url: bestRepository.html_url,
homepage: bestRepository.homepage,
stargazers_count: bestRepository.stargazers_count,
forks_count: bestRepository.forks_count,
language: bestRepository.language,
open_issues_count: bestRepository.open_issues_count,
pushed_at: bestRepository.pushed_at
}
}, },
revalidate: 60 * 5 revalidate: 60 * 5
}; };
@ -61,14 +29,15 @@ export const getStaticProps: GetStaticProps = async () => {
const Index: NextPage = ({ const Index: NextPage = ({
repositories, repositories,
isAvailable, user,
bestRepository isAvailable
}: InferGetStaticPropsType<typeof getStaticProps>) => ( }: // bestRepository
InferGetStaticPropsType<typeof getStaticProps>) => (
<Layout> <Layout>
<NextSeo title="Home" /> <NextSeo title="Home" />
<Intro isAvailable={isAvailable} /> <User isAvailable={isAvailable} user={user} />
<RecentRepositories repositories={repositories} /> <FeaturedRepositories repositories={repositories} />
<BestRepository repository={bestRepository} /> {/* <BestRepository repository={bestRepository} /> */}
</Layout> </Layout>
); );

@ -0,0 +1,5 @@
export const giteaServerUrl =
process.env.NEXT_PUBLIC_GITEA_SERVER ?? "https://git.guusvanmeerveld.dev";
export const giteaUsername =
process.env.NEXT_PUBLIC_GITEA_USERNAME ?? "Guusvanmeerveld";

@ -0,0 +1,52 @@
import axios from "axios";
import z from "zod";
import createConfigCatClient from "./createConfigCatClient";
import {
RepositoryResponse,
SearchResultsResponse,
UserResponse
} from "@models/responses";
import { giteaServerUrl } from "./config";
const apiUrl = `https://${giteaServerUrl}/api/v1`;
export const fetchAvailability = async (): Promise<boolean> => {
const configCatClient = createConfigCatClient();
const isAvailable: boolean =
(await configCatClient?.getValueAsync("amiavailable", true)) ?? true;
return isAvailable;
};
export const fetchUser = async (
giteaUsername: string
): Promise<z.infer<typeof UserResponse>> => {
const { data: user } = await axios.get<unknown>(
`${apiUrl}/users/${giteaUsername}`
);
return UserResponse.parse(user);
};
export const fetchRepositories = async (
giteaUserUid: number
): Promise<z.infer<typeof RepositoryResponse>[]> => {
const { data: repositories } = await axios.get<unknown>(
`${apiUrl}/repos/search`,
{
params: {
topic: true,
q: "on-portfolio",
id: giteaUserUid
}
}
);
const results = SearchResultsResponse.parse(repositories);
if (results.ok) return results.data;
else throw results.data;
};

@ -20,6 +20,7 @@
"@styles/*": ["styles/*"], "@styles/*": ["styles/*"],
"@components/*": ["components/*"], "@components/*": ["components/*"],
"@interfaces/*": ["interfaces/*"], "@interfaces/*": ["interfaces/*"],
"@models/*": ["models/*"],
"@utils/*": ["utils/*"], "@utils/*": ["utils/*"],
"@src/*": ["src/*"] "@src/*": ["src/*"]
} }

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save