BLOG-8 Set up gitea action #22
35
.gitea/workflows/frontend-ci.yaml
Normal file
35
.gitea/workflows/frontend-ci.yaml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: Frontend CI
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./frontend
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Install node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: "pnpm"
|
||||||
|
cache-dependency-path: frontend/pnpm-lock.yaml
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: ESLint
|
||||||
|
run: pnpm run lint
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm run build
|
@ -9,6 +9,7 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
|
"packageManager": "pnpm@10.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
|
|
||||||
import { useTagDispatch, useTagSelector } from "@/lib/home/presenter/tagHooks";
|
import { useTagDispatch, useTagSelector } from "@/lib/home/presenter/tagHook";
|
||||||
import { tagStartedAction, tagStoppedAction } from "@/lib/home/presenter/tagSlice";
|
import { tagShuffledAction } from "@/lib/home/presenter/tagReducer";
|
||||||
import tagStore from "@/lib/home/presenter/tagStore";
|
import tagStore from "@/lib/home/presenter/tagStore";
|
||||||
|
|
||||||
export default function SelfTags() {
|
export default function SelfTags() {
|
||||||
@ -16,25 +16,35 @@ export default function SelfTags() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SelfTagsProvided() {
|
function SelfTagsProvided() {
|
||||||
const tags = useTagSelector((state) => state.tag.tags);
|
const tags = useTagSelector((state) => state.tags);
|
||||||
const dispatch = useTagDispatch();
|
const dispatch = useTagDispatch();
|
||||||
|
|
||||||
|
// Initialize with placeholder to prevent flickering
|
||||||
|
const [tagsToShow, setTagsToShow] = useState<string[]>([""]);
|
||||||
const [isTagsVisible, setIsTagsVisible] = useState(false);
|
const [isTagsVisible, setIsTagsVisible] = useState(false);
|
||||||
|
|
||||||
|
const timer = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||||
|
|
||||||
|
// On mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(tagStartedAction({ interval: 4000 }));
|
timer.current = setInterval(() => {
|
||||||
|
dispatch(tagShuffledAction());
|
||||||
|
}, 4000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
dispatch(tagStoppedAction());
|
clearInterval(timer.current);
|
||||||
setIsTagsVisible(false);
|
timer.current = undefined;
|
||||||
};
|
};
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
// On tags changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tags.length === 0) return;
|
|
||||||
setIsTagsVisible(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsTagsVisible(false);
|
setIsTagsVisible(false);
|
||||||
}, 3500);
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setTagsToShow(tags);
|
||||||
|
setIsTagsVisible(true);
|
||||||
|
}, 500);
|
||||||
}, [tags]);
|
}, [tags]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -43,7 +53,7 @@ function SelfTagsProvided() {
|
|||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-transparent via-60% to-white" />
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-transparent via-60% to-white" />
|
||||||
<div className="flex flex-row items-center gap-x-2 overflow-hidden">
|
<div className="flex flex-row items-center gap-x-2 overflow-hidden">
|
||||||
{tags.map((tag) => (
|
{tagsToShow.map((tag) => (
|
||||||
<Hashtag key={tag} tag={tag} />
|
<Hashtag key={tag} tag={tag} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
55
frontend/src/lib/home/presenter/tagReducer.ts
Normal file
55
frontend/src/lib/home/presenter/tagReducer.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { createAction, createReducer } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
import shuffleArray from "@/lib/util/shuffleArray";
|
||||||
|
|
||||||
|
export const tagShuffledAction = createAction("tag/shuffled");
|
||||||
|
|
||||||
|
const tagReducer = createReducer<TagState>(
|
||||||
|
() => ({
|
||||||
|
tags: shuffleArray(tagsCollection),
|
||||||
|
}),
|
||||||
|
(builder) => {
|
||||||
|
builder.addCase(tagShuffledAction, (state) => {
|
||||||
|
state.tags = shuffleArray(tagsCollection);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default tagReducer;
|
||||||
|
export type TagAction = ReturnType<typeof tagShuffledAction>;
|
||||||
|
|
||||||
|
export interface TagState {
|
||||||
|
tags: string[];
|
||||||
|
timer?: NodeJS.Timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagsCollection = [
|
||||||
|
"APP",
|
||||||
|
"C++",
|
||||||
|
"Design Pattern",
|
||||||
|
"Docker",
|
||||||
|
"Flutter",
|
||||||
|
"Go",
|
||||||
|
"Java",
|
||||||
|
"LINER",
|
||||||
|
"Linux",
|
||||||
|
"Python",
|
||||||
|
"Squid",
|
||||||
|
"TypeScript",
|
||||||
|
"中央大學",
|
||||||
|
"全端",
|
||||||
|
"分享",
|
||||||
|
"前端",
|
||||||
|
"後端",
|
||||||
|
"教學",
|
||||||
|
"暴肝",
|
||||||
|
"知識",
|
||||||
|
"碼農",
|
||||||
|
"科技",
|
||||||
|
"科普",
|
||||||
|
"程式設計",
|
||||||
|
"資工系",
|
||||||
|
"軟體工程",
|
||||||
|
"遊戲",
|
||||||
|
"魷魚",
|
||||||
|
];
|
@ -1,73 +0,0 @@
|
|||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
|
||||||
|
|
||||||
import tagStore from "@/lib/home/presenter/tagStore";
|
|
||||||
import shuffleArray from "@/lib/util/shuffleArray";
|
|
||||||
|
|
||||||
export interface TagState {
|
|
||||||
tags: string[];
|
|
||||||
timer?: NodeJS.Timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TagStartedActionPayload {
|
|
||||||
interval: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const tagSlice = createSlice({
|
|
||||||
name: "tag",
|
|
||||||
initialState: {
|
|
||||||
tags: [],
|
|
||||||
timer: undefined,
|
|
||||||
} as TagState,
|
|
||||||
reducers: {
|
|
||||||
started: (state, action: PayloadAction<TagStartedActionPayload>) => {
|
|
||||||
state.tags = shuffleArray(tagsCollection);
|
|
||||||
state.timer = setInterval(() => {
|
|
||||||
tagStore.dispatch(tagSlice.actions.shuffled());
|
|
||||||
}, action.payload.interval);
|
|
||||||
},
|
|
||||||
shuffled: (state) => {
|
|
||||||
state.tags = shuffleArray(tagsCollection);
|
|
||||||
},
|
|
||||||
stopped: (state) => {
|
|
||||||
clearInterval(state.timer);
|
|
||||||
state.timer = undefined;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const tagStartedAction = tagSlice.actions.started;
|
|
||||||
export const tagStoppedAction = tagSlice.actions.stopped;
|
|
||||||
|
|
||||||
const tagReducer = tagSlice.reducer;
|
|
||||||
export default tagReducer;
|
|
||||||
|
|
||||||
const tagsCollection = [
|
|
||||||
"APP",
|
|
||||||
"C++",
|
|
||||||
"Design Pattern",
|
|
||||||
"Docker",
|
|
||||||
"Flutter",
|
|
||||||
"Go",
|
|
||||||
"Java",
|
|
||||||
"LINER",
|
|
||||||
"Linux",
|
|
||||||
"Python",
|
|
||||||
"Squid",
|
|
||||||
"TypeScript",
|
|
||||||
"中央大學",
|
|
||||||
"全端",
|
|
||||||
"分享",
|
|
||||||
"前端",
|
|
||||||
"後端",
|
|
||||||
"教學",
|
|
||||||
"暴肝",
|
|
||||||
"知識",
|
|
||||||
"碼農",
|
|
||||||
"科技",
|
|
||||||
"科普",
|
|
||||||
"程式設計",
|
|
||||||
"資工系",
|
|
||||||
"軟體工程",
|
|
||||||
"遊戲",
|
|
||||||
"魷魚",
|
|
||||||
];
|
|
@ -1,11 +1,7 @@
|
|||||||
import { configureStore } from "@reduxjs/toolkit";
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
import tagReducer from "@/lib/home/presenter/tagSlice";
|
import tagReducer, { TagAction, TagState } from "@/lib/home/presenter/tagReducer";
|
||||||
|
|
||||||
const tagStore = configureStore({
|
export default configureStore<TagState, TagAction>({
|
||||||
reducer: {
|
reducer: tagReducer,
|
||||||
tag: tagReducer,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default tagStore;
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user