diff --git a/.eslintrc.json b/.eslintrc.json index 74cb88ea..58302230 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,7 +2,7 @@ { "extends": "next/core-web-vitals", "rules": { - "react-hooks/exhaustive-deps": "off", + "react-hooks/exhaustive-deps": 0, "react/jsx-no-target-blank": "off", "no-unexpected-multiline": 2, "no-implied-eval": 2, diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 9c83eeb9..6ca3cedf 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -53,9 +53,9 @@ jobs: cd ~/clickdi # sh ./backup.sh sudo docker login -u ${{ github.repository_owner }} -p ${{ secrets.GITHUB_TOKEN }} - sudo docker compose pull sudo docker compose down + sudo docker compose pull sudo docker compose up -d - sudo docker system prune -f sudo sh ./migrate.sh sudo sh ./puppeteer.sh + sudo docker system prune -f diff --git a/package.json b/package.json index 1ee8caba..88450d20 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,14 @@ }, "dependencies": { "@cloudamqp/amqp-client": "^2.1.1", + "@hookform/error-message": "^2.0.1", "@prisma/client": "4.9.0", "@styled-icons/feather": "^10.47.0", "@tailwindcss/line-clamp": "^0.4.2", "@textea/json-viewer": "^2.16.0", "@types/jest": "^29.5.2", "@types/nodemailer": "^6.4.9", + "@types/ramda": "^0.29.3", "add": "^2.0.6", "axios": "^1.2.6", "clsx": "^1.2.1", @@ -44,19 +46,21 @@ "prisma": "^4.9.0", "puppeteer": "^19.7.1", "qrcode": "^1.5.1", + "ramda": "^0.29.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-hook-form": "^7.42.1", + "react-hook-form": "^7.45.4", "react-hot-toast": "^2.4.1", "react-i18next": "^13.2.0", "react-query": "^3.39.3", "request-ip": "^3.3.0", "sass": "^1.57.1", - "styled-components": "^5.3.8", + "styled-components": "^6.0.7", "ts-jest": "^29.1.0", "ua-parser-js": "^1.0.34", "yarn": "^1.22.19", - "zod": "^3.20.6" + "zod": "^3.20.6", + "zustand": "^4.4.1" }, "devDependencies": { "@types/crypto-js": "^4.1.1", diff --git a/prisma/migrations/20230828114002_add_custom_og_shorten_url/migration.sql b/prisma/migrations/20230828114002_add_custom_og_shorten_url/migration.sql new file mode 100644 index 00000000..2689ed6d --- /dev/null +++ b/prisma/migrations/20230828114002_add_custom_og_shorten_url/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "UrlShortenerHistory" ADD COLUMN "ogDescription" TEXT, +ADD COLUMN "ogDomain" TEXT, +ADD COLUMN "ogImgSrc" TEXT, +ADD COLUMN "ogTitle" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f6516e4d..d1ae33b1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -24,22 +24,23 @@ model UrlShortenerHistory { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - url String - hash String @unique - email String? - password String? - + url String + hash String @unique + email String? + password String? + ogTitle String? + ogDomain String? + ogDescription String? + ogImgSrc String? UrlShortenerRecord UrlShortenerRecord @relation(fields: [urlShortenerRecordId], references: [id], onDelete: Cascade) urlShortenerRecordId Int - - urlForwardMeta UrlForwardMeta[] + urlForwardMeta UrlForwardMeta[] } model UrlForwardMeta { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt userAgent String? countryCode String? ip String? diff --git a/public/assets/clickdi-banner-en.jpeg b/public/assets/clickdi-banner-en.jpeg new file mode 100644 index 00000000..2d0a3af0 Binary files /dev/null and b/public/assets/clickdi-banner-en.jpeg differ diff --git a/public/assets/clickdi-banner-vi.jpeg b/public/assets/clickdi-banner-vi.jpeg new file mode 100644 index 00000000..98625f8f Binary files /dev/null and b/public/assets/clickdi-banner-vi.jpeg differ diff --git a/public/lib/styles.min.css b/public/lib/styles.min.css index cb75df37..e8981adc 100644 --- a/public/lib/styles.min.css +++ b/public/lib/styles.min.css @@ -2529,7 +2529,7 @@ html.dark { } .border-\[\#3b71ca\] { --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .border-transparent { border-color: transparent; @@ -2611,7 +2611,7 @@ html.dark { } .border-primary { --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .border-purple-600 { --tw-border-opacity: 1; @@ -2661,7 +2661,7 @@ html.dark { } .bg-primary { --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .bg-primary-100 { --tw-bg-opacity: 1; @@ -2684,7 +2684,7 @@ html.dark { } .bg-\[\#3b71ca\] { --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .bg-\[\#f9e1e5\] { --tw-bg-opacity: 1; @@ -4044,7 +4044,7 @@ html.dark { } .text-\[\#3b71ca\] { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .text-\[\#285192\] { --tw-text-opacity: 1; @@ -4060,7 +4060,7 @@ html.dark { } .text-primary { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .text-red-600 { --tw-text-opacity: 1; @@ -4282,7 +4282,7 @@ html.dark { } .\!text-primary { --tw-text-opacity: 1 !important; - color: rgb(59 113 202 / var(--tw-text-opacity)) !important; + color: rgb(6 182 212 / var(--tw-text-opacity)) !important; } .text-gray-400 { --tw-text-opacity: 1; @@ -5201,7 +5201,7 @@ code { } .checked\:border-primary:checked { --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .checked\:border-blue-600:checked { --tw-border-opacity: 1; @@ -5209,7 +5209,7 @@ code { } .checked\:bg-primary:checked { --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .checked\:bg-white:checked { --tw-bg-opacity: 1; @@ -5341,7 +5341,7 @@ code { .checked\:after\:border-primary:checked:after { content: var(--tw-content); --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .checked\:after\:bg-transparent:checked:after { content: var(--tw-content); @@ -5350,7 +5350,7 @@ code { .checked\:after\:bg-primary:checked:after { content: var(--tw-content); --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .checked\:after\:shadow-\[0_3px_1px_-2px_rgba\(0\2c 0\2c @@ -5386,11 +5386,11 @@ code { } .indeterminate\:border-primary:indeterminate { --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .indeterminate\:bg-primary:indeterminate { --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .indeterminate\:after\:absolute:indeterminate:after { content: var(--tw-content); @@ -5458,7 +5458,7 @@ code { } .hover\:border-\[\#3b71ca\]:hover { --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .hover\:border-\[\#9fa6b2\]:hover { --tw-border-opacity: 1; @@ -5605,7 +5605,7 @@ code { } .hover\:bg-primary:hover { --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .hover\:bg-slate-50:hover { --tw-bg-opacity: 1; @@ -5719,7 +5719,7 @@ code { } .hover\:text-primary:hover { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .hover\:text-neutral-600:hover { --tw-text-opacity: 1; @@ -5746,7 +5746,7 @@ code { } .hover\:text-\[\#3b71ca\]:hover { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .hover\:no-underline:hover { -webkit-text-decoration-line: none; @@ -5944,7 +5944,7 @@ code { } .focus\:border-primary:focus { --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .focus\:border-blue-600:focus { --tw-border-opacity: 1; @@ -6052,7 +6052,7 @@ code { } .focus\:bg-primary:focus { --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .focus\:bg-slate-50:focus { --tw-bg-opacity: 1; @@ -6131,7 +6131,7 @@ code { } .focus\:text-primary:focus { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .focus\:text-gray-700:focus { --tw-text-opacity: 1; @@ -6146,7 +6146,7 @@ code { } .focus\:text-\[\#3b71ca\]:focus { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .focus\:no-underline:focus { -webkit-text-decoration-line: none; @@ -6382,11 +6382,11 @@ code { } .checked\:focus\:border-primary:focus:checked { --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .checked\:focus\:bg-primary:focus:checked { --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .checked\:focus\:bg-white:focus:checked { --tw-bg-opacity: 1; @@ -6530,7 +6530,7 @@ code { } .active\:bg-primary:active { --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .active\:bg-neutral-200:active { --tw-bg-opacity: 1; @@ -6830,7 +6830,7 @@ code { } .group[data-te-datepicker-cell-selected] .group-\[\[data-te-datepicker-cell-selected\]\]\:bg-primary { --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .group:not([data-te-datepicker-cell-selected])[data-te-datepicker-cell-focused] .group-\[\:not\(\[data-te-datepicker-cell-selected\]\)\[data-te-datepicker-cell-focused\]\]\:bg-neutral-100 { @@ -6890,7 +6890,7 @@ code { } .peer:focus ~ .peer-focus\:text-primary { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .peer:focus ~ .peer-focus\:text-white { --tw-text-opacity: 1; @@ -7020,7 +7020,7 @@ code { } .data-\[te-nav-active\]\:border-primary[data-te-nav-active] { --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .data-\[te-nav-active\]\:border-secondary[data-te-nav-active] { --tw-border-opacity: 1; @@ -7139,7 +7139,7 @@ code { } .data-\[te-nav-active\]\:text-primary[data-te-nav-active] { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .data-\[te-nav-active\]\:text-secondary[data-te-nav-active] { --tw-text-opacity: 1; @@ -7223,7 +7223,7 @@ code { .group[data-te-datepicker-cell-focused] .group-\[\[data-te-datepicker-cell-focused\]\]\:data-\[te-datepicker-cell-selected\]\:bg-primary[data-te-datepicker-cell-selected] { --tw-bg-opacity: 1; - background-color: rgb(59 113 202 / var(--tw-bg-opacity)); + background-color: rgb(6 182 212 / var(--tw-bg-opacity)); } .group[data-te-select-option-text-ref] .group-data-\[te-select-option-text-ref\]\:mr-2\.5 { margin-right: 0.625rem; @@ -7254,7 +7254,7 @@ code { } .group[data-te-input-focused] .group-data-\[te-input-focused\]\:border-primary { --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .group[data-te-input-focused] .group-data-\[te-input-focused\]\:border-white { --tw-border-opacity: 1; @@ -7636,7 +7636,7 @@ code { } .dark .dark\:hover\:text-\[\#3b71ca\]:hover { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .dark .dark\:hover\:shadow-\[0_8px_9px_-4px_rgba\(27\2c @@ -7678,7 +7678,7 @@ code { } .dark .dark\:focus\:text-\[\#3b71ca\]:focus { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .dark .dark\:focus\:shadow-\[0_8px_9px_-4px_rgba\(27\2c @@ -7785,7 +7785,7 @@ code { } .dark .dark\:data-\[te-nav-active\]\:text-primary[data-te-nav-active] { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .dark .dark\:data-\[te-nav-active\]\:text-secondary[data-te-nav-active] { --tw-text-opacity: 1; @@ -8799,7 +8799,7 @@ code { } .\[\&\:not\(\[data-te-collapse-collapsed\]\)\]\:text-primary:not([data-te-collapse-collapsed]) { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .\[\&\:not\(\[data-te-collapse-collapsed\]\)\]\:\[box-shadow\:inset_0_-1px_0_rgba\(229\2c 231\2c @@ -9020,7 +9020,7 @@ code { } .\[\&\>svg\]\:text-primary > svg { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .\[\&\>svg\]\:text-neutral-400 > svg { --tw-text-opacity: 1; @@ -9086,14 +9086,14 @@ code { } .\[\&\.active\]\:border-primary.active { --tw-border-opacity: 1; - border-color: rgb(59 113 202 / var(--tw-border-opacity)); + border-color: rgb(6 182 212 / var(--tw-border-opacity)); } .\[\&\.active\]\:text-black\/90.active { color: rgba(0, 0, 0, 0.9); } .\[\&\.active\]\:text-primary.active { --tw-text-opacity: 1; - color: rgb(59 113 202 / var(--tw-text-opacity)); + color: rgb(6 182 212 / var(--tw-text-opacity)); } .\[\&\:not\(\[data-te-input-placeholder-active\]\)\]\:placeholder\:opacity-0:not( [data-te-input-placeholder-active] diff --git a/public/locales/en/common.json b/public/locales/en/common.json index bdfb8ec6..f6e0f90a 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -51,9 +51,10 @@ "refresh": "Refresh", "noData": "No data found", "helpShortUrlHead": "- Shorten link to 3 unique hash xxx (clid.top/xxx)\n- Link saved by IP, no sign in required\n- To tracking link, check \"View more\"\n- Tracking shortened link goes publicly, unless set new password", - "ogBrand": "U2FsdGVkX1+IEpdIZm+U48yjdQMVqcCOVBw2TQjQriMnsP0paNBd7TqAWmeqTJScL0trSU9MdKZEysQO5YkJXw==", "brandTitle": "Clickdi - URL shortener, quick and all free.", "brandDescription": "Clickdi is more than a URL shortener, help you track the usage of the shortened URLs, providing analytics on the number of clicks, geographic location of clicks, and other relevant data. Take action on the effectiveness of shared links and make data-driven decisions to improve their outreach efforts.", "advancedView": "Advanced info", - "loadMore": "Load more" + "loadMore": "Load more", + "editPreview": "More options", + "save": "Save" } diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json index f10977f8..417bd51d 100644 --- a/public/locales/vi/common.json +++ b/public/locales/vi/common.json @@ -43,7 +43,7 @@ "emailRecoverLabel": "Email (để lấy lại mật khẩu)*", "dismiss": "Hủy", "confirm": "Xác nhận", - "updated": "Đã cập nhật", + "updated": "Cập nhật thành công", "password": "Mật khẩu", "unlock": "Mở khóa", "inputManyTimes": "Nhập quá nhiều lần. Refresh trang và thử lại.", @@ -51,9 +51,10 @@ "refresh": "Cập nhật lại", "noData": "Chưa có dữ liệu", "helpShortUrlHead": "- Rút gọn mọi link thành 3 ký tự định danh xxx (clid.top/xxx)\n- Link lưu theo IP, không cần đăng nhập\n- Để theo dõi link, vào \"Xem thêm\"\n- Theo dõi link rút gọn là công khai, trừ khi đặt mật khẩu", - "ogBrand": "U2FsdGVkX1/peYMg6O52ZA6SJEW4ehfDrnQIHtZraMP+Dby16K1dgvrghOpE0DRGJLK9GyCc911FkTbB8jlOt6BzKSZY9FJ8npAFD0HnaBU=", "brandTitle": "Clickdi - Rút gọn link miễn phí", "brandDescription": "Clickdi rút gọn URL miễn phí. Clickdi rút gọn link nhanh chóng, giúp bạn theo dõi các đường dẫn được rút gọn, số lượng lượt nhấp chuột, vị trí của và các dữ liệu liên quan khác.", "advancedView": "Xem nâng cao", - "loadMore": "Xem thêm" + "loadMore": "Xem thêm", + "editPreview": "Chỉnh sửa", + "save": "Lưu thay đổi" } diff --git a/src/api/axios.ts b/src/api/axios.ts index 2349b9e8..1a918bc8 100644 --- a/src/api/axios.ts +++ b/src/api/axios.ts @@ -25,7 +25,7 @@ export function withAuth(token?: string) { const allowedOrigins = [brandUrl, brandUrlShort, ...[alternateBrandUrl]]; export const allowCors = (handler: any) => async (req: NextApiRequest, res: NextApiResponse) => { - const origin = req.headers['origin']; + const origin = req.headers?.origin; console.log('origin', origin); if (!!origin && allowedOrigins.includes(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); diff --git a/src/api/requests.ts b/src/api/requests.ts index 621a6031..890fe744 100644 --- a/src/api/requests.ts +++ b/src/api/requests.ts @@ -12,6 +12,24 @@ export const getOrCreateShortenUrlRequest = async ({ url, hash }: { url?: string return data as ShortenUrl; }; +export const updateShortenUrlRequest = async ({ + hash, + ogTitle, + ogDescription, +}: { + hash: string; + ogDescription?: string; + ogTitle?: string; +}) => { + const rs = await API.put(`/api/shorten/update`, { + hash, + ogTitle, + ogDescription, + }); + const data = rs.data; + return data as ShortenUrl; +}; + export const getForwardUrl = async ({ hash, userAgent, diff --git a/src/bear/exampleSlice.ts b/src/bear/exampleSlice.ts new file mode 100644 index 00000000..2922ca59 --- /dev/null +++ b/src/bear/exampleSlice.ts @@ -0,0 +1,17 @@ +import { withDevTools } from 'bear/middleware'; +import { StateCreator, create } from 'zustand'; + +export interface ExampleSlice { + counter: number; + increment: () => void; +} + +const slice: StateCreator = (set, get) => ({ + counter: 0, + get2xCounter: () => get().counter * 2, + increment: () => set((state) => ({ counter: state.counter + 1 })), +}); + +const exampleSlice = create(withDevTools(slice, { anonymousActionType: 'ExampleSlice' })); + +export default exampleSlice; diff --git a/src/bear/index.ts b/src/bear/index.ts new file mode 100644 index 00000000..428fee39 --- /dev/null +++ b/src/bear/index.ts @@ -0,0 +1,14 @@ +import { isProduction } from 'types/constants'; +import { UseBoundStore } from 'zustand'; +import { devtools } from 'zustand/middleware'; +import exampleSlice from './exampleSlice'; +import shortenSlice from './shortenSlice'; + +export const withDevTools = (slice: UseBoundStore) => devtools(slice, { enabled: !isProduction }); + +export const useBearStore = () => { + return { + exampleSlice, + shortenSlice, + }; +}; diff --git a/src/bear/middleware.ts b/src/bear/middleware.ts new file mode 100644 index 00000000..4112a374 --- /dev/null +++ b/src/bear/middleware.ts @@ -0,0 +1,6 @@ +import { isProduction } from 'types/constants'; +import { StateCreator } from 'zustand'; +import { DevtoolsOptions, devtools } from 'zustand/middleware'; + +export const withDevTools = (slice: StateCreator, devtoolsOptions?: DevtoolsOptions) => + devtools(slice, { ...devtoolsOptions, enabled: !isProduction }); diff --git a/src/bear/shortenSlice.ts b/src/bear/shortenSlice.ts new file mode 100644 index 00000000..c7b08218 --- /dev/null +++ b/src/bear/shortenSlice.ts @@ -0,0 +1,26 @@ +import { UrlShortenerHistory } from '@prisma/client'; +import { withDevTools } from 'bear/middleware'; +import { BASE_URL_SHORT } from 'types/constants'; +import { StateCreator, create } from 'zustand'; + +export interface ShortenSlice { + shortenHistory?: Partial; + setShortenHistory: (history?: Partial) => void; + shortenHistoryForm?: Partial; + setShortenHistoryForm: (history?: Partial) => void; + getShortenUrl: () => string; +} + +const slice: StateCreator = (set, get) => ({ + shortenHistory: undefined, + shortenHistoryForm: undefined, + getShortenUrl: () => (!!get().shortenHistory ? `${BASE_URL_SHORT}/${get().shortenHistory?.hash}` : ''), + + setShortenHistory: (history) => set((state) => ({ shortenHistory: { ...state.shortenHistory, ...history } })), + setShortenHistoryForm: (history) => + set((state) => ({ shortenHistoryForm: { ...state.shortenHistoryForm, ...history } })), +}); + +const shortenSlice = create(withDevTools(slice, { anonymousActionType: 'ShortenSlice' })); + +export default shortenSlice; diff --git a/src/components/atoms/Accordion.tsx b/src/components/atoms/Accordion.tsx index f083cb10..708f0d4e 100644 --- a/src/components/atoms/Accordion.tsx +++ b/src/components/atoms/Accordion.tsx @@ -1,52 +1,45 @@ import { useId } from 'react'; interface Props { - children: JSX.Element | JSX.Element[]; - title?: string[] | JSX.Element[]; + children: JSX.Element; + title?: string | JSX.Element; } export const Accordion = ({ children, title }: Props) => { - const l = (children as []).length ?? 1; - const id = useId().replaceAll(':', '-'); - return (
- {(((children as []).length ? children : [children]) as []).map((c, i) => { - return ( -
-

- -

-
-
{c}
-
-
- ); - })} +
+

+ +

+
+
{children}
+
+
); }; diff --git a/src/components/atoms/Button.tsx b/src/components/atoms/Button.tsx index e5a21a85..b14fc18a 100644 --- a/src/components/atoms/Button.tsx +++ b/src/components/atoms/Button.tsx @@ -17,7 +17,7 @@ export const Button = (props: ButtonProps) => {