Compare commits
10 Commits
452be796f0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fede32d150 | ||
|
|
d6ca9e1429 | ||
|
|
9231e0642c | ||
|
|
68fb45d6ef | ||
|
|
af4689d726 | ||
|
|
01c80758bf | ||
|
|
3ed7b14f1b | ||
|
|
9079a52778 | ||
|
|
cb0962f184 | ||
|
|
137c620a48 |
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"arrowParens": "avoid"
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { defineConfig } from "drizzle-kit";
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
out: "./drizzle",
|
|
||||||
schema: "./src/lib/db/schema.ts",
|
|
||||||
dialect: "sqlite",
|
|
||||||
dbCredentials: {
|
|
||||||
url: process.env.DB_FILE_NAME!,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
CREATE TABLE `movies` (
|
|
||||||
`id` integer PRIMARY KEY NOT NULL,
|
|
||||||
`title` text NOT NULL,
|
|
||||||
`overview` text NOT NULL,
|
|
||||||
`popularity` real NOT NULL,
|
|
||||||
`release_date` text NOT NULL,
|
|
||||||
`poster_path` text NOT NULL,
|
|
||||||
`seen` integer DEFAULT 0 NOT NULL,
|
|
||||||
`favorite` integer DEFAULT 0 NOT NULL,
|
|
||||||
`notes` text DEFAULT '' NOT NULL
|
|
||||||
);
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
|
||||||
CREATE TABLE `__new_movies` (
|
|
||||||
`id` integer PRIMARY KEY NOT NULL,
|
|
||||||
`title` text NOT NULL,
|
|
||||||
`adult` integer NOT NULL,
|
|
||||||
`backdrop_path` text NOT NULL,
|
|
||||||
`genre_ids` text NOT NULL,
|
|
||||||
`original_language` text NOT NULL,
|
|
||||||
`original_title` text NOT NULL,
|
|
||||||
`overview` text NOT NULL,
|
|
||||||
`popularity` real NOT NULL,
|
|
||||||
`poster_path` text NOT NULL,
|
|
||||||
`release_date` text NOT NULL,
|
|
||||||
`video` integer NOT NULL,
|
|
||||||
`vote_average` real NOT NULL,
|
|
||||||
`vote_count` integer NOT NULL,
|
|
||||||
`seen` integer DEFAULT false,
|
|
||||||
`favorite` integer DEFAULT false
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
INSERT INTO `__new_movies`("id", "title", "overview", "popularity", "poster_path", "release_date", "seen", "favorite", "adult", "backdrop_path", "genre_ids", "original_language", "original_title", "video", "vote_average", "vote_count")
|
|
||||||
SELECT
|
|
||||||
"id",
|
|
||||||
"title",
|
|
||||||
"overview",
|
|
||||||
"popularity",
|
|
||||||
"poster_path",
|
|
||||||
"release_date",
|
|
||||||
"seen",
|
|
||||||
"favorite",
|
|
||||||
0 as "adult", -- wartość domyślna
|
|
||||||
'' as "backdrop_path", -- wartość domyślna
|
|
||||||
'[]' as "genre_ids", -- wartość domyślna (pusta tablica JSON)
|
|
||||||
'pl-PL' as "original_language", -- wartość domyślna
|
|
||||||
"title" as "original_title", -- kopiuj z title
|
|
||||||
0 as "video", -- wartość domyślna
|
|
||||||
0.0 as "vote_average", -- wartość domyślna
|
|
||||||
0 as "vote_count" -- wartość domyślna
|
|
||||||
FROM `movies`;--> statement-breakpoint
|
|
||||||
DROP TABLE `movies`;--> statement-breakpoint
|
|
||||||
ALTER TABLE `__new_movies` RENAME TO `movies`;--> statement-breakpoint
|
|
||||||
PRAGMA foreign_keys=ON;
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "6",
|
|
||||||
"dialect": "sqlite",
|
|
||||||
"id": "19a2bad6-49be-485d-ac5e-291bd3d664ad",
|
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
||||||
"tables": {
|
|
||||||
"movies": {
|
|
||||||
"name": "movies",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"name": "title",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"overview": {
|
|
||||||
"name": "overview",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"popularity": {
|
|
||||||
"name": "popularity",
|
|
||||||
"type": "real",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"release_date": {
|
|
||||||
"name": "release_date",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"poster_path": {
|
|
||||||
"name": "poster_path",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"seen": {
|
|
||||||
"name": "seen",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"favorite": {
|
|
||||||
"name": "favorite",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"notes": {
|
|
||||||
"name": "notes",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": "''"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"views": {},
|
|
||||||
"enums": {},
|
|
||||||
"_meta": {
|
|
||||||
"schemas": {},
|
|
||||||
"tables": {},
|
|
||||||
"columns": {}
|
|
||||||
},
|
|
||||||
"internal": {
|
|
||||||
"indexes": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "6",
|
|
||||||
"dialect": "sqlite",
|
|
||||||
"id": "c3b4d292-f58b-4df8-844c-6e534034c832",
|
|
||||||
"prevId": "19a2bad6-49be-485d-ac5e-291bd3d664ad",
|
|
||||||
"tables": {
|
|
||||||
"movies": {
|
|
||||||
"name": "movies",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"name": "title",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"adult": {
|
|
||||||
"name": "adult",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"backdrop_path": {
|
|
||||||
"name": "backdrop_path",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"genre_ids": {
|
|
||||||
"name": "genre_ids",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"original_language": {
|
|
||||||
"name": "original_language",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"original_title": {
|
|
||||||
"name": "original_title",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"overview": {
|
|
||||||
"name": "overview",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"popularity": {
|
|
||||||
"name": "popularity",
|
|
||||||
"type": "real",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"poster_path": {
|
|
||||||
"name": "poster_path",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"release_date": {
|
|
||||||
"name": "release_date",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"video": {
|
|
||||||
"name": "video",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"vote_average": {
|
|
||||||
"name": "vote_average",
|
|
||||||
"type": "real",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"vote_count": {
|
|
||||||
"name": "vote_count",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false
|
|
||||||
},
|
|
||||||
"seen": {
|
|
||||||
"name": "seen",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"favorite": {
|
|
||||||
"name": "favorite",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {},
|
|
||||||
"checkConstraints": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"views": {},
|
|
||||||
"enums": {},
|
|
||||||
"_meta": {
|
|
||||||
"schemas": {},
|
|
||||||
"tables": {},
|
|
||||||
"columns": {}
|
|
||||||
},
|
|
||||||
"internal": {
|
|
||||||
"indexes": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "7",
|
|
||||||
"dialect": "sqlite",
|
|
||||||
"entries": [
|
|
||||||
{
|
|
||||||
"idx": 0,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1754676538678,
|
|
||||||
"tag": "0000_breezy_lester",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 1,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1754948246595,
|
|
||||||
"tag": "0001_elite_odin",
|
|
||||||
"breakpoints": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
760
package-lock.json
generated
760
package-lock.json
generated
@@ -11,20 +11,21 @@
|
|||||||
"@formkit/auto-animate": "^0.8.2",
|
"@formkit/auto-animate": "^0.8.2",
|
||||||
"@libsql/client": "^0.15.10",
|
"@libsql/client": "^0.15.10",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"drizzle-orm": "^0.44.4",
|
|
||||||
"lightgallery": "^2.9.0-beta.1",
|
"lightgallery": "^2.9.0-beta.1",
|
||||||
|
"motion": "^12.23.12",
|
||||||
"next": "15.4.5",
|
"next": "15.4.5",
|
||||||
|
"pocketbase": "^0.26.3",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"swr": "^2.3.6"
|
"swr": "^2.3.6",
|
||||||
|
"zustand": "^5.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"drizzle-kit": "^0.31.4",
|
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"tsx": "^4.20.3",
|
"tsx": "^4.20.3",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
@@ -57,13 +58,6 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@drizzle-team/brocli": {
|
|
||||||
"version": "0.10.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
|
|
||||||
"integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/@emnapi/runtime": {
|
"node_modules/@emnapi/runtime": {
|
||||||
"version": "1.4.5",
|
"version": "1.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
|
||||||
@@ -74,442 +68,6 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild-kit/core-utils": {
|
|
||||||
"version": "3.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
|
|
||||||
"integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==",
|
|
||||||
"deprecated": "Merged into tsx: https://tsx.is",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"esbuild": "~0.18.20",
|
|
||||||
"source-map-support": "^0.5.21"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"freebsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"freebsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
|
|
||||||
"cpu": [
|
|
||||||
"ia32"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
|
|
||||||
"cpu": [
|
|
||||||
"loong64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
|
|
||||||
"cpu": [
|
|
||||||
"mips64el"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
|
|
||||||
"cpu": [
|
|
||||||
"ppc64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
|
|
||||||
"cpu": [
|
|
||||||
"riscv64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
|
|
||||||
"cpu": [
|
|
||||||
"s390x"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"netbsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"openbsd"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"sunos"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
|
|
||||||
"cpu": [
|
|
||||||
"ia32"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/core-utils/node_modules/esbuild": {
|
|
||||||
"version": "0.18.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
|
||||||
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"esbuild": "bin/esbuild"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@esbuild/android-arm": "0.18.20",
|
|
||||||
"@esbuild/android-arm64": "0.18.20",
|
|
||||||
"@esbuild/android-x64": "0.18.20",
|
|
||||||
"@esbuild/darwin-arm64": "0.18.20",
|
|
||||||
"@esbuild/darwin-x64": "0.18.20",
|
|
||||||
"@esbuild/freebsd-arm64": "0.18.20",
|
|
||||||
"@esbuild/freebsd-x64": "0.18.20",
|
|
||||||
"@esbuild/linux-arm": "0.18.20",
|
|
||||||
"@esbuild/linux-arm64": "0.18.20",
|
|
||||||
"@esbuild/linux-ia32": "0.18.20",
|
|
||||||
"@esbuild/linux-loong64": "0.18.20",
|
|
||||||
"@esbuild/linux-mips64el": "0.18.20",
|
|
||||||
"@esbuild/linux-ppc64": "0.18.20",
|
|
||||||
"@esbuild/linux-riscv64": "0.18.20",
|
|
||||||
"@esbuild/linux-s390x": "0.18.20",
|
|
||||||
"@esbuild/linux-x64": "0.18.20",
|
|
||||||
"@esbuild/netbsd-x64": "0.18.20",
|
|
||||||
"@esbuild/openbsd-x64": "0.18.20",
|
|
||||||
"@esbuild/sunos-x64": "0.18.20",
|
|
||||||
"@esbuild/win32-arm64": "0.18.20",
|
|
||||||
"@esbuild/win32-ia32": "0.18.20",
|
|
||||||
"@esbuild/win32-x64": "0.18.20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild-kit/esm-loader": {
|
|
||||||
"version": "2.6.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz",
|
|
||||||
"integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==",
|
|
||||||
"deprecated": "Merged into tsx: https://tsx.is",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@esbuild-kit/core-utils": "^3.3.2",
|
|
||||||
"get-tsconfig": "^4.7.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.8",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
||||||
@@ -2036,7 +1594,7 @@
|
|||||||
"version": "19.1.9",
|
"version": "19.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz",
|
||||||
"integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==",
|
"integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -2061,13 +1619,6 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/buffer-from": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001731",
|
"version": "1.0.30001731",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
|
||||||
@@ -2153,7 +1704,7 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/data-uri-to-buffer": {
|
"node_modules/data-uri-to-buffer": {
|
||||||
@@ -2165,24 +1716,6 @@
|
|||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
|
||||||
"version": "4.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
|
||||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "^2.1.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"supports-color": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dequal": {
|
"node_modules/dequal": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||||
@@ -2214,147 +1747,6 @@
|
|||||||
"url": "https://dotenvx.com"
|
"url": "https://dotenvx.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/drizzle-kit": {
|
|
||||||
"version": "0.31.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.4.tgz",
|
|
||||||
"integrity": "sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@drizzle-team/brocli": "^0.10.2",
|
|
||||||
"@esbuild-kit/esm-loader": "^2.5.5",
|
|
||||||
"esbuild": "^0.25.4",
|
|
||||||
"esbuild-register": "^3.5.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"drizzle-kit": "bin.cjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/drizzle-orm": {
|
|
||||||
"version": "0.44.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.4.tgz",
|
|
||||||
"integrity": "sha512-ZyzKFpTC/Ut3fIqc2c0dPZ6nhchQXriTsqTNs4ayRgl6sZcFlMs9QZKPSHXK4bdOf41GHGWf+FrpcDDYwW+W6Q==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@aws-sdk/client-rds-data": ">=3",
|
|
||||||
"@cloudflare/workers-types": ">=4",
|
|
||||||
"@electric-sql/pglite": ">=0.2.0",
|
|
||||||
"@libsql/client": ">=0.10.0",
|
|
||||||
"@libsql/client-wasm": ">=0.10.0",
|
|
||||||
"@neondatabase/serverless": ">=0.10.0",
|
|
||||||
"@op-engineering/op-sqlite": ">=2",
|
|
||||||
"@opentelemetry/api": "^1.4.1",
|
|
||||||
"@planetscale/database": ">=1.13",
|
|
||||||
"@prisma/client": "*",
|
|
||||||
"@tidbcloud/serverless": "*",
|
|
||||||
"@types/better-sqlite3": "*",
|
|
||||||
"@types/pg": "*",
|
|
||||||
"@types/sql.js": "*",
|
|
||||||
"@upstash/redis": ">=1.34.7",
|
|
||||||
"@vercel/postgres": ">=0.8.0",
|
|
||||||
"@xata.io/client": "*",
|
|
||||||
"better-sqlite3": ">=7",
|
|
||||||
"bun-types": "*",
|
|
||||||
"expo-sqlite": ">=14.0.0",
|
|
||||||
"gel": ">=2",
|
|
||||||
"knex": "*",
|
|
||||||
"kysely": "*",
|
|
||||||
"mysql2": ">=2",
|
|
||||||
"pg": ">=8",
|
|
||||||
"postgres": ">=3",
|
|
||||||
"sql.js": ">=1",
|
|
||||||
"sqlite3": ">=5"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@aws-sdk/client-rds-data": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@cloudflare/workers-types": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@electric-sql/pglite": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@libsql/client": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@libsql/client-wasm": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@neondatabase/serverless": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@op-engineering/op-sqlite": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@opentelemetry/api": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@planetscale/database": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@prisma/client": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@tidbcloud/serverless": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/better-sqlite3": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/pg": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/sql.js": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@upstash/redis": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@vercel/postgres": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@xata.io/client": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"better-sqlite3": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"bun-types": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"expo-sqlite": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"gel": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"knex": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"kysely": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"mysql2": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"pg": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"postgres": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prisma": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"sql.js": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"sqlite3": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.18.2",
|
"version": "5.18.2",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
|
||||||
@@ -2411,19 +1803,6 @@
|
|||||||
"@esbuild/win32-x64": "0.25.8"
|
"@esbuild/win32-x64": "0.25.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild-register": {
|
|
||||||
"version": "3.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
|
|
||||||
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "^4.3.4"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"esbuild": ">=0.12 <1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fetch-blob": {
|
"node_modules/fetch-blob": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||||
@@ -2459,6 +1838,33 @@
|
|||||||
"node": ">=12.20.0"
|
"node": ">=12.20.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.23.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.12.tgz",
|
||||||
|
"integrity": "sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.23.12",
|
||||||
|
"motion-utils": "^12.23.6",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -2855,11 +2261,45 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/motion": {
|
||||||
"version": "2.1.3",
|
"version": "12.23.12",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/motion/-/motion-12.23.12.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-8jCD8uW5GD1csOoqh1WhH1A6j5APHVE15nuBkFeRiMzYBdRwyAHmSP/oXSuW0WJPZRXTFdBoG4hY9TFWNhhwng==",
|
||||||
"dev": true,
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^12.23.12",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.23.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.12.tgz",
|
||||||
|
"integrity": "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.23.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.23.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||||
|
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
@@ -3004,6 +2444,12 @@
|
|||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/pocketbase": {
|
||||||
|
"version": "0.26.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.26.3.tgz",
|
||||||
|
"integrity": "sha512-5deUKRoEczpxxuHzwr6/DHVmgbggxylEVig8CKN+MjvtYxPUqX/C6puU0yaR2yhTi8zrh7J9s7Ty+qBGwVzWOQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||||
@@ -3151,16 +2597,6 @@
|
|||||||
"is-arrayish": "^0.3.1"
|
"is-arrayish": "^0.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map": {
|
|
||||||
"version": "0.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -3170,17 +2606,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-support": {
|
|
||||||
"version": "0.5.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
|
||||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"buffer-from": "^1.0.0",
|
|
||||||
"source-map": "^0.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/styled-jsx": {
|
"node_modules/styled-jsx": {
|
||||||
"version": "5.1.6",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||||
@@ -3346,6 +2771,35 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zustand": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": ">=18.0.0",
|
||||||
|
"immer": ">=9.0.6",
|
||||||
|
"react": ">=18.0.0",
|
||||||
|
"use-sync-external-store": ">=1.2.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"immer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"use-sync-external-store": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,20 +12,21 @@
|
|||||||
"@formkit/auto-animate": "^0.8.2",
|
"@formkit/auto-animate": "^0.8.2",
|
||||||
"@libsql/client": "^0.15.10",
|
"@libsql/client": "^0.15.10",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"drizzle-orm": "^0.44.4",
|
|
||||||
"lightgallery": "^2.9.0-beta.1",
|
"lightgallery": "^2.9.0-beta.1",
|
||||||
|
"motion": "^12.23.12",
|
||||||
"next": "15.4.5",
|
"next": "15.4.5",
|
||||||
|
"pocketbase": "^0.26.3",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"swr": "^2.3.6"
|
"swr": "^2.3.6",
|
||||||
|
"zustand": "^5.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"drizzle-kit": "^0.31.4",
|
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"tsx": "^4.20.3",
|
"tsx": "^4.20.3",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { MovieCard } from "@/components/atoms/MovieCard";
|
import { MovieCard } from '@/components/atoms/MovieCard';
|
||||||
import { ActorHero } from "@/components/molecules/ActorHero";
|
import { ActorHero } from '@/components/molecules/ActorHero';
|
||||||
import { Carousel } from "@/components/molecules/Carousel";
|
import { Carousel } from '@/components/molecules/Carousel';
|
||||||
import { Gallery } from "@/components/molecules/Gallery";
|
import { Gallery } from '@/components/molecules/Gallery';
|
||||||
import { convertToMovie } from "@/helpers/convertToMovie";
|
import { convertToMovie } from '@/helpers/convertToMovie';
|
||||||
import { TMDB } from "@/lib/tmdb";
|
import { TMDB } from '@/lib/tmdb';
|
||||||
import { FaStar } from "react-icons/fa";
|
import { FaStar } from 'react-icons/fa';
|
||||||
|
|
||||||
export default async function Page({
|
export default async function Page({
|
||||||
params,
|
params,
|
||||||
@@ -32,7 +32,7 @@ export default async function Page({
|
|||||||
new Date(b.release_date).getTime() -
|
new Date(b.release_date).getTime() -
|
||||||
new Date(a.release_date).getTime()
|
new Date(a.release_date).getTime()
|
||||||
)
|
)
|
||||||
.map((movie) => {
|
.map(movie => {
|
||||||
const convertedMovie = convertToMovie(movie);
|
const convertedMovie = convertToMovie(movie);
|
||||||
if (!convertedMovie) return null;
|
if (!convertedMovie) return null;
|
||||||
return <MovieCard key={movie.id} {...convertedMovie} />;
|
return <MovieCard key={movie.id} {...convertedMovie} />;
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import { getMovies } from "@/lib/db";
|
|
||||||
import { NextResponse } from "next/server";
|
|
||||||
|
|
||||||
export const GET = async () => {
|
|
||||||
const movies = await getMovies();
|
|
||||||
const res = NextResponse.json(movies);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import "./globals.css";
|
import './globals.css';
|
||||||
|
|
||||||
import { Navbar } from "@/components/organisms/Navbar";
|
import { Navbar } from '@/components/organisms/Navbar';
|
||||||
import { AuroraBackground } from "@/components/effects";
|
import { AuroraBackground } from '@/components/effects';
|
||||||
import { GlobalStoreProvider } from "./store/globalStore";
|
import { DB_getMovies } from '@/lib/db/pb';
|
||||||
import { getMovies } from "@/lib/db";
|
import { GlobalProvider } from './store/global';
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default async function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
const movies = await getMovies();
|
const movies = await DB_getMovies();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="pl">
|
<html lang="pl">
|
||||||
@@ -23,11 +23,11 @@ export default async function RootLayout({
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
</head>
|
</head>
|
||||||
<body className={`antialiased`}>
|
<body className={`antialiased`}>
|
||||||
<GlobalStoreProvider initialMovies={movies}>
|
<GlobalProvider initialMovies={movies}>
|
||||||
<AuroraBackground />
|
<AuroraBackground />
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="relative [&>*:last-child]:pb-16">{children}</main>
|
<main className="relative [&>*:last-child]:pb-16">{children}</main>
|
||||||
</GlobalStoreProvider>
|
</GlobalProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { GenreList } from "@/components/molecules/GenreList";
|
import { GenreList } from '@/components/molecules/GenreList';
|
||||||
import { MovieList } from "@/components/molecules/MovieList";
|
import { MovieList } from '@/components/molecules/MovieList';
|
||||||
import { TrackedMovies } from "@/components/molecules/TrackedMovies";
|
import { RandomMovie } from '@/components/molecules/RandomMovie';
|
||||||
|
import { TrackedMovies } from '@/components/molecules/TrackedMovies';
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TrackedMovies />
|
<TrackedMovies />
|
||||||
<MovieList heading="Moja lista" />
|
<MovieList heading="Moja lista" showSearch />
|
||||||
|
<RandomMovie heading="Ciężko wybrać?" />
|
||||||
<GenreList heading="Odkrywaj nowe filmy według gatunku" />
|
<GenreList heading="Odkrywaj nowe filmy według gatunku" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
107
src/app/store/global.tsx
Normal file
107
src/app/store/global.tsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
'use client';
|
||||||
|
import { Spinner } from '@/components/atoms/Spinner';
|
||||||
|
import { DB_addMovie, DB_deleteMovie, DB_updateMovie } from '@/lib/db/pb';
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
FC,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { create, useStore } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
initialMovies: Movie[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
movies: Movie[];
|
||||||
|
setMovies: (movies: Movie[]) => void;
|
||||||
|
addMovie: (movie: Movie) => void;
|
||||||
|
deleteMovie: (id: number) => void;
|
||||||
|
updateMovie: (id: number, movie: Partial<Movie>) => void;
|
||||||
|
displayType: 'grid' | 'list';
|
||||||
|
setDisplayType: (type: 'grid' | 'list') => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Store = ReturnType<typeof createStore>;
|
||||||
|
|
||||||
|
const createStore = ({ initialMovies }: Props) => {
|
||||||
|
return create<State>()(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
movies: initialMovies,
|
||||||
|
setMovies: (movies: Movie[]) => set({ movies }),
|
||||||
|
|
||||||
|
addMovie: (movie: Movie) => {
|
||||||
|
if (get().movies.find(m => m.id == movie.id)) return;
|
||||||
|
|
||||||
|
DB_addMovie(movie);
|
||||||
|
set(state => ({ movies: [...state.movies, movie] }));
|
||||||
|
},
|
||||||
|
deleteMovie: (id: number) => {
|
||||||
|
DB_deleteMovie(id);
|
||||||
|
set(state => ({
|
||||||
|
movies: state.movies.filter(m => m.id != id),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
updateMovie: (id: number, movie: Partial<Movie>) => {
|
||||||
|
DB_updateMovie(id, movie);
|
||||||
|
set(state => ({
|
||||||
|
movies: state.movies.map(m =>
|
||||||
|
m.id == id ? { ...m, ...movie } : m
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
displayType: 'grid',
|
||||||
|
setDisplayType: (type: 'grid' | 'list') => set({ displayType: type }),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'global',
|
||||||
|
partialize: state => ({ displayType: state.displayType }),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GlobalContext = createContext<Store | null>(null);
|
||||||
|
|
||||||
|
export const GlobalProvider: FC<
|
||||||
|
{
|
||||||
|
children: React.ReactNode;
|
||||||
|
} & Props
|
||||||
|
> = ({ children, initialMovies = [] }) => {
|
||||||
|
const store = useRef(createStore({ initialMovies }));
|
||||||
|
const [firstRender, setFirstRender] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (firstRender) {
|
||||||
|
setFirstRender(false);
|
||||||
|
}
|
||||||
|
}, [firstRender]);
|
||||||
|
|
||||||
|
if (firstRender) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center h-screen bg-black/80">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GlobalContext.Provider value={store.current}>
|
||||||
|
{children}
|
||||||
|
</GlobalContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGlobalStore = <T,>(selector: (state: State) => T): T => {
|
||||||
|
const store = useContext(GlobalContext);
|
||||||
|
if (!store) throw new Error('GlobalStore not found');
|
||||||
|
return useStore(store, selector);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGlobalZustandStore = useGlobalStore;
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import { Spinner } from "@/components/atoms/Spinner";
|
|
||||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
|
||||||
import { addMovieToDB, deleteMovieFromDB, updateMovieInDB } from "@/lib/db";
|
|
||||||
import { movies } from "@/lib/db/schema";
|
|
||||||
import { createContext, FC, use, useEffect, useState } from "react";
|
|
||||||
|
|
||||||
type Movie = typeof movies.$inferSelect;
|
|
||||||
|
|
||||||
type GlobalStore = {
|
|
||||||
movies: Movie[];
|
|
||||||
addMovie: (movie: Movie) => void;
|
|
||||||
deleteMovie: (id: number) => void;
|
|
||||||
updateMovie: (id: number, movie: Partial<Movie>) => void;
|
|
||||||
displayType: "grid" | "list";
|
|
||||||
setDisplayType: (type: "grid" | "list") => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const globalStore = createContext<GlobalStore>({
|
|
||||||
movies: [],
|
|
||||||
addMovie: () => {},
|
|
||||||
deleteMovie: () => {},
|
|
||||||
updateMovie: () => {},
|
|
||||||
displayType: "grid",
|
|
||||||
setDisplayType: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
initialMovies?: Movie[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GlobalStoreProvider: FC<Props> = ({
|
|
||||||
children,
|
|
||||||
initialMovies = [],
|
|
||||||
}) => {
|
|
||||||
// Optimistic update
|
|
||||||
const [firstRender, setFirstRender] = useState(true);
|
|
||||||
const [movies, setMovies] = useState<GlobalStore["movies"]>(initialMovies);
|
|
||||||
const [displayType, setDisplayType] = useLocalStorage<
|
|
||||||
GlobalStore["displayType"]
|
|
||||||
>("displayType", "grid");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (firstRender) {
|
|
||||||
setFirstRender(false);
|
|
||||||
}
|
|
||||||
}, [firstRender]);
|
|
||||||
|
|
||||||
const addMovie = async (movie: Movie) => {
|
|
||||||
if (movies.find((m) => m.id === movie.id)) return;
|
|
||||||
|
|
||||||
addMovieToDB(movie);
|
|
||||||
setMovies((prev) => [...prev, movie]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteMovie = async (id: number) => {
|
|
||||||
deleteMovieFromDB(id);
|
|
||||||
setMovies((prev) => prev.filter((m) => m.id !== id));
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateMovie = async (id: number, movie: Partial<Movie>) => {
|
|
||||||
updateMovieInDB(id, movie);
|
|
||||||
setMovies((prev) =>
|
|
||||||
prev.map((m) => (m.id === id ? { ...m, ...movie } : m))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<globalStore.Provider
|
|
||||||
value={{
|
|
||||||
movies,
|
|
||||||
addMovie,
|
|
||||||
deleteMovie,
|
|
||||||
updateMovie,
|
|
||||||
displayType,
|
|
||||||
setDisplayType,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{firstRender ? (
|
|
||||||
<div className="flex justify-center items-center h-screen bg-black/80">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
children
|
|
||||||
)}
|
|
||||||
</globalStore.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGlobalStore = () => {
|
|
||||||
return use(globalStore);
|
|
||||||
};
|
|
||||||
@@ -29,10 +29,14 @@ export const Button: FC<Props> = ({
|
|||||||
|
|
||||||
const buttonColor = gradient ?? colors[theme];
|
const buttonColor = gradient ?? colors[theme];
|
||||||
|
|
||||||
|
if (theme === "slate" && !className.includes("shadow-")) {
|
||||||
|
className += " shadow-cyan-500/20 hover:shadow-cyan-500/40";
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
className={`flex items-center justify-center gap-2 cursor-pointer text-white rounded-xl font-semibold shadow-2xl transition-colors duration-300
|
className={`flex items-center justify-center gap-2 cursor-pointer text-white rounded-xl font-semibold shadow-2xl transition-all duration-300
|
||||||
bg-gradient-to-r ${buttonColor?.from} ${buttonColor?.to} cursor-pointer ${sizes[size]} ${className}`}
|
bg-gradient-to-br ${buttonColor?.from} ${buttonColor?.to} cursor-pointer ${sizes[size]} ${className}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
{...(href && { href })}
|
{...(href && { href })}
|
||||||
>
|
>
|
||||||
@@ -45,7 +49,7 @@ const sizes = {
|
|||||||
small: "px-4 py-2 text-sm",
|
small: "px-4 py-2 text-sm",
|
||||||
medium: "px-8 py-4 text-lg",
|
medium: "px-8 py-4 text-lg",
|
||||||
large: "px-12 py-6 text-xl",
|
large: "px-12 py-6 text-xl",
|
||||||
icon: "p-3 [&>*]:w-5 [&>*]:h-5",
|
icon: "w-12 h-12 !rounded-full border border-white/20 hover:scale-105 [&>svg]:w-5 [&>svg]:h-5 shadow-lg ",
|
||||||
};
|
};
|
||||||
|
|
||||||
const colors = {
|
const colors = {
|
||||||
@@ -55,30 +59,38 @@ const colors = {
|
|||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
from: "from-purple-600 hover:from-purple-500",
|
from: "from-purple-600 hover:from-purple-500",
|
||||||
to: "to-pink-600 hover:to-pink-500",
|
to: "to-cyan-600 hover:to-cyan-500",
|
||||||
},
|
},
|
||||||
glass: {
|
glass: {
|
||||||
from: "from-white/15 via-white/8 to-white/12 border border-white/20",
|
from: "from-white/15 border border-white/20",
|
||||||
to: "to-white/15 hover:to-white/10",
|
to: "to-white/5 hover:to-white/10",
|
||||||
},
|
},
|
||||||
rose: {
|
rosePink: {
|
||||||
from: "from-rose-600/90 hover:from-rose-500/90",
|
from: "from-rose-600/90 hover:from-rose-500/90",
|
||||||
to: "to-pink-600/90 hover:to-pink-500/90",
|
to: "to-pink-600/90 hover:to-pink-500/90",
|
||||||
},
|
},
|
||||||
emerald: {
|
emeraldTeal: {
|
||||||
from: "from-emerald-600/90 hover:from-emerald-500/90",
|
from: "from-emerald-600/90 hover:from-emerald-500/90",
|
||||||
to: "to-teal-600/90 hover:to-teal-500/90",
|
to: "to-teal-600/90 hover:to-teal-500/90",
|
||||||
},
|
},
|
||||||
purple: {
|
purplePink: {
|
||||||
from: "from-purple-600/90 hover:from-purple-500/90",
|
from: "from-purple-600/90 hover:from-purple-500/90",
|
||||||
to: "to-pink-600/90 hover:to-pink-500/90",
|
to: "to-pink-600/90 hover:to-pink-500/90",
|
||||||
},
|
},
|
||||||
pink: {
|
pinkEmerald: {
|
||||||
from: "from-pink-600/90 hover:from-pink-500/90",
|
from: "from-pink-600/90 hover:from-pink-500/90",
|
||||||
to: "to-emerald-600/90 hover:to-emerald-500/90",
|
to: "to-emerald-600/90 hover:to-emerald-500/90",
|
||||||
},
|
},
|
||||||
teal: {
|
tealEmerald: {
|
||||||
from: "from-teal-600/90 hover:from-teal-500/90",
|
from: "from-teal-600/90 hover:from-teal-500/90",
|
||||||
to: "to-emerald-600/90 hover:to-emerald-500/90",
|
to: "to-emerald-600/90 hover:to-emerald-500/90",
|
||||||
},
|
},
|
||||||
|
cyanPurple: {
|
||||||
|
from: "from-cyan-600/90 hover:from-cyan-500/90",
|
||||||
|
to: "to-purple-600/90 hover:to-purple-500/90",
|
||||||
|
},
|
||||||
|
slate: {
|
||||||
|
from: "from-slate-800/95",
|
||||||
|
to: "to-slate-900/95",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const Dropdown: FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className="relative inline-block">
|
<div ref={ref} className="relative inline-block">
|
||||||
<Button theme="glass" size="icon" onClick={() => setIsOpen(!isOpen)}>
|
<Button theme="slate" size="icon" onClick={() => setIsOpen(!isOpen)}>
|
||||||
{icon || <FaFilter />}
|
{icon || <FaFilter />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
"use client";
|
'use client';
|
||||||
import { FC } from "react";
|
import { FC } from 'react';
|
||||||
import { useGlobalStore } from "@/app/store/globalStore";
|
import { FaFire, FaPlusCircle, FaTrash } from 'react-icons/fa';
|
||||||
import { Movie } from "@/types/global";
|
import Link from 'next/link';
|
||||||
import { FaFire, FaPlusCircle, FaTrash } from "react-icons/fa";
|
import { RxEyeOpen } from 'react-icons/rx';
|
||||||
import Link from "next/link";
|
import { MdFavorite } from 'react-icons/md';
|
||||||
import { RxEyeOpen } from "react-icons/rx";
|
import { RiCalendarCheckLine, RiCalendarScheduleLine } from 'react-icons/ri';
|
||||||
import { MdFavorite } from "react-icons/md";
|
import { useGlobalStore } from '@/app/store/global';
|
||||||
import { RiCalendarCheckLine, RiCalendarScheduleLine } from "react-icons/ri";
|
|
||||||
|
|
||||||
type Props = Movie & {
|
type Props = Movie & {
|
||||||
showDayCounter?: boolean;
|
showDayCounter?: boolean;
|
||||||
@@ -18,16 +17,14 @@ export const MovieCard: FC<Props> = ({
|
|||||||
simpleToggle = false,
|
simpleToggle = false,
|
||||||
...movie
|
...movie
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const movies = useGlobalStore(state => state.movies);
|
||||||
movies,
|
const addMovie = useGlobalStore(state => state.addMovie);
|
||||||
addMovie: addMovieToStore,
|
const deleteMovie = useGlobalStore(state => state.deleteMovie);
|
||||||
deleteMovie: deleteMovieFromStore,
|
const updateMovie = useGlobalStore(state => state.updateMovie);
|
||||||
updateMovie: updateMovieInStore,
|
|
||||||
} = useGlobalStore();
|
|
||||||
const { vote_average, popularity, poster_path, title, overview } = movie;
|
const { vote_average, popularity, poster_path, title, overview } = movie;
|
||||||
|
|
||||||
const { id } = movie;
|
const { id } = movie;
|
||||||
const alreadyInStore = movies.find((m) => m.id === id);
|
const alreadyInStore = movies.find(m => m.id == id);
|
||||||
|
|
||||||
const seen = alreadyInStore?.seen || movie.seen;
|
const seen = alreadyInStore?.seen || movie.seen;
|
||||||
const favorite = alreadyInStore?.favorite || movie.favorite;
|
const favorite = alreadyInStore?.favorite || movie.favorite;
|
||||||
@@ -35,28 +32,28 @@ export const MovieCard: FC<Props> = ({
|
|||||||
const isReleased = new Date(movie.release_date) < new Date();
|
const isReleased = new Date(movie.release_date) < new Date();
|
||||||
const scoreColor =
|
const scoreColor =
|
||||||
vote_average >= 8
|
vote_average >= 8
|
||||||
? "from-emerald-400 to-teal-400"
|
? 'from-emerald-400 to-teal-400'
|
||||||
: vote_average >= 6
|
: vote_average >= 6
|
||||||
? "from-yellow-400 to-orange-400"
|
? 'from-yellow-400 to-orange-400'
|
||||||
: "from-red-400 to-pink-400";
|
: 'from-red-400 to-pink-400';
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
addMovieToStore(movie);
|
addMovie(movie);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemove = () => {
|
const handleRemove = () => {
|
||||||
deleteMovieFromStore(id);
|
deleteMovie(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSeen = () => {
|
const handleSeen = () => {
|
||||||
updateMovieInStore(id, {
|
updateMovie(id, {
|
||||||
seen: !movie.seen,
|
seen: !movie.seen,
|
||||||
favorite: false,
|
favorite: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFavorite = () => {
|
const handleFavorite = () => {
|
||||||
updateMovieInStore(id, {
|
updateMovie(id, {
|
||||||
favorite: !movie.favorite,
|
favorite: !movie.favorite,
|
||||||
seen: movie.seen || !movie.favorite,
|
seen: movie.seen || !movie.favorite,
|
||||||
});
|
});
|
||||||
@@ -129,8 +126,8 @@ export const MovieCard: FC<Props> = ({
|
|||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
seen
|
seen
|
||||||
? "bg-gradient-to-r from-emerald-500/95 to-emerald-600/90"
|
? 'bg-gradient-to-r from-emerald-500/95 to-emerald-600/90'
|
||||||
: "bg-gradient-to-r from-white/25 to-white/15"
|
: 'bg-gradient-to-r from-white/25 to-white/15'
|
||||||
} p-2 rounded-full cursor-pointer hover:bg-emerald-400 transition-colors border border-white/10 shadow-lg`}
|
} p-2 rounded-full cursor-pointer hover:bg-emerald-400 transition-colors border border-white/10 shadow-lg`}
|
||||||
onClick={handleSeen}
|
onClick={handleSeen}
|
||||||
>
|
>
|
||||||
@@ -139,8 +136,8 @@ export const MovieCard: FC<Props> = ({
|
|||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
favorite
|
favorite
|
||||||
? "bg-gradient-to-r from-rose-500/95 to-rose-600/90"
|
? 'bg-gradient-to-r from-rose-500/95 to-rose-600/90'
|
||||||
: "bg-gradient-to-r from-white/25 to-white/15"
|
: 'bg-gradient-to-r from-white/25 to-white/15'
|
||||||
} p-2 rounded-full cursor-pointer hover:bg-rose-400 transition-colors border border-white/10 shadow-lg`}
|
} p-2 rounded-full cursor-pointer hover:bg-rose-400 transition-colors border border-white/10 shadow-lg`}
|
||||||
onClick={handleFavorite}
|
onClick={handleFavorite}
|
||||||
>
|
>
|
||||||
@@ -185,7 +182,7 @@ export const MovieCard: FC<Props> = ({
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-1 text-sm ${
|
className={`flex items-center gap-1 text-sm ${
|
||||||
isReleased ? "text-emerald-400" : "text-amber-400"
|
isReleased ? 'text-emerald-400' : 'text-amber-400'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isReleased ? (
|
{isReleased ? (
|
||||||
@@ -194,10 +191,10 @@ export const MovieCard: FC<Props> = ({
|
|||||||
<RiCalendarScheduleLine />
|
<RiCalendarScheduleLine />
|
||||||
)}
|
)}
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{releaseDate.toLocaleDateString("pl-PL", {
|
{releaseDate.toLocaleDateString('pl-PL', {
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
month: "short",
|
month: 'short',
|
||||||
year: "numeric",
|
year: 'numeric',
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { formatter } from "@/helpers/formater";
|
'use client';
|
||||||
import { Movie } from "@/types/global";
|
import { formatter } from '@/helpers/formater';
|
||||||
import Link from "next/link";
|
import Link from 'next/link';
|
||||||
import { FC } from "react";
|
import { FC } from 'react';
|
||||||
import { FaCalendar, FaClock, FaStar } from "react-icons/fa";
|
import { FaCalendar, FaClock, FaStar, FaEye, FaHeart } from 'react-icons/fa';
|
||||||
|
import { useGlobalStore } from '@/app/store/global';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
movie: Movie;
|
movie: Movie;
|
||||||
@@ -15,6 +16,8 @@ export const MovieRow: FC<Props> = ({
|
|||||||
isUpcoming = false,
|
isUpcoming = false,
|
||||||
compact = false,
|
compact = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const movies = useGlobalStore(state => state.movies);
|
||||||
|
|
||||||
const daysSinceRelease = Math.abs(
|
const daysSinceRelease = Math.abs(
|
||||||
Math.floor(
|
Math.floor(
|
||||||
(new Date().getTime() - new Date(movie.release_date).getTime()) /
|
(new Date().getTime() - new Date(movie.release_date).getTime()) /
|
||||||
@@ -22,60 +25,78 @@ export const MovieRow: FC<Props> = ({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check if movie is already in store.
|
||||||
|
const movieInStore = movies.find(m => m.id == movie.id);
|
||||||
|
const isWatched = movieInStore?.seen || false;
|
||||||
|
const isFavorite = movieInStore?.favorite || false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<div className="relative overflow-hidden rounded-xl">
|
||||||
href={`/film/${movie.id}`}
|
<Link
|
||||||
className="flex items-center gap-4 p-3 rounded-lg bg-gray-800/30 hover:bg-gray-800/50 transition-colors group"
|
href={`/film/${movie.id}`}
|
||||||
>
|
draggable={false}
|
||||||
<div className="relative w-12 h-16 rounded overflow-hidden flex-shrink-0">
|
className="flex items-center gap-4 p-3 rounded-lg bg-gray-800 hover:bg-gray-800 transition-colors group"
|
||||||
<img
|
>
|
||||||
src={`https://image.tmdb.org/t/p/w154${movie.poster_path}`}
|
<div className="relative w-12 h-16 rounded overflow-hidden flex-shrink-0">
|
||||||
alt={movie.title}
|
<img
|
||||||
className="object-cover inset-0"
|
src={`https://image.tmdb.org/t/p/w154${movie.poster_path}`}
|
||||||
sizes="48px"
|
alt={movie.title}
|
||||||
/>
|
className="object-cover inset-0"
|
||||||
</div>
|
sizes="48px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="text-white font-medium text-sm truncate group-hover:text-blue-400 transition-colors">
|
<h3 className="text-white font-medium text-sm truncate group-hover:text-blue-400 transition-colors">
|
||||||
{movie.title}
|
{movie.title}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-center gap-3 mt-1">
|
<div className="flex items-center gap-3 mt-1">
|
||||||
<div className="flex items-center gap-1 text-gray-400 text-xs">
|
<div className="flex items-center gap-1 text-gray-400 text-xs">
|
||||||
{isUpcoming ? (
|
{isUpcoming ? (
|
||||||
<FaCalendar className="w-3 h-3" />
|
<FaCalendar className="w-3 h-3" />
|
||||||
) : (
|
) : (
|
||||||
<FaClock className="w-3 h-3" />
|
<FaClock className="w-3 h-3" />
|
||||||
)}
|
)}
|
||||||
<span>{formatter.formatDate(movie.release_date)}</span>
|
<span>{formatter.formatDate(movie.release_date)}</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
{!!movie.vote_average && (
|
|
||||||
<div className="flex items-center gap-1 text-yellow-400 text-xs">
|
|
||||||
<FaStar className="w-3 h-3 fill-current" />
|
|
||||||
<span>{movie.vote_average.toFixed(1)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{movie.favorite && (
|
{!!movie.vote_average && (
|
||||||
<div className="w-2 h-2 bg-red-500 rounded-full" title="Ulubione" />
|
<div className="flex items-center gap-1 text-yellow-400 text-xs">
|
||||||
)}
|
<FaStar className="w-3 h-3 fill-current" />
|
||||||
</div>
|
<span>{movie.vote_average.toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{!compact && (
|
{(isFavorite || movie.favorite) && (
|
||||||
<div
|
<div
|
||||||
className={`text-xs px-2 py-1 rounded-full font-medium ${
|
className="w-2 h-2 bg-red-500 rounded-full"
|
||||||
isUpcoming
|
title="Ulubione"
|
||||||
? "bg-blue-500/20 text-blue-400"
|
/>
|
||||||
: "bg-green-500/20 text-green-400"
|
)}
|
||||||
}`}
|
|
||||||
>
|
{(isWatched || movie.seen) && (
|
||||||
{isUpcoming
|
<div
|
||||||
? `za ${daysSinceRelease} dni`
|
className="w-2 h-2 bg-green-500 rounded-full"
|
||||||
: `od ${daysSinceRelease} dni`}
|
title="Obejrzane"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</Link>
|
{!compact && (
|
||||||
|
<div
|
||||||
|
className={`text-xs px-2 py-1 rounded-full font-medium ${
|
||||||
|
isUpcoming
|
||||||
|
? 'bg-blue-500/20 text-blue-400'
|
||||||
|
: 'bg-green-500/20 text-green-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isUpcoming
|
||||||
|
? `za ${daysSinceRelease} dni`
|
||||||
|
: `od ${daysSinceRelease} dni`}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ export const Pagination: FC<Props> = ({
|
|||||||
onPageChange,
|
onPageChange,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<ul className="flex justify-center gap-3 my-10">
|
<ul className="flex justify-center gap-3 my-10 items-center">
|
||||||
{currentPage > 1 && (
|
{currentPage > 1 && (
|
||||||
<li>
|
<li>
|
||||||
<Button
|
<Button
|
||||||
theme="glass"
|
|
||||||
size="icon"
|
size="icon"
|
||||||
|
theme="slate"
|
||||||
|
className="shadow-amber-400/20 hover:shadow-amber-400/40 "
|
||||||
aria-label="Previous page"
|
aria-label="Previous page"
|
||||||
onClick={() => onPageChange(currentPage - 1)}
|
onClick={() => onPageChange(currentPage - 1)}
|
||||||
>
|
>
|
||||||
@@ -38,15 +39,16 @@ export const Pagination: FC<Props> = ({
|
|||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<li className="text-sm/8 font-medium tracking-widest leading-[2.8]">
|
<li className="text-sm/8 font-medium tracking-widest">
|
||||||
{currentPage}/{totalPages}
|
{currentPage}/{totalPages}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{currentPage < totalPages && (
|
{currentPage < totalPages && (
|
||||||
<li>
|
<li>
|
||||||
<Button
|
<Button
|
||||||
theme="glass"
|
theme="slate"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
className="shadow-amber-400/20 hover:shadow-amber-400/40"
|
||||||
aria-label="Next page"
|
aria-label="Next page"
|
||||||
onClick={() => onPageChange(currentPage + 1)}
|
onClick={() => onPageChange(currentPage + 1)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { IoSearch } from "react-icons/io5";
|
import { IoSearch } from "react-icons/io5";
|
||||||
|
import { Button } from "../Button";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -30,19 +31,21 @@ export const SearchInput: FC<Props> = ({
|
|||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="relative flex items-center gap-4">
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
name="search"
|
name="search"
|
||||||
value={value}
|
value={value}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className={className}
|
className={
|
||||||
|
"w-full bg-gradient-to-br from-slate-800/95 to-slate-900/90 border border-white/20 rounded-full h-12 px-6 shadow-lg shadow-purple-500/15 outline-none focus:shadow-purple-500/20"
|
||||||
|
}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
/>
|
/>
|
||||||
<button type="submit" className="absolute right-0 top-1 mt-3 mr-4">
|
<Button theme="slate" size="icon" className="shrink-0">
|
||||||
<IoSearch />
|
<IoSearch />
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client";
|
'use client';
|
||||||
|
|
||||||
import { BackButton } from "@/components/atoms/BackButton";
|
import { BackButton } from '@/components/atoms/BackButton';
|
||||||
import { formatter } from "@/helpers/formater";
|
import { formatter } from '@/helpers/formater';
|
||||||
import { PersonDetailsRich } from "@/lib/tmdb/types";
|
import { PersonDetailsRich } from '@/lib/tmdb/types';
|
||||||
import { FC } from "react";
|
import { FC } from 'react';
|
||||||
import {
|
import {
|
||||||
FaCalendarAlt,
|
FaCalendarAlt,
|
||||||
FaMapMarkerAlt,
|
FaMapMarkerAlt,
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
FaTwitter,
|
FaTwitter,
|
||||||
FaYoutube,
|
FaYoutube,
|
||||||
FaTiktok,
|
FaTiktok,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
personDetails: PersonDetailsRich;
|
personDetails: PersonDetailsRich;
|
||||||
@@ -37,13 +37,13 @@ export const ActorHero: FC<Props> = ({ personDetails }) => {
|
|||||||
const getGenderText = (gender: number) => {
|
const getGenderText = (gender: number) => {
|
||||||
switch (gender) {
|
switch (gender) {
|
||||||
case 1:
|
case 1:
|
||||||
return "Kobieta";
|
return 'Kobieta';
|
||||||
case 2:
|
case 2:
|
||||||
return "Mężczyzna";
|
return 'Mężczyzna';
|
||||||
case 3:
|
case 3:
|
||||||
return "Niebinarne";
|
return 'Niebinarne';
|
||||||
default:
|
default:
|
||||||
return "Nie określono";
|
return 'Nie określono';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ export const ActorHero: FC<Props> = ({ personDetails }) => {
|
|||||||
src={
|
src={
|
||||||
personDetails.profile_path
|
personDetails.profile_path
|
||||||
? `https://image.tmdb.org/t/p/w500${personDetails.profile_path}`
|
? `https://image.tmdb.org/t/p/w500${personDetails.profile_path}`
|
||||||
: "/api/placeholder/400/600"
|
: '/api/placeholder/400/600'
|
||||||
}
|
}
|
||||||
alt={personDetails.name}
|
alt={personDetails.name}
|
||||||
className="w-80 h-auto rounded-2xl shadow-2xl shadow-purple-500/20 group-hover:shadow-purple-500/40 transition-all duration-500"
|
className="w-80 h-auto rounded-2xl shadow-2xl shadow-purple-500/20 group-hover:shadow-purple-500/40 transition-all duration-500"
|
||||||
@@ -88,9 +88,9 @@ export const ActorHero: FC<Props> = ({ personDetails }) => {
|
|||||||
{calculateAge(
|
{calculateAge(
|
||||||
personDetails.birthday,
|
personDetails.birthday,
|
||||||
personDetails.deathday
|
personDetails.deathday
|
||||||
)}{" "}
|
)}{' '}
|
||||||
lat
|
lat
|
||||||
{personDetails.deathday && " w chwili śmierci"})
|
{personDetails.deathday && ' w chwili śmierci'})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -114,8 +114,8 @@ export const ActorHero: FC<Props> = ({ personDetails }) => {
|
|||||||
{personDetails.also_known_as.length > 0 && (
|
{personDetails.also_known_as.length > 0 && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-gray-400 text-sm">
|
<p className="text-gray-400 text-sm">
|
||||||
Znany również jako:{" "}
|
Znany również jako:{' '}
|
||||||
{personDetails.also_known_as.slice(0, 3).join(", ")}
|
{personDetails.also_known_as.slice(0, 3).join(', ')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -171,7 +171,7 @@ export const ActorHero: FC<Props> = ({ personDetails }) => {
|
|||||||
</h3>
|
</h3>
|
||||||
<div className="text-gray-300 leading-relaxed text-lg space-y-4">
|
<div className="text-gray-300 leading-relaxed text-lg space-y-4">
|
||||||
{personDetails.biography
|
{personDetails.biography
|
||||||
.split("\n\n")
|
.split('\n\n')
|
||||||
.map((paragraph, index) => (
|
.map((paragraph, index) => (
|
||||||
<p key={index}>{paragraph}</p>
|
<p key={index}>{paragraph}</p>
|
||||||
))}
|
))}
|
||||||
@@ -185,7 +185,7 @@ export const ActorHero: FC<Props> = ({ personDetails }) => {
|
|||||||
<h3 className="text-lg font-semibold mb-3 text-purple-300">
|
<h3 className="text-lg font-semibold mb-3 text-purple-300">
|
||||||
Linki
|
Linki
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
{Object.entries(personDetails.external_ids).map(
|
{Object.entries(personDetails.external_ids).map(
|
||||||
([key, value]) => {
|
([key, value]) => {
|
||||||
if (!(key in externalIdsMap) || !value) {
|
if (!(key in externalIdsMap) || !value) {
|
||||||
@@ -222,32 +222,32 @@ export const ActorHero: FC<Props> = ({ personDetails }) => {
|
|||||||
|
|
||||||
const externalIdsMap = {
|
const externalIdsMap = {
|
||||||
facebook_id: {
|
facebook_id: {
|
||||||
label: "Facebook",
|
label: 'Facebook',
|
||||||
icon: <FaFacebook />,
|
icon: <FaFacebook />,
|
||||||
url: (id: string) => `https://www.facebook.com/${id}`,
|
url: (id: string) => `https://www.facebook.com/${id}`,
|
||||||
},
|
},
|
||||||
instagram_id: {
|
instagram_id: {
|
||||||
label: "Instagram",
|
label: 'Instagram',
|
||||||
icon: <FaInstagram />,
|
icon: <FaInstagram />,
|
||||||
url: (id: string) => `https://www.instagram.com/${id}`,
|
url: (id: string) => `https://www.instagram.com/${id}`,
|
||||||
},
|
},
|
||||||
twitter_id: {
|
twitter_id: {
|
||||||
label: "Twitter",
|
label: 'Twitter',
|
||||||
icon: <FaTwitter />,
|
icon: <FaTwitter />,
|
||||||
url: (id: string) => `https://www.twitter.com/${id}`,
|
url: (id: string) => `https://www.twitter.com/${id}`,
|
||||||
},
|
},
|
||||||
tiktok_id: {
|
tiktok_id: {
|
||||||
label: "TikTok",
|
label: 'TikTok',
|
||||||
icon: <FaTiktok />,
|
icon: <FaTiktok />,
|
||||||
url: (id: string) => `https://www.tiktok.com/${id}`,
|
url: (id: string) => `https://www.tiktok.com/${id}`,
|
||||||
},
|
},
|
||||||
youtube_id: {
|
youtube_id: {
|
||||||
label: "YouTube",
|
label: 'YouTube',
|
||||||
icon: <FaYoutube />,
|
icon: <FaYoutube />,
|
||||||
url: (id: string) => `https://www.youtube.com/${id}`,
|
url: (id: string) => `https://www.youtube.com/${id}`,
|
||||||
},
|
},
|
||||||
imdb_id: {
|
imdb_id: {
|
||||||
label: "IMDb",
|
label: 'IMDb',
|
||||||
icon: <FaImdb />,
|
icon: <FaImdb />,
|
||||||
url: (id: string) => `https://www.imdb.com/name/${id}`,
|
url: (id: string) => `https://www.imdb.com/name/${id}`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const Gallery: FC<Props> = ({
|
|||||||
{limit < currentImages.length && (
|
{limit < currentImages.length && (
|
||||||
<div className="flex justify-center mt-6">
|
<div className="flex justify-center mt-6">
|
||||||
<Button
|
<Button
|
||||||
theme="teal"
|
theme="emeraldTeal"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => setLimit(currentImages.length)}
|
onClick={() => setLimit(currentImages.length)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
"use client";
|
'use client';
|
||||||
import { BackButton } from "@/components/atoms/BackButton";
|
import { BackButton } from '@/components/atoms/BackButton';
|
||||||
import { Button } from "@/components/atoms/Button";
|
import { Button } from '@/components/atoms/Button';
|
||||||
import { GenreLabel } from "@/components/atoms/GenreLabel";
|
import { GenreLabel } from '@/components/atoms/GenreLabel';
|
||||||
import { MovieDetailsRich } from "@/lib/tmdb/types";
|
import { MovieDetailsRich } from '@/lib/tmdb/types';
|
||||||
import { useGlobalStore } from "@/app/store/globalStore";
|
import { FC } from 'react';
|
||||||
import { FC } from "react";
|
|
||||||
import {
|
import {
|
||||||
FaHeart,
|
FaHeart,
|
||||||
FaBookmark,
|
FaBookmark,
|
||||||
@@ -12,19 +11,23 @@ import {
|
|||||||
FaCalendar,
|
FaCalendar,
|
||||||
FaGlobe,
|
FaGlobe,
|
||||||
FaEye,
|
FaEye,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa';
|
||||||
import { convertToMovie } from "@/helpers/convertToMovie";
|
import { convertToMovie } from '@/helpers/convertToMovie';
|
||||||
import { formatter } from "@/helpers/formater";
|
import { formatter } from '@/helpers/formater';
|
||||||
|
import { useGlobalStore } from '@/app/store/global';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
movieDetails: MovieDetailsRich;
|
movieDetails: MovieDetailsRich;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HeroMovie: FC<Props> = ({ movieDetails }) => {
|
export const HeroMovie: FC<Props> = ({ movieDetails }) => {
|
||||||
const { movies, addMovie, deleteMovie, updateMovie } = useGlobalStore();
|
const movies = useGlobalStore(state => state.movies);
|
||||||
|
const addMovie = useGlobalStore(state => state.addMovie);
|
||||||
|
const deleteMovie = useGlobalStore(state => state.deleteMovie);
|
||||||
|
const updateMovie = useGlobalStore(state => state.updateMovie);
|
||||||
|
|
||||||
// Check if movie is in store and get its state.
|
// Check if movie is in store and get its state.
|
||||||
const movieInStore = movies.find((m) => m.id === movieDetails.id);
|
const movieInStore = movies.find(m => m.id == movieDetails.id);
|
||||||
const isInStore = !!movieInStore;
|
const isInStore = !!movieInStore;
|
||||||
const isFavorite = movieInStore?.favorite || false;
|
const isFavorite = movieInStore?.favorite || false;
|
||||||
const isSeen = movieInStore?.seen || false;
|
const isSeen = movieInStore?.seen || false;
|
||||||
@@ -115,8 +118,8 @@ export const HeroMovie: FC<Props> = ({ movieDetails }) => {
|
|||||||
key={i}
|
key={i}
|
||||||
className={`text-2xl ${
|
className={`text-2xl ${
|
||||||
i < Math.round(movieDetails.vote_average / 2)
|
i < Math.round(movieDetails.vote_average / 2)
|
||||||
? "text-yellow-400"
|
? 'text-yellow-400'
|
||||||
: "text-gray-600"
|
: 'text-gray-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
★
|
★
|
||||||
@@ -173,7 +176,7 @@ export const HeroMovie: FC<Props> = ({ movieDetails }) => {
|
|||||||
Gatunki
|
Gatunki
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{movieDetails.genres.map((genre) => (
|
{movieDetails.genres.map(genre => (
|
||||||
<GenreLabel
|
<GenreLabel
|
||||||
key={genre.id}
|
key={genre.id}
|
||||||
genre={genre.name}
|
genre={genre.name}
|
||||||
@@ -203,47 +206,47 @@ export const HeroMovie: FC<Props> = ({ movieDetails }) => {
|
|||||||
gradient={
|
gradient={
|
||||||
isInStore
|
isInStore
|
||||||
? {
|
? {
|
||||||
from: "from-purple-600 hover:from-purple-500",
|
from: 'from-purple-600 hover:from-purple-500',
|
||||||
to: "to-emerald-600 hover:to-emerald-500",
|
to: 'to-emerald-600 hover:to-emerald-500',
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
from: "from-purple-600 hover:from-purple-500",
|
from: 'from-purple-600 hover:from-purple-500',
|
||||||
to: "to-pink-600 hover:to-pink-500",
|
to: 'to-pink-600 hover:to-pink-500',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
className={`flex items-center gap-3`}
|
className={`flex items-center gap-3`}
|
||||||
onClick={handleAddToList}
|
onClick={handleAddToList}
|
||||||
>
|
>
|
||||||
<FaBookmark />
|
<FaBookmark />
|
||||||
{isInStore ? "Usuń z listy" : "Dodaj do listy"}
|
{isInStore ? 'Usuń z listy' : 'Dodaj do listy'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
theme={isFavorite ? "rose" : "glass"}
|
theme={isFavorite ? 'rosePink' : 'glass'}
|
||||||
className={`flex items-center gap-3 ${
|
className={`flex items-center gap-3 ${
|
||||||
isFavorite
|
isFavorite
|
||||||
? "bg-gradient-to-r border-rose-400/30"
|
? 'bg-gradient-to-r border-rose-400/30'
|
||||||
: ""
|
: ''
|
||||||
}`}
|
}`}
|
||||||
onClick={handleToggleFavorite}
|
onClick={handleToggleFavorite}
|
||||||
>
|
>
|
||||||
<FaHeart
|
<FaHeart
|
||||||
className={isFavorite ? "text-rose-200" : ""}
|
className={isFavorite ? 'text-rose-200' : ''}
|
||||||
/>
|
/>
|
||||||
{isFavorite
|
{isFavorite
|
||||||
? "Usuń z ulubionych"
|
? 'Usuń z ulubionych'
|
||||||
: "Dodaj do ulubionych"}
|
: 'Dodaj do ulubionych'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
theme={isSeen ? "emerald" : "glass"}
|
theme={isSeen ? 'emeraldTeal' : 'glass'}
|
||||||
className={`flex items-center gap-3 ${
|
className={`flex items-center gap-3 ${
|
||||||
isSeen ? "bg-gradient-to-r border-emerald-400/30" : ""
|
isSeen ? 'bg-gradient-to-r border-emerald-400/30' : ''
|
||||||
}`}
|
}`}
|
||||||
onClick={handleToggleSeen}
|
onClick={handleToggleSeen}
|
||||||
>
|
>
|
||||||
<FaEye className={isSeen ? "text-emerald-200" : ""} />
|
<FaEye className={isSeen ? 'text-emerald-200' : ''} />
|
||||||
{isSeen
|
{isSeen
|
||||||
? "Oznacz jako nieobejrzany"
|
? 'Oznacz jako nieobejrzany'
|
||||||
: "Oznacz jako obejrzany"}
|
: 'Oznacz jako obejrzany'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client";
|
'use client';
|
||||||
import Link from "next/link";
|
import Link from 'next/link';
|
||||||
import { Button } from "@/components/atoms/Button";
|
import { Button } from '@/components/atoms/Button';
|
||||||
import { MovieDetailsRich } from "@/lib/tmdb/types";
|
import { MovieDetailsRich } from '@/lib/tmdb/types';
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from 'react';
|
||||||
import { FaDollarSign } from "react-icons/fa";
|
import { FaDollarSign } from 'react-icons/fa';
|
||||||
import { formatter } from "@/helpers/formater";
|
import { formatter } from '@/helpers/formater';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
movieDetails: MovieDetailsRich;
|
movieDetails: MovieDetailsRich;
|
||||||
@@ -13,16 +13,10 @@ type Props = {
|
|||||||
export const MovieCast: FC<Props> = ({ movieDetails }) => {
|
export const MovieCast: FC<Props> = ({ movieDetails }) => {
|
||||||
const [limit, setLimit] = useState(8);
|
const [limit, setLimit] = useState(8);
|
||||||
const director = movieDetails?.credits.crew.find(
|
const director = movieDetails?.credits.crew.find(
|
||||||
(member) => member.job === "Director"
|
member => member.job === 'Director'
|
||||||
);
|
);
|
||||||
const mainCast = movieDetails?.credits.cast.slice(0, limit) || [];
|
const mainCast = movieDetails?.credits.cast.slice(0, limit) || [];
|
||||||
|
|
||||||
const formatCurrency = (amount: number) =>
|
|
||||||
new Intl.NumberFormat("pl-PL", {
|
|
||||||
style: "currency",
|
|
||||||
currency: "USD",
|
|
||||||
}).format(amount);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="blocks">
|
<section className="blocks">
|
||||||
<div className="container mx-auto">
|
<div className="container mx-auto">
|
||||||
@@ -32,7 +26,7 @@ export const MovieCast: FC<Props> = ({ movieDetails }) => {
|
|||||||
<div className="lg:col-span-2">
|
<div className="lg:col-span-2">
|
||||||
<h2 className="text-2xl font-bold text-white mb-6">Obsada</h2>
|
<h2 className="text-2xl font-bold text-white mb-6">Obsada</h2>
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6">
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6">
|
||||||
{mainCast.map((actor) => (
|
{mainCast.map(actor => (
|
||||||
<Link
|
<Link
|
||||||
key={actor.id}
|
key={actor.id}
|
||||||
href={`/aktor/${actor.id}`}
|
href={`/aktor/${actor.id}`}
|
||||||
@@ -41,12 +35,12 @@ export const MovieCast: FC<Props> = ({ movieDetails }) => {
|
|||||||
<div className="relative overflow-hidden rounded-xl mb-3">
|
<div className="relative overflow-hidden rounded-xl mb-3">
|
||||||
<img
|
<img
|
||||||
style={{
|
style={{
|
||||||
aspectRatio: "185/278",
|
aspectRatio: '185/278',
|
||||||
}}
|
}}
|
||||||
src={
|
src={
|
||||||
actor.profile_path
|
actor.profile_path
|
||||||
? `https://image.tmdb.org/t/p/w185${actor.profile_path}`
|
? `https://image.tmdb.org/t/p/w185${actor.profile_path}`
|
||||||
: "/api/placeholder/185/278"
|
: '/api/placeholder/185/278'
|
||||||
}
|
}
|
||||||
alt={actor.name}
|
alt={actor.name}
|
||||||
className="w-full object-cover group-hover:scale-110 transition-transform duration-500 bg-gradient-to-br from-purple-500/20 to-cyan-500/20"
|
className="w-full object-cover group-hover:scale-110 transition-transform duration-500 bg-gradient-to-br from-purple-500/20 to-cyan-500/20"
|
||||||
@@ -147,7 +141,7 @@ export const MovieCast: FC<Props> = ({ movieDetails }) => {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{movieDetails.production_companies
|
{movieDetails.production_companies
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
.map((company) => (
|
.map(company => (
|
||||||
<p key={company.id} className="text-gray-300">
|
<p key={company.id} className="text-gray-300">
|
||||||
{company.name}
|
{company.name}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
"use client";
|
'use client';
|
||||||
import { FC, ReactNode, useState } from "react";
|
import { FC, ReactNode, useState } from 'react';
|
||||||
import { MovieCard } from "@/components/atoms/MovieCard";
|
import { MovieCard } from '@/components/atoms/MovieCard';
|
||||||
import { Movie } from "@/types/global";
|
import { Dropdown } from '@/components/atoms/Dropdown';
|
||||||
import { useGlobalStore } from "@/app/store/globalStore";
|
import { useAutoAnimate } from '@formkit/auto-animate/react';
|
||||||
import { Dropdown } from "@/components/atoms/Dropdown";
|
import { Button } from '@/components/atoms/Button';
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { Label } from '@/components/atoms/Label';
|
||||||
import { Button } from "@/components/atoms/Button";
|
import { FaList } from 'react-icons/fa';
|
||||||
import { Label } from "@/components/atoms/Label";
|
import { MovieRow } from '@/components/atoms/MovieRow';
|
||||||
import { FaList } from "react-icons/fa";
|
import { useGlobalStore } from '@/app/store/global';
|
||||||
import { MovieRow } from "@/components/atoms/MovieRow";
|
import { SearchInput } from '@/components/atoms/SearchInput';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
heading?: string;
|
heading?: string;
|
||||||
@@ -16,8 +16,9 @@ type Props = {
|
|||||||
colors?: keyof typeof colorsMap;
|
colors?: keyof typeof colorsMap;
|
||||||
|
|
||||||
overrideMovies?: Movie[];
|
overrideMovies?: Movie[];
|
||||||
overrideDisplayType?: "grid" | "list";
|
overrideDisplayType?: 'grid' | 'list';
|
||||||
|
|
||||||
|
showSearch?: boolean;
|
||||||
showFilters?: boolean;
|
showFilters?: boolean;
|
||||||
filterSeen?: 0 | 1;
|
filterSeen?: 0 | 1;
|
||||||
filterFavorites?: 0 | 1;
|
filterFavorites?: 0 | 1;
|
||||||
@@ -26,8 +27,8 @@ type Props = {
|
|||||||
|
|
||||||
fluid?: boolean;
|
fluid?: boolean;
|
||||||
showSorting?: boolean;
|
showSorting?: boolean;
|
||||||
sort?: "title" | "releaseDate" | "popularity";
|
sort?: 'title' | 'releaseDate' | 'popularity' | 'voteAverage';
|
||||||
sortDirection?: "asc" | "desc";
|
sortDirection?: 'asc' | 'desc';
|
||||||
|
|
||||||
loadMore?: boolean;
|
loadMore?: boolean;
|
||||||
};
|
};
|
||||||
@@ -35,42 +36,43 @@ type Props = {
|
|||||||
export const MovieList: FC<Props> = ({
|
export const MovieList: FC<Props> = ({
|
||||||
heading,
|
heading,
|
||||||
icon,
|
icon,
|
||||||
colors = "white",
|
colors = 'white',
|
||||||
overrideMovies,
|
overrideMovies,
|
||||||
showFilters = true,
|
showFilters = true,
|
||||||
|
showSearch = false,
|
||||||
filterSeen: filterSeenInitial,
|
filterSeen: filterSeenInitial,
|
||||||
filterFavorites: filterFavoritesInitial,
|
filterFavorites: filterFavoritesInitial,
|
||||||
filterUpcoming: filterUpcomingInitial,
|
filterUpcoming: filterUpcomingInitial,
|
||||||
filterReleased: filterReleasedInitial,
|
filterReleased: filterReleasedInitial,
|
||||||
fluid = false,
|
fluid = false,
|
||||||
showSorting = true,
|
showSorting = true,
|
||||||
sort: sortType = "releaseDate",
|
sort: sortType = 'releaseDate',
|
||||||
sortDirection = "asc",
|
sortDirection = 'asc',
|
||||||
loadMore = false,
|
loadMore = false,
|
||||||
overrideDisplayType,
|
overrideDisplayType,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const storeMovies = useGlobalStore(state => state.movies);
|
||||||
movies: storeMovies,
|
const displayTypeInitial = useGlobalStore(state => state.displayType);
|
||||||
displayType: displayTypeInitial,
|
const setDisplayType = useGlobalStore(state => state.setDisplayType);
|
||||||
setDisplayType,
|
|
||||||
} = useGlobalStore();
|
|
||||||
const movies = overrideMovies || storeMovies;
|
const movies = overrideMovies || storeMovies;
|
||||||
const displayType = overrideDisplayType || displayTypeInitial;
|
const displayType = overrideDisplayType || displayTypeInitial;
|
||||||
|
|
||||||
const [filter, setFilter] = useState({
|
const [filter, setFilter] = useState({
|
||||||
|
search: '',
|
||||||
seen: filterSeenInitial,
|
seen: filterSeenInitial,
|
||||||
favorites: filterFavoritesInitial,
|
favorites: filterFavoritesInitial,
|
||||||
upcoming: filterUpcomingInitial,
|
upcoming: filterUpcomingInitial,
|
||||||
released: filterReleasedInitial,
|
released: filterReleasedInitial,
|
||||||
});
|
});
|
||||||
const [sort, setSort] = useState<"title" | "releaseDate" | "popularity">(
|
const [sort, setSort] = useState<
|
||||||
sortType
|
'title' | 'releaseDate' | 'popularity' | 'voteAverage'
|
||||||
);
|
>(sortType);
|
||||||
|
|
||||||
const [loaded, setLoaded] = useState(loadMore ? 8 : movies.length);
|
const [loaded, setLoaded] = useState(8);
|
||||||
const [parent] = useAutoAnimate();
|
const [parent] = useAutoAnimate();
|
||||||
|
|
||||||
const filteredMovies = movies.filter((movie) => {
|
const filteredMovies = movies.filter(movie => {
|
||||||
let result = true;
|
let result = true;
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
if (filter.seen !== undefined) {
|
if (filter.seen !== undefined) {
|
||||||
@@ -89,26 +91,31 @@ export const MovieList: FC<Props> = ({
|
|||||||
result =
|
result =
|
||||||
result && filter.released ? releaseDate < today : releaseDate > today;
|
result && filter.released ? releaseDate < today : releaseDate > today;
|
||||||
}
|
}
|
||||||
|
if (filter.search) {
|
||||||
|
result = movie.title.toLowerCase().includes(filter.search.toLowerCase());
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
let sortedMovies = filteredMovies.sort((a, b) => {
|
let sortedMovies = filteredMovies.sort((a, b) => {
|
||||||
if (sort === "title") return a.title.localeCompare(b.title);
|
if (sort === 'title') return a.title.localeCompare(b.title);
|
||||||
if (sort === "releaseDate")
|
if (sort === 'releaseDate')
|
||||||
return (
|
return (
|
||||||
new Date(b.release_date).getTime() - new Date(a.release_date).getTime()
|
new Date(b.release_date).getTime() - new Date(a.release_date).getTime()
|
||||||
);
|
);
|
||||||
if (sort === "popularity") return b.popularity - a.popularity;
|
if (sort === 'popularity') return b.popularity - a.popularity;
|
||||||
|
if (sort === 'voteAverage') return b.vote_average - a.vote_average;
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sortDirection === "desc") {
|
if (sortDirection === 'desc') {
|
||||||
sortedMovies = sortedMovies.reverse();
|
sortedMovies = sortedMovies.reverse();
|
||||||
}
|
}
|
||||||
sortedMovies = sortedMovies.slice(0, loaded);
|
sortedMovies = sortedMovies.slice(0, loadMore ? loaded : movies.length);
|
||||||
|
|
||||||
const handleFilter = (key?: keyof typeof filter) => {
|
const handleFilter = (key?: keyof typeof filter) => {
|
||||||
setFilter({
|
setFilter({
|
||||||
|
search: '',
|
||||||
seen: filterSeenInitial,
|
seen: filterSeenInitial,
|
||||||
favorites: filterFavoritesInitial,
|
favorites: filterFavoritesInitial,
|
||||||
upcoming: filterUpcomingInitial,
|
upcoming: filterUpcomingInitial,
|
||||||
@@ -119,7 +126,7 @@ export const MovieList: FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="blocks">
|
<section className="blocks">
|
||||||
<div className={`${fluid ? "max-w-full px-4" : "container"}`}>
|
<div className={`${fluid ? 'max-w-full px-4' : 'container'}`}>
|
||||||
{heading && (
|
{heading && (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{icon && (
|
{icon && (
|
||||||
@@ -151,17 +158,17 @@ export const MovieList: FC<Props> = ({
|
|||||||
</Label>
|
</Label>
|
||||||
<Label
|
<Label
|
||||||
active={filter.favorites !== undefined}
|
active={filter.favorites !== undefined}
|
||||||
onClick={() => handleFilter("favorites")}
|
onClick={() => handleFilter('favorites')}
|
||||||
>
|
>
|
||||||
Ulubione ({movies.filter((movie) => movie.favorite).length})
|
Ulubione ({movies.filter(movie => movie.favorite).length})
|
||||||
</Label>
|
</Label>
|
||||||
<Label
|
<Label
|
||||||
active={
|
active={
|
||||||
filter.seen !== undefined && filter.released === undefined
|
filter.seen !== undefined && filter.released === undefined
|
||||||
}
|
}
|
||||||
onClick={() => handleFilter("seen")}
|
onClick={() => handleFilter('seen')}
|
||||||
>
|
>
|
||||||
Obejrzane ({movies.filter((movie) => movie.seen).length})
|
Obejrzane ({movies.filter(movie => movie.seen).length})
|
||||||
</Label>
|
</Label>
|
||||||
<Label
|
<Label
|
||||||
active={
|
active={
|
||||||
@@ -169,6 +176,7 @@ export const MovieList: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setFilter({
|
setFilter({
|
||||||
|
search: '',
|
||||||
seen: 0,
|
seen: 0,
|
||||||
released: 1,
|
released: 1,
|
||||||
favorites: filterFavoritesInitial,
|
favorites: filterFavoritesInitial,
|
||||||
@@ -179,7 +187,7 @@ export const MovieList: FC<Props> = ({
|
|||||||
Do obejrzenia (
|
Do obejrzenia (
|
||||||
{
|
{
|
||||||
movies.filter(
|
movies.filter(
|
||||||
(movie) =>
|
movie =>
|
||||||
new Date(movie.release_date) < new Date() && !movie.seen
|
new Date(movie.release_date) < new Date() && !movie.seen
|
||||||
).length
|
).length
|
||||||
}
|
}
|
||||||
@@ -187,12 +195,12 @@ export const MovieList: FC<Props> = ({
|
|||||||
</Label>
|
</Label>
|
||||||
<Label
|
<Label
|
||||||
active={filter.upcoming !== undefined}
|
active={filter.upcoming !== undefined}
|
||||||
onClick={() => handleFilter("upcoming")}
|
onClick={() => handleFilter('upcoming')}
|
||||||
>
|
>
|
||||||
Nadchodzące (
|
Nadchodzące (
|
||||||
{
|
{
|
||||||
movies.filter(
|
movies.filter(
|
||||||
(movie) => new Date(movie.release_date) > new Date()
|
movie => new Date(movie.release_date) > new Date()
|
||||||
).length
|
).length
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -200,30 +208,40 @@ export const MovieList: FC<Props> = ({
|
|||||||
|
|
||||||
{showSorting && (
|
{showSorting && (
|
||||||
<div className="flex items-center gap-3 ml-auto">
|
<div className="flex items-center gap-3 ml-auto">
|
||||||
<Dropdown
|
|
||||||
items={[
|
|
||||||
{ label: "Tytuł", value: "title" },
|
|
||||||
{ label: "Data premiery", value: "releaseDate" },
|
|
||||||
{ label: "Popularność", value: "popularity" },
|
|
||||||
]}
|
|
||||||
defaultValue={sort}
|
|
||||||
callback={(value) => setSort(value as "title")}
|
|
||||||
/>
|
|
||||||
{!overrideDisplayType && (
|
{!overrideDisplayType && (
|
||||||
<Button
|
<Button
|
||||||
theme="glass"
|
|
||||||
size="icon"
|
size="icon"
|
||||||
|
theme="slate"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setDisplayType(displayType === "grid" ? "list" : "grid")
|
setDisplayType(displayType === 'grid' ? 'list' : 'grid')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FaList />
|
<FaList />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
<Dropdown
|
||||||
|
items={[
|
||||||
|
{ label: 'Tytuł', value: 'title' },
|
||||||
|
{ label: 'Data premiery', value: 'releaseDate' },
|
||||||
|
{ label: 'Popularność', value: 'popularity' },
|
||||||
|
{ label: 'Ocena', value: 'voteAverage' },
|
||||||
|
]}
|
||||||
|
defaultValue={sort}
|
||||||
|
callback={value => setSort(value as 'title')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{showSearch && (
|
||||||
|
<div className="flex justify-end mt-6">
|
||||||
|
<SearchInput
|
||||||
|
className="w-full"
|
||||||
|
placeholder="Wyszukaj film..."
|
||||||
|
onChange={value => setFilter({ ...filter, search: value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{filteredMovies.length === 0 && (
|
{filteredMovies.length === 0 && (
|
||||||
<p className="text-text/60 text-sm">Brak filmów</p>
|
<p className="text-text/60 text-sm">Brak filmów</p>
|
||||||
)}
|
)}
|
||||||
@@ -232,8 +250,8 @@ export const MovieList: FC<Props> = ({
|
|||||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-6 gap-3 sm:gap-6 mt-8 justify-center"
|
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-6 gap-3 sm:gap-6 mt-8 justify-center"
|
||||||
ref={parent}
|
ref={parent}
|
||||||
>
|
>
|
||||||
{sortedMovies.map((movie) =>
|
{sortedMovies.map(movie =>
|
||||||
displayType === "grid" ? (
|
displayType === 'grid' ? (
|
||||||
<MovieCard key={movie.id} {...movie} />
|
<MovieCard key={movie.id} {...movie} />
|
||||||
) : (
|
) : (
|
||||||
<MovieRow key={movie.id} movie={movie} compact />
|
<MovieRow key={movie.id} movie={movie} compact />
|
||||||
@@ -258,14 +276,14 @@ export const MovieList: FC<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const colorsMap = {
|
const colorsMap = {
|
||||||
white: "bg-gradient-to-r from-white to-gray-300",
|
white: 'bg-gradient-to-r from-white to-gray-300',
|
||||||
yellow: "bg-gradient-to-r from-yellow-400 to-orange-400",
|
yellow: 'bg-gradient-to-r from-yellow-400 to-orange-400',
|
||||||
blue: "bg-gradient-to-r from-blue-400 to-purple-400",
|
blue: 'bg-gradient-to-r from-blue-400 to-purple-400',
|
||||||
green: "bg-gradient-to-r from-green-400 to-teal-400",
|
green: 'bg-gradient-to-r from-green-400 to-teal-400',
|
||||||
red: "bg-gradient-to-r from-red-400 to-pink-400",
|
red: 'bg-gradient-to-r from-red-400 to-pink-400',
|
||||||
purple: "bg-gradient-to-r from-purple-400 to-pink-400",
|
purple: 'bg-gradient-to-r from-purple-400 to-pink-400',
|
||||||
orange: "bg-gradient-to-r from-orange-400 to-yellow-400",
|
orange: 'bg-gradient-to-r from-orange-400 to-yellow-400',
|
||||||
pink: "bg-gradient-to-r from-pink-400 to-purple-400",
|
pink: 'bg-gradient-to-r from-pink-400 to-purple-400',
|
||||||
teal: "bg-gradient-to-r from-teal-400 to-green-400",
|
teal: 'bg-gradient-to-r from-teal-400 to-green-400',
|
||||||
gray: "bg-gradient-to-r from-gray-400 to-gray-400",
|
gray: 'bg-gradient-to-r from-gray-400 to-gray-400',
|
||||||
};
|
};
|
||||||
|
|||||||
135
src/components/molecules/RandomMovie/index.tsx
Normal file
135
src/components/molecules/RandomMovie/index.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
'use client';
|
||||||
|
import { FC, useMemo, useState } from 'react';
|
||||||
|
import { Button } from '@/components/atoms/Button';
|
||||||
|
import { FaDice } from 'react-icons/fa';
|
||||||
|
import { useGlobalStore } from '@/app/store/global';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
type StoreFilter = 'all' | 'not_seen' | 'released' | 'favorites' | 'to_watch';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
heading?: string;
|
||||||
|
storeFilter?: StoreFilter;
|
||||||
|
colors?: keyof typeof colorsMap;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RandomMovie: FC<Props> = ({
|
||||||
|
heading = 'Losowy film',
|
||||||
|
storeFilter = 'not_seen',
|
||||||
|
colors = 'purple',
|
||||||
|
className = '',
|
||||||
|
}) => {
|
||||||
|
const movies = useGlobalStore(state => state.movies);
|
||||||
|
const [selectedMovie, setSelectedMovie] = useState<Movie | null>(null);
|
||||||
|
|
||||||
|
// Filter movies based on the selected store filter.
|
||||||
|
const filteredMovies = useMemo(() => {
|
||||||
|
const today = new Date();
|
||||||
|
|
||||||
|
return movies.filter(movie => {
|
||||||
|
switch (storeFilter) {
|
||||||
|
case 'not_seen':
|
||||||
|
return !movie.seen;
|
||||||
|
case 'released':
|
||||||
|
return new Date(movie.release_date) < today;
|
||||||
|
case 'favorites':
|
||||||
|
return movie.favorite;
|
||||||
|
case 'to_watch':
|
||||||
|
return !movie.seen && new Date(movie.release_date) < today;
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [movies, storeFilter]);
|
||||||
|
|
||||||
|
const handleRandomize = () => {
|
||||||
|
if (filteredMovies.length === 0) return;
|
||||||
|
|
||||||
|
const randomIndex = Math.floor(Math.random() * filteredMovies.length);
|
||||||
|
const randomMovie = filteredMovies[randomIndex];
|
||||||
|
|
||||||
|
setSelectedMovie(randomMovie);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (filteredMovies.length === 0) {
|
||||||
|
return (
|
||||||
|
<section className={`blocks ${className}`}>
|
||||||
|
<div className="container">
|
||||||
|
{heading && (
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<div className={`p-2 rounded-lg ${colorsMap[colors]}`}>
|
||||||
|
<FaDice className="text-white" />
|
||||||
|
</div>
|
||||||
|
<h2
|
||||||
|
className={`text-3xl font-bold ${colorsMap[colors]} bg-clip-text text-transparent`}
|
||||||
|
>
|
||||||
|
{heading}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<p className="text-text/60 text-lg">Brak filmów w kategorii</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={`blocks ${className}`}>
|
||||||
|
<div className="container">
|
||||||
|
{heading && (
|
||||||
|
<div className="flex justify-center mb-6">
|
||||||
|
<h2
|
||||||
|
className={`text-3xl font-bold ${colorsMap[colors]} bg-clip-text text-transparent`}
|
||||||
|
>
|
||||||
|
{heading}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="text-center mb-4">
|
||||||
|
<p className="text-text/70 text-sm">
|
||||||
|
Dostępnych {filteredMovies.length} filmów
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
theme="secondary"
|
||||||
|
onClick={handleRandomize}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<FaDice />
|
||||||
|
Losuj film
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedMovie && (
|
||||||
|
<div className="text-center mt-4">
|
||||||
|
<h3 className="text-2xl font-bold">
|
||||||
|
<Link href={`/film/${selectedMovie.id}`}>
|
||||||
|
{selectedMovie.title}
|
||||||
|
</Link>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorsMap = {
|
||||||
|
white: 'bg-gradient-to-r from-white to-gray-300',
|
||||||
|
yellow: 'bg-gradient-to-r from-yellow-400 to-orange-400',
|
||||||
|
blue: 'bg-gradient-to-r from-blue-400 to-purple-400',
|
||||||
|
green: 'bg-gradient-to-r from-green-400 to-teal-400',
|
||||||
|
red: 'bg-gradient-to-r from-red-400 to-pink-400',
|
||||||
|
purple: 'bg-gradient-to-r from-purple-400 to-pink-400',
|
||||||
|
orange: 'bg-gradient-to-r from-orange-400 to-yellow-400',
|
||||||
|
pink: 'bg-gradient-to-r from-pink-400 to-purple-400',
|
||||||
|
teal: 'bg-gradient-to-r from-teal-400 to-green-400',
|
||||||
|
gray: 'bg-gradient-to-r from-gray-400 to-gray-400',
|
||||||
|
};
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client";
|
'use client';
|
||||||
import { SearchResult } from "@/lib/tmdb/types";
|
import { SearchResult } from '@/lib/tmdb/types';
|
||||||
import { MovieCard } from "@/components/atoms/MovieCard";
|
import { MovieCard } from '@/components/atoms/MovieCard';
|
||||||
import { FC } from "react";
|
import { FC } from 'react';
|
||||||
import { FaStar } from "react-icons/fa";
|
import { FaStar } from 'react-icons/fa';
|
||||||
import { Carousel } from "../Carousel";
|
import { Carousel } from '../Carousel';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
movies: SearchResult;
|
movies: SearchResult;
|
||||||
@@ -26,10 +26,9 @@ export const RecommendedMovies: FC<Props> = ({ movies }) => {
|
|||||||
new Date(b.release_date).getTime() -
|
new Date(b.release_date).getTime() -
|
||||||
new Date(a.release_date).getTime()
|
new Date(a.release_date).getTime()
|
||||||
)
|
)
|
||||||
.map((movie) => (
|
.map(movie => (
|
||||||
<MovieCard
|
<MovieCard
|
||||||
key={movie.id}
|
key={movie.id}
|
||||||
layout="aurora"
|
|
||||||
id={movie.id}
|
id={movie.id}
|
||||||
title={movie.title}
|
title={movie.title}
|
||||||
overview={movie.overview}
|
overview={movie.overview}
|
||||||
@@ -39,7 +38,7 @@ export const RecommendedMovies: FC<Props> = ({ movies }) => {
|
|||||||
popularity={movie.popularity}
|
popularity={movie.popularity}
|
||||||
adult={movie.adult}
|
adult={movie.adult}
|
||||||
backdrop_path={movie.backdrop_path}
|
backdrop_path={movie.backdrop_path}
|
||||||
genre_ids={movie.genre_ids.join(",")}
|
genre_ids={movie.genre_ids.join(',')}
|
||||||
original_language={movie.original_language}
|
original_language={movie.original_language}
|
||||||
original_title={movie.original_title}
|
original_title={movie.original_title}
|
||||||
video={movie.video}
|
video={movie.video}
|
||||||
|
|||||||
@@ -13,15 +13,14 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SearchList: FC<Props> = ({ query }) => {
|
export const SearchList: FC<Props> = ({ query }) => {
|
||||||
const [response, setResponse] = useState<SearchResult | null>(null);
|
const [response, setResponse] = useState<SearchResult>({
|
||||||
const {
|
results: [],
|
||||||
results,
|
total_results: 0,
|
||||||
total_results = 0,
|
total_pages: 0,
|
||||||
total_pages = 0,
|
page: 1,
|
||||||
page = 1,
|
});
|
||||||
} = response ?? {};
|
const { results, total_results, total_pages, page } = response;
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
|
|
||||||
const handleSearch = async (query: string, page: number) => {
|
const handleSearch = async (query: string, page: number) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -44,6 +43,13 @@ export const SearchList: FC<Props> = ({ query }) => {
|
|||||||
handleSearch(query, page);
|
handleSearch(query, page);
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
|
const movies = results.map((m) => ({
|
||||||
|
...m,
|
||||||
|
favorite: false,
|
||||||
|
seen: false,
|
||||||
|
genre_ids: JSON.stringify(m.genre_ids),
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mb-4 md:mb-10">
|
<section className="mb-4 md:mb-10">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
@@ -53,28 +59,25 @@ export const SearchList: FC<Props> = ({ query }) => {
|
|||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="absolute -inset-10 flex pt-60 justify-center bg-gradient-to-t from-slate-900/90 via-slate-800/50 to-transparent z-10">
|
<div className="absolute flex inset-0 items-center justify-center z-10 backdrop-blur">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<MovieList
|
<MovieList
|
||||||
showFilters={false}
|
showFilters={false}
|
||||||
overrideDisplayType="grid"
|
overrideDisplayType="grid"
|
||||||
overrideMovies={results?.map((m) => ({
|
overrideMovies={movies}
|
||||||
...m,
|
|
||||||
favorite: false,
|
|
||||||
seen: false,
|
|
||||||
genre_ids: JSON.stringify(m.genre_ids),
|
|
||||||
}))}
|
|
||||||
fluid
|
fluid
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Pagination
|
{total_pages > 1 && (
|
||||||
totalPages={total_pages}
|
<Pagination
|
||||||
currentPage={page}
|
totalPages={total_pages}
|
||||||
onPageChange={handlePageChange}
|
currentPage={page}
|
||||||
/>
|
onPageChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ export const SimilarMovies: FC<Props> = ({ movies }) => {
|
|||||||
{currentMovies.map((movie) => (
|
{currentMovies.map((movie) => (
|
||||||
<MovieCard
|
<MovieCard
|
||||||
key={movie.id}
|
key={movie.id}
|
||||||
layout="aurora"
|
|
||||||
id={movie.id}
|
id={movie.id}
|
||||||
title={movie.title}
|
title={movie.title}
|
||||||
overview={movie.overview}
|
overview={movie.overview}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
"use client";
|
'use client';
|
||||||
import { FC } from "react";
|
import { FC } from 'react';
|
||||||
import { useGlobalStore } from "@/app/store/globalStore";
|
import { FaCalendar, FaClock } from 'react-icons/fa';
|
||||||
import { FaCalendar, FaClock } from "react-icons/fa";
|
import { MovieRow } from '@/components/atoms/MovieRow';
|
||||||
import { MovieRow } from "@/components/atoms/MovieRow";
|
import { useGlobalStore } from '@/app/store/global';
|
||||||
import { Movie } from "@/types/global";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
overrideMovies?: Movie[];
|
overrideMovies?: Movie[];
|
||||||
@@ -15,10 +14,10 @@ type Props = {
|
|||||||
export const TrackedMovies: FC<Props> = ({
|
export const TrackedMovies: FC<Props> = ({
|
||||||
overrideMovies,
|
overrideMovies,
|
||||||
daysLimit = 30,
|
daysLimit = 30,
|
||||||
labelCurrent = "Aktualnie w kinach",
|
labelCurrent = 'Aktualnie w kinach',
|
||||||
labelUpcoming = "Nadchodzące premiery",
|
labelUpcoming = 'Nadchodzące premiery',
|
||||||
}) => {
|
}) => {
|
||||||
const { movies: storeMovies } = useGlobalStore();
|
const storeMovies = useGlobalStore(state => state.movies);
|
||||||
|
|
||||||
const movies = overrideMovies || storeMovies;
|
const movies = overrideMovies || storeMovies;
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ export const TrackedMovies: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const upcoming = movies.filter((movie) => {
|
const upcoming = movies.filter(movie => {
|
||||||
const daysSinceRelease = Math.abs(
|
const daysSinceRelease = Math.abs(
|
||||||
Math.floor(
|
Math.floor(
|
||||||
(new Date().getTime() - new Date(movie.release_date).getTime()) /
|
(new Date().getTime() - new Date(movie.release_date).getTime()) /
|
||||||
@@ -38,7 +37,7 @@ export const TrackedMovies: FC<Props> = ({
|
|||||||
new Date(movie.release_date) > today && daysSinceRelease <= daysLimit
|
new Date(movie.release_date) > today && daysSinceRelease <= daysLimit
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const inCinema = movies.filter((movie) => {
|
const inCinema = movies.filter(movie => {
|
||||||
const daysSinceRelease = Math.floor(
|
const daysSinceRelease = Math.floor(
|
||||||
(new Date().getTime() - new Date(movie.release_date).getTime()) /
|
(new Date().getTime() - new Date(movie.release_date).getTime()) /
|
||||||
(1000 * 60 * 60 * 24)
|
(1000 * 60 * 60 * 24)
|
||||||
@@ -73,7 +72,7 @@ export const TrackedMovies: FC<Props> = ({
|
|||||||
{labelCurrent} ({sortedInCinema.length})
|
{labelCurrent} ({sortedInCinema.length})
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{sortedInCinema.map((movie) => (
|
{sortedInCinema.map(movie => (
|
||||||
<MovieRow key={movie.id} movie={movie} isUpcoming={false} />
|
<MovieRow key={movie.id} movie={movie} isUpcoming={false} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -87,7 +86,7 @@ export const TrackedMovies: FC<Props> = ({
|
|||||||
{labelUpcoming} ({sortedUpcoming.length})
|
{labelUpcoming} ({sortedUpcoming.length})
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{sortedUpcoming.map((movie) => (
|
{sortedUpcoming.map(movie => (
|
||||||
<MovieRow key={movie.id} movie={movie} isUpcoming />
|
<MovieRow key={movie.id} movie={movie} isUpcoming />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
"use client";
|
'use client';
|
||||||
import { FC, useState, useEffect, useCallback } from "react";
|
import { FC, useState, useEffect, useCallback } from 'react';
|
||||||
import { Movie } from "@/types/global";
|
|
||||||
import {
|
import {
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaFire,
|
FaFire,
|
||||||
FaChevronLeft,
|
FaChevronLeft,
|
||||||
FaChevronRight,
|
FaChevronRight,
|
||||||
FaMinus,
|
FaMinus,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa';
|
||||||
import { RiCalendarCheckLine, RiCalendarScheduleLine } from "react-icons/ri";
|
import { RiCalendarCheckLine, RiCalendarScheduleLine } from 'react-icons/ri';
|
||||||
import { useGlobalStore } from "@/app/store/globalStore";
|
import Link from 'next/link';
|
||||||
import Link from "next/link";
|
import { Button } from '@/components/atoms/Button';
|
||||||
import { Button } from "@/components/atoms/Button";
|
import { useGlobalStore } from '@/app/store/global';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
movies: Movie[];
|
movies: Movie[];
|
||||||
@@ -29,11 +28,9 @@ export const Hero: FC<Props> = ({
|
|||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||||
|
|
||||||
const {
|
const storedMovies = useGlobalStore(state => state.movies);
|
||||||
movies: storedMovies,
|
const addMovie = useGlobalStore(state => state.addMovie);
|
||||||
addMovie: addMovieToStore,
|
const deleteMovie = useGlobalStore(state => state.deleteMovie);
|
||||||
deleteMovie: deleteMovieInStore,
|
|
||||||
} = useGlobalStore();
|
|
||||||
|
|
||||||
const currentMovie = movies[currentIndex];
|
const currentMovie = movies[currentIndex];
|
||||||
|
|
||||||
@@ -50,7 +47,7 @@ export const Hero: FC<Props> = ({
|
|||||||
vote_average,
|
vote_average,
|
||||||
} = currentMovie;
|
} = currentMovie;
|
||||||
|
|
||||||
const alreadyInStore = storedMovies.find((m) => m.id === id);
|
const alreadyInStore = storedMovies.find(m => m.id === id);
|
||||||
const isReleased = new Date(release_date) < new Date();
|
const isReleased = new Date(release_date) < new Date();
|
||||||
const releaseDate = new Date(release_date);
|
const releaseDate = new Date(release_date);
|
||||||
|
|
||||||
@@ -58,7 +55,7 @@ export const Hero: FC<Props> = ({
|
|||||||
if (isTransitioning) return;
|
if (isTransitioning) return;
|
||||||
setIsTransitioning(true);
|
setIsTransitioning(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setCurrentIndex((prev) => (prev + 1) % movies.length);
|
setCurrentIndex(prev => (prev + 1) % movies.length);
|
||||||
setIsTransitioning(false);
|
setIsTransitioning(false);
|
||||||
}, 500);
|
}, 500);
|
||||||
}, [movies.length, isTransitioning]);
|
}, [movies.length, isTransitioning]);
|
||||||
@@ -67,7 +64,7 @@ export const Hero: FC<Props> = ({
|
|||||||
if (isTransitioning) return;
|
if (isTransitioning) return;
|
||||||
setIsTransitioning(true);
|
setIsTransitioning(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setCurrentIndex((prev) => (prev - 1 + movies.length) % movies.length);
|
setCurrentIndex(prev => (prev - 1 + movies.length) % movies.length);
|
||||||
setIsTransitioning(false);
|
setIsTransitioning(false);
|
||||||
}, 500);
|
}, 500);
|
||||||
}, [movies.length, isTransitioning]);
|
}, [movies.length, isTransitioning]);
|
||||||
@@ -93,11 +90,11 @@ export const Hero: FC<Props> = ({
|
|||||||
}, [autoRotate, rotateInterval, nextSlide, movies.length]);
|
}, [autoRotate, rotateInterval, nextSlide, movies.length]);
|
||||||
|
|
||||||
const handleAdd = async () => {
|
const handleAdd = async () => {
|
||||||
addMovieToStore(currentMovie);
|
addMovie(currentMovie);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemove = async () => {
|
const handleRemove = async () => {
|
||||||
deleteMovieInStore(id);
|
deleteMovie(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -108,7 +105,7 @@ export const Hero: FC<Props> = ({
|
|||||||
<div
|
<div
|
||||||
key={movie.id}
|
key={movie.id}
|
||||||
className={`absolute inset-0 transition-opacity duration-500 ${
|
className={`absolute inset-0 transition-opacity duration-500 ${
|
||||||
index === currentIndex ? "opacity-100" : "opacity-0"
|
index === currentIndex ? 'opacity-100' : 'opacity-0'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -146,7 +143,7 @@ export const Hero: FC<Props> = ({
|
|||||||
<div className="container relative z-10">
|
<div className="container relative z-10">
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col lg:flex-row items-center gap-8 lg:gap-12 transition-opacity duration-500 ${
|
className={`flex flex-col lg:flex-row items-center gap-8 lg:gap-12 transition-opacity duration-500 ${
|
||||||
isTransitioning ? "opacity-0" : "opacity-100"
|
isTransitioning ? 'opacity-0' : 'opacity-100'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{/* Poster */}
|
{/* Poster */}
|
||||||
@@ -176,7 +173,7 @@ export const Hero: FC<Props> = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
className={`flex items-center gap-1 text-sm ${
|
className={`flex items-center gap-1 text-sm ${
|
||||||
isReleased ? "text-green-400" : "text-yellow-400"
|
isReleased ? 'text-green-400' : 'text-yellow-400'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isReleased ? (
|
{isReleased ? (
|
||||||
@@ -184,10 +181,10 @@ export const Hero: FC<Props> = ({
|
|||||||
) : (
|
) : (
|
||||||
<RiCalendarScheduleLine />
|
<RiCalendarScheduleLine />
|
||||||
)}
|
)}
|
||||||
{releaseDate.toLocaleDateString("pl-PL", {
|
{releaseDate.toLocaleDateString('pl-PL', {
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
month: "long",
|
month: 'long',
|
||||||
year: "numeric",
|
year: 'numeric',
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -211,12 +208,12 @@ export const Hero: FC<Props> = ({
|
|||||||
{/* Action buttons */}
|
{/* Action buttons */}
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
|
||||||
<Button
|
<Button
|
||||||
theme={alreadyInStore ? "primary" : "secondary"}
|
theme={alreadyInStore ? 'primary' : 'secondary'}
|
||||||
onClick={alreadyInStore ? handleRemove : handleAdd}
|
onClick={alreadyInStore ? handleRemove : handleAdd}
|
||||||
>
|
>
|
||||||
{alreadyInStore ? <FaMinus /> : <FaPlus />}
|
{alreadyInStore ? <FaMinus /> : <FaPlus />}
|
||||||
<span>
|
<span>
|
||||||
{alreadyInStore ? "Usuń z listy" : "Dodaj do listy"}
|
{alreadyInStore ? 'Usuń z listy' : 'Dodaj do listy'}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -234,8 +231,8 @@ export const Hero: FC<Props> = ({
|
|||||||
disabled={isTransitioning}
|
disabled={isTransitioning}
|
||||||
className={`w-3 h-3 rounded-full transition-all duration-300 disabled:cursor-not-allowed cursor-pointer ${
|
className={`w-3 h-3 rounded-full transition-all duration-300 disabled:cursor-not-allowed cursor-pointer ${
|
||||||
index === currentIndex
|
index === currentIndex
|
||||||
? "bg-secondary scale-125"
|
? 'bg-secondary scale-125'
|
||||||
: "bg-white/50 hover:bg-secondary/70"
|
: 'bg-white/50 hover:bg-secondary/70'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ export const Search: FC<Props> = ({ onClose }) => {
|
|||||||
<div className="fixed inset-0 z-[60] overflow-y-auto">
|
<div className="fixed inset-0 z-[60] overflow-y-auto">
|
||||||
{/* Close button */}
|
{/* Close button */}
|
||||||
<Button
|
<Button
|
||||||
theme="glass"
|
theme="slate"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="absolute top-6 right-6 z-10 group hover:!bg-red-500/50"
|
className="absolute top-6 right-6 z-10 group shadow-lg shadow-red-500/20 hover:shadow-red-500/40"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>
|
>
|
||||||
<IoClose className="text-white transition-transform duration-300 group-hover:rotate-90" />
|
<IoClose className="text-white transition-transform duration-300 group-hover:rotate-90" />
|
||||||
@@ -71,15 +71,11 @@ export const Search: FC<Props> = ({ onClose }) => {
|
|||||||
|
|
||||||
{/* Enhanced Search Input */}
|
{/* Enhanced Search Input */}
|
||||||
<div className="relative max-w-2xl mx-auto">
|
<div className="relative max-w-2xl mx-auto">
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-purple-500/30 to-cyan-500/30 rounded-2xl"></div>
|
<SearchInput
|
||||||
<div className="relative bg-gradient-to-br from-white/15 via-white/8 to-white/12 border border-white/20 rounded-2xl p-2 shadow-2xl shadow-purple-500/10">
|
onChange={handleSearch}
|
||||||
<SearchInput
|
placeholder="Wpisz tytuł filmu..."
|
||||||
className="w-full px-3 bg-transparent border-none text-lg lg:text-xl placeholder-gray-400 text-white focus:outline-none"
|
autoFocus={true}
|
||||||
onChange={handleSearch}
|
/>
|
||||||
placeholder="Wpisz tytuł filmu..."
|
|
||||||
autoFocus={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,9 @@ import { useState } from "react";
|
|||||||
import { HiSearch, HiHome, HiSparkles } from "react-icons/hi";
|
import { HiSearch, HiHome, HiSparkles } from "react-icons/hi";
|
||||||
import { Search } from "./components/Search";
|
import { Search } from "./components/Search";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
|
import { Button } from "@/components/atoms/Button";
|
||||||
|
|
||||||
const navigationItems = [
|
const navigationItems = [
|
||||||
{
|
|
||||||
label: "Strona Główna",
|
|
||||||
href: "/",
|
|
||||||
icon: HiHome,
|
|
||||||
emoji: "🏠",
|
|
||||||
color: "from-blue-500 to-purple-600",
|
|
||||||
description: "Twoja lista filmów",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "Odkrywaj",
|
label: "Odkrywaj",
|
||||||
href: "/odkrywaj",
|
href: "/odkrywaj",
|
||||||
@@ -22,6 +15,14 @@ const navigationItems = [
|
|||||||
color: "from-purple-500 to-pink-600",
|
color: "from-purple-500 to-pink-600",
|
||||||
description: "Znajdź nowe filmy",
|
description: "Znajdź nowe filmy",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Strona Główna",
|
||||||
|
href: "/",
|
||||||
|
icon: HiHome,
|
||||||
|
emoji: "🏠",
|
||||||
|
color: "from-blue-500 to-purple-600",
|
||||||
|
description: "Twoja lista filmów",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Navbar = () => {
|
export const Navbar = () => {
|
||||||
@@ -31,82 +32,36 @@ export const Navbar = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Elegant Floating Navigation */}
|
{/* Elegant Floating Navigation */}
|
||||||
<nav className="fixed bottom-0 left-0 right-0 z-50 pointer-events-none bg-black/90 lg:bg-transparent">
|
<nav className="fixed bottom-0 left-0 right-0 z-50 bg-gradient-to-t from-black to-transparent">
|
||||||
<div className="relative h-24 flex items-center justify-between px-6">
|
<div className="relative flex items-center justify-center px-6 py-4 gap-3">
|
||||||
{/* Brand Name - Floating Left */}
|
{/* Desktop Navigation Orbs */}
|
||||||
<div className="pointer-events-auto">
|
{navigationItems.map((item, index) => {
|
||||||
<Link href="/" className="group flex items-center gap-3">
|
const isActive = pathname === item.href;
|
||||||
<div className="relative">
|
return (
|
||||||
<h1 className="text-2xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-white via-purple-200 to-cyan-200 group-hover:from-purple-300 group-hover:to-cyan-300 transition-all duration-500">
|
<Link
|
||||||
MovieBox
|
key={item.href}
|
||||||
</h1>
|
href={item.href}
|
||||||
</div>
|
className="relative group cursor-pointer"
|
||||||
</Link>
|
>
|
||||||
</div>
|
{/* Main orb */}
|
||||||
|
<Button theme={isActive ? "secondary" : "slate"} size="icon">
|
||||||
{/* Navigation & Action Orbs - Right Side */}
|
{/* Icon */}
|
||||||
<div className="flex items-center gap-3 pointer-events-auto">
|
<item.icon
|
||||||
{/* Desktop Navigation Orbs */}
|
className={`transition-colors duration-300
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
{navigationItems.map((item, index) => {
|
|
||||||
const isActive = pathname === item.href;
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
key={item.href}
|
|
||||||
href={item.href}
|
|
||||||
className="relative group cursor-pointer"
|
|
||||||
>
|
|
||||||
{/* Main orb */}
|
|
||||||
<div
|
|
||||||
className={`relative w-12 h-12 rounded-full border border-white/20 shadow-lg transition-all duration-300 hover:scale-105
|
|
||||||
${
|
|
||||||
isActive
|
|
||||||
? "bg-gradient-to-br from-purple-500/80 to-cyan-500/80 shadow-purple-500/40"
|
|
||||||
: "bg-gradient-to-br from-slate-800/95 to-slate-900/95 shadow-slate-500/20"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{/* Icon */}
|
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
|
||||||
<item.icon
|
|
||||||
className={`w-5 h-5 transition-colors duration-300
|
|
||||||
${
|
${
|
||||||
isActive
|
isActive
|
||||||
? "text-white"
|
? "text-white"
|
||||||
: "text-gray-300 group-hover:text-white"
|
: "text-gray-300 group-hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Button>
|
||||||
</div>
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{/* Tooltip */}
|
<Button theme="slate" size="icon" onClick={() => setSearchOpen(true)}>
|
||||||
<div className="absolute -bottom-12 left-1/2 transform -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none">
|
<HiSearch className="text-cyan-400 mx-auto" />
|
||||||
<div className="bg-black/90 text-white text-xs px-3 py-1 rounded-lg whitespace-nowrap border border-white/10">
|
</Button>
|
||||||
{item.label}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Search Orb */}
|
|
||||||
<div className="relative group">
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-cyan-500/30 to-purple-500/30 rounded-full transition-all duration-300"></div>
|
|
||||||
<button
|
|
||||||
onClick={() => setSearchOpen(true)}
|
|
||||||
className="relative w-12 h-12 rounded-full bg-gradient-to-br from-slate-800/95 to-slate-900/95 border border-white/20 shadow-lg shadow-cyan-500/20 hover:shadow-cyan-500/40 transition-all duration-300 hover:scale-105"
|
|
||||||
>
|
|
||||||
<HiSearch className="w-5 h-5 text-cyan-400 mx-auto" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Tooltip */}
|
|
||||||
<div className="absolute -bottom-12 left-1/2 transform -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none">
|
|
||||||
<div className="bg-black/90 text-white text-xs px-3 py-1 rounded-lg whitespace-nowrap border border-white/10">
|
|
||||||
Szukaj filmów
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Movie } from "@/types/global";
|
|
||||||
|
|
||||||
export const convertToMovie = (
|
export const convertToMovie = (
|
||||||
movie: any,
|
movie: any,
|
||||||
override?: Partial<Movie>
|
override?: Partial<Movie>
|
||||||
@@ -12,13 +10,13 @@ export const convertToMovie = (
|
|||||||
id: movie.id,
|
id: movie.id,
|
||||||
title: movie.title,
|
title: movie.title,
|
||||||
adult: movie.adult,
|
adult: movie.adult,
|
||||||
backdrop_path: movie.backdrop_path || "",
|
backdrop_path: movie.backdrop_path || '',
|
||||||
genre_ids: movie.genres?.join(",") || "",
|
genre_ids: movie.genres?.join(',') || '',
|
||||||
original_language: movie.original_language,
|
original_language: movie.original_language,
|
||||||
original_title: movie.original_title,
|
original_title: movie.original_title,
|
||||||
overview: movie.overview || "",
|
overview: movie.overview || '',
|
||||||
popularity: movie.popularity,
|
popularity: movie.popularity,
|
||||||
poster_path: movie.poster_path || "",
|
poster_path: movie.poster_path || '',
|
||||||
release_date: movie.release_date,
|
release_date: movie.release_date,
|
||||||
video: movie.video,
|
video: movie.video,
|
||||||
vote_average: movie.vote_average,
|
vote_average: movie.vote_average,
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
"use server";
|
|
||||||
import { drizzle } from "drizzle-orm/libsql";
|
|
||||||
import { movies } from "./schema";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { Movie } from "@/types/global";
|
|
||||||
import { revalidatePath } from "next/cache";
|
|
||||||
|
|
||||||
const db = drizzle(process.env.DB_FILE_NAME!);
|
|
||||||
|
|
||||||
export const getMovies = async () => {
|
|
||||||
return await db.select().from(movies).$withCache();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addMovieToDB = async (movie: Movie) => {
|
|
||||||
await db
|
|
||||||
.insert(movies)
|
|
||||||
.values({
|
|
||||||
...movie,
|
|
||||||
genre_ids: JSON.stringify(movie.genre_ids),
|
|
||||||
})
|
|
||||||
.onConflictDoNothing();
|
|
||||||
|
|
||||||
revalidatePath("/", "layout");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteMovieFromDB = async (id: number) => {
|
|
||||||
await db.delete(movies).where(eq(movies.id, id));
|
|
||||||
revalidatePath("/", "layout");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateMovieInDB = async (
|
|
||||||
movieId: number,
|
|
||||||
movie: Partial<Movie>
|
|
||||||
) => {
|
|
||||||
await db.update(movies).set(movie).where(eq(movies.id, movieId));
|
|
||||||
revalidatePath("/", "layout");
|
|
||||||
};
|
|
||||||
65
src/lib/db/pb.ts
Normal file
65
src/lib/db/pb.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
'use server';
|
||||||
|
import { revalidatePath } from 'next/cache';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
|
||||||
|
const pb = new PocketBase(process.env.POCKET_URL!);
|
||||||
|
pb.autoCancellation(false);
|
||||||
|
|
||||||
|
const collection = 'movies_prod';
|
||||||
|
|
||||||
|
type CollectionMovie = Movie & {
|
||||||
|
id_imdb: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DB_getMovies = async () => {
|
||||||
|
const records = await pb.collection<CollectionMovie>(collection).getFullList({
|
||||||
|
sort: '-created',
|
||||||
|
});
|
||||||
|
|
||||||
|
return records.map(record => ({
|
||||||
|
...record,
|
||||||
|
id: record.id_imdb,
|
||||||
|
})) as Movie[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DB_addMovie = async (movie: Movie) => {
|
||||||
|
try {
|
||||||
|
await pb.collection<CollectionMovie>(collection).create({
|
||||||
|
...movie,
|
||||||
|
id: undefined,
|
||||||
|
id_imdb: movie.id,
|
||||||
|
genre_ids: JSON.stringify(movie.genre_ids),
|
||||||
|
});
|
||||||
|
revalidatePath('/', 'layout');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DB_deleteMovie = async (id: number) => {
|
||||||
|
try {
|
||||||
|
const record = await pb
|
||||||
|
.collection<CollectionMovie>(collection)
|
||||||
|
.getFirstListItem(`id_imdb = "${id}"`);
|
||||||
|
await pb.collection(collection).delete(record.id.toString());
|
||||||
|
revalidatePath('/', 'layout');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DB_updateMovie = async (id: number, movie: Partial<Movie>) => {
|
||||||
|
try {
|
||||||
|
const record = await pb
|
||||||
|
.collection<CollectionMovie>(collection)
|
||||||
|
.getFirstListItem(`id_imdb = "${id}"`);
|
||||||
|
await pb
|
||||||
|
.collection<CollectionMovie>(collection)
|
||||||
|
.update(record.id.toString(), {
|
||||||
|
...movie,
|
||||||
|
});
|
||||||
|
revalidatePath('/', 'layout');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
||||||
|
|
||||||
export const movies = sqliteTable("movies", {
|
|
||||||
id: integer("id").primaryKey(),
|
|
||||||
title: text("title").notNull(),
|
|
||||||
adult: integer("adult", { mode: "boolean" }).notNull(),
|
|
||||||
backdrop_path: text("backdrop_path").notNull(),
|
|
||||||
genre_ids: text("genre_ids").notNull(),
|
|
||||||
original_language: text("original_language").notNull(),
|
|
||||||
original_title: text("original_title").notNull(),
|
|
||||||
overview: text("overview").notNull(),
|
|
||||||
popularity: real("popularity").notNull(),
|
|
||||||
poster_path: text("poster_path").notNull(),
|
|
||||||
release_date: text("release_date").notNull(),
|
|
||||||
video: integer("video", { mode: "boolean" }).notNull(),
|
|
||||||
vote_average: real("vote_average").notNull(),
|
|
||||||
vote_count: integer("vote_count").notNull(),
|
|
||||||
seen: integer("seen", { mode: "boolean" }).default(false),
|
|
||||||
favorite: integer("favorite", { mode: "boolean" }).default(false),
|
|
||||||
});
|
|
||||||
22
src/types/global.d.ts
vendored
22
src/types/global.d.ts
vendored
@@ -1,4 +1,18 @@
|
|||||||
import { movies } from "@/lib/db/schema";
|
type Movie = {
|
||||||
import { SearchResult } from "@/lib/tmdb/types";
|
id: number;
|
||||||
|
title: string;
|
||||||
type Movie = typeof movies.$inferSelect;
|
adult: boolean;
|
||||||
|
backdrop_path: string;
|
||||||
|
genre_ids: string;
|
||||||
|
original_language: string;
|
||||||
|
original_title: string;
|
||||||
|
overview: string;
|
||||||
|
popularity: number;
|
||||||
|
poster_path: string;
|
||||||
|
release_date: string;
|
||||||
|
video: boolean;
|
||||||
|
vote_average: number;
|
||||||
|
vote_count: number;
|
||||||
|
seen: boolean;
|
||||||
|
favorite: boolean;
|
||||||
|
};
|
||||||
|
|||||||
73
todo.md
73
todo.md
@@ -1,36 +1,51 @@
|
|||||||
#
|
UI/UX Improvements
|
||||||
|
Dark/Light Mode Toggle - Obecnie tylko ciemny motyw
|
||||||
|
Responsywny design na urządzenia mobilne - Niektóre komponenty mogą wymagać poprawy
|
||||||
|
Loading states - Dodać skeletony zamiast spinnerów
|
||||||
|
Infinite scroll - Zamiast paginacji dla lepszego UX
|
||||||
|
Gesture support - Swipe na mobilnych dla akcji (dodaj/usuń film)
|
||||||
|
|
||||||
## ✅ `TODO.md` – Etapy rozwoju aplikacji
|
Zarządzanie filmami
|
||||||
|
Własne notatki do filmów - Pole w bazie danych już wspomniane w README
|
||||||
|
Tagi/kategorie użytkownika - Własne etykiety
|
||||||
|
Oceny użytkownika - Osobne od TMDB
|
||||||
|
Data obejrzenia - Kiedy użytkownik obejrzał film
|
||||||
|
Lista "Do obejrzenia" - Oddzielna od "Obejrzane"
|
||||||
|
Planowanie seansów - Kalendarz z datami
|
||||||
|
Eksport/import listy - JSON/CSV backup
|
||||||
|
|
||||||
```md
|
Funkcje społecznościowe
|
||||||
# TODO – MovieBox
|
Udostępnianie list - Link do publicznej listy
|
||||||
|
Rekomendacje na podstawie gustu - ML/AI sugestie
|
||||||
|
Porównanie list z znajomymi - Wspólne filmy
|
||||||
|
|
||||||
## 🔧 Faza 1 – MVP (funkcjonalna wersja lokalna)
|
Dodatkowe dane i integracje
|
||||||
|
Informacje o aktorach - Rozszerzone profile (już częściowo jest)
|
||||||
|
Gdzie obejrzeć - Streaming platforms API
|
||||||
|
Zwiastuny - YouTube API integration
|
||||||
|
Recenzje użytkowników - Własne mini-forum
|
||||||
|
Galeria zdjęć z filmu - Więcej materiałów wizualnych
|
||||||
|
|
||||||
- [ ] Integracja z TMDB API (wyszukiwanie filmów)
|
Performance i techniczne
|
||||||
- [ ] Utworzenie bazy danych (SQLite + Drizzle)
|
PWA - Offline support, push notifications o premierach
|
||||||
- [ ] Modele: Movie, WatchlistEntry
|
Lepsze caching - Redis/SWR optimizations
|
||||||
- [ ] Dodanie filmu do watchlisty (z podglądem szczegółów)
|
Lazy loading - Obrazy i komponenty
|
||||||
- [ ] Lista “Do obejrzenia” i “Obejrzane”
|
Search indexing - Full-text search w bazie
|
||||||
- [ ] Możliwość dodania tagu lub notatki do filmu
|
API rate limiting - Lepsze zarządzanie requestami do TMDB
|
||||||
- [ ] UI (Tailwind + ShadCN) – responsywna siatka filmów
|
|
||||||
|
|
||||||
## 🌐 Faza 2 – Rozszerzenie
|
Statystyki i analytics
|
||||||
|
Dashboard statystyk - Filmy obejrzane/miesiąc, ulubione gatunki
|
||||||
|
Streak tracking - Dni z rzędu oglądania filmów
|
||||||
|
Cele filmowe - X filmów do obejrzenia w roku
|
||||||
|
Porównanie z poprzednimi latami - Trendy
|
||||||
|
|
||||||
- [ ] Podgląd dat premier z TMDB
|
Powiadomienia
|
||||||
- [ ] Filtrowanie według daty premiery
|
Email notifications - O premierach z listy
|
||||||
- [ ] Sortowanie / filtrowanie po tagach/statusie
|
Push notifications - PWA alerts
|
||||||
|
Reminder system - Przypomnienia o filmach do obejrzenia
|
||||||
|
|
||||||
## 🔐 Faza 3 – Rozszerzenia prywatne
|
Baza danych i backend
|
||||||
|
Migracja na PostgreSQL - Jak wspomniano w README
|
||||||
- [ ] Dodanie Auth.js (logowanie)
|
User authentication - Currently brak systemu użytkowników
|
||||||
- [ ] Migracja bazy do PostgreSQL
|
API endpoints - Własne REST API
|
||||||
- [ ] Eksport listy filmów (np. JSON)
|
Backup system - Automatyczne kopie zapasowe
|
||||||
- [ ] Backup na GitHub (np. GitHub Actions)
|
|
||||||
|
|
||||||
## 💡 Pomysły na później
|
|
||||||
|
|
||||||
- [ ] System rekomendacji (podobne filmy)
|
|
||||||
- [ ] Powiadomienia o premierach
|
|
||||||
- [ ] Integracja z Letterboxd
|
|
||||||
```
|
|
||||||
|
|||||||
Reference in New Issue
Block a user