diff --git a/components/SettingsSidebar.tsx b/components/SettingsSidebar.tsx index b985f9f..b7b1e27 100644 --- a/components/SettingsSidebar.tsx +++ b/components/SettingsSidebar.tsx @@ -5,6 +5,7 @@ import { faPalette, faBoxArchive, faKey, + faLock, } from "@fortawesome/free-solid-svg-icons"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -20,7 +21,7 @@ import { } from "@fortawesome/free-brands-svg-icons"; export default function SettingsSidebar({ className }: { className?: string }) { - const LINKWARDEN_VERSION = "v2.2.1"; + const LINKWARDEN_VERSION = "v2.3.0"; const { collections } = useCollectionStore(); @@ -96,6 +97,23 @@ export default function SettingsSidebar({ className }: { className?: string }) { + +
+ + +

+ API Keys +

+
+ +
diff --git a/layouts/SettingsLayout.tsx b/layouts/SettingsLayout.tsx index 0989ceb..7a9a2af 100644 --- a/layouts/SettingsLayout.tsx +++ b/layouts/SettingsLayout.tsx @@ -49,8 +49,8 @@ export default function SettingsLayout({ children }: Props) {
-
-
+
+
- -

- {router.asPath.split("/").pop()} Settings -

-
- {children} {sidebar ? ( diff --git a/package.json b/package.json index 0078a7b..53a75ee 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@types/dompurify": "^3.0.4", "@types/jsdom": "^21.1.3", "autoprefixer": "^10.4.14", + "daisyui": "^4.4.2", "postcss": "^8.4.26", "prisma": "^5.1.0", "tailwindcss": "^3.3.3" diff --git a/pages/settings/account.tsx b/pages/settings/account.tsx index a34ac99..95d98af 100644 --- a/pages/settings/account.tsx +++ b/pages/settings/account.tsx @@ -151,6 +151,10 @@ export default function Account() { return ( +

Account Settings

+ +
+
diff --git a/pages/settings/api.tsx b/pages/settings/api.tsx new file mode 100644 index 0000000..377f39b --- /dev/null +++ b/pages/settings/api.tsx @@ -0,0 +1,78 @@ +import Checkbox from "@/components/Checkbox"; +import SubmitButton from "@/components/SubmitButton"; +import SettingsLayout from "@/layouts/SettingsLayout"; +import React, { useEffect, useState } from "react"; +import useAccountStore from "@/store/account"; +import { toast } from "react-hot-toast"; +import { AccountSettings } from "@/types/global"; +import TextInput from "@/components/TextInput"; + +export default function Api() { + const [submitLoader, setSubmitLoader] = useState(false); + const { account, updateAccount } = useAccountStore(); + const [user, setUser] = useState(account); + + const [archiveAsScreenshot, setArchiveAsScreenshot] = + useState(false); + const [archiveAsPDF, setArchiveAsPDF] = useState(false); + const [archiveAsWaybackMachine, setArchiveAsWaybackMachine] = + useState(false); + + useEffect(() => { + setUser({ + ...account, + archiveAsScreenshot, + archiveAsPDF, + archiveAsWaybackMachine, + }); + }, [account, archiveAsScreenshot, archiveAsPDF, archiveAsWaybackMachine]); + + function objectIsEmpty(obj: object) { + return Object.keys(obj).length === 0; + } + + useEffect(() => { + if (!objectIsEmpty(account)) { + setArchiveAsScreenshot(account.archiveAsScreenshot); + setArchiveAsPDF(account.archiveAsPDF); + setArchiveAsWaybackMachine(account.archiveAsWaybackMachine); + } + }, [account]); + + const submit = async () => { + // setSubmitLoader(true); + // const load = toast.loading("Applying..."); + // const response = await updateAccount({ + // ...user, + // }); + // toast.dismiss(load); + // if (response.ok) { + // toast.success("Settings Applied!"); + // } else toast.error(response.data as string); + // setSubmitLoader(false); + }; + + return ( + +

API Keys

+ +
+ +
+
+ Status: Under Development +
+ +

This page will be for creating and managing your API keys.

+ +

+ For now, you can temporarily use your{" "} + + next-auth.session-token + {" "} + in your browser cookies as the API key for your integrations. +

+
+
+ ); +} diff --git a/pages/settings/appearance.tsx b/pages/settings/appearance.tsx index 6986b85..7f960d1 100644 --- a/pages/settings/appearance.tsx +++ b/pages/settings/appearance.tsx @@ -68,6 +68,10 @@ export default function Appearance() { return ( +

Appearance

+ +
+

Select Theme

diff --git a/pages/settings/archive.tsx b/pages/settings/archive.tsx index a5f18f3..6633af5 100644 --- a/pages/settings/archive.tsx +++ b/pages/settings/archive.tsx @@ -57,6 +57,10 @@ export default function Archive() { return ( +

Archive Settings

+ +
+

Formats to Archive webpages:

+

Billing Settings

+ +
+

- To manage/cancel your subsciption, visit the{" "} + To manage/cancel your subscription, visit the{" "} +

Change Password

+ +
+

To change your password, please fill out the following. Your password should be at least 8 characters. diff --git a/prisma/migrations/20231120135053_add_apikeys_table/migration.sql b/prisma/migrations/20231120135053_add_apikeys_table/migration.sql new file mode 100644 index 0000000..7798f17 --- /dev/null +++ b/prisma/migrations/20231120135053_add_apikeys_table/migration.sql @@ -0,0 +1,22 @@ +-- CreateTable +CREATE TABLE "ApiKeys" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + "token" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + "lastUsedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ApiKeys_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ApiKeys_token_key" ON "ApiKeys"("token"); + +-- CreateIndex +CREATE UNIQUE INDEX "ApiKeys_token_userId_key" ON "ApiKeys"("token", "userId"); + +-- AddForeignKey +ALTER TABLE "ApiKeys" ADD CONSTRAINT "ApiKeys_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20231120135140_rename_apikeys_to_apikey/migration.sql b/prisma/migrations/20231120135140_rename_apikeys_to_apikey/migration.sql new file mode 100644 index 0000000..809ec77 --- /dev/null +++ b/prisma/migrations/20231120135140_rename_apikeys_to_apikey/migration.sql @@ -0,0 +1,34 @@ +/* + Warnings: + + - You are about to drop the `ApiKeys` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "ApiKeys" DROP CONSTRAINT "ApiKeys_userId_fkey"; + +-- DropTable +DROP TABLE "ApiKeys"; + +-- CreateTable +CREATE TABLE "ApiKey" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + "token" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + "lastUsedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ApiKey_token_key" ON "ApiKey"("token"); + +-- CreateIndex +CREATE UNIQUE INDEX "ApiKey_token_userId_key" ON "ApiKey"("token", "userId"); + +-- AddForeignKey +ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c4d72c1..5f4dd23 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,7 +20,6 @@ model Account { scope String? id_token String? session_state String? - user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) @@ -29,45 +28,34 @@ model Account { model User { id Int @id @default(autoincrement()) name String - username String? @unique - email String? @unique emailVerified DateTime? image String? - accounts Account[] - password String? - collections Collection[] tags Tag[] pinnedLinks Link[] collectionsJoined UsersAndCollections[] whitelistedUsers WhitelistedUser[] - + apiKeys ApiKey[] subscriptions Subscription? - archiveAsScreenshot Boolean @default(true) archiveAsPDF Boolean @default(true) archiveAsWaybackMachine Boolean @default(false) - isPrivate Boolean @default(false) - displayLinkIcons Boolean @default(true) blurredFavicons Boolean @default(true) - createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @default(now()) } model WhitelistedUser { id Int @id @default(autoincrement()) - username String @default("") User User? @relation(fields: [userId], references: [id]) userId Int? - createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @default(now()) } @@ -76,7 +64,6 @@ model VerificationToken { identifier String token String @unique expires DateTime - createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @default(now()) @@ -89,13 +76,12 @@ model Collection { description String @default("") color String @default("#0ea5e9") isPublic Boolean @default(false) - - owner User @relation(fields: [ownerId], references: [id]) - ownerId Int - members UsersAndCollections[] - links Link[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt @default(now()) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + members UsersAndCollections[] + links Link[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @default(now()) @@unique([name, ownerId]) } @@ -103,16 +89,13 @@ model Collection { model UsersAndCollections { user User @relation(fields: [userId], references: [id]) userId Int - collection Collection @relation(fields: [collectionId], references: [id]) collectionId Int - canCreate Boolean canUpdate Boolean canDelete Boolean - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt @default(now()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @default(now()) @@id([userId, collectionId]) } @@ -122,32 +105,25 @@ model Link { name String url String description String @default("") - pinnedBy User[] - collection Collection @relation(fields: [collectionId], references: [id]) collectionId Int tags Tag[] - textContent String? - screenshotPath String? pdfPath String? readabilityPath String? - lastPreserved DateTime? - createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @default(now()) } model Tag { - id Int @id @default(autoincrement()) - name String - links Link[] - owner User @relation(fields: [ownerId], references: [id]) - ownerId Int - + id Int @id @default(autoincrement()) + name String + links Link[] + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @default(now()) @@ -160,10 +136,22 @@ model Subscription { stripeSubscriptionId String @unique currentPeriodStart DateTime currentPeriodEnd DateTime - user User @relation(fields: [userId], references: [id]) userId Int @unique - createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @default(now()) } + +model ApiKey { + id Int @id @default(autoincrement()) + name String + user User @relation(fields: [userId], references: [id]) + userId Int + token String @unique + expires DateTime + lastUsedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @default(now()) + + @@unique([token, userId]) +} diff --git a/styles/globals.css b/styles/globals.css index bf9183c..eaf3dbb 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -142,7 +142,7 @@ body { /* Theme */ @layer base { body { - @apply dark:bg-neutral-900 bg-white dark:text-white; + @apply dark:bg-neutral-900 bg-white text-black dark:text-white; } } diff --git a/tailwind.config.js b/tailwind.config.js index 6ad2b8c..a704256 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,6 +2,9 @@ module.exports = { darkMode: "class", + // daisyui: { + // themes: ["light", "dark"], + // }, content: [ "./app/**/*.{js,ts,jsx,tsx}", "./pages/**/*.{js,ts,jsx,tsx}", @@ -10,5 +13,5 @@ module.exports = { // For the "layouts" directory "./layouts/**/*.{js,ts,jsx,tsx}", ], - plugins: [], + plugins: [require("daisyui")], }; diff --git a/yarn.lock b/yarn.lock index 57493d9..feb1783 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2129,6 +2129,14 @@ crypto-js@^4.2.0: resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== +css-selector-tokenizer@^0.8: + version "0.8.0" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz#88267ef6238e64f2215ea2764b3e2cf498b845dd" + integrity sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg== + dependencies: + cssesc "^3.0.0" + fastparse "^1.1.2" + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -2151,6 +2159,11 @@ csstype@^3.1.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +culori@^3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/culori/-/culori-3.2.0.tgz#df6561503f0cc20e8e1c029f086466666c0ac62f" + integrity sha512-HIEbTSP7vs1mPq/2P9In6QyFE0Tkpevh0k9a+FkjhD+cwsYm9WRSbn4uMdW9O0yXlNYC3ppxL3gWWPOcvEl57w== + cwise-compiler@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/cwise-compiler/-/cwise-compiler-1.1.3.tgz#f4d667410e850d3a313a7d2db7b1e505bb034cc5" @@ -2158,6 +2171,16 @@ cwise-compiler@^1.1.2: dependencies: uniq "^1.0.0" +daisyui@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-4.4.2.tgz#669ee310be42894abe36d493635000d010fa4023" + integrity sha512-Ecg5loskj9dkaAnTSK5Xn5jb24TqDlQIg/NJ025jCkw2S/zw12btjvLgY2Sv5Ws1DFVoVBRs3XYXyojZG7zVnw== + dependencies: + css-selector-tokenizer "^0.8" + culori "^3" + picocolors "^1" + postcss-js "^4" + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" @@ -2726,6 +2749,11 @@ fast-xml-parser@4.2.5: dependencies: strnum "^1.0.5" +fastparse@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" + integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== + fastq@^1.6.0: version "1.15.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" @@ -4113,7 +4141,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -picocolors@^1.0.0: +picocolors@^1, picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== @@ -4159,7 +4187,7 @@ postcss-import@^15.1.0: read-cache "^1.0.0" resolve "^1.1.7" -postcss-js@^4.0.1: +postcss-js@^4, postcss-js@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==